@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,337 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
JournalEntryNotFoundError,
|
|
6
|
+
InvalidStatusForReversalError,
|
|
7
|
+
AlreadyReversedError,
|
|
8
|
+
InvalidPeriodStatusError,
|
|
9
|
+
} from "../lib/errors.generated";
|
|
10
|
+
import {
|
|
11
|
+
baseJournalEntry,
|
|
12
|
+
postedJournalEntry,
|
|
13
|
+
reversedJournalEntry,
|
|
14
|
+
baseJournalLine,
|
|
15
|
+
baseJournalLine2,
|
|
16
|
+
baseAccountingPeriod,
|
|
17
|
+
futureEnterablePeriod,
|
|
18
|
+
neverOpenedPeriod,
|
|
19
|
+
closedPeriod,
|
|
20
|
+
permanentlyClosedPeriod,
|
|
21
|
+
} from "../testing/fixtures";
|
|
22
|
+
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
|
|
23
|
+
import { run } from "./reverseJournalEntry";
|
|
24
|
+
|
|
25
|
+
const postedLines = [
|
|
26
|
+
{ ...baseJournalLine, journalEntryId: postedJournalEntry.id },
|
|
27
|
+
{ ...baseJournalLine2, journalEntryId: postedJournalEntry.id },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
describe("reverseJournalEntry", () => {
|
|
31
|
+
it("returns error when journal entry does not exist", async () => {
|
|
32
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
33
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
34
|
+
|
|
35
|
+
const result = await run(db, { id: "nonexistent" }, commandCtx);
|
|
36
|
+
|
|
37
|
+
expectErr(result, JournalEntryNotFoundError);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns error when journal entry is in DRAFT status", async () => {
|
|
41
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
42
|
+
spies.select.mockReturnValueOnce(baseJournalEntry); // DRAFT entry
|
|
43
|
+
|
|
44
|
+
const result = await run(db, { id: baseJournalEntry.id }, commandCtx);
|
|
45
|
+
|
|
46
|
+
expectErr(result, InvalidStatusForReversalError);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns error when journal entry is already REVERSED", async () => {
|
|
50
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
51
|
+
spies.select.mockReturnValueOnce(reversedJournalEntry);
|
|
52
|
+
|
|
53
|
+
const result = await run(db, { id: reversedJournalEntry.id }, commandCtx);
|
|
54
|
+
|
|
55
|
+
expectErr(result, AlreadyReversedError);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns error when reversal period is in NEVER_OPENED status", async () => {
|
|
59
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
60
|
+
spies.select
|
|
61
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
62
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
63
|
+
.mockReturnValueOnce(neverOpenedPeriod); // period lookup
|
|
64
|
+
|
|
65
|
+
const result = await run(
|
|
66
|
+
db,
|
|
67
|
+
{ id: postedJournalEntry.id, reversalPeriodId: neverOpenedPeriod.id },
|
|
68
|
+
commandCtx,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expectErr(result, InvalidPeriodStatusError);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns error when reversal period is in CLOSED status", async () => {
|
|
75
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
76
|
+
spies.select
|
|
77
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
78
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
79
|
+
.mockReturnValueOnce(closedPeriod); // period lookup
|
|
80
|
+
|
|
81
|
+
const result = await run(
|
|
82
|
+
db,
|
|
83
|
+
{ id: postedJournalEntry.id, reversalPeriodId: closedPeriod.id },
|
|
84
|
+
commandCtx,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expectErr(result, InvalidPeriodStatusError);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns error when reversal period is in PERMANENTLY_CLOSED status", async () => {
|
|
91
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
92
|
+
spies.select
|
|
93
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
94
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
95
|
+
.mockReturnValueOnce(permanentlyClosedPeriod); // period lookup
|
|
96
|
+
|
|
97
|
+
const result = await run(
|
|
98
|
+
db,
|
|
99
|
+
{ id: postedJournalEntry.id, reversalPeriodId: permanentlyClosedPeriod.id },
|
|
100
|
+
commandCtx,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expectErr(result, InvalidPeriodStatusError);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("creates mirror entry with all debit/credit amounts inverted", async () => {
|
|
107
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
108
|
+
const reversalEntry = {
|
|
109
|
+
...postedJournalEntry,
|
|
110
|
+
id: "reversal-entry-1",
|
|
111
|
+
status: "POSTED",
|
|
112
|
+
reversalOfId: postedJournalEntry.id,
|
|
113
|
+
};
|
|
114
|
+
const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
|
|
115
|
+
|
|
116
|
+
spies.select
|
|
117
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
118
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
119
|
+
.mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
|
|
120
|
+
spies.insert
|
|
121
|
+
.mockReturnValueOnce(reversalEntry) // insert reversal entry
|
|
122
|
+
.mockImplementationOnce(() => [
|
|
123
|
+
// insert reversal lines
|
|
124
|
+
{
|
|
125
|
+
...postedLines[0],
|
|
126
|
+
debitAmount: null,
|
|
127
|
+
creditAmount: 1000,
|
|
128
|
+
functionalDebitAmount: null,
|
|
129
|
+
functionalCreditAmount: 1000,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
...postedLines[1],
|
|
133
|
+
debitAmount: 1000,
|
|
134
|
+
creditAmount: null,
|
|
135
|
+
functionalDebitAmount: 1000,
|
|
136
|
+
functionalCreditAmount: null,
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
|
|
140
|
+
|
|
141
|
+
const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
|
|
142
|
+
|
|
143
|
+
expectOk(result);
|
|
144
|
+
// Verify values were called with inverted amounts
|
|
145
|
+
expect(spies.values).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("reversal entry references the original entry", async () => {
|
|
149
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
150
|
+
const reversalEntry = {
|
|
151
|
+
...postedJournalEntry,
|
|
152
|
+
id: "reversal-entry-1",
|
|
153
|
+
status: "POSTED",
|
|
154
|
+
reversalOfId: postedJournalEntry.id,
|
|
155
|
+
};
|
|
156
|
+
const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
|
|
157
|
+
|
|
158
|
+
spies.select
|
|
159
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
160
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
161
|
+
.mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
|
|
162
|
+
spies.insert
|
|
163
|
+
.mockReturnValueOnce(reversalEntry) // insert reversal entry
|
|
164
|
+
.mockImplementationOnce(() => []); // insert reversal lines
|
|
165
|
+
spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
|
|
166
|
+
|
|
167
|
+
const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
|
|
168
|
+
|
|
169
|
+
const value = expectOk(result);
|
|
170
|
+
expect(value.reversalEntry.reversalOfId).toBe(postedJournalEntry.id);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("reversal entry is automatically posted", async () => {
|
|
174
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
175
|
+
const reversalEntry = {
|
|
176
|
+
...postedJournalEntry,
|
|
177
|
+
id: "reversal-entry-1",
|
|
178
|
+
status: "POSTED",
|
|
179
|
+
reversalOfId: postedJournalEntry.id,
|
|
180
|
+
postedAt: new Date(),
|
|
181
|
+
};
|
|
182
|
+
const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
|
|
183
|
+
|
|
184
|
+
spies.select
|
|
185
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
186
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
187
|
+
.mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
|
|
188
|
+
spies.insert
|
|
189
|
+
.mockReturnValueOnce(reversalEntry) // insert reversal entry
|
|
190
|
+
.mockImplementationOnce(() => []); // insert reversal lines
|
|
191
|
+
spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
|
|
192
|
+
|
|
193
|
+
const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
|
|
194
|
+
|
|
195
|
+
const value = expectOk(result);
|
|
196
|
+
expect(value.reversalEntry.status).toBe("POSTED");
|
|
197
|
+
expect(value.reversalEntry.postedAt).toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("original entry transitions from POSTED to REVERSED", async () => {
|
|
201
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
202
|
+
const reversalEntry = {
|
|
203
|
+
...postedJournalEntry,
|
|
204
|
+
id: "reversal-entry-1",
|
|
205
|
+
status: "POSTED",
|
|
206
|
+
reversalOfId: postedJournalEntry.id,
|
|
207
|
+
};
|
|
208
|
+
const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
|
|
209
|
+
|
|
210
|
+
spies.select
|
|
211
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
212
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
213
|
+
.mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
|
|
214
|
+
spies.insert
|
|
215
|
+
.mockReturnValueOnce(reversalEntry) // insert reversal entry
|
|
216
|
+
.mockImplementationOnce(() => []); // insert reversal lines
|
|
217
|
+
spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
|
|
218
|
+
|
|
219
|
+
const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
|
|
220
|
+
|
|
221
|
+
const value = expectOk(result);
|
|
222
|
+
expect(value.originalEntry.status).toBe("REVERSED");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("reversal entry targets the specified accounting period", async () => {
|
|
226
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
227
|
+
const reversalEntry = {
|
|
228
|
+
...postedJournalEntry,
|
|
229
|
+
id: "reversal-entry-1",
|
|
230
|
+
status: "POSTED",
|
|
231
|
+
reversalOfId: postedJournalEntry.id,
|
|
232
|
+
accountingPeriodId: futureEnterablePeriod.id,
|
|
233
|
+
};
|
|
234
|
+
const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
|
|
235
|
+
|
|
236
|
+
spies.select
|
|
237
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
238
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
239
|
+
.mockReturnValueOnce(futureEnterablePeriod); // period lookup (FUTURE_ENTERABLE)
|
|
240
|
+
spies.insert
|
|
241
|
+
.mockReturnValueOnce(reversalEntry) // insert reversal entry
|
|
242
|
+
.mockImplementationOnce(() => []); // insert reversal lines
|
|
243
|
+
spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
|
|
244
|
+
|
|
245
|
+
const result = await run(
|
|
246
|
+
db,
|
|
247
|
+
{ id: postedJournalEntry.id, reversalPeriodId: futureEnterablePeriod.id },
|
|
248
|
+
commandCtx,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const value = expectOk(result);
|
|
252
|
+
expect(value.reversalEntry.accountingPeriodId).toBe(futureEnterablePeriod.id);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("multi-currency lines are reversed with same currency and exchange rate", async () => {
|
|
256
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
257
|
+
const multiCurrencyLines = [
|
|
258
|
+
{
|
|
259
|
+
...postedLines[0],
|
|
260
|
+
currencyCode: "currency-1",
|
|
261
|
+
exchangeRate: 1.5,
|
|
262
|
+
debitAmount: 1500,
|
|
263
|
+
functionalDebitAmount: 1000,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
...postedLines[1],
|
|
267
|
+
currencyCode: "currency-1",
|
|
268
|
+
exchangeRate: 1.5,
|
|
269
|
+
creditAmount: 1500,
|
|
270
|
+
functionalCreditAmount: 1000,
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
const reversalEntry = {
|
|
274
|
+
...postedJournalEntry,
|
|
275
|
+
id: "reversal-entry-1",
|
|
276
|
+
status: "POSTED",
|
|
277
|
+
reversalOfId: postedJournalEntry.id,
|
|
278
|
+
};
|
|
279
|
+
const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
|
|
280
|
+
|
|
281
|
+
spies.select
|
|
282
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
283
|
+
.mockImplementationOnce(() => multiCurrencyLines) // journal lines
|
|
284
|
+
.mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
|
|
285
|
+
spies.insert
|
|
286
|
+
.mockReturnValueOnce(reversalEntry) // insert reversal entry
|
|
287
|
+
.mockImplementationOnce(() => []); // insert reversal lines
|
|
288
|
+
spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
|
|
289
|
+
|
|
290
|
+
const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
|
|
291
|
+
|
|
292
|
+
expectOk(result);
|
|
293
|
+
// Verify that values() was called with reversed amounts preserving currency info
|
|
294
|
+
expect(spies.values).toHaveBeenCalled();
|
|
295
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
296
|
+
const insertedLines = spies.values.mock.calls[1]?.[0];
|
|
297
|
+
if (insertedLines) {
|
|
298
|
+
for (const line of insertedLines as { currencyCode: string; exchangeRate: number }[]) {
|
|
299
|
+
expect(line.currencyCode).toBe("currency-1");
|
|
300
|
+
expect(line.exchangeRate).toBe(1.5);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("emits audit event recording status transition, reversal entry reference, and acting user", async () => {
|
|
306
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
307
|
+
const reversalEntry = {
|
|
308
|
+
...postedJournalEntry,
|
|
309
|
+
id: "reversal-entry-1",
|
|
310
|
+
status: "POSTED",
|
|
311
|
+
reversalOfId: postedJournalEntry.id,
|
|
312
|
+
};
|
|
313
|
+
const reversedOriginal = { ...postedJournalEntry, status: "REVERSED" };
|
|
314
|
+
|
|
315
|
+
spies.select
|
|
316
|
+
.mockReturnValueOnce(postedJournalEntry) // entry lookup
|
|
317
|
+
.mockImplementationOnce(() => postedLines) // journal lines
|
|
318
|
+
.mockReturnValueOnce(baseAccountingPeriod); // period lookup (OPEN)
|
|
319
|
+
spies.insert
|
|
320
|
+
.mockReturnValueOnce(reversalEntry) // insert reversal entry
|
|
321
|
+
.mockImplementationOnce(() => []); // insert reversal lines
|
|
322
|
+
spies.update.mockReturnValueOnce(reversedOriginal); // update original entry
|
|
323
|
+
|
|
324
|
+
const result = await run(db, { id: postedJournalEntry.id }, commandCtx);
|
|
325
|
+
|
|
326
|
+
const value = expectOk(result);
|
|
327
|
+
expect(value.auditTrail).toBeDefined();
|
|
328
|
+
expect(value.auditTrail).toContainEqual(
|
|
329
|
+
expect.objectContaining({
|
|
330
|
+
type: "STATUS_TRANSITION",
|
|
331
|
+
from: "POSTED",
|
|
332
|
+
to: "REVERSED",
|
|
333
|
+
actorId: commandCtx.actorId,
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
2
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
|
+
import {
|
|
4
|
+
JournalEntryNotFoundError,
|
|
5
|
+
InvalidStatusForReversalError,
|
|
6
|
+
AlreadyReversedError,
|
|
7
|
+
InvalidPeriodStatusError,
|
|
8
|
+
} from "../lib/errors.generated";
|
|
9
|
+
|
|
10
|
+
export interface ReverseJournalEntryInput {
|
|
11
|
+
id: string;
|
|
12
|
+
reversalPeriodId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface AuditEntry {
|
|
16
|
+
type: string;
|
|
17
|
+
from?: string;
|
|
18
|
+
to?: string;
|
|
19
|
+
actorId?: string;
|
|
20
|
+
reversalEntryId?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const VALID_PERIOD_STATUSES = ["OPEN", "FUTURE_ENTERABLE"];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Function: reverseJournalEntry
|
|
27
|
+
*
|
|
28
|
+
* Creates a mirror journal entry with all debit and credit amounts inverted
|
|
29
|
+
* from the original posted entry, and automatically posts the reversal entry.
|
|
30
|
+
* The original entry transitions from POSTED to REVERSED status.
|
|
31
|
+
*/
|
|
32
|
+
export async function run(db: Transaction, input: ReverseJournalEntryInput, ctx: CommandContext) {
|
|
33
|
+
const { id, reversalPeriodId } = input;
|
|
34
|
+
const auditTrail: AuditEntry[] = [];
|
|
35
|
+
|
|
36
|
+
// 1. Find journal entry
|
|
37
|
+
const journalEntry = await db
|
|
38
|
+
.selectFrom("JournalEntry")
|
|
39
|
+
.selectAll()
|
|
40
|
+
.where("id", "=", id)
|
|
41
|
+
.forUpdate()
|
|
42
|
+
.executeTakeFirst();
|
|
43
|
+
|
|
44
|
+
if (!journalEntry) {
|
|
45
|
+
return err(new JournalEntryNotFoundError(id));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. Validate status is POSTED
|
|
49
|
+
if (journalEntry.status === "REVERSED") {
|
|
50
|
+
return err(new AlreadyReversedError(id));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (journalEntry.status !== "POSTED") {
|
|
54
|
+
return err(new InvalidStatusForReversalError(id));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3. Fetch original journal lines
|
|
58
|
+
const originalLines = await db
|
|
59
|
+
.selectFrom("JournalLine")
|
|
60
|
+
.selectAll()
|
|
61
|
+
.where("journalEntryId", "=", id)
|
|
62
|
+
.execute();
|
|
63
|
+
|
|
64
|
+
// 4. Determine target period and validate
|
|
65
|
+
const targetPeriodId = reversalPeriodId ?? journalEntry.accountingPeriodId;
|
|
66
|
+
const period = await db
|
|
67
|
+
.selectFrom("AccountingPeriod")
|
|
68
|
+
.selectAll()
|
|
69
|
+
.where("id", "=", targetPeriodId)
|
|
70
|
+
.executeTakeFirst();
|
|
71
|
+
|
|
72
|
+
if (!period || !VALID_PERIOD_STATUSES.includes(period.status)) {
|
|
73
|
+
return err(new InvalidPeriodStatusError(targetPeriodId));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 5. Create reversal journal entry (auto-posted)
|
|
77
|
+
const now = new Date();
|
|
78
|
+
const reversalEntry = await db
|
|
79
|
+
.insertInto("JournalEntry")
|
|
80
|
+
.values({
|
|
81
|
+
companyId: journalEntry.companyId,
|
|
82
|
+
accountingPeriodId: targetPeriodId,
|
|
83
|
+
entryDate: now,
|
|
84
|
+
journalType: journalEntry.journalType,
|
|
85
|
+
referenceNumber: `REV-${journalEntry.referenceNumber}`,
|
|
86
|
+
status: "POSTED",
|
|
87
|
+
description: `Reversal of ${journalEntry.referenceNumber}`,
|
|
88
|
+
sourceDocumentReference: journalEntry.sourceDocumentReference,
|
|
89
|
+
adjustingEntryType: journalEntry.adjustingEntryType,
|
|
90
|
+
reversalOfId: journalEntry.id,
|
|
91
|
+
postedAt: now,
|
|
92
|
+
createdAt: now,
|
|
93
|
+
updatedAt: null,
|
|
94
|
+
})
|
|
95
|
+
.returningAll()
|
|
96
|
+
.executeTakeFirst();
|
|
97
|
+
|
|
98
|
+
// 6. Create mirror journal lines with inverted amounts
|
|
99
|
+
const reversalLines = originalLines.map((line) => ({
|
|
100
|
+
journalEntryId: reversalEntry!.id,
|
|
101
|
+
accountId: line.accountId,
|
|
102
|
+
debitAmount: line.creditAmount,
|
|
103
|
+
creditAmount: line.debitAmount,
|
|
104
|
+
description: line.description,
|
|
105
|
+
currencyCode: line.currencyCode,
|
|
106
|
+
exchangeRate: line.exchangeRate,
|
|
107
|
+
functionalDebitAmount: line.functionalCreditAmount,
|
|
108
|
+
functionalCreditAmount: line.functionalDebitAmount,
|
|
109
|
+
createdAt: now,
|
|
110
|
+
updatedAt: null,
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
await db.insertInto("JournalLine").values(reversalLines).returningAll().execute();
|
|
114
|
+
|
|
115
|
+
// 7. Transition original entry to REVERSED
|
|
116
|
+
const updatedOriginal = await db
|
|
117
|
+
.updateTable("JournalEntry")
|
|
118
|
+
.set({
|
|
119
|
+
status: "REVERSED",
|
|
120
|
+
updatedAt: now,
|
|
121
|
+
})
|
|
122
|
+
.where("id", "=", id)
|
|
123
|
+
.returningAll()
|
|
124
|
+
.executeTakeFirst();
|
|
125
|
+
|
|
126
|
+
// 8. Record audit events
|
|
127
|
+
auditTrail.push({
|
|
128
|
+
type: "STATUS_TRANSITION",
|
|
129
|
+
from: "POSTED",
|
|
130
|
+
to: "REVERSED",
|
|
131
|
+
actorId: ctx.actorId,
|
|
132
|
+
reversalEntryId: reversalEntry!.id,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return ok({
|
|
136
|
+
reversalEntry: reversalEntry!,
|
|
137
|
+
originalEntry: updatedOriginal!,
|
|
138
|
+
auditTrail,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
3
|
+
import { permissions } from "../lib/permissions.generated";
|
|
4
|
+
import { run } from "./revertSoftLock";
|
|
5
|
+
|
|
6
|
+
export const revertSoftLock = defineCommand(permissions.revertSoftLock, run);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
PeriodCloseNotFoundError,
|
|
6
|
+
InvalidStatusTransitionError,
|
|
7
|
+
PeriodPermanentlyClosedError,
|
|
8
|
+
} from "../lib/errors.generated";
|
|
9
|
+
import {
|
|
10
|
+
softLockedPeriodClose,
|
|
11
|
+
inProgressPeriodClose,
|
|
12
|
+
underReviewPeriodClose,
|
|
13
|
+
permanentlyClosedPeriodClose,
|
|
14
|
+
basePeriodClose,
|
|
15
|
+
} from "../testing/fixtures";
|
|
16
|
+
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
|
|
17
|
+
import { run } from "./revertSoftLock";
|
|
18
|
+
|
|
19
|
+
describe("revertSoftLock", () => {
|
|
20
|
+
it("returns error when PeriodClose record does not exist", async () => {
|
|
21
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
22
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
23
|
+
|
|
24
|
+
const result = await run(db, { id: "non-existent" }, commandCtx);
|
|
25
|
+
|
|
26
|
+
expectErr(result, PeriodCloseNotFoundError);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns error when PeriodClose is in NOT_STARTED status", async () => {
|
|
30
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
31
|
+
spies.select.mockReturnValueOnce(basePeriodClose);
|
|
32
|
+
|
|
33
|
+
const result = await run(db, { id: basePeriodClose.id }, commandCtx);
|
|
34
|
+
|
|
35
|
+
expectErr(result, InvalidStatusTransitionError);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns error when PeriodClose is in IN_PROGRESS status", async () => {
|
|
39
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
40
|
+
spies.select.mockReturnValueOnce(inProgressPeriodClose);
|
|
41
|
+
|
|
42
|
+
const result = await run(db, { id: inProgressPeriodClose.id }, commandCtx);
|
|
43
|
+
|
|
44
|
+
expectErr(result, InvalidStatusTransitionError);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns error when PeriodClose is in UNDER_REVIEW status", async () => {
|
|
48
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
49
|
+
spies.select.mockReturnValueOnce(underReviewPeriodClose);
|
|
50
|
+
|
|
51
|
+
const result = await run(db, { id: underReviewPeriodClose.id }, commandCtx);
|
|
52
|
+
|
|
53
|
+
expectErr(result, InvalidStatusTransitionError);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns error when PeriodClose is PERMANENTLY_CLOSED", async () => {
|
|
57
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
58
|
+
spies.select.mockReturnValueOnce(permanentlyClosedPeriodClose);
|
|
59
|
+
|
|
60
|
+
const result = await run(db, { id: permanentlyClosedPeriodClose.id }, commandCtx);
|
|
61
|
+
|
|
62
|
+
expectErr(result, PeriodPermanentlyClosedError);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("transitions SOFT_LOCKED period close to IN_PROGRESS", async () => {
|
|
66
|
+
const updatedPeriodClose = { ...softLockedPeriodClose, status: "IN_PROGRESS" };
|
|
67
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
68
|
+
spies.select.mockReturnValueOnce(softLockedPeriodClose);
|
|
69
|
+
spies.update.mockReturnValueOnce(updatedPeriodClose);
|
|
70
|
+
|
|
71
|
+
const result = await run(db, { id: softLockedPeriodClose.id }, commandCtx);
|
|
72
|
+
|
|
73
|
+
const value = expectOk(result);
|
|
74
|
+
expect(value.periodClose.status).toBe("IN_PROGRESS");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("emits audit event recording status transition and acting user", async () => {
|
|
78
|
+
const updatedPeriodClose = { ...softLockedPeriodClose, status: "IN_PROGRESS" };
|
|
79
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
80
|
+
spies.select.mockReturnValueOnce(softLockedPeriodClose);
|
|
81
|
+
spies.update.mockReturnValueOnce(updatedPeriodClose);
|
|
82
|
+
|
|
83
|
+
const result = await run(db, { id: softLockedPeriodClose.id }, commandCtx);
|
|
84
|
+
|
|
85
|
+
const value = expectOk(result);
|
|
86
|
+
expect(value.auditTrail).toEqual([
|
|
87
|
+
{
|
|
88
|
+
type: "STATUS_TRANSITION",
|
|
89
|
+
from: "SOFT_LOCKED",
|
|
90
|
+
to: "IN_PROGRESS",
|
|
91
|
+
actorId: commandCtx.actorId,
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
expect(spies.update).toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
2
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
|
+
import {
|
|
4
|
+
PeriodCloseNotFoundError,
|
|
5
|
+
PeriodPermanentlyClosedError,
|
|
6
|
+
InvalidStatusTransitionError,
|
|
7
|
+
} from "../lib/errors.generated";
|
|
8
|
+
|
|
9
|
+
export interface RevertSoftLockInput {
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Function: revertSoftLock
|
|
15
|
+
*
|
|
16
|
+
* Transitions a PeriodClose record from SOFT_LOCKED back to IN_PROGRESS status,
|
|
17
|
+
* allowing the close workflow to be revisited when errors are discovered.
|
|
18
|
+
* Only permitted before permanent close.
|
|
19
|
+
*/
|
|
20
|
+
export async function run(db: Transaction, input: RevertSoftLockInput, ctx: CommandContext) {
|
|
21
|
+
const { id } = input;
|
|
22
|
+
|
|
23
|
+
// 1. Find PeriodClose record
|
|
24
|
+
const periodClose = await db
|
|
25
|
+
.selectFrom("PeriodClose")
|
|
26
|
+
.selectAll()
|
|
27
|
+
.where("id", "=", id)
|
|
28
|
+
.forUpdate()
|
|
29
|
+
.executeTakeFirst();
|
|
30
|
+
|
|
31
|
+
if (!periodClose) {
|
|
32
|
+
return err(new PeriodCloseNotFoundError(id));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Reject permanently closed periods
|
|
36
|
+
if (periodClose.status === "PERMANENTLY_CLOSED") {
|
|
37
|
+
return err(new PeriodPermanentlyClosedError(id));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Validate status is SOFT_LOCKED
|
|
41
|
+
if (periodClose.status !== "SOFT_LOCKED") {
|
|
42
|
+
return err(new InvalidStatusTransitionError(id));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 4. Transition back to IN_PROGRESS and record acting user
|
|
46
|
+
const updated = await db
|
|
47
|
+
.updateTable("PeriodClose")
|
|
48
|
+
.set({
|
|
49
|
+
status: "IN_PROGRESS",
|
|
50
|
+
updatedAt: new Date(),
|
|
51
|
+
})
|
|
52
|
+
.where("id", "=", id)
|
|
53
|
+
.returningAll()
|
|
54
|
+
.executeTakeFirstOrThrow();
|
|
55
|
+
|
|
56
|
+
// 5. Emit audit event recording status transition
|
|
57
|
+
const auditTrail = [
|
|
58
|
+
{
|
|
59
|
+
type: "STATUS_TRANSITION",
|
|
60
|
+
from: "SOFT_LOCKED",
|
|
61
|
+
to: "IN_PROGRESS",
|
|
62
|
+
actorId: ctx.actorId,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return ok({ periodClose: updated, auditTrail });
|
|
67
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
3
|
+
import { permissions } from "../lib/permissions.generated";
|
|
4
|
+
import { run } from "./updateFiscalYear";
|
|
5
|
+
|
|
6
|
+
export const updateFiscalYear = defineCommand(permissions.updateFiscalYear, run);
|