@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,254 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { SHAAM_VERSION } from '../../src/constants';
3
+ import { encodeZ900, parseZ900, Z900Schema, type Z900 } from '../../src/generator/records/z900';
4
+
5
+ describe('Z900 Record', () => {
6
+ const validZ900: Z900 = {
7
+ code: 'Z900',
8
+ recordNumber: '123',
9
+ vatId: '987654321',
10
+ uniqueId: '123456789012345',
11
+ totalRecords: '1000',
12
+ reserved: '',
13
+ };
14
+
15
+ describe('Z900Schema validation', () => {
16
+ it('should validate a correct Z900 record', () => {
17
+ expect(() => Z900Schema.parse(validZ900)).not.toThrow();
18
+ });
19
+
20
+ it('should require code to be exactly "Z900"', () => {
21
+ const invalidRecord = { ...validZ900, code: 'Z800' };
22
+ expect(() => Z900Schema.parse(invalidRecord)).toThrow();
23
+ });
24
+
25
+ it('should require recordNumber to be non-empty and max 9 characters', () => {
26
+ expect(() => Z900Schema.parse({ ...validZ900, recordNumber: '' })).toThrow();
27
+ expect(() => Z900Schema.parse({ ...validZ900, recordNumber: '1234567890' })).toThrow();
28
+ expect(() => Z900Schema.parse({ ...validZ900, recordNumber: '123456789' })).not.toThrow();
29
+ });
30
+
31
+ it('should require vatId to be non-empty and max 9 characters', () => {
32
+ expect(() => Z900Schema.parse({ ...validZ900, vatId: '' })).toThrow();
33
+ expect(() => Z900Schema.parse({ ...validZ900, vatId: '1234567890' })).toThrow();
34
+ expect(() => Z900Schema.parse({ ...validZ900, vatId: '123456789' })).not.toThrow();
35
+ });
36
+
37
+ it('should require uniqueId to be non-empty and max 15 characters', () => {
38
+ expect(() => Z900Schema.parse({ ...validZ900, uniqueId: '' })).toThrow();
39
+ expect(() => Z900Schema.parse({ ...validZ900, uniqueId: '1234567890123456' })).toThrow();
40
+ expect(() => Z900Schema.parse({ ...validZ900, uniqueId: '123456789012345' })).not.toThrow();
41
+ });
42
+
43
+ it('should require totalRecords to be non-empty and max 15 characters', () => {
44
+ expect(() => Z900Schema.parse({ ...validZ900, totalRecords: '' })).toThrow();
45
+ expect(() => Z900Schema.parse({ ...validZ900, totalRecords: '1234567890123456' })).toThrow();
46
+ expect(() =>
47
+ Z900Schema.parse({ ...validZ900, totalRecords: '123456789012345' }),
48
+ ).not.toThrow();
49
+ });
50
+
51
+ it('should validate reserved field', () => {
52
+ expect(() => Z900Schema.parse({ ...validZ900, reserved: '' })).not.toThrow();
53
+ expect(() => Z900Schema.parse({ ...validZ900, reserved: 'Reserved data' })).not.toThrow();
54
+ expect(() => Z900Schema.parse({ ...validZ900, reserved: 'A'.repeat(50) })).not.toThrow();
55
+ expect(() => Z900Schema.parse({ ...validZ900, reserved: 'A'.repeat(51) })).toThrow();
56
+ });
57
+
58
+ it('should set default empty string for reserved field when missing', () => {
59
+ const { reserved: _reserved, ...recordWithoutReserved } = validZ900;
60
+ const parsed = Z900Schema.parse(recordWithoutReserved);
61
+ expect(parsed.reserved).toBe('');
62
+ });
63
+ });
64
+
65
+ describe('encodeZ900', () => {
66
+ it('should encode a Z900 record to fixed-width format', () => {
67
+ const encoded = encodeZ900(validZ900);
68
+
69
+ // Should end with CRLF
70
+ expect(encoded).toMatch(/\r\n$/);
71
+
72
+ // Remove CRLF for length check
73
+ const withoutCRLF = encoded.replace(/\r\n$/, '');
74
+ expect(withoutCRLF).toHaveLength(110);
75
+
76
+ // Check field positions and padding
77
+ expect(withoutCRLF.slice(0, 4)).toBe('Z900'); // code (4)
78
+ expect(withoutCRLF.slice(4, 13)).toBe('000000123'); // recordNumber zero-padded (9)
79
+ expect(withoutCRLF.slice(13, 22)).toBe('987654321'); // vatId zero-padded (9)
80
+ expect(withoutCRLF.slice(22, 37)).toBe('123456789012345'); // uniqueId zero-padded (15)
81
+ expect(withoutCRLF.slice(37, 45)).toBe(SHAAM_VERSION); // static SHAAM version (8)
82
+ expect(withoutCRLF.slice(45, 60)).toBe('000000000001000'); // totalRecords zero-padded (15)
83
+ expect(withoutCRLF.slice(60, 110)).toBe(' '.repeat(50)); // reserved left-aligned (50)
84
+ });
85
+
86
+ it('should handle maximum length fields', () => {
87
+ const maxLengthRecord: Z900 = {
88
+ code: 'Z900',
89
+ recordNumber: '123456789', // 9 chars
90
+ vatId: '123456789', // 9 chars
91
+ uniqueId: '123456789012345', // 15 chars
92
+ totalRecords: '123456789012345', // 15 chars
93
+ reserved: 'A'.repeat(50), // 50 chars
94
+ };
95
+
96
+ const encoded = encodeZ900(maxLengthRecord);
97
+ const withoutCRLF = encoded.replace(/\r\n$/, '');
98
+ expect(withoutCRLF).toHaveLength(110);
99
+
100
+ expect(withoutCRLF.slice(0, 4)).toBe('Z900');
101
+ expect(withoutCRLF.slice(4, 13)).toBe('123456789');
102
+ expect(withoutCRLF.slice(13, 22)).toBe('123456789');
103
+ expect(withoutCRLF.slice(22, 37)).toBe('123456789012345');
104
+ expect(withoutCRLF.slice(37, 45)).toBe(SHAAM_VERSION);
105
+ expect(withoutCRLF.slice(45, 60)).toBe('123456789012345');
106
+ expect(withoutCRLF.slice(60, 110)).toBe('A'.repeat(50));
107
+ });
108
+
109
+ it('should truncate fields that exceed maximum length', () => {
110
+ const oversizedRecord: Z900 = {
111
+ code: 'Z900',
112
+ recordNumber: '1234567890', // 10 chars (max 9)
113
+ vatId: '1234567890', // 10 chars (max 9)
114
+ uniqueId: '1234567890123456', // 16 chars (max 15)
115
+ totalRecords: '1234567890123456', // 16 chars (max 15)
116
+ reserved: 'A'.repeat(60), // 60 chars (max 50)
117
+ };
118
+
119
+ const encoded = encodeZ900(oversizedRecord);
120
+ const withoutCRLF = encoded.replace(/\r\n$/, '');
121
+ expect(withoutCRLF).toHaveLength(110);
122
+
123
+ expect(withoutCRLF.slice(4, 13)).toBe('123456789'); // truncated from right
124
+ expect(withoutCRLF.slice(13, 22)).toBe('123456789'); // truncated
125
+ expect(withoutCRLF.slice(22, 37)).toBe('123456789012345'); // truncated
126
+ expect(withoutCRLF.slice(37, 45)).toBe(SHAAM_VERSION); // always the constant
127
+ expect(withoutCRLF.slice(45, 60)).toBe('123456789012345'); // truncated from right
128
+ expect(withoutCRLF.slice(60, 110)).toBe('A'.repeat(50)); // truncated
129
+ });
130
+ });
131
+
132
+ describe('parseZ900', () => {
133
+ it('should parse a valid Z900 record line', () => {
134
+ const line =
135
+ 'Z900000000123987654321123456789012345' +
136
+ SHAAM_VERSION +
137
+ '000000000001000' +
138
+ ' '.repeat(50) +
139
+ '\r\n';
140
+ const parsed = parseZ900(line);
141
+
142
+ expect(parsed).toEqual({
143
+ code: 'Z900',
144
+ recordNumber: '123',
145
+ vatId: '987654321',
146
+ uniqueId: '123456789012345',
147
+ totalRecords: '1000',
148
+ reserved: '',
149
+ });
150
+ });
151
+
152
+ it('should parse line without CRLF', () => {
153
+ const line =
154
+ 'Z900000000123987654321123456789012345' +
155
+ SHAAM_VERSION +
156
+ '000000000001000' +
157
+ ' '.repeat(50);
158
+ const parsed = parseZ900(line);
159
+
160
+ expect(parsed.code).toBe('Z900');
161
+ expect(parsed.recordNumber).toBe('123');
162
+ });
163
+
164
+ it('should throw error for invalid line length', () => {
165
+ const shortLine = 'Z900' + ' '.repeat(50);
166
+ expect(() => parseZ900(shortLine)).toThrow(
167
+ 'Invalid Z900 record length: expected 110 characters, got 54',
168
+ );
169
+
170
+ const longLine = 'Z900' + ' '.repeat(200);
171
+ expect(() => parseZ900(longLine)).toThrow(
172
+ 'Invalid Z900 record length: expected 110 characters, got 204',
173
+ );
174
+ });
175
+
176
+ it('should throw error for invalid record code', () => {
177
+ const invalidLine = 'Z800' + ' '.repeat(106) + '\r\n';
178
+ expect(() => parseZ900(invalidLine)).toThrow(
179
+ 'Invalid Z900 record code: expected "Z900", got "Z800"',
180
+ );
181
+ });
182
+
183
+ it('should throw error for invalid SHAAM version', () => {
184
+ const invalidLine =
185
+ 'Z900000000123987654321123456789012345INVALID 000000000001000' + ' '.repeat(50) + '\r\n';
186
+ expect(() => parseZ900(invalidLine)).toThrow('Invalid SHAAM version');
187
+ });
188
+
189
+ it('should handle fields with trailing spaces correctly', () => {
190
+ const line =
191
+ 'Z900000000123000123456000000000123456' +
192
+ SHAAM_VERSION +
193
+ '000000000009999' +
194
+ ' '.repeat(50) +
195
+ '\r\n';
196
+ const parsed = parseZ900(line);
197
+
198
+ expect(parsed.vatId).toBe('123456');
199
+ expect(parsed.uniqueId).toBe('123456');
200
+ expect(parsed.totalRecords).toBe('9999');
201
+ expect(parsed.reserved).toBe('');
202
+ });
203
+ });
204
+
205
+ describe('Round-trip encoding and parsing', () => {
206
+ it('should maintain data integrity through encode -> parse cycle', () => {
207
+ const original = validZ900;
208
+ const encoded = encodeZ900(original);
209
+ const parsed = parseZ900(encoded);
210
+
211
+ expect(parsed).toEqual(original);
212
+ });
213
+
214
+ it('should handle round-trip with maximum length fields', () => {
215
+ const original: Z900 = {
216
+ code: 'Z900',
217
+ recordNumber: '999999999',
218
+ vatId: '999888777',
219
+ uniqueId: '999888777666555',
220
+ totalRecords: '999888777666555',
221
+ reserved: 'A'.repeat(50),
222
+ };
223
+
224
+ const encoded = encodeZ900(original);
225
+ const parsed = parseZ900(encoded);
226
+
227
+ expect(parsed).toEqual(original);
228
+ });
229
+
230
+ it('should handle round-trip with minimal fields', () => {
231
+ const original: Z900 = {
232
+ code: 'Z900',
233
+ recordNumber: '1',
234
+ vatId: '1',
235
+ uniqueId: '1',
236
+ totalRecords: '1',
237
+ reserved: '',
238
+ };
239
+
240
+ const encoded = encodeZ900(original);
241
+ const parsed = parseZ900(encoded);
242
+
243
+ expect(parsed).toEqual(original);
244
+ });
245
+ });
246
+
247
+ describe('Error handling', () => {
248
+ it('should throw validation error for invalid parsed data', () => {
249
+ // Create a line that parses but fails schema validation
250
+ const invalidLine = 'Z900 ' + ' '.repeat(97); // empty recordNumber
251
+ expect(() => parseZ900(invalidLine)).toThrow();
252
+ });
253
+ });
254
+ });
@@ -0,0 +1,290 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ AccountingTypeEnum,
4
+ AccountingTypeLabels,
5
+ BalanceRequiredEnum,
6
+ BranchInfoFlagEnum,
7
+ CharacterEncodingEnum,
8
+ CountryCodeEnum,
9
+ CountryLabels,
10
+ CurrencyCodeEnum,
11
+ CurrencyLabels,
12
+ DebitCreditIndicatorEnum,
13
+ DebitCreditIndicatorLabels,
14
+ // Enums
15
+ DocumentTypeEnum,
16
+ // Labels
17
+ DocumentTypeLabels,
18
+ DocumentTypeLabelsEn,
19
+ LanguageCodeEnum,
20
+ PaymentMethodEnum,
21
+ PaymentMethodLabels,
22
+ RecordTypeEnum,
23
+ // Constants and utilities
24
+ SHAAM_CONSTANTS,
25
+ ShaamEnumSchemas,
26
+ ShaamLabels,
27
+ SoftwareTypeEnum,
28
+ SoftwareTypeLabels,
29
+ TrialBalanceCodeEnum,
30
+ TrialBalanceCodeLabels,
31
+ type AccountingType,
32
+ type CountryCode,
33
+ type CurrencyCode,
34
+ type DebitCreditIndicator,
35
+ // Types
36
+ type DocumentType,
37
+ type PaymentMethod,
38
+ type RecordType,
39
+ type SoftwareType,
40
+ type TrialBalanceCode,
41
+ } from '../../src/types/enums';
42
+
43
+ describe('SHAAM Format Enums', () => {
44
+ describe('DocumentTypeEnum', () => {
45
+ it('should validate correct document types', () => {
46
+ expect(DocumentTypeEnum.parse('320')).toBe('320');
47
+ expect(DocumentTypeEnum.parse('100')).toBe('100');
48
+ expect(DocumentTypeEnum.parse('710')).toBe('710');
49
+ });
50
+
51
+ it('should reject invalid document types', () => {
52
+ expect(() => DocumentTypeEnum.parse('999')).toThrow();
53
+ expect(() => DocumentTypeEnum.parse('ABC')).toThrow();
54
+ expect(() => DocumentTypeEnum.parse('')).toThrow();
55
+ });
56
+
57
+ it('should have corresponding labels', () => {
58
+ expect(DocumentTypeLabels['320']).toBe('חשבונית מס קבלה');
59
+ expect(DocumentTypeLabelsEn['320']).toBe('Receipt Tax Invoice');
60
+ expect(DocumentTypeLabels['100']).toBe('הזמנה');
61
+ expect(DocumentTypeLabelsEn['100']).toBe('Order');
62
+ });
63
+ });
64
+
65
+ describe('CurrencyCodeEnum', () => {
66
+ it('should validate common currency codes', () => {
67
+ expect(CurrencyCodeEnum.parse('ILS')).toBe('ILS');
68
+ expect(CurrencyCodeEnum.parse('USD')).toBe('USD');
69
+ expect(CurrencyCodeEnum.parse('EUR')).toBe('EUR');
70
+ });
71
+
72
+ it('should reject invalid currency codes', () => {
73
+ expect(() => CurrencyCodeEnum.parse('XXX')).toThrow();
74
+ expect(() => CurrencyCodeEnum.parse('123')).toThrow();
75
+ expect(() => CurrencyCodeEnum.parse('')).toThrow();
76
+ });
77
+
78
+ it('should have Hebrew labels', () => {
79
+ expect(CurrencyLabels['ILS']).toBe('שקל חדש');
80
+ expect(CurrencyLabels['USD']).toBe('דולר אמריקאי');
81
+ expect(CurrencyLabels['EUR']).toBe('יורו');
82
+ });
83
+ });
84
+
85
+ describe('RecordTypeEnum', () => {
86
+ it('should validate all SHAAM record types', () => {
87
+ const expectedTypes: RecordType[] = [
88
+ 'A000',
89
+ 'A100',
90
+ 'B100',
91
+ 'B110',
92
+ 'C100',
93
+ 'D110',
94
+ 'D120',
95
+ 'M100',
96
+ 'Z900',
97
+ ];
98
+
99
+ for (const type of expectedTypes) {
100
+ expect(RecordTypeEnum.parse(type)).toBe(type);
101
+ }
102
+ });
103
+
104
+ it('should reject invalid record types', () => {
105
+ expect(() => RecordTypeEnum.parse('X999')).toThrow();
106
+ expect(() => RecordTypeEnum.parse('A999')).toThrow();
107
+ expect(() => RecordTypeEnum.parse('')).toThrow();
108
+ });
109
+ });
110
+
111
+ describe('SoftwareTypeEnum', () => {
112
+ it('should validate software types', () => {
113
+ expect(SoftwareTypeEnum.parse('1')).toBe('1');
114
+ expect(SoftwareTypeEnum.parse('2')).toBe('2');
115
+ });
116
+
117
+ it('should have descriptive labels', () => {
118
+ expect(SoftwareTypeLabels['1']).toBe('Single-year');
119
+ expect(SoftwareTypeLabels['2']).toBe('Multi-year');
120
+ });
121
+ });
122
+
123
+ describe('AccountingTypeEnum', () => {
124
+ it('should validate accounting types', () => {
125
+ expect(AccountingTypeEnum.parse('0')).toBe('0');
126
+ expect(AccountingTypeEnum.parse('1')).toBe('1');
127
+ expect(AccountingTypeEnum.parse('2')).toBe('2');
128
+ });
129
+
130
+ it('should have descriptive labels', () => {
131
+ expect(AccountingTypeLabels['0']).toBe('N/A');
132
+ expect(AccountingTypeLabels['1']).toBe('Single-entry');
133
+ expect(AccountingTypeLabels['2']).toBe('Double-entry');
134
+ });
135
+ });
136
+
137
+ describe('DebitCreditIndicatorEnum', () => {
138
+ it('should validate debit/credit indicators', () => {
139
+ expect(DebitCreditIndicatorEnum.parse('1')).toBe('1');
140
+ expect(DebitCreditIndicatorEnum.parse('2')).toBe('2');
141
+ });
142
+
143
+ it('should have descriptive labels', () => {
144
+ expect(DebitCreditIndicatorLabels['1']).toBe('Debit');
145
+ expect(DebitCreditIndicatorLabels['2']).toBe('Credit');
146
+ });
147
+ });
148
+
149
+ describe('PaymentMethodEnum', () => {
150
+ it('should validate payment methods', () => {
151
+ expect(PaymentMethodEnum.parse('1')).toBe('1');
152
+ expect(PaymentMethodEnum.parse('3')).toBe('3');
153
+ expect(PaymentMethodEnum.parse('4')).toBe('4');
154
+ });
155
+
156
+ it('should have descriptive labels', () => {
157
+ expect(PaymentMethodLabels['1']).toBe('Cash');
158
+ expect(PaymentMethodLabels['3']).toBe('Credit Card');
159
+ expect(PaymentMethodLabels['4']).toBe('Bank Transfer');
160
+ });
161
+ });
162
+
163
+ describe('TrialBalanceCodeEnum', () => {
164
+ it('should validate trial balance codes', () => {
165
+ expect(TrialBalanceCodeEnum.parse('Asset')).toBe('Asset');
166
+ expect(TrialBalanceCodeEnum.parse('Liability')).toBe('Liability');
167
+ expect(TrialBalanceCodeEnum.parse('Revenue')).toBe('Revenue');
168
+ });
169
+
170
+ it('should have Hebrew labels', () => {
171
+ expect(TrialBalanceCodeLabels['Asset']).toBe('נכסים');
172
+ expect(TrialBalanceCodeLabels['Liability']).toBe('התחייבויות');
173
+ expect(TrialBalanceCodeLabels['Revenue']).toBe('הכנסות');
174
+ });
175
+ });
176
+
177
+ describe('CountryCodeEnum', () => {
178
+ it('should validate common country codes', () => {
179
+ expect(CountryCodeEnum.parse('IL')).toBe('IL');
180
+ expect(CountryCodeEnum.parse('US')).toBe('US');
181
+ expect(CountryCodeEnum.parse('DE')).toBe('DE');
182
+ });
183
+
184
+ it('should have Hebrew labels', () => {
185
+ expect(CountryLabels['IL']).toBe('ישראל');
186
+ expect(CountryLabels['US']).toBe('ארצות הברית');
187
+ expect(CountryLabels['DE']).toBe('גרמניה');
188
+ });
189
+ });
190
+
191
+ describe('SHAAM_CONSTANTS', () => {
192
+ it('should have correct default values', () => {
193
+ expect(SHAAM_CONSTANTS.VERSION).toBe('&OF1.31&');
194
+ expect(SHAAM_CONSTANTS.DEFAULT_CURRENCY).toBe('ILS');
195
+ expect(SHAAM_CONSTANTS.DEFAULT_LANGUAGE).toBe('0');
196
+ expect(SHAAM_CONSTANTS.DEFAULT_ENCODING).toBe('1');
197
+ expect(SHAAM_CONSTANTS.DEFAULT_SOFTWARE_TYPE).toBe('2');
198
+ expect(SHAAM_CONSTANTS.DEFAULT_ACCOUNTING_TYPE).toBe('2');
199
+ expect(SHAAM_CONSTANTS.DEFAULT_BALANCE_REQUIRED).toBe('1');
200
+ expect(SHAAM_CONSTANTS.DEFAULT_BRANCH_INFO).toBe('0');
201
+ });
202
+
203
+ it('should have constants that validate against their enums', () => {
204
+ expect(CurrencyCodeEnum.parse(SHAAM_CONSTANTS.DEFAULT_CURRENCY)).toBe('ILS');
205
+ expect(LanguageCodeEnum.parse(SHAAM_CONSTANTS.DEFAULT_LANGUAGE)).toBe('0');
206
+ expect(CharacterEncodingEnum.parse(SHAAM_CONSTANTS.DEFAULT_ENCODING)).toBe('1');
207
+ expect(SoftwareTypeEnum.parse(SHAAM_CONSTANTS.DEFAULT_SOFTWARE_TYPE)).toBe('2');
208
+ expect(AccountingTypeEnum.parse(SHAAM_CONSTANTS.DEFAULT_ACCOUNTING_TYPE)).toBe('2');
209
+ expect(BalanceRequiredEnum.parse(SHAAM_CONSTANTS.DEFAULT_BALANCE_REQUIRED)).toBe('1');
210
+ expect(BranchInfoFlagEnum.parse(SHAAM_CONSTANTS.DEFAULT_BRANCH_INFO)).toBe('0');
211
+ });
212
+ });
213
+
214
+ describe('ShaamEnumSchemas', () => {
215
+ it('should provide access to all enum schemas', () => {
216
+ expect(ShaamEnumSchemas.DocumentType).toBe(DocumentTypeEnum);
217
+ expect(ShaamEnumSchemas.CurrencyCode).toBe(CurrencyCodeEnum);
218
+ expect(ShaamEnumSchemas.RecordType).toBe(RecordTypeEnum);
219
+ expect(ShaamEnumSchemas.SoftwareType).toBe(SoftwareTypeEnum);
220
+ expect(ShaamEnumSchemas.AccountingType).toBe(AccountingTypeEnum);
221
+ });
222
+ });
223
+
224
+ describe('ShaamLabels', () => {
225
+ it('should provide access to all label mappings', () => {
226
+ expect(ShaamLabels.DocumentType).toBe(DocumentTypeLabels);
227
+ expect(ShaamLabels.DocumentTypeEn).toBe(DocumentTypeLabelsEn);
228
+ expect(ShaamLabels.Currency).toBe(CurrencyLabels);
229
+ expect(ShaamLabels.Country).toBe(CountryLabels);
230
+ expect(ShaamLabels.SoftwareType).toBe(SoftwareTypeLabels);
231
+ });
232
+ });
233
+
234
+ describe('Type Safety', () => {
235
+ it('should enforce correct types at compile time', () => {
236
+ // These should compile without errors
237
+ const docType: DocumentType = '320';
238
+ const currency: CurrencyCode = 'ILS';
239
+ const recordType: RecordType = 'A100';
240
+ const softwareType: SoftwareType = '2';
241
+ const accountingType: AccountingType = '2';
242
+ const debitCredit: DebitCreditIndicator = '1';
243
+ const paymentMethod: PaymentMethod = '3';
244
+ const trialBalance: TrialBalanceCode = 'Asset';
245
+ const country: CountryCode = 'IL';
246
+
247
+ // Verify they hold the expected values
248
+ expect(docType).toBe('320');
249
+ expect(currency).toBe('ILS');
250
+ expect(recordType).toBe('A100');
251
+ expect(softwareType).toBe('2');
252
+ expect(accountingType).toBe('2');
253
+ expect(debitCredit).toBe('1');
254
+ expect(paymentMethod).toBe('3');
255
+ expect(trialBalance).toBe('Asset');
256
+ expect(country).toBe('IL');
257
+ });
258
+ });
259
+
260
+ describe('Coverage and Completeness', () => {
261
+ it('should have all document types covered in both label sets', () => {
262
+ const docTypes = DocumentTypeEnum.options;
263
+
264
+ for (const type of docTypes) {
265
+ expect(DocumentTypeLabels[type]).toBeDefined();
266
+ expect(DocumentTypeLabelsEn[type]).toBeDefined();
267
+ expect(typeof DocumentTypeLabels[type]).toBe('string');
268
+ expect(typeof DocumentTypeLabelsEn[type]).toBe('string');
269
+ }
270
+ });
271
+
272
+ it('should have all currencies covered in labels', () => {
273
+ const currencies = CurrencyCodeEnum.options;
274
+
275
+ for (const currency of currencies) {
276
+ expect(CurrencyLabels[currency]).toBeDefined();
277
+ expect(typeof CurrencyLabels[currency]).toBe('string');
278
+ }
279
+ });
280
+
281
+ it('should have all countries covered in labels', () => {
282
+ const countries = CountryCodeEnum.options;
283
+
284
+ for (const country of countries) {
285
+ expect(CountryLabels[country]).toBeDefined();
286
+ expect(typeof CountryLabels[country]).toBe('string');
287
+ }
288
+ });
289
+ });
290
+ });