Merge pull request #911 from bigcapitalhq/feature/20260125001703

fix(server): landed cost gl transactions
This commit is contained in:
Ahmed Bouhuolia
2026-01-25 00:19:07 +02:00
committed by GitHub
4 changed files with 202 additions and 241 deletions

View File

@@ -2,9 +2,11 @@ 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 { LandedCostGLEntriesService } from './commands/LandedCostGLEntries.service';
import { LandedCostSyncCostTransactions } from './commands/LandedCostSyncCostTransactions.service'; import { LandedCostSyncCostTransactions } from './commands/LandedCostSyncCostTransactions.service';
import { LedgerModule } from '../Ledger/Ledger.module';
import { LandedCostSyncCostTransactionsSubscriber } from './commands/LandedCostSyncCostTransactions.subscriber'; import { LandedCostSyncCostTransactionsSubscriber } from './commands/LandedCostSyncCostTransactions.subscriber';
import { LandedCostInventoryTransactionsSubscriber } from './commands/LandedCostInventoryTransactions.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';
@@ -16,12 +18,12 @@ import { ExpenseLandedCost } from './commands/ExpenseLandedCost.service';
import { BillLandedCost } from './commands/BillLandedCost.service'; import { BillLandedCost } from './commands/BillLandedCost.service';
@Module({ @Module({
imports: [forwardRef(() => InventoryCostModule)], imports: [forwardRef(() => InventoryCostModule), LedgerModule],
providers: [ providers: [
AllocateLandedCostService, AllocateLandedCostService,
TransactionLandedCostEntriesService, TransactionLandedCostEntriesService,
BillAllocatedLandedCostTransactions, BillAllocatedLandedCostTransactions,
LandedCostGLEntriesSubscriber, LandedCostGLEntriesService,
TransactionLandedCost, TransactionLandedCost,
BillLandedCost, BillLandedCost,
ExpenseLandedCost, ExpenseLandedCost,
@@ -29,6 +31,8 @@ import { BillLandedCost } from './commands/BillLandedCost.service';
RevertAllocatedLandedCost, RevertAllocatedLandedCost,
LandedCostInventoryTransactions, LandedCostInventoryTransactions,
LandedCostTranasctions, LandedCostTranasctions,
LandedCostGLEntriesSubscriber,
LandedCostInventoryTransactionsSubscriber,
LandedCostSyncCostTransactionsSubscriber, LandedCostSyncCostTransactionsSubscriber,
], ],
exports: [TransactionLandedCostEntriesService], exports: [TransactionLandedCostEntriesService],

View File

@@ -23,7 +23,9 @@ export class AllocateLandedCostService extends BaseLandedCostService {
private readonly billModel: TenantModelProxy<typeof Bill>, private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(BillLandedCost.name) @Inject(BillLandedCost.name)
protected readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost> protected readonly billLandedCostModel: TenantModelProxy<
typeof BillLandedCost
>,
) { ) {
super(); super();
} }
@@ -54,7 +56,8 @@ 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 this.billModel().query() const bill = await this.billModel()
.query()
.findById(billId) .findById(billId)
.withGraphFetched('entries') .withGraphFetched('entries')
.throwIfNotFound(); .throwIfNotFound();
@@ -89,8 +92,9 @@ export class AllocateLandedCostService extends BaseLandedCostService {
// unit-of-work eniverment. // unit-of-work eniverment.
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Save the bill landed cost model. // Save the bill landed cost model.
const billLandedCost = const billLandedCost = await this.billLandedCostModel()
await BillLandedCost.query(trx).insertGraph(billLandedCostObj); .query(trx)
.insertGraph(billLandedCostObj);
// Triggers `onBillLandedCostCreated` event. // Triggers `onBillLandedCostCreated` event.
await this.eventPublisher.emitAsync(events.billLandedCost.onCreated, { await this.eventPublisher.emitAsync(events.billLandedCost.onCreated, {
bill, bill,
@@ -103,5 +107,5 @@ export class AllocateLandedCostService extends BaseLandedCostService {
return billLandedCost; return billLandedCost;
}); });
}; }
} }

View File

@@ -1,236 +1,188 @@
// 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 * as moment from 'moment';
// 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 { Bill } from '@/modules/Bills/models/Bill';
// import { Bill } from '@/modules/Bills/models/Bill'; import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
// import { BillLandedCostEntry } from '../models/BillLandedCostEntry'; import { 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 { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
// import { AccountNormal } from '@/interfaces/Account'; import { AccountNormal } from '@/modules/Accounts/Accounts.types';
// import { ILandedCostTransactionEntry } from '../types/BillLandedCosts.types'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
// @Injectable() @Injectable()
// export class LandedCostGLEntries extends BaseLandedCostService { export class LandedCostGLEntriesService extends BaseLandedCostService {
// constructor( constructor(
// private readonly journalService: JournalPosterService, private readonly ledgerStorage: LedgerStorageService,
// private readonly ledgerRepository: LedgerRepository,
// @Inject(BillLandedCost.name) @Inject(BillLandedCost.name)
// private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>, protected 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 {IBillLandedCost} allocatedLandedCost private getLandedCostGLCommonEntry(
// * @returns bill: Bill,
// */ allocatedLandedCost: BillLandedCost,
// private getLandedCostGLCommonEntry = ( ) {
// bill: Bill, return {
// allocatedLandedCost: BillLandedCost date: moment(bill.billDate).format('YYYY-MM-DD'),
// ) => { currencyCode: allocatedLandedCost.currencyCode,
// return { exchangeRate: allocatedLandedCost.exchangeRate,
// date: bill.billDate,
// currencyCode: allocatedLandedCost.currencyCode,
// 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, branchId: bill.branchId,
// debit: 0, projectId: bill.projectId,
// };
// };
// /** credit: 0,
// * Retrieves the landed cost GL inventory entry. debit: 0,
// * @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. * Retrieves the landed cost GL inventory entry for an allocated item.
// * @param {IBill} bill */
// * @param {IBillLandedCost} allocatedLandedCost private getLandedCostGLInventoryEntry(
// * @param {ILandedCostTransactionEntry} fromTransactionEntry bill: Bill,
// * @returns {ILedgerEntry} allocatedLandedCost: BillLandedCost,
// */ allocatedEntry: BillLandedCostEntry,
// private getLandedCostGLCostEntry = ( index: number,
// bill: Bill, ): ILedgerEntry {
// allocatedLandedCost: BillLandedCost, const commonEntry = this.getLandedCostGLCommonEntry(
// fromTransactionEntry: ILandedCostTransactionEntry bill,
// ): ILedgerEntry => { allocatedLandedCost,
// const commonEntry = this.getLandedCostGLCommonEntry( );
// bill, const itemEntry = (
// allocatedLandedCost allocatedEntry as BillLandedCostEntry & {
// ); itemEntry?: {
// return { item?: { type?: string; inventoryAccountId?: number };
// ...commonEntry, costAccountId?: number;
// credit: allocatedLandedCost.localAmount, itemId?: number;
// accountId: fromTransactionEntry.costAccountId, };
// index: 2, }
// accountNormal: AccountNormal.CREDIT, ).itemEntry;
// }; const item = itemEntry?.item;
// }; const isInventory = item && ['inventory'].indexOf(item.type) !== -1;
const accountId = isInventory
? item?.inventoryAccountId
: itemEntry?.costAccountId;
// /** if (!accountId) {
// * Retrieve allocated landed cost entry GL entries. throw new Error(
// * @param {IBill} bill `Cannot determine GL account for landed cost allocate entry (entryId: ${allocatedEntry.entryId})`,
// * @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];
// }
// );
// /** const localAmount =
// * Compose the landed cost GL entries. allocatedEntry.cost * (allocatedLandedCost.exchangeRate || 1);
// * @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();
// };
// /** return {
// * Retrieves the landed cost GL ledger. ...commonEntry,
// * @param {BillLandedCost} allocatedLandedCost debit: localAmount,
// * @param {Bill} bill accountId,
// * @param {ILandedCostTransactionEntry} fromTransactionEntry index: index + 1,
// * @returns {ILedger} indexGroup: 10,
// */ itemId: itemEntry?.itemId,
// public getLandedCostLedger = ( accountNormal: AccountNormal.DEBIT,
// 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. * Retrieves the landed cost GL cost entry (credit to cost account).
// * @param {number} tenantId - */
// */ private getLandedCostGLCostEntry(
// public writeLandedCostGLEntries = async ( bill: Bill,
// allocatedLandedCost: BillLandedCost, allocatedLandedCost: BillLandedCost,
// bill: Bill, ): ILedgerEntry {
// fromTransactionEntry: ILandedCostTransactionEntry, const commonEntry = this.getLandedCostGLCommonEntry(
// trx?: Knex.Transaction bill,
// ) => { allocatedLandedCost,
// const ledgerEntries = this.getLandedCostGLEntries( );
// allocatedLandedCost,
// bill,
// fromTransactionEntry
// );
// await this.ledgerRepository.saveLedgerEntries(ledgerEntries, trx);
// };
// /** return {
// * Generates and writes GL entries of the given landed cost. ...commonEntry,
// * @param {number} billLandedCostId credit: allocatedLandedCost.localAmount,
// * @param {Knex.Transaction} trx accountId: allocatedLandedCost.costAccountId,
// */ index: 1,
// public createLandedCostGLEntries = async ( indexGroup: 20,
// billLandedCostId: number, accountNormal: AccountNormal.CREDIT,
// 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( * Composes the landed cost GL entries.
// allocatedLandedCost.fromTransactionType, */
// allocatedLandedCost.fromTransactionId, public getLandedCostGLEntries(
// allocatedLandedCost.fromTransactionEntryId allocatedLandedCost: BillLandedCost,
// ); bill: Bill,
// // Writes the given landed cost GL entries to the storage layer. ): ILedgerEntry[] {
// await this.writeLandedCostGLEntries( const inventoryEntries = allocatedLandedCost.allocateEntries.map(
// allocatedLandedCost, (allocatedEntry, index) =>
// allocatedLandedCost.bill, this.getLandedCostGLInventoryEntry(
// transactionEntry, bill,
// trx allocatedLandedCost,
// ); allocatedEntry,
// }; index,
),
);
const costEntry = this.getLandedCostGLCostEntry(bill, allocatedLandedCost);
// /** return [...inventoryEntries, costEntry];
// * Reverts GL entries of the given allocated landed cost transaction. }
// * @param {number} tenantId
// * @param {number} landedCostId /**
// * @param {Knex.Transaction} trx * Retrieves the landed cost GL ledger.
// */ */
// public revertLandedCostGLEntries = async ( public getLandedCostLedger(
// landedCostId: number, allocatedLandedCost: BillLandedCost,
// trx: Knex.Transaction bill: Bill,
// ) => { ): Ledger {
// await this.journalService.revertJournalTransactions( const entries = this.getLandedCostGLEntries(allocatedLandedCost, bill);
// landedCostId, return new Ledger(entries);
// 'LandedCost', }
// trx
// ); /**
// }; * Generates and writes GL entries of the given landed cost.
// } */
public createLandedCostGLEntries = async (
billLandedCostId: number,
trx?: Knex.Transaction,
) => {
const allocatedLandedCost = await this.billLandedCostModel()
.query(trx)
.findById(billLandedCostId)
.withGraphFetched('bill')
.withGraphFetched('allocateEntries.itemEntry.item');
if (!allocatedLandedCost?.bill) {
throw new Error('BillLandedCost or associated Bill not found');
}
const ledger = this.getLandedCostLedger(
allocatedLandedCost,
allocatedLandedCost.bill,
);
await this.ledgerStorage.commit(ledger, trx);
};
/**
* Reverts GL entries of the given allocated landed cost transaction.
*/
public revertLandedCostGLEntries = async (
landedCostId: number,
trx?: Knex.Transaction,
) => {
await this.ledgerStorage.deleteByReference(landedCostId, 'LandedCost', trx);
};
}

