@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.
- package/README.md +366 -13
- package/dist/README.md +476 -0
- package/dist/cjs/api/generate-report-legacy.js +6 -0
- package/dist/cjs/api/generate-report.js +328 -0
- package/dist/cjs/constants.js +11 -0
- package/dist/cjs/generator/format/encoder.js +86 -0
- package/dist/cjs/generator/records/a000-sum.js +66 -0
- package/dist/cjs/generator/records/a000.js +349 -0
- package/dist/cjs/generator/records/a100.js +107 -0
- package/dist/cjs/generator/records/b100.js +305 -0
- package/dist/cjs/generator/records/b110.js +255 -0
- package/dist/cjs/generator/records/c100.js +338 -0
- package/dist/cjs/generator/records/d110.js +272 -0
- package/dist/cjs/generator/records/d120.js +278 -0
- package/{cjs → dist/cjs}/generator/records/index.js +1 -0
- package/dist/cjs/generator/records/m100.js +177 -0
- package/dist/cjs/generator/records/z900.js +93 -0
- package/{cjs → dist/cjs}/index.js +3 -0
- package/dist/cjs/records/a100.js +78 -0
- package/dist/cjs/records/index.js +20 -0
- package/dist/cjs/records/z900.js +82 -0
- package/dist/cjs/types/enums.js +457 -0
- package/{cjs → dist/cjs}/types/index.js +6 -1
- package/dist/cjs/utils/file-helpers.js +198 -0
- package/dist/cjs/utils/index.js +8 -0
- package/dist/cjs/utils/key-generator.js +71 -0
- package/dist/esm/api/generate-report-legacy.js +2 -0
- package/dist/esm/api/generate-report.js +325 -0
- package/dist/esm/constants.js +8 -0
- package/dist/esm/generator/format/encoder.js +77 -0
- package/dist/esm/generator/records/a000-sum.js +61 -0
- package/dist/esm/generator/records/a000.js +344 -0
- package/dist/esm/generator/records/a100.js +102 -0
- package/dist/esm/generator/records/b100.js +300 -0
- package/dist/esm/generator/records/b110.js +250 -0
- package/dist/esm/generator/records/c100.js +333 -0
- package/dist/esm/generator/records/d110.js +267 -0
- package/dist/esm/generator/records/d120.js +273 -0
- package/{esm → dist/esm}/generator/records/index.js +1 -0
- package/dist/esm/generator/records/m100.js +172 -0
- package/dist/esm/generator/records/z900.js +88 -0
- package/{esm → dist/esm}/index.js +3 -0
- package/dist/esm/records/a100.js +73 -0
- package/dist/esm/records/index.js +11 -0
- package/dist/esm/records/z900.js +77 -0
- package/dist/esm/types/enums.js +454 -0
- package/{esm → dist/esm}/types/index.js +5 -1
- package/dist/esm/utils/file-helpers.js +188 -0
- package/dist/esm/utils/index.js +5 -0
- package/dist/esm/utils/key-generator.js +65 -0
- package/dist/package.json +54 -0
- package/dist/typings/api/generate-report-legacy.d.cts +1 -0
- package/dist/typings/api/generate-report-legacy.d.ts +1 -0
- package/dist/typings/constants.d.cts +8 -0
- package/dist/typings/constants.d.ts +8 -0
- package/dist/typings/generator/format/encoder.d.cts +57 -0
- package/dist/typings/generator/format/encoder.d.ts +57 -0
- package/dist/typings/generator/records/a000-sum.d.cts +40 -0
- package/dist/typings/generator/records/a000-sum.d.ts +40 -0
- package/dist/typings/generator/records/a000.d.cts +238 -0
- package/dist/typings/generator/records/a000.d.ts +238 -0
- package/dist/typings/generator/records/a100.d.cts +59 -0
- package/dist/typings/generator/records/a100.d.ts +59 -0
- package/dist/typings/generator/records/b100.d.cts +101 -0
- package/dist/typings/generator/records/b100.d.ts +101 -0
- package/dist/typings/generator/records/b110.d.cts +89 -0
- package/dist/typings/generator/records/b110.d.ts +89 -0
- package/dist/typings/generator/records/c100.d.cts +133 -0
- package/dist/typings/generator/records/c100.d.ts +133 -0
- package/dist/typings/generator/records/d110.d.cts +98 -0
- package/dist/typings/generator/records/d110.d.ts +98 -0
- package/dist/typings/generator/records/d120.d.cts +95 -0
- package/dist/typings/generator/records/d120.d.ts +95 -0
- package/{typings → dist/typings}/generator/records/index.d.cts +1 -0
- package/{typings → dist/typings}/generator/records/index.d.ts +1 -0
- package/dist/typings/generator/records/m100.d.cts +69 -0
- package/dist/typings/generator/records/m100.d.ts +69 -0
- package/dist/typings/generator/records/z900.d.cts +61 -0
- package/dist/typings/generator/records/z900.d.ts +61 -0
- package/{typings → dist/typings}/index.d.cts +3 -0
- package/{typings → dist/typings}/index.d.ts +3 -0
- package/dist/typings/records/a100.d.cts +35 -0
- package/dist/typings/records/a100.d.ts +35 -0
- package/dist/typings/records/index.d.cts +2 -0
- package/dist/typings/records/index.d.ts +2 -0
- package/dist/typings/records/z900.d.cts +38 -0
- package/dist/typings/records/z900.d.ts +38 -0
- package/dist/typings/types/enums.d.cts +162 -0
- package/dist/typings/types/enums.d.ts +162 -0
- package/{typings → dist/typings}/types/index.d.cts +17 -14
- package/{typings → dist/typings}/types/index.d.ts +17 -14
- package/dist/typings/utils/file-helpers.d.cts +131 -0
- package/dist/typings/utils/file-helpers.d.ts +131 -0
- package/dist/typings/utils/index.d.cts +5 -0
- package/dist/typings/utils/index.d.ts +5 -0
- package/dist/typings/utils/key-generator.d.cts +41 -0
- package/dist/typings/utils/key-generator.d.ts +41 -0
- package/documentation/IncomeTax_IncomeTaxSoftwareHousesInfo_horaot1.31_2_05.pdf +0 -0
- package/documentation/_4D6963726F736F667420576F7264202D20F8E5E0E9ED20F8E7E5F720F8E5E0E9ED20F9F7E5F32E646F63_.pdf +0 -0
- package/documentation/a000-sum.csv +3 -0
- package/documentation/a000.csv +37 -0
- package/documentation/a100.csv +7 -0
- package/documentation/b100.csv +29 -0
- package/documentation/b110.csv +26 -0
- package/documentation/c100.csv +37 -0
- package/documentation/d110.csv +27 -0
- package/documentation/d120.csv +26 -0
- package/documentation/m100.csv +17 -0
- package/documentation/z900.csv +8 -0
- package/package.json +50 -29
- package/prompt_plan.md +259 -0
- package/spec.md +206 -0
- package/src/api/generate-report.ts +366 -0
- package/src/api/parse-files.ts +33 -0
- package/src/constants.ts +9 -0
- package/src/format/index.ts +6 -0
- package/src/format/newline.ts +8 -0
- package/src/format/padding.ts +39 -0
- package/src/generator/format/decoder.ts +15 -0
- package/src/generator/format/encoder.ts +95 -0
- package/src/generator/format/index.ts +6 -0
- package/src/generator/index.ts +6 -0
- package/src/generator/records/a000-sum.ts +78 -0
- package/src/generator/records/a000.ts +373 -0
- package/src/generator/records/a100.ts +118 -0
- package/src/generator/records/b100.ts +317 -0
- package/src/generator/records/b110.ts +267 -0
- package/src/generator/records/c100.ts +347 -0
- package/src/generator/records/d110.ts +286 -0
- package/src/generator/records/d120.ts +293 -0
- package/src/generator/records/index.ts +14 -0
- package/src/generator/records/m100.ts +185 -0
- package/src/generator/records/z900.ts +104 -0
- package/src/index.ts +18 -0
- package/src/parser/data-parser.ts +14 -0
- package/src/parser/index.ts +6 -0
- package/src/parser/ini-parser.ts +14 -0
- package/src/types/enums.ts +531 -0
- package/src/types/index.ts +110 -0
- package/src/utils/file-helpers.ts +221 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/key-generator.ts +75 -0
- package/src/validation/errors.ts +35 -0
- package/src/validation/index.ts +6 -0
- package/src/validation/validate-input.ts +67 -0
- package/tests/debug-output.test.ts +81 -0
- package/tests/format/crlf-join.test.ts +124 -0
- package/tests/format/encoder.test.ts +80 -0
- package/tests/format/newline.test.ts +19 -0
- package/tests/format/padding.test.ts +74 -0
- package/tests/index.test.ts +29 -0
- package/tests/ini-text.test.ts +122 -0
- package/tests/integration/comprehensive.integration.test.ts +350 -0
- package/tests/integration/roundtrip.integration.test.ts +377 -0
- package/tests/records/a000-sum.test.ts +278 -0
- package/tests/records/a000.test.ts +318 -0
- package/tests/records/a100.test.ts +239 -0
- package/tests/records/b100.test.ts +419 -0
- package/tests/records/b110.test.ts +445 -0
- package/tests/records/c100.test.ts +333 -0
- package/tests/records/d110.test.ts +93 -0
- package/tests/records/d120.test.ts +275 -0
- package/tests/records/m100.test.ts +437 -0
- package/tests/records/z900.test.ts +254 -0
- package/tests/types/enums.test.ts +290 -0
- package/tests/utils/file-helpers.test.ts +276 -0
- package/tests/utils/key-generator.test.ts +121 -0
- package/tests/validation/document-type-validation.test.ts +521 -0
- package/tests/validation/validate-input.test.ts +219 -0
- package/todo.md +203 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
- package/cjs/api/generate-report.js +0 -53
- package/cjs/generator/format/encoder.js +0 -46
- package/cjs/generator/records/a000.js +0 -8
- package/cjs/generator/records/a100.js +0 -8
- package/cjs/generator/records/b100.js +0 -8
- package/cjs/generator/records/b110.js +0 -8
- package/cjs/generator/records/c100.js +0 -8
- package/cjs/generator/records/d110.js +0 -8
- package/cjs/generator/records/d120.js +0 -8
- package/cjs/generator/records/m100.js +0 -8
- package/cjs/generator/records/z900.js +0 -8
- package/esm/api/generate-report.js +0 -50
- package/esm/generator/format/encoder.js +0 -42
- package/esm/generator/records/a000.js +0 -5
- package/esm/generator/records/a100.js +0 -5
- package/esm/generator/records/b100.js +0 -5
- package/esm/generator/records/b110.js +0 -5
- package/esm/generator/records/c100.js +0 -5
- package/esm/generator/records/d110.js +0 -5
- package/esm/generator/records/d120.js +0 -5
- package/esm/generator/records/m100.js +0 -5
- package/esm/generator/records/z900.js +0 -5
- package/typings/generator/format/encoder.d.cts +0 -33
- package/typings/generator/format/encoder.d.ts +0 -33
- package/typings/generator/records/a000.d.cts +0 -4
- package/typings/generator/records/a000.d.ts +0 -4
- package/typings/generator/records/a100.d.cts +0 -4
- package/typings/generator/records/a100.d.ts +0 -4
- package/typings/generator/records/b100.d.cts +0 -4
- package/typings/generator/records/b100.d.ts +0 -4
- package/typings/generator/records/b110.d.cts +0 -4
- package/typings/generator/records/b110.d.ts +0 -4
- package/typings/generator/records/c100.d.cts +0 -4
- package/typings/generator/records/c100.d.ts +0 -4
- package/typings/generator/records/d110.d.cts +0 -4
- package/typings/generator/records/d110.d.ts +0 -4
- package/typings/generator/records/d120.d.cts +0 -4
- package/typings/generator/records/d120.d.ts +0 -4
- package/typings/generator/records/m100.d.cts +0 -4
- package/typings/generator/records/m100.d.ts +0 -4
- package/typings/generator/records/z900.d.cts +0 -4
- package/typings/generator/records/z900.d.ts +0 -4
- /package/{cjs → dist/cjs}/api/parse-files.js +0 -0
- /package/{cjs → dist/cjs}/format/index.js +0 -0
- /package/{cjs → dist/cjs}/format/newline.js +0 -0
- /package/{cjs → dist/cjs}/format/padding.js +0 -0
- /package/{cjs → dist/cjs}/generator/format/decoder.js +0 -0
- /package/{cjs → dist/cjs}/generator/format/index.js +0 -0
- /package/{cjs → dist/cjs}/generator/index.js +0 -0
- /package/{cjs → dist/cjs}/package.json +0 -0
- /package/{cjs → dist/cjs}/parser/data-parser.js +0 -0
- /package/{cjs → dist/cjs}/parser/index.js +0 -0
- /package/{cjs → dist/cjs}/parser/ini-parser.js +0 -0
- /package/{cjs → dist/cjs}/validation/errors.js +0 -0
- /package/{cjs → dist/cjs}/validation/index.js +0 -0
- /package/{cjs → dist/cjs}/validation/validate-input.js +0 -0
- /package/{esm → dist/esm}/api/parse-files.js +0 -0
- /package/{esm → dist/esm}/format/index.js +0 -0
- /package/{esm → dist/esm}/format/newline.js +0 -0
- /package/{esm → dist/esm}/format/padding.js +0 -0
- /package/{esm → dist/esm}/generator/format/decoder.js +0 -0
- /package/{esm → dist/esm}/generator/format/index.js +0 -0
- /package/{esm → dist/esm}/generator/index.js +0 -0
- /package/{esm → dist/esm}/parser/data-parser.js +0 -0
- /package/{esm → dist/esm}/parser/index.js +0 -0
- /package/{esm → dist/esm}/parser/ini-parser.js +0 -0
- /package/{esm → dist/esm}/validation/errors.js +0 -0
- /package/{esm → dist/esm}/validation/index.js +0 -0
- /package/{esm → dist/esm}/validation/validate-input.js +0 -0
- /package/{typings → dist/typings}/api/generate-report.d.cts +0 -0
- /package/{typings → dist/typings}/api/generate-report.d.ts +0 -0
- /package/{typings → dist/typings}/api/parse-files.d.cts +0 -0
- /package/{typings → dist/typings}/api/parse-files.d.ts +0 -0
- /package/{typings → dist/typings}/format/index.d.cts +0 -0
- /package/{typings → dist/typings}/format/index.d.ts +0 -0
- /package/{typings → dist/typings}/format/newline.d.cts +0 -0
- /package/{typings → dist/typings}/format/newline.d.ts +0 -0
- /package/{typings → dist/typings}/format/padding.d.cts +0 -0
- /package/{typings → dist/typings}/format/padding.d.ts +0 -0
- /package/{typings → dist/typings}/generator/format/decoder.d.cts +0 -0
- /package/{typings → dist/typings}/generator/format/decoder.d.ts +0 -0
- /package/{typings → dist/typings}/generator/format/index.d.cts +0 -0
- /package/{typings → dist/typings}/generator/format/index.d.ts +0 -0
- /package/{typings → dist/typings}/generator/index.d.cts +0 -0
- /package/{typings → dist/typings}/generator/index.d.ts +0 -0
- /package/{typings → dist/typings}/parser/data-parser.d.cts +0 -0
- /package/{typings → dist/typings}/parser/data-parser.d.ts +0 -0
- /package/{typings → dist/typings}/parser/index.d.cts +0 -0
- /package/{typings → dist/typings}/parser/index.d.ts +0 -0
- /package/{typings → dist/typings}/parser/ini-parser.d.cts +0 -0
- /package/{typings → dist/typings}/parser/ini-parser.d.ts +0 -0
- /package/{typings → dist/typings}/validation/errors.d.cts +0 -0
- /package/{typings → dist/typings}/validation/errors.d.ts +0 -0
- /package/{typings → dist/typings}/validation/index.d.cts +0 -0
- /package/{typings → dist/typings}/validation/index.d.ts +0 -0
- /package/{typings → dist/typings}/validation/validate-input.d.cts +0 -0
- /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
|
+
}
|