@accounter/server 0.0.9-alpha-20251210155614-e6e65aaecafef9e8fedd0b933f613ffcf478cecf → 0.0.9-alpha-20251210171954-c9c0e7693ebe08d3643d9ee2c00c03606a53e334

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/CHANGELOG.md +43 -5
  2. package/dist/server/src/__tests__/factories/business.d.ts +2 -2
  3. package/dist/server/src/__tests__/factories/business.js +5 -4
  4. package/dist/server/src/__tests__/factories/business.js.map +1 -1
  5. package/dist/server/src/__tests__/factories/business.test.js +2 -2
  6. package/dist/server/src/__tests__/factories/business.test.js.map +1 -1
  7. package/dist/server/src/__tests__/factories/charge.d.ts +9 -9
  8. package/dist/server/src/__tests__/factories/charge.js +15 -15
  9. package/dist/server/src/__tests__/factories/charge.js.map +1 -1
  10. package/dist/server/src/__tests__/factories/charge.test.js +14 -14
  11. package/dist/server/src/__tests__/factories/charge.test.js.map +1 -1
  12. package/dist/server/src/__tests__/factories/document.d.ts +9 -9
  13. package/dist/server/src/__tests__/factories/document.js +11 -11
  14. package/dist/server/src/__tests__/factories/document.js.map +1 -1
  15. package/dist/server/src/__tests__/factories/document.test.js +38 -38
  16. package/dist/server/src/__tests__/factories/document.test.js.map +1 -1
  17. package/dist/server/src/__tests__/factories/financial-account.js +2 -2
  18. package/dist/server/src/__tests__/factories/financial-account.js.map +1 -1
  19. package/dist/server/src/__tests__/factories/financial-account.test.js +7 -7
  20. package/dist/server/src/__tests__/factories/financial-account.test.js.map +1 -1
  21. package/dist/server/src/__tests__/factories/index.d.ts +2 -2
  22. package/dist/server/src/__tests__/factories/index.js +2 -2
  23. package/dist/server/src/__tests__/factories/index.js.map +1 -1
  24. package/dist/server/src/__tests__/factories/index.test.js +12 -12
  25. package/dist/server/src/__tests__/factories/index.test.js.map +1 -1
  26. package/dist/server/src/__tests__/factories/tax-category.d.ts +3 -3
  27. package/dist/server/src/__tests__/factories/tax-category.js +6 -5
  28. package/dist/server/src/__tests__/factories/tax-category.js.map +1 -1
  29. package/dist/server/src/__tests__/factories/tax-category.test.js +4 -4
  30. package/dist/server/src/__tests__/factories/tax-category.test.js.map +1 -1
  31. package/dist/server/src/__tests__/factories/transaction.d.ts +7 -7
  32. package/dist/server/src/__tests__/factories/transaction.js +11 -11
  33. package/dist/server/src/__tests__/factories/transaction.js.map +1 -1
  34. package/dist/server/src/__tests__/factories/transaction.test.js +27 -27
  35. package/dist/server/src/__tests__/factories/transaction.test.js.map +1 -1
  36. package/dist/server/src/__tests__/fixtures/expenses/expense-scenario-a.js +20 -20
  37. package/dist/server/src/__tests__/fixtures/expenses/expense-scenario-a.js.map +1 -1
  38. package/dist/server/src/__tests__/fixtures/expenses/expense-scenario-b.js +20 -20
  39. package/dist/server/src/__tests__/fixtures/expenses/expense-scenario-b.js.map +1 -1
  40. package/dist/server/src/__tests__/fixtures/expenses/expense-scenario-b.test.js +8 -8
  41. package/dist/server/src/__tests__/fixtures/expenses/expense-scenario-b.test.js.map +1 -1
  42. package/dist/server/src/__tests__/helpers/db-setup.d.ts +0 -1
  43. package/dist/server/src/__tests__/helpers/db-setup.js +0 -2
  44. package/dist/server/src/__tests__/helpers/db-setup.js.map +1 -1
  45. package/dist/server/src/__tests__/helpers/fixture-loader.js +2 -2
  46. package/dist/server/src/__tests__/helpers/fixture-loader.js.map +1 -1
  47. package/dist/server/src/__tests__/helpers/fixture-loader.test.js +32 -29
  48. package/dist/server/src/__tests__/helpers/fixture-loader.test.js.map +1 -1
  49. package/dist/server/src/__tests__/helpers/fixture-validation.test.js +50 -50
  50. package/dist/server/src/__tests__/helpers/fixture-validation.test.js.map +1 -1
  51. package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js +23 -31
  52. package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js.map +1 -1
  53. package/dist/server/src/__tests__/helpers/seed-helpers.concurrent.test.js +8 -8
  54. package/dist/server/src/__tests__/helpers/seed-helpers.concurrent.test.js.map +1 -1
  55. package/dist/server/src/__tests__/helpers/seed-helpers.financial-entity.test.js +41 -50
  56. package/dist/server/src/__tests__/helpers/seed-helpers.financial-entity.test.js.map +1 -1
  57. package/dist/server/src/__tests__/helpers/seed-helpers.tax-category.test.js +23 -31
  58. package/dist/server/src/__tests__/helpers/seed-helpers.tax-category.test.js.map +1 -1
  59. package/dist/server/src/__tests__/helpers/test-db-config.js +1 -1
  60. package/dist/server/src/__tests__/helpers/test-db-config.js.map +1 -1
  61. package/dist/server/src/__tests__/seed-admin-context.integration.test.js +128 -131
  62. package/dist/server/src/__tests__/seed-admin-context.integration.test.js.map +1 -1
  63. package/dist/server/src/demo-fixtures/__tests__/deterministic-uuid.test.js +58 -0
  64. package/dist/server/src/demo-fixtures/__tests__/deterministic-uuid.test.js.map +1 -0
  65. package/dist/server/src/demo-fixtures/helpers/deterministic-uuid.d.ts +50 -0
  66. package/dist/server/src/demo-fixtures/helpers/deterministic-uuid.js +66 -0
  67. package/dist/server/src/demo-fixtures/helpers/deterministic-uuid.js.map +1 -0
  68. package/dist/server/src/modules/admin-context/{heplers → helpers}/admin-context.helper.d.ts +1 -1
  69. package/dist/server/src/modules/admin-context/{heplers → helpers}/admin-context.helper.js +2 -2
  70. package/dist/server/src/modules/admin-context/{heplers → helpers}/admin-context.helper.js.map +1 -1
  71. package/dist/server/src/modules/admin-context/resolvers/admin-context.resolvers.js +1 -1
  72. package/dist/server/src/modules/business-trips/providers/business-trips.provider.d.ts +1 -1
  73. package/dist/server/src/modules/business-trips/providers/business-trips.provider.js +1 -1
  74. package/dist/server/src/modules/business-trips/providers/business-trips.provider.js.map +1 -1
  75. package/dist/server/src/modules/charges/helpers/common.helper.js +3 -3
  76. package/dist/server/src/modules/charges/helpers/common.helper.js.map +1 -1
  77. package/dist/server/src/modules/charges/helpers/{merge-charges.hepler.js → merge-charges.helper.js} +6 -6
  78. package/dist/server/src/modules/charges/helpers/{merge-charges.hepler.js.map → merge-charges.helper.js.map} +1 -1
  79. package/dist/server/src/modules/charges/resolvers/charges.resolver.js +1 -1
  80. package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js +2 -2
  81. package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js.map +1 -1
  82. package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js +1 -1
  83. package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js.map +1 -1
  84. package/dist/server/src/modules/corn-jobs/resolvers/corn-jobs.resolver.js +1 -1
  85. package/dist/server/src/modules/corn-jobs/resolvers/corn-jobs.resolver.js.map +1 -1
  86. package/dist/server/src/modules/ledger/__tests__/helpers/ledger-assertions.d.ts +0 -2
  87. package/dist/server/src/modules/ledger/__tests__/helpers/ledger-assertions.js +0 -4
  88. package/dist/server/src/modules/ledger/__tests__/helpers/ledger-assertions.js.map +1 -1
  89. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.js +20 -20
  90. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.js.map +1 -1
  91. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.js +21 -21
  92. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.js.map +1 -1
  93. package/dist/server/src/modules/transactions/helpers/common.helper.js +11 -6
  94. package/dist/server/src/modules/transactions/helpers/common.helper.js.map +1 -1
  95. package/package.json +1 -1
  96. package/src/__tests__/factories/business.test.ts +3 -3
  97. package/src/__tests__/factories/business.ts +5 -4
  98. package/src/__tests__/factories/charge.test.ts +14 -14
  99. package/src/__tests__/factories/charge.ts +16 -16
  100. package/src/__tests__/factories/document.test.ts +38 -38
  101. package/src/__tests__/factories/document.ts +11 -11
  102. package/src/__tests__/factories/financial-account.test.ts +7 -7
  103. package/src/__tests__/factories/financial-account.ts +3 -3
  104. package/src/__tests__/factories/index.test.ts +12 -12
  105. package/src/__tests__/factories/index.ts +2 -2
  106. package/src/__tests__/factories/tax-category.test.ts +4 -4
  107. package/src/__tests__/factories/tax-category.ts +7 -6
  108. package/src/__tests__/factories/transaction.test.ts +27 -27
  109. package/src/__tests__/factories/transaction.ts +11 -11
  110. package/src/__tests__/fixtures/expenses/expense-scenario-a.ts +20 -20
  111. package/src/__tests__/fixtures/expenses/expense-scenario-b.test.ts +8 -8
  112. package/src/__tests__/fixtures/expenses/expense-scenario-b.ts +20 -20
  113. package/src/__tests__/helpers/db-setup.ts +0 -3
  114. package/src/__tests__/helpers/fixture-loader.test.ts +31 -29
  115. package/src/__tests__/helpers/fixture-loader.ts +2 -2
  116. package/src/__tests__/helpers/fixture-validation.test.ts +50 -50
  117. package/src/__tests__/helpers/seed-helpers.business.test.ts +145 -147
  118. package/src/__tests__/helpers/seed-helpers.concurrent.test.ts +10 -10
  119. package/src/__tests__/helpers/seed-helpers.financial-entity.test.ts +218 -231
  120. package/src/__tests__/helpers/seed-helpers.tax-category.test.ts +162 -164
  121. package/src/__tests__/helpers/test-db-config.ts +1 -1
  122. package/src/__tests__/seed-admin-context.integration.test.ts +199 -208
  123. package/src/demo-fixtures/__tests__/deterministic-uuid.test.ts +75 -0
  124. package/src/demo-fixtures/helpers/deterministic-uuid.ts +68 -0
  125. package/src/modules/admin-context/{heplers → helpers}/admin-context.helper.ts +3 -3
  126. package/src/modules/admin-context/resolvers/admin-context.resolvers.ts +1 -1
  127. package/src/modules/business-trips/providers/business-trips.provider.ts +1 -1
  128. package/src/modules/charges/helpers/common.helper.ts +3 -3
  129. package/src/modules/charges/helpers/{merge-charges.hepler.ts → merge-charges.helper.ts} +5 -5
  130. package/src/modules/charges/resolvers/charges.resolver.ts +1 -1
  131. package/src/modules/charges-matcher/__tests__/auto-match-integration.test.ts +2 -2
  132. package/src/modules/charges-matcher/providers/charges-matcher.provider.ts +1 -1
  133. package/src/modules/corn-jobs/resolvers/corn-jobs.resolver.ts +1 -1
  134. package/src/modules/ledger/__tests__/helpers/ledger-assertions.ts +0 -5
  135. package/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.ts +20 -20
  136. package/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.ts +21 -21
  137. package/src/modules/transactions/helpers/common.helper.ts +12 -6
  138. package/dist/server/src/__tests__/factories/ids.d.ts +0 -22
  139. package/dist/server/src/__tests__/factories/ids.js +0 -46
  140. package/dist/server/src/__tests__/factories/ids.js.map +0 -1
  141. package/dist/server/src/__tests__/factories/ids.test.js +0 -71
  142. package/dist/server/src/__tests__/factories/ids.test.js.map +0 -1
  143. package/src/__tests__/factories/ids.test.ts +0 -80
  144. package/src/__tests__/factories/ids.ts +0 -49
  145. /package/dist/server/src/{__tests__/factories/ids.test.d.ts → demo-fixtures/__tests__/deterministic-uuid.test.d.ts} +0 -0
  146. /package/dist/server/src/modules/charges/helpers/{merge-charges.hepler.d.ts → merge-charges.helper.d.ts} +0 -0
