@accounter/server
Version:
Accounter GraphQL server
219 lines (203 loc) • 6.34 kB
text/typescript
import DataLoader from 'dataloader';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { reassureOwnerIdExists } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import type {
IGetChargeIdsByPaymentIdsQuery,
IGetInvoicesByIdsQuery,
IGetInvoicesByIssueDatesQuery,
IGetReceiptToChargeQuery,
IInsertDeelInvoiceRecordsParams,
IInsertDeelInvoiceRecordsQuery,
} from '../types.js';
const getInvoicesByIssueDates = sql<IGetInvoicesByIssueDatesQuery>`
SELECT *
FROM accounter_schema.deel_invoices
WHERE issued_at >= $from
AND issued_at <= $to;`;
const getInvoicesByIds = sql<IGetInvoicesByIdsQuery>`
SELECT *
FROM accounter_schema.deel_invoices
WHERE id in $$ids;`;
const getChargeIdsByPaymentIds = sql<IGetChargeIdsByPaymentIdsQuery>`
SELECT d.charge_id, i.payment_id
FROM accounter_schema.deel_invoices i
LEFT JOIN accounter_schema.documents d
ON i.document_id = d.id AND d.charge_id IS NOT NULL
WHERE i.payment_id in $$paymentIds;`;
const getReceiptToCharge = sql<IGetReceiptToChargeQuery>`
SELECT DISTINCT ON (di.payment_id)
di.payment_id,
d.charge_id
FROM
accounter_schema.deel_invoices di
LEFT JOIN accounter_schema.documents d
ON d.id = di.document_id
ORDER BY
di.payment_id, di.created_at ASC;`;
const insertDeelInvoiceRecords = sql<IInsertDeelInvoiceRecordsQuery>`
INSERT INTO accounter_schema.deel_invoices (
id,
document_id,
amount,
contract_id,
created_at,
currency,
deel_fee,
due_date,
is_overdue,
issued_at,
label,
paid_at,
status,
total,
vat_id,
vat_percentage,
vat_total,
adjustment,
approve_date,
approvers,
bonus,
commissions,
contract_country,
contract_start_date,
contract_type,
contractor_email,
contractor_employee_name,
contractor_unique_identifier,
deductions,
expenses,
frequency,
general_ledger_account,
group_id,
others,
overtime,
payment_currency,
pro_rata,
processing_fee,
"work",
total_payment_currency,
payment_id,
recipient_legal_entity_id,
owner_id)
VALUES ($id,
$documentId,
$amount,
$contractId,
$createdAt,
$currency,
$deelFee,
$dueDate,
$isOverdue,
$issuedAt,
$label,
$paidAt,
$status,
$total,
$vatId,
$vatPercentage,
$vatTotal,
$adjustment,
$approveDate,
$approvers,
$bonus,
$commissions,
$contractCountry,
$contractStartDate,
$contractType,
$contractorEmail,
$contractorEmployeeName,
$contractorUniqueIdentifier,
$deductions,
$expenses,
$frequency,
$generalLedgerAccount,
$groupId,
$others,
$overtime,
$paymentCurrency,
$proRata,
$processingFee,
$work,
$totalPaymentCurrency,
$paymentId,
$recipientLegalEntityId,
$ownerId)
RETURNING *;`;
({
scope: Scope.Operation,
global: true,
})
export class DeelInvoicesProvider {
constructor(
private db: TenantAwareDBClient,
private adminContextProvider: AdminContextProvider,
) {}
private async batchInvoicesByIssueDates(issueDates: readonly Date[]) {
const times = issueDates.map(date => date.getTime());
const from = new Date(Math.min(...times));
const to = new Date(Math.max(...times));
const records = await getInvoicesByIssueDates.run({ from, to }, this.db);
return issueDates.map(date => {
return records.filter(record => record.issued_at.getTime() === date.getTime());
});
}
public async getInvoicesByIssueDates(from: Date, to: Date) {
try {
return getInvoicesByIssueDates.run({ from, to }, this.db);
} catch (e) {
const message = `Error getting Deel invoices by issue dates`;
console.error(message, e);
throw new Error(message, { cause: e });
}
}
public getInvoicesByIssueDateLoader = new DataLoader(
(dates: readonly Date[]) => this.batchInvoicesByIssueDates(dates),
{ cacheKeyFn: date => date.getTime().toString() },
);
private async batchInvoicesByIds(ids: readonly string[]) {
const invoices = await getInvoicesByIds.run({ ids }, this.db);
return ids.map(id => invoices.find(invoice => invoice.id === id));
}
public getInvoicesByIdLoader = new DataLoader((ids: readonly string[]) =>
this.batchInvoicesByIds(ids),
);
private async batchChargeIdsByPaymentIds(paymentIds: readonly string[]) {
const matches = await getChargeIdsByPaymentIds.run({ paymentIds }, this.db);
return paymentIds.map(
paymentId => matches.find(match => match.payment_id === paymentId)?.charge_id,
);
}
public getChargeIdByPaymentIdLoader = new DataLoader((paymentIds: readonly string[]) =>
this.batchChargeIdsByPaymentIds(paymentIds),
);
public async getReceiptToCharge() {
try {
const records = await getReceiptToCharge.run(undefined, this.db);
const receiptChargeMap = new Map<string, string>();
for (const record of records) {
if (record.charge_id) {
receiptChargeMap.set(record.payment_id, record.charge_id);
}
}
return receiptChargeMap;
} catch (e) {
const message = `Error getting receipt to charge mapping`;
console.error(message, e);
throw new Error(message, { cause: e });
}
}
public async insertDeelInvoiceRecords(params: IInsertDeelInvoiceRecordsParams) {
try {
const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext();
// invalidate cache
return insertDeelInvoiceRecords.run(reassureOwnerIdExists(params, ownerId), this.db);
} catch (e) {
const message = `Error inserting Deel invoice`;
console.error(message, e);
throw new Error(message, { cause: e });
}
}
}