@tailor-platform/erp-kit 0.1.1 → 0.2.0

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 (342) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +158 -62
  3. package/dist/cli.js +1010 -270
  4. package/package.json +11 -8
  5. package/schemas/module/command.yml +1 -0
  6. package/schemas/module/model.yml +14 -0
  7. package/schemas/module/query.yml +53 -0
  8. package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/SKILL.md +2 -2
  9. package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/SKILL.md +3 -3
  10. package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/SKILL.md +2 -2
  11. package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/SKILL.md +3 -3
  12. package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/SKILL.md +4 -4
  13. package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/SKILL.md +3 -3
  14. package/skills/{mock-scenario → erp-kit-mock-scenario}/SKILL.md +1 -1
  15. package/skills/{1-module-docs → erp-kit-module-1-docs}/SKILL.md +2 -2
  16. package/skills/{2-module-feature-breakdown → erp-kit-module-2-feature-breakdown}/SKILL.md +13 -9
  17. package/skills/erp-kit-module-2-feature-breakdown/references/naming.md +59 -0
  18. package/skills/{3-module-doc-review → erp-kit-module-3-doc-review}/SKILL.md +83 -25
  19. package/skills/erp-kit-module-4-tdd/SKILL.md +94 -0
  20. package/skills/erp-kit-module-4-tdd/references/cross-module-dependency.md +133 -0
  21. package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/db-relations.md +5 -1
  22. package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/exports.md +1 -1
  23. package/skills/erp-kit-module-4-tdd/references/generated-code.md +32 -0
  24. package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/SKILL.md +46 -44
  25. package/skills/erp-kit-module-5-impl-review/references/commands.md +62 -0
  26. package/skills/erp-kit-module-5-impl-review/references/errors.md +10 -0
  27. package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/references/testing.md +1 -1
  28. package/skills/erp-kit-module-shared/SKILL.md +16 -0
  29. package/skills/erp-kit-module-shared/references/commands.md +203 -0
  30. package/skills/erp-kit-module-shared/references/errors.md +35 -0
  31. package/skills/erp-kit-module-shared/references/queries.md +168 -0
  32. package/skills/erp-kit-module-shared/references/structure.md +36 -0
  33. package/skills/{3-module-doc-review → erp-kit-module-shared}/references/testing.md +4 -3
  34. package/skills/erp-kit-update/SKILL.md +64 -0
  35. package/src/cli.doc.test.ts +65 -0
  36. package/src/cli.ts +3 -117
  37. package/src/commands/app/index.ts +74 -0
  38. package/src/commands/check.test.ts +3 -2
  39. package/src/commands/check.ts +3 -2
  40. package/src/commands/index.ts +73 -0
  41. package/src/commands/init.test.ts +22 -5
  42. package/src/commands/init.ts +25 -16
  43. package/src/commands/license.ts +193 -0
  44. package/src/commands/mock/index.ts +2 -2
  45. package/src/commands/mock/start.ts +1 -1
  46. package/src/commands/mock/validate.test.ts +1 -1
  47. package/src/commands/module/generate.ts +35 -0
  48. package/src/commands/module/index.ts +87 -0
  49. package/src/commands/module/list.test.ts +57 -0
  50. package/src/commands/module/list.ts +64 -0
  51. package/src/commands/scaffold-templates.ts +65 -0
  52. package/src/commands/scaffold.test.ts +97 -2
  53. package/src/commands/scaffold.ts +24 -3
  54. package/src/commands/sync-check.test.ts +88 -1
  55. package/src/commands/sync-check.ts +41 -2
  56. package/src/generator/generate-code.test.ts +200 -0
  57. package/src/generator/generate-code.ts +260 -0
  58. package/src/generator/parse-command-doc.test.ts +159 -0
  59. package/src/generator/parse-command-doc.ts +116 -0
  60. package/src/integration.test.ts +6 -8
  61. package/src/module.ts +10 -9
  62. package/src/modules/item-management/README.md +38 -0
  63. package/src/modules/item-management/command/activateItem.generated.ts +6 -0
  64. package/src/modules/item-management/command/activateItem.test.ts +76 -0
  65. package/src/modules/item-management/command/activateItem.ts +42 -0
  66. package/src/modules/item-management/command/assignItemToTaxonomy.generated.ts +6 -0
  67. package/src/modules/item-management/command/assignItemToTaxonomy.test.ts +88 -0
  68. package/src/modules/item-management/command/assignItemToTaxonomy.ts +63 -0
  69. package/src/modules/item-management/command/createItem.generated.ts +6 -0
  70. package/src/modules/item-management/command/createItem.test.ts +152 -0
  71. package/src/modules/item-management/command/createItem.ts +72 -0
  72. package/src/modules/item-management/command/createTaxonomyNode.generated.ts +6 -0
  73. package/src/modules/item-management/command/createTaxonomyNode.test.ts +126 -0
  74. package/src/modules/item-management/command/createTaxonomyNode.ts +70 -0
  75. package/src/modules/item-management/command/deactivateItem.generated.ts +6 -0
  76. package/src/modules/item-management/command/deactivateItem.test.ts +76 -0
  77. package/src/modules/item-management/command/deactivateItem.ts +42 -0
  78. package/src/modules/item-management/command/deleteItem.generated.ts +6 -0
  79. package/src/modules/item-management/command/deleteItem.test.ts +61 -0
  80. package/src/modules/item-management/command/deleteItem.ts +38 -0
  81. package/src/modules/item-management/command/deleteTaxonomyNode.generated.ts +6 -0
  82. package/src/modules/item-management/command/deleteTaxonomyNode.test.ts +73 -0
  83. package/src/modules/item-management/command/deleteTaxonomyNode.ts +50 -0
  84. package/src/modules/item-management/command/moveTaxonomyNode.generated.ts +6 -0
  85. package/src/modules/item-management/command/moveTaxonomyNode.test.ts +136 -0
  86. package/src/modules/item-management/command/moveTaxonomyNode.ts +85 -0
  87. package/src/modules/item-management/command/reactivateItem.generated.ts +6 -0
  88. package/src/modules/item-management/command/reactivateItem.test.ts +76 -0
  89. package/src/modules/item-management/command/reactivateItem.ts +42 -0
  90. package/src/modules/item-management/command/removeItemFromTaxonomy.generated.ts +6 -0
  91. package/src/modules/item-management/command/removeItemFromTaxonomy.test.ts +43 -0
  92. package/src/modules/item-management/command/removeItemFromTaxonomy.ts +30 -0
  93. package/src/modules/item-management/command/updateItem.generated.ts +6 -0
  94. package/src/modules/item-management/command/updateItem.test.ts +178 -0
  95. package/src/modules/item-management/command/updateItem.ts +103 -0
  96. package/src/modules/item-management/command/updateTaxonomyNode.generated.ts +6 -0
  97. package/src/modules/item-management/command/updateTaxonomyNode.test.ts +88 -0
  98. package/src/modules/item-management/command/updateTaxonomyNode.ts +62 -0
  99. package/src/modules/item-management/db/item.ts +47 -0
  100. package/src/modules/item-management/db/itemTaxonomyAssignment.ts +49 -0
  101. package/src/modules/item-management/db/taxonomyNode.ts +34 -0
  102. package/src/modules/item-management/docs/commands/ActivateItem.md +32 -0
  103. package/src/modules/item-management/docs/commands/AssignItemToTaxonomy.md +38 -0
  104. package/src/modules/item-management/docs/commands/CreateItem.md +44 -0
  105. package/src/modules/item-management/docs/commands/CreateTaxonomyNode.md +44 -0
  106. package/src/modules/item-management/docs/commands/DeactivateItem.md +34 -0
  107. package/src/modules/item-management/docs/commands/DeleteItem.md +35 -0
  108. package/src/modules/item-management/docs/commands/DeleteTaxonomyNode.md +39 -0
  109. package/src/modules/item-management/docs/commands/MoveTaxonomyNode.md +45 -0
  110. package/src/modules/item-management/docs/commands/ReactivateItem.md +34 -0
  111. package/src/modules/item-management/docs/commands/RemoveItemFromTaxonomy.md +30 -0
  112. package/src/modules/item-management/docs/commands/UpdateItem.md +55 -0
  113. package/src/modules/item-management/docs/commands/UpdateTaxonomyNode.md +36 -0
  114. package/src/modules/item-management/docs/features/item-lifecycle.md +60 -0
  115. package/src/modules/item-management/docs/features/item-taxonomy.md +65 -0
  116. package/src/modules/item-management/docs/models/ItemTaxonomyAssignment.md +36 -0
  117. package/src/modules/item-management/docs/models/TaxonomyNode.md +47 -0
  118. package/src/modules/item-management/docs/models/item.md +59 -0
  119. package/src/modules/item-management/docs/queries/CalculateNodeDepth.md +36 -0
  120. package/src/modules/item-management/docs/queries/CalculateSubtreeDepth.md +40 -0
  121. package/src/modules/item-management/docs/queries/DetectCircularReference.md +41 -0
  122. package/src/modules/item-management/docs/queries/GetItem.md +38 -0
  123. package/src/modules/item-management/docs/queries/GetItemTaxonomyAssignment.md +29 -0
  124. package/src/modules/item-management/docs/queries/GetTaxonomyNode.md +35 -0
  125. package/src/modules/item-management/docs/queries/GetTaxonomyNodeAssignments.md +29 -0
  126. package/src/modules/item-management/docs/queries/GetTaxonomyNodeChildren.md +29 -0
  127. package/src/modules/item-management/generated/enums.ts +9 -0
  128. package/src/modules/item-management/generated/kysely-tailordb.ts +62 -0
  129. package/src/modules/item-management/index.ts +53 -0
  130. package/src/modules/item-management/lib/_db_deps.ts +13 -0
  131. package/src/modules/item-management/lib/errors.generated.ts +117 -0
  132. package/src/modules/item-management/lib/permissions.generated.ts +17 -0
  133. package/src/modules/item-management/lib/types.ts +19 -0
  134. package/src/modules/item-management/module.ts +97 -0
  135. package/src/modules/item-management/query/calculateNodeDepth.generated.ts +5 -0
  136. package/src/modules/item-management/query/calculateNodeDepth.test.ts +56 -0
  137. package/src/modules/item-management/query/calculateNodeDepth.ts +28 -0
  138. package/src/modules/item-management/query/calculateSubtreeDepth.generated.ts +5 -0
  139. package/src/modules/item-management/query/calculateSubtreeDepth.test.ts +75 -0
  140. package/src/modules/item-management/query/calculateSubtreeDepth.ts +29 -0
  141. package/src/modules/item-management/query/detectCircularReference.generated.ts +5 -0
  142. package/src/modules/item-management/query/detectCircularReference.test.ts +61 -0
  143. package/src/modules/item-management/query/detectCircularReference.ts +32 -0
  144. package/src/modules/item-management/query/getItem.generated.ts +5 -0
  145. package/src/modules/item-management/query/getItem.test.ts +67 -0
  146. package/src/modules/item-management/query/getItem.ts +20 -0
  147. package/src/modules/item-management/query/getItemTaxonomyAssignment.generated.ts +5 -0
  148. package/src/modules/item-management/query/getItemTaxonomyAssignment.test.ts +25 -0
  149. package/src/modules/item-management/query/getItemTaxonomyAssignment.ts +18 -0
  150. package/src/modules/item-management/query/getTaxonomyNode.generated.ts +5 -0
  151. package/src/modules/item-management/query/getTaxonomyNode.test.ts +47 -0
  152. package/src/modules/item-management/query/getTaxonomyNode.ts +18 -0
  153. package/src/modules/item-management/query/getTaxonomyNodeAssignments.generated.ts +5 -0
  154. package/src/modules/item-management/query/getTaxonomyNodeAssignments.test.ts +25 -0
  155. package/src/modules/item-management/query/getTaxonomyNodeAssignments.ts +16 -0
  156. package/src/modules/item-management/query/getTaxonomyNodeChildren.generated.ts +5 -0
  157. package/src/modules/item-management/query/getTaxonomyNodeChildren.test.ts +34 -0
  158. package/src/modules/item-management/query/getTaxonomyNodeChildren.ts +16 -0
  159. package/src/modules/item-management/tailor.config.ts +11 -0
  160. package/src/modules/item-management/testing/fixtures.ts +81 -0
  161. package/src/modules/primitives/command/activateCategory.generated.ts +6 -0
  162. package/src/modules/primitives/command/activateCategory.test.ts +11 -29
  163. package/src/modules/primitives/command/activateCategory.ts +27 -34
  164. package/src/modules/primitives/command/activateCurrency.generated.ts +6 -0
  165. package/src/modules/primitives/command/activateCurrency.test.ts +11 -29
  166. package/src/modules/primitives/command/activateCurrency.ts +27 -34
  167. package/src/modules/primitives/command/activateUnit.generated.ts +6 -0
  168. package/src/modules/primitives/command/activateUnit.test.ts +11 -15
  169. package/src/modules/primitives/command/activateUnit.ts +27 -34
  170. package/src/modules/primitives/command/createCategory.generated.ts +6 -0
  171. package/src/modules/primitives/command/createCategory.test.ts +27 -39
  172. package/src/modules/primitives/command/createCategory.ts +53 -62
  173. package/src/modules/primitives/command/createCurrency.generated.ts +6 -0
  174. package/src/modules/primitives/command/createCurrency.test.ts +78 -71
  175. package/src/modules/primitives/command/createCurrency.ts +43 -48
  176. package/src/modules/primitives/command/createExchangeRate.generated.ts +6 -0
  177. package/src/modules/primitives/command/createExchangeRate.test.ts +101 -100
  178. package/src/modules/primitives/command/createExchangeRate.ts +50 -59
  179. package/src/modules/primitives/command/createUnit.generated.ts +6 -0
  180. package/src/modules/primitives/command/createUnit.test.ts +92 -95
  181. package/src/modules/primitives/command/createUnit.ts +54 -57
  182. package/src/modules/primitives/command/deactivateCategory.generated.ts +6 -0
  183. package/src/modules/primitives/command/deactivateCategory.test.ts +27 -28
  184. package/src/modules/primitives/command/deactivateCategory.ts +43 -50
  185. package/src/modules/primitives/command/deactivateCurrency.generated.ts +6 -0
  186. package/src/modules/primitives/command/deactivateCurrency.test.ts +23 -38
  187. package/src/modules/primitives/command/deactivateCurrency.ts +31 -38
  188. package/src/modules/primitives/command/deactivateUnit.generated.ts +6 -0
  189. package/src/modules/primitives/command/deactivateUnit.test.ts +27 -23
  190. package/src/modules/primitives/command/deactivateUnit.ts +39 -49
  191. package/src/modules/primitives/command/setBaseCurrency.generated.ts +6 -0
  192. package/src/modules/primitives/command/setBaseCurrency.test.ts +40 -33
  193. package/src/modules/primitives/command/setBaseCurrency.ts +43 -50
  194. package/src/modules/primitives/command/setReferenceUnit.generated.ts +6 -0
  195. package/src/modules/primitives/command/setReferenceUnit.test.ts +39 -35
  196. package/src/modules/primitives/command/setReferenceUnit.ts +46 -59
  197. package/src/modules/primitives/db/unit.ts +13 -3
  198. package/src/modules/primitives/docs/commands/ActivateCategory.md +1 -2
  199. package/src/modules/primitives/docs/commands/ActivateCurrency.md +1 -2
  200. package/src/modules/primitives/docs/commands/ActivateUnit.md +1 -2
  201. package/src/modules/primitives/docs/commands/CreateCategory.md +1 -4
  202. package/src/modules/primitives/docs/commands/CreateCurrency.md +3 -4
  203. package/src/modules/primitives/docs/commands/CreateExchangeRate.md +4 -5
  204. package/src/modules/primitives/docs/commands/CreateUnit.md +5 -5
  205. package/src/modules/primitives/docs/commands/DeactivateCategory.md +2 -3
  206. package/src/modules/primitives/docs/commands/DeactivateCurrency.md +2 -3
  207. package/src/modules/primitives/docs/commands/DeactivateUnit.md +2 -3
  208. package/src/modules/primitives/docs/commands/SetBaseCurrency.md +2 -3
  209. package/src/modules/primitives/docs/commands/SetReferenceUnit.md +2 -3
  210. package/src/modules/primitives/docs/models/Currency.md +4 -0
  211. package/src/modules/primitives/docs/models/ExchangeRate.md +4 -1
  212. package/src/modules/primitives/docs/models/Unit.md +4 -1
  213. package/src/modules/primitives/docs/models/UoMCategory.md +2 -0
  214. package/src/modules/primitives/docs/{commands → queries}/ConvertAmount.md +3 -5
  215. package/src/modules/primitives/docs/{commands → queries}/ConvertQuantity.md +3 -5
  216. package/src/modules/primitives/docs/queries/GetBaseCurrency.md +32 -0
  217. package/src/modules/primitives/docs/queries/GetCurrency.md +36 -0
  218. package/src/modules/primitives/docs/queries/GetUnit.md +36 -0
  219. package/src/modules/primitives/docs/queries/GetUoMCategory.md +36 -0
  220. package/src/modules/primitives/docs/queries/ListUnitsByCategory.md +26 -0
  221. package/src/modules/primitives/generated/kysely-tailordb.ts +24 -45
  222. package/src/modules/primitives/index.ts +17 -6
  223. package/src/modules/primitives/lib/errors.generated.ts +112 -0
  224. package/src/modules/primitives/{permissions.ts → lib/permissions.generated.ts} +9 -10
  225. package/src/modules/primitives/module.ts +39 -27
  226. package/src/modules/primitives/query/convertAmount.generated.ts +5 -0
  227. package/src/modules/primitives/{command → query}/convertAmount.test.ts +4 -21
  228. package/src/modules/primitives/query/convertAmount.ts +121 -0
  229. package/src/modules/primitives/query/convertQuantity.generated.ts +5 -0
  230. package/src/modules/primitives/{command → query}/convertQuantity.test.ts +8 -15
  231. package/src/modules/primitives/query/convertQuantity.ts +63 -0
  232. package/src/modules/primitives/query/getBaseCurrency.generated.ts +5 -0
  233. package/src/modules/primitives/query/getBaseCurrency.test.ts +28 -0
  234. package/src/modules/primitives/query/getBaseCurrency.ts +16 -0
  235. package/src/modules/primitives/query/getCurrency.generated.ts +5 -0
  236. package/src/modules/primitives/query/getCurrency.test.ts +47 -0
  237. package/src/modules/primitives/query/getCurrency.ts +18 -0
  238. package/src/modules/primitives/query/getUnit.generated.ts +5 -0
  239. package/src/modules/primitives/query/getUnit.test.ts +47 -0
  240. package/src/modules/primitives/query/getUnit.ts +18 -0
  241. package/src/modules/primitives/query/getUoMCategory.generated.ts +5 -0
  242. package/src/modules/primitives/query/getUoMCategory.test.ts +47 -0
  243. package/src/modules/primitives/query/getUoMCategory.ts +18 -0
  244. package/src/modules/primitives/query/listUnitsByCategory.generated.ts +5 -0
  245. package/src/modules/primitives/query/listUnitsByCategory.ts +16 -0
  246. package/src/modules/primitives/tailor.config.ts +3 -3
  247. package/src/modules/shared/defineCommand.test.ts +23 -10
  248. package/src/modules/shared/defineCommand.ts +23 -10
  249. package/src/modules/shared/defineQuery.test.ts +28 -0
  250. package/src/modules/shared/defineQuery.ts +16 -0
  251. package/src/modules/shared/internal.ts +3 -1
  252. package/src/modules/shared/requirePermission.test.ts +22 -21
  253. package/src/modules/shared/requirePermission.ts +8 -2
  254. package/src/modules/shared/result.ts +12 -0
  255. package/src/modules/shared/types.ts +8 -0
  256. package/src/modules/testing/index.ts +36 -11
  257. package/src/modules/user-management/command/activateUser.generated.ts +6 -0
  258. package/src/modules/user-management/command/activateUser.test.ts +27 -27
  259. package/src/modules/user-management/command/activateUser.ts +40 -48
  260. package/src/modules/user-management/command/assignPermissionToRole.generated.ts +6 -0
  261. package/src/modules/user-management/command/assignPermissionToRole.test.ts +42 -43
  262. package/src/modules/user-management/command/assignPermissionToRole.ts +59 -62
  263. package/src/modules/user-management/command/assignRoleToUser.generated.ts +6 -0
  264. package/src/modules/user-management/command/assignRoleToUser.test.ts +70 -63
  265. package/src/modules/user-management/command/assignRoleToUser.ts +63 -66
  266. package/src/modules/user-management/command/createPermission.generated.ts +6 -0
  267. package/src/modules/user-management/command/createPermission.test.ts +45 -38
  268. package/src/modules/user-management/command/createPermission.ts +42 -46
  269. package/src/modules/user-management/command/createRole.generated.ts +6 -0
  270. package/src/modules/user-management/command/createRole.test.ts +30 -29
  271. package/src/modules/user-management/command/createRole.ts +33 -33
  272. package/src/modules/user-management/command/createUser.generated.ts +6 -0
  273. package/src/modules/user-management/command/createUser.test.ts +64 -42
  274. package/src/modules/user-management/command/createUser.ts +54 -56
  275. package/src/modules/user-management/command/deactivateUser.generated.ts +6 -0
  276. package/src/modules/user-management/command/deactivateUser.test.ts +27 -27
  277. package/src/modules/user-management/command/deactivateUser.ts +40 -48
  278. package/src/modules/user-management/command/logAuditEvent.generated.ts +6 -0
  279. package/src/modules/user-management/command/logAuditEvent.test.ts +50 -42
  280. package/src/modules/user-management/command/logAuditEvent.ts +25 -28
  281. package/src/modules/user-management/command/reactivateUser.generated.ts +6 -0
  282. package/src/modules/user-management/command/reactivateUser.test.ts +31 -27
  283. package/src/modules/user-management/command/reactivateUser.ts +40 -48
  284. package/src/modules/user-management/command/revokePermissionFromRole.generated.ts +6 -0
  285. package/src/modules/user-management/command/revokePermissionFromRole.test.ts +52 -51
  286. package/src/modules/user-management/command/revokePermissionFromRole.ts +60 -57
  287. package/src/modules/user-management/command/revokeRoleFromUser.generated.ts +6 -0
  288. package/src/modules/user-management/command/revokeRoleFromUser.test.ts +53 -48
  289. package/src/modules/user-management/command/revokeRoleFromUser.ts +58 -57
  290. package/src/modules/user-management/docs/commands/CreatePermission.md +2 -2
  291. package/src/modules/user-management/docs/commands/CreateRole.md +1 -1
  292. package/src/modules/user-management/docs/models/AuditEvent.md +2 -0
  293. package/src/modules/user-management/docs/models/Permission.md +2 -0
  294. package/src/modules/user-management/docs/models/Role.md +2 -0
  295. package/src/modules/user-management/docs/models/RolePermission.md +2 -0
  296. package/src/modules/user-management/docs/models/User.md +2 -0
  297. package/src/modules/user-management/docs/models/UserRole.md +2 -0
  298. package/src/modules/user-management/generated/enums.ts +11 -11
  299. package/src/modules/user-management/generated/kysely-tailordb.ts +27 -56
  300. package/src/modules/user-management/index.ts +2 -2
  301. package/src/modules/user-management/lib/errors.generated.ts +67 -0
  302. package/src/modules/user-management/{permissions.ts → lib/permissions.generated.ts} +8 -7
  303. package/src/modules/user-management/module.ts +22 -22
  304. package/src/modules/user-management/tailor.config.ts +3 -3
  305. package/src/schemas.ts +2 -1
  306. package/skills/1-module-docs/references/structure.md +0 -22
  307. package/skills/2-module-feature-breakdown/references/commands.md +0 -48
  308. package/skills/2-module-feature-breakdown/references/structure.md +0 -22
  309. package/skills/3-module-doc-review/references/commands.md +0 -54
  310. package/skills/3-module-doc-review/references/models.md +0 -29
  311. package/skills/4-module-tdd-implementation/SKILL.md +0 -74
  312. package/skills/4-module-tdd-implementation/references/commands.md +0 -45
  313. package/skills/4-module-tdd-implementation/references/errors.md +0 -7
  314. package/skills/4-module-tdd-implementation/references/models.md +0 -30
  315. package/skills/4-module-tdd-implementation/references/structure.md +0 -22
  316. package/skills/4-module-tdd-implementation/references/testing.md +0 -37
  317. package/skills/5-module-implementation-review/references/commands.md +0 -45
  318. package/skills/5-module-implementation-review/references/errors.md +0 -7
  319. package/skills/5-module-implementation-review/references/exports.md +0 -8
  320. package/skills/5-module-implementation-review/references/models.md +0 -30
  321. package/src/modules/primitives/command/convertAmount.ts +0 -126
  322. package/src/modules/primitives/command/convertQuantity.ts +0 -73
  323. package/src/modules/primitives/lib/errors.ts +0 -138
  324. package/src/modules/user-management/lib/errors.ts +0 -81
  325. /package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/references/structure.md +0 -0
  326. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-detailview.md +0 -0
  327. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-form.md +0 -0
  328. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-listview.md +0 -0
  329. /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/structure.md +0 -0
  330. /package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/references/structure.md +0 -0
  331. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/component.md +0 -0
  332. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-detailview.md +0 -0
  333. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-form.md +0 -0
  334. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-listview.md +0 -0
  335. /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/structure.md +0 -0
  336. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/component.md +0 -0
  337. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-detailview.md +0 -0
  338. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-form.md +0 -0
  339. /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-listview.md +0 -0
  340. /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/auth.md +0 -0
  341. /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/structure.md +0 -0
  342. /package/skills/{2-module-feature-breakdown → erp-kit-module-4-tdd}/references/models.md +0 -0
