@accounter/server
Version:
Accounter GraphQL server
187 lines • 8.99 kB
JavaScript
import { endOfDay, lastDayOfMonth, startOfDay, startOfMonth } from 'date-fns';
import { GraphQLError } from 'graphql';
import { DocumentType } from '../../../shared/enums.js';
import { dateToTimelessDateString } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { BusinessTripsProvider } from '../../business-trips/providers/business-trips.provider.js';
import { getChargeBusinesses } from '../../charges/helpers/common.helper.js';
import { validateCharge } from '../../charges/helpers/validate.helper.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { DocumentsProvider } from '../../documents/providers/documents.provider.js';
import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js';
import { isRefundCharge } from '../../ledger/helpers/common-charge-ledger.helper.js';
import { adjustTaxRecord, } from '../helpers/vat-report.helper.js';
export const getVatRecords = async ({ filters }, injector) => {
const { authorities: { vatReportExcludedBusinessNames }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
try {
const response = {
income: [],
expenses: [],
missingInfo: [],
differentMonthDoc: [],
businessTrips: [],
};
const docsChargesIDs = new Set();
const reportIssuerId = filters?.financialEntityId;
const fromDate = filters?.monthDate
? dateToTimelessDateString(startOfMonth(new Date(filters.monthDate)))
: undefined;
const toDate = filters?.monthDate
? dateToTimelessDateString(lastDayOfMonth(new Date(filters.monthDate)))
: undefined;
// get all documents by date filters
const relevantDocumentsPromise = injector
.get(DocumentsProvider)
.getDocumentsByFilters({
fromVatDate: fromDate,
toVatDate: toDate,
})
.then(documents => documents.filter(doc => {
if (doc.charge_id) {
docsChargesIDs.add(doc.charge_id);
}
// filter documents with vat_report_date_override outside of the date range
if (doc.vat_report_date_override) {
const isBeforeFromDate = fromDate && doc.vat_report_date_override < startOfDay(fromDate);
const isAfterToDate = toDate && doc.vat_report_date_override > endOfDay(toDate);
if (isBeforeFromDate || isAfterToDate) {
return false;
}
}
if (!doc.charge_id || !doc.creditor_id || !doc.debtor_id) {
// filter invoice documents with linked charge
return false;
}
const isRelevantDoc = ['INVOICE', 'INVOICE_RECEIPT', 'CREDIT_INVOICE'].includes(doc.type);
return isRelevantDoc;
}));
// get all businesses
const businessesPromise = injector.get(BusinessesProvider).getAllBusinesses();
const chargesPromise = injector.get(ChargesProvider).getChargesByFilters({
fromDate,
toDate,
ownerIds: reportIssuerId ? [reportIssuerId] : undefined,
chargeType: filters?.chargesType,
});
const [relevantDocuments, businesses, charges] = await Promise.all([
relevantDocumentsPromise,
businessesPromise,
chargesPromise,
]);
let docsCharges = charges.filter(charge => docsChargesIDs.has(charge.id));
if (docsCharges.length < docsChargesIDs.size) {
const moreDocsCharges = await injector.get(ChargesProvider).getChargesByFilters({
IDs: Array.from(docsChargesIDs).filter(id => !docsCharges.find(charge => charge.id === id)),
ownerIds: [reportIssuerId],
});
charges.push(...moreDocsCharges);
docsCharges.push(...moreDocsCharges);
}
const extendedCharges = await Promise.all(charges.map(async (charge) => {
const { allBusinessIds } = await getChargeBusinesses(charge.id, injector);
return {
...charge,
allBusinessIds,
};
}));
docsCharges = extendedCharges.filter(charge => {
for (const businessId of charge.allBusinessIds) {
if (vatReportExcludedBusinessNames.includes(businessId)) {
return false;
}
}
return true;
});
const incomeRecords = [];
const expenseRecords = [];
const includedChargeIDs = new Set();
relevantDocuments.map(doc => {
// filter charge linked to document
const charge = docsCharges.find(charge => charge.id === doc.charge_id);
if (!charge) {
throw new GraphQLError(`for some weird reason no charge found for document ID=${doc.id}`);
}
// filter business linked to document
const counterpartyId = doc.creditor_id === doc.owner_id
? doc.debtor_id
: doc.debtor_id === doc.owner_id
? doc.creditor_id
: null;
const business = businesses.find(business => business.id === counterpartyId);
if (!business) {
throw new GraphQLError(`for some weird reason no counterparty found for document ID=${doc.id}`);
}
const isReimbursement = isRefundCharge(charge.user_description);
// add charge to income/expense records
if (doc.vat_amount &&
(doc.type === DocumentType.CreditInvoice ? doc.creditor_id : doc.debtor_id) ===
reportIssuerId) {
includedChargeIDs.add(charge.id);
if (filters?.chargesType !== 'EXPENSE') {
expenseRecords.push({ charge, doc, business });
}
}
else if (doc.vat_amount != null &&
(doc.type === DocumentType.CreditInvoice
? doc.debtor_id === reportIssuerId
: doc.creditor_id === reportIssuerId) &&
Number(doc.total_amount) > 0 &&
!isReimbursement) {
includedChargeIDs.add(charge.id);
if (filters?.chargesType !== 'INCOME') {
incomeRecords.push({ charge, doc, business });
}
}
});
if (incomeRecords.length + expenseRecords.length > 0) {
// TODO: what if no exchange dates found?
await Promise.all([
...incomeRecords.map(async (rawRecord) => adjustTaxRecord(rawRecord, injector).then(res => {
response.income.push(res);
})),
...expenseRecords.map(async (rawRecord) => adjustTaxRecord(rawRecord, injector).then(res => {
response.expenses.push(res);
})),
]);
}
// validate charges for missing info
const validatedCharges = await Promise.all(charges.map(async (charge) => {
const [validation, businessTrip] = await Promise.all([
validateCharge(charge, injector),
injector.get(BusinessTripsProvider).getBusinessTripsByChargeIdLoader.load(charge.id),
]);
if (!('isValid' in validation)) {
throw new Error('Error validating charge');
}
return {
charge,
isValid: validation.isValid,
businessTripId: businessTrip?.id ?? null,
};
}));
for (const { charge, isValid, businessTripId } of validatedCharges) {
if (businessTripId) {
// If valid and has business trip, add to business trips
response.businessTrips.push(charge);
}
else if (isValid) {
if (!includedChargeIDs.has(charge.id)) {
// If valid but not yet included, add to charges with different month doc
response.differentMonthDoc.push(charge);
}
}
else {
// add to charges with missing info
response.missingInfo.push(charge);
}
}
response.income = response.income.sort((a, b) => (b.documentDate?.getDate() ?? 0) - (a.documentDate?.getDate() ?? 0));
response.expenses = response.expenses.sort((a, b) => (b.documentDate?.getDate() ?? 0) - (a.documentDate?.getDate() ?? 0));
return response;
}
catch (e) {
console.error('Error fetching vat report records:', e);
throw new GraphQLError(e?.message ?? 'Error fetching vat report records');
}
};
//# sourceMappingURL=get-vat-records.resolver.js.map