@accounter/server
Version:
Accounter GraphQL server
235 lines (209 loc) • 7.81 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 {
IDeleteExpenseParams,
IDeleteExpenseQuery,
IGetExpensesByChargeIdsQuery,
IGetExpensesByChargeIdsResult,
IGetExpensesByFinancialEntityIdsQuery,
IGetExpensesByIdsQuery,
IInsertExpenseParams,
IInsertExpenseQuery,
IInsertExpensesParams,
IInsertExpensesQuery,
IReplaceMiscExpensesChargeIdParams,
IReplaceMiscExpensesChargeIdQuery,
IUpdateExpenseParams,
IUpdateExpenseQuery,
} from '../types.js';
const getExpensesByChargeIds = sql<IGetExpensesByChargeIdsQuery>`
SELECT *
FROM accounter_schema.misc_expenses
WHERE charge_id IN $$chargeIds;`;
const getExpensesByIds = sql<IGetExpensesByIdsQuery>`
SELECT *
FROM accounter_schema.misc_expenses
WHERE id IN $$ids;`;
const getExpensesByFinancialEntityIds = sql<IGetExpensesByFinancialEntityIdsQuery>`
SELECT *
FROM accounter_schema.misc_expenses
WHERE creditor_id IN $$financialEntityIds OR debtor_id IN $$financialEntityIds;`;
const updateExpense = sql<IUpdateExpenseQuery>`
UPDATE accounter_schema.misc_expenses
SET
charge_id = COALESCE(
$chargeId,
charge_id
),
creditor_id = COALESCE(
$creditorId,
creditor_id
),
debtor_id = COALESCE(
$debtorId,
debtor_id
),
amount = COALESCE(
$amount,
amount
),
currency = COALESCE(
$currency,
currency
),
description = COALESCE(
$description,
description
),
invoice_date = COALESCE(
$invoiceDate,
invoice_date
),
value_date = COALESCE(
$valueDate,
value_date
)
WHERE
id = $miscExpenseId
RETURNING *;`;
const replaceMiscExpensesChargeId = sql<IReplaceMiscExpensesChargeIdQuery>`
UPDATE accounter_schema.misc_expenses
SET
charge_id = $assertChargeID
WHERE
charge_id = $replaceChargeID
RETURNING *
`;
const insertExpense = sql<IInsertExpenseQuery>`
INSERT INTO accounter_schema.misc_expenses (charge_id, creditor_id, debtor_id, amount, currency, description, invoice_date, value_date, owner_id)
VALUES ($chargeId, $creditorId, $debtorId, $amount, $currency, $description, $invoiceDate, $valueDate, $ownerId)
RETURNING *;`;
const insertExpenses = sql<IInsertExpensesQuery>`
INSERT INTO accounter_schema.misc_expenses (charge_id, creditor_id, debtor_id, amount, currency, description, invoice_date, value_date, owner_id)
VALUES $$miscExpenses(chargeId, creditorId, debtorId, amount, currency, description, invoiceDate, valueDate, ownerId)
RETURNING *;`;
const deleteExpense = sql<IDeleteExpenseQuery>`
DELETE FROM accounter_schema.misc_expenses
WHERE id = $id
RETURNING id;`;
({
scope: Scope.Operation,
global: true,
})
export class MiscExpensesProvider {
constructor(
private db: TenantAwareDBClient,
private adminContextProvider: AdminContextProvider,
) {}
private async batchExpensesByChargeIds(chargeIds: readonly string[]) {
const expenses = await getExpensesByChargeIds.run({ chargeIds }, this.db);
const expensesByChargeId = new Map<string, IGetExpensesByChargeIdsResult[]>();
expenses.map(expense => {
if (expensesByChargeId.has(expense.charge_id)) {
expensesByChargeId.get(expense.charge_id)?.push(expense);
} else {
expensesByChargeId.set(expense.charge_id, [expense]);
}
});
return chargeIds.map(id => expensesByChargeId.get(id) ?? []);
}
public getExpensesByChargeIdLoader = new DataLoader((chargeIds: readonly string[]) =>
this.batchExpensesByChargeIds(chargeIds),
);
private async batchExpensesByFinancialEntityIds(financialEntityIds: readonly string[]) {
const expenses = await getExpensesByFinancialEntityIds.run({ financialEntityIds }, this.db);
const expensesByFinancialEntityId = new Map<string, IGetExpensesByChargeIdsResult[]>();
expenses.map(expense => {
if (expensesByFinancialEntityId.has(expense.creditor_id)) {
expensesByFinancialEntityId.get(expense.creditor_id)?.push(expense);
} else {
expensesByFinancialEntityId.set(expense.creditor_id, [expense]);
}
if (expensesByFinancialEntityId.has(expense.debtor_id)) {
expensesByFinancialEntityId.get(expense.debtor_id)?.push(expense);
} else {
expensesByFinancialEntityId.set(expense.debtor_id, [expense]);
}
});
return financialEntityIds.map(id => expensesByFinancialEntityId.get(id) ?? []);
}
public getExpensesByFinancialEntityIdLoader = new DataLoader(
(financialEntityIds: readonly string[]) =>
this.batchExpensesByFinancialEntityIds(financialEntityIds),
);
private async batchExpensesByIds(ids: readonly string[]) {
const expenses = await getExpensesByIds.run({ ids }, this.db);
return ids.map(id => expenses.find(expense => expense.id === id));
}
public getExpensesByIdLoader = new DataLoader((ids: readonly string[]) =>
this.batchExpensesByIds(ids),
);
public async updateExpense(params: IUpdateExpenseParams) {
if (params.miscExpenseId) await this.invalidateById(params.miscExpenseId);
return updateExpense.run(params, this.db);
}
public async replaceMiscExpensesChargeId(params: IReplaceMiscExpensesChargeIdParams) {
if (params.assertChargeID) {
await this.invalidateByChargeId(params.assertChargeID);
}
if (params.replaceChargeID) {
await this.invalidateByChargeId(params.replaceChargeID);
}
return replaceMiscExpensesChargeId.run(params, this.db);
}
public async insertExpense(params: IInsertExpenseParams) {
if (params.chargeId) await this.invalidateByChargeId(params.chargeId);
const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext();
return insertExpense.run(reassureOwnerIdExists(params, ownerId), this.db);
}
public async insertExpenses(params: IInsertExpensesParams) {
const chargeIds = Array.from(
new Set(params.miscExpenses.map(({ chargeId }) => chargeId)),
).filter(id => !!id) as string[];
if (chargeIds.length) {
await Promise.all(chargeIds.map(chargeId => this.invalidateByChargeId(chargeId)));
}
const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext();
return insertExpenses.run(
{
miscExpenses: params.miscExpenses.map(expense => ({
...expense,
ownerId: expense.ownerId || ownerId,
})),
},
this.db,
);
}
public async deleteMiscExpense(params: IDeleteExpenseParams) {
if (params.id) await this.invalidateById(params.id);
return deleteExpense.run(params, this.db);
}
public async invalidateByChargeId(chargeId: string) {
this.getExpensesByChargeIdLoader.clear(chargeId);
}
public async invalidateByFinancialEntityId(financialEntityId: string) {
this.getExpensesByFinancialEntityIdLoader.clear(financialEntityId);
}
public async invalidateById(id: string) {
const expense = await this.getExpensesByIdLoader.load(id);
if (expense) {
this.getExpensesByChargeIdLoader.clear(expense.charge_id);
if (expense.creditor_id) {
this.getExpensesByFinancialEntityIdLoader.clear(expense.creditor_id);
}
if (expense.debtor_id) {
this.getExpensesByFinancialEntityIdLoader.clear(expense.debtor_id);
}
}
this.getExpensesByIdLoader.clear(id);
}
public clearCache() {
this.getExpensesByIdLoader.clearAll();
this.getExpensesByChargeIdLoader.clearAll();
this.getExpensesByFinancialEntityIdLoader.clearAll();
}
}