@@ -0,0 +1,26 @@
1
+ # ListUnitsByCategory
2
+
3
+ ## Overview
4
+
5
+ ListUnitsByCategory retrieves all units belonging to a specific UoM category. Used to find related units within a category and to validate category constraints such as ensuring a category has units before deactivation.
6
+
7
+ ## Business Rules
8
+
9
+ - Returns all units with the matching categoryId
10
+ - Returns empty array if no units match
11
+
12
+ ## Process Flow
13
+
14
+ ```mermaid
15
+ flowchart TD
16
+ A[Receive categoryId] --> B[SELECT from Unit where categoryId = input]
17
+ B --> C[Return units array]
18
+ ```
19
+
20
+ ## External Dependencies
21
+
22
+ - None
23
+
24
+ ## Error Scenarios
25
+
26
+ - None
@@ -1,19 +1,15 @@
1
1
  import {
2
- type ColumnType,
3
- Kysely,
4
- type KyselyConfig,
5
- type Transaction as KyselyTransaction,
6
- type Insertable as KyselyInsertable,
7
- type Selectable as KyselySelectable,
8
- type Updateable as KyselyUpdateable,
9
- } from "kysely";
10
- import { TailordbDialect } from "@tailor-platform/function-kysely-tailordb";
11
-
12
- type Timestamp = ColumnType<Date, Date | string, Date | string>;
13
- type Generated<T> =
14
- T extends ColumnType<infer S, infer I, infer U>
15
- ? ColumnType<S, I | undefined, U>
16
- : ColumnType<T, T | undefined, T>;
2
+ createGetDB,
3
+ type Generated,
4
+ type Timestamp,
5
+ type NamespaceDB,
6
+ type NamespaceInsertable,
7
+ type NamespaceSelectable,
8
+ type NamespaceTable,
9
+ type NamespaceTableName,
10
+ type NamespaceTransaction,
11
+ type NamespaceUpdateable,
12
+ } from "@tailor-platform/sdk/kysely";
17
13
 
