@k2works/claude-code-booster 6.2.0 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (618) hide show
  1. package/lib/assets/.claude/skills/practicing-getting-start-tdd/SKILL.md +3 -7
  2. package/lib/assets/docs/article/index.md +17 -41
  3. package/lib/assets/docs/index.md +1 -1
  4. package/lib/assets/mkdocs.yml +0 -6
  5. package/package.json +1 -1
  6. package/lib/assets/docs/article/functional-desgin-ppp/all/01-immutability-and-data-transformation.md +0 -484
  7. package/lib/assets/docs/article/functional-desgin-ppp/all/02-function-composition.md +0 -528
  8. package/lib/assets/docs/article/functional-desgin-ppp/all/03-polymorphism.md +0 -546
  9. package/lib/assets/docs/article/functional-desgin-ppp/all/04-data-validation.md +0 -309
  10. package/lib/assets/docs/article/functional-desgin-ppp/all/05-property-based-testing.md +0 -329
  11. package/lib/assets/docs/article/functional-desgin-ppp/all/06-tdd-and-functional.md +0 -507
  12. package/lib/assets/docs/article/functional-desgin-ppp/all/07-composite-pattern.md +0 -307
  13. package/lib/assets/docs/article/functional-desgin-ppp/all/08-decorator-pattern.md +0 -300
  14. package/lib/assets/docs/article/functional-desgin-ppp/all/09-adapter-pattern.md +0 -345
  15. package/lib/assets/docs/article/functional-desgin-ppp/all/10-strategy-pattern.md +0 -312
  16. package/lib/assets/docs/article/functional-desgin-ppp/all/11-command-pattern.md +0 -295
  17. package/lib/assets/docs/article/functional-desgin-ppp/all/12-visitor-pattern.md +0 -331
  18. package/lib/assets/docs/article/functional-desgin-ppp/all/13-abstract-factory-pattern.md +0 -328
  19. package/lib/assets/docs/article/functional-desgin-ppp/all/14-abstract-server-pattern.md +0 -374
  20. package/lib/assets/docs/article/functional-desgin-ppp/all/15-gossiping-bus-drivers.md +0 -165
  21. package/lib/assets/docs/article/functional-desgin-ppp/all/16-payroll-system.md +0 -187
  22. package/lib/assets/docs/article/functional-desgin-ppp/all/17-video-rental-system.md +0 -321
  23. package/lib/assets/docs/article/functional-desgin-ppp/all/18-concurrency-system.md +0 -296
  24. package/lib/assets/docs/article/functional-desgin-ppp/all/19-wa-tor-simulation.md +0 -295
  25. package/lib/assets/docs/article/functional-desgin-ppp/all/20-pattern-interactions.md +0 -283
  26. package/lib/assets/docs/article/functional-desgin-ppp/all/21-best-practices.md +0 -303
  27. package/lib/assets/docs/article/functional-desgin-ppp/all/22-oo-to-fp-migration.md +0 -346
  28. package/lib/assets/docs/article/functional-desgin-ppp/all/index.md +0 -388
  29. package/lib/assets/docs/article/functional-desgin-ppp/clojure/01-immutability-and-data-transformation.md +0 -282
  30. package/lib/assets/docs/article/functional-desgin-ppp/clojure/02-function-composition.md +0 -389
  31. package/lib/assets/docs/article/functional-desgin-ppp/clojure/03-polymorphism.md +0 -393
  32. package/lib/assets/docs/article/functional-desgin-ppp/clojure/04-clojure-spec.md +0 -359
  33. package/lib/assets/docs/article/functional-desgin-ppp/clojure/05-property-based-testing.md +0 -361
  34. package/lib/assets/docs/article/functional-desgin-ppp/clojure/06-tdd-in-functional.md +0 -392
  35. package/lib/assets/docs/article/functional-desgin-ppp/clojure/07-composite-pattern.md +0 -538
  36. package/lib/assets/docs/article/functional-desgin-ppp/clojure/08-decorator-pattern.md +0 -404
  37. package/lib/assets/docs/article/functional-desgin-ppp/clojure/09-adapter-pattern.md +0 -408
  38. package/lib/assets/docs/article/functional-desgin-ppp/clojure/10-strategy-pattern.md +0 -494
  39. package/lib/assets/docs/article/functional-desgin-ppp/clojure/11-command-pattern.md +0 -575
  40. package/lib/assets/docs/article/functional-desgin-ppp/clojure/12-visitor-pattern.md +0 -576
  41. package/lib/assets/docs/article/functional-desgin-ppp/clojure/13-abstract-factory-pattern.md +0 -484
  42. package/lib/assets/docs/article/functional-desgin-ppp/clojure/14-abstract-server-pattern.md +0 -471
  43. package/lib/assets/docs/article/functional-desgin-ppp/clojure/15-gossiping-bus-drivers.md +0 -334
  44. package/lib/assets/docs/article/functional-desgin-ppp/clojure/16-payroll-system.md +0 -410
  45. package/lib/assets/docs/article/functional-desgin-ppp/clojure/17-video-rental-system.md +0 -459
  46. package/lib/assets/docs/article/functional-desgin-ppp/clojure/18-concurrency-system.md +0 -484
  47. package/lib/assets/docs/article/functional-desgin-ppp/clojure/19-wator-simulation.md +0 -748
  48. package/lib/assets/docs/article/functional-desgin-ppp/clojure/20-pattern-interactions.md +0 -576
  49. package/lib/assets/docs/article/functional-desgin-ppp/clojure/21-best-practices.md +0 -527
  50. package/lib/assets/docs/article/functional-desgin-ppp/clojure/22-oo-to-fp-migration.md +0 -541
  51. package/lib/assets/docs/article/functional-desgin-ppp/clojure/index.md +0 -241
  52. package/lib/assets/docs/article/functional-desgin-ppp/elixir/01-immutability-and-data-transformation.md +0 -392
  53. package/lib/assets/docs/article/functional-desgin-ppp/elixir/02-function-composition.md +0 -383
  54. package/lib/assets/docs/article/functional-desgin-ppp/elixir/03-polymorphism.md +0 -384
  55. package/lib/assets/docs/article/functional-desgin-ppp/elixir/04-data-validation.md +0 -204
  56. package/lib/assets/docs/article/functional-desgin-ppp/elixir/05-property-based-testing.md +0 -277
  57. package/lib/assets/docs/article/functional-desgin-ppp/elixir/06-tdd-and-fp.md +0 -303
  58. package/lib/assets/docs/article/functional-desgin-ppp/elixir/07-effects-and-pure-functions.md +0 -173
  59. package/lib/assets/docs/article/functional-desgin-ppp/elixir/08-error-handling-strategies.md +0 -177
  60. package/lib/assets/docs/article/functional-desgin-ppp/elixir/09-io-and-external-systems.md +0 -263
  61. package/lib/assets/docs/article/functional-desgin-ppp/elixir/10-concurrency-patterns.md +0 -278
  62. package/lib/assets/docs/article/functional-desgin-ppp/elixir/11-command-pattern.md +0 -157
  63. package/lib/assets/docs/article/functional-desgin-ppp/elixir/12-visitor-pattern.md +0 -185
  64. package/lib/assets/docs/article/functional-desgin-ppp/elixir/13-abstract-factory-pattern.md +0 -613
  65. package/lib/assets/docs/article/functional-desgin-ppp/elixir/14-abstract-server-pattern.md +0 -738
  66. package/lib/assets/docs/article/functional-desgin-ppp/elixir/15-gossiping-bus-drivers.md +0 -300
  67. package/lib/assets/docs/article/functional-desgin-ppp/elixir/16-payroll-system.md +0 -429
  68. package/lib/assets/docs/article/functional-desgin-ppp/elixir/17-video-rental-system.md +0 -328
  69. package/lib/assets/docs/article/functional-desgin-ppp/elixir/18-concurrency-system.md +0 -475
  70. package/lib/assets/docs/article/functional-desgin-ppp/elixir/19-wator-simulation.md +0 -532
  71. package/lib/assets/docs/article/functional-desgin-ppp/elixir/20-pattern-interactions.md +0 -296
  72. package/lib/assets/docs/article/functional-desgin-ppp/elixir/21-best-practices.md +0 -349
  73. package/lib/assets/docs/article/functional-desgin-ppp/elixir/22-oo-to-fp-migration.md +0 -404
  74. package/lib/assets/docs/article/functional-desgin-ppp/elixir/index.md +0 -248
  75. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/01-immutability-and-data-transformation.md +0 -393
  76. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/02-function-composition.md +0 -461
  77. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/03-polymorphism.md +0 -504
  78. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/04-data-validation.md +0 -425
  79. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/05-property-based-testing.md +0 -391
  80. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/06-tdd-functional.md +0 -696
  81. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/07-composite-pattern.md +0 -451
  82. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/08-decorator-pattern.md +0 -488
  83. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/09-adapter-pattern.md +0 -488
  84. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/10-strategy-pattern.md +0 -436
  85. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/11-command-pattern.md +0 -437
  86. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/12-visitor-pattern.md +0 -348
  87. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/13-abstract-factory-pattern.md +0 -318
  88. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/14-abstract-server-pattern.md +0 -605
  89. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/15-gossiping-bus-drivers.md +0 -364
  90. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/16-payroll-system.md +0 -359
  91. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/17-video-rental-system.md +0 -423
  92. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/18-concurrency-system.md +0 -376
  93. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/19-wator-simulation.md +0 -412
  94. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/20-pattern-interactions.md +0 -300
  95. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/21-best-practices.md +0 -333
  96. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/22-oo-to-fp-migration.md +0 -341
  97. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/index.md +0 -274
  98. package/lib/assets/docs/article/functional-desgin-ppp/haskell/01-immutability-and-data-transformation.md +0 -307
  99. package/lib/assets/docs/article/functional-desgin-ppp/haskell/02-function-composition.md +0 -313
  100. package/lib/assets/docs/article/functional-desgin-ppp/haskell/03-polymorphism.md +0 -371
  101. package/lib/assets/docs/article/functional-desgin-ppp/haskell/04-data-validation.md +0 -266
  102. package/lib/assets/docs/article/functional-desgin-ppp/haskell/05-property-based-testing.md +0 -263
  103. package/lib/assets/docs/article/functional-desgin-ppp/haskell/06-tdd-functional.md +0 -292
  104. package/lib/assets/docs/article/functional-desgin-ppp/haskell/07-composite-pattern.md +0 -404
  105. package/lib/assets/docs/article/functional-desgin-ppp/haskell/08-decorator-pattern.md +0 -328
  106. package/lib/assets/docs/article/functional-desgin-ppp/haskell/09-adapter-pattern.md +0 -391
  107. package/lib/assets/docs/article/functional-desgin-ppp/haskell/10-strategy-pattern.md +0 -296
  108. package/lib/assets/docs/article/functional-desgin-ppp/haskell/11-command-pattern.md +0 -312
  109. package/lib/assets/docs/article/functional-desgin-ppp/haskell/12-visitor-pattern.md +0 -335
  110. package/lib/assets/docs/article/functional-desgin-ppp/haskell/13-abstract-factory-pattern.md +0 -341
  111. package/lib/assets/docs/article/functional-desgin-ppp/haskell/14-abstract-server-pattern.md +0 -388
  112. package/lib/assets/docs/article/functional-desgin-ppp/haskell/15-gossiping-bus-drivers.md +0 -186
  113. package/lib/assets/docs/article/functional-desgin-ppp/haskell/16-payroll-system.md +0 -228
  114. package/lib/assets/docs/article/functional-desgin-ppp/haskell/17-video-rental-system.md +0 -253
  115. package/lib/assets/docs/article/functional-desgin-ppp/haskell/18-concurrency-system.md +0 -372
  116. package/lib/assets/docs/article/functional-desgin-ppp/haskell/19-wator-simulation.md +0 -447
  117. package/lib/assets/docs/article/functional-desgin-ppp/haskell/20-pattern-interactions.md +0 -334
  118. package/lib/assets/docs/article/functional-desgin-ppp/haskell/21-best-practices.md +0 -412
  119. package/lib/assets/docs/article/functional-desgin-ppp/haskell/22-oo-to-fp-migration.md +0 -478
  120. package/lib/assets/docs/article/functional-desgin-ppp/haskell/index.md +0 -174
  121. package/lib/assets/docs/article/functional-desgin-ppp/index.md +0 -90
  122. package/lib/assets/docs/article/functional-desgin-ppp/rust/01-immutability-and-data-transformation.md +0 -459
  123. package/lib/assets/docs/article/functional-desgin-ppp/rust/02-function-composition.md +0 -472
  124. package/lib/assets/docs/article/functional-desgin-ppp/rust/03-polymorphism.md +0 -434
  125. package/lib/assets/docs/article/functional-desgin-ppp/rust/04-data-validation.md +0 -282
  126. package/lib/assets/docs/article/functional-desgin-ppp/rust/05-property-based-testing.md +0 -256
  127. package/lib/assets/docs/article/functional-desgin-ppp/rust/06-tdd-and-functional.md +0 -850
  128. package/lib/assets/docs/article/functional-desgin-ppp/rust/07-composite-pattern.md +0 -393
  129. package/lib/assets/docs/article/functional-desgin-ppp/rust/08-decorator-pattern.md +0 -392
  130. package/lib/assets/docs/article/functional-desgin-ppp/rust/09-adapter-pattern.md +0 -348
  131. package/lib/assets/docs/article/functional-desgin-ppp/rust/10-strategy-pattern.md +0 -340
  132. package/lib/assets/docs/article/functional-desgin-ppp/rust/11-command-pattern.md +0 -365
  133. package/lib/assets/docs/article/functional-desgin-ppp/rust/12-visitor-pattern.md +0 -388
  134. package/lib/assets/docs/article/functional-desgin-ppp/rust/13-abstract-factory-pattern.md +0 -370
  135. package/lib/assets/docs/article/functional-desgin-ppp/rust/14-abstract-server-pattern.md +0 -401
  136. package/lib/assets/docs/article/functional-desgin-ppp/rust/15-gossiping-bus-drivers.md +0 -309
  137. package/lib/assets/docs/article/functional-desgin-ppp/rust/16-payroll-system.md +0 -306
  138. package/lib/assets/docs/article/functional-desgin-ppp/rust/17-video-rental-system.md +0 -313
  139. package/lib/assets/docs/article/functional-desgin-ppp/rust/18-concurrency-system.md +0 -324
  140. package/lib/assets/docs/article/functional-desgin-ppp/rust/19-wator-simulation.md +0 -320
  141. package/lib/assets/docs/article/functional-desgin-ppp/rust/20-pattern-interactions.md +0 -313
  142. package/lib/assets/docs/article/functional-desgin-ppp/rust/21-best-practices.md +0 -345
  143. package/lib/assets/docs/article/functional-desgin-ppp/rust/22-oo-to-fp-migration.md +0 -358
  144. package/lib/assets/docs/article/functional-desgin-ppp/rust/index.md +0 -243
  145. package/lib/assets/docs/article/functional-desgin-ppp/scala/01-immutability-and-data-transformation.md +0 -337
  146. package/lib/assets/docs/article/functional-desgin-ppp/scala/02-function-composition.md +0 -357
  147. package/lib/assets/docs/article/functional-desgin-ppp/scala/03-polymorphism.md +0 -366
  148. package/lib/assets/docs/article/functional-desgin-ppp/scala/04-data-validation.md +0 -373
  149. package/lib/assets/docs/article/functional-desgin-ppp/scala/05-property-based-testing.md +0 -524
  150. package/lib/assets/docs/article/functional-desgin-ppp/scala/06-tdd-functional.md +0 -566
  151. package/lib/assets/docs/article/functional-desgin-ppp/scala/07-composite-pattern.md +0 -372
  152. package/lib/assets/docs/article/functional-desgin-ppp/scala/08-decorator-pattern.md +0 -336
  153. package/lib/assets/docs/article/functional-desgin-ppp/scala/09-adapter-pattern.md +0 -526
  154. package/lib/assets/docs/article/functional-desgin-ppp/scala/10-strategy-pattern.md +0 -450
  155. package/lib/assets/docs/article/functional-desgin-ppp/scala/11-command-pattern.md +0 -416
  156. package/lib/assets/docs/article/functional-desgin-ppp/scala/12-visitor-pattern.md +0 -388
  157. package/lib/assets/docs/article/functional-desgin-ppp/scala/13-abstract-factory-pattern.md +0 -407
  158. package/lib/assets/docs/article/functional-desgin-ppp/scala/14-abstract-server-pattern.md +0 -485
  159. package/lib/assets/docs/article/functional-desgin-ppp/scala/15-gossiping-bus-drivers.md +0 -400
  160. package/lib/assets/docs/article/functional-desgin-ppp/scala/16-payroll-system.md +0 -351
  161. package/lib/assets/docs/article/functional-desgin-ppp/scala/17-video-rental-system.md +0 -333
  162. package/lib/assets/docs/article/functional-desgin-ppp/scala/18-concurrency-system.md +0 -739
  163. package/lib/assets/docs/article/functional-desgin-ppp/scala/19-wator-simulation.md +0 -633
  164. package/lib/assets/docs/article/functional-desgin-ppp/scala/20-pattern-interactions.md +0 -521
  165. package/lib/assets/docs/article/functional-desgin-ppp/scala/21-best-practices.md +0 -442
  166. package/lib/assets/docs/article/functional-desgin-ppp/scala/22-oo-to-fp-migration.md +0 -697
  167. package/lib/assets/docs/article/functional-desgin-ppp/scala/index.md +0 -243
  168. package/lib/assets/docs/article/getting-start-docker-kubernetes/01-container-and-docker-basics.md +0 -341
  169. package/lib/assets/docs/article/getting-start-docker-kubernetes/02-container-deployment.md +0 -785
  170. package/lib/assets/docs/article/getting-start-docker-kubernetes/03-practical-container-build-deploy.md +0 -466
  171. package/lib/assets/docs/article/getting-start-docker-kubernetes/04-multi-container-application.md +0 -878
  172. package/lib/assets/docs/article/getting-start-docker-kubernetes/05-kubernetes-introduction.md +0 -585
  173. package/lib/assets/docs/article/getting-start-docker-kubernetes/06-kubernetes-deploy-cluster.md +0 -620
  174. package/lib/assets/docs/article/getting-start-docker-kubernetes/07-kubernetes-advanced.md +0 -621
  175. package/lib/assets/docs/article/getting-start-docker-kubernetes/08-kubernetes-packaging.md +0 -518
  176. package/lib/assets/docs/article/getting-start-docker-kubernetes/09-container-operations.md +0 -546
  177. package/lib/assets/docs/article/getting-start-docker-kubernetes/10-optimal-container-image.md +0 -640
  178. package/lib/assets/docs/article/getting-start-docker-kubernetes/11-continuous-delivery.md +0 -607
  179. package/lib/assets/docs/article/getting-start-docker-kubernetes/12-container-use-cases.md +0 -463
  180. package/lib/assets/docs/article/getting-start-docker-kubernetes/13-case-monolith-compose-vs-kustomize.md +0 -346
  181. package/lib/assets/docs/article/getting-start-docker-kubernetes/14-case-event-driven-kustomize-vs-helm.md +0 -324
  182. package/lib/assets/docs/article/getting-start-docker-kubernetes/15-case-escqrs-axon-kustomize-vs-helm.md +0 -297
  183. package/lib/assets/docs/article/getting-start-docker-kubernetes/16-case-escqrs-kafka-kustomize-vs-helm.md +0 -256
  184. package/lib/assets/docs/article/getting-start-docker-kubernetes/17-case-comparison-summary.md +0 -141
  185. package/lib/assets/docs/article/getting-start-docker-kubernetes/18-case-load-testing.md +0 -303
  186. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-a-dev-tools-setup.md +0 -274
  187. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-b-orchestration-environments.md +0 -530
  188. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-c-tips.md +0 -519
  189. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-01-comic.png +0 -0
  190. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-02-comic.png +0 -0
  191. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-03-comic.png +0 -0
  192. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-04-comic.png +0 -0
  193. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-05-comic.png +0 -0
  194. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-06-comic.png +0 -0
  195. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-07-comic.png +0 -0
  196. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-08-comic.png +0 -0
  197. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-09-comic.png +0 -0
  198. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-10-comic.png +0 -0
  199. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-11-comic.png +0 -0
  200. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-12-comic.png +0 -0
  201. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/overview-comic.png +0 -0
  202. package/lib/assets/docs/article/getting-start-docker-kubernetes/index.md +0 -82
  203. package/lib/assets/docs/article/getting-start-docker-kubernetes/outline.md +0 -220
  204. package/lib/assets/docs/article/getting-start-tdd/clojure/01-todo-list-and-first-test.md +0 -175
  205. package/lib/assets/docs/article/getting-start-tdd/clojure/02-fake-it-and-triangulation.md +0 -171
  206. package/lib/assets/docs/article/getting-start-tdd/clojure/03-obvious-implementation-and-refactoring.md +0 -144
  207. package/lib/assets/docs/article/getting-start-tdd/clojure/04-version-control-and-conventional-commits.md +0 -97
  208. package/lib/assets/docs/article/getting-start-tdd/clojure/05-package-management-and-static-analysis.md +0 -308
  209. package/lib/assets/docs/article/getting-start-tdd/clojure/06-task-runner-and-ci-cd.md +0 -250
  210. package/lib/assets/docs/article/getting-start-tdd/clojure/07-protocols-and-records.md +0 -140
  211. package/lib/assets/docs/article/getting-start-tdd/clojure/08-multimethods-and-design-patterns.md +0 -139
  212. package/lib/assets/docs/article/getting-start-tdd/clojure/09-namespaces-and-module-design.md +0 -136
  213. package/lib/assets/docs/article/getting-start-tdd/clojure/10-higher-order-functions-and-composition.md +0 -123
  214. package/lib/assets/docs/article/getting-start-tdd/clojure/11-persistent-data-and-pipeline.md +0 -147
  215. package/lib/assets/docs/article/getting-start-tdd/clojure/12-error-handling-and-spec.md +0 -170
  216. package/lib/assets/docs/article/getting-start-tdd/clojure/index.md +0 -65
  217. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter01.md +0 -241
  218. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter02.md +0 -253
  219. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter03.md +0 -211
  220. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter04.md +0 -101
  221. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter05.md +0 -265
  222. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter06.md +0 -204
  223. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter07.md +0 -223
  224. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter08.md +0 -258
  225. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter09.md +0 -183
  226. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter10.md +0 -175
  227. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter11.md +0 -201
  228. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter12.md +0 -220
  229. package/lib/assets/docs/article/getting-start-tdd/csharp/index.md +0 -83
  230. package/lib/assets/docs/article/getting-start-tdd/elixir/01-todo-list-and-first-test.md +0 -96
  231. package/lib/assets/docs/article/getting-start-tdd/elixir/02-fake-it-and-triangulation.md +0 -104
  232. package/lib/assets/docs/article/getting-start-tdd/elixir/03-obvious-implementation-and-refactoring.md +0 -118
  233. package/lib/assets/docs/article/getting-start-tdd/elixir/04-version-control-and-conventional-commits.md +0 -105
  234. package/lib/assets/docs/article/getting-start-tdd/elixir/05-package-management-and-static-analysis.md +0 -97
  235. package/lib/assets/docs/article/getting-start-tdd/elixir/06-task-runner-and-ci-cd.md +0 -80
  236. package/lib/assets/docs/article/getting-start-tdd/elixir/07-structs-and-protocols.md +0 -119
  237. package/lib/assets/docs/article/getting-start-tdd/elixir/08-pattern-matching-and-guards.md +0 -117
  238. package/lib/assets/docs/article/getting-start-tdd/elixir/09-module-design-and-behaviours.md +0 -113
  239. package/lib/assets/docs/article/getting-start-tdd/elixir/10-higher-order-functions-and-pipeline.md +0 -187
  240. package/lib/assets/docs/article/getting-start-tdd/elixir/11-stream-and-lazy-evaluation.md +0 -151
  241. package/lib/assets/docs/article/getting-start-tdd/elixir/12-error-handling-and-with.md +0 -154
  242. package/lib/assets/docs/article/getting-start-tdd/elixir/index.md +0 -35
  243. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter01.md +0 -211
  244. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter02.md +0 -255
  245. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter03.md +0 -227
  246. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter04.md +0 -188
  247. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter05.md +0 -276
  248. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter06.md +0 -199
  249. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter07.md +0 -170
  250. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter08.md +0 -184
  251. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter09.md +0 -231
  252. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter10.md +0 -198
  253. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter11.md +0 -221
  254. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter12.md +0 -224
  255. package/lib/assets/docs/article/getting-start-tdd/fsharp/index.md +0 -71
  256. package/lib/assets/docs/article/getting-start-tdd/go/01-todo-list-and-first-test.md +0 -222
  257. package/lib/assets/docs/article/getting-start-tdd/go/02-fake-it-and-triangulation.md +0 -311
  258. package/lib/assets/docs/article/getting-start-tdd/go/03-obvious-implementation-and-refactoring.md +0 -348
  259. package/lib/assets/docs/article/getting-start-tdd/go/04-version-control-and-conventional-commits.md +0 -121
  260. package/lib/assets/docs/article/getting-start-tdd/go/05-package-management-and-static-analysis.md +0 -281
  261. package/lib/assets/docs/article/getting-start-tdd/go/06-task-runner-and-ci-cd.md +0 -242
  262. package/lib/assets/docs/article/getting-start-tdd/go/07-encapsulation-and-polymorphism.md +0 -402
  263. package/lib/assets/docs/article/getting-start-tdd/go/08-design-patterns.md +0 -430
  264. package/lib/assets/docs/article/getting-start-tdd/go/09-solid-principles-and-module-design.md +0 -408
  265. package/lib/assets/docs/article/getting-start-tdd/go/10-higher-order-functions-and-composition.md +0 -234
  266. package/lib/assets/docs/article/getting-start-tdd/go/11-immutable-data-and-pipeline.md +0 -304
  267. package/lib/assets/docs/article/getting-start-tdd/go/12-error-handling-and-type-safety.md +0 -419
  268. package/lib/assets/docs/article/getting-start-tdd/go/index.md +0 -83
  269. package/lib/assets/docs/article/getting-start-tdd/haskell/01-todo-list-and-first-test.md +0 -288
  270. package/lib/assets/docs/article/getting-start-tdd/haskell/02-fake-it-and-triangulation.md +0 -346
  271. package/lib/assets/docs/article/getting-start-tdd/haskell/03-obvious-implementation-and-refactoring.md +0 -266
  272. package/lib/assets/docs/article/getting-start-tdd/haskell/04-version-control-and-conventional-commits.md +0 -191
  273. package/lib/assets/docs/article/getting-start-tdd/haskell/05-package-management-and-static-analysis.md +0 -322
  274. package/lib/assets/docs/article/getting-start-tdd/haskell/06-task-runner-and-ci-cd.md +0 -318
  275. package/lib/assets/docs/article/getting-start-tdd/haskell/07-algebraic-data-types-and-type-classes.md +0 -421
  276. package/lib/assets/docs/article/getting-start-tdd/haskell/08-pattern-matching-and-guards.md +0 -399
  277. package/lib/assets/docs/article/getting-start-tdd/haskell/09-module-design-and-smart-constructors.md +0 -470
  278. package/lib/assets/docs/article/getting-start-tdd/haskell/10-higher-order-functions-and-currying.md +0 -443
  279. package/lib/assets/docs/article/getting-start-tdd/haskell/11-function-composition-and-point-free.md +0 -401
  280. package/lib/assets/docs/article/getting-start-tdd/haskell/12-monad-and-error-handling.md +0 -640
  281. package/lib/assets/docs/article/getting-start-tdd/haskell/index.md +0 -49
  282. package/lib/assets/docs/article/getting-start-tdd/index.md +0 -93
  283. package/lib/assets/docs/article/getting-start-tdd/integration/01-language-overview.md +0 -384
  284. package/lib/assets/docs/article/getting-start-tdd/integration/02-test-framework-comparison.md +0 -358
  285. package/lib/assets/docs/article/getting-start-tdd/integration/03-tdd-pattern-comparison.md +0 -454
  286. package/lib/assets/docs/article/getting-start-tdd/integration/04-type-system-comparison.md +0 -418
  287. package/lib/assets/docs/article/getting-start-tdd/integration/05-dev-environment-comparison.md +0 -339
  288. package/lib/assets/docs/article/getting-start-tdd/integration/06-learning-roadmap.md +0 -299
  289. package/lib/assets/docs/article/getting-start-tdd/integration/index.md +0 -69
  290. package/lib/assets/docs/article/getting-start-tdd/java/01-todo-list-and-first-test.md +0 -243
  291. package/lib/assets/docs/article/getting-start-tdd/java/02-fake-it-and-triangulation.md +0 -270
  292. package/lib/assets/docs/article/getting-start-tdd/java/03-obvious-implementation-and-refactoring.md +0 -194
  293. package/lib/assets/docs/article/getting-start-tdd/java/04-version-control-and-conventional-commits.md +0 -124
  294. package/lib/assets/docs/article/getting-start-tdd/java/05-package-management-and-static-analysis.md +0 -391
  295. package/lib/assets/docs/article/getting-start-tdd/java/06-task-runner-and-ci-cd.md +0 -281
  296. package/lib/assets/docs/article/getting-start-tdd/java/07-encapsulation-and-polymorphism.md +0 -635
  297. package/lib/assets/docs/article/getting-start-tdd/java/08-design-patterns.md +0 -402
  298. package/lib/assets/docs/article/getting-start-tdd/java/09-solid-principles-and-module-design.md +0 -319
  299. package/lib/assets/docs/article/getting-start-tdd/java/10-higher-order-functions-and-composition.md +0 -197
  300. package/lib/assets/docs/article/getting-start-tdd/java/11-immutable-data-and-pipeline.md +0 -176
  301. package/lib/assets/docs/article/getting-start-tdd/java/12-error-handling-and-type-safety.md +0 -214
  302. package/lib/assets/docs/article/getting-start-tdd/java/index.md +0 -61
  303. package/lib/assets/docs/article/getting-start-tdd/node/01-todo-list-and-first-test.md +0 -253
  304. package/lib/assets/docs/article/getting-start-tdd/node/02-fake-it-and-triangulation.md +0 -271
  305. package/lib/assets/docs/article/getting-start-tdd/node/03-obvious-implementation-and-refactoring.md +0 -178
  306. package/lib/assets/docs/article/getting-start-tdd/node/04-version-control-and-conventional-commits.md +0 -121
  307. package/lib/assets/docs/article/getting-start-tdd/node/05-package-management-and-static-analysis.md +0 -323
  308. package/lib/assets/docs/article/getting-start-tdd/node/06-task-runner-and-ci-cd.md +0 -244
  309. package/lib/assets/docs/article/getting-start-tdd/node/07-encapsulation-and-polymorphism.md +0 -336
  310. package/lib/assets/docs/article/getting-start-tdd/node/08-design-patterns.md +0 -331
  311. package/lib/assets/docs/article/getting-start-tdd/node/09-solid-principles-and-module-design.md +0 -294
  312. package/lib/assets/docs/article/getting-start-tdd/node/10-higher-order-functions-and-composition.md +0 -208
  313. package/lib/assets/docs/article/getting-start-tdd/node/11-immutable-data-and-pipeline.md +0 -216
  314. package/lib/assets/docs/article/getting-start-tdd/node/12-error-handling-and-type-safety.md +0 -304
  315. package/lib/assets/docs/article/getting-start-tdd/node/index.md +0 -56
  316. package/lib/assets/docs/article/getting-start-tdd/php/01-todo-list-and-first-test.md +0 -268
  317. package/lib/assets/docs/article/getting-start-tdd/php/02-fake-it-and-triangulation.md +0 -209
  318. package/lib/assets/docs/article/getting-start-tdd/php/03-obvious-implementation-and-refactoring.md +0 -257
  319. package/lib/assets/docs/article/getting-start-tdd/php/04-version-control-and-conventional-commits.md +0 -150
  320. package/lib/assets/docs/article/getting-start-tdd/php/05-package-management-and-static-analysis.md +0 -419
  321. package/lib/assets/docs/article/getting-start-tdd/php/06-task-runner-and-ci-cd.md +0 -330
  322. package/lib/assets/docs/article/getting-start-tdd/php/07-encapsulation-and-polymorphism.md +0 -380
  323. package/lib/assets/docs/article/getting-start-tdd/php/08-design-patterns.md +0 -461
  324. package/lib/assets/docs/article/getting-start-tdd/php/09-solid-principles-and-module-design.md +0 -468
  325. package/lib/assets/docs/article/getting-start-tdd/php/10-higher-order-functions-and-composition.md +0 -190
  326. package/lib/assets/docs/article/getting-start-tdd/php/11-immutable-data-and-pipeline.md +0 -274
  327. package/lib/assets/docs/article/getting-start-tdd/php/12-error-handling-and-type-safety.md +0 -316
  328. package/lib/assets/docs/article/getting-start-tdd/php/index.md +0 -84
  329. package/lib/assets/docs/article/getting-start-tdd/python/01-todo-list-and-first-test.md +0 -210
  330. package/lib/assets/docs/article/getting-start-tdd/python/02-fake-it-and-triangulation.md +0 -256
  331. package/lib/assets/docs/article/getting-start-tdd/python/03-obvious-implementation-and-refactoring.md +0 -208
  332. package/lib/assets/docs/article/getting-start-tdd/python/04-version-control-and-conventional-commits.md +0 -96
  333. package/lib/assets/docs/article/getting-start-tdd/python/05-package-management-and-static-analysis.md +0 -283
  334. package/lib/assets/docs/article/getting-start-tdd/python/06-task-runner-and-ci-cd.md +0 -199
  335. package/lib/assets/docs/article/getting-start-tdd/python/07-encapsulation-and-polymorphism.md +0 -217
  336. package/lib/assets/docs/article/getting-start-tdd/python/08-design-patterns.md +0 -181
  337. package/lib/assets/docs/article/getting-start-tdd/python/09-solid-principles-and-module-design.md +0 -139
  338. package/lib/assets/docs/article/getting-start-tdd/python/10-higher-order-functions-and-composition.md +0 -131
  339. package/lib/assets/docs/article/getting-start-tdd/python/11-immutable-data-and-pipeline.md +0 -125
  340. package/lib/assets/docs/article/getting-start-tdd/python/12-error-handling-and-type-safety.md +0 -135
  341. package/lib/assets/docs/article/getting-start-tdd/python/index.md +0 -55
  342. package/lib/assets/docs/article/getting-start-tdd/ruby/01-todo-list-and-first-test.md +0 -240
  343. package/lib/assets/docs/article/getting-start-tdd/ruby/02-fake-it-and-triangulation.md +0 -247
  344. package/lib/assets/docs/article/getting-start-tdd/ruby/03-obvious-implementation-and-refactoring.md +0 -237
  345. package/lib/assets/docs/article/getting-start-tdd/ruby/04-version-control-and-conventional-commits.md +0 -121
  346. package/lib/assets/docs/article/getting-start-tdd/ruby/05-package-management-and-static-analysis.md +0 -296
  347. package/lib/assets/docs/article/getting-start-tdd/ruby/06-task-runner-and-ci-cd.md +0 -257
  348. package/lib/assets/docs/article/getting-start-tdd/ruby/07-encapsulation-and-polymorphism.md +0 -288
  349. package/lib/assets/docs/article/getting-start-tdd/ruby/08-design-patterns.md +0 -338
  350. package/lib/assets/docs/article/getting-start-tdd/ruby/09-solid-principles-and-module-design.md +0 -205
  351. package/lib/assets/docs/article/getting-start-tdd/ruby/10-higher-order-functions-and-composition.md +0 -184
  352. package/lib/assets/docs/article/getting-start-tdd/ruby/11-immutable-data-and-pipeline.md +0 -246
  353. package/lib/assets/docs/article/getting-start-tdd/ruby/12-error-handling-and-type-safety.md +0 -407
  354. package/lib/assets/docs/article/getting-start-tdd/ruby/index.md +0 -83
  355. package/lib/assets/docs/article/getting-start-tdd/rust/01-todo-list-and-first-test.md +0 -220
  356. package/lib/assets/docs/article/getting-start-tdd/rust/02-fake-it-and-triangulation.md +0 -273
  357. package/lib/assets/docs/article/getting-start-tdd/rust/03-obvious-implementation-and-refactoring.md +0 -242
  358. package/lib/assets/docs/article/getting-start-tdd/rust/04-version-control-and-conventional-commits.md +0 -101
  359. package/lib/assets/docs/article/getting-start-tdd/rust/05-package-management-and-static-analysis.md +0 -221
  360. package/lib/assets/docs/article/getting-start-tdd/rust/06-task-runner-and-ci-cd.md +0 -173
  361. package/lib/assets/docs/article/getting-start-tdd/rust/07-encapsulation-and-polymorphism.md +0 -151
  362. package/lib/assets/docs/article/getting-start-tdd/rust/08-design-patterns.md +0 -154
  363. package/lib/assets/docs/article/getting-start-tdd/rust/09-solid-principles-and-module-design.md +0 -119
  364. package/lib/assets/docs/article/getting-start-tdd/rust/10-higher-order-functions-and-composition.md +0 -103
  365. package/lib/assets/docs/article/getting-start-tdd/rust/11-immutable-data-and-pipeline.md +0 -114
  366. package/lib/assets/docs/article/getting-start-tdd/rust/12-error-handling-and-type-safety.md +0 -121
  367. package/lib/assets/docs/article/getting-start-tdd/rust/index.md +0 -83
  368. package/lib/assets/docs/article/getting-start-tdd/scala/01-todo-list-and-first-test.md +0 -120
  369. package/lib/assets/docs/article/getting-start-tdd/scala/02-fake-it-and-triangulation.md +0 -116
  370. package/lib/assets/docs/article/getting-start-tdd/scala/03-obvious-implementation-and-refactoring.md +0 -108
  371. package/lib/assets/docs/article/getting-start-tdd/scala/04-version-control-and-conventional-commits.md +0 -132
  372. package/lib/assets/docs/article/getting-start-tdd/scala/05-package-management-and-static-analysis.md +0 -205
  373. package/lib/assets/docs/article/getting-start-tdd/scala/06-task-runner-and-ci-cd.md +0 -195
  374. package/lib/assets/docs/article/getting-start-tdd/scala/07-case-classes-and-traits.md +0 -148
  375. package/lib/assets/docs/article/getting-start-tdd/scala/08-pattern-matching-and-sealed-traits.md +0 -115
  376. package/lib/assets/docs/article/getting-start-tdd/scala/09-packages-and-module-design.md +0 -84
  377. package/lib/assets/docs/article/getting-start-tdd/scala/10-higher-order-functions-and-composition.md +0 -113
  378. package/lib/assets/docs/article/getting-start-tdd/scala/11-collections-and-lazy-evaluation.md +0 -103
  379. package/lib/assets/docs/article/getting-start-tdd/scala/12-error-handling-and-type-safety.md +0 -101
  380. package/lib/assets/docs/article/getting-start-tdd/scala/index.md +0 -65
  381. package/lib/assets/docs/article/grokking-concurrency/all/index.md +0 -403
  382. package/lib/assets/docs/article/grokking-concurrency/all/part-1-ch02-sequential.md +0 -563
  383. package/lib/assets/docs/article/grokking-concurrency/all/part-2-ch04-05-threads.md +0 -478
  384. package/lib/assets/docs/article/grokking-concurrency/all/part-3-ch06-multitasking.md +0 -529
  385. package/lib/assets/docs/article/grokking-concurrency/all/part-4-ch07-parallel-patterns.md +0 -429
  386. package/lib/assets/docs/article/grokking-concurrency/all/part-5-ch08-09-synchronization.md +0 -519
  387. package/lib/assets/docs/article/grokking-concurrency/all/part-6-ch10-11-nonblocking-io.md +0 -444
  388. package/lib/assets/docs/article/grokking-concurrency/all/part-7-ch12-async.md +0 -474
  389. package/lib/assets/docs/article/grokking-concurrency/all/part-8-ch13-mapreduce.md +0 -386
  390. package/lib/assets/docs/article/grokking-concurrency/clojure/index.md +0 -106
  391. package/lib/assets/docs/article/grokking-concurrency/clojure/part-1.md +0 -107
  392. package/lib/assets/docs/article/grokking-concurrency/clojure/part-2.md +0 -100
  393. package/lib/assets/docs/article/grokking-concurrency/clojure/part-3.md +0 -121
  394. package/lib/assets/docs/article/grokking-concurrency/clojure/part-4.md +0 -122
  395. package/lib/assets/docs/article/grokking-concurrency/clojure/part-5.md +0 -117
  396. package/lib/assets/docs/article/grokking-concurrency/clojure/part-6.md +0 -88
  397. package/lib/assets/docs/article/grokking-concurrency/clojure/part-7.md +0 -99
  398. package/lib/assets/docs/article/grokking-concurrency/clojure/part-8.md +0 -119
  399. package/lib/assets/docs/article/grokking-concurrency/csharp/index.md +0 -91
  400. package/lib/assets/docs/article/grokking-concurrency/csharp/part-1.md +0 -96
  401. package/lib/assets/docs/article/grokking-concurrency/csharp/part-2.md +0 -122
  402. package/lib/assets/docs/article/grokking-concurrency/csharp/part-3.md +0 -100
  403. package/lib/assets/docs/article/grokking-concurrency/csharp/part-4.md +0 -111
  404. package/lib/assets/docs/article/grokking-concurrency/csharp/part-5.md +0 -98
  405. package/lib/assets/docs/article/grokking-concurrency/csharp/part-6.md +0 -60
  406. package/lib/assets/docs/article/grokking-concurrency/csharp/part-7.md +0 -83
  407. package/lib/assets/docs/article/grokking-concurrency/csharp/part-8.md +0 -91
  408. package/lib/assets/docs/article/grokking-concurrency/fsharp/index.md +0 -55
  409. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-1.md +0 -79
  410. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-2.md +0 -102
  411. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-3.md +0 -93
  412. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-4.md +0 -109
  413. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-5.md +0 -103
  414. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-6.md +0 -92
  415. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-7.md +0 -120
  416. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-8.md +0 -106
  417. package/lib/assets/docs/article/grokking-concurrency/haskell/index.md +0 -238
  418. package/lib/assets/docs/article/grokking-concurrency/haskell/part-1.md +0 -95
  419. package/lib/assets/docs/article/grokking-concurrency/haskell/part-2.md +0 -95
  420. package/lib/assets/docs/article/grokking-concurrency/haskell/part-3.md +0 -90
  421. package/lib/assets/docs/article/grokking-concurrency/haskell/part-4.md +0 -105
  422. package/lib/assets/docs/article/grokking-concurrency/haskell/part-5.md +0 -98
  423. package/lib/assets/docs/article/grokking-concurrency/haskell/part-6.md +0 -94
  424. package/lib/assets/docs/article/grokking-concurrency/haskell/part-7.md +0 -110
  425. package/lib/assets/docs/article/grokking-concurrency/haskell/part-8.md +0 -117
  426. package/lib/assets/docs/article/grokking-concurrency/index.md +0 -66
  427. package/lib/assets/docs/article/grokking-concurrency/java/index.md +0 -92
  428. package/lib/assets/docs/article/grokking-concurrency/java/part-1.md +0 -307
  429. package/lib/assets/docs/article/grokking-concurrency/java/part-2.md +0 -333
  430. package/lib/assets/docs/article/grokking-concurrency/java/part-3.md +0 -220
  431. package/lib/assets/docs/article/grokking-concurrency/java/part-4.md +0 -212
  432. package/lib/assets/docs/article/grokking-concurrency/java/part-5.md +0 -111
  433. package/lib/assets/docs/article/grokking-concurrency/java/part-6.md +0 -68
  434. package/lib/assets/docs/article/grokking-concurrency/java/part-7.md +0 -100
  435. package/lib/assets/docs/article/grokking-concurrency/java/part-8.md +0 -100
  436. package/lib/assets/docs/article/grokking-concurrency/python/index.md +0 -303
  437. package/lib/assets/docs/article/grokking-concurrency/python/part-1.md +0 -238
  438. package/lib/assets/docs/article/grokking-concurrency/python/part-2.md +0 -417
  439. package/lib/assets/docs/article/grokking-concurrency/python/part-3.md +0 -226
  440. package/lib/assets/docs/article/grokking-concurrency/python/part-4.md +0 -298
  441. package/lib/assets/docs/article/grokking-concurrency/python/part-5.md +0 -314
  442. package/lib/assets/docs/article/grokking-concurrency/python/part-6.md +0 -296
  443. package/lib/assets/docs/article/grokking-concurrency/python/part-7.md +0 -313
  444. package/lib/assets/docs/article/grokking-concurrency/python/part-8.md +0 -359
  445. package/lib/assets/docs/article/grokking-concurrency/rust/index.md +0 -260
  446. package/lib/assets/docs/article/grokking-concurrency/rust/part-1.md +0 -107
  447. package/lib/assets/docs/article/grokking-concurrency/rust/part-2.md +0 -119
  448. package/lib/assets/docs/article/grokking-concurrency/rust/part-3.md +0 -125
  449. package/lib/assets/docs/article/grokking-concurrency/rust/part-4.md +0 -174
  450. package/lib/assets/docs/article/grokking-concurrency/rust/part-5.md +0 -157
  451. package/lib/assets/docs/article/grokking-concurrency/rust/part-6.md +0 -93
  452. package/lib/assets/docs/article/grokking-concurrency/rust/part-7.md +0 -132
  453. package/lib/assets/docs/article/grokking-concurrency/rust/part-8.md +0 -154
  454. package/lib/assets/docs/article/grokking-concurrency/scala/index.md +0 -59
  455. package/lib/assets/docs/article/grokking-concurrency/scala/part-1.md +0 -77
  456. package/lib/assets/docs/article/grokking-concurrency/scala/part-2.md +0 -111
  457. package/lib/assets/docs/article/grokking-concurrency/scala/part-3.md +0 -92
  458. package/lib/assets/docs/article/grokking-concurrency/scala/part-4.md +0 -109
  459. package/lib/assets/docs/article/grokking-concurrency/scala/part-5.md +0 -118
  460. package/lib/assets/docs/article/grokking-concurrency/scala/part-6.md +0 -82
  461. package/lib/assets/docs/article/grokking-concurrency/scala/part-7.md +0 -130
  462. package/lib/assets/docs/article/grokking-concurrency/scala/part-8.md +0 -128
  463. package/lib/assets/docs/article/grokkingfp/all/index.md +0 -368
  464. package/lib/assets/docs/article/grokkingfp/all/part-1-ch01-fp-introduction.md +0 -539
  465. package/lib/assets/docs/article/grokkingfp/all/part-1-ch02-pure-functions.md +0 -932
  466. package/lib/assets/docs/article/grokkingfp/all/part-2-ch03-immutable-data.md +0 -1137
  467. package/lib/assets/docs/article/grokkingfp/all/part-2-ch04-higher-order-functions.md +0 -1113
  468. package/lib/assets/docs/article/grokkingfp/all/part-2-ch05-flatmap.md +0 -1035
  469. package/lib/assets/docs/article/grokkingfp/all/part-3-ch06-option.md +0 -794
  470. package/lib/assets/docs/article/grokkingfp/all/part-3-ch07-either-adt.md +0 -880
  471. package/lib/assets/docs/article/grokkingfp/all/part-4-ch08-io-monad.md +0 -981
  472. package/lib/assets/docs/article/grokkingfp/all/part-4-ch09-streams.md +0 -935
  473. package/lib/assets/docs/article/grokkingfp/all/part-5-ch10-concurrency.md +0 -879
  474. package/lib/assets/docs/article/grokkingfp/all/part-6-ch11-application.md +0 -724
  475. package/lib/assets/docs/article/grokkingfp/all/part-6-ch12-testing.md +0 -635
  476. package/lib/assets/docs/article/grokkingfp/all/writing-plan.md +0 -721
  477. package/lib/assets/docs/article/grokkingfp/clojure/index.md +0 -276
  478. package/lib/assets/docs/article/grokkingfp/clojure/part-1.md +0 -676
  479. package/lib/assets/docs/article/grokkingfp/clojure/part-2.md +0 -652
  480. package/lib/assets/docs/article/grokkingfp/clojure/part-3.md +0 -629
  481. package/lib/assets/docs/article/grokkingfp/clojure/part-4.md +0 -706
  482. package/lib/assets/docs/article/grokkingfp/clojure/part-5.md +0 -760
  483. package/lib/assets/docs/article/grokkingfp/clojure/part-6.md +0 -730
  484. package/lib/assets/docs/article/grokkingfp/csharp/index.md +0 -246
  485. package/lib/assets/docs/article/grokkingfp/csharp/part-1.md +0 -820
  486. package/lib/assets/docs/article/grokkingfp/csharp/part-2.md +0 -980
  487. package/lib/assets/docs/article/grokkingfp/csharp/part-3.md +0 -990
  488. package/lib/assets/docs/article/grokkingfp/csharp/part-4.md +0 -958
  489. package/lib/assets/docs/article/grokkingfp/csharp/part-5.md +0 -956
  490. package/lib/assets/docs/article/grokkingfp/csharp/part-6.md +0 -748
  491. package/lib/assets/docs/article/grokkingfp/elixir/index.md +0 -203
  492. package/lib/assets/docs/article/grokkingfp/elixir/part-1.md +0 -721
  493. package/lib/assets/docs/article/grokkingfp/elixir/part-2.md +0 -847
  494. package/lib/assets/docs/article/grokkingfp/elixir/part-3.md +0 -994
  495. package/lib/assets/docs/article/grokkingfp/elixir/part-4.md +0 -983
  496. package/lib/assets/docs/article/grokkingfp/elixir/part-5.md +0 -1295
  497. package/lib/assets/docs/article/grokkingfp/elixir/part-6.md +0 -1058
  498. package/lib/assets/docs/article/grokkingfp/fsharp/index.md +0 -210
  499. package/lib/assets/docs/article/grokkingfp/fsharp/part-1.md +0 -723
  500. package/lib/assets/docs/article/grokkingfp/fsharp/part-2.md +0 -970
  501. package/lib/assets/docs/article/grokkingfp/fsharp/part-3.md +0 -981
  502. package/lib/assets/docs/article/grokkingfp/fsharp/part-4.md +0 -841
  503. package/lib/assets/docs/article/grokkingfp/fsharp/part-5.md +0 -920
  504. package/lib/assets/docs/article/grokkingfp/fsharp/part-6.md +0 -931
  505. package/lib/assets/docs/article/grokkingfp/haskell/index.md +0 -234
  506. package/lib/assets/docs/article/grokkingfp/haskell/part-1.md +0 -600
  507. package/lib/assets/docs/article/grokkingfp/haskell/part-2.md +0 -875
  508. package/lib/assets/docs/article/grokkingfp/haskell/part-3.md +0 -924
  509. package/lib/assets/docs/article/grokkingfp/haskell/part-4.md +0 -887
  510. package/lib/assets/docs/article/grokkingfp/haskell/part-5.md +0 -854
  511. package/lib/assets/docs/article/grokkingfp/haskell/part-6.md +0 -853
  512. package/lib/assets/docs/article/grokkingfp/index.md +0 -143
  513. package/lib/assets/docs/article/grokkingfp/java/index.md +0 -211
  514. package/lib/assets/docs/article/grokkingfp/java/part-1.md +0 -657
  515. package/lib/assets/docs/article/grokkingfp/java/part-2.md +0 -684
  516. package/lib/assets/docs/article/grokkingfp/java/part-3.md +0 -681
  517. package/lib/assets/docs/article/grokkingfp/java/part-4.md +0 -780
  518. package/lib/assets/docs/article/grokkingfp/java/part-5.md +0 -968
  519. package/lib/assets/docs/article/grokkingfp/java/part-6.md +0 -1337
  520. package/lib/assets/docs/article/grokkingfp/python/index.md +0 -258
  521. package/lib/assets/docs/article/grokkingfp/python/part-1.md +0 -452
  522. package/lib/assets/docs/article/grokkingfp/python/part-2.md +0 -967
  523. package/lib/assets/docs/article/grokkingfp/python/part-3.md +0 -1013
  524. package/lib/assets/docs/article/grokkingfp/python/part-4.md +0 -774
  525. package/lib/assets/docs/article/grokkingfp/python/part-5.md +0 -756
  526. package/lib/assets/docs/article/grokkingfp/python/part-6.md +0 -870
  527. package/lib/assets/docs/article/grokkingfp/ruby/index.md +0 -330
  528. package/lib/assets/docs/article/grokkingfp/ruby/part-1.md +0 -764
  529. package/lib/assets/docs/article/grokkingfp/ruby/part-2.md +0 -947
  530. package/lib/assets/docs/article/grokkingfp/ruby/part-3.md +0 -955
  531. package/lib/assets/docs/article/grokkingfp/ruby/part-4.md +0 -930
  532. package/lib/assets/docs/article/grokkingfp/ruby/part-5.md +0 -917
  533. package/lib/assets/docs/article/grokkingfp/ruby/part-6.md +0 -1421
  534. package/lib/assets/docs/article/grokkingfp/rust/index.md +0 -242
  535. package/lib/assets/docs/article/grokkingfp/rust/part-1.md +0 -643
  536. package/lib/assets/docs/article/grokkingfp/rust/part-2.md +0 -1069
  537. package/lib/assets/docs/article/grokkingfp/rust/part-3.md +0 -1003
  538. package/lib/assets/docs/article/grokkingfp/rust/part-4.md +0 -582
  539. package/lib/assets/docs/article/grokkingfp/rust/part-5.md +0 -714
  540. package/lib/assets/docs/article/grokkingfp/rust/part-6.md +0 -517
  541. package/lib/assets/docs/article/grokkingfp/scala/index.md +0 -171
  542. package/lib/assets/docs/article/grokkingfp/scala/part-1.md +0 -552
  543. package/lib/assets/docs/article/grokkingfp/scala/part-2.md +0 -955
  544. package/lib/assets/docs/article/grokkingfp/scala/part-3.md +0 -928
  545. package/lib/assets/docs/article/grokkingfp/scala/part-4.md +0 -751
  546. package/lib/assets/docs/article/grokkingfp/scala/part-5.md +0 -731
  547. package/lib/assets/docs/article/grokkingfp/scala/part-6.md +0 -876
  548. package/lib/assets/docs/article/grokkingfp/typescript/index.md +0 -273
  549. package/lib/assets/docs/article/grokkingfp/typescript/part-1.md +0 -570
  550. package/lib/assets/docs/article/grokkingfp/typescript/part-2.md +0 -1138
  551. package/lib/assets/docs/article/grokkingfp/typescript/part-3.md +0 -851
  552. package/lib/assets/docs/article/grokkingfp/typescript/part-4.md +0 -1096
  553. package/lib/assets/docs/article/grokkingfp/typescript/part-5.md +0 -726
  554. package/lib/assets/docs/article/grokkingfp/typescript/part-6.md +0 -991
  555. package/lib/assets/docs/article/practical-database-design/index.md +0 -121
  556. package/lib/assets/docs/article/practical-database-design/part1/chapter01.md +0 -297
  557. package/lib/assets/docs/article/practical-database-design/part1/chapter02.md +0 -527
  558. package/lib/assets/docs/article/practical-database-design/part1/chapter03.md +0 -566
  559. package/lib/assets/docs/article/practical-database-design/part2/chapter04.md +0 -933
  560. package/lib/assets/docs/article/practical-database-design/part2/chapter05.md +0 -1636
  561. package/lib/assets/docs/article/practical-database-design/part2/chapter06.md +0 -2725
  562. package/lib/assets/docs/article/practical-database-design/part2/chapter07.md +0 -2091
  563. package/lib/assets/docs/article/practical-database-design/part2/chapter08.md +0 -2114
  564. package/lib/assets/docs/article/practical-database-design/part2/chapter09.md +0 -2040
  565. package/lib/assets/docs/article/practical-database-design/part2/chapter10.md +0 -1396
  566. package/lib/assets/docs/article/practical-database-design/part2/chapter11.md +0 -1686
  567. package/lib/assets/docs/article/practical-database-design/part2/chapter12.md +0 -1426
  568. package/lib/assets/docs/article/practical-database-design/part2/chapter13.md +0 -1443
  569. package/lib/assets/docs/article/practical-database-design/part3/chapter14.md +0 -676
  570. package/lib/assets/docs/article/practical-database-design/part3/chapter15.md +0 -1634
  571. package/lib/assets/docs/article/practical-database-design/part3/chapter16.md +0 -1924
  572. package/lib/assets/docs/article/practical-database-design/part3/chapter17.md +0 -1717
  573. package/lib/assets/docs/article/practical-database-design/part3/chapter18.md +0 -2104
  574. package/lib/assets/docs/article/practical-database-design/part3/chapter19.md +0 -1132
  575. package/lib/assets/docs/article/practical-database-design/part3/chapter20.md +0 -1040
  576. package/lib/assets/docs/article/practical-database-design/part3/chapter21.md +0 -1391
  577. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter14-orm.md +0 -1000
  578. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter15-orm.md +0 -1309
  579. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter16-orm.md +0 -1175
  580. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter17-orm.md +0 -1593
  581. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter18-orm.md +0 -1192
  582. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter19-orm.md +0 -1025
  583. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter20-orm.md +0 -1762
  584. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter21-orm.md +0 -1456
  585. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter22-orm.md +0 -1887
  586. package/lib/assets/docs/article/practical-database-design/part4/chapter22.md +0 -974
  587. package/lib/assets/docs/article/practical-database-design/part4/chapter23.md +0 -2078
  588. package/lib/assets/docs/article/practical-database-design/part4/chapter24.md +0 -2448
  589. package/lib/assets/docs/article/practical-database-design/part4/chapter25.md +0 -3670
  590. package/lib/assets/docs/article/practical-database-design/part4/chapter26.md +0 -2925
  591. package/lib/assets/docs/article/practical-database-design/part4/chapter27.md +0 -3114
  592. package/lib/assets/docs/article/practical-database-design/part4/chapter28.md +0 -2706
  593. package/lib/assets/docs/article/practical-database-design/part4/chapter29.md +0 -2552
  594. package/lib/assets/docs/article/practical-database-design/part4/chapter30.md +0 -2188
  595. package/lib/assets/docs/article/practical-database-design/part4/chapter31.md +0 -1200
  596. package/lib/assets/docs/article/practical-database-design/part4/chapter32.md +0 -2109
  597. package/lib/assets/docs/article/practical-database-design/part5/chapter33.md +0 -1041
  598. package/lib/assets/docs/article/practical-database-design/part5/chapter34.md +0 -1618
  599. package/lib/assets/docs/article/practical-database-design/part5/chapter35.md +0 -1462
  600. package/lib/assets/docs/article/practical-database-design/part5/chapter36.md +0 -1301
  601. package/lib/assets/docs/article/practical-database-design/part5/chapter37.md +0 -1479
  602. package/lib/assets/docs/article/practical-database-design/part5/chapter38.md +0 -1707
  603. package/lib/assets/docs/article/practical-database-design/part5/chapter39.md +0 -2343
  604. package/lib/assets/docs/article/practical-database-design/study/study2-1.md +0 -1702
  605. package/lib/assets/docs/article/practical-database-design/study/study2-2.md +0 -1356
  606. package/lib/assets/docs/article/practical-database-design/study/study2-3.md +0 -2053
  607. package/lib/assets/docs/article/practical-database-design/study/study2-4.md +0 -2238
  608. package/lib/assets/docs/article/practical-database-design/study/study2-5.md +0 -2427
  609. package/lib/assets/docs/article/practical-database-design/study/study3-1.md +0 -2214
  610. package/lib/assets/docs/article/practical-database-design/study/study3-2.md +0 -2230
  611. package/lib/assets/docs/article/practical-database-design/study/study3-3.md +0 -2262
  612. package/lib/assets/docs/article/practical-database-design/study/study3-4.md +0 -2115
  613. package/lib/assets/docs/article/practical-database-design/study/study3-5.md +0 -2516
  614. package/lib/assets/docs/article/practical-database-design/study/study4-1.md +0 -2596
  615. package/lib/assets/docs/article/practical-database-design/study/study4-2.md +0 -2084
  616. package/lib/assets/docs/article/practical-database-design/study/study4-3.md +0 -1814
  617. package/lib/assets/docs/article/practical-database-design/study/study4-4.md +0 -1904
  618. package/lib/assets/docs/article/practical-database-design/study/study4-5.md +0 -2887
