@tailor-platform/erp-kit 0.6.0 → 0.7.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 (290) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +10 -10
  3. package/dist/cli.mjs +407 -69
  4. package/package.json +1 -1
  5. package/skills/erp-kit-app-1-requirements/SKILL.md +33 -17
  6. package/skills/erp-kit-app-2-requirements-review/SKILL.md +12 -0
  7. package/skills/erp-kit-app-3-plan/SKILL.md +18 -4
  8. package/skills/erp-kit-app-3-plan/references/resolver-extraction.md +1 -1
  9. package/skills/erp-kit-app-3-plan/references/screen-extraction.md +1 -1
  10. package/skills/erp-kit-app-4-plan-review/SKILL.md +12 -0
  11. package/skills/erp-kit-app-5-impl-backend/SKILL.md +12 -0
  12. package/skills/erp-kit-app-6-impl-frontend/SKILL.md +12 -0
  13. package/skills/erp-kit-app-7-impl-review/SKILL.md +13 -1
  14. package/skills/erp-kit-app-shared/references/progress-protocol.md +77 -0
  15. package/skills/erp-kit-mock-scenario/SKILL.md +1 -1
  16. package/skills/erp-kit-module-1-requirements/SKILL.md +1 -1
  17. package/skills/erp-kit-module-3-plan/SKILL.md +3 -3
  18. package/skills/erp-kit-module-3-update-plan/SKILL.md +3 -3
  19. package/skills/erp-kit-module-5-impl/SKILL.md +1 -1
  20. package/src/commands/app/index.ts +2 -0
  21. package/src/commands/app/progress/git-context.ts +16 -0
  22. package/src/commands/app/progress/index.ts +45 -0
  23. package/src/commands/app/progress/log.ts +49 -0
  24. package/src/commands/app/progress/progress.test.ts +128 -0
  25. package/src/commands/app/progress/schema-cmd.ts +10 -0
  26. package/src/commands/check.test.ts +4 -4
  27. package/src/commands/lib/discovery.test.ts +5 -7
  28. package/src/commands/lib/discovery.ts +8 -8
  29. package/src/commands/lib/sync-check-source.test.ts +1 -1
  30. package/src/commands/lib/sync-check-source.ts +6 -1
  31. package/src/commands/lib/sync-check-tests.test.ts +43 -0
  32. package/src/commands/lib/sync-check-tests.ts +20 -2
  33. package/src/commands/sync-check.ts +3 -0
  34. package/src/generator/generate-app-code.test.ts +0 -6
  35. package/src/generator/generate-app-code.ts +3 -13
  36. package/src/generator/generate-code.test.ts +10 -40
  37. package/src/generator/generate-code.ts +6 -12
  38. package/src/generator/stub-templates.test.ts +0 -7
  39. package/src/generator/stub-templates.ts +0 -14
  40. package/src/modules/finance-ledger/README.md +50 -0
  41. package/src/modules/finance-ledger/command/.gitkeep +0 -0
  42. package/src/modules/finance-ledger/command/addJournalLine.generated.ts +6 -0
  43. package/src/modules/finance-ledger/command/addJournalLine.test.ts +438 -0
  44. package/src/modules/finance-ledger/command/addJournalLine.ts +122 -0
  45. package/src/modules/finance-ledger/command/approveAndLockPeriod.generated.ts +6 -0
  46. package/src/modules/finance-ledger/command/approveAndLockPeriod.test.ts +107 -0
  47. package/src/modules/finance-ledger/command/approveAndLockPeriod.ts +72 -0
  48. package/src/modules/finance-ledger/command/beginClose.generated.ts +6 -0
  49. package/src/modules/finance-ledger/command/beginClose.test.ts +106 -0
  50. package/src/modules/finance-ledger/command/beginClose.ts +58 -0
  51. package/src/modules/finance-ledger/command/closePeriod.generated.ts +6 -0
  52. package/src/modules/finance-ledger/command/closePeriod.test.ts +87 -0
  53. package/src/modules/finance-ledger/command/closePeriod.ts +44 -0
  54. package/src/modules/finance-ledger/command/createAccountingPeriod.generated.ts +6 -0
  55. package/src/modules/finance-ledger/command/createAccountingPeriod.test.ts +425 -0
  56. package/src/modules/finance-ledger/command/createAccountingPeriod.ts +133 -0
  57. package/src/modules/finance-ledger/command/createFiscalYear.generated.ts +6 -0
  58. package/src/modules/finance-ledger/command/createFiscalYear.test.ts +197 -0
  59. package/src/modules/finance-ledger/command/createFiscalYear.ts +70 -0
  60. package/src/modules/finance-ledger/command/createJournalEntry.generated.ts +6 -0
  61. package/src/modules/finance-ledger/command/createJournalEntry.test.ts +261 -0
  62. package/src/modules/finance-ledger/command/createJournalEntry.ts +121 -0
  63. package/src/modules/finance-ledger/command/deleteAccountingPeriod.generated.ts +6 -0
  64. package/src/modules/finance-ledger/command/deleteAccountingPeriod.test.ts +71 -0
  65. package/src/modules/finance-ledger/command/deleteAccountingPeriod.ts +55 -0
  66. package/src/modules/finance-ledger/command/deleteFiscalYear.generated.ts +6 -0
  67. package/src/modules/finance-ledger/command/deleteFiscalYear.test.ts +38 -0
  68. package/src/modules/finance-ledger/command/deleteFiscalYear.ts +34 -0
  69. package/src/modules/finance-ledger/command/deleteJournalEntry.generated.ts +6 -0
  70. package/src/modules/finance-ledger/command/deleteJournalEntry.test.ts +58 -0
  71. package/src/modules/finance-ledger/command/deleteJournalEntry.ts +43 -0
  72. package/src/modules/finance-ledger/command/executeYearEndClose.generated.ts +6 -0
  73. package/src/modules/finance-ledger/command/executeYearEndClose.test.ts +239 -0
  74. package/src/modules/finance-ledger/command/executeYearEndClose.ts +415 -0
  75. package/src/modules/finance-ledger/command/finalCloseAndLockPeriod.generated.ts +6 -0
  76. package/src/modules/finance-ledger/command/finalCloseAndLockPeriod.test.ts +102 -0
  77. package/src/modules/finance-ledger/command/finalCloseAndLockPeriod.ts +76 -0
  78. package/src/modules/finance-ledger/command/finalizeFinancialStatement.generated.ts +6 -0
  79. package/src/modules/finance-ledger/command/finalizeFinancialStatement.test.ts +73 -0
  80. package/src/modules/finance-ledger/command/finalizeFinancialStatement.ts +73 -0
  81. package/src/modules/finance-ledger/command/generateFinancialStatement.generated.ts +6 -0
  82. package/src/modules/finance-ledger/command/generateFinancialStatement.test.ts +311 -0
  83. package/src/modules/finance-ledger/command/generateFinancialStatement.ts +275 -0
  84. package/src/modules/finance-ledger/command/generatePreliminaryStatements.generated.ts +6 -0
  85. package/src/modules/finance-ledger/command/generatePreliminaryStatements.test.ts +152 -0
  86. package/src/modules/finance-ledger/command/generatePreliminaryStatements.ts +140 -0
  87. package/src/modules/finance-ledger/command/generateTrialBalance.generated.ts +6 -0
  88. package/src/modules/finance-ledger/command/generateTrialBalance.test.ts +439 -0
  89. package/src/modules/finance-ledger/command/generateTrialBalance.ts +268 -0
  90. package/src/modules/finance-ledger/command/initiatePeriodClose.generated.ts +6 -0
  91. package/src/modules/finance-ledger/command/initiatePeriodClose.test.ts +153 -0
  92. package/src/modules/finance-ledger/command/initiatePeriodClose.ts +84 -0
  93. package/src/modules/finance-ledger/command/openForAdvanceEntry.generated.ts +6 -0
  94. package/src/modules/finance-ledger/command/openForAdvanceEntry.test.ts +87 -0
  95. package/src/modules/finance-ledger/command/openForAdvanceEntry.ts +44 -0
  96. package/src/modules/finance-ledger/command/openPeriod.generated.ts +6 -0
  97. package/src/modules/finance-ledger/command/openPeriod.test.ts +90 -0
  98. package/src/modules/finance-ledger/command/openPeriod.ts +44 -0
  99. package/src/modules/finance-ledger/command/permanentlyClosePeriod.generated.ts +6 -0
  100. package/src/modules/finance-ledger/command/permanentlyClosePeriod.test.ts +87 -0
  101. package/src/modules/finance-ledger/command/permanentlyClosePeriod.ts +48 -0
  102. package/src/modules/finance-ledger/command/postAdjustingEntries.generated.ts +6 -0
  103. package/src/modules/finance-ledger/command/postAdjustingEntries.test.ts +392 -0
  104. package/src/modules/finance-ledger/command/postAdjustingEntries.ts +156 -0
  105. package/src/modules/finance-ledger/command/postJournalEntry.generated.ts +6 -0
  106. package/src/modules/finance-ledger/command/postJournalEntry.test.ts +346 -0
  107. package/src/modules/finance-ledger/command/postJournalEntry.ts +160 -0
  108. package/src/modules/finance-ledger/command/processInventoryHandoff.generated.ts +6 -0
  109. package/src/modules/finance-ledger/command/processInventoryHandoff.test.ts +211 -0
  110. package/src/modules/finance-ledger/command/processInventoryHandoff.ts +133 -0
  111. package/src/modules/finance-ledger/command/processManufacturingHandoff.generated.ts +6 -0
  112. package/src/modules/finance-ledger/command/processManufacturingHandoff.test.ts +221 -0
  113. package/src/modules/finance-ledger/command/processManufacturingHandoff.ts +133 -0
  114. package/src/modules/finance-ledger/command/processPurchaseHandoff.generated.ts +6 -0
  115. package/src/modules/finance-ledger/command/processPurchaseHandoff.test.ts +222 -0
  116. package/src/modules/finance-ledger/command/processPurchaseHandoff.ts +133 -0
  117. package/src/modules/finance-ledger/command/processSalesHandoff.generated.ts +6 -0
  118. package/src/modules/finance-ledger/command/processSalesHandoff.test.ts +257 -0
  119. package/src/modules/finance-ledger/command/processSalesHandoff.ts +135 -0
  120. package/src/modules/finance-ledger/command/regenerateFinancialStatement.generated.ts +6 -0
  121. package/src/modules/finance-ledger/command/regenerateFinancialStatement.test.ts +129 -0
  122. package/src/modules/finance-ledger/command/regenerateFinancialStatement.ts +186 -0
  123. package/src/modules/finance-ledger/command/removeJournalLine.generated.ts +6 -0
  124. package/src/modules/finance-ledger/command/removeJournalLine.test.ts +65 -0
  125. package/src/modules/finance-ledger/command/removeJournalLine.ts +39 -0
  126. package/src/modules/finance-ledger/command/reopenPeriod.generated.ts +6 -0
  127. package/src/modules/finance-ledger/command/reopenPeriod.test.ts +87 -0
  128. package/src/modules/finance-ledger/command/reopenPeriod.ts +44 -0
  129. package/src/modules/finance-ledger/command/reverseJournalEntry.generated.ts +6 -0
  130. package/src/modules/finance-ledger/command/reverseJournalEntry.test.ts +337 -0
  131. package/src/modules/finance-ledger/command/reverseJournalEntry.ts +140 -0
  132. package/src/modules/finance-ledger/command/revertSoftLock.generated.ts +6 -0
  133. package/src/modules/finance-ledger/command/revertSoftLock.test.ts +96 -0
  134. package/src/modules/finance-ledger/command/revertSoftLock.ts +67 -0
  135. package/src/modules/finance-ledger/command/updateFiscalYear.generated.ts +6 -0
  136. package/src/modules/finance-ledger/command/updateFiscalYear.test.ts +138 -0
  137. package/src/modules/finance-ledger/command/updateFiscalYear.ts +85 -0
  138. package/src/modules/finance-ledger/command/updateJournalEntry.generated.ts +6 -0
  139. package/src/modules/finance-ledger/command/updateJournalEntry.test.ts +195 -0
  140. package/src/modules/finance-ledger/command/updateJournalEntry.ts +86 -0
  141. package/src/modules/finance-ledger/command/updateJournalLine.generated.ts +6 -0
  142. package/src/modules/finance-ledger/command/updateJournalLine.test.ts +385 -0
  143. package/src/modules/finance-ledger/command/updateJournalLine.ts +155 -0
  144. package/src/modules/finance-ledger/command/verifySubledgerTransfers.generated.ts +6 -0
  145. package/src/modules/finance-ledger/command/verifySubledgerTransfers.test.ts +201 -0
  146. package/src/modules/finance-ledger/command/verifySubledgerTransfers.ts +113 -0
  147. package/src/modules/finance-ledger/command/verifyTrialBalance.generated.ts +6 -0
  148. package/src/modules/finance-ledger/command/verifyTrialBalance.test.ts +136 -0
  149. package/src/modules/finance-ledger/command/verifyTrialBalance.ts +97 -0
  150. package/src/modules/finance-ledger/db/.gitkeep +0 -0
  151. package/src/modules/finance-ledger/db/accountingPeriod.ts +58 -0
  152. package/src/modules/finance-ledger/db/financialStatement.ts +92 -0
  153. package/src/modules/finance-ledger/db/financialStatementLineItem.ts +76 -0
  154. package/src/modules/finance-ledger/db/fiscalYear.ts +41 -0
  155. package/src/modules/finance-ledger/db/journalEntry.ts +101 -0
  156. package/src/modules/finance-ledger/db/journalLine.ts +64 -0
  157. package/src/modules/finance-ledger/db/periodClose.ts +97 -0
  158. package/src/modules/finance-ledger/db/trialBalance.ts +63 -0
  159. package/src/modules/finance-ledger/db/trialBalanceLine.ts +63 -0
  160. package/src/modules/finance-ledger/docs/commands/AddJournalLine.md +74 -0
  161. package/src/modules/finance-ledger/docs/commands/ApproveAndLockPeriod.md +53 -0
  162. package/src/modules/finance-ledger/docs/commands/BeginClose.md +47 -0
  163. package/src/modules/finance-ledger/docs/commands/ClosePeriod.md +45 -0
  164. package/src/modules/finance-ledger/docs/commands/CreateAccountingPeriod.md +69 -0
  165. package/src/modules/finance-ledger/docs/commands/CreateFiscalYear.md +56 -0
  166. package/src/modules/finance-ledger/docs/commands/CreateJournalEntry.md +63 -0
  167. package/src/modules/finance-ledger/docs/commands/DeleteAccountingPeriod.md +46 -0
  168. package/src/modules/finance-ledger/docs/commands/DeleteFiscalYear.md +40 -0
  169. package/src/modules/finance-ledger/docs/commands/DeleteJournalEntry.md +44 -0
  170. package/src/modules/finance-ledger/docs/commands/ExecuteYearEndClose.md +81 -0
  171. package/src/modules/finance-ledger/docs/commands/FinalCloseAndLockPeriod.md +49 -0
  172. package/src/modules/finance-ledger/docs/commands/FinalizeFinancialStatement.md +43 -0
  173. package/src/modules/finance-ledger/docs/commands/GenerateFinancialStatement.md +86 -0
  174. package/src/modules/finance-ledger/docs/commands/GeneratePreliminaryStatements.md +53 -0
  175. package/src/modules/finance-ledger/docs/commands/GenerateTrialBalance.md +75 -0
  176. package/src/modules/finance-ledger/docs/commands/InitiatePeriodClose.md +58 -0
  177. package/src/modules/finance-ledger/docs/commands/OpenForAdvanceEntry.md +44 -0
  178. package/src/modules/finance-ledger/docs/commands/OpenPeriod.md +45 -0
  179. package/src/modules/finance-ledger/docs/commands/PermanentlyClosePeriod.md +45 -0
  180. package/src/modules/finance-ledger/docs/commands/PostAdjustingEntries.md +61 -0
  181. package/src/modules/finance-ledger/docs/commands/PostJournalEntry.md +81 -0
  182. package/src/modules/finance-ledger/docs/commands/ProcessInventoryHandoff.md +72 -0
  183. package/src/modules/finance-ledger/docs/commands/ProcessManufacturingHandoff.md +68 -0
  184. package/src/modules/finance-ledger/docs/commands/ProcessPurchaseHandoff.md +68 -0
  185. package/src/modules/finance-ledger/docs/commands/ProcessSalesHandoff.md +71 -0
  186. package/src/modules/finance-ledger/docs/commands/RegenerateFinancialStatement.md +60 -0
  187. package/src/modules/finance-ledger/docs/commands/RemoveJournalLine.md +42 -0
  188. package/src/modules/finance-ledger/docs/commands/ReopenPeriod.md +45 -0
  189. package/src/modules/finance-ledger/docs/commands/ReverseJournalEntry.md +62 -0
  190. package/src/modules/finance-ledger/docs/commands/RevertSoftLock.md +49 -0
  191. package/src/modules/finance-ledger/docs/commands/UpdateFiscalYear.md +60 -0
  192. package/src/modules/finance-ledger/docs/commands/UpdateJournalEntry.md +50 -0
  193. package/src/modules/finance-ledger/docs/commands/UpdateJournalLine.md +61 -0
  194. package/src/modules/finance-ledger/docs/commands/VerifySubledgerTransfers.md +59 -0
  195. package/src/modules/finance-ledger/docs/commands/VerifyTrialBalance.md +53 -0
  196. package/src/modules/finance-ledger/docs/features/accounting-period-management.md +110 -0
  197. package/src/modules/finance-ledger/docs/features/financial-statement-generation.md +115 -0
  198. package/src/modules/finance-ledger/docs/features/journal-entry-management.md +138 -0
  199. package/src/modules/finance-ledger/docs/features/period-end-close.md +102 -0
  200. package/src/modules/finance-ledger/docs/features/subledger-integration.md +141 -0
  201. package/src/modules/finance-ledger/docs/features/trial-balance.md +99 -0
  202. package/src/modules/finance-ledger/docs/features/year-end-close.md +84 -0
  203. package/src/modules/finance-ledger/docs/models/AccountingPeriod.md +71 -0
  204. package/src/modules/finance-ledger/docs/models/FinancialStatement.md +76 -0
  205. package/src/modules/finance-ledger/docs/models/FinancialStatementLineItem.md +41 -0
  206. package/src/modules/finance-ledger/docs/models/FiscalYear.md +41 -0
  207. package/src/modules/finance-ledger/docs/models/JournalEntry.md +80 -0
  208. package/src/modules/finance-ledger/docs/models/JournalLine.md +47 -0
  209. package/src/modules/finance-ledger/docs/models/PeriodClose.md +83 -0
  210. package/src/modules/finance-ledger/docs/models/TrialBalance.md +56 -0
  211. package/src/modules/finance-ledger/docs/models/TrialBalanceLine.md +37 -0
  212. package/src/modules/finance-ledger/docs/queries/GetAccountingPeriod.md +35 -0
  213. package/src/modules/finance-ledger/docs/queries/GetFinancialStatement.md +38 -0
  214. package/src/modules/finance-ledger/docs/queries/GetFiscalYear.md +35 -0
  215. package/src/modules/finance-ledger/docs/queries/GetJournalEntry.md +37 -0
  216. package/src/modules/finance-ledger/docs/queries/GetPeriodByDate.md +38 -0
  217. package/src/modules/finance-ledger/docs/queries/GetPeriodClose.md +36 -0
  218. package/src/modules/finance-ledger/docs/queries/GetSubledgerTransferStatus.md +45 -0
  219. package/src/modules/finance-ledger/docs/queries/GetTrialBalance.md +38 -0
  220. package/src/modules/finance-ledger/docs/queries/ListAccountingPeriods.md +46 -0
  221. package/src/modules/finance-ledger/docs/queries/ListFinancialStatements.md +46 -0
  222. package/src/modules/finance-ledger/docs/queries/ListFiscalYears.md +42 -0
  223. package/src/modules/finance-ledger/docs/queries/ListJournalEntries.md +48 -0
  224. package/src/modules/finance-ledger/docs/queries/ListPeriodCloses.md +46 -0
  225. package/src/modules/finance-ledger/docs/queries/ListTrialBalances.md +51 -0
  226. package/src/modules/finance-ledger/executor/.gitkeep +0 -0
  227. package/src/modules/finance-ledger/generated/enums.ts +109 -0
  228. package/src/modules/finance-ledger/generated/kysely-tailordb.ts +202 -0
  229. package/src/modules/finance-ledger/index.ts +2 -0
  230. package/src/modules/finance-ledger/lib/_db_deps.ts +56 -0
  231. package/src/modules/finance-ledger/lib/errors.generated.ts +332 -0
  232. package/src/modules/finance-ledger/lib/permissions.generated.ts +41 -0
  233. package/src/modules/finance-ledger/lib/types.ts +66 -0
  234. package/src/modules/finance-ledger/module.ts +262 -0
  235. package/src/modules/finance-ledger/package.json +26 -0
  236. package/src/modules/finance-ledger/permissions.ts +3 -0
  237. package/src/modules/finance-ledger/query/.gitkeep +0 -0
  238. package/src/modules/finance-ledger/query/getAccountingPeriod.generated.ts +5 -0
  239. package/src/modules/finance-ledger/query/getAccountingPeriod.test.ts +31 -0
  240. package/src/modules/finance-ledger/query/getAccountingPeriod.ts +21 -0
  241. package/src/modules/finance-ledger/query/getFinancialStatement.generated.ts +5 -0
  242. package/src/modules/finance-ledger/query/getFinancialStatement.test.ts +35 -0
  243. package/src/modules/finance-ledger/query/getFinancialStatement.ts +29 -0
  244. package/src/modules/finance-ledger/query/getFiscalYear.generated.ts +5 -0
  245. package/src/modules/finance-ledger/query/getFiscalYear.test.ts +31 -0
  246. package/src/modules/finance-ledger/query/getFiscalYear.ts +21 -0
  247. package/src/modules/finance-ledger/query/getJournalEntry.generated.ts +5 -0
  248. package/src/modules/finance-ledger/query/getJournalEntry.test.ts +35 -0
  249. package/src/modules/finance-ledger/query/getJournalEntry.ts +29 -0
  250. package/src/modules/finance-ledger/query/getPeriodByDate.generated.ts +5 -0
  251. package/src/modules/finance-ledger/query/getPeriodByDate.test.ts +53 -0
  252. package/src/modules/finance-ledger/query/getPeriodByDate.ts +27 -0
  253. package/src/modules/finance-ledger/query/getPeriodClose.generated.ts +5 -0
  254. package/src/modules/finance-ledger/query/getPeriodClose.test.ts +31 -0
  255. package/src/modules/finance-ledger/query/getPeriodClose.ts +21 -0
  256. package/src/modules/finance-ledger/query/getSubledgerTransferStatus.generated.ts +5 -0
  257. package/src/modules/finance-ledger/query/getSubledgerTransferStatus.test.ts +101 -0
  258. package/src/modules/finance-ledger/query/getSubledgerTransferStatus.ts +68 -0
  259. package/src/modules/finance-ledger/query/getTrialBalance.generated.ts +5 -0
  260. package/src/modules/finance-ledger/query/getTrialBalance.test.ts +33 -0
  261. package/src/modules/finance-ledger/query/getTrialBalance.ts +30 -0
  262. package/src/modules/finance-ledger/query/listAccountingPeriods.generated.ts +5 -0
  263. package/src/modules/finance-ledger/query/listAccountingPeriods.test.ts +81 -0
  264. package/src/modules/finance-ledger/query/listAccountingPeriods.ts +61 -0
  265. package/src/modules/finance-ledger/query/listFinancialStatements.generated.ts +5 -0
  266. package/src/modules/finance-ledger/query/listFinancialStatements.test.ts +76 -0
  267. package/src/modules/finance-ledger/query/listFinancialStatements.ts +62 -0
  268. package/src/modules/finance-ledger/query/listFiscalYears.generated.ts +5 -0
  269. package/src/modules/finance-ledger/query/listFiscalYears.test.ts +63 -0
  270. package/src/modules/finance-ledger/query/listFiscalYears.ts +45 -0
  271. package/src/modules/finance-ledger/query/listJournalEntries.generated.ts +5 -0
  272. package/src/modules/finance-ledger/query/listJournalEntries.test.ts +91 -0
  273. package/src/modules/finance-ledger/query/listJournalEntries.ts +64 -0
  274. package/src/modules/finance-ledger/query/listPeriodCloses.generated.ts +5 -0
  275. package/src/modules/finance-ledger/query/listPeriodCloses.test.ts +63 -0
  276. package/src/modules/finance-ledger/query/listPeriodCloses.ts +64 -0
  277. package/src/modules/finance-ledger/query/listTrialBalances.generated.ts +5 -0
  278. package/src/modules/finance-ledger/query/listTrialBalances.test.ts +78 -0
  279. package/src/modules/finance-ledger/query/listTrialBalances.ts +56 -0
  280. package/src/modules/finance-ledger/seed/index.ts +19 -0
  281. package/src/modules/finance-ledger/tailor.config.ts +13 -0
  282. package/src/modules/finance-ledger/tailor.d.ts +13 -0
  283. package/src/modules/finance-ledger/testing/commandTestUtils.ts +35 -0
  284. package/src/modules/finance-ledger/testing/fixtures.ts +382 -0
  285. package/src/modules/finance-ledger/tsconfig.json +16 -0
  286. package/src/progress/schema.test.ts +161 -0
  287. package/src/progress/schema.ts +316 -0
  288. package/templates/scaffold/app/backend/package.json +1 -3
  289. package/templates/scaffold/app/backend/vitest.config.ts +4 -21
  290. package/src/generator/generate-stubs.ts +0 -35
