UNPKG

@accounter/server

Version:
140 lines • 6.25 kB
import { z } from 'zod'; import { ALLOWED_PROFIT_LOSS_CODES, NEGATIVE_ALLOWED_PROFIT_LOSS_CODES, PROFIT_LOSS_CODE_NAMES, SECTION_1_1, SECTION_1_4, SECTION_1_9, SECTION_1_10, SECTION_1_11, SECTION_1_12, SECTION_1_14, SECTION_1_15, SECTION_1_16, } from '../types/index.js'; /** * Schema for validating profit and loss records in SHAAM6111 reports * Based on the Israeli Tax Authority specification */ // Create a set for faster lookup of allowed codes const allowedCodesSet = new Set(ALLOWED_PROFIT_LOSS_CODES); const negativeAllowedCodesSet = new Set(NEGATIVE_ALLOWED_PROFIT_LOSS_CODES); function codeToName(code) { return `${PROFIT_LOSS_CODE_NAMES[code] ?? 'Unknown code'} (${code})`; } // Schema for a single profit and loss record const profitLossRecordSchema = z .object({ // Code field - must be one of the allowed codes code: z .number() .int() .refine(code => allowedCodesSet.has(code), { message: 'Invalid profit and loss code', }), // Amount field - must be a valid integer amount: z.number().int(), }) .superRefine((record, ctx) => { // Validate that non-negative codes don't have negative amounts if (!negativeAllowedCodesSet.has(record.code) && record.amount < 0) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Code ${codeToName(record.code)} does not allow negative amounts`, path: ['amount'], }); } }); function cumulativeCodeValidationFormula(code, sectionCodes, subtractSectionCodes) { return (map) => { const sectionAmount = sectionCodes .filter(sectionCode => sectionCode !== code) .map(sectionCode => map.get(sectionCode) || 0) .reduce((a, b) => a + b, 0); const subtractAmount = subtractSectionCodes ? subtractSectionCodes .filter(sectionCode => sectionCode !== code) .map(sectionCode => map.get(sectionCode) || 0) .reduce((a, b) => a + b, 0) : 0; return sectionAmount - subtractAmount; }; } function commonCumulativeCodeValidation(code, sectionCodes, subtractSectionCodes) { return { formula: cumulativeCodeValidationFormula(code, sectionCodes, subtractSectionCodes), description: 'Sum of all subsections', }; } // Define arrays for validation formulas const INCOME_COST_CODES = [ 1306, 1308, 1307, 1310, 1320, 1330, 1340, 1350, 1360, 1371, 1372, 1390, 1400, ]; // Define the validation logic for each summary code const summaryValidations = { 1000: commonCumulativeCodeValidation(1000, SECTION_1_1), 1300: { formula: cumulativeCodeValidationFormula(1300, INCOME_COST_CODES, [1450]), description: `${INCOME_COST_CODES.join('+')}-1450`, }, 2000: { formula: cumulativeCodeValidationFormula(2000, [ 2005, 2006, 2011, 2012, 2015, 2020, 2025, 2030, 2035, 2040, 2045, 2050, 2060, 2066, 2067, 2070, 2075, 2080, 2085, 2090, ], [2068, 2095]), description: '2005+2006+2011+2012+2015+2020+2025+2030+2035+2040+2045+2050+2060+2066+2067-2068+2070+2075+2080+2085+2090-2095', }, 2500: commonCumulativeCodeValidation(2500, SECTION_1_4), 3000: { formula: cumulativeCodeValidationFormula(3000, [ 3011, 3013, 3012, 3015, 3020, 3025, 3030, 3040, 3045, 3050, 3060, 3066, 3067, 3070, 3075, 3080, 3085, 3090, 3100, 3120, 3190, ], [3068]), description: '3011+3013+3012+3015+3020+3025+3030+3040+3045+3050+3060+3066+3067-3068+3070+3075+3080+3085+3090+3100+3120+3190', }, 3500: { formula: cumulativeCodeValidationFormula(3500, [ 3511, 3513, 3512, 3515, 3520, 3530, 3535, 3540, 3545, 3550, 3560, 3566, 3567, 3570, 3575, 3580, 3590, 3595, 3600, 3610, 3620, 3625, 3631, 3632, 3640, 3650, 3660, 3665, 3680, 3685, 3690, ], [3568]), description: '3511+3513+3512+3515+3520+3530+3535+3540+3545+3550+3560+3566+3567-3568+3570+3575+3580+3590+3595+3600+3610+3620+3625+3631+3632+3640+3650+3660+3665+3680+3685+3690', }, 5000: commonCumulativeCodeValidation(5000, SECTION_1_9), 5100: commonCumulativeCodeValidation(5100, SECTION_1_10), 5200: commonCumulativeCodeValidation(5200, SECTION_1_11), 5300: commonCumulativeCodeValidation(5300, SECTION_1_12), 5600: commonCumulativeCodeValidation(5600, SECTION_1_14), 5700: commonCumulativeCodeValidation(5700, SECTION_1_15), 5800: commonCumulativeCodeValidation(5800, SECTION_1_16), 6666: { formula: cumulativeCodeValidationFormula(6666, [1000, 5000, 5100, 5200], [1300, 2000, 2500, 3000, 3500, 5300]), description: '1000-1300-2000-2500-3000-3500+5000+5100+5200-5300', }, }; // Schema for an array of profit and loss records export const profitLossArraySchema = z .array(profitLossRecordSchema) .max(150) .superRefine((records, ctx) => { // Check for unique code values & create a map for faster lookups const codeToAmountMap = new Map(); records.map((record, i) => { const code = record.code; if (codeToAmountMap.has(code)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Duplicate code: ${codeToName(code)}`, path: [i, 'code'], }); } else { codeToAmountMap.set(code, record.amount); } }); // Validate each summary field with its formula for (const [codeStr, validation] of Object.entries(summaryValidations)) { const code = parseInt(codeStr); if (codeToAmountMap.has(code)) { const actualAmount = codeToAmountMap.get(code); const expectedAmount = validation.formula(codeToAmountMap); if (actualAmount !== expectedAmount) { const index = records.findIndex(r => r.code === code); ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Value for code ${codeToName(code)} should equal the sum: ${validation.description}`, path: [index, 'amount'], }); } } } }); //# sourceMappingURL=profit-and-loss.js.map