@@ -1,3670 +0,0 @@
1
- ---
2
- type: Article
3
- title: "第25章:購買管理の設計"
4
- description: "本章では、MRP で生成された購買オーダを実際の発注に変換し、入荷・検収までの一連の購買業務を設計します。"
5
- tags: [article, practical-database-design, part4]
6
- status: stable
7
- generated: { by: human:kakimomokuri, at: 2026-03-30T08:34:28Z }
8
- ---
9
-
10
- # 第25章:購買管理の設計
11
-
12
- 本章では、MRP で生成された購買オーダを実際の発注に変換し、入荷・検収までの一連の購買業務を設計します。
13
-
14
- ---
15
-
16
- ## 25.1 発注業務の DB 設計
17
-
18
- 発注業務は、購買オーダを基に取引先への発注を行い、納期管理を行う業務です。
19
-
20
- ### 発注業務の流れ
21
-
22
- ```plantuml
23
- @startuml
24
-
25
- title 発注から検収までの業務フロー
26
-
27
- |生産管理部|
28
- start
29
- :オーダ情報;
30
-
31
- |購買部|
32
- :発注;
33
- fork
34
- :検査表;
35
- fork again
36
- :現品票;
37
- fork again
38
- :受領書;
39
- fork again
40
- :納品書;
41
- fork again
42
- :注文書;
43
- fork end
44
-
45
- |仕入先|
46
- fork
47
- :検査表;
48
- fork again
49
- :現品票;
50
- fork again
51
- :受領書;
52
- fork again
53
- :納品書;
54
- fork again
55
- :注文書;
56
- fork end
57
- :材料;
58
-
59
- |資材倉庫|
60
- fork
61
- :現品表;
62
- fork again
63
- :材料;
64
- fork end
65
-
66
- |品質管理部|
67
- fork
68
- :現品表;
69
- fork again
70
- :材料;
71
- fork again
72
- :検査表;
73
- fork end
74
-
75
- |購買部|
76
- :検収;
77
-
78
- |品質管理部|
79
- :受入検査;
80
- if (不良品) then
81
- |仕入先|
82
- :材料;
83
- |購買部|
84
- :検収返品;
85
- |仕入先|
86
- :返品通知;
87
- else (合格品)
88
- |資材倉庫|
89
- fork
90
- :現品表;
91
- fork again
92
- :材料;
93
- fork end
94
- endif
95
- stop
96
-
97
- @enduml
98
- ```
99
-
100
- ### 発注業務のデータ関連
101
-
102
- 発注業務では、オーダ情報を基に発注データを生成し、単価マスタを参照して発注金額を計算します。
103
-
104
- ```plantuml
105
- @startuml
106
-
107
- title 発注業務のデータ関連図
108
-
109
- database "場所マスタ" as location
110
- database "オーダ情報" as order
111
- database "単価マスタ" as price
112
-
113
- rectangle "購買計画作成" as plan
114
-
115
- database "購買計画" as purchasePlan
116
-
117
- rectangle "発注" as po
118
-
119
- database "発注情報" as poData
120
-
121
- location --> plan
122
- order --> plan
123
- price --> plan
124
- plan --> purchasePlan
125
- purchasePlan --> po
126
- po --> poData
127
-
128
- @enduml
129
- ```
130
-
131
- ### 発注関連のスキーマ設計
132
-
133
- 発注業務で使用するテーブルを定義します。
134
-
135
- #### テーブル構造
136
-
137
- | テーブル名 | 説明 |
138
- |-----------|------|
139
- | 単価マスタ | 品目・取引先ごとの単価情報を管理 |
140
- | 発注データ | 発注ヘッダ情報(発注番号、取引先、ステータス等) |
141
- | 発注明細データ | 発注明細情報(品目、数量、単価、金額等) |
142
- | 諸口品目情報 | マスタに登録されていない臨時品目の情報 |
143
-
144
- #### 発注ステータスの状態遷移
145
-
146
- ```plantuml
147
- @startuml
148
-
149
- title 発注ステータスの状態遷移
150
-
151
- [*] --> 作成中 : 発注作成
152
- 作成中 --> 発注済 : 発注確定
153
- 作成中 --> 取消 : 取消
154
- 発注済 --> 一部入荷 : 分割入荷
155
- 発注済 --> 入荷完了 : 全数入荷
156
- 一部入荷 --> 入荷完了 : 残数入荷
157
- 入荷完了 --> 検収完了 : 検収処理
158
- 検収完了 --> [*]
159
- 取消 --> [*]
160
-
161
- @enduml
162
- ```
163
-
164
- <details>
165
- <summary>DDL: 発注関連テーブル</summary>
166
-
167
- ```sql
168
- -- V009__create_purchasing_tables.sql
169
-
170
- -- 発注ステータス
171
- CREATE TYPE 発注ステータス AS ENUM ('作成中', '発注済', '一部入荷', '入荷完了', '検収完了', '取消');
172
-
173
- -- 入荷受入区分
174
- CREATE TYPE 入荷受入区分 AS ENUM ('通常入荷', '分割入荷', '返品入荷');
175
-
176
- -- 単価マスタ
177
- CREATE TABLE "単価マスタ" (
178
- "ID" SERIAL PRIMARY KEY,
179
- "品目コード" VARCHAR(20) NOT NULL,
180
- "取引先コード" VARCHAR(20) NOT NULL,
181
- "ロット単位数" DECIMAL(15, 2) DEFAULT 1 NOT NULL,
182
- "使用開始日" DATE NOT NULL,
183
- "使用停止日" DATE,
184
- "単価" DECIMAL(15, 2) NOT NULL,
185
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
186
- "作成者" VARCHAR(50),
187
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
188
- "更新者" VARCHAR(50),
189
- CONSTRAINT "fk_単価マスタ_品目"
190
- FOREIGN KEY ("品目コード") REFERENCES "品目マスタ"("品目コード"),
191
- CONSTRAINT "fk_単価マスタ_取引先"
192
- FOREIGN KEY ("取引先コード") REFERENCES "取引先マスタ"("取引先コード"),
193
- UNIQUE ("品目コード", "取引先コード", "ロット単位数", "使用開始日")
194
- );
195
-
196
- -- 発注データ
197
- CREATE TABLE "発注データ" (
198
- "ID" SERIAL PRIMARY KEY,
199
- "発注番号" VARCHAR(20) UNIQUE NOT NULL,
200
- "発注日" DATE NOT NULL,
201
- "取引先コード" VARCHAR(20) NOT NULL,
202
- "発注担当者コード" VARCHAR(20),
203
- "発注部門コード" VARCHAR(20),
204
- "ステータス" 発注ステータス DEFAULT '作成中' NOT NULL,
205
- "備考" TEXT,
206
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
207
- "作成者" VARCHAR(50),
208
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
209
- "更新者" VARCHAR(50),
210
- CONSTRAINT "fk_発注データ_取引先"
211
- FOREIGN KEY ("取引先コード") REFERENCES "取引先マスタ"("取引先コード")
212
- );
213
-
214
- -- 発注明細データ
215
- CREATE TABLE "発注明細データ" (
216
- "ID" SERIAL PRIMARY KEY,
217
- "発注番号" VARCHAR(20) NOT NULL,
218
- "発注行番号" INTEGER NOT NULL,
219
- "オーダNO" VARCHAR(20),
220
- "納入場所コード" VARCHAR(20),
221
- "品目コード" VARCHAR(20) NOT NULL,
222
- "諸口品目区分" BOOLEAN DEFAULT FALSE NOT NULL,
223
- "受入予定日" DATE NOT NULL,
224
- "回答納期" DATE,
225
- "発注単価" DECIMAL(15, 2) NOT NULL,
226
- "発注数量" DECIMAL(15, 2) NOT NULL,
227
- "入荷済数量" DECIMAL(15, 2) DEFAULT 0 NOT NULL,
228
- "検査済数量" DECIMAL(15, 2) DEFAULT 0 NOT NULL,
229
- "検収済数量" DECIMAL(15, 2) DEFAULT 0 NOT NULL,
230
- "発注金額" DECIMAL(15, 2) NOT NULL,
231
- "消費税金額" DECIMAL(15, 2) DEFAULT 0 NOT NULL,
232
- "完了フラグ" BOOLEAN DEFAULT FALSE NOT NULL,
233
- "明細備考" TEXT,
234
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
235
- "作成者" VARCHAR(50),
236
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
237
- "更新者" VARCHAR(50),
238
- CONSTRAINT "fk_発注明細_発注"
239
- FOREIGN KEY ("発注番号") REFERENCES "発注データ"("発注番号"),
240
- CONSTRAINT "fk_発注明細_品目"
241
- FOREIGN KEY ("品目コード") REFERENCES "品目マスタ"("品目コード"),
242
- UNIQUE ("発注番号", "発注行番号")
243
- );
244
-
245
- -- 諸口品目情報(マスタに登録されていない臨時品目)
246
- CREATE TABLE "諸口品目情報" (
247
- "ID" SERIAL PRIMARY KEY,
248
- "発注番号" VARCHAR(20) NOT NULL,
249
- "発注行番号" INTEGER NOT NULL,
250
- "品目コード" VARCHAR(20) NOT NULL,
251
- "品名" VARCHAR(100) NOT NULL,
252
- "規格" VARCHAR(100),
253
- "図番メーカー" VARCHAR(100),
254
- "版数" VARCHAR(20),
255
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
256
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
257
- UNIQUE ("発注番号", "発注行番号", "品目コード")
258
- );
259
-
260
- -- インデックス
261
- CREATE INDEX "idx_発注データ_取引先コード" ON "発注データ"("取引先コード");
262
- CREATE INDEX "idx_発注データ_発注日" ON "発注データ"("発注日");
263
- CREATE INDEX "idx_発注明細_発注番号" ON "発注明細データ"("発注番号");
264
- CREATE INDEX "idx_発注明細_品目コード" ON "発注明細データ"("品目コード");
265
- CREATE INDEX "idx_単価マスタ_品目取引先" ON "単価マスタ"("品目コード", "取引先コード");
266
- ```
267
-
268
- </details>
269
-
270
- ### オーダ情報・場所マスタ・単価マスタとの連携
271
-
272
- 発注データは以下のマスタ情報と連携します。
273
-
274
- ```plantuml
275
- @startuml
276
-
277
- title 発注データとマスタの関連
278
-
279
- entity "発注データ" as po {
280
- * 発注番号 [PK]
281
- --
282
- 取引先コード [FK]
283
- 発注日
284
- ステータス
285
- }
286
-
287
- entity "発注明細データ" as pod {
288
- * 発注番号 [PK,FK]
289
- * 発注行番号 [PK]
290
- --
291
- オーダNO [FK]
292
- 品目コード [FK]
293
- 納入場所コード [FK]
294
- 発注数量
295
- 発注単価
296
- }
297
-
298
- entity "オーダ情報" as order {
299
- * オーダNO [PK]
300
- --
301
- 品目コード
302
- 所要数量
303
- 所要日
304
- }
305
-
306
- entity "単価マスタ" as price {
307
- * ID [PK]
308
- --
309
- 品目コード [FK]
310
- 取引先コード [FK]
311
- 単価
312
- 使用開始日
313
- 使用停止日
314
- }
315
-
316
- entity "場所マスタ" as location {
317
- * 場所コード [PK]
318
- --
319
- 場所名
320
- }
321
-
322
- entity "取引先マスタ" as supplier {
323
- * 取引先コード [PK]
324
- --
325
- 取引先名
326
- }
327
-
328
- po ||--o{ pod : contains
329
- po }o--|| supplier : 発注先
330
- pod }o--o| order : 紐付け
331
- pod }o--|| price : 単価参照
332
- pod }o--o| location : 納入先
333
-
334
- @enduml
335
- ```
336
-
337
- ### 諸口品目の扱い
338
-
339
- マスタに登録されていない臨時品目(諸口品目)を発注する場合、発注明細データの「諸口品目区分」を `TRUE` に設定し、諸口品目情報テーブルに品目の詳細情報を登録します。
340
-
341
- ```plantuml
342
- @startuml
343
-
344
- title 諸口品目の関連
345
-
346
- entity "発注明細データ" as pod {
347
- * 発注番号 [PK]
348
- * 発注行番号 [PK]
349
- --
350
- 品目コード
351
- 諸口品目区分
352
- }
353
-
354
- entity "諸口品目情報" as misc {
355
- * ID [PK]
356
- --
357
- 発注番号 [FK]
358
- 発注行番号 [FK]
359
- 品目コード
360
- 品名
361
- 規格
362
- 図番メーカー
363
- 版数
364
- }
365
-
366
- pod ||--o| misc : 諸口品目の場合
367
-
368
- note right of misc
369
- 品目マスタに登録されていない
370
- 臨時品目の情報を保持
371
- end note
372
-
373
- @enduml
374
- ```
375
-
376
- ### 納期管理・分納対応
377
-
378
- 発注明細には「受入予定日」と「回答納期」の 2 つの日付を持ちます。
379
-
380
- | フィールド | 説明 |
381
- |-----------|------|
382
- | 受入予定日 | 発注時に指定した希望納期 |
383
- | 回答納期 | 仕入先から回答された実際の納期 |
384
-
385
- 分納(分割納品)に対応するため、発注明細ごとに「入荷済数量」「検査済数量」「検収済数量」を管理します。
386
-
387
- ### Java エンティティの定義
388
-
389
- <details>
390
- <summary>発注ステータス Enum</summary>
391
-
392
- ```java
393
- // src/main/java/com/example/pms/domain/model/purchase/PurchaseOrderStatus.java
394
- package com.example.pms.domain.model.purchase;
395
-
396
- import lombok.Getter;
397
- import lombok.RequiredArgsConstructor;
398
-
399
- @Getter
400
- @RequiredArgsConstructor
401
- public enum PurchaseOrderStatus {
402
- CREATING("作成中"),
403
- ORDERED("発注済"),
404
- PARTIALLY_RECEIVED("一部入荷"),
405
- RECEIVED("入荷完了"),
406
- ACCEPTED("検収完了"),
407
- CANCELLED("取消");
408
-
409
- private final String displayName;
410
-
411
- public static PurchaseOrderStatus fromDisplayName(String displayName) {
412
- for (PurchaseOrderStatus status : values()) {
413
- if (status.displayName.equals(displayName)) {
414
- return status;
415
- }
416
- }
417
- throw new IllegalArgumentException("不正な発注ステータス: " + displayName);
418
- }
419
- }
420
- ```
421
-
422
- </details>
423
-
424
- <details>
425
- <summary>入荷受入区分 Enum</summary>
426
-
427
- ```java
428
- // src/main/java/com/example/pms/domain/model/purchase/ReceivingType.java
429
- package com.example.pms.domain.model.purchase;
430
-
431
- import lombok.Getter;
432
- import lombok.RequiredArgsConstructor;
433
-
434
- @Getter
435
- @RequiredArgsConstructor
436
- public enum ReceivingType {
437
- NORMAL("通常入荷"),
438
- SPLIT("分割入荷"),
439
- RETURN("返品入荷");
440
-
441
- private final String displayName;
442
-
443
- public static ReceivingType fromDisplayName(String displayName) {
444
- for (ReceivingType type : values()) {
445
- if (type.displayName.equals(displayName)) {
446
- return type;
447
- }
448
- }
449
- throw new IllegalArgumentException("不正な入荷受入区分: " + displayName);
450
- }
451
- }
452
- ```
453
-
454
- </details>
455
-
456
- <details>
457
- <summary>単価マスタエンティティ</summary>
458
-
459
- ```java
460
- // src/main/java/com/example/pms/domain/model/purchase/UnitPrice.java
461
- package com.example.pms.domain.model.purchase;
462
-
463
- import com.example.pms.domain.model.item.Item;
464
- import com.example.pms.domain.model.supplier.Supplier;
465
- import lombok.AllArgsConstructor;
466
- import lombok.Builder;
467
- import lombok.Data;
468
- import lombok.NoArgsConstructor;
469
-
470
- import java.math.BigDecimal;
471
- import java.time.LocalDate;
472
- import java.time.LocalDateTime;
473
-
474
- @Data
475
- @Builder
476
- @NoArgsConstructor
477
- @AllArgsConstructor
478
- public class UnitPrice {
479
- private Integer id;
480
- private String itemCode;
481
- private String supplierCode;
482
- private BigDecimal lotUnitQuantity;
483
- private LocalDate effectiveFrom;
484
- private LocalDate effectiveTo;
485
- private BigDecimal unitPrice;
486
- private LocalDateTime createdAt;
487
- private String createdBy;
488
- private LocalDateTime updatedAt;
489
- private String updatedBy;
490
-
491
- // リレーション
492
- private Item item;
493
- private Supplier supplier;
494
- }
495
- ```
496
-
497
- </details>
498
-
499
- <details>
500
- <summary>発注データエンティティ</summary>
501
-
502
- ```java
503
- // src/main/java/com/example/pms/domain/model/purchase/PurchaseOrder.java
504
- package com.example.pms.domain.model.purchase;
505
-
506
- import com.example.pms.domain.model.supplier.Supplier;
507
- import lombok.AllArgsConstructor;
508
- import lombok.Builder;
509
- import lombok.Data;
510
- import lombok.NoArgsConstructor;
511
-
512
- import java.time.LocalDate;
513
- import java.time.LocalDateTime;
514
- import java.util.List;
515
-
516
- @Data
517
- @Builder
518
- @NoArgsConstructor
519
- @AllArgsConstructor
520
- public class PurchaseOrder {
521
- private Integer id;
522
- private String purchaseOrderNumber;
523
- private LocalDate orderDate;
524
- private String supplierCode;
525
- private String ordererCode;
526
- private String departmentCode;
527
- private PurchaseOrderStatus status;
528
- private String remarks;
529
- private LocalDateTime createdAt;
530
- private String createdBy;
531
- private LocalDateTime updatedAt;
532
- private String updatedBy;
533
-
534
- // リレーション
535
- private Supplier supplier;
536
- private List<PurchaseOrderDetail> details;
537
- }
538
- ```
539
-
540
- </details>
541
-
542
- <details>
543
- <summary>発注明細データエンティティ</summary>
544
-
545
- ```java
546
- // src/main/java/com/example/pms/domain/model/purchase/PurchaseOrderDetail.java
547
- package com.example.pms.domain.model.purchase;
548
-
549
- import com.example.pms.domain.model.item.Item;
550
- import com.example.pms.domain.model.plan.Order;
551
- import lombok.AllArgsConstructor;
552
- import lombok.Builder;
553
- import lombok.Data;
554
- import lombok.NoArgsConstructor;
555
-
556
- import java.math.BigDecimal;
557
- import java.time.LocalDate;
558
- import java.time.LocalDateTime;
559
- import java.util.List;
560
-
561
- @Data
562
- @Builder
563
- @NoArgsConstructor
564
- @AllArgsConstructor
565
- public class PurchaseOrderDetail {
566
- private Integer id;
567
- private String purchaseOrderNumber;
568
- private Integer lineNumber;
569
- private String orderNumber;
570
- private String deliveryLocationCode;
571
- private String itemCode;
572
- private Boolean miscellaneousItemFlag;
573
- private LocalDate expectedReceivingDate;
574
- private LocalDate confirmedDeliveryDate;
575
- private BigDecimal orderUnitPrice;
576
- private BigDecimal orderQuantity;
577
- private BigDecimal receivedQuantity;
578
- private BigDecimal inspectedQuantity;
579
- private BigDecimal acceptedQuantity;
580
- private BigDecimal orderAmount;
581
- private BigDecimal taxAmount;
582
- private Boolean completedFlag;
583
- private String detailRemarks;
584
- private LocalDateTime createdAt;
585
- private String createdBy;
586
- private LocalDateTime updatedAt;
587
- private String updatedBy;
588
-
589
- // リレーション
590
- private PurchaseOrder purchaseOrder;
591
- private Item item;
592
- private Order order;
593
- private MiscellaneousItem miscellaneousItem;
594
- private List<Receiving> receivings;
595
- private List<Acceptance> acceptances;
596
- }
597
- ```
598
-
599
- </details>
600
-
601
- <details>
602
- <summary>諸口品目情報エンティティ</summary>
603
-
604
- ```java
605
- // src/main/java/com/example/pms/domain/model/purchase/MiscellaneousItem.java
606
- package com.example.pms.domain.model.purchase;
607
-
608
- import lombok.AllArgsConstructor;
609
- import lombok.Builder;
610
- import lombok.Data;
611
- import lombok.NoArgsConstructor;
612
-
613
- import java.time.LocalDateTime;
614
-
615
- @Data
616
- @Builder
617
- @NoArgsConstructor
618
- @AllArgsConstructor
619
- public class MiscellaneousItem {
620
- private Integer id;
621
- private String purchaseOrderNumber;
622
- private Integer lineNumber;
623
- private String itemCode;
624
- private String itemName;
625
- private String specification;
626
- private String manufacturerDrawingNumber;
627
- private String revision;
628
- private LocalDateTime createdAt;
629
- private LocalDateTime updatedAt;
630
- }
631
- ```
632
-
633
- </details>
634
-
635
- ### TypeHandler の実装
636
-
637
- PostgreSQL の日本語 ENUM 型と Java の英語 Enum を相互変換するために TypeHandler を実装します。
638
-
639
- <details>
640
- <summary>PurchaseOrderStatusTypeHandler</summary>
641
-
642
- ```java
643
- // src/main/java/com/example/pms/infrastructure/out/persistence/typehandler/PurchaseOrderStatusTypeHandler.java
644
- package com.example.pms.infrastructure.out.persistence.typehandler;
645
-
646
- import com.example.pms.domain.model.purchase.PurchaseOrderStatus;
647
- import org.apache.ibatis.type.BaseTypeHandler;
648
- import org.apache.ibatis.type.JdbcType;
649
- import org.apache.ibatis.type.MappedTypes;
650
-
651
- import java.sql.CallableStatement;
652
- import java.sql.PreparedStatement;
653
- import java.sql.ResultSet;
654
- import java.sql.SQLException;
655
-
656
- @MappedTypes(PurchaseOrderStatus.class)
657
- public class PurchaseOrderStatusTypeHandler extends BaseTypeHandler<PurchaseOrderStatus> {
658
-
659
- @Override
660
- public void setNonNullParameter(PreparedStatement ps, int i, PurchaseOrderStatus parameter, JdbcType jdbcType)
661
- throws SQLException {
662
- ps.setString(i, parameter.getDisplayName());
663
- }
664
-
665
- @Override
666
- public PurchaseOrderStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
667
- String value = rs.getString(columnName);
668
- return value == null ? null : PurchaseOrderStatus.fromDisplayName(value);
669
- }
670
-
671
- @Override
672
- public PurchaseOrderStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
673
- String value = rs.getString(columnIndex);
674
- return value == null ? null : PurchaseOrderStatus.fromDisplayName(value);
675
- }
676
-
677
- @Override
678
- public PurchaseOrderStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
679
- String value = cs.getString(columnIndex);
680
- return value == null ? null : PurchaseOrderStatus.fromDisplayName(value);
681
- }
682
- }
683
- ```
684
-
685
- </details>
686
-
687
- <details>
688
- <summary>ReceivingTypeTypeHandler</summary>
689
-
690
- ```java
691
- // src/main/java/com/example/pms/infrastructure/out/persistence/typehandler/ReceivingTypeTypeHandler.java
692
- package com.example.pms.infrastructure.out.persistence.typehandler;
693
-
694
- import com.example.pms.domain.model.purchase.ReceivingType;
695
- import org.apache.ibatis.type.BaseTypeHandler;
696
- import org.apache.ibatis.type.JdbcType;
697
- import org.apache.ibatis.type.MappedTypes;
698
-
699
- import java.sql.CallableStatement;
700
- import java.sql.PreparedStatement;
701
- import java.sql.ResultSet;
702
- import java.sql.SQLException;
703
-
704
- @MappedTypes(ReceivingType.class)
705
- public class ReceivingTypeTypeHandler extends BaseTypeHandler<ReceivingType> {
706
-
707
- @Override
708
- public void setNonNullParameter(PreparedStatement ps, int i, ReceivingType parameter, JdbcType jdbcType)
709
- throws SQLException {
710
- ps.setString(i, parameter.getDisplayName());
711
- }
712
-
713
- @Override
714
- public ReceivingType getNullableResult(ResultSet rs, String columnName) throws SQLException {
715
- String value = rs.getString(columnName);
716
- return value == null ? null : ReceivingType.fromDisplayName(value);
717
- }
718
-
719
- @Override
720
- public ReceivingType getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
721
- String value = rs.getString(columnIndex);
722
- return value == null ? null : ReceivingType.fromDisplayName(value);
723
- }
724
-
725
- @Override
726
- public ReceivingType getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
727
- String value = cs.getString(columnIndex);
728
- return value == null ? null : ReceivingType.fromDisplayName(value);
729
- }
730
- }
731
- ```
732
-
733
- </details>
734
-
735
- ### MyBatis Mapper XML
736
-
737
- <details>
738
- <summary>UnitPriceMapper.xml</summary>
739
-
740
- ```xml
741
- <!-- src/main/resources/mapper/UnitPriceMapper.xml -->
742
- <?xml version="1.0" encoding="UTF-8" ?>
743
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
744
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
745
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.UnitPriceMapper">
746
-
747
- <resultMap id="UnitPriceResultMap" type="com.example.pms.domain.model.purchase.UnitPrice">
748
- <id property="id" column="ID"/>
749
- <result property="itemCode" column="品目コード"/>
750
- <result property="supplierCode" column="取引先コード"/>
751
- <result property="lotUnitQuantity" column="ロット単位数"/>
752
- <result property="effectiveFrom" column="使用開始日"/>
753
- <result property="effectiveTo" column="使用停止日"/>
754
- <result property="unitPrice" column="単価"/>
755
- <result property="createdAt" column="作成日時"/>
756
- <result property="createdBy" column="作成者"/>
757
- <result property="updatedAt" column="更新日時"/>
758
- <result property="updatedBy" column="更新者"/>
759
- </resultMap>
760
-
761
- <insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="ID">
762
- INSERT INTO "単価マスタ" (
763
- "品目コード", "取引先コード", "ロット単位数", "使用開始日",
764
- "使用停止日", "単価", "作成者"
765
- ) VALUES (
766
- #{itemCode},
767
- #{supplierCode},
768
- #{lotUnitQuantity},
769
- #{effectiveFrom},
770
- #{effectiveTo},
771
- #{unitPrice},
772
- #{createdBy}
773
- )
774
- </insert>
775
-
776
- <select id="findEffectiveUnitPrice" resultMap="UnitPriceResultMap">
777
- SELECT * FROM "単価マスタ"
778
- WHERE "品目コード" = #{itemCode}
779
- AND "取引先コード" = #{supplierCode}
780
- AND "使用開始日" &lt;= #{date}
781
- AND ("使用停止日" IS NULL OR "使用停止日" &gt;= #{date})
782
- ORDER BY "使用開始日" DESC
783
- LIMIT 1
784
- </select>
785
-
786
- <delete id="deleteAll">
787
- DELETE FROM "単価マスタ"
788
- </delete>
789
- </mapper>
790
- ```
791
-
792
- </details>
793
-
794
- <details>
795
- <summary>PurchaseOrderMapper.xml</summary>
796
-
797
- ```xml
798
- <!-- src/main/resources/mapper/PurchaseOrderMapper.xml -->
799
- <?xml version="1.0" encoding="UTF-8" ?>
800
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
801
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
802
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.PurchaseOrderMapper">
803
-
804
- <resultMap id="PurchaseOrderResultMap" type="com.example.pms.domain.model.purchase.PurchaseOrder">
805
- <id property="id" column="ID"/>
806
- <result property="purchaseOrderNumber" column="発注番号"/>
807
- <result property="orderDate" column="発注日"/>
808
- <result property="supplierCode" column="取引先コード"/>
809
- <result property="ordererCode" column="発注担当者コード"/>
810
- <result property="departmentCode" column="発注部門コード"/>
811
- <result property="status" column="ステータス"
812
- typeHandler="com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler"/>
813
- <result property="remarks" column="備考"/>
814
- <result property="createdAt" column="作成日時"/>
815
- <result property="createdBy" column="作成者"/>
816
- <result property="updatedAt" column="更新日時"/>
817
- <result property="updatedBy" column="更新者"/>
818
- </resultMap>
819
-
820
- <!-- PostgreSQL用 INSERT -->
821
- <insert id="insert" parameterType="com.example.pms.domain.model.purchase.PurchaseOrder"
822
- useGeneratedKeys="true" keyProperty="id" keyColumn="ID" databaseId="postgresql">
823
- INSERT INTO "発注データ" (
824
- "発注番号", "発注日", "取引先コード", "発注担当者コード", "発注部門コード",
825
- "ステータス", "備考", "作成者", "更新者"
826
- ) VALUES (
827
- #{purchaseOrderNumber},
828
- #{orderDate},
829
- #{supplierCode},
830
- #{ordererCode},
831
- #{departmentCode},
832
- #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler}::発注ステータス,
833
- #{remarks},
834
- #{createdBy},
835
- #{updatedBy}
836
- )
837
- </insert>
838
-
839
- <!-- H2用 INSERT -->
840
- <insert id="insert" parameterType="com.example.pms.domain.model.purchase.PurchaseOrder"
841
- useGeneratedKeys="true" keyProperty="id" keyColumn="ID" databaseId="h2">
842
- INSERT INTO "発注データ" (
843
- "発注番号", "発注日", "取引先コード", "発注担当者コード", "発注部門コード",
844
- "ステータス", "備考", "作成者", "更新者"
845
- ) VALUES (
846
- #{purchaseOrderNumber},
847
- #{orderDate},
848
- #{supplierCode},
849
- #{ordererCode},
850
- #{departmentCode},
851
- #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler},
852
- #{remarks},
853
- #{createdBy},
854
- #{updatedBy}
855
- )
856
- </insert>
857
-
858
- <select id="findById" resultMap="PurchaseOrderResultMap">
859
- SELECT * FROM "発注データ" WHERE "ID" = #{id}
860
- </select>
861
-
862
- <select id="findByPurchaseOrderNumber" resultMap="PurchaseOrderResultMap">
863
- SELECT * FROM "発注データ" WHERE "発注番号" = #{purchaseOrderNumber}
864
- </select>
865
-
866
- <select id="findBySupplierCode" resultMap="PurchaseOrderResultMap">
867
- SELECT * FROM "発注データ" WHERE "取引先コード" = #{supplierCode} ORDER BY "発注日" DESC
868
- </select>
869
-
870
- <!-- PostgreSQL用 findByStatus -->
871
- <select id="findByStatus" resultMap="PurchaseOrderResultMap" databaseId="postgresql">
872
- SELECT * FROM "発注データ" WHERE "ステータス" = #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler}::発注ステータス ORDER BY "発注日" DESC
873
- </select>
874
-
875
- <!-- H2用 findByStatus -->
876
- <select id="findByStatus" resultMap="PurchaseOrderResultMap" databaseId="h2">
877
- SELECT * FROM "発注データ" WHERE "ステータス" = #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler} ORDER BY "発注日" DESC
878
- </select>
879
-
880
- <select id="findAll" resultMap="PurchaseOrderResultMap">
881
- SELECT * FROM "発注データ" ORDER BY "発注日" DESC
882
- </select>
883
-
884
- <!-- PostgreSQL用 updateStatus -->
885
- <update id="updateStatus" databaseId="postgresql">
886
- UPDATE "発注データ"
887
- SET "ステータス" = #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler}::発注ステータス,
888
- "更新日時" = CURRENT_TIMESTAMP
889
- WHERE "ID" = #{id}
890
- </update>
891
-
892
- <!-- H2用 updateStatus -->
893
- <update id="updateStatus" databaseId="h2">
894
- UPDATE "発注データ"
895
- SET "ステータス" = #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler},
896
- "更新日時" = CURRENT_TIMESTAMP
897
- WHERE "ID" = #{id}
898
- </update>
899
-
900
- <!-- PostgreSQL用 DELETE -->
901
- <delete id="deleteAll" databaseId="postgresql">
902
- TRUNCATE TABLE "発注データ" CASCADE
903
- </delete>
904
-
905
- <!-- H2用 DELETE -->
906
- <delete id="deleteAll" databaseId="h2">
907
- DELETE FROM "発注データ"
908
- </delete>
909
- </mapper>
910
- ```
911
-
912
- </details>
913
-
914
- <details>
915
- <summary>PurchaseOrderDetailMapper.xml</summary>
916
-
917
- ```xml
918
- <!-- src/main/resources/mapper/PurchaseOrderDetailMapper.xml -->
919
- <?xml version="1.0" encoding="UTF-8" ?>
920
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
921
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
922
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.PurchaseOrderDetailMapper">
923
-
924
- <resultMap id="PurchaseOrderDetailResultMap" type="com.example.pms.domain.model.purchase.PurchaseOrderDetail">
925
- <id property="id" column="ID"/>
926
- <result property="purchaseOrderNumber" column="発注番号"/>
927
- <result property="lineNumber" column="発注行番号"/>
928
- <result property="orderNumber" column="オーダNO"/>
929
- <result property="deliveryLocationCode" column="納入場所コード"/>
930
- <result property="itemCode" column="品目コード"/>
931
- <result property="miscellaneousItemFlag" column="諸口品目区分"/>
932
- <result property="expectedReceivingDate" column="受入予定日"/>
933
- <result property="confirmedDeliveryDate" column="回答納期"/>
934
- <result property="orderUnitPrice" column="発注単価"/>
935
- <result property="orderQuantity" column="発注数量"/>
936
- <result property="receivedQuantity" column="入荷済数量"/>
937
- <result property="inspectedQuantity" column="検査済数量"/>
938
- <result property="acceptedQuantity" column="検収済数量"/>
939
- <result property="orderAmount" column="発注金額"/>
940
- <result property="taxAmount" column="消費税金額"/>
941
- <result property="completedFlag" column="完了フラグ"/>
942
- <result property="detailRemarks" column="明細備考"/>
943
- <result property="createdAt" column="作成日時"/>
944
- <result property="createdBy" column="作成者"/>
945
- <result property="updatedAt" column="更新日時"/>
946
- <result property="updatedBy" column="更新者"/>
947
- </resultMap>
948
-
949
- <insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="ID">
950
- INSERT INTO "発注明細データ" (
951
- "発注番号", "発注行番号", "オーダNO", "納入場所コード", "品目コード",
952
- "諸口品目区分", "受入予定日", "回答納期", "発注単価", "発注数量",
953
- "発注金額", "消費税金額", "作成者"
954
- ) VALUES (
955
- #{purchaseOrderNumber},
956
- #{lineNumber},
957
- #{orderNumber},
958
- #{deliveryLocationCode},
959
- #{itemCode},
960
- #{miscellaneousItemFlag},
961
- #{expectedReceivingDate},
962
- #{confirmedDeliveryDate},
963
- #{orderUnitPrice},
964
- #{orderQuantity},
965
- #{orderAmount},
966
- #{taxAmount},
967
- #{createdBy}
968
- )
969
- </insert>
970
-
971
- <select id="findByPurchaseOrderNumber" resultMap="PurchaseOrderDetailResultMap">
972
- SELECT * FROM "発注明細データ"
973
- WHERE "発注番号" = #{purchaseOrderNumber}
974
- ORDER BY "発注行番号"
975
- </select>
976
-
977
- <select id="findByPurchaseOrderNumberAndLineNumber" resultMap="PurchaseOrderDetailResultMap">
978
- SELECT * FROM "発注明細データ"
979
- WHERE "発注番号" = #{purchaseOrderNumber} AND "発注行番号" = #{lineNumber}
980
- </select>
981
-
982
- <update id="updateReceivedQuantity">
983
- UPDATE "発注明細データ"
984
- SET "入荷済数量" = #{receivedQuantity}, "更新日時" = CURRENT_TIMESTAMP
985
- WHERE "発注番号" = #{purchaseOrderNumber} AND "発注行番号" = #{lineNumber}
986
- </update>
987
-
988
- <update id="updateAcceptedQuantity">
989
- UPDATE "発注明細データ"
990
- SET "検収済数量" = #{acceptedQuantity}, "更新日時" = CURRENT_TIMESTAMP
991
- WHERE "発注番号" = #{purchaseOrderNumber} AND "発注行番号" = #{lineNumber}
992
- </update>
993
-
994
- <delete id="deleteAll">
995
- DELETE FROM "発注明細データ"
996
- </delete>
997
- </mapper>
998
- ```
999
-
1000
- </details>
1001
-
1002
- ### Mapper インターフェース
1003
-
1004
- <details>
1005
- <summary>UnitPriceMapper</summary>
1006
-
1007
- ```java
1008
- // src/main/java/com/example/pms/infrastructure/out/persistence/mapper/UnitPriceMapper.java
1009
- package com.example.pms.infrastructure.out.persistence.mapper;
1010
-
1011
- import com.example.pms.domain.model.purchase.UnitPrice;
1012
- import org.apache.ibatis.annotations.Mapper;
1013
- import org.apache.ibatis.annotations.Param;
1014
-
1015
- import java.time.LocalDate;
1016
-
1017
- @Mapper
1018
- public interface UnitPriceMapper {
1019
- void insert(UnitPrice unitPrice);
1020
- UnitPrice findEffectiveUnitPrice(@Param("itemCode") String itemCode,
1021
- @Param("supplierCode") String supplierCode,
1022
- @Param("date") LocalDate date);
1023
- void deleteAll();
1024
- }
1025
- ```
1026
-
1027
- </details>
1028
-
1029
- <details>
1030
- <summary>PurchaseOrderMapper</summary>
1031
-
1032
- ```java
1033
- // src/main/java/com/example/pms/infrastructure/out/persistence/mapper/PurchaseOrderMapper.java
1034
- package com.example.pms.infrastructure.out.persistence.mapper;
1035
-
1036
- import com.example.pms.domain.model.purchase.PurchaseOrder;
1037
- import com.example.pms.domain.model.purchase.PurchaseOrderStatus;
1038
- import org.apache.ibatis.annotations.Mapper;
1039
- import org.apache.ibatis.annotations.Param;
1040
-
1041
- import java.util.List;
1042
-
1043
- @Mapper
1044
- public interface PurchaseOrderMapper {
1045
- void insert(PurchaseOrder purchaseOrder);
1046
- PurchaseOrder findById(Integer id);
1047
- PurchaseOrder findByPurchaseOrderNumber(String purchaseOrderNumber);
1048
- List<PurchaseOrder> findBySupplierCode(String supplierCode);
1049
- List<PurchaseOrder> findByStatus(PurchaseOrderStatus status);
1050
- List<PurchaseOrder> findAll();
1051
- void updateStatus(@Param("id") Integer id, @Param("status") PurchaseOrderStatus status);
1052
- void deleteAll();
1053
- }
1054
- ```
1055
-
1056
- </details>
1057
-
1058
- <details>
1059
- <summary>PurchaseOrderDetailMapper</summary>
1060
-
1061
- ```java
1062
- // src/main/java/com/example/pms/infrastructure/out/persistence/mapper/PurchaseOrderDetailMapper.java
1063
- package com.example.pms.infrastructure.out.persistence.mapper;
1064
-
1065
- import com.example.pms.domain.model.purchase.PurchaseOrderDetail;
1066
- import org.apache.ibatis.annotations.Mapper;
1067
- import org.apache.ibatis.annotations.Param;
1068
-
1069
- import java.math.BigDecimal;
1070
- import java.util.List;
1071
-
1072
- @Mapper
1073
- public interface PurchaseOrderDetailMapper {
1074
- void insert(PurchaseOrderDetail detail);
1075
- List<PurchaseOrderDetail> findByPurchaseOrderNumber(String purchaseOrderNumber);
1076
- PurchaseOrderDetail findByPurchaseOrderNumberAndLineNumber(
1077
- @Param("purchaseOrderNumber") String purchaseOrderNumber,
1078
- @Param("lineNumber") Integer lineNumber);
1079
- void updateReceivedQuantity(@Param("purchaseOrderNumber") String purchaseOrderNumber,
1080
- @Param("lineNumber") Integer lineNumber,
1081
- @Param("receivedQuantity") BigDecimal receivedQuantity);
1082
- void updateAcceptedQuantity(@Param("purchaseOrderNumber") String purchaseOrderNumber,
1083
- @Param("lineNumber") Integer lineNumber,
1084
- @Param("acceptedQuantity") BigDecimal acceptedQuantity);
1085
- void deleteAll();
1086
- }
1087
- ```
1088
-
1089
- </details>
1090
-
1091
- ### 発注サービスの実装
1092
-
1093
- <details>
1094
- <summary>PurchaseOrderService</summary>
1095
-
1096
- ```java
1097
- // src/main/java/com/example/pms/application/service/PurchaseOrderService.java
1098
- package com.example.pms.application.service;
1099
-
1100
- import com.example.pms.domain.model.purchase.*;
1101
- import com.example.pms.infrastructure.out.persistence.mapper.*;
1102
- import lombok.RequiredArgsConstructor;
1103
- import org.springframework.stereotype.Service;
1104
- import org.springframework.transaction.annotation.Transactional;
1105
-
1106
- import java.math.BigDecimal;
1107
- import java.math.RoundingMode;
1108
- import java.time.LocalDate;
1109
- import java.time.format.DateTimeFormatter;
1110
- import java.util.ArrayList;
1111
- import java.util.List;
1112
-
1113
- @Service
1114
- @RequiredArgsConstructor
1115
- public class PurchaseOrderService {
1116
-
1117
- private final PurchaseOrderMapper purchaseOrderMapper;
1118
- private final PurchaseOrderDetailMapper purchaseOrderDetailMapper;
1119
- private final UnitPriceMapper unitPriceMapper;
1120
-
1121
- /**
1122
- * 発注番号を生成する
1123
- */
1124
- private String generatePurchaseOrderNumber(LocalDate orderDate) {
1125
- String prefix = "PO-" + orderDate.format(DateTimeFormatter.ofPattern("yyyyMM")) + "-";
1126
- String latestNumber = purchaseOrderMapper.findLatestPurchaseOrderNumber(prefix + "%");
1127
-
1128
- int sequence = 1;
1129
- if (latestNumber != null) {
1130
- int currentSequence = Integer.parseInt(latestNumber.substring(latestNumber.length() - 4));
1131
- sequence = currentSequence + 1;
1132
- }
1133
-
1134
- return prefix + String.format("%04d", sequence);
1135
- }
1136
-
1137
- /**
1138
- * 発注を作成する
1139
- */
1140
- @Transactional
1141
- public PurchaseOrder createPurchaseOrder(PurchaseOrderCreateInput input) {
1142
- String purchaseOrderNumber = generatePurchaseOrderNumber(input.getOrderDate());
1143
- BigDecimal taxRate = input.getTaxRate() != null ? input.getTaxRate() : new BigDecimal("10");
1144
-
1145
- // 発注ヘッダを作成
1146
- PurchaseOrder purchaseOrder = PurchaseOrder.builder()
1147
- .purchaseOrderNumber(purchaseOrderNumber)
1148
- .orderDate(input.getOrderDate())
1149
- .supplierCode(input.getSupplierCode())
1150
- .ordererCode(input.getOrdererCode())
1151
- .departmentCode(input.getDepartmentCode())
1152
- .status(PurchaseOrderStatus.CREATING)
1153
- .remarks(input.getRemarks())
1154
- .build();
1155
- purchaseOrderMapper.insert(purchaseOrder);
1156
-
1157
- // 発注明細を作成
1158
- List<PurchaseOrderDetail> details = new ArrayList<>();
1159
- int lineNumber = 0;
1160
-
1161
- for (PurchaseOrderDetailInput detailInput : input.getDetails()) {
1162
- lineNumber++;
1163
-
1164
- // 単価を取得
1165
- UnitPrice unitPrice = unitPriceMapper.findEffectiveUnitPrice(
1166
- detailInput.getItemCode(),
1167
- input.getSupplierCode(),
1168
- input.getOrderDate()
1169
- );
1170
-
1171
- if (unitPrice == null) {
1172
- throw new IllegalArgumentException(
1173
- "Unit price not found: " + detailInput.getItemCode() + " / " + input.getSupplierCode());
1174
- }
1175
-
1176
- // 金額計算
1177
- BigDecimal orderAmount = unitPrice.getUnitPrice().multiply(detailInput.getOrderQuantity());
1178
- BigDecimal taxAmount = orderAmount.multiply(taxRate)
1179
- .divide(new BigDecimal("100"), 0, RoundingMode.HALF_UP);
1180
-
1181
- PurchaseOrderDetail detail = PurchaseOrderDetail.builder()
1182
- .purchaseOrderNumber(purchaseOrderNumber)
1183
- .lineNumber(lineNumber)
1184
- .orderNumber(detailInput.getOrderNumber())
1185
- .deliveryLocationCode(detailInput.getDeliveryLocationCode())
1186
- .itemCode(detailInput.getItemCode())
1187
- .miscellaneousItemFlag(false)
1188
- .expectedReceivingDate(detailInput.getExpectedReceivingDate())
1189
- .orderUnitPrice(unitPrice.getUnitPrice())
1190
- .orderQuantity(detailInput.getOrderQuantity())
1191
- .orderAmount(orderAmount)
1192
- .taxAmount(taxAmount)
1193
- .build();
1194
- purchaseOrderDetailMapper.insert(detail);
1195
-
1196
- details.add(detail);
1197
- }
1198
-
1199
- purchaseOrder.setDetails(details);
1200
- return purchaseOrder;
1201
- }
1202
-
1203
- /**
1204
- * 発注を確定する
1205
- */
1206
- @Transactional
1207
- public PurchaseOrder confirmPurchaseOrder(String purchaseOrderNumber) {
1208
- PurchaseOrder purchaseOrder = purchaseOrderMapper.findByPurchaseOrderNumber(purchaseOrderNumber);
1209
-
1210
- if (purchaseOrder == null) {
1211
- throw new IllegalArgumentException("Purchase order not found: " + purchaseOrderNumber);
1212
- }
1213
-
1214
- if (purchaseOrder.getStatus() != PurchaseOrderStatus.CREATING) {
1215
- throw new IllegalStateException("Only creating purchase orders can be confirmed: " + purchaseOrderNumber);
1216
- }
1217
-
1218
- purchaseOrderMapper.updateStatus(purchaseOrderNumber, PurchaseOrderStatus.ORDERED);
1219
- purchaseOrder.setStatus(PurchaseOrderStatus.ORDERED);
1220
- return purchaseOrder;
1221
- }
1222
-
1223
- /**
1224
- * 発注を取消する
1225
- */
1226
- @Transactional
1227
- public PurchaseOrder cancelPurchaseOrder(String purchaseOrderNumber) {
1228
- PurchaseOrder purchaseOrder = purchaseOrderMapper.findByPurchaseOrderNumber(purchaseOrderNumber);
1229
-
1230
- if (purchaseOrder == null) {
1231
- throw new IllegalArgumentException("Purchase order not found: " + purchaseOrderNumber);
1232
- }
1233
-
1234
- List<PurchaseOrderDetail> details = purchaseOrderDetailMapper.findByPurchaseOrderNumber(purchaseOrderNumber);
1235
- boolean hasReceived = details.stream()
1236
- .anyMatch(d -> d.getReceivedQuantity() != null &&
1237
- d.getReceivedQuantity().compareTo(BigDecimal.ZERO) > 0);
1238
-
1239
- if (hasReceived) {
1240
- throw new IllegalStateException("Cannot cancel purchase order with received items: " + purchaseOrderNumber);
1241
- }
1242
-
1243
- purchaseOrderMapper.updateStatus(purchaseOrderNumber, PurchaseOrderStatus.CANCELLED);
1244
- purchaseOrder.setStatus(PurchaseOrderStatus.CANCELLED);
1245
- return purchaseOrder;
1246
- }
1247
- }
1248
- ```
1249
-
1250
- </details>
1251
-
1252
- <details>
1253
- <summary>入力 DTO クラス</summary>
1254
-
1255
- ```java
1256
- // src/main/java/com/example/pms/application/service/PurchaseOrderCreateInput.java
1257
- package com.example.pms.application.service;
1258
-
1259
- import lombok.Builder;
1260
- import lombok.Data;
1261
-
1262
- import java.math.BigDecimal;
1263
- import java.time.LocalDate;
1264
- import java.util.List;
1265
-
1266
- @Data
1267
- @Builder
1268
- public class PurchaseOrderCreateInput {
1269
- private String supplierCode;
1270
- private LocalDate orderDate;
1271
- private String ordererCode;
1272
- private String departmentCode;
1273
- private BigDecimal taxRate;
1274
- private String remarks;
1275
- private List<PurchaseOrderDetailInput> details;
1276
- }
1277
- ```
1278
-
1279
- ```java
1280
- // src/main/java/com/example/pms/application/service/PurchaseOrderDetailInput.java
1281
- package com.example.pms.application.service;
1282
-
1283
- import lombok.Builder;
1284
- import lombok.Data;
1285
-
1286
- import java.math.BigDecimal;
1287
- import java.time.LocalDate;
1288
-
1289
- @Data
1290
- @Builder
1291
- public class PurchaseOrderDetailInput {
1292
- private String itemCode;
1293
- private BigDecimal orderQuantity;
1294
- private LocalDate expectedReceivingDate;
1295
- private String orderNumber;
1296
- private String deliveryLocationCode;
1297
- }
1298
- ```
1299
-
1300
- </details>
1301
-
1302
- ### TDD: 発注データの登録テスト
1303
-
1304
- <details>
1305
- <summary>PurchaseOrderServiceTest</summary>
1306
-
1307
- ```java
1308
- // src/test/java/com/example/pms/application/service/PurchaseOrderServiceTest.java
1309
- package com.example.pms.application.service;
1310
-
1311
- import com.example.pms.domain.model.item.Item;
1312
- import com.example.pms.domain.model.item.ItemCategory;
1313
- import com.example.pms.domain.model.master.Supplier;
1314
- import com.example.pms.domain.model.purchase.*;
1315
- import com.example.pms.infrastructure.out.persistence.mapper.*;
1316
- import org.junit.jupiter.api.*;
1317
- import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
1318
- import org.springframework.beans.factory.annotation.Autowired;
1319
- import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
1320
- import org.springframework.context.annotation.Import;
1321
- import org.springframework.test.context.DynamicPropertyRegistry;
1322
- import org.springframework.test.context.DynamicPropertySource;
1323
- import org.testcontainers.containers.PostgreSQLContainer;
1324
- import org.testcontainers.junit.jupiter.Container;
1325
- import org.testcontainers.junit.jupiter.Testcontainers;
1326
-
1327
- import java.math.BigDecimal;
1328
- import java.time.LocalDate;
1329
- import java.util.List;
1330
-
1331
- import static org.assertj.core.api.Assertions.*;
1332
-
1333
- @MybatisTest
1334
- @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
1335
- @Import(PurchaseOrderService.class)
1336
- @Testcontainers
1337
- @DisplayName("発注業務")
1338
- class PurchaseOrderServiceTest {
1339
-
1340
- @Container
1341
- static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
1342
- .withDatabaseName("testdb")
1343
- .withUsername("testuser")
1344
- .withPassword("testpass");
1345
-
1346
- @DynamicPropertySource
1347
- static void configureProperties(DynamicPropertyRegistry registry) {
1348
- registry.add("spring.datasource.url", postgres::getJdbcUrl);
1349
- registry.add("spring.datasource.username", postgres::getUsername);
1350
- registry.add("spring.datasource.password", postgres::getPassword);
1351
- }
1352
-
1353
- @Autowired
1354
- private PurchaseOrderService purchaseOrderService;
1355
-
1356
- @Autowired
1357
- private PurchaseOrderMapper purchaseOrderMapper;
1358
-
1359
- @Autowired
1360
- private PurchaseOrderDetailMapper purchaseOrderDetailMapper;
1361
-
1362
- @Autowired
1363
- private ItemMapper itemMapper;
1364
-
1365
- @Autowired
1366
- private SupplierMapper supplierMapper;
1367
-
1368
- @Autowired
1369
- private UnitPriceMapper unitPriceMapper;
1370
-
1371
- @BeforeEach
1372
- void setUp() {
1373
- purchaseOrderDetailMapper.deleteAll();
1374
- purchaseOrderMapper.deleteAll();
1375
- unitPriceMapper.deleteAll();
1376
- supplierMapper.deleteAll();
1377
- itemMapper.deleteAll();
1378
- }
1379
-
1380
- @Nested
1381
- @DisplayName("発注データの作成")
1382
- class PurchaseOrderCreation {
1383
-
1384
- @Test
1385
- @DisplayName("購買オーダから発注データを作成できる")
1386
- void canCreatePurchaseOrderFromOrder() {
1387
- // Arrange: マスタデータを準備
1388
- Supplier supplier = Supplier.builder()
1389
- .supplierCode("SUP-001")
1390
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1391
- .supplierName("テスト仕入先株式会社")
1392
- .supplierNameKana("テストシイレサキカブシキガイシャ")
1393
- .build();
1394
- supplierMapper.insert(supplier);
1395
-
1396
- Item item = Item.builder()
1397
- .itemCode("MAT-001")
1398
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1399
- .itemName("材料A")
1400
- .itemCategory(ItemCategory.MATERIAL)
1401
- .build();
1402
- itemMapper.insert(item);
1403
-
1404
- UnitPrice unitPrice = UnitPrice.builder()
1405
- .itemCode("MAT-001")
1406
- .supplierCode("SUP-001")
1407
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1408
- .unitPrice(new BigDecimal("1000"))
1409
- .build();
1410
- unitPriceMapper.insert(unitPrice);
1411
-
1412
- // Act: 発注を作成
1413
- PurchaseOrderCreateInput input = PurchaseOrderCreateInput.builder()
1414
- .supplierCode("SUP-001")
1415
- .orderDate(LocalDate.of(2025, 1, 15))
1416
- .taxRate(new BigDecimal("10"))
1417
- .details(List.of(
1418
- PurchaseOrderDetailInput.builder()
1419
- .itemCode("MAT-001")
1420
- .orderQuantity(new BigDecimal("100"))
1421
- .expectedReceivingDate(LocalDate.of(2025, 1, 25))
1422
- .build()
1423
- ))
1424
- .build();
1425
-
1426
- PurchaseOrder purchaseOrder = purchaseOrderService.createPurchaseOrder(input);
1427
-
1428
- // Assert
1429
- assertThat(purchaseOrder).isNotNull();
1430
- assertThat(purchaseOrder.getPurchaseOrderNumber()).startsWith("PO-");
1431
- assertThat(purchaseOrder.getStatus()).isEqualTo(PurchaseOrderStatus.CREATING);
1432
- assertThat(purchaseOrder.getDetails()).hasSize(1);
1433
- assertThat(purchaseOrder.getDetails().get(0).getOrderQuantity())
1434
- .isEqualByComparingTo(new BigDecimal("100"));
1435
- assertThat(purchaseOrder.getDetails().get(0).getOrderUnitPrice())
1436
- .isEqualByComparingTo(new BigDecimal("1000"));
1437
- assertThat(purchaseOrder.getDetails().get(0).getOrderAmount())
1438
- .isEqualByComparingTo(new BigDecimal("100000"));
1439
- }
1440
-
1441
- @Test
1442
- @DisplayName("発注を発注済ステータスに変更できる")
1443
- void canConfirmPurchaseOrder() {
1444
- // Arrange: 発注データを準備
1445
- Supplier supplier = Supplier.builder()
1446
- .supplierCode("SUP-003")
1447
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1448
- .supplierName("ステータス確認用仕入先")
1449
- .build();
1450
- supplierMapper.insert(supplier);
1451
-
1452
- Item item = Item.builder()
1453
- .itemCode("MAT-004")
1454
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1455
- .itemName("材料D")
1456
- .itemCategory(ItemCategory.MATERIAL)
1457
- .build();
1458
- itemMapper.insert(item);
1459
-
1460
- unitPriceMapper.insert(UnitPrice.builder()
1461
- .itemCode("MAT-004")
1462
- .supplierCode("SUP-003")
1463
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1464
- .unitPrice(new BigDecimal("2000"))
1465
- .build());
1466
-
1467
- PurchaseOrderCreateInput input = PurchaseOrderCreateInput.builder()
1468
- .supplierCode("SUP-003")
1469
- .orderDate(LocalDate.of(2025, 1, 15))
1470
- .details(List.of(
1471
- PurchaseOrderDetailInput.builder()
1472
- .itemCode("MAT-004")
1473
- .orderQuantity(new BigDecimal("10"))
1474
- .expectedReceivingDate(LocalDate.of(2025, 1, 25))
1475
- .build()
1476
- ))
1477
- .build();
1478
-
1479
- PurchaseOrder purchaseOrder = purchaseOrderService.createPurchaseOrder(input);
1480
-
1481
- // Act: 発注確定
1482
- PurchaseOrder confirmedOrder = purchaseOrderService.confirmPurchaseOrder(
1483
- purchaseOrder.getPurchaseOrderNumber());
1484
-
1485
- // Assert
1486
- assertThat(confirmedOrder.getStatus()).isEqualTo(PurchaseOrderStatus.ORDERED);
1487
- }
1488
- }
1489
-
1490
- @Nested
1491
- @DisplayName("消費税計算")
1492
- class TaxCalculation {
1493
-
1494
- @Test
1495
- @DisplayName("消費税額が正しく計算される")
1496
- void calculatesTaxCorrectly() {
1497
- // Arrange
1498
- Supplier supplier = Supplier.builder()
1499
- .supplierCode("SUP-004")
1500
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1501
- .supplierName("消費税確認用仕入先")
1502
- .build();
1503
- supplierMapper.insert(supplier);
1504
-
1505
- Item item = Item.builder()
1506
- .itemCode("MAT-005")
1507
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1508
- .itemName("材料E")
1509
- .itemCategory(ItemCategory.MATERIAL)
1510
- .build();
1511
- itemMapper.insert(item);
1512
-
1513
- unitPriceMapper.insert(UnitPrice.builder()
1514
- .itemCode("MAT-005")
1515
- .supplierCode("SUP-004")
1516
- .effectiveFrom(LocalDate.of(2025, 1, 1))
1517
- .unitPrice(new BigDecimal("1000"))
1518
- .build());
1519
-
1520
- // Act
1521
- PurchaseOrderCreateInput input = PurchaseOrderCreateInput.builder()
1522
- .supplierCode("SUP-004")
1523
- .orderDate(LocalDate.of(2025, 1, 15))
1524
- .taxRate(new BigDecimal("10"))
1525
- .details(List.of(
1526
- PurchaseOrderDetailInput.builder()
1527
- .itemCode("MAT-005")
1528
- .orderQuantity(new BigDecimal("100"))
1529
- .expectedReceivingDate(LocalDate.of(2025, 1, 25))
1530
- .build()
1531
- ))
1532
- .build();
1533
-
1534
- PurchaseOrder purchaseOrder = purchaseOrderService.createPurchaseOrder(input);
1535
-
1536
- // Assert: 100 × 1000 = 100,000円、消費税 10,000円
1537
- assertThat(purchaseOrder.getDetails().get(0).getOrderAmount())
1538
- .isEqualByComparingTo(new BigDecimal("100000"));
1539
- assertThat(purchaseOrder.getDetails().get(0).getTaxAmount())
1540
- .isEqualByComparingTo(new BigDecimal("10000"));
1541
- }
1542
- }
1543
- }
1544
- ```
1545
-
1546
- </details>
1547
-
1548
- ---
1549
-
1550
- ## 25.2 入荷・検収業務の DB 設計
1551
-
1552
- 入荷・検収業務は、発注した品目が納品された後の一連の処理を管理します。
1553
-
1554
- ### 入荷・受入業務の流れ
1555
-
1556
- ```plantuml
1557
- @startuml
1558
-
1559
- title 入荷から検収までの流れ
1560
-
1561
- start
1562
-
1563
- :発注明細;
1564
-
1565
- :入荷受付;
1566
- note right
1567
- ・納品書と現品の確認
1568
- ・入荷数量の記録
1569
- end note
1570
-
1571
- :受入検査;
1572
- note right
1573
- ・品質検査の実施
1574
- ・良品/不良品の判定
1575
- end note
1576
-
1577
- if (検査結果) then (合格)
1578
- :検収処理;
1579
- note right
1580
- ・検収数量の確定
1581
- ・仕入計上
1582
- end note
1583
-
1584
- :在庫計上;
1585
- else (不合格)
1586
- :検収返品;
1587
- note right
1588
- ・返品処理
1589
- ・仕入先への通知
1590
- end note
1591
- endif
1592
-
1593
- stop
1594
-
1595
- @enduml
1596
- ```
1597
-
1598
- ### 入荷・検収関連のスキーマ設計
1599
-
1600
- #### テーブル構造
1601
-
1602
- | テーブル名 | 説明 |
1603
- |-----------|------|
1604
- | 入荷受入データ | 入荷情報(入荷番号、数量、担当者等) |
1605
- | 欠点マスタ | 不良品の欠点コード・内容 |
1606
- | 受入検査データ | 受入検査情報(良品数、不良品数等) |
1607
- | 検収データ | 検収情報(検収数、検収金額等) |
1608
-
1609
- #### データの関連
1610
-
1611
- ```plantuml
1612
- @startuml
1613
-
1614
- title 入荷・検収データの関連
1615
-
1616
- entity "発注明細データ" as pod {
1617
- * 発注番号 [PK]
1618
- * 発注行番号 [PK]
1619
- --
1620
- 発注数量
1621
- 入荷済数量
1622
- 検収済数量
1623
- }
1624
-
1625
- entity "入荷受入データ" as rcv {
1626
- * 入荷番号 [PK]
1627
- --
1628
- 発注番号 [FK]
1629
- 発注行番号 [FK]
1630
- 入荷日
1631
- 入荷数量
1632
- 入荷受入区分
1633
- }
1634
-
1635
- entity "受入検査データ" as ins {
1636
- * 受入検査番号 [PK]
1637
- --
1638
- 入荷番号 [FK]
1639
- 受入検査日
1640
- 良品数
1641
- 不良品数
1642
- }
1643
-
1644
- entity "検収データ" as acc {
1645
- * 検収番号 [PK]
1646
- --
1647
- 受入検査番号 [FK]
1648
- 発注番号 [FK]
1649
- 発注行番号 [FK]
1650
- 検収日
1651
- 検収数
1652
- 検収金額
1653
- }
1654
-
1655
- pod ||--o{ rcv : 入荷
1656
- rcv ||--o{ ins : 検査
1657
- ins ||--o{ acc : 検収
1658
- pod ||--o{ acc : 検収
1659
-
1660
- @enduml
1661
- ```
1662
-
1663
- <details>
1664
- <summary>DDL: 入荷・検収関連テーブル</summary>
1665
-
1666
- ```sql
1667
- -- V010__create_receiving_tables.sql
1668
-
1669
- -- 入荷受入データ
1670
- CREATE TABLE "入荷受入データ" (
1671
- "ID" SERIAL PRIMARY KEY,
1672
- "入荷番号" VARCHAR(20) UNIQUE NOT NULL,
1673
- "発注番号" VARCHAR(20) NOT NULL,
1674
- "発注行番号" INTEGER NOT NULL,
1675
- "入荷日" DATE NOT NULL,
1676
- "入荷担当者コード" VARCHAR(20),
1677
- "入荷受入区分" 入荷受入区分 DEFAULT '通常入荷' NOT NULL,
1678
- "品目コード" VARCHAR(20) NOT NULL,
1679
- "諸口品目区分" BOOLEAN DEFAULT FALSE NOT NULL,
1680
- "入荷数量" DECIMAL(15, 2) NOT NULL,
1681
- "入荷備考" TEXT,
1682
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1683
- "作成者" VARCHAR(50),
1684
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1685
- "更新者" VARCHAR(50),
1686
- CONSTRAINT "fk_入荷受入_発注明細"
1687
- FOREIGN KEY ("発注番号", "発注行番号") REFERENCES "発注明細データ"("発注番号", "発注行番号"),
1688
- CONSTRAINT "fk_入荷受入_品目"
1689
- FOREIGN KEY ("品目コード") REFERENCES "品目マスタ"("品目コード")
1690
- );
1691
-
1692
- -- 欠点マスタ
1693
- CREATE TABLE "欠点マスタ" (
1694
- "ID" SERIAL PRIMARY KEY,
1695
- "欠点コード" VARCHAR(20) UNIQUE NOT NULL,
1696
- "欠点内容" VARCHAR(200) NOT NULL,
1697
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1698
- "作成者" VARCHAR(50),
1699
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1700
- "更新者" VARCHAR(50)
1701
- );
1702
-
1703
- -- 受入検査データ
1704
- CREATE TABLE "受入検査データ" (
1705
- "ID" SERIAL PRIMARY KEY,
1706
- "受入検査番号" VARCHAR(20) UNIQUE NOT NULL,
1707
- "入荷番号" VARCHAR(20) NOT NULL,
1708
- "発注番号" VARCHAR(20) NOT NULL,
1709
- "発注行番号" INTEGER NOT NULL,
1710
- "受入検査日" DATE NOT NULL,
1711
- "受入検査担当者コード" VARCHAR(20),
1712
- "品目コード" VARCHAR(20) NOT NULL,
1713
- "諸口品目区分" BOOLEAN DEFAULT FALSE NOT NULL,
1714
- "良品数" DECIMAL(15, 2) NOT NULL,
1715
- "不良品数" DECIMAL(15, 2) DEFAULT 0 NOT NULL,
1716
- "受入検査備考" TEXT,
1717
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1718
- "作成者" VARCHAR(50),
1719
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1720
- "更新者" VARCHAR(50),
1721
- CONSTRAINT "fk_受入検査_入荷"
1722
- FOREIGN KEY ("入荷番号") REFERENCES "入荷受入データ"("入荷番号"),
1723
- CONSTRAINT "fk_受入検査_品目"
1724
- FOREIGN KEY ("品目コード") REFERENCES "品目マスタ"("品目コード")
1725
- );
1726
-
1727
- -- 検収データ
1728
- CREATE TABLE "検収データ" (
1729
- "ID" SERIAL PRIMARY KEY,
1730
- "検収番号" VARCHAR(20) UNIQUE NOT NULL,
1731
- "受入検査番号" VARCHAR(20) NOT NULL,
1732
- "発注番号" VARCHAR(20) NOT NULL,
1733
- "発注行番号" INTEGER NOT NULL,
1734
- "検収日" DATE NOT NULL,
1735
- "検収担当者コード" VARCHAR(20),
1736
- "取引先コード" VARCHAR(20) NOT NULL,
1737
- "品目コード" VARCHAR(20) NOT NULL,
1738
- "諸口品目区分" BOOLEAN DEFAULT FALSE NOT NULL,
1739
- "検収数" DECIMAL(15, 2) NOT NULL,
1740
- "検収単価" DECIMAL(15, 2) NOT NULL,
1741
- "検収金額" DECIMAL(15, 2) NOT NULL,
1742
- "検収消費税額" DECIMAL(15, 2) DEFAULT 0 NOT NULL,
1743
- "検収備考" TEXT,
1744
- "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1745
- "作成者" VARCHAR(50),
1746
- "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
1747
- "更新者" VARCHAR(50),
1748
- CONSTRAINT "fk_検収_受入検査"
1749
- FOREIGN KEY ("受入検査番号") REFERENCES "受入検査データ"("受入検査番号"),
1750
- CONSTRAINT "fk_検収_発注明細"
1751
- FOREIGN KEY ("発注番号", "発注行番号") REFERENCES "発注明細データ"("発注番号", "発注行番号"),
1752
- CONSTRAINT "fk_検収_取引先"
1753
- FOREIGN KEY ("取引先コード") REFERENCES "取引先マスタ"("取引先コード"),
1754
- CONSTRAINT "fk_検収_品目"
1755
- FOREIGN KEY ("品目コード") REFERENCES "品目マスタ"("品目コード")
1756
- );
1757
-
1758
- -- インデックス
1759
- CREATE INDEX "idx_入荷受入_発注番号" ON "入荷受入データ"("発注番号", "発注行番号");
1760
- CREATE INDEX "idx_入荷受入_入荷日" ON "入荷受入データ"("入荷日");
1761
- CREATE INDEX "idx_受入検査_入荷番号" ON "受入検査データ"("入荷番号");
1762
- CREATE INDEX "idx_検収_受入検査番号" ON "検収データ"("受入検査番号");
1763
- CREATE INDEX "idx_検収_発注番号" ON "検収データ"("発注番号", "発注行番号");
1764
- ```
1765
-
1766
- </details>
1767
-
1768
- ### 発注明細との紐付け
1769
-
1770
- 入荷受入データは発注明細データと紐付けられ、入荷のたびに発注明細の「入荷済数量」が更新されます。これにより、分納への対応が可能になります。
1771
-
1772
- ### 入荷受入区分(入荷 / 受入返品)
1773
-
1774
- 入荷受入区分は以下の値を取ります。
1775
-
1776
- | 区分 | 説明 |
1777
- |-----|------|
1778
- | 通常入荷 | 通常の入荷処理 |
1779
- | 分割入荷 | 発注数量を分割して入荷する場合 |
1780
- | 返品入荷 | 検収後の返品を受け入れる場合 |
1781
-
1782
- ### Java エンティティの定義
1783
-
1784
- <details>
1785
- <summary>入荷受入データエンティティ</summary>
1786
-
1787
- ```java
1788
- // src/main/java/com/example/pms/domain/model/purchase/Receiving.java
1789
- package com.example.pms.domain.model.purchase;
1790
-
1791
- import com.example.pms.domain.model.item.Item;
1792
- import lombok.AllArgsConstructor;
1793
- import lombok.Builder;
1794
- import lombok.Data;
1795
- import lombok.NoArgsConstructor;
1796
-
1797
- import java.math.BigDecimal;
1798
- import java.time.LocalDate;
1799
- import java.time.LocalDateTime;
1800
- import java.util.List;
1801
-
1802
- @Data
1803
- @Builder
1804
- @NoArgsConstructor
1805
- @AllArgsConstructor
1806
- public class Receiving {
1807
- private Integer id;
1808
- private String receivingNumber;
1809
- private String purchaseOrderNumber;
1810
- private Integer lineNumber;
1811
- private LocalDate receivingDate;
1812
- private String receiverCode;
1813
- private ReceivingType receivingType;
1814
- private String itemCode;
1815
- private Boolean miscellaneousItemFlag;
1816
- private BigDecimal receivingQuantity;
1817
- private String remarks;
1818
- private LocalDateTime createdAt;
1819
- private String createdBy;
1820
- private LocalDateTime updatedAt;
1821
- private String updatedBy;
1822
-
1823
- // リレーション
1824
- private PurchaseOrderDetail purchaseOrderDetail;
1825
- private Item item;
1826
- private List<Inspection> inspections;
1827
- }
1828
- ```
1829
-
1830
- </details>
1831
-
1832
- <details>
1833
- <summary>受入検査データエンティティ</summary>
1834
-
1835
- ```java
1836
- // src/main/java/com/example/pms/domain/model/purchase/Inspection.java
1837
- package com.example.pms.domain.model.purchase;
1838
-
1839
- import com.example.pms.domain.model.item.Item;
1840
- import lombok.AllArgsConstructor;
1841
- import lombok.Builder;
1842
- import lombok.Data;
1843
- import lombok.NoArgsConstructor;
1844
-
1845
- import java.math.BigDecimal;
1846
- import java.time.LocalDate;
1847
- import java.time.LocalDateTime;
1848
- import java.util.List;
1849
-
1850
- @Data
1851
- @Builder
1852
- @NoArgsConstructor
1853
- @AllArgsConstructor
1854
- public class Inspection {
1855
- private Integer id;
1856
- private String inspectionNumber;
1857
- private String receivingNumber;
1858
- private String purchaseOrderNumber;
1859
- private Integer lineNumber;
1860
- private LocalDate inspectionDate;
1861
- private String inspectorCode;
1862
- private String itemCode;
1863
- private Boolean miscellaneousItemFlag;
1864
- private BigDecimal goodQuantity;
1865
- private BigDecimal defectQuantity;
1866
- private String remarks;
1867
- private LocalDateTime createdAt;
1868
- private String createdBy;
1869
- private LocalDateTime updatedAt;
1870
- private String updatedBy;
1871
-
1872
- // リレーション
1873
- private Receiving receiving;
1874
- private Item item;
1875
- private List<Acceptance> acceptances;
1876
- }
1877
- ```
1878
-
1879
- </details>
1880
-
1881
- <details>
1882
- <summary>検収データエンティティ</summary>
1883
-
1884
- ```java
1885
- // src/main/java/com/example/pms/domain/model/purchase/Acceptance.java
1886
- package com.example.pms.domain.model.purchase;
1887
-
1888
- import com.example.pms.domain.model.item.Item;
1889
- import com.example.pms.domain.model.supplier.Supplier;
1890
- import lombok.AllArgsConstructor;
1891
- import lombok.Builder;
1892
- import lombok.Data;
1893
- import lombok.NoArgsConstructor;
1894
-
1895
- import java.math.BigDecimal;
1896
- import java.time.LocalDate;
1897
- import java.time.LocalDateTime;
1898
-
1899
- @Data
1900
- @Builder
1901
- @NoArgsConstructor
1902
- @AllArgsConstructor
1903
- public class Acceptance {
1904
- private Integer id;
1905
- private String acceptanceNumber;
1906
- private String inspectionNumber;
1907
- private String purchaseOrderNumber;
1908
- private Integer lineNumber;
1909
- private LocalDate acceptanceDate;
1910
- private String acceptorCode;
1911
- private String supplierCode;
1912
- private String itemCode;
1913
- private Boolean miscellaneousItemFlag;
1914
- private BigDecimal acceptedQuantity;
1915
- private BigDecimal unitPrice;
1916
- private BigDecimal amount;
1917
- private BigDecimal taxAmount;
1918
- private String remarks;
1919
- private LocalDateTime createdAt;
1920
- private String createdBy;
1921
- private LocalDateTime updatedAt;
1922
- private String updatedBy;
1923
-
1924
- // リレーション
1925
- private Inspection inspection;
1926
- private PurchaseOrderDetail purchaseOrderDetail;
1927
- private Supplier supplier;
1928
- private Item item;
1929
- }
1930
- ```
1931
-
1932
- </details>
1933
-
1934
- ### MyBatis Mapper XML(入荷・検収)
1935
-
1936
- <details>
1937
- <summary>ReceivingMapper.xml</summary>
1938
-
1939
- ```xml
1940
- <!-- src/main/resources/mapper/ReceivingMapper.xml -->
1941
- <?xml version="1.0" encoding="UTF-8" ?>
1942
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
1943
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
1944
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.ReceivingMapper">
1945
-
1946
- <resultMap id="ReceivingResultMap" type="com.example.pms.domain.model.purchase.Receiving">
1947
- <id property="id" column="ID"/>
1948
- <result property="receivingNumber" column="入荷番号"/>
1949
- <result property="purchaseOrderNumber" column="発注番号"/>
1950
- <result property="lineNumber" column="発注行番号"/>
1951
- <result property="receivingDate" column="入荷日"/>
1952
- <result property="receiverCode" column="入荷担当者コード"/>
1953
- <result property="receivingType" column="入荷受入区分"
1954
- typeHandler="com.example.pms.infrastructure.out.persistence.typehandler.ReceivingTypeTypeHandler"/>
1955
- <result property="itemCode" column="品目コード"/>
1956
- <result property="miscellaneousItemFlag" column="諸口品目区分"/>
1957
- <result property="receivingQuantity" column="入荷数量"/>
1958
- <result property="remarks" column="入荷備考"/>
1959
- <result property="createdAt" column="作成日時"/>
1960
- <result property="createdBy" column="作成者"/>
1961
- <result property="updatedAt" column="更新日時"/>
1962
- <result property="updatedBy" column="更新者"/>
1963
- </resultMap>
1964
-
1965
- <!-- PostgreSQL用 INSERT -->
1966
- <insert id="insert" parameterType="com.example.pms.domain.model.purchase.Receiving"
1967
- useGeneratedKeys="true" keyProperty="id" keyColumn="ID" databaseId="postgresql">
1968
- INSERT INTO "入荷受入データ" (
1969
- "入荷番号", "発注番号", "発注行番号", "入荷日", "入荷担当者コード",
1970
- "入荷受入区分", "品目コード", "諸口品目区分", "入荷数量", "入荷備考", "作成者", "更新者"
1971
- ) VALUES (
1972
- #{receivingNumber},
1973
- #{purchaseOrderNumber},
1974
- #{lineNumber},
1975
- #{receivingDate},
1976
- #{receiverCode},
1977
- #{receivingType, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.ReceivingTypeTypeHandler}::入荷受入区分,
1978
- #{itemCode},
1979
- #{miscellaneousItemFlag},
1980
- #{receivingQuantity},
1981
- #{remarks},
1982
- #{createdBy},
1983
- #{updatedBy}
1984
- )
1985
- </insert>
1986
-
1987
- <!-- H2用 INSERT -->
1988
- <insert id="insert" parameterType="com.example.pms.domain.model.purchase.Receiving"
1989
- useGeneratedKeys="true" keyProperty="id" keyColumn="ID" databaseId="h2">
1990
- INSERT INTO "入荷受入データ" (
1991
- "入荷番号", "発注番号", "発注行番号", "入荷日", "入荷担当者コード",
1992
- "入荷受入区分", "品目コード", "諸口品目区分", "入荷数量", "入荷備考", "作成者", "更新者"
1993
- ) VALUES (
1994
- #{receivingNumber},
1995
- #{purchaseOrderNumber},
1996
- #{lineNumber},
1997
- #{receivingDate},
1998
- #{receiverCode},
1999
- #{receivingType, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.ReceivingTypeTypeHandler},
2000
- #{itemCode},
2001
- #{miscellaneousItemFlag},
2002
- #{receivingQuantity},
2003
- #{remarks},
2004
- #{createdBy},
2005
- #{updatedBy}
2006
- )
2007
- </insert>
2008
-
2009
- <select id="findById" resultMap="ReceivingResultMap">
2010
- SELECT * FROM "入荷受入データ" WHERE "ID" = #{id}
2011
- </select>
2012
-
2013
- <select id="findByReceivingNumber" resultMap="ReceivingResultMap">
2014
- SELECT * FROM "入荷受入データ" WHERE "入荷番号" = #{receivingNumber}
2015
- </select>
2016
-
2017
- <select id="findByPurchaseOrderNumber" resultMap="ReceivingResultMap">
2018
- SELECT * FROM "入荷受入データ" WHERE "発注番号" = #{purchaseOrderNumber} ORDER BY "入荷日" DESC
2019
- </select>
2020
-
2021
- <select id="findByPurchaseOrderNumberAndLineNumber" resultMap="ReceivingResultMap">
2022
- SELECT * FROM "入荷受入データ"
2023
- WHERE "発注番号" = #{purchaseOrderNumber} AND "発注行番号" = #{lineNumber}
2024
- ORDER BY "入荷日" DESC
2025
- </select>
2026
-
2027
- <select id="findAll" resultMap="ReceivingResultMap">
2028
- SELECT * FROM "入荷受入データ" ORDER BY "入荷日" DESC
2029
- </select>
2030
-
2031
- <!-- PostgreSQL用 DELETE -->
2032
- <delete id="deleteAll" databaseId="postgresql">
2033
- TRUNCATE TABLE "入荷受入データ" CASCADE
2034
- </delete>
2035
-
2036
- <!-- H2用 DELETE -->
2037
- <delete id="deleteAll" databaseId="h2">
2038
- DELETE FROM "入荷受入データ"
2039
- </delete>
2040
- </mapper>
2041
- ```
2042
-
2043
- </details>
2044
-
2045
- <details>
2046
- <summary>InspectionMapper.xml</summary>
2047
-
2048
- ```xml
2049
- <!-- src/main/resources/mapper/InspectionMapper.xml -->
2050
- <?xml version="1.0" encoding="UTF-8" ?>
2051
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
2052
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
2053
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.InspectionMapper">
2054
-
2055
- <resultMap id="InspectionResultMap" type="com.example.pms.domain.model.purchase.Inspection">
2056
- <id property="id" column="ID"/>
2057
- <result property="inspectionNumber" column="受入検査番号"/>
2058
- <result property="receivingNumber" column="入荷番号"/>
2059
- <result property="purchaseOrderNumber" column="発注番号"/>
2060
- <result property="lineNumber" column="発注行番号"/>
2061
- <result property="inspectionDate" column="受入検査日"/>
2062
- <result property="inspectorCode" column="受入検査担当者コード"/>
2063
- <result property="itemCode" column="品目コード"/>
2064
- <result property="miscellaneousItemFlag" column="諸口品目区分"/>
2065
- <result property="goodQuantity" column="良品数"/>
2066
- <result property="defectQuantity" column="不良品数"/>
2067
- <result property="remarks" column="受入検査備考"/>
2068
- <result property="createdAt" column="作成日時"/>
2069
- <result property="createdBy" column="作成者"/>
2070
- <result property="updatedAt" column="更新日時"/>
2071
- <result property="updatedBy" column="更新者"/>
2072
- </resultMap>
2073
-
2074
- <insert id="insert" parameterType="com.example.pms.domain.model.purchase.Inspection"
2075
- useGeneratedKeys="true" keyProperty="id" keyColumn="ID">
2076
- INSERT INTO "受入検査データ" (
2077
- "受入検査番号", "入荷番号", "発注番号", "発注行番号", "受入検査日",
2078
- "受入検査担当者コード", "品目コード", "諸口品目区分", "良品数", "不良品数",
2079
- "受入検査備考", "作成者", "更新者"
2080
- ) VALUES (
2081
- #{inspectionNumber},
2082
- #{receivingNumber},
2083
- #{purchaseOrderNumber},
2084
- #{lineNumber},
2085
- #{inspectionDate},
2086
- #{inspectorCode},
2087
- #{itemCode},
2088
- #{miscellaneousItemFlag},
2089
- #{goodQuantity},
2090
- #{defectQuantity},
2091
- #{remarks},
2092
- #{createdBy},
2093
- #{updatedBy}
2094
- )
2095
- </insert>
2096
-
2097
- <select id="findById" resultMap="InspectionResultMap">
2098
- SELECT * FROM "受入検査データ" WHERE "ID" = #{id}
2099
- </select>
2100
-
2101
- <select id="findByInspectionNumber" resultMap="InspectionResultMap">
2102
- SELECT * FROM "受入検査データ" WHERE "受入検査番号" = #{inspectionNumber}
2103
- </select>
2104
-
2105
- <select id="findByReceivingNumber" resultMap="InspectionResultMap">
2106
- SELECT * FROM "受入検査データ" WHERE "入荷番号" = #{receivingNumber} ORDER BY "受入検査日" DESC
2107
- </select>
2108
-
2109
- <select id="findAll" resultMap="InspectionResultMap">
2110
- SELECT * FROM "受入検査データ" ORDER BY "受入検査日" DESC
2111
- </select>
2112
-
2113
- <!-- PostgreSQL用 DELETE -->
2114
- <delete id="deleteAll" databaseId="postgresql">
2115
- TRUNCATE TABLE "受入検査データ" CASCADE
2116
- </delete>
2117
-
2118
- <!-- H2用 DELETE -->
2119
- <delete id="deleteAll" databaseId="h2">
2120
- DELETE FROM "受入検査データ"
2121
- </delete>
2122
- </mapper>
2123
- ```
2124
-
2125
- </details>
2126
-
2127
- <details>
2128
- <summary>AcceptanceMapper.xml</summary>
2129
-
2130
- ```xml
2131
- <!-- src/main/resources/mapper/AcceptanceMapper.xml -->
2132
- <?xml version="1.0" encoding="UTF-8" ?>
2133
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
2134
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
2135
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.AcceptanceMapper">
2136
-
2137
- <resultMap id="AcceptanceResultMap" type="com.example.pms.domain.model.purchase.Acceptance">
2138
- <id property="id" column="ID"/>
2139
- <result property="acceptanceNumber" column="検収番号"/>
2140
- <result property="inspectionNumber" column="受入検査番号"/>
2141
- <result property="purchaseOrderNumber" column="発注番号"/>
2142
- <result property="lineNumber" column="発注行番号"/>
2143
- <result property="acceptanceDate" column="検収日"/>
2144
- <result property="acceptorCode" column="検収担当者コード"/>
2145
- <result property="supplierCode" column="取引先コード"/>
2146
- <result property="itemCode" column="品目コード"/>
2147
- <result property="miscellaneousItemFlag" column="諸口品目区分"/>
2148
- <result property="acceptedQuantity" column="検収数"/>
2149
- <result property="unitPrice" column="検収単価"/>
2150
- <result property="amount" column="検収金額"/>
2151
- <result property="taxAmount" column="検収消費税額"/>
2152
- <result property="remarks" column="検収備考"/>
2153
- <result property="createdAt" column="作成日時"/>
2154
- <result property="createdBy" column="作成者"/>
2155
- <result property="updatedAt" column="更新日時"/>
2156
- <result property="updatedBy" column="更新者"/>
2157
- </resultMap>
2158
-
2159
- <insert id="insert" parameterType="com.example.pms.domain.model.purchase.Acceptance"
2160
- useGeneratedKeys="true" keyProperty="id" keyColumn="ID">
2161
- INSERT INTO "検収データ" (
2162
- "検収番号", "受入検査番号", "発注番号", "発注行番号", "検収日",
2163
- "検収担当者コード", "取引先コード", "品目コード", "諸口品目区分",
2164
- "検収数", "検収単価", "検収金額", "検収消費税額", "検収備考", "作成者", "更新者"
2165
- ) VALUES (
2166
- #{acceptanceNumber},
2167
- #{inspectionNumber},
2168
- #{purchaseOrderNumber},
2169
- #{lineNumber},
2170
- #{acceptanceDate},
2171
- #{acceptorCode},
2172
- #{supplierCode},
2173
- #{itemCode},
2174
- #{miscellaneousItemFlag},
2175
- #{acceptedQuantity},
2176
- #{unitPrice},
2177
- #{amount},
2178
- #{taxAmount},
2179
- #{remarks},
2180
- #{createdBy},
2181
- #{updatedBy}
2182
- )
2183
- </insert>
2184
-
2185
- <select id="findById" resultMap="AcceptanceResultMap">
2186
- SELECT * FROM "検収データ" WHERE "ID" = #{id}
2187
- </select>
2188
-
2189
- <select id="findByAcceptanceNumber" resultMap="AcceptanceResultMap">
2190
- SELECT * FROM "検収データ" WHERE "検収番号" = #{acceptanceNumber}
2191
- </select>
2192
-
2193
- <select id="findByInspectionNumber" resultMap="AcceptanceResultMap">
2194
- SELECT * FROM "検収データ" WHERE "受入検査番号" = #{inspectionNumber} ORDER BY "検収日" DESC
2195
- </select>
2196
-
2197
- <select id="findByPurchaseOrderNumber" resultMap="AcceptanceResultMap">
2198
- SELECT * FROM "検収データ" WHERE "発注番号" = #{purchaseOrderNumber} ORDER BY "検収日" DESC
2199
- </select>
2200
-
2201
- <select id="findAll" resultMap="AcceptanceResultMap">
2202
- SELECT * FROM "検収データ" ORDER BY "検収日" DESC
2203
- </select>
2204
-
2205
- <!-- PostgreSQL用 DELETE -->
2206
- <delete id="deleteAll" databaseId="postgresql">
2207
- TRUNCATE TABLE "検収データ" CASCADE
2208
- </delete>
2209
-
2210
- <!-- H2用 DELETE -->
2211
- <delete id="deleteAll" databaseId="h2">
2212
- DELETE FROM "検収データ"
2213
- </delete>
2214
- </mapper>
2215
- ```
2216
-
2217
- </details>
2218
-
2219
- ### Mapper インターフェース(入荷・検収)
2220
-
2221
- <details>
2222
- <summary>ReceivingMapper</summary>
2223
-
2224
- ```java
2225
- // src/main/java/com/example/pms/infrastructure/out/persistence/mapper/ReceivingMapper.java
2226
- package com.example.pms.infrastructure.out.persistence.mapper;
2227
-
2228
- import com.example.pms.domain.model.purchase.Receiving;
2229
- import org.apache.ibatis.annotations.Mapper;
2230
- import org.apache.ibatis.annotations.Param;
2231
-
2232
- import java.util.List;
2233
-
2234
- @Mapper
2235
- public interface ReceivingMapper {
2236
- void insert(Receiving receiving);
2237
- Receiving findByReceivingNumber(String receivingNumber);
2238
- String findLatestReceivingNumber(String prefix);
2239
- List<Receiving> findByPurchaseOrderNumber(String purchaseOrderNumber);
2240
- void deleteAll();
2241
- }
2242
- ```
2243
-
2244
- </details>
2245
-
2246
- <details>
2247
- <summary>InspectionMapper</summary>
2248
-
2249
- ```java
2250
- // src/main/java/com/example/pms/infrastructure/out/persistence/mapper/InspectionMapper.java
2251
- package com.example.pms.infrastructure.out.persistence.mapper;
2252
-
2253
- import com.example.pms.domain.model.purchase.Inspection;
2254
- import org.apache.ibatis.annotations.Mapper;
2255
-
2256
- @Mapper
2257
- public interface InspectionMapper {
2258
- void insert(Inspection inspection);
2259
- Inspection findByInspectionNumber(String inspectionNumber);
2260
- String findLatestInspectionNumber(String prefix);
2261
- void deleteAll();
2262
- }
2263
- ```
2264
-
2265
- </details>
2266
-
2267
- <details>
2268
- <summary>AcceptanceMapper</summary>
2269
-
2270
- ```java
2271
- // src/main/java/com/example/pms/infrastructure/out/persistence/mapper/AcceptanceMapper.java
2272
- package com.example.pms.infrastructure.out.persistence.mapper;
2273
-
2274
- import com.example.pms.domain.model.purchase.Acceptance;
2275
- import org.apache.ibatis.annotations.Mapper;
2276
-
2277
- import java.util.List;
2278
-
2279
- @Mapper
2280
- public interface AcceptanceMapper {
2281
- void insert(Acceptance acceptance);
2282
- Acceptance findByAcceptanceNumber(String acceptanceNumber);
2283
- String findLatestAcceptanceNumber(String prefix);
2284
- List<Acceptance> findByPurchaseOrderNumber(String purchaseOrderNumber);
2285
- void deleteAll();
2286
- }
2287
- ```
2288
-
2289
- </details>
2290
-
2291
- ### コマンドクラス
2292
-
2293
- ヘキサゴナルアーキテクチャに従い、入力用 DTO は `application/port/in/command` に配置し、命名は `xxxCommand` とします。
2294
-
2295
- <details>
2296
- <summary>ReceivingCommand</summary>
2297
-
2298
- ```java
2299
- // src/main/java/com/example/pms/application/port/in/command/ReceivingCommand.java
2300
- package com.example.pms.application.port.in.command;
2301
-
2302
- import com.example.pms.domain.model.purchase.ReceivingType;
2303
- import lombok.Builder;
2304
- import lombok.Data;
2305
-
2306
- import java.math.BigDecimal;
2307
- import java.time.LocalDate;
2308
-
2309
- @Data
2310
- @Builder
2311
- public class ReceivingCommand {
2312
- private String purchaseOrderNumber;
2313
- private Integer lineNumber;
2314
- private LocalDate receivingDate;
2315
- private String receiverCode;
2316
- private ReceivingType receivingType;
2317
- private BigDecimal receivingQuantity;
2318
- private String remarks;
2319
- }
2320
- ```
2321
-
2322
- </details>
2323
-
2324
- <details>
2325
- <summary>InspectionCommand</summary>
2326
-
2327
- ```java
2328
- // src/main/java/com/example/pms/application/port/in/command/InspectionCommand.java
2329
- package com.example.pms.application.port.in.command;
2330
-
2331
- import lombok.Builder;
2332
- import lombok.Data;
2333
-
2334
- import java.math.BigDecimal;
2335
- import java.time.LocalDate;
2336
-
2337
- @Data
2338
- @Builder
2339
- public class InspectionCommand {
2340
- private String receivingNumber;
2341
- private LocalDate inspectionDate;
2342
- private String inspectorCode;
2343
- private BigDecimal goodQuantity;
2344
- private BigDecimal defectQuantity;
2345
- private String remarks;
2346
- }
2347
- ```
2348
-
2349
- </details>
2350
-
2351
- <details>
2352
- <summary>AcceptanceCommand</summary>
2353
-
2354
- ```java
2355
- // src/main/java/com/example/pms/application/port/in/command/AcceptanceCommand.java
2356
- package com.example.pms.application.port.in.command;
2357
-
2358
- import lombok.Builder;
2359
- import lombok.Data;
2360
-
2361
- import java.math.BigDecimal;
2362
- import java.time.LocalDate;
2363
-
2364
- @Data
2365
- @Builder
2366
- public class AcceptanceCommand {
2367
- private String inspectionNumber;
2368
- private LocalDate acceptanceDate;
2369
- private String acceptorCode;
2370
- private BigDecimal taxRate;
2371
- private String remarks;
2372
- }
2373
- ```
2374
-
2375
- </details>
2376
-
2377
- ### 入荷検収サービスの実装
2378
-
2379
- <details>
2380
- <summary>ReceivingService</summary>
2381
-
2382
- ```java
2383
- // src/main/java/com/example/pms/application/service/ReceivingService.java
2384
- package com.example.pms.application.service;
2385
-
2386
- import com.example.pms.domain.model.purchase.*;
2387
- import com.example.pms.infrastructure.out.persistence.mapper.*;
2388
- import lombok.RequiredArgsConstructor;
2389
- import org.springframework.stereotype.Service;
2390
- import org.springframework.transaction.annotation.Transactional;
2391
-
2392
- import java.math.BigDecimal;
2393
- import java.math.RoundingMode;
2394
- import java.time.LocalDate;
2395
- import java.time.format.DateTimeFormatter;
2396
-
2397
- @Service
2398
- @RequiredArgsConstructor
2399
- public class ReceivingService {
2400
-
2401
- private final ReceivingMapper receivingMapper;
2402
- private final InspectionMapper inspectionMapper;
2403
- private final AcceptanceMapper acceptanceMapper;
2404
- private final PurchaseOrderMapper purchaseOrderMapper;
2405
- private final PurchaseOrderDetailMapper purchaseOrderDetailMapper;
2406
-
2407
- /**
2408
- * 入荷を登録する
2409
- */
2410
- @Transactional
2411
- public Receiving registerReceiving(ReceivingCommand command) {
2412
- PurchaseOrderDetail detail = purchaseOrderDetailMapper.findByPurchaseOrderNumberAndLineNumber(
2413
- command.getPurchaseOrderNumber(), command.getLineNumber());
2414
-
2415
- if (detail == null) {
2416
- throw new IllegalArgumentException(
2417
- "Purchase order detail not found: " + command.getPurchaseOrderNumber() + "-" + command.getLineNumber());
2418
- }
2419
-
2420
- BigDecimal receivedQuantity = detail.getReceivedQuantity() != null ?
2421
- detail.getReceivedQuantity() : BigDecimal.ZERO;
2422
- BigDecimal remainingQuantity = detail.getOrderQuantity().subtract(receivedQuantity);
2423
-
2424
- if (command.getReceivingQuantity().compareTo(remainingQuantity) > 0) {
2425
- throw new IllegalStateException("Cannot receive more than ordered quantity");
2426
- }
2427
-
2428
- String receivingNumber = generateReceivingNumber(command.getReceivingDate());
2429
-
2430
- Receiving receiving = Receiving.builder()
2431
- .receivingNumber(receivingNumber)
2432
- .purchaseOrderNumber(command.getPurchaseOrderNumber())
2433
- .lineNumber(command.getLineNumber())
2434
- .receivingDate(command.getReceivingDate())
2435
- .receiverCode(command.getReceiverCode())
2436
- .receivingType(command.getReceivingType() != null ? command.getReceivingType() : ReceivingType.NORMAL)
2437
- .itemCode(detail.getItemCode())
2438
- .miscellaneousItemFlag(detail.getMiscellaneousItemFlag())
2439
- .receivingQuantity(command.getReceivingQuantity())
2440
- .remarks(command.getRemarks())
2441
- .build();
2442
- receivingMapper.insert(receiving);
2443
-
2444
- BigDecimal newReceivedQuantity = receivedQuantity.add(command.getReceivingQuantity());
2445
- purchaseOrderDetailMapper.updateReceivedQuantity(
2446
- command.getPurchaseOrderNumber(), command.getLineNumber(), newReceivedQuantity);
2447
-
2448
- PurchaseOrderStatus newStatus = newReceivedQuantity.compareTo(detail.getOrderQuantity()) >= 0
2449
- ? PurchaseOrderStatus.RECEIVED : PurchaseOrderStatus.PARTIALLY_RECEIVED;
2450
- purchaseOrderMapper.updateStatus(command.getPurchaseOrderNumber(), newStatus);
2451
-
2452
- return receiving;
2453
- }
2454
-
2455
- private String generateReceivingNumber(LocalDate date) {
2456
- String prefix = "RCV-" + date.format(DateTimeFormatter.ofPattern("yyyyMM")) + "-";
2457
- String latestNumber = receivingMapper.findLatestReceivingNumber(prefix + "%");
2458
-
2459
- int sequence = 1;
2460
- if (latestNumber != null) {
2461
- int currentSequence = Integer.parseInt(latestNumber.substring(latestNumber.length() - 4));
2462
- sequence = currentSequence + 1;
2463
- }
2464
-
2465
- return prefix + String.format("%04d", sequence);
2466
- }
2467
-
2468
- /**
2469
- * 受入検査を登録する
2470
- */
2471
- @Transactional
2472
- public Inspection registerInspection(InspectionCommand command) {
2473
- Receiving receiving = receivingMapper.findByReceivingNumber(command.getReceivingNumber());
2474
-
2475
- if (receiving == null) {
2476
- throw new IllegalArgumentException("Receiving not found: " + command.getReceivingNumber());
2477
- }
2478
-
2479
- BigDecimal totalQuantity = command.getGoodQuantity().add(command.getDefectQuantity());
2480
- if (totalQuantity.compareTo(receiving.getReceivingQuantity()) > 0) {
2481
- throw new IllegalStateException("Inspection quantity exceeds receiving quantity");
2482
- }
2483
-
2484
- String inspectionNumber = generateInspectionNumber(command.getInspectionDate());
2485
-
2486
- Inspection inspection = Inspection.builder()
2487
- .inspectionNumber(inspectionNumber)
2488
- .receivingNumber(command.getReceivingNumber())
2489
- .purchaseOrderNumber(receiving.getPurchaseOrderNumber())
2490
- .lineNumber(receiving.getLineNumber())
2491
- .inspectionDate(command.getInspectionDate())
2492
- .inspectorCode(command.getInspectorCode())
2493
- .itemCode(receiving.getItemCode())
2494
- .miscellaneousItemFlag(receiving.getMiscellaneousItemFlag())
2495
- .goodQuantity(command.getGoodQuantity())
2496
- .defectQuantity(command.getDefectQuantity())
2497
- .remarks(command.getRemarks())
2498
- .build();
2499
- inspectionMapper.insert(inspection);
2500
-
2501
- return inspection;
2502
- }
2503
-
2504
- private String generateInspectionNumber(LocalDate date) {
2505
- String prefix = "INS-" + date.format(DateTimeFormatter.ofPattern("yyyyMM")) + "-";
2506
- String latestNumber = inspectionMapper.findLatestInspectionNumber(prefix + "%");
2507
-
2508
- int sequence = 1;
2509
- if (latestNumber != null) {
2510
- int currentSequence = Integer.parseInt(latestNumber.substring(latestNumber.length() - 4));
2511
- sequence = currentSequence + 1;
2512
- }
2513
-
2514
- return prefix + String.format("%04d", sequence);
2515
- }
2516
-
2517
- /**
2518
- * 検収処理を行う
2519
- */
2520
- @Transactional
2521
- public Acceptance processAcceptance(AcceptanceCommand command) {
2522
- Inspection inspection = inspectionMapper.findByInspectionNumber(command.getInspectionNumber());
2523
-
2524
- if (inspection == null) {
2525
- throw new IllegalArgumentException("Inspection not found: " + command.getInspectionNumber());
2526
- }
2527
-
2528
- PurchaseOrderDetail detail = purchaseOrderDetailMapper.findByPurchaseOrderNumberAndLineNumber(
2529
- inspection.getPurchaseOrderNumber(), inspection.getLineNumber());
2530
-
2531
- PurchaseOrder purchaseOrder = purchaseOrderMapper.findByPurchaseOrderNumber(
2532
- inspection.getPurchaseOrderNumber());
2533
-
2534
- BigDecimal taxRate = command.getTaxRate() != null ? command.getTaxRate() : new BigDecimal("10");
2535
-
2536
- BigDecimal amount = detail.getOrderUnitPrice().multiply(inspection.getGoodQuantity());
2537
- BigDecimal taxAmount = amount.multiply(taxRate)
2538
- .divide(new BigDecimal("100"), 0, RoundingMode.HALF_UP);
2539
-
2540
- String acceptanceNumber = generateAcceptanceNumber(command.getAcceptanceDate());
2541
-
2542
- Acceptance acceptance = Acceptance.builder()
2543
- .acceptanceNumber(acceptanceNumber)
2544
- .inspectionNumber(command.getInspectionNumber())
2545
- .purchaseOrderNumber(inspection.getPurchaseOrderNumber())
2546
- .lineNumber(inspection.getLineNumber())
2547
- .acceptanceDate(command.getAcceptanceDate())
2548
- .acceptorCode(command.getAcceptorCode())
2549
- .supplierCode(purchaseOrder.getSupplierCode())
2550
- .itemCode(inspection.getItemCode())
2551
- .miscellaneousItemFlag(inspection.getMiscellaneousItemFlag())
2552
- .acceptedQuantity(inspection.getGoodQuantity())
2553
- .unitPrice(detail.getOrderUnitPrice())
2554
- .amount(amount)
2555
- .taxAmount(taxAmount)
2556
- .remarks(command.getRemarks())
2557
- .build();
2558
- acceptanceMapper.insert(acceptance);
2559
-
2560
- return acceptance;
2561
- }
2562
-
2563
- private String generateAcceptanceNumber(LocalDate date) {
2564
- String prefix = "ACC-" + date.format(DateTimeFormatter.ofPattern("yyyyMM")) + "-";
2565
- String latestNumber = acceptanceMapper.findLatestAcceptanceNumber(prefix + "%");
2566
-
2567
- int sequence = 1;
2568
- if (latestNumber != null) {
2569
- int currentSequence = Integer.parseInt(latestNumber.substring(latestNumber.length() - 4));
2570
- sequence = currentSequence + 1;
2571
- }
2572
-
2573
- return prefix + String.format("%04d", sequence);
2574
- }
2575
- }
2576
- ```
2577
-
2578
- </details>
2579
-
2580
- ### 在庫計上処理
2581
-
2582
- 検収処理が完了すると、良品数が在庫に計上されます。在庫計上処理の詳細は第28章で解説します。
2583
-
2584
- ### TDD: 入荷・検収のテスト
2585
-
2586
- <details>
2587
- <summary>ReceivingServiceTest</summary>
2588
-
2589
- ```java
2590
- // src/test/java/com/example/pms/application/service/ReceivingServiceTest.java
2591
- package com.example.pms.application.service;
2592
-
2593
- import com.example.pms.domain.model.item.Item;
2594
- import com.example.pms.domain.model.item.ItemCategory;
2595
- import com.example.pms.domain.model.master.Supplier;
2596
- import com.example.pms.domain.model.purchase.*;
2597
- import com.example.pms.infrastructure.out.persistence.mapper.*;
2598
- import org.junit.jupiter.api.*;
2599
- import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
2600
- import org.springframework.beans.factory.annotation.Autowired;
2601
- import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
2602
- import org.springframework.context.annotation.Import;
2603
- import org.springframework.test.context.DynamicPropertyRegistry;
2604
- import org.springframework.test.context.DynamicPropertySource;
2605
- import org.testcontainers.containers.PostgreSQLContainer;
2606
- import org.testcontainers.junit.jupiter.Container;
2607
- import org.testcontainers.junit.jupiter.Testcontainers;
2608
-
2609
- import java.math.BigDecimal;
2610
- import java.time.LocalDate;
2611
- import java.util.List;
2612
-
2613
- import static org.assertj.core.api.Assertions.*;
2614
-
2615
- @MybatisTest
2616
- @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
2617
- @Import({ReceivingService.class, PurchaseOrderService.class})
2618
- @Testcontainers
2619
- @DisplayName("入荷・検収業務")
2620
- class ReceivingServiceTest {
2621
-
2622
- @Container
2623
- static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
2624
- .withDatabaseName("testdb")
2625
- .withUsername("testuser")
2626
- .withPassword("testpass");
2627
-
2628
- @DynamicPropertySource
2629
- static void configureProperties(DynamicPropertyRegistry registry) {
2630
- registry.add("spring.datasource.url", postgres::getJdbcUrl);
2631
- registry.add("spring.datasource.username", postgres::getUsername);
2632
- registry.add("spring.datasource.password", postgres::getPassword);
2633
- }
2634
-
2635
- @Autowired
2636
- private ReceivingService receivingService;
2637
-
2638
- @Autowired
2639
- private PurchaseOrderService purchaseOrderService;
2640
-
2641
- @Autowired
2642
- private ItemMapper itemMapper;
2643
-
2644
- @Autowired
2645
- private SupplierMapper supplierMapper;
2646
-
2647
- @Autowired
2648
- private UnitPriceMapper unitPriceMapper;
2649
-
2650
- @Autowired
2651
- private AcceptanceMapper acceptanceMapper;
2652
-
2653
- @Autowired
2654
- private InspectionMapper inspectionMapper;
2655
-
2656
- @Autowired
2657
- private ReceivingMapper receivingMapper;
2658
-
2659
- @Autowired
2660
- private PurchaseOrderDetailMapper purchaseOrderDetailMapper;
2661
-
2662
- @Autowired
2663
- private PurchaseOrderMapper purchaseOrderMapper;
2664
-
2665
- private PurchaseOrder testPurchaseOrder;
2666
-
2667
- @BeforeEach
2668
- void setUp() {
2669
- // テストデータのクリーンアップ
2670
- acceptanceMapper.deleteAll();
2671
- inspectionMapper.deleteAll();
2672
- receivingMapper.deleteAll();
2673
- purchaseOrderDetailMapper.deleteAll();
2674
- purchaseOrderMapper.deleteAll();
2675
- unitPriceMapper.deleteAll();
2676
- supplierMapper.deleteAll();
2677
- itemMapper.deleteAll();
2678
-
2679
- // マスタデータの準備
2680
- Supplier supplier = Supplier.builder()
2681
- .supplierCode("SUP-RCV-001")
2682
- .effectiveFrom(LocalDate.of(2025, 1, 1))
2683
- .supplierName("入荷テスト用仕入先")
2684
- .build();
2685
- supplierMapper.insert(supplier);
2686
-
2687
- Item item = Item.builder()
2688
- .itemCode("MAT-RCV-001")
2689
- .effectiveFrom(LocalDate.of(2025, 1, 1))
2690
- .itemName("入荷テスト用材料")
2691
- .itemCategory(ItemCategory.MATERIAL)
2692
- .build();
2693
- itemMapper.insert(item);
2694
-
2695
- unitPriceMapper.insert(UnitPrice.builder()
2696
- .itemCode("MAT-RCV-001")
2697
- .supplierCode("SUP-RCV-001")
2698
- .effectiveFrom(LocalDate.of(2025, 1, 1))
2699
- .unitPrice(new BigDecimal("1000"))
2700
- .build());
2701
-
2702
- // テスト用発注を作成
2703
- PurchaseOrderCreateInput poInput = PurchaseOrderCreateInput.builder()
2704
- .supplierCode("SUP-RCV-001")
2705
- .orderDate(LocalDate.of(2025, 1, 15))
2706
- .details(List.of(
2707
- PurchaseOrderDetailInput.builder()
2708
- .itemCode("MAT-RCV-001")
2709
- .orderQuantity(new BigDecimal("100"))
2710
- .expectedReceivingDate(LocalDate.of(2025, 1, 25))
2711
- .build()
2712
- ))
2713
- .build();
2714
- testPurchaseOrder = purchaseOrderService.createPurchaseOrder(poInput);
2715
- purchaseOrderService.confirmPurchaseOrder(testPurchaseOrder.getPurchaseOrderNumber());
2716
- }
2717
-
2718
- @Nested
2719
- @DisplayName("入荷登録")
2720
- class ReceivingRegistration {
2721
-
2722
- @Test
2723
- @DisplayName("発注に対して入荷を登録できる")
2724
- void canRegisterReceiving() {
2725
- // Act
2726
- ReceivingCommand command = ReceivingCommand.builder()
2727
- .purchaseOrderNumber(testPurchaseOrder.getPurchaseOrderNumber())
2728
- .lineNumber(1)
2729
- .receivingDate(LocalDate.of(2025, 1, 25))
2730
- .receivingQuantity(new BigDecimal("50"))
2731
- .build();
2732
-
2733
- Receiving receiving = receivingService.registerReceiving(command);
2734
-
2735
- // Assert
2736
- assertThat(receiving).isNotNull();
2737
- assertThat(receiving.getReceivingNumber()).startsWith("RCV-");
2738
- assertThat(receiving.getReceivingQuantity()).isEqualByComparingTo(new BigDecimal("50"));
2739
- assertThat(receiving.getReceivingType()).isEqualTo(ReceivingType.NORMAL);
2740
- }
2741
-
2742
- @Test
2743
- @DisplayName("分割入荷を登録できる")
2744
- void canRegisterSplitReceiving() {
2745
- // Arrange: 1回目の入荷
2746
- receivingService.registerReceiving(ReceivingCommand.builder()
2747
- .purchaseOrderNumber(testPurchaseOrder.getPurchaseOrderNumber())
2748
- .lineNumber(1)
2749
- .receivingDate(LocalDate.of(2025, 1, 25))
2750
- .receivingQuantity(new BigDecimal("30"))
2751
- .receivingType(ReceivingType.SPLIT)
2752
- .build());
2753
-
2754
- // Act: 2回目の入荷
2755
- Receiving secondReceiving = receivingService.registerReceiving(ReceivingCommand.builder()
2756
- .purchaseOrderNumber(testPurchaseOrder.getPurchaseOrderNumber())
2757
- .lineNumber(1)
2758
- .receivingDate(LocalDate.of(2025, 1, 28))
2759
- .receivingQuantity(new BigDecimal("70"))
2760
- .receivingType(ReceivingType.SPLIT)
2761
- .build());
2762
-
2763
- // Assert
2764
- assertThat(secondReceiving).isNotNull();
2765
- assertThat(secondReceiving.getReceivingType()).isEqualTo(ReceivingType.SPLIT);
2766
- }
2767
-
2768
- @Test
2769
- @DisplayName("発注数量を超える入荷はエラーになる")
2770
- void cannotReceiveMoreThanOrdered() {
2771
- // Act & Assert
2772
- ReceivingCommand command = ReceivingCommand.builder()
2773
- .purchaseOrderNumber(testPurchaseOrder.getPurchaseOrderNumber())
2774
- .lineNumber(1)
2775
- .receivingDate(LocalDate.of(2025, 1, 25))
2776
- .receivingQuantity(new BigDecimal("150"))
2777
- .build();
2778
-
2779
- assertThatThrownBy(() -> receivingService.registerReceiving(command))
2780
- .isInstanceOf(IllegalStateException.class)
2781
- .hasMessageContaining("Cannot receive more than ordered quantity");
2782
- }
2783
- }
2784
-
2785
- @Nested
2786
- @DisplayName("受入検査")
2787
- class InspectionRegistration {
2788
-
2789
- @Test
2790
- @DisplayName("入荷に対して受入検査を登録できる")
2791
- void canRegisterInspection() {
2792
- // Arrange
2793
- Receiving receiving = receivingService.registerReceiving(ReceivingCommand.builder()
2794
- .purchaseOrderNumber(testPurchaseOrder.getPurchaseOrderNumber())
2795
- .lineNumber(1)
2796
- .receivingDate(LocalDate.of(2025, 1, 25))
2797
- .receivingQuantity(new BigDecimal("100"))
2798
- .build());
2799
-
2800
- // Act
2801
- InspectionCommand command = InspectionCommand.builder()
2802
- .receivingNumber(receiving.getReceivingNumber())
2803
- .inspectionDate(LocalDate.of(2025, 1, 26))
2804
- .goodQuantity(new BigDecimal("95"))
2805
- .defectQuantity(new BigDecimal("5"))
2806
- .build();
2807
-
2808
- Inspection inspection = receivingService.registerInspection(command);
2809
-
2810
- // Assert
2811
- assertThat(inspection).isNotNull();
2812
- assertThat(inspection.getInspectionNumber()).startsWith("INS-");
2813
- assertThat(inspection.getGoodQuantity()).isEqualByComparingTo(new BigDecimal("95"));
2814
- assertThat(inspection.getDefectQuantity()).isEqualByComparingTo(new BigDecimal("5"));
2815
- }
2816
- }
2817
-
2818
- @Nested
2819
- @DisplayName("検収処理")
2820
- class AcceptanceProcessing {
2821
-
2822
- @Test
2823
- @DisplayName("検査合格品を検収できる")
2824
- void canProcessAcceptance() {
2825
- // Arrange
2826
- Receiving receiving = receivingService.registerReceiving(ReceivingCommand.builder()
2827
- .purchaseOrderNumber(testPurchaseOrder.getPurchaseOrderNumber())
2828
- .lineNumber(1)
2829
- .receivingDate(LocalDate.of(2025, 1, 25))
2830
- .receivingQuantity(new BigDecimal("100"))
2831
- .build());
2832
-
2833
- Inspection inspection = receivingService.registerInspection(InspectionCommand.builder()
2834
- .receivingNumber(receiving.getReceivingNumber())
2835
- .inspectionDate(LocalDate.of(2025, 1, 26))
2836
- .goodQuantity(new BigDecimal("100"))
2837
- .defectQuantity(BigDecimal.ZERO)
2838
- .build());
2839
-
2840
- // Act
2841
- AcceptanceCommand command = AcceptanceCommand.builder()
2842
- .inspectionNumber(inspection.getInspectionNumber())
2843
- .acceptanceDate(LocalDate.of(2025, 1, 27))
2844
- .taxRate(new BigDecimal("10"))
2845
- .build();
2846
-
2847
- Acceptance acceptance = receivingService.processAcceptance(command);
2848
-
2849
- // Assert
2850
- assertThat(acceptance).isNotNull();
2851
- assertThat(acceptance.getAcceptanceNumber()).startsWith("ACC-");
2852
- assertThat(acceptance.getAcceptedQuantity()).isEqualByComparingTo(new BigDecimal("100"));
2853
- // 100個 × 1000円 = 100,000円
2854
- assertThat(acceptance.getAmount()).isEqualByComparingTo(new BigDecimal("100000"));
2855
- // 消費税 10% = 10,000円
2856
- assertThat(acceptance.getTaxAmount()).isEqualByComparingTo(new BigDecimal("10000"));
2857
- }
2858
- }
2859
- }
2860
- ```
2861
-
2862
- </details>
2863
-
2864
- ---
2865
-
2866
- ## 25.3 リレーションと楽観ロックの設計
2867
-
2868
- ### MyBatis ネストした ResultMap によるリレーション設定
2869
-
2870
- 購買管理データは、発注→発注明細、入荷→発注明細、検収→入荷 という複数のリレーションを持ちます。MyBatis でこれらの関係を効率的に取得するための設定を実装します。
2871
-
2872
- #### ネストした ResultMap の定義
2873
-
2874
- <details>
2875
- <summary>PurchaseOrderMapper.xml(リレーション設定)</summary>
2876
-
2877
- ```xml
2878
- <?xml version="1.0" encoding="UTF-8" ?>
2879
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
2880
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
2881
-
2882
- <!-- src/main/resources/mapper/PurchaseOrderMapper.xml -->
2883
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.PurchaseOrderMapper">
2884
-
2885
- <!-- 発注データ ResultMap(明細込み) -->
2886
- <resultMap id="purchaseOrderWithDetailsResultMap" type="com.example.pms.domain.model.purchase.PurchaseOrder">
2887
- <id property="id" column="po_ID"/>
2888
- <result property="purchaseOrderNumber" column="po_発注番号"/>
2889
- <result property="orderDate" column="po_発注日"/>
2890
- <result property="supplierCode" column="po_取引先コード"/>
2891
- <result property="purchaserCode" column="po_発注担当者コード"/>
2892
- <result property="departmentCode" column="po_発注部門コード"/>
2893
- <result property="status" column="po_ステータス"
2894
- typeHandler="com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler"/>
2895
- <result property="remarks" column="po_備考"/>
2896
- <result property="version" column="po_バージョン"/>
2897
- <result property="createdAt" column="po_作成日時"/>
2898
- <result property="createdBy" column="po_作成者"/>
2899
- <result property="updatedAt" column="po_更新日時"/>
2900
- <result property="updatedBy" column="po_更新者"/>
2901
- <!-- 取引先マスタとの N:1 関連 -->
2902
- <association property="supplier" javaType="com.example.pms.domain.model.master.Supplier">
2903
- <id property="supplierCode" column="s_取引先コード"/>
2904
- <result property="supplierName" column="s_取引先名"/>
2905
- <result property="supplierType" column="s_取引先区分"/>
2906
- </association>
2907
- <!-- 発注明細との 1:N 関連 -->
2908
- <collection property="details" ofType="com.example.pms.domain.model.purchase.PurchaseOrderDetail"
2909
- resultMap="purchaseOrderDetailNestedResultMap"/>
2910
- </resultMap>
2911
-
2912
- <!-- 発注明細のネスト ResultMap -->
2913
- <resultMap id="purchaseOrderDetailNestedResultMap" type="com.example.pms.domain.model.purchase.PurchaseOrderDetail">
2914
- <id property="id" column="pod_ID"/>
2915
- <result property="purchaseOrderNumber" column="pod_発注番号"/>
2916
- <result property="lineNumber" column="pod_発注行番号"/>
2917
- <result property="orderNumber" column="pod_オーダNO"/>
2918
- <result property="deliveryLocationCode" column="pod_納入場所コード"/>
2919
- <result property="itemCode" column="pod_品目コード"/>
2920
- <result property="isMiscellaneous" column="pod_諸口品目区分"/>
2921
- <result property="expectedReceiveDate" column="pod_受入予定日"/>
2922
- <result property="confirmedDeliveryDate" column="pod_回答納期"/>
2923
- <result property="unitPrice" column="pod_発注単価"/>
2924
- <result property="orderQuantity" column="pod_発注数量"/>
2925
- <result property="receivedQuantity" column="pod_入荷済数量"/>
2926
- <result property="inspectedQuantity" column="pod_検査済数量"/>
2927
- <result property="acceptedQuantity" column="pod_検収済数量"/>
2928
- <result property="amount" column="pod_発注金額"/>
2929
- <result property="taxAmount" column="pod_消費税金額"/>
2930
- <result property="isCompleted" column="pod_完了フラグ"/>
2931
- <result property="lineRemarks" column="pod_明細備考"/>
2932
- <result property="version" column="pod_バージョン"/>
2933
- <!-- 品目マスタとの N:1 関連 -->
2934
- <association property="item" javaType="com.example.pms.domain.model.item.Item">
2935
- <id property="itemCode" column="i_品目コード"/>
2936
- <result property="itemName" column="i_品名"/>
2937
- <result property="itemCategory" column="i_品目区分"/>
2938
- <result property="unit" column="i_単位"/>
2939
- </association>
2940
- </resultMap>
2941
-
2942
- <!-- JOIN による一括取得クエリ -->
2943
- <select id="findWithDetailsByPurchaseOrderNumber" resultMap="purchaseOrderWithDetailsResultMap">
2944
- SELECT
2945
- -- 発注データ
2946
- po."ID" AS po_ID,
2947
- po."発注番号" AS po_発注番号,
2948
- po."発注日" AS po_発注日,
2949
- po."取引先コード" AS po_取引先コード,
2950
- po."発注担当者コード" AS po_発注担当者コード,
2951
- po."発注部門コード" AS po_発注部門コード,
2952
- po."ステータス" AS po_ステータス,
2953
- po."備考" AS po_備考,
2954
- po."バージョン" AS po_バージョン,
2955
- po."作成日時" AS po_作成日時,
2956
- po."作成者" AS po_作成者,
2957
- po."更新日時" AS po_更新日時,
2958
- po."更新者" AS po_更新者,
2959
- -- 取引先マスタ
2960
- s."取引先コード" AS s_取引先コード,
2961
- s."取引先名" AS s_取引先名,
2962
- s."取引先区分" AS s_取引先区分,
2963
- -- 発注明細データ
2964
- pod."ID" AS pod_ID,
2965
- pod."発注番号" AS pod_発注番号,
2966
- pod."発注行番号" AS pod_発注行番号,
2967
- pod."オーダNO" AS pod_オーダNO,
2968
- pod."納入場所コード" AS pod_納入場所コード,
2969
- pod."品目コード" AS pod_品目コード,
2970
- pod."諸口品目区分" AS pod_諸口品目区分,
2971
- pod."受入予定日" AS pod_受入予定日,
2972
- pod."回答納期" AS pod_回答納期,
2973
- pod."発注単価" AS pod_発注単価,
2974
- pod."発注数量" AS pod_発注数量,
2975
- pod."入荷済数量" AS pod_入荷済数量,
2976
- pod."検査済数量" AS pod_検査済数量,
2977
- pod."検収済数量" AS pod_検収済数量,
2978
- pod."発注金額" AS pod_発注金額,
2979
- pod."消費税金額" AS pod_消費税金額,
2980
- pod."完了フラグ" AS pod_完了フラグ,
2981
- pod."明細備考" AS pod_明細備考,
2982
- pod."バージョン" AS pod_バージョン,
2983
- -- 品目マスタ
2984
- i."品目コード" AS i_品目コード,
2985
- i."品名" AS i_品名,
2986
- i."品目区分" AS i_品目区分,
2987
- i."単位" AS i_単位
2988
- FROM "発注データ" po
2989
- LEFT JOIN "取引先マスタ" s ON po."取引先コード" = s."取引先コード"
2990
- LEFT JOIN "発注明細データ" pod ON po."発注番号" = pod."発注番号"
2991
- LEFT JOIN "品目マスタ" i ON pod."品目コード" = i."品目コード"
2992
- AND i."適用開始日" = (
2993
- SELECT MAX("適用開始日") FROM "品目マスタ"
2994
- WHERE "品目コード" = pod."品目コード"
2995
- AND "適用開始日" <= CURRENT_DATE
2996
- )
2997
- WHERE po."発注番号" = #{purchaseOrderNumber}
2998
- ORDER BY pod."発注行番号"
2999
- </select>
3000
-
3001
- </mapper>
3002
- ```
3003
-
3004
- </details>
3005
-
3006
- <details>
3007
- <summary>ReceivingMapper.xml(リレーション設定)</summary>
3008
-
3009
- ```xml
3010
- <?xml version="1.0" encoding="UTF-8" ?>
3011
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3012
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3013
-
3014
- <!-- src/main/resources/mapper/ReceivingMapper.xml -->
3015
- <mapper namespace="com.example.pms.infrastructure.out.persistence.mapper.ReceivingMapper">
3016
-
3017
- <!-- 入荷受入データ ResultMap(発注明細・検査・検収込み) -->
3018
- <resultMap id="receivingWithRelationsResultMap" type="com.example.pms.domain.model.purchase.Receiving">
3019
- <id property="id" column="r_ID"/>
3020
- <result property="receivingNumber" column="r_入荷受入番号"/>
3021
- <result property="purchaseOrderNumber" column="r_発注番号"/>
3022
- <result property="lineNumber" column="r_発注行番号"/>
3023
- <result property="receivingDate" column="r_入荷日"/>
3024
- <result property="receivingType" column="r_入荷受入区分"
3025
- typeHandler="com.example.pms.infrastructure.out.persistence.typehandler.ReceivingTypeTypeHandler"/>
3026
- <result property="receivedQuantity" column="r_入荷数量"/>
3027
- <result property="locationCode" column="r_入荷場所コード"/>
3028
- <result property="remarks" column="r_備考"/>
3029
- <result property="version" column="r_バージョン"/>
3030
- <result property="createdAt" column="r_作成日時"/>
3031
- <result property="updatedAt" column="r_更新日時"/>
3032
- <!-- 発注明細との N:1 関連 -->
3033
- <association property="purchaseOrderDetail" javaType="com.example.pms.domain.model.purchase.PurchaseOrderDetail">
3034
- <id property="id" column="pod_ID"/>
3035
- <result property="purchaseOrderNumber" column="pod_発注番号"/>
3036
- <result property="lineNumber" column="pod_発注行番号"/>
3037
- <result property="itemCode" column="pod_品目コード"/>
3038
- <result property="orderQuantity" column="pod_発注数量"/>
3039
- <result property="unitPrice" column="pod_発注単価"/>
3040
- </association>
3041
- <!-- 検査データとの 1:1 関連 -->
3042
- <association property="inspection" javaType="com.example.pms.domain.model.purchase.Inspection">
3043
- <id property="id" column="ins_ID"/>
3044
- <result property="inspectionNumber" column="ins_検査番号"/>
3045
- <result property="inspectionDate" column="ins_検査日"/>
3046
- <result property="inspectedQuantity" column="ins_検査数量"/>
3047
- <result property="goodQuantity" column="ins_良品数量"/>
3048
- <result property="defectQuantity" column="ins_不良数量"/>
3049
- </association>
3050
- <!-- 検収データとの 1:1 関連 -->
3051
- <association property="acceptance" javaType="com.example.pms.domain.model.purchase.Acceptance">
3052
- <id property="id" column="acc_ID"/>
3053
- <result property="acceptanceNumber" column="acc_検収番号"/>
3054
- <result property="acceptanceDate" column="acc_検収日"/>
3055
- <result property="acceptedQuantity" column="acc_検収数量"/>
3056
- <result property="amount" column="acc_検収金額"/>
3057
- <result property="taxAmount" column="acc_消費税額"/>
3058
- </association>
3059
- </resultMap>
3060
-
3061
- <!-- JOIN による一括取得クエリ -->
3062
- <select id="findWithRelationsByReceivingNumber" resultMap="receivingWithRelationsResultMap">
3063
- SELECT
3064
- -- 入荷受入データ
3065
- r."ID" AS r_ID,
3066
- r."入荷受入番号" AS r_入荷受入番号,
3067
- r."発注番号" AS r_発注番号,
3068
- r."発注行番号" AS r_発注行番号,
3069
- r."入荷日" AS r_入荷日,
3070
- r."入荷受入区分" AS r_入荷受入区分,
3071
- r."入荷数量" AS r_入荷数量,
3072
- r."入荷場所コード" AS r_入荷場所コード,
3073
- r."備考" AS r_備考,
3074
- r."バージョン" AS r_バージョン,
3075
- r."作成日時" AS r_作成日時,
3076
- r."更新日時" AS r_更新日時,
3077
- -- 発注明細データ
3078
- pod."ID" AS pod_ID,
3079
- pod."発注番号" AS pod_発注番号,
3080
- pod."発注行番号" AS pod_発注行番号,
3081
- pod."品目コード" AS pod_品目コード,
3082
- pod."発注数量" AS pod_発注数量,
3083
- pod."発注単価" AS pod_発注単価,
3084
- -- 受入検査データ
3085
- ins."ID" AS ins_ID,
3086
- ins."検査番号" AS ins_検査番号,
3087
- ins."検査日" AS ins_検査日,
3088
- ins."検査数量" AS ins_検査数量,
3089
- ins."良品数量" AS ins_良品数量,
3090
- ins."不良数量" AS ins_不良数量,
3091
- -- 検収データ
3092
- acc."ID" AS acc_ID,
3093
- acc."検収番号" AS acc_検収番号,
3094
- acc."検収日" AS acc_検収日,
3095
- acc."検収数量" AS acc_検収数量,
3096
- acc."検収金額" AS acc_検収金額,
3097
- acc."消費税額" AS acc_消費税額
3098
- FROM "入荷受入データ" r
3099
- LEFT JOIN "発注明細データ" pod
3100
- ON r."発注番号" = pod."発注番号" AND r."発注行番号" = pod."発注行番号"
3101
- LEFT JOIN "受入検査データ" ins ON r."入荷受入番号" = ins."入荷受入番号"
3102
- LEFT JOIN "検収データ" acc ON r."入荷受入番号" = acc."入荷受入番号"
3103
- WHERE r."入荷受入番号" = #{receivingNumber}
3104
- </select>
3105
-
3106
- </mapper>
3107
- ```
3108
-
3109
- </details>
3110
-
3111
- #### リレーション設定のポイント
3112
-
3113
- | 設定項目 | 説明 |
3114
- |---------|------|
3115
- | `<collection>` | 発注→発注明細 の 1:N 関連 |
3116
- | `<association>` | 入荷→発注明細、入荷→検査、入荷→検収 の N:1/1:1 関連 |
3117
- | エイリアス | `po_`(発注)、`pod_`(発注明細)、`r_`(入荷)、`ins_`(検査)、`acc_`(検収) |
3118
- | 有効日サブクエリ | 品目マスタの適用開始日を考慮した最新レコード取得 |
3119
-
3120
- ### 楽観ロックの実装
3121
-
3122
- 発注から検収までの一連の業務で複数ユーザーが同時に操作する可能性があるため、楽観ロックを実装します。
3123
-
3124
- #### Flyway マイグレーション: バージョンカラム追加
3125
-
3126
- <details>
3127
- <summary>V010__add_purchasing_version_columns.sql</summary>
3128
-
3129
- ```sql
3130
- -- src/main/resources/db/migration/V010__add_purchasing_version_columns.sql
3131
-
3132
- -- 発注データテーブルにバージョンカラムを追加
3133
- ALTER TABLE "発注データ" ADD COLUMN "バージョン" INTEGER DEFAULT 1 NOT NULL;
3134
-
3135
- -- 発注明細データテーブルにバージョンカラムを追加
3136
- ALTER TABLE "発注明細データ" ADD COLUMN "バージョン" INTEGER DEFAULT 1 NOT NULL;
3137
-
3138
- -- 入荷受入データテーブルにバージョンカラムを追加
3139
- ALTER TABLE "入荷受入データ" ADD COLUMN "バージョン" INTEGER DEFAULT 1 NOT NULL;
3140
-
3141
- -- 受入検査データテーブルにバージョンカラムを追加
3142
- ALTER TABLE "受入検査データ" ADD COLUMN "バージョン" INTEGER DEFAULT 1 NOT NULL;
3143
-
3144
- -- 検収データテーブルにバージョンカラムを追加
3145
- ALTER TABLE "検収データ" ADD COLUMN "バージョン" INTEGER DEFAULT 1 NOT NULL;
3146
-
3147
- -- コメント追加
3148
- COMMENT ON COLUMN "発注データ"."バージョン" IS '楽観ロック用バージョン番号';
3149
- COMMENT ON COLUMN "発注明細データ"."バージョン" IS '楽観ロック用バージョン番号';
3150
- COMMENT ON COLUMN "入荷受入データ"."バージョン" IS '楽観ロック用バージョン番号';
3151
- COMMENT ON COLUMN "受入検査データ"."バージョン" IS '楽観ロック用バージョン番号';
3152
- COMMENT ON COLUMN "検収データ"."バージョン" IS '楽観ロック用バージョン番号';
3153
- ```
3154
-
3155
- </details>
3156
-
3157
- #### エンティティへのバージョンフィールド追加
3158
-
3159
- <details>
3160
- <summary>PurchaseOrder.java(バージョンフィールド追加)</summary>
3161
-
3162
- ```java
3163
- // src/main/java/com/example/pms/domain/model/purchase/PurchaseOrder.java
3164
- package com.example.pms.domain.model.purchase;
3165
-
3166
- import com.example.pms.domain.model.master.Supplier;
3167
- import lombok.Builder;
3168
- import lombok.Data;
3169
-
3170
- import java.time.LocalDate;
3171
- import java.time.LocalDateTime;
3172
- import java.util.ArrayList;
3173
- import java.util.List;
3174
-
3175
- @Data
3176
- @Builder
3177
- public class PurchaseOrder {
3178
- private Integer id;
3179
- private String purchaseOrderNumber;
3180
- private LocalDate orderDate;
3181
- private String supplierCode;
3182
- private String purchaserCode;
3183
- private String departmentCode;
3184
- private PurchaseOrderStatus status;
3185
- private String remarks;
3186
- private LocalDateTime createdAt;
3187
- private String createdBy;
3188
- private LocalDateTime updatedAt;
3189
- private String updatedBy;
3190
-
3191
- // 楽観ロック用バージョン
3192
- @Builder.Default
3193
- private Integer version = 1;
3194
-
3195
- // リレーション
3196
- private Supplier supplier;
3197
- @Builder.Default
3198
- private List<PurchaseOrderDetail> details = new ArrayList<>();
3199
- }
3200
- ```
3201
-
3202
- </details>
3203
-
3204
- <details>
3205
- <summary>Receiving.java(バージョンフィールド追加)</summary>
3206
-
3207
- ```java
3208
- // src/main/java/com/example/pms/domain/model/purchase/Receiving.java
3209
- package com.example.pms.domain.model.purchase;
3210
-
3211
- import lombok.Builder;
3212
- import lombok.Data;
3213
-
3214
- import java.math.BigDecimal;
3215
- import java.time.LocalDate;
3216
- import java.time.LocalDateTime;
3217
-
3218
- @Data
3219
- @Builder
3220
- public class Receiving {
3221
- private Integer id;
3222
- private String receivingNumber;
3223
- private String purchaseOrderNumber;
3224
- private Integer lineNumber;
3225
- private LocalDate receivingDate;
3226
- private ReceivingType receivingType;
3227
- private BigDecimal receivedQuantity;
3228
- private String locationCode;
3229
- private String remarks;
3230
- private LocalDateTime createdAt;
3231
- private LocalDateTime updatedAt;
3232
-
3233
- // 楽観ロック用バージョン
3234
- @Builder.Default
3235
- private Integer version = 1;
3236
-
3237
- // リレーション
3238
- private PurchaseOrderDetail purchaseOrderDetail;
3239
- private Inspection inspection;
3240
- private Acceptance acceptance;
3241
- }
3242
- ```
3243
-
3244
- </details>
3245
-
3246
- #### MyBatis Mapper: 楽観ロック対応の更新
3247
-
3248
- <details>
3249
- <summary>PurchaseOrderMapper.xml(楽観ロック対応 UPDATE)</summary>
3250
-
3251
- ```xml
3252
- <!-- 楽観ロック対応の更新(バージョンチェック付き) -->
3253
- <update id="updateWithOptimisticLock" parameterType="com.example.pms.domain.model.purchase.PurchaseOrder">
3254
- UPDATE "発注データ"
3255
- SET
3256
- "発注日" = #{orderDate},
3257
- "取引先コード" = #{supplierCode},
3258
- "発注担当者コード" = #{purchaserCode},
3259
- "発注部門コード" = #{departmentCode},
3260
- "ステータス" = #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler}::発注ステータス,
3261
- "備考" = #{remarks},
3262
- "更新日時" = CURRENT_TIMESTAMP,
3263
- "更新者" = #{updatedBy},
3264
- "バージョン" = "バージョン" + 1
3265
- WHERE "ID" = #{id}
3266
- AND "バージョン" = #{version}
3267
- </update>
3268
-
3269
- <!-- ステータス更新(楽観ロック対応) -->
3270
- <update id="updateStatusWithOptimisticLock">
3271
- UPDATE "発注データ"
3272
- SET
3273
- "ステータス" = #{status, typeHandler=com.example.pms.infrastructure.out.persistence.typehandler.PurchaseOrderStatusTypeHandler}::発注ステータス,
3274
- "更新日時" = CURRENT_TIMESTAMP,
3275
- "バージョン" = "バージョン" + 1
3276
- WHERE "ID" = #{id}
3277
- AND "バージョン" = #{version}
3278
- </update>
3279
-
3280
- <!-- 現在のバージョン取得 -->
3281
- <select id="findVersionById" resultType="java.lang.Integer">
3282
- SELECT "バージョン" FROM "発注データ" WHERE "ID" = #{id}
3283
- </select>
3284
- ```
3285
-
3286
- </details>
3287
-
3288
- <details>
3289
- <summary>PurchaseOrderDetailMapper.xml(楽観ロック対応 UPDATE)</summary>
3290
-
3291
- ```xml
3292
- <!-- 入荷数量更新(楽観ロック対応) -->
3293
- <update id="updateReceivedQuantityWithOptimisticLock">
3294
- UPDATE "発注明細データ"
3295
- SET
3296
- "入荷済数量" = "入荷済数量" + #{additionalQuantity},
3297
- "更新日時" = CURRENT_TIMESTAMP,
3298
- "バージョン" = "バージョン" + 1
3299
- WHERE "ID" = #{id}
3300
- AND "バージョン" = #{version}
3301
- </update>
3302
-
3303
- <!-- 検収数量更新(楽観ロック対応) -->
3304
- <update id="updateAcceptedQuantityWithOptimisticLock">
3305
- UPDATE "発注明細データ"
3306
- SET
3307
- "検収済数量" = "検収済数量" + #{additionalQuantity},
3308
- "完了フラグ" = CASE
3309
- WHEN "検収済数量" + #{additionalQuantity} >= "発注数量" THEN TRUE
3310
- ELSE FALSE
3311
- END,
3312
- "更新日時" = CURRENT_TIMESTAMP,
3313
- "バージョン" = "バージョン" + 1
3314
- WHERE "ID" = #{id}
3315
- AND "バージョン" = #{version}
3316
- </update>
3317
-
3318
- <!-- 現在のバージョン取得 -->
3319
- <select id="findVersionById" resultType="java.lang.Integer">
3320
- SELECT "バージョン" FROM "発注明細データ" WHERE "ID" = #{id}
3321
- </select>
3322
- ```
3323
-
3324
- </details>
3325
-
3326
- #### Repository 実装: 楽観ロック対応
3327
-
3328
- <details>
3329
- <summary>PurchaseOrderRepositoryImpl.java(楽観ロック対応)</summary>
3330
-
3331
- ```java
3332
- // src/main/java/com/example/pms/infrastructure/persistence/repository/PurchaseOrderRepositoryImpl.java
3333
- package com.example.pms.infrastructure.out.persistence.typehandler.repository;
3334
-
3335
- import com.example.pms.application.port.out.PurchaseOrderRepository;
3336
- import com.example.pms.domain.exception.OptimisticLockException;
3337
- import com.example.pms.domain.model.purchase.PurchaseOrder;
3338
- import com.example.pms.domain.model.purchase.PurchaseOrderStatus;
3339
- import com.example.pms.infrastructure.out.persistence.mapper.PurchaseOrderMapper;
3340
- import lombok.RequiredArgsConstructor;
3341
- import org.springframework.stereotype.Repository;
3342
- import org.springframework.transaction.annotation.Transactional;
3343
-
3344
- import java.util.Optional;
3345
-
3346
- @Repository
3347
- @RequiredArgsConstructor
3348
- public class PurchaseOrderRepositoryImpl implements PurchaseOrderRepository {
3349
-
3350
- private final PurchaseOrderMapper mapper;
3351
-
3352
- @Override
3353
- @Transactional
3354
- public void update(PurchaseOrder purchaseOrder) {
3355
- int updatedCount = mapper.updateWithOptimisticLock(purchaseOrder);
3356
-
3357
- if (updatedCount == 0) {
3358
- Integer currentVersion = mapper.findVersionById(purchaseOrder.getId());
3359
- if (currentVersion == null) {
3360
- throw new OptimisticLockException("発注", purchaseOrder.getId());
3361
- } else {
3362
- throw new OptimisticLockException("発注", purchaseOrder.getId(),
3363
- purchaseOrder.getVersion(), currentVersion);
3364
- }
3365
- }
3366
- }
3367
-
3368
- @Override
3369
- @Transactional
3370
- public void updateStatus(Integer id, PurchaseOrderStatus status, Integer version) {
3371
- int updatedCount = mapper.updateStatusWithOptimisticLock(id, status, version);
3372
-
3373
- if (updatedCount == 0) {
3374
- Integer currentVersion = mapper.findVersionById(id);
3375
- if (currentVersion == null) {
3376
- throw new OptimisticLockException("発注", id);
3377
- } else {
3378
- throw new OptimisticLockException("発注", id, version, currentVersion);
3379
- }
3380
- }
3381
- }
3382
-
3383
- @Override
3384
- public Optional<PurchaseOrder> findWithDetailsByPurchaseOrderNumber(String purchaseOrderNumber) {
3385
- return Optional.ofNullable(mapper.findWithDetailsByPurchaseOrderNumber(purchaseOrderNumber));
3386
- }
3387
-
3388
- // その他のメソッド...
3389
- }
3390
- ```
3391
-
3392
- </details>
3393
-
3394
- #### TDD: 楽観ロックのテスト
3395
-
3396
- <details>
3397
- <summary>PurchaseOrderRepositoryOptimisticLockTest.java</summary>
3398
-
3399
- ```java
3400
- // src/test/java/com/example/pms/infrastructure/persistence/repository/PurchaseOrderRepositoryOptimisticLockTest.java
3401
- package com.example.pms.infrastructure.out.persistence.typehandler.repository;
3402
-
3403
- import com.example.pms.application.port.out.PurchaseOrderRepository;
3404
- import com.example.pms.domain.exception.OptimisticLockException;
3405
- import com.example.pms.domain.model.purchase.PurchaseOrder;
3406
- import com.example.pms.domain.model.purchase.PurchaseOrderStatus;
3407
- import com.example.pms.testsetup.BaseIntegrationTest;
3408
- import org.junit.jupiter.api.*;
3409
- import org.springframework.beans.factory.annotation.Autowired;
3410
-
3411
- import java.time.LocalDate;
3412
-
3413
- import static org.assertj.core.api.Assertions.*;
3414
-
3415
- @DisplayName("発注リポジトリ - 楽観ロック")
3416
- class PurchaseOrderRepositoryOptimisticLockTest extends BaseIntegrationTest {
3417
-
3418
- @Autowired
3419
- private PurchaseOrderRepository purchaseOrderRepository;
3420
-
3421
- @BeforeEach
3422
- void setUp() {
3423
- purchaseOrderRepository.deleteAll();
3424
- }
3425
-
3426
- @Nested
3427
- @DisplayName("楽観ロック")
3428
- class OptimisticLocking {
3429
-
3430
- @Test
3431
- @DisplayName("同じバージョンで更新できる")
3432
- void canUpdateWithSameVersion() {
3433
- // Arrange
3434
- var po = PurchaseOrder.builder()
3435
- .purchaseOrderNumber("PO-2025-0001")
3436
- .orderDate(LocalDate.of(2025, 1, 20))
3437
- .supplierCode("SUP-001")
3438
- .status(PurchaseOrderStatus.CREATING)
3439
- .build();
3440
- purchaseOrderRepository.save(po);
3441
-
3442
- // Act
3443
- var fetched = purchaseOrderRepository.findByPurchaseOrderNumber("PO-2025-0001").get();
3444
- fetched.setRemarks("更新テスト");
3445
- purchaseOrderRepository.update(fetched);
3446
-
3447
- // Assert
3448
- var updated = purchaseOrderRepository.findByPurchaseOrderNumber("PO-2025-0001").get();
3449
- assertThat(updated.getRemarks()).isEqualTo("更新テスト");
3450
- assertThat(updated.getVersion()).isEqualTo(2);
3451
- }
3452
-
3453
- @Test
3454
- @DisplayName("異なるバージョンで更新すると楽観ロック例外が発生する")
3455
- void throwsExceptionWhenVersionMismatch() {
3456
- // Arrange
3457
- var po = PurchaseOrder.builder()
3458
- .purchaseOrderNumber("PO-2025-0002")
3459
- .orderDate(LocalDate.of(2025, 1, 20))
3460
- .supplierCode("SUP-001")
3461
- .status(PurchaseOrderStatus.CREATING)
3462
- .build();
3463
- purchaseOrderRepository.save(po);
3464
-
3465
- // ユーザーAが取得
3466
- var poA = purchaseOrderRepository.findByPurchaseOrderNumber("PO-2025-0002").get();
3467
- // ユーザーBが取得
3468
- var poB = purchaseOrderRepository.findByPurchaseOrderNumber("PO-2025-0002").get();
3469
-
3470
- // ユーザーAが更新(成功)
3471
- poA.setRemarks("ユーザーAの更新");
3472
- purchaseOrderRepository.update(poA);
3473
-
3474
- // Act & Assert: ユーザーBが古いバージョンで更新(失敗)
3475
- poB.setRemarks("ユーザーBの更新");
3476
- assertThatThrownBy(() -> purchaseOrderRepository.update(poB))
3477
- .isInstanceOf(OptimisticLockException.class)
3478
- .hasMessageContaining("他のユーザーによって更新されています");
3479
- }
3480
-
3481
- @Test
3482
- @DisplayName("ステータス更新も楽観ロックが適用される")
3483
- void statusUpdateWithOptimisticLock() {
3484
- // Arrange
3485
- var po = PurchaseOrder.builder()
3486
- .purchaseOrderNumber("PO-2025-0003")
3487
- .orderDate(LocalDate.of(2025, 1, 20))
3488
- .supplierCode("SUP-001")
3489
- .status(PurchaseOrderStatus.CREATING)
3490
- .build();
3491
- purchaseOrderRepository.save(po);
3492
-
3493
- // ユーザーAとBが同時に取得
3494
- var poA = purchaseOrderRepository.findByPurchaseOrderNumber("PO-2025-0003").get();
3495
- var poB = purchaseOrderRepository.findByPurchaseOrderNumber("PO-2025-0003").get();
3496
-
3497
- // ユーザーAがステータス更新(成功)
3498
- purchaseOrderRepository.updateStatus(poA.getId(), PurchaseOrderStatus.ORDERED, poA.getVersion());
3499
-
3500
- // Act & Assert: ユーザーBが古いバージョンでステータス更新(失敗)
3501
- assertThatThrownBy(() ->
3502
- purchaseOrderRepository.updateStatus(poB.getId(), PurchaseOrderStatus.CANCELLED, poB.getVersion()))
3503
- .isInstanceOf(OptimisticLockException.class);
3504
- }
3505
- }
3506
- }
3507
- ```
3508
-
3509
- </details>
3510
-
3511
- ### 入荷・検収処理における楽観ロックの考慮
3512
-
3513
- 入荷から検収までの一連の処理では、発注明細の数量を段階的に更新するため、楽観ロックの適切な処理が重要です。
3514
-
3515
- ```plantuml
3516
- @startuml
3517
-
3518
- title 入荷・検収処理と楽観ロック
3519
-
3520
- participant "入荷サービス" as RecvSvc
3521
- participant "発注明細リポジトリ" as PODRepo
3522
- participant "入荷リポジトリ" as RecvRepo
3523
- database "DB" as DB
3524
-
3525
- RecvSvc -> PODRepo: findByPurchaseOrderAndLine(poNumber, lineNumber)
3526
- PODRepo -> DB: SELECT ... WHERE 発注番号 = ? AND 発注行番号 = ?
3527
- DB --> PODRepo: 発注明細(バージョン込み)
3528
- PODRepo --> RecvSvc: PurchaseOrderDetail
3529
-
3530
- RecvSvc -> RecvSvc: 入荷データ作成
3531
-
3532
- RecvSvc -> RecvRepo: save(receiving)
3533
- RecvRepo -> DB: INSERT INTO 入荷受入データ
3534
- DB --> RecvRepo: OK
3535
-
3536
- RecvSvc -> PODRepo: updateReceivedQuantity(id, quantity, version)
3537
- PODRepo -> DB: UPDATE ... WHERE ID = ? AND バージョン = ?
3538
- alt バージョン一致
3539
- DB --> PODRepo: 更新成功(1件)
3540
- PODRepo --> RecvSvc: OK
3541
- else バージョン不一致
3542
- DB --> PODRepo: 更新失敗(0件)
3543
- PODRepo --> RecvSvc: OptimisticLockException
3544
- RecvSvc -> RecvSvc: ロールバック
3545
- end
3546
-
3547
- @enduml
3548
- ```
3549
-
3550
- #### 分割入荷時の楽観ロック戦略
3551
-
3552
- <details>
3553
- <summary>ReceivingService.java(楽観ロック対応)</summary>
3554
-
3555
- ```java
3556
- /**
3557
- * 入荷処理(楽観ロック対応)
3558
- */
3559
- @Transactional
3560
- public Receiving processReceiving(ReceivingRequest request) {
3561
- // 発注明細を取得
3562
- var detail = purchaseOrderDetailRepository
3563
- .findByPurchaseOrderAndLine(request.getPurchaseOrderNumber(), request.getLineNumber())
3564
- .orElseThrow(() -> new IllegalArgumentException("発注明細が見つかりません"));
3565
-
3566
- // 入荷可能数量チェック
3567
- var remainingQuantity = detail.getOrderQuantity()
3568
- .subtract(detail.getReceivedQuantity());
3569
- if (request.getReceivedQuantity().compareTo(remainingQuantity) > 0) {
3570
- throw new IllegalArgumentException(
3571
- String.format("入荷数量が残数を超えています(残数: %s)", remainingQuantity));
3572
- }
3573
-
3574
- // 入荷データ作成
3575
- var receiving = Receiving.builder()
3576
- .receivingNumber(generateReceivingNumber())
3577
- .purchaseOrderNumber(request.getPurchaseOrderNumber())
3578
- .lineNumber(request.getLineNumber())
3579
- .receivingDate(request.getReceivingDate())
3580
- .receivingType(determineReceivingType(detail, request.getReceivedQuantity()))
3581
- .receivedQuantity(request.getReceivedQuantity())
3582
- .locationCode(request.getLocationCode())
3583
- .build();
3584
- receivingRepository.save(receiving);
3585
-
3586
- // 発注明細の入荷済数量を更新(楽観ロック)
3587
- try {
3588
- purchaseOrderDetailRepository.updateReceivedQuantity(
3589
- detail.getId(),
3590
- request.getReceivedQuantity(),
3591
- detail.getVersion()
3592
- );
3593
- } catch (OptimisticLockException e) {
3594
- // 他ユーザーが先に入荷処理を行った場合
3595
- throw new ConcurrentUpdateException(
3596
- "他のユーザーが同時に入荷処理を行いました。画面を更新して再度お試しください。", e);
3597
- }
3598
-
3599
- // 発注ステータス更新
3600
- updatePurchaseOrderStatus(request.getPurchaseOrderNumber());
3601
-
3602
- return receiving;
3603
- }
3604
-
3605
- private ReceivingType determineReceivingType(PurchaseOrderDetail detail, BigDecimal receivedQuantity) {
3606
- var totalReceived = detail.getReceivedQuantity().add(receivedQuantity);
3607
- if (totalReceived.compareTo(detail.getOrderQuantity()) < 0) {
3608
- return ReceivingType.SPLIT; // 分割入荷
3609
- }
3610
- return ReceivingType.NORMAL; // 通常入荷(全数)
3611
- }
3612
- ```
3613
-
3614
- </details>
3615
-
3616
- #### 楽観ロックのベストプラクティス(購買管理向け)
3617
-
3618
- | ポイント | 説明 |
3619
- |---------|------|
3620
- | **発注明細の数量更新** | 入荷・検査・検収それぞれで楽観ロックを適用 |
3621
- | **分割入荷対応** | 複数回の入荷でも整合性を保証 |
3622
- | **ステータス連動** | 発注ステータスと明細の完了フラグを連動更新 |
3623
- | **エラーメッセージ** | ユーザーに再操作を促す明確なメッセージ |
3624
- | **トランザクション境界** | 入荷→明細更新→ステータス更新を1トランザクションで |
3625
-
3626
- ---
3627
-
3628
- ## 25.4 まとめ
3629
-
3630
- 本章では、購買管理(発注から検収まで)の DB 設計と実装について学びました。
3631
-
3632
- ### 学んだこと
3633
-
3634
- 1. **発注業務の設計**
3635
- - 発注データと発注明細データの親子関係
3636
- - 単価マスタによる価格管理
3637
- - 発注ステータスの状態遷移
3638
- - 諸口品目(マスタ外品目)の扱い
3639
-
3640
- 2. **入荷・検収業務の設計**
3641
- - 入荷→検査→検収の業務フロー
3642
- - 分割入荷への対応
3643
- - 良品・不良品の管理
3644
- - 消費税計算
3645
-
3646
- 3. **命名規則のパターン**
3647
- - **DB(日本語)**: テーブル名、カラム名、ENUM 型・値は日本語
3648
- - **Java(英語)**: クラス名、フィールド名、enum 値は英語
3649
- - **MyBatis resultMap**: 日本語カラムと英語プロパティのマッピング
3650
- - **TypeHandler**: 日本語 ENUM 値と英語 enum の相互変換
3651
-
3652
- ### テーブル一覧
3653
-
3654
- | テーブル名(日本語) | Java エンティティ | 説明 |
3655
- |---|---|---|
3656
- | 単価マスタ | UnitPrice | 品目・取引先ごとの単価情報 |
3657
- | 発注データ | PurchaseOrder | 発注ヘッダ情報 |
3658
- | 発注明細データ | PurchaseOrderDetail | 発注明細情報 |
3659
- | 諸口品目情報 | MiscellaneousItem | マスタ外品目の臨時情報 |
3660
- | 入荷受入データ | Receiving | 入荷情報 |
3661
- | 欠点マスタ | Defect | 不良品の欠点コード |
3662
- | 受入検査データ | Inspection | 受入検査情報 |
3663
- | 検収データ | Acceptance | 検収情報 |
3664
-
3665
- ### ENUM 一覧
3666
-
3667
- | DB ENUM 型(日本語) | Java Enum | 値 |
3668
- |---|---|---|
3669
- | 発注ステータス | PurchaseOrderStatus | 作成中→CREATING, 発注済→ORDERED, 一部入荷→PARTIALLY_RECEIVED, 入荷完了→RECEIVED, 検収完了→ACCEPTED, 取消→CANCELLED |
3670
- | 入荷受入区分 | ReceivingType | 通常入荷→NORMAL, 分割入荷→SPLIT, 返品入荷→RETURN |