mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
refactor(nestjs): landed cost
This commit is contained in:
@@ -0,0 +1,191 @@
|
|||||||
|
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<typeof BillLandedCost>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<any> => {
|
||||||
|
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<IBillLandedCost>}
|
||||||
|
*/
|
||||||
|
public getBillLandedCostOrThrowError = async (
|
||||||
|
landedCostId: number,
|
||||||
|
): Promise<BillLandedCost> => {
|
||||||
|
// 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,8 +1,32 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TransactionLandedCostEntriesService } from './TransactionLandedCostEntries.service';
|
import { TransactionLandedCostEntriesService } from './TransactionLandedCostEntries.service';
|
||||||
|
import { AllocateLandedCostService } from './commands/AllocateLandedCost.service';
|
||||||
|
import { LandedCostGLEntriesSubscriber } from './commands/LandedCostGLEntries.subscriber';
|
||||||
|
import { LandedCostGLEntries } from './commands/LandedCostGLEntries.service';
|
||||||
|
import { LandedCostSyncCostTransactions } from './commands/LandedCostSyncCostTransactions.service';
|
||||||
|
import { LandedCostSyncCostTransactionsSubscriber } from './commands/LandedCostSyncCostTransactions.subscriber';
|
||||||
|
import { BillAllocatedLandedCostTransactions } from './commands/BillAllocatedLandedCostTransactions.service';
|
||||||
|
import { BillAllocateLandedCostController } from './LandedCost.controller';
|
||||||
|
import { RevertAllocatedLandedCost } from './commands/RevertAllocatedLandedCost.service';
|
||||||
|
import LandedCostTranasctions from './commands/LandedCostTransactions.service';
|
||||||
|
import { LandedCostInventoryTransactions } from './commands/LandedCostInventoryTransactions.service';
|
||||||
|
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [TransactionLandedCostEntriesService],
|
imports: [InventoryCostModule],
|
||||||
|
providers: [
|
||||||
|
AllocateLandedCostService,
|
||||||
|
TransactionLandedCostEntriesService,
|
||||||
|
BillAllocatedLandedCostTransactions,
|
||||||
|
LandedCostGLEntriesSubscriber,
|
||||||
|
LandedCostGLEntries,
|
||||||
|
LandedCostSyncCostTransactions,
|
||||||
|
RevertAllocatedLandedCost,
|
||||||
|
LandedCostInventoryTransactions,
|
||||||
|
LandedCostTranasctions,
|
||||||
|
LandedCostSyncCostTransactionsSubscriber,
|
||||||
|
],
|
||||||
exports: [TransactionLandedCostEntriesService],
|
exports: [TransactionLandedCostEntriesService],
|
||||||
|
controllers: [BillAllocateLandedCostController],
|
||||||
})
|
})
|
||||||
export class BillLandedCostsModule {}
|
export class BillLandedCostsModule {}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AllocateBillLandedCostDto } from './dtos/AllocateBillLandedCost.dto';
|
||||||
|
import { AllocateLandedCostService } from './commands/AllocateLandedCost.service';
|
||||||
|
import { BillAllocatedLandedCostTransactions } from './commands/BillAllocatedLandedCostTransactions.service';
|
||||||
|
import { RevertAllocatedLandedCost } from './commands/RevertAllocatedLandedCost.service';
|
||||||
|
import { LandedCostTranasctions } from './commands/LandedCostTransactions.service';
|
||||||
|
|
||||||
|
@Controller('landed-cost')
|
||||||
|
export class BillAllocateLandedCostController {
|
||||||
|
constructor(
|
||||||
|
private allocateLandedCost: AllocateLandedCostService,
|
||||||
|
private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions,
|
||||||
|
private revertAllocatedLandedCost: RevertAllocatedLandedCost,
|
||||||
|
private landedCostTranasctions: LandedCostTranasctions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('/transactions')
|
||||||
|
async getLandedCostTransactions(
|
||||||
|
@Query('transaction_type') transactionType: string,
|
||||||
|
) {
|
||||||
|
const transactions =
|
||||||
|
await this.landedCostTranasctions.getLandedCostTransactions(transactionType);
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/bills/:billId/allocate')
|
||||||
|
public async calculateLandedCost(
|
||||||
|
@Param('billId') billId: number,
|
||||||
|
@Body() landedCostDTO: AllocateBillLandedCostDto,
|
||||||
|
) {
|
||||||
|
const billLandedCost = await this.allocateLandedCost.allocateLandedCost(
|
||||||
|
landedCostDTO,
|
||||||
|
billId,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: billLandedCost.id,
|
||||||
|
message: 'The items cost are located successfully.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/:allocatedLandedCostId')
|
||||||
|
public async deleteAllocatedLandedCost(
|
||||||
|
@Param('allocatedLandedCostId') allocatedLandedCostId: number,
|
||||||
|
) {
|
||||||
|
await this.revertAllocatedLandedCost.deleteAllocatedLandedCost(
|
||||||
|
allocatedLandedCostId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: allocatedLandedCostId,
|
||||||
|
message: 'The allocated landed cost are delete successfully.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listLandedCosts(
|
||||||
|
) {
|
||||||
|
const transactions =
|
||||||
|
await this.landedCostTranasctions.getLandedCostTransactions(query);
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Get('/bills/:billId/transactions')
|
||||||
|
async getBillLandedCostTransactions(@Param('billId') billId: number) {
|
||||||
|
const transactions =
|
||||||
|
await this.billAllocatedCostTransactions.getBillLandedCostTransactions(
|
||||||
|
billId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
billId,
|
||||||
|
transactions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ServiceError } from '../Items/ServiceError';
|
import { ServiceError } from '../Items/ServiceError';
|
||||||
import { transformToMap } from '@/utils/transform-to-key';
|
import { transformToMap } from '@/utils/transform-to-key';
|
||||||
import { ICommonLandedCostEntry, ICommonLandedCostEntryDTO } from './types/BillLandedCosts.types';
|
import {
|
||||||
|
ICommonLandedCostEntry,
|
||||||
|
ICommonLandedCostEntryDTO,
|
||||||
|
} from './types/BillLandedCosts.types';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
|
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
|
||||||
@@ -19,7 +22,7 @@ export class TransactionLandedCostEntriesService {
|
|||||||
*/
|
*/
|
||||||
public getLandedCostEntriesDeleted(
|
public getLandedCostEntriesDeleted(
|
||||||
oldCommonEntries: ICommonLandedCostEntry[],
|
oldCommonEntries: ICommonLandedCostEntry[],
|
||||||
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
|
newCommonEntriesDTO: ICommonLandedCostEntryDTO[],
|
||||||
): ICommonLandedCostEntry[] {
|
): ICommonLandedCostEntry[] {
|
||||||
const newBillEntriesById = transformToMap(newCommonEntriesDTO, 'id');
|
const newBillEntriesById = transformToMap(newCommonEntriesDTO, 'id');
|
||||||
|
|
||||||
@@ -40,11 +43,11 @@ export class TransactionLandedCostEntriesService {
|
|||||||
*/
|
*/
|
||||||
public validateLandedCostEntriesNotDeleted(
|
public validateLandedCostEntriesNotDeleted(
|
||||||
oldCommonEntries: ICommonLandedCostEntry[],
|
oldCommonEntries: ICommonLandedCostEntry[],
|
||||||
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
|
newCommonEntriesDTO: ICommonLandedCostEntryDTO[],
|
||||||
): void {
|
): void {
|
||||||
const entriesDeleted = this.getLandedCostEntriesDeleted(
|
const entriesDeleted = this.getLandedCostEntriesDeleted(
|
||||||
oldCommonEntries,
|
oldCommonEntries,
|
||||||
newCommonEntriesDTO
|
newCommonEntriesDTO,
|
||||||
);
|
);
|
||||||
if (entriesDeleted.length > 0) {
|
if (entriesDeleted.length > 0) {
|
||||||
throw new ServiceError(ERRORS.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED);
|
throw new ServiceError(ERRORS.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED);
|
||||||
@@ -58,7 +61,7 @@ export class TransactionLandedCostEntriesService {
|
|||||||
*/
|
*/
|
||||||
public validateLocatedCostEntriesSmallerThanNewEntries(
|
public validateLocatedCostEntriesSmallerThanNewEntries(
|
||||||
oldCommonEntries: ICommonLandedCostEntry[],
|
oldCommonEntries: ICommonLandedCostEntry[],
|
||||||
newCommonEntriesDTO: ICommonLandedCostEntryDTO[]
|
newCommonEntriesDTO: ICommonLandedCostEntryDTO[],
|
||||||
): void {
|
): void {
|
||||||
const oldBillEntriesById = transformToMap(oldCommonEntries, 'id');
|
const oldBillEntriesById = transformToMap(oldCommonEntries, 'id');
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ export class TransactionLandedCostEntriesService {
|
|||||||
|
|
||||||
if (oldEntry && oldEntry.allocatedCostAmount > entry.amount) {
|
if (oldEntry && oldEntry.allocatedCostAmount > entry.amount) {
|
||||||
throw new ServiceError(
|
throw new ServiceError(
|
||||||
ERRORS.LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES
|
ERRORS.LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
ILandedCostDTO,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
import { BaseLandedCostService } from '../BaseLandedCost.service';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { AllocateBillLandedCostDto } from '../dtos/AllocateBillLandedCost.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AllocateLandedCostService extends BaseLandedCostService {
|
||||||
|
constructor(
|
||||||
|
private readonly uow: UnitOfWork,
|
||||||
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
|
||||||
|
@Inject(Bill.name)
|
||||||
|
private readonly billModel: TenantModelProxy<typeof Bill>,
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =================================
|
||||||
|
* - Allocate landed cost.
|
||||||
|
* =================================
|
||||||
|
* - Validates the allocate cost not the same purchase invoice id.
|
||||||
|
* - Get the given bill (purchase invoice) or throw not found error.
|
||||||
|
* - Get the given landed cost transaction or throw not found error.
|
||||||
|
* - Validate landed cost transaction has enough unallocated cost amount.
|
||||||
|
* - Validate landed cost transaction entry has enough unallocated cost amount.
|
||||||
|
* - Validate allocate entries existance and associated with cost bill transaction.
|
||||||
|
* - Writes inventory landed cost transaction.
|
||||||
|
* - Increment the allocated landed cost transaction.
|
||||||
|
* - Increment the allocated landed cost transaction entry.
|
||||||
|
* --------------------------------
|
||||||
|
* @param {ILandedCostDTO} landedCostDTO - Landed cost DTO.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} billId - Purchase invoice id.
|
||||||
|
*/
|
||||||
|
public async allocateLandedCost(
|
||||||
|
allocateCostDTO: AllocateBillLandedCostDto,
|
||||||
|
billId: number,
|
||||||
|
): Promise<BillLandedCost> {
|
||||||
|
// Retrieve total cost of allocated items.
|
||||||
|
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
|
||||||
|
|
||||||
|
// Retrieve the purchase invoice or throw not found error.
|
||||||
|
const bill = await Bill.query()
|
||||||
|
.findById(billId)
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Retrieve landed cost transaction or throw not found service error.
|
||||||
|
const costTransaction = await this.getLandedCostOrThrowError(
|
||||||
|
allocateCostDTO.transactionType,
|
||||||
|
allocateCostDTO.transactionId,
|
||||||
|
);
|
||||||
|
// Retrieve landed cost transaction entries.
|
||||||
|
const costTransactionEntry = await this.getLandedCostEntry(
|
||||||
|
allocateCostDTO.transactionType,
|
||||||
|
allocateCostDTO.transactionId,
|
||||||
|
allocateCostDTO.transactionEntryId,
|
||||||
|
);
|
||||||
|
// Validates allocate cost items association with the purchase invoice entries.
|
||||||
|
this.validateAllocateCostItems(bill.entries, allocateCostDTO.items);
|
||||||
|
|
||||||
|
// Validate the amount of cost with unallocated landed cost.
|
||||||
|
this.validateLandedCostEntryAmount(
|
||||||
|
costTransactionEntry.unallocatedCostAmount,
|
||||||
|
amount,
|
||||||
|
);
|
||||||
|
// Transformes DTO to bill landed cost model object.
|
||||||
|
const billLandedCostObj = this.transformToBillLandedCost(
|
||||||
|
allocateCostDTO,
|
||||||
|
bill,
|
||||||
|
costTransaction,
|
||||||
|
costTransactionEntry,
|
||||||
|
);
|
||||||
|
// Saves landed cost transactions with associated tranasctions under
|
||||||
|
// unit-of-work eniverment.
|
||||||
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
// Save the bill landed cost model.
|
||||||
|
const billLandedCost =
|
||||||
|
await BillLandedCost.query(trx).insertGraph(billLandedCostObj);
|
||||||
|
// Triggers `onBillLandedCostCreated` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.billLandedCost.onCreated, {
|
||||||
|
bill,
|
||||||
|
billLandedCostId: billLandedCost.id,
|
||||||
|
billLandedCost,
|
||||||
|
costTransaction,
|
||||||
|
costTransactionEntry,
|
||||||
|
trx,
|
||||||
|
} as IAllocatedLandedCostCreatedPayload);
|
||||||
|
|
||||||
|
return billLandedCost;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
import { IBillLandedCostTransaction } from '../types/BillLandedCosts.types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BillAllocatedLandedCostTransactions {
|
||||||
|
constructor(
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
|
|
||||||
|
@Inject(Bill.name)
|
||||||
|
private readonly billModel: TenantModelProxy<typeof Bill>,
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<
|
||||||
|
typeof BillLandedCost
|
||||||
|
>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the bill associated landed cost transactions.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} billId - Bill id.
|
||||||
|
* @return {Promise<IBillLandedCostTransaction>}
|
||||||
|
*/
|
||||||
|
public getBillLandedCostTransactions = async (
|
||||||
|
billId: number,
|
||||||
|
): Promise<IBillLandedCostTransaction> => {
|
||||||
|
// Retrieve the given bill id or throw not found service error.
|
||||||
|
const bill = await this.billModel()
|
||||||
|
.query()
|
||||||
|
.findById(billId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
// Retrieve the bill associated allocated landed cost with bill and expense entry.
|
||||||
|
const landedCostTransactions = await this.billLandedCostModel()
|
||||||
|
.query()
|
||||||
|
.where('bill_id', billId)
|
||||||
|
.withGraphFetched('allocateEntries')
|
||||||
|
.withGraphFetched('allocatedFromBillEntry.item')
|
||||||
|
.withGraphFetched('allocatedFromExpenseEntry.expenseAccount')
|
||||||
|
.withGraphFetched('bill');
|
||||||
|
|
||||||
|
const transactionsJson = this.i18nService.i18nApply(
|
||||||
|
[[qim.$each, 'allocationMethodFormatted']],
|
||||||
|
landedCostTransactions.map((a) => a.toJSON()),
|
||||||
|
tenantId,
|
||||||
|
);
|
||||||
|
return this.transformBillLandedCostTransactions(transactionsJson);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IBillLandedCostTransaction[]} landedCostTransactions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private transformBillLandedCostTransactions = (
|
||||||
|
landedCostTransactions: IBillLandedCostTransaction[],
|
||||||
|
) => {
|
||||||
|
return landedCostTransactions.map(this.transformBillLandedCostTransaction);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IBillLandedCostTransaction} transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private transformBillLandedCostTransaction = (
|
||||||
|
transaction: IBillLandedCostTransaction,
|
||||||
|
) => {
|
||||||
|
const getTransactionName = R.curry(this.condBillLandedTransactionName)(
|
||||||
|
transaction.fromTransactionType,
|
||||||
|
);
|
||||||
|
const getTransactionDesc = R.curry(
|
||||||
|
this.condBillLandedTransactionDescription,
|
||||||
|
)(transaction.fromTransactionType);
|
||||||
|
|
||||||
|
return {
|
||||||
|
formattedAmount: formatNumber(transaction.amount, {
|
||||||
|
currencyCode: transaction.currencyCode,
|
||||||
|
}),
|
||||||
|
...omit(transaction, [
|
||||||
|
'allocatedFromBillEntry',
|
||||||
|
'allocatedFromExpenseEntry',
|
||||||
|
]),
|
||||||
|
name: getTransactionName(transaction),
|
||||||
|
description: getTransactionDesc(transaction),
|
||||||
|
formattedLocalAmount: formatNumber(transaction.localAmount, {
|
||||||
|
currencyCode: 'USD',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve bill landed cost tranaction name based on the given transaction type.
|
||||||
|
* @param transactionType
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private condBillLandedTransactionName = (
|
||||||
|
transactionType: string,
|
||||||
|
transaction,
|
||||||
|
) => {
|
||||||
|
return R.cond([
|
||||||
|
[
|
||||||
|
R.always(R.equals(transactionType, 'Bill')),
|
||||||
|
this.getLandedBillTransactionName,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
R.always(R.equals(transactionType, 'Expense')),
|
||||||
|
this.getLandedExpenseTransactionName,
|
||||||
|
],
|
||||||
|
])(transaction);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedBillTransactionName = (transaction): string => {
|
||||||
|
return transaction.allocatedFromBillEntry.item.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedExpenseTransactionName = (transaction): string => {
|
||||||
|
return transaction.allocatedFromExpenseEntry.expenseAccount.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve landed cost.
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedBillTransactionDescription = (transaction): string => {
|
||||||
|
return transaction.allocatedFromBillEntry.description;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedExpenseTransactionDescription = (transaction): string => {
|
||||||
|
return transaction.allocatedFromExpenseEntry.description;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the bill landed cost transaction description based on transaction type.
|
||||||
|
* @param {string} tranasctionType
|
||||||
|
* @param transaction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private condBillLandedTransactionDescription = (
|
||||||
|
tranasctionType: string,
|
||||||
|
transaction,
|
||||||
|
) => {
|
||||||
|
return R.cond([
|
||||||
|
[
|
||||||
|
R.always(R.equals(tranasctionType, 'Bill')),
|
||||||
|
this.getLandedBillTransactionDescription,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
R.always(R.equals(tranasctionType, 'Expense')),
|
||||||
|
this.getLandedExpenseTransactionDescription,
|
||||||
|
],
|
||||||
|
])(transaction);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { BaseLandedCostService } from '../BaseLandedCost.service';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
|
||||||
|
import { ILedger, ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostGLEntries extends BaseLandedCostService {
|
||||||
|
constructor(
|
||||||
|
private readonly journalService: JournalPosterService,
|
||||||
|
private readonly ledgerRepository: LedgerRepository,
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL common entry.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getLandedCostGLCommonEntry = (
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
date: bill.billDate,
|
||||||
|
currencyCode: allocatedLandedCost.currencyCode,
|
||||||
|
exchangeRate: allocatedLandedCost.exchangeRate,
|
||||||
|
|
||||||
|
transactionType: 'LandedCost',
|
||||||
|
transactionId: allocatedLandedCost.id,
|
||||||
|
transactionNumber: bill.billNumber,
|
||||||
|
|
||||||
|
referenceNumber: bill.referenceNo,
|
||||||
|
|
||||||
|
credit: 0,
|
||||||
|
debit: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL inventory entry.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {IBillLandedCostEntry} allocatedEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getLandedCostGLInventoryEntry = (
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
allocatedEntry: BillLandedCostEntry
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonEntry = this.getLandedCostGLCommonEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...commonEntry,
|
||||||
|
debit: allocatedLandedCost.localAmount,
|
||||||
|
accountId: allocatedEntry.itemEntry.item.inventoryAccountId,
|
||||||
|
index: 1,
|
||||||
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL cost entry.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getLandedCostGLCostEntry = (
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
fromTransactionEntry: ILandedCostTransactionEntry
|
||||||
|
): ILedgerEntry => {
|
||||||
|
const commonEntry = this.getLandedCostGLCommonEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...commonEntry,
|
||||||
|
credit: allocatedLandedCost.localAmount,
|
||||||
|
accountId: fromTransactionEntry.costAccountId,
|
||||||
|
index: 2,
|
||||||
|
accountNormal: AccountNormal.CREDIT,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve allocated landed cost entry GL entries.
|
||||||
|
* @param {IBill} bill
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @param {IBillLandedCostEntry} allocatedEntry
|
||||||
|
* @returns {ILedgerEntry}
|
||||||
|
*/
|
||||||
|
private getLandedCostGLAllocateEntry = R.curry(
|
||||||
|
(
|
||||||
|
bill: Bill,
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
fromTransactionEntry: LandedCostTransactionEntry,
|
||||||
|
allocatedEntry: BillLandedCostEntry
|
||||||
|
): ILedgerEntry[] => {
|
||||||
|
const inventoryEntry = this.getLandedCostGLInventoryEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost,
|
||||||
|
allocatedEntry
|
||||||
|
);
|
||||||
|
const costEntry = this.getLandedCostGLCostEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
return [inventoryEntry, costEntry];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose the landed cost GL entries.
|
||||||
|
* @param {BillLandedCost} allocatedLandedCost
|
||||||
|
* @param {Bill} bill
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @returns {ILedgerEntry[]}
|
||||||
|
*/
|
||||||
|
public getLandedCostGLEntries = (
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
bill: Bill,
|
||||||
|
fromTransactionEntry: LandedCostTransactionEntry
|
||||||
|
): ILedgerEntry[] => {
|
||||||
|
const getEntry = this.getLandedCostGLAllocateEntry(
|
||||||
|
bill,
|
||||||
|
allocatedLandedCost,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
return allocatedLandedCost.allocateEntries.map(getEntry).flat();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the landed cost GL ledger.
|
||||||
|
* @param {IBillLandedCost} allocatedLandedCost
|
||||||
|
* @param {Bill} bill
|
||||||
|
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||||
|
* @returns {ILedger}
|
||||||
|
*/
|
||||||
|
public getLandedCostLedger = (
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
bill: Bill,
|
||||||
|
fromTransactionEntry: LandedCostTransactionEntry
|
||||||
|
): ILedger => {
|
||||||
|
const entries = this.getLandedCostGLEntries(
|
||||||
|
allocatedLandedCost,
|
||||||
|
bill,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
return new Ledger(entries);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes landed cost GL entries to the storage layer.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
*/
|
||||||
|
public writeLandedCostGLEntries = async (
|
||||||
|
allocatedLandedCost: BillLandedCost,
|
||||||
|
bill: Bill,
|
||||||
|
fromTransactionEntry: ILandedCostTransactionEntry,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
const ledgerEntries = this.getLandedCostGLEntries(
|
||||||
|
allocatedLandedCost,
|
||||||
|
bill,
|
||||||
|
fromTransactionEntry
|
||||||
|
);
|
||||||
|
await this.ledgerRepository.saveLedgerEntries(ledgerEntries, trx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and writes GL entries of the given landed cost.
|
||||||
|
* @param {number} billLandedCostId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public createLandedCostGLEntries = async (
|
||||||
|
billLandedCostId: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
// Retrieve the bill landed cost transacion with associated
|
||||||
|
// allocated entries and items.
|
||||||
|
const allocatedLandedCost = await this.billLandedCostModel().query(trx)
|
||||||
|
.findById(billLandedCostId)
|
||||||
|
.withGraphFetched('bill')
|
||||||
|
.withGraphFetched('allocateEntries.itemEntry.item');
|
||||||
|
|
||||||
|
// Retrieve the allocated from transactione entry.
|
||||||
|
const transactionEntry = await this.getLandedCostEntry(
|
||||||
|
allocatedLandedCost.fromTransactionType,
|
||||||
|
allocatedLandedCost.fromTransactionId,
|
||||||
|
allocatedLandedCost.fromTransactionEntryId
|
||||||
|
);
|
||||||
|
// Writes the given landed cost GL entries to the storage layer.
|
||||||
|
await this.writeLandedCostGLEntries(
|
||||||
|
allocatedLandedCost,
|
||||||
|
allocatedLandedCost.bill,
|
||||||
|
transactionEntry,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts GL entries of the given allocated landed cost transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} landedCostId
|
||||||
|
* @param {Knex.Transaction} trx
|
||||||
|
*/
|
||||||
|
public revertLandedCostGLEntries = async (
|
||||||
|
landedCostId: number,
|
||||||
|
trx: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
await this.journalService.revertJournalTransactions(
|
||||||
|
landedCostId,
|
||||||
|
'LandedCost',
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
IAllocatedLandedCostDeletedPayload,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import { LandedCostGLEntries } from './LandedCostGLEntries.service';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostGLEntriesSubscriber {
|
||||||
|
constructor(
|
||||||
|
private readonly billLandedCostGLEntries: LandedCostGLEntries,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes GL entries once landed cost transaction created.
|
||||||
|
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onCreated)
|
||||||
|
async writeGLEntriesOnceLandedCostCreated({
|
||||||
|
billLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostCreatedPayload) {
|
||||||
|
await this.billLandedCostGLEntries.createLandedCostGLEntries(
|
||||||
|
billLandedCost.id,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts GL entries associated to landed cost transaction once deleted.
|
||||||
|
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onDeleted)
|
||||||
|
async revertGLEnteriesOnceLandedCostDeleted({
|
||||||
|
oldBillLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostDeletedPayload) {
|
||||||
|
await this.billLandedCostGLEntries.revertLandedCostGLEntries(
|
||||||
|
oldBillLandedCost.id,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { IBillLandedCostTransaction } from '../types/BillLandedCosts.types';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { mergeLocatedWithBillEntries } from '../utils';
|
||||||
|
import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostInventoryTransactions {
|
||||||
|
constructor(
|
||||||
|
private readonly inventoryTransactionsService: InventoryTransactionsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records inventory transactions.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IBillLandedCostTransaction} billLandedCost
|
||||||
|
* @param {IBill} bill -
|
||||||
|
*/
|
||||||
|
public recordInventoryTransactions = async (
|
||||||
|
billLandedCost: IBillLandedCostTransaction,
|
||||||
|
bill: Bill,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) => {
|
||||||
|
// Retrieve the merged allocated entries with bill entries.
|
||||||
|
const allocateEntries = mergeLocatedWithBillEntries(
|
||||||
|
billLandedCost.allocateEntries,
|
||||||
|
bill.entries,
|
||||||
|
);
|
||||||
|
// Mappes the allocate cost entries to inventory transactions.
|
||||||
|
const inventoryTransactions = allocateEntries.map((allocateEntry) => ({
|
||||||
|
date: bill.billDate,
|
||||||
|
itemId: allocateEntry.entry.itemId,
|
||||||
|
direction: 'IN',
|
||||||
|
quantity: null,
|
||||||
|
rate: allocateEntry.cost,
|
||||||
|
transactionType: 'LandedCost',
|
||||||
|
transactionId: billLandedCost.id,
|
||||||
|
entryId: allocateEntry.entryId,
|
||||||
|
}));
|
||||||
|
// Writes inventory transactions.
|
||||||
|
return this.inventoryTransactionsService.recordInventoryTransactions(
|
||||||
|
inventoryTransactions,
|
||||||
|
false,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the inventory transaction.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} landedCostId - Landed cost id.
|
||||||
|
* @param {Knex.Transaction} trx - Knex transactions.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public removeInventoryTransactions = (
|
||||||
|
landedCostId: number,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
) => {
|
||||||
|
return this.inventoryTransactionsService.deleteInventoryTransactions(
|
||||||
|
landedCostId,
|
||||||
|
'LandedCost',
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
IAllocatedLandedCostDeletedPayload,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { LandedCostInventoryTransactions } from './LandedCostInventoryTransactions.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostInventoryTransactionsSubscriber {
|
||||||
|
constructor(
|
||||||
|
private readonly landedCostInventory: LandedCostInventoryTransactions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes inventory transactions of the landed cost transaction once created.
|
||||||
|
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onCreated)
|
||||||
|
async writeInventoryTransactionsOnceCreated({
|
||||||
|
billLandedCost,
|
||||||
|
trx,
|
||||||
|
bill,
|
||||||
|
}: IAllocatedLandedCostCreatedPayload) {
|
||||||
|
// Records the inventory transactions.
|
||||||
|
await this.landedCostInventory.recordInventoryTransactions(
|
||||||
|
billLandedCost,
|
||||||
|
bill,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts inventory transactions of the landed cost transaction once deleted.
|
||||||
|
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onDeleted)
|
||||||
|
async revertInventoryTransactionsOnceDeleted({
|
||||||
|
oldBillLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostDeletedPayload) {
|
||||||
|
// Removes the inventory transactions.
|
||||||
|
await this.landedCostInventory.removeInventoryTransactions(
|
||||||
|
oldBillLandedCost.id,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { CONFIG } from '../utils';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TransactionLandedCost } from './TransctionLandedCost.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostSyncCostTransactions {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionLandedCost: TransactionLandedCost,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate the landed cost amount to cost transactions.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {string} transactionType
|
||||||
|
* @param {number} transactionId
|
||||||
|
*/
|
||||||
|
public incrementLandedCostAmount = async (
|
||||||
|
transactionType: string,
|
||||||
|
transactionId: number,
|
||||||
|
transactionEntryId: number,
|
||||||
|
amount: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
): Promise<void> => {
|
||||||
|
const Model = this.transactionLandedCost.getModel(
|
||||||
|
transactionType
|
||||||
|
);
|
||||||
|
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||||
|
|
||||||
|
// Increment the landed cost transaction amount.
|
||||||
|
await Model.query(trx)
|
||||||
|
.where('id', transactionId)
|
||||||
|
.increment('allocatedCostAmount', amount);
|
||||||
|
|
||||||
|
// Increment the landed cost entry.
|
||||||
|
await Model.relatedQuery(relation, trx)
|
||||||
|
.for(transactionId)
|
||||||
|
.where('id', transactionEntryId)
|
||||||
|
.increment('allocatedCostAmount', amount);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts the landed cost amount to cost transaction.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {string} transactionType - Transaction type.
|
||||||
|
* @param {number} transactionId - Transaction id.
|
||||||
|
* @param {number} transactionEntryId - Transaction entry id.
|
||||||
|
* @param {number} amount - Amount
|
||||||
|
*/
|
||||||
|
public revertLandedCostAmount = async (
|
||||||
|
transactionType: string,
|
||||||
|
transactionId: number,
|
||||||
|
transactionEntryId: number,
|
||||||
|
amount: number,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) => {
|
||||||
|
const Model = this.transactionLandedCost.getModel(
|
||||||
|
transactionType
|
||||||
|
);
|
||||||
|
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||||
|
|
||||||
|
// Decrement the allocate cost amount of cost transaction.
|
||||||
|
await Model.query(trx)
|
||||||
|
.where('id', transactionId)
|
||||||
|
.decrement('allocatedCostAmount', amount);
|
||||||
|
|
||||||
|
// Decrement the allocated cost amount cost transaction entry.
|
||||||
|
await Model.relatedQuery(relation, trx)
|
||||||
|
.for(transactionId)
|
||||||
|
.where('id', transactionEntryId)
|
||||||
|
.decrement('allocatedCostAmount', amount);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
IAllocatedLandedCostCreatedPayload,
|
||||||
|
IAllocatedLandedCostDeletedPayload,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { LandedCostSyncCostTransactions } from './LandedCostSyncCostTransactions.service';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LandedCostSyncCostTransactionsSubscriber {
|
||||||
|
constructor(
|
||||||
|
private landedCostSyncCostTransaction: LandedCostSyncCostTransactions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment cost transactions once the landed cost allocated.
|
||||||
|
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onCreated)
|
||||||
|
async incrementCostTransactionsOnceCreated({
|
||||||
|
billLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostCreatedPayload) {
|
||||||
|
// Increment landed cost amount on transaction and entry.
|
||||||
|
await this.landedCostSyncCostTransaction.incrementLandedCostAmount(
|
||||||
|
billLandedCost.fromTransactionType,
|
||||||
|
billLandedCost.fromTransactionId,
|
||||||
|
billLandedCost.fromTransactionEntryId,
|
||||||
|
billLandedCost.amount,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrement cost transactions once the allocated landed cost reverted.
|
||||||
|
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||||
|
*/
|
||||||
|
@OnEvent(events.billLandedCost.onDeleted)
|
||||||
|
async decrementCostTransactionsOnceDeleted({
|
||||||
|
oldBillLandedCost,
|
||||||
|
trx,
|
||||||
|
}: IAllocatedLandedCostDeletedPayload) {
|
||||||
|
// Reverts the landed cost amount to the cost transaction.
|
||||||
|
await this.landedCostSyncCostTransaction.revertLandedCostAmount(
|
||||||
|
oldBillLandedCost.fromTransactionType,
|
||||||
|
oldBillLandedCost.fromTransactionId,
|
||||||
|
oldBillLandedCost.fromTransactionEntryId,
|
||||||
|
oldBillLandedCost.amount,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { ref } from 'objection';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import {
|
||||||
|
ILandedCostTransactionsQueryDTO,
|
||||||
|
ILandedCostTransaction,
|
||||||
|
ILandedCostTransactionDOJO,
|
||||||
|
ILandedCostTransactionEntry,
|
||||||
|
ILandedCostTransactionEntryDOJO,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import TransactionLandedCost from './TransctionLandedCost';
|
||||||
|
import { formatNumber } from 'utils';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class LandedCostTranasctions {
|
||||||
|
@Inject()
|
||||||
|
private transactionLandedCost: TransactionLandedCost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the landed costs based on the given query.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ILandedCostTransactionsQueryDTO} query
|
||||||
|
* @returns {Promise<ILandedCostTransaction[]>}
|
||||||
|
*/
|
||||||
|
public getLandedCostTransactions = async (
|
||||||
|
query: ILandedCostTransactionsQueryDTO
|
||||||
|
): Promise<ILandedCostTransaction[]> => {
|
||||||
|
const { transactionType } = query;
|
||||||
|
const Model = this.transactionLandedCost.getModel(
|
||||||
|
query.transactionType
|
||||||
|
);
|
||||||
|
// Retrieve the model entities.
|
||||||
|
const transactions = await Model.query().onBuild((q) => {
|
||||||
|
q.where('allocated_cost_amount', '<', ref('landed_cost_amount'));
|
||||||
|
|
||||||
|
if (query.transactionType === 'Bill') {
|
||||||
|
q.withGraphFetched('entries.item');
|
||||||
|
} else if (query.transactionType === 'Expense') {
|
||||||
|
q.withGraphFetched('categories.expenseAccount');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const transformLandedCost =
|
||||||
|
this.transactionLandedCost.transformToLandedCost(transactionType);
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
this.transformLandedCostTransactions,
|
||||||
|
R.map(transformLandedCost)
|
||||||
|
)(transactions);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param transactions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public transformLandedCostTransactions = (
|
||||||
|
transactions: ILandedCostTransaction[]
|
||||||
|
) => {
|
||||||
|
return R.map(this.transformLandedCostTransaction)(transactions);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the landed cost transaction.
|
||||||
|
* @param {ILandedCostTransaction} transaction
|
||||||
|
*/
|
||||||
|
public transformLandedCostTransaction = (
|
||||||
|
transaction: ILandedCostTransaction
|
||||||
|
): ILandedCostTransactionDOJO => {
|
||||||
|
const { currencyCode } = transaction;
|
||||||
|
|
||||||
|
// Formatted transaction amount.
|
||||||
|
const formattedAmount = formatNumber(transaction.amount, { currencyCode });
|
||||||
|
|
||||||
|
// Formatted transaction unallocated cost amount.
|
||||||
|
const formattedUnallocatedCostAmount = formatNumber(
|
||||||
|
transaction.unallocatedCostAmount,
|
||||||
|
{ currencyCode }
|
||||||
|
);
|
||||||
|
// Formatted transaction allocated cost amount.
|
||||||
|
const formattedAllocatedCostAmount = formatNumber(
|
||||||
|
transaction.allocatedCostAmount,
|
||||||
|
{ currencyCode }
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...transaction,
|
||||||
|
formattedAmount,
|
||||||
|
formattedUnallocatedCostAmount,
|
||||||
|
formattedAllocatedCostAmount,
|
||||||
|
entries: R.map(this.transformLandedCostEntry(transaction))(
|
||||||
|
transaction.entries
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {ILandedCostTransaction} transaction
|
||||||
|
* @param {ILandedCostTransactionEntry} entry
|
||||||
|
* @returns {ILandedCostTransactionEntryDOJO}
|
||||||
|
*/
|
||||||
|
public transformLandedCostEntry = R.curry(
|
||||||
|
(
|
||||||
|
transaction: ILandedCostTransaction,
|
||||||
|
entry: ILandedCostTransactionEntry
|
||||||
|
): ILandedCostTransactionEntryDOJO => {
|
||||||
|
const { currencyCode } = transaction;
|
||||||
|
|
||||||
|
// Formatted entry amount.
|
||||||
|
const formattedAmount = formatNumber(entry.amount, { currencyCode });
|
||||||
|
|
||||||
|
// Formatted entry unallocated cost amount.
|
||||||
|
const formattedUnallocatedCostAmount = formatNumber(
|
||||||
|
entry.unallocatedCostAmount,
|
||||||
|
{ currencyCode }
|
||||||
|
);
|
||||||
|
// Formatted entry allocated cost amount.
|
||||||
|
const formattedAllocatedCostAmount = formatNumber(
|
||||||
|
entry.allocatedCostAmount,
|
||||||
|
{ currencyCode }
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...entry,
|
||||||
|
formattedAmount,
|
||||||
|
formattedUnallocatedCostAmount,
|
||||||
|
formattedAllocatedCostAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { BaseLandedCostService } from '../BaseLandedCost.service';
|
||||||
|
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { IAllocatedLandedCostDeletedPayload } from '../types/BillLandedCosts.types';
|
||||||
|
import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RevertAllocatedLandedCost extends BaseLandedCostService {
|
||||||
|
constructor(
|
||||||
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
private readonly uow: UnitOfWork,
|
||||||
|
|
||||||
|
@Inject(BillLandedCost.name)
|
||||||
|
private readonly billLandedCostModel: TenantModelProxy<
|
||||||
|
typeof BillLandedCost
|
||||||
|
>,
|
||||||
|
|
||||||
|
@Inject(BillLandedCostEntry.name)
|
||||||
|
private readonly billLandedCostEntryModel: TenantModelProxy<
|
||||||
|
typeof BillLandedCostEntry
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the allocated landed cost.
|
||||||
|
* ==================================
|
||||||
|
* - Delete bill landed cost transaction with associated allocate entries.
|
||||||
|
* - Delete the associated inventory transactions.
|
||||||
|
* - Decrement allocated amount of landed cost transaction and entry.
|
||||||
|
* - Revert journal entries.
|
||||||
|
* ----------------------------------
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} landedCostId - Landed cost id.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async deleteAllocatedLandedCost(landedCostId: number): Promise<{
|
||||||
|
landedCostId: number;
|
||||||
|
}> {
|
||||||
|
// Retrieves the bill landed cost.
|
||||||
|
const oldBillLandedCost =
|
||||||
|
await this.getBillLandedCostOrThrowError(landedCostId);
|
||||||
|
// Deletes landed cost with associated transactions.
|
||||||
|
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
|
// Delete landed cost transaction with associated locate entries.
|
||||||
|
await this.deleteLandedCost(landedCostId, trx);
|
||||||
|
|
||||||
|
// Triggers the event `onBillLandedCostCreated`.
|
||||||
|
await this.eventPublisher.emitAsync(events.billLandedCost.onDeleted, {
|
||||||
|
oldBillLandedCost: oldBillLandedCost,
|
||||||
|
billId: oldBillLandedCost.billId,
|
||||||
|
trx,
|
||||||
|
} as IAllocatedLandedCostDeletedPayload);
|
||||||
|
|
||||||
|
return { landedCostId };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the landed cost transaction with associated allocate entries.
|
||||||
|
* @param {number} landedCostId - Landed cost id.
|
||||||
|
*/
|
||||||
|
public deleteLandedCost = async (
|
||||||
|
landedCostId: number,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> => {
|
||||||
|
// Deletes the bill landed cost allocated entries associated to landed cost.
|
||||||
|
await this.billLandedCostEntryModel()
|
||||||
|
.query(trx)
|
||||||
|
.where('bill_located_cost_id', landedCostId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Delete the bill landed cost from the storage.
|
||||||
|
await this.billLandedCostModel()
|
||||||
|
.query(trx)
|
||||||
|
.where('id', landedCostId)
|
||||||
|
.delete();
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { Model } from 'objection';
|
||||||
|
import {
|
||||||
|
ILandedCostTransaction,
|
||||||
|
ILandedCostTransactionEntry,
|
||||||
|
} from '../types/BillLandedCosts.types';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { BillLandedCost } from '../models/BillLandedCost';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { Expense } from '@/modules/Expenses/models/Expense.model';
|
||||||
|
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||||
|
import { ERRORS } from '../utils';
|
||||||
|
import { ExpenseLandedCost } from '../models/ExpenseLandedCost';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionLandedCost {
|
||||||
|
constructor(
|
||||||
|
private readonly billLandedCost: BillLandedCost,
|
||||||
|
private readonly expenseLandedCost: ExpenseLandedCost,
|
||||||
|
) {}
|
||||||
|
/**
|
||||||
|
* Retrieve the cost transaction code model.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {string} transactionType - Transaction type.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getModel = (tenantId: number, transactionType: string): Model => {
|
||||||
|
const Models = this.tenancy.models(tenantId);
|
||||||
|
const Model = Models[transactionType];
|
||||||
|
|
||||||
|
if (!Model) {
|
||||||
|
throw new ServiceError(ERRORS.COST_TYPE_UNDEFINED);
|
||||||
|
}
|
||||||
|
return Model;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappes the given expense or bill transaction to landed cost transaction.
|
||||||
|
* @param {string} transactionType - Transaction type.
|
||||||
|
* @param {IBill|IExpense} transaction - Expense or bill transaction.
|
||||||
|
* @returns {ILandedCostTransaction}
|
||||||
|
*/
|
||||||
|
public transformToLandedCost = R.curry(
|
||||||
|
(
|
||||||
|
transactionType: string,
|
||||||
|
transaction: Bill | Expense,
|
||||||
|
): ILandedCostTransaction => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
R.always(transactionType === 'Bill'),
|
||||||
|
this.billLandedCost.transformToLandedCost,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
R.always(transactionType === 'Expense'),
|
||||||
|
this.expenseLandedCost.transformToLandedCost,
|
||||||
|
),
|
||||||
|
)(transaction);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the given expense or bill entry to landed cost transaction entry.
|
||||||
|
* @param {string} transactionType
|
||||||
|
* @param {} transactionEntry
|
||||||
|
* @returns {ILandedCostTransactionEntry}
|
||||||
|
*/
|
||||||
|
public transformToLandedCostEntry = (
|
||||||
|
transactionType: 'Bill' | 'Expense',
|
||||||
|
transactionEntry,
|
||||||
|
): ILandedCostTransactionEntry => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
R.always(transactionType === 'Bill'),
|
||||||
|
this.billLandedCost.transformToLandedCostEntry,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
R.always(transactionType === 'Expense'),
|
||||||
|
this.expenseLandedCost.transformToLandedCostEntry,
|
||||||
|
),
|
||||||
|
)(transactionEntry);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
IsInt,
|
||||||
|
IsIn,
|
||||||
|
IsOptional,
|
||||||
|
IsArray,
|
||||||
|
ValidateNested,
|
||||||
|
IsDecimal,
|
||||||
|
IsString,
|
||||||
|
IsNumber,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { ToNumber } from '@/common/decorators/Validators';
|
||||||
|
|
||||||
|
class AllocateBillLandedCostItemDto {
|
||||||
|
@IsInt()
|
||||||
|
@ToNumber()
|
||||||
|
entryId: number;
|
||||||
|
|
||||||
|
@IsDecimal()
|
||||||
|
cost: string; // Use string for IsDecimal, or use @IsNumber() if you want a number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AllocateBillLandedCostDto {
|
||||||
|
@IsInt()
|
||||||
|
@ToNumber()
|
||||||
|
transactionId: number;
|
||||||
|
|
||||||
|
@IsIn(['Expense', 'Bill'])
|
||||||
|
transactionType: string;
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
transactionEntryId: number;
|
||||||
|
|
||||||
|
@IsIn(['value', 'quantity'])
|
||||||
|
allocationMethod: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string | null;
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => AllocateBillLandedCostItemDto)
|
||||||
|
items: AllocateBillLandedCostItemDto[];
|
||||||
|
}
|
||||||
@@ -2,6 +2,10 @@ import { Model } from 'objection';
|
|||||||
import { lowerCase } from 'lodash';
|
import { lowerCase } from 'lodash';
|
||||||
// import TenantModel from 'models/TenantModel';
|
// import TenantModel from 'models/TenantModel';
|
||||||
import { BaseModel } from '@/models/Model';
|
import { BaseModel } from '@/models/Model';
|
||||||
|
import { Bill } from '@/modules/Bills/models/Bill';
|
||||||
|
import { BillLandedCostEntry } from './BillLandedCostEntry';
|
||||||
|
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
||||||
|
import { ExpenseCategory } from '@/modules/Expenses/models/ExpenseCategory.model';
|
||||||
|
|
||||||
export class BillLandedCost extends BaseModel {
|
export class BillLandedCost extends BaseModel {
|
||||||
amount!: number;
|
amount!: number;
|
||||||
@@ -13,6 +17,12 @@ export class BillLandedCost extends BaseModel {
|
|||||||
description!: string;
|
description!: string;
|
||||||
billId!: number;
|
billId!: number;
|
||||||
exchangeRate!: number;
|
exchangeRate!: number;
|
||||||
|
currencyCode!: string;
|
||||||
|
|
||||||
|
bill!: Bill;
|
||||||
|
allocateEntries!: BillLandedCostEntry[];
|
||||||
|
allocatedFromBillEntry!: ItemEntry;
|
||||||
|
allocatedFromExpenseEntry!: ExpenseCategory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
|
|||||||
@@ -107,14 +107,12 @@ export interface IBillLandedCostTransactionEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IAllocatedLandedCostDeletedPayload {
|
export interface IAllocatedLandedCostDeletedPayload {
|
||||||
tenantId: number;
|
|
||||||
oldBillLandedCost: IBillLandedCostTransaction;
|
oldBillLandedCost: IBillLandedCostTransaction;
|
||||||
billId: number;
|
billId: number;
|
||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAllocatedLandedCostCreatedPayload {
|
export interface IAllocatedLandedCostCreatedPayload {
|
||||||
tenantId: number;
|
|
||||||
bill: Bill;
|
bill: Bill;
|
||||||
billLandedCostId: number;
|
billLandedCostId: number;
|
||||||
billLandedCost: IBillLandedCostTransaction;
|
billLandedCost: IBillLandedCostTransaction;
|
||||||
|
|||||||
46
packages/server/src/modules/BillLandedCosts/utils.ts
Normal file
46
packages/server/src/modules/BillLandedCosts/utils.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { IItemEntry, IBillLandedCostTransactionEntry } from '@/interfaces';
|
||||||
|
import { transformToMap } from 'utils';
|
||||||
|
|
||||||
|
export const ERRORS = {
|
||||||
|
COST_TYPE_UNDEFINED: 'COST_TYPE_UNDEFINED',
|
||||||
|
LANDED_COST_ITEMS_IDS_NOT_FOUND: 'LANDED_COST_ITEMS_IDS_NOT_FOUND',
|
||||||
|
COST_TRANSACTION_HAS_NO_ENOUGH_TO_LOCATE:
|
||||||
|
'COST_TRANSACTION_HAS_NO_ENOUGH_TO_LOCATE',
|
||||||
|
BILL_LANDED_COST_NOT_FOUND: 'BILL_LANDED_COST_NOT_FOUND',
|
||||||
|
COST_ENTRY_ID_NOT_FOUND: 'COST_ENTRY_ID_NOT_FOUND',
|
||||||
|
LANDED_COST_TRANSACTION_NOT_FOUND: 'LANDED_COST_TRANSACTION_NOT_FOUND',
|
||||||
|
LANDED_COST_ENTRY_NOT_FOUND: 'LANDED_COST_ENTRY_NOT_FOUND',
|
||||||
|
COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT:
|
||||||
|
'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT',
|
||||||
|
ALLOCATE_COST_SHOULD_NOT_BE_BILL: 'ALLOCATE_COST_SHOULD_NOT_BE_BILL',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges item entry to bill located landed cost entry.
|
||||||
|
* @param {IBillLandedCostTransactionEntry[]} locatedEntries -
|
||||||
|
* @param {IItemEntry[]} billEntries -
|
||||||
|
* @returns {(IBillLandedCostTransactionEntry & { entry: IItemEntry })[]}
|
||||||
|
*/
|
||||||
|
export const mergeLocatedWithBillEntries = (
|
||||||
|
locatedEntries: IBillLandedCostTransactionEntry[],
|
||||||
|
billEntries: IItemEntry[]
|
||||||
|
): (IBillLandedCostTransactionEntry & { entry: IItemEntry })[] => {
|
||||||
|
const billEntriesByEntryId = transformToMap(billEntries, 'id');
|
||||||
|
|
||||||
|
return locatedEntries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
entry: billEntriesByEntryId.get(entry.entryId),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const CONFIG = {
|
||||||
|
COST_TYPES: {
|
||||||
|
Expense: {
|
||||||
|
entries: 'categories',
|
||||||
|
},
|
||||||
|
Bill: {
|
||||||
|
entries: 'entries',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -10,7 +10,6 @@ import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
|||||||
import { Item } from '@/modules/Items/models/Item';
|
import { Item } from '@/modules/Items/models/Item';
|
||||||
import { Vendor } from '@/modules/Vendors/models/Vendor';
|
import { Vendor } from '@/modules/Vendors/models/Vendor';
|
||||||
import { ItemEntriesTaxTransactions } from '@/modules/TaxRates/ItemEntriesTaxTransactions.service';
|
import { ItemEntriesTaxTransactions } from '@/modules/TaxRates/ItemEntriesTaxTransactions.service';
|
||||||
import { IBillDTO } from '../Bills.types';
|
|
||||||
import { Bill } from '../models/Bill';
|
import { Bill } from '../models/Bill';
|
||||||
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
|
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
|
||||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
|||||||
Reference in New Issue
Block a user