@accounter/pcn874-generator
Version:
Fully typed application that generates PCN874 text file
331 lines (330 loc) • 12.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.transactionCoreSchema = exports.transactionTransformerSchema = exports.transactionStrictSchema = exports.headerCoreSchema = exports.headerTransformerSchema = exports.headerStrictSchema = void 0;
const zod_1 = require("zod");
const types_js_1 = require("./types.js");
const index_js_1 = require("./utils/index.js");
const DIGITS_REGEX = /^\d+$/;
const MONTH_REGEX = /^(20\d{2})(?:0[1-9]|1[0-2])$/;
const DATE_REGEX = /^(20\d{2})(?:0[1-9]|1[0-2])(?:0[1-9]|[1-2][0-9]|3[0-1])$/;
// most strict requirements
exports.headerStrictSchema = zod_1.z
.object({
licensedDealerId: zod_1.z
.string()
.length(9, "Dealer's license ID must include 9 digits")
.regex(DIGITS_REGEX, 'Only digits are allowed'),
reportMonth: zod_1.z.string().length(6).regex(MONTH_REGEX, 'Dates must be in YYYYMM format'),
generationDate: zod_1.z
.string()
.length(8, 'Dates must be in YYYYMMDD format')
.regex(DATE_REGEX, 'Dates must be in YYYYMMDD format'),
taxableSalesAmount: zod_1.z.number().transform(value => (0, index_js_1.numToSignedString)(value, 11)),
taxableSalesVat: zod_1.z.number().transform(value => (0, index_js_1.numToSignedString)(value, 9)),
salesRecordCount: zod_1.z
.number()
.int()
.min(0, 'Sales record count must be >= 0')
.transform(value => (0, index_js_1.addLeadingZeros)(value, 9)),
zeroValOrExemptSalesCount: zod_1.z
.number()
.int()
.transform(value => (0, index_js_1.numToSignedString)(value, 11)),
otherInputsVat: zod_1.z.number().transform(value => (0, index_js_1.numToSignedString)(value, 9)),
equipmentInputsVat: zod_1.z.number().transform(value => (0, index_js_1.numToSignedString)(value, 9)),
inputsCount: zod_1.z
.number()
.int()
.min(0, 'Inputs count must be >= 0')
.transform(value => (0, index_js_1.addLeadingZeros)(value, 9)),
totalVat: zod_1.z.number().transform(value => (0, index_js_1.numToSignedString)(value, 11)),
})
.strict();
// transformations that can be preformed by generator in non-strict mode
exports.headerTransformerSchema = zod_1.z
.object({
licensedDealerId: zod_1.z.string().transform(id => (0, index_js_1.digitsAdjuster)(id, 9)),
reportMonth: zod_1.z.string(),
generationDate: zod_1.z
.string()
.optional()
.transform(date => {
{
if (date)
return date;
// if not provided - use current date
const now = new Date();
const year = now.getUTCFullYear();
const month = `0${now.getUTCMonth() + 1}`.slice(-2);
const day = `0${now.getUTCDate()}`.slice(-2);
return `${year}${month}${day}`;
}
}),
taxableSalesAmount: zod_1.z.number(),
taxableSalesVat: zod_1.z.number(),
salesRecordCount: zod_1.z.number().int(),
zeroValOrExemptSalesCount: zod_1.z.number().int(),
otherInputsVat: zod_1.z.number(),
equipmentInputsVat: zod_1.z.number(),
inputsCount: zod_1.z.number().int(),
totalVat: zod_1.z.number(),
})
.strict();
// most basic requirements + obvious transformations
exports.headerCoreSchema = zod_1.z.object({
/**
* User Licensed Dealer identification Number
* 9 digits
* מספר עוסק
*/
licensedDealerId: zod_1.z.string().transform(id => (0, index_js_1.cleanNonDigitChars)(id)),
/**
* Month for which detailed report is being submitted
* YYYYMM
* תקופת הדיווח
*/
reportMonth: zod_1.z.string().length(6).regex(MONTH_REGEX, 'Dates must be in YYYYMM format'),
/**
* File Generation Date
* YYYYMM
* if undefined - current date will be inputed
* תאריך הגשה
*/
generationDate: zod_1.z
.string()
.length(8)
.regex(DATE_REGEX, 'Dates must be in YYYYMMDD format')
.optional(),
/**
* Total amount of taxable sales (excluding VAT)
* in the reported file
* עסקאות חייבות
*/
taxableSalesAmount: zod_1.z.number(),
/**
* Total VAT on taxable sales
* in the reported file
* מע"מ עסקאות חייבות
*/
taxableSalesVat: zod_1.z.number(),
/**
* Total number of records for "sales".
* Number of sales records - both taxable and zero-rated/ exempt
* מס' עסקאות
*/
salesRecordCount: zod_1.z.number().int(),
/**
* Total of zero value/exempt sales for period
* עסקאות פטורות / אפס
*/
zeroValOrExemptSalesCount: zod_1.z.number().int(),
/**
* Total VAT on "other" inputs required during period
* תשומות אחרות
*/
otherInputsVat: zod_1.z.number(),
/**
* Total VAT on "equipment" inputs required during period
* תשומות ציוד
*/
equipmentInputsVat: zod_1.z.number(),
/**
* Total number of records for inputs (other and equipment)
* מס' תשומות
*/
inputsCount: zod_1.z.number().int(),
/**
* Total VAT to pay / receive for period
* positive value => pay
* negative value => receive
* סכום מדווח
*/
totalVat: zod_1.z.number(),
});
// most strict requirements
exports.transactionStrictSchema = zod_1.z
.object({
entryType: zod_1.z.nativeEnum(types_js_1.EntryType).transform(entryType => entryType.valueOf()[0]),
vatId: zod_1.z.string().length(9).regex(DIGITS_REGEX, 'Only digits are allowed'),
allocationNumber: zod_1.z.string().length(9).regex(DIGITS_REGEX, 'Only digits are allowed'),
invoiceDate: zod_1.z.string().length(8).regex(DATE_REGEX, 'Dates must be in YYYYMMDD format'),
refGroup: zod_1.z.string().length(4),
refNumber: zod_1.z.string().length(9).regex(DIGITS_REGEX, 'Only digits are allowed'),
totalVat: zod_1.z
.number()
.int()
.transform(value => (0, index_js_1.addLeadingZeros)(value, 9)),
invoiceSum: zod_1.z
.number()
.int()
.transform(value => (0, index_js_1.numToSignedString)(value, 10)),
})
.strict()
.refine(transaction => {
switch (transaction.entryType) {
case types_js_1.EntryType.SALE_ZERO_OR_EXEMPT: {
if (transaction.totalVat !== '000000000') {
throw new Error(`Transactions of entry type "SALE_ZERO_OR_EXEMPT" VAT should be 0, received "${transaction.totalVat}". Replacing with 0`);
}
break;
}
case types_js_1.EntryType.SALE_UNIDENTIFIED_CUSTOMER: {
if (transaction.vatId !== '000000000') {
throw new Error(`Transactions of entry type "SALE_UNIDENTIFIED_CUSTOMER" should not include vatId, received "${transaction.vatId}".`);
}
break;
}
case types_js_1.EntryType.SALE_UNIDENTIFIED_ZERO_OR_EXEMPT: {
if (transaction.vatId !== '000000000') {
throw new Error(`Transactions of entry type "SALE_UNIDENTIFIED_ZERO_OR_EXEMPT" should not include vatId, received "${transaction.vatId}".`);
}
if (transaction.totalVat !== '000000000') {
throw new Error(`Transactions of entry type "SALE_UNIDENTIFIED_ZERO_OR_EXEMPT" VAT should be 0, received "${transaction.totalVat}". Replacing with 0`);
}
break;
}
case types_js_1.EntryType.SALE_EXPORT: {
if (transaction.totalVat !== '000000000') {
throw new Error(`Transactions of entry type "SALE_EXPORT" should not include totalVat, received "${transaction.totalVat}"`);
}
break;
}
case types_js_1.EntryType.INPUT_PETTY_CASH: {
if (transaction.vatId !== '000000000') {
throw new Error(`Transactions of entry type "INPUT_PETTY_CASH" should not include vatId, received "${transaction.vatId}".`);
}
if (transaction.refNumber === '000000000') {
throw new Error(`On transactions of entry type "INPUT_PETTY_CASH", refNumber should reflect the number of invoices in the entry (hence > 0), received "${transaction.refNumber}"`);
}
break;
}
case types_js_1.EntryType.INPUT_IMPORT: {
if (transaction.refNumber !== '000000000') {
throw new Error(`Transactions of entry type "INPUT_IMPORT" should not include refNumber, received "${transaction.refNumber}".`);
}
break;
}
}
return transaction;
});
// transformations that can be preformed by generator in non-strict mode
exports.transactionTransformerSchema = zod_1.z
.object({
allocationNumber: zod_1.z
.string()
.optional()
.transform(allocationNumber => (0, index_js_1.digitsAdjuster)(allocationNumber ?? '', 9)),
entryType: zod_1.z.nativeEnum(types_js_1.EntryType),
invoiceDate: zod_1.z.string(),
invoiceSum: zod_1.z.number().int(),
refGroup: zod_1.z
.string()
.optional()
.transform(refGroup => (0, index_js_1.digitsAdjuster)(refGroup ?? '', 4)),
refNumber: zod_1.z
.string()
.optional()
.transform(refNumber => (0, index_js_1.digitsAdjuster)(refNumber ?? '', 9)),
totalVat: zod_1.z.number().int().optional(),
vatId: zod_1.z
.string()
.optional()
.transform(id => (0, index_js_1.digitsAdjuster)(id ?? '', 9)),
})
.strict()
.refine(transaction => {
switch (transaction.entryType) {
case types_js_1.EntryType.SALE_REGULAR: {
if (transaction.invoiceSum <= 5000) {
transaction.vatId ??= '000000000';
}
break;
}
case types_js_1.EntryType.SALE_ZERO_OR_EXEMPT: {
if (transaction.totalVat && transaction.totalVat !== 0) {
transaction.totalVat = 0;
}
if (transaction.invoiceSum <= 5000) {
transaction.vatId ??= '000000000';
}
break;
}
case types_js_1.EntryType.SALE_UNIDENTIFIED_ZERO_OR_EXEMPT: {
if (transaction.totalVat && transaction.totalVat !== 0) {
transaction.totalVat = 0;
}
break;
}
case types_js_1.EntryType.SALE_EXPORT: {
transaction.vatId ??= '999999999';
break;
}
case types_js_1.EntryType.INPUT_SINGLE_DOC_BY_LAW: {
transaction.refNumber ??= '000000000';
break;
}
}
return transaction;
});
// most basic requirements + obvious transformations
exports.transactionCoreSchema = zod_1.z.object({
/**
* Entry Type (document type)
* סוג רשומה
*/
entryType: zod_1.z.nativeEnum(types_js_1.EntryType),
/**
* VAT identification number – of the other side of the transaction.
* For transactions entries – the customer
* For inputs – the supplier
* ספק / רשימון
*/
vatId: zod_1.z
.string()
.optional()
.transform(id => (id ? (0, index_js_1.cleanNonDigitChars)(id) : undefined)),
/**
* Short allocation number – last 9 digits of full number.
* מספר הקצאה קצר
*/
allocationNumber: zod_1.z
.string()
.optional()
.transform(allocationNumber => allocationNumber ? (0, index_js_1.cleanNonDigitChars)(allocationNumber) : undefined),
/**
* Invoice Date/Reference.
* YYYYMMDD
* תאריך החשבונית
*/
invoiceDate: zod_1.z.string().length(8).regex(DIGITS_REGEX, 'Only digits are allowed'),
/**
* Reference group.
* Series etc. zeros are possible at this stage
* קבוצת אסמכתא
*/
refGroup: zod_1.z
.string()
.optional()
.transform(refGroup => (refGroup ? (0, index_js_1.cleanNonDigitChars)(refGroup) : undefined)),
/**
* Reference number.
* First 9 positions from the right
* מספר אסמכתא
*/
refNumber: zod_1.z
.string()
.optional()
.transform(refNumber => (refNumber ? (0, index_js_1.cleanNonDigitChars)(refNumber) : undefined)),
/**
* Total VAT in invoice / total VAT that is allowed (1/4…. 2/3…).
* Rounded to the nearest shekel – always a positive value
* סכום המע"מ
*/
totalVat: zod_1.z.number().int().min(0).optional(),
/**
* Invoice total (excluding VAT)
* Always the 100%, always a positive value, rounded to the nearest shekel
* סכום
*/
invoiceSum: zod_1.z.number().int(),
});