@accounter/shaam-uniform-format-generator 0.1.0 → 0.1.1

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 (269) hide show
  1. package/README.md +366 -13
  2. package/dist/README.md +476 -0
  3. package/dist/cjs/api/generate-report-legacy.js +6 -0
  4. package/dist/cjs/api/generate-report.js +328 -0
  5. package/dist/cjs/constants.js +11 -0
  6. package/dist/cjs/generator/format/encoder.js +86 -0
  7. package/dist/cjs/generator/records/a000-sum.js +66 -0
  8. package/dist/cjs/generator/records/a000.js +349 -0
  9. package/dist/cjs/generator/records/a100.js +107 -0
  10. package/dist/cjs/generator/records/b100.js +305 -0
  11. package/dist/cjs/generator/records/b110.js +255 -0
  12. package/dist/cjs/generator/records/c100.js +338 -0
  13. package/dist/cjs/generator/records/d110.js +272 -0
  14. package/dist/cjs/generator/records/d120.js +278 -0
  15. package/{cjs → dist/cjs}/generator/records/index.js +1 -0
  16. package/dist/cjs/generator/records/m100.js +177 -0
  17. package/dist/cjs/generator/records/z900.js +93 -0
  18. package/{cjs → dist/cjs}/index.js +3 -0
  19. package/dist/cjs/records/a100.js +78 -0
  20. package/dist/cjs/records/index.js +20 -0
  21. package/dist/cjs/records/z900.js +82 -0
  22. package/dist/cjs/types/enums.js +457 -0
  23. package/{cjs → dist/cjs}/types/index.js +6 -1
  24. package/dist/cjs/utils/file-helpers.js +198 -0
  25. package/dist/cjs/utils/index.js +8 -0
  26. package/dist/cjs/utils/key-generator.js +71 -0
  27. package/dist/esm/api/generate-report-legacy.js +2 -0
  28. package/dist/esm/api/generate-report.js +325 -0
  29. package/dist/esm/constants.js +8 -0
  30. package/dist/esm/generator/format/encoder.js +77 -0
  31. package/dist/esm/generator/records/a000-sum.js +61 -0
  32. package/dist/esm/generator/records/a000.js +344 -0
  33. package/dist/esm/generator/records/a100.js +102 -0
  34. package/dist/esm/generator/records/b100.js +300 -0
  35. package/dist/esm/generator/records/b110.js +250 -0
  36. package/dist/esm/generator/records/c100.js +333 -0
  37. package/dist/esm/generator/records/d110.js +267 -0
  38. package/dist/esm/generator/records/d120.js +273 -0
  39. package/{esm → dist/esm}/generator/records/index.js +1 -0
  40. package/dist/esm/generator/records/m100.js +172 -0
  41. package/dist/esm/generator/records/z900.js +88 -0
  42. package/{esm → dist/esm}/index.js +3 -0
  43. package/dist/esm/records/a100.js +73 -0
  44. package/dist/esm/records/index.js +11 -0
  45. package/dist/esm/records/z900.js +77 -0
  46. package/dist/esm/types/enums.js +454 -0
  47. package/{esm → dist/esm}/types/index.js +5 -1
  48. package/dist/esm/utils/file-helpers.js +188 -0
  49. package/dist/esm/utils/index.js +5 -0
  50. package/dist/esm/utils/key-generator.js +65 -0
  51. package/dist/package.json +54 -0
  52. package/dist/typings/api/generate-report-legacy.d.cts +1 -0
  53. package/dist/typings/api/generate-report-legacy.d.ts +1 -0
  54. package/dist/typings/constants.d.cts +8 -0
  55. package/dist/typings/constants.d.ts +8 -0
  56. package/dist/typings/generator/format/encoder.d.cts +57 -0
  57. package/dist/typings/generator/format/encoder.d.ts +57 -0
  58. package/dist/typings/generator/records/a000-sum.d.cts +40 -0
  59. package/dist/typings/generator/records/a000-sum.d.ts +40 -0
  60. package/dist/typings/generator/records/a000.d.cts +238 -0
  61. package/dist/typings/generator/records/a000.d.ts +238 -0
  62. package/dist/typings/generator/records/a100.d.cts +59 -0
  63. package/dist/typings/generator/records/a100.d.ts +59 -0
  64. package/dist/typings/generator/records/b100.d.cts +101 -0
  65. package/dist/typings/generator/records/b100.d.ts +101 -0
  66. package/dist/typings/generator/records/b110.d.cts +89 -0
  67. package/dist/typings/generator/records/b110.d.ts +89 -0
  68. package/dist/typings/generator/records/c100.d.cts +133 -0
  69. package/dist/typings/generator/records/c100.d.ts +133 -0
  70. package/dist/typings/generator/records/d110.d.cts +98 -0
  71. package/dist/typings/generator/records/d110.d.ts +98 -0
  72. package/dist/typings/generator/records/d120.d.cts +95 -0
  73. package/dist/typings/generator/records/d120.d.ts +95 -0
  74. package/{typings → dist/typings}/generator/records/index.d.cts +1 -0
  75. package/{typings → dist/typings}/generator/records/index.d.ts +1 -0
  76. package/dist/typings/generator/records/m100.d.cts +69 -0
  77. package/dist/typings/generator/records/m100.d.ts +69 -0
  78. package/dist/typings/generator/records/z900.d.cts +61 -0
  79. package/dist/typings/generator/records/z900.d.ts +61 -0
  80. package/{typings → dist/typings}/index.d.cts +3 -0
  81. package/{typings → dist/typings}/index.d.ts +3 -0
  82. package/dist/typings/records/a100.d.cts +35 -0
  83. package/dist/typings/records/a100.d.ts +35 -0
  84. package/dist/typings/records/index.d.cts +2 -0
  85. package/dist/typings/records/index.d.ts +2 -0
  86. package/dist/typings/records/z900.d.cts +38 -0
  87. package/dist/typings/records/z900.d.ts +38 -0
  88. package/dist/typings/types/enums.d.cts +162 -0
  89. package/dist/typings/types/enums.d.ts +162 -0
  90. package/{typings → dist/typings}/types/index.d.cts +17 -14
  91. package/{typings → dist/typings}/types/index.d.ts +17 -14
  92. package/dist/typings/utils/file-helpers.d.cts +131 -0
  93. package/dist/typings/utils/file-helpers.d.ts +131 -0
  94. package/dist/typings/utils/index.d.cts +5 -0
  95. package/dist/typings/utils/index.d.ts +5 -0
  96. package/dist/typings/utils/key-generator.d.cts +41 -0
  97. package/dist/typings/utils/key-generator.d.ts +41 -0
  98. package/documentation/IncomeTax_IncomeTaxSoftwareHousesInfo_horaot1.31_2_05.pdf +0 -0
  99. package/documentation/_4D6963726F736F667420576F7264202D20F8E5E0E9ED20F8E7E5F720F8E5E0E9ED20F9F7E5F32E646F63_.pdf +0 -0
  100. package/documentation/a000-sum.csv +3 -0
  101. package/documentation/a000.csv +37 -0
  102. package/documentation/a100.csv +7 -0
  103. package/documentation/b100.csv +29 -0
  104. package/documentation/b110.csv +26 -0
  105. package/documentation/c100.csv +37 -0
  106. package/documentation/d110.csv +27 -0
  107. package/documentation/d120.csv +26 -0
  108. package/documentation/m100.csv +17 -0
  109. package/documentation/z900.csv +8 -0
  110. package/package.json +50 -29
  111. package/prompt_plan.md +259 -0
  112. package/spec.md +206 -0
  113. package/src/api/generate-report.ts +366 -0
  114. package/src/api/parse-files.ts +33 -0
  115. package/src/constants.ts +9 -0
  116. package/src/format/index.ts +6 -0
  117. package/src/format/newline.ts +8 -0
  118. package/src/format/padding.ts +39 -0
  119. package/src/generator/format/decoder.ts +15 -0
  120. package/src/generator/format/encoder.ts +95 -0
  121. package/src/generator/format/index.ts +6 -0
  122. package/src/generator/index.ts +6 -0
  123. package/src/generator/records/a000-sum.ts +78 -0
  124. package/src/generator/records/a000.ts +373 -0
  125. package/src/generator/records/a100.ts +118 -0
  126. package/src/generator/records/b100.ts +317 -0
  127. package/src/generator/records/b110.ts +267 -0
  128. package/src/generator/records/c100.ts +347 -0
  129. package/src/generator/records/d110.ts +286 -0
  130. package/src/generator/records/d120.ts +293 -0
  131. package/src/generator/records/index.ts +14 -0
  132. package/src/generator/records/m100.ts +185 -0
  133. package/src/generator/records/z900.ts +104 -0
  134. package/src/index.ts +18 -0
  135. package/src/parser/data-parser.ts +14 -0
  136. package/src/parser/index.ts +6 -0
  137. package/src/parser/ini-parser.ts +14 -0
  138. package/src/types/enums.ts +531 -0
  139. package/src/types/index.ts +110 -0
  140. package/src/utils/file-helpers.ts +221 -0
  141. package/src/utils/index.ts +6 -0
  142. package/src/utils/key-generator.ts +75 -0
  143. package/src/validation/errors.ts +35 -0
  144. package/src/validation/index.ts +6 -0
  145. package/src/validation/validate-input.ts +67 -0
  146. package/tests/debug-output.test.ts +81 -0
  147. package/tests/format/crlf-join.test.ts +124 -0
  148. package/tests/format/encoder.test.ts +80 -0
  149. package/tests/format/newline.test.ts +19 -0
  150. package/tests/format/padding.test.ts +74 -0
  151. package/tests/index.test.ts +29 -0
  152. package/tests/ini-text.test.ts +122 -0
  153. package/tests/integration/comprehensive.integration.test.ts +350 -0
  154. package/tests/integration/roundtrip.integration.test.ts +377 -0
  155. package/tests/records/a000-sum.test.ts +278 -0
  156. package/tests/records/a000.test.ts +318 -0
  157. package/tests/records/a100.test.ts +239 -0
  158. package/tests/records/b100.test.ts +419 -0
  159. package/tests/records/b110.test.ts +445 -0
  160. package/tests/records/c100.test.ts +333 -0
  161. package/tests/records/d110.test.ts +93 -0
  162. package/tests/records/d120.test.ts +275 -0
  163. package/tests/records/m100.test.ts +437 -0
  164. package/tests/records/z900.test.ts +254 -0
  165. package/tests/types/enums.test.ts +290 -0
  166. package/tests/utils/file-helpers.test.ts +276 -0
  167. package/tests/utils/key-generator.test.ts +121 -0
  168. package/tests/validation/document-type-validation.test.ts +521 -0
  169. package/tests/validation/validate-input.test.ts +219 -0
  170. package/todo.md +203 -0
  171. package/tsconfig.json +10 -0
  172. package/vitest.config.ts +11 -0
  173. package/cjs/api/generate-report.js +0 -53
  174. package/cjs/generator/format/encoder.js +0 -46
  175. package/cjs/generator/records/a000.js +0 -8
  176. package/cjs/generator/records/a100.js +0 -8
  177. package/cjs/generator/records/b100.js +0 -8
  178. package/cjs/generator/records/b110.js +0 -8
  179. package/cjs/generator/records/c100.js +0 -8
  180. package/cjs/generator/records/d110.js +0 -8
  181. package/cjs/generator/records/d120.js +0 -8
  182. package/cjs/generator/records/m100.js +0 -8
  183. package/cjs/generator/records/z900.js +0 -8
  184. package/esm/api/generate-report.js +0 -50
  185. package/esm/generator/format/encoder.js +0 -42
  186. package/esm/generator/records/a000.js +0 -5
  187. package/esm/generator/records/a100.js +0 -5
  188. package/esm/generator/records/b100.js +0 -5
  189. package/esm/generator/records/b110.js +0 -5
  190. package/esm/generator/records/c100.js +0 -5
  191. package/esm/generator/records/d110.js +0 -5
  192. package/esm/generator/records/d120.js +0 -5
  193. package/esm/generator/records/m100.js +0 -5
  194. package/esm/generator/records/z900.js +0 -5
  195. package/typings/generator/format/encoder.d.cts +0 -33
  196. package/typings/generator/format/encoder.d.ts +0 -33
  197. package/typings/generator/records/a000.d.cts +0 -4
  198. package/typings/generator/records/a000.d.ts +0 -4
  199. package/typings/generator/records/a100.d.cts +0 -4
  200. package/typings/generator/records/a100.d.ts +0 -4
  201. package/typings/generator/records/b100.d.cts +0 -4
  202. package/typings/generator/records/b100.d.ts +0 -4
  203. package/typings/generator/records/b110.d.cts +0 -4
  204. package/typings/generator/records/b110.d.ts +0 -4
  205. package/typings/generator/records/c100.d.cts +0 -4
  206. package/typings/generator/records/c100.d.ts +0 -4
  207. package/typings/generator/records/d110.d.cts +0 -4
  208. package/typings/generator/records/d110.d.ts +0 -4
  209. package/typings/generator/records/d120.d.cts +0 -4
  210. package/typings/generator/records/d120.d.ts +0 -4
  211. package/typings/generator/records/m100.d.cts +0 -4
  212. package/typings/generator/records/m100.d.ts +0 -4
  213. package/typings/generator/records/z900.d.cts +0 -4
  214. package/typings/generator/records/z900.d.ts +0 -4
  215. /package/{cjs → dist/cjs}/api/parse-files.js +0 -0
  216. /package/{cjs → dist/cjs}/format/index.js +0 -0
  217. /package/{cjs → dist/cjs}/format/newline.js +0 -0
  218. /package/{cjs → dist/cjs}/format/padding.js +0 -0
  219. /package/{cjs → dist/cjs}/generator/format/decoder.js +0 -0
  220. /package/{cjs → dist/cjs}/generator/format/index.js +0 -0
  221. /package/{cjs → dist/cjs}/generator/index.js +0 -0
  222. /package/{cjs → dist/cjs}/package.json +0 -0
  223. /package/{cjs → dist/cjs}/parser/data-parser.js +0 -0
  224. /package/{cjs → dist/cjs}/parser/index.js +0 -0
  225. /package/{cjs → dist/cjs}/parser/ini-parser.js +0 -0
  226. /package/{cjs → dist/cjs}/validation/errors.js +0 -0
  227. /package/{cjs → dist/cjs}/validation/index.js +0 -0
  228. /package/{cjs → dist/cjs}/validation/validate-input.js +0 -0
  229. /package/{esm → dist/esm}/api/parse-files.js +0 -0
  230. /package/{esm → dist/esm}/format/index.js +0 -0
  231. /package/{esm → dist/esm}/format/newline.js +0 -0
  232. /package/{esm → dist/esm}/format/padding.js +0 -0
  233. /package/{esm → dist/esm}/generator/format/decoder.js +0 -0
  234. /package/{esm → dist/esm}/generator/format/index.js +0 -0
  235. /package/{esm → dist/esm}/generator/index.js +0 -0
  236. /package/{esm → dist/esm}/parser/data-parser.js +0 -0
  237. /package/{esm → dist/esm}/parser/index.js +0 -0
  238. /package/{esm → dist/esm}/parser/ini-parser.js +0 -0
  239. /package/{esm → dist/esm}/validation/errors.js +0 -0
  240. /package/{esm → dist/esm}/validation/index.js +0 -0
  241. /package/{esm → dist/esm}/validation/validate-input.js +0 -0
  242. /package/{typings → dist/typings}/api/generate-report.d.cts +0 -0
  243. /package/{typings → dist/typings}/api/generate-report.d.ts +0 -0
  244. /package/{typings → dist/typings}/api/parse-files.d.cts +0 -0
  245. /package/{typings → dist/typings}/api/parse-files.d.ts +0 -0
  246. /package/{typings → dist/typings}/format/index.d.cts +0 -0
  247. /package/{typings → dist/typings}/format/index.d.ts +0 -0
  248. /package/{typings → dist/typings}/format/newline.d.cts +0 -0
  249. /package/{typings → dist/typings}/format/newline.d.ts +0 -0
  250. /package/{typings → dist/typings}/format/padding.d.cts +0 -0
  251. /package/{typings → dist/typings}/format/padding.d.ts +0 -0
  252. /package/{typings → dist/typings}/generator/format/decoder.d.cts +0 -0
  253. /package/{typings → dist/typings}/generator/format/decoder.d.ts +0 -0
  254. /package/{typings → dist/typings}/generator/format/index.d.cts +0 -0
  255. /package/{typings → dist/typings}/generator/format/index.d.ts +0 -0
  256. /package/{typings → dist/typings}/generator/index.d.cts +0 -0
  257. /package/{typings → dist/typings}/generator/index.d.ts +0 -0
  258. /package/{typings → dist/typings}/parser/data-parser.d.cts +0 -0
  259. /package/{typings → dist/typings}/parser/data-parser.d.ts +0 -0
  260. /package/{typings → dist/typings}/parser/index.d.cts +0 -0
  261. /package/{typings → dist/typings}/parser/index.d.ts +0 -0
  262. /package/{typings → dist/typings}/parser/ini-parser.d.cts +0 -0
  263. /package/{typings → dist/typings}/parser/ini-parser.d.ts +0 -0
  264. /package/{typings → dist/typings}/validation/errors.d.cts +0 -0
  265. /package/{typings → dist/typings}/validation/errors.d.ts +0 -0
  266. /package/{typings → dist/typings}/validation/index.d.cts +0 -0
  267. /package/{typings → dist/typings}/validation/index.d.ts +0 -0
  268. /package/{typings → dist/typings}/validation/validate-input.d.cts +0 -0
  269. /package/{typings → dist/typings}/validation/validate-input.d.ts +0 -0
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { CRLF } from '../../src/format/newline';
3
+
4
+ describe('Newline constants', () => {
5
+ describe('CRLF', () => {
6
+ it('should be carriage return + line feed', () => {
7
+ expect(CRLF).toBe('\r\n');
8
+ });
9
+
10
+ it('should have correct character codes', () => {
11
+ expect(CRLF.charCodeAt(0)).toBe(13); // CR
12
+ expect(CRLF.charCodeAt(1)).toBe(10); // LF
13
+ });
14
+
15
+ it('should have length of 2', () => {
16
+ expect(CRLF.length).toBe(2);
17
+ });
18
+ });
19
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { padLeft, padRight } from '../../src/format/padding';
3
+
4
+ describe('Padding utilities', () => {
5
+ describe('padLeft', () => {
6
+ it('should pad string on the left with spaces by default', () => {
7
+ expect(padLeft('test', 8)).toBe(' test');
8
+ expect(padLeft('hello', 10)).toBe(' hello');
9
+ });
10
+
11
+ it('should pad string on the left with custom character', () => {
12
+ expect(padLeft('123', 6, '0')).toBe('000123');
13
+ expect(padLeft('abc', 5, '-')).toBe('--abc');
14
+ });
15
+
16
+ it('should truncate string if longer than width', () => {
17
+ expect(padLeft('verylongstring', 5)).toBe('veryl');
18
+ expect(padLeft('truncated', 4)).toBe('trun');
19
+ });
20
+
21
+ it('should return original string if same length as width', () => {
22
+ expect(padLeft('exact', 5)).toBe('exact');
23
+ });
24
+
25
+ it('should handle empty string', () => {
26
+ expect(padLeft('', 3)).toBe(' ');
27
+ expect(padLeft('', 3, 'x')).toBe('xxx');
28
+ });
29
+
30
+ it('should handle zero width', () => {
31
+ expect(padLeft('test', 0)).toBe('');
32
+ });
33
+
34
+ it('should handle single character', () => {
35
+ expect(padLeft('a', 5)).toBe(' a');
36
+ expect(padLeft('a', 5, '0')).toBe('0000a');
37
+ });
38
+ });
39
+
40
+ describe('padRight', () => {
41
+ it('should pad string on the right with spaces by default', () => {
42
+ expect(padRight('test', 8)).toBe('test ');
43
+ expect(padRight('hello', 10)).toBe('hello ');
44
+ });
45
+
46
+ it('should pad string on the right with custom character', () => {
47
+ expect(padRight('123', 6, '0')).toBe('123000');
48
+ expect(padRight('abc', 5, '-')).toBe('abc--');
49
+ });
50
+
51
+ it('should truncate string if longer than width', () => {
52
+ expect(padRight('verylongstring', 5)).toBe('veryl');
53
+ expect(padRight('truncated', 4)).toBe('trun');
54
+ });
55
+
56
+ it('should return original string if same length as width', () => {
57
+ expect(padRight('exact', 5)).toBe('exact');
58
+ });
59
+
60
+ it('should handle empty string', () => {
61
+ expect(padRight('', 3)).toBe(' ');
62
+ expect(padRight('', 3, 'x')).toBe('xxx');
63
+ });
64
+
65
+ it('should handle zero width', () => {
66
+ expect(padRight('test', 0)).toBe('');
67
+ });
68
+
69
+ it('should handle single character', () => {
70
+ expect(padRight('a', 5)).toBe('a ');
71
+ expect(padRight('a', 5, '0')).toBe('a0000');
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { generateUniformFormatReport } from '../src/index';
3
+
4
+ describe('SHAAM Uniform Format Generator', () => {
5
+ it('should generate a basic report', () => {
6
+ const input = {
7
+ business: {
8
+ businessId: 'test123',
9
+ name: 'Test Business',
10
+ taxId: '123456789',
11
+ reportingPeriod: {
12
+ startDate: '2024-01-01',
13
+ endDate: '2024-12-31',
14
+ },
15
+ },
16
+ documents: [],
17
+ journalEntries: [],
18
+ accounts: [],
19
+ inventory: [],
20
+ };
21
+
22
+ const result = generateUniformFormatReport(input);
23
+
24
+ expect(result).toBeDefined();
25
+ expect(result.iniText).toContain('A000');
26
+ expect(result.dataText).toContain('A100');
27
+ expect(result.summary.totalRecords).toBeGreaterThan(0);
28
+ });
29
+ });
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { generateUniformFormatReport } from '../src/index';
3
+
4
+ describe('INI.TXT Generation', () => {
5
+ it('should generate iniText with A100 summary line for minimal business metadata', () => {
6
+ // Minimal business metadata as specified in todo
7
+ const input = {
8
+ business: {
9
+ businessId: 'minimal123',
10
+ name: 'Minimal Test Company',
11
+ taxId: '123456789',
12
+ reportingPeriod: {
13
+ startDate: '2024-01-01',
14
+ endDate: '2024-12-31',
15
+ },
16
+ },
17
+ documents: [],
18
+ journalEntries: [],
19
+ accounts: [],
20
+ inventory: [],
21
+ };
22
+
23
+ const result = generateUniformFormatReport(input);
24
+
25
+ // Basic assertions
26
+ expect(result.iniText).toBeDefined();
27
+ expect(typeof result.iniText).toBe('string');
28
+
29
+ // Split INI text into lines
30
+ const iniLines = result.iniText.split('\r\n').filter(line => line.trim().length > 0);
31
+
32
+ // Should contain A000 header record
33
+ const a000Line = iniLines.find(line => line.startsWith('A000'));
34
+ expect(a000Line).toBeDefined();
35
+ expect(a000Line).toContain(input.business.taxId);
36
+ expect(a000Line).toContain(input.business.name);
37
+
38
+ // Should contain A100 summary record (as requested in todo)
39
+ const a100SummaryLine = iniLines.find(line => line.startsWith('A100') && line.length === 19);
40
+ expect(a100SummaryLine).toBeDefined();
41
+ expect(a100SummaryLine).toBe('A100000000000000001'); // A100 + 15-digit count (1)
42
+
43
+ // Should contain other expected summary records
44
+ // Only A100 and Z900 should be present for minimal input
45
+ const actualSummaryRecords = iniLines
46
+ .filter(line => line.length === 19 && !line.startsWith('A000'))
47
+ .map(line => line.substring(0, 4));
48
+
49
+ expect(actualSummaryRecords).toContain('A100');
50
+ expect(actualSummaryRecords).toContain('Z900');
51
+
52
+ // Total lines should be: A000 + A100 summary + Z900 summary = 3
53
+ expect(iniLines.length).toBe(3);
54
+ });
55
+
56
+ it('should generate correct A100 summary count when there are records', () => {
57
+ const input = {
58
+ business: {
59
+ businessId: 'test123',
60
+ name: 'Test Company',
61
+ taxId: '987654321',
62
+ reportingPeriod: {
63
+ startDate: '2024-01-01',
64
+ endDate: '2024-12-31',
65
+ },
66
+ },
67
+ documents: [
68
+ {
69
+ id: 'DOC001',
70
+ type: '320' as const,
71
+ date: '2024-03-15',
72
+ amount: 1000.5,
73
+ description: 'Test document',
74
+ },
75
+ ],
76
+ journalEntries: [
77
+ {
78
+ id: 'JE001',
79
+ date: '2024-03-15',
80
+ amount: 1000.5,
81
+ description: 'Test journal entry',
82
+ accountId: 'ACC001',
83
+ },
84
+ ],
85
+ accounts: [
86
+ {
87
+ id: 'ACC001',
88
+ name: 'Test Account',
89
+ type: 'Asset',
90
+ balance: 0,
91
+ },
92
+ ],
93
+ inventory: [
94
+ {
95
+ id: 'ITEM001',
96
+ name: 'Test Item',
97
+ quantity: 100,
98
+ unitPrice: 10.0,
99
+ },
100
+ ],
101
+ };
102
+
103
+ const result = generateUniformFormatReport(input);
104
+ const iniLines = result.iniText.split('\r\n').filter(line => line.trim().length > 0);
105
+
106
+ // Check that all expected summary records are present with correct counts
107
+ const summaryLines = iniLines.filter(line => line.length === 19 && !line.startsWith('A000'));
108
+
109
+ // Expected: A100(1), C100(1), D110(1), D120(1), B100(1), B110(1), M100(1), Z900(1)
110
+ expect(summaryLines).toHaveLength(8);
111
+
112
+ // Verify specific counts
113
+ expect(summaryLines).toContain('A100000000000000001'); // 1 A100 record
114
+ expect(summaryLines).toContain('C100000000000000001'); // 1 C100 record
115
+ expect(summaryLines).toContain('D110000000000000001'); // 1 D110 record
116
+ expect(summaryLines).toContain('D120000000000000001'); // 1 D120 record
117
+ expect(summaryLines).toContain('B100000000000000001'); // 1 B100 record
118
+ expect(summaryLines).toContain('B110000000000000001'); // 1 B110 record
119
+ expect(summaryLines).toContain('M100000000000000001'); // 1 M100 record
120
+ expect(summaryLines).toContain('Z900000000000000001'); // 1 Z900 record
121
+ });
122
+ });
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Comprehensive integration test for full round-trip generation and parsing
3
+ * including A000Sum summary records and full validation
4
+ */
5
+
6
+ import { describe, expect, it } from 'vitest';
7
+ import { generateUniformFormatReport } from '../../src/api/generate-report';
8
+ import {
9
+ parseA000,
10
+ parseA000Sum,
11
+ parseA100,
12
+ parseB100,
13
+ parseB110,
14
+ parseC100,
15
+ parseD110,
16
+ parseD120,
17
+ parseM100,
18
+ parseZ900,
19
+ } from '../../src/generator/records/index';
20
+ import type { ReportInput } from '../../src/types/index';
21
+
22
+ describe('Comprehensive SHAAM Format Integration Test', () => {
23
+ it('should generate complete SHAAM files and parse back all records including A000Sum', () => {
24
+ // Full ReportInput fixture
25
+ const input: ReportInput = {
26
+ business: {
27
+ businessId: '12345',
28
+ name: 'Test Company Ltd',
29
+ taxId: '123456789',
30
+ reportingPeriod: {
31
+ startDate: '2024-01-01',
32
+ endDate: '2024-12-31',
33
+ },
34
+ },
35
+ documents: [
36
+ {
37
+ id: 'DOC001',
38
+ type: '320', // Invoice
39
+ date: '2024-03-15',
40
+ amount: 1000.5,
41
+ description: 'Consulting services',
42
+ },
43
+ {
44
+ id: 'DOC002',
45
+ type: '330', // Credit memo
46
+ date: '2024-03-20',
47
+ amount: 250.75,
48
+ description: 'Product return',
49
+ },
50
+ ],
51
+ journalEntries: [
52
+ {
53
+ id: 'JE001',
54
+ date: '2024-03-15',
55
+ amount: 1000.5,
56
+ accountId: '1100',
57
+ description: 'Sales revenue',
58
+ },
59
+ {
60
+ id: 'JE002',
61
+ date: '2024-03-20',
62
+ amount: -250.75,
63
+ accountId: '1200',
64
+ description: 'Returns and allowances',
65
+ },
66
+ ],
67
+ accounts: [
68
+ {
69
+ id: '1100',
70
+ name: 'Cash',
71
+ type: 'Asset',
72
+ balance: 5000.0,
73
+ },
74
+ {
75
+ id: '1200',
76
+ name: 'Accounts Receivable',
77
+ type: 'Asset',
78
+ balance: 3000.0,
79
+ },
80
+ {
81
+ id: '4000',
82
+ name: 'Sales Revenue',
83
+ type: 'Revenue',
84
+ balance: 8000.0,
85
+ },
86
+ ],
87
+ inventory: [
88
+ {
89
+ id: 'ITEM001',
90
+ name: 'Product A',
91
+ quantity: 100,
92
+ unitPrice: 25.0,
93
+ },
94
+ {
95
+ id: 'ITEM002',
96
+ name: 'Product B',
97
+ quantity: 50,
98
+ unitPrice: 45.0,
99
+ },
100
+ ],
101
+ };
102
+
103
+ // Generate the report
104
+ const result = generateUniformFormatReport(input);
105
+
106
+ expect(result).toBeDefined();
107
+ expect(result.dataText).toBeDefined();
108
+ expect(result.iniText).toBeDefined();
109
+ expect(result.summary.totalRecords).toBeGreaterThan(0);
110
+
111
+ // Parse INI file
112
+ const iniLines = result.iniText.split('\r\n').filter(line => line.trim().length > 0);
113
+
114
+ interface ParsedIniData {
115
+ headerRecord: ReturnType<typeof parseA000> | null;
116
+ summaryRecords: ReturnType<typeof parseA000Sum>[];
117
+ }
118
+
119
+ const parsedIni: ParsedIniData = {
120
+ headerRecord: null,
121
+ summaryRecords: [],
122
+ };
123
+
124
+ // Parse INI file records
125
+ for (const line of iniLines) {
126
+ const recordType = line.substring(0, 4);
127
+
128
+ if (recordType === 'A000') {
129
+ parsedIni.headerRecord = parseA000(line);
130
+ } else if (line.length === 19) {
131
+ // A000Sum records are 19 characters long (4-char code + 15-char count)
132
+ // They start with record type codes like A100, B100, C100, etc.
133
+ try {
134
+ const summaryRecord = parseA000Sum(line);
135
+ parsedIni.summaryRecords.push(summaryRecord);
136
+ } catch {
137
+ // If parsing fails, it's not an A000Sum record
138
+ }
139
+ }
140
+ }
141
+
142
+ // Verify A000 header record
143
+ expect(parsedIni.headerRecord).toBeDefined();
144
+ expect(parsedIni.headerRecord?.vatId).toBe(input.business.taxId);
145
+ expect(parsedIni.headerRecord?.businessName).toBe(input.business.name);
146
+
147
+ // Verify A000Sum summary records
148
+ expect(parsedIni.summaryRecords.length).toBe(8); // One for each record type
149
+
150
+ const summaryByType = Object.fromEntries(
151
+ parsedIni.summaryRecords.map(record => [record.code, parseInt(record.recordCount)]),
152
+ );
153
+
154
+ expect(summaryByType.A100).toBe(1);
155
+ expect(summaryByType.C100).toBe(input.documents.length);
156
+ expect(summaryByType.D110).toBe(input.documents.length);
157
+ expect(summaryByType.D120).toBe(input.documents.length);
158
+ expect(summaryByType.B100).toBe(input.journalEntries.length);
159
+ expect(summaryByType.B110).toBe(input.accounts.length);
160
+ expect(summaryByType.M100).toBe(input.inventory.length);
161
+ expect(summaryByType.Z900).toBe(1);
162
+
163
+ // Parse data file
164
+ const dataLines = result.dataText.split('\r\n').filter(line => line.trim().length > 0);
165
+
166
+ interface ParsedDataFile {
167
+ businessMetadata: ReturnType<typeof parseA100> | null;
168
+ documents: ReturnType<typeof parseC100>[];
169
+ documentLines: ReturnType<typeof parseD110>[];
170
+ payments: ReturnType<typeof parseD120>[];
171
+ journalEntries: ReturnType<typeof parseB100>[];
172
+ accounts: ReturnType<typeof parseB110>[];
173
+ inventory: ReturnType<typeof parseM100>[];
174
+ closingRecord: ReturnType<typeof parseZ900> | null;
175
+ }
176
+
177
+ const parsedData: ParsedDataFile = {
178
+ businessMetadata: null,
179
+ documents: [],
180
+ documentLines: [],
181
+ payments: [],
182
+ journalEntries: [],
183
+ accounts: [],
184
+ inventory: [],
185
+ closingRecord: null,
186
+ };
187
+
188
+ // Parse each line based on record type
189
+ for (const line of dataLines) {
190
+ const recordType = line.substring(0, 4);
191
+
192
+ switch (recordType) {
193
+ case 'A100':
194
+ parsedData.businessMetadata = parseA100(line);
195
+ break;
196
+ case 'C100':
197
+ parsedData.documents.push(parseC100(line));
198
+ break;
199
+ case 'D110':
200
+ parsedData.documentLines.push(parseD110(line));
201
+ break;
202
+ case 'D120':
203
+ parsedData.payments.push(parseD120(line));
204
+ break;
205
+ case 'B100':
206
+ parsedData.journalEntries.push(parseB100(line));
207
+ break;
208
+ case 'B110':
209
+ parsedData.accounts.push(parseB110(line));
210
+ break;
211
+ case 'M100':
212
+ parsedData.inventory.push(parseM100(line));
213
+ break;
214
+ case 'Z900':
215
+ parsedData.closingRecord = parseZ900(line);
216
+ break;
217
+ default:
218
+ // Unknown record type, skip silently for test purposes
219
+ break;
220
+ }
221
+ }
222
+
223
+ // Verify business metadata
224
+ expect(parsedData.businessMetadata).toBeDefined();
225
+ expect(parsedData.businessMetadata?.vatId).toBe(input.business.taxId);
226
+
227
+ // Verify documents
228
+ expect(parsedData.documents).toHaveLength(input.documents.length);
229
+ for (let i = 0; i < input.documents.length; i++) {
230
+ const original = input.documents[i];
231
+ const parsed = parsedData.documents[i];
232
+ expect(parsed.documentId).toBe(original.id);
233
+ expect(parsed.documentType).toBe(original.type);
234
+ expect(parsed.documentIssueDate).toBe(original.date.replace(/-/g, ''));
235
+ }
236
+
237
+ // Verify document lines
238
+ expect(parsedData.documentLines).toHaveLength(input.documents.length);
239
+ for (let i = 0; i < input.documents.length; i++) {
240
+ const original = input.documents[i];
241
+ const parsed = parsedData.documentLines[i];
242
+ expect(parsed.documentNumber).toBe(original.id);
243
+ expect(parsed.documentType).toBe(original.type);
244
+ expect(parsed.goodsServiceDescription).toBe(original.description || 'Item');
245
+ }
246
+
247
+ // Verify payments
248
+ expect(parsedData.payments).toHaveLength(input.documents.length);
249
+ for (let i = 0; i < input.documents.length; i++) {
250
+ const original = input.documents[i];
251
+ const parsed = parsedData.payments[i];
252
+ expect(parsed.documentNumber).toBe(original.id);
253
+ expect(parsed.documentType).toBe(original.type);
254
+ expect(parsed.lineAmount).toBe(original.amount.toFixed(2));
255
+ }
256
+
257
+ // Verify journal entries
258
+ expect(parsedData.journalEntries).toHaveLength(input.journalEntries.length);
259
+ for (let i = 0; i < input.journalEntries.length; i++) {
260
+ const original = input.journalEntries[i];
261
+ const parsed = parsedData.journalEntries[i];
262
+ const expectedTransactionNumber =
263
+ (original.id.replace(/\D/g, '') || '1').replace(/^0+/, '') || '0';
264
+ expect(parsed.transactionNumber).toBe(expectedTransactionNumber);
265
+ expect(parsed.accountKey).toBe(original.accountId);
266
+ expect(parsed.transactionAmount).toBe(Math.abs(original.amount).toFixed(2));
267
+ expect(parsed.debitCreditIndicator).toBe(original.amount >= 0 ? '1' : '2');
268
+ }
269
+
270
+ // Verify accounts
271
+ expect(parsedData.accounts).toHaveLength(input.accounts.length);
272
+ for (let i = 0; i < input.accounts.length; i++) {
273
+ const original = input.accounts[i];
274
+ const parsed = parsedData.accounts[i];
275
+ expect(parsed.accountKey).toBe(original.id);
276
+ expect(parsed.accountName).toBe(original.name);
277
+ expect(parsed.trialBalanceCode).toBe(original.type);
278
+ }
279
+
280
+ // Verify inventory
281
+ expect(parsedData.inventory).toHaveLength(input.inventory.length);
282
+ for (let i = 0; i < input.inventory.length; i++) {
283
+ const original = input.inventory[i];
284
+ const parsed = parsedData.inventory[i];
285
+ expect(parsed.internalItemCode).toBe(original.id);
286
+ expect(parsed.itemName).toBe(original.name);
287
+ expect(parsed.totalStockOut).toBe(original.quantity.toString());
288
+ }
289
+
290
+ // Verify closing record
291
+ expect(parsedData.closingRecord).toBeDefined();
292
+ expect(parsedData.closingRecord?.vatId).toBe(input.business.taxId);
293
+ expect(parseInt(parsedData.closingRecord?.totalRecords || '0')).toBeGreaterThan(0);
294
+
295
+ // Cross-verify: Summary record counts should match actual parsed data counts
296
+ expect(summaryByType.A100).toBe(parsedData.businessMetadata ? 1 : 0);
297
+ expect(summaryByType.C100).toBe(parsedData.documents.length);
298
+ expect(summaryByType.D110).toBe(parsedData.documentLines.length);
299
+ expect(summaryByType.D120).toBe(parsedData.payments.length);
300
+ expect(summaryByType.B100).toBe(parsedData.journalEntries.length);
301
+ expect(summaryByType.B110).toBe(parsedData.accounts.length);
302
+ expect(summaryByType.M100).toBe(parsedData.inventory.length);
303
+ expect(summaryByType.Z900).toBe(parsedData.closingRecord ? 1 : 0);
304
+
305
+ // Verify that Z900 total records matches data records count (excludes INI records)
306
+ const dataRecordsCount =
307
+ 1 + // A100
308
+ parsedData.documents.length + // C100
309
+ parsedData.documentLines.length + // D110
310
+ parsedData.payments.length + // D120
311
+ parsedData.journalEntries.length + // B100
312
+ parsedData.accounts.length + // B110
313
+ parsedData.inventory.length; // M100
314
+ // Z900 doesn't count itself
315
+
316
+ expect(parseInt(parsedData.closingRecord?.totalRecords || '0')).toBe(dataRecordsCount);
317
+ });
318
+
319
+ it('should validate File objects are created correctly', () => {
320
+ const minimalInput: ReportInput = {
321
+ business: {
322
+ businessId: '54321',
323
+ name: 'Minimal Company',
324
+ taxId: '987654321',
325
+ reportingPeriod: {
326
+ startDate: '2024-01-01',
327
+ endDate: '2024-12-31',
328
+ },
329
+ },
330
+ documents: [],
331
+ journalEntries: [],
332
+ accounts: [],
333
+ inventory: [],
334
+ };
335
+
336
+ const result = generateUniformFormatReport(minimalInput, { fileNameBase: 'test-report' });
337
+
338
+ // Verify File objects
339
+ expect(result.iniFile).toBeInstanceOf(File);
340
+ expect(result.dataFile).toBeInstanceOf(File);
341
+ expect(result.iniFile.name).toBe('test-report.INI.TXT');
342
+ expect(result.dataFile.name).toBe('test-report.BKMVDATA.TXT');
343
+ expect(result.iniFile.type).toBe('text/plain');
344
+ expect(result.dataFile.type).toBe('text/plain');
345
+
346
+ // Verify file content matches text content
347
+ expect(result.iniFile.size).toBe(result.iniText.length);
348
+ expect(result.dataFile.size).toBe(result.dataText.length);
349
+ });
350
+ });