@accounter/server
Version:
Accounter GraphQL server
328 lines • 11.7 kB
JavaScript
import { z } from 'zod';
import { EntryType } from './types.js';
import { addLeadingZeros, cleanNonDigitChars, digitsAdjuster, numToSignedString, } from './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
export const headerStrictSchema = z
.object({
licensedDealerId: z
.string()
.length(9, "Dealer's license ID must include 9 digits")
.regex(DIGITS_REGEX, 'Only digits are allowed'),
reportMonth: z.string().length(6).regex(MONTH_REGEX, 'Dates must be in YYYYMM format'),
generationDate: z
.string()
.length(8, 'Dates must be in YYYYMMDD format')
.regex(DATE_REGEX, 'Dates must be in YYYYMMDD format'),
taxableSalesAmount: z.number().transform(value => numToSignedString(value, 11)),
taxableSalesVat: z.number().transform(value => numToSignedString(value, 9)),
salesRecordCount: z
.number()
.int()
.min(0, 'Sales record count must be >= 0')
.transform(value => addLeadingZeros(value, 9)),
zeroValOrExemptSalesCount: z
.number()
.int()
.transform(value => numToSignedString(value, 11)),
otherInputsVat: z.number().transform(value => numToSignedString(value, 9)),
equipmentInputsVat: z.number().transform(value => numToSignedString(value, 9)),
inputsCount: z
.number()
.int()
.min(0, 'Inputs count must be >= 0')
.transform(value => addLeadingZeros(value, 9)),
totalVat: z.number().transform(value => numToSignedString(value, 11)),
})
.strict();
// transformations that can be preformed by generator in non-strict mode
export const headerTransformerSchema = z
.object({
licensedDealerId: z.string().transform(id => digitsAdjuster(id, 9)),
reportMonth: z.string(),
generationDate: 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: z.number(),
taxableSalesVat: z.number(),
salesRecordCount: z.number().int(),
zeroValOrExemptSalesCount: z.number().int(),
otherInputsVat: z.number(),
equipmentInputsVat: z.number(),
inputsCount: z.number().int(),
totalVat: z.number(),
})
.strict();
// most basic requirements + obvious transformations
export const headerCoreSchema = z.object({
/**
* User Licensed Dealer identification Number
* 9 digits
* מספר עוסק
*/
licensedDealerId: z.string().transform(id => cleanNonDigitChars(id)),
/**
* Month for which detailed report is being submitted
* YYYYMM
* תקופת הדיווח
*/
reportMonth: 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: 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: z.number(),
/**
* Total VAT on taxable sales
* in the reported file
* מע"מ עסקאות חייבות
*/
taxableSalesVat: z.number(),
/**
* Total number of records for "sales".
* Number of sales records - both taxable and zero-rated/ exempt
* מס' עסקאות
*/
salesRecordCount: z.number().int(),
/**
* Total of zero value/exempt sales for period
* עסקאות פטורות / אפס
*/
zeroValOrExemptSalesCount: z.number().int(),
/**
* Total VAT on "other" inputs required during period
* תשומות אחרות
*/
otherInputsVat: z.number(),
/**
* Total VAT on "equipment" inputs required during period
* תשומות ציוד
*/
equipmentInputsVat: z.number(),
/**
* Total number of records for inputs (other and equipment)
* מס' תשומות
*/
inputsCount: z.number().int(),
/**
* Total VAT to pay / receive for period
* positive value => pay
* negative value => receive
* סכום מדווח
*/
totalVat: z.number(),
});
// most strict requirements
export const transactionStrictSchema = z
.object({
entryType: z.nativeEnum(EntryType).transform(entryType => entryType.valueOf()[0]),
vatId: z.string().length(9).regex(DIGITS_REGEX, 'Only digits are allowed'),
allocationNumber: z.string().length(9).regex(DIGITS_REGEX, 'Only digits are allowed'),
invoiceDate: z.string().length(8).regex(DATE_REGEX, 'Dates must be in YYYYMMDD format'),
refGroup: z.string().length(4),
refNumber: z.string().length(9).regex(DIGITS_REGEX, 'Only digits are allowed'),
totalVat: z
.number()
.int()
.transform(value => addLeadingZeros(value, 9)),
invoiceSum: z
.number()
.int()
.transform(value => numToSignedString(value, 10)),
})
.strict()
.refine(transaction => {
switch (transaction.entryType) {
case 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 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 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 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 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 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
export const transactionTransformerSchema = z
.object({
allocationNumber: z
.string()
.optional()
.transform(allocationNumber => digitsAdjuster(allocationNumber ?? '', 9)),
entryType: z.nativeEnum(EntryType),
invoiceDate: z.string(),
invoiceSum: z.number().int(),
refGroup: z
.string()
.optional()
.transform(refGroup => digitsAdjuster(refGroup ?? '', 4)),
refNumber: z
.string()
.optional()
.transform(refNumber => digitsAdjuster(refNumber ?? '', 9)),
totalVat: z.number().int().optional(),
vatId: z
.string()
.optional()
.transform(id => digitsAdjuster(id ?? '', 9)),
})
.strict()
.refine(transaction => {
switch (transaction.entryType) {
case EntryType.SALE_REGULAR: {
if (transaction.invoiceSum <= 5000) {
transaction.vatId ??= '000000000';
}
break;
}
case EntryType.SALE_ZERO_OR_EXEMPT: {
if (transaction.totalVat && transaction.totalVat !== 0) {
transaction.totalVat = 0;
}
if (transaction.invoiceSum <= 5000) {
transaction.vatId ??= '000000000';
}
break;
}
case EntryType.SALE_UNIDENTIFIED_ZERO_OR_EXEMPT: {
if (transaction.totalVat && transaction.totalVat !== 0) {
transaction.totalVat = 0;
}
break;
}
case EntryType.SALE_EXPORT: {
transaction.vatId ??= '999999999';
break;
}
case EntryType.INPUT_SINGLE_DOC_BY_LAW: {
transaction.refNumber ??= '000000000';
break;
}
}
return transaction;
});
// most basic requirements + obvious transformations
export const transactionCoreSchema = z.object({
/**
* Entry Type (document type)
* סוג רשומה
*/
entryType: z.nativeEnum(EntryType),
/**
* VAT identification number – of the other side of the transaction.
* For transactions entries – the customer
* For inputs – the supplier
* ספק / רשימון
*/
vatId: z
.string()
.optional()
.transform(id => (id ? cleanNonDigitChars(id) : undefined)),
/**
* Short allocation number – last 9 digits of full number.
* מספר הקצאה קצר
*/
allocationNumber: z
.string()
.optional()
.transform(allocationNumber => allocationNumber ? cleanNonDigitChars(allocationNumber) : undefined),
/**
* Invoice Date/Reference.
* YYYYMMDD
* תאריך החשבונית
*/
invoiceDate: z.string().length(8).regex(DIGITS_REGEX, 'Only digits are allowed'),
/**
* Reference group.
* Series etc. zeros are possible at this stage
* קבוצת אסמכתא
*/
refGroup: z
.string()
.optional()
.transform(refGroup => (refGroup ? cleanNonDigitChars(refGroup) : undefined)),
/**
* Reference number.
* First 9 positions from the right
* מספר אסמכתא
*/
refNumber: z
.string()
.optional()
.transform(refNumber => (refNumber ? 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: z.number().int().min(0).optional(),
/**
* Invoice total (excluding VAT)
* Always the 100%, always a positive value, rounded to the nearest shekel
* סכום
*/
invoiceSum: z.number().int(),
});
//# sourceMappingURL=schemas.js.map