18
14
  export interface Namespace {
19
15
  "main-db": {
@@ -27,7 +23,7 @@ export interface Namespace {
27
23
  isActive: boolean;
28
24
  createdAt: Generated<Timestamp>;
29
25
  updatedAt: Timestamp | null;
30
- };
26
+ }
31
27
 
32
28
  ExchangeRate: {
33
29
  id: Generated<string>;
@@ -37,7 +33,7 @@ export interface Namespace {
37
33
  effectiveDate: Timestamp;
38
34
  createdAt: Generated<Timestamp>;
39
35
  updatedAt: Timestamp | null;
40
- };
36
+ }
41
37
 
42
38
  Unit: {
43
39
  id: Generated<string>;
@@ -49,7 +45,7 @@ export interface Namespace {
49
45
  isActive: boolean;
50
46
  createdAt: Generated<Timestamp>;
51
47
  updatedAt: Timestamp | null;
52
- };
48
+ }
53
49
 
54
50
  UoMCategory: {
55
51
  id: Generated<string>;
@@ -59,37 +55,20 @@ export interface Namespace {
59
55
  isActive: boolean;
60
56
  createdAt: Generated<Timestamp>;
61
57
  updatedAt: Timestamp | null;
62
- };
63
- };
58
+ }
59
+ }
64
60
  }
