├── .circleci └── config.yml ├── .gitignore ├── Procfile ├── README.md ├── architecture.png ├── build.gradle ├── docs ├── images │ ├── overLegalWorkTime1.png │ └── overLegalWorkTime2.png └── overLegalWorkTime.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── src ├── main │ ├── java │ │ └── example │ │ │ ├── Application.java │ │ │ ├── application │ │ │ ├── coordinator │ │ │ │ ├── employee │ │ │ │ │ └── EmployeeRecordCoordinator.java │ │ │ │ ├── package-info.java │ │ │ │ ├── payroll │ │ │ │ │ ├── PayrollQueryCoordinator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── timerecord │ │ │ │ │ ├── TimeRecordCoordinator.java │ │ │ │ │ └── TimeRecordQueryCoordinator.java │ │ │ ├── package-info.java │ │ │ ├── repository │ │ │ │ ├── ContractRepository.java │ │ │ │ ├── DaysOffRepository.java │ │ │ │ ├── EmployeeRepository.java │ │ │ │ └── TimeRecordRepository.java │ │ │ └── service │ │ │ │ ├── attendance │ │ │ │ ├── AttendanceQueryService.java │ │ │ │ └── package-info.java │ │ │ │ ├── contract │ │ │ │ ├── ContractQueryService.java │ │ │ │ ├── ContractRecordService.java │ │ │ │ └── package-info.java │ │ │ │ ├── daysoff │ │ │ │ └── DaysOffRecordService.java │ │ │ │ ├── employee │ │ │ │ ├── EmployeeQueryService.java │ │ │ │ ├── EmployeeRecordService.java │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── timerecord │ │ │ │ ├── TimeRecordQueryService.java │ │ │ │ ├── TimeRecordRecordService.java │ │ │ │ └── package-info.java │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── attendance │ │ │ │ │ ├── Attendance.java │ │ │ │ │ ├── AttendanceStatus.java │ │ │ │ │ ├── PayableWork.java │ │ │ │ │ ├── TotalWorkTime.java │ │ │ │ │ ├── WorkMonth.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── contract │ │ │ │ │ ├── Contract.java │ │ │ │ │ ├── ContractCondition.java │ │ │ │ │ ├── ContractConditions.java │ │ │ │ │ ├── ContractEffectiveDate.java │ │ │ │ │ ├── ContractStatus.java │ │ │ │ │ ├── Contracts.java │ │ │ │ │ ├── LegalOvertimeHoursStatus.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── shift │ │ │ │ │ │ ├── BreakTime.java │ │ │ │ │ │ ├── DayStatus.java │ │ │ │ │ │ ├── EndingTime.java │ │ │ │ │ │ ├── MonthlyShift.java │ │ │ │ │ │ ├── Shifts.java │ │ │ │ │ │ ├── StartingTime.java │ │ │ │ │ │ ├── WeeklyShift.java │ │ │ │ │ │ ├── WorkingDay.java │ │ │ │ │ │ ├── WorkingHoursPerDayStatus.java │ │ │ │ │ │ ├── WorkingTimeZone.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── wage │ │ │ │ │ │ ├── BaseHourlyWage.java │ │ │ │ │ │ ├── LegalDaysOffExtraRate.java │ │ │ │ │ │ ├── NightExtraRate.java │ │ │ │ │ │ ├── OverLegalMoreThan60HoursExtraRate.java │ │ │ │ │ │ ├── OverLegalWithin60HoursExtraRate.java │ │ │ │ │ │ ├── OverTimeExtraRate.java │ │ │ │ │ │ ├── WageCondition.java │ │ │ │ │ │ └── package-info.java │ │ │ │ ├── daysoff │ │ │ │ │ ├── DaysOff.java │ │ │ │ │ ├── DaysOffRecords.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── employee │ │ │ │ │ ├── ContractingEmployees.java │ │ │ │ │ ├── Employee.java │ │ │ │ │ ├── EmployeeNumber.java │ │ │ │ │ ├── EmployeeToRegister.java │ │ │ │ │ ├── MailAddress.java │ │ │ │ │ ├── MailAddressToChange.java │ │ │ │ │ ├── Name.java │ │ │ │ │ ├── NameToChange.java │ │ │ │ │ ├── PhoneNumber.java │ │ │ │ │ ├── PhoneNumberToChange.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── legislation │ │ │ │ │ ├── DailyWorkingHoursLimit.java │ │ │ │ │ ├── DaysOffStatus.java │ │ │ │ │ ├── ExtraPayRate.java │ │ │ │ │ ├── Night.java │ │ │ │ │ ├── WeeklyWorkingHoursLimit.java │ │ │ │ │ ├── WeeklyWorkingHoursStatus.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── payroll │ │ │ │ │ ├── PaymentAmount.java │ │ │ │ │ ├── Payroll.java │ │ │ │ │ ├── PayrollStatus.java │ │ │ │ │ ├── Payrolls.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── timerecord │ │ │ │ │ ├── evaluation │ │ │ │ │ │ ├── ActualWorkDateTime.java │ │ │ │ │ │ ├── AttendDates.java │ │ │ │ │ │ ├── BeforeMonthlyTimeRecord.java │ │ │ │ │ │ ├── BindingTime.java │ │ │ │ │ │ ├── BreakTime.java │ │ │ │ │ │ ├── DailyWorkingHoursStatus.java │ │ │ │ │ │ ├── DaytimeBindingTime.java │ │ │ │ │ │ ├── DaytimeBreakTime.java │ │ │ │ │ │ ├── DaytimeWorkTime.java │ │ │ │ │ │ ├── EndTimeValidResult.java │ │ │ │ │ │ ├── LegalDaysOffWorkTime.java │ │ │ │ │ │ ├── MonthlyOverLegalHoursStatus.java │ │ │ │ │ │ ├── MonthlyTimeRecord.java │ │ │ │ │ │ ├── NightBindingTime.java │ │ │ │ │ │ ├── NightBreakTime.java │ │ │ │ │ │ ├── NightWorkTime.java │ │ │ │ │ │ ├── OverLegalHoursWorkTime.java │ │ │ │ │ │ ├── OverLegalMoreThan60HoursWorkTime.java │ │ │ │ │ │ ├── OverLegalWithin60HoursWorkTime.java │ │ │ │ │ │ ├── Recorded.java │ │ │ │ │ │ ├── StartTimeValidResult.java │ │ │ │ │ │ ├── TimeRecord.java │ │ │ │ │ │ ├── TimeRecordValidResult.java │ │ │ │ │ │ ├── TimeRecords.java │ │ │ │ │ │ ├── WeeklyTimeRecord.java │ │ │ │ │ │ ├── WithinLegalHoursWorkTime.java │ │ │ │ │ │ ├── WorkDate.java │ │ │ │ │ │ ├── WorkTime.java │ │ │ │ │ │ ├── WorkTimes.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── timefact │ │ │ │ │ │ ├── EndDateTime.java │ │ │ │ │ │ ├── StartDateTime.java │ │ │ │ │ │ ├── WorkRange.java │ │ │ │ │ │ └── package-info.java │ │ │ │ └── wage │ │ │ │ │ ├── HourlyWage.java │ │ │ │ │ ├── LegalDaysOffHourlyExtraWage.java │ │ │ │ │ ├── NightHourlyExtraWage.java │ │ │ │ │ ├── OverLegalMoreThan60HoursHourlyExtraWage.java │ │ │ │ │ ├── OverLegalWithin60HoursHourlyExtraWage.java │ │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── type │ │ │ │ ├── amount │ │ │ │ │ ├── Amount.java │ │ │ │ │ ├── Percentage.java │ │ │ │ │ ├── RoundingMode.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── date │ │ │ │ │ ├── DayOfWeek.java │ │ │ │ │ ├── Month.java │ │ │ │ │ ├── Week.java │ │ │ │ │ ├── Year.java │ │ │ │ │ ├── YearMonth.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── datetime │ │ │ │ │ ├── DateTime.java │ │ │ │ │ ├── QuarterRoundDateTime.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── time │ │ │ │ │ ├── Hour.java │ │ │ │ │ ├── HourAndMinute.java │ │ │ │ │ ├── Minute.java │ │ │ │ │ ├── QuarterHour.java │ │ │ │ │ ├── Time.java │ │ │ │ │ └── package-info.java │ │ │ └── validation │ │ │ │ ├── BusinessLogic.java │ │ │ │ ├── Conversion.java │ │ │ │ ├── FormatCheck.java │ │ │ │ └── Required.java │ │ │ ├── infrastructure │ │ │ ├── datasource │ │ │ │ ├── contract │ │ │ │ │ ├── ContractDataSource.java │ │ │ │ │ ├── ContractMapper.java │ │ │ │ │ └── ContractMapper.xml │ │ │ │ ├── daysoff │ │ │ │ │ ├── DaysOffDataSource.java │ │ │ │ │ ├── DaysOffMapper.java │ │ │ │ │ └── DaysOffMapper.xml │ │ │ │ ├── employee │ │ │ │ │ ├── EmployeeDataSource.java │ │ │ │ │ ├── EmployeeMapper.java │ │ │ │ │ └── EmployeeMapper.xml │ │ │ │ ├── package-info.java │ │ │ │ └── timerecord │ │ │ │ │ ├── TimeRecordDataSource.java │ │ │ │ │ ├── TimeRecordMapper.java │ │ │ │ │ └── TimeRecordMapper.xml │ │ │ ├── package-info.java │ │ │ └── transfer │ │ │ │ └── package-info.java │ │ │ └── presentation │ │ │ ├── controller │ │ │ ├── BaseControllerAdvice.java │ │ │ ├── DashboardController.java │ │ │ ├── attendance │ │ │ │ └── AttendanceController.java │ │ │ ├── employee │ │ │ │ ├── BulkProfileUpdateForm.java │ │ │ │ ├── EmployeeController.java │ │ │ │ ├── EmployeeDeleteController.java │ │ │ │ ├── EmployeeRegisterController.java │ │ │ │ ├── EmployeeUpdateController.java │ │ │ │ └── EmployeesController.java │ │ │ ├── package-info.java │ │ │ ├── payroll │ │ │ │ └── PayrollController.java │ │ │ ├── timerecord │ │ │ │ ├── AttendanceForm.java │ │ │ │ ├── EndHour.java │ │ │ │ ├── EndMinute.java │ │ │ │ ├── EndTime.java │ │ │ │ ├── StartHour.java │ │ │ │ ├── StartMinute.java │ │ │ │ ├── StartTime.java │ │ │ │ └── TimeRecordRegisterController.java │ │ │ └── wage │ │ │ │ ├── WageListController.java │ │ │ │ └── WageRegisterController.java │ │ │ ├── package-info.java │ │ │ └── view │ │ │ └── package-info.java │ └── resources │ │ ├── application.properties │ │ ├── data.sql │ │ ├── messages.properties │ │ ├── messages_ja.properties │ │ ├── mybatis.xml │ │ ├── schema.sql │ │ ├── static │ │ └── css │ │ │ └── site.css │ │ └── templates │ │ ├── attendance │ │ └── list.html │ │ ├── dashboard.html │ │ ├── employee │ │ ├── detail.html │ │ ├── list.html │ │ ├── register │ │ │ ├── confirm.html │ │ │ ├── form.html │ │ │ └── result.html │ │ └── update │ │ │ └── form.html │ │ ├── fragments │ │ ├── field.html │ │ ├── header.html │ │ ├── input.html │ │ ├── layout.html │ │ ├── navigation.html │ │ └── radio.html │ │ ├── payroll │ │ └── list.html │ │ ├── timerecord │ │ └── form.html │ │ └── wage │ │ ├── confirm.html │ │ ├── form.html │ │ ├── list.html │ │ └── result.html └── test │ ├── java │ ├── example │ │ ├── BootUpTest.java │ │ ├── application │ │ │ └── service │ │ │ │ ├── ContractConditionRecordServiceTest.java │ │ │ │ ├── EmployeeRecordServiceTest.java │ │ │ │ ├── PayrollQueryCoordinatorTest.java │ │ │ │ └── TimeRecordRecordServiceTest.java │ │ ├── domain │ │ │ └── model │ │ │ │ ├── payroll │ │ │ │ └── PayrollTest.java │ │ │ │ └── timerecord │ │ │ │ ├── ActualWorkTimeTest.java │ │ │ │ └── evaluation │ │ │ │ ├── ActualWorkDateTimeTest.java │ │ │ │ └── WeeklyTimeRecordTest.java │ │ └── presentation │ │ │ └── controller │ │ │ ├── timerecord │ │ │ └── TimeRecordRegisterControllerTest.java │ │ │ └── wage │ │ │ └── WageRegisterControllerTest.java │ └── integration │ │ └── SmokeScenario.java │ └── resources │ ├── application-test.properties │ └── jig.properties └── system.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # For idea 2 | **/out 3 | .idea 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # For eclipse 9 | .project 10 | .classpath 11 | .settings/ 12 | bin/ 13 | 14 | # For Gradle 15 | **/build 16 | build/** 17 | .gradle 18 | 19 | # For astah 20 | *.lock 21 | 22 | # For local 23 | temp 24 | 25 | # For mac 26 | .DS_Store 27 | 28 | # backup files 29 | *.un~ 30 | 31 | 32 | # log files 33 | *.log 34 | 35 | # For Eclipse 36 | .classpath 37 | .project 38 | .settings 39 | 40 | # For npm 41 | node_modules/ 42 | 43 | # For Cypress 44 | cypress/videos 45 | cypress/screenshots 46 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java -Dserver.port=$PORT $JAVA_OPTS -jar build/libs/webapp.jar 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **本リポジトリは2020年時点から更新されていません。近々アップデートを予定していますが、それまでは以下から差分を抽出してください。** 2 | 3 | - https://speakerdeck.com/masuda220/ 4 | - [Software Design 2023年2月号](https://gihyo.jp/magazine/SD/archive/2023/202302) 5 | 6 | また、技術要素としてはJava11&SpringBoot2.xとなっています。SpringBoot3.x対応の例は [system-sekkei/library](https://github.com/system-sekkei/library) を参照してください。 7 | libraryはCCSR(isolating-the-domainにRDRA)のサンプルとなっています。 8 | 9 | # ドメインを独立させる Isolating the Domain 10 | - Spring Boot 11 | - Spring MVC (direct field access) 12 | - Thymeleaf 13 | - MyBatis SQL Mapper 14 | 15 | ## 起動方法 16 | 17 | ```sh 18 | ./gradlew bootRun 19 | ``` 20 | 21 | http://localhost:8080 でダッシュボード画面を表示 22 | 23 | ## 実行可能Jarのビルドと実行 24 | 25 | ```sh 26 | ./gradlew clean build 27 | java -jar webapp/build/libs/webapp.jar 28 | ``` 29 | 30 | ## JIG 設計ドキュメントの自動生成 31 | 32 | ```sh 33 | ./gradlew jig 34 | ``` 35 | 36 | `webapp/build/jig` 以下にソースコードから自動生成したクラス一覧やクラスの関連図を出力 37 | 38 | [JIG 設計ドキュメント](https://github.com/dddjava/jig) 39 | 40 | ## 設計ガイド 41 | 42 | [ドメインを独立させる](https://github.com/system-sekkei/isolating-the-domain/wiki) 43 | 44 | [ドメイン駆動設計本格入門](https://www.slideshare.net/masuda220/ss-137608652) 45 | 46 | [型指向のプログラミング:設計ガイドライン](https://github.com/masuda220/business-logic-patterns/wiki/%E8%A8%AD%E8%A8%88%E3%82%AC%E3%82%A4%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3) 47 | 48 | [書籍:現場で役立つシステム設計の原則](https://gihyo.jp/book/2017/978-4-7741-9087-7) 49 | 50 | ## アーキテクチャ 51 | 52 | ![アーキテクチャ](architecture.png) 53 | 54 | ## 参考資料 55 | 56 | - [労働基準法](https://elaws.e-gov.go.jp/search/elawsSearch/elaws_search/lsg0500/detail?lawId=322AC0000000049#171) 57 | - [労働基準関係法令のあらまし | 大阪労働局](https://jsite.mhlw.go.jp/osaka-roudoukyoku/hourei_seido_tetsuzuki/roudoukijun_keiyaku/hourei_seido/_122090.html) 58 | - 事業者向けに労働基準法をわかりやすくまとめたパンフレット。労働条件通知書の見本付き。 59 | - [時間外労働の上限規制 わかりやすい解説](https://www.mhlw.go.jp/content/000463185.pdf) 60 | - [労働基準法が改正されました](https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/koyou_roudou/roudoukijun/roukikaitei/index.html) 61 | - [改正労働基準法に係る質疑応答](https://www.mhlw.go.jp/topics/2008/12/dl/tp1216-1k.pdf) 62 | - [モデル就業規則](https://www.mhlw.go.jp/bunya/roudoukijun/model/dl/model.pdf) 63 | - [法定時間外労働 労働時間計算についてのメモ](./docs/overLegalWorkTime.md) -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/system-sekkei/isolating-the-domain/d9c76ffc8396c2b8754f5555b0fabf06d01a04cb/architecture.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'idea' 4 | id 'org.springframework.boot' version '2.7.9' 5 | id "org.dddjava.jig-gradle-plugin" version "2023.2.1" 6 | } 7 | 8 | [compileJava, processResources]*.shouldRunAfter(clean) 9 | jigReports.dependsOn(clean, classes) 10 | 11 | description 'ドメイン駆動設計によるアプリケーションテンプレート' 12 | 13 | sourceCompatibility = '11' 14 | 15 | sourceSets { 16 | main { 17 | // mybatis SQL map XML ファイルを java 以下でも検知する 18 | resources.srcDirs = ["src/main/java", "src/main/resources"] 19 | } 20 | } 21 | 22 | repositories { 23 | mavenCentral() 24 | } 25 | 26 | dependencies { 27 | implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) 28 | implementation("org.springframework.boot:spring-boot-starter-web") 29 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf") 30 | implementation("org.springframework.boot:spring-boot-starter-validation") 31 | 32 | implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2") 33 | 34 | runtimeOnly("com.h2database:h2") 35 | 36 | testImplementation("org.springframework.boot:spring-boot-starter-test") 37 | testImplementation('org.seleniumhq.selenium:htmlunit-driver') 38 | testImplementation 'com.github.irof:jig-erd:0.0.16' // JDK11 39 | } 40 | 41 | test { 42 | useJUnitPlatform() 43 | } 44 | 45 | bootJar { 46 | archivesBaseName = "webapp" 47 | } 48 | -------------------------------------------------------------------------------- /docs/images/overLegalWorkTime1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/system-sekkei/isolating-the-domain/d9c76ffc8396c2b8754f5555b0fabf06d01a04cb/docs/images/overLegalWorkTime1.png -------------------------------------------------------------------------------- /docs/images/overLegalWorkTime2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/system-sekkei/isolating-the-domain/d9c76ffc8396c2b8754f5555b0fabf06d01a04cb/docs/images/overLegalWorkTime2.png -------------------------------------------------------------------------------- /docs/overLegalWorkTime.md: -------------------------------------------------------------------------------- 1 | ## 法定時間外労働 労働時間計算についてのメモ 2 | 3 | ![](./images/overLegalWorkTime1.png) 4 | ![](./images/overLegalWorkTime2.png) -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/system-sekkei/isolating-the-domain/d9c76ffc8396c2b8754f5555b0fabf06d01a04cb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'isolating-the-domain' 2 | -------------------------------------------------------------------------------- /src/main/java/example/Application.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | public static void main(String[] args) { 9 | SpringApplication.run(Application.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/example/application/coordinator/employee/EmployeeRecordCoordinator.java: -------------------------------------------------------------------------------- 1 | package example.application.coordinator.employee; 2 | 3 | import example.application.service.employee.EmployeeRecordService; 4 | import example.domain.model.employee.*; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * 従業員登録コーディネーター 9 | */ 10 | @Service 11 | public class EmployeeRecordCoordinator { 12 | 13 | EmployeeRecordService employeeRecordService; 14 | 15 | public EmployeeRecordCoordinator(EmployeeRecordService employeeRecordService) { 16 | this.employeeRecordService = employeeRecordService; 17 | } 18 | 19 | /** 20 | * 従業員登録 21 | */ 22 | public EmployeeNumber register(EmployeeToRegister employeeToRegister) { 23 | EmployeeNumber employeeNumber = employeeRecordService.prepareNewContract(); 24 | employeeRecordService.registerName(new NameToChange(employeeNumber, employeeToRegister.name())); 25 | employeeRecordService.registerMailAddress(new MailAddressToChange(employeeNumber, employeeToRegister.mailAddress())); 26 | employeeRecordService.registerPhoneNumber(new PhoneNumberToChange(employeeNumber, employeeToRegister.phoneNumber())); 27 | employeeRecordService.inspireContract(employeeNumber); 28 | return employeeNumber; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/example/application/coordinator/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * コーディネーター 3 | * 複数の機能(サービス要素)を組み合わせた、複合サービスを記述する 4 | */ 5 | package example.application.coordinator; -------------------------------------------------------------------------------- /src/main/java/example/application/coordinator/payroll/PayrollQueryCoordinator.java: -------------------------------------------------------------------------------- 1 | package example.application.coordinator.payroll; 2 | 3 | import example.application.service.attendance.AttendanceQueryService; 4 | import example.application.service.contract.ContractQueryService; 5 | import example.application.service.timerecord.TimeRecordQueryService; 6 | import example.domain.model.attendance.Attendance; 7 | import example.domain.model.attendance.WorkMonth; 8 | import example.domain.model.contract.Contract; 9 | import example.domain.model.contract.ContractConditions; 10 | import example.domain.model.employee.ContractingEmployees; 11 | import example.domain.model.employee.Employee; 12 | import example.domain.model.payroll.Payroll; 13 | import example.domain.model.payroll.Payrolls; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * 給与参照コーディネーター 21 | */ 22 | @Service 23 | public class PayrollQueryCoordinator { 24 | ContractQueryService contractQueryService; 25 | AttendanceQueryService attendanceQueryService; 26 | 27 | /** 28 | * 月次給与取得 29 | */ 30 | public Payrolls payrolls(ContractingEmployees contractingEmployees, WorkMonth workMonth) { 31 | List list = new ArrayList<>(); 32 | for (Employee employee : contractingEmployees.list()) { 33 | list.add(payroll(employee, workMonth)); 34 | } 35 | return new Payrolls(workMonth, list); 36 | } 37 | 38 | /** 39 | * 従業員別月次給与取得 40 | */ 41 | public Payroll payroll(Employee employee, WorkMonth workMonth) { 42 | ContractConditions contractConditions = contractQueryService.getContractWages(employee); 43 | Attendance attendance = attendanceQueryService.findAttendance(employee, workMonth); 44 | Attendance beforeMonthAttendance = attendanceQueryService.findAttendance(employee, workMonth.before()); 45 | 46 | return new Payroll(new Contract(employee, contractConditions), attendance, beforeMonthAttendance); 47 | } 48 | 49 | PayrollQueryCoordinator(ContractQueryService contractQueryService, AttendanceQueryService attendanceQueryService) { 50 | this.contractQueryService = contractQueryService; 51 | this.attendanceQueryService = attendanceQueryService; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/example/application/coordinator/payroll/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 給与機能 3 | */ 4 | package example.application.coordinator.payroll; -------------------------------------------------------------------------------- /src/main/java/example/application/coordinator/timerecord/TimeRecordCoordinator.java: -------------------------------------------------------------------------------- 1 | package example.application.coordinator.timerecord; 2 | 3 | import example.application.service.attendance.AttendanceQueryService; 4 | import example.application.service.employee.EmployeeQueryService; 5 | import example.application.service.timerecord.TimeRecordQueryService; 6 | import example.domain.model.employee.Employee; 7 | import example.domain.model.timerecord.evaluation.*; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * 勤務実績登録コーディネーター 12 | */ 13 | @Service 14 | public class TimeRecordCoordinator { 15 | TimeRecordQueryService timeRecordQueryService; 16 | AttendanceQueryService attendanceQueryService; 17 | EmployeeQueryService employeeQueryService; 18 | 19 | public TimeRecordCoordinator(TimeRecordQueryService timeRecordQueryService, AttendanceQueryService attendanceQueryService, EmployeeQueryService employeeQueryService) { 20 | this.timeRecordQueryService = timeRecordQueryService; 21 | this.attendanceQueryService = attendanceQueryService; 22 | this.employeeQueryService = employeeQueryService; 23 | } 24 | 25 | public TimeRecordValidResult isValid(TimeRecord timeRecord) { 26 | StartTimeValidResult startTimeValidResult = checkOverlapWithPreviousWorkRange(timeRecord); 27 | EndTimeValidResult endTimeValidResult = checkOverlapWithNextWorkRange(timeRecord); 28 | 29 | return new TimeRecordValidResult(startTimeValidResult, endTimeValidResult); 30 | } 31 | 32 | /** 33 | * 前の勤務日と勤務開始時刻が重複していないかどうか 34 | */ 35 | StartTimeValidResult checkOverlapWithPreviousWorkRange(TimeRecord timeRecord) { 36 | Employee employee = employeeQueryService.choose(timeRecord.employeeNumber()); 37 | 38 | WorkDate previousDate = new WorkDate(timeRecord.workDate().value().minusDays(1)); 39 | 40 | if (attendanceQueryService.attendanceStatus(employee, previousDate).isWork()) { 41 | TimeRecord previous = timeRecordQueryService.timeRecord(employee, previousDate); 42 | if (timeRecord.isOverlap(previous)) { 43 | return StartTimeValidResult.前日の勤務時刻と重複; 44 | } 45 | } 46 | 47 | return StartTimeValidResult.正常; 48 | } 49 | 50 | /** 51 | * 次の勤務日と勤務終了時刻が重複していないかどうか 52 | */ 53 | EndTimeValidResult checkOverlapWithNextWorkRange(TimeRecord timeRecord) { 54 | Employee employee = employeeQueryService.choose(timeRecord.employeeNumber()); 55 | 56 | WorkDate nextDate = new WorkDate(timeRecord.workDate().value().plusDays(1)); 57 | 58 | if (attendanceQueryService.attendanceStatus(employee, nextDate).isWork()) { 59 | TimeRecord next = timeRecordQueryService.timeRecord(employee, nextDate); 60 | if (timeRecord.isOverlap(next)) { 61 | return EndTimeValidResult.翌日の勤務時刻と重複; 62 | } 63 | } 64 | 65 | return EndTimeValidResult.正常; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/example/application/coordinator/timerecord/TimeRecordQueryCoordinator.java: -------------------------------------------------------------------------------- 1 | package example.application.coordinator.timerecord; 2 | 3 | import example.application.service.attendance.AttendanceQueryService; 4 | import example.application.service.employee.EmployeeQueryService; 5 | import example.application.service.timerecord.TimeRecordQueryService; 6 | import example.domain.model.employee.Employee; 7 | import example.domain.model.employee.EmployeeNumber; 8 | import example.domain.model.timerecord.evaluation.*; 9 | import example.domain.model.timerecord.timefact.EndDateTime; 10 | import example.domain.model.timerecord.timefact.StartDateTime; 11 | import example.domain.model.timerecord.timefact.WorkRange; 12 | import example.domain.type.datetime.DateTime; 13 | import example.domain.type.time.Minute; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.time.LocalDateTime; 17 | import java.time.LocalTime; 18 | 19 | @Service 20 | public class TimeRecordQueryCoordinator { 21 | 22 | EmployeeQueryService employeeQueryService; 23 | TimeRecordQueryService timeRecordQueryService; 24 | AttendanceQueryService attendanceQueryService; 25 | 26 | public TimeRecordQueryCoordinator(EmployeeQueryService employeeQueryService, TimeRecordQueryService timeRecordQueryService, AttendanceQueryService attendanceQueryService) { 27 | this.employeeQueryService = employeeQueryService; 28 | this.timeRecordQueryService = timeRecordQueryService; 29 | this.attendanceQueryService = attendanceQueryService; 30 | } 31 | 32 | public TimeRecord timeRecord(EmployeeNumber employeeNumber, WorkDate workDate) { 33 | Employee employee = employeeQueryService.choose(employeeNumber); 34 | if (attendanceQueryService.attendanceStatus(employee, workDate).isWork()) { 35 | return timeRecordQueryService.timeRecord(employee, workDate); 36 | } 37 | 38 | return standardTimeRecord(employeeNumber, workDate); 39 | } 40 | 41 | // TODO 雇用契約から取得する #117 42 | public TimeRecord standardTimeRecord(EmployeeNumber employeeNumber, WorkDate workDate) { 43 | LocalTime startTime = LocalTime.of(9, 30); 44 | LocalTime endTime = LocalTime.of(18, 0); 45 | return new TimeRecord(employeeNumber, 46 | new ActualWorkDateTime( 47 | new WorkRange( 48 | new StartDateTime(new DateTime(LocalDateTime.of(workDate.value(), startTime))), 49 | new EndDateTime(new DateTime(LocalDateTime.of(workDate.value(), endTime))) 50 | ), 51 | new DaytimeBreakTime(new Minute(60)), 52 | new NightBreakTime(new Minute(0)) 53 | )); 54 | } 55 | 56 | public TimeRecord defaultTimeRecord(WorkDate workDate) { 57 | Employee employee = employeeQueryService.contractingEmployees().list().get(0); 58 | return standardTimeRecord(employee.employeeNumber(), workDate); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/example/application/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーション層 3 | * 実現するアプリケーション機能を表現する 4 | * ドメイン層へのファサード 5 | */ 6 | package example.application; -------------------------------------------------------------------------------- /src/main/java/example/application/repository/ContractRepository.java: -------------------------------------------------------------------------------- 1 | package example.application.repository; 2 | 3 | 4 | import example.domain.model.contract.ContractEffectiveDate; 5 | import example.domain.model.contract.ContractConditions; 6 | import example.domain.model.contract.Contracts; 7 | import example.domain.model.contract.wage.WageCondition; 8 | import example.domain.model.employee.ContractingEmployees; 9 | import example.domain.model.employee.Employee; 10 | 11 | /** 12 | * 契約リポジトリ 13 | */ 14 | public interface ContractRepository { 15 | ContractConditions getContractConditions(Employee employee); 16 | 17 | void registerHourlyWage(Employee employee, ContractEffectiveDate effectiveDate, WageCondition wageCondition); 18 | 19 | Contracts findContracts(ContractingEmployees contractingEmployees); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/example/application/repository/DaysOffRepository.java: -------------------------------------------------------------------------------- 1 | package example.application.repository; 2 | 3 | import example.domain.model.daysoff.DaysOff; 4 | 5 | /** 6 | * 休日リポジトリ 7 | */ 8 | public interface DaysOffRepository { 9 | 10 | void registerDaysOff(DaysOff daysOff); 11 | 12 | void deleteDaysOff(DaysOff daysOff); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/application/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package example.application.repository; 2 | 3 | import example.domain.model.employee.*; 4 | 5 | /** 6 | * 従業員リポジトリ 7 | */ 8 | public interface EmployeeRepository { 9 | Employee choose(EmployeeNumber employeeNumber); 10 | 11 | ContractingEmployees findUnderContracts(); 12 | 13 | void registerName(EmployeeNumber employeeNumber, Name name); 14 | 15 | void registerMailAddress(EmployeeNumber employeeNumber, MailAddress mailAddress); 16 | 17 | void registerPhoneNumber(EmployeeNumber employeeNumber, PhoneNumber phoneNumber); 18 | 19 | void registerInspireContract(EmployeeNumber employeeNumber); 20 | 21 | void registerExpireContract(Employee employee); 22 | 23 | EmployeeNumber registerNew(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/application/repository/TimeRecordRepository.java: -------------------------------------------------------------------------------- 1 | package example.application.repository; 2 | 3 | import example.domain.model.timerecord.evaluation.TimeRecords; 4 | import example.domain.model.attendance.WorkMonth; 5 | import example.domain.model.employee.Employee; 6 | import example.domain.model.timerecord.evaluation.TimeRecord; 7 | 8 | /** 9 | * 勤務実績リポジトリ 10 | */ 11 | public interface TimeRecordRepository { 12 | 13 | void registerTimeRecord(TimeRecord timeRecord); 14 | 15 | TimeRecords findTimeRecords(Employee employee, WorkMonth month); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/attendance/AttendanceQueryService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.attendance; 2 | 3 | import example.application.repository.TimeRecordRepository; 4 | import example.domain.model.attendance.Attendance; 5 | import example.domain.model.attendance.AttendanceStatus; 6 | import example.domain.model.timerecord.evaluation.TimeRecords; 7 | import example.domain.model.attendance.WorkMonth; 8 | import example.domain.model.employee.Employee; 9 | import example.domain.model.timerecord.evaluation.WorkDate; 10 | import org.springframework.stereotype.Service; 11 | 12 | /** 13 | * 勤務時間参照サービス 14 | */ 15 | @Service 16 | public class AttendanceQueryService { 17 | 18 | TimeRecordRepository timeRecordRepository; 19 | 20 | /** 21 | * 月次勤怠取得 22 | */ 23 | public Attendance findAttendance(Employee employee, WorkMonth month) { 24 | TimeRecords timeRecords = timeRecordRepository.findTimeRecords(employee, month); 25 | return new Attendance(month, timeRecords); 26 | } 27 | 28 | /** 29 | * 勤怠状況取得 30 | */ 31 | public AttendanceStatus attendanceStatus(Employee employee, WorkDate workDate) { 32 | return findAttendance(employee, WorkMonth.from(workDate)).statusOf(workDate); 33 | } 34 | 35 | AttendanceQueryService(TimeRecordRepository timeRecordRepository) { 36 | this.timeRecordRepository = timeRecordRepository; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/attendance/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 勤怠機能 3 | */ 4 | package example.application.service.attendance; -------------------------------------------------------------------------------- /src/main/java/example/application/service/contract/ContractQueryService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.contract; 2 | 3 | import example.application.repository.ContractRepository; 4 | import example.domain.model.contract.ContractConditions; 5 | import example.domain.model.contract.Contracts; 6 | import example.domain.model.employee.ContractingEmployees; 7 | import example.domain.model.employee.Employee; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * 契約参照サービス 12 | */ 13 | @Service 14 | public class ContractQueryService { 15 | ContractRepository contractRepository; 16 | 17 | /** 18 | * 契約取得 19 | */ 20 | public ContractConditions getContractWages(Employee employee) { 21 | return contractRepository.getContractConditions(employee); 22 | } 23 | 24 | /** 25 | * 従業員契約一覧 26 | */ 27 | public Contracts findContracts(ContractingEmployees contractingEmployees) { 28 | return contractRepository.findContracts(contractingEmployees); 29 | } 30 | 31 | ContractQueryService(ContractRepository contractRepository) { 32 | this.contractRepository = contractRepository; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/contract/ContractRecordService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.contract; 2 | 3 | import example.application.repository.ContractRepository; 4 | import example.domain.model.contract.ContractEffectiveDate; 5 | import example.domain.model.contract.wage.WageCondition; 6 | import example.domain.model.employee.Employee; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | * 契約登録サービス 11 | */ 12 | @Service 13 | public class ContractRecordService { 14 | ContractRepository contractRepository; 15 | 16 | ContractRecordService(ContractRepository contractRepository) { 17 | this.contractRepository = contractRepository; 18 | } 19 | 20 | /** 21 | * 時給登録 22 | */ 23 | public void registerHourlyWage(Employee employee, ContractEffectiveDate effectiveDate, WageCondition wageCondition) { 24 | contractRepository.registerHourlyWage(employee, effectiveDate, wageCondition); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/contract/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 契約機能 3 | */ 4 | package example.application.service.contract; -------------------------------------------------------------------------------- /src/main/java/example/application/service/daysoff/DaysOffRecordService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.daysoff; 2 | 3 | import example.application.repository.DaysOffRepository; 4 | import example.domain.model.daysoff.DaysOff; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * 休日登録サービス 9 | */ 10 | @Service 11 | public class DaysOffRecordService { 12 | DaysOffRepository daysOffRepository; 13 | 14 | DaysOffRecordService(DaysOffRepository daysOffRepository) { 15 | this.daysOffRepository = daysOffRepository; 16 | } 17 | 18 | public void registerDaysOffRecord(DaysOff daysOff, boolean isDaysOff) { 19 | if (isDaysOff) { 20 | daysOffRepository.registerDaysOff(daysOff); 21 | } else { 22 | daysOffRepository.deleteDaysOff(daysOff); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/employee/EmployeeQueryService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.employee; 2 | 3 | import example.application.repository.EmployeeRepository; 4 | import example.domain.model.employee.ContractingEmployees; 5 | import example.domain.model.employee.Employee; 6 | import example.domain.model.employee.EmployeeNumber; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | * 従業員参照サービス 11 | */ 12 | @Service 13 | public class EmployeeQueryService { 14 | 15 | EmployeeRepository employeeRepository; 16 | 17 | /** 18 | * 従業員選択 19 | */ 20 | public Employee choose(EmployeeNumber employeeNumber) { 21 | return employeeRepository.choose(employeeNumber); 22 | } 23 | 24 | /** 25 | * 契約中従業員一覧 26 | */ 27 | public ContractingEmployees contractingEmployees() { 28 | return employeeRepository.findUnderContracts(); 29 | } 30 | 31 | EmployeeQueryService(EmployeeRepository employeeRepository) { 32 | this.employeeRepository = employeeRepository; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/employee/EmployeeRecordService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.employee; 2 | 3 | import example.application.repository.EmployeeRepository; 4 | import example.domain.model.employee.*; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * 従業員登録更新サービス 9 | */ 10 | @Service 11 | public class EmployeeRecordService { 12 | 13 | EmployeeRepository employeeRepository; 14 | 15 | /** 16 | * 従業員契約準備 17 | */ 18 | public EmployeeNumber prepareNewContract() { 19 | return employeeRepository.registerNew(); 20 | } 21 | 22 | /** 23 | * 従業員名登録 24 | */ 25 | public void registerName(NameToChange name) { 26 | employeeRepository.registerName(name.employeeNumber(), name.name()); 27 | } 28 | 29 | /** 30 | * 従業員メールアドレス登録 31 | */ 32 | public void registerMailAddress(MailAddressToChange mail) { 33 | employeeRepository.registerMailAddress(mail.employeeNumber(), mail.mailAddress()); 34 | } 35 | 36 | /** 37 | * 従業員電話番号登録 38 | */ 39 | public void registerPhoneNumber(PhoneNumberToChange phone) { 40 | employeeRepository.registerPhoneNumber(phone.employeeNumber(), phone.phoneNumber()); 41 | } 42 | 43 | /** 44 | * 従業員契約開始 45 | */ 46 | public void inspireContract(EmployeeNumber employeeNumber) { 47 | employeeRepository.registerInspireContract(employeeNumber); 48 | } 49 | 50 | /** 51 | * 従業員契約終了 52 | */ 53 | public void expireContract(Employee employee) { 54 | employeeRepository.registerExpireContract(employee); 55 | } 56 | 57 | 58 | EmployeeRecordService(EmployeeRepository employeeRepository) { 59 | this.employeeRepository = employeeRepository; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/employee/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 従業員機能 3 | */ 4 | package example.application.service.employee; -------------------------------------------------------------------------------- /src/main/java/example/application/service/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * サービス定義 3 | * 個々の機能部品を定義する 4 | */ 5 | package example.application.service; -------------------------------------------------------------------------------- /src/main/java/example/application/service/timerecord/TimeRecordQueryService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.timerecord; 2 | 3 | import example.application.repository.TimeRecordRepository; 4 | import example.domain.model.attendance.WorkMonth; 5 | import example.domain.model.employee.Employee; 6 | import example.domain.model.timerecord.evaluation.TimeRecord; 7 | import example.domain.model.timerecord.evaluation.WorkDate; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * 勤務実績参照サービス 12 | */ 13 | @Service 14 | public class TimeRecordQueryService { 15 | 16 | TimeRecordRepository timeRecordRepository; 17 | 18 | /** 19 | * 日次勤務実績取得 20 | */ 21 | public TimeRecord timeRecord(Employee employee, WorkDate workDate) { 22 | return timeRecordRepository.findTimeRecords(employee, WorkMonth.from(workDate)).at(workDate); 23 | } 24 | 25 | TimeRecordQueryService(TimeRecordRepository timeRecordRepository) { 26 | this.timeRecordRepository = timeRecordRepository; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/timerecord/TimeRecordRecordService.java: -------------------------------------------------------------------------------- 1 | package example.application.service.timerecord; 2 | 3 | import example.application.repository.TimeRecordRepository; 4 | import example.domain.model.timerecord.evaluation.TimeRecord; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * 勤務実績記録サービス 9 | */ 10 | @Service 11 | public class TimeRecordRecordService { 12 | 13 | TimeRecordRepository timeRecordRepository; 14 | 15 | /** 16 | * 勤務登録 17 | */ 18 | public void registerTimeRecord(TimeRecord timeRecord) { 19 | timeRecordRepository.registerTimeRecord(timeRecord); 20 | } 21 | 22 | TimeRecordRecordService(TimeRecordRepository timeRecordRepository) { 23 | this.timeRecordRepository = timeRecordRepository; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/example/application/service/timerecord/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 勤務実績機能 3 | */ 4 | package example.application.service.timerecord; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/attendance/Attendance.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.attendance; 2 | 3 | import example.domain.model.timerecord.evaluation.AttendDates; 4 | import example.domain.model.timerecord.evaluation.TimeRecord; 5 | import example.domain.model.timerecord.evaluation.TimeRecords; 6 | import example.domain.model.timerecord.evaluation.WorkDate; 7 | import example.domain.type.time.QuarterHour; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | /** 13 | * 勤怠 14 | */ 15 | public class Attendance { 16 | 17 | WorkMonth month; 18 | TimeRecords timeRecords; 19 | 20 | public Attendance(WorkMonth month, TimeRecords timeRecords) { 21 | this.month = month; 22 | this.timeRecords = timeRecords; 23 | } 24 | 25 | public WorkMonth month() { 26 | return month; 27 | } 28 | 29 | public List listWorkDates() { 30 | return month.days(); 31 | } 32 | 33 | public TimeRecord at(WorkDate workDate) { 34 | return timeRecords.at(workDate); 35 | } 36 | 37 | public AttendanceStatus statusOf(WorkDate workDate) { 38 | return AttendanceStatus.from(timeRecords.recordedAt(workDate)); 39 | } 40 | 41 | public AttendDates attendDates() { 42 | return timeRecords.attendDates(); 43 | } 44 | 45 | public TotalWorkTime totalWorkTime() { 46 | return new TotalWorkTime(timeRecords.list().stream() 47 | .map(timeRecord -> timeRecord.actualWorkDateTime().workTime().quarterHour()) 48 | .reduce(QuarterHour::add) 49 | .orElseGet(QuarterHour::new)); 50 | } 51 | 52 | public List listPayableWork() { 53 | return timeRecords.list().stream() 54 | .map(timeRecord -> new PayableWork(timeRecord.actualWorkDateTime())) 55 | .collect(Collectors.toList()); 56 | } 57 | 58 | public TimeRecords timeRecords() { 59 | return timeRecords; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/java/example/domain/model/attendance/AttendanceStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.attendance; 2 | 3 | import example.domain.model.timerecord.evaluation.Recorded; 4 | 5 | /** 6 | * 勤怠状況 7 | */ 8 | public enum AttendanceStatus { 9 | 出勤, 10 | 非出勤; 11 | 12 | public static AttendanceStatus from(Recorded recorded) { 13 | if (recorded == Recorded.記録あり) return 出勤; 14 | return 非出勤; 15 | } 16 | 17 | public boolean isWork() { 18 | return this == 出勤; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/attendance/PayableWork.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.attendance; 2 | 3 | import example.domain.model.timerecord.evaluation.*; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | import java.time.LocalDate; 7 | 8 | /** 9 | * 支払い対象となる稼働 10 | */ 11 | public class PayableWork { 12 | 13 | ActualWorkDateTime actualWorkDateTime; 14 | 15 | public PayableWork(ActualWorkDateTime actualWorkDateTime) { 16 | this.actualWorkDateTime = actualWorkDateTime; 17 | } 18 | 19 | public WorkDate workDate() { 20 | return actualWorkDateTime.workDate(); 21 | } 22 | 23 | public QuarterHour workTime() { 24 | return actualWorkDateTime.workTime().quarterHour(); 25 | } 26 | 27 | public QuarterHour nightWorkTime() { 28 | return actualWorkDateTime.nightWorkTime().quarterHour(); 29 | } 30 | 31 | public OverLegalHoursWorkTime overLegalHoursWorkTime(MonthlyTimeRecord monthlyTimeRecord, BeforeMonthlyTimeRecord beforeMonthlyTimeRecord, WorkDate workDate) { 32 | return OverLegalHoursWorkTime.from(monthlyTimeRecord, beforeMonthlyTimeRecord, workDate); 33 | } 34 | 35 | public LegalDaysOffWorkTime legalDaysOffWorkTime(WeeklyTimeRecord weeklyTimeRecord) { 36 | return actualWorkDateTime.legalDaysOffWorkTime(weeklyTimeRecord); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/attendance/TotalWorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.attendance; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | /** 6 | * 総勤務時間 7 | */ 8 | public class TotalWorkTime { 9 | 10 | QuarterHour value; 11 | 12 | public TotalWorkTime(QuarterHour value) { 13 | this.value = value; 14 | } 15 | 16 | public QuarterHour quarterHour() { 17 | return value; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return value.toString(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/attendance/WorkMonth.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.attendance; 2 | 3 | import example.domain.model.timerecord.evaluation.WorkDate; 4 | import example.domain.type.date.Month; 5 | import example.domain.type.date.Year; 6 | import example.domain.type.date.YearMonth; 7 | 8 | import java.time.LocalDate; 9 | import java.time.format.DateTimeFormatter; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * 勤務月 15 | */ 16 | public class WorkMonth { 17 | YearMonth value; 18 | 19 | public WorkMonth() { 20 | this(new YearMonth()); 21 | } 22 | 23 | public WorkMonth(int year, int month) { 24 | this(new YearMonth(year, month)); 25 | } 26 | 27 | public WorkMonth(Year year, Month month) { 28 | this(new YearMonth(year, month)); 29 | } 30 | 31 | public WorkMonth(String yearMonth) { 32 | this(new YearMonth(yearMonth)); 33 | } 34 | 35 | public WorkMonth(YearMonth value) { 36 | this.value = value; 37 | } 38 | 39 | public static WorkMonth from(WorkDate workDate) { 40 | return new WorkMonth(new Year(workDate.toDate().getYear()), Month.of(workDate.toDate().getMonth().getValue())); 41 | } 42 | 43 | public List days() { 44 | List days = value.days(); 45 | List workDates = days.stream().map(WorkDate::new) 46 | .collect(Collectors.toList()); 47 | return workDates; 48 | } 49 | 50 | public WorkMonth before() { 51 | return new WorkMonth(value.before()); 52 | } 53 | 54 | public WorkMonth after() { 55 | return new WorkMonth(value.after()); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return value.toString(); 61 | } 62 | 63 | public String toStringWithUnit() { 64 | if (value.isThisYear()) { 65 | return String.format("%s月", value.month().toString()); 66 | } 67 | return String.format("%s年%s月", value.year().toString(), value.month().toString()); 68 | } 69 | 70 | public String yyyyMM() { 71 | return value.value().format(DateTimeFormatter.ofPattern("uuuuMM")); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/attendance/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 給与単位の勤怠 3 | * 4 | * 1ヶ月の勤務記録を束ね、出勤欠勤を扱う。 5 | */ 6 | package example.domain.model.attendance; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/Contract.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract; 2 | 3 | import example.domain.model.contract.wage.BaseHourlyWage; 4 | import example.domain.model.contract.wage.WageCondition; 5 | import example.domain.model.employee.Employee; 6 | import example.domain.model.employee.EmployeeNumber; 7 | import example.domain.model.employee.Name; 8 | 9 | import java.time.LocalDate; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * 従業員契約 15 | */ 16 | public class Contract { 17 | Employee employee; 18 | ContractConditions contractConditions; 19 | 20 | public Contract(Employee employee, ContractConditions contractConditions) { 21 | this.employee = employee; 22 | this.contractConditions = contractConditions; 23 | } 24 | 25 | public EmployeeNumber employeeNumber() { 26 | return employee.employeeNumber(); 27 | } 28 | 29 | public Name employeeName() { 30 | return employee.name(); 31 | } 32 | 33 | public ContractEffectiveDate contractStartingDate() { 34 | ArrayList list = new ArrayList<>(contractConditions.list()); 35 | if (list.isEmpty()) { 36 | return ContractEffectiveDate.none(); 37 | } 38 | return list.get(list.size() - 1).effectiveDate(); 39 | } 40 | 41 | public BaseHourlyWage todayHourlyWage() { 42 | LocalDate today = LocalDate.now(); 43 | if (contractStatus(today).disable()) { 44 | return BaseHourlyWage.disable(); 45 | } 46 | return availableContractAt(today).baseHourlyWage(); 47 | } 48 | 49 | public ContractCondition availableContractAt(LocalDate date) { 50 | return contractConditions.list().stream() 51 | .filter(contractCondition -> contractCondition.availableAt(date)) 52 | .findFirst() 53 | .orElseThrow(() -> new IllegalStateException(date.toString())); 54 | } 55 | 56 | public ContractStatus contractStatus(LocalDate date) { 57 | return contractStartingDate().isAfter(date) ? ContractStatus.契約なし : ContractStatus.契約あり; 58 | } 59 | 60 | public ContractStatus contractStatus(List dates) { 61 | if (dates.isEmpty()) { 62 | return ContractStatus.判定不能; 63 | } 64 | return contractStatus(dates.iterator().next()); 65 | } 66 | 67 | public WageCondition wageConditionAt(LocalDate date) { 68 | ContractCondition contractCondition = availableContractAt(date); 69 | return contractCondition.wageCondition(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/ContractCondition.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract; 2 | 3 | import example.domain.model.contract.shift.Shifts; 4 | import example.domain.model.contract.wage.BaseHourlyWage; 5 | import example.domain.model.contract.wage.WageCondition; 6 | 7 | import java.time.LocalDate; 8 | 9 | /** 10 | * 労働条件 11 | */ 12 | public class ContractCondition { 13 | ContractEffectiveDate effectiveDate; 14 | Shifts shifts; 15 | WageCondition wageCondition; 16 | 17 | @Deprecated 18 | public ContractCondition() { 19 | } 20 | 21 | public ContractCondition(ContractEffectiveDate effectiveDate, WageCondition wageCondition) { 22 | this.effectiveDate = effectiveDate; 23 | this.wageCondition = wageCondition; 24 | } 25 | 26 | public WageCondition wageCondition() { 27 | return wageCondition; 28 | } 29 | 30 | public BaseHourlyWage baseHourlyWage() { 31 | return wageCondition.baseHourlyWage(); 32 | } 33 | 34 | public ContractEffectiveDate effectiveDate() { 35 | return effectiveDate; 36 | } 37 | 38 | public boolean availableAt(LocalDate date) { 39 | return effectiveDate.value().equals(date) || date.isAfter(effectiveDate.value()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/ContractConditions.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | /** 7 | * 労働条件一覧 8 | */ 9 | public class ContractConditions { 10 | List list; 11 | 12 | public ContractConditions(List contractConditions) { 13 | this.list = contractConditions; 14 | } 15 | 16 | public List list() { 17 | return list.stream() 18 | .sorted((c1, c2) -> c2.effectiveDate().value().compareTo(c1.effectiveDate().value())) 19 | .collect(Collectors.toList()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/ContractEffectiveDate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract; 2 | 3 | import org.springframework.format.annotation.DateTimeFormat; 4 | 5 | import java.time.LocalDate; 6 | import java.time.format.DateTimeFormatter; 7 | 8 | /** 9 | * 契約開始日 10 | */ 11 | public class ContractEffectiveDate { 12 | 13 | @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) 14 | LocalDate value; 15 | 16 | @Deprecated 17 | public ContractEffectiveDate() { 18 | } 19 | 20 | public ContractEffectiveDate(String value) { 21 | this(LocalDate.parse(value, DateTimeFormatter.ISO_DATE)); 22 | } 23 | 24 | public ContractEffectiveDate(LocalDate value) { 25 | this.value = value; 26 | } 27 | 28 | // TODO: 契約期間がない契約はありえない 29 | public static ContractEffectiveDate none() { 30 | return new ContractEffectiveDate(distantFuture()); 31 | } 32 | 33 | public static LocalDate distantFuture() { 34 | return LocalDate.of(9999, 12, 31); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | if (notAvailable()) { 40 | return "未設定"; 41 | } 42 | return value.format(DateTimeFormatter.ofPattern("uuuu年M月d日")); 43 | } 44 | 45 | private boolean notAvailable() { 46 | return value.equals(distantFuture()); 47 | } 48 | 49 | public boolean isAfter(LocalDate date) { 50 | return value.isAfter(date); 51 | } 52 | 53 | public LocalDate value() { 54 | return value; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/ContractStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract; 2 | 3 | /** 4 | * 契約状態 5 | */ 6 | public enum ContractStatus { 7 | 判定不能, 8 | 契約あり, 9 | 契約なし; 10 | 11 | public boolean disable() { 12 | return this == 契約なし; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/Contracts.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 従業員契約一覧 7 | */ 8 | public class Contracts { 9 | List list; 10 | 11 | public Contracts(List list) { 12 | this.list = list; 13 | } 14 | 15 | public List list() { 16 | return list; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/LegalOvertimeHoursStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract; 2 | 3 | /** 4 | * 法定時間外労働の区分 5 | */ 6 | public enum LegalOvertimeHoursStatus { 7 | 法定時間外労働が月60時間以内, 8 | 法定時間外労働が月60時間超 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 雇用契約 3 | */ 4 | package example.domain.model.contract; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/BreakTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | import example.domain.type.time.Minute; 4 | 5 | /** 6 | * 休憩時間 7 | */ 8 | public class BreakTime { 9 | Minute value; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/DayStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | /** 4 | * 労働日/休日 5 | */ 6 | public enum DayStatus { 7 | 労働日, 8 | 法定外休日, 9 | 法定休日 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/EndingTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | import example.domain.type.time.Time; 4 | 5 | /** 6 | * 終業時刻 7 | */ 8 | public class EndingTime { 9 | Time time; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/MonthlyShift.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | import example.domain.type.date.Month; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 月間シフト 9 | */ 10 | public class MonthlyShift { 11 | Month month; 12 | List weeklyShifts; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/Shifts.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * シフト 7 | */ 8 | public class Shifts { 9 | List monthlyShifts; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/StartingTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | import example.domain.type.time.Time; 4 | 5 | /** 6 | * 始業時刻 7 | */ 8 | public class StartingTime { 9 | Time time; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/WeeklyShift.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | import example.domain.type.date.Week; 4 | 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | 8 | /** 9 | * *週間シフト 10 | */ 11 | public class WeeklyShift { 12 | Week week; 13 | List workingDays; 14 | 15 | public DayStatus dayStatus(LocalDate date) { 16 | // TODO: 指定の日の種別(労働日・休日)を判定して返却 17 | return DayStatus.労働日; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/WorkingDay.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | import java.time.LocalDate; 4 | 5 | /** 6 | * 勤務日 7 | */ 8 | public class WorkingDay { 9 | LocalDate date; 10 | StartingTime startingTime; 11 | EndingTime endingTime; 12 | BreakTime breakTime; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/WorkingHoursPerDayStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | /** 4 | * 1日の労働時間 5 | */ 6 | public enum WorkingHoursPerDayStatus { 7 | 労働時間6時間超, 8 | 労働時間6時間以内 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/WorkingTimeZone.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.shift; 2 | 3 | /** 4 | * 勤務時間帯 5 | */ 6 | public enum WorkingTimeZone { 7 | 日中, 8 | 深夜; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/shift/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 勤務日・勤務時間 3 | */ 4 | package example.domain.model.contract.shift; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/BaseHourlyWage.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.wage; 2 | 3 | import example.domain.model.legislation.ExtraPayRate; 4 | import example.domain.model.wage.HourlyWage; 5 | import example.domain.type.amount.Amount; 6 | import example.domain.type.amount.RoundingMode; 7 | 8 | import java.math.BigDecimal; 9 | 10 | /** 11 | * 基本の時給 12 | */ 13 | public class BaseHourlyWage { 14 | 15 | HourlyWage value; 16 | 17 | @Deprecated 18 | public BaseHourlyWage() { 19 | } 20 | 21 | public BaseHourlyWage(Integer value) { 22 | this(new HourlyWage(value)); 23 | } 24 | 25 | public BaseHourlyWage(String value) { 26 | this(new HourlyWage(new Amount(value))); 27 | } 28 | 29 | BaseHourlyWage(HourlyWage value) { 30 | 31 | this.value = value; 32 | } 33 | 34 | public static BaseHourlyWage disable() { 35 | return new BaseHourlyWage(HourlyWage.disable()); 36 | } 37 | 38 | public BigDecimal toBigDecimal() { 39 | return new BigDecimal(value.value().value()); 40 | } 41 | 42 | public HourlyWage value() { 43 | return value; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return value.toString(); 49 | } 50 | 51 | HourlyWage withExtraPayRate(ExtraPayRate extraPayRate) { 52 | return new HourlyWage(value.value().multiply(extraPayRate.value(), RoundingMode.切り捨て)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/LegalDaysOffExtraRate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.wage; 2 | 3 | import example.domain.model.legislation.ExtraPayRate; 4 | 5 | /** 6 | * 法定休日労働 時間外割増率 7 | */ 8 | public class LegalDaysOffExtraRate { 9 | ExtraPayRate value; 10 | 11 | @Deprecated 12 | public LegalDaysOffExtraRate() { 13 | } 14 | 15 | public LegalDaysOffExtraRate(Integer value) { 16 | this.value = new ExtraPayRate(value); 17 | } 18 | 19 | public static LegalDaysOffExtraRate regulation() { 20 | return new LegalDaysOffExtraRate(35); 21 | } 22 | 23 | public ExtraPayRate value() { 24 | return value; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/NightExtraRate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.wage; 2 | 3 | import example.domain.model.legislation.ExtraPayRate; 4 | 5 | /** 6 | * 深夜割増率 7 | */ 8 | public class NightExtraRate { 9 | ExtraPayRate value; 10 | 11 | @Deprecated 12 | public NightExtraRate() { 13 | } 14 | 15 | public NightExtraRate(Integer value) { 16 | this.value = new ExtraPayRate(value); 17 | } 18 | 19 | public static NightExtraRate regulation() { 20 | return new NightExtraRate(35); 21 | } 22 | 23 | public ExtraPayRate value() { 24 | return value; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/OverLegalMoreThan60HoursExtraRate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.wage; 2 | 3 | import example.domain.model.legislation.ExtraPayRate; 4 | 5 | /** 6 | * 法定時間外労働 月60時間超の時間外割増率 7 | */ 8 | public class OverLegalMoreThan60HoursExtraRate { 9 | ExtraPayRate value; 10 | 11 | @Deprecated 12 | public OverLegalMoreThan60HoursExtraRate() { 13 | } 14 | 15 | public OverLegalMoreThan60HoursExtraRate(Integer value) { 16 | this.value = new ExtraPayRate(value); 17 | } 18 | 19 | public static OverLegalMoreThan60HoursExtraRate regulation() { 20 | return new OverLegalMoreThan60HoursExtraRate(50); 21 | } 22 | 23 | public ExtraPayRate value() { 24 | return value; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/OverLegalWithin60HoursExtraRate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.wage; 2 | 3 | import example.domain.model.legislation.ExtraPayRate; 4 | 5 | /** 6 | * 法定時間外労働 月60時間以内の時間外割増率 7 | */ 8 | public class OverLegalWithin60HoursExtraRate { 9 | ExtraPayRate value; 10 | 11 | @Deprecated 12 | public OverLegalWithin60HoursExtraRate() { 13 | } 14 | 15 | public OverLegalWithin60HoursExtraRate(Integer value) { 16 | this.value = new ExtraPayRate(value); 17 | } 18 | 19 | public static OverLegalWithin60HoursExtraRate regulation() { 20 | return new OverLegalWithin60HoursExtraRate(25); 21 | } 22 | 23 | public ExtraPayRate value() { 24 | return value; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/OverTimeExtraRate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.wage; 2 | 3 | /** 4 | * 時間外及び休日、深夜の割増賃金 5 | */ 6 | public class OverTimeExtraRate { 7 | OverLegalWithin60HoursExtraRate overLegalWithin60HoursExtraRate; 8 | OverLegalMoreThan60HoursExtraRate overLegalMoreThan60HoursExtraRate; 9 | LegalDaysOffExtraRate legalDaysOffExtraRate; 10 | NightExtraRate nightExtraRate; 11 | 12 | @Deprecated 13 | public OverTimeExtraRate() { 14 | } 15 | 16 | public OverTimeExtraRate(OverLegalWithin60HoursExtraRate overLegalWithin60HoursExtraRate, OverLegalMoreThan60HoursExtraRate overLegalMoreThan60HoursExtraRate, LegalDaysOffExtraRate legalDaysOffExtraRate, NightExtraRate nightExtraRate) { 17 | this.overLegalWithin60HoursExtraRate = overLegalWithin60HoursExtraRate; 18 | this.overLegalMoreThan60HoursExtraRate = overLegalMoreThan60HoursExtraRate; 19 | this.legalDaysOffExtraRate = legalDaysOffExtraRate; 20 | this.nightExtraRate = nightExtraRate; 21 | } 22 | 23 | public static OverTimeExtraRate regulation() { 24 | return new OverTimeExtraRate(OverLegalWithin60HoursExtraRate.regulation(), OverLegalMoreThan60HoursExtraRate.regulation(), 25 | LegalDaysOffExtraRate.regulation(), NightExtraRate.regulation()); 26 | } 27 | 28 | public NightExtraRate nightExtraRate() { 29 | return nightExtraRate; 30 | } 31 | 32 | public OverLegalWithin60HoursExtraRate overLegalWithin60HoursExtraRate() { 33 | // TODO: 34 | return new OverLegalWithin60HoursExtraRate(0); 35 | //return overLegalWithin60HoursExtraRate; 36 | } 37 | 38 | public OverLegalMoreThan60HoursExtraRate overLegalMoreThan60HoursExtraRate() { 39 | // TODO: 40 | return new OverLegalMoreThan60HoursExtraRate(0); 41 | // return overLegalMoreThan60HoursExtraRate; 42 | } 43 | 44 | public LegalDaysOffExtraRate legalDaysOffExtraRate() { 45 | // TODO: 46 | return new LegalDaysOffExtraRate(0); 47 | // return legalDaysOffExtraRate; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/WageCondition.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.contract.wage; 2 | 3 | import example.domain.model.wage.*; 4 | 5 | /** 6 | * 給与条件 7 | */ 8 | public class WageCondition { 9 | BaseHourlyWage baseHourlyWage; 10 | OverTimeExtraRate overTimeExtraRate; 11 | 12 | @Deprecated 13 | public WageCondition() { 14 | } 15 | 16 | public WageCondition(BaseHourlyWage baseHourlyWage, OverTimeExtraRate overTimeExtraRate) { 17 | this.baseHourlyWage = baseHourlyWage; 18 | this.overTimeExtraRate = overTimeExtraRate; 19 | } 20 | 21 | public WageCondition(BaseHourlyWage baseHourlyWage) { 22 | this(baseHourlyWage, OverTimeExtraRate.regulation()); 23 | } 24 | 25 | public BaseHourlyWage baseHourlyWage() { 26 | return baseHourlyWage; 27 | } 28 | 29 | public NightHourlyExtraWage nightHourlyExtraWage() { 30 | return new NightHourlyExtraWage(baseHourlyWage.withExtraPayRate(overTimeExtraRate.nightExtraRate().value())); 31 | } 32 | 33 | public LegalDaysOffHourlyExtraWage legalDaysOffHourlyExtraWage() { 34 | return new LegalDaysOffHourlyExtraWage(baseHourlyWage.withExtraPayRate(overTimeExtraRate.legalDaysOffExtraRate().value())); 35 | } 36 | 37 | public OverLegalMoreThan60HoursHourlyExtraWage overLegalMoreThan60HoursHourlyExtraWage() { 38 | return new OverLegalMoreThan60HoursHourlyExtraWage(baseHourlyWage.withExtraPayRate(overTimeExtraRate.overLegalMoreThan60HoursExtraRate().value())); 39 | } 40 | 41 | public OverLegalWithin60HoursHourlyExtraWage overLegalWithin60HoursHourlyExtraWage() { 42 | return new OverLegalWithin60HoursHourlyExtraWage(baseHourlyWage.withExtraPayRate(overTimeExtraRate.overLegalWithin60HoursExtraRate().value())); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/contract/wage/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 賃金及び時間外賃金等について 3 | */ 4 | package example.domain.model.contract.wage; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/daysoff/DaysOff.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.daysoff; 2 | 3 | import example.domain.model.employee.EmployeeNumber; 4 | 5 | import java.time.LocalDate; 6 | 7 | /** 8 | * 休日 9 | */ 10 | public class DaysOff { 11 | EmployeeNumber employeeNumber; 12 | 13 | LocalDate date; 14 | 15 | public DaysOff(EmployeeNumber employeeNumber, LocalDate date) { 16 | this.employeeNumber = employeeNumber; 17 | this.date = date; 18 | } 19 | 20 | public EmployeeNumber employeeNumber() { 21 | return employeeNumber; 22 | } 23 | 24 | public LocalDate date() { 25 | return date; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/daysoff/DaysOffRecords.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.daysoff; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 休日一覧 7 | */ 8 | public class DaysOffRecords { 9 | List list; 10 | 11 | public DaysOffRecords(List list) { 12 | this.list = list; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/daysoff/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 休日 3 | */ 4 | package example.domain.model.daysoff; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/ContractingEmployees.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 契約中従業員一覧 7 | */ 8 | public class ContractingEmployees { 9 | List list; 10 | 11 | public ContractingEmployees(List list) { 12 | this.list = list; 13 | } 14 | 15 | public List list() { 16 | return list; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return list.toString(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/Employee.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | /** 4 | * 従業員 5 | */ 6 | public class Employee { 7 | EmployeeNumber employeeNumber; 8 | Name name; 9 | MailAddress mailAddress; 10 | PhoneNumber phoneNumber; 11 | 12 | @Deprecated 13 | public Employee() { 14 | this(new EmployeeNumber(), new Name(), new MailAddress(), new PhoneNumber()); 15 | } 16 | 17 | public Employee(EmployeeNumber employeeNumber, Name name, MailAddress mailAddress, PhoneNumber phoneNumber) { 18 | this.employeeNumber = employeeNumber; 19 | this.name = name; 20 | this.mailAddress = mailAddress; 21 | this.phoneNumber = phoneNumber; 22 | } 23 | 24 | public EmployeeNumber employeeNumber() { 25 | return employeeNumber; 26 | } 27 | 28 | public Name name() { 29 | return name; 30 | } 31 | 32 | public PhoneNumber phoneNumber() { 33 | return phoneNumber; 34 | } 35 | 36 | public MailAddress mailAddress() { 37 | return mailAddress; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "Employee{" + 43 | "employeeNumber=" + employeeNumber + 44 | ", name=" + name + 45 | ", phoneNumber=" + phoneNumber + 46 | ", mailAddress=" + mailAddress + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/EmployeeNumber.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | /** 4 | * 従業員番号 5 | */ 6 | public class EmployeeNumber { 7 | 8 | Integer value; 9 | 10 | public EmployeeNumber() { 11 | } 12 | 13 | public static EmployeeNumber from(String value) { 14 | return new EmployeeNumber(Integer.parseInt(value)); 15 | } 16 | 17 | public EmployeeNumber(Integer value) { 18 | this.value = value; 19 | } 20 | 21 | public Integer value() { 22 | return value; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return value.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/EmployeeToRegister.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | import javax.validation.Valid; 4 | import javax.validation.constraints.NotNull; 5 | 6 | /** 7 | * 従業員登録 8 | */ 9 | public class EmployeeToRegister { 10 | 11 | @NotNull 12 | @Valid 13 | Name name; 14 | 15 | @NotNull 16 | @Valid 17 | MailAddress mailAddress; 18 | 19 | @NotNull 20 | @Valid 21 | PhoneNumber phoneNumber; 22 | 23 | public EmployeeToRegister(Name name, MailAddress mailAddress, PhoneNumber phoneNumber) { 24 | this.name = name; 25 | this.mailAddress = mailAddress; 26 | this.phoneNumber = phoneNumber; 27 | } 28 | 29 | public static EmployeeToRegister blank() { 30 | return new EmployeeToRegister(new Name(), new MailAddress(), new PhoneNumber()); 31 | } 32 | 33 | public Name name() { 34 | return name; 35 | } 36 | 37 | public MailAddress mailAddress() { 38 | return mailAddress; 39 | } 40 | 41 | public PhoneNumber phoneNumber() { 42 | return phoneNumber; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/MailAddress.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | import javax.validation.constraints.Email; 4 | import javax.validation.constraints.NotBlank; 5 | 6 | /** 7 | * メールアドレス 8 | */ 9 | public class MailAddress { 10 | 11 | @NotBlank(message = "メールアドレスを入力してください") 12 | @Email(message = "メールアドレスが正しくありません。") 13 | String value = ""; 14 | 15 | public MailAddress() { 16 | } 17 | 18 | public MailAddress(String mailAddress) { 19 | value = mailAddress; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return value; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/MailAddressToChange.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | /** 4 | * メールアドレスの変更 5 | */ 6 | public class MailAddressToChange { 7 | 8 | EmployeeNumber employeeNumber; 9 | 10 | MailAddress mailAddress; 11 | 12 | public MailAddressToChange(EmployeeNumber employeeNumber, MailAddress mailAddress) { 13 | this.employeeNumber = employeeNumber; 14 | this.mailAddress = mailAddress; 15 | } 16 | 17 | public EmployeeNumber employeeNumber() { 18 | return employeeNumber; 19 | } 20 | 21 | public MailAddress mailAddress() { 22 | return mailAddress; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/Name.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | import javax.validation.constraints.NotBlank; 4 | import javax.validation.constraints.Size; 5 | 6 | /** 7 | * 氏名 8 | */ 9 | public class Name { 10 | 11 | final int 字数制限 = 40; 12 | @NotBlank(message = "名前を入力してください。") 13 | @Size(max = 字数制限, message = "{max}文字以内で入力してください。") 14 | String value = ""; 15 | 16 | public Name() { 17 | } 18 | 19 | public Name(String name) { 20 | value = name; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/NameToChange.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | /** 4 | * 氏名の変更 5 | */ 6 | public class NameToChange { 7 | 8 | EmployeeNumber employeeNumber; 9 | 10 | Name name; 11 | 12 | public NameToChange(EmployeeNumber employeeNumber, Name name) { 13 | this.employeeNumber = employeeNumber; 14 | this.name = name; 15 | } 16 | 17 | public EmployeeNumber employeeNumber() { 18 | return employeeNumber; 19 | } 20 | 21 | public Name name() { 22 | return name; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | 4 | import javax.validation.constraints.NotBlank; 5 | import javax.validation.constraints.Pattern; 6 | import javax.validation.constraints.Size; 7 | 8 | /** 9 | * 電話番号 10 | */ 11 | public class PhoneNumber { 12 | 13 | @NotBlank(message = "電話番号を入力してください") 14 | @Pattern(regexp = "([0-9]{2,4}-[0-9]{2,4}-[0-9]{2,4})?", message = "xx-xxxx-xxxxの形式で入力してください") 15 | @Size(min = 8, max = 13, message = "桁数は8桁以上13桁以下で入力してください") 16 | String value = ""; 17 | 18 | public PhoneNumber() { 19 | } 20 | 21 | public PhoneNumber(String phoneNumber) { 22 | value = phoneNumber; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/PhoneNumberToChange.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.employee; 2 | 3 | /** 4 | * 電話番号の変更 5 | */ 6 | public class PhoneNumberToChange { 7 | 8 | EmployeeNumber employeeNumber; 9 | 10 | PhoneNumber phoneNumber; 11 | 12 | public PhoneNumberToChange(EmployeeNumber employeeNumber, PhoneNumber phoneNumber) { 13 | this.employeeNumber = employeeNumber; 14 | this.phoneNumber = phoneNumber; 15 | } 16 | 17 | public EmployeeNumber employeeNumber() { 18 | return employeeNumber; 19 | } 20 | 21 | public PhoneNumber phoneNumber() { 22 | return phoneNumber; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/employee/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 従業員 3 | */ 4 | package example.domain.model.employee; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/legislation/DailyWorkingHoursLimit.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.legislation; 2 | 3 | import example.domain.type.time.Hour; 4 | import example.domain.type.time.Minute; 5 | 6 | /** 7 | * 1日の労働時間上限 8 | */ 9 | public class DailyWorkingHoursLimit { 10 | Hour value; 11 | 12 | DailyWorkingHoursLimit(Hour value) { 13 | this.value = value; 14 | } 15 | 16 | public static DailyWorkingHoursLimit legal() { 17 | // 労働基準法第32条 18 | return new DailyWorkingHoursLimit(new Hour(8)); 19 | } 20 | 21 | public Minute toMinute() { 22 | return value.toMinute(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/legislation/DaysOffStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.legislation; 2 | 3 | /** 4 | * 労働日/休日 5 | */ 6 | public enum DaysOffStatus { 7 | 休日, 8 | 労働日; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/legislation/ExtraPayRate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.legislation; 2 | 3 | import example.domain.type.amount.Percentage; 4 | 5 | /** 6 | * 割増率(%) 7 | */ 8 | public class ExtraPayRate { 9 | Percentage value; 10 | 11 | @Deprecated 12 | public ExtraPayRate() { 13 | } 14 | 15 | public ExtraPayRate(Integer value) { 16 | this.value = new Percentage(value); 17 | } 18 | 19 | public Percentage value() { 20 | return value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/legislation/Night.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.legislation; 2 | 3 | import example.domain.type.datetime.DateTime; 4 | import example.domain.type.datetime.QuarterRoundDateTime; 5 | import example.domain.type.time.Minute; 6 | import example.domain.type.time.Time; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * 深夜 12 | */ 13 | public class Night { 14 | 15 | Time nightStartTime; 16 | Time nightFinishTime; 17 | 18 | public Night(Time nightStartTime, Time nightFinishTime) { 19 | this.nightStartTime = nightStartTime; 20 | this.nightFinishTime = nightFinishTime; 21 | } 22 | 23 | public static Night legal() { 24 | // 第三十七条第四項で定められている深夜 25 | return new Night(new Time("22:00"), new Time("05:00")); 26 | } 27 | 28 | public Minute nightMinute(QuarterRoundDateTime startDateTime, QuarterRoundDateTime endDateTime) { 29 | Minute before = before(startDateTime, endDateTime); 30 | Minute after = after(startDateTime, endDateTime); 31 | return before.add(after); 32 | } 33 | 34 | private Minute before(QuarterRoundDateTime startDateTime, QuarterRoundDateTime endDateTime) { 35 | DateTime earlyMorningFinishDateTime = new DateTime(LocalDateTime.of(startDateTime.value().date(), nightFinishTime.value())); 36 | 37 | if (startDateTime.isBefore(earlyMorningFinishDateTime) 38 | && endDateTime.isAfter(earlyMorningFinishDateTime)) { 39 | return QuarterRoundDateTime.between(startDateTime, earlyMorningFinishDateTime); 40 | } 41 | 42 | if (startDateTime.isBefore(earlyMorningFinishDateTime) 43 | && endDateTime.isBefore(earlyMorningFinishDateTime)) { 44 | return QuarterRoundDateTime.between(startDateTime, endDateTime); 45 | } 46 | 47 | return new Minute(0); 48 | } 49 | 50 | private Minute after(QuarterRoundDateTime startDateTime, QuarterRoundDateTime endDateTime) { 51 | DateTime nightStartDateTime = new DateTime(LocalDateTime.of(startDateTime.value().date(), nightStartTime.value())); 52 | DateTime nightFinishDateTime = new DateTime(LocalDateTime.of(startDateTime.value().date().plusDays(1), nightFinishTime.value())); 53 | 54 | if (endDateTime.isAfter(nightStartDateTime) 55 | && endDateTime.isBefore(nightFinishDateTime) 56 | && startDateTime.isAfter(nightStartDateTime)) { 57 | return QuarterRoundDateTime.between(startDateTime, endDateTime); 58 | } 59 | 60 | if (endDateTime.isAfter(nightStartDateTime) 61 | && endDateTime.isBefore(nightFinishDateTime)) { 62 | return DateTime.between(nightStartDateTime, endDateTime); 63 | } 64 | 65 | if (endDateTime.isAfter(nightStartDateTime)) { 66 | return DateTime.between(nightStartDateTime, nightFinishDateTime); 67 | } 68 | 69 | return new Minute(0); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/legislation/WeeklyWorkingHoursLimit.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.legislation; 2 | 3 | import example.domain.type.time.Hour; 4 | import example.domain.type.time.Minute; 5 | 6 | /** 7 | * 1週間の労働時間上限 8 | */ 9 | public class WeeklyWorkingHoursLimit { 10 | Hour value; 11 | 12 | WeeklyWorkingHoursLimit(Hour value) { 13 | this.value = value; 14 | } 15 | 16 | public static WeeklyWorkingHoursLimit legal() { 17 | // 労働基準法第32条 18 | return new WeeklyWorkingHoursLimit(new Hour(40)); 19 | } 20 | 21 | public Minute toMinute() { 22 | return value.toMinute(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/legislation/WeeklyWorkingHoursStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.legislation; 2 | 3 | /** 4 | * 週の労働時間の状態 5 | */ 6 | public enum WeeklyWorkingHoursStatus { 7 | 法定時間内労働時間の累計が40時間以内, 8 | 法定時間内労働時間の累計が40時間を超えている 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/legislation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 関連法規 3 | */ 4 | package example.domain.model.legislation; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ビジネスロジックをモデルを表現する複合型 3 | */ 4 | package example.domain.model; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/payroll/PaymentAmount.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.payroll; 2 | 3 | import example.domain.model.wage.HourlyWage; 4 | import example.domain.type.amount.Amount; 5 | import example.domain.type.time.QuarterHour; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.Arrays; 9 | 10 | /** 11 | * 支払い金額 12 | */ 13 | public class PaymentAmount { 14 | 15 | Amount value; 16 | 17 | public PaymentAmount(BigDecimal value) { 18 | this(new Amount(value.intValue())); 19 | } 20 | 21 | public PaymentAmount(Amount value) { 22 | this.value = value; 23 | } 24 | 25 | public static PaymentAmount from(HourlyWage wage, QuarterHour workTime) { 26 | return new PaymentAmount(wage.multiply(workTime)); 27 | } 28 | 29 | PaymentAmount add(PaymentAmount paymentAmount) { 30 | return new PaymentAmount(this.value.add(paymentAmount.value)); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return value.toString(); 36 | } 37 | 38 | public PaymentAmount addAll(PaymentAmount... paymentAmounts) { 39 | Amount amount = value; 40 | Amount[] amounts = Arrays.stream(paymentAmounts).map(paymentAmount -> paymentAmount.value).toArray(Amount[]::new); 41 | return new PaymentAmount(amount.addAll(amounts)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/payroll/PayrollStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.payroll; 2 | 3 | import example.domain.model.contract.ContractStatus; 4 | 5 | /** 6 | * 給与ステータス 7 | */ 8 | public enum PayrollStatus { 9 | 有効("payroll.normal", ContractStatus.契約あり), 10 | 時給登録無し("payroll.payment-amount-unregistered", ContractStatus.契約なし), 11 | 稼働登録無し("payroll.not-working", ContractStatus.判定不能); 12 | 13 | String messageKey; 14 | ContractStatus contractStatus; 15 | 16 | PayrollStatus(String messageKey, ContractStatus contractStatus) { 17 | this.messageKey = messageKey; 18 | this.contractStatus = contractStatus; 19 | } 20 | 21 | public static PayrollStatus from(ContractStatus contractStatus) { 22 | for (PayrollStatus value : values()) { 23 | if (value.contractStatus == contractStatus) { 24 | return value; 25 | } 26 | } 27 | throw new IllegalStateException(contractStatus.toString()); 28 | } 29 | 30 | public String messageKey() { 31 | return messageKey; 32 | } 33 | 34 | public boolean available() { 35 | return this == 有効; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/payroll/Payrolls.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.payroll; 2 | 3 | import example.domain.model.attendance.WorkMonth; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 給与一覧 9 | */ 10 | public class Payrolls { 11 | WorkMonth workMonth; 12 | List list; 13 | 14 | public Payrolls(WorkMonth workMonth, List list) { 15 | this.workMonth = workMonth; 16 | this.list = list; 17 | } 18 | 19 | public WorkMonth workMonth() { 20 | return workMonth; 21 | } 22 | 23 | public List list() { 24 | return list; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/payroll/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 給与 3 | */ 4 | package example.domain.model.payroll; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/AttendDates.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import java.time.LocalDate; 4 | import java.util.List; 5 | 6 | import static java.util.stream.Collectors.toList; 7 | 8 | /** 9 | * 実勤務日付一覧 10 | */ 11 | public class AttendDates { 12 | List list; 13 | 14 | public AttendDates(List list) { 15 | this.list = list; 16 | } 17 | 18 | public List toDates() { 19 | return list.stream().map(WorkDate::toDate).collect(toList()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/BeforeMonthlyTimeRecord.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 前月の勤務実績 7 | */ 8 | public class BeforeMonthlyTimeRecord { 9 | TimeRecords value; 10 | 11 | public BeforeMonthlyTimeRecord(TimeRecords value) { 12 | this.value = value; 13 | } 14 | 15 | public List list() { 16 | return value.list(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/BindingTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.model.timerecord.timefact.WorkRange; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | /** 7 | * 拘束時間 8 | */ 9 | public class BindingTime { 10 | 11 | WorkRange workRange; 12 | 13 | public BindingTime(WorkRange workRange) { 14 | this.workRange = workRange; 15 | } 16 | 17 | public QuarterHour quarterHour() { 18 | return workRange.quarterHour(); 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return quarterHour().toString(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/BreakTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.HourAndMinute; 4 | import example.domain.type.time.Minute; 5 | 6 | /** 7 | * 休憩時間合計 8 | */ 9 | public class BreakTime { 10 | 11 | Minute value; 12 | 13 | public BreakTime(DaytimeBreakTime daytimeBreakTime, NightBreakTime nightBreakTime) { 14 | value = daytimeBreakTime.minute().add(nightBreakTime.minute()); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return HourAndMinute.from(value).toString(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/DailyWorkingHoursStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.model.legislation.DailyWorkingHoursLimit; 4 | 5 | /** 6 | * 1日の労働時間の状態 7 | */ 8 | public enum DailyWorkingHoursStatus { 9 | 一日8時間以下, 10 | 一日8時間を超えている; 11 | 12 | public static DailyWorkingHoursStatus from(WorkTime workTime) { 13 | if (workTime.quarterHour().moreThan(DailyWorkingHoursLimit.legal().toMinute())) { 14 | return DailyWorkingHoursStatus.一日8時間を超えている; 15 | } 16 | return DailyWorkingHoursStatus.一日8時間以下; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/DaytimeBindingTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | /** 6 | * 日中拘束時間 7 | */ 8 | public class DaytimeBindingTime { 9 | 10 | QuarterHour value; 11 | 12 | public DaytimeBindingTime(BindingTime bindingTime, NightBindingTime nightBindingTime) { 13 | this.value = bindingTime.quarterHour().subtract(nightBindingTime.quarterHour()); 14 | } 15 | 16 | public QuarterHour quarterHour() { 17 | return value; 18 | } 19 | 20 | DaytimeWorkTime subtract(DaytimeBreakTime daytimeBreakTime) { 21 | QuarterHour quarterHour = quarterHour().subtract(daytimeBreakTime.quarterHourRoundUp()); 22 | return new DaytimeWorkTime(quarterHour); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/DaytimeBreakTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.Minute; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | import javax.validation.Valid; 7 | 8 | /** 9 | * 日中休憩時間 10 | */ 11 | public class DaytimeBreakTime { 12 | 13 | @Valid 14 | Minute value; 15 | 16 | @Deprecated 17 | DaytimeBreakTime() { 18 | } 19 | 20 | public DaytimeBreakTime(Minute value) { 21 | this.value = value; 22 | } 23 | 24 | public static DaytimeBreakTime from(String value) { 25 | return new DaytimeBreakTime(Minute.from(value)); 26 | } 27 | 28 | public Minute minute() { 29 | return value; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return value.toString(); 35 | } 36 | 37 | public QuarterHour quarterHourRoundUp() { 38 | return value.quarterHourRoundUp(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/DaytimeWorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.Minute; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | /** 7 | * 日中勤務時間 8 | */ 9 | public class DaytimeWorkTime { 10 | 11 | QuarterHour value; 12 | 13 | public DaytimeWorkTime(QuarterHour value) { 14 | this.value = value; 15 | } 16 | 17 | public DaytimeWorkTime(Integer value) { 18 | this(new QuarterHour(new Minute(value))); 19 | } 20 | 21 | public QuarterHour quarterHour() { 22 | return value; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return value.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/EndTimeValidResult.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | 4 | /** 5 | * 勤務終了時刻 検証エラー 6 | */ 7 | public enum EndTimeValidResult { 8 | 正常(""), 9 | 翌日の勤務時刻と重複("翌日の勤務時刻と重複しています"); 10 | 11 | String message; 12 | 13 | EndTimeValidResult(String message) { 14 | this.message = message; 15 | } 16 | 17 | public String message() { 18 | return message; 19 | } 20 | 21 | public boolean hasError() { 22 | return this != 正常; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/LegalDaysOffWorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | /** 6 | * 法定休日勤務時間 7 | */ 8 | public class LegalDaysOffWorkTime { 9 | 10 | QuarterHour value; 11 | 12 | public LegalDaysOffWorkTime(QuarterHour value) { 13 | this.value = value; 14 | } 15 | 16 | public QuarterHour quarterHour() { 17 | return value; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return value.toString(); 23 | } 24 | 25 | LegalDaysOffWorkTime add(LegalDaysOffWorkTime value) { 26 | return new LegalDaysOffWorkTime(this.value.add(value.value)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/MonthlyOverLegalHoursStatus.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | /** 4 | * 月の法定時間外労働の状態 5 | */ 6 | public enum MonthlyOverLegalHoursStatus { 7 | 月60時間以内, 8 | 月60時間超; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/MonthlyTimeRecord.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 月の勤務実績 7 | */ 8 | public class MonthlyTimeRecord { 9 | TimeRecords value; 10 | 11 | public MonthlyTimeRecord(TimeRecords value) { 12 | this.value = value; 13 | } 14 | 15 | public List list() { 16 | return value.list(); 17 | } 18 | 19 | public MonthlyOverLegalHoursStatus monthlyWorkingHoursStatus(WorkDate workDate) { 20 | // TODO: 21 | return MonthlyOverLegalHoursStatus.月60時間以内; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/NightBindingTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.model.legislation.Night; 4 | import example.domain.model.timerecord.timefact.WorkRange; 5 | import example.domain.type.time.QuarterHour; 6 | 7 | /** 8 | * 深夜拘束時間 9 | */ 10 | public class NightBindingTime { 11 | 12 | QuarterHour value; 13 | 14 | public NightBindingTime(QuarterHour value) { 15 | this.value = value; 16 | } 17 | 18 | public NightBindingTime(WorkRange workRange) { 19 | this(workRange, Night.legal()); 20 | } 21 | 22 | public NightBindingTime(WorkRange workRange, Night night) { 23 | this(new QuarterHour(night.nightMinute(workRange.start().normalized(), workRange.end().normalized()))); 24 | } 25 | 26 | public QuarterHour quarterHour() { 27 | return value; 28 | } 29 | 30 | public NightWorkTime subtract(NightBreakTime nightBreakTime) { 31 | QuarterHour quarterHour = quarterHour().subtract(nightBreakTime.quarterHourRoundUp()); 32 | return new NightWorkTime(quarterHour); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/NightBreakTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.Minute; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | import javax.validation.Valid; 7 | 8 | /** 9 | * 休憩時間(深夜) 10 | */ 11 | public class NightBreakTime { 12 | 13 | @Valid 14 | Minute value; 15 | 16 | @Deprecated 17 | NightBreakTime() { 18 | } 19 | 20 | public NightBreakTime(Minute value) { 21 | this.value = value; 22 | } 23 | 24 | public static NightBreakTime from(String value) { 25 | return new NightBreakTime(Minute.from(value)); 26 | } 27 | 28 | public Minute minute() { 29 | return value; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return value.toString(); 35 | } 36 | 37 | public QuarterHour quarterHourRoundUp() { 38 | return value.quarterHourRoundUp(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/NightWorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.Minute; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | /** 7 | * 深夜勤務時間 8 | */ 9 | public class NightWorkTime { 10 | 11 | QuarterHour value; 12 | 13 | public NightWorkTime(QuarterHour value) { 14 | this.value = value; 15 | } 16 | 17 | public NightWorkTime(Integer value) { 18 | this(new QuarterHour(new Minute(value))); 19 | } 20 | 21 | public QuarterHour quarterHour() { 22 | return value; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return value.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/OverLegalMoreThan60HoursWorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | /** 6 | * 法定時間外労働 月60時間超 勤務時間 7 | */ 8 | public class OverLegalMoreThan60HoursWorkTime { 9 | 10 | QuarterHour value; 11 | 12 | public OverLegalMoreThan60HoursWorkTime(QuarterHour value) { 13 | this.value = value; 14 | } 15 | 16 | public QuarterHour quarterHour() { 17 | return value; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return value.toString(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/OverLegalWithin60HoursWorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | /** 6 | * 法定時間外労働 月60時間以内 労働時間 7 | */ 8 | public class OverLegalWithin60HoursWorkTime { 9 | 10 | QuarterHour value; 11 | 12 | public OverLegalWithin60HoursWorkTime(QuarterHour value) { 13 | this.value = value; 14 | } 15 | 16 | public QuarterHour quarterHour() { 17 | return value; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return value.toString(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/Recorded.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | /** 4 | * 勤務記録有無 5 | */ 6 | public enum Recorded { 7 | 記録あり, 8 | 記録なし 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/StartTimeValidResult.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | 4 | /** 5 | * 勤務開始時刻 検証エラー 6 | */ 7 | public enum StartTimeValidResult { 8 | 正常(""), 9 | 前日の勤務時刻と重複( "前日の勤務時刻と重複しています"); 10 | 11 | String message; 12 | 13 | StartTimeValidResult(String message) { 14 | this.message = message; 15 | } 16 | 17 | public String message() { 18 | return message; 19 | } 20 | 21 | public boolean hasError() { 22 | return this != 正常; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/TimeRecord.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.model.employee.EmployeeNumber; 4 | import example.domain.model.legislation.DaysOffStatus; 5 | import example.domain.type.time.QuarterHour; 6 | 7 | import javax.validation.Valid; 8 | 9 | /** 10 | * *勤務実績 11 | */ 12 | public class TimeRecord { 13 | 14 | EmployeeNumber employeeNumber; 15 | 16 | @Valid 17 | ActualWorkDateTime actualWorkDateTime; 18 | 19 | // FIXME: 休日はシフトを元に判断するように変更する。 20 | DaysOffStatus daysOffStatus; 21 | 22 | @Deprecated 23 | TimeRecord() { 24 | } 25 | 26 | public TimeRecord(EmployeeNumber employeeNumber, ActualWorkDateTime actualWorkDateTime, DaysOffStatus daysOffStatus) { 27 | this.employeeNumber = employeeNumber; 28 | this.actualWorkDateTime = actualWorkDateTime; 29 | this.daysOffStatus = daysOffStatus; 30 | } 31 | 32 | public TimeRecord(EmployeeNumber employeeNumber, ActualWorkDateTime actualWorkDateTime) { 33 | this(employeeNumber, actualWorkDateTime, DaysOffStatus.労働日); 34 | } 35 | 36 | public WorkDate workDate() { 37 | return actualWorkDateTime.workDate(); 38 | } 39 | 40 | public ActualWorkDateTime actualWorkDateTime() { 41 | return actualWorkDateTime; 42 | } 43 | 44 | public EmployeeNumber employeeNumber() { 45 | return employeeNumber; 46 | } 47 | 48 | public boolean isWorkedAt(WorkDate other) { 49 | return this.actualWorkDateTime.workDate().hasSameValue(other); 50 | } 51 | 52 | public boolean isOverlap(TimeRecord other) { 53 | return this.actualWorkDateTime.workRange.isOverlap(other.actualWorkDateTime.workRange); 54 | } 55 | 56 | public DaysOffStatus daysOffStatus() { 57 | return daysOffStatus; 58 | } 59 | 60 | public QuarterHour withinDailyLimitWorkTime() { 61 | return actualWorkDateTime.workTime().withinDailyLimitWorkTime(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/TimeRecordValidResult.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | public class TimeRecordValidResult { 4 | StartTimeValidResult startTimeValidResult; 5 | EndTimeValidResult endTimeValidResult; 6 | 7 | public TimeRecordValidResult(StartTimeValidResult startTimeValidResult, EndTimeValidResult endTimeValidResult) { 8 | this.startTimeValidResult = startTimeValidResult; 9 | this.endTimeValidResult = endTimeValidResult; 10 | } 11 | 12 | public StartTimeValidResult startTimeValidResult() { 13 | return startTimeValidResult; 14 | } 15 | 16 | public EndTimeValidResult endTimeValidResult() { 17 | return endTimeValidResult; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/TimeRecords.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | import java.util.List; 6 | 7 | import static java.util.stream.Collectors.toList; 8 | 9 | /** 10 | * 勤務実績一覧 11 | */ 12 | public class TimeRecords { 13 | List list; 14 | 15 | public TimeRecords(List list) { 16 | this.list = list; 17 | } 18 | 19 | public List list() { 20 | return list; 21 | } 22 | 23 | public TimeRecord at(WorkDate day) { 24 | return list.stream() 25 | .filter(worked -> worked.isWorkedAt(day)) 26 | .findFirst() 27 | .orElseThrow(() -> new IllegalStateException(day.toString())); 28 | } 29 | 30 | public Recorded recordedAt(WorkDate workDate) { 31 | return list.stream().anyMatch(timeRecord -> timeRecord.isWorkedAt(workDate)) ? Recorded.記録あり : Recorded.記録なし; 32 | } 33 | 34 | public AttendDates attendDates() { 35 | return new AttendDates(list.stream().map(TimeRecord::workDate).collect(toList())); 36 | } 37 | 38 | public TimeRecords recordsToDate(WorkDate workDate) { 39 | return new TimeRecords(list.stream().filter(record -> record.workDate().isBefore(workDate) || record.workDate().hasSameValue(workDate)).collect(toList())); 40 | } 41 | 42 | public TimeRecords recordsDayBefore(WorkDate workDate) { 43 | return new TimeRecords(list.stream().filter(record -> record.workDate().isBefore(workDate)).collect(toList())); 44 | } 45 | 46 | public WorkTimes workTimes() { 47 | return new WorkTimes(list().stream() 48 | .map(timeRecord -> timeRecord.actualWorkDateTime.workTime()).collect(toList())); 49 | } 50 | 51 | public QuarterHour withinDailyLimitWorkTimeTotal() { 52 | QuarterHour total = new QuarterHour(); 53 | for (TimeRecord timeRecord : list) { 54 | QuarterHour workTime = timeRecord.withinDailyLimitWorkTime(); 55 | total = total.add(workTime); 56 | } 57 | 58 | return total; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/WeeklyTimeRecord.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.model.legislation.DaysOffStatus; 4 | import example.domain.model.legislation.WeeklyWorkingHoursLimit; 5 | import example.domain.model.legislation.WeeklyWorkingHoursStatus; 6 | 7 | import java.time.LocalDate; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | import static java.util.stream.Collectors.toList; 15 | 16 | /** 17 | * 週の勤務実績 18 | */ 19 | public class WeeklyTimeRecord { 20 | TimeRecords value; 21 | 22 | public WeeklyTimeRecord(TimeRecords value) { 23 | this.value = value; 24 | } 25 | 26 | public static WeeklyTimeRecord from(MonthlyTimeRecord monthlyRecords, BeforeMonthlyTimeRecord beforeMonthlyRecords, WorkDate workDate) { 27 | List list = Stream.concat( 28 | beforeMonthlyRecords.list().stream(), 29 | monthlyRecords.list().stream()).collect(Collectors.toList()); 30 | return new WeeklyTimeRecord(new TimeRecords(list.stream().filter(record -> record.workDate().sameWeek(workDate.value)).collect(toList()))); 31 | } 32 | 33 | public WeeklyWorkingHoursStatus weeklyWorkingHoursStatus(WorkDate workDate) { 34 | if (recordsToDate(workDate).value.withinDailyLimitWorkTimeTotal().moreThan(WeeklyWorkingHoursLimit.legal().toMinute())) { 35 | return WeeklyWorkingHoursStatus.法定時間内労働時間の累計が40時間を超えている; 36 | } else { 37 | return WeeklyWorkingHoursStatus.法定時間内労働時間の累計が40時間以内; 38 | } 39 | } 40 | 41 | WeeklyTimeRecord recordsToDate(WorkDate workDate) { 42 | return new WeeklyTimeRecord(value.recordsToDate(workDate)); 43 | } 44 | 45 | public Optional lastDayOff() { 46 | return value.list.stream() 47 | .filter(record -> record.daysOffStatus == DaysOffStatus.休日) 48 | .max(Comparator.comparing(r -> r.workDate().toDate())); 49 | } 50 | 51 | public WorkTimes workTimes() { 52 | return value.workTimes(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/WithinLegalHoursWorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | /** 6 | * 法定時間内労働 労働時間 7 | */ 8 | public class WithinLegalHoursWorkTime { 9 | QuarterHour value; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/WorkDate.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.date.DayOfWeek; 4 | import example.domain.validation.Required; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | 7 | import javax.validation.Valid; 8 | import javax.validation.constraints.NotNull; 9 | import java.time.LocalDate; 10 | import java.time.format.DateTimeFormatter; 11 | import java.time.temporal.WeekFields; 12 | import java.util.Locale; 13 | 14 | /** 15 | * 勤務日付 16 | */ 17 | public class WorkDate { 18 | 19 | @Valid 20 | @NotNull(message = "勤務日を入力してください", groups = Required.class) 21 | @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) 22 | LocalDate value; 23 | 24 | @Deprecated 25 | public WorkDate() { 26 | } 27 | 28 | public WorkDate(LocalDate date) { 29 | value = date; 30 | } 31 | 32 | public static WorkDate from(String value) { 33 | return new WorkDate(LocalDate.parse(value, DateTimeFormatter.ISO_DATE)); 34 | } 35 | 36 | public LocalDate value() { 37 | return value; 38 | } 39 | 40 | public int dayOfMonth() { 41 | return value.getDayOfMonth(); 42 | } 43 | 44 | public DayOfWeek dayOfWeek() { 45 | return DayOfWeek.of(value.getDayOfWeek()); 46 | } 47 | 48 | public boolean hasSameValue(WorkDate other) { 49 | return value.equals(other.value); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return value.toString(); 55 | } 56 | 57 | public LocalDate toDate() { 58 | return value; 59 | } 60 | 61 | public boolean isBefore(WorkDate workDate) { 62 | return value.isBefore(workDate.value); 63 | } 64 | 65 | public boolean sameWeek(LocalDate date) { 66 | return weekBasedYear(value()) == weekBasedYear(date) && weekOfWeekBasedYear(value()) == weekOfWeekBasedYear(date); 67 | } 68 | 69 | int weekBasedYear(LocalDate date) { 70 | return date.get(WeekFields.of(Locale.JAPANESE).weekBasedYear()); 71 | } 72 | 73 | int weekOfWeekBasedYear(LocalDate date) { 74 | return date.get(WeekFields.of(Locale.JAPANESE).weekOfWeekBasedYear()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/WorkTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.model.legislation.DailyWorkingHoursLimit; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | /** 7 | * 勤務時間 8 | */ 9 | public class WorkTime { 10 | 11 | QuarterHour value; 12 | 13 | public WorkTime(DaytimeWorkTime daytimeWorkTime, NightWorkTime nightWorkTime) { 14 | this.value = daytimeWorkTime.quarterHour().add(nightWorkTime.quarterHour()); 15 | } 16 | 17 | public QuarterHour quarterHour() { 18 | return value; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return value.toString(); 24 | } 25 | 26 | QuarterHour overDailyLimitWorkTime() { 27 | return value.overMinute(DailyWorkingHoursLimit.legal().toMinute()); 28 | } 29 | 30 | public DailyWorkingHoursStatus dailyWorkingHoursStatus() { 31 | return DailyWorkingHoursStatus.from(this); 32 | } 33 | 34 | QuarterHour withinDailyLimitWorkTime() { 35 | if (value.moreThan(DailyWorkingHoursLimit.legal().toMinute())) { 36 | return new QuarterHour(DailyWorkingHoursLimit.legal().toMinute()); 37 | } 38 | 39 | return value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/WorkTimes.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.type.time.Minute; 4 | import example.domain.type.time.QuarterHour; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 勤務時間のリスト 10 | */ 11 | public class WorkTimes { 12 | List list; 13 | 14 | public WorkTimes(List list) { 15 | this.list = list; 16 | } 17 | 18 | public QuarterHour total() { 19 | QuarterHour total = new QuarterHour(new Minute(0)); 20 | for (WorkTime workTime : list) { 21 | total = total.add(workTime.value); 22 | } 23 | return total; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/evaluation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 評価する時間 3 | */ 4 | package example.domain.model.timerecord.evaluation; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 勤務単位の記録 3 | * 4 | * 出勤から退勤までの時間を扱う。 5 | */ 6 | package example.domain.model.timerecord; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/timefact/EndDateTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.timefact; 2 | 3 | import example.domain.type.datetime.DateTime; 4 | import example.domain.type.datetime.QuarterRoundDateTime; 5 | 6 | import java.time.Duration; 7 | import java.time.LocalTime; 8 | import java.time.Period; 9 | 10 | /** 11 | * 勤務終了日時 12 | */ 13 | public class EndDateTime { 14 | 15 | DateTime value; 16 | 17 | @Deprecated 18 | EndDateTime() { 19 | } 20 | 21 | public EndDateTime(DateTime value) { 22 | this.value = value; 23 | } 24 | 25 | public boolean isAfter(StartDateTime startDateTime) { 26 | return value.isAfter(startDateTime.value); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return value.toString(); 32 | } 33 | 34 | public String endTimeTextWith(StartDateTime startDateTime) { 35 | Period period = startDateTime.value.date().until(value.date()); 36 | return clockTimeTextOverDays(period.getDays()); 37 | } 38 | 39 | String clockTimeTextOverDays(int days) { 40 | LocalTime value = this.value.time().value(); 41 | Duration duration = Duration.ofDays(days) 42 | .plusHours(value.getHour()) 43 | .plusMinutes(value.getMinute()); 44 | 45 | return String.format("%02d:%02d", 46 | duration.toHours(), 47 | // duration.toMinutesPart() はJava9から 48 | duration.toMinutes() % 60 49 | ); 50 | } 51 | 52 | public QuarterRoundDateTime normalized() { 53 | return value.quarterRoundDown(); 54 | } 55 | 56 | public boolean isAfter(DateTime other) { 57 | return value.isAfter(other); 58 | } 59 | 60 | public boolean isBefore(DateTime other) { 61 | return value.isBefore(other); 62 | } 63 | 64 | boolean isBefore(StartDateTime other) { 65 | return value.isBefore(other.value); 66 | } 67 | 68 | boolean isBefore(EndDateTime other) { 69 | return value.isBefore(other.value); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/timefact/StartDateTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.timefact; 2 | 3 | import example.domain.type.datetime.DateTime; 4 | import example.domain.type.datetime.QuarterRoundDateTime; 5 | import example.domain.type.time.Time; 6 | 7 | import java.time.LocalDate; 8 | 9 | /** 10 | * 勤務開始日時 11 | */ 12 | public class StartDateTime { 13 | 14 | DateTime value; 15 | 16 | @Deprecated 17 | StartDateTime() { 18 | } 19 | 20 | public StartDateTime(DateTime value) { 21 | this.value = value; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return value.toString(); 27 | } 28 | 29 | public QuarterRoundDateTime normalized() { 30 | return value.quarterRoundUp(); 31 | } 32 | 33 | public LocalDate date() { 34 | return value.date(); 35 | } 36 | 37 | public Time time() { 38 | return value.time(); 39 | } 40 | 41 | public boolean isAfter(DateTime other) { 42 | return value.isAfter(other); 43 | } 44 | 45 | public boolean isBefore(DateTime other) { 46 | return value.isBefore(other); 47 | } 48 | 49 | boolean isAfter(StartDateTime other) { 50 | return value.isAfter(other.value); 51 | } 52 | 53 | boolean isAfter(EndDateTime other) { 54 | return value.isAfter(other.value); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/timefact/WorkRange.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.timefact; 2 | 3 | import example.domain.validation.BusinessLogic; 4 | import example.domain.type.datetime.QuarterRoundDateTime; 5 | import example.domain.type.time.Minute; 6 | import example.domain.type.time.QuarterHour; 7 | 8 | import javax.validation.constraints.AssertTrue; 9 | 10 | /** 11 | * 勤務の開始と終了 12 | */ 13 | public class WorkRange { 14 | 15 | StartDateTime startDateTime; 16 | EndDateTime endDateTime; 17 | 18 | boolean workTimeValid; 19 | 20 | @Deprecated 21 | WorkRange() { 22 | } 23 | 24 | public WorkRange(StartDateTime startDateTime, EndDateTime endDateTime) { 25 | this.startDateTime = startDateTime; 26 | this.endDateTime = endDateTime; 27 | } 28 | 29 | public QuarterHour quarterHour() { 30 | Minute duration = QuarterRoundDateTime.between(startDateTime.normalized(), endDateTime.normalized()); 31 | return new QuarterHour(duration); 32 | } 33 | 34 | public StartDateTime start() { 35 | return startDateTime; 36 | } 37 | 38 | public EndDateTime end() { 39 | return endDateTime; 40 | } 41 | 42 | public String endTimeText() { 43 | return endDateTime.endTimeTextWith(startDateTime); 44 | } 45 | 46 | public boolean isOverlap(WorkRange other) { 47 | return !notOverlap(other); 48 | } 49 | 50 | boolean notOverlap(WorkRange other) { 51 | return (startDateTime.isAfter(other.startDateTime) && startDateTime.isAfter(other.endDateTime)) 52 | || (endDateTime.isBefore(other.startDateTime) && endDateTime.isBefore(other.endDateTime)); 53 | } 54 | 55 | @AssertTrue(message = "終了時刻には開始時刻よりあとの時刻を入力してください", groups = BusinessLogic.class) 56 | public boolean isWorkTimeValid() { 57 | return endDateTime.isAfter(startDateTime); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/timerecord/timefact/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 記録された時間 3 | */ 4 | package example.domain.model.timerecord.timefact; -------------------------------------------------------------------------------- /src/main/java/example/domain/model/wage/HourlyWage.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.wage; 2 | 3 | import example.domain.type.amount.Amount; 4 | import example.domain.type.amount.RoundingMode; 5 | import example.domain.type.time.QuarterHour; 6 | 7 | /** 8 | * 時給 9 | */ 10 | public class HourlyWage { 11 | Amount value; 12 | 13 | @Deprecated 14 | public HourlyWage() { 15 | } 16 | 17 | public HourlyWage(Amount value) { 18 | this.value = value; 19 | } 20 | 21 | public HourlyWage(Integer value) { 22 | this(new Amount(value)); 23 | } 24 | 25 | public static HourlyWage disable() { 26 | return new HourlyWage(Integer.MIN_VALUE); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | if (value.value() == disable().value().value()) { 32 | return ""; 33 | } 34 | return value.toString(); 35 | } 36 | 37 | public Amount value() { 38 | return value; 39 | } 40 | 41 | public Amount multiply(QuarterHour time) { 42 | return value.multiply(time, RoundingMode.四捨五入); // TODO: 端数処理の仕方は就業規則で決める 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/wage/LegalDaysOffHourlyExtraWage.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.wage; 2 | 3 | /** 4 | * 法定休日労働 時給割増額 5 | */ 6 | public class LegalDaysOffHourlyExtraWage { 7 | HourlyWage value; 8 | 9 | public LegalDaysOffHourlyExtraWage(HourlyWage value) { 10 | this.value = value; 11 | } 12 | 13 | public HourlyWage value() { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/wage/NightHourlyExtraWage.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.wage; 2 | 3 | /** 4 | * 深夜 時給割増額 5 | */ 6 | public class NightHourlyExtraWage { 7 | HourlyWage value; 8 | 9 | public NightHourlyExtraWage(HourlyWage value) { 10 | this.value = value; 11 | } 12 | 13 | public HourlyWage value() { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/wage/OverLegalMoreThan60HoursHourlyExtraWage.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.wage; 2 | 3 | /** 4 | * 法定時間外労働 月60時間超 時給割増額 5 | */ 6 | public class OverLegalMoreThan60HoursHourlyExtraWage { 7 | HourlyWage value; 8 | 9 | public OverLegalMoreThan60HoursHourlyExtraWage(HourlyWage value) { 10 | this.value = value; 11 | } 12 | 13 | public HourlyWage value() { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/wage/OverLegalWithin60HoursHourlyExtraWage.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.wage; 2 | 3 | /** 4 | * 法定時間外労働 月60時間以内 時給割増額 5 | */ 6 | public class OverLegalWithin60HoursHourlyExtraWage { 7 | HourlyWage value; 8 | 9 | public OverLegalWithin60HoursHourlyExtraWage(HourlyWage value) { 10 | this.value = value; 11 | } 12 | 13 | public HourlyWage value() { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/domain/model/wage/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 賃金 3 | */ 4 | package example.domain.model.wage; -------------------------------------------------------------------------------- /src/main/java/example/domain/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ドメイン層 3 | * 業務の知識、利用者の関心事を記述する 4 | * type : 基本型 5 | * model : モデルを表現する複合型 6 | */ 7 | package example.domain; 8 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/amount/Amount.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.amount; 2 | 3 | import example.domain.type.time.QuarterHour; 4 | 5 | import java.math.BigDecimal; 6 | import java.text.DecimalFormat; 7 | import java.text.ParseException; 8 | 9 | /** 10 | * 金額 11 | */ 12 | public class Amount { 13 | 14 | int value; 15 | 16 | @Deprecated 17 | public Amount() { 18 | } 19 | 20 | public Amount(int value) { 21 | this.value = value; 22 | } 23 | 24 | public Amount(String value) { 25 | try { 26 | DecimalFormat decimalFormat = new DecimalFormat("#,##0"); 27 | Number number = decimalFormat.parse(value); 28 | this.value = number.intValue(); 29 | } catch (ParseException e) { 30 | throw new NumberFormatException(value); 31 | } 32 | } 33 | 34 | public Amount add(Amount other) { 35 | return new Amount(value + other.value); 36 | } 37 | 38 | public Amount multiply(Percentage rate, RoundingMode roundingMode) { 39 | return multiply(rate.rate(), roundingMode); 40 | } 41 | 42 | public Amount multiply(QuarterHour time, RoundingMode roundingMode) { 43 | return multiply(time.bigDecimalValue(), roundingMode); 44 | } 45 | 46 | Amount multiply(BigDecimal other, RoundingMode roundingMode) { 47 | return new Amount(new BigDecimal(value()).multiply(other).setScale(0, roundingMode.value).intValue()); 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return new DecimalFormat("#,##0'円'").format(value); 53 | } 54 | 55 | public int value() { 56 | return value; 57 | } 58 | 59 | public Amount addAll(Amount... amounts) { 60 | int total = value; 61 | for (Amount amount : amounts) { 62 | total = total + amount.value; 63 | } 64 | return new Amount(total); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/amount/Percentage.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.amount; 2 | 3 | import java.math.BigDecimal; 4 | 5 | /** 6 | * 率(割増や税などの金額に掛けられるもの) 7 | */ 8 | public class Percentage { 9 | 10 | Integer value; 11 | 12 | @Deprecated 13 | public Percentage() { 14 | } 15 | 16 | public Percentage(Integer value) { 17 | this.value = value; 18 | } 19 | 20 | public BigDecimal rate() { 21 | return BigDecimal.valueOf(value).divide(BigDecimal.valueOf(100), 2, java.math.RoundingMode.UNNECESSARY); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/amount/RoundingMode.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.amount; 2 | 3 | /** 4 | * 端数処理 5 | */ 6 | public enum RoundingMode { 7 | 切り上げ(java.math.RoundingMode.UP), 8 | 切り捨て(java.math.RoundingMode.DOWN), 9 | 四捨五入(java.math.RoundingMode.HALF_UP); 10 | 11 | java.math.RoundingMode value; 12 | 13 | RoundingMode(java.math.RoundingMode value) { 14 | this.value = value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/amount/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 金額 3 | */ 4 | package example.domain.type.amount; -------------------------------------------------------------------------------- /src/main/java/example/domain/type/date/DayOfWeek.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.date; 2 | 3 | /** 4 | * 曜日 5 | */ 6 | public enum DayOfWeek { 7 | 日(java.time.DayOfWeek.SUNDAY), 8 | 月(java.time.DayOfWeek.MONDAY), 9 | 火(java.time.DayOfWeek.TUESDAY), 10 | 水(java.time.DayOfWeek.WEDNESDAY), 11 | 木(java.time.DayOfWeek.THURSDAY), 12 | 金(java.time.DayOfWeek.FRIDAY), 13 | 土(java.time.DayOfWeek.SATURDAY); 14 | 15 | private final java.time.DayOfWeek dayOfWeek; 16 | 17 | DayOfWeek(java.time.DayOfWeek dayOfWeek) { 18 | this.dayOfWeek = dayOfWeek; 19 | } 20 | 21 | public static DayOfWeek of(java.time.DayOfWeek dayOfWeek) { 22 | for (DayOfWeek value : values()) { 23 | if (value.dayOfWeek == dayOfWeek) { 24 | return value; 25 | } 26 | } 27 | throw new IllegalArgumentException(dayOfWeek.toString()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/date/Month.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.date; 2 | 3 | import java.util.stream.Stream; 4 | 5 | /** 6 | * 月 7 | */ 8 | public enum Month { 9 | JANUARY(1), 10 | FEBRUARY(2), 11 | MARCH(3), 12 | APRIL(4), 13 | MAY(5), 14 | JUNE(6), 15 | JULY(7), 16 | AUGUST(8), 17 | SEPTEMBER(9), 18 | OCTOBER(10), 19 | NOVEMBER(11), 20 | DECEMBER(12); 21 | 22 | Integer month; 23 | 24 | private Month(int month) { 25 | this.month = month; 26 | } 27 | 28 | public Integer value() { 29 | return month; 30 | } 31 | 32 | public String toString() { 33 | return String.format("%2d", value()); 34 | } 35 | 36 | public static Month of(Integer month) { 37 | return Stream.of(values()).filter(m -> m.month.equals(month)).findFirst().orElseThrow(() -> new IllegalArgumentException()); 38 | } 39 | 40 | public static Month of(String month) { 41 | return of(Integer.parseInt(month)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/date/Week.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.date; 2 | 3 | import java.time.LocalDate; 4 | import java.util.List; 5 | 6 | /** 7 | * 週 8 | */ 9 | public class Week { 10 | List dates; 11 | 12 | public Week(List dates) { 13 | this.dates = dates; 14 | } 15 | 16 | public static Week from(List list) { 17 | return new Week(list); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/date/Year.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.date; 2 | 3 | /** 4 | * 年 5 | */ 6 | public class Year { 7 | Integer year; 8 | 9 | public Year(Integer year) { 10 | this.year = year; 11 | } 12 | 13 | public Year(String year) { 14 | this(Integer.parseInt(year)); 15 | } 16 | 17 | public Integer value() { 18 | return year; 19 | } 20 | 21 | public String toString() { 22 | return year.toString(); 23 | } 24 | 25 | public boolean sameValue(Year other) { 26 | return year.equals(other.year); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/date/YearMonth.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.date; 2 | 3 | import java.time.LocalDate; 4 | import java.time.format.DateTimeFormatter; 5 | import java.time.format.DateTimeParseException; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.IntStream; 9 | 10 | /** 11 | * 年月 12 | */ 13 | public class YearMonth { 14 | private final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM"); 15 | java.time.YearMonth value; 16 | 17 | public YearMonth() { 18 | value = java.time.YearMonth.now(); 19 | } 20 | 21 | public YearMonth(Year year, Month month) { 22 | this.value = java.time.YearMonth.of(year.value(), month.value()); 23 | } 24 | 25 | public YearMonth(Integer year, Integer month) { 26 | this(new Year(year), Month.of(month)); 27 | } 28 | 29 | public YearMonth(String yearMonth) { 30 | try { 31 | this.value = java.time.YearMonth.parse(yearMonth, FORMATTER); 32 | } catch (DateTimeParseException e) { 33 | throw new IllegalArgumentException("月のフォーマットが誤っています", e); 34 | } 35 | } 36 | 37 | public YearMonth(java.time.YearMonth value) { 38 | this.value = value; 39 | } 40 | 41 | public Year year() { 42 | return new Year(value.getYear()); 43 | } 44 | 45 | public Month month() { 46 | return Month.of(value.getMonthValue()); 47 | } 48 | 49 | public LocalDate start() { 50 | return value.atDay(1); 51 | } 52 | 53 | public LocalDate end() { 54 | return value.atEndOfMonth(); 55 | } 56 | 57 | public List days() { 58 | IntStream intStream = IntStream.rangeClosed(start().getDayOfMonth(), end().getDayOfMonth()); 59 | return intStream.mapToObj(i -> start().plusDays((long) i - 1)).collect(Collectors.toList()); 60 | } 61 | 62 | public YearMonth before() { 63 | return new YearMonth(value.minusMonths(1)); 64 | } 65 | 66 | public YearMonth after() { 67 | return new YearMonth(value.plusMonths(1)); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return value.format(FORMATTER); 73 | } 74 | 75 | public boolean isThisYear() { 76 | return value.getYear() == java.time.YearMonth.now().getYear(); 77 | } 78 | 79 | public java.time.YearMonth value() { 80 | return value; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/date/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 日付 3 | */ 4 | package example.domain.type.date; -------------------------------------------------------------------------------- /src/main/java/example/domain/type/datetime/QuarterRoundDateTime.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.datetime; 2 | 3 | 4 | import example.domain.type.time.Minute; 5 | 6 | 7 | /** 8 | * 15分単位の日時 9 | */ 10 | public class QuarterRoundDateTime { 11 | 12 | DateTime value; 13 | 14 | public QuarterRoundDateTime(DateTime value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return value.toString(); 21 | } 22 | 23 | public static Minute between(QuarterRoundDateTime start, QuarterRoundDateTime end) { 24 | return DateTime.between(start.value, end.value); 25 | } 26 | 27 | public static Minute between(QuarterRoundDateTime start, DateTime end) { 28 | return DateTime.between(start.value, end); 29 | } 30 | 31 | public DateTime value() { 32 | return value; 33 | } 34 | 35 | public boolean isAfter(DateTime other) { 36 | return value.isAfter(other); 37 | } 38 | 39 | public boolean isBefore(DateTime other) { 40 | return value.isBefore(other); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/datetime/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 日時 3 | */ 4 | package example.domain.type.datetime; -------------------------------------------------------------------------------- /src/main/java/example/domain/type/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ビジネスロジックを表現する基本型 3 | */ 4 | package example.domain.type; -------------------------------------------------------------------------------- /src/main/java/example/domain/type/time/Hour.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.time; 2 | 3 | /** 4 | * 時間(数) 5 | */ 6 | public class Hour { 7 | int value; 8 | 9 | public Hour(int time) { 10 | value = time; 11 | } 12 | 13 | public Minute toMinute() { 14 | return new Minute(value * 60); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return String.format("%d", value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/time/HourAndMinute.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.time; 2 | 3 | /** 4 | * x時間y分 5 | */ 6 | public class HourAndMinute { 7 | Hour hour; 8 | Minute minute; 9 | 10 | HourAndMinute(Hour hour, Minute minute) { 11 | this.hour = hour; 12 | this.minute = minute; 13 | } 14 | 15 | public static HourAndMinute from(Minute minute) { 16 | return from(minute.value); 17 | } 18 | 19 | static HourAndMinute from(int minute) { 20 | Hour quotient = new Hour(minute / 60); 21 | Minute remainder = new Minute(minute % 60); 22 | return new HourAndMinute(quotient, remainder); 23 | } 24 | 25 | public Minute toMinute() { 26 | return minute.add(hour.toMinute()); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return String.format("%d時間%d分", hour.value, minute.value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/time/Minute.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.time; 2 | 3 | import example.domain.validation.FormatCheck; 4 | 5 | import javax.validation.constraints.Min; 6 | import java.math.BigDecimal; 7 | import java.time.DateTimeException; 8 | 9 | /** 10 | * 分(数) 11 | */ 12 | public class Minute { 13 | @Min(value = 0, groups = FormatCheck.class, message = "分が負の値になっています") 14 | int value; 15 | 16 | @Deprecated 17 | Minute() { 18 | } 19 | 20 | public static Minute from(String time) { 21 | Integer value = time.isEmpty() ? 0 : Integer.parseInt(time); 22 | if (value < 0) { 23 | throw new DateTimeException("分が負の値になっています"); 24 | } 25 | return new Minute(value); 26 | } 27 | 28 | public Minute(int time) { 29 | value = time; 30 | } 31 | 32 | public Minute subtract(Minute minute) { 33 | return add(0 - minute.value); 34 | } 35 | 36 | public Minute add(Minute minute) { 37 | return add(minute.value); 38 | } 39 | 40 | private Minute add(int value) { 41 | return new Minute(this.value + value); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return String.format("%d", value); 47 | } 48 | 49 | public QuarterHour quarterHourRoundUp() { 50 | return new QuarterHour((value % 15 == 0) ? this : new Minute((value / 15 + 1) * 15)); 51 | } 52 | 53 | public boolean lessThan(Minute value) { 54 | return this.value < value.value; 55 | } 56 | 57 | public BigDecimal bigDecimalValue() { 58 | return BigDecimal.valueOf(value); 59 | } 60 | 61 | public boolean moreThan(Minute value) { 62 | return this.value > value.value; 63 | } 64 | 65 | public int toInt() { 66 | return value; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/time/QuarterHour.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.time; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.RoundingMode; 5 | 6 | /** 7 | * 15分単位の時間 8 | */ 9 | // TODO: ドメイン(ビジネス)への移動を検討した方がいいかもしれない 10 | public class QuarterHour { 11 | 12 | Minute value; 13 | 14 | public QuarterHour() { 15 | this(new Minute(0)); 16 | } 17 | 18 | public QuarterHour(Minute value) { 19 | this.value = value; 20 | } 21 | 22 | public QuarterHour(Hour hour) { 23 | this(hour.toMinute()); 24 | } 25 | 26 | public Minute minute() { 27 | return value; 28 | } 29 | 30 | public QuarterHour subtract(QuarterHour other) { 31 | return new QuarterHour(value.subtract(other.value)); 32 | } 33 | 34 | public QuarterHour add(QuarterHour other) { 35 | return new QuarterHour(value.add(other.value)); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return HourAndMinute.from(value).toString(); 41 | } 42 | 43 | public BigDecimal bigDecimalValue() { 44 | return value.bigDecimalValue().divide(BigDecimal.valueOf(60), 2, RoundingMode.UNNECESSARY); 45 | } 46 | 47 | public boolean moreThan(QuarterHour other) { 48 | return moreThan(other.value); 49 | } 50 | 51 | public QuarterHour overMinute(QuarterHour other) { 52 | return overMinute(other.value); 53 | } 54 | 55 | public boolean moreThan(Minute other) { 56 | return this.value.moreThan(other); 57 | } 58 | 59 | public boolean moreThan(Hour hour) { 60 | return moreThan(hour.toMinute()); 61 | } 62 | 63 | public QuarterHour overMinute(Minute other) { 64 | if (value.lessThan(other)) { 65 | return new QuarterHour(new Minute(0)); 66 | } 67 | return new QuarterHour(value.subtract(other)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/time/Time.java: -------------------------------------------------------------------------------- 1 | package example.domain.type.time; 2 | 3 | import java.time.LocalTime; 4 | import java.time.format.DateTimeFormatter; 5 | 6 | /** 7 | * 時刻を時分単位で表す 8 | */ 9 | public class Time { 10 | 11 | LocalTime value; 12 | 13 | @Deprecated 14 | Time() { 15 | } 16 | 17 | public Time(String value) { 18 | this(LocalTime.parse(value, DateTimeFormatter.ofPattern("H:m"))); 19 | } 20 | 21 | public Time(LocalTime value) { 22 | this.value = value; 23 | } 24 | 25 | public Time(Integer hour, Integer minute) { 26 | this(LocalTime.of(hour, minute)); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return DateTimeFormatter.ofPattern("HH:mm").format(value); 32 | } 33 | 34 | public boolean isAfter(Time other) { 35 | return value.isAfter(other.value); 36 | } 37 | 38 | public boolean isBefore(Time other) { 39 | return value.isBefore(other.value); 40 | } 41 | 42 | public LocalTime value() { 43 | return value; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/example/domain/type/time/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 時刻・時間 3 | */ 4 | // TODO: 時刻と時間は別では 5 | package example.domain.type.time; -------------------------------------------------------------------------------- /src/main/java/example/domain/validation/BusinessLogic.java: -------------------------------------------------------------------------------- 1 | package example.domain.validation; 2 | 3 | public interface BusinessLogic { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/example/domain/validation/Conversion.java: -------------------------------------------------------------------------------- 1 | package example.domain.validation; 2 | 3 | public interface Conversion { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/example/domain/validation/FormatCheck.java: -------------------------------------------------------------------------------- 1 | package example.domain.validation; 2 | 3 | public interface FormatCheck { 4 | } -------------------------------------------------------------------------------- /src/main/java/example/domain/validation/Required.java: -------------------------------------------------------------------------------- 1 | package example.domain.validation; 2 | 3 | public interface Required { 4 | } -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/contract/ContractDataSource.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.contract; 2 | 3 | import example.application.repository.ContractRepository; 4 | import example.domain.model.contract.*; 5 | import example.domain.model.contract.ContractCondition; 6 | import example.domain.model.contract.ContractConditions; 7 | import example.domain.model.contract.wage.WageCondition; 8 | import example.domain.model.employee.ContractingEmployees; 9 | import example.domain.model.employee.Employee; 10 | import example.domain.model.employee.EmployeeNumber; 11 | import org.springframework.stereotype.Repository; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @Repository 17 | public class ContractDataSource implements ContractRepository { 18 | ContractMapper mapper; 19 | 20 | @Override 21 | public void registerHourlyWage(Employee employee, ContractEffectiveDate effectiveDate, WageCondition wageCondition) { 22 | EmployeeNumber employeeNumber = employee.employeeNumber(); 23 | mapper.deleteContractData(employeeNumber, effectiveDate); 24 | 25 | Integer hourlyWageId = mapper.newHourlyWageIdentifier(); 26 | mapper.insertContractHistory(employeeNumber, hourlyWageId, effectiveDate, wageCondition); 27 | mapper.insertContract(employeeNumber, effectiveDate, wageCondition); 28 | } 29 | 30 | @Override 31 | public ContractConditions getContractConditions(Employee employee) { 32 | List list = mapper.selectContracts(employee.employeeNumber()); 33 | return new ContractConditions(list); 34 | } 35 | 36 | @Override 37 | public Contracts findContracts(ContractingEmployees contractingEmployees) { 38 | List list = new ArrayList<>(); 39 | for (Employee employee : contractingEmployees.list()) { 40 | list.add(new Contract(employee, getContractConditions(employee))); 41 | } 42 | return new Contracts(list); 43 | } 44 | 45 | ContractDataSource(ContractMapper payrollMapper) { 46 | this.mapper = payrollMapper; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/contract/ContractMapper.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.contract; 2 | 3 | import example.domain.model.contract.ContractEffectiveDate; 4 | import example.domain.model.contract.ContractCondition; 5 | import example.domain.model.contract.wage.WageCondition; 6 | import example.domain.model.employee.EmployeeNumber; 7 | import org.apache.ibatis.annotations.Mapper; 8 | import org.apache.ibatis.annotations.Param; 9 | 10 | import java.util.List; 11 | 12 | @Mapper 13 | public interface ContractMapper { 14 | Integer newHourlyWageIdentifier(); 15 | 16 | void insertContractHistory(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("id") Integer hourlyWageId, 17 | @Param("effectiveDate") ContractEffectiveDate effectiveDate, 18 | @Param("wageCondition") WageCondition wageCondition); 19 | 20 | List selectContracts(@Param("employeeNumber") EmployeeNumber employeeNumber); 21 | 22 | void insertContract(@Param("employeeNumber") EmployeeNumber employeeNumber, 23 | @Param("effectiveDate") ContractEffectiveDate effectiveDate, 24 | @Param("wageCondition") WageCondition wageCondition); 25 | 26 | void deleteContractData(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("effectiveDate") ContractEffectiveDate effectiveDate); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/contract/ContractMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | INSERT INTO 給与.時給契約履歴 (時給ID, 従業員ID, 時給, 適用開始日, 法定時間外月60時間以内割増率, 法定時間外月60時間超割増率, 法定休日労働割増率, 深夜割増率) 12 | VALUES (#{id}, #{employeeNumber.value},#{wageCondition.baseHourlyWage.value.value.value}, #{effectiveDate.value}, 13 | #{wageCondition.overTimeExtraRate.overLegalWithin60HoursExtraRate.value.value.value}, 14 | #{wageCondition.overTimeExtraRate.overLegalMoreThan60HoursExtraRate.value.value.value}, 15 | #{wageCondition.overTimeExtraRate.legalDaysOffExtraRate.value.value.value}, 16 | #{wageCondition.overTimeExtraRate.nightExtraRate.value.value.value}); 17 | 18 | 19 | 30 | 31 | 32 | INSERT INTO 給与.時給契約(従業員ID, 適用開始日, 時給, 法定時間外月60時間以内割増率, 法定時間外月60時間超割増率, 法定休日労働割増率, 深夜割増率) VALUES 33 | (#{employeeNumber.value}, #{effectiveDate.value}, #{wageCondition.baseHourlyWage.value.value.value}, 34 | #{wageCondition.overTimeExtraRate.overLegalWithin60HoursExtraRate.value.value.value}, 35 | #{wageCondition.overTimeExtraRate.overLegalMoreThan60HoursExtraRate.value.value.value}, 36 | #{wageCondition.overTimeExtraRate.legalDaysOffExtraRate.value.value.value}, 37 | #{wageCondition.overTimeExtraRate.nightExtraRate.value.value.value}) 38 | 39 | 40 | 41 | DELETE FROM 給与.時給契約 WHERE 42 | 従業員ID = #{employeeNumber.value} AND 43 | 時給契約.適用開始日 = #{effectiveDate.value} 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/daysoff/DaysOffDataSource.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.daysoff; 2 | 3 | import example.application.repository.DaysOffRepository; 4 | import example.domain.model.daysoff.DaysOff; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public class DaysOffDataSource implements DaysOffRepository { 9 | DaysOffMapper mapper; 10 | 11 | @Override 12 | public void registerDaysOff(DaysOff daysOff) { 13 | mapper.deleteDaysOff(daysOff.employeeNumber(), daysOff.date()); 14 | mapper.insertDaysOffHistory(daysOff.employeeNumber(), daysOff.date()); 15 | mapper.insertDaysOff(daysOff.employeeNumber(), daysOff.date()); 16 | } 17 | 18 | @Override 19 | public void deleteDaysOff(DaysOff daysOff) { 20 | mapper.deleteDaysOff(daysOff.employeeNumber(), daysOff.date()); 21 | } 22 | 23 | DaysOffDataSource(DaysOffMapper mapper) { 24 | this.mapper = mapper; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/daysoff/DaysOffMapper.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.daysoff; 2 | 3 | import example.domain.model.employee.EmployeeNumber; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.time.LocalDate; 8 | 9 | @Mapper 10 | public interface DaysOffMapper { 11 | void insertDaysOffHistory(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("daysOff") LocalDate daysOff); 12 | 13 | void insertDaysOff(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("daysOff") LocalDate daysOff); 14 | 15 | void deleteDaysOff(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("daysOff") LocalDate daysOff); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/daysoff/DaysOffMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | INSERT INTO 給与.休日履歴(従業員ID, 休日) VALUES 8 | (#{employeeNumber.value}, #{daysOff}); 9 | 10 | 11 | 12 | INSERT INTO 給与.休日(従業員ID, 休日) VALUES 13 | (#{employeeNumber.value}, #{daysOff}); 14 | 15 | 16 | 17 | DELETE FROM 給与.休日 WHERE 18 | 従業員ID = #{employeeNumber.value} AND 19 | 休日 = #{daysOff}; 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/employee/EmployeeDataSource.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.employee; 2 | 3 | import example.application.repository.EmployeeRepository; 4 | import example.domain.model.employee.*; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public class EmployeeDataSource implements EmployeeRepository { 9 | EmployeeMapper mapper; 10 | 11 | @Override 12 | public Employee choose(EmployeeNumber employeeNumber) { 13 | return mapper.selectByEmployeeNumber(employeeNumber); 14 | } 15 | 16 | @Override 17 | public ContractingEmployees findUnderContracts() { 18 | return new ContractingEmployees(mapper.selectContracts()); 19 | } 20 | 21 | @Override 22 | public EmployeeNumber registerNew() { 23 | EmployeeNumber employeeNumber = new EmployeeNumber(mapper.newEmployeeNumber()); 24 | mapper.insertEmployee(employeeNumber); 25 | return employeeNumber; 26 | } 27 | 28 | @Override 29 | public void registerName(EmployeeNumber employeeNumber, Name name) { 30 | Integer nameId = mapper.newEmployeeNameIdentifier(); 31 | mapper.insertEmployeeNameHistory(nameId, employeeNumber, name); 32 | mapper.deleteEmployeeName(employeeNumber); 33 | mapper.insertEmployeeName(employeeNumber, nameId, name); 34 | } 35 | 36 | @Override 37 | public void registerMailAddress(EmployeeNumber employeeNumber, MailAddress mailAddress) { 38 | Integer mailAddressId = mapper.newEmployeeMailAddressIdentifier(); 39 | mapper.insertEmployeeMailAddressHistory(mailAddressId, employeeNumber, mailAddress); 40 | mapper.deleteEmployeeMailAddress(employeeNumber); 41 | mapper.insertEmployeeMailAddress(employeeNumber, mailAddressId, mailAddress); 42 | } 43 | 44 | @Override 45 | public void registerPhoneNumber(EmployeeNumber employeeNumber, PhoneNumber phoneNumber) { 46 | Integer phoneNumberId = mapper.newEmployeePhoneNumberIdentifier(); 47 | mapper.insertEmployeePhoneNumberHistory(phoneNumberId, employeeNumber, phoneNumber); 48 | mapper.deleteEmployeePhoneNumber(employeeNumber); 49 | mapper.insertEmployeePhoneNumber(employeeNumber, phoneNumberId, phoneNumber); 50 | } 51 | 52 | @Override 53 | public void registerInspireContract(EmployeeNumber employeeNumber) { 54 | mapper.insertInspireContract(employeeNumber); 55 | } 56 | 57 | @Override 58 | public void registerExpireContract(Employee employee) { 59 | mapper.deleteInspireContract(employee.employeeNumber()); 60 | mapper.insertExpireContract(employee.employeeNumber()); 61 | } 62 | 63 | public EmployeeDataSource(EmployeeMapper mapper) { 64 | this.mapper = mapper; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/employee/EmployeeMapper.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.employee; 2 | 3 | import example.domain.model.employee.*; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface EmployeeMapper { 11 | 12 | Employee selectByEmployeeNumber(@Param("employeeNumber") EmployeeNumber employeeNumber); 13 | 14 | List selectContracts(); 15 | 16 | void insertEmployee(@Param("employeeNumber") EmployeeNumber employeeNumber); 17 | 18 | void deleteEmployeeName(@Param("employeeNumber") EmployeeNumber employeeNumber); 19 | 20 | void insertEmployeeNameHistory(@Param("id") Integer id, @Param("employeeNumber") EmployeeNumber employeeNumber, @Param("name") Name name); 21 | 22 | void insertEmployeeName(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("nameId") Integer nameId, @Param("name") Name employeeName); 23 | 24 | void deleteEmployeePhoneNumber(@Param("employeeNumber") EmployeeNumber employeeNumber); 25 | 26 | void insertEmployeePhoneNumberHistory(@Param("id") Integer id, @Param("employeeNumber") EmployeeNumber employeeNumber, @Param("phoneNumber") PhoneNumber phoneNumber); 27 | 28 | void insertEmployeePhoneNumber(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("phoneNumberId") Integer phoneId, @Param("phoneNumber") PhoneNumber phoneNumber); 29 | 30 | void deleteEmployeeMailAddress(@Param("employeeNumber") EmployeeNumber employeeNumber); 31 | 32 | void insertEmployeeMailAddressHistory(@Param("id") Integer id, @Param("employeeNumber") EmployeeNumber employeeNumber, @Param("mailAddress") MailAddress mailAddress); 33 | 34 | void insertEmployeeMailAddress(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("mailAddressId") Integer mailAddressId, @Param("mailAddress") MailAddress mailAddress); 35 | 36 | void insertInspireContract(@Param("employeeNumber") EmployeeNumber employeeNumber); 37 | 38 | void deleteInspireContract(@Param("employeeNumber") EmployeeNumber employeeNumber); 39 | 40 | void insertExpireContract(@Param("employeeNumber") EmployeeNumber employeeNumber); 41 | 42 | Integer newEmployeeNumber(); 43 | 44 | Integer newEmployeeNameIdentifier(); 45 | 46 | Integer newEmployeePhoneNumberIdentifier(); 47 | 48 | Integer newEmployeeMailAddressIdentifier(); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * データベースアクセスの実装クラス群 3 | */ 4 | package example.infrastructure.datasource; -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/timerecord/TimeRecordDataSource.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.timerecord; 2 | 3 | import example.application.repository.TimeRecordRepository; 4 | import example.domain.model.timerecord.evaluation.TimeRecords; 5 | import example.domain.model.attendance.WorkMonth; 6 | import example.domain.model.employee.Employee; 7 | import example.domain.model.employee.EmployeeNumber; 8 | import example.domain.model.timerecord.evaluation.TimeRecord; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import java.util.List; 12 | 13 | @Repository 14 | public class TimeRecordDataSource implements TimeRecordRepository { 15 | TimeRecordMapper mapper; 16 | 17 | @Override 18 | public void registerTimeRecord(TimeRecord timeRecord) { 19 | Integer identifier = mapper.newWorkTimeIdentifier(); 20 | EmployeeNumber employeeNumber = timeRecord.employeeNumber(); 21 | mapper.insertWorkTimeHistory(identifier, employeeNumber, timeRecord, timeRecord.workDate()); 22 | mapper.deleteWorkTime(employeeNumber, timeRecord.workDate()); 23 | mapper.insertWorkTime(employeeNumber, identifier, timeRecord, timeRecord.workDate()); 24 | } 25 | 26 | @Override 27 | public TimeRecords findTimeRecords(Employee employee, WorkMonth month) { 28 | List timeRecords = mapper.selectByMonth(employee.employeeNumber(), month.yyyyMM()); 29 | return new TimeRecords(timeRecords); 30 | } 31 | 32 | TimeRecordDataSource(TimeRecordMapper mapper) { 33 | this.mapper = mapper; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/timerecord/TimeRecordMapper.java: -------------------------------------------------------------------------------- 1 | package example.infrastructure.datasource.timerecord; 2 | 3 | import example.domain.model.employee.EmployeeNumber; 4 | import example.domain.model.timerecord.evaluation.TimeRecord; 5 | import example.domain.model.timerecord.evaluation.WorkDate; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | import java.util.List; 10 | 11 | @Mapper 12 | public interface TimeRecordMapper { 13 | Integer newWorkTimeIdentifier(); 14 | 15 | void insertWorkTimeHistory(@Param("id") Integer id, @Param("employeeNumber") EmployeeNumber employeeNumber, @Param("work") TimeRecord work, @Param("workDate") WorkDate workDate); 16 | 17 | void insertWorkTime(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("workTimeId") Integer workTimeId, @Param("work") TimeRecord work, @Param("workDate") WorkDate workDate); 18 | 19 | void deleteWorkTime(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("workDate") WorkDate workDate); 20 | 21 | List selectByMonth(@Param("employeeNumber") EmployeeNumber employeeNumber, @Param("yearMonth") String yearMonth); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/datasource/timerecord/TimeRecordMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | INSERT INTO 給与.就業時間履歴 (就業時間ID, 従業員ID, 勤務日, 開始日時, 終了日時, 休憩時間, 深夜休憩時間, 休日) 11 | VALUES (#{id}, #{employeeNumber.value}, 12 | #{workDate.value}, 13 | #{work.actualWorkDateTime.workRange.startDateTime.value.value}, 14 | #{work.actualWorkDateTime.workRange.endDateTime.value.value}, 15 | #{work.actualWorkDateTime.daytimeBreakTime.value.value}, 16 | #{work.actualWorkDateTime.nightBreakTime.value.value}, 17 | #{work.daysOffStatus}); 18 | 19 | 20 | INSERT INTO 給与.就業時間 (従業員ID, 就業時間ID, 勤務日, 開始日時, 終了日時, 休憩時間, 深夜休憩時間, 休日) 21 | VALUES (#{employeeNumber.value}, #{workTimeId}, 22 | #{workDate.value}, 23 | #{work.actualWorkDateTime.workRange.startDateTime.value.value}, 24 | #{work.actualWorkDateTime.workRange.endDateTime.value.value}, 25 | #{work.actualWorkDateTime.daytimeBreakTime.value.value}, 26 | #{work.actualWorkDateTime.nightBreakTime.value.value}, 27 | #{work.daysOffStatus}); 28 | 29 | 30 | DELETE FROM 給与.就業時間 WHERE 従業員ID = #{employeeNumber.value} and 勤務日 = #{workDate.value} 31 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 技術基盤 3 | *

