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