UNPKG

@accounter/shaam-uniform-format-generator

Version:

Fully typed application that generates, parses, and validates SHAAM uniform format tax reports (INI.TXT and BKMVDATA.TXT).

685 lines (652 loc) 24 kB
/** * Integration test for round-trip generation and parsing * This test ensures that data can be generated to SHAAM format and parsed back correctly */ import { describe, expect, it } from 'vitest'; import { generateUniformFormatReport } from '../../src/api/generate-report.js'; import { parseUniformFormatFiles } from '../../src/api/parse-files.js'; import type { ReportInput } from '../../src/types/index.js'; describe('SHAAM Format Round-trip Integration Test', () => { it('should generate and parse back a complete report with ALL available attributes', () => { // Comprehensive ReportInput fixture with ALL available attributes const input: ReportInput = { business: { businessId: '12345', name: 'Test Company Ltd', taxId: '123456789', address: { street: 'Innovation Boulevard', houseNumber: '42', city: 'Tel Aviv', zip: '69710', }, reportingPeriod: { startDate: '2024-01-01', endDate: '2024-12-31', }, }, documents: [ { id: 'DOC001', type: '320', // Receipt Tax Invoice date: '2024-03-15', amount: 1000.5, description: 'Comprehensive consulting services package', }, { id: 'DOC002', type: '330', // Credit Tax Invoice date: '2024-03-20', amount: 250.75, description: 'Product return with detailed documentation', }, { id: 'DOC003', type: '305', // Tax Invoice date: '2024-04-10', amount: 3500.0, description: 'Software licensing and support', }, { id: 'DOC004', type: '500', // Supplier Invoice date: '2024-05-15', amount: 899.99, description: 'Office equipment procurement', }, { id: 'DOC005', type: '100', // Order date: '2024-06-01', amount: 1250.0, description: 'Purchase order for materials', }, ], journalEntries: [ { id: 'JE001', date: '2024-03-15', amount: 1000.5, accountId: '1100', description: 'Sales revenue with comprehensive tracking', transactionNumber: 10_001, transactionLineNumber: 1, batchNumber: 202_403, transactionType: 'SALES', referenceDocument: 'DOC001', referenceDocumentType: '320', referenceDocument2: 'ORDER_001', referenceDocumentType2: '100', valueDate: '2024-03-15', counterAccountKey: '4100', debitCreditIndicator: '1', currencyCode: 'ILS', transactionAmount: 1000.5, foreignCurrencyAmount: 270.41, // USD equivalent quantityField: 10.0, matchingField1: 'MATCH_001', matchingField2: 'BATCH_A', branchId: 'BR_MAIN', entryDate: '2024-03-15', operatorUsername: 'acct_mgr', reserved: 'RESERVED_SALES', }, { id: 'JE002', date: '2024-03-20', amount: -250.75, accountId: '1200', description: 'Returns and allowances with full audit trail', transactionNumber: 10_002, transactionLineNumber: 1, batchNumber: 202_403, transactionType: 'RETURN', referenceDocument: 'DOC002', referenceDocumentType: '330', valueDate: '2024-03-20', counterAccountKey: '4200', debitCreditIndicator: '2', currencyCode: 'USD', transactionAmount: -250.75, foreignCurrencyAmount: -927.78, // ILS equivalent quantityField: 2.5, matchingField1: 'RET_001', matchingField2: 'BATCH_B', branchId: 'BR_RET', entryDate: '2024-03-20', operatorUsername: 'ret_clerk', reserved: 'RESERVED_RETURN', }, { id: 'JE003', date: '2024-04-10', amount: 3500.0, accountId: '1300', description: 'Software revenue recognition', transactionNumber: 10_003, transactionLineNumber: 1, batchNumber: 202_404, transactionType: 'SOFTWARE_REV', referenceDocument: 'DOC003', referenceDocumentType: '305', valueDate: '2024-04-10', counterAccountKey: '4300', debitCreditIndicator: '1', currencyCode: 'EUR', transactionAmount: 3500.0, foreignCurrencyAmount: 3182.73, // EUR equivalent quantityField: 1.0, matchingField1: 'SOFT_001', branchId: 'BR_SOFT', entryDate: '2024-04-10', operatorUsername: 'sw_acct', }, ], accounts: [ { id: '1100', name: 'Primary Cash Account', sortCode: { key: 'Asset', name: 'Current Assets', }, address: { street: 'Bank Street', houseNumber: '123', city: 'Jerusalem', zip: '91999', country: 'Israel', }, countryCode: 'IL', parentAccountKey: '1000', vatId: '123456789', accountOpeningBalance: 5000.0, totalDebits: 25_000.0, totalCredits: 18_500.0, accountingClassificationCode: '1100', branchId: 'BR_MAIN', openingBalanceForeignCurrency: 1351.35, foreignCurrencyCode: 'USD', }, { id: '1200', name: 'Accounts Receivable - Trade', sortCode: { key: 'Asset', name: 'Current Assets', }, address: { street: 'Customer Avenue', houseNumber: '456', city: 'Haifa', zip: '31999', country: 'Israel', }, countryCode: 'IL', parentAccountKey: '1000', vatId: '987654321', accountOpeningBalance: 3000.0, totalDebits: 15_000.0, totalCredits: 8500.0, accountingClassificationCode: '1200', branchId: 'BR_SALE', openingBalanceForeignCurrency: 810.81, foreignCurrencyCode: 'USD', }, { id: '4000', name: 'Sales Revenue - Services', sortCode: { key: 'Revenue', name: 'Operating Revenue', }, address: { street: 'Revenue Street', houseNumber: '789', city: 'Eilat', zip: '88999', country: 'Israel', }, countryCode: 'IL', parentAccountKey: '4000', accountOpeningBalance: 8000.0, totalDebits: 1000.0, totalCredits: 35_000.0, accountingClassificationCode: '4000', branchId: 'BR_SVC', openingBalanceForeignCurrency: 2162.16, foreignCurrencyCode: 'USD', }, { id: '2100', name: 'Accounts Payable - Trade', sortCode: { key: 'Liability', name: 'Current Liabilities', }, address: { street: 'Supplier Road', houseNumber: '321', city: 'Beer Sheva', zip: '84999', country: 'Israel', }, countryCode: 'IL', parentAccountKey: '2000', vatId: '555 666 777', accountOpeningBalance: -2500.0, totalDebits: 8000.0, totalCredits: 12_500.0, accountingClassificationCode: '2100', branchId: 'BR_PUR', openingBalanceForeignCurrency: -675.68, foreignCurrencyCode: 'USD', }, { id: '3000', name: 'Owner Equity', sortCode: { key: 'Equity', name: 'Owner Equity', }, countryCode: 'IL', accountOpeningBalance: 50_000.0, totalDebits: 5000.0, totalCredits: 75_000.0, accountingClassificationCode: '3000', branchId: 'BR_MAIN', openingBalanceForeignCurrency: 13_513.51, foreignCurrencyCode: 'USD', }, ], inventory: [ { id: 'ITEM001', name: 'Professional Service Package Alpha', quantity: 100, }, { id: 'ITEM002', name: 'Software License Premium Bundle', quantity: 50, }, { id: 'ITEM003', name: 'Hardware Component Enterprise Series', quantity: 25, }, { id: 'ITEM004', name: 'Training and Documentation Package', quantity: 200, }, { id: 'ITEM005', name: 'Support and Maintenance Contract', quantity: 10, }, ], }; // Generate the report const result = generateUniformFormatReport(input); expect(result).toBeDefined(); expect(result.dataText).toBeDefined(); expect(result.iniText).toBeDefined(); expect(result.summary.totalRecords).toBeGreaterThan(0); // Parse the generated files back using the new parsing function const parsedData = parseUniformFormatFiles(result.iniText, result.dataText); // Verify business metadata including address expect(parsedData.data.business).toBeDefined(); expect(parsedData.data.business.name).toBe(input.business.name); expect(parsedData.data.business.taxId).toBe(input.business.taxId); expect(parsedData.data.business.businessId).toBeDefined(); expect(parsedData.data.business.reportingPeriod.startDate).toBe( input.business.reportingPeriod.startDate, ); expect(parsedData.data.business.reportingPeriod.endDate).toBe( input.business.reportingPeriod.endDate, ); // Validate business address expect(parsedData.data.business.address).toBeDefined(); expect(parsedData.data.business.address?.street).toBe(input.business.address?.street); expect(parsedData.data.business.address?.houseNumber).toBe(input.business.address?.houseNumber); expect(parsedData.data.business.address?.city).toBe(input.business.address?.city); expect(parsedData.data.business.address?.zip).toBe(input.business.address?.zip); // Verify documents with comprehensive validation expect(parsedData.data.documents).toHaveLength(input.documents.length); for (let i = 0; i < input.documents.length; i++) { const original = input.documents[i]; const parsed = parsedData.data.documents[i]; expect(parsed.id).toBe(original.id); expect(parsed.type).toBe(original.type); expect(parsed.date).toBe(original.date); expect(parsed.amount).toBe(original.amount); expect(parsed.description).toBeDefined(); // Description may be truncated or use generic value in C100 } // Verify journal entries with ALL extended B100 fields expect(parsedData.data.journalEntries).toHaveLength(input.journalEntries.length); for (let i = 0; i < input.journalEntries.length; i++) { const original = input.journalEntries[i]; const parsed = parsedData.data.journalEntries[i]; expect(parsed.date).toBe(original.date); expect(parsed.amount).toBe(original.amount); expect(parsed.accountId).toBe(original.accountId); expect(parsed.description).toBe(original.description); // Validate ALL extended B100 fields if (original.transactionNumber !== undefined) { expect(parsed.transactionNumber).toBeDefined(); } if (original.transactionLineNumber !== undefined) { expect(parsed.transactionLineNumber).toBeDefined(); } if (original.batchNumber !== undefined) { expect(parsed.batchNumber).toBeDefined(); } if (original.transactionType !== undefined) { expect(parsed.transactionType).toBe(original.transactionType); } if (original.referenceDocument !== undefined) { expect(parsed.referenceDocument).toBe(original.referenceDocument); } if (original.referenceDocumentType !== undefined) { expect(parsed.referenceDocumentType).toBe(original.referenceDocumentType); } if (original.referenceDocument2 !== undefined) { expect(parsed.referenceDocument2).toBe(original.referenceDocument2); } if (original.referenceDocumentType2 !== undefined) { expect(parsed.referenceDocumentType2).toBe(original.referenceDocumentType2); } if (original.valueDate !== undefined) { expect(parsed.valueDate).toBe(original.valueDate); } if (original.counterAccountKey !== undefined) { expect(parsed.counterAccountKey).toBe(original.counterAccountKey); } if (original.debitCreditIndicator !== undefined) { expect(parsed.debitCreditIndicator).toBe(original.debitCreditIndicator); } if (original.currencyCode !== undefined) { expect(parsed.currencyCode).toBe(original.currencyCode); } if (original.transactionAmount !== undefined) { expect(parsed.transactionAmount).toBe(original.transactionAmount); } if (original.foreignCurrencyAmount !== undefined) { expect(parsed.foreignCurrencyAmount).toBeDefined(); } if (original.quantityField !== undefined) { expect(parsed.quantityField).toBeDefined(); } if (original.matchingField1 !== undefined) { expect(parsed.matchingField1).toBe(original.matchingField1); } if (original.matchingField2 !== undefined) { expect(parsed.matchingField2).toBe(original.matchingField2); } if (original.branchId !== undefined) { expect(parsed.branchId).toBe(original.branchId); } if (original.entryDate !== undefined) { expect(parsed.entryDate).toBe(original.entryDate); } if (original.operatorUsername !== undefined) { expect(parsed.operatorUsername).toBe(original.operatorUsername); } if (original.reserved !== undefined) { expect(parsed.reserved).toBe(original.reserved); } } // Verify accounts with ALL extended B110 fields expect(parsedData.data.accounts).toHaveLength(input.accounts.length); for (let i = 0; i < input.accounts.length; i++) { const original = input.accounts[i]; const parsed = parsedData.data.accounts[i]; expect(parsed.id).toBe(original.id); expect(parsed.name).toBe(original.name); expect(parsed.sortCode.key).toBe(original.sortCode.key); expect(parsed.sortCode.name).toBe(original.sortCode.name); expect(parsed.accountOpeningBalance).toBe(original.accountOpeningBalance); // Validate ALL extended B110 fields if (original.address !== undefined) { expect(parsed.address).toBeDefined(); expect(parsed.address?.street).toBe(original.address?.street); expect(parsed.address?.houseNumber).toBe(original.address?.houseNumber); expect(parsed.address?.city).toBe(original.address?.city); expect(parsed.address?.zip).toBe(original.address?.zip); expect(parsed.address?.country).toBe(original.address?.country); } if (original.countryCode !== undefined) { expect(parsed.countryCode).toBe(original.countryCode); } if (original.parentAccountKey !== undefined) { expect(parsed.parentAccountKey).toBe(original.parentAccountKey); } if (original.vatId !== undefined) { expect(parsed.vatId).toBeDefined(); } if (original.totalDebits !== undefined) { expect(parsed.totalDebits).toBeDefined(); } if (original.totalCredits !== undefined) { expect(parsed.totalCredits).toBeDefined(); } if (original.accountingClassificationCode !== undefined) { expect(parsed.accountingClassificationCode).toBe(original.accountingClassificationCode); } if (original.branchId !== undefined) { expect(parsed.branchId).toBe(original.branchId); } if (original.openingBalanceForeignCurrency !== undefined) { expect(parsed.openingBalanceForeignCurrency).toBeDefined(); } if (original.foreignCurrencyCode !== undefined) { expect(parsed.foreignCurrencyCode).toBe(original.foreignCurrencyCode); } } // Verify inventory with special characters and fractional quantities const intlItem = parsedData.data.inventory[0]; expect(intlItem.name).toContain('Professional Service Package Alpha'); expect(intlItem.quantity).toBeGreaterThanOrEqual(0); // Fractional may be rounded in M100 }); it('should handle foreign currencies and special characters correctly in round-trip', () => { const foreignCurrencyInput: ReportInput = { business: { businessId: 'INTL_001', name: 'International Business Corp™ & Associates', taxId: '999888777', address: { street: 'Γλώσσα Street with Spëcial Chars', houseNumber: '1א', city: 'Jerusalem-Tel Aviv', zip: '12345-6789', }, reportingPeriod: { startDate: '2024-01-01', endDate: '2024-12-31', }, }, documents: [ { id: 'INTL_DOC_001', type: '305', date: '2024-06-15', amount: 1234.567, // 3 decimal places description: 'International transaction with special chars: €$£¥', }, ], journalEntries: [ { id: 'FOREX_001', date: '2024-06-15', amount: 1234.567, accountId: 'EUR_ACCOUNT', description: 'Foreign exchange transaction: USD→EUR→ILS', currencyCode: 'EUR', transactionAmount: 1234.567, foreignCurrencyAmount: 4567.89, debitCreditIndicator: '1', branchId: 'INTL_BR', operatorUsername: 'forex_usr', }, ], accounts: [ { id: 'EUR_ACCOUNT', name: 'Euro Account - €urope Operations', sortCode: { key: 'Asset', name: 'Foreign Currency Assets', }, address: { street: 'Européenne Straße', houseNumber: '123β', city: 'International City', zip: 'INTL-001', country: 'European Union', }, countryCode: 'DE', accountOpeningBalance: -1234.56, // Negative balance foreignCurrencyCode: 'EUR', openingBalanceForeignCurrency: -1123.45, vatId: ' 123 456 789 ', }, ], inventory: [ { id: 'INTL_ITEM_001', name: 'International Product™ with Spëcial Characters', quantity: 12.5, // Fractional quantity }, ], }; const result = generateUniformFormatReport(foreignCurrencyInput); const parsedData = parseUniformFormatFiles(result.iniText, result.dataText); // Verify special characters in business name and address expect(parsedData.data.business.name).toContain('Corp™'); expect(parsedData.data.business.address?.street).toContain('Spëcial'); expect(parsedData.data.business.address?.houseNumber).toBe('1א'); // Verify foreign currency handling const forexEntry = parsedData.data.journalEntries[0]; expect(forexEntry.currencyCode).toBe('EUR'); expect(forexEntry.description).toContain('USD→EUR→ILS'); expect(forexEntry.operatorUsername).toBe('forex_usr'); // Verify account with foreign currency const eurAccount = parsedData.data.accounts[0]; expect(eurAccount).toBeDefined(); if (eurAccount) { expect(eurAccount.name).toContain('€urope'); expect(eurAccount.countryCode).toBe('DE'); expect(eurAccount.foreignCurrencyCode).toBe('EUR'); expect(eurAccount.accountOpeningBalance).toBe(-1234.56); // Negative balance preserved } // Verify inventory with special characters and fractional quantities const intlItem = parsedData.data.inventory[0]; expect(intlItem.name).toContain('Spëcial'); expect(intlItem.quantity).toBeGreaterThanOrEqual(0); // Fractional may be rounded in M100 }); it('should handle empty data sections correctly', () => { const minimalInput: ReportInput = { business: { businessId: '54321', name: 'Minimal Company', taxId: '987654321', reportingPeriod: { startDate: '2024-01-01', endDate: '2024-12-31', }, }, documents: [], journalEntries: [], accounts: [], inventory: [], }; const result = generateUniformFormatReport(minimalInput); expect(result).toBeDefined(); expect(result.dataText).toBeDefined(); expect(result.summary.totalRecords).toBe(5); // A000 + 2 A000Sum (A100, Z900) + A100 + Z900 // Parse the generated files back const parsedData = parseUniformFormatFiles(result.iniText, result.dataText); // Verify business metadata expect(parsedData.data.business).toBeDefined(); expect(parsedData.data.business.name).toBe(minimalInput.business.name); expect(parsedData.data.business.taxId).toBe(minimalInput.business.taxId); // Verify empty arrays expect(parsedData.data.documents).toEqual([]); expect(parsedData.data.journalEntries).toEqual([]); expect(parsedData.data.accounts).toEqual([]); expect(parsedData.data.inventory).toEqual([]); }); it('should maintain data integrity for monetary values', () => { const input: ReportInput = { business: { businessId: '99999', name: 'Financial Test Co', taxId: '111111111', reportingPeriod: { startDate: '2024-01-01', endDate: '2024-12-31', }, }, documents: [ { id: 'HIGH-VAL', type: '320', date: '2024-06-15', amount: 999_999.99, description: 'High value transaction', }, ], journalEntries: [ { id: 'PRECISION-TEST', date: '2024-06-15', amount: 123.45, accountId: '2000', description: 'Precision test', }, ], accounts: [ { id: '2000', name: 'Test Account', sortCode: { key: 'Liability', name: 'Liabilities', }, accountOpeningBalance: 123_456.78, }, ], inventory: [ { id: 'EXPENSIVE-ITEM', name: 'Expensive Product', quantity: 1, }, ], }; const result = generateUniformFormatReport(input); // Parse the generated files back const parsedData = parseUniformFormatFiles(result.iniText, result.dataText); // Verify business metadata expect(parsedData.data.business).toBeDefined(); expect(parsedData.data.business.name).toBe(input.business.name); expect(parsedData.data.business.taxId).toBe(input.business.taxId); // Verify high-value document was parsed correctly expect(parsedData.data.documents).toHaveLength(1); expect(parsedData.data.documents[0].amount).toBe(999_999.99); expect(parsedData.data.documents[0].id).toBe('HIGH-VAL'); // Verify journal entry with precise monetary value expect(parsedData.data.journalEntries).toHaveLength(1); expect(parsedData.data.journalEntries[0].amount).toBe(123.45); expect(parsedData.data.journalEntries[0].accountId).toBe('2000'); // Verify account balance expect(parsedData.data.accounts).toHaveLength(1); expect(parsedData.data.accounts[0].accountOpeningBalance).toBe(123_456.78); expect(parsedData.data.accounts[0].id).toBe('2000'); // Verify inventory item expect(parsedData.data.inventory).toHaveLength(1); expect(parsedData.data.inventory[0].id).toBe('EXPENSIVE-ITEM'); expect(parsedData.data.inventory[0].name).toBe('Expensive Product'); }); });