@accounter/server
Version:
144 lines (127 loc) • 4.2 kB
text/typescript
import { format } from 'date-fns';
import { EntryType, pcnGenerator } from '@accounter-toolkit/pcn874-generator';
import {
type Header,
type Transaction,
} from '@accounter-toolkit/pcn874-generator/typings/types.js';
import { idValidator, yearMonthValidator } from '@shared/helpers';
import type { RawVatReportRecord } from './vat-report.helper';
export type ExtendedPCNTransaction = Omit<Transaction, 'totalVat'> &
Required<Pick<Transaction, 'totalVat'>> & { isProperty: boolean };
const headerPropsFromTransactions = (
transactions: ExtendedPCNTransaction[],
licensedDealerId: string,
reportMonth = '',
generationDate?: string,
) => {
let derivedReportMonth: string = reportMonth;
let taxableSalesAmount = 0;
let taxableSalesVat = 0;
let salesRecordCount = 0;
let zeroValOrExemptSalesCount = 0;
let otherInputsVat = 0;
let equipmentInputsVat = 0;
let inputsCount = 0;
let totalVat = 0;
for (const t of transactions) {
switch (t.entryType) {
case EntryType.SALE_REGULAR: {
taxableSalesVat += t.totalVat;
taxableSalesAmount += t.invoiceSum;
salesRecordCount += 1;
break;
}
case EntryType.SALE_UNIDENTIFIED_ZERO_OR_EXEMPT: {
salesRecordCount += 1;
zeroValOrExemptSalesCount += t.invoiceSum;
break;
}
case EntryType.INPUT_REGULAR: {
if (t.isProperty) {
equipmentInputsVat += t.totalVat;
} else {
otherInputsVat += t.totalVat;
}
inputsCount += 1;
break;
}
case EntryType.INPUT_PETTY_CASH: {
if (t.isProperty) {
equipmentInputsVat += t.totalVat;
} else {
otherInputsVat += t.totalVat;
}
inputsCount += 1;
break;
}
default: {
console.debug(`Transaction EntryType ${t.entryType} is not implemented yet`);
}
}
if (t.invoiceDate.substring(0, 6) > derivedReportMonth) {
derivedReportMonth = t.invoiceDate.substring(0, 6);
}
}
totalVat = taxableSalesVat - otherInputsVat - equipmentInputsVat;
const header: Header = {
licensedDealerId,
reportMonth: reportMonth || derivedReportMonth,
generationDate,
taxableSalesAmount,
taxableSalesVat,
salesRecordCount,
zeroValOrExemptSalesCount,
otherInputsVat,
equipmentInputsVat,
inputsCount,
totalVat,
};
return header;
};
const transformTransactions = (vatRecords: RawVatReportRecord[]): ExtendedPCNTransaction[] => {
const transactions: ExtendedPCNTransaction[] = [];
for (const t of vatRecords) {
if (!t.documentDate) {
console.debug(`Document ${t.documentId} has no tax_invoice_date. Skipping it.`);
continue;
}
let entryType = EntryType.INPUT_REGULAR;
if (!t.isExpense) {
if (Number(t.vatAfterDeduction) > 0) {
entryType = EntryType.SALE_REGULAR;
} else {
entryType = EntryType.SALE_UNIDENTIFIED_ZERO_OR_EXEMPT;
}
}
transactions.push({
entryType,
vatId: t.vatNumber ?? '0',
invoiceDate: format(new Date(t.documentDate!), 'yyyyMMdd'),
refGroup: '0000',
refNumber: t.documentSerial ?? undefined,
totalVat: Math.round(Math.abs(Number(t.vatAfterDeduction ?? 0))),
invoiceSum: Math.round(Number(t.amountBeforeVAT)),
isProperty: t.isProperty,
});
}
return transactions.sort((a, b) => a.invoiceDate.localeCompare(b.invoiceDate));
};
export const generatePcnFromCharges = (
vatRecords: RawVatReportRecord[],
vatNumber: string,
reportMonth: string,
) => {
if (!yearMonthValidator(reportMonth)) {
throw new Error(
`Expected reportMonth to be legit date formed as YYYYMM, received "${reportMonth}"`,
);
}
if (!idValidator(vatNumber, 9)) {
throw new Error(`Expected vatNumber to be 9 digits, received "${vatNumber}"`);
}
const transactions = transformTransactions(vatRecords);
const header = headerPropsFromTransactions(transactions, vatNumber, reportMonth);
const reportContent = pcnGenerator(header, transactions, { strict: false });
const fileName = `pcn874_${vatNumber}_${reportMonth}.txt`;
return { reportContent, fileName };
};