@accounter/server
Version:
Accounter GraphQL server
161 lines • 7.25 kB
JavaScript
import { z } from 'zod';
import { ALLOWED_BALANCE_SHEET_CODES, BALANCE_SHEET_CODE_NAMES, NEGATIVE_ALLOWED_BALANCE_SHEET_CODES, SECTION_1_21, SECTION_1_27, SECTION_1_28, SECTION_1_30, SECTION_1_32, SECTION_1_33, SECTION_1_36, SECTION_1_37, SECTION_1_38, SECTION_1_39, SECTION_1_40, SECTION_1_41, SECTION_1_43, SECTION_1_44, SECTION_1_45, SECTION_1_46, SECTION_1_48, SECTION_1_50, SECTION_1_51, } from '../types/index.js';
// Create a set for faster lookup of allowed codes
const allowedCodesSet = new Set(ALLOWED_BALANCE_SHEET_CODES);
const negativeAllowedCodesSet = new Set(NEGATIVE_ALLOWED_BALANCE_SHEET_CODES);
function codeToName(code) {
return `${BALANCE_SHEET_CODE_NAMES[code] ?? 'Unknown code'} (${code})`;
}
// Zod schema for a single balance sheet record
const balanceSheetRecordSchema = z
.object({
code: z
.number()
.int()
.superRefine((value, ctx) => {
if (!allowedCodesSet.has(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid balance sheet code: ${value}`,
path: ['code'],
});
}
}),
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 the validation logic for each summary code
const summaryValidations = {
7000: commonCumulativeCodeValidation(7000, [7100, 7200, 7300, 7400, 7600, 7700, 7800]),
7100: commonCumulativeCodeValidation(7100, SECTION_1_27),
7200: commonCumulativeCodeValidation(7200, SECTION_1_28),
7300: {
formula: cumulativeCodeValidationFormula(7300, [7310, 7320, 7330, 7350, 7360, 7390], [7380]),
description: '7310+7320+7330+7350+7360-7380+7390',
},
7400: commonCumulativeCodeValidation(7400, SECTION_1_30),
7600: commonCumulativeCodeValidation(7600, SECTION_1_21),
7700: commonCumulativeCodeValidation(7700, SECTION_1_32),
7800: commonCumulativeCodeValidation(7800, SECTION_1_33),
8000: {
formula: cumulativeCodeValidationFormula(8000, [8010, 8020, 8025, 8030, 8040, 8050, 8060, 8080, 8090, 8095, 8100, 8105, 8170], [8110, 8120, 8130, 8140, 8150, 8160, 8180, 8190]),
description: '8010+8020+8025+8030+8040+8050+8060+8080+8090+8095+8100+8105-8110-8120-8130-8140-8150-8160+8170-8180-8190',
},
8200: commonCumulativeCodeValidation(8200, SECTION_1_37),
8300: commonCumulativeCodeValidation(8300, SECTION_1_38),
8400: commonCumulativeCodeValidation(8400, SECTION_1_39),
8500: commonCumulativeCodeValidation(8500, SECTION_1_40),
8600: commonCumulativeCodeValidation(8600, SECTION_1_41),
8700: commonCumulativeCodeValidation(8700, SECTION_1_36),
8888: {
formula: cumulativeCodeValidationFormula(8888, [7000, 8000, 8700, 8200, 8300, 8400, 8500, 8600]),
description: '7000+8000+8700+8200+8300+8400+8500+8600',
},
9000: {
formula: cumulativeCodeValidationFormula(9000, [9100, 9200, 9400, 9500]),
description: '9100+9200+9400+9500',
},
9100: commonCumulativeCodeValidation(9100, SECTION_1_43),
9200: commonCumulativeCodeValidation(9200, SECTION_1_44),
9400: commonCumulativeCodeValidation(9400, SECTION_1_45),
9500: commonCumulativeCodeValidation(9500, SECTION_1_46),
9600: commonCumulativeCodeValidation(9600, SECTION_1_48),
9700: {
formula: cumulativeCodeValidationFormula(9700, [9710, 9790], [9720]),
description: '9710-9720+9790',
},
9800: commonCumulativeCodeValidation(9800, SECTION_1_50),
9900: commonCumulativeCodeValidation(9900, SECTION_1_51),
9999: {
formula: cumulativeCodeValidationFormula(9999, [9000, 9600, 9700, 9800, 9900]),
description: '9000+9600+9700+9800+9900',
},
};
// Zod schema for the array of balance sheet records
export const balanceSheetArraySchema = z
.array(balanceSheetRecordSchema)
.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'],
});
}
}
}
// Check total assets equals total liabilities + equity
const totalAssets = codeToAmountMap.get(8888);
const totalLiabilitiesEquity = codeToAmountMap.get(9999);
if (totalAssets !== totalLiabilitiesEquity) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Total assets (8888) must equal total liabilities + equity (9999). Got: ${totalAssets} vs ${totalLiabilitiesEquity}`,
});
}
// Check if all required summary codes are present
const requiredSummaryCodes = [7000, 8888, 9000, 9999];
for (const code of requiredSummaryCodes) {
if (!codeToAmountMap.has(code)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Missing required summary code: ${code}`,
});
}
}
});
//# sourceMappingURL=balance-sheet.js.map