UNPKG

@accounter/server

Version:
244 lines 9.53 kB
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