4 | * このレイヤのクラスを他のレイヤのクラスは直接呼び出さないこと 5 | * 使う側の意図をインタフェースで宣言 6 | * 実装クラスを、このレイヤで宣言(命名ガイド:実装技術名+インタフェース名) 7 | */ 8 | package example.infrastructure; -------------------------------------------------------------------------------- /src/main/java/example/infrastructure/transfer/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * トランスファー層 3 | *

4 | * 外部との通信の実装クラス 5 | */ 6 | package example.infrastructure.transfer; -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/BaseControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller; 2 | 3 | import org.springframework.beans.propertyeditors.StringTrimmerEditor; 4 | import org.springframework.web.bind.WebDataBinder; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.InitBinder; 7 | 8 | @ControllerAdvice(basePackageClasses = BaseControllerAdvice.class) 9 | public class BaseControllerAdvice { 10 | 11 | private static final String[] allowFields; 12 | private static final String[] disallowFields; 13 | 14 | static { 15 | allowFields = new String[]{ 16 | "to be specified", 17 | }; 18 | 19 | disallowFields = new String[]{ 20 | "protected*", 21 | }; 22 | } 23 | 24 | @InitBinder 25 | public void initBinder(WebDataBinder binder) { 26 | binder.initDirectFieldAccess(); 27 | binder.setAllowedFields(allowFields); 28 | binder.setDisallowedFields(disallowFields); 29 | 30 | binder.registerCustomEditor(String.class, new StringTrimmerEditor(false)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/DashboardController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller; 2 | 3 | import example.application.service.employee.EmployeeQueryService; 4 | import example.domain.model.employee.ContractingEmployees; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.ModelAttribute; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | 10 | /** 11 | * ダッシュボード 12 | */ 13 | @Controller 14 | @RequestMapping("/") 15 | class DashboardController { 16 | EmployeeQueryService employeeQueryService; 17 | 18 | @ModelAttribute("employees") 19 | ContractingEmployees employees() { 20 | return employeeQueryService.contractingEmployees(); 21 | } 22 | 23 | @GetMapping 24 | String show() { 25 | return "dashboard"; 26 | } 27 | 28 | DashboardController(EmployeeQueryService employeeQueryService) { 29 | this.employeeQueryService = employeeQueryService; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/attendance/AttendanceController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.attendance; 2 | 3 | import example.application.service.attendance.AttendanceQueryService; 4 | import example.application.service.employee.EmployeeQueryService; 5 | import example.application.service.timerecord.TimeRecordRecordService; 6 | import example.domain.model.attendance.Attendance; 7 | import example.domain.model.attendance.WorkMonth; 8 | import example.domain.model.employee.Employee; 9 | import example.domain.model.employee.EmployeeNumber; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.ui.Model; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.ModelAttribute; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | 17 | /** 18 | * 勤務時間の一覧 19 | */ 20 | @Controller 21 | @RequestMapping("attendances/{employeeNumber}/{yearMonth}") 22 | public class AttendanceController { 23 | 24 | EmployeeQueryService employeeQueryService; 25 | TimeRecordRecordService timeRecordRecordService; 26 | AttendanceQueryService attendanceQueryService; 27 | 28 | public AttendanceController(EmployeeQueryService employeeQueryService, TimeRecordRecordService timeRecordRecordService, AttendanceQueryService attendanceQueryService) { 29 | this.employeeQueryService = employeeQueryService; 30 | this.timeRecordRecordService = timeRecordRecordService; 31 | this.attendanceQueryService = attendanceQueryService; 32 | } 33 | 34 | @ModelAttribute("beforeMonth") 35 | String beforeMonth(@PathVariable(value = "yearMonth") WorkMonth month) { 36 | return month.before().toString(); 37 | } 38 | 39 | @ModelAttribute("afterMonth") 40 | String afterMonth(@PathVariable(value = "yearMonth") WorkMonth month) { 41 | return month.after().toString(); 42 | } 43 | 44 | @GetMapping 45 | String list(Model model, 46 | @PathVariable("employeeNumber") EmployeeNumber employeeNumber, 47 | @PathVariable("yearMonth") WorkMonth workMonth) { 48 | Employee employee = employeeQueryService.choose(employeeNumber); 49 | model.addAttribute("employee", employee); 50 | 51 | Attendance attendance = attendanceQueryService.findAttendance(employee, workMonth); 52 | model.addAttribute("attendance", attendance); 53 | return "attendance/list"; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/employee/BulkProfileUpdateForm.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.employee; 2 | 3 | import example.domain.model.employee.*; 4 | 5 | import javax.validation.Valid; 6 | import javax.validation.constraints.NotNull; 7 | 8 | /** 9 | * 従業員情報一括更新フォーム 10 | */ 11 | public class BulkProfileUpdateForm { 12 | 13 | @NotNull 14 | @Valid 15 | Name name; 16 | 17 | @NotNull 18 | @Valid 19 | MailAddress mailAddress; 20 | 21 | @NotNull 22 | @Valid 23 | PhoneNumber phoneNumber; 24 | 25 | public BulkProfileUpdateForm(Name name, MailAddress mailAddress, PhoneNumber phoneNumber) { 26 | this.name = name; 27 | this.mailAddress = mailAddress; 28 | this.phoneNumber = phoneNumber; 29 | } 30 | 31 | public static BulkProfileUpdateForm from(Employee employee) { 32 | return new BulkProfileUpdateForm(employee.name(), employee.mailAddress(), employee.phoneNumber()); 33 | } 34 | 35 | public NameToChange updateName(EmployeeNumber employeeNumber) { 36 | return new NameToChange(employeeNumber, name); 37 | } 38 | 39 | public PhoneNumberToChange updatePhoneNumber(EmployeeNumber employeeNumber) { 40 | return new PhoneNumberToChange(employeeNumber, phoneNumber); 41 | } 42 | 43 | public MailAddressToChange updateMailAddress(EmployeeNumber employeeNumber) { 44 | return new MailAddressToChange(employeeNumber, mailAddress); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/employee/EmployeeController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.employee; 2 | 3 | import example.application.service.employee.EmployeeQueryService; 4 | import example.domain.model.employee.Employee; 5 | import example.domain.model.employee.EmployeeNumber; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | /** 11 | * 従業員の詳細 12 | */ 13 | @Controller 14 | @RequestMapping("employees/{employeeNumber}") 15 | public class EmployeeController { 16 | 17 | EmployeeQueryService employeeQueryService; 18 | 19 | public EmployeeController(EmployeeQueryService employeeQueryService) { 20 | this.employeeQueryService = employeeQueryService; 21 | } 22 | 23 | @ModelAttribute("employee") 24 | Employee employee(@PathVariable(value = "employeeNumber") EmployeeNumber employeeNumber) { 25 | return employeeQueryService.choose(employeeNumber); 26 | } 27 | 28 | @GetMapping 29 | public String init(Model model, 30 | @RequestParam(value = "updateResult", required = false) String updateResult) { 31 | 32 | model.addAttribute("updateResult", updateResult); 33 | 34 | return "employee/detail"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/employee/EmployeeDeleteController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.employee; 2 | 3 | import example.application.service.employee.EmployeeQueryService; 4 | import example.application.service.employee.EmployeeRecordService; 5 | import example.domain.model.employee.Employee; 6 | import example.domain.model.employee.EmployeeNumber; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | /** 13 | * 従業員の削除 14 | */ 15 | @Controller 16 | @RequestMapping("employees/{employeeNumber}/delete") 17 | public class EmployeeDeleteController { 18 | 19 | EmployeeRecordService employeeRecordService; 20 | EmployeeQueryService employeeQueryService; 21 | 22 | @GetMapping 23 | String deleteThenRedirect(@PathVariable(value = "employeeNumber") EmployeeNumber employeeNumber) { 24 | Employee employee = employeeQueryService.choose(employeeNumber); 25 | employeeRecordService.expireContract(employee); 26 | return "redirect:/employees"; 27 | } 28 | 29 | EmployeeDeleteController(EmployeeRecordService employeeRecordService, EmployeeQueryService employeeQueryService) { 30 | this.employeeRecordService = employeeRecordService; 31 | this.employeeQueryService = employeeQueryService; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/employee/EmployeeUpdateController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.employee; 2 | 3 | import example.application.service.employee.EmployeeQueryService; 4 | import example.application.service.employee.EmployeeRecordService; 5 | import example.domain.model.employee.Employee; 6 | import example.domain.model.employee.EmployeeNumber; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.validation.BindingResult; 10 | import org.springframework.validation.annotation.Validated; 11 | import org.springframework.web.bind.annotation.*; 12 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 13 | 14 | /** 15 | * 従業員の更新 16 | */ 17 | @Controller 18 | @RequestMapping("employees/{employeeNumber}/update") 19 | class EmployeeUpdateController { 20 | 21 | EmployeeQueryService employeeQueryService; 22 | EmployeeRecordService employeeRecordService; 23 | 24 | @GetMapping 25 | String open(@PathVariable(value = "employeeNumber") EmployeeNumber employeeNumber, 26 | Model model) { 27 | Employee employee = employeeQueryService.choose(employeeNumber); 28 | model.addAttribute("bulkProfileUpdateForm", BulkProfileUpdateForm.from(employee)); 29 | return "employee/update/form"; 30 | } 31 | 32 | @PostMapping("register") 33 | String registerThenRedirect(@PathVariable(value = "employeeNumber") EmployeeNumber employeeNumber, 34 | @Validated @ModelAttribute("bulkProfileUpdateForm") BulkProfileUpdateForm bulkProfileUpdateForm, BindingResult result, 35 | RedirectAttributes attributes) { 36 | if (result.hasErrors()) return "employee/update/form"; 37 | 38 | employeeRecordService.registerName(bulkProfileUpdateForm.updateName(employeeNumber)); 39 | employeeRecordService.registerMailAddress(bulkProfileUpdateForm.updateMailAddress(employeeNumber)); 40 | employeeRecordService.registerPhoneNumber(bulkProfileUpdateForm.updatePhoneNumber(employeeNumber)); 41 | 42 | attributes.addAttribute("updateResult", "completed"); 43 | 44 | return "redirect:/employees/" + employeeNumber; 45 | } 46 | 47 | public EmployeeUpdateController(EmployeeQueryService employeeQueryService, EmployeeRecordService employeeRecordService) { 48 | this.employeeQueryService = employeeQueryService; 49 | this.employeeRecordService = employeeRecordService; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/employee/EmployeesController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.employee; 2 | 3 | import example.application.service.contract.ContractQueryService; 4 | import example.application.service.employee.EmployeeQueryService; 5 | import example.domain.model.contract.Contracts; 6 | import example.domain.model.employee.ContractingEmployees; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | /** 13 | * 従業員の一覧 14 | */ 15 | @Controller 16 | @RequestMapping("employees") 17 | public class EmployeesController { 18 | 19 | EmployeeQueryService employeeQueryService; 20 | ContractQueryService contractQueryService; 21 | 22 | @GetMapping 23 | String employees(Model model) { 24 | ContractingEmployees contractingEmployees = employeeQueryService.contractingEmployees(); 25 | Contracts contracts = contractQueryService.findContracts(contractingEmployees); 26 | 27 | model.addAttribute("contracts", contracts); 28 | return "employee/list"; 29 | } 30 | 31 | EmployeesController(EmployeeQueryService employeeQueryService, ContractQueryService contractQueryService) { 32 | this.employeeQueryService = employeeQueryService; 33 | this.contractQueryService = contractQueryService; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * コントローラ定義 3 | */ 4 | package example.presentation.controller; -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/payroll/PayrollController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.payroll; 2 | 3 | import example.application.coordinator.payroll.PayrollQueryCoordinator; 4 | import example.application.service.employee.EmployeeQueryService; 5 | import example.domain.model.attendance.WorkMonth; 6 | import example.domain.model.employee.ContractingEmployees; 7 | import example.domain.model.payroll.Payrolls; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | 14 | /** 15 | * 給与の一覧 16 | */ 17 | @Controller 18 | @RequestMapping("payroll") 19 | public class PayrollController { 20 | 21 | EmployeeQueryService employeeQueryService; 22 | PayrollQueryCoordinator payrollQueryCoordinator; 23 | 24 | public PayrollController(EmployeeQueryService employeeQueryService, PayrollQueryCoordinator payrollQueryCoordinator) { 25 | this.employeeQueryService = employeeQueryService; 26 | this.payrollQueryCoordinator = payrollQueryCoordinator; 27 | } 28 | 29 | @GetMapping 30 | String employees(Model model) { 31 | return employees(model, new WorkMonth()); 32 | } 33 | 34 | @GetMapping("{workMonth}") 35 | String employees(Model model, @PathVariable("workMonth") WorkMonth workMonth) { 36 | ContractingEmployees contractingEmployees = employeeQueryService.contractingEmployees(); 37 | Payrolls payrolls = payrollQueryCoordinator.payrolls(contractingEmployees, workMonth); 38 | model.addAttribute("payrolls", payrolls); 39 | return "payroll/list"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/timerecord/EndHour.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.timerecord; 2 | 3 | import example.domain.validation.Required; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | 7 | public class EndHour { 8 | @NotBlank(message = "終了時刻(時)を入力してください", groups = Required.class) 9 | String value; 10 | 11 | public EndHour() { 12 | } 13 | 14 | public EndHour(String value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return value; 21 | } 22 | 23 | int toInt() { 24 | return Integer.parseInt(value); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/timerecord/EndMinute.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.timerecord; 2 | 3 | import example.domain.validation.Required; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | 7 | public class EndMinute { 8 | @NotBlank(message = "終了時刻(分)を入力してください", groups = Required.class) 9 | String value; 10 | 11 | public EndMinute() { 12 | } 13 | 14 | public EndMinute(String value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return value; 21 | } 22 | 23 | int toInt() { 24 | return Integer.parseInt(value); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/timerecord/EndTime.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.timerecord; 2 | 3 | import example.domain.model.timerecord.timefact.EndDateTime; 4 | import example.domain.model.timerecord.timefact.StartDateTime; 5 | import example.domain.type.datetime.DateTime; 6 | import example.domain.validation.FormatCheck; 7 | 8 | import javax.validation.Valid; 9 | import javax.validation.constraints.AssertTrue; 10 | import java.time.LocalDate; 11 | import java.time.LocalDateTime; 12 | import java.time.LocalTime; 13 | 14 | /** 15 | * 勤務終了日時 16 | */ 17 | public class EndTime { 18 | @Valid 19 | EndHour hour; 20 | 21 | @Valid 22 | EndMinute minute; 23 | 24 | public EndTime() { 25 | } 26 | 27 | EndTime(EndHour hour, EndMinute minute) { 28 | this.hour = hour; 29 | this.minute = minute; 30 | } 31 | 32 | public static EndTime from(String time) { 33 | String[] s = time.split(":"); 34 | return new EndTime(new EndHour(s[0]), new EndMinute(s[1])); 35 | } 36 | 37 | public EndDateTime endDateTime(StartDateTime startDateTime) { 38 | LocalDate date = startDateTime.date(); 39 | // TODO 1日を超える扱い 40 | LocalDate endDate = isOverFlow() ? date.plusDays(1) : date; 41 | LocalTime endTime = LocalTime.of(hour.toInt() % 24, minute.toInt()); 42 | return new EndDateTime(new DateTime(LocalDateTime.of(endDate, endTime))); 43 | } 44 | 45 | boolean isOverFlow() { 46 | return hour.toInt() > 23; 47 | } 48 | 49 | boolean valid; 50 | 51 | @AssertTrue(message = "終了時刻が不正です", groups = FormatCheck.class) 52 | public boolean isValid() { 53 | try { 54 | // とりあえず、数値かどうかだけチェック 55 | int h = hour.toInt(); 56 | int m = minute.toInt(); 57 | } catch (NumberFormatException ex) { 58 | return false; 59 | } 60 | 61 | return true; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/timerecord/StartHour.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.timerecord; 2 | 3 | import example.domain.validation.Required; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | 7 | public class StartHour { 8 | @NotBlank(message = "開始時刻(時)を入力してください", groups = Required.class) 9 | String value; 10 | 11 | public StartHour() { 12 | } 13 | 14 | public StartHour(String value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return value; 21 | } 22 | 23 | int toInt() { 24 | return Integer.parseInt(value); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/timerecord/StartMinute.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.timerecord; 2 | 3 | import example.domain.validation.Required; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | 7 | public class StartMinute { 8 | @NotBlank(message = "開始時刻(分)を入力してください", groups = Required.class) 9 | String value; 10 | 11 | public StartMinute() { 12 | } 13 | 14 | public StartMinute(String value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return value; 21 | } 22 | 23 | int toInt() { 24 | return Integer.parseInt(value); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/timerecord/StartTime.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.timerecord; 2 | 3 | import example.domain.validation.FormatCheck; 4 | import example.domain.model.timerecord.evaluation.WorkDate; 5 | import example.domain.model.timerecord.timefact.StartDateTime; 6 | import example.domain.type.datetime.DateTime; 7 | import example.domain.type.time.Time; 8 | 9 | import javax.validation.Valid; 10 | import javax.validation.constraints.AssertTrue; 11 | import java.time.DateTimeException; 12 | 13 | public class StartTime { 14 | @Valid 15 | StartHour hour; 16 | 17 | @Valid 18 | StartMinute minute; 19 | 20 | public StartTime() { 21 | } 22 | 23 | public StartTime(StartHour hour, StartMinute minute) { 24 | this.hour = hour; 25 | this.minute = minute; 26 | } 27 | 28 | public StartDateTime startDateTime(WorkDate workDate) { 29 | return new StartDateTime(DateTime.parse(workDate.toString(), hour.toString(), minute.toString())); 30 | } 31 | 32 | boolean valid; 33 | 34 | @AssertTrue(message = "開始時刻が不正です", groups = FormatCheck.class) 35 | public boolean isValid() { 36 | try { 37 | new Time(hour.toInt(), minute.toInt()); 38 | } catch (NumberFormatException | DateTimeException ex) { 39 | return false; 40 | } 41 | 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/controller/wage/WageListController.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.wage; 2 | 3 | import example.application.service.contract.ContractQueryService; 4 | import example.application.service.employee.EmployeeQueryService; 5 | import example.domain.model.contract.ContractConditions; 6 | import example.domain.model.employee.Employee; 7 | import example.domain.model.employee.EmployeeNumber; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.ModelAttribute; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | 15 | /** 16 | * 従業員ごとの時給の変遷 17 | */ 18 | @Controller 19 | @RequestMapping("wages/{employeeNumber}") 20 | public class WageListController { 21 | 22 | EmployeeQueryService employeeQueryService; 23 | ContractQueryService contractQueryService; 24 | 25 | public WageListController(EmployeeQueryService employeeQueryService, ContractQueryService contractQueryService) { 26 | this.employeeQueryService = employeeQueryService; 27 | this.contractQueryService = contractQueryService; 28 | } 29 | 30 | @ModelAttribute("employee") 31 | Employee employee(@PathVariable(value = "employeeNumber") EmployeeNumber employeeNumber) { 32 | return employeeQueryService.choose(employeeNumber); 33 | } 34 | 35 | @GetMapping 36 | public String list(Employee employee, Model model) { 37 | ContractConditions contractConditions = contractQueryService.getContractWages(employee); 38 | model.addAttribute("contractConditions", contractConditions); 39 | return "wage/list"; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/example/presentation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * プレゼンテーション層 3 | * ビューとコントローラの置き場所 4 | */ 5 | package example.presentation; -------------------------------------------------------------------------------- /src/main/java/example/presentation/view/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ビュー定義 3 | * ビューを表現するクラス群 4 | *

5 | * JSON <-> オブジェクト マッピングクラス 6 | * View Helper 7 | * etc. 8 | */ 9 | package example.presentation.view; -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #logging.path=. 2 | 3 | spring.thymeleaf.cache=false 4 | 5 | mybatis.config-location=classpath:mybatis.xml 6 | 7 | spring.datasource.sql-script-encoding=utf-8 8 | 9 | spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL 10 | spring.h2.console.enabled=true 11 | 12 | hourly-wage.base=985 13 | -------------------------------------------------------------------------------- /src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | # messages_ja.properties ��ǂނ��߂̃f�t�H���g�ien�p) -------------------------------------------------------------------------------- /src/main/resources/messages_ja.properties: -------------------------------------------------------------------------------- 1 | # Errors with arguments 2 | error.id.already.exists={0}は登録済みです 3 | 4 | # Errors [Type] 5 | typeMismatch.int=整数を入力して下さい。 6 | typeMismatch.java.math.BigDecimal=数字を入力して下さい。 7 | typeMismatch.java.time.LocalDate=日付を入力して下さい。 8 | 9 | typeMismatch.example.domain.type.date.Date=日付を入力して下さい。 10 | 11 | payroll.normal= 12 | payroll.payment-amount-unregistered=時給未設定の稼働が登録されています。 13 | payroll.not-working=勤務時間が登録されていません。 -------------------------------------------------------------------------------- /src/main/resources/mybatis.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/templates/dashboard.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | ダッシュボード 6 | 7 | 8 |

9 | 10 |
11 |
12 | 13 |
14 | 15 | 23 | 24 | 32 | 33 | 41 | 42 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/resources/templates/employee/list.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 従業員の一覧 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 | 16 | 17 | 従業員の新規登録 18 | 19 |
20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 43 | 44 | 45 | 46 | 47 |
従業員番号氏名現在の時給開始日
123 39 | 40 | 41 | 42 | 1,234円2001-01-02
48 |
49 |
50 | 51 | -------------------------------------------------------------------------------- /src/main/resources/templates/employee/register/confirm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 従業員情報の登録の確認 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 |

16 | 従業員情報を登録します。
17 | 確認の上、登録するボタンを押してください。 18 |

19 |
20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 39 | 40 |
氏名 25 |

山田 太郎

26 |
メールアドレス 31 |

someone@example.com

32 |
電話番号 37 |

0120-888-888

38 |
41 |
42 | 入力に戻る 44 | 登録 46 |
47 |
48 |
49 | 50 | -------------------------------------------------------------------------------- /src/main/resources/templates/employee/register/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 従業員情報の新規登録 6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 |
17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | 一覧に戻る 33 | 34 |
35 |
36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/templates/employee/register/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 従業員情報の登録の完了 6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |

16 | 登録完了しました。 17 |

18 |
19 | 20 | 24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/templates/employee/update/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 従業員情報の変更 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 |
従業員番号9999
20 |
21 | 22 |
23 | 24 |
25 |
29 |
31 | 32 | 33 |
34 | 35 |
37 | 38 | 39 |
40 | 41 |
43 | 44 | 45 |
46 | 47 |
48 | 詳細に戻る 50 | 51 |
52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | フォームフィールド 6 | 7 | 8 | 9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ページヘッダー 6 | 7 | 8 |
9 |
10 |
11 |

12 |

13 |
14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 入力フィールドの部品 6 | 7 | 8 | 9 | 10 |
13 | 14 |
15 | 16 |
17 |
18 |
19 |

20 | 23 |

24 |

26 | エラー 27 |

28 |
29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | (title) 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/navigation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ナビゲーションバー 6 | 7 | 8 |
9 | 33 |
34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/radio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ラジオボタンのセット 6 | 7 | 8 | 9 |
12 | 13 | 14 |
15 |
16 | 19 | 22 |
23 |
24 | 25 |
27 | 性別 入力エラー 28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/templates/wage/confirm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 時給の登録の確認 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 36 | 37 | 38 |
氏名 19 | 21 |
開始日 26 | 28 |
時給 33 | 35 |
39 | 40 |
41 | 42 | 43 |
44 |
45 |
46 | 47 |
48 | 49 | 50 |
51 |
52 | 53 | -------------------------------------------------------------------------------- /src/main/resources/templates/wage/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 時給の登録 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 |
氏名テスト太郎
20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 |
28 |

29 | 31 |

32 |
33 |
34 | 35 |
36 |
37 |
38 |

39 | 41 |

42 |

43 | 44 |

45 |
46 |
47 |
48 | 49 |
50 | 時給の履歴に戻る 52 | 53 |
54 |
55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/resources/templates/wage/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 時給の履歴 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
従業員番号1
氏名テスト太郎
24 |
25 | 26 |
27 | 28 |
29 | 30 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
開始日付時給
2018/12/01937
53 |
54 | 55 |
56 |
57 | 詳細に戻る 59 |
60 |
61 |
62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /src/main/resources/templates/wage/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 時給の登録の完了 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 |

時給を登録しました。

17 |
18 |
19 |
20 | 21 |
22 | 24 | 時給の履歴に戻る 25 | 26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /src/test/java/example/BootUpTest.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import jig.erd.JigErd; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | import javax.sql.DataSource; 9 | 10 | @SpringBootTest 11 | public class BootUpTest { 12 | 13 | @Test 14 | void 起動確認(@Autowired DataSource dataSource) { 15 | JigErd.run(dataSource); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/example/application/service/EmployeeRecordServiceTest.java: -------------------------------------------------------------------------------- 1 | package example.application.service; 2 | 3 | import example.application.coordinator.employee.EmployeeRecordCoordinator; 4 | import example.application.service.employee.EmployeeQueryService; 5 | import example.domain.model.employee.*; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertAll; 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | 13 | @SpringBootTest 14 | class EmployeeRecordServiceTest { 15 | @Autowired 16 | EmployeeRecordCoordinator sut; 17 | @Autowired 18 | EmployeeQueryService query; 19 | 20 | @Test 21 | void list() { 22 | Employee employee = query.contractingEmployees().list().stream().filter( 23 | us -> us.employeeNumber().value().equals(1)).findFirst().get(); 24 | assertAll( 25 | () -> assertEquals(employee.mailAddress().toString(), "fukawa_teruyoshi_new@example.com"), 26 | () -> assertEquals(employee.phoneNumber().toString(), "03-1234-9999"), 27 | () -> assertEquals(employee.name().toString(), "布川 光義")); 28 | } 29 | 30 | @Test 31 | void findById() { 32 | Employee employee = query.choose(new EmployeeNumber(1)); 33 | assertAll( 34 | () -> assertEquals(employee.mailAddress().toString(), "fukawa_teruyoshi_new@example.com"), 35 | () -> assertEquals(employee.phoneNumber().toString(), "03-1234-9999"), 36 | () -> assertEquals(employee.name().toString(), "布川 光義")); 37 | } 38 | 39 | @Test 40 | void registerAndDelete() { 41 | Name name = new Name("Eiji Yamane"); 42 | PhoneNumber phoneNumber = new PhoneNumber("090-6559-1234"); 43 | MailAddress mailAddress = new MailAddress("hogehoge_hogeo@example.com"); 44 | 45 | EmployeeNumber employeeNumber = sut.register(new EmployeeToRegister(name, mailAddress, phoneNumber)); 46 | 47 | Employee foundEmployee = query.choose(employeeNumber); 48 | assertAll( 49 | () -> assertEquals(foundEmployee.name().toString(), name.toString()), 50 | () -> assertEquals(foundEmployee.phoneNumber().toString(), phoneNumber.toString()), 51 | () -> assertEquals(foundEmployee.mailAddress().toString(), mailAddress.toString()) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/example/domain/model/payroll/PayrollTest.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.payroll; 2 | 3 | import example.domain.model.attendance.Attendance; 4 | import example.domain.model.timerecord.evaluation.TimeRecords; 5 | import example.domain.model.contract.Contract; 6 | import example.domain.model.contract.ContractCondition; 7 | import example.domain.model.contract.ContractConditions; 8 | import example.domain.model.contract.wage.BaseHourlyWage; 9 | import example.domain.model.contract.wage.WageCondition; 10 | import example.domain.model.timerecord.evaluation.ActualWorkDateTime; 11 | import example.domain.model.timerecord.evaluation.TimeRecord; 12 | import example.presentation.controller.timerecord.AttendanceForm; 13 | import org.junit.jupiter.params.ParameterizedTest; 14 | import org.junit.jupiter.params.provider.CsvSource; 15 | 16 | import java.math.BigDecimal; 17 | import java.util.List; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | class PayrollTest { 22 | @ParameterizedTest 23 | @CsvSource({ 24 | // 通常 25 | "09:00, 10:00, 0, 0, 1000, 1000", 26 | // 深夜 27 | "23:00, 24:00, 0, 0, 1000, 1350", 28 | // 深夜(早朝) 29 | "01:00, 03:00, 0, 0, 1000, 2700", 30 | // 通常+深夜+休憩 31 | "20:00, 24:00, 30, 60, 1000, 2850", 32 | // 通常10時間(超過2時間) 33 | "09:00, 19:00, 0, 0, 1000, 10500", 34 | // 通常13時間+深夜1時間(超過6時間) 35 | "8:00, 24:00, 60, 60, 1000, 15850", 36 | // 通常17時間+深夜7時間(超過16時間) 37 | "0:00, 24:00, 0, 0, 1000, 30450" 38 | }) 39 | static void 割増含めた賃金計算ができる(String begin, String end, String breakMinute, String nightBreakMinute, int hourlyWage, int expected) { 40 | ActualWorkDateTime actualWorkDateTime = AttendanceForm.toActualWorkDateTime("2018-11-25", begin, end, breakMinute, nightBreakMinute); 41 | WageCondition wageCondition = new WageCondition(new BaseHourlyWage(hourlyWage)); 42 | 43 | ContractConditions contractConditions = new ContractConditions(List.of(new ContractCondition(null, wageCondition))); 44 | 45 | Contract contract = new Contract(null, contractConditions); 46 | TimeRecord timeRecord = new TimeRecord(null, actualWorkDateTime, null); 47 | TimeRecords timeRecords = new TimeRecords(List.of(timeRecord)); 48 | Attendance attendance = new Attendance(null, timeRecords); 49 | Attendance beforeMonthAttendance = new Attendance(null, new TimeRecords(List.of())); 50 | Payroll payroll = new Payroll(contract, attendance, beforeMonthAttendance); 51 | 52 | PaymentAmount paymentAmount = payroll.totalPayment(); 53 | 54 | assertEquals(expected, new BigDecimal(paymentAmount.value.value()).intValue()); 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/example/domain/model/timerecord/ActualWorkTimeTest.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord; 2 | 3 | import example.domain.model.timerecord.evaluation.ActualWorkDateTime; 4 | import example.presentation.controller.timerecord.AttendanceForm; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertAll; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | class ActualWorkTimeTest { 13 | 14 | @ParameterizedTest 15 | @CsvSource({ 16 | "9:00, 18:00, 60, 8時間0分", 17 | // 休憩時間 18 | "9:00, 18:00, 61, 7時間45分", 19 | "9:00, 18:00, 75, 7時間45分", 20 | "9:00, 18:00, 76, 7時間30分", 21 | // 始業時間 22 | "9:01, 18:00, 60, 7時間45分", 23 | "9:15, 18:00, 60, 7時間45分", 24 | "9:16, 18:00, 60, 7時間30分", 25 | // 終業時間 26 | "9:00, 18:01, 60, 8時間0分", 27 | "9:00, 18:14, 60, 8時間0分", 28 | "9:00, 18:15, 60, 8時間15分", 29 | // 組み合わせ 30 | "9:16, 18:00, 76, 7時間0分", 31 | "9:01, 18:01, 59, 7時間45分", 32 | "0:00, 24:00, 60, 16時間0分", 33 | "9:00, 33:00, 60, 16時間0分", 34 | }) 35 | void 日中作業時間が計算できる(String begin, String end, String breaks, String expected) { 36 | ActualWorkDateTime sut = AttendanceForm.toActualWorkDateTime("2018-11-25", begin, end, breaks, "0"); 37 | assertEquals(expected, sut.daytimeWorkTime().toString()); 38 | } 39 | 40 | @ParameterizedTest 41 | @CsvSource({ 42 | "1:00, 2:00, 0, 1時間0分", 43 | "22:00, 23:00, 0, 1時間0分", 44 | "18:00, 27:00, 60, 4時間0分", 45 | "8:00, 17:00, 0, 0時間0分", 46 | "0:00, 24:00, 0, 7時間0分", 47 | "23:00, 24:00, 0, 1時間0分", 48 | }) 49 | void 深夜作業時間が計算できる(String begin, String end, String breaks, String expected) { 50 | ActualWorkDateTime sut = AttendanceForm.toActualWorkDateTime("2018-11-25", begin, end, "0", breaks); 51 | assertEquals(expected, sut.nightWorkTime().toString()); 52 | } 53 | 54 | @Test 55 | void 作業時間が計算できる() { 56 | ActualWorkDateTime sut = AttendanceForm.toActualWorkDateTime("2018-11-25", "8:00", "24:00", "120", "30"); 57 | assertAll( 58 | () -> assertEquals("12時間0分", sut.daytimeWorkTime().toString()) 59 | , () -> assertEquals("1時間30分", sut.nightWorkTime().toString()) 60 | ); 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/java/example/domain/model/timerecord/evaluation/ActualWorkDateTimeTest.java: -------------------------------------------------------------------------------- 1 | package example.domain.model.timerecord.evaluation; 2 | 3 | import example.domain.model.employee.EmployeeNumber; 4 | import example.presentation.controller.timerecord.AttendanceForm; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | class ActualWorkDateTimeTest { 13 | 14 | @Test 15 | void 労働時間が週40時間を超えていない場合の1日あたりの法定時間外労働時間を計算できる() { 16 | EmployeeNumber en = new EmployeeNumber(1); 17 | List list = Arrays.asList( 18 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2019-12-27", "9:00", "18:00", "60", "0")), 19 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2019-12-28", "9:00", "18:00", "60", "0")), 20 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2019-12-29", "9:00", "18:00", "60", "0")), 21 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2019-12-30", "9:00", "20:00", "60", "0"))); 22 | WorkDate workDate = WorkDate.from("2019-12-30"); 23 | OverLegalHoursWorkTime overLegalHoursWorkTime = OverLegalHoursWorkTime.from( 24 | new MonthlyTimeRecord(new TimeRecords(list)), 25 | new BeforeMonthlyTimeRecord(new TimeRecords(List.of())), 26 | workDate); 27 | 28 | assertEquals("120", overLegalHoursWorkTime.quarterHour().minute().toString()); 29 | } 30 | 31 | @Test 32 | void 労働時間が週40時間を超えている場合の1日あたりの法定時間外労働時間を計算できる() { 33 | EmployeeNumber en = new EmployeeNumber(1); 34 | List list = Arrays.asList( 35 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2020-05-25", "9:00", "18:00", "60", "0")), 36 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2020-05-26", "9:00", "20:00", "60", "0")), 37 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2020-05-27", "9:00", "17:00", "60", "0")), 38 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2020-05-28", "9:00", "20:00", "60", "0")), 39 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2020-05-29", "9:00", "18:00", "60", "0")), 40 | new TimeRecord(en, AttendanceForm.toActualWorkDateTime("2020-05-30", "9:00", "19:00", "60", "0"))); 41 | WorkDate workDate = WorkDate.from("2020-05-30"); 42 | OverLegalHoursWorkTime overLegalHoursWorkTime = OverLegalHoursWorkTime.from( 43 | new MonthlyTimeRecord(new TimeRecords(list)), 44 | new BeforeMonthlyTimeRecord(new TimeRecords(List.of())), 45 | workDate); 46 | 47 | assertEquals("480", overLegalHoursWorkTime.quarterHour().minute().toString()); 48 | } 49 | } -------------------------------------------------------------------------------- /src/test/java/example/presentation/controller/wage/WageRegisterControllerTest.java: -------------------------------------------------------------------------------- 1 | package example.presentation.controller.wage; 2 | 3 | import example.application.repository.ContractRepository; 4 | import example.application.repository.EmployeeRepository; 5 | import example.domain.model.employee.Employee; 6 | import example.domain.model.employee.EmployeeNumber; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | 15 | import static org.mockito.ArgumentMatchers.any; 16 | import static org.mockito.Mockito.when; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; 21 | 22 | @SpringBootTest 23 | @AutoConfigureMockMvc 24 | class WageRegisterControllerTest { 25 | 26 | @Autowired 27 | MockMvc mockMvc; 28 | 29 | @MockBean 30 | EmployeeRepository employeeRepository; 31 | 32 | @MockBean 33 | ContractRepository contractRepository; 34 | 35 | @BeforeEach 36 | void setup() { 37 | when(employeeRepository.choose(any())).thenReturn(new Employee(new EmployeeNumber(1), null, null, null)); 38 | } 39 | 40 | @Test 41 | void 登録画面が開ける() throws Exception { 42 | mockMvc.perform(get("/wages/1/register")) 43 | .andExpect(view().name("wage/form")); 44 | } 45 | 46 | @Test 47 | void 登録で確認画面に遷移する() throws Exception { 48 | mockMvc.perform( 49 | post("/wages/1/register/confirm") 50 | .param("effectiveDate", "2011-11-11") 51 | .param("baseHourlyWage", "1234")) 52 | .andExpect(view().name("wage/confirm")); 53 | } 54 | 55 | @Test 56 | void 戻るで登録画面に遷移する() throws Exception { 57 | mockMvc.perform( 58 | post("/wages/1/register/again") 59 | .param("effectiveDate", "2011-11-11") 60 | .param("baseHourlyWage", "1234")) 61 | .andExpect(view().name("wage/form")); 62 | } 63 | 64 | @Test 65 | void 確認OKで完了画面に遷移する() throws Exception { 66 | mockMvc.perform( 67 | post("/wages/1/register/register") 68 | .param("effectiveDate", "2011-11-11") 69 | .param("baseHourlyWage", "1234")) 70 | .andExpect(redirectedUrl("/wages/1/register/completed")); 71 | } 72 | } -------------------------------------------------------------------------------- /src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.initialize=true 2 | spring.datasource.sql-script-encoding=utf-8 3 | -------------------------------------------------------------------------------- /src/test/resources/jig.properties: -------------------------------------------------------------------------------- 1 | jig.erd.output.directory=./build 2 | #jig.erd.output.prefix=jig-erd 3 | jig.erd.output.format=png 4 | #jig.erd.output.rankdir=LR 5 | -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=11 2 | --------------------------------------------------------------------------------