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

@@ -92,6 +92,7 @@ import { ContactsModule } from '../Contacts/Contacts.module';
import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module'; import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.module'; import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.module';
import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module'; import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module';
import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module';
@Module({ @Module({
imports: [ imports: [
@@ -174,6 +175,7 @@ import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.
SaleEstimatesModule, SaleEstimatesModule,
SaleReceiptsModule, SaleReceiptsModule,
BillsModule, BillsModule,
BillLandedCostsModule,
ManualJournalsModule, ManualJournalsModule,
CreditNotesModule, CreditNotesModule,
VendorCreditsModule, VendorCreditsModule,

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,8 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Bill } from '@/modules/Bills/models/Bill'; import { Bill } from '@/modules/Bills/models/Bill';
import { BillLandedCost } from '../models/BillLandedCost'; import { BillLandedCost } from '../models/BillLandedCost';
import { IBillLandedCostTransaction } from '../types/BillLandedCosts.types'; import { IBillLandedCostTransaction } from '../types/BillLandedCosts.types';
import { ModelObject } from 'objection';
import { formatNumber } from '@/utils/format-number';
@Injectable() @Injectable()
export class BillAllocatedLandedCostTransactions { export class BillAllocatedLandedCostTransactions {
@@ -23,19 +25,17 @@ export class BillAllocatedLandedCostTransactions {
/** /**
* Retrieve the bill associated landed cost transactions. * Retrieve the bill associated landed cost transactions.
* @param {number} tenantId - Tenant id.
* @param {number} billId - Bill id. * @param {number} billId - Bill id.
* @return {Promise<IBillLandedCostTransaction>} * @return {Promise<IBillLandedCostTransaction>}
*/ */
public getBillLandedCostTransactions = async ( public getBillLandedCostTransactions = async (
billId: number, billId: number,
): Promise<IBillLandedCostTransaction> => { ): Promise<Array<IBillLandedCostTransaction>> => {
// Retrieve the given bill id or throw not found service error. // Retrieve the given bill id or throw not found service error.
const bill = await this.billModel() const bill = await this.billModel()
.query() .query()
.findById(billId) .findById(billId)
.throwIfNotFound(); .throwIfNotFound();
// Retrieve the bill associated allocated landed cost with bill and expense entry. // Retrieve the bill associated allocated landed cost with bill and expense entry.
const landedCostTransactions = await this.billLandedCostModel() const landedCostTransactions = await this.billLandedCostModel()
.query() .query()
@@ -45,11 +45,8 @@ export class BillAllocatedLandedCostTransactions {
.withGraphFetched('allocatedFromExpenseEntry.expenseAccount') .withGraphFetched('allocatedFromExpenseEntry.expenseAccount')
.withGraphFetched('bill'); .withGraphFetched('bill');
const transactionsJson = this.i18nService.i18nApply( const transactionsJson = landedCostTransactions.map((a) => a.toJSON());
[[qim.$each, 'allocationMethodFormatted']],
landedCostTransactions.map((a) => a.toJSON()),
tenantId,
);
return this.transformBillLandedCostTransactions(transactionsJson); return this.transformBillLandedCostTransactions(transactionsJson);
}; };
@@ -59,7 +56,7 @@ export class BillAllocatedLandedCostTransactions {
* @returns * @returns
*/ */
private transformBillLandedCostTransactions = ( private transformBillLandedCostTransactions = (
landedCostTransactions: IBillLandedCostTransaction[], landedCostTransactions: ModelObject<BillLandedCost>[],
) => { ) => {
return landedCostTransactions.map(this.transformBillLandedCostTransaction); return landedCostTransactions.map(this.transformBillLandedCostTransaction);
}; };
@@ -70,15 +67,16 @@ export class BillAllocatedLandedCostTransactions {
* @returns * @returns
*/ */
private transformBillLandedCostTransaction = ( private transformBillLandedCostTransaction = (
transaction: IBillLandedCostTransaction, transaction: ModelObject<BillLandedCost>,
) => { ): IBillLandedCostTransaction => {
const getTransactionName = R.curry(this.condBillLandedTransactionName)( const name = this.condBillLandedTransactionName(
transaction.fromTransactionType, transaction.fromTransactionType,
transaction,
);
const description = this.condBillLandedTransactionDescription(
transaction.fromTransactionType,
transaction,
); );
const getTransactionDesc = R.curry(
this.condBillLandedTransactionDescription,
)(transaction.fromTransactionType);
return { return {
formattedAmount: formatNumber(transaction.amount, { formattedAmount: formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode, currencyCode: transaction.currencyCode,
@@ -87,8 +85,8 @@ export class BillAllocatedLandedCostTransactions {
'allocatedFromBillEntry', 'allocatedFromBillEntry',
'allocatedFromExpenseEntry', 'allocatedFromExpenseEntry',
]), ]),
name: getTransactionName(transaction), name,
description: getTransactionDesc(transaction), description,
formattedLocalAmount: formatNumber(transaction.localAmount, { formattedLocalAmount: formatNumber(transaction.localAmount, {
currencyCode: 'USD', 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 * as R from 'ramda';
import { Knex } from 'knex'; // import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common'; // import { Inject, Injectable } from '@nestjs/common';
import { BaseLandedCostService } from '../BaseLandedCost.service'; // import { BaseLandedCostService } from '../BaseLandedCost.service';
import { BillLandedCost } from '../models/BillLandedCost'; // import { BillLandedCost } from '../models/BillLandedCost';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; // import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Bill } from '@/modules/Bills/models/Bill'; // import { Bill } from '@/modules/Bills/models/Bill';
import { BillLandedCostEntry } from '../models/BillLandedCostEntry'; // import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
import { ILedger, ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; // import { ILedger, ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { Ledger } from '@/modules/Ledger/Ledger'; // import { Ledger } from '@/modules/Ledger/Ledger';
// import { AccountNormal } from '@/interfaces/Account';
// import { ILandedCostTransactionEntry } from '../types/BillLandedCosts.types';
@Injectable() // @Injectable()
export class LandedCostGLEntries extends BaseLandedCostService { // export class LandedCostGLEntries extends BaseLandedCostService {
constructor( // constructor(
private readonly journalService: JournalPosterService, // private readonly journalService: JournalPosterService,
private readonly ledgerRepository: LedgerRepository, // private readonly ledgerRepository: LedgerRepository,
@Inject(BillLandedCost.name) // @Inject(BillLandedCost.name)
private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>, // private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>,
) { // ) {
super(); // super();
} // }
/** // /**
* Retrieves the landed cost GL common entry. // * Retrieves the landed cost GL common entry.
* @param {IBill} bill // * @param {IBill} bill
* @param {IBillLandedCost} allocatedLandedCost // * @param {IBillLandedCost} allocatedLandedCost
* @returns // * @returns
*/ // */
private getLandedCostGLCommonEntry = ( // private getLandedCostGLCommonEntry = (
bill: Bill, // bill: Bill,
allocatedLandedCost: BillLandedCost // allocatedLandedCost: BillLandedCost
) => { // ) => {
return { // return {
date: bill.billDate, // date: bill.billDate,
currencyCode: allocatedLandedCost.currencyCode, // currencyCode: allocatedLandedCost.currencyCode,
exchangeRate: allocatedLandedCost.exchangeRate, // exchangeRate: allocatedLandedCost.exchangeRate,
transactionType: 'LandedCost', // transactionType: 'LandedCost',
transactionId: allocatedLandedCost.id, // transactionId: allocatedLandedCost.id,
transactionNumber: bill.billNumber, // transactionNumber: bill.billNumber,
referenceNumber: bill.referenceNo, // referenceNumber: bill.referenceNo,
credit: 0, // credit: 0,
debit: 0, // debit: 0,
}; // };
}; // };
/** // /**
* Retrieves the landed cost GL inventory entry. // * Retrieves the landed cost GL inventory entry.
* @param {IBill} bill // * @param {IBill} bill
* @param {IBillLandedCost} allocatedLandedCost // * @param {IBillLandedCost} allocatedLandedCost
* @param {IBillLandedCostEntry} allocatedEntry // * @param {IBillLandedCostEntry} allocatedEntry
* @returns {ILedgerEntry} // * @returns {ILedgerEntry}
*/ // */
private getLandedCostGLInventoryEntry = ( // private getLandedCostGLInventoryEntry = (
bill: Bill, // bill: Bill,
allocatedLandedCost: BillLandedCost, // allocatedLandedCost: BillLandedCost,
allocatedEntry: BillLandedCostEntry // allocatedEntry: BillLandedCostEntry
): ILedgerEntry => { // ): ILedgerEntry => {
const commonEntry = this.getLandedCostGLCommonEntry( // const commonEntry = this.getLandedCostGLCommonEntry(
bill, // bill,
allocatedLandedCost // allocatedLandedCost
); // );
return { // return {
...commonEntry, // ...commonEntry,
debit: allocatedLandedCost.localAmount, // debit: allocatedLandedCost.localAmount,
accountId: allocatedEntry.itemEntry.item.inventoryAccountId, // accountId: allocatedEntry.itemEntry.item.inventoryAccountId,
index: 1, // index: 1,
accountNormal: AccountNormal.DEBIT, // accountNormal: AccountNormal.DEBIT,
}; // };
}; // };
/** // /**
* Retrieves the landed cost GL cost entry. // * Retrieves the landed cost GL cost entry.
* @param {IBill} bill // * @param {IBill} bill
* @param {IBillLandedCost} allocatedLandedCost // * @param {IBillLandedCost} allocatedLandedCost
* @param {ILandedCostTransactionEntry} fromTransactionEntry // * @param {ILandedCostTransactionEntry} fromTransactionEntry
* @returns {ILedgerEntry} // * @returns {ILedgerEntry}
*/ // */
private getLandedCostGLCostEntry = ( // private getLandedCostGLCostEntry = (
bill: Bill, // bill: Bill,
allocatedLandedCost: BillLandedCost, // allocatedLandedCost: BillLandedCost,
fromTransactionEntry: ILandedCostTransactionEntry // fromTransactionEntry: ILandedCostTransactionEntry
): ILedgerEntry => { // ): ILedgerEntry => {
const commonEntry = this.getLandedCostGLCommonEntry( // const commonEntry = this.getLandedCostGLCommonEntry(
bill, // bill,
allocatedLandedCost // allocatedLandedCost
); // );
return { // return {
...commonEntry, // ...commonEntry,
credit: allocatedLandedCost.localAmount, // credit: allocatedLandedCost.localAmount,
accountId: fromTransactionEntry.costAccountId, // accountId: fromTransactionEntry.costAccountId,
index: 2, // index: 2,
accountNormal: AccountNormal.CREDIT, // accountNormal: AccountNormal.CREDIT,
}; // };
}; // };
/** // /**
* Retrieve allocated landed cost entry GL entries. // * Retrieve allocated landed cost entry GL entries.
* @param {IBill} bill // * @param {IBill} bill
* @param {IBillLandedCost} allocatedLandedCost // * @param {IBillLandedCost} allocatedLandedCost
* @param {ILandedCostTransactionEntry} fromTransactionEntry // * @param {ILandedCostTransactionEntry} fromTransactionEntry
* @param {IBillLandedCostEntry} allocatedEntry // * @param {IBillLandedCostEntry} allocatedEntry
* @returns {ILedgerEntry} // * @returns {ILedgerEntry}
*/ // */
private getLandedCostGLAllocateEntry = R.curry( // private getLandedCostGLAllocateEntry = R.curry(
( // (
bill: Bill, // bill: Bill,
allocatedLandedCost: BillLandedCost, // allocatedLandedCost: BillLandedCost,
fromTransactionEntry: LandedCostTransactionEntry, // fromTransactionEntry: ILandedCostTransactionEntry,
allocatedEntry: BillLandedCostEntry // allocatedEntry: BillLandedCostEntry
): ILedgerEntry[] => { // ): ILedgerEntry[] => {
const inventoryEntry = this.getLandedCostGLInventoryEntry( // const inventoryEntry = this.getLandedCostGLInventoryEntry(
bill, // bill,
allocatedLandedCost, // allocatedLandedCost,
allocatedEntry // allocatedEntry
); // );
const costEntry = this.getLandedCostGLCostEntry( // const costEntry = this.getLandedCostGLCostEntry(
bill, // bill,
allocatedLandedCost, // allocatedLandedCost,
fromTransactionEntry // fromTransactionEntry
); // );
return [inventoryEntry, costEntry]; // return [inventoryEntry, costEntry];
} // }
); // );
/** // /**
* Compose the landed cost GL entries. // * Compose the landed cost GL entries.
* @param {BillLandedCost} allocatedLandedCost // * @param {BillLandedCost} allocatedLandedCost
* @param {Bill} bill // * @param {Bill} bill
* @param {ILandedCostTransactionEntry} fromTransactionEntry // * @param {ILandedCostTransactionEntry} fromTransactionEntry
* @returns {ILedgerEntry[]} // * @returns {ILedgerEntry[]}
*/ // */
public getLandedCostGLEntries = ( // public getLandedCostGLEntries = (
allocatedLandedCost: BillLandedCost, // allocatedLandedCost: BillLandedCost,
bill: Bill, // bill: Bill,
fromTransactionEntry: LandedCostTransactionEntry // fromTransactionEntry: ILandedCostTransactionEntry
): ILedgerEntry[] => { // ): ILedgerEntry[] => {
const getEntry = this.getLandedCostGLAllocateEntry( // const getEntry = this.getLandedCostGLAllocateEntry(
bill, // bill,
allocatedLandedCost, // allocatedLandedCost,
fromTransactionEntry // fromTransactionEntry
); // );
return allocatedLandedCost.allocateEntries.map(getEntry).flat(); // return allocatedLandedCost.allocateEntries.map(getEntry).flat();
}; // };
/** // /**
* Retrieves the landed cost GL ledger. // * Retrieves the landed cost GL ledger.
* @param {IBillLandedCost} allocatedLandedCost // * @param {BillLandedCost} allocatedLandedCost
* @param {Bill} bill // * @param {Bill} bill
* @param {ILandedCostTransactionEntry} fromTransactionEntry // * @param {ILandedCostTransactionEntry} fromTransactionEntry
* @returns {ILedger} // * @returns {ILedger}
*/ // */
public getLandedCostLedger = ( // public getLandedCostLedger = (
allocatedLandedCost: BillLandedCost, // allocatedLandedCost: BillLandedCost,
bill: Bill, // bill: Bill,
fromTransactionEntry: LandedCostTransactionEntry // fromTransactionEntry: ILandedCostTransactionEntry
): ILedger => { // ): ILedger => {
const entries = this.getLandedCostGLEntries( // const entries = this.getLandedCostGLEntries(
allocatedLandedCost, // allocatedLandedCost,
bill, // bill,
fromTransactionEntry // fromTransactionEntry
); // );
return new Ledger(entries); // return new Ledger(entries);
}; // };
/** // /**
* Writes landed cost GL entries to the storage layer. // * Writes landed cost GL entries to the storage layer.
* @param {number} tenantId - // * @param {number} tenantId -
*/ // */
public writeLandedCostGLEntries = async ( // public writeLandedCostGLEntries = async (
allocatedLandedCost: BillLandedCost, // allocatedLandedCost: BillLandedCost,
bill: Bill, // bill: Bill,
fromTransactionEntry: ILandedCostTransactionEntry, // fromTransactionEntry: ILandedCostTransactionEntry,
trx?: Knex.Transaction // trx?: Knex.Transaction
) => { // ) => {
const ledgerEntries = this.getLandedCostGLEntries( // const ledgerEntries = this.getLandedCostGLEntries(
allocatedLandedCost, // allocatedLandedCost,
bill, // bill,
fromTransactionEntry // fromTransactionEntry
); // );
await this.ledgerRepository.saveLedgerEntries(ledgerEntries, trx); // await this.ledgerRepository.saveLedgerEntries(ledgerEntries, trx);
}; // };
/** // /**
* Generates and writes GL entries of the given landed cost. // * Generates and writes GL entries of the given landed cost.
* @param {number} billLandedCostId // * @param {number} billLandedCostId
* @param {Knex.Transaction} trx // * @param {Knex.Transaction} trx
*/ // */
public createLandedCostGLEntries = async ( // public createLandedCostGLEntries = async (
billLandedCostId: number, // billLandedCostId: number,
trx?: Knex.Transaction // trx?: Knex.Transaction
) => { // ) => {
// Retrieve the bill landed cost transacion with associated // // Retrieve the bill landed cost transacion with associated
// allocated entries and items. // // allocated entries and items.
const allocatedLandedCost = await this.billLandedCostModel().query(trx) // const allocatedLandedCost = await this.billLandedCostModel().query(trx)
.findById(billLandedCostId) // .findById(billLandedCostId)
.withGraphFetched('bill') // .withGraphFetched('bill')
.withGraphFetched('allocateEntries.itemEntry.item'); // .withGraphFetched('allocateEntries.itemEntry.item');
// Retrieve the allocated from transactione entry. // // Retrieve the allocated from transactione entry.
const transactionEntry = await this.getLandedCostEntry( // const transactionEntry = await this.getLandedCostEntry(
allocatedLandedCost.fromTransactionType, // allocatedLandedCost.fromTransactionType,
allocatedLandedCost.fromTransactionId, // allocatedLandedCost.fromTransactionId,
allocatedLandedCost.fromTransactionEntryId // allocatedLandedCost.fromTransactionEntryId
); // );
// Writes the given landed cost GL entries to the storage layer. // // Writes the given landed cost GL entries to the storage layer.
await this.writeLandedCostGLEntries( // await this.writeLandedCostGLEntries(
allocatedLandedCost, // allocatedLandedCost,
allocatedLandedCost.bill, // allocatedLandedCost.bill,
transactionEntry, // transactionEntry,
trx // trx
); // );
}; // };
/** // /**
* Reverts GL entries of the given allocated landed cost transaction. // * Reverts GL entries of the given allocated landed cost transaction.
* @param {number} tenantId // * @param {number} tenantId
* @param {number} landedCostId // * @param {number} landedCostId
* @param {Knex.Transaction} trx // * @param {Knex.Transaction} trx
*/ // */
public revertLandedCostGLEntries = async ( // public revertLandedCostGLEntries = async (
landedCostId: number, // landedCostId: number,
trx: Knex.Transaction // trx: Knex.Transaction
) => { // ) => {
await this.journalService.revertJournalTransactions( // await this.journalService.revertJournalTransactions(
landedCostId, // landedCostId,
'LandedCost', // 'LandedCost',
trx // trx
); // );
}; // };
} // }

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,6 @@ import { events } from '@/common/events/events';
import { IAllocatedLandedCostDeletedPayload } from '../types/BillLandedCosts.types'; import { IAllocatedLandedCostDeletedPayload } from '../types/BillLandedCosts.types';
import { BillLandedCostEntry } from '../models/BillLandedCostEntry'; import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { BillLandedCost } from '../models/BillLandedCost';
@Injectable() @Injectable()
export class RevertAllocatedLandedCost extends BaseLandedCostService { export class RevertAllocatedLandedCost extends BaseLandedCostService {
@@ -15,11 +14,6 @@ export class RevertAllocatedLandedCost extends BaseLandedCostService {
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
@Inject(BillLandedCost.name)
private readonly billLandedCostModel: TenantModelProxy<
typeof BillLandedCost
>,
@Inject(BillLandedCostEntry.name) @Inject(BillLandedCostEntry.name)
private readonly billLandedCostEntryModel: TenantModelProxy< private readonly billLandedCostEntryModel: TenantModelProxy<
typeof BillLandedCostEntry typeof BillLandedCostEntry

View File

@@ -3,35 +3,46 @@ import { Model } from 'objection';
import { import {
ILandedCostTransaction, ILandedCostTransaction,
ILandedCostTransactionEntry, ILandedCostTransactionEntry,
LandedCostTransactionModel,
LandedCostTransactionType,
} from '../types/BillLandedCosts.types'; } from '../types/BillLandedCosts.types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { BillLandedCost } from '../models/BillLandedCost';
import { Bill } from '@/modules/Bills/models/Bill'; import { Bill } from '@/modules/Bills/models/Bill';
import { Expense } from '@/modules/Expenses/models/Expense.model'; import { Expense } from '@/modules/Expenses/models/Expense.model';
import { ServiceError } from '@/modules/Items/ServiceError'; 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 { ERRORS } from '../utils';
import { ExpenseLandedCost } from '../models/ExpenseLandedCost';
@Injectable() @Injectable()
export class TransactionLandedCost { export class TransactionLandedCost {
constructor( constructor(
private readonly billLandedCost: BillLandedCost, private readonly billLandedCost: BillLandedCost,
private readonly expenseLandedCost: ExpenseLandedCost, private readonly expenseLandedCost: ExpenseLandedCost,
private readonly moduleRef: ModuleRef,
) {} ) {}
/** /**
* Retrieve the cost transaction code model. * Retrieve the cost transaction code model.
* @param {number} tenantId - Tenant id.
* @param {string} transactionType - Transaction type. * @param {string} transactionType - Transaction type.
* @returns * @returns
*/ */
public getModel = (tenantId: number, transactionType: string): Model => { public getModel = async (
const Models = this.tenancy.models(tenantId); transactionType: string,
const Model = Models[transactionType]; ): 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); 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. * @param {IBill|IExpense} transaction - Expense or bill transaction.
* @returns {ILandedCostTransaction} * @returns {ILandedCostTransaction}
*/ */
public transformToLandedCost = R.curry( public transformToLandedCost =
( (
transactionType: string, transactionType: LandedCostTransactionType,
transaction: Bill | Expense, transaction: LandedCostTransactionModel,
): ILandedCostTransaction => { ): ILandedCostTransaction => {
return R.compose( return R.compose(
R.when( R.when(
@@ -54,9 +65,8 @@ export class TransactionLandedCost {
R.always(transactionType === 'Expense'), R.always(transactionType === 'Expense'),
this.expenseLandedCost.transformToLandedCost, this.expenseLandedCost.transformToLandedCost,
), ),
)(transaction); )(transaction) as ILandedCostTransaction;
}, };
);
/** /**
* Transformes the given expense or bill entry to landed cost transaction entry. * Transformes the given expense or bill entry to landed cost transaction entry.
@@ -65,7 +75,7 @@ export class TransactionLandedCost {
* @returns {ILandedCostTransactionEntry} * @returns {ILandedCostTransactionEntry}
*/ */
public transformToLandedCostEntry = ( public transformToLandedCostEntry = (
transactionType: 'Bill' | 'Expense', transactionType: LandedCostTransactionType,
transactionEntry, transactionEntry,
): ILandedCostTransactionEntry => { ): ILandedCostTransactionEntry => {
return R.compose( return R.compose(
@@ -77,6 +87,6 @@ export class TransactionLandedCost {
R.always(transactionType === 'Expense'), R.always(transactionType === 'Expense'),
this.expenseLandedCost.transformToLandedCostEntry, this.expenseLandedCost.transformToLandedCostEntry,
), ),
)(transactionEntry); )(transactionEntry) as ILandedCostTransactionEntry;
}; };
} }

View File

@@ -10,8 +10,9 @@ import {
} from 'class-validator'; } from 'class-validator';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { ToNumber } from '@/common/decorators/Validators'; import { ToNumber } from '@/common/decorators/Validators';
import { LandedCostTransactionType } from '../types/BillLandedCosts.types';
class AllocateBillLandedCostItemDto { export class AllocateBillLandedCostItemDto {
@IsInt() @IsInt()
@ToNumber() @ToNumber()
entryId: number; entryId: number;
@@ -26,7 +27,7 @@ export class AllocateBillLandedCostDto {
transactionId: number; transactionId: number;
@IsIn(['Expense', 'Bill']) @IsIn(['Expense', 'Bill'])
transactionType: string; transactionType: LandedCostTransactionType;
@IsInt() @IsInt()
transactionEntryId: number; 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 { Knex } from 'knex';
import { Bill } from '@/modules/Bills/models/Bill'; import { Bill } from '@/modules/Bills/models/Bill';
import { ModelObject } from 'objection';
import { Expense } from '@/modules/Expenses/models/Expense.model';
export interface ILandedCostItemDTO { export interface ILandedCostItemDTO {
entryId: number; entryId: number;
@@ -140,3 +142,7 @@ interface ICommonEntryDTO {
export interface ICommonLandedCostEntryDTO extends ICommonEntryDTO { export interface ICommonLandedCostEntryDTO extends ICommonEntryDTO {
landedCost?: boolean; landedCost?: boolean;
} }
export type LandedCostTransactionType = 'Bill' | 'Expense';
export type LandedCostTransactionModel = Bill | Expense;

View File

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

View File

@@ -8,6 +8,7 @@ import { OpenBillService } from './commands/OpenBill.service';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { GetBillsService } from './queries/GetBills.service'; import { GetBillsService } from './queries/GetBills.service';
import { CreateBillDto, EditBillDto } from './dtos/Bill.dto'; import { CreateBillDto, EditBillDto } from './dtos/Bill.dto';
import { GetBillPaymentTransactionsService } from './queries/GetBillPayments';
// import { GetBillPayments } from './queries/GetBillPayments'; // import { GetBillPayments } from './queries/GetBillPayments';
// import { GetBills } from './queries/GetBills'; // import { GetBills } from './queries/GetBills';
@@ -21,7 +22,7 @@ export class BillsApplication {
private getDueBillsService: GetDueBills, private getDueBillsService: GetDueBills,
private openBillService: OpenBillService, private openBillService: OpenBillService,
private getBillsService: GetBillsService, private getBillsService: GetBillsService,
// private getBillPaymentsService: GetBillPayments, private getBillPaymentTransactionsService: GetBillPaymentTransactionsService,
) {} ) {}
/** /**
@@ -71,7 +72,6 @@ export class BillsApplication {
/** /**
* Open the given bill. * Open the given bill.
* @param {number} tenantId
* @param {number} billId * @param {number} billId
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
@@ -91,10 +91,11 @@ export class BillsApplication {
/** /**
* Retrieve the specific bill associated payment transactions. * Retrieve the specific bill associated payment transactions.
* @param {number} tenantId
* @param {number} billId * @param {number} billId
*/ */
// public getBillPayments(billId: number) { public getBillPaymentTransactions(billId: number) {
// return this.getBillPaymentsService.getBillPayments(billId); return this.getBillPaymentTransactionsService.getBillPaymentTransactions(
// } billId,
);
}
} }

View File

@@ -60,6 +60,18 @@ export class BillsController {
return this.billsApplication.getBills(filterDTO); return this.billsApplication.getBills(filterDTO);
} }
@Get(':id/payment-transactions')
@ApiOperation({ summary: 'Retrieve the specific bill associated payment transactions.' })
@ApiParam({
name: 'id',
required: true,
type: Number,
description: 'The bill id',
})
getBillPaymentTransactions(@Param('id') billId: number) {
return this.billsApplication.getBillPaymentTransactions(billId);
}
@Get(':id') @Get(':id')
@ApiOperation({ summary: 'Retrieves the bill details.' }) @ApiOperation({ summary: 'Retrieves the bill details.' })
@ApiParam({ @ApiParam({

View File

@@ -28,6 +28,7 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module'; import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
import { BillsExportable } from './commands/BillsExportable'; import { BillsExportable } from './commands/BillsExportable';
import { BillsImportable } from './commands/BillsImportable'; import { BillsImportable } from './commands/BillsImportable';
import { GetBillPaymentTransactionsService } from './queries/GetBillPayments';
@Module({ @Module({
imports: [ imports: [
@@ -60,7 +61,8 @@ import { BillsImportable } from './commands/BillsImportable';
BillInventoryTransactions, BillInventoryTransactions,
BillWriteInventoryTransactionsSubscriber, BillWriteInventoryTransactionsSubscriber,
BillsExportable, BillsExportable,
BillsImportable BillsImportable,
GetBillPaymentTransactionsService,
], ],
controllers: [BillsController], controllers: [BillsController],
exports: [BillsExportable, BillsImportable], exports: [BillsExportable, BillsImportable],

View File

@@ -2,12 +2,13 @@ import { Inject, Injectable } from '@nestjs/common';
import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry'; import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry';
import { BillPaymentTransactionTransformer } from '@/modules/BillPayments/queries/BillPaymentTransactionTransformer'; import { BillPaymentTransactionTransformer } from '@/modules/BillPayments/queries/BillPaymentTransactionTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class GetBillPayments { export class GetBillPaymentTransactionsService {
constructor( constructor(
@Inject(BillPaymentEntry.name) @Inject(BillPaymentEntry.name)
private billPaymentEntryModel: typeof BillPaymentEntry, private billPaymentEntryModel: TenantModelProxy<typeof BillPaymentEntry>,
private transformer: TransformerInjectable, private transformer: TransformerInjectable,
) {} ) {}
@@ -15,8 +16,8 @@ export class GetBillPayments {
* Retrieve the specific bill associated payment transactions. * Retrieve the specific bill associated payment transactions.
* @param {number} billId - Bill id. * @param {number} billId - Bill id.
*/ */
public getBillPayments = async (billId: number) => { public getBillPaymentTransactions = async (billId: number) => {
const billsEntries = await this.billPaymentEntryModel const billsEntries = await this.billPaymentEntryModel()
.query() .query()
.where('billId', billId) .where('billId', billId)
.withGraphJoined('payment.paymentAccount') .withGraphJoined('payment.paymentAccount')

View File

@@ -162,6 +162,14 @@ export class ItemEntry extends BaseModel {
: getExlusiveTaxAmount(this.amount, this.taxRate); : getExlusiveTaxAmount(this.amount, this.taxRate);
} }
/**
* Remain unallocated landed cost.
* @return {number}
*/
get unallocatedCostAmount() {
return Math.max(this.amount - this.allocatedCostAmount, 0);
}
static calcAmount(itemEntry) { static calcAmount(itemEntry) {
const { discount, quantity, rate } = itemEntry; const { discount, quantity, rate } = itemEntry;
const total = quantity * rate; const total = quantity * rate;

View File

@@ -0,0 +1,5 @@
import { camelCase, upperFirst } from 'lodash';
export const sanitizeModelName = (modelName: string) => {
return upperFirst(camelCase(modelName));
}

View File

@@ -27,6 +27,8 @@ function BillDrawerProvider({ billId, ...props }) {
enabled: !!billId, enabled: !!billId,
}); });
console.log(transactions, 'ahmed');
//provider. //provider.
const provider = { const provider = {
billId, billId,

View File

@@ -34,6 +34,8 @@ function LocatedLandedCostTable({
// Bill drawer context. // Bill drawer context.
const { transactions, billId } = useBillDrawerContext(); const { transactions, billId } = useBillDrawerContext();
console.log(transactions, 'ahmed');
// Handle the transaction delete action. // Handle the transaction delete action.
const handleDeleteTransaction = ({ id }) => { const handleDeleteTransaction = ({ id }) => {
openAlert('bill-located-cost-delete', { BillId: id }); openAlert('bill-located-cost-delete', { BillId: id });

View File

@@ -163,7 +163,7 @@ export function useBill(id, props) {
[t.BILL, id], [t.BILL, id],
{ method: 'get', url: `/bills/${id}` }, { method: 'get', url: `/bills/${id}` },
{ {
select: (res) => res.data.bill, select: (res) => res.data,
defaultData: {}, defaultData: {},
...props, ...props,
}, },
@@ -208,7 +208,7 @@ export function useBillPaymentTransactions(id, props) {
url: `bills/${id}/payment-transactions`, url: `bills/${id}/payment-transactions`,
}, },
{ {
select: (res) => res.data.data, select: (res) => res.data,
defaultData: [], defaultData: [],
...props, ...props,
}, },

View File

@@ -23,7 +23,7 @@ export function useCreateLandedCost(props) {
return useMutation( return useMutation(
([id, values]) => ([id, values]) =>
apiRequest.post(`purchases/landed-cost/bills/${id}/allocate`, values), apiRequest.post(`landed-cost/bills/${id}/allocate`, values),
{ {
onSuccess: (res, id) => { onSuccess: (res, id) => {
// Common invalidate queries. // Common invalidate queries.
@@ -43,7 +43,7 @@ export function useDeleteLandedCost(props) {
return useMutation( return useMutation(
(landedCostId) => (landedCostId) =>
apiRequest.delete(`purchases/landed-cost/${landedCostId}`), apiRequest.delete(`landed-cost/${landedCostId}`),
{ {
onSuccess: (res, id) => { onSuccess: (res, id) => {
// Common invalidate queries. // Common invalidate queries.
@@ -62,7 +62,7 @@ export function useLandedCostTransaction(query, props) {
[t.LANDED_COST, query], [t.LANDED_COST, query],
{ {
method: 'get', method: 'get',
url: 'purchases/landed-cost/transactions', url: 'landed-cost/transactions',
params: { transaction_type: query }, params: { transaction_type: query },
}, },
{ {
@@ -81,10 +81,10 @@ export function useLandedCostTransaction(query, props) {
export function useBillLocatedLandedCost(id, props) { export function useBillLocatedLandedCost(id, props) {
return useRequestQuery( return useRequestQuery(
[t.LANDED_COST_TRANSACTION, id], [t.LANDED_COST_TRANSACTION, id],
{ method: 'get', url: `purchases/landed-cost/bills/${id}/transactions` }, { method: 'get', url: `landed-cost/bills/${id}/transactions` },
{ {
select: (res) => res.data.transactions, select: (res) => res.data?.data,
defaultData: {}, defaultData: [],
...props, ...props,
}, },
); );