UNPKG

@accounter/server

Version:

127 lines (110 loc) 4.61 kB
import { GraphQLError } from 'graphql'; import type { IGetChargesByFiltersResult } from '@modules/charges/types'; import type { IGetDocumentsByFiltersResult } from '@modules/documents/types'; import { getClosestRateForDate, getRateForCurrency, } from '@modules/exchange-rates/helpers/exchange.helper.js'; import type { IGetExchangeRatesByDatesResult } from '@modules/exchange-rates/types'; import type { IGetBusinessesByIdsResult } from '@modules/financial-entities/types'; import { DECREASED_VAT_RATIO, DEFAULT_VAT_PERCENTAGE } from '@shared/constants'; import { DocumentType } from '@shared/enums'; export type VatReportRecordSources = { charge: IGetChargesByFiltersResult; doc: IGetDocumentsByFiltersResult; business: IGetBusinessesByIdsResult; }; export type RawVatReportRecord = { amountBeforeVAT?: number; businessId: string | null; chargeAccountantReviewed: boolean; chargeDate: Date; chargeId: string; currencyCode: string; documentAmount: string; documentId: string; documentDate: Date | null; documentSerial: string | null; documentUrl: string | null; eventAmountILS?: number; isExpense: boolean; isProperty: boolean; roundedVATToAdd?: number; vat: number | null; vatAfterDeduction?: number; vatAfterDeductionILS?: number; vatNumber?: string | null; }; export function adjustTaxRecords( rawRecords: Array<VatReportRecordSources>, exchangeRatesList: Array<IGetExchangeRatesByDatesResult>, ): RawVatReportRecord[] { const records: RawVatReportRecord[] = []; for (const rawRecord of rawRecords) { const { charge, doc, business } = rawRecord; if (!doc.total_amount) { throw new Error(`Amount missing for invoice ID=${doc.id}`); } if (!doc.currency_code) { throw new Error(`Currency missing for invoice ID=${doc.id}`); } if (!doc.date) { throw new Error(`Date is missing for invoice ID=${doc.id}`); } // get exchange rate const exchangeRates = getClosestRateForDate(doc.date, exchangeRatesList); const rate = getRateForCurrency(doc.currency_code, exchangeRates); // convert document vat to ILS doc.vat_amount &&= doc.vat_amount * rate; const partialRecord: RawVatReportRecord = { businessId: charge.business_id, chargeAccountantReviewed: charge.accountant_reviewed, chargeDate: charge.transactions_min_event_date ?? charge.documents_min_date!, // must have min_date, as will throw if local doc is missing date chargeId: charge.id, currencyCode: doc.currency_code, documentDate: doc.date, documentId: doc.id, documentSerial: doc.serial_number, documentUrl: doc.image_url, documentAmount: String((doc.type === DocumentType.CreditInvoice ? -1 : 1) * doc.total_amount), vat: doc.vat_amount, isProperty: charge.is_property, vatNumber: business.vat_number, isExpense: doc.type === DocumentType.CreditInvoice ? doc.debtor_id !== charge.owner_id : doc.debtor_id === charge.owner_id, }; // set default amountBeforeVAT if (!partialRecord.vat) { partialRecord.amountBeforeVAT = doc.total_amount * rate * (doc.type === DocumentType.CreditInvoice ? -1 : 1); } if (partialRecord.businessId && partialRecord.vat) { // TODO: figure out how to handle VAT != DEFAULT_VAT_PERCENTAGE const convertedVat = DEFAULT_VAT_PERCENTAGE / (1 + DEFAULT_VAT_PERCENTAGE); const tiplessTotalAmount = doc.total_amount - (doc.no_vat_amount ? Number(doc.no_vat_amount) : 0); const vatDiff = Math.abs(tiplessTotalAmount * convertedVat - doc.vat_amount!); if (vatDiff > 0.005) { throw new GraphQLError( `Expected VAT amount is not ${DEFAULT_VAT_PERCENTAGE}%, but got ${ doc.vat_amount! / (tiplessTotalAmount - doc.vat_amount!) } for invoice ID=${doc.id}`, ); } // TODO: implement based on tax category / sort code const isDecreasedVat = false; // decorate record with additional fields const vatAfterDeduction = partialRecord.vat * (isDecreasedVat ? DECREASED_VAT_RATIO : 1); const amountBeforeVAT = doc.total_amount - vatAfterDeduction; partialRecord.vatAfterDeduction = vatAfterDeduction; partialRecord.roundedVATToAdd = Math.round(vatAfterDeduction * rate); partialRecord.amountBeforeVAT = amountBeforeVAT * rate; partialRecord.eventAmountILS = doc.total_amount * rate; partialRecord.vatAfterDeductionILS = vatAfterDeduction * rate; } records.push(partialRecord); } return records; }