65
61
 
66
- export function getDB<const N extends keyof Namespace>(
67
- namespace: N,
68
- kyselyConfig?: Omit<KyselyConfig, "dialect">,
69
- ): Kysely<Namespace[N]> {
70
- const client = new tailordb.Client({ namespace });
71
- return new Kysely<Namespace[N]>({
72
- dialect: new TailordbDialect(client),
73
- ...kyselyConfig,
74
- });
75
- }
62
+ export const getDB = createGetDB<Namespace>();
76
63
 
77
- export type DB<N extends keyof Namespace = keyof Namespace> = ReturnType<typeof getDB<N>>;
64
+ export type DB<N extends keyof Namespace = keyof Namespace> = NamespaceDB<Namespace, N>;
78
65
 
79
66
  export type Transaction<K extends keyof Namespace | DB = keyof Namespace> =
80
- K extends DB<infer N>
81
- ? KyselyTransaction<Namespace[N]>
82
- : K extends keyof Namespace
83
- ? KyselyTransaction<Namespace[K]>
84
- : never;
67
+ NamespaceTransaction<Namespace, K>;
85
68
 
86
- type TableName = {
87
- [N in keyof Namespace]: keyof Namespace[N];
88
- }[keyof Namespace];
89
- export type Table<T extends TableName> = {
90
- [N in keyof Namespace]: T extends keyof Namespace[N] ? Namespace[N][T] : never;
91
- }[keyof Namespace];
69
+ type TableName = NamespaceTableName<Namespace>;
70
+ export type Table<T extends TableName> = NamespaceTable<Namespace, T>;
92
71
 
