import { Inject } from '@nestjs/common'; import { difference, sumBy } from 'lodash'; import { ILandedCostItemDTO, ILandedCostDTO, IBillLandedCostTransaction, ILandedCostTransaction, ILandedCostTransactionEntry, } from './types/BillLandedCosts.types'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { BillLandedCost } from './models/BillLandedCost'; import { ServiceError } from '../Items/ServiceError'; import { CONFIG, ERRORS } from './utils'; import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry'; import { Bill } from '../Bills/models/Bill'; import { TransactionLandedCost } from './commands/TransctionLandedCost.service'; export class BaseLandedCostService { @Inject() public readonly transactionLandedCost: TransactionLandedCost; @Inject(BillLandedCost.name) private readonly billLandedCostModel: TenantModelProxy; /** * Validates allocate cost items association with the purchase invoice entries. * @param {IItemEntry[]} purchaseInvoiceEntries * @param {ILandedCostItemDTO[]} landedCostItems */ protected validateAllocateCostItems = ( purchaseInvoiceEntries: ItemEntry[], landedCostItems: ILandedCostItemDTO[], ): void => { // Purchase invoice entries items ids. const purchaseInvoiceItems = purchaseInvoiceEntries.map((e) => e.id); const landedCostItemsIds = landedCostItems.map((item) => item.entryId); // Not found items ids. const notFoundItemsIds = difference( purchaseInvoiceItems, landedCostItemsIds, ); // Throw items ids not found service error. if (notFoundItemsIds.length > 0) { throw new ServiceError(ERRORS.LANDED_COST_ITEMS_IDS_NOT_FOUND); } }; /** * Transformes DTO to bill landed cost model object. * @param {ILandedCostDTO} landedCostDTO * @param {IBill} bill * @param {ILandedCostTransaction} costTransaction * @param {ILandedCostTransactionEntry} costTransactionEntry * @returns */ protected transformToBillLandedCost( landedCostDTO: ILandedCostDTO, bill: Bill, costTransaction: ILandedCostTransaction, costTransactionEntry: ILandedCostTransactionEntry, ) { const amount = sumBy(landedCostDTO.items, 'cost'); return { billId: bill.id, fromTransactionType: landedCostDTO.transactionType, fromTransactionId: landedCostDTO.transactionId, fromTransactionEntryId: landedCostDTO.transactionEntryId, amount, currencyCode: costTransaction.currencyCode, exchangeRate: costTransaction.exchangeRate || 1, allocationMethod: landedCostDTO.allocationMethod, allocateEntries: landedCostDTO.items, description: landedCostDTO.description, costAccountId: costTransactionEntry.costAccountId, }; } /** * Retrieve the cost transaction or throw not found error. * @param {number} tenantId * @param {transactionType} transactionType - * @param {transactionId} transactionId - */ public getLandedCostOrThrowError = async ( transactionType: string, transactionId: number, ) => { const Model = this.transactionLandedCost.getModel( transactionType, ); const model = await Model.query().findById(transactionId); if (!model) { throw new ServiceError(ERRORS.LANDED_COST_TRANSACTION_NOT_FOUND); } return this.transactionLandedCost.transformToLandedCost( transactionType, model, ); }; /** * Retrieve the landed cost entries. * @param {number} tenantId * @param {string} transactionType * @param {number} transactionId * @returns */ public getLandedCostEntry = async ( transactionType: string, transactionId: number, transactionEntryId: number, ): Promise => { const Model = this.transactionLandedCost.getModel( tenantId, transactionType, ); const relation = CONFIG.COST_TYPES[transactionType].entries; const entry = await Model.relatedQuery(relation) .for(transactionId) .findOne('id', transactionEntryId) .where('landedCost', true) .onBuild((q) => { if (transactionType === 'Bill') { q.withGraphFetched('item'); } else if (transactionType === 'Expense') { q.withGraphFetched('expenseAccount'); } }); if (!entry) { throw new ServiceError(ERRORS.LANDED_COST_ENTRY_NOT_FOUND); } return this.transactionLandedCost.transformToLandedCostEntry( transactionType, entry, ); }; /** * Retrieve allocate items cost total. * @param {ILandedCostDTO} landedCostDTO * @returns {number} */ protected getAllocateItemsCostTotal = ( landedCostDTO: ILandedCostDTO, ): number => { return sumBy(landedCostDTO.items, 'cost'); }; /** * Validates the landed cost entry amount. * @param {number} unallocatedCost - * @param {number} amount - */ protected validateLandedCostEntryAmount = ( unallocatedCost: number, amount: number, ): void => { if (unallocatedCost < amount) { throw new ServiceError(ERRORS.COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT); } }; /** * Retrieve the give bill landed cost or throw not found service error. * @param {number} tenantId - Tenant id. * @param {number} landedCostId - Landed cost id. * @returns {Promise} */ public getBillLandedCostOrThrowError = async ( landedCostId: number, ): Promise => { // Retrieve the bill landed cost model. const billLandedCost = await this.billLandedCostModel() .query() .findById(landedCostId); if (!billLandedCost) { throw new ServiceError(ERRORS.BILL_LANDED_COST_NOT_FOUND); } return billLandedCost; }; }