@@ -1,240 +1,231 @@
1
- import { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
2
- import pg from 'pg';
3
- import { testDbConfig } from './helpers/test-db-config.js';
1
+ import { describe, expect, it, beforeAll, afterAll } from 'vitest';
2
+ import { TestDatabase } from './helpers/db-setup.js';
4
3
  import { seedAdminCore } from '../../scripts/seed-admin-context.js';
5
4
 
6
5
  describe('seedAdminCore integration', () => {
7
- let pool: pg.Pool;
8
- let client: pg.PoolClient;
6
+ let db: TestDatabase;
9
7
 
10
8
  beforeAll(async () => {
11
- pool = new pg.Pool(testDbConfig);
12
- client = await pool.connect();
9
+ db = new TestDatabase();
10
+ await db.connect();
13
11
  });
14
12
 
15
13
  afterAll(async () => {
16
- client.release();
17
- await pool.end();
14
+ await db.close();
18
15
  });
19
16
 
20
- beforeEach(async () => {
21
- await client.query('BEGIN');
22
- });
17
+ it('should create admin business context with all required entities', async () => {
18
+ await db.withTransaction(async client => {
19
+ // Execute seed
20
+ const { adminEntityId } = await seedAdminCore(client);
21
+
22
+ // Verify admin entity exists
23
+ expect(adminEntityId).toBeTruthy();
24
+ expect(adminEntityId).toMatch(
25
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
26
+ );
23
27
 
24
- afterEach(async () => {
25
- await client.query('ROLLBACK');
26
- });
28
+ // Verify admin financial entity
29
+ const adminEntity = await client.query(
30
+ `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
31
+ [adminEntityId],
32
+ );
33
+ expect(adminEntity.rows).toHaveLength(1);
34
+ expect(adminEntity.rows[0].name).toBe('Admin Business');
35
+ expect(adminEntity.rows[0].type).toBe('business');
36
+ expect(adminEntity.rows[0].owner_id).toBe(adminEntityId); // self-owned
37
+
38
+ // Verify admin business record
39
+ const adminBusiness = await client.query(
40
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
41
+ [adminEntityId],
42
+ );
43
+ expect(adminBusiness.rows).toHaveLength(1);
27
44
 
28
- it('should create admin business context with all required entities', async () => {
45
+ // Verify user_context exists and has required fields
46
+ const userContext = await client.query(
47
+ `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
48
+ [adminEntityId],
49
+ );
50
+ expect(userContext.rows).toHaveLength(1);
29
51
 
30
- // Execute seed
31
- const { adminEntityId } = await seedAdminCore(client);
32
-
33
- // Verify admin entity exists
34
- expect(adminEntityId).toBeTruthy();
35
- expect(adminEntityId).toMatch(
36
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
37
- );
38
-
39
- // Verify admin financial entity
40
- const adminEntity = await client.query(
41
- `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
42
- [adminEntityId],
43
- );
44
- expect(adminEntity.rows).toHaveLength(1);
45
- expect(adminEntity.rows[0].name).toBe('Admin Business');
46
- expect(adminEntity.rows[0].type).toBe('business');
47
- expect(adminEntity.rows[0].owner_id).toBe(adminEntityId); // self-owned
48
-
49
- // Verify admin business record
50
- const adminBusiness = await client.query(
51
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
52
- [adminEntityId],
53
- );
54
- expect(adminBusiness.rows).toHaveLength(1);
55
-
56
- // Verify user_context exists and has required fields
57
- const userContext = await client.query(
58
- `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
59
- [adminEntityId],
60
- );
61
- expect(userContext.rows).toHaveLength(1);
62
-
63
- const context = userContext.rows[0];
64
-
65
- // Verify currencies
66
- expect(context.default_local_currency).toBe('ILS');
67
- expect(context.default_fiat_currency_for_crypto_conversions).toBe('USD');
68
-
69
- // Verify required authority businesses exist
70
- const vatBusiness = await client.query(
71
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
72
- [context.vat_business_id],
73
- );
74
- expect(vatBusiness.rows).toHaveLength(1);
75
-
76
- const taxBusiness = await client.query(
77
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
78
- [context.tax_business_id],
79
- );
80
- expect(taxBusiness.rows).toHaveLength(1);
81
-
82
- const socialSecurityBusiness = await client.query(
83
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
84
- [context.social_security_business_id],
85
- );
86
- expect(socialSecurityBusiness.rows).toHaveLength(1);
87
-
88
- // Verify required tax categories exist
89
- const requiredTaxCategoryFields = [
90
- 'default_tax_category_id',
91
- 'input_vat_tax_category_id',
92
- 'output_vat_tax_category_id',
93
- 'tax_expenses_tax_category_id',
94
- 'exchange_rate_tax_category_id',
95
- 'income_exchange_rate_tax_category_id',
96
- 'exchange_rate_revaluation_tax_category_id',
97
- 'fee_tax_category_id',
98
- 'general_fee_tax_category_id',
99
- 'fine_tax_category_id',
100
- 'untaxable_gifts_tax_category_id',
101
- 'balance_cancellation_tax_category_id',
102
- 'development_foreign_tax_category_id',
103
- 'development_local_tax_category_id',
104
- 'expenses_to_pay_tax_category_id',
105
- 'expenses_in_advance_tax_category_id',
106
- 'income_to_collect_tax_category_id',
107
- 'income_in_advance_tax_category_id',
108
- 'salary_excess_expenses_tax_category_id',
109
- ];
110
-
111
- for (const field of requiredTaxCategoryFields) {
112
- const taxCategoryId = context[field];
113
- expect(taxCategoryId).toBeTruthy();
114
-
115
- const taxCategory = await client.query(
116
- `SELECT * FROM accounter_schema.tax_categories WHERE id = $1`,
117
- [taxCategoryId],
52
+ const context = userContext.rows[0];
53
+
54
+ // Verify currencies
55
+ expect(context.default_local_currency).toBe('ILS');
56
+ expect(context.default_fiat_currency_for_crypto_conversions).toBe('USD');
57
+
58
+ // Verify required authority businesses exist
59
+ const vatBusiness = await client.query(
60
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
61
+ [context.vat_business_id],
118
62
  );
119
- expect(taxCategory.rows).toHaveLength(1);
120
- }
63
+ expect(vatBusiness.rows).toHaveLength(1);
64
+
65
+ const taxBusiness = await client.query(
66
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
67
+ [context.tax_business_id],
68
+ );
69
+ expect(taxBusiness.rows).toHaveLength(1);
70
+
71
+ const socialSecurityBusiness = await client.query(
72
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
73
+ [context.social_security_business_id],
74
+ );
75
+ expect(socialSecurityBusiness.rows).toHaveLength(1);
76
+
77
+ // Verify required tax categories exist
78
+ const requiredTaxCategoryFields = [
79
+ 'default_tax_category_id',
80
+ 'input_vat_tax_category_id',
81
+ 'output_vat_tax_category_id',
82
+ 'tax_expenses_tax_category_id',
83
+ 'exchange_rate_tax_category_id',
84
+ 'income_exchange_rate_tax_category_id',
85
+ 'exchange_rate_revaluation_tax_category_id',
86
+ 'fee_tax_category_id',
87
+ 'general_fee_tax_category_id',
88
+ 'fine_tax_category_id',
89
+ 'untaxable_gifts_tax_category_id',
90
+ 'balance_cancellation_tax_category_id',
91
+ 'development_foreign_tax_category_id',
92
+ 'development_local_tax_category_id',
93
+ 'expenses_to_pay_tax_category_id',
94
+ 'expenses_in_advance_tax_category_id',
95
+ 'income_to_collect_tax_category_id',
96
+ 'income_in_advance_tax_category_id',
97
+ 'salary_excess_expenses_tax_category_id',
98
+ ];
99
+
100
+ for (const field of requiredTaxCategoryFields) {
101
+ const taxCategoryId = context[field];
102
+ expect(taxCategoryId).toBeTruthy();
103
+
104
+ const taxCategory = await client.query(
105
+ `SELECT * FROM accounter_schema.tax_categories WHERE id = $1`,
106
+ [taxCategoryId],
107
+ );
108
+ expect(taxCategory.rows).toHaveLength(1);
109
+ }
110
+ });
121
111
  });
122
112
 
123
113
  it('should be idempotent (safe to call multiple times)', async () => {
124
- // Call seed twice in same transaction
125
- await seedAdminCore(client);
126
-
127
- // Count entities before second call
128
- const countBefore = await client.query(
129
- `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
130
- );
131
- const entitiesBeforeSecondCall = parseInt(countBefore.rows[0].count);
132
-
133
- // Second call should reuse existing entities
134
- await seedAdminCore(client);
135
-
136
- // Count entities after second call - should be same
137
- const countAfter = await client.query(
138
- `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
139
- );
140
- const entitiesAfterSecondCall = parseInt(countAfter.rows[0].count);
141
-
142
- // Idempotent: no new entities created on second call
143
- expect(entitiesAfterSecondCall).toBe(entitiesBeforeSecondCall);
144
-
145
- // Verify only one user_context exists
146
- const userContextCount = await client.query(
147
- `SELECT COUNT(*) as count FROM accounter_schema.user_context`,
148
- );
149
- expect(userContextCount.rows[0].count).toBe('1');
114
+ await db.withTransaction(async client => {
115
+ // Call seed twice in same transaction
116
+ await seedAdminCore(client);
117
+
118
+ // Count entities before second call
119
+ const countBefore = await client.query(
120
+ `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
121
+ );
122
+ const entitiesBeforeSecondCall = parseInt(countBefore.rows[0].count);
123
+
124
+ // Second call should reuse existing entities
125
+ await seedAdminCore(client);
126
+
127
+ // Count entities after second call - should be same
128
+ const countAfter = await client.query(
129
+ `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
130
+ );
131
+ const entitiesAfterSecondCall = parseInt(countAfter.rows[0].count);
132
+
133
+ // Idempotent: no new entities created on second call
134
+ expect(entitiesAfterSecondCall).toBe(entitiesBeforeSecondCall);
135
+
136
+ // Verify only one user_context exists
137
+ const userContextCount = await client.query(
138
+ `SELECT COUNT(*) as count FROM accounter_schema.user_context`,
139
+ );
140
+ expect(userContextCount.rows[0].count).toBe('1');
141
+ });
150
142
  });
151
143
 
152
144
  it('should not leak data between tests (transactional isolation)', async () => {
153
- const TEMP_NAME = 'seed-admin-context.integration.test.ts: temp rollback entity';
145
+ await db.withTransaction(async client => {
146
+ const TEMP_NAME = 'seed-admin-context.integration.test.ts: temp rollback entity';
154
147
 
155
- // Insert a throwaway entity inside a transaction
156
- const insertEntity = await client.query(
157
- `INSERT INTO accounter_schema.financial_entities (name, type)
148
+ // Insert a throwaway entity inside a transaction
149
+ const insertEntity = await client.query(
150
+ `INSERT INTO accounter_schema.financial_entities (name, type)
158
151
  VALUES ($1, 'business')
159
152
  RETURNING id`,
160
- [TEMP_NAME],
161
- );
162
- const tempId = insertEntity.rows[0].id;
163
- await client.query(
164
- `INSERT INTO accounter_schema.businesses (id) VALUES ($1)`,
165
- [tempId],
166
- );
167
-
168
- // Verify exists within the same transaction
169
- const inTx = await client.query(
170
- `SELECT COUNT(*) as count FROM accounter_schema.financial_entities WHERE name = $1`,
171
- [TEMP_NAME],
172
- );
173
- expect(inTx.rows[0].count).toBe('1');
174
-
175
- // Note: The afterEach hook will rollback this transaction,
176
- // and the next test will start with a clean slate
153
+ [TEMP_NAME],
154
+ );
155
+ const tempId = insertEntity.rows[0].id;
156
+ await client.query(`INSERT INTO accounter_schema.businesses (id) VALUES ($1)`, [tempId]);
157
+
158
+ // Verify exists within the same transaction
159
+ const inTx = await client.query(
160
+ `SELECT COUNT(*) as count FROM accounter_schema.financial_entities WHERE name = $1`,
161
+ [TEMP_NAME],
162
+ );
163
+ expect(inTx.rows[0].count).toBe('1');
164
+
165
+ // Note: withTransaction automatically rolls back at the end,
166
+ // so the next test will start with a clean slate
167
+ });
177
168
  });
178
169
 
179
170
  it('should create all expected entity counts', async () => {
180
- await seedAdminCore(client);
181
-
182
- // Count businesses (Admin + 3 authorities = 4)
183
- const businessCount = await client.query(
184
- `SELECT COUNT(*) as count FROM accounter_schema.businesses`,
185
- );
186
- expect(parseInt(businessCount.rows[0].count)).toBeGreaterThanOrEqual(4);
187
-
188
- // Count tax categories (3 authority + 12 general + 4 cross-year = 19)
189
- const taxCategoryCount = await client.query(
190
- `SELECT COUNT(*) as count FROM accounter_schema.tax_categories`,
191
- );
192
- expect(parseInt(taxCategoryCount.rows[0].count)).toBeGreaterThanOrEqual(19);
171
+ await db.withTransaction(async client => {
172
+ await seedAdminCore(client);
173
+
174
+ // Count businesses (Admin + 3 authorities = 4)
175
+ const businessCount = await client.query(
176
+ `SELECT COUNT(*) as count FROM accounter_schema.businesses`,
177
+ );
178
+ expect(parseInt(businessCount.rows[0].count)).toBeGreaterThanOrEqual(4);
179
+
180
+ // Count tax categories (3 authority + 12 general + 4 cross-year = 19)
181
+ const taxCategoryCount = await client.query(
182
+ `SELECT COUNT(*) as count FROM accounter_schema.tax_categories`,
183
+ );
184
+ expect(parseInt(taxCategoryCount.rows[0].count)).toBeGreaterThanOrEqual(19);
185
+ });
193
186
  });
194
187
 
195
188
  it('should validate foreign key relationships', async () => {
196
- const { adminEntityId } = await seedAdminCore(client);
197
-
198
- // Get user_context
199
- const userContext = await client.query(
200
- `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
201
- [adminEntityId],
202
- );
203
-
204
- const context = userContext.rows[0];
205
-
206
- // Verify all business FKs point to valid financial_entities
207
- const businessFields = [
208
- 'vat_business_id',
209
- 'tax_business_id',
210
- 'social_security_business_id',
211
- ];
212
-
213
- for (const field of businessFields) {
214
- const businessId = context[field];
215
- const entity = await client.query(
216
- `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
217
- [businessId],
218
- );
219
- expect(entity.rows).toHaveLength(1);
220
- expect(entity.rows[0].type).toBe('business');
221
- }
222
-
223
- // Verify all tax_category FKs point to valid financial_entities
224
- const taxCategoryFields = [
225
- 'default_tax_category_id',
226
- 'input_vat_tax_category_id',
227
- 'output_vat_tax_category_id',
228
- ];
229
-
230
- for (const field of taxCategoryFields) {
231
- const taxCategoryId = context[field];
232
- const entity = await client.query(
233
- `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
234
- [taxCategoryId],
189
+ await db.withTransaction(async client => {
190
+ const { adminEntityId } = await seedAdminCore(client);
191
+
192
+ // Get user_context
193
+ const userContext = await client.query(
194
+ `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
195
+ [adminEntityId],
235
196
  );
236
- expect(entity.rows).toHaveLength(1);
237
- expect(entity.rows[0].type).toBe('tax_category');
238
- }
197
+
198
+ const context = userContext.rows[0];
199
+
200
+ // Verify all business FKs point to valid financial_entities
201
+ const businessFields = ['vat_business_id', 'tax_business_id', 'social_security_business_id'];
202
+
203
+ for (const field of businessFields) {
204
+ const businessId = context[field];
205
+ const entity = await client.query(
206
+ `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
207
+ [businessId],
208
+ );
209
+ expect(entity.rows).toHaveLength(1);
210
+ expect(entity.rows[0].type).toBe('business');
211
+ }
212
+
213
+ // Verify all tax_category FKs point to valid financial_entities
214
+ const taxCategoryFields = [
215
+ 'default_tax_category_id',
216
+ 'input_vat_tax_category_id',
217
+ 'output_vat_tax_category_id',
218
+ ];
219
+
220
+ for (const field of taxCategoryFields) {
221
+ const taxCategoryId = context[field];
222
+ const entity = await client.query(
223
+ `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
224
+ [taxCategoryId],
225
+ );
226
+ expect(entity.rows).toHaveLength(1);
227
+ expect(entity.rows[0].type).toBe('tax_category');
228
+ }
229
+ });
239
230
  });
240
231
  });
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { makeUUID } from '../helpers/deterministic-uuid.js';
3
+
4
+ describe('deterministic-uuid', () => {
5
+ describe('makeUUID', () => {
6
+ it('generates same UUID for same inputs', () => {
7
+ const uuid1 = makeUUID('business', 'acme-consulting-llc');
8
+ const uuid2 = makeUUID('business', 'acme-consulting-llc');
9
+
10
+ expect(uuid1).toBe(uuid2);
11
+ expect(uuid1).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
12
+ });
13
+
14
+ it('generates different UUIDs for different names', () => {
15
+ const uuid1 = makeUUID('business', 'acme-llc');
16
+ const uuid2 = makeUUID('business', 'acme-corp');
17
+
18
+ expect(uuid1).not.toBe(uuid2);
19
+ });
20
+
21
+ it('generates different UUIDs for different namespaces', () => {
22
+ const businessUUID = makeUUID('business', 'acme');
23
+ const chargeUUID = makeUUID('charge', 'acme');
24
+
25
+ expect(businessUUID).not.toBe(chargeUUID);
26
+ });
27
+
28
+ it('includes namespace in composite key', () => {
29
+ // Same name, different namespace types
30
+ const bizUUID = makeUUID('business', 'test-entity');
31
+ const transactionUUID = makeUUID('transaction', 'test-entity');
32
+ const documentUUID = makeUUID('document', 'test-entity');
33
+
34
+ expect(bizUUID).not.toBe(transactionUUID);
35
+ expect(bizUUID).not.toBe(documentUUID);
36
+ expect(transactionUUID).not.toBe(documentUUID);
37
+ });
38
+
39
+ it('generates valid UUID v5 format', () => {
40
+ const uuid = makeUUID('charge', 'monthly-invoice-2024-11');
41
+
42
+ // UUID v5 format: xxxxxxxx-xxxx-5xxx-yxxx-xxxxxxxxxxxx
43
+ // where y is one of [8, 9, a, b]
44
+ expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
45
+ });
46
+
47
+ it('handles kebab-case names correctly', () => {
48
+ const uuid1 = makeUUID('business', 'us-supplier-acme-llc');
49
+ const uuid2 = makeUUID('business', 'us-supplier-acme-llc');
50
+
51
+ expect(uuid1).toBe(uuid2);
52
+ });
53
+
54
+ it('handles numeric components in names', () => {
55
+ const uuid1 = makeUUID('charge', 'invoice-2024-11-15');
56
+ const uuid2 = makeUUID('charge', 'invoice-2024-11-15');
57
+
58
+ expect(uuid1).toBe(uuid2);
59
+ });
60
+
61
+ it('is case-sensitive for names', () => {
62
+ const uuid1 = makeUUID('business', 'acme-llc');
63
+ const uuid2 = makeUUID('business', 'ACME-LLC');
64
+
65
+ expect(uuid1).not.toBe(uuid2);
66
+ });
67
+
68
+ it('is case-sensitive for namespaces', () => {
69
+ const uuid1 = makeUUID('business', 'acme');
70
+ const uuid2 = makeUUID('Business', 'acme');
71
+
72
+ expect(uuid1).not.toBe(uuid2);
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,68 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { v5 as uuidv5 } from 'uuid';
3
+
4
+ /**
5
+ * Fixed namespace for all demo data (regenerate on schema-breaking changes if needed).
6
+ * Using standard DNS namespace UUID as recommended by RFC 4122.
7
+ */
8
+ const DEMO_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
9
+
10
+ /**
11
+ * Generate deterministic UUID v5 from semantic name.
12
+ *
13
+ * This function creates stable, reproducible UUIDs for demo/staging data entities.
14
+ * The same namespace + name combination will always produce the same UUID across
15
+ * deployments, making it safe to reference these IDs in documentation, screenshots,
16
+ * and external links.
17
+ *
18
+ * @param namespace - Entity type: 'business', 'charge', 'transaction', 'document', etc.
19
+ * @param name - Semantic identifier (kebab-case recommended)
20
+ * @returns UUID v5 string (e.g., '550e8400-e29b-41d4-a716-446655440000')
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // Business entity
25
+ * makeUUID('business', 'acme-consulting-llc')
26
+ * // => Always produces same UUID
27
+ *
28
+ * // Charge entity
29
+ * makeUUID('charge', 'consulting-invoice-2024-11')
30
+ * // => Stable across deploys
31
+ *
32
+ * // Financial account
33
+ * makeUUID('financial-account', 'bank-usd-account')
34
+ * // => Different from makeUUID('business', 'bank-usd-account')
35
+ * ```
36
+ *
37
+ * @remarks
38
+ * **Naming Conventions:**
39
+ * - Use kebab-case for all semantic names
40
+ * - Combine entity type + descriptive name ensures no collisions
41
+ * - Never change a semantic name once deployed (breaks external links)
42
+ * - For updates to same entity, append version: `acme-consulting-llc-v2`
43
+ *
44
+ * **Stability Warning:**
45
+ * Once a UUID is generated and used in staging/production, the semantic name
46
+ * MUST NOT be changed. Doing so will generate a different UUID and break:
47
+ * - External documentation with embedded links
48
+ * - Screenshots with visible UUIDs
49
+ * - Any hardcoded references in client applications
50
+ *
51
+ * If you need to modify an entity, create a new semantic name with a version suffix.
52
+ */
53
+ export function makeUUID(namespace: string, name: string): string {
54
+ const composite = `${namespace}:${name}`;
55
+ return uuidv5(composite, DEMO_NAMESPACE);
56
+ }
57
+
58
+ /**
59
+ * Backward compatible adapter for legacy single-argument API.
60
+ * - When seed is provided, returns deterministic UUID v5 scoped under 'legacy' namespace
61
+ * - When seed omitted, returns a random UUID to preserve previous behavior
62
+ */
63
+ export function makeUUIDLegacy(seed?: string): string {
64
+ if (seed === undefined || seed === null) {
65
+ return randomUUID();
66
+ }
67
+ return makeUUID('legacy', seed);
68
+ }
@@ -1,11 +1,11 @@
1
1
  import { GraphQLError } from 'graphql';
2
2
  import { Injector } from 'graphql-modules';
3
- import { BusinessesProvider } from '../../../modules/financial-entities/providers/businesses.provider.js';
4
- import { TaxCategoriesProvider } from '../../../modules/financial-entities/providers/tax-categories.provider.js';
3
+ import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js';
4
+ import { TaxCategoriesProvider } from '../../financial-entities/providers/tax-categories.provider.js';
5
5
  import type {
6
6
  IGetAllTaxCategoriesResult,
7
7
  IGetBusinessesByIdsResult,
8
- } from '../../../modules/financial-entities/types.js';
8
+ } from '../../financial-entities/types.js';
9
9
 
10
10
  export function fetchTaxCategory(
11
11
  injector: Injector,
@@ -2,7 +2,7 @@ import { GraphQLError } from 'graphql';
2
2
  import { TagsProvider } from '../../../modules/tags/providers/tags.provider.js';
3
3
  import { Currency } from '../../../shared/enums.js';
4
4
  import { dateToTimelessDateString } from '../../../shared/helpers/index.js';
5
- import { fetchBusiness, fetchTaxCategory } from '../heplers/admin-context.helper.js';
5
+ import { fetchBusiness, fetchTaxCategory } from '../helpers/admin-context.helper.js';
6
6
  import { AdminContextProvider } from '../providers/admin-context.provider.js';
7
7
  import type { AdminContextModule } from '../types.js';
8
8
 
@@ -1,8 +1,8 @@
1
1
  import DataLoader from 'dataloader';
2
2
  import { Injectable, Scope } from 'graphql-modules';
3
3
  import { sql } from '@pgtyped/runtime';
4
- import { DBProvider } from '../../../modules/app-providers/db.provider.js';
5
4
  import { getCacheInstance } from '../../../shared/helpers/index.js';
5
+ import { DBProvider } from '../../app-providers/db.provider.js';
6
6
  import type {
7
7
  BusinessTripProto,
8
8
  IDeleteChargeBusinessTripQuery,
@@ -28,13 +28,13 @@ export async function calculateTotalAmount(
28
28
  getChargeDocumentsMeta(chargeId, injector),
29
29
  ]);
30
30
 
31
- if (charge.type === 'PAYROLL' && transactionsAmount != null) {
31
+ if (charge.type === 'PAYROLL' && transactionsAmount) {
32
32
  return formatFinancialAmount(transactionsAmount, defaultLocalCurrency);
33
33
  }
34
- if (documentsAmount != null && documentsCurrency) {
34
+ if (documentsAmount && documentsCurrency) {
35
35
  return formatFinancialAmount(documentsAmount, documentsCurrency);
36
36
  }
37
- if (transactionsAmount != null && transactionsCurrency) {
37
+ if (transactionsAmount && transactionsCurrency) {
38
38
  return formatFinancialAmount(transactionsAmount, transactionsCurrency);
39
39
  }
40
40
  return null;
@@ -1,10 +1,10 @@
1
1
  import { GraphQLError } from 'graphql';
2
2
  import { Injector } from 'graphql-modules';
3
- import { BusinessTripEmployeePaymentsProvider } from '../../../modules/business-trips/providers/business-trips-employee-payments.provider.js';
4
- import { DocumentsProvider } from '../../../modules/documents/providers/documents.provider.js';
5
- import { LedgerProvider } from '../../../modules/ledger/providers/ledger.provider.js';
6
- import { MiscExpensesProvider } from '../../../modules/misc-expenses/providers/misc-expenses.provider.js';
7
- import { TransactionsProvider } from '../../../modules/transactions/providers/transactions.provider.js';
3
+ import { BusinessTripEmployeePaymentsProvider } from '../../business-trips/providers/business-trips-employee-payments.provider.js';
4
+ import { DocumentsProvider } from '../../documents/providers/documents.provider.js';
5
+ import { LedgerProvider } from '../../ledger/providers/ledger.provider.js';
6
+ import { MiscExpensesProvider } from '../../misc-expenses/providers/misc-expenses.provider.js';
7
+ import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
8
8
  import { deleteCharges } from './delete-charges.helper.js';
9
9
 
10
10
  export const mergeChargesExecutor = async (
@@ -26,7 +26,7 @@ import {
26
26
  getChargeTransactionsMeta,
27
27
  } from '../helpers/common.helper.js';
28
28
  import { deleteCharges } from '../helpers/delete-charges.helper.js';
29
- import { mergeChargesExecutor } from '../helpers/merge-charges.hepler.js';
29
+ import { mergeChargesExecutor } from '../helpers/merge-charges.helper.js';
30
30
  import { ChargeSpreadProvider } from '../providers/charge-spread.provider.js';
31
31
  import { ChargeRequiredWrapper, ChargesProvider } from '../providers/charges.provider.js';
32
32
  import type {