93
- export type Insertable<T extends keyof Namespace[keyof Namespace]> = KyselyInsertable<Table<T>>;
94
- export type Selectable<T extends keyof Namespace[keyof Namespace]> = KyselySelectable<Table<T>>;
95
- export type Updateable<T extends keyof Namespace[keyof Namespace]> = KyselyUpdateable<Table<T>>;
72
+ export type Insertable<T extends TableName> = NamespaceInsertable<Namespace, T>;
73
+ export type Selectable<T extends TableName> = NamespaceSelectable<Namespace, T>;
74
+ export type Updateable<T extends TableName> = NamespaceUpdateable<Namespace, T>;
@@ -1,9 +1,9 @@
1
1
  export { defineModule } from "./module";
2
- export { permissions, own, all } from "./permissions";
2
+ export { permissions, own, all } from "./lib/permissions.generated";
3
3
 
4
4
  // errors
5
5
  export {
6
- UoMCategoryNotFoundError,
6
+ UomCategoryNotFoundError,
7
7
  UnitNotFoundError,
8
8
  IncompatibleUnitsError,
9
9
  InactiveUnitError,
@@ -18,23 +18,34 @@ export {
18
18
  DuplicateCategoryNameError,
19
19
  CategoryHasActiveUnitsError,
20
20
  UnitNotInCategoryError,
21
- InvalidISOCodeError,
21
+ InvalidIsoCodeError,
22
22
  DuplicateCurrencyCodeError,
23
23
  InvalidDecimalPlacesError,
24
24
  CannotDeactivateBaseCurrencyError,
25
25
  CannotSetInactiveAsBaseCurrencyError,
26
26
  SameCurrencyPairError,
27
27
  InvalidExchangeRateError,
28
- } from "./lib/errors";
28
+ } from "./lib/errors.generated";
29
+
30
+ // queries
31
+ export { getUnit } from "./query/getUnit.generated";
32
+ export { getCurrency } from "./query/getCurrency.generated";
33
+ export { getUoMCategory } from "./query/getUoMCategory.generated";
34
+ export { getBaseCurrency } from "./query/getBaseCurrency.generated";
35
+ export { listUnitsByCategory } from "./query/listUnitsByCategory.generated";
29
36
 
30
37
  // input types
31
- export { type ConvertQuantityInput } from "./command/convertQuantity";
38
+ export { type GetUnitInput } from "./query/getUnit";
39
+ export { type GetCurrencyInput } from "./query/getCurrency";
40
+ export { type GetUoMCategoryInput } from "./query/getUoMCategory";
41
+ export { type ListUnitsByCategoryInput } from "./query/listUnitsByCategory";
42
+ export { type ConvertQuantityInput } from "./query/convertQuantity";
32
43
  export { type ActivateCategoryInput } from "./command/activateCategory";
33
44
  export { type DeactivateCategoryInput } from "./command/deactivateCategory";
34
45
  export { type SetReferenceUnitInput } from "./command/setReferenceUnit";
35
46
  export { type ActivateUnitInput } from "./command/activateUnit";
36
47
  export { type DeactivateUnitInput } from "./command/deactivateUnit";
37
- export { type ConvertAmountInput } from "./command/convertAmount";
48
+ export { type ConvertAmountInput } from "./query/convertAmount";
38
49
  export { type ActivateCurrencyInput } from "./command/activateCurrency";
39
50
  export { type DeactivateCurrencyInput } from "./command/deactivateCurrency";
40
51
  export { type SetBaseCurrencyInput } from "./command/setBaseCurrency";
