@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,419 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { B100Schema, encodeB100, parseB100, type B100 } from '../../src/generator/records/b100';
3
+
4
+ describe('B100 Record', () => {
5
+ const validB100: B100 = {
6
+ code: 'B100',
7
+ recordNumber: '1',
8
+ vatId: '123456789',
9
+ transactionNumber: '1234567890',
10
+ transactionLineNumber: '1',
11
+ batchNumber: '12345678',
12
+ transactionType: 'Sale',
13
+ referenceDocument: 'INV-2023-001',
14
+ referenceDocumentType: '1',
15
+ referenceDocument2: 'REF-2023-001',
16
+ referenceDocumentType2: '2',
17
+ details: 'Payment for services',
18
+ date: '20231215',
19
+ valueDate: '20231215',
20
+ accountKey: 'ACC001',
21
+ counterAccountKey: 'ACC002',
22
+ debitCreditIndicator: '1',
23
+ currencyCode: 'ILS',
24
+ transactionAmount: '1000.00',
25
+ foreignCurrencyAmount: '300.00',
26
+ quantityField: '5.00',
27
+ matchingField1: 'MATCH001',
28
+ matchingField2: 'MATCH002',
29
+ branchId: 'BR001',
30
+ entryDate: '20231215',
31
+ operatorUsername: 'operator1',
32
+ reserved: '',
33
+ };
34
+
35
+ describe('B100Schema validation', () => {
36
+ it('should validate a complete B100 record', () => {
37
+ expect(() => B100Schema.parse(validB100)).not.toThrow();
38
+ });
39
+
40
+ it('should require code to be exactly "B100"', () => {
41
+ expect(() => B100Schema.parse({ ...validB100, code: 'B10' })).toThrow();
42
+ expect(() => B100Schema.parse({ ...validB100, code: 'B1000' })).toThrow();
43
+ expect(() => B100Schema.parse({ ...validB100, code: 'A100' })).toThrow();
44
+ });
45
+
46
+ it('should require non-empty required fields', () => {
47
+ expect(() => B100Schema.parse({ ...validB100, recordNumber: '' })).toThrow();
48
+ expect(() => B100Schema.parse({ ...validB100, vatId: '' })).toThrow();
49
+ expect(() => B100Schema.parse({ ...validB100, transactionNumber: '' })).toThrow();
50
+ expect(() => B100Schema.parse({ ...validB100, transactionLineNumber: '' })).toThrow();
51
+ expect(() => B100Schema.parse({ ...validB100, accountKey: '' })).toThrow();
52
+ expect(() => B100Schema.parse({ ...validB100, transactionAmount: '' })).toThrow();
53
+ });
54
+
55
+ it('should allow empty optional fields', () => {
56
+ const minimal: B100 = {
57
+ code: 'B100',
58
+ recordNumber: '1',
59
+ vatId: '123456789',
60
+ transactionNumber: '1234567890',
61
+ transactionLineNumber: '1',
62
+ batchNumber: '',
63
+ transactionType: '',
64
+ referenceDocument: '',
65
+ referenceDocumentType: '',
66
+ referenceDocument2: '',
67
+ referenceDocumentType2: '',
68
+ details: '',
69
+ date: '20231215',
70
+ valueDate: '20231215',
71
+ accountKey: 'ACC001',
72
+ counterAccountKey: '',
73
+ debitCreditIndicator: '1',
74
+ currencyCode: '',
75
+ transactionAmount: '1000.00',
76
+ foreignCurrencyAmount: '',
77
+ quantityField: '',
78
+ matchingField1: '',
79
+ matchingField2: '',
80
+ branchId: '',
81
+ entryDate: '20231215',
82
+ operatorUsername: '',
83
+ reserved: '',
84
+ };
85
+ expect(() => B100Schema.parse(minimal)).not.toThrow();
86
+ });
87
+
88
+ it('should validate debit/credit indicator', () => {
89
+ expect(() => B100Schema.parse({ ...validB100, debitCreditIndicator: '1' })).not.toThrow();
90
+ expect(() => B100Schema.parse({ ...validB100, debitCreditIndicator: '2' })).not.toThrow();
91
+ expect(() => B100Schema.parse({ ...validB100, debitCreditIndicator: '0' })).toThrow();
92
+ expect(() => B100Schema.parse({ ...validB100, debitCreditIndicator: '3' })).toThrow();
93
+ });
94
+
95
+ it('should validate date format (8 digits)', () => {
96
+ expect(() => B100Schema.parse({ ...validB100, date: '2023121' })).toThrow(); // Too short
97
+ expect(() => B100Schema.parse({ ...validB100, date: '202312150' })).toThrow(); // Too long
98
+ expect(() => B100Schema.parse({ ...validB100, date: '20231a15' })).toThrow(); // Non-numeric
99
+ });
100
+
101
+ it('should enforce maximum field lengths', () => {
102
+ expect(() => B100Schema.parse({ ...validB100, recordNumber: '1234567890' })).toThrow();
103
+ expect(() => B100Schema.parse({ ...validB100, vatId: '1234567890' })).toThrow();
104
+ expect(() => B100Schema.parse({ ...validB100, transactionNumber: '12345678901' })).toThrow();
105
+ expect(() => B100Schema.parse({ ...validB100, transactionLineNumber: '123456' })).toThrow();
106
+ expect(() => B100Schema.parse({ ...validB100, batchNumber: '123456789' })).toThrow();
107
+ expect(() => B100Schema.parse({ ...validB100, transactionType: 'x'.repeat(16) })).toThrow();
108
+ expect(() => B100Schema.parse({ ...validB100, referenceDocument: 'x'.repeat(21) })).toThrow();
109
+ expect(() => B100Schema.parse({ ...validB100, details: 'x'.repeat(51) })).toThrow();
110
+ expect(() => B100Schema.parse({ ...validB100, accountKey: 'x'.repeat(16) })).toThrow();
111
+ expect(() => B100Schema.parse({ ...validB100, reserved: 'x'.repeat(26) })).toThrow();
112
+ });
113
+ });
114
+
115
+ describe('encodeB100', () => {
116
+ it('should encode a valid B100 record to fixed-width format', () => {
117
+ const encoded = encodeB100(validB100);
118
+
119
+ // Should end with CRLF
120
+ expect(encoded).toMatch(/\r\n$/);
121
+
122
+ // Remove CRLF for length check
123
+ const withoutCrlf = encoded.replace(/\r\n$/, '');
124
+ expect(withoutCrlf).toHaveLength(317);
125
+
126
+ // Check specific field positions
127
+ expect(withoutCrlf.slice(0, 4)).toBe('B100'); // Record code
128
+ expect(withoutCrlf.slice(4, 13)).toBe('000000001'); // Zero-padded record number
129
+ expect(withoutCrlf.slice(13, 22)).toBe('123456789'); // VAT ID
130
+ expect(withoutCrlf.slice(22, 32)).toBe('1234567890'); // Transaction number
131
+ expect(withoutCrlf.slice(32, 37)).toBe('00001'); // Zero-padded transaction line number
132
+ });
133
+
134
+ it('should handle numeric fields with zero padding', () => {
135
+ const shortFields: B100 = {
136
+ ...validB100,
137
+ recordNumber: '42',
138
+ transactionNumber: '123',
139
+ transactionLineNumber: '5',
140
+ batchNumber: '99',
141
+ };
142
+
143
+ const encoded = encodeB100(shortFields);
144
+ const withoutCrlf = encoded.replace(/\r\n$/, '');
145
+
146
+ // Record number should be zero-padded
147
+ expect(withoutCrlf.slice(4, 13)).toBe('000000042');
148
+ // Transaction number should be zero-padded
149
+ expect(withoutCrlf.slice(22, 32)).toBe('0000000123');
150
+ // Transaction line number should be zero-padded
151
+ expect(withoutCrlf.slice(32, 37)).toBe('00005');
152
+ // Batch number should be zero-padded
153
+ expect(withoutCrlf.slice(37, 45)).toBe('00000099');
154
+ });
155
+
156
+ it('should handle amount fields with right alignment', () => {
157
+ const encoded = encodeB100(validB100);
158
+ const withoutCrlf = encoded.replace(/\r\n$/, '');
159
+
160
+ // Find transaction amount field position (after all previous fields)
161
+ const transactionAmountPos =
162
+ 4 + 9 + 9 + 10 + 5 + 8 + 15 + 20 + 3 + 20 + 3 + 50 + 8 + 8 + 15 + 15 + 1 + 3;
163
+ expect(withoutCrlf.slice(transactionAmountPos, transactionAmountPos + 15)).toBe(
164
+ ' 1000.00',
165
+ ); // Right-aligned
166
+ });
167
+
168
+ it('should pad empty fields correctly', () => {
169
+ const minimal: B100 = {
170
+ code: 'B100',
171
+ recordNumber: '1',
172
+ vatId: '123456789',
173
+ transactionNumber: '1',
174
+ transactionLineNumber: '1',
175
+ batchNumber: '',
176
+ transactionType: '',
177
+ referenceDocument: '',
178
+ referenceDocumentType: '',
179
+ referenceDocument2: '',
180
+ referenceDocumentType2: '',
181
+ details: '',
182
+ date: '20231215',
183
+ valueDate: '20231215',
184
+ accountKey: 'ACC001',
185
+ counterAccountKey: '',
186
+ debitCreditIndicator: '1',
187
+ currencyCode: '',
188
+ transactionAmount: '1000.00',
189
+ foreignCurrencyAmount: '',
190
+ quantityField: '',
191
+ matchingField1: '',
192
+ matchingField2: '',
193
+ branchId: '',
194
+ entryDate: '20231215',
195
+ operatorUsername: '',
196
+ reserved: '',
197
+ };
198
+
199
+ const encoded = encodeB100(minimal);
200
+ const withoutCrlf = encoded.replace(/\r\n$/, '');
201
+
202
+ // Should still be exactly 317 characters
203
+ expect(withoutCrlf).toHaveLength(317);
204
+
205
+ // Empty batch number should be zero-padded
206
+ expect(withoutCrlf.slice(37, 45)).toBe('00000000');
207
+ });
208
+ });
209
+
210
+ describe('parseB100', () => {
211
+ it('should parse a valid encoded B100 record', () => {
212
+ const encoded = encodeB100(validB100);
213
+ const parsed = parseB100(encoded);
214
+
215
+ expect(parsed.code).toBe('B100');
216
+ expect(parsed.recordNumber).toBe('1');
217
+ expect(parsed.vatId).toBe('123456789');
218
+ expect(parsed.transactionNumber).toBe('1234567890');
219
+ expect(parsed.transactionLineNumber).toBe('1');
220
+ expect(parsed.accountKey).toBe('ACC001');
221
+ expect(parsed.debitCreditIndicator).toBe('1');
222
+ expect(parsed.date).toBe('20231215');
223
+ expect(parsed.transactionAmount).toBe('1000.00');
224
+ });
225
+
226
+ it('should handle lines without CRLF', () => {
227
+ const encoded = encodeB100(validB100);
228
+ const withoutCrlf = encoded.replace(/\r\n$/, '');
229
+ const parsed = parseB100(withoutCrlf);
230
+
231
+ expect(parsed.code).toBe(validB100.code);
232
+ expect(parsed.recordNumber).toBe(validB100.recordNumber);
233
+ });
234
+
235
+ it('should throw on invalid line length', () => {
236
+ const shortLine = 'B100 1123456789';
237
+ expect(() => parseB100(shortLine)).toThrow('Invalid B100 record length');
238
+ });
239
+
240
+ it('should throw on invalid record code', () => {
241
+ const invalidCode = 'A100' + ' '.repeat(313);
242
+ expect(() => parseB100(invalidCode)).toThrow('Invalid B100 record code');
243
+ });
244
+
245
+ it('should strip leading zeros from numeric fields correctly', () => {
246
+ // Create a line with zero-padded numeric fields
247
+ const paddedLine =
248
+ 'B100' + // code (4)
249
+ '000000042' + // recordNumber (9)
250
+ '000000123' + // vatId (9)
251
+ '0000000456' + // transactionNumber (10)
252
+ '00005' + // transactionLineNumber (5)
253
+ '00000099' + // batchNumber (8)
254
+ ' '.repeat(15) + // transactionType (15)
255
+ ' '.repeat(20) + // referenceDocument (20)
256
+ '000' + // referenceDocumentType (3)
257
+ ' '.repeat(20) + // referenceDocument2 (20)
258
+ '000' + // referenceDocumentType2 (3)
259
+ ' '.repeat(50) + // details (50)
260
+ '20231215' + // date (8)
261
+ '20231215' + // valueDate (8)
262
+ 'ACC001 ' + // accountKey (15)
263
+ ' '.repeat(15) + // counterAccountKey (15)
264
+ '1' + // debitCreditIndicator (1)
265
+ ' ' + // currencyCode (3)
266
+ ' 1000.00' + // transactionAmount (15)
267
+ ' '.repeat(15) + // foreignCurrencyAmount (15)
268
+ ' '.repeat(12) + // quantityField (12)
269
+ ' '.repeat(10) + // matchingField1 (10)
270
+ ' '.repeat(10) + // matchingField2 (10)
271
+ ' '.repeat(7) + // branchId (7)
272
+ '20231215' + // entryDate (8)
273
+ ' '.repeat(9) + // operatorUsername (9)
274
+ ' '.repeat(25); // reserved (25)
275
+
276
+ expect(paddedLine.length).toBe(317); // Verify correct length
277
+
278
+ const parsed = parseB100(paddedLine);
279
+
280
+ expect(parsed.recordNumber).toBe('42');
281
+ expect(parsed.vatId).toBe('123');
282
+ expect(parsed.transactionNumber).toBe('456');
283
+ expect(parsed.transactionLineNumber).toBe('5');
284
+ expect(parsed.batchNumber).toBe('99');
285
+ expect(parsed.referenceDocumentType).toBe('');
286
+ expect(parsed.referenceDocumentType2).toBe('');
287
+ });
288
+
289
+ it('should trim whitespace from parsed fields', () => {
290
+ const encoded = encodeB100(validB100);
291
+ const parsed = parseB100(encoded);
292
+
293
+ // All string fields should be trimmed
294
+ expect(parsed.transactionType).toBe('Sale');
295
+ expect(parsed.referenceDocument).toBe('INV-2023-001');
296
+ expect(parsed.details).toBe('Payment for services');
297
+ expect(parsed.accountKey).toBe('ACC001');
298
+ });
299
+ });
300
+
301
+ describe('round-trip encoding/parsing', () => {
302
+ it('should maintain data integrity through encode/parse cycle', () => {
303
+ const original = validB100;
304
+ const encoded = encodeB100(original);
305
+ const parsed = parseB100(encoded);
306
+
307
+ expect(parsed).toEqual(original);
308
+ });
309
+
310
+ it('should work with various field values', () => {
311
+ const testCases: B100[] = [
312
+ {
313
+ ...validB100,
314
+ recordNumber: '999999999',
315
+ transactionNumber: '9999999999',
316
+ transactionLineNumber: '99999',
317
+ debitCreditIndicator: '2',
318
+ },
319
+ {
320
+ ...validB100,
321
+ recordNumber: '1',
322
+ transactionNumber: '1',
323
+ transactionLineNumber: '1',
324
+ batchNumber: '',
325
+ transactionType: '',
326
+ referenceDocument: '',
327
+ details: '',
328
+ counterAccountKey: '',
329
+ currencyCode: '',
330
+ foreignCurrencyAmount: '',
331
+ quantityField: '',
332
+ matchingField1: '',
333
+ matchingField2: '',
334
+ branchId: '',
335
+ operatorUsername: '',
336
+ reserved: '',
337
+ },
338
+ ];
339
+
340
+ for (const testCase of testCases) {
341
+ const encoded = encodeB100(testCase);
342
+ const parsed = parseB100(encoded);
343
+ expect(parsed).toEqual(testCase);
344
+ }
345
+ });
346
+
347
+ it('should preserve empty optional fields', () => {
348
+ const withEmptyFields: B100 = {
349
+ ...validB100,
350
+ batchNumber: '',
351
+ transactionType: '',
352
+ referenceDocument: '',
353
+ referenceDocumentType: '',
354
+ referenceDocument2: '',
355
+ referenceDocumentType2: '',
356
+ details: '',
357
+ counterAccountKey: '',
358
+ currencyCode: '',
359
+ foreignCurrencyAmount: '',
360
+ quantityField: '',
361
+ matchingField1: '',
362
+ matchingField2: '',
363
+ branchId: '',
364
+ operatorUsername: '',
365
+ reserved: '',
366
+ };
367
+
368
+ const encoded = encodeB100(withEmptyFields);
369
+ const parsed = parseB100(encoded);
370
+
371
+ expect(parsed.batchNumber).toBe('');
372
+ expect(parsed.transactionType).toBe('');
373
+ expect(parsed.reserved).toBe('');
374
+ expect(parsed).toEqual(withEmptyFields);
375
+ });
376
+ });
377
+
378
+ describe('error cases', () => {
379
+ it('should handle malformed input gracefully', () => {
380
+ expect(() => parseB100('')).toThrow();
381
+ expect(() => parseB100('invalid')).toThrow();
382
+ expect(() => parseB100('B' + ' '.repeat(400))).toThrow();
383
+ });
384
+
385
+ it('should validate against schema after parsing', () => {
386
+ // Create a line with invalid debit/credit indicator
387
+ const invalidLine =
388
+ 'B100' +
389
+ '000000001' +
390
+ '123456789' +
391
+ '1234567890' +
392
+ '00001' +
393
+ '00000000' +
394
+ ' '.repeat(15) +
395
+ ' '.repeat(20) +
396
+ '000' +
397
+ ' '.repeat(20) +
398
+ '000' +
399
+ ' '.repeat(50) +
400
+ '20231215' +
401
+ '20231215' +
402
+ 'ACC001 ' +
403
+ ' '.repeat(15) +
404
+ '9' + // Invalid debit/credit indicator
405
+ ' '.repeat(3) +
406
+ ' 1000.00' +
407
+ ' '.repeat(15) +
408
+ ' '.repeat(12) +
409
+ ' '.repeat(10) +
410
+ ' '.repeat(10) +
411
+ ' '.repeat(7) +
412
+ '20231215' +
413
+ ' '.repeat(9) +
414
+ ' '.repeat(25);
415
+
416
+ expect(() => parseB100(invalidLine)).toThrow();
417
+ });
418
+ });
419
+ });