@accounter/server
Version:
Accounter GraphQL server
244 lines • 9.53 kB
JavaScript
import { __decorate, __metadata } from "tslib";
import DataLoader from 'dataloader';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import { BusinessesOperationProvider } from './businesses-operation.provider.js';
import { BusinessesProvider } from './businesses.provider.js';
import { TaxCategoriesProvider } from './tax-categories.provider.js';
const getFinancialEntitiesByIds = sql `
SELECT *
FROM accounter_schema.financial_entities
WHERE id IN $$ids;`;
const getAllFinancialEntities = sql `
SELECT *
FROM accounter_schema.financial_entities;`;
const updateFinancialEntity = sql `
UPDATE accounter_schema.financial_entities
SET
name = COALESCE(
$name,
name
),
sort_code = COALESCE(
$sortCode,
sort_code
),
type = COALESCE(
$type,
type
),
irs_code = COALESCE(
$irsCode,
irs_code
),
is_active = COALESCE(
$isActive,
is_active
)
WHERE
id = $financialEntityId
RETURNING *;
`;
const insertFinancialEntities = sql `
INSERT INTO accounter_schema.financial_entities (type, owner_id, name, sort_code, irs_code, is_active)
VALUES $$financialEntities(type, ownerId, name, sortCode, irsCode, isActive)
RETURNING *;`;
const deleteFinancialEntity = sql `
DELETE FROM accounter_schema.financial_entities
WHERE id = $financialEntityId
RETURNING id;
`;
const replaceFinancialEntities = sql `
WITH ledger_debit1 AS (
UPDATE accounter_schema.ledger_records
SET debit_entity1 = $targetEntityId
WHERE debit_entity1 = $entityIdToReplace
RETURNING id
),
ledger_debit2 AS (
UPDATE accounter_schema.ledger_records
SET debit_entity2 = $targetEntityId
WHERE debit_entity2 = $entityIdToReplace
RETURNING id
),
ledger_credit1 AS (
UPDATE accounter_schema.ledger_records
SET credit_entity1 = $targetEntityId
WHERE credit_entity1 = $entityIdToReplace
RETURNING id
),
ledger_credit2 AS (
UPDATE accounter_schema.ledger_records
SET credit_entity2 = $targetEntityId
WHERE credit_entity2 = $entityIdToReplace
RETURNING id
),
misc_expenses_creditor AS (
UPDATE accounter_schema.misc_expenses
SET creditor_id = $targetEntityId
WHERE creditor_id = $entityIdToReplace
RETURNING id
)
UPDATE accounter_schema.misc_expenses
SET debtor_id = $targetEntityId
WHERE debtor_id = $entityIdToReplace
RETURNING id;
`;
let FinancialEntitiesProvider = class FinancialEntitiesProvider {
db;
businessesProvider;
businessesOperationProvider;
taxCategoriesProvider;
constructor(db, businessesProvider, businessesOperationProvider, taxCategoriesProvider) {
this.db = db;
this.businessesProvider = businessesProvider;
this.businessesOperationProvider = businessesOperationProvider;
this.taxCategoriesProvider = taxCategoriesProvider;
}
async batchFinancialEntitiesByIds(ids) {
const financialEntities = await getFinancialEntitiesByIds.run({
ids,
}, this.db);
return ids.map(id => financialEntities.find(fe => fe.id === id));
}
getFinancialEntityByIdLoader = new DataLoader((keys) => this.batchFinancialEntitiesByIds(keys));
allFinancialEntitiesCache = null;
getAllFinancialEntities() {
if (this.allFinancialEntitiesCache) {
return this.allFinancialEntitiesCache;
}
const result = getAllFinancialEntities.run(undefined, this.db).then(entities => {
entities.map(entity => {
this.getFinancialEntityByIdLoader.prime(entity.id, entity);
});
return entities;
});
this.allFinancialEntitiesCache = result;
return result;
}
/** Returns a map of sort_code → entity ids, built once per request from the cached entity list. */
entityBySortCodeMapCache = null;
getEntityBySortCodeMap() {
if (this.entityBySortCodeMapCache)
return this.entityBySortCodeMapCache;
this.entityBySortCodeMapCache = this.getAllFinancialEntities().then(entities => {
const map = new Map();
for (const entity of entities) {
if (entity.sort_code == null)
continue;
const bucket = map.get(entity.sort_code);
if (bucket) {
bucket.push(entity.id);
}
else {
map.set(entity.sort_code, [entity.id]);
}
}
return map;
});
return this.entityBySortCodeMapCache;
}
updateFinancialEntity(params) {
if (params.financialEntityId) {
this.invalidateFinancialEntityById(params.financialEntityId);
}
return updateFinancialEntity.run(params, this.db);
}
insertFinancialEntity(params, client) {
this.allFinancialEntitiesCache = null;
return insertFinancialEntities.run({ financialEntities: [params] }, client ?? this.db);
}
async batchInsertFinancialEntities(newFinancialEntities) {
const financialEntities = await insertFinancialEntities.run({
financialEntities: newFinancialEntities,
}, this.db);
return newFinancialEntities.map(fe => financialEntities.find(f => f.name === fe.name) ?? null);
}
insertFinancialEntitiesLoader = new DataLoader((financialEntities) => this.batchInsertFinancialEntities(financialEntities), {
cache: false,
});
async deleteFinancialEntityById(financialEntityId) {
const entity = await this.getFinancialEntityByIdLoader.load(financialEntityId);
if (!entity) {
throw new Error(`Financial entity with id ${financialEntityId} not found`);
}
if (entity.id === entity.owner_id) {
throw new Error('Cannot delete owner entity');
}
this.invalidateFinancialEntityById(financialEntityId);
// remove business
const deleteBusiness = entity.type === 'business'
? this.businessesOperationProvider.deleteBusinessById(financialEntityId)
: Promise.resolve();
// remove tax category
const deleteTaxCategory = entity.type === 'tax_category'
? this.taxCategoriesProvider.deleteTaxCategoryById(financialEntityId)
: Promise.resolve();
await Promise.all([deleteBusiness, deleteTaxCategory]);
// TODO: should remove ledger, misc expenses?
// delete entity
deleteFinancialEntity.run({ financialEntityId }, this.db);
}
async replaceFinancialEntity(targetEntityId, entityIdToReplace, deleteEntity = false) {
const [entityToReplace, entity] = await Promise.all([
this.getFinancialEntityByIdLoader.load(entityIdToReplace),
this.getFinancialEntityByIdLoader.load(targetEntityId),
]);
if (!entityToReplace) {
throw new Error(`Financial entity with id ${entityIdToReplace} not found`);
}
if (!entity) {
throw new Error(`Financial entity with id ${targetEntityId} not found`);
}
if (entityToReplace.type !== entity.type) {
throw new Error('Cannot replace entities of different types');
}
if (entityToReplace.id === entityToReplace.owner_id) {
throw new Error('Cannot replace owner entity');
}
this.invalidateFinancialEntityById(entityIdToReplace);
this.invalidateFinancialEntityById(targetEntityId);
// convert ledger, misc expenses
await replaceFinancialEntities.run({ targetEntityId, entityIdToReplace }, this.db);
// convert business
const businessReplacementPromise = entity.type === 'business'
? this.businessesProvider.replaceBusiness(targetEntityId, entityIdToReplace).then(() => {
if (deleteEntity)
this.businessesOperationProvider.deleteBusinessById(entityIdToReplace);
})
: Promise.resolve();
// convert tax category
const taxCategoryReplacementPromise = entity.type === 'tax_category'
? this.taxCategoriesProvider.replaceTaxCategory(targetEntityId, entityIdToReplace, deleteEntity)
: Promise.resolve();
await Promise.all([businessReplacementPromise, taxCategoryReplacementPromise]);
if (deleteEntity) {
await this.deleteFinancialEntityById(entityIdToReplace);
}
}
invalidateFinancialEntityById(financialEntityId) {
this.businessesProvider.invalidateBusinessById(financialEntityId);
this.taxCategoriesProvider.invalidateTaxCategoryById(financialEntityId);
this.allFinancialEntitiesCache = null;
this.getFinancialEntityByIdLoader.clear(financialEntityId);
}
clearCache() {
this.taxCategoriesProvider.clearCache();
this.businessesProvider.clearCache();
this.getFinancialEntityByIdLoader.clearAll();
this.allFinancialEntitiesCache = null;
}
};
FinancialEntitiesProvider = __decorate([
Injectable({
scope: Scope.Operation,
global: true,
}),
__metadata("design:paramtypes", [TenantAwareDBClient,
BusinessesProvider,
BusinessesOperationProvider,
TaxCategoriesProvider])
], FinancialEntitiesProvider);
export { FinancialEntitiesProvider };
//# sourceMappingURL=financial-entities.provider.js.map