refactor(nestjs): landed cost

This commit is contained in:
Ahmed Bouhuolia
2025-06-11 14:04:37 +02:00
parent 1130975efd
commit ff93168d72
28 changed files with 622 additions and 417 deletions

View File

@@ -1,11 +1,10 @@
import { Inject } from '@nestjs/common';
import { difference, sumBy } from 'lodash';
import {
ILandedCostItemDTO,
ILandedCostDTO,
IBillLandedCostTransaction,
ILandedCostTransaction,
ILandedCostTransactionEntry,
LandedCostTransactionModel,
LandedCostTransactionType,
} from './types/BillLandedCosts.types';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { BillLandedCost } from './models/BillLandedCost';
@@ -14,13 +13,19 @@ import { CONFIG, ERRORS } from './utils';
import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry';
import { Bill } from '../Bills/models/Bill';
import { TransactionLandedCost } from './commands/TransctionLandedCost.service';
import {
AllocateBillLandedCostDto,
AllocateBillLandedCostItemDto,
} from './dtos/AllocateBillLandedCost.dto';
export class BaseLandedCostService {
@Inject()
public readonly transactionLandedCost: TransactionLandedCost;
protected readonly transactionLandedCost: TransactionLandedCost;
@Inject(BillLandedCost.name)
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>;
protected readonly billLandedCostModel: TenantModelProxy<
typeof BillLandedCost
>;
/**
* Validates allocate cost items association with the purchase invoice entries.
@@ -29,7 +34,7 @@ export class BaseLandedCostService {
*/
protected validateAllocateCostItems = (
purchaseInvoiceEntries: ItemEntry[],
landedCostItems: ILandedCostItemDTO[],
landedCostItems: AllocateBillLandedCostItemDto[],
): void => {
// Purchase invoice entries items ids.
const purchaseInvoiceItems = purchaseInvoiceEntries.map((e) => e.id);
@@ -55,7 +60,7 @@ export class BaseLandedCostService {
* @returns
*/
protected transformToBillLandedCost(
landedCostDTO: ILandedCostDTO,
landedCostDTO: AllocateBillLandedCostDto,
bill: Bill,
costTransaction: ILandedCostTransaction,
costTransactionEntry: ILandedCostTransactionEntry,
@@ -88,20 +93,18 @@ export class BaseLandedCostService {
* @param {transactionId} transactionId -
*/
public getLandedCostOrThrowError = async (
transactionType: string,
transactionType: LandedCostTransactionType,
transactionId: number,
) => {
const Model = this.transactionLandedCost.getModel(
transactionType,
);
const model = await Model.query().findById(transactionId);
const Model = await 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,
model as LandedCostTransactionModel,
);
};
@@ -117,13 +120,11 @@ export class BaseLandedCostService {
transactionId: number,
transactionEntryId: number,
): Promise<any> => {
const Model = this.transactionLandedCost.getModel(
tenantId,
transactionType,
);
const Model = await this.transactionLandedCost.getModel(transactionType);
const relation = CONFIG.COST_TYPES[transactionType].entries;
const entry = await Model.relatedQuery(relation)
const entry = await Model()
.relatedQuery(relation)
.for(transactionId)
.findOne('id', transactionEntryId)
.where('landedCost', true)
@@ -139,7 +140,7 @@ export class BaseLandedCostService {
throw new ServiceError(ERRORS.LANDED_COST_ENTRY_NOT_FOUND);
}
return this.transactionLandedCost.transformToLandedCostEntry(
transactionType,
transactionType as LandedCostTransactionType,
entry,
);
};
@@ -150,7 +151,7 @@ export class BaseLandedCostService {
* @returns {number}
*/
protected getAllocateItemsCostTotal = (
landedCostDTO: ILandedCostDTO,
landedCostDTO: AllocateBillLandedCostDto,
): number => {
return sumBy(landedCostDTO.items, 'cost');
};

View File

@@ -1,25 +1,30 @@
import { Module } from '@nestjs/common';
import { forwardRef, Module } from '@nestjs/common';
import { TransactionLandedCostEntriesService } from './TransactionLandedCostEntries.service';
import { AllocateLandedCostService } from './commands/AllocateLandedCost.service';
import { LandedCostGLEntriesSubscriber } from './commands/LandedCostGLEntries.subscriber';
import { LandedCostGLEntries } from './commands/LandedCostGLEntries.service';
// 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 { LandedCostTranasctions } from './commands/LandedCostTransactions.service';
import { LandedCostInventoryTransactions } from './commands/LandedCostInventoryTransactions.service';
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
import { TransactionLandedCost } from './commands/TransctionLandedCost.service';
import { ExpenseLandedCost } from './commands/ExpenseLandedCost.service';
import { BillLandedCost } from './commands/BillLandedCost.service';
@Module({
imports: [InventoryCostModule],
imports: [forwardRef(() => InventoryCostModule)],
providers: [
AllocateLandedCostService,
TransactionLandedCostEntriesService,
BillAllocatedLandedCostTransactions,
LandedCostGLEntriesSubscriber,
LandedCostGLEntries,
TransactionLandedCost,
BillLandedCost,
ExpenseLandedCost,
LandedCostSyncCostTransactions,
RevertAllocatedLandedCost,
LandedCostInventoryTransactions,

View File

@@ -7,32 +7,45 @@ import {
Post,
Query,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
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';
import { LandedCostTransactionsQueryDto } from './dtos/LandedCostTransactionsQuery.dto';
@ApiTags('Landed Cost')
@Controller('landed-cost')
export class BillAllocateLandedCostController {
constructor(
private allocateLandedCost: AllocateLandedCostService,
private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions,
private revertAllocatedLandedCost: RevertAllocatedLandedCost,
private landedCostTranasctions: LandedCostTranasctions,
private landedCostTransactions: LandedCostTranasctions,
) {}
@Get('/transactions')
@ApiOperation({ summary: 'Get landed cost transactions' })
@ApiResponse({
status: 200,
description: 'List of landed cost transactions.',
})
async getLandedCostTransactions(
@Query('transaction_type') transactionType: string,
@Query() query: LandedCostTransactionsQueryDto,
) {
const transactions =
await this.landedCostTranasctions.getLandedCostTransactions(transactionType);
await this.landedCostTransactions.getLandedCostTransactions(query);
return transactions;
}
@Post('/bills/:billId/allocate')
@ApiOperation({ summary: 'Allocate landed cost to bill items' })
@ApiResponse({
status: 201,
description: 'Landed cost allocated successfully.',
})
public async calculateLandedCost(
@Param('billId') billId: number,
@Body() landedCostDTO: AllocateBillLandedCostDto,
@@ -48,37 +61,37 @@ export class BillAllocateLandedCostController {
}
@Delete('/:allocatedLandedCostId')
@ApiOperation({ summary: 'Delete allocated landed cost' })
@ApiResponse({
status: 200,
description: 'Allocated landed cost deleted successfully.',
})
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')
@ApiOperation({ summary: 'Get bill landed cost transactions' })
@ApiResponse({
status: 200,
description: 'List of bill landed cost transactions.',
})
async getBillLandedCostTransactions(@Param('billId') billId: number) {
const transactions =
const data =
await this.billAllocatedCostTransactions.getBillLandedCostTransactions(
billId,
);
return {
billId,
transactions,
data,
};
}
}

View File

@@ -23,7 +23,7 @@ export class AllocateLandedCostService extends BaseLandedCostService {
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(BillLandedCost.name)
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>
protected readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>
) {
super();
}
@@ -54,7 +54,7 @@ export class AllocateLandedCostService extends BaseLandedCostService {
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
// Retrieve the purchase invoice or throw not found error.
const bill = await Bill.query()
const bill = await this.billModel().query()
.findById(billId)
.withGraphFetched('entries')
.throwIfNotFound();

View File

@@ -6,6 +6,8 @@ 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';
import { ModelObject } from 'objection';
import { formatNumber } from '@/utils/format-number';
@Injectable()
export class BillAllocatedLandedCostTransactions {
@@ -23,19 +25,17 @@ export class BillAllocatedLandedCostTransactions {
/**
* 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> => {
): Promise<Array<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()
@@ -45,11 +45,8 @@ export class BillAllocatedLandedCostTransactions {
.withGraphFetched('allocatedFromExpenseEntry.expenseAccount')
.withGraphFetched('bill');
const transactionsJson = this.i18nService.i18nApply(
[[qim.$each, 'allocationMethodFormatted']],
landedCostTransactions.map((a) => a.toJSON()),
tenantId,
);
const transactionsJson = landedCostTransactions.map((a) => a.toJSON());
return this.transformBillLandedCostTransactions(transactionsJson);
};
@@ -59,7 +56,7 @@ export class BillAllocatedLandedCostTransactions {
* @returns
*/
private transformBillLandedCostTransactions = (
landedCostTransactions: IBillLandedCostTransaction[],
landedCostTransactions: ModelObject<BillLandedCost>[],
) => {
return landedCostTransactions.map(this.transformBillLandedCostTransaction);
};
@@ -70,15 +67,16 @@ export class BillAllocatedLandedCostTransactions {
* @returns
*/
private transformBillLandedCostTransaction = (
transaction: IBillLandedCostTransaction,
) => {
const getTransactionName = R.curry(this.condBillLandedTransactionName)(
transaction: ModelObject<BillLandedCost>,
): IBillLandedCostTransaction => {
const name = this.condBillLandedTransactionName(
transaction.fromTransactionType,
transaction,
);
const description = this.condBillLandedTransactionDescription(
transaction.fromTransactionType,
transaction,
);
const getTransactionDesc = R.curry(
this.condBillLandedTransactionDescription,
)(transaction.fromTransactionType);
return {
formattedAmount: formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode,
@@ -87,8 +85,8 @@ export class BillAllocatedLandedCostTransactions {
'allocatedFromBillEntry',
'allocatedFromExpenseEntry',
]),
name: getTransactionName(transaction),
description: getTransactionDesc(transaction),
name,
description,
formattedLocalAmount: formatNumber(transaction.localAmount, {
currencyCode: 'USD',
}),

View File

@@ -0,0 +1,61 @@
import { isEmpty } from 'lodash';
import {
ILandedCostTransactionEntry,
ILandedCostTransaction,
} from '../types/BillLandedCosts.types';
import { Injectable } from '@nestjs/common';
import { Bill } from '@/modules/Bills/models/Bill';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Item } from '@/modules/Items/models/Item';
import { ModelObject } from 'objection';
@Injectable()
export class BillLandedCost {
/**
* Retrieve the landed cost transaction from the given bill transaction.
* @param {IBill} bill - Bill transaction.
* @returns {ILandedCostTransaction} - Landed cost transaction.
*/
public transformToLandedCost = (
bill: ModelObject<Bill>,
): ILandedCostTransaction => {
const name = bill.billNumber || bill.referenceNo;
return {
id: bill.id,
name,
allocatedCostAmount: bill.allocatedCostAmount,
amount: bill.landedCostAmount,
unallocatedCostAmount: bill.unallocatedCostAmount,
transactionType: 'Bill',
currencyCode: bill.currencyCode,
exchangeRate: bill.exchangeRate,
...(!isEmpty(bill.entries) && {
entries: bill.entries.map(this.transformToLandedCostEntry),
}),
};
};
/**
* Transformes bill entry to landed cost entry.
* @param {IBill} bill - Bill model.
* @param {IItemEntry} billEntry - Bill entry.
* @return {ILandedCostTransactionEntry}
*/
public transformToLandedCostEntry(
billEntry: ItemEntry & { item: Item },
): ILandedCostTransactionEntry {
return {
id: billEntry.id,
name: billEntry.item.name,
code: billEntry.item.code,
amount: billEntry.amount,
unallocatedCostAmount: billEntry.unallocatedCostAmount,
allocatedCostAmount: billEntry.allocatedCostAmount,
description: billEntry.description,
costAccountId: billEntry.costAccountId || billEntry.item.costAccountId,
};
}
}

View File

@@ -0,0 +1,59 @@
import { Expense } from '@/modules/Expenses/models/Expense.model';
import { Injectable } from '@nestjs/common';
import { isEmpty } from 'lodash';
import { ModelObject } from 'objection';
import {
ILandedCostTransaction,
ILandedCostTransactionEntry,
} from '../types/BillLandedCosts.types';
import { ExpenseCategory } from '@/modules/Expenses/models/ExpenseCategory.model';
import { Account } from '@/modules/Accounts/models/Account.model';
@Injectable()
export class ExpenseLandedCost {
/**
* Retrieve the landed cost transaction from the given expense transaction.
* @param {IExpense} expense
* @returns {ILandedCostTransaction}
*/
public transformToLandedCost = (
expense: ModelObject<Expense>,
): ILandedCostTransaction => {
const name = 'EXP-100';
return {
id: expense.id,
name,
amount: expense.landedCostAmount,
allocatedCostAmount: expense.allocatedCostAmount,
unallocatedCostAmount: expense.unallocatedCostAmount,
transactionType: 'Expense',
currencyCode: expense.currencyCode,
exchangeRate: expense.exchangeRate || 1,
...(!isEmpty(expense.categories) && {
entries: expense.categories.map(this.transformToLandedCostEntry),
}),
};
};
/**
* Transformes expense entry to landed cost entry.
* @param {IExpenseCategory & { expenseAccount: IAccount }} expenseEntry -
* @return {ILandedCostTransactionEntry}
*/
public transformToLandedCostEntry = (
expenseEntry: ExpenseCategory & { expenseAccount: Account },
): ILandedCostTransactionEntry => {
return {
id: expenseEntry.id,
name: expenseEntry.expenseAccount.name,
code: expenseEntry.expenseAccount.code,
amount: expenseEntry.amount,
description: expenseEntry.description,
allocatedCostAmount: expenseEntry.allocatedCostAmount,
unallocatedCostAmount: expenseEntry.unallocatedCostAmount,
costAccountId: expenseEntry.expenseAccount.id,
};
};
}

View File

@@ -1,234 +1,236 @@
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';
// 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';
// import { AccountNormal } from '@/interfaces/Account';
// import { ILandedCostTransactionEntry } from '../types/BillLandedCosts.types';
@Injectable()
export class LandedCostGLEntries extends BaseLandedCostService {
constructor(
private readonly journalService: JournalPosterService,
private readonly ledgerRepository: LedgerRepository,
// @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();
}
// @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,
// /**
// * 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,
// transactionType: 'LandedCost',
// transactionId: allocatedLandedCost.id,
// transactionNumber: bill.billNumber,
referenceNumber: bill.referenceNo,
// referenceNumber: bill.referenceNo,
credit: 0,
debit: 0,
};
};
// 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 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,
};
};
// /**
// * 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];
}
);
// /**
// * 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: ILandedCostTransactionEntry,
// 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();
};
// /**
// * Compose the landed cost GL entries.
// * @param {BillLandedCost} allocatedLandedCost
// * @param {Bill} bill
// * @param {ILandedCostTransactionEntry} fromTransactionEntry
// * @returns {ILedgerEntry[]}
// */
// public getLandedCostGLEntries = (
// allocatedLandedCost: BillLandedCost,
// bill: Bill,
// fromTransactionEntry: ILandedCostTransactionEntry
// ): 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);
};
// /**
// * Retrieves the landed cost GL ledger.
// * @param {BillLandedCost} allocatedLandedCost
// * @param {Bill} bill
// * @param {ILandedCostTransactionEntry} fromTransactionEntry
// * @returns {ILedger}
// */
// public getLandedCostLedger = (
// allocatedLandedCost: BillLandedCost,
// bill: Bill,
// fromTransactionEntry: ILandedCostTransactionEntry
// ): 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);
};
// /**
// * 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');
// /**
// * 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
);
};
// // 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
);
};
}
// /**
// * 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
// );
// };
// }

View File

@@ -3,15 +3,14 @@ import {
IAllocatedLandedCostDeletedPayload,
} from '../types/BillLandedCosts.types';
import { OnEvent } from '@nestjs/event-emitter';
import { LandedCostGLEntries } from './LandedCostGLEntries.service';
// 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,
) {}
constructor() // private readonly billLandedCostGLEntries: LandedCostGLEntries,
{}
/**
* Writes GL entries once landed cost transaction created.
@@ -22,11 +21,11 @@ export class LandedCostGLEntriesSubscriber {
billLandedCost,
trx,
}: IAllocatedLandedCostCreatedPayload) {
await this.billLandedCostGLEntries.createLandedCostGLEntries(
billLandedCost.id,
trx
);
};
// await this.billLandedCostGLEntries.createLandedCostGLEntries(
// billLandedCost.id,
// trx
// );
}
/**
* Reverts GL entries associated to landed cost transaction once deleted.
@@ -37,9 +36,9 @@ export class LandedCostGLEntriesSubscriber {
oldBillLandedCost,
trx,
}: IAllocatedLandedCostDeletedPayload) {
await this.billLandedCostGLEntries.revertLandedCostGLEntries(
oldBillLandedCost.id,
trx
);
};
}
// await this.billLandedCostGLEntries.revertLandedCostGLEntries(
// oldBillLandedCost.id,
// trx
// );
}
}

View File

@@ -11,9 +11,8 @@ export class LandedCostSyncCostTransactions {
/**
* Allocate the landed cost amount to cost transactions.
* @param {number} tenantId -
* @param {string} transactionType
* @param {number} transactionId
* @param {string} transactionType - Transaction type.
* @param {number} transactionId - Transaction id.
*/
public incrementLandedCostAmount = async (
transactionType: string,
@@ -22,18 +21,18 @@ export class LandedCostSyncCostTransactions {
amount: number,
trx?: Knex.Transaction
): Promise<void> => {
const Model = this.transactionLandedCost.getModel(
const Model = await this.transactionLandedCost.getModel(
transactionType
);
const relation = CONFIG.COST_TYPES[transactionType].entries;
// Increment the landed cost transaction amount.
await Model.query(trx)
await Model().query(trx)
.where('id', transactionId)
.increment('allocatedCostAmount', amount);
// Increment the landed cost entry.
await Model.relatedQuery(relation, trx)
await Model().relatedQuery(relation, trx)
.for(transactionId)
.where('id', transactionEntryId)
.increment('allocatedCostAmount', amount);
@@ -54,18 +53,18 @@ export class LandedCostSyncCostTransactions {
amount: number,
trx?: Knex.Transaction
) => {
const Model = this.transactionLandedCost.getModel(
const Model = await this.transactionLandedCost.getModel(
transactionType
);
const relation = CONFIG.COST_TYPES[transactionType].entries;
// Decrement the allocate cost amount of cost transaction.
await Model.query(trx)
await Model().query(trx)
.where('id', transactionId)
.decrement('allocatedCostAmount', amount);
// Decrement the allocated cost amount cost transaction entry.
await Model.relatedQuery(relation, trx)
await Model().relatedQuery(relation, trx)
.for(transactionId)
.where('id', transactionEntryId)
.decrement('allocatedCostAmount', amount);

View File

@@ -1,70 +1,73 @@
import { Inject, Service } from 'typedi';
import { Injectable } from '@nestjs/common';
import { ref } from 'objection';
import { curry, pipe, map } from 'lodash/fp';
import * as R from 'ramda';
import {
ILandedCostTransactionsQueryDTO,
ILandedCostTransaction,
ILandedCostTransactionDOJO,
ILandedCostTransactionEntry,
ILandedCostTransactionEntryDOJO,
} from '@/interfaces';
import TransactionLandedCost from './TransctionLandedCost';
import { formatNumber } from 'utils';
} from '../types/BillLandedCosts.types';
import { TransactionLandedCost } from './TransctionLandedCost.service';
import { formatNumber } from '@/utils/format-number';
import { LandedCostTransactionsQueryDto } from '../dtos/LandedCostTransactionsQuery.dto';
@Service()
export default class LandedCostTranasctions {
@Inject()
private transactionLandedCost: TransactionLandedCost;
@Injectable()
export class LandedCostTranasctions {
constructor(private readonly transactionLandedCost: TransactionLandedCost) {}
/**
* Retrieve the landed costs based on the given query.
* @param {number} tenantId
* @param {ILandedCostTransactionsQueryDTO} query
* @param {LandedCostTransactionsQueryDto} query -
* @returns {Promise<ILandedCostTransaction[]>}
*/
public getLandedCostTransactions = async (
query: ILandedCostTransactionsQueryDTO
query: LandedCostTransactionsQueryDto,
): Promise<ILandedCostTransaction[]> => {
const { transactionType } = query;
const Model = this.transactionLandedCost.getModel(
query.transactionType
const Model = await 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'));
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);
if (query.transactionType === 'Bill') {
q.withGraphFetched('entries.item');
} else if (query.transactionType === 'Expense') {
q.withGraphFetched('categories.expenseAccount');
}
});
const transformLandedCost = curry(
this.transactionLandedCost.transformToLandedCost,
)(transactionType);
return R.compose(
return pipe(
this.transformLandedCostTransactions,
R.map(transformLandedCost)
R.map(transformLandedCost),
)(transactions);
};
/**
*
* @param transactions
* @returns
* Transformes the landed cost transactions.
* @param {ILandedCostTransaction[]} transactions
* @returns {ILandedCostTransactionDOJO[]}
*/
public transformLandedCostTransactions = (
transactions: ILandedCostTransaction[]
transactions: ILandedCostTransaction[],
) => {
return R.map(this.transformLandedCostTransaction)(transactions);
};
/**
* Transformes the landed cost transaction.
* @param {ILandedCostTransaction} transaction
* @param {ILandedCostTransaction} transaction - Landed cost transaction.
* @returns {ILandedCostTransactionDOJO}
*/
public transformLandedCostTransaction = (
transaction: ILandedCostTransaction
transaction: ILandedCostTransaction,
): ILandedCostTransactionDOJO => {
const { currencyCode } = transaction;
@@ -74,57 +77,60 @@ export default class LandedCostTranasctions {
// Formatted transaction unallocated cost amount.
const formattedUnallocatedCostAmount = formatNumber(
transaction.unallocatedCostAmount,
{ currencyCode }
{ currencyCode },
);
// Formatted transaction allocated cost amount.
const formattedAllocatedCostAmount = formatNumber(
transaction.allocatedCostAmount,
{ currencyCode }
{ currencyCode },
);
const transformLandedCostEntry = R.curry(this.transformLandedCostEntry)(
transaction,
);
const entries = R.map<
ILandedCostTransactionEntry,
ILandedCostTransactionEntryDOJO
>(transformLandedCostEntry)(transaction.entries);
return {
...transaction,
formattedAmount,
formattedUnallocatedCostAmount,
formattedAllocatedCostAmount,
entries: R.map(this.transformLandedCostEntry(transaction))(
transaction.entries
),
entries,
};
};
/**
*
* @param {ILandedCostTransaction} transaction
* @param {ILandedCostTransactionEntry} entry
* Transformes the landed cost transaction entry.
* @param {ILandedCostTransaction} transaction - Landed cost transaction.
* @param {ILandedCostTransactionEntry} entry - Landed cost transaction entry.
* @returns {ILandedCostTransactionEntryDOJO}
*/
public transformLandedCostEntry = R.curry(
(
transaction: ILandedCostTransaction,
entry: ILandedCostTransactionEntry
): ILandedCostTransactionEntryDOJO => {
const { currencyCode } = transaction;
public transformLandedCostEntry = (
transaction: ILandedCostTransaction,
entry: ILandedCostTransactionEntry,
): ILandedCostTransactionEntryDOJO => {
const { currencyCode } = transaction;
// Formatted entry amount.
const formattedAmount = formatNumber(entry.amount, { currencyCode });
// 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,
};
}
);
}
// 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,
};
};
}

View File

@@ -7,7 +7,6 @@ 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 {
@@ -15,11 +14,6 @@ export class RevertAllocatedLandedCost extends BaseLandedCostService {
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

View File

@@ -3,35 +3,46 @@ import { Model } from 'objection';
import {
ILandedCostTransaction,
ILandedCostTransactionEntry,
LandedCostTransactionModel,
LandedCostTransactionType,
} 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 { ContextIdFactory, ModuleRef } from '@nestjs/core';
import { sanitizeModelName } from '@/utils/sanitize-model-name';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ExpenseLandedCost } from './ExpenseLandedCost.service';
import { BillLandedCost } from './BillLandedCost.service';
import { ERRORS } from '../utils';
import { ExpenseLandedCost } from '../models/ExpenseLandedCost';
@Injectable()
export class TransactionLandedCost {
constructor(
private readonly billLandedCost: BillLandedCost,
private readonly expenseLandedCost: ExpenseLandedCost,
private readonly moduleRef: ModuleRef,
) {}
/**
* 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];
public getModel = async (
transactionType: string,
): Promise<TenantModelProxy<typeof Model>> => {
const contextId = ContextIdFactory.create();
const modelName = sanitizeModelName(transactionType);
if (!Model) {
const instance = await this.moduleRef.resolve(modelName, contextId, {
strict: false,
});
if (!instance) {
throw new ServiceError(ERRORS.COST_TYPE_UNDEFINED);
}
return Model;
return instance;
};
/**
@@ -40,10 +51,10 @@ export class TransactionLandedCost {
* @param {IBill|IExpense} transaction - Expense or bill transaction.
* @returns {ILandedCostTransaction}
*/
public transformToLandedCost = R.curry(
public transformToLandedCost =
(
transactionType: string,
transaction: Bill | Expense,
transactionType: LandedCostTransactionType,
transaction: LandedCostTransactionModel,
): ILandedCostTransaction => {
return R.compose(
R.when(
@@ -54,9 +65,8 @@ export class TransactionLandedCost {
R.always(transactionType === 'Expense'),
this.expenseLandedCost.transformToLandedCost,
),
)(transaction);
},
);
)(transaction) as ILandedCostTransaction;
};
/**
* Transformes the given expense or bill entry to landed cost transaction entry.
@@ -65,7 +75,7 @@ export class TransactionLandedCost {
* @returns {ILandedCostTransactionEntry}
*/
public transformToLandedCostEntry = (
transactionType: 'Bill' | 'Expense',
transactionType: LandedCostTransactionType,
transactionEntry,
): ILandedCostTransactionEntry => {
return R.compose(
@@ -77,6 +87,6 @@ export class TransactionLandedCost {
R.always(transactionType === 'Expense'),
this.expenseLandedCost.transformToLandedCostEntry,
),
)(transactionEntry);
)(transactionEntry) as ILandedCostTransactionEntry;
};
}

View File

@@ -10,8 +10,9 @@ import {
} from 'class-validator';
import { Type } from 'class-transformer';
import { ToNumber } from '@/common/decorators/Validators';
import { LandedCostTransactionType } from '../types/BillLandedCosts.types';
class AllocateBillLandedCostItemDto {
export class AllocateBillLandedCostItemDto {
@IsInt()
@ToNumber()
entryId: number;
@@ -26,7 +27,7 @@ export class AllocateBillLandedCostDto {
transactionId: number;
@IsIn(['Expense', 'Bill'])
transactionType: string;
transactionType: LandedCostTransactionType;
@IsInt()
transactionEntryId: number;

View File

@@ -0,0 +1,14 @@
import { IsDateString, IsEnum, IsIn, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { LandedCostTransactionType } from "../types/BillLandedCosts.types";
export class LandedCostTransactionsQueryDto {
@IsString()
@IsNotEmpty()
@IsIn(['Expense', 'Bill'])
transactionType: LandedCostTransactionType;
@IsDateString()
@IsOptional()
date: string;
}

View File

@@ -1,5 +1,7 @@
import { Knex } from 'knex';
import { Bill } from '@/modules/Bills/models/Bill';
import { ModelObject } from 'objection';
import { Expense } from '@/modules/Expenses/models/Expense.model';
export interface ILandedCostItemDTO {
entryId: number;
@@ -140,3 +142,7 @@ interface ICommonEntryDTO {
export interface ICommonLandedCostEntryDTO extends ICommonEntryDTO {
landedCost?: boolean;
}
export type LandedCostTransactionType = 'Bill' | 'Expense';
export type LandedCostTransactionModel = Bill | Expense;

View File

@@ -1,5 +1,7 @@
import { IItemEntry, IBillLandedCostTransactionEntry } from '@/interfaces';
import { transformToMap } from 'utils';
import { ModelObject } from 'objection';
import { transformToMap } from '@/utils/transform-to-key';
import { IBillLandedCostTransactionEntry } from './types/BillLandedCosts.types';
import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry';
export const ERRORS = {
COST_TYPE_UNDEFINED: 'COST_TYPE_UNDEFINED',
@@ -23,8 +25,8 @@ export const ERRORS = {
*/
export const mergeLocatedWithBillEntries = (
locatedEntries: IBillLandedCostTransactionEntry[],
billEntries: IItemEntry[]
): (IBillLandedCostTransactionEntry & { entry: IItemEntry })[] => {
billEntries: ModelObject<ItemEntry>[]
): (IBillLandedCostTransactionEntry & { entry: ModelObject<ItemEntry> })[] => {
const billEntriesByEntryId = transformToMap(billEntries, 'id');
return locatedEntries.map((entry) => ({