@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,73 @@
|
|
|
1
|
+
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
2
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
|
+
import {
|
|
4
|
+
FinancialStatementNotFoundError,
|
|
5
|
+
InvalidStatusTransitionError,
|
|
6
|
+
} from "../lib/errors.generated";
|
|
7
|
+
|
|
8
|
+
export interface FinalizeFinancialStatementInput {
|
|
9
|
+
id: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface AuditEntry {
|
|
13
|
+
type: string;
|
|
14
|
+
from?: string;
|
|
15
|
+
to?: string;
|
|
16
|
+
actorId?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Function: finalizeFinancialStatement
|
|
21
|
+
*
|
|
22
|
+
* Transitions a financial statement from DRAFT to FINALIZED status.
|
|
23
|
+
* Once finalized, the statement becomes immutable and cannot be
|
|
24
|
+
* modified or regenerated.
|
|
25
|
+
*/
|
|
26
|
+
export async function run(
|
|
27
|
+
db: Transaction,
|
|
28
|
+
input: FinalizeFinancialStatementInput,
|
|
29
|
+
ctx: CommandContext,
|
|
30
|
+
) {
|
|
31
|
+
const { id } = input;
|
|
32
|
+
const auditTrail: AuditEntry[] = [];
|
|
33
|
+
|
|
34
|
+
// 1. Find FinancialStatement by id with forUpdate()
|
|
35
|
+
const financialStatement = await db
|
|
36
|
+
.selectFrom("FinancialStatement")
|
|
37
|
+
.selectAll()
|
|
38
|
+
.where("id", "=", id)
|
|
39
|
+
.forUpdate()
|
|
40
|
+
.executeTakeFirst();
|
|
41
|
+
|
|
42
|
+
if (!financialStatement) {
|
|
43
|
+
return err(new FinancialStatementNotFoundError(id));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Validate status is DRAFT
|
|
47
|
+
if (financialStatement.status !== "DRAFT") {
|
|
48
|
+
return err(new InvalidStatusTransitionError(id));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Update to FINALIZED, set finalizedAt to now
|
|
52
|
+
const now = new Date();
|
|
53
|
+
const updatedStatement = await db
|
|
54
|
+
.updateTable("FinancialStatement")
|
|
55
|
+
.set({
|
|
56
|
+
status: "FINALIZED",
|
|
57
|
+
finalizedAt: now,
|
|
58
|
+
updatedAt: now,
|
|
59
|
+
})
|
|
60
|
+
.where("id", "=", id)
|
|
61
|
+
.returningAll()
|
|
62
|
+
.executeTakeFirst();
|
|
63
|
+
|
|
64
|
+
// 4. Record audit event
|
|
65
|
+
auditTrail.push({
|
|
66
|
+
type: "STATUS_TRANSITION",
|
|
67
|
+
from: "DRAFT",
|
|
68
|
+
to: "FINALIZED",
|
|
69
|
+
actorId: ctx.actorId,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return ok({ financialStatement: updatedStatement!, auditTrail });
|
|
73
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
3
|
+
import { permissions } from "../lib/permissions.generated";
|
|
4
|
+
import { run } from "./generateFinancialStatement";
|
|
5
|
+
|
|
6
|
+
export const generateFinancialStatement = defineCommand(permissions.generateFinancialStatement, run);
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
CompanyNotFoundError,
|
|
6
|
+
ChartOfAccountsNotFoundError,
|
|
7
|
+
InvalidStatementTypeError,
|
|
8
|
+
InvalidReportingParametersError,
|
|
9
|
+
NoTrialBalanceDataError,
|
|
10
|
+
ComparativeDataUnavailableError,
|
|
11
|
+
} from "../lib/errors.generated";
|
|
12
|
+
import {
|
|
13
|
+
baseCompany,
|
|
14
|
+
baseChartOfAccounts,
|
|
15
|
+
baseTrialBalance,
|
|
16
|
+
baseTrialBalanceLine,
|
|
17
|
+
baseFinancialStatement,
|
|
18
|
+
baseAccount,
|
|
19
|
+
revenueAccount,
|
|
20
|
+
expenseAccount,
|
|
21
|
+
} from "../testing/fixtures";
|
|
22
|
+
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
|
|
23
|
+
import { run, type GenerateFinancialStatementInput } from "./generateFinancialStatement";
|
|
24
|
+
|
|
25
|
+
describe("generateFinancialStatement", () => {
|
|
26
|
+
const balanceSheetInput: GenerateFinancialStatementInput = {
|
|
27
|
+
companyId: baseCompany.id,
|
|
28
|
+
chartOfAccountsId: baseChartOfAccounts.id,
|
|
29
|
+
trialBalanceId: baseTrialBalance.id,
|
|
30
|
+
statementType: "BALANCE_SHEET",
|
|
31
|
+
reportingDate: new Date("2024-01-31"),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const profitAndLossInput: GenerateFinancialStatementInput = {
|
|
35
|
+
companyId: baseCompany.id,
|
|
36
|
+
chartOfAccountsId: baseChartOfAccounts.id,
|
|
37
|
+
trialBalanceId: baseTrialBalance.id,
|
|
38
|
+
statementType: "PROFIT_AND_LOSS",
|
|
39
|
+
startDate: new Date("2024-01-01"),
|
|
40
|
+
endDate: new Date("2024-01-31"),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
it("returns error when company does not exist", async () => {
|
|
44
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
45
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
46
|
+
|
|
47
|
+
const result = await run(db, { ...balanceSheetInput, companyId: "missing" }, commandCtx);
|
|
48
|
+
|
|
49
|
+
expectErr(result, CompanyNotFoundError);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns error when company is not ACTIVE", async () => {
|
|
53
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
54
|
+
spies.select.mockReturnValueOnce({ ...baseCompany, status: "INACTIVE" });
|
|
55
|
+
|
|
56
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
57
|
+
|
|
58
|
+
expectErr(result, CompanyNotFoundError);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns error when CoA does not exist", async () => {
|
|
62
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
63
|
+
spies.select
|
|
64
|
+
.mockReturnValueOnce(baseCompany) // company lookup
|
|
65
|
+
.mockReturnValueOnce(undefined); // CoA lookup
|
|
66
|
+
|
|
67
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
68
|
+
|
|
69
|
+
expectErr(result, ChartOfAccountsNotFoundError);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("returns error when CoA is not ACTIVE", async () => {
|
|
73
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
74
|
+
spies.select
|
|
75
|
+
.mockReturnValueOnce(baseCompany)
|
|
76
|
+
.mockReturnValueOnce({ ...baseChartOfAccounts, status: "INACTIVE" });
|
|
77
|
+
|
|
78
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
79
|
+
|
|
80
|
+
expectErr(result, ChartOfAccountsNotFoundError);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns error for invalid statement type", async () => {
|
|
84
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
85
|
+
spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(baseChartOfAccounts);
|
|
86
|
+
|
|
87
|
+
const result = await run(
|
|
88
|
+
db,
|
|
89
|
+
{ ...balanceSheetInput, statementType: "INVALID_TYPE" },
|
|
90
|
+
commandCtx,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expectErr(result, InvalidStatementTypeError);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns error when BALANCE_SHEET is missing reportingDate", async () => {
|
|
97
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
98
|
+
spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(baseChartOfAccounts);
|
|
99
|
+
|
|
100
|
+
const input: GenerateFinancialStatementInput = {
|
|
101
|
+
...balanceSheetInput,
|
|
102
|
+
reportingDate: undefined,
|
|
103
|
+
};
|
|
104
|
+
const result = await run(db, input, commandCtx);
|
|
105
|
+
|
|
106
|
+
expectErr(result, InvalidReportingParametersError);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("returns error when PROFIT_AND_LOSS is missing date range", async () => {
|
|
110
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
111
|
+
spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(baseChartOfAccounts);
|
|
112
|
+
|
|
113
|
+
const input: GenerateFinancialStatementInput = {
|
|
114
|
+
companyId: baseCompany.id,
|
|
115
|
+
chartOfAccountsId: baseChartOfAccounts.id,
|
|
116
|
+
trialBalanceId: baseTrialBalance.id,
|
|
117
|
+
statementType: "PROFIT_AND_LOSS",
|
|
118
|
+
};
|
|
119
|
+
const result = await run(db, input, commandCtx);
|
|
120
|
+
|
|
121
|
+
expectErr(result, InvalidReportingParametersError);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("returns error when CASH_FLOW is missing cashFlowMethod", async () => {
|
|
125
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
126
|
+
spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(baseChartOfAccounts);
|
|
127
|
+
|
|
128
|
+
const input: GenerateFinancialStatementInput = {
|
|
129
|
+
companyId: baseCompany.id,
|
|
130
|
+
chartOfAccountsId: baseChartOfAccounts.id,
|
|
131
|
+
trialBalanceId: baseTrialBalance.id,
|
|
132
|
+
statementType: "CASH_FLOW",
|
|
133
|
+
startDate: new Date("2024-01-01"),
|
|
134
|
+
endDate: new Date("2024-01-31"),
|
|
135
|
+
};
|
|
136
|
+
const result = await run(db, input, commandCtx);
|
|
137
|
+
|
|
138
|
+
expectErr(result, InvalidReportingParametersError);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("returns error when trial balance does not exist", async () => {
|
|
142
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
143
|
+
spies.select
|
|
144
|
+
.mockReturnValueOnce(baseCompany) // company
|
|
145
|
+
.mockReturnValueOnce(baseChartOfAccounts) // CoA
|
|
146
|
+
.mockReturnValueOnce(undefined); // trial balance
|
|
147
|
+
|
|
148
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
149
|
+
|
|
150
|
+
expectErr(result, NoTrialBalanceDataError);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("returns error when comparative trial balance does not exist", async () => {
|
|
154
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
155
|
+
spies.select
|
|
156
|
+
.mockReturnValueOnce(baseCompany) // company
|
|
157
|
+
.mockReturnValueOnce(baseChartOfAccounts) // CoA
|
|
158
|
+
.mockReturnValueOnce(baseTrialBalance) // trial balance
|
|
159
|
+
.mockReturnValueOnce(undefined); // comparative trial balance
|
|
160
|
+
|
|
161
|
+
const result = await run(
|
|
162
|
+
db,
|
|
163
|
+
{ ...balanceSheetInput, comparativePeriodTrialBalanceId: "missing-comparative" },
|
|
164
|
+
commandCtx,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expectErr(result, ComparativeDataUnavailableError);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("successfully generates a BALANCE_SHEET financial statement", async () => {
|
|
171
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
172
|
+
spies.select
|
|
173
|
+
.mockReturnValueOnce(baseCompany) // company
|
|
174
|
+
.mockReturnValueOnce(baseChartOfAccounts) // CoA
|
|
175
|
+
.mockReturnValueOnce(baseTrialBalance) // trial balance
|
|
176
|
+
.mockReturnValueOnce([baseTrialBalanceLine]) // trial balance lines (execute)
|
|
177
|
+
.mockReturnValueOnce(baseAccount); // account lookup for line
|
|
178
|
+
spies.insert
|
|
179
|
+
.mockReturnValueOnce(baseFinancialStatement) // FinancialStatement insert
|
|
180
|
+
.mockReturnValueOnce({
|
|
181
|
+
...baseTrialBalanceLine,
|
|
182
|
+
id: "fs-line-1",
|
|
183
|
+
financialStatementId: baseFinancialStatement.id,
|
|
184
|
+
}) // DETAIL line item insert
|
|
185
|
+
.mockReturnValueOnce({
|
|
186
|
+
id: "fs-line-subtotal-1",
|
|
187
|
+
financialStatementId: baseFinancialStatement.id,
|
|
188
|
+
lineType: "SUBTOTAL",
|
|
189
|
+
}); // SUBTOTAL line item insert
|
|
190
|
+
|
|
191
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
192
|
+
|
|
193
|
+
const value = expectOk(result);
|
|
194
|
+
expect(value.financialStatement).toEqual(baseFinancialStatement);
|
|
195
|
+
expect(value.auditTrail).toBeDefined();
|
|
196
|
+
expect(value.auditTrail.length).toBeGreaterThan(0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("successfully generates a PROFIT_AND_LOSS financial statement", async () => {
|
|
200
|
+
const plTrialBalanceLine = {
|
|
201
|
+
...baseTrialBalanceLine,
|
|
202
|
+
id: "tb-line-revenue",
|
|
203
|
+
accountId: revenueAccount.id,
|
|
204
|
+
accountCode: revenueAccount.code,
|
|
205
|
+
accountName: revenueAccount.name,
|
|
206
|
+
closingBalance: 8000,
|
|
207
|
+
};
|
|
208
|
+
const plExpenseLine = {
|
|
209
|
+
...baseTrialBalanceLine,
|
|
210
|
+
id: "tb-line-expense",
|
|
211
|
+
accountId: expenseAccount.id,
|
|
212
|
+
accountCode: expenseAccount.code,
|
|
213
|
+
accountName: expenseAccount.name,
|
|
214
|
+
closingBalance: 3000,
|
|
215
|
+
};
|
|
216
|
+
const plFinancialStatement = {
|
|
217
|
+
...baseFinancialStatement,
|
|
218
|
+
id: "financial-statement-pl",
|
|
219
|
+
statementType: "PROFIT_AND_LOSS",
|
|
220
|
+
reportingDate: null,
|
|
221
|
+
startDate: new Date("2024-01-01"),
|
|
222
|
+
endDate: new Date("2024-01-31"),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
226
|
+
spies.select
|
|
227
|
+
.mockReturnValueOnce(baseCompany) // company
|
|
228
|
+
.mockReturnValueOnce(baseChartOfAccounts) // CoA
|
|
229
|
+
.mockReturnValueOnce(baseTrialBalance) // trial balance
|
|
230
|
+
.mockReturnValueOnce([plTrialBalanceLine, plExpenseLine]) // trial balance lines
|
|
231
|
+
.mockReturnValueOnce(revenueAccount) // account lookup for revenue line
|
|
232
|
+
.mockReturnValueOnce(expenseAccount); // account lookup for expense line
|
|
233
|
+
spies.insert
|
|
234
|
+
.mockReturnValueOnce(plFinancialStatement) // FinancialStatement insert
|
|
235
|
+
.mockReturnValueOnce({}) // DETAIL line item 1 (revenue)
|
|
236
|
+
.mockReturnValueOnce({}) // DETAIL line item 2 (expense)
|
|
237
|
+
.mockReturnValueOnce({}) // SUBTOTAL line item for Revenue
|
|
238
|
+
.mockReturnValueOnce({}); // SUBTOTAL line item for Expenses
|
|
239
|
+
|
|
240
|
+
const result = await run(db, profitAndLossInput, commandCtx);
|
|
241
|
+
|
|
242
|
+
const value = expectOk(result);
|
|
243
|
+
expect(value.financialStatement.statementType).toBe("PROFIT_AND_LOSS");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("returns error when trial balance has no lines", async () => {
|
|
247
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
248
|
+
spies.select
|
|
249
|
+
.mockReturnValueOnce(baseCompany) // company
|
|
250
|
+
.mockReturnValueOnce(baseChartOfAccounts) // CoA
|
|
251
|
+
.mockReturnValueOnce(baseTrialBalance) // trial balance exists
|
|
252
|
+
.mockReturnValueOnce([]); // but has no lines
|
|
253
|
+
|
|
254
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
255
|
+
|
|
256
|
+
expectErr(result, NoTrialBalanceDataError);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("emits audit event recording the generation", async () => {
|
|
260
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
261
|
+
spies.select
|
|
262
|
+
.mockReturnValueOnce(baseCompany) // company
|
|
263
|
+
.mockReturnValueOnce(baseChartOfAccounts) // CoA
|
|
264
|
+
.mockReturnValueOnce(baseTrialBalance) // trial balance
|
|
265
|
+
.mockReturnValueOnce([baseTrialBalanceLine]) // trial balance lines
|
|
266
|
+
.mockReturnValueOnce(baseAccount); // account lookup for line
|
|
267
|
+
spies.insert
|
|
268
|
+
.mockReturnValueOnce(baseFinancialStatement) // FinancialStatement insert
|
|
269
|
+
.mockReturnValueOnce({}) // DETAIL line item
|
|
270
|
+
.mockReturnValueOnce({}); // SUBTOTAL line item
|
|
271
|
+
|
|
272
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
273
|
+
|
|
274
|
+
const value = expectOk(result);
|
|
275
|
+
expect(value.auditTrail).toEqual([
|
|
276
|
+
{
|
|
277
|
+
type: "FINANCIAL_STATEMENT_GENERATED",
|
|
278
|
+
statementType: "BALANCE_SHEET",
|
|
279
|
+
actorId: commandCtx.actorId,
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("generates subtotal lines for each section", async () => {
|
|
285
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
286
|
+
spies.select
|
|
287
|
+
.mockReturnValueOnce(baseCompany) // company
|
|
288
|
+
.mockReturnValueOnce(baseChartOfAccounts) // CoA
|
|
289
|
+
.mockReturnValueOnce(baseTrialBalance) // trial balance
|
|
290
|
+
.mockReturnValueOnce([baseTrialBalanceLine]) // trial balance lines
|
|
291
|
+
.mockReturnValueOnce(baseAccount); // account lookup for line
|
|
292
|
+
const subtotalLineItem = {
|
|
293
|
+
id: "fs-line-subtotal-1",
|
|
294
|
+
financialStatementId: baseFinancialStatement.id,
|
|
295
|
+
lineType: "SUBTOTAL",
|
|
296
|
+
label: "Total Assets",
|
|
297
|
+
amount: 3000,
|
|
298
|
+
sectionType: "Assets",
|
|
299
|
+
};
|
|
300
|
+
spies.insert
|
|
301
|
+
.mockReturnValueOnce(baseFinancialStatement) // FinancialStatement insert
|
|
302
|
+
.mockReturnValueOnce({}) // DETAIL line item
|
|
303
|
+
.mockReturnValueOnce(subtotalLineItem); // SUBTOTAL line item
|
|
304
|
+
|
|
305
|
+
const result = await run(db, balanceSheetInput, commandCtx);
|
|
306
|
+
|
|
307
|
+
expectOk(result);
|
|
308
|
+
// Verify insert was called for both DETAIL and SUBTOTAL lines (2 line item inserts)
|
|
309
|
+
expect(spies.insert).toHaveBeenCalledTimes(3); // 1 statement + 2 line items
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
2
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
|
+
import {
|
|
4
|
+
CompanyNotFoundError,
|
|
5
|
+
ChartOfAccountsNotFoundError,
|
|
6
|
+
InvalidStatementTypeError,
|
|
7
|
+
InvalidReportingParametersError,
|
|
8
|
+
NoTrialBalanceDataError,
|
|
9
|
+
ComparativeDataUnavailableError,
|
|
10
|
+
} from "../lib/errors.generated";
|
|
11
|
+
|
|
12
|
+
export interface GenerateFinancialStatementInput {
|
|
13
|
+
companyId: string;
|
|
14
|
+
chartOfAccountsId: string;
|
|
15
|
+
trialBalanceId: string;
|
|
16
|
+
statementType: string;
|
|
17
|
+
reportingDate?: Date;
|
|
18
|
+
startDate?: Date;
|
|
19
|
+
endDate?: Date;
|
|
20
|
+
cashFlowMethod?: string;
|
|
21
|
+
comparativePeriodTrialBalanceId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const VALID_STATEMENT_TYPES = ["BALANCE_SHEET", "PROFIT_AND_LOSS", "CASH_FLOW"] as const;
|
|
25
|
+
|
|
26
|
+
const BALANCE_SHEET_CLASSIFICATIONS = ["ASSET", "LIABILITY", "EQUITY", "RETAINED_EARNINGS"];
|
|
27
|
+
const PROFIT_AND_LOSS_CLASSIFICATIONS = ["REVENUE", "EXPENSE"];
|
|
28
|
+
const CASH_FLOW_CLASSIFICATIONS = ["ASSET", "LIABILITY", "REVENUE", "EXPENSE"];
|
|
29
|
+
|
|
30
|
+
function getClassificationsForType(statementType: string): string[] {
|
|
31
|
+
switch (statementType) {
|
|
32
|
+
case "BALANCE_SHEET":
|
|
33
|
+
return BALANCE_SHEET_CLASSIFICATIONS;
|
|
34
|
+
case "PROFIT_AND_LOSS":
|
|
35
|
+
return PROFIT_AND_LOSS_CLASSIFICATIONS;
|
|
36
|
+
case "CASH_FLOW":
|
|
37
|
+
return CASH_FLOW_CLASSIFICATIONS;
|
|
38
|
+
default:
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getSectionType(classification: string, statementType: string): string {
|
|
44
|
+
if (statementType === "BALANCE_SHEET") {
|
|
45
|
+
if (classification === "ASSET") return "Assets";
|
|
46
|
+
if (classification === "LIABILITY") return "Liabilities";
|
|
47
|
+
return "Equity";
|
|
48
|
+
}
|
|
49
|
+
if (statementType === "PROFIT_AND_LOSS") {
|
|
50
|
+
if (classification === "REVENUE") return "Revenue";
|
|
51
|
+
return "Expenses";
|
|
52
|
+
}
|
|
53
|
+
// CASH_FLOW
|
|
54
|
+
if (classification === "REVENUE" || classification === "EXPENSE") return "Operating Activities";
|
|
55
|
+
if (classification === "ASSET") return "Investing Activities";
|
|
56
|
+
return "Financing Activities";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface AuditEntry {
|
|
60
|
+
type: string;
|
|
61
|
+
statementType?: string;
|
|
62
|
+
actorId?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function run(
|
|
66
|
+
db: Transaction,
|
|
67
|
+
input: GenerateFinancialStatementInput,
|
|
68
|
+
ctx: CommandContext,
|
|
69
|
+
) {
|
|
70
|
+
const auditTrail: AuditEntry[] = [];
|
|
71
|
+
|
|
72
|
+
// 1. Validate company exists and is ACTIVE
|
|
73
|
+
const company = await db
|
|
74
|
+
.selectFrom("Company" as never)
|
|
75
|
+
.where("id" as never, "=", input.companyId as never)
|
|
76
|
+
.selectAll()
|
|
77
|
+
.executeTakeFirst();
|
|
78
|
+
|
|
79
|
+
if (!company || (company as Record<string, unknown>).status !== "ACTIVE") {
|
|
80
|
+
return err(new CompanyNotFoundError(input.companyId));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 2. Validate CoA exists and is ACTIVE
|
|
84
|
+
const coa = await db
|
|
85
|
+
.selectFrom("ChartOfAccounts" as never)
|
|
86
|
+
.where("id" as never, "=", input.chartOfAccountsId as never)
|
|
87
|
+
.selectAll()
|
|
88
|
+
.executeTakeFirst();
|
|
89
|
+
|
|
90
|
+
if (!coa || (coa as Record<string, unknown>).status !== "ACTIVE") {
|
|
91
|
+
return err(new ChartOfAccountsNotFoundError(input.chartOfAccountsId));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. Validate statementType is valid
|
|
95
|
+
if (
|
|
96
|
+
!VALID_STATEMENT_TYPES.includes(input.statementType as (typeof VALID_STATEMENT_TYPES)[number])
|
|
97
|
+
) {
|
|
98
|
+
return err(new InvalidStatementTypeError(input.statementType));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 4. Validate reporting parameters
|
|
102
|
+
if (input.statementType === "BALANCE_SHEET") {
|
|
103
|
+
if (!input.reportingDate) {
|
|
104
|
+
return err(new InvalidReportingParametersError(input.statementType));
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// PROFIT_AND_LOSS and CASH_FLOW need startDate + endDate
|
|
108
|
+
if (!input.startDate || !input.endDate) {
|
|
109
|
+
return err(new InvalidReportingParametersError(input.statementType));
|
|
110
|
+
}
|
|
111
|
+
if (input.statementType === "CASH_FLOW" && !input.cashFlowMethod) {
|
|
112
|
+
return err(new InvalidReportingParametersError(input.statementType));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 5. Validate trial balance exists
|
|
117
|
+
const trialBalance = await db
|
|
118
|
+
.selectFrom("TrialBalance" as never)
|
|
119
|
+
.where("id" as never, "=", input.trialBalanceId as never)
|
|
120
|
+
.selectAll()
|
|
121
|
+
.executeTakeFirst();
|
|
122
|
+
|
|
123
|
+
if (!trialBalance) {
|
|
124
|
+
return err(new NoTrialBalanceDataError(input.trialBalanceId));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 6. If comparativePeriodTrialBalanceId, validate it exists
|
|
128
|
+
if (input.comparativePeriodTrialBalanceId) {
|
|
129
|
+
const comparativeTb = await db
|
|
130
|
+
.selectFrom("TrialBalance" as never)
|
|
131
|
+
.where("id" as never, "=", input.comparativePeriodTrialBalanceId as never)
|
|
132
|
+
.selectAll()
|
|
133
|
+
.executeTakeFirst();
|
|
134
|
+
|
|
135
|
+
if (!comparativeTb) {
|
|
136
|
+
return err(new ComparativeDataUnavailableError(input.comparativePeriodTrialBalanceId));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 7. Fetch trial balance lines
|
|
141
|
+
const trialBalanceLines = (await db
|
|
142
|
+
.selectFrom("TrialBalanceLine" as never)
|
|
143
|
+
.where("trialBalanceId" as never, "=", input.trialBalanceId as never)
|
|
144
|
+
.selectAll()
|
|
145
|
+
.execute()) as Record<string, unknown>[];
|
|
146
|
+
|
|
147
|
+
if (trialBalanceLines.length === 0) {
|
|
148
|
+
return err(new NoTrialBalanceDataError(input.trialBalanceId));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fetch comparative lines if applicable
|
|
152
|
+
let comparativeLines: Record<string, unknown>[] = [];
|
|
153
|
+
if (input.comparativePeriodTrialBalanceId) {
|
|
154
|
+
comparativeLines = (await db
|
|
155
|
+
.selectFrom("TrialBalanceLine" as never)
|
|
156
|
+
.where("trialBalanceId" as never, "=", input.comparativePeriodTrialBalanceId as never)
|
|
157
|
+
.selectAll()
|
|
158
|
+
.execute()) as Record<string, unknown>[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Build a map of comparative amounts by accountId
|
|
162
|
+
const comparativeMap = new Map<string, number>();
|
|
163
|
+
for (const line of comparativeLines) {
|
|
164
|
+
comparativeMap.set(line.accountId as string, line.closingBalance as number);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 8. Fetch accounts to get classification info
|
|
168
|
+
const classifications = getClassificationsForType(input.statementType);
|
|
169
|
+
|
|
170
|
+
// Build line items from trial balance lines, filtering by account classification
|
|
171
|
+
const now = new Date();
|
|
172
|
+
const lineItems: Record<string, unknown>[] = [];
|
|
173
|
+
let sortOrder = 0;
|
|
174
|
+
|
|
175
|
+
for (const tbLine of trialBalanceLines) {
|
|
176
|
+
// Look up the account to check classification
|
|
177
|
+
const account = await db
|
|
178
|
+
.selectFrom("Account" as never)
|
|
179
|
+
.where("id" as never, "=", tbLine.accountId as never)
|
|
180
|
+
.selectAll()
|
|
181
|
+
.executeTakeFirst();
|
|
182
|
+
|
|
183
|
+
if (!account) continue;
|
|
184
|
+
const acct = account as Record<string, unknown>;
|
|
185
|
+
if (!classifications.includes(acct.classification as string)) continue;
|
|
186
|
+
|
|
187
|
+
sortOrder++;
|
|
188
|
+
lineItems.push({
|
|
189
|
+
accountId: tbLine.accountId,
|
|
190
|
+
accountGroupId: tbLine.accountGroupId ?? null,
|
|
191
|
+
label: tbLine.accountName as string,
|
|
192
|
+
amount: tbLine.closingBalance as number,
|
|
193
|
+
comparativeAmount: comparativeMap.get(tbLine.accountId as string) ?? null,
|
|
194
|
+
lineType: "DETAIL",
|
|
195
|
+
sectionType: tbLine.accountGroupId
|
|
196
|
+
? getSectionType(acct.classification as string, input.statementType)
|
|
197
|
+
: "Unclassified",
|
|
198
|
+
sortOrder,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Add SUBTOTAL lines for each section
|
|
203
|
+
const sectionTotals = new Map<string, number>();
|
|
204
|
+
for (const item of lineItems) {
|
|
205
|
+
const current = sectionTotals.get(item.sectionType as string) ?? 0;
|
|
206
|
+
sectionTotals.set(item.sectionType as string, current + (item.amount as number));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const [section, total] of sectionTotals) {
|
|
210
|
+
sortOrder++;
|
|
211
|
+
lineItems.push({
|
|
212
|
+
accountId: null,
|
|
213
|
+
accountGroupId: null,
|
|
214
|
+
label: `Total ${section}`,
|
|
215
|
+
amount: total,
|
|
216
|
+
comparativeAmount: null,
|
|
217
|
+
lineType: "SUBTOTAL",
|
|
218
|
+
sectionType: section,
|
|
219
|
+
sortOrder,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 9. Insert FinancialStatement (DRAFT)
|
|
224
|
+
const financialStatement = await db
|
|
225
|
+
.insertInto("FinancialStatement")
|
|
226
|
+
.values({
|
|
227
|
+
companyId: input.companyId,
|
|
228
|
+
chartOfAccountsId: input.chartOfAccountsId,
|
|
229
|
+
trialBalanceId: input.trialBalanceId,
|
|
230
|
+
comparativePeriodTrialBalanceId: input.comparativePeriodTrialBalanceId ?? null,
|
|
231
|
+
statementType: input.statementType as never,
|
|
232
|
+
status: "DRAFT",
|
|
233
|
+
reportingDate: input.reportingDate ?? null,
|
|
234
|
+
startDate: input.startDate ?? null,
|
|
235
|
+
endDate: input.endDate ?? null,
|
|
236
|
+
cashFlowMethod: input.cashFlowMethod ?? null,
|
|
237
|
+
generatedAt: now,
|
|
238
|
+
finalizedAt: null,
|
|
239
|
+
createdAt: now,
|
|
240
|
+
updatedAt: null,
|
|
241
|
+
})
|
|
242
|
+
.returningAll()
|
|
243
|
+
.executeTakeFirst();
|
|
244
|
+
|
|
245
|
+
// 10. Insert FinancialStatementLineItems
|
|
246
|
+
for (const item of lineItems) {
|
|
247
|
+
await db
|
|
248
|
+
.insertInto("FinancialStatementLineItem")
|
|
249
|
+
.values({
|
|
250
|
+
financialStatementId: financialStatement!.id,
|
|
251
|
+
accountId: item.accountId as string,
|
|
252
|
+
accountGroupId: item.accountGroupId as string | null,
|
|
253
|
+
label: item.label as string,
|
|
254
|
+
amount: item.amount as number,
|
|
255
|
+
comparativeAmount: item.comparativeAmount as number | null,
|
|
256
|
+
lineType: item.lineType as never,
|
|
257
|
+
sectionType: item.sectionType as string,
|
|
258
|
+
sortOrder: item.sortOrder as number,
|
|
259
|
+
createdAt: now,
|
|
260
|
+
updatedAt: null,
|
|
261
|
+
})
|
|
262
|
+
.returningAll()
|
|
263
|
+
.executeTakeFirst();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Record audit event
|
|
267
|
+
auditTrail.push({
|
|
268
|
+
type: "FINANCIAL_STATEMENT_GENERATED",
|
|
269
|
+
statementType: input.statementType,
|
|
270
|
+
actorId: ctx.actorId,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// 11. Return financialStatement
|
|
274
|
+
return ok({ financialStatement: financialStatement!, auditTrail });
|
|
275
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
3
|
+
import { permissions } from "../lib/permissions.generated";
|
|
4
|
+
import { run } from "./generatePreliminaryStatements";
|
|
5
|
+
|
|
6
|
+
export const generatePreliminaryStatements = defineCommand(permissions.generatePreliminaryStatements, run);
|