@@ -0,0 +1,112 @@
1
+ // @generated — do not edit
2
+ import { createDomainError } from "../../shared/internal";
3
+
4
+ export const UomCategoryNotFoundError = createDomainError(
5
+ "UomCategoryNotFoundError", "UOM_CATEGORY_NOT_FOUND",
6
+ (identifier: string) => `Specified category ID does not exist: ${identifier}`,
7
+ );
8
+
9
+ export const CurrencyNotFoundError = createDomainError(
10
+ "CurrencyNotFoundError", "CURRENCY_NOT_FOUND",
11
+ (identifier: string) => `Specified currency ID does not exist: ${identifier}`,
12
+ );
13
+
14
+ export const UnitNotFoundError = createDomainError(
15
+ "UnitNotFoundError", "UNIT_NOT_FOUND",
16
+ (identifier: string) => `Specified unit ID does not exist: ${identifier}`,
17
+ );
18
+
19
+ export const DuplicateCategoryNameError = createDomainError(
20
+ "DuplicateCategoryNameError", "DUPLICATE_CATEGORY_NAME",
21
+ (identifier: string) => `Category with same name already exists: ${identifier}`,
22
+ );
23
+
24
+ export const InvalidIsoCodeError = createDomainError(
25
+ "InvalidIsoCodeError", "INVALID_ISO_CODE",
26
+ (identifier: string) => `Code is not exactly 3 uppercase letters: ${identifier}`,
27
+ );
28
+
29
+ export const DuplicateCurrencyCodeError = createDomainError(
30
+ "DuplicateCurrencyCodeError", "DUPLICATE_CURRENCY_CODE",
31
+ (identifier: string) => `Currency with same code already exists: ${identifier}`,
32
+ );
33
+
34
+ export const InvalidDecimalPlacesError = createDomainError(
35
+ "InvalidDecimalPlacesError", "INVALID_DECIMAL_PLACES",
36
+ (identifier: string) => `Value is negative or exceeds maximum (typically 4): ${identifier}`,
37
+ );
38
+
39
+ export const InactiveCurrencyError = createDomainError(
40
+ "InactiveCurrencyError", "INACTIVE_CURRENCY",
41
+ (identifier: string) => `Source or target currency is inactive: ${identifier}`,
42
+ );
43
+
44
+ export const SameCurrencyPairError = createDomainError(
45
+ "SameCurrencyPairError", "SAME_CURRENCY_PAIR",
46
+ (identifier: string) => `Source and target currencies are the same: ${identifier}`,
47
+ );
48
+
49
+ export const InvalidExchangeRateError = createDomainError(
50
+ "InvalidExchangeRateError", "INVALID_EXCHANGE_RATE",
51
+ (identifier: string) => `Rate is zero, negative, or not a valid number: ${identifier}`,
52
+ );
53
+
54
+ export const CategoryNotActiveError = createDomainError(
55
+ "CategoryNotActiveError", "CATEGORY_NOT_ACTIVE",
56
+ (identifier: string) => `Category is inactive: ${identifier}`,
57
+ );
58
+
59
+ export const DuplicateUnitSymbolError = createDomainError(
60
+ "DuplicateUnitSymbolError", "DUPLICATE_UNIT_SYMBOL",
61
+ (identifier: string) => `Unit with same symbol already exists in category: ${identifier}`,
62
+ );
63
+
64
+ export const InvalidConversionFactorError = createDomainError(
65
+ "InvalidConversionFactorError", "INVALID_CONVERSION_FACTOR",
66
+ (identifier: string) => `Factor is zero, negative, or not a valid number: ${identifier}`,
67
+ );
68
+
69
+ export const InvalidRoundingPrecisionError = createDomainError(
70
+ "InvalidRoundingPrecisionError", "INVALID_ROUNDING_PRECISION",
71
+ (identifier: string) => `Precision is negative: ${identifier}`,
72
+ );
73
+
74
+ export const CategoryHasActiveUnitsError = createDomainError(
75
+ "CategoryHasActiveUnitsError", "CATEGORY_HAS_ACTIVE_UNITS",
76
+ (identifier: string) => `Category still contains active units that must be deactivated first: ${identifier}`,
77
+ );
78
+
79
+ export const CannotDeactivateBaseCurrencyError = createDomainError(
80
+ "CannotDeactivateBaseCurrencyError", "CANNOT_DEACTIVATE_BASE_CURRENCY",
81
+ (identifier: string) => `Attempting to deactivate the base currency, must change base currency first: ${identifier}`,
82
+ );
83
+
84
+ export const CannotDeactivateReferenceUnitError = createDomainError(
85
+ "CannotDeactivateReferenceUnitError", "CANNOT_DEACTIVATE_REFERENCE_UNIT",
86
+ (identifier: string) => `Attempting to deactivate the category's reference unit, must change reference unit first: ${identifier}`,
87
+ );
88
+
89
+ export const CannotSetInactiveAsBaseCurrencyError = createDomainError(
90
+ "CannotSetInactiveAsBaseCurrencyError", "CANNOT_SET_INACTIVE_AS_BASE_CURRENCY",
91
+ (identifier: string) => `Target currency is not in Active status, must be activated first: ${identifier}`,
92
+ );
93
+
94
+ export const UnitNotInCategoryError = createDomainError(
95
+ "UnitNotInCategoryError", "UNIT_NOT_IN_CATEGORY",
96
+ (identifier: string) => `Unit belongs to a different category than specified: ${identifier}`,
97
+ );
98
+
99
+ export const ExchangeRateNotFoundError = createDomainError(
100
+ "ExchangeRateNotFoundError", "EXCHANGE_RATE_NOT_FOUND",
101
+ (identifier: string) => `No rate found for the currency pair on or before the specified date: ${identifier}`,
102
+ );
103
+
104
+ export const IncompatibleUnitsError = createDomainError(
105
+ "IncompatibleUnitsError", "INCOMPATIBLE_UNITS",
106
+ (identifier: string) => `Source and target units belong to different categories: ${identifier}`,
107
+ );
108
+
109
+ export const InactiveUnitError = createDomainError(
110
+ "InactiveUnitError", "INACTIVE_UNIT",
111
+ (identifier: string) => `Either source or target unit is inactive: ${identifier}`,
112
+ );
@@ -1,18 +1,17 @@
1
- import { definePermissions } from "../shared/internal";
1
+ // @generated do not edit
2
+ import { definePermissions } from "../../shared/internal";
2
3
 
3
4
  export const { permissions, own, all } = definePermissions("primitives", [
4
- "convertQuantity",
5
- "createCategory",
6
5
  "activateCategory",
7
- "deactivateCategory",
8
- "setReferenceUnit",
9
- "createUnit",
6
+ "activateCurrency",
10
7
  "activateUnit",
11
- "deactivateUnit",
12
- "convertAmount",
8
+ "createCategory",
13
9
  "createCurrency",
14
- "activateCurrency",
10
+ "createExchangeRate",
11
+ "createUnit",
12
+ "deactivateCategory",
15
13
  "deactivateCurrency",
14
+ "deactivateUnit",
16
15
  "setBaseCurrency",
17
- "createExchangeRate",
16
+ "setReferenceUnit",
18
17
  ] as const);
@@ -1,19 +1,24 @@
1
1
  import { type TailorAnyDBField } from "@tailor-platform/sdk";
2
2
  import { type EmptyFields, type FieldsToInsertable } from "../shared/internal";
