@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.
- package/CHANGELOG.md +14 -0
- package/README.md +10 -10
- package/dist/cli.mjs +407 -69
- package/package.json +1 -1
- package/skills/erp-kit-app-1-requirements/SKILL.md +33 -17
- package/skills/erp-kit-app-2-requirements-review/SKILL.md +12 -0
- package/skills/erp-kit-app-3-plan/SKILL.md +18 -4
- package/skills/erp-kit-app-3-plan/references/resolver-extraction.md +1 -1
- package/skills/erp-kit-app-3-plan/references/screen-extraction.md +1 -1
- package/skills/erp-kit-app-4-plan-review/SKILL.md +12 -0
- package/skills/erp-kit-app-5-impl-backend/SKILL.md +12 -0
- package/skills/erp-kit-app-6-impl-frontend/SKILL.md +12 -0
- package/skills/erp-kit-app-7-impl-review/SKILL.md +13 -1
- package/skills/erp-kit-app-shared/references/progress-protocol.md +77 -0
- package/skills/erp-kit-mock-scenario/SKILL.md +1 -1
- package/skills/erp-kit-module-1-requirements/SKILL.md +1 -1
- package/skills/erp-kit-module-3-plan/SKILL.md +3 -3
- package/skills/erp-kit-module-3-update-plan/SKILL.md +3 -3
- package/skills/erp-kit-module-5-impl/SKILL.md +1 -1
- package/src/commands/app/index.ts +2 -0
- package/src/commands/app/progress/git-context.ts +16 -0
- package/src/commands/app/progress/index.ts +45 -0
- package/src/commands/app/progress/log.ts +49 -0
- package/src/commands/app/progress/progress.test.ts +128 -0
- package/src/commands/app/progress/schema-cmd.ts +10 -0
- package/src/commands/check.test.ts +4 -4
- package/src/commands/lib/discovery.test.ts +5 -7
- package/src/commands/lib/discovery.ts +8 -8
- package/src/commands/lib/sync-check-source.test.ts +1 -1
- package/src/commands/lib/sync-check-source.ts +6 -1
- package/src/commands/lib/sync-check-tests.test.ts +43 -0
- package/src/commands/lib/sync-check-tests.ts +20 -2
- package/src/commands/sync-check.ts +3 -0
- package/src/generator/generate-app-code.test.ts +0 -6
- package/src/generator/generate-app-code.ts +3 -13
- package/src/generator/generate-code.test.ts +10 -40
- package/src/generator/generate-code.ts +6 -12
- package/src/generator/stub-templates.test.ts +0 -7
- package/src/generator/stub-templates.ts +0 -14
- package/src/modules/finance-ledger/README.md +50 -0
- package/src/modules/finance-ledger/command/.gitkeep +0 -0
- package/src/modules/finance-ledger/command/addJournalLine.generated.ts +6 -0
- package/src/modules/finance-ledger/command/addJournalLine.test.ts +438 -0
- package/src/modules/finance-ledger/command/addJournalLine.ts +122 -0
- package/src/modules/finance-ledger/command/approveAndLockPeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/approveAndLockPeriod.test.ts +107 -0
- package/src/modules/finance-ledger/command/approveAndLockPeriod.ts +72 -0
- package/src/modules/finance-ledger/command/beginClose.generated.ts +6 -0
- package/src/modules/finance-ledger/command/beginClose.test.ts +106 -0
- package/src/modules/finance-ledger/command/beginClose.ts +58 -0
- package/src/modules/finance-ledger/command/closePeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/closePeriod.test.ts +87 -0
- package/src/modules/finance-ledger/command/closePeriod.ts +44 -0
- package/src/modules/finance-ledger/command/createAccountingPeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/createAccountingPeriod.test.ts +425 -0
- package/src/modules/finance-ledger/command/createAccountingPeriod.ts +133 -0
- package/src/modules/finance-ledger/command/createFiscalYear.generated.ts +6 -0
- package/src/modules/finance-ledger/command/createFiscalYear.test.ts +197 -0
- package/src/modules/finance-ledger/command/createFiscalYear.ts +70 -0
- package/src/modules/finance-ledger/command/createJournalEntry.generated.ts +6 -0
- package/src/modules/finance-ledger/command/createJournalEntry.test.ts +261 -0
- package/src/modules/finance-ledger/command/createJournalEntry.ts +121 -0
- package/src/modules/finance-ledger/command/deleteAccountingPeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/deleteAccountingPeriod.test.ts +71 -0
- package/src/modules/finance-ledger/command/deleteAccountingPeriod.ts +55 -0
- package/src/modules/finance-ledger/command/deleteFiscalYear.generated.ts +6 -0
- package/src/modules/finance-ledger/command/deleteFiscalYear.test.ts +38 -0
- package/src/modules/finance-ledger/command/deleteFiscalYear.ts +34 -0
- package/src/modules/finance-ledger/command/deleteJournalEntry.generated.ts +6 -0
- package/src/modules/finance-ledger/command/deleteJournalEntry.test.ts +58 -0
- package/src/modules/finance-ledger/command/deleteJournalEntry.ts +43 -0
- package/src/modules/finance-ledger/command/executeYearEndClose.generated.ts +6 -0
- package/src/modules/finance-ledger/command/executeYearEndClose.test.ts +239 -0
- package/src/modules/finance-ledger/command/executeYearEndClose.ts +415 -0
- package/src/modules/finance-ledger/command/finalCloseAndLockPeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/finalCloseAndLockPeriod.test.ts +102 -0
- package/src/modules/finance-ledger/command/finalCloseAndLockPeriod.ts +76 -0
- package/src/modules/finance-ledger/command/finalizeFinancialStatement.generated.ts +6 -0
- package/src/modules/finance-ledger/command/finalizeFinancialStatement.test.ts +73 -0
- package/src/modules/finance-ledger/command/finalizeFinancialStatement.ts +73 -0
- package/src/modules/finance-ledger/command/generateFinancialStatement.generated.ts +6 -0
- package/src/modules/finance-ledger/command/generateFinancialStatement.test.ts +311 -0
- package/src/modules/finance-ledger/command/generateFinancialStatement.ts +275 -0
- package/src/modules/finance-ledger/command/generatePreliminaryStatements.generated.ts +6 -0
- package/src/modules/finance-ledger/command/generatePreliminaryStatements.test.ts +152 -0
- package/src/modules/finance-ledger/command/generatePreliminaryStatements.ts +140 -0
- package/src/modules/finance-ledger/command/generateTrialBalance.generated.ts +6 -0
- package/src/modules/finance-ledger/command/generateTrialBalance.test.ts +439 -0
- package/src/modules/finance-ledger/command/generateTrialBalance.ts +268 -0
- package/src/modules/finance-ledger/command/initiatePeriodClose.generated.ts +6 -0
- package/src/modules/finance-ledger/command/initiatePeriodClose.test.ts +153 -0
- package/src/modules/finance-ledger/command/initiatePeriodClose.ts +84 -0
- package/src/modules/finance-ledger/command/openForAdvanceEntry.generated.ts +6 -0
- package/src/modules/finance-ledger/command/openForAdvanceEntry.test.ts +87 -0
- package/src/modules/finance-ledger/command/openForAdvanceEntry.ts +44 -0
- package/src/modules/finance-ledger/command/openPeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/openPeriod.test.ts +90 -0
- package/src/modules/finance-ledger/command/openPeriod.ts +44 -0
- package/src/modules/finance-ledger/command/permanentlyClosePeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/permanentlyClosePeriod.test.ts +87 -0
- package/src/modules/finance-ledger/command/permanentlyClosePeriod.ts +48 -0
- package/src/modules/finance-ledger/command/postAdjustingEntries.generated.ts +6 -0
- package/src/modules/finance-ledger/command/postAdjustingEntries.test.ts +392 -0
- package/src/modules/finance-ledger/command/postAdjustingEntries.ts +156 -0
- package/src/modules/finance-ledger/command/postJournalEntry.generated.ts +6 -0
- package/src/modules/finance-ledger/command/postJournalEntry.test.ts +346 -0
- package/src/modules/finance-ledger/command/postJournalEntry.ts +160 -0
- package/src/modules/finance-ledger/command/processInventoryHandoff.generated.ts +6 -0
- package/src/modules/finance-ledger/command/processInventoryHandoff.test.ts +211 -0
- package/src/modules/finance-ledger/command/processInventoryHandoff.ts +133 -0
- package/src/modules/finance-ledger/command/processManufacturingHandoff.generated.ts +6 -0
- package/src/modules/finance-ledger/command/processManufacturingHandoff.test.ts +221 -0
- package/src/modules/finance-ledger/command/processManufacturingHandoff.ts +133 -0
- package/src/modules/finance-ledger/command/processPurchaseHandoff.generated.ts +6 -0
- package/src/modules/finance-ledger/command/processPurchaseHandoff.test.ts +222 -0
- package/src/modules/finance-ledger/command/processPurchaseHandoff.ts +133 -0
- package/src/modules/finance-ledger/command/processSalesHandoff.generated.ts +6 -0
- package/src/modules/finance-ledger/command/processSalesHandoff.test.ts +257 -0
- package/src/modules/finance-ledger/command/processSalesHandoff.ts +135 -0
- package/src/modules/finance-ledger/command/regenerateFinancialStatement.generated.ts +6 -0
- package/src/modules/finance-ledger/command/regenerateFinancialStatement.test.ts +129 -0
- package/src/modules/finance-ledger/command/regenerateFinancialStatement.ts +186 -0
- package/src/modules/finance-ledger/command/removeJournalLine.generated.ts +6 -0
- package/src/modules/finance-ledger/command/removeJournalLine.test.ts +65 -0
- package/src/modules/finance-ledger/command/removeJournalLine.ts +39 -0
- package/src/modules/finance-ledger/command/reopenPeriod.generated.ts +6 -0
- package/src/modules/finance-ledger/command/reopenPeriod.test.ts +87 -0
- package/src/modules/finance-ledger/command/reopenPeriod.ts +44 -0
- package/src/modules/finance-ledger/command/reverseJournalEntry.generated.ts +6 -0
- package/src/modules/finance-ledger/command/reverseJournalEntry.test.ts +337 -0
- package/src/modules/finance-ledger/command/reverseJournalEntry.ts +140 -0
- package/src/modules/finance-ledger/command/revertSoftLock.generated.ts +6 -0
- package/src/modules/finance-ledger/command/revertSoftLock.test.ts +96 -0
- package/src/modules/finance-ledger/command/revertSoftLock.ts +67 -0
- package/src/modules/finance-ledger/command/updateFiscalYear.generated.ts +6 -0
- package/src/modules/finance-ledger/command/updateFiscalYear.test.ts +138 -0
- package/src/modules/finance-ledger/command/updateFiscalYear.ts +85 -0
- package/src/modules/finance-ledger/command/updateJournalEntry.generated.ts +6 -0
- package/src/modules/finance-ledger/command/updateJournalEntry.test.ts +195 -0
- package/src/modules/finance-ledger/command/updateJournalEntry.ts +86 -0
- package/src/modules/finance-ledger/command/updateJournalLine.generated.ts +6 -0
- package/src/modules/finance-ledger/command/updateJournalLine.test.ts +385 -0
- package/src/modules/finance-ledger/command/updateJournalLine.ts +155 -0
- package/src/modules/finance-ledger/command/verifySubledgerTransfers.generated.ts +6 -0
- package/src/modules/finance-ledger/command/verifySubledgerTransfers.test.ts +201 -0
- package/src/modules/finance-ledger/command/verifySubledgerTransfers.ts +113 -0
- package/src/modules/finance-ledger/command/verifyTrialBalance.generated.ts +6 -0
- package/src/modules/finance-ledger/command/verifyTrialBalance.test.ts +136 -0
- package/src/modules/finance-ledger/command/verifyTrialBalance.ts +97 -0
- package/src/modules/finance-ledger/db/.gitkeep +0 -0
- package/src/modules/finance-ledger/db/accountingPeriod.ts +58 -0
- package/src/modules/finance-ledger/db/financialStatement.ts +92 -0
- package/src/modules/finance-ledger/db/financialStatementLineItem.ts +76 -0
- package/src/modules/finance-ledger/db/fiscalYear.ts +41 -0
- package/src/modules/finance-ledger/db/journalEntry.ts +101 -0
- package/src/modules/finance-ledger/db/journalLine.ts +64 -0
- package/src/modules/finance-ledger/db/periodClose.ts +97 -0
- package/src/modules/finance-ledger/db/trialBalance.ts +63 -0
- package/src/modules/finance-ledger/db/trialBalanceLine.ts +63 -0
- package/src/modules/finance-ledger/docs/commands/AddJournalLine.md +74 -0
- package/src/modules/finance-ledger/docs/commands/ApproveAndLockPeriod.md +53 -0
- package/src/modules/finance-ledger/docs/commands/BeginClose.md +47 -0
- package/src/modules/finance-ledger/docs/commands/ClosePeriod.md +45 -0
- package/src/modules/finance-ledger/docs/commands/CreateAccountingPeriod.md +69 -0
- package/src/modules/finance-ledger/docs/commands/CreateFiscalYear.md +56 -0
- package/src/modules/finance-ledger/docs/commands/CreateJournalEntry.md +63 -0
- package/src/modules/finance-ledger/docs/commands/DeleteAccountingPeriod.md +46 -0
- package/src/modules/finance-ledger/docs/commands/DeleteFiscalYear.md +40 -0
- package/src/modules/finance-ledger/docs/commands/DeleteJournalEntry.md +44 -0
- package/src/modules/finance-ledger/docs/commands/ExecuteYearEndClose.md +81 -0
- package/src/modules/finance-ledger/docs/commands/FinalCloseAndLockPeriod.md +49 -0
- package/src/modules/finance-ledger/docs/commands/FinalizeFinancialStatement.md +43 -0
- package/src/modules/finance-ledger/docs/commands/GenerateFinancialStatement.md +86 -0
- package/src/modules/finance-ledger/docs/commands/GeneratePreliminaryStatements.md +53 -0
- package/src/modules/finance-ledger/docs/commands/GenerateTrialBalance.md +75 -0
- package/src/modules/finance-ledger/docs/commands/InitiatePeriodClose.md +58 -0
- package/src/modules/finance-ledger/docs/commands/OpenForAdvanceEntry.md +44 -0
- package/src/modules/finance-ledger/docs/commands/OpenPeriod.md +45 -0
- package/src/modules/finance-ledger/docs/commands/PermanentlyClosePeriod.md +45 -0
- package/src/modules/finance-ledger/docs/commands/PostAdjustingEntries.md +61 -0
- package/src/modules/finance-ledger/docs/commands/PostJournalEntry.md +81 -0
- package/src/modules/finance-ledger/docs/commands/ProcessInventoryHandoff.md +72 -0
- package/src/modules/finance-ledger/docs/commands/ProcessManufacturingHandoff.md +68 -0
- package/src/modules/finance-ledger/docs/commands/ProcessPurchaseHandoff.md +68 -0
- package/src/modules/finance-ledger/docs/commands/ProcessSalesHandoff.md +71 -0
- package/src/modules/finance-ledger/docs/commands/RegenerateFinancialStatement.md +60 -0
- package/src/modules/finance-ledger/docs/commands/RemoveJournalLine.md +42 -0
- package/src/modules/finance-ledger/docs/commands/ReopenPeriod.md +45 -0
- package/src/modules/finance-ledger/docs/commands/ReverseJournalEntry.md +62 -0
- package/src/modules/finance-ledger/docs/commands/RevertSoftLock.md +49 -0
- package/src/modules/finance-ledger/docs/commands/UpdateFiscalYear.md +60 -0
- package/src/modules/finance-ledger/docs/commands/UpdateJournalEntry.md +50 -0
- package/src/modules/finance-ledger/docs/commands/UpdateJournalLine.md +61 -0
- package/src/modules/finance-ledger/docs/commands/VerifySubledgerTransfers.md +59 -0
- package/src/modules/finance-ledger/docs/commands/VerifyTrialBalance.md +53 -0
- package/src/modules/finance-ledger/docs/features/accounting-period-management.md +110 -0
- package/src/modules/finance-ledger/docs/features/financial-statement-generation.md +115 -0
- package/src/modules/finance-ledger/docs/features/journal-entry-management.md +138 -0
- package/src/modules/finance-ledger/docs/features/period-end-close.md +102 -0
- package/src/modules/finance-ledger/docs/features/subledger-integration.md +141 -0
- package/src/modules/finance-ledger/docs/features/trial-balance.md +99 -0
- package/src/modules/finance-ledger/docs/features/year-end-close.md +84 -0
- package/src/modules/finance-ledger/docs/models/AccountingPeriod.md +71 -0
- package/src/modules/finance-ledger/docs/models/FinancialStatement.md +76 -0
- package/src/modules/finance-ledger/docs/models/FinancialStatementLineItem.md +41 -0
- package/src/modules/finance-ledger/docs/models/FiscalYear.md +41 -0
- package/src/modules/finance-ledger/docs/models/JournalEntry.md +80 -0
- package/src/modules/finance-ledger/docs/models/JournalLine.md +47 -0
- package/src/modules/finance-ledger/docs/models/PeriodClose.md +83 -0
- package/src/modules/finance-ledger/docs/models/TrialBalance.md +56 -0
- package/src/modules/finance-ledger/docs/models/TrialBalanceLine.md +37 -0
- package/src/modules/finance-ledger/docs/queries/GetAccountingPeriod.md +35 -0
- package/src/modules/finance-ledger/docs/queries/GetFinancialStatement.md +38 -0
- package/src/modules/finance-ledger/docs/queries/GetFiscalYear.md +35 -0
- package/src/modules/finance-ledger/docs/queries/GetJournalEntry.md +37 -0
- package/src/modules/finance-ledger/docs/queries/GetPeriodByDate.md +38 -0
- package/src/modules/finance-ledger/docs/queries/GetPeriodClose.md +36 -0
- package/src/modules/finance-ledger/docs/queries/GetSubledgerTransferStatus.md +45 -0
- package/src/modules/finance-ledger/docs/queries/GetTrialBalance.md +38 -0
- package/src/modules/finance-ledger/docs/queries/ListAccountingPeriods.md +46 -0
- package/src/modules/finance-ledger/docs/queries/ListFinancialStatements.md +46 -0
- package/src/modules/finance-ledger/docs/queries/ListFiscalYears.md +42 -0
- package/src/modules/finance-ledger/docs/queries/ListJournalEntries.md +48 -0
- package/src/modules/finance-ledger/docs/queries/ListPeriodCloses.md +46 -0
- package/src/modules/finance-ledger/docs/queries/ListTrialBalances.md +51 -0
- package/src/modules/finance-ledger/executor/.gitkeep +0 -0
- package/src/modules/finance-ledger/generated/enums.ts +109 -0
- package/src/modules/finance-ledger/generated/kysely-tailordb.ts +202 -0
- package/src/modules/finance-ledger/index.ts +2 -0
- package/src/modules/finance-ledger/lib/_db_deps.ts +56 -0
- package/src/modules/finance-ledger/lib/errors.generated.ts +332 -0
- package/src/modules/finance-ledger/lib/permissions.generated.ts +41 -0
- package/src/modules/finance-ledger/lib/types.ts +66 -0
- package/src/modules/finance-ledger/module.ts +262 -0
- package/src/modules/finance-ledger/package.json +26 -0
- package/src/modules/finance-ledger/permissions.ts +3 -0
- package/src/modules/finance-ledger/query/.gitkeep +0 -0
- package/src/modules/finance-ledger/query/getAccountingPeriod.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getAccountingPeriod.test.ts +31 -0
- package/src/modules/finance-ledger/query/getAccountingPeriod.ts +21 -0
- package/src/modules/finance-ledger/query/getFinancialStatement.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getFinancialStatement.test.ts +35 -0
- package/src/modules/finance-ledger/query/getFinancialStatement.ts +29 -0
- package/src/modules/finance-ledger/query/getFiscalYear.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getFiscalYear.test.ts +31 -0
- package/src/modules/finance-ledger/query/getFiscalYear.ts +21 -0
- package/src/modules/finance-ledger/query/getJournalEntry.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getJournalEntry.test.ts +35 -0
- package/src/modules/finance-ledger/query/getJournalEntry.ts +29 -0
- package/src/modules/finance-ledger/query/getPeriodByDate.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getPeriodByDate.test.ts +53 -0
- package/src/modules/finance-ledger/query/getPeriodByDate.ts +27 -0
- package/src/modules/finance-ledger/query/getPeriodClose.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getPeriodClose.test.ts +31 -0
- package/src/modules/finance-ledger/query/getPeriodClose.ts +21 -0
- package/src/modules/finance-ledger/query/getSubledgerTransferStatus.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getSubledgerTransferStatus.test.ts +101 -0
- package/src/modules/finance-ledger/query/getSubledgerTransferStatus.ts +68 -0
- package/src/modules/finance-ledger/query/getTrialBalance.generated.ts +5 -0
- package/src/modules/finance-ledger/query/getTrialBalance.test.ts +33 -0
- package/src/modules/finance-ledger/query/getTrialBalance.ts +30 -0
- package/src/modules/finance-ledger/query/listAccountingPeriods.generated.ts +5 -0
- package/src/modules/finance-ledger/query/listAccountingPeriods.test.ts +81 -0
- package/src/modules/finance-ledger/query/listAccountingPeriods.ts +61 -0
- package/src/modules/finance-ledger/query/listFinancialStatements.generated.ts +5 -0
- package/src/modules/finance-ledger/query/listFinancialStatements.test.ts +76 -0
- package/src/modules/finance-ledger/query/listFinancialStatements.ts +62 -0
- package/src/modules/finance-ledger/query/listFiscalYears.generated.ts +5 -0
- package/src/modules/finance-ledger/query/listFiscalYears.test.ts +63 -0
- package/src/modules/finance-ledger/query/listFiscalYears.ts +45 -0
- package/src/modules/finance-ledger/query/listJournalEntries.generated.ts +5 -0
- package/src/modules/finance-ledger/query/listJournalEntries.test.ts +91 -0
- package/src/modules/finance-ledger/query/listJournalEntries.ts +64 -0
- package/src/modules/finance-ledger/query/listPeriodCloses.generated.ts +5 -0
- package/src/modules/finance-ledger/query/listPeriodCloses.test.ts +63 -0
- package/src/modules/finance-ledger/query/listPeriodCloses.ts +64 -0
- package/src/modules/finance-ledger/query/listTrialBalances.generated.ts +5 -0
- package/src/modules/finance-ledger/query/listTrialBalances.test.ts +78 -0
- package/src/modules/finance-ledger/query/listTrialBalances.ts +56 -0
- package/src/modules/finance-ledger/seed/index.ts +19 -0
- package/src/modules/finance-ledger/tailor.config.ts +13 -0
- package/src/modules/finance-ledger/tailor.d.ts +13 -0
- package/src/modules/finance-ledger/testing/commandTestUtils.ts +35 -0
- package/src/modules/finance-ledger/testing/fixtures.ts +382 -0
- package/src/modules/finance-ledger/tsconfig.json +16 -0
- package/src/progress/schema.test.ts +161 -0
- package/src/progress/schema.ts +316 -0
- package/templates/scaffold/app/backend/package.json +1 -3
- package/templates/scaffold/app/backend/vitest.config.ts +4 -21
- 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
|
+
}
|