UNPKG

@accounter/server

Version:
245 lines (227 loc) • 9.04 kB
import { endOfYear, startOfYear } from 'date-fns'; import { GraphQLError } from 'graphql'; import { Currency } from '../../../shared/enums.js'; import { dateToTimelessDateString, formatFinancialAmount } from '../../../shared/helpers/index.js'; import { TimelessDateString } from '../../../shared/types/index.js'; import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js'; import { ScopeProvider } from '../../auth/providers/scope.provider.js'; import { CountriesProvider } from '../../countries/providers/countries.provider.js'; import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js'; import { TaxCategoriesProvider } from '../../financial-entities/providers/tax-categories.provider.js'; import { AnnualRevenueReportProvider } from '../providers/annual-revenue-report.provider.js'; import type { ReportsModule } from '../types.js'; interface NormalizedRecord { clientId: string; countryCode: string; amountIls: number; amountUsd: number; record: { id: string; chargeId: string; businessId: string; date: TimelessDateString; description: string | null; reference: string | null; amountIls: number; amountUsd: number; amountOriginal: number; currency: Currency; }; } export const annualRevenueResolvers: ReportsModule.Resolvers = { Query: { annualRevenueReport: async (_, { filters: { year, adminBusinessId } }, { injector }) => { // Per-business report: validate an explicitly requested business against // the read scope and use its admin context; default to the primary. let targetAdminContext; if (adminBusinessId) { // Validates the id is within the read scope (throws otherwise), so the // requested id can be used directly. await injector.get(ScopeProvider).getReadScope([adminBusinessId]); targetAdminContext = await injector .get(AdminContextProvider) .adminContextByOwnerIdLoader.load(adminBusinessId); if (!targetAdminContext) { throw new GraphQLError('Admin context not found for the requested business', { extensions: { code: 'NOT_FOUND' }, }); } } else { targetAdminContext = await injector.get(AdminContextProvider).getVerifiedAdminContext(); } const ownerId = targetAdminContext.ownerId; const incomeTaxCategories = await injector .get(TaxCategoriesProvider) .taxCategoriesBySortCodeLoader.load(810); const date = new Date(year, 6, 1); const records = await injector.get(AnnualRevenueReportProvider).getNormalizedRevenueRecords({ incomeTaxCategoriesIDs: incomeTaxCategories.map(tc => tc.id), incomeToCollectId: targetAdminContext.crossYear.incomeToCollectTaxCategoryId, ownerId, fromDate: dateToTimelessDateString(startOfYear(date)), toDate: dateToTimelessDateString(endOfYear(date)), }); // Normalize records (client, amount local currency, amount default foreign currency) const businessesProvider = injector.get(BusinessesProvider); const normalizedRecords: NormalizedRecord[] = []; const businesses = await Promise.all( records.map(r => r.business_id ? businessesProvider.getBusinessByIdLoader.load(r.business_id) : null, ), ); records.map((record, index) => { if (!record.business_id) return; const business = businesses[index]; if (!business) return; const amountIls = record.amount_local ? parseFloat(record.amount_local) : 0; const amountUsd = record.amount_usd ? parseFloat(record.amount_usd) : 0; normalizedRecords.push({ clientId: record.business_id, countryCode: business.country, amountIls, amountUsd, record: { id: record.id ?? '', chargeId: record.charge_id!, businessId: record.business_id!, date: dateToTimelessDateString(record.date!), description: record.description ?? null, reference: record.reference ?? null, amountIls, amountUsd, amountOriginal: record.amount_foreign ? parseFloat(record.amount_foreign) : 0, currency: record.currency as Currency, }, }); }); // Group and sum by client const clientSums = new Map< string, { countryCode: string; amountIls: number; amountUsd: number; records: { id: string; chargeId: string; businessId: string; date: TimelessDateString; description: string | null; reference: string | null; amountIls: number; amountUsd: number; amountOriginal: number; currency: Currency; }[]; } >(); for (const normalized of normalizedRecords) { const existing = clientSums.get(normalized.clientId); if (existing) { existing.amountIls += normalized.amountIls; existing.amountUsd += normalized.amountUsd; existing.records.push(normalized.record); } else { clientSums.set(normalized.clientId, { countryCode: normalized.countryCode, amountIls: normalized.amountIls, amountUsd: normalized.amountUsd, records: [normalized.record], }); } } // Group by country const countrySums = new Map< string, { amountIls: number; amountUsd: number; clients: Array<{ id: string; amountIls: number; amountUsd: number; records: { id: string; chargeId: string; businessId: string; date: TimelessDateString; description: string | null; reference: string | null; amountIls: number; amountUsd: number; amountOriginal: number; currency: Currency; }[]; }>; } >(); for (const [clientId, data] of clientSums.entries()) { const existing = countrySums.get(data.countryCode); if (existing) { existing.amountIls += data.amountIls; existing.amountUsd += data.amountUsd; existing.clients.push({ id: clientId, amountIls: data.amountIls, amountUsd: data.amountUsd, records: data.records, }); } else { countrySums.set(data.countryCode, { amountIls: data.amountIls, amountUsd: data.amountUsd, clients: [ { id: clientId, amountIls: data.amountIls, amountUsd: data.amountUsd, records: data.records, }, ], }); } } // Build the response const countriesProvider = injector.get(CountriesProvider); const countries = await Promise.all( Array.from(countrySums.entries()).map(async ([countryCode, data]) => { const country = await countriesProvider.getCountryByCodeLoader.load(countryCode); const clients = await Promise.all( data.clients.map(async client => { const business = await businessesProvider.getBusinessByIdLoader.load(client.id); return { id: `annual-revenue-report-${year}-${countryCode}-${client.id}`, name: business?.name ?? '', revenueLocal: formatFinancialAmount(client.amountIls, Currency.Ils), revenueDefaultForeign: formatFinancialAmount(client.amountUsd, Currency.Usd), records: client.records.map(record => ({ id: `annual-revenue-report-${year}-${countryCode}-${client.id}-record-${record.id}`, chargeId: record.chargeId, date: record.date, description: record.description, reference: record.reference, revenueLocal: formatFinancialAmount(record.amountIls, Currency.Ils), revenueDefaultForeign: formatFinancialAmount(record.amountUsd, Currency.Usd), revenueOriginal: formatFinancialAmount(record.amountOriginal, record.currency), })), }; }), ); return { id: `annual-revenue-report-${year}-${countryCode}`, code: countryCode, name: country?.name ?? countryCode, revenueLocal: formatFinancialAmount(data.amountIls, Currency.Ils), revenueDefaultForeign: formatFinancialAmount(data.amountUsd, Currency.Usd), clients, }; }), ); return { id: year.toString(), year, countries, }; }, }, };