@@ -0,0 +1,337 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ JournalEntryNotFoundError,
6
+ InvalidStatusForReversalError,
7
+ AlreadyReversedError,
8
+ InvalidPeriodStatusError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ baseJournalEntry,
12
+ postedJournalEntry,
13
+ reversedJournalEntry,
14
+ baseJournalLine,
15
+ baseJournalLine2,
16
+ baseAccountingPeriod,
17
+ futureEnterablePeriod,
18
+ neverOpenedPeriod,
19
+ closedPeriod,
20
+ permanentlyClosedPeriod,
21
+ } from "../testing/fixtures";
22
+ import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
23
+ import { run } from "./reverseJournalEntry";
24
+
25
+ const postedLines = [
26
+ { ...baseJournalLine, journalEntryId: postedJournalEntry.id },
27
+ { ...baseJournalLine2, journalEntryId: postedJournalEntry.id },
28
+ ];
29
+
30
+ describe("reverseJournalEntry", () => {
31
+ it("returns error when journal entry does not exist", async () => {
32
+ const { db, spies } = createMockDb<Transaction>();
33
+ spies.select.mockReturnValueOnce(undefined);
34
+
35
+ const result = await run(db, { id: "nonexistent" }, commandCtx);
36
+
37
+ expectErr(result, JournalEntryNotFoundError);
38
+ });
39
+
40
+ it("returns error when journal entry is in DRAFT status", async () => {
41
+ const { db, spies } = createMockDb<Transaction>();
42
+ spies.select.mockReturnValueOnce(baseJournalEntry); // DRAFT entry
43
+
44
+ const result = await run(db, { id: baseJournalEntry.id }, commandCtx);
45
+
46
+ expectErr(result, InvalidStatusForReversalError);
47
+ });
48
+
49
+ it("returns error when journal entry is already REVERSED", async () => {
50
+ const { db, spies } = createMockDb<Transaction>();
51
+ spies.select.mockReturnValueOnce(reversedJournalEntry);
52
+
53
+ const result = await run(db, { id: reversedJournalEntry.id }, commandCtx);
54
+
55
+ expectErr(result, AlreadyReversedError);
56
+ });
57
+
58
+ it("returns error when reversal period is in NEVER_OPENED status", async () => {
59
+ const { db, spies } = createMockDb<Transaction>();
60
+ spies.select
61
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
62
+ .mockImplementationOnce(() => postedLines) // journal lines
63
+ .mockReturnValueOnce(neverOpenedPeriod); // period lookup
64
+
65
+ const result = await run(
66
+ db,
67
+ { id: postedJournalEntry.id, reversalPeriodId: neverOpenedPeriod.id },
68
+ commandCtx,
69
+ );
70
+
71
+ expectErr(result, InvalidPeriodStatusError);
72
+ });
73
+
74
+ it("returns error when reversal period is in CLOSED status", async () => {
75
+ const { db, spies } = createMockDb<Transaction>();
76
+ spies.select
77
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
78
+ .mockImplementationOnce(() => postedLines) // journal lines
79
+ .mockReturnValueOnce(closedPeriod); // period lookup
80
+
81
+ const result = await run(
82
+ db,
83
+ { id: postedJournalEntry.id, reversalPeriodId: closedPeriod.id },
84
+ commandCtx,
85
+ );
86
+
87
+ expectErr(result, InvalidPeriodStatusError);
88
+ });
89
+
90
+ it("returns error when reversal period is in PERMANENTLY_CLOSED status", async () => {
91
+ const { db, spies } = createMockDb<Transaction>();
92
+ spies.select
93
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
94
+ .mockImplementationOnce(() => postedLines) // journal lines
95
+ .mockReturnValueOnce(permanentlyClosedPeriod); // period lookup
96
+
97
+ const result = await run(
98
+ db,
99
+ { id: postedJournalEntry.id, reversalPeriodId: permanentlyClosedPeriod.id },
100
+ commandCtx,
101
+ );
102
+
103
+ expectErr(result, InvalidPeriodStatusError);
104
+ });
105
+
106
+ it("creates mirror entry with all debit/credit amounts inverted", async () => {
107
+ const { db, spies } = createMockDb<Transaction>();
108
+ const reversalEntry = {
109
+ ...postedJournalEntry,
110
+ id: "reversal-entry-1",
111
+ status: "POSTED",
112
+ reversalOfId: postedJournalEntry.id,
113
+ };
114
+ const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
115
+
116
+ spies.select
117
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
118
+ .mockImplementationOnce(() => postedLines) // journal lines
119
+ .mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
120
+ spies.insert
121
+ .mockReturnValueOnce(reversalEntry) // insert reversal entry
122
+ .mockImplementationOnce(() => [
123
+ // insert reversal lines
124
+ {
125
+ ...postedLines[0],
126
+ debitAmount: null,
127
+ creditAmount: 1000,
128
+ functionalDebitAmount: null,
129
+ functionalCreditAmount: 1000,
130
+ },
131
+ {
132
+ ...postedLines[1],
133
+ debitAmount: 1000,
134
+ creditAmount: null,
135
+ functionalDebitAmount: 1000,
136
+ functionalCreditAmount: null,
137
+ },
138
+ ]);
139
+ spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
140
+
141
+ const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
142
+
143
+ expectOk(result);
144
+ // Verify values were called with inverted amounts
145
+ expect(spies.values).toHaveBeenCalled();
146
+ });
147
+
148
+ it("reversal entry references the original entry", async () => {
149
+ const { db, spies } = createMockDb<Transaction>();
150
+ const reversalEntry = {
151
+ ...postedJournalEntry,
152
+ id: "reversal-entry-1",
153
+ status: "POSTED",
154
+ reversalOfId: postedJournalEntry.id,
155
+ };
156
+ const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
157
+
158
+ spies.select
159
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
160
+ .mockImplementationOnce(() => postedLines) // journal lines
161
+ .mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
162
+ spies.insert
163
+ .mockReturnValueOnce(reversalEntry) // insert reversal entry
164
+ .mockImplementationOnce(() => []); // insert reversal lines
165
+ spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
166
+
167
+ const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
168
+
169
+ const value = expectOk(result);
170
+ expect(value.reversalEntry.reversalOfId).toBe(postedJournalEntry.id);
171
+ });
172
+
173
+ it("reversal entry is automatically posted", async () => {
174
+ const { db, spies } = createMockDb<Transaction>();
175
+ const reversalEntry = {
176
+ ...postedJournalEntry,
177
+ id: "reversal-entry-1",
178
+ status: "POSTED",
179
+ reversalOfId: postedJournalEntry.id,
180
+ postedAt: new Date(),
181
+ };
182
+ const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
183
+
184
+ spies.select
185
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
186
+ .mockImplementationOnce(() => postedLines) // journal lines
187
+ .mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
188
+ spies.insert
189
+ .mockReturnValueOnce(reversalEntry) // insert reversal entry
190
+ .mockImplementationOnce(() => []); // insert reversal lines
191
+ spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
192
+
193
+ const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
194
+
195
+ const value = expectOk(result);
196
+ expect(value.reversalEntry.status).toBe("POSTED");
197
+ expect(value.reversalEntry.postedAt).toBeDefined();
198
+ });
199
+
200
+ it("original entry transitions from POSTED to REVERSED", async () => {
201
+ const { db, spies } = createMockDb<Transaction>();
202
+ const reversalEntry = {
203
+ ...postedJournalEntry,
204
+ id: "reversal-entry-1",
205
+ status: "POSTED",
206
+ reversalOfId: postedJournalEntry.id,
207
+ };
208
+ const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
209
+
210
+ spies.select
211
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
212
+ .mockImplementationOnce(() => postedLines) // journal lines
213
+ .mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
214
+ spies.insert
215
+ .mockReturnValueOnce(reversalEntry) // insert reversal entry
216
+ .mockImplementationOnce(() => []); // insert reversal lines
217
+ spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
218
+
219
+ const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
220
+
221
+ const value = expectOk(result);
222
+ expect(value.originalEntry.status).toBe("REVERSED");
223
+ });
224
+
225
+ it("reversal entry targets the specified accounting period", async () => {
226
+ const { db, spies } = createMockDb<Transaction>();
227
+ const reversalEntry = {
228
+ ...postedJournalEntry,
229
+ id: "reversal-entry-1",
230
+ status: "POSTED",
231
+ reversalOfId: postedJournalEntry.id,
232
+ accountingPeriodId: futureEnterablePeriod.id,
233
+ };
234
+ const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
235
+
236
+ spies.select
237
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
238
+ .mockImplementationOnce(() => postedLines) // journal lines
239
+ .mockReturnValueOnce(futureEnterablePeriod); // period lookup (FUTURE_ENTERABLE)
240
+ spies.insert
241
+ .mockReturnValueOnce(reversalEntry) // insert reversal entry
242
+ .mockImplementationOnce(() => []); // insert reversal lines
243
+ spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
244
+
245
+ const result = await run(
246
+ db,
247
+ { id: postedJournalEntry.id, reversalPeriodId: futureEnterablePeriod.id },
248
+ commandCtx,
249
+ );
250
+
251
+ const value = expectOk(result);
252
+ expect(value.reversalEntry.accountingPeriodId).toBe(futureEnterablePeriod.id);
253
+ });
254
+
255
+ it("multi-currency lines are reversed with same currency and exchange rate", async () => {
256
+ const { db, spies } = createMockDb<Transaction>();
257
+ const multiCurrencyLines = [
258
+ {
259
+ ...postedLines[0],
260
+ currencyCode: "currency-1",
261
+ exchangeRate: 1.5,
262
+ debitAmount: 1500,
263
+ functionalDebitAmount: 1000,
264
+ },
265
+ {
266
+ ...postedLines[1],
267
+ currencyCode: "currency-1",
268
+ exchangeRate: 1.5,
269
+ creditAmount: 1500,
270
+ functionalCreditAmount: 1000,
271
+ },
272
+ ];
273
+ const reversalEntry = {
274
+ ...postedJournalEntry,
275
+ id: "reversal-entry-1",
276
+ status: "POSTED",
277
+ reversalOfId: postedJournalEntry.id,
278
+ };
279
+ const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
280
+
281
+ spies.select
282
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
283
+ .mockImplementationOnce(() => multiCurrencyLines) // journal lines
284
+ .mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
285
+ spies.insert
286
+ .mockReturnValueOnce(reversalEntry) // insert reversal entry
287
+ .mockImplementationOnce(() => []); // insert reversal lines
288
+ spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
289
+
290
+ const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
291
+
292
+ expectOk(result);
293
+ // Verify that values() was called with reversed amounts preserving currency info
294
+ expect(spies.values).toHaveBeenCalled();
295
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
296
+ const insertedLines = spies.values.mock.calls[1]?.[0];
297
+ if (insertedLines) {
298
+ for (const line of insertedLines as { currencyCode: string; exchangeRate: number }[]) {
299
+ expect(line.currencyCode).toBe("currency-1");
300
+ expect(line.exchangeRate).toBe(1.5);
301
+ }
302
+ }
303
+ });
304
+
305
+ it("emits audit event recording status transition, reversal entry reference, and acting user", async () => {
306
+ const { db, spies } = createMockDb<Transaction>();
307
+ const reversalEntry = {
308
+ ...postedJournalEntry,
309
+ id: "reversal-entry-1",
310
+ status: "POSTED",
311
+ reversalOfId: postedJournalEntry.id,
312
+ };
313
+ const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
314
+
315
+ spies.select
316
+ .mockReturnValueOnce(postedJournalEntry) // entry lookup
317
+ .mockImplementationOnce(() => postedLines) // journal lines
318
+ .mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
319
+ spies.insert
320
+ .mockReturnValueOnce(reversalEntry) // insert reversal entry
321
+ .mockImplementationOnce(() => []); // insert reversal lines
322
+ spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
323
+
324
+ const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
325
+
326
+ const value = expectOk(result);
327
+ expect(value.auditTrail).toBeDefined();
328
+ expect(value.auditTrail).toContainEqual(
329
+ expect.objectContaining({
330
+ type: "STATUS_TRANSITION",
331
+ from: "POSTED",
332
+ to: "REVERSED",
333
+ actorId: commandCtx.actorId,
334
+ }),
335
+ );
336
+ });
337
+ });
@@ -0,0 +1,140 @@
1
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
2
+ import type { Transaction } from "../generated/kysely-tailordb";
3
+ import {
4
+ JournalEntryNotFoundError,
5
+ InvalidStatusForReversalError,
6
+ AlreadyReversedError,
7
+ InvalidPeriodStatusError,
8
+ } from "../lib/errors.generated";
9
+
10
+ export interface ReverseJournalEntryInput {
11
+ id: string;
12
+ reversalPeriodId?: string;
13
+ }
14
+
15
+ interface AuditEntry {
16
+ type: string;
17
+ from?: string;
18
+ to?: string;
19
+ actorId?: string;
20
+ reversalEntryId?: string;
21
+ }
22
+
23
+ const VALID_PERIOD_STATUSES = ["OPEN", "FUTURE_ENTERABLE"];
24
+
25
+ /**
26
+ * Function: reverseJournalEntry
27
+ *
28
+ * Creates a mirror journal entry with all debit and credit amounts inverted
29
+ * from the original posted entry, and automatically posts the reversal entry.
30
+ * The original entry transitions from POSTED to REVERSED status.
31
+ */
32
+ export async function run(db: Transaction, input: ReverseJournalEntryInput, ctx: CommandContext) {
33
+ const { id, reversalPeriodId } = input;
34
+ const auditTrail: AuditEntry[] = [];
35
+
36
+ // 1. Find journal entry
37
+ const journalEntry = await db
38
+ .selectFrom("JournalEntry")
39
+ .selectAll()
40
+ .where("id", "=", id)
41
+ .forUpdate()
42
+ .executeTakeFirst();
43
+
44
+ if (!journalEntry) {
45
+ return err(new JournalEntryNotFoundError(id));
46
+ }
47
+
48
+ // 2. Validate status is POSTED
49
+ if (journalEntry.status === "REVERSED") {
50
+ return err(new AlreadyReversedError(id));
51
+ }
52
+
53
+ if (journalEntry.status !== "POSTED") {
54
+ return err(new InvalidStatusForReversalError(id));
55
+ }
56
+
57
+ // 3. Fetch original journal lines
58
+ const originalLines = await db
59
+ .selectFrom("JournalLine")
60
+ .selectAll()
61
+ .where("journalEntryId", "=", id)
62
+ .execute();
63
+
64
+ // 4. Determine target period and validate
65
+ const targetPeriodId = reversalPeriodId ?? journalEntry.accountingPeriodId;
66
+ const period = await db
67
+ .selectFrom("AccountingPeriod")
68
+ .selectAll()
69
+ .where("id", "=", targetPeriodId)
70
+ .executeTakeFirst();
71
+
72
+ if (!period || !VALID_PERIOD_STATUSES.includes(period.status)) {
73
+ return err(new InvalidPeriodStatusError(targetPeriodId));
74
+ }
75
+
76
+ // 5. Create reversal journal entry (auto-posted)
77
+ const now = new Date();
78
+ const reversalEntry = await db
79
+ .insertInto("JournalEntry")
80
+ .values({
81
+ companyId: journalEntry.companyId,
82
+ accountingPeriodId: targetPeriodId,
83
+ entryDate: now,
84
+ journalType: journalEntry.journalType,
85
+ referenceNumber: `REV-${journalEntry.referenceNumber}`,
86
+ status: "POSTED",
87
+ description: `Reversal of ${journalEntry.referenceNumber}`,
88
+ sourceDocumentReference: journalEntry.sourceDocumentReference,
89
+ adjustingEntryType: journalEntry.adjustingEntryType,
90
+ reversalOfId: journalEntry.id,
91
+ postedAt: now,
92
+ createdAt: now,
93
+ updatedAt: null,
94
+ })
95
+ .returningAll()
96
+ .executeTakeFirst();
97
+
98
+ // 6. Create mirror journal lines with inverted amounts
99
+ const reversalLines = originalLines.map((line) => ({
100
+ journalEntryId: reversalEntry!.id,
101
+ accountId: line.accountId,
102
+ debitAmount: line.creditAmount,
103
+ creditAmount: line.debitAmount,
104
+ description: line.description,
105
+ currencyCode: line.currencyCode,
106
+ exchangeRate: line.exchangeRate,
107
+ functionalDebitAmount: line.functionalCreditAmount,
108
+ functionalCreditAmount: line.functionalDebitAmount,
109
+ createdAt: now,
110
+ updatedAt: null,
111
+ }));
112
+
113
+ await db.insertInto("JournalLine").values(reversalLines).returningAll().execute();
114
+
115
+ // 7. Transition original entry to REVERSED
116
+ const updatedOriginal = await db
117
+ .updateTable("JournalEntry")
118
+ .set({
119
+ status: "REVERSED",
120
+ updatedAt: now,
121
+ })
122
+ .where("id", "=", id)
123
+ .returningAll()
124
+ .executeTakeFirst();
125
+
126
+ // 8. Record audit events
127
+ auditTrail.push({
128
+ type: "STATUS_TRANSITION",
129
+ from: "POSTED",
130
+ to: "REVERSED",
131
+ actorId: ctx.actorId,
132
+ reversalEntryId: reversalEntry!.id,
133
+ });
134
+
135
+ return ok({
136
+ reversalEntry: reversalEntry!,
137
+ originalEntry: updatedOriginal!,
138
+ auditTrail,
139
+ });
140
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
3
+ import { permissions } from "../lib/permissions.generated";
4
+ import { run } from "./revertSoftLock";
5
+
6
+ export const revertSoftLock = defineCommand(permissions.revertSoftLock, run);
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ PeriodCloseNotFoundError,
6
+ InvalidStatusTransitionError,
7
+ PeriodPermanentlyClosedError,
8
+ } from "../lib/errors.generated";
9
+ import {
10
+ softLockedPeriodClose,
11
+ inProgressPeriodClose,
12
+ underReviewPeriodClose,
13
+ permanentlyClosedPeriodClose,
14
+ basePeriodClose,
15
+ } from "../testing/fixtures";
16
+ import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
17
+ import { run } from "./revertSoftLock";
18
+
19
+ describe("revertSoftLock", () => {
20
+ it("returns error when PeriodClose record does not exist", async () => {
21
+ const { db, spies } = createMockDb<Transaction>();
22
+ spies.select.mockReturnValueOnce(undefined);
23
+
24
+ const result = await run(db, { id: "non-existent" }, commandCtx);
25
+
26
+ expectErr(result, PeriodCloseNotFoundError);
27
+ });
28
+
29
+ it("returns error when PeriodClose is in NOT_STARTED status", async () => {
30
+ const { db, spies } = createMockDb<Transaction>();
31
+ spies.select.mockReturnValueOnce(basePeriodClose);
32
+
33
+ const result = await run(db, { id: basePeriodClose.id }, commandCtx);
34
+
35
+ expectErr(result, InvalidStatusTransitionError);
36
+ });
37
+
38
+ it("returns error when PeriodClose is in IN_PROGRESS status", async () => {
39
+ const { db, spies } = createMockDb<Transaction>();
40
+ spies.select.mockReturnValueOnce(inProgressPeriodClose);
41
+
42
+ const result = await run(db, { id: inProgressPeriodClose.id }, commandCtx);
43
+
44
+ expectErr(result, InvalidStatusTransitionError);
45
+ });
46
+
47
+ it("returns error when PeriodClose is in UNDER_REVIEW status", async () => {
48
+ const { db, spies } = createMockDb<Transaction>();
49
+ spies.select.mockReturnValueOnce(underReviewPeriodClose);
50
+
51
+ const result = await run(db, { id: underReviewPeriodClose.id }, commandCtx);
52
+
53
+ expectErr(result, InvalidStatusTransitionError);
54
+ });
55
+
56
+ it("returns error when PeriodClose is PERMANENTLY_CLOSED", async () => {
57
+ const { db, spies } = createMockDb<Transaction>();
58
+ spies.select.mockReturnValueOnce(permanentlyClosedPeriodClose);
59
+
60
+ const result = await run(db, { id: permanentlyClosedPeriodClose.id }, commandCtx);
61
+
62
+ expectErr(result, PeriodPermanentlyClosedError);
63
+ });
64
+
65
+ it("transitions SOFT_LOCKED period close to IN_PROGRESS", async () => {
66
+ const updatedPeriodClose = { ...softLockedPeriodClose, status: "IN_PROGRESS" };
67
+ const { db, spies } = createMockDb<Transaction>();
68
+ spies.select.mockReturnValueOnce(softLockedPeriodClose);
69
+ spies.update.mockReturnValueOnce(updatedPeriodClose);
70
+
71
+ const result = await run(db, { id: softLockedPeriodClose.id }, commandCtx);
72
+
73
+ const value = expectOk(result);
74
+ expect(value.periodClose.status).toBe("IN_PROGRESS");
75
+ });
76
+
77
+ it("emits audit event recording status transition and acting user", async () => {
78
+ const updatedPeriodClose = { ...softLockedPeriodClose, status: "IN_PROGRESS" };
79
+ const { db, spies } = createMockDb<Transaction>();
80
+ spies.select.mockReturnValueOnce(softLockedPeriodClose);
81
+ spies.update.mockReturnValueOnce(updatedPeriodClose);
82
+
83
+ const result = await run(db, { id: softLockedPeriodClose.id }, commandCtx);
84
+
85
+ const value = expectOk(result);
86
+ expect(value.auditTrail).toEqual([
87
+ {
88
+ type: "STATUS_TRANSITION",
89
+ from: "SOFT_LOCKED",
90
+ to: "IN_PROGRESS",
91
+ actorId: commandCtx.actorId,
92
+ },
93
+ ]);
94
+ expect(spies.update).toHaveBeenCalled();
95
+ });
96
+ });
@@ -0,0 +1,67 @@
1
+ import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
2
+ import type { Transaction } from "../generated/kysely-tailordb";
3
+ import {
4
+ PeriodCloseNotFoundError,
5
+ PeriodPermanentlyClosedError,
6
+ InvalidStatusTransitionError,
7
+ } from "../lib/errors.generated";
8
+
9
+ export interface RevertSoftLockInput {
10
+ id: string;
11
+ }
12
+
13
+ /**
14
+ * Function: revertSoftLock
15
+ *
16
+ * Transitions a PeriodClose record from SOFT_LOCKED back to IN_PROGRESS status,
17
+ * allowing the close workflow to be revisited when errors are discovered.
18
+ * Only permitted before permanent close.
19
+ */
20
+ export async function run(db: Transaction, input: RevertSoftLockInput, ctx: CommandContext) {
21
+ const { id } = input;
22
+
23
+ // 1. Find PeriodClose record
24
+ const periodClose = await db
25
+ .selectFrom("PeriodClose")
26
+ .selectAll()
27
+ .where("id", "=", id)
28
+ .forUpdate()
29
+ .executeTakeFirst();
30
+
31
+ if (!periodClose) {
32
+ return err(new PeriodCloseNotFoundError(id));
33
+ }
34
+
35
+ // 2. Reject permanently closed periods
36
+ if (periodClose.status === "PERMANENTLY_CLOSED") {
37
+ return err(new PeriodPermanentlyClosedError(id));
38
+ }
39
+
40
+ // 3. Validate status is SOFT_LOCKED
41
+ if (periodClose.status !== "SOFT_LOCKED") {
42
+ return err(new InvalidStatusTransitionError(id));
43
+ }
44
+
45
+ // 4. Transition back to IN_PROGRESS and record acting user
46
+ const updated = await db
47
+ .updateTable("PeriodClose")
48
+ .set({
49
+ status: "IN_PROGRESS",
50
+ updatedAt: new Date(),
51
+ })
52
+ .where("id", "=", id)
53
+ .returningAll()
54
+ .executeTakeFirstOrThrow();
55
+
56
+ // 5. Emit audit event recording status transition
57
+ const auditTrail = [
58
+ {
59
+ type: "STATUS_TRANSITION",
60
+ from: "SOFT_LOCKED",
61
+ to: "IN_PROGRESS",
62
+ actorId: ctx.actorId,
63
+ },
64
+ ];
65
+
66
+ return ok({ periodClose: updated, auditTrail });
67
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
3
+ import { permissions } from "../lib/permissions.generated";
4
+ import { run } from "./updateFiscalYear";
5
+
6
+ export const updateFiscalYear = defineCommand(permissions.updateFiscalYear, run);