View File

@@ -3,14 +3,15 @@ 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 { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { LandedCostGLEntriesService } from './LandedCostGLEntries.service';
@Injectable() @Injectable()
export class LandedCostGLEntriesSubscriber { export class LandedCostGLEntriesSubscriber {
constructor() // private readonly billLandedCostGLEntries: LandedCostGLEntries, constructor(
{} private readonly landedCostGLEntries: LandedCostGLEntriesService,
) {}
/** /**
* Writes GL entries once landed cost transaction created. * Writes GL entries once landed cost transaction created.
@@ -21,10 +22,10 @@ export class LandedCostGLEntriesSubscriber {
billLandedCost, billLandedCost,
trx, trx,
}: IAllocatedLandedCostCreatedPayload) { }: IAllocatedLandedCostCreatedPayload) {
// await this.billLandedCostGLEntries.createLandedCostGLEntries( await this.landedCostGLEntries.createLandedCostGLEntries(
// billLandedCost.id, billLandedCost.id,
// trx trx,
// ); );
} }
/** /**
@@ -32,13 +33,13 @@ export class LandedCostGLEntriesSubscriber {
* @param {IAllocatedLandedCostDeletedPayload} payload - * @param {IAllocatedLandedCostDeletedPayload} payload -
*/ */
@OnEvent(events.billLandedCost.onDeleted) @OnEvent(events.billLandedCost.onDeleted)
async revertGLEnteriesOnceLandedCostDeleted({ async revertGLEntriesOnceLandedCostDeleted({
oldBillLandedCost, oldBillLandedCost,
trx, trx,
}: IAllocatedLandedCostDeletedPayload) { }: IAllocatedLandedCostDeletedPayload) {
// await this.billLandedCostGLEntries.revertLandedCostGLEntries( await this.landedCostGLEntries.revertLandedCostGLEntries(
// oldBillLandedCost.id, oldBillLandedCost.id,
// trx trx,
// ); );
} }
} }