@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,415 @@
1
+ import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
2
+ import type { Transaction } from "../generated/kysely-tailordb";
3
+ import {
4
+ AccountingPeriodNotFoundError,
5
+ NotFinalPeriodError,
6
+ StandardCloseNotCompleteError,
7
+ DuplicateYearEndCloseError,
8
+ RetainedEarningsAccountNotFoundError,
9
+ NewFiscalYearNotDefinedError,
10
+ } from "../lib/errors.generated";
11
+
12
+ export interface ExecuteYearEndCloseInput {
13
+ accountingPeriodId: string;
14
+ }
15
+
16
+ interface AuditEntry {
17
+ type: string;
18
+ fiscalYearId?: string;
19
+ closingEntryId?: string;
20
+ openingEntryId?: string;
21
+ netIncome?: number;
22
+ actorId?: string;
23
+ }
24
+
25
+ const TEMPORARY_CLASSIFICATIONS = ["REVENUE", "EXPENSE"];
26
+ const PERMANENT_CLASSIFICATIONS = ["ASSET", "LIABILITY", "EQUITY"];
27
+ const VALID_CLOSE_STATUSES = ["SOFT_LOCKED", "PERMANENTLY_CLOSED"];
28
+
29
+ /**
30
+ * Function: executeYearEndClose
31
+ *
32
+ * Executes fiscal year-end close on the final period of a fiscal year.
33
+ * Generates a closing journal entry that zeros temporary accounts (REVENUE, EXPENSE)
34
+ * and transfers net income/loss to retained earnings. Then generates opening balance
35
+ * entries for the new fiscal year's permanent accounts.
36
+ */
37
+ export async function run(db: Transaction, input: ExecuteYearEndCloseInput, ctx: CommandContext) {
38
+ const { accountingPeriodId } = input;
39
+ const auditTrail: AuditEntry[] = [];
40
+
41
+ // 1. Find AccountingPeriod
42
+ const period = await db
43
+ .selectFrom("AccountingPeriod")
44
+ .selectAll()
45
+ .where("id", "=", accountingPeriodId)
46
+ .executeTakeFirst();
47
+
48
+ if (!period) {
49
+ return err(new AccountingPeriodNotFoundError(accountingPeriodId));
50
+ }
51
+
52
+ // 2. Find FiscalYear for period
53
+ const fiscalYear = await db
54
+ .selectFrom("FiscalYear")
55
+ .selectAll()
56
+ .where("id", "=", period.fiscalYearId)
57
+ .forUpdate()
58
+ .executeTakeFirst();
59
+
60
+ // 3. Validate this is the final period of the fiscal year
61
+ const periodEnd = new Date(period.endDate).getTime();
62
+ const fyEnd = new Date(fiscalYear!.endDate).getTime();
63
+ if (periodEnd !== fyEnd) {
64
+ return err(new NotFinalPeriodError(accountingPeriodId));
65
+ }
66
+
67
+ // 4. Check PeriodClose exists and is in SOFT_LOCKED or PERMANENTLY_CLOSED status
68
+ const periodClose = await db
69
+ .selectFrom("PeriodClose")
70
+ .selectAll()
71
+ .where("accountingPeriodId", "=", accountingPeriodId)
72
+ .executeTakeFirst();
73
+
74
+ if (!periodClose || !VALID_CLOSE_STATUSES.includes(periodClose.status as string)) {
75
+ return err(new StandardCloseNotCompleteError(accountingPeriodId));
76
+ }
77
+
78
+ // 5. Check fiscal year yearEndCloseExecuted flag
79
+ if (fiscalYear!.yearEndCloseExecuted) {
80
+ return err(new DuplicateYearEndCloseError(fiscalYear!.id));
81
+ }
82
+
83
+ // 6. Find retained earnings account in active CoA
84
+ const _chartOfAccounts = await db
85
+ .selectFrom("ChartOfAccounts")
86
+ .selectAll()
87
+ .where("companyId", "=", period.companyId)
88
+ .where("status", "=", "ACTIVE")
89
+ .executeTakeFirst();
90
+
91
+ const retainedEarningsAccount = await db
92
+ .selectFrom("Account")
93
+ .selectAll()
94
+ .where("classification", "=", "RETAINED_EARNINGS")
95
+ .where("status", "=", "ACTIVE")
96
+ .executeTakeFirst();
97
+
98
+ if (!retainedEarningsAccount) {
99
+ return err(new RetainedEarningsAccountNotFoundError(period.companyId));
100
+ }
101
+
102
+ // 7. Find all posted journal lines for temporary accounts in this fiscal year
103
+ // Get all accounting period IDs for this fiscal year
104
+ const allPeriods = await db
105
+ .selectFrom("AccountingPeriod")
106
+ .select("id")
107
+ .where("fiscalYearId", "=", fiscalYear!.id)
108
+ .execute();
109
+
110
+ const allPeriodIds = allPeriods.map((p: { id: string }) => p.id);
111
+
112
+ // Find all posted journal lines for temporary accounts across ALL periods in the fiscal year
113
+ const journalLines = await db
114
+ .selectFrom("JournalLine")
115
+ .innerJoin("JournalEntry", "JournalEntry.id", "JournalLine.journalEntryId")
116
+ .innerJoin("Account", "Account.id", "JournalLine.accountId")
117
+ .selectAll()
118
+ .where("JournalEntry.accountingPeriodId", "in", allPeriodIds)
119
+ .where("JournalEntry.status", "=", "POSTED")
120
+ .execute();
121
+
122
+ // Filter for temporary account lines
123
+ const temporaryLines = journalLines.filter((line: Record<string, unknown>) =>
124
+ TEMPORARY_CLASSIFICATIONS.includes(line.classification as string),
125
+ );
126
+
127
+ // 8. Calculate net income
128
+ const revenueLines = temporaryLines.filter(
129
+ (line: Record<string, unknown>) => line.classification === "REVENUE",
130
+ );
131
+ const expenseLines = temporaryLines.filter(
132
+ (line: Record<string, unknown>) => line.classification === "EXPENSE",
133
+ );
134
+
135
+ const totalRevenueCredits = revenueLines.reduce(
136
+ (sum: number, line: Record<string, unknown>) =>
137
+ sum + ((line.functionalCreditAmount as number) ?? 0),
138
+ 0,
139
+ );
140
+ const totalRevenueDebits = revenueLines.reduce(
141
+ (sum: number, line: Record<string, unknown>) =>
142
+ sum + ((line.functionalDebitAmount as number) ?? 0),
143
+ 0,
144
+ );
145
+ const totalExpenseDebits = expenseLines.reduce(
146
+ (sum: number, line: Record<string, unknown>) =>
147
+ sum + ((line.functionalDebitAmount as number) ?? 0),
148
+ 0,
149
+ );
150
+ const totalExpenseCredits = expenseLines.reduce(
151
+ (sum: number, line: Record<string, unknown>) =>
152
+ sum + ((line.functionalCreditAmount as number) ?? 0),
153
+ 0,
154
+ );
155
+
156
+ const netIncome =
157
+ totalRevenueCredits - totalRevenueDebits - (totalExpenseDebits - totalExpenseCredits);
158
+
159
+ // 9. Create CLOSING type journal entry
160
+ const now = new Date();
161
+ const closingEntry = await db
162
+ .insertInto("JournalEntry")
163
+ .values({
164
+ companyId: period.companyId,
165
+ accountingPeriodId,
166
+ entryDate: now,
167
+ journalType: "CLOSING",
168
+ referenceNumber: `CLOSE-${fiscalYear!.id}`,
169
+ status: "POSTED",
170
+ description: `Year-end closing entry for ${fiscalYear!.name}`,
171
+ sourceDocumentReference: null,
172
+ adjustingEntryType: null,
173
+ reversalOfId: null,
174
+ postedAt: now,
175
+ createdAt: now,
176
+ updatedAt: null,
177
+ })
178
+ .returningAll()
179
+ .executeTakeFirst();
180
+
181
+ // Create closing journal lines: debit revenue accounts, credit expense accounts, net to retained earnings
182
+ const closingLines: {
183
+ journalEntryId: string;
184
+ accountId: string;
185
+ debitAmount: number | null;
186
+ creditAmount: number | null;
187
+ description: string;
188
+ currencyCode: null;
189
+ exchangeRate: null;
190
+ functionalDebitAmount: number | null;
191
+ functionalCreditAmount: number | null;
192
+ createdAt: Date;
193
+ updatedAt: null;
194
+ }[] = [];
195
+
196
+ // Debit revenue accounts (to zero them out)
197
+ for (const line of revenueLines) {
198
+ closingLines.push({
199
+ journalEntryId: closingEntry!.id,
200
+ accountId: line.accountId,
201
+ debitAmount: line.functionalCreditAmount! ?? 0,
202
+ creditAmount: null,
203
+ description: "Close revenue account",
204
+ currencyCode: null,
205
+ exchangeRate: null,
206
+ functionalDebitAmount: line.functionalCreditAmount! ?? 0,
207
+ functionalCreditAmount: null,
208
+ createdAt: now,
209
+ updatedAt: null,
210
+ });
211
+ }
212
+
213
+ // Credit expense accounts (to zero them out)
214
+ for (const line of expenseLines) {
215
+ closingLines.push({
216
+ journalEntryId: closingEntry!.id,
217
+ accountId: line.accountId,
218
+ debitAmount: null,
219
+ creditAmount: line.functionalDebitAmount! ?? 0,
220
+ description: "Close expense account",
221
+ currencyCode: null,
222
+ exchangeRate: null,
223
+ functionalDebitAmount: null,
224
+ functionalCreditAmount: line.functionalDebitAmount! ?? 0,
225
+ createdAt: now,
226
+ updatedAt: null,
227
+ });
228
+ }
229
+
230
+ // Net income/loss to retained earnings
231
+ if (netIncome >= 0) {
232
+ closingLines.push({
233
+ journalEntryId: closingEntry!.id,
234
+ accountId: retainedEarningsAccount.id,
235
+ debitAmount: null,
236
+ creditAmount: netIncome,
237
+ description: "Transfer net income to retained earnings",
238
+ currencyCode: null,
239
+ exchangeRate: null,
240
+ functionalDebitAmount: null,
241
+ functionalCreditAmount: netIncome,
242
+ createdAt: now,
243
+ updatedAt: null,
244
+ });
245
+ } else {
246
+ closingLines.push({
247
+ journalEntryId: closingEntry!.id,
248
+ accountId: retainedEarningsAccount.id,
249
+ debitAmount: Math.abs(netIncome),
250
+ creditAmount: null,
251
+ description: "Transfer net loss to retained earnings",
252
+ currencyCode: null,
253
+ exchangeRate: null,
254
+ functionalDebitAmount: Math.abs(netIncome),
255
+ functionalCreditAmount: null,
256
+ createdAt: now,
257
+ updatedAt: null,
258
+ });
259
+ }
260
+
261
+ if (closingLines.length > 0) {
262
+ await db.insertInto("JournalLine").values(closingLines).returningAll().execute();
263
+ }
264
+
265
+ // 10. Find next fiscal year
266
+ const nextFiscalYear = await db
267
+ .selectFrom("FiscalYear")
268
+ .selectAll()
269
+ .where("companyId", "=", period.companyId)
270
+ .where("startDate", ">", fiscalYear!.endDate)
271
+ .orderBy("startDate", "asc")
272
+ .limit(1)
273
+ .executeTakeFirst();
274
+
275
+ if (!nextFiscalYear) {
276
+ return err(new NewFiscalYearNotDefinedError(fiscalYear!.id));
277
+ }
278
+
279
+ // 11. Generate OPENING type journal entry for permanent accounts in new fiscal year
280
+ const permanentLines = journalLines.filter((line: Record<string, unknown>) =>
281
+ PERMANENT_CLASSIFICATIONS.includes(line.classification as string),
282
+ );
283
+
284
+ // Find first period of new fiscal year
285
+ const newFirstPeriod = await db
286
+ .selectFrom("AccountingPeriod")
287
+ .selectAll()
288
+ .where("fiscalYearId", "=", nextFiscalYear.id)
289
+ .orderBy("startDate", "asc")
290
+ .limit(1)
291
+ .executeTakeFirst();
292
+
293
+ const openingEntry = await db
294
+ .insertInto("JournalEntry")
295
+ .values({
296
+ companyId: period.companyId,
297
+ accountingPeriodId: newFirstPeriod ? newFirstPeriod.id : nextFiscalYear.id,
298
+ entryDate: now,
299
+ journalType: "OPENING",
300
+ referenceNumber: `OPEN-${nextFiscalYear.id}`,
301
+ status: "POSTED",
302
+ description: `Opening balance entry for ${nextFiscalYear.name}`,
303
+ sourceDocumentReference: null,
304
+ adjustingEntryType: null,
305
+ reversalOfId: null,
306
+ postedAt: now,
307
+ createdAt: now,
308
+ updatedAt: null,
309
+ })
310
+ .returningAll()
311
+ .executeTakeFirst();
312
+
313
+ // Create opening journal lines for permanent accounts
314
+ const openingLines: {
315
+ journalEntryId: string;
316
+ accountId: string;
317
+ debitAmount: number | null;
318
+ creditAmount: number | null;
319
+ description: string;
320
+ currencyCode: null;
321
+ exchangeRate: null;
322
+ functionalDebitAmount: number | null;
323
+ functionalCreditAmount: number | null;
324
+ createdAt: Date;
325
+ updatedAt: null;
326
+ }[] = [];
327
+ for (const line of permanentLines) {
328
+ openingLines.push({
329
+ journalEntryId: openingEntry!.id,
330
+ accountId: line.accountId,
331
+ debitAmount: line.functionalDebitAmount! ?? null,
332
+ creditAmount: line.functionalCreditAmount! ?? null,
333
+ description: "Opening balance",
334
+ currencyCode: null,
335
+ exchangeRate: null,
336
+ functionalDebitAmount: line.functionalDebitAmount! ?? null,
337
+ functionalCreditAmount: line.functionalCreditAmount! ?? null,
338
+ createdAt: now,
339
+ updatedAt: null,
340
+ });
341
+ }
342
+
343
+ // Include retained earnings in opening entry
344
+ if (netIncome >= 0) {
345
+ openingLines.push({
346
+ journalEntryId: openingEntry!.id,
347
+ accountId: retainedEarningsAccount.id,
348
+ debitAmount: null,
349
+ creditAmount: netIncome,
350
+ description: "Opening retained earnings balance",
351
+ currencyCode: null,
352
+ exchangeRate: null,
353
+ functionalDebitAmount: null,
354
+ functionalCreditAmount: netIncome,
355
+ createdAt: now,
356
+ updatedAt: null,
357
+ });
358
+ } else {
359
+ openingLines.push({
360
+ journalEntryId: openingEntry!.id,
361
+ accountId: retainedEarningsAccount.id,
362
+ debitAmount: Math.abs(netIncome),
363
+ creditAmount: null,
364
+ description: "Opening retained earnings balance",
365
+ currencyCode: null,
366
+ exchangeRate: null,
367
+ functionalDebitAmount: Math.abs(netIncome),
368
+ functionalCreditAmount: null,
369
+ createdAt: now,
370
+ updatedAt: null,
371
+ });
372
+ }
373
+
374
+ if (openingLines.length > 0) {
375
+ await db.insertInto("JournalLine").values(openingLines).returningAll().execute();
376
+ }
377
+
378
+ // 12. Update fiscal year yearEndCloseExecuted = true
379
+ await db
380
+ .updateTable("FiscalYear")
381
+ .set({
382
+ yearEndCloseExecuted: true,
383
+ updatedAt: now,
384
+ })
385
+ .where("id", "=", fiscalYear!.id)
386
+ .returningAll()
387
+ .executeTakeFirstOrThrow();
388
+
389
+ // 13. Update PeriodClose yearEndCloseExecuted = true
390
+ await db
391
+ .updateTable("PeriodClose")
392
+ .set({
393
+ yearEndCloseExecuted: true,
394
+ updatedAt: now,
395
+ })
396
+ .where("id", "=", periodClose.id)
397
+ .returningAll()
398
+ .executeTakeFirstOrThrow();
399
+
400
+ // 14. Record audit trail
401
+ auditTrail.push({
402
+ type: "YEAR_END_CLOSE",
403
+ fiscalYearId: fiscalYear!.id,
404
+ closingEntryId: closingEntry!.id,
405
+ openingEntryId: openingEntry!.id,
406
+ netIncome,
407
+ actorId: ctx.actorId,
408
+ });
409
+
410
+ return ok({
411
+ closingEntry: closingEntry!,
412
+ openingEntry: openingEntry!,
413
+ auditTrail,
414
+ });
415
+ }
@@ -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 "./finalCloseAndLockPeriod";
5
+
6
+ export const finalCloseAndLockPeriod = defineCommand(permissions.finalCloseAndLockPeriod, run);
@@ -0,0 +1,102 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import { PeriodCloseNotFoundError, InvalidStatusTransitionError } from "../lib/errors.generated";
5
+ import {
6
+ softLockedPeriodClose,
7
+ underReviewPeriodClose,
8
+ inProgressPeriodClose,
9
+ } from "../testing/fixtures";
10
+ import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
11
+ import { run } from "./finalCloseAndLockPeriod";
12
+
13
+ describe("finalCloseAndLockPeriod", () => {
14
+ it("returns error when PeriodClose record does not exist", async () => {
15
+ const { db, spies } = createMockDb<Transaction>();
16
+ spies.select.mockReturnValueOnce(undefined);
17
+
18
+ const result = await run(db, { id: "nonexistent" }, commandCtx);
19
+
20
+ expectErr(result, PeriodCloseNotFoundError);
21
+ });
22
+
23
+ it("returns error when PeriodClose is in UNDER_REVIEW status", async () => {
24
+ const { db, spies } = createMockDb<Transaction>();
25
+ spies.select.mockReturnValueOnce(underReviewPeriodClose);
26
+
27
+ const result = await run(db, { id: underReviewPeriodClose.id }, commandCtx);
28
+
29
+ expectErr(result, InvalidStatusTransitionError);
30
+ });
31
+
32
+ it("returns error when PeriodClose is in IN_PROGRESS status", async () => {
33
+ const { db, spies } = createMockDb<Transaction>();
34
+ spies.select.mockReturnValueOnce(inProgressPeriodClose);
35
+
36
+ const result = await run(db, { id: inProgressPeriodClose.id }, commandCtx);
37
+
38
+ expectErr(result, InvalidStatusTransitionError);
39
+ });
40
+
41
+ it("transitions SOFT_LOCKED PeriodClose to PERMANENTLY_CLOSED with completionPercentage 100", async () => {
42
+ const updatedPeriodClose = {
43
+ ...softLockedPeriodClose,
44
+ status: "PERMANENTLY_CLOSED",
45
+ completionPercentage: 100,
46
+ finalizedByUserId: commandCtx.actorId,
47
+ };
48
+ const { db, spies } = createMockDb<Transaction>();
49
+ spies.select.mockReturnValueOnce(softLockedPeriodClose);
50
+ spies.update.mockReturnValueOnce(updatedPeriodClose);
51
+ spies.update.mockReturnValueOnce({ status: "PERMANENTLY_CLOSED" });
52
+
53
+ const result = await run(db, { id: softLockedPeriodClose.id }, commandCtx);
54
+
55
+ const value = expectOk(result);
56
+ expect(value.periodClose.status).toBe("PERMANENTLY_CLOSED");
57
+ expect(value.periodClose.completionPercentage).toBe(100);
58
+ expect(value.periodClose.finalizedByUserId).toBe(commandCtx.actorId);
59
+ });
60
+
61
+ it("also updates the associated AccountingPeriod to PERMANENTLY_CLOSED", async () => {
62
+ const updatedPeriodClose = {
63
+ ...softLockedPeriodClose,
64
+ status: "PERMANENTLY_CLOSED",
65
+ completionPercentage: 100,
66
+ };
67
+ const updatedAccountingPeriod = { status: "PERMANENTLY_CLOSED" };
68
+ const { db, spies } = createMockDb<Transaction>();
69
+ spies.select.mockReturnValueOnce(softLockedPeriodClose);
70
+ spies.update.mockReturnValueOnce(updatedPeriodClose);
71
+ spies.update.mockReturnValueOnce(updatedAccountingPeriod);
72
+
73
+ const result = await run(db, { id: softLockedPeriodClose.id }, commandCtx);
74
+
75
+ expectOk(result);
76
+ expect(spies.update).toHaveBeenCalledTimes(2);
77
+ });
78
+
79
+ it("emits audit event recording status transition from SOFT_LOCKED to PERMANENTLY_CLOSED", async () => {
80
+ const updatedPeriodClose = {
81
+ ...softLockedPeriodClose,
82
+ status: "PERMANENTLY_CLOSED",
83
+ completionPercentage: 100,
84
+ };
85
+ const { db, spies } = createMockDb<Transaction>();
86
+ spies.select.mockReturnValueOnce(softLockedPeriodClose);
87
+ spies.update.mockReturnValueOnce(updatedPeriodClose);
88
+ spies.update.mockReturnValueOnce({ status: "PERMANENTLY_CLOSED" });
89
+
90
+ const result = await run(db, { id: softLockedPeriodClose.id }, commandCtx);
91
+
92
+ const value = expectOk(result);
93
+ expect(value.auditTrail).toContainEqual(
94
+ expect.objectContaining({
95
+ type: "STATUS_TRANSITION",
96
+ from: "SOFT_LOCKED",
97
+ to: "PERMANENTLY_CLOSED",
98
+ actorId: commandCtx.actorId,
99
+ }),
100
+ );
101
+ });
102
+ });
@@ -0,0 +1,76 @@
1
+ import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
2
+ import type { Transaction } from "../generated/kysely-tailordb";
3
+ import { PeriodCloseNotFoundError, InvalidStatusTransitionError } from "../lib/errors.generated";
4
+
5
+ export interface FinalCloseAndLockPeriodInput {
6
+ id: string;
7
+ }
8
+
9
+ /**
10
+ * Function: finalCloseAndLockPeriod
11
+ *
12
+ * Transitions a PeriodClose record from SOFT_LOCKED to PERMANENTLY_CLOSED status,
13
+ * making the period fully immutable — no further journal entries allowed regardless of role.
14
+ * Also transitions the underlying AccountingPeriod to PERMANENTLY_CLOSED.
15
+ * Sets completionPercentage to 100% and records the operator's userId.
16
+ */
17
+ export async function run(
18
+ db: Transaction,
19
+ input: FinalCloseAndLockPeriodInput,
20
+ ctx: CommandContext,
21
+ ) {
22
+ const { id } = input;
23
+
24
+ // 1. Find PeriodClose record
25
+ const periodClose = await db
26
+ .selectFrom("PeriodClose")
27
+ .selectAll()
28
+ .where("id", "=", id)
29
+ .forUpdate()
30
+ .executeTakeFirst();
31
+
32
+ if (!periodClose) {
33
+ return err(new PeriodCloseNotFoundError(id));
34
+ }
35
+
36
+ // 2. Validate status is SOFT_LOCKED
37
+ if (periodClose.status !== "SOFT_LOCKED") {
38
+ return err(new InvalidStatusTransitionError(id));
39
+ }
40
+
41
+ // 3. Transition PeriodClose to PERMANENTLY_CLOSED with completionPercentage 100
42
+ const updated = await db
43
+ .updateTable("PeriodClose")
44
+ .set({
45
+ status: "PERMANENTLY_CLOSED",
46
+ completionPercentage: 100,
47
+ finalizedByUserId: ctx.actorId,
48
+ updatedAt: new Date(),
49
+ })
50
+ .where("id", "=", id)
51
+ .returningAll()
52
+ .executeTakeFirstOrThrow();
53
+
54
+ // 4. Also transition the associated AccountingPeriod to PERMANENTLY_CLOSED
55
+ await db
56
+ .updateTable("AccountingPeriod")
57
+ .set({
58
+ status: "PERMANENTLY_CLOSED",
59
+ updatedAt: new Date(),
60
+ })
61
+ .where("id", "=", periodClose.accountingPeriodId)
62
+ .returningAll()
63
+ .executeTakeFirstOrThrow();
64
+
65
+ // 5. Emit audit event recording status transition and operator
66
+ const auditTrail = [
67
+ {
68
+ type: "STATUS_TRANSITION",
69
+ from: "SOFT_LOCKED",
70
+ to: "PERMANENTLY_CLOSED",
71
+ actorId: ctx.actorId,
72
+ },
73
+ ];
74
+
75
+ return ok({ periodClose: updated, auditTrail });
76
+ }
@@ -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 "./finalizeFinancialStatement";
5
+
6
+ export const finalizeFinancialStatement = defineCommand(permissions.finalizeFinancialStatement, run);
@@ -0,0 +1,73 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ FinancialStatementNotFoundError,
6
+ InvalidStatusTransitionError,
7
+ } from "../lib/errors.generated";
8
+ import { baseFinancialStatement, finalizedFinancialStatement } from "../testing/fixtures";
9
+ import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
10
+ import { run } from "./finalizeFinancialStatement";
11
+
12
+ describe("finalizeFinancialStatement", () => {
13
+ it("returns error when financial statement does not exist", async () => {
14
+ const { db, spies } = createMockDb<Transaction>();
15
+ spies.select.mockReturnValueOnce(undefined); // statement lookup
16
+
17
+ const result = await run(db, { id: "nonexistent" }, commandCtx);
18
+
19
+ expectErr(result, FinancialStatementNotFoundError);
20
+ });
21
+
22
+ it("returns error when financial statement is already FINALIZED", async () => {
23
+ const { db, spies } = createMockDb<Transaction>();
24
+ spies.select.mockReturnValueOnce(finalizedFinancialStatement); // statement lookup
25
+
26
+ const result = await run(db, { id: finalizedFinancialStatement.id }, commandCtx);
27
+
28
+ expectErr(result, InvalidStatusTransitionError);
29
+ });
30
+
31
+ it("transitions DRAFT statement to FINALIZED with finalizedAt set", async () => {
32
+ const { db, spies } = createMockDb<Transaction>();
33
+ const finalizedResult = {
34
+ ...baseFinancialStatement,
35
+ status: "FINALIZED",
36
+ finalizedAt: new Date(),
37
+ updatedAt: new Date(),
38
+ };
39
+ spies.select.mockReturnValueOnce(baseFinancialStatement); // statement lookup
40
+ spies.update.mockReturnValueOnce(finalizedResult); // update result
41
+
42
+ const result = await run(db, { id: baseFinancialStatement.id }, commandCtx);
43
+
44
+ const value = expectOk(result);
45
+ expect(value.financialStatement.status).toBe("FINALIZED");
46
+ expect(value.financialStatement.finalizedAt).toBeDefined();
47
+ });
48
+
49
+ it("emits audit event recording status transition from DRAFT to FINALIZED", async () => {
50
+ const { db, spies } = createMockDb<Transaction>();
51
+ const finalizedResult = {
52
+ ...baseFinancialStatement,
53
+ status: "FINALIZED",
54
+ finalizedAt: new Date(),
55
+ updatedAt: new Date(),
56
+ };
57
+ spies.select.mockReturnValueOnce(baseFinancialStatement); // statement lookup
58
+ spies.update.mockReturnValueOnce(finalizedResult); // update result
59
+
60
+ const result = await run(db, { id: baseFinancialStatement.id }, commandCtx);
61
+
62
+ const value = expectOk(result);
63
+ expect(value.auditTrail).toBeDefined();
64
+ expect(value.auditTrail).toContainEqual(
65
+ expect.objectContaining({
66
+ type: "STATUS_TRANSITION",
67
+ from: "DRAFT",
68
+ to: "FINALIZED",
69
+ actorId: commandCtx.actorId,
70
+ }),
71
+ );
72
+ });
73
+ });