@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,439 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ NoActiveChartOfAccountsError,
6
+ AccountingPeriodNotFoundError,
7
+ InvalidVariantError,
8
+ TrialBalanceImbalancedError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ baseCompany,
12
+ baseChartOfAccounts,
13
+ baseAccountingPeriod,
14
+ baseAccount,
15
+ baseAccount2,
16
+ baseAccountGroup,
17
+ baseTrialBalance,
18
+ baseTrialBalanceLine,
19
+ revenueAccount,
20
+ expenseAccount,
21
+ } from "../testing/fixtures";
22
+ import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
23
+ import { run, type GenerateTrialBalanceInput } from "./generateTrialBalance";
24
+
25
+ const baseInput: GenerateTrialBalanceInput = {
26
+ companyId: baseCompany.id,
27
+ accountingPeriodId: baseAccountingPeriod.id,
28
+ variant: "PRE_CLOSE",
29
+ };
30
+
31
+ // Fixtures with accountGroupId for testing issue #1
32
+ const accountWithGroup = {
33
+ ...baseAccount,
34
+ accountGroupId: baseAccountGroup.id,
35
+ };
36
+
37
+ const account2WithGroup = {
38
+ ...baseAccount2,
39
+ accountGroupId: null,
40
+ };
41
+
42
+ describe("generateTrialBalance", () => {
43
+ it("returns error when company has no active CoA", async () => {
44
+ const { db, spies } = createMockDb<Transaction>();
45
+ spies.select.mockReturnValueOnce(undefined); // ChartOfAccounts lookup
46
+
47
+ const result = await run(db, baseInput, commandCtx);
48
+
49
+ expectErr(result, NoActiveChartOfAccountsError);
50
+ });
51
+
52
+ it("returns error when accounting period not found", async () => {
53
+ const { db, spies } = createMockDb<Transaction>();
54
+ spies.select
55
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
56
+ .mockReturnValueOnce(undefined); // AccountingPeriod lookup
57
+
58
+ const result = await run(db, baseInput, commandCtx);
59
+
60
+ expectErr(result, AccountingPeriodNotFoundError);
61
+ });
62
+
63
+ it("returns error when variant is invalid", async () => {
64
+ const { db, spies } = createMockDb<Transaction>();
65
+ spies.select
66
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
67
+ .mockReturnValueOnce(baseAccountingPeriod); // AccountingPeriod lookup
68
+
69
+ const result = await run(db, { ...baseInput, variant: "INVALID" }, commandCtx);
70
+
71
+ expectErr(result, InvalidVariantError);
72
+ });
73
+
74
+ it("generates trial balance with correct balances for PRE_CLOSE", async () => {
75
+ const { db, spies } = createMockDb<Transaction>();
76
+
77
+ const journalEntry = {
78
+ id: "je-1",
79
+ accountingPeriodId: baseAccountingPeriod.id,
80
+ status: "POSTED",
81
+ journalType: "MISCELLANEOUS",
82
+ };
83
+
84
+ const journalLine1 = {
85
+ id: "jl-1",
86
+ journalEntryId: "je-1",
87
+ accountId: baseAccount.id,
88
+ functionalDebitAmount: 5000,
89
+ functionalCreditAmount: null,
90
+ };
91
+
92
+ const journalLine2 = {
93
+ id: "jl-2",
94
+ journalEntryId: "je-1",
95
+ accountId: baseAccount2.id,
96
+ functionalDebitAmount: null,
97
+ functionalCreditAmount: 5000,
98
+ };
99
+
100
+ const insertedTrialBalance = {
101
+ ...baseTrialBalance,
102
+ id: "tb-new",
103
+ totalDebits: 5000,
104
+ totalCredits: 5000,
105
+ };
106
+
107
+ spies.select
108
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
109
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
110
+ .mockImplementationOnce(() => [baseAccount, baseAccount2]) // accounts
111
+ .mockImplementationOnce(() => [journalEntry]) // journal entries
112
+ .mockImplementationOnce(() => [journalLine1, journalLine2]) // journal lines
113
+ .mockReturnValueOnce(undefined) // preceding period lookup (no preceding period)
114
+ .mockReturnValueOnce(undefined); // existingTrialBalance lookup (no prior TB for upsert)
115
+
116
+ spies.insert
117
+ .mockReturnValueOnce(insertedTrialBalance) // TrialBalance insert
118
+ .mockReturnValueOnce({ ...baseTrialBalanceLine, trialBalanceId: "tb-new" }) // line 1
119
+ .mockReturnValueOnce({
120
+ ...baseTrialBalanceLine,
121
+ trialBalanceId: "tb-new",
122
+ accountId: baseAccount2.id,
123
+ }); // line 2
124
+
125
+ const result = await run(db, baseInput, commandCtx);
126
+
127
+ const value = expectOk(result);
128
+ expect(value.trialBalance.totalDebits).toBe(5000);
129
+ expect(value.trialBalance.totalCredits).toBe(5000);
130
+ expect(value.trialBalance.id).toBe("tb-new");
131
+ });
132
+
133
+ it("includes zero-balance accounts from active CoA", async () => {
134
+ const { db, spies } = createMockDb<Transaction>();
135
+
136
+ // No journal entries at all — all accounts should still appear
137
+ const insertedTrialBalance = {
138
+ ...baseTrialBalance,
139
+ id: "tb-zero",
140
+ totalDebits: 0,
141
+ totalCredits: 0,
142
+ };
143
+
144
+ spies.select
145
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
146
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
147
+ .mockImplementationOnce(() => [baseAccount, baseAccount2]) // accounts (both active)
148
+ .mockImplementationOnce(() => []) // journal entries (none)
149
+ .mockImplementationOnce(() => []) // journal lines (none)
150
+ .mockReturnValueOnce(undefined) // preceding period lookup (no preceding period)
151
+ .mockReturnValueOnce(undefined); // existingTrialBalance lookup (no prior TB for upsert)
152
+
153
+ spies.insert
154
+ .mockReturnValueOnce(insertedTrialBalance) // TrialBalance insert
155
+ .mockReturnValueOnce({}) // line 1 insert
156
+ .mockReturnValueOnce({}); // line 2 insert
157
+
158
+ const result = await run(db, baseInput, commandCtx);
159
+
160
+ const value = expectOk(result);
161
+ expect(value.trialBalance.totalDebits).toBe(0);
162
+ expect(value.trialBalance.totalCredits).toBe(0);
163
+ // Verify insert was called: 1 for TB + 2 for TB lines (one per account)
164
+ expect(spies.insert).toHaveBeenCalledTimes(3);
165
+ });
166
+
167
+ it("returns error when trial balance is imbalanced", async () => {
168
+ const { db, spies } = createMockDb<Transaction>();
169
+
170
+ // Create journal lines where debits != credits (imbalanced data)
171
+ const journalEntry = {
172
+ id: "je-1",
173
+ accountingPeriodId: baseAccountingPeriod.id,
174
+ status: "POSTED",
175
+ journalType: "MISCELLANEOUS",
176
+ };
177
+
178
+ const journalLine1 = {
179
+ id: "jl-1",
180
+ journalEntryId: "je-1",
181
+ accountId: baseAccount.id,
182
+ functionalDebitAmount: 5000,
183
+ functionalCreditAmount: null,
184
+ };
185
+
186
+ const journalLine2 = {
187
+ id: "jl-2",
188
+ journalEntryId: "je-1",
189
+ accountId: baseAccount2.id,
190
+ functionalDebitAmount: null,
191
+ functionalCreditAmount: 3000, // intentionally imbalanced
192
+ };
193
+
194
+ spies.select
195
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
196
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
197
+ .mockImplementationOnce(() => [baseAccount, baseAccount2]) // accounts
198
+ .mockImplementationOnce(() => [journalEntry]) // journal entries
199
+ .mockImplementationOnce(() => [journalLine1, journalLine2]) // journal lines
200
+ .mockReturnValueOnce(undefined); // preceding period lookup (no preceding period)
201
+
202
+ const result = await run(db, baseInput, commandCtx);
203
+
204
+ expectErr(result, TrialBalanceImbalancedError);
205
+ });
206
+
207
+ it("emits audit event with correct actor and trial balance details", async () => {
208
+ const { db, spies } = createMockDb<Transaction>();
209
+
210
+ const insertedTrialBalance = {
211
+ ...baseTrialBalance,
212
+ id: "tb-audit",
213
+ totalDebits: 0,
214
+ totalCredits: 0,
215
+ };
216
+
217
+ spies.select
218
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
219
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
220
+ .mockImplementationOnce(() => [baseAccount, baseAccount2]) // accounts
221
+ .mockImplementationOnce(() => []) // journal entries
222
+ .mockImplementationOnce(() => []) // journal lines
223
+ .mockReturnValueOnce(undefined) // preceding period lookup (no preceding period)
224
+ .mockReturnValueOnce(undefined); // existingTrialBalance lookup (no prior TB for upsert)
225
+
226
+ spies.insert
227
+ .mockReturnValueOnce(insertedTrialBalance)
228
+ .mockReturnValueOnce({})
229
+ .mockReturnValueOnce({});
230
+
231
+ const result = await run(db, baseInput, commandCtx);
232
+
233
+ const value = expectOk(result);
234
+ expect(value.auditEvent).toBeDefined();
235
+ expect(value.auditEvent.type).toBe("TRIAL_BALANCE_GENERATED");
236
+ expect(value.auditEvent.actorId).toBe(commandCtx.actorId);
237
+ expect(value.auditEvent.timestamp).toBeInstanceOf(Date);
238
+ expect(value.auditEvent.trialBalanceId).toBe("tb-audit");
239
+ expect(value.auditEvent.variant).toBe("PRE_CLOSE");
240
+ expect(value.auditEvent.isBalanced).toBe(true);
241
+ });
242
+
243
+ it("uses accountGroupId from Account record when available", async () => {
244
+ const { db, spies } = createMockDb<Transaction>();
245
+
246
+ const insertedTrialBalance = {
247
+ ...baseTrialBalance,
248
+ id: "tb-group",
249
+ totalDebits: 0,
250
+ totalCredits: 0,
251
+ };
252
+
253
+ spies.select
254
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
255
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
256
+ .mockImplementationOnce(() => [accountWithGroup, account2WithGroup]) // accounts with groupId
257
+ .mockImplementationOnce(() => []) // journal entries
258
+ .mockImplementationOnce(() => []) // journal lines
259
+ .mockReturnValueOnce(undefined) // preceding period lookup (no preceding period)
260
+ .mockReturnValueOnce(undefined); // existingTrialBalance lookup
261
+
262
+ spies.insert
263
+ .mockReturnValueOnce(insertedTrialBalance)
264
+ .mockReturnValueOnce({})
265
+ .mockReturnValueOnce({});
266
+
267
+ const result = await run(db, baseInput, commandCtx);
268
+
269
+ expectOk(result);
270
+
271
+ // The first line insert should pass accountGroupId from the account fixture
272
+ const firstLineValues = spies.values.mock.calls.find(
273
+ (call: unknown[]) =>
274
+ call[0] &&
275
+ typeof call[0] === "object" &&
276
+ "accountGroupId" in (call[0] as Record<string, unknown>) &&
277
+ (call[0] as Record<string, unknown>).accountGroupId === baseAccountGroup.id,
278
+ );
279
+ expect(firstLineValues).toBeDefined();
280
+ });
281
+
282
+ it("zeros closing balances for revenue and expense accounts in POST_CLOSE variant", async () => {
283
+ const { db, spies } = createMockDb<Transaction>();
284
+
285
+ const postCloseInput: GenerateTrialBalanceInput = {
286
+ ...baseInput,
287
+ variant: "POST_CLOSE",
288
+ };
289
+
290
+ // Revenue account: 3000 debit, 8000 credit → closingBalance would be -5000 but POST_CLOSE zeros it
291
+ // Expense account: 4000 debit, 4000 credit → balanced, still zeroed for POST_CLOSE
292
+ // Asset account: 8000 debit, 3000 credit → not zeroed
293
+ const journalEntry = {
294
+ id: "je-post",
295
+ accountingPeriodId: baseAccountingPeriod.id,
296
+ status: "POSTED",
297
+ journalType: "CLOSING",
298
+ };
299
+
300
+ const revenueJournalLine = {
301
+ id: "jl-rev",
302
+ journalEntryId: "je-post",
303
+ accountId: revenueAccount.id,
304
+ functionalDebitAmount: 3000,
305
+ functionalCreditAmount: null,
306
+ };
307
+
308
+ const expenseJournalLine = {
309
+ id: "jl-exp",
310
+ journalEntryId: "je-post",
311
+ accountId: expenseAccount.id,
312
+ functionalDebitAmount: null,
313
+ functionalCreditAmount: 3000,
314
+ };
315
+
316
+ // Total debits = 3000, total credits = 3000 — balanced
317
+ const insertedTrialBalance = {
318
+ ...baseTrialBalance,
319
+ id: "tb-post-close",
320
+ variant: "POST_CLOSE" as const,
321
+ totalDebits: 3000,
322
+ totalCredits: 3000,
323
+ };
324
+
325
+ spies.select
326
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
327
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
328
+ .mockImplementationOnce(() => [revenueAccount, expenseAccount]) // accounts
329
+ .mockImplementationOnce(() => [journalEntry]) // journal entries (CLOSING included for POST_CLOSE)
330
+ .mockImplementationOnce(() => [revenueJournalLine, expenseJournalLine]) // journal lines
331
+ .mockReturnValueOnce(undefined) // preceding period lookup (no preceding period)
332
+ .mockReturnValueOnce(undefined); // existingTrialBalance lookup
333
+
334
+ spies.insert
335
+ .mockReturnValueOnce(insertedTrialBalance)
336
+ .mockReturnValueOnce({})
337
+ .mockReturnValueOnce({});
338
+
339
+ const result = await run(db, postCloseInput, commandCtx);
340
+
341
+ expectOk(result);
342
+
343
+ // Verify that TrialBalanceLine inserts have closingBalance = 0 for REVENUE/EXPENSE accounts
344
+ const lineInsertCalls = spies.values.mock.calls.filter(
345
+ (call: unknown[]) =>
346
+ call[0] &&
347
+ typeof call[0] === "object" &&
348
+ "closingBalance" in (call[0] as Record<string, unknown>),
349
+ );
350
+ expect(lineInsertCalls.length).toBe(2);
351
+ for (const call of lineInsertCalls) {
352
+ expect((call[0] as Record<string, unknown>).closingBalance).toBe(0);
353
+ }
354
+ });
355
+
356
+ it("deletes existing TrialBalance before inserting when regenerating for same period and variant", async () => {
357
+ const { db, spies } = createMockDb<Transaction>();
358
+
359
+ const existingTb = {
360
+ ...baseTrialBalance,
361
+ id: "tb-existing",
362
+ };
363
+
364
+ const insertedTrialBalance = {
365
+ ...baseTrialBalance,
366
+ id: "tb-regenerated",
367
+ totalDebits: 0,
368
+ totalCredits: 0,
369
+ };
370
+
371
+ spies.select
372
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
373
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
374
+ .mockImplementationOnce(() => [baseAccount, baseAccount2]) // accounts
375
+ .mockImplementationOnce(() => []) // journal entries
376
+ .mockImplementationOnce(() => []) // journal lines
377
+ .mockReturnValueOnce(undefined) // preceding period lookup (no preceding period)
378
+ .mockReturnValueOnce(existingTb); // existingTrialBalance lookup — found!
379
+
380
+ spies.insert
381
+ .mockReturnValueOnce(insertedTrialBalance)
382
+ .mockReturnValueOnce({})
383
+ .mockReturnValueOnce({});
384
+
385
+ const result = await run(db, baseInput, commandCtx);
386
+
387
+ expectOk(result);
388
+
389
+ // delete spy should have been called twice: once for TrialBalanceLine, once for TrialBalance
390
+ expect(spies.delete).toHaveBeenCalledTimes(2);
391
+ // New trial balance should be inserted fresh
392
+ expect(spies.insert).toHaveBeenCalledTimes(3); // 1 TB + 2 lines
393
+ });
394
+
395
+ it("sorts trial balance lines by account code ascending", async () => {
396
+ const { db, spies } = createMockDb<Transaction>();
397
+
398
+ // Provide accounts out of order — code "2000" before "1000"
399
+ const insertedTrialBalance = {
400
+ ...baseTrialBalance,
401
+ id: "tb-sorted",
402
+ totalDebits: 0,
403
+ totalCredits: 0,
404
+ };
405
+
406
+ spies.select
407
+ .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
408
+ .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
409
+ .mockImplementationOnce(() => [baseAccount2, baseAccount]) // accounts: "2000" first, then "1000"
410
+ .mockImplementationOnce(() => []) // journal entries
411
+ .mockImplementationOnce(() => []) // journal lines
412
+ .mockReturnValueOnce(undefined) // preceding period lookup (no preceding period)
413
+ .mockReturnValueOnce(undefined); // existingTrialBalance lookup
414
+
415
+ spies.insert
416
+ .mockReturnValueOnce(insertedTrialBalance)
417
+ .mockReturnValueOnce({})
418
+ .mockReturnValueOnce({});
419
+
420
+ const result = await run(db, baseInput, commandCtx);
421
+
422
+ expectOk(result);
423
+
424
+ // The first TrialBalanceLine insert should be for account code "1000" (sortOrder: 1)
425
+ // and second for "2000" (sortOrder: 2)
426
+ const lineInsertCalls = spies.values.mock.calls.filter(
427
+ (call: unknown[]) =>
428
+ call[0] &&
429
+ typeof call[0] === "object" &&
430
+ "sortOrder" in (call[0] as Record<string, unknown>),
431
+ );
432
+
433
+ expect(lineInsertCalls.length).toBe(2);
434
+ expect((lineInsertCalls[0][0] as Record<string, unknown>).accountCode).toBe("1000");
435
+ expect((lineInsertCalls[0][0] as Record<string, unknown>).sortOrder).toBe(1);
436
+ expect((lineInsertCalls[1][0] as Record<string, unknown>).accountCode).toBe("2000");
437
+ expect((lineInsertCalls[1][0] as Record<string, unknown>).sortOrder).toBe(2);
438
+ });
439
+ });
@@ -0,0 +1,268 @@
1
+ import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
2
+ import type { Transaction } from "../generated/kysely-tailordb";
3
+ import {
4
+ NoActiveChartOfAccountsError,
5
+ AccountingPeriodNotFoundError,
6
+ InvalidVariantError,
7
+ TrialBalanceImbalancedError,
8
+ } from "../lib/errors.generated";
9
+
10
+ export interface GenerateTrialBalanceInput {
11
+ companyId: string;
12
+ accountingPeriodId: string;
13
+ variant: string;
14
+ }
15
+
16
+ const VALID_VARIANTS = ["PRE_CLOSE", "POST_CLOSE"] as const;
17
+
18
+ /**
19
+ * Function: generateTrialBalance
20
+ *
21
+ * Creates a point-in-time snapshot report of GL account balances for a specific
22
+ * accounting period. Includes opening balance, period debits, credits, closing
23
+ * balance for each account organized by account group hierarchy. Supports
24
+ * PRE_CLOSE and POST_CLOSE variants.
25
+ */
26
+ export async function run(db: Transaction, input: GenerateTrialBalanceInput, ctx: CommandContext) {
27
+ const { companyId, accountingPeriodId, variant } = input;
28
+
29
+ // 1. Find the active ChartOfAccounts for the company
30
+ const chartOfAccounts = await db
31
+ .selectFrom("ChartOfAccounts")
32
+ .selectAll()
33
+ .where("companyId", "=", companyId)
34
+ .where("status", "=", "ACTIVE")
35
+ .executeTakeFirst();
36
+
37
+ if (!chartOfAccounts) {
38
+ return err(new NoActiveChartOfAccountsError(companyId));
39
+ }
40
+
41
+ // 2. Find the AccountingPeriod
42
+ const accountingPeriod = await db
43
+ .selectFrom("AccountingPeriod")
44
+ .selectAll()
45
+ .where("id", "=", accountingPeriodId)
46
+ .executeTakeFirst();
47
+
48
+ if (!accountingPeriod) {
49
+ return err(new AccountingPeriodNotFoundError(accountingPeriodId));
50
+ }
51
+
52
+ // 3. Validate variant is PRE_CLOSE or POST_CLOSE
53
+ if (!VALID_VARIANTS.includes(variant as (typeof VALID_VARIANTS)[number])) {
54
+ return err(new InvalidVariantError(variant));
55
+ }
56
+
57
+ // 4. Fetch all accounts from the CoA, sorted by account code ascending
58
+ const accountsRaw = await db
59
+ .selectFrom("Account")
60
+ .selectAll()
61
+ .where("status", "=", "ACTIVE")
62
+ .execute();
63
+
64
+ const accounts = [
65
+ ...(accountsRaw as {
66
+ id: string;
67
+ code: string | null;
68
+ name: string | null;
69
+ classification: string | null;
70
+ accountGroupId?: string | null;
71
+ }[]),
72
+ ].sort((a, b) => (a.code ?? "").localeCompare(b.code ?? ""));
73
+
74
+ // 5. Fetch posted journal lines for the period
75
+ const journalEntries = await db
76
+ .selectFrom("JournalEntry")
77
+ .selectAll()
78
+ .where("accountingPeriodId", "=", accountingPeriodId)
79
+ .where("status", "=", "POSTED")
80
+ .execute();
81
+
82
+ // 6. For PRE_CLOSE, exclude CLOSING journal type entries; for POST_CLOSE include all
83
+ const filteredEntries =
84
+ variant === "PRE_CLOSE"
85
+ ? journalEntries.filter((e: { journalType: string }) => e.journalType !== "CLOSING")
86
+ : journalEntries;
87
+
88
+ const entryIds = new Set(filteredEntries.map((e: { id: string }) => e.id));
89
+
90
+ const allJournalLines = await db
91
+ .selectFrom("JournalLine")
92
+ .selectAll()
93
+ .where("journalEntryId", "in", [...entryIds])
94
+ .execute();
95
+
96
+ const journalLines = allJournalLines.filter((line: { journalEntryId: string }) =>
97
+ entryIds.has(line.journalEntryId),
98
+ );
99
+
100
+ // 7. Fetch opening balances from the immediately preceding period's trial balance
101
+ const precedingPeriod = await db
102
+ .selectFrom("AccountingPeriod")
103
+ .selectAll()
104
+ .where("companyId", "=", companyId)
105
+ .where("endDate", "<", accountingPeriod.startDate)
106
+ .orderBy("endDate", "desc")
107
+ .executeTakeFirst();
108
+
109
+ const priorBalanceByAccount = new Map<string, number>();
110
+
111
+ if (precedingPeriod) {
112
+ const precedingTrialBalance = await db
113
+ .selectFrom("TrialBalance")
114
+ .selectAll()
115
+ .where("companyId", "=", companyId)
116
+ .where("chartOfAccountsId", "=", chartOfAccounts.id)
117
+ .where("accountingPeriodId", "=", precedingPeriod.id)
118
+ .where("variant", "=", variant as "PRE_CLOSE" | "POST_CLOSE")
119
+ .executeTakeFirst();
120
+
121
+ if (precedingTrialBalance) {
122
+ const priorLines = await db
123
+ .selectFrom("TrialBalanceLine")
124
+ .selectAll()
125
+ .where("trialBalanceId", "=", precedingTrialBalance.id)
126
+ .execute();
127
+
128
+ for (const line of priorLines) {
129
+ priorBalanceByAccount.set(line.accountId, line.closingBalance ?? 0);
130
+ }
131
+ }
132
+ }
133
+
134
+ // Calculate per-account balances
135
+ const linesByAccount = new Map<string, { debits: number; credits: number }>();
136
+ for (const line of journalLines) {
137
+ const accountId = line.accountId;
138
+ const current = linesByAccount.get(accountId) ?? { debits: 0, credits: 0 };
139
+ current.debits += line.functionalDebitAmount ?? 0;
140
+ current.credits += line.functionalCreditAmount ?? 0;
141
+ linesByAccount.set(accountId, current);
142
+ }
143
+
144
+ // Build trial balance lines for all accounts (including zero-balance),
145
+ // accounts are already sorted by code ascending.
146
+ let totalDebits = 0;
147
+ let totalCredits = 0;
148
+
149
+ const trialBalanceLines = accounts.map(
150
+ (
151
+ account: {
152
+ id: string;
153
+ code: string | null;
154
+ name: string | null;
155
+ classification: string | null;
156
+ accountGroupId?: string | null;
157
+ },
158
+ index: number,
159
+ ) => {
160
+ const openingBalance = priorBalanceByAccount.get(account.id) ?? 0;
161
+ const periodData = linesByAccount.get(account.id) ?? { debits: 0, credits: 0 };
162
+ const periodDebits = periodData.debits;
163
+ const periodCredits = periodData.credits;
164
+ let closingBalance = openingBalance + periodDebits - periodCredits;
165
+
166
+ // POST_CLOSE variant: zero out balances for temporary accounts (revenue and expense)
167
+ if (
168
+ variant === "POST_CLOSE" &&
169
+ (account.classification === "REVENUE" || account.classification === "EXPENSE")
170
+ ) {
171
+ closingBalance = 0;
172
+ }
173
+
174
+ totalDebits += periodDebits;
175
+ totalCredits += periodCredits;
176
+
177
+ return {
178
+ accountId: account.id,
179
+ accountGroupId: account.accountGroupId ?? null,
180
+ accountCode: account.code ?? "",
181
+ accountName: account.name ?? "",
182
+ openingBalance,
183
+ periodDebits,
184
+ periodCredits,
185
+ closingBalance,
186
+ sortOrder: index + 1,
187
+ };
188
+ },
189
+ );
190
+
191
+ // 8. Check equilibrium: totalDebits == totalCredits
192
+ if (totalDebits !== totalCredits) {
193
+ const discrepancy = Math.abs(totalDebits - totalCredits);
194
+ return err(new TrialBalanceImbalancedError(`${companyId} (discrepancy: ${discrepancy})`));
195
+ }
196
+
197
+ // 9. Upsert: delete existing TrialBalance (and lines) for same period/variant if any
198
+ const existingTrialBalance = await db
199
+ .selectFrom("TrialBalance")
200
+ .selectAll()
201
+ .where("companyId", "=", companyId)
202
+ .where("accountingPeriodId", "=", accountingPeriodId)
203
+ .where("chartOfAccountsId", "=", chartOfAccounts.id)
204
+ .where("variant", "=", variant as "PRE_CLOSE" | "POST_CLOSE")
205
+ .executeTakeFirst();
206
+
207
+ if (existingTrialBalance) {
208
+ await db
209
+ .deleteFrom("TrialBalanceLine")
210
+ .where("trialBalanceId", "=", existingTrialBalance.id)
211
+ .execute();
212
+
213
+ await db.deleteFrom("TrialBalance").where("id", "=", existingTrialBalance.id).execute();
214
+ }
215
+
216
+ // 10. Insert TrialBalance record
217
+ const now = new Date();
218
+ const trialBalance = await db
219
+ .insertInto("TrialBalance")
220
+ .values({
221
+ companyId,
222
+ accountingPeriodId,
223
+ chartOfAccountsId: chartOfAccounts.id,
224
+ variant: variant as "PRE_CLOSE" | "POST_CLOSE",
225
+ generatedAt: now,
226
+ totalDebits,
227
+ totalCredits,
228
+ isBalanced: true,
229
+ updatedAt: null,
230
+ })
231
+ .returningAll()
232
+ .executeTakeFirstOrThrow();
233
+
234
+ // 11. Insert TrialBalanceLine records for each account
235
+ for (const line of trialBalanceLines) {
236
+ await db
237
+ .insertInto("TrialBalanceLine")
238
+ .values({
239
+ trialBalanceId: trialBalance.id,
240
+ accountId: line.accountId,
241
+ accountGroupId: line.accountGroupId,
242
+ accountCode: line.accountCode,
243
+ accountName: line.accountName,
244
+ openingBalance: line.openingBalance,
245
+ periodDebits: line.periodDebits,
246
+ periodCredits: line.periodCredits,
247
+ closingBalance: line.closingBalance,
248
+ sortOrder: line.sortOrder,
249
+ updatedAt: null,
250
+ })
251
+ .returningAll()
252
+ .executeTakeFirstOrThrow();
253
+ }
254
+
255
+ // 12. Emit audit event and return
256
+ const auditEvent = {
257
+ type: "TRIAL_BALANCE_GENERATED",
258
+ variant,
259
+ actorId: ctx.actorId,
260
+ timestamp: now,
261
+ trialBalanceId: trialBalance.id,
262
+ totalDebits,
263
+ totalCredits,
264
+ isBalanced: true,
265
+ };
266
+
267
+ return ok({ trialBalance, auditEvent });
268
+ }