3
- import { makeCreateCategory } from "./command/createCategory";
4
- import { makeCreateCurrency } from "./command/createCurrency";
5
- import { makeCreateUnit } from "./command/createUnit";
6
- import { makeCreateExchangeRate } from "./command/createExchangeRate";
7
- import { activateCategory } from "./command/activateCategory";
8
- import { deactivateCategory } from "./command/deactivateCategory";
9
- import { activateUnit } from "./command/activateUnit";
10
- import { deactivateUnit } from "./command/deactivateUnit";
11
- import { setReferenceUnit } from "./command/setReferenceUnit";
12
- import { convertQuantity } from "./command/convertQuantity";
13
- import { activateCurrency } from "./command/activateCurrency";
14
- import { deactivateCurrency } from "./command/deactivateCurrency";
15
- import { setBaseCurrency } from "./command/setBaseCurrency";
16
- import { convertAmount } from "./command/convertAmount";
3
+ import { createCategory } from "./command/createCategory.generated";
4
+ import { createCurrency } from "./command/createCurrency.generated";
5
+ import { createUnit } from "./command/createUnit.generated";
6
+ import { createExchangeRate } from "./command/createExchangeRate.generated";
7
+ import { activateCategory } from "./command/activateCategory.generated";
8
+ import { deactivateCategory } from "./command/deactivateCategory.generated";
9
+ import { activateUnit } from "./command/activateUnit.generated";
10
+ import { deactivateUnit } from "./command/deactivateUnit.generated";
11
+ import { setReferenceUnit } from "./command/setReferenceUnit.generated";
12
+ import { convertQuantity } from "./query/convertQuantity.generated";
13
+ import { getUnit } from "./query/getUnit.generated";
14
+ import { getCurrency } from "./query/getCurrency.generated";
15
+ import { getUoMCategory } from "./query/getUoMCategory.generated";
16
+ import { getBaseCurrency } from "./query/getBaseCurrency.generated";
17
+ import { listUnitsByCategory } from "./query/listUnitsByCategory.generated";
18
+ import { activateCurrency } from "./command/activateCurrency.generated";
19
+ import { deactivateCurrency } from "./command/deactivateCurrency.generated";
20
+ import { setBaseCurrency } from "./command/setBaseCurrency.generated";
21
+ import { convertAmount } from "./query/convertAmount.generated";
17
22
  import { createUoMCategoryType, CreateUoMCategoryTypeParams } from "./db/uomCategory";
18
23
  import { createUnitType, CreateUnitTypeParams } from "./db/unit";
19
24
  import { createCurrencyType, CreateCurrencyTypeParams } from "./db/currency";
@@ -40,26 +45,33 @@ export const defineModule = <
40
45
  params: DefineModuleParams<CatF, UnitF, CurF, ERF>,
