@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,347 @@
1
+ import { z } from 'zod';
2
+ import { CRLF } from '../../format/index.js';
3
+ import { DocumentTypeEnum } from '../../types/index.js';
4
+ import { formatField, formatNumericField } from '../index.js';
5
+
6
+ /**
7
+ * C100 Record Schema - Document header record
8
+ * Fields 1200-1235 based on SHAAM 1.31 specification
9
+ * Total record length: 445 characters
10
+ */
11
+ export const C100Schema = z.object({
12
+ // Field 1200: Record code (4) - positions 1-4
13
+ code: z.literal('C100').describe('Record type code - always "C100"'),
14
+ // Field 1201: Record number in BKMVDATA.TXT file (9) - positions 5-13
15
+ recordNumber: z.string().min(1).max(9).regex(/^\d+$/).describe('Sequential record number'),
16
+ // Field 1202: VAT ID number without control digits (9) - positions 14-22
17
+ vatId: z.string().min(1).max(9).regex(/^\d+$/).describe('VAT identification number'),
18
+ // Field 1203: Document type (3) - positions 23-25
19
+ documentType: DocumentTypeEnum.describe('Document type code'),
20
+ // Field 1204: Document ID/sequence number (20) - positions 26-45
21
+ documentId: z.string().min(1).max(20).describe('Document identification number'),
22
+ // Field 1205: Document issue date YYYYMMDD format (8) - positions 46-53
23
+ documentIssueDate: z
24
+ .string()
25
+ .length(8)
26
+ .regex(/^\d{8}$/)
27
+ .describe('Document issue date YYYYMMDD'),
28
+ // Field 1206: Document issue time HHMM format in 24h (4) - positions 54-57
29
+ documentIssueTime: z
30
+ .string()
31
+ .max(4)
32
+ .regex(/^(\d{4}|)$/)
33
+ .default('')
34
+ .describe('Document issue time HHMM (24h format)'),
35
+ // Field 1207: Customer/Supplier name/description (50) - positions 58-107
36
+ customerName: z.string().max(50).default('').describe('Customer or supplier name'),
37
+ // Field 1208: Customer/Supplier street (50) - positions 108-157
38
+ customerStreet: z.string().max(50).default('').describe('Customer street address'),
39
+ // Field 1209: Customer/Supplier house number (10) - positions 158-167
40
+ customerHouseNumber: z.string().max(10).default('').describe('Customer house number'),
41
+ // Field 1210: Customer/Supplier city (30) - positions 168-197
42
+ customerCity: z.string().max(30).default('').describe('Customer city'),
43
+ // Field 1211: Customer/Supplier post code (8) - positions 198-205
44
+ customerPostCode: z.string().max(8).default('').describe('Customer post code'),
45
+ // Field 1212: Customer/Supplier country (30) - positions 206-235
46
+ customerCountry: z.string().max(30).default('').describe('Customer country'),
47
+ // Field 1213: Customer/Supplier country code (2) - positions 236-237
48
+ customerCountryCode: z.string().max(2).default('').describe('Customer country code'),
49
+ // Field 1214: Customer/Supplier phone (15) - positions 238-252
50
+ customerPhone: z.string().max(15).default('').describe('Customer phone number'),
51
+ // Field 1215: Customer/Supplier VAT ID (9) - positions 253-261
52
+ customerVatId: z
53
+ .string()
54
+ .max(9)
55
+ .regex(/^(\d{1,9}|)$/)
56
+ .default('')
57
+ .describe('Customer VAT identification'),
58
+ // Field 1216: Document value date (8) - positions 262-269
59
+ documentValueDate: z
60
+ .string()
61
+ .max(8)
62
+ .regex(/^(\d{8}|)$/)
63
+ .default('')
64
+ .describe('Document value date YYYYMMDD'),
65
+ // Field 1217: Document foreign currency final amount (15) - positions 270-284
66
+ foreignCurrencyAmount: z
67
+ .string()
68
+ .max(15)
69
+ .regex(/^(\d{1,13}(\.\d{2})?|)$/)
70
+ .default('')
71
+ .describe('Document foreign currency final amount'),
72
+ // Field 1218: Currency code (3) - positions 285-287
73
+ currencyCode: z.string().max(3).default('').describe('Currency code'),
74
+ // Field 1219: Document amount before discount (15) - positions 288-302
75
+ amountBeforeDiscount: z
76
+ .string()
77
+ .max(15)
78
+ .regex(/^(\d{1,13}(\.\d{2})?|)$/)
79
+ .default('')
80
+ .describe('Document amount before discount'),
81
+ // Field 1220: Document discount (15) - positions 303-317
82
+ documentDiscount: z
83
+ .string()
84
+ .max(15)
85
+ .regex(/^(\d{1,13}(\.\d{2})?|)$/)
86
+ .default('')
87
+ .describe('Document discount amount'),
88
+ // Field 1221: Document amount after discount, not including VAT (15) - positions 318-332
89
+ amountAfterDiscountExcludingVat: z
90
+ .string()
91
+ .max(15)
92
+ .regex(/^(\d{1,13}(\.\d{2})?|)$/)
93
+ .default('')
94
+ .describe('Amount after discount excluding VAT'),
95
+ // Field 1222: VAT amount (15) - positions 333-347
96
+ vatAmount: z
97
+ .string()
98
+ .max(15)
99
+ .regex(/^(\d{1,13}(\.\d{2})?|)$/)
100
+ .default('')
101
+ .describe('VAT amount'),
102
+ // Field 1223: Document amount including VAT (15) - positions 348-362
103
+ amountIncludingVat: z
104
+ .string()
105
+ .max(15)
106
+ .regex(/^(\d{1,13}(\.\d{2})?|)$/)
107
+ .default('')
108
+ .describe('Document amount including VAT'),
109
+ // Field 1224: Withholding tax amount (12) - positions 363-374
110
+ withholdingTaxAmount: z
111
+ .string()
112
+ .max(12)
113
+ .regex(/^(\d{1,10}(\.\d{2})?|)$/)
114
+ .default('')
115
+ .describe('Withholding tax amount'),
116
+ // Field 1225: Customer key at seller or provider key at buyer (15) - positions 375-389
117
+ customerKey: z
118
+ .string()
119
+ .max(15)
120
+ .default('')
121
+ .describe('Customer key at seller or provider key at buyer'),
122
+ // Field 1226: Matching field (10) - positions 390-399
123
+ matchingField: z.string().max(10).default('').describe('Matching field'),
124
+ // Field 1227: [cancelled attribute] (8) - positions 400-407
125
+ cancelledAttribute1: z.string().max(8).default('').describe('Cancelled attribute'),
126
+ // Field 1228: Cancelled document (1) - positions 408-408
127
+ cancelledDocument: z.string().max(1).default('').describe('Cancelled document flag'),
128
+ // Field 1229: [cancelled attribute] (8) - positions 409-416
129
+ cancelledAttribute2: z.string().max(8).default('').describe('Cancelled attribute'),
130
+ // Field 1230: Document date (7) - positions 417-423
131
+ documentDate: z
132
+ .string()
133
+ .max(7)
134
+ .regex(/^(\d{7}|)$/)
135
+ .default('')
136
+ .describe('Document date'),
137
+ // Field 1231: Branch key (8) - positions 424-431
138
+ branchKey: z.string().max(8).default('').describe('Branch key'),
139
+ // Field 1232: [cancelled attribute] (1) - positions 432-432
140
+ cancelledAttribute3: z.string().max(1).default('').describe('Cancelled attribute'),
141
+ // Field 1233: Action executor (13) - positions 433-445
142
+ actionExecutor: z.string().max(13).default('').describe('Action executor'),
143
+ // Field 1234: Line connecting field (unused in this record layout)
144
+ lineConnectingField: z.string().max(0).default('').describe('Line connecting field (unused)'),
145
+ // Field 1235: Reserved (unused in this record layout)
146
+ reserved: z.string().max(0).default('').describe('Reserved field (unused)'),
147
+ });
148
+
149
+ export type C100 = z.infer<typeof C100Schema>;
150
+
151
+ /**
152
+ * Encodes a C100 record to fixed-width string format
153
+ * Total line width: 445 characters + CRLF
154
+ */
155
+ export function encodeC100(input: C100): string {
156
+ const fields = [
157
+ formatField(input.code, 4, 'left'), // Field 1200: Record code (4) - positions 1-4 - Alphanumeric
158
+ formatNumericField(input.recordNumber, 9), // Field 1201: Record number (9) - positions 5-13 - Numeric
159
+ formatNumericField(input.vatId, 9), // Field 1202: VAT ID (9) - positions 14-22 - Numeric
160
+ formatNumericField(input.documentType, 3), // Field 1203: Document type (3) - positions 23-25 - Numeric
161
+ formatField(input.documentId, 20, 'left'), // Field 1204: Document ID (20) - positions 26-45 - Alphanumeric
162
+ formatNumericField(input.documentIssueDate, 8), // Field 1205: Document issue date (8) - positions 46-53 - Numeric
163
+ formatNumericField(input.documentIssueTime, 4), // Field 1206: Document issue time (4) - positions 54-57 - Numeric
164
+ formatField(input.customerName, 50, 'left'), // Field 1207: Customer name (50) - positions 58-107 - Alphanumeric
165
+ formatField(input.customerStreet, 50, 'left'), // Field 1208: Customer street (50) - positions 108-157 - Alphanumeric
166
+ formatField(input.customerHouseNumber, 10, 'left'), // Field 1209: Customer house number (10) - positions 158-167 - Alphanumeric
167
+ formatField(input.customerCity, 30, 'left'), // Field 1210: Customer city (30) - positions 168-197 - Alphanumeric
168
+ formatField(input.customerPostCode, 8, 'left'), // Field 1211: Customer post code (8) - positions 198-205 - Alphanumeric
169
+ formatField(input.customerCountry, 30, 'left'), // Field 1212: Customer country (30) - positions 206-235 - Alphanumeric
170
+ formatField(input.customerCountryCode, 2, 'left'), // Field 1213: Customer country code (2) - positions 236-237 - Alphanumeric
171
+ formatField(input.customerPhone, 15, 'left'), // Field 1214: Customer phone (15) - positions 238-252 - Alphanumeric
172
+ formatNumericField(input.customerVatId, 9), // Field 1215: Customer VAT ID (9) - positions 253-261 - Numeric
173
+ formatNumericField(input.documentValueDate, 8), // Field 1216: Document value date (8) - positions 262-269 - Numeric
174
+ formatField(input.foreignCurrencyAmount, 15, 'left'), // Field 1217: Foreign currency amount (15) - positions 270-284 - Alphanumeric
175
+ formatField(input.currencyCode, 3, 'left'), // Field 1218: Currency code (3) - positions 285-287 - Alphanumeric
176
+ formatField(input.amountBeforeDiscount, 15, 'left'), // Field 1219: Amount before discount (15) - positions 288-302 - Alphanumeric
177
+ formatField(input.documentDiscount, 15, 'left'), // Field 1220: Document discount (15) - positions 303-317 - Alphanumeric
178
+ formatField(input.amountAfterDiscountExcludingVat, 15, 'left'), // Field 1221: Amount after discount excluding VAT (15) - positions 318-332 - Alphanumeric
179
+ formatField(input.vatAmount, 15, 'left'), // Field 1222: VAT amount (15) - positions 333-347 - Alphanumeric
180
+ formatField(input.amountIncludingVat, 15, 'left'), // Field 1223: Amount including VAT (15) - positions 348-362 - Alphanumeric
181
+ formatField(input.withholdingTaxAmount, 12, 'left'), // Field 1224: Withholding tax amount (12) - positions 363-374 - Alphanumeric
182
+ formatField(input.customerKey, 15, 'left'), // Field 1225: Customer key (15) - positions 375-389 - Alphanumeric
183
+ formatField(input.matchingField, 10, 'left'), // Field 1226: Matching field (10) - positions 390-399 - Alphanumeric
184
+ formatField(input.cancelledAttribute1, 8, 'left'), // Field 1227: Cancelled attribute 1 (8) - positions 400-407 - Alphanumeric (deprecated)
185
+ formatField(input.cancelledDocument, 1, 'left'), // Field 1228: Cancelled document (1) - positions 408-408 - Alphanumeric
186
+ formatField(input.cancelledAttribute2, 8, 'left'), // Field 1229: Cancelled attribute 2 (8) - positions 409-416 - Alphanumeric (deprecated)
187
+ formatNumericField(input.documentDate, 7), // Field 1230: Document date (7) - positions 417-423 - Numeric
188
+ formatField(input.branchKey, 8, 'left'), // Field 1231: Branch key (8) - positions 424-431 - Alphanumeric
189
+ formatField(input.cancelledAttribute3, 1, 'left'), // Field 1232: Cancelled attribute 3 (1) - positions 432-432 - Alphanumeric (deprecated)
190
+ formatField(input.actionExecutor, 13, 'left'), // Field 1233: Action executor (13) - positions 433-445 - Alphanumeric
191
+ ];
192
+
193
+ return fields.join('') + CRLF;
194
+ }
195
+
196
+ /**
197
+ * Parses a fixed-width C100 record line back to object
198
+ * Expected line length: 445 characters (excluding CRLF)
199
+ */
200
+ /**
201
+ * Parses a fixed-width C100 record line back to object
202
+ * Expected line length: 445 characters (excluding CRLF)
203
+ */
204
+ export function parseC100(line: string): C100 {
205
+ // Remove CRLF if present
206
+ const cleanLine = line.replace(/\r?\n$/, '');
207
+
208
+ if (cleanLine.length !== 445) {
209
+ throw new Error(`Invalid C100 record length: expected 445 characters, got ${cleanLine.length}`);
210
+ }
211
+
212
+ // Extract fields at their fixed positions based on specification
213
+ let pos = 0;
214
+ const code = cleanLine.slice(pos, pos + 4).trim();
215
+ pos += 4; // positions 1-4
216
+ const recordNumber =
217
+ cleanLine
218
+ .slice(pos, pos + 9)
219
+ .trim()
220
+ .replace(/^0+/, '') || '0';
221
+ pos += 9; // positions 5-13
222
+ const vatId =
223
+ cleanLine
224
+ .slice(pos, pos + 9)
225
+ .trim()
226
+ .replace(/^0+/, '') || '0';
227
+ pos += 9; // positions 14-22
228
+ const documentType = (cleanLine
229
+ .slice(pos, pos + 3)
230
+ .trim()
231
+ .replace(/^0+/, '') || '0') as z.infer<typeof DocumentTypeEnum>;
232
+ pos += 3; // positions 23-25
233
+ const documentId = cleanLine.slice(pos, pos + 20).trim();
234
+ pos += 20; // positions 26-45
235
+ const documentIssueDate =
236
+ cleanLine
237
+ .slice(pos, pos + 8)
238
+ .trim()
239
+ .replace(/^0+/, '') || '0';
240
+ pos += 8; // positions 46-53
241
+ const documentIssueTime = cleanLine.slice(pos, pos + 4).trim();
242
+ const processedDocumentIssueTime = documentIssueTime === '0000' ? '' : documentIssueTime;
243
+ pos += 4; // positions 54-57
244
+ const customerName = cleanLine.slice(pos, pos + 50).trim();
245
+ pos += 50; // positions 58-107
246
+ const customerStreet = cleanLine.slice(pos, pos + 50).trim();
247
+ pos += 50; // positions 108-157
248
+ const customerHouseNumber = cleanLine.slice(pos, pos + 10).trim();
249
+ pos += 10; // positions 158-167
250
+ const customerCity = cleanLine.slice(pos, pos + 30).trim();
251
+ pos += 30; // positions 168-197
252
+ const customerPostCode = cleanLine.slice(pos, pos + 8).trim();
253
+ pos += 8; // positions 198-205
254
+ const customerCountry = cleanLine.slice(pos, pos + 30).trim();
255
+ pos += 30; // positions 206-235
256
+ const customerCountryCode = cleanLine.slice(pos, pos + 2).trim();
257
+ pos += 2; // positions 236-237
258
+ const customerPhone = cleanLine.slice(pos, pos + 15).trim();
259
+ pos += 15; // positions 238-252
260
+ const customerVatId = cleanLine.slice(pos, pos + 9).trim();
261
+ const processedCustomerVatId = customerVatId ? customerVatId.replace(/^0+/, '') || '0' : '';
262
+ pos += 9; // positions 253-261
263
+ const documentValueDate = cleanLine.slice(pos, pos + 8).trim();
264
+ const processedDocumentValueDate = documentValueDate === '00000000' ? '' : documentValueDate;
265
+ pos += 8; // positions 262-269
266
+ const foreignCurrencyAmount = cleanLine.slice(pos, pos + 15).trim();
267
+ pos += 15; // positions 270-284
268
+ const currencyCode = cleanLine.slice(pos, pos + 3).trim();
269
+ pos += 3; // positions 285-287
270
+ const amountBeforeDiscount = cleanLine.slice(pos, pos + 15).trim();
271
+ pos += 15; // positions 288-302
272
+ const documentDiscount = cleanLine.slice(pos, pos + 15).trim();
273
+ pos += 15; // positions 303-317
274
+ const amountAfterDiscountExcludingVat = cleanLine.slice(pos, pos + 15).trim();
275
+ pos += 15; // positions 318-332
276
+ const vatAmount = cleanLine.slice(pos, pos + 15).trim();
277
+ pos += 15; // positions 333-347
278
+ const amountIncludingVat = cleanLine.slice(pos, pos + 15).trim();
279
+ pos += 15; // positions 348-362
280
+ const withholdingTaxAmount = cleanLine.slice(pos, pos + 12).trim();
281
+ pos += 12; // positions 363-374
282
+ const customerKey = cleanLine.slice(pos, pos + 15).trim();
283
+ pos += 15; // positions 375-389
284
+ const matchingField = cleanLine.slice(pos, pos + 10).trim();
285
+ pos += 10; // positions 390-399
286
+ const cancelledAttribute1 = cleanLine.slice(pos, pos + 8).trim();
287
+ pos += 8; // positions 400-407
288
+ const cancelledDocument = cleanLine.slice(pos, pos + 1).trim();
289
+ pos += 1; // positions 408-408
290
+ const cancelledAttribute2 = cleanLine.slice(pos, pos + 8).trim();
291
+ pos += 8; // positions 409-416
292
+ const documentDate = cleanLine.slice(pos, pos + 7).trim();
293
+ const processedDocumentDate = documentDate === '0000000' ? '' : documentDate;
294
+ pos += 7; // positions 417-423
295
+ const branchKey = cleanLine.slice(pos, pos + 8).trim();
296
+ pos += 8; // positions 424-431
297
+ const cancelledAttribute3 = cleanLine.slice(pos, pos + 1).trim();
298
+ pos += 1; // positions 432-432
299
+ const actionExecutor = cleanLine.slice(pos, pos + 13).trim(); // positions 433-445
300
+
301
+ // Validate the code field
302
+ if (code !== 'C100') {
303
+ throw new Error(`Invalid C100 record code: expected "C100", got "${code}"`);
304
+ }
305
+
306
+ const parsed: C100 = {
307
+ code,
308
+ recordNumber,
309
+ vatId,
310
+ documentType,
311
+ documentId,
312
+ documentIssueDate,
313
+ documentIssueTime: processedDocumentIssueTime,
314
+ customerName,
315
+ customerStreet,
316
+ customerHouseNumber,
317
+ customerCity,
318
+ customerPostCode,
319
+ customerCountry,
320
+ customerCountryCode,
321
+ customerPhone,
322
+ customerVatId: processedCustomerVatId,
323
+ documentValueDate: processedDocumentValueDate,
324
+ foreignCurrencyAmount,
325
+ currencyCode,
326
+ amountBeforeDiscount,
327
+ documentDiscount,
328
+ amountAfterDiscountExcludingVat,
329
+ vatAmount,
330
+ amountIncludingVat,
331
+ withholdingTaxAmount,
332
+ customerKey,
333
+ matchingField,
334
+ cancelledAttribute1,
335
+ cancelledDocument,
336
+ cancelledAttribute2,
337
+ documentDate: processedDocumentDate,
338
+ branchKey,
339
+ cancelledAttribute3,
340
+ actionExecutor,
341
+ lineConnectingField: '',
342
+ reserved: '',
343
+ };
344
+
345
+ // Validate against schema
346
+ return C100Schema.parse(parsed);
347
+ }
@@ -0,0 +1,286 @@
1
+ import { z } from 'zod';
2
+ import { CRLF } from '../../format/index.js';
3
+ import { DocumentTypeEnum } from '../../types/index.js';
4
+ import { formatField, formatNumericField } from '../index.js';
5
+
6
+ /**
7
+ * D110 Record Schema - Document Line record
8
+ * Fields 1250-1275 based on SHAAM 1.31 specification table
9
+ */
10
+ export const D110Schema = z.object({
11
+ // Field 1250: Record code (4) - Required - Alphanumeric
12
+ code: z.literal('D110').describe('Record type code - always "D110"'),
13
+ // Field 1251: Record number in file (9) - Required - Numeric
14
+ recordNumber: z.string().min(1).max(9).regex(/^\d+$/).describe('Sequential record number'),
15
+ // Field 1252: Tax ID (9) - Required - Numeric
16
+ vatId: z.string().min(1).max(9).regex(/^\d+$/).describe('VAT identification number'),
17
+ // Field 1253: Document type (3) - Required - Numeric
18
+ documentType: DocumentTypeEnum.describe('Document type - see Appendix 1'),
19
+ // Field 1254: Document number (20) - Required - Alphanumeric
20
+ documentNumber: z.string().min(1).max(20).describe('Document number'),
21
+ // Field 1255: Line number in document (4) - Required - Numeric
22
+ lineNumber: z.string().min(1).max(4).regex(/^\d+$/).describe('Line number in document'),
23
+ // Field 1256: Base document type (3) - Conditional - Numeric
24
+ baseDocumentType: z
25
+ .string()
26
+ .max(3)
27
+ .regex(/^(\d{1,3}|)$/)
28
+ .default('')
29
+ .transform(val => (val === '' ? val : (val as z.infer<typeof DocumentTypeEnum>)))
30
+ .describe('Base document type - required if based on another document'),
31
+ // Field 1257: Base document number (20) - Conditional - Alphanumeric
32
+ baseDocumentNumber: z
33
+ .string()
34
+ .max(20)
35
+ .default('')
36
+ .describe('Base document number - required if based on another document'),
37
+ // Field 1258: Transaction type (1) - Optional - Numeric
38
+ transactionType: z
39
+ .string()
40
+ .max(1)
41
+ .regex(/^([123]|)$/)
42
+ .default('')
43
+ .describe('Transaction type: 1-Service; 2-Goods; 3-Service & Goods'),
44
+ // Field 1259: Internal catalog code (20) - Optional - Alphanumeric
45
+ internalCatalogCode: z.string().max(20).default('').describe('Internal catalog code'),
46
+ // Field 1260: Goods/Service description (30) - Required - Alphanumeric
47
+ goodsServiceDescription: z.string().min(1).max(30).describe('Goods or service description'),
48
+ // Field 1261: Manufacturer name (50) - Optional - Alphanumeric
49
+ manufacturerName: z
50
+ .string()
51
+ .max(50)
52
+ .default('')
53
+ .describe('Manufacturer name - relevant for goods listed in Appendix G'),
54
+ // Field 1262: Serial number (30) - Optional - Alphanumeric
55
+ serialNumber: z
56
+ .string()
57
+ .max(30)
58
+ .default('')
59
+ .describe('Serial number - relevant for goods listed in Appendix G'),
60
+ // Field 1263: Unit of measure description (20) - Optional - Alphanumeric
61
+ unitOfMeasureDescription: z
62
+ .string()
63
+ .max(20)
64
+ .default('')
65
+ .describe('Unit of measure description - use descriptive name or יחידה'),
66
+ // Field 1264: Quantity (17) - Required - Alphanumeric with decimal format X9(12)V9999
67
+ quantity: z.string().min(1).max(17).describe('Quantity'),
68
+ // Field 1265: Unit price excluding VAT (15) - Optional - Alphanumeric with decimal format X9(12)V99
69
+ unitPriceExcludingVat: z.string().max(15).default('').describe('Unit price excluding VAT in NIS'),
70
+ // Field 1266: Line discount (15) - Optional - Alphanumeric with decimal format X9(12)V99
71
+ lineDiscount: z.string().max(15).default('').describe('Line discount in NIS'),
72
+ // Field 1267: Line total (15) - Optional - Alphanumeric with decimal format X9(12)V99
73
+ lineTotal: z
74
+ .string()
75
+ .max(15)
76
+ .default('')
77
+ .describe('Line total: quantity * unit price - discount'),
78
+ // Field 1268: VAT rate percentage (4) - Optional - Numeric with decimal format V99(2)
79
+ vatRatePercent: z
80
+ .string()
81
+ .max(4)
82
+ .regex(/^(\d{1,4}|)$/)
83
+ .default('')
84
+ .describe('VAT rate percentage'),
85
+ // Field 1269: Reserved field (0) - Deprecated - Alphanumeric
86
+ reserved1: z.string().max(0).default('').describe('Reserved field - deprecated'),
87
+ // Field 1270: Branch ID (7) - Required - Alphanumeric
88
+ branchId: z.string().min(1).max(7).describe('Branch ID - required if field 1034 = 1'),
89
+ // Field 1271: Reserved field (0) - Deprecated - Alphanumeric
90
+ reserved2: z.string().max(0).default('').describe('Reserved field - deprecated'),
91
+ // Field 1272: Document date (8) - Required - Numeric YYYYMMDD format
92
+ documentDate: z
93
+ .string()
94
+ .min(1)
95
+ .max(8)
96
+ .regex(/^\d{8}$/)
97
+ .describe('Document date in YYYYMMDD format'),
98
+ // Field 1273: Header link field (7) - Optional - Numeric
99
+ headerLinkField: z
100
+ .string()
101
+ .max(7)
102
+ .regex(/^(\d{1,7}|)$/)
103
+ .default('')
104
+ .describe('Header link field - link to C100'),
105
+ // Field 1274: Base document branch ID (7) - Optional - Alphanumeric
106
+ baseDocumentBranchId: z
107
+ .string()
108
+ .max(7)
109
+ .default('')
110
+ .describe('Base document branch ID - if field 1034 = 1'),
111
+ // Field 1275: Reserved field (21) - Optional - Alphanumeric
112
+ reserved3: z.string().max(21).default('').describe('Reserved field'),
113
+ });
114
+
115
+ export type D110 = z.infer<typeof D110Schema>;
116
+
117
+ /**
118
+ * Encodes a D110 record to fixed-width string format
119
+ * Total line width: 339 characters + CRLF
120
+ */
121
+ export function encodeD110(input: D110): string {
122
+ const fields = [
123
+ formatField(input.code, 4, 'left'), // Field 1250: Record code (4) - Alphanumeric
124
+ formatNumericField(input.recordNumber, 9), // Field 1251: Record number (9) - Numeric
125
+ formatNumericField(input.vatId, 9), // Field 1252: VAT ID (9) - Numeric
126
+ formatNumericField(input.documentType, 3), // Field 1253: Document type (3) - Numeric
127
+ formatField(input.documentNumber, 20, 'left'), // Field 1254: Document number (20) - Alphanumeric
128
+ formatNumericField(input.lineNumber, 4), // Field 1255: Line number (4) - Numeric
129
+ formatNumericField(input.baseDocumentType, 3), // Field 1256: Base document type (3) - Numeric
130
+ formatField(input.baseDocumentNumber, 20, 'left'), // Field 1257: Base document number (20) - Alphanumeric
131
+ formatNumericField(input.transactionType, 1), // Field 1258: Transaction type (1) - Numeric
132
+ formatField(input.internalCatalogCode, 20, 'left'), // Field 1259: Internal catalog code (20) - Alphanumeric
133
+ formatField(input.goodsServiceDescription, 30, 'left'), // Field 1260: Goods/Service description (30) - Alphanumeric
134
+ formatField(input.manufacturerName, 50, 'left'), // Field 1261: Manufacturer name (50) - Alphanumeric
135
+ formatField(input.serialNumber, 30, 'left'), // Field 1262: Serial number (30) - Alphanumeric
136
+ formatField(input.unitOfMeasureDescription, 20, 'left'), // Field 1263: Unit of measure description (20) - Alphanumeric
137
+ formatField(input.quantity, 17, 'left'), // Field 1264: Quantity (17) - Alphanumeric but monetary format
138
+ formatField(input.unitPriceExcludingVat, 15, 'left'), // Field 1265: Unit price excluding VAT (15) - Alphanumeric but monetary format
139
+ formatField(input.lineDiscount, 15, 'left'), // Field 1266: Line discount (15) - Alphanumeric but monetary format
140
+ formatField(input.lineTotal, 15, 'left'), // Field 1267: Line total (15) - Alphanumeric but monetary format
141
+ formatNumericField(input.vatRatePercent, 4), // Field 1268: VAT rate % (4) - Numeric with decimal
142
+ formatField(input.reserved1, 0, 'left'), // Field 1269: Reserved (0) - Deprecated
143
+ formatField(input.branchId, 7, 'left'), // Field 1270: Branch ID (7) - Alphanumeric
144
+ formatField(input.reserved2, 0, 'left'), // Field 1271: Reserved (0) - Deprecated
145
+ formatNumericField(input.documentDate, 8), // Field 1272: Document date (8) - Numeric YYYYMMDD
146
+ formatNumericField(input.headerLinkField, 7), // Field 1273: Header link field (7) - Numeric
147
+ formatField(input.baseDocumentBranchId, 7, 'left'), // Field 1274: Base document branch ID (7) - Alphanumeric
148
+ formatField(input.reserved3, 21, 'left'), // Field 1275: Reserved (21) - Alphanumeric
149
+ ];
150
+
151
+ return fields.join('') + CRLF;
152
+ }
153
+
154
+ /**
155
+ * Parses a fixed-width D110 record line back to object
156
+ * Expected line length: 339 characters (excluding CRLF)
157
+ */
158
+ export function parseD110(line: string): D110 {
159
+ // Remove CRLF if present
160
+ const cleanLine = line.replace(/\r?\n$/, '');
161
+
162
+ if (cleanLine.length !== 339) {
163
+ throw new Error(`Invalid D110 record length: expected 339 characters, got ${cleanLine.length}`);
164
+ }
165
+
166
+ // Extract fields at their fixed positions
167
+ let pos = 0;
168
+ const code = cleanLine.slice(pos, pos + 4).trim();
169
+ pos += 4;
170
+ const recordNumber =
171
+ cleanLine
172
+ .slice(pos, pos + 9)
173
+ .trim()
174
+ .replace(/^0+/, '') || '0';
175
+ pos += 9;
176
+ const vatId =
177
+ cleanLine
178
+ .slice(pos, pos + 9)
179
+ .trim()
180
+ .replace(/^0+/, '') || '0';
181
+ pos += 9;
182
+ const documentType = (cleanLine
183
+ .slice(pos, pos + 3)
184
+ .trim()
185
+ .replace(/^0+/, '') || '0') as z.infer<typeof DocumentTypeEnum>;
186
+ pos += 3;
187
+ const documentNumber = cleanLine.slice(pos, pos + 20).trim();
188
+ pos += 20;
189
+ const lineNumber =
190
+ cleanLine
191
+ .slice(pos, pos + 4)
192
+ .trim()
193
+ .replace(/^0+/, '') || '0';
194
+ pos += 4;
195
+ const baseDocumentType = cleanLine.slice(pos, pos + 3).trim() as
196
+ | ''
197
+ | z.infer<typeof DocumentTypeEnum>;
198
+ pos += 3;
199
+ const baseDocumentNumber = cleanLine.slice(pos, pos + 20).trim();
200
+ pos += 20;
201
+ const transactionType = cleanLine.slice(pos, pos + 1).trim();
202
+ const processedTransactionType = transactionType === '0' ? '' : transactionType;
203
+ pos += 1;
204
+ const internalCatalogCode = cleanLine.slice(pos, pos + 20).trim();
205
+ pos += 20;
206
+ const goodsServiceDescription = cleanLine.slice(pos, pos + 30).trim();
207
+ pos += 30;
208
+ const manufacturerName = cleanLine.slice(pos, pos + 50).trim();
209
+ pos += 50;
210
+ const serialNumber = cleanLine.slice(pos, pos + 30).trim();
211
+ pos += 30;
212
+ const unitOfMeasureDescription = cleanLine.slice(pos, pos + 20).trim();
213
+ pos += 20;
214
+ const quantity = cleanLine.slice(pos, pos + 17).trim();
215
+ pos += 17;
216
+ const unitPriceExcludingVat = cleanLine.slice(pos, pos + 15).trim();
217
+ pos += 15;
218
+ const lineDiscount = cleanLine.slice(pos, pos + 15).trim();
219
+ pos += 15;
220
+ const lineTotal = cleanLine.slice(pos, pos + 15).trim();
221
+ pos += 15;
222
+ const vatRatePercent =
223
+ cleanLine
224
+ .slice(pos, pos + 4)
225
+ .trim()
226
+ .replace(/^0+/, '') || '0';
227
+ pos += 4;
228
+ const reserved1 = cleanLine.slice(pos, pos + 0).trim();
229
+ pos += 0; // 0 width
230
+ const branchId = cleanLine.slice(pos, pos + 7).trim();
231
+ pos += 7;
232
+ const reserved2 = cleanLine.slice(pos, pos + 0).trim();
233
+ pos += 0; // 0 width
234
+ const documentDate =
235
+ cleanLine
236
+ .slice(pos, pos + 8)
237
+ .trim()
238
+ .replace(/^0+/, '') || '0';
239
+ pos += 8;
240
+ const headerLinkField =
241
+ cleanLine
242
+ .slice(pos, pos + 7)
243
+ .trim()
244
+ .replace(/^0+/, '') || '0';
245
+ pos += 7;
246
+ const baseDocumentBranchId = cleanLine.slice(pos, pos + 7).trim();
247
+ pos += 7;
248
+ const reserved3 = cleanLine.slice(pos, pos + 21).trim(); // 21 width
249
+
250
+ // Validate the code field
251
+ if (code !== 'D110') {
252
+ throw new Error(`Invalid D110 record code: expected "D110", got "${code}"`);
253
+ }
254
+
255
+ const parsed: D110 = {
256
+ code,
257
+ recordNumber,
258
+ vatId,
259
+ documentType,
260
+ documentNumber,
261
+ lineNumber,
262
+ baseDocumentType,
263
+ baseDocumentNumber,
264
+ transactionType: processedTransactionType,
265
+ internalCatalogCode,
266
+ goodsServiceDescription,
267
+ manufacturerName,
268
+ serialNumber,
269
+ unitOfMeasureDescription,
270
+ quantity,
271
+ unitPriceExcludingVat,
272
+ lineDiscount,
273
+ lineTotal,
274
+ vatRatePercent,
275
+ reserved1,
276
+ branchId,
277
+ reserved2,
278
+ documentDate,
279
+ headerLinkField,
280
+ baseDocumentBranchId,
281
+ reserved3,
282
+ };
283
+
284
+ // Validate against schema
285
+ return D110Schema.parse(parsed);
286
+ }