@tailor-platform/erp-kit 0.8.0 → 0.9.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 +15 -0
- package/package.json +1 -1
- package/skills/erp-kit-app-1-requirements/SKILL.md +6 -0
- package/skills/erp-kit-app-2-requirements-review/SKILL.md +6 -0
- package/skills/erp-kit-app-3-plan/SKILL.md +6 -0
- package/skills/erp-kit-app-4-plan-review/SKILL.md +6 -0
- package/skills/erp-kit-app-5-impl-backend/SKILL.md +7 -0
- package/skills/erp-kit-app-6-impl-frontend/SKILL.md +6 -0
- package/skills/erp-kit-app-7-impl-review/SKILL.md +6 -0
- package/skills/erp-kit-app-shared/SKILL.md +2 -0
- package/skills/erp-kit-mock-scenario/SKILL.md +6 -0
- package/skills/erp-kit-module-1-requirements/SKILL.md +6 -0
- package/skills/erp-kit-module-2-requirements-review/SKILL.md +6 -0
- package/skills/erp-kit-module-3-plan/SKILL.md +6 -0
- package/skills/erp-kit-module-3-update-plan/SKILL.md +6 -0
- package/skills/erp-kit-module-4-plan-review/SKILL.md +6 -0
- package/skills/erp-kit-module-5-impl/SKILL.md +6 -0
- package/skills/erp-kit-module-6-impl-review/SKILL.md +6 -0
- package/skills/erp-kit-module-shared/SKILL.md +2 -0
- package/skills/erp-kit-module-shared/references/commands.md +3 -0
- package/skills/erp-kit-update/SKILL.md +6 -0
- package/src/modules/accounting/command/assignProfitCenterToHierarchyNode.test.ts +3 -3
- package/src/modules/accounting/command/assignProfitCenterToHierarchyNode.ts +1 -1
- package/src/modules/accounting/command/createCostCenterHierarchyNode.test.ts +3 -3
- package/src/modules/accounting/command/createCostCenterHierarchyNode.ts +1 -1
- package/src/modules/accounting/command/moveCostCenterHierarchyNode.test.ts +2 -2
- package/src/modules/accounting/command/moveCostCenterHierarchyNode.ts +1 -1
- package/src/modules/accounting/module.ts +1 -0
- package/src/modules/business-partner/command/createPartnerBankAccount.test.ts +3 -3
- package/src/modules/business-partner/command/createPartnerBankAccount.ts +1 -1
- package/src/modules/business-partner/module.ts +1 -0
- package/src/modules/coa-management/command/activateAccount.test.ts +0 -15
- package/src/modules/coa-management/command/activateAccount.ts +1 -42
- package/src/modules/coa-management/command/activateChartOfAccounts.test.ts +2 -31
- package/src/modules/coa-management/command/activateChartOfAccounts.ts +1 -37
- package/src/modules/coa-management/command/createAccount.test.ts +0 -28
- package/src/modules/coa-management/command/createAccount.ts +0 -43
- package/src/modules/coa-management/command/createAccountGroup.test.ts +2 -51
- package/src/modules/coa-management/command/createAccountGroup.ts +1 -56
- package/src/modules/coa-management/command/createChartOfAccounts.test.ts +1 -49
- package/src/modules/coa-management/command/createChartOfAccounts.ts +0 -51
- package/src/modules/coa-management/command/deactivateAccount.test.ts +0 -15
- package/src/modules/coa-management/command/deactivateAccount.ts +1 -53
- package/src/modules/coa-management/command/deactivateChartOfAccounts.test.ts +2 -29
- package/src/modules/coa-management/command/deactivateChartOfAccounts.ts +1 -37
- package/src/modules/coa-management/command/deleteAccount.test.ts +0 -13
- package/src/modules/coa-management/command/deleteAccount.ts +1 -42
- package/src/modules/coa-management/command/deleteAccountGroup.test.ts +0 -19
- package/src/modules/coa-management/command/deleteAccountGroup.ts +1 -42
- package/src/modules/coa-management/command/deleteChartOfAccounts.test.ts +2 -58
- package/src/modules/coa-management/command/deleteChartOfAccounts.ts +4 -88
- package/src/modules/coa-management/command/moveAccountGroup.test.ts +0 -27
- package/src/modules/coa-management/command/moveAccountGroup.ts +1 -48
- package/src/modules/coa-management/command/reactivateAccount.test.ts +0 -15
- package/src/modules/coa-management/command/reactivateAccount.ts +1 -53
- package/src/modules/coa-management/command/updateAccount.test.ts +0 -15
- package/src/modules/coa-management/command/updateAccount.ts +0 -92
- package/src/modules/coa-management/command/updateAccountGroup.test.ts +0 -20
- package/src/modules/coa-management/command/updateAccountGroup.ts +1 -61
- package/src/modules/coa-management/command/updateChartOfAccounts.test.ts +2 -31
- package/src/modules/coa-management/command/updateChartOfAccounts.ts +1 -50
- package/src/modules/coa-management/docs/commands/ActivateAccount.md +1 -4
- package/src/modules/coa-management/docs/commands/ActivateChartOfAccounts.md +1 -4
- package/src/modules/coa-management/docs/commands/CreateAccount.md +1 -4
- package/src/modules/coa-management/docs/commands/CreateAccountGroup.md +2 -5
- package/src/modules/coa-management/docs/commands/CreateChartOfAccounts.md +2 -6
- package/src/modules/coa-management/docs/commands/DeactivateAccount.md +1 -4
- package/src/modules/coa-management/docs/commands/DeactivateChartOfAccounts.md +1 -4
- package/src/modules/coa-management/docs/commands/DeleteAccount.md +1 -4
- package/src/modules/coa-management/docs/commands/DeleteAccountGroup.md +1 -4
- package/src/modules/coa-management/docs/commands/DeleteChartOfAccounts.md +1 -6
- package/src/modules/coa-management/docs/commands/MoveAccountGroup.md +1 -4
- package/src/modules/coa-management/docs/commands/ReactivateAccount.md +1 -4
- package/src/modules/coa-management/docs/commands/UpdateAccount.md +1 -4
- package/src/modules/coa-management/docs/commands/UpdateAccountGroup.md +2 -5
- package/src/modules/coa-management/docs/commands/UpdateChartOfAccounts.md +1 -4
- package/src/modules/coa-management/module.ts +16 -27
- package/src/modules/finance-ledger/module.ts +1 -0
- package/src/modules/inventory/command/approveInventoryAdjustment.test.ts +1 -1
- package/src/modules/inventory/command/approveInventoryAdjustment.ts +1 -1
- package/src/modules/inventory/command/cancelStockMovement.test.ts +2 -2
- package/src/modules/inventory/command/cancelStockMovement.ts +1 -1
- package/src/modules/inventory/command/confirmInventoryAdjustment.test.ts +4 -4
- package/src/modules/inventory/command/confirmInventoryAdjustment.ts +1 -1
- package/src/modules/inventory/command/confirmStockMovement.test.ts +1 -1
- package/src/modules/inventory/command/confirmStockMovement.ts +1 -1
- package/src/modules/inventory/command/createInventoryAdjustment.test.ts +6 -6
- package/src/modules/inventory/command/createInventoryAdjustment.ts +1 -1
- package/src/modules/inventory/command/createStockMovement.test.ts +3 -3
- package/src/modules/inventory/command/createStockMovement.ts +1 -1
- package/src/modules/inventory/command/executeStockMovement.test.ts +5 -5
- package/src/modules/inventory/command/executeStockMovement.ts +1 -1
- package/src/modules/inventory/command/rejectInventoryAdjustment.test.ts +1 -1
- package/src/modules/inventory/command/rejectInventoryAdjustment.ts +1 -1
- package/src/modules/inventory/command/reviseInventoryAdjustment.test.ts +2 -2
- package/src/modules/inventory/command/reviseInventoryAdjustment.ts +1 -1
- package/src/modules/inventory/command/submitInventoryAdjustment.test.ts +1 -1
- package/src/modules/inventory/command/submitInventoryAdjustment.ts +1 -1
- package/src/modules/inventory/command/updateStockMovement.test.ts +4 -4
- package/src/modules/inventory/command/updateStockMovement.ts +2 -2
- package/src/modules/inventory/module.ts +1 -0
- package/src/modules/item-management/command/createTaxonomyNode.test.ts +4 -4
- package/src/modules/item-management/command/createTaxonomyNode.ts +1 -1
- package/src/modules/item-management/command/moveTaxonomyNode.test.ts +2 -2
- package/src/modules/item-management/command/moveTaxonomyNode.ts +2 -2
- package/src/modules/item-management/command/updateTaxonomyNode.test.ts +2 -2
- package/src/modules/item-management/command/updateTaxonomyNode.ts +1 -1
- package/src/modules/item-management/module.ts +1 -0
- package/src/modules/manufacturing/command/createRouting.ts +1 -1
- package/src/modules/manufacturing/command/recordInventoryIssueOutcome.test.ts +1 -1
- package/src/modules/manufacturing/command/recordInventoryIssueOutcome.ts +1 -1
- package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.test.ts +1 -1
- package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.ts +1 -1
- package/src/modules/manufacturing/command/reviewManufacturingCostSummary.test.ts +1 -1
- package/src/modules/manufacturing/command/reviewManufacturingCostSummary.ts +1 -1
- package/src/modules/manufacturing/command/updateRouting.ts +1 -1
- package/src/modules/manufacturing/module.ts +1 -0
- package/src/modules/organization/module.ts +1 -0
- package/src/modules/primitives/module.ts +1 -0
- package/src/modules/product-management/command/assignProductToCategory.test.ts +2 -2
- package/src/modules/product-management/command/assignProductToCategory.ts +2 -2
- package/src/modules/product-management/command/createProductAttribute.test.ts +1 -1
- package/src/modules/product-management/command/createProductAttribute.ts +1 -1
- package/src/modules/product-management/command/createProductAttributeValue.test.ts +1 -1
- package/src/modules/product-management/command/createProductAttributeValue.ts +1 -1
- package/src/modules/product-management/command/createProductCategory.test.ts +2 -2
- package/src/modules/product-management/command/createProductCategory.ts +1 -1
- package/src/modules/product-management/command/setProductAttributeAssignment.test.ts +3 -3
- package/src/modules/product-management/command/setProductAttributeAssignment.ts +2 -2
- package/src/modules/product-management/module.ts +1 -0
- package/src/modules/purchase/command/activatePurchasePaymentTerm.test.ts +4 -4
- package/src/modules/purchase/command/activatePurchasePaymentTerm.ts +2 -2
- package/src/modules/purchase/command/deactivatePurchasePaymentTerm.test.ts +4 -4
- package/src/modules/purchase/command/deactivatePurchasePaymentTerm.ts +2 -2
- package/src/modules/purchase/command/updatePurchasePaymentTerm.test.ts +2 -2
- package/src/modules/purchase/command/updatePurchasePaymentTerm.ts +1 -1
- package/src/modules/purchase/module.ts +1 -0
- package/src/modules/sales/command/createSalesOrder.ts +1 -1
- package/src/modules/sales/module.ts +1 -0
- package/templates/scaffold/app/backend/eslint.config.js +17 -0
- package/templates/scaffold/app/backend/package.json +1 -0
- package/templates/scaffold/app/backend/src/tests/stories/audit-log/user--view-audit-log-detail.test.ts +3 -3
- package/templates/scaffold/app/backend/src/tests/stories/role-management/user--assign-role-to-user.test.ts +4 -4
- package/templates/scaffold/app/backend/src/tests/stories/role-management/user--remove-role-from-user.test.ts +4 -4
- package/templates/scaffold/app/backend/src/tests/stories/user-lifecycle/user--toggle-user-status.test.ts +6 -6
- package/templates/scaffold/app/backend/src/tests/stories/user-lifecycle/user--update-own-profile.test.ts +13 -13
- package/templates/scaffold/app/backend/src/tests/stories/user-lifecycle/user--update-user-profile.test.ts +16 -17
- package/templates/scaffold/app/backend/src/tests/stories/user-lifecycle/user--view-user-detail.test.ts +3 -3
- package/templates/scaffold/app/backend/tailor.config.ts +15 -1
- package/templates/scaffold/app/backend/tsconfig.json +1 -1
- package/templates/scaffold/app/frontend/src/App.tsx +57 -16
- package/templates/scaffold/module/eslint.config.js +24 -0
- package/templates/scaffold/module/module.ts +1 -0
- package/templates/scaffold/module/package.json +3 -1
- package/templates/scaffold/module/vitest.config.ts +11 -0
- /package/templates/scaffold/app/frontend/src/pages/{user-management/audit → audit}/[id]/components/audit-entry-detail.tsx +0 -0
- /package/templates/scaffold/app/frontend/src/pages/{user-management/audit → audit}/[id]/page.tsx +0 -0
- /package/templates/scaffold/app/frontend/src/pages/{user-management/audit → audit}/components/audit-entries-table.tsx +0 -0
- /package/templates/scaffold/app/frontend/src/pages/{user-management/audit → audit}/page.tsx +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
2
|
import { ChartOfAccountsNotFoundError, InvalidStateTransitionError } from "../lib/errors.generated";
|
|
4
3
|
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
@@ -8,23 +7,6 @@ export interface DeactivateChartOfAccountsInput {
|
|
|
8
7
|
from?: string[];
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
export interface AuditCommands {
|
|
12
|
-
logAuditEvent: (
|
|
13
|
-
db: Transaction,
|
|
14
|
-
input: {
|
|
15
|
-
eventId: string;
|
|
16
|
-
actorType: string;
|
|
17
|
-
actorId: string;
|
|
18
|
-
entityType: string;
|
|
19
|
-
entityId: string;
|
|
20
|
-
operationType: string;
|
|
21
|
-
companyId?: string;
|
|
22
|
-
changes: { fieldName: string; oldValue?: unknown; newValue?: unknown }[];
|
|
23
|
-
},
|
|
24
|
-
ctx: CommandContext,
|
|
25
|
-
) => Promise<{ ok: true; value: unknown }>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
10
|
/**
|
|
29
11
|
* Function: deactivateChartOfAccounts
|
|
30
12
|
*
|
|
@@ -34,8 +16,7 @@ export interface AuditCommands {
|
|
|
34
16
|
export async function run(
|
|
35
17
|
db: Transaction,
|
|
36
18
|
input: DeactivateChartOfAccountsInput,
|
|
37
|
-
|
|
38
|
-
auditCommands?: AuditCommands,
|
|
19
|
+
_ctx: CommandContext,
|
|
39
20
|
) {
|
|
40
21
|
const { id, from = ["ACTIVE"] } = input;
|
|
41
22
|
|
|
@@ -67,22 +48,5 @@ export async function run(
|
|
|
67
48
|
.returningAll()
|
|
68
49
|
.executeTakeFirst();
|
|
69
50
|
|
|
70
|
-
if (auditCommands) {
|
|
71
|
-
await auditCommands.logAuditEvent(
|
|
72
|
-
db,
|
|
73
|
-
{
|
|
74
|
-
eventId: randomUUID(),
|
|
75
|
-
actorType: "USER",
|
|
76
|
-
actorId: ctx.actorId,
|
|
77
|
-
entityType: "ChartOfAccounts",
|
|
78
|
-
entityId: id,
|
|
79
|
-
operationType: "UPDATE",
|
|
80
|
-
companyId: existing.companyId,
|
|
81
|
-
changes: [{ fieldName: "status", oldValue: existing.status, newValue: "ARCHIVED" }],
|
|
82
|
-
},
|
|
83
|
-
ctx,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
51
|
return ok({ chartOfAccounts: chartOfAccounts! });
|
|
88
52
|
}
|
|
@@ -106,17 +106,4 @@ describe("deleteAccount", () => {
|
|
|
106
106
|
expect(result.value.success).toBe(true);
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
|
-
|
|
110
|
-
it("emits audit event recording deletion, acting user, and account code", async () => {
|
|
111
|
-
const { db, spies } = createMockDb<Transaction>();
|
|
112
|
-
spies.select.mockReturnValueOnce(baseDraftAccount).mockReturnValueOnce(baseActiveCoa);
|
|
113
|
-
|
|
114
|
-
const result = await run(db, { id: baseDraftAccount.id }, ctx);
|
|
115
|
-
|
|
116
|
-
// Audit event is emitted as part of successful deletion
|
|
117
|
-
expect(result.ok).toBe(true);
|
|
118
|
-
if (result.ok) {
|
|
119
|
-
expect(result.value.success).toBe(true);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
109
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
2
|
import {
|
|
4
3
|
AccountNotFoundError,
|
|
@@ -12,23 +11,6 @@ export interface DeleteAccountInput {
|
|
|
12
11
|
id: string;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
export interface AuditCommands {
|
|
16
|
-
logAuditEvent: (
|
|
17
|
-
db: Transaction,
|
|
18
|
-
input: {
|
|
19
|
-
eventId: string;
|
|
20
|
-
actorType: string;
|
|
21
|
-
actorId: string;
|
|
22
|
-
entityType: string;
|
|
23
|
-
entityId: string;
|
|
24
|
-
operationType: string;
|
|
25
|
-
companyId?: string;
|
|
26
|
-
changes: { fieldName: string; oldValue?: unknown; newValue?: unknown }[];
|
|
27
|
-
},
|
|
28
|
-
ctx: CommandContext,
|
|
29
|
-
) => Promise<{ ok: true; value: unknown }>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
14
|
/**
|
|
33
15
|
* Function: deleteAccount
|
|
34
16
|
*
|
|
@@ -36,12 +18,7 @@ export interface AuditCommands {
|
|
|
36
18
|
* transactions. ACTIVE/INACTIVE accounts and accounts with posted
|
|
37
19
|
* transactions cannot be deleted.
|
|
38
20
|
*/
|
|
39
|
-
export async function run(
|
|
40
|
-
db: Transaction,
|
|
41
|
-
input: DeleteAccountInput,
|
|
42
|
-
ctx: CommandContext,
|
|
43
|
-
auditCommands?: AuditCommands,
|
|
44
|
-
) {
|
|
21
|
+
export async function run(db: Transaction, input: DeleteAccountInput, _ctx: CommandContext) {
|
|
45
22
|
const { id } = input;
|
|
46
23
|
|
|
47
24
|
// 1. Find account with forUpdate
|
|
@@ -81,23 +58,5 @@ export async function run(
|
|
|
81
58
|
// 6. Delete account
|
|
82
59
|
await db.deleteFrom("Account").where("id", "=", id).execute();
|
|
83
60
|
|
|
84
|
-
// 7. Emit audit event
|
|
85
|
-
if (auditCommands) {
|
|
86
|
-
await auditCommands.logAuditEvent(
|
|
87
|
-
db,
|
|
88
|
-
{
|
|
89
|
-
eventId: randomUUID(),
|
|
90
|
-
actorType: "USER",
|
|
91
|
-
actorId: ctx.actorId,
|
|
92
|
-
entityType: "Account",
|
|
93
|
-
entityId: id,
|
|
94
|
-
operationType: "DELETE",
|
|
95
|
-
companyId: coa.companyId,
|
|
96
|
-
changes: [{ fieldName: "id", oldValue: id }],
|
|
97
|
-
},
|
|
98
|
-
ctx,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
61
|
return ok({ success: true as const });
|
|
103
62
|
}
|
|
@@ -98,23 +98,4 @@ describe("deleteAccountGroup", () => {
|
|
|
98
98
|
}
|
|
99
99
|
expect(spies.delete).toHaveBeenCalled();
|
|
100
100
|
});
|
|
101
|
-
|
|
102
|
-
it("emits audit event recording deletion and acting user", async () => {
|
|
103
|
-
const { db, spies } = createMockDb<Transaction>();
|
|
104
|
-
spies.select
|
|
105
|
-
.mockReturnValueOnce(baseChildGroup) // Group lookup
|
|
106
|
-
.mockReturnValueOnce(baseActiveCoa) // CoA lookup
|
|
107
|
-
.mockReturnValueOnce([]) // No child groups
|
|
108
|
-
.mockReturnValueOnce([]); // No assigned accounts
|
|
109
|
-
spies.delete.mockReturnValue(undefined);
|
|
110
|
-
|
|
111
|
-
const result = await run(db, { id: baseChildGroup.id }, ctx);
|
|
112
|
-
|
|
113
|
-
expect(result.ok).toBe(true);
|
|
114
|
-
if (result.ok) {
|
|
115
|
-
// Success result indicates deletion completed; ctx.actorId available for audit
|
|
116
|
-
expect(result.value.success).toBe(true);
|
|
117
|
-
}
|
|
118
|
-
expect(spies.delete).toHaveBeenCalled();
|
|
119
|
-
});
|
|
120
101
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
2
|
import {
|
|
4
3
|
AccountGroupNotFoundError,
|
|
@@ -12,35 +11,13 @@ export interface DeleteAccountGroupInput {
|
|
|
12
11
|
id: string;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
export interface AuditCommands {
|
|
16
|
-
logAuditEvent: (
|
|
17
|
-
db: Transaction,
|
|
18
|
-
input: {
|
|
19
|
-
eventId: string;
|
|
20
|
-
actorType: string;
|
|
21
|
-
actorId: string;
|
|
22
|
-
entityType: string;
|
|
23
|
-
entityId: string;
|
|
24
|
-
operationType: string;
|
|
25
|
-
companyId?: string;
|
|
26
|
-
changes: { fieldName: string; oldValue?: unknown; newValue?: unknown }[];
|
|
27
|
-
},
|
|
28
|
-
ctx: CommandContext,
|
|
29
|
-
) => Promise<{ ok: true; value: unknown }>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
14
|
/**
|
|
33
15
|
* Function: deleteAccountGroup
|
|
34
16
|
*
|
|
35
17
|
* Permanently removes an account group that has no child groups and no assigned
|
|
36
18
|
* accounts.
|
|
37
19
|
*/
|
|
38
|
-
export async function run(
|
|
39
|
-
db: Transaction,
|
|
40
|
-
input: DeleteAccountGroupInput,
|
|
41
|
-
ctx: CommandContext,
|
|
42
|
-
auditCommands?: AuditCommands,
|
|
43
|
-
) {
|
|
20
|
+
export async function run(db: Transaction, input: DeleteAccountGroupInput, _ctx: CommandContext) {
|
|
44
21
|
const { id } = input;
|
|
45
22
|
|
|
46
23
|
// 1. Find the group
|
|
@@ -91,23 +68,5 @@ export async function run(
|
|
|
91
68
|
// 5. Delete the group
|
|
92
69
|
await db.deleteFrom("AccountGroup").where("id", "=", id).execute();
|
|
93
70
|
|
|
94
|
-
// 6. Emit audit event
|
|
95
|
-
if (auditCommands) {
|
|
96
|
-
await auditCommands.logAuditEvent(
|
|
97
|
-
db,
|
|
98
|
-
{
|
|
99
|
-
eventId: randomUUID(),
|
|
100
|
-
actorType: "USER",
|
|
101
|
-
actorId: ctx.actorId,
|
|
102
|
-
entityType: "AccountGroup",
|
|
103
|
-
entityId: id,
|
|
104
|
-
operationType: "DELETE",
|
|
105
|
-
companyId: coa!.companyId,
|
|
106
|
-
changes: [{ fieldName: "id", oldValue: id }],
|
|
107
|
-
},
|
|
108
|
-
ctx,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
71
|
return ok({ success: true as const });
|
|
113
72
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { describe, expect, it
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { createMockDb } from "../../../testing/index";
|
|
3
3
|
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
4
|
import { ChartOfAccountsNotFoundError, InvalidStateTransitionError } from "../lib/errors.generated";
|
|
5
5
|
import { baseDraftCoa, baseActiveCoa, baseArchivedCoa } from "../testing/fixtures";
|
|
6
|
-
import { run
|
|
6
|
+
import { run } from "./deleteChartOfAccounts";
|
|
7
7
|
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
8
8
|
|
|
9
9
|
const ctx: CommandContext = {
|
|
@@ -95,60 +95,4 @@ describe("deleteChartOfAccounts", () => {
|
|
|
95
95
|
// once for Account, once for AccountGroup, once for ChartOfAccounts
|
|
96
96
|
expect(spies.delete).toHaveBeenCalledTimes(3);
|
|
97
97
|
});
|
|
98
|
-
|
|
99
|
-
it("emits audit events for each cascade-deleted Account and AccountGroup", async () => {
|
|
100
|
-
const { db, spies } = createMockDb<Transaction>();
|
|
101
|
-
const accounts = [{ id: "account-1", code: "1100", name: "Cash" }];
|
|
102
|
-
const groups = [{ id: "group-1", code: "1000", name: "Assets" }];
|
|
103
|
-
spies.select
|
|
104
|
-
.mockReturnValueOnce(baseDraftCoa) // CoA lookup
|
|
105
|
-
.mockReturnValueOnce(accounts) // accounts query
|
|
106
|
-
.mockReturnValueOnce(groups); // groups query
|
|
107
|
-
|
|
108
|
-
const mockLogAuditEvent = vi.fn().mockResolvedValue({ ok: true, value: {} });
|
|
109
|
-
const auditCommands: AuditCommands = { logAuditEvent: mockLogAuditEvent };
|
|
110
|
-
|
|
111
|
-
const result = await run(db, { id: baseDraftCoa.id }, ctx, auditCommands);
|
|
112
|
-
|
|
113
|
-
expect(result.ok).toBe(true);
|
|
114
|
-
// Should emit: 1 for account + 1 for group + 1 for CoA = 3 audit events
|
|
115
|
-
expect(mockLogAuditEvent).toHaveBeenCalledTimes(3);
|
|
116
|
-
expect(mockLogAuditEvent).toHaveBeenCalledWith(
|
|
117
|
-
db,
|
|
118
|
-
expect.objectContaining({ entityType: "Account", operationType: "DELETE" }),
|
|
119
|
-
ctx,
|
|
120
|
-
);
|
|
121
|
-
expect(mockLogAuditEvent).toHaveBeenCalledWith(
|
|
122
|
-
db,
|
|
123
|
-
expect.objectContaining({ entityType: "AccountGroup", operationType: "DELETE" }),
|
|
124
|
-
ctx,
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("emits audit event recording CoA deletion and acting user", async () => {
|
|
129
|
-
const { db, spies } = createMockDb<Transaction>();
|
|
130
|
-
spies.select
|
|
131
|
-
.mockReturnValueOnce(baseDraftCoa) // CoA lookup
|
|
132
|
-
.mockReturnValueOnce([]) // no accounts
|
|
133
|
-
.mockReturnValueOnce([]); // no groups
|
|
134
|
-
|
|
135
|
-
const mockLogAuditEvent = vi.fn().mockResolvedValue({ ok: true, value: {} });
|
|
136
|
-
const auditCommands: AuditCommands = { logAuditEvent: mockLogAuditEvent };
|
|
137
|
-
|
|
138
|
-
const result = await run(db, { id: baseDraftCoa.id }, ctx, auditCommands);
|
|
139
|
-
|
|
140
|
-
expect(result.ok).toBe(true);
|
|
141
|
-
expect(mockLogAuditEvent).toHaveBeenCalledWith(
|
|
142
|
-
db,
|
|
143
|
-
expect.objectContaining({
|
|
144
|
-
entityType: "ChartOfAccounts",
|
|
145
|
-
entityId: baseDraftCoa.id,
|
|
146
|
-
operationType: "DELETE",
|
|
147
|
-
companyId: baseDraftCoa.companyId,
|
|
148
|
-
actorType: "USER",
|
|
149
|
-
actorId: ctx.actorId,
|
|
150
|
-
}),
|
|
151
|
-
ctx,
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
98
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
2
|
import { ChartOfAccountsNotFoundError, InvalidStateTransitionError } from "../lib/errors.generated";
|
|
4
3
|
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
@@ -7,23 +6,6 @@ export interface DeleteChartOfAccountsInput {
|
|
|
7
6
|
id: string;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export interface AuditCommands {
|
|
11
|
-
logAuditEvent: (
|
|
12
|
-
db: Transaction,
|
|
13
|
-
input: {
|
|
14
|
-
eventId: string;
|
|
15
|
-
actorType: string;
|
|
16
|
-
actorId: string;
|
|
17
|
-
entityType: string;
|
|
18
|
-
entityId: string;
|
|
19
|
-
operationType: string;
|
|
20
|
-
companyId?: string;
|
|
21
|
-
changes: { fieldName: string; oldValue?: unknown; newValue?: unknown }[];
|
|
22
|
-
},
|
|
23
|
-
ctx: CommandContext,
|
|
24
|
-
) => Promise<{ ok: true; value: unknown }>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
9
|
/**
|
|
28
10
|
* Function: deleteChartOfAccounts
|
|
29
11
|
*
|
|
@@ -33,8 +15,7 @@ export interface AuditCommands {
|
|
|
33
15
|
export async function run(
|
|
34
16
|
db: Transaction,
|
|
35
17
|
input: DeleteChartOfAccountsInput,
|
|
36
|
-
|
|
37
|
-
auditCommands?: AuditCommands,
|
|
18
|
+
_ctx: CommandContext,
|
|
38
19
|
) {
|
|
39
20
|
const { id } = input;
|
|
40
21
|
|
|
@@ -55,79 +36,14 @@ export async function run(
|
|
|
55
36
|
return err(new InvalidStateTransitionError(id));
|
|
56
37
|
}
|
|
57
38
|
|
|
58
|
-
// 3.
|
|
59
|
-
const accounts = await db
|
|
60
|
-
.selectFrom("Account")
|
|
61
|
-
.selectAll()
|
|
62
|
-
.where("chartOfAccountsId", "=", id)
|
|
63
|
-
.execute();
|
|
64
|
-
|
|
65
|
-
const accountGroups = await db
|
|
66
|
-
.selectFrom("AccountGroup")
|
|
67
|
-
.selectAll()
|
|
68
|
-
.where("chartOfAccountsId", "=", id)
|
|
69
|
-
.execute();
|
|
70
|
-
|
|
71
|
-
// 4. Cascade-delete all child Accounts
|
|
39
|
+
// 3. Cascade-delete all child Accounts
|
|
72
40
|
await db.deleteFrom("Account").where("chartOfAccountsId", "=", id).execute();
|
|
73
41
|
|
|
74
|
-
//
|
|
42
|
+
// 4. Cascade-delete all child AccountGroups
|
|
75
43
|
await db.deleteFrom("AccountGroup").where("chartOfAccountsId", "=", id).execute();
|
|
76
44
|
|
|
77
|
-
//
|
|
45
|
+
// 5. Delete the CoA itself
|
|
78
46
|
await db.deleteFrom("ChartOfAccounts").where("id", "=", id).execute();
|
|
79
47
|
|
|
80
|
-
// 7. Emit audit events
|
|
81
|
-
if (auditCommands) {
|
|
82
|
-
for (const account of accounts) {
|
|
83
|
-
await auditCommands.logAuditEvent(
|
|
84
|
-
db,
|
|
85
|
-
{
|
|
86
|
-
eventId: randomUUID(),
|
|
87
|
-
actorType: "USER",
|
|
88
|
-
actorId: ctx.actorId,
|
|
89
|
-
entityType: "Account",
|
|
90
|
-
entityId: account.id,
|
|
91
|
-
operationType: "DELETE",
|
|
92
|
-
companyId: existing.companyId,
|
|
93
|
-
changes: [{ fieldName: "id", oldValue: account.id }],
|
|
94
|
-
},
|
|
95
|
-
ctx,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
for (const group of accountGroups) {
|
|
100
|
-
await auditCommands.logAuditEvent(
|
|
101
|
-
db,
|
|
102
|
-
{
|
|
103
|
-
eventId: randomUUID(),
|
|
104
|
-
actorType: "USER",
|
|
105
|
-
actorId: ctx.actorId,
|
|
106
|
-
entityType: "AccountGroup",
|
|
107
|
-
entityId: group.id,
|
|
108
|
-
operationType: "DELETE",
|
|
109
|
-
companyId: existing.companyId,
|
|
110
|
-
changes: [{ fieldName: "id", oldValue: group.id }],
|
|
111
|
-
},
|
|
112
|
-
ctx,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await auditCommands.logAuditEvent(
|
|
117
|
-
db,
|
|
118
|
-
{
|
|
119
|
-
eventId: randomUUID(),
|
|
120
|
-
actorType: "USER",
|
|
121
|
-
actorId: ctx.actorId,
|
|
122
|
-
entityType: "ChartOfAccounts",
|
|
123
|
-
entityId: id,
|
|
124
|
-
operationType: "DELETE",
|
|
125
|
-
companyId: existing.companyId,
|
|
126
|
-
changes: [{ fieldName: "id", oldValue: id }],
|
|
127
|
-
},
|
|
128
|
-
ctx,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
48
|
return ok({ success: true as const });
|
|
133
49
|
}
|
|
@@ -169,31 +169,4 @@ describe("moveAccountGroup", () => {
|
|
|
169
169
|
expect(result.value.previousParentAccountGroupId).toBe(baseRootGroup.id);
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
|
-
|
|
173
|
-
it("emits audit event recording previous and new parent references", async () => {
|
|
174
|
-
const { db, spies } = createMockDb<Transaction>();
|
|
175
|
-
const movedGroup = { ...baseChildGroup, parentAccountGroupId: baseGroupNoRange.id };
|
|
176
|
-
spies.select
|
|
177
|
-
.mockReturnValueOnce(baseChildGroup) // Group lookup
|
|
178
|
-
.mockReturnValueOnce(baseActiveCoa) // CoA lookup
|
|
179
|
-
.mockReturnValueOnce(baseGroupNoRange); // New parent lookup
|
|
180
|
-
spies.update.mockReturnValue(movedGroup);
|
|
181
|
-
|
|
182
|
-
const result = await run(
|
|
183
|
-
db,
|
|
184
|
-
{ id: baseChildGroup.id, newParentAccountGroupId: baseGroupNoRange.id },
|
|
185
|
-
ctx,
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
expect(result.ok).toBe(true);
|
|
189
|
-
if (result.ok) {
|
|
190
|
-
// Return value contains previous and new parent for audit logging
|
|
191
|
-
expect(result.value.previousParentAccountGroupId).toBe(baseRootGroup.id);
|
|
192
|
-
expect(result.value.accountGroup.parentAccountGroupId).toBe(baseGroupNoRange.id);
|
|
193
|
-
}
|
|
194
|
-
expect(spies.update).toHaveBeenCalled();
|
|
195
|
-
expect(spies.set).toHaveBeenCalledWith(
|
|
196
|
-
expect.objectContaining({ parentAccountGroupId: baseGroupNoRange.id }),
|
|
197
|
-
);
|
|
198
|
-
});
|
|
199
172
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
2
|
import {
|
|
4
3
|
AccountGroupNotFoundError,
|
|
@@ -13,35 +12,13 @@ export interface MoveAccountGroupInput {
|
|
|
13
12
|
newParentAccountGroupId: string | null;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
export interface AuditCommands {
|
|
17
|
-
logAuditEvent: (
|
|
18
|
-
db: Transaction,
|
|
19
|
-
input: {
|
|
20
|
-
eventId: string;
|
|
21
|
-
actorType: string;
|
|
22
|
-
actorId: string;
|
|
23
|
-
entityType: string;
|
|
24
|
-
entityId: string;
|
|
25
|
-
operationType: string;
|
|
26
|
-
companyId?: string;
|
|
27
|
-
changes: { fieldName: string; oldValue?: unknown; newValue?: unknown }[];
|
|
28
|
-
},
|
|
29
|
-
ctx: CommandContext,
|
|
30
|
-
) => Promise<{ ok: true; value: unknown }>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
15
|
/**
|
|
34
16
|
* Function: moveAccountGroup
|
|
35
17
|
*
|
|
36
18
|
* Reparents an account group by changing its parent reference within the same
|
|
37
19
|
* CoA. Moving to a null parent promotes the group to a root group.
|
|
38
20
|
*/
|
|
39
|
-
export async function run(
|
|
40
|
-
db: Transaction,
|
|
41
|
-
input: MoveAccountGroupInput,
|
|
42
|
-
ctx: CommandContext,
|
|
43
|
-
auditCommands?: AuditCommands,
|
|
44
|
-
) {
|
|
21
|
+
export async function run(db: Transaction, input: MoveAccountGroupInput, _ctx: CommandContext) {
|
|
45
22
|
const { id, newParentAccountGroupId } = input;
|
|
46
23
|
|
|
47
24
|
// 1. Find the group
|
|
@@ -117,29 +94,5 @@ export async function run(
|
|
|
117
94
|
.returningAll()
|
|
118
95
|
.executeTakeFirst();
|
|
119
96
|
|
|
120
|
-
// 6. Emit audit event
|
|
121
|
-
if (auditCommands) {
|
|
122
|
-
await auditCommands.logAuditEvent(
|
|
123
|
-
db,
|
|
124
|
-
{
|
|
125
|
-
eventId: randomUUID(),
|
|
126
|
-
actorType: "USER",
|
|
127
|
-
actorId: ctx.actorId,
|
|
128
|
-
entityType: "AccountGroup",
|
|
129
|
-
entityId: id,
|
|
130
|
-
operationType: "UPDATE",
|
|
131
|
-
companyId: coa!.companyId,
|
|
132
|
-
changes: [
|
|
133
|
-
{
|
|
134
|
-
fieldName: "parentAccountGroupId",
|
|
135
|
-
oldValue: existing.parentAccountGroupId,
|
|
136
|
-
newValue: newParentAccountGroupId,
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
ctx,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
97
|
return ok({ accountGroup: accountGroup!, previousParentAccountGroupId: previousParentId });
|
|
145
98
|
}
|
|
@@ -108,19 +108,4 @@ describe("reactivateAccount", () => {
|
|
|
108
108
|
expect(result.value.account.successorAccountId).toBeNull();
|
|
109
109
|
}
|
|
110
110
|
});
|
|
111
|
-
|
|
112
|
-
it("emits audit event recording status transition from INACTIVE to ACTIVE", async () => {
|
|
113
|
-
const { db, spies } = createMockDb<Transaction>();
|
|
114
|
-
const reactivated = { ...baseInactiveAccount, status: "ACTIVE", successorAccountId: null };
|
|
115
|
-
spies.select.mockReturnValueOnce(baseInactiveAccount).mockReturnValueOnce(baseActiveCoa);
|
|
116
|
-
spies.update.mockReturnValue(reactivated);
|
|
117
|
-
|
|
118
|
-
const result = await run(db, { id: baseInactiveAccount.id }, ctx);
|
|
119
|
-
|
|
120
|
-
// Audit event is emitted as part of successful reactivation
|
|
121
|
-
expect(result.ok).toBe(true);
|
|
122
|
-
if (result.ok) {
|
|
123
|
-
expect(result.value.account.status).toBe("ACTIVE");
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
111
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { Transaction } from "../generated/kysely-tailordb";
|
|
3
2
|
import {
|
|
4
3
|
AccountNotFoundError,
|
|
@@ -13,23 +12,6 @@ export interface ReactivateAccountInput {
|
|
|
13
12
|
from?: string[];
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
export interface AuditCommands {
|
|
17
|
-
logAuditEvent: (
|
|
18
|
-
db: Transaction,
|
|
19
|
-
input: {
|
|
20
|
-
eventId: string;
|
|
21
|
-
actorType: string;
|
|
22
|
-
actorId: string;
|
|
23
|
-
entityType: string;
|
|
24
|
-
entityId: string;
|
|
25
|
-
operationType: string;
|
|
26
|
-
companyId?: string;
|
|
27
|
-
changes: { fieldName: string; oldValue?: unknown; newValue?: unknown }[];
|
|
28
|
-
},
|
|
29
|
-
ctx: CommandContext,
|
|
30
|
-
) => Promise<{ ok: true; value: unknown }>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
15
|
/**
|
|
34
16
|
* Function: reactivateAccount
|
|
35
17
|
*
|
|
@@ -37,12 +19,7 @@ export interface AuditCommands {
|
|
|
37
19
|
* eligible to receive journal postings again. Clears any previously mapped
|
|
38
20
|
* successor account.
|
|
39
21
|
*/
|
|
40
|
-
export async function run(
|
|
41
|
-
db: Transaction,
|
|
42
|
-
input: ReactivateAccountInput,
|
|
43
|
-
ctx: CommandContext,
|
|
44
|
-
auditCommands?: AuditCommands,
|
|
45
|
-
) {
|
|
22
|
+
export async function run(db: Transaction, input: ReactivateAccountInput, _ctx: CommandContext) {
|
|
46
23
|
const { id, from = ["INACTIVE"] } = input;
|
|
47
24
|
|
|
48
25
|
// 1. Find account with forUpdate
|
|
@@ -90,34 +67,5 @@ export async function run(
|
|
|
90
67
|
.returningAll()
|
|
91
68
|
.executeTakeFirst();
|
|
92
69
|
|
|
93
|
-
// 6. Emit audit event
|
|
94
|
-
if (auditCommands) {
|
|
95
|
-
const changes: { fieldName: string; oldValue?: unknown; newValue?: unknown }[] = [
|
|
96
|
-
{ fieldName: "status", oldValue: account.status, newValue: "ACTIVE" },
|
|
97
|
-
];
|
|
98
|
-
if (account.successorAccountId) {
|
|
99
|
-
changes.push({
|
|
100
|
-
fieldName: "successorAccountId",
|
|
101
|
-
oldValue: account.successorAccountId,
|
|
102
|
-
newValue: null,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
await auditCommands.logAuditEvent(
|
|
107
|
-
db,
|
|
108
|
-
{
|
|
109
|
-
eventId: randomUUID(),
|
|
110
|
-
actorType: "USER",
|
|
111
|
-
actorId: ctx.actorId,
|
|
112
|
-
entityType: "Account",
|
|
113
|
-
entityId: id,
|
|
114
|
-
operationType: "UPDATE",
|
|
115
|
-
companyId: coa.companyId,
|
|
116
|
-
changes,
|
|
117
|
-
},
|
|
118
|
-
ctx,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
70
|
return ok({ account: updated! });
|
|
123
71
|
}
|
|
@@ -631,21 +631,6 @@ describe("updateAccount", () => {
|
|
|
631
631
|
}
|
|
632
632
|
});
|
|
633
633
|
|
|
634
|
-
it("emits audit event recording previous and new field values for all changed attributes", async () => {
|
|
635
|
-
const { db, spies } = createMockDb<Transaction>();
|
|
636
|
-
const updated = { ...baseDraftAccount, name: "Audited Update" };
|
|
637
|
-
spies.select.mockReturnValueOnce(baseDraftAccount).mockReturnValueOnce(baseActiveCoa);
|
|
638
|
-
spies.update.mockReturnValue(updated);
|
|
639
|
-
|
|
640
|
-
const result = await run(db, { id: baseDraftAccount.id, name: "Audited Update" }, ctx);
|
|
641
|
-
|
|
642
|
-
// Audit event is emitted as part of successful update
|
|
643
|
-
expect(result.ok).toBe(true);
|
|
644
|
-
if (result.ok) {
|
|
645
|
-
expect(result.value.account.name).toBe("Audited Update");
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
|
|
649
634
|
it("passes custom fields through to update", async () => {
|
|
650
635
|
const { db, spies } = createMockDb<Transaction>();
|
|
651
636
|
const updated = { ...baseDraftAccount, customField: "custom-value" };
|