41
46
  ) => {
42
47
  const uomCategory = createUoMCategoryType(params.uomCategory ?? {});
43
- const unit = createUnitType(params.unit ?? {});
48
+ const unit = createUnitType({ ...params.unit, uomCategoryType: uomCategory });
44
49
  const currency = createCurrencyType(params.currency ?? {});
45
50
  const exchangeRate = createExchangeRateType(params.exchangeRate ?? {});
46
51
 
47
52
  return {
48
53
  db: { uomCategory, unit, currency, exchangeRate },
49
54
  commands: {
50
- createCategory: makeCreateCategory<FieldsToInsertable<CatF>, FieldsToInsertable<UnitF>>(),
51
- createCurrency: makeCreateCurrency<FieldsToInsertable<CurF>>(),
52
- createUnit: makeCreateUnit<FieldsToInsertable<UnitF>>(),
53
- createExchangeRate: makeCreateExchangeRate<FieldsToInsertable<ERF>>(),
54
- activateCategory,
55
- deactivateCategory,
56
- activateUnit,
57
- deactivateUnit,
58
- setReferenceUnit,
55
+ createCategory: createCategory<FieldsToInsertable<CatF>, FieldsToInsertable<UnitF>>(),
56
+ createCurrency: createCurrency<FieldsToInsertable<CurF>>(),
57
+ createUnit: createUnit<FieldsToInsertable<UnitF>>(),
58
+ createExchangeRate: createExchangeRate<FieldsToInsertable<ERF>>(),
59
+ activateCategory: activateCategory(),
60
+ deactivateCategory: deactivateCategory(),
61
+ activateUnit: activateUnit(),
62
+ deactivateUnit: deactivateUnit(),
63
+ setReferenceUnit: setReferenceUnit(),
64
+ activateCurrency: activateCurrency(),
65
+ deactivateCurrency: deactivateCurrency(),
66
+ setBaseCurrency: setBaseCurrency(),
67
+ },
68
+ queries: {
69
+ getUnit,
70
+ getCurrency,
71
+ getUoMCategory,
72
+ getBaseCurrency,
73
+ listUnitsByCategory,
59
74
  convertQuantity,
60
- activateCurrency,
61
- deactivateCurrency,
62
- setBaseCurrency,
63
75
  convertAmount,
64
76
  },
65
77
  };
@@ -0,0 +1,5 @@
1
+ // @generated — do not edit
2
+ import { defineQuery } from "../../shared/internal";
3
+ import { run } from "./convertAmount";
4
+
5
+ export const convertAmount = defineQuery(run);
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { InsufficientPermissionError, type CommandContext } from "../../shared/internal";
3
2
  import { createMockDb } from "../../testing/index";
4
3
  import { DB } from "../generated/kysely-tailordb";
4
+ import type { QueryContext } from "../../shared/internal";
5
5
  import {
6
6
  CurrencyNotFoundError,
7
7
  ExchangeRateNotFoundError,
8
8
  InactiveCurrencyError,
9
- } from "../lib/errors";
9
+ } from "../lib/errors.generated";
10
10
  import {
11
11
  baseCurrencyEUR,
12
12
  baseCurrencyJPY,
@@ -16,10 +16,10 @@ import {
16
16
  inactiveCurrency,
17
17
  olderExchangeRateUSDtoEUR,
18
18
  } from "../testing/fixtures";
19
- import { convertAmount } from "./convertAmount";
19
+ import { convertAmount } from "./convertAmount.generated";
20
20
 
21
21
  describe("convertAmount", () => {
22
- const ctx: CommandContext = { actorId: "test-actor", permissions: ["primitives:convertAmount"] };
22
+ const ctx: QueryContext = { actorId: "test-actor" };
23
23
 
24
24
  // Error cases first
25
25
  it("throws when source currency doesn't exist", async () => {
@@ -255,21 +255,4 @@ describe("convertAmount", () => {
255
255
 
256
256
  expect(result.convertedAmount).toBe(0);
257
257
  });
258
-
259
- it("throws when permission is missing", async () => {
260
- const { db } = createMockDb<DB>();
261
- const denied: CommandContext = { actorId: "test-actor", permissions: [] };
262
- await expect(
263
- convertAmount(
264
- db,
265
- {
266
- amount: 100,
267
- sourceCurrencyCode: "USD",
268
- targetCurrencyCode: "EUR",
269
- conversionDate: "2024-01-15",
270
- },
271
- denied,
272
- ),
273
- ).rejects.toBeInstanceOf(InsufficientPermissionError);
274
- });
275
258
  });
@@ -0,0 +1,121 @@
1
+ import type { ReadonlyDB, QueryContext } from "../../shared/internal";
2
+ import type { DB } from "../generated/kysely-tailordb";
3
+ import {
4
+ CurrencyNotFoundError,
5
+ ExchangeRateNotFoundError,
6
+ InactiveCurrencyError,
7
+ } from "../lib/errors.generated";
8
+ import { getCurrency } from "./getCurrency.generated";
9
+
10
+ export interface ConvertAmountInput {
11
+ amount: number;
12
+ sourceCurrencyCode: string;
13
+ targetCurrencyCode: string;
14
+ conversionDate: string;
15
+ }
16
+
17
+ /**
18
+ * Converts a monetary amount from one currency to another using the applicable
19
+ * exchange rate for a given date. Currencies are identified by their ISO 4217
20
+ * code (e.g., "USD", "EUR", "JPY"). The function looks up the most recent exchange
21
+ * rate on or before the specified date. If no direct rate exists, it calculates
22
+ * the inverse rate. Result is rounded to the target currency's decimal precision.
23
+ */
24
+ export async function run(db: ReadonlyDB<DB>, input: ConvertAmountInput, ctx: QueryContext) {
25
+ // Validate source currency exists
26
+ const { currency: sourceCurrency } = await getCurrency(
27
+ db,
28
+ {
29
+ code: input.sourceCurrencyCode,
30
+ },
31
+ ctx,
32
+ );
33
+
34
+ if (!sourceCurrency) {
35
+ throw new CurrencyNotFoundError(input.sourceCurrencyCode);
36
+ }
37
+
38
+ // Validate target currency exists
39
+ const { currency: targetCurrency } = await getCurrency(
40
+ db,
41
+ {
42
+ code: input.targetCurrencyCode,
43
+ },
44
+ ctx,
45
+ );
46
+
47
+ if (!targetCurrency) {
48
+ throw new CurrencyNotFoundError(input.targetCurrencyCode);
49
+ }
50
+
51
+ // Validate both currencies are active
52
+ if (!sourceCurrency.isActive) {
53
+ throw new InactiveCurrencyError(input.sourceCurrencyCode);
54
+ }
55
+
56
+ if (!targetCurrency.isActive) {
57
+ throw new InactiveCurrencyError(input.targetCurrencyCode);
58
+ }
59
+
60
+ // Same currency - return original amount
61
+ if (sourceCurrency.id === targetCurrency.id) {
62
+ return {
63
+ convertedAmount: input.amount,
64
+ exchangeRate: 1,
65
+ sourceCurrency,
66
+ targetCurrency,
67
+ exchangeRateRecord: null,
68
+ };
69
+ }
70
+
71
+ // Find direct exchange rate (most recent on or before conversion date)
72
+ const conversionDate = new Date(input.conversionDate);
73
+ const directRate = await db
74
+ .selectFrom("ExchangeRate")
75
+ .selectAll()
76
+ .where("sourceCurrencyId", "=", sourceCurrency.id)
77
+ .where("targetCurrencyId", "=", targetCurrency.id)
78
+ .where("effectiveDate", "<=", conversionDate)
79
+ .orderBy("effectiveDate", "desc")
80
+ .executeTakeFirst();
81
+
82
+ let exchangeRate: number;
83
+ let exchangeRateRecord = null;
84
+
85
+ if (directRate) {
86
+ exchangeRate = directRate.rate;
87
+ exchangeRateRecord = directRate;
88
+ } else {
89
+ // Try inverse rate
90
+ const inverseRate = await db
91
+ .selectFrom("ExchangeRate")
92
+ .selectAll()
93
+ .where("sourceCurrencyId", "=", targetCurrency.id)
94
+ .where("targetCurrencyId", "=", sourceCurrency.id)
95
+ .where("effectiveDate", "<=", conversionDate)
96
+ .orderBy("effectiveDate", "desc")
97
+ .executeTakeFirst();
98
+
99
+ if (!inverseRate) {
100
+ throw new ExchangeRateNotFoundError(
101
+ `${input.sourceCurrencyCode} to ${input.targetCurrencyCode} on ${input.conversionDate}`,
102
+ );
103
+ }
104
+
105
+ exchangeRate = 1 / inverseRate.rate;
106
+ exchangeRateRecord = inverseRate;
107
+ }
108
+
109
+ // Calculate converted amount, rounded to target currency's decimal places
110
+ const rawResult = input.amount * exchangeRate;
111
+ const roundingFactor = Math.pow(10, targetCurrency.decimalPlaces);
112
+ const convertedAmount = Math.round(rawResult * roundingFactor) / roundingFactor;
113
+
114
+ return {
115
+ convertedAmount,
116
+ exchangeRate,
117
+ sourceCurrency,
118
+ targetCurrency,
119
+ exchangeRateRecord,
120
+ };
121
+ }
@@ -0,0 +1,5 @@
1
+ // @generated — do not edit
2
+ import { defineQuery } from "../../shared/internal";
3
+ import { run } from "./convertQuantity";
4
+
5
+ export const convertQuantity = defineQuery(run);
@@ -1,8 +1,12 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { createMockDb } from "../../testing/index";
3
- import { InsufficientPermissionError, type CommandContext } from "../../shared/internal";
4
3
  import { DB } from "../generated/kysely-tailordb";
5
- import { InactiveUnitError, IncompatibleUnitsError, UnitNotFoundError } from "../lib/errors";
4
+ import type { QueryContext } from "../../shared/internal";
5
+ import {
6
+ InactiveUnitError,
7
+ IncompatibleUnitsError,
8
+ UnitNotFoundError,
9
+ } from "../lib/errors.generated";
6
10
  import {
7
11
  baseUnitGram,
8
12
  baseUnitKg,
@@ -10,13 +14,10 @@ import {
10
14
  baseUnitPound,
11
15
  inactiveUnit,
12
16
  } from "../testing/fixtures";
13
- import { convertQuantity } from "./convertQuantity";
17
+ import { convertQuantity } from "./convertQuantity.generated";
14
18
 
15
19
  describe("convertQuantity", () => {
16
- const ctx: CommandContext = {
17
- actorId: "test-actor",
18
- permissions: ["primitives:convertQuantity"],
19
- };
20
+ const ctx: QueryContext = { actorId: "test-actor" };
20
21
 
21
22
  // Error cases first
22
23
  it("throws when source unit doesn't exist", async () => {
@@ -208,12 +209,4 @@ describe("convertQuantity", () => {
208
209
 
209
210
  expect(result.convertedQuantity).toBe(0);
210
211
  });
211
-
212
- it("throws when permission is missing", async () => {
213
- const { db } = createMockDb<DB>();
214
- const denied: CommandContext = { actorId: "test-actor", permissions: [] };
215
- await expect(
216
- convertQuantity(db, { quantity: 1, sourceUnitSymbol: "kg", targetUnitSymbol: "g" }, denied),
217
- ).rejects.toBeInstanceOf(InsufficientPermissionError);
218
- });
219
212
  });