mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
IAllocatedLandedCostCreatedPayload,
|
||||
IBillLandedCost,
|
||||
ILandedCostDTO,
|
||||
} from '@/interfaces';
|
||||
import BaseLandedCostService from './BaseLandedCost';
|
||||
import events from '@/subscribers/events';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export default class AllocateLandedCost extends BaseLandedCostService {
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* =================================
|
||||
* - Allocate landed cost.
|
||||
* =================================
|
||||
* - Validates the allocate cost not the same purchase invoice id.
|
||||
* - Get the given bill (purchase invoice) or throw not found error.
|
||||
* - Get the given landed cost transaction or throw not found error.
|
||||
* - Validate landed cost transaction has enough unallocated cost amount.
|
||||
* - Validate landed cost transaction entry has enough unallocated cost amount.
|
||||
* - Validate allocate entries existance and associated with cost bill transaction.
|
||||
* - Writes inventory landed cost transaction.
|
||||
* - Increment the allocated landed cost transaction.
|
||||
* - Increment the allocated landed cost transaction entry.
|
||||
* --------------------------------
|
||||
* @param {ILandedCostDTO} landedCostDTO - Landed cost DTO.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Purchase invoice id.
|
||||
*/
|
||||
public allocateLandedCost = async (
|
||||
tenantId: number,
|
||||
allocateCostDTO: ILandedCostDTO,
|
||||
billId: number
|
||||
): Promise<IBillLandedCost> => {
|
||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve total cost of allocated items.
|
||||
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
|
||||
|
||||
// Retrieve the purchase invoice or throw not found error.
|
||||
const bill = await this.billsService.getBillOrThrowError(tenantId, billId);
|
||||
|
||||
// Retrieve landed cost transaction or throw not found service error.
|
||||
const costTransaction = await this.getLandedCostOrThrowError(
|
||||
tenantId,
|
||||
allocateCostDTO.transactionType,
|
||||
allocateCostDTO.transactionId
|
||||
);
|
||||
// Retrieve landed cost transaction entries.
|
||||
const costTransactionEntry = await this.getLandedCostEntry(
|
||||
tenantId,
|
||||
allocateCostDTO.transactionType,
|
||||
allocateCostDTO.transactionId,
|
||||
allocateCostDTO.transactionEntryId
|
||||
);
|
||||
// Validates allocate cost items association with the purchase invoice entries.
|
||||
this.validateAllocateCostItems(bill.entries, allocateCostDTO.items);
|
||||
|
||||
// Validate the amount of cost with unallocated landed cost.
|
||||
this.validateLandedCostEntryAmount(
|
||||
costTransactionEntry.unallocatedCostAmount,
|
||||
amount
|
||||
);
|
||||
// Transformes DTO to bill landed cost model object.
|
||||
const billLandedCostObj = this.transformToBillLandedCost(
|
||||
allocateCostDTO,
|
||||
bill,
|
||||
costTransaction,
|
||||
costTransactionEntry
|
||||
);
|
||||
// Saves landed cost transactions with associated tranasctions under
|
||||
// unit-of-work eniverment.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Save the bill landed cost model.
|
||||
const billLandedCost = await BillLandedCost.query(trx).insertGraph(
|
||||
billLandedCostObj
|
||||
);
|
||||
// Triggers `onBillLandedCostCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.billLandedCost.onCreated, {
|
||||
tenantId,
|
||||
bill,
|
||||
billLandedCostId: billLandedCost.id,
|
||||
billLandedCost,
|
||||
costTransaction,
|
||||
costTransactionEntry,
|
||||
trx,
|
||||
} as IAllocatedLandedCostCreatedPayload);
|
||||
|
||||
return billLandedCost;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { difference, sumBy } from 'lodash';
|
||||
import BillsService from '../Bills';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
IItemEntry,
|
||||
IBill,
|
||||
ILandedCostItemDTO,
|
||||
ILandedCostDTO,
|
||||
IBillLandedCostTransaction,
|
||||
ILandedCostTransaction,
|
||||
ILandedCostTransactionEntry,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import TransactionLandedCost from './TransctionLandedCost';
|
||||
import { ERRORS } from './utils';
|
||||
import { CONFIG } from './utils';
|
||||
|
||||
@Service()
|
||||
export default class BaseLandedCostService {
|
||||
@Inject()
|
||||
public billsService: BillsService;
|
||||
|
||||
@Inject()
|
||||
public tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
public transactionLandedCost: TransactionLandedCost;
|
||||
|
||||
/**
|
||||
* Validates allocate cost items association with the purchase invoice entries.
|
||||
* @param {IItemEntry[]} purchaseInvoiceEntries
|
||||
* @param {ILandedCostItemDTO[]} landedCostItems
|
||||
*/
|
||||
protected validateAllocateCostItems = (
|
||||
purchaseInvoiceEntries: IItemEntry[],
|
||||
landedCostItems: ILandedCostItemDTO[]
|
||||
): void => {
|
||||
// Purchase invoice entries items ids.
|
||||
const purchaseInvoiceItems = purchaseInvoiceEntries.map((e) => e.id);
|
||||
const landedCostItemsIds = landedCostItems.map((item) => item.entryId);
|
||||
|
||||
// Not found items ids.
|
||||
const notFoundItemsIds = difference(
|
||||
purchaseInvoiceItems,
|
||||
landedCostItemsIds
|
||||
);
|
||||
// Throw items ids not found service error.
|
||||
if (notFoundItemsIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.LANDED_COST_ITEMS_IDS_NOT_FOUND);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes DTO to bill landed cost model object.
|
||||
* @param {ILandedCostDTO} landedCostDTO
|
||||
* @param {IBill} bill
|
||||
* @param {ILandedCostTransaction} costTransaction
|
||||
* @param {ILandedCostTransactionEntry} costTransactionEntry
|
||||
* @returns
|
||||
*/
|
||||
protected transformToBillLandedCost(
|
||||
landedCostDTO: ILandedCostDTO,
|
||||
bill: IBill,
|
||||
costTransaction: ILandedCostTransaction,
|
||||
costTransactionEntry: ILandedCostTransactionEntry
|
||||
) {
|
||||
const amount = sumBy(landedCostDTO.items, 'cost');
|
||||
|
||||
return {
|
||||
billId: bill.id,
|
||||
|
||||
fromTransactionType: landedCostDTO.transactionType,
|
||||
fromTransactionId: landedCostDTO.transactionId,
|
||||
fromTransactionEntryId: landedCostDTO.transactionEntryId,
|
||||
|
||||
amount,
|
||||
currencyCode: costTransaction.currencyCode,
|
||||
exchangeRate: costTransaction.exchangeRate || 1,
|
||||
|
||||
allocationMethod: landedCostDTO.allocationMethod,
|
||||
allocateEntries: landedCostDTO.items,
|
||||
|
||||
description: landedCostDTO.description,
|
||||
costAccountId: costTransactionEntry.costAccountId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cost transaction or throw not found error.
|
||||
* @param {number} tenantId
|
||||
* @param {transactionType} transactionType -
|
||||
* @param {transactionId} transactionId -
|
||||
*/
|
||||
public getLandedCostOrThrowError = async (
|
||||
tenantId: number,
|
||||
transactionType: string,
|
||||
transactionId: number
|
||||
) => {
|
||||
const Model = this.transactionLandedCost.getModel(
|
||||
tenantId,
|
||||
transactionType
|
||||
);
|
||||
const model = await Model.query().findById(transactionId);
|
||||
|
||||
if (!model) {
|
||||
throw new ServiceError(ERRORS.LANDED_COST_TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
return this.transactionLandedCost.transformToLandedCost(
|
||||
transactionType,
|
||||
model
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the landed cost entries.
|
||||
* @param {number} tenantId
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
* @returns
|
||||
*/
|
||||
public getLandedCostEntry = async (
|
||||
tenantId: number,
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
transactionEntryId: number
|
||||
): Promise<any> => {
|
||||
const Model = this.transactionLandedCost.getModel(
|
||||
tenantId,
|
||||
transactionType
|
||||
);
|
||||
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||
|
||||
const entry = await Model.relatedQuery(relation)
|
||||
.for(transactionId)
|
||||
.findOne('id', transactionEntryId)
|
||||
.where('landedCost', true)
|
||||
.onBuild((q) => {
|
||||
if (transactionType === 'Bill') {
|
||||
q.withGraphFetched('item');
|
||||
} else if (transactionType === 'Expense') {
|
||||
q.withGraphFetched('expenseAccount');
|
||||
}
|
||||
});
|
||||
|
||||
if (!entry) {
|
||||
throw new ServiceError(ERRORS.LANDED_COST_ENTRY_NOT_FOUND);
|
||||
}
|
||||
return this.transactionLandedCost.transformToLandedCostEntry(
|
||||
transactionType,
|
||||
entry
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve allocate items cost total.
|
||||
* @param {ILandedCostDTO} landedCostDTO
|
||||
* @returns {number}
|
||||
*/
|
||||
protected getAllocateItemsCostTotal = (
|
||||
landedCostDTO: ILandedCostDTO
|
||||
): number => {
|
||||
return sumBy(landedCostDTO.items, 'cost');
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the landed cost entry amount.
|
||||
* @param {number} unallocatedCost -
|
||||
* @param {number} amount -
|
||||
*/
|
||||
protected validateLandedCostEntryAmount = (
|
||||
unallocatedCost: number,
|
||||
amount: number
|
||||
): void => {
|
||||
if (unallocatedCost < amount) {
|
||||
throw new ServiceError(ERRORS.COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the give bill landed cost or throw not found service error.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} landedCostId - Landed cost id.
|
||||
* @returns {Promise<IBillLandedCost>}
|
||||
*/
|
||||
public getBillLandedCostOrThrowError = async (
|
||||
tenantId: number,
|
||||
landedCostId: number
|
||||
): Promise<IBillLandedCostTransaction> => {
|
||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the bill landed cost model.
|
||||
const billLandedCost = await BillLandedCost.query().findById(landedCostId);
|
||||
|
||||
if (!billLandedCost) {
|
||||
throw new ServiceError(ERRORS.BILL_LANDED_COST_NOT_FOUND);
|
||||
}
|
||||
return billLandedCost;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { omit } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import * as qim from 'qim';
|
||||
import { IBillLandedCostTransaction } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { formatNumber } from 'utils';
|
||||
import I18nService from '@/services/I18n/I18nService';
|
||||
|
||||
@Service()
|
||||
export default class BillAllocatedLandedCostTransactions {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private i18nService: I18nService;
|
||||
|
||||
/**
|
||||
* Retrieve the bill associated landed cost transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<IBillLandedCostTransaction>}
|
||||
*/
|
||||
public getBillLandedCostTransactions = async (
|
||||
tenantId: number,
|
||||
billId: number
|
||||
): Promise<IBillLandedCostTransaction> => {
|
||||
const { BillLandedCost, Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill id or throw not found service error.
|
||||
const bill = await Bill.query().findById(billId).throwIfNotFound();
|
||||
|
||||
// Retrieve the bill associated allocated landed cost with bill and expense entry.
|
||||
const landedCostTransactions = await BillLandedCost.query()
|
||||
.where('bill_id', billId)
|
||||
.withGraphFetched('allocateEntries')
|
||||
.withGraphFetched('allocatedFromBillEntry.item')
|
||||
.withGraphFetched('allocatedFromExpenseEntry.expenseAccount')
|
||||
.withGraphFetched('bill');
|
||||
|
||||
const transactionsJson = this.i18nService.i18nApply(
|
||||
[[qim.$each, 'allocationMethodFormatted']],
|
||||
landedCostTransactions.map((a) => a.toJSON()),
|
||||
tenantId
|
||||
);
|
||||
return this.transformBillLandedCostTransactions(transactionsJson);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {IBillLandedCostTransaction[]} landedCostTransactions
|
||||
* @returns
|
||||
*/
|
||||
private transformBillLandedCostTransactions = (
|
||||
landedCostTransactions: IBillLandedCostTransaction[]
|
||||
) => {
|
||||
return landedCostTransactions.map(this.transformBillLandedCostTransaction);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {IBillLandedCostTransaction} transaction
|
||||
* @returns
|
||||
*/
|
||||
private transformBillLandedCostTransaction = (
|
||||
transaction: IBillLandedCostTransaction
|
||||
) => {
|
||||
const getTransactionName = R.curry(this.condBillLandedTransactionName)(
|
||||
transaction.fromTransactionType
|
||||
);
|
||||
const getTransactionDesc = R.curry(
|
||||
this.condBillLandedTransactionDescription
|
||||
)(transaction.fromTransactionType);
|
||||
|
||||
return {
|
||||
formattedAmount: formatNumber(transaction.amount, {
|
||||
currencyCode: transaction.currencyCode,
|
||||
}),
|
||||
...omit(transaction, [
|
||||
'allocatedFromBillEntry',
|
||||
'allocatedFromExpenseEntry',
|
||||
]),
|
||||
name: getTransactionName(transaction),
|
||||
description: getTransactionDesc(transaction),
|
||||
formattedLocalAmount: formatNumber(transaction.localAmount, {
|
||||
currencyCode: 'USD',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve bill landed cost tranaction name based on the given transaction type.
|
||||
* @param transactionType
|
||||
* @param transaction
|
||||
* @returns
|
||||
*/
|
||||
private condBillLandedTransactionName = (
|
||||
transactionType: string,
|
||||
transaction
|
||||
) => {
|
||||
return R.cond([
|
||||
[
|
||||
R.always(R.equals(transactionType, 'Bill')),
|
||||
this.getLandedBillTransactionName,
|
||||
],
|
||||
[
|
||||
R.always(R.equals(transactionType, 'Expense')),
|
||||
this.getLandedExpenseTransactionName,
|
||||
],
|
||||
])(transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transaction
|
||||
* @returns
|
||||
*/
|
||||
private getLandedBillTransactionName = (transaction): string => {
|
||||
return transaction.allocatedFromBillEntry.item.name;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transaction
|
||||
* @returns
|
||||
*/
|
||||
private getLandedExpenseTransactionName = (transaction): string => {
|
||||
return transaction.allocatedFromExpenseEntry.expenseAccount.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve landed cost.
|
||||
* @param transaction
|
||||
* @returns
|
||||
*/
|
||||
private getLandedBillTransactionDescription = (transaction): string => {
|
||||
return transaction.allocatedFromBillEntry.description;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transaction
|
||||
* @returns
|
||||
*/
|
||||
private getLandedExpenseTransactionDescription = (transaction): string => {
|
||||
return transaction.allocatedFromExpenseEntry.description;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the bill landed cost transaction description based on transaction type.
|
||||
* @param {string} tranasctionType
|
||||
* @param transaction
|
||||
* @returns
|
||||
*/
|
||||
private condBillLandedTransactionDescription = (
|
||||
tranasctionType: string,
|
||||
transaction
|
||||
) => {
|
||||
return R.cond([
|
||||
[
|
||||
R.always(R.equals(tranasctionType, 'Bill')),
|
||||
this.getLandedBillTransactionDescription,
|
||||
],
|
||||
[
|
||||
R.always(R.equals(tranasctionType, 'Expense')),
|
||||
this.getLandedExpenseTransactionDescription,
|
||||
],
|
||||
])(transaction);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Service } from 'typedi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
IBill,
|
||||
IItem,
|
||||
ILandedCostTransactionEntry,
|
||||
ILandedCostTransaction,
|
||||
IItemEntry,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default 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: IBill): 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: IItemEntry & { item: IItem }
|
||||
): 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Service } from 'typedi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IExpense,
|
||||
ILandedCostTransactionEntry,
|
||||
IExpenseCategory,
|
||||
IAccount,
|
||||
ILandedCostTransaction,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class ExpenseLandedCost {
|
||||
/**
|
||||
* Retrieve the landed cost transaction from the given expense transaction.
|
||||
* @param {IExpense} expense
|
||||
* @returns {ILandedCostTransaction}
|
||||
*/
|
||||
public transformToLandedCost = (
|
||||
expense: IExpense
|
||||
): 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: IExpenseCategory & { expenseAccount: IAccount }
|
||||
): 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,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import * as R from 'ramda';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
AccountNormal,
|
||||
IBill,
|
||||
IBillLandedCost,
|
||||
IBillLandedCostEntry,
|
||||
ILandedCostTransactionEntry,
|
||||
ILedger,
|
||||
ILedgerEntry,
|
||||
} from '@/interfaces';
|
||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import LedgerRepository from '@/services/Ledger/LedgerRepository';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import BaseLandedCostService from './BaseLandedCost';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostGLEntries extends BaseLandedCostService {
|
||||
@Inject()
|
||||
private journalService: JournalPosterService;
|
||||
|
||||
@Inject()
|
||||
private ledgerRepository: LedgerRepository;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieves the landed cost GL common entry.
|
||||
* @param {IBill} bill
|
||||
* @param {IBillLandedCost} allocatedLandedCost
|
||||
* @returns
|
||||
*/
|
||||
private getLandedCostGLCommonEntry = (
|
||||
bill: IBill,
|
||||
allocatedLandedCost: IBillLandedCost
|
||||
) => {
|
||||
return {
|
||||
date: bill.billDate,
|
||||
currencyCode: allocatedLandedCost.currencyCode,
|
||||
exchangeRate: allocatedLandedCost.exchangeRate,
|
||||
|
||||
transactionType: 'LandedCost',
|
||||
transactionId: allocatedLandedCost.id,
|
||||
transactionNumber: bill.billNumber,
|
||||
|
||||
referenceNumber: bill.referenceNo,
|
||||
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the landed cost GL inventory entry.
|
||||
* @param {IBill} bill
|
||||
* @param {IBillLandedCost} allocatedLandedCost
|
||||
* @param {IBillLandedCostEntry} allocatedEntry
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getLandedCostGLInventoryEntry = (
|
||||
bill: IBill,
|
||||
allocatedLandedCost: IBillLandedCost,
|
||||
allocatedEntry: IBillLandedCostEntry
|
||||
): 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: IBill,
|
||||
allocatedLandedCost: IBillLandedCost,
|
||||
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: IBill,
|
||||
allocatedLandedCost: IBillLandedCost,
|
||||
fromTransactionEntry: ILandedCostTransactionEntry,
|
||||
allocatedEntry: IBillLandedCostEntry
|
||||
): 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 {IBillLandedCost} allocatedLandedCost
|
||||
* @param {IBill} bill
|
||||
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getLandedCostGLEntries = (
|
||||
allocatedLandedCost: IBillLandedCost,
|
||||
bill: IBill,
|
||||
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 {IBill} bill
|
||||
* @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||
* @returns {ILedger}
|
||||
*/
|
||||
public getLandedCostLedger = (
|
||||
allocatedLandedCost: IBillLandedCost,
|
||||
bill: IBill,
|
||||
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 (
|
||||
tenantId: number,
|
||||
allocatedLandedCost: IBillLandedCost,
|
||||
bill: IBill,
|
||||
fromTransactionEntry: ILandedCostTransactionEntry,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const ledgerEntries = this.getLandedCostGLEntries(
|
||||
allocatedLandedCost,
|
||||
bill,
|
||||
fromTransactionEntry
|
||||
);
|
||||
await this.ledgerRepository.saveLedgerEntries(tenantId, ledgerEntries, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates and writes GL entries of the given landed cost.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billLandedCostId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public createLandedCostGLEntries = async (
|
||||
tenantId: number,
|
||||
billLandedCostId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the bill landed cost transacion with associated
|
||||
// allocated entries and items.
|
||||
const allocatedLandedCost = await BillLandedCost.query(trx)
|
||||
.findById(billLandedCostId)
|
||||
.withGraphFetched('bill')
|
||||
.withGraphFetched('allocateEntries.itemEntry.item');
|
||||
|
||||
// Retrieve the allocated from transactione entry.
|
||||
const transactionEntry = await this.getLandedCostEntry(
|
||||
tenantId,
|
||||
allocatedLandedCost.fromTransactionType,
|
||||
allocatedLandedCost.fromTransactionId,
|
||||
allocatedLandedCost.fromTransactionEntryId
|
||||
);
|
||||
// Writes the given landed cost GL entries to the storage layer.
|
||||
await this.writeLandedCostGLEntries(
|
||||
tenantId,
|
||||
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 (
|
||||
tenantId: number,
|
||||
landedCostId: number,
|
||||
trx: Knex.Transaction
|
||||
) => {
|
||||
await this.journalService.revertJournalTransactions(
|
||||
tenantId,
|
||||
landedCostId,
|
||||
'LandedCost',
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import LandedCostGLEntries from './LandedCostGLEntries';
|
||||
import {
|
||||
IAllocatedLandedCostCreatedPayload,
|
||||
IAllocatedLandedCostDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostGLEntriesSubscriber {
|
||||
@Inject()
|
||||
billLandedCostGLEntries: LandedCostGLEntries;
|
||||
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.billLandedCost.onCreated,
|
||||
this.writeGLEntriesOnceLandedCostCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.billLandedCost.onDeleted,
|
||||
this.revertGLEnteriesOnceLandedCostDeleted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes GL entries once landed cost transaction created.
|
||||
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||
*/
|
||||
private writeGLEntriesOnceLandedCostCreated = async ({
|
||||
tenantId,
|
||||
billLandedCost,
|
||||
trx,
|
||||
}: IAllocatedLandedCostCreatedPayload) => {
|
||||
await this.billLandedCostGLEntries.createLandedCostGLEntries(
|
||||
tenantId,
|
||||
billLandedCost.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts GL entries associated to landed cost transaction once deleted.
|
||||
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||
*/
|
||||
private revertGLEnteriesOnceLandedCostDeleted = async ({
|
||||
tenantId,
|
||||
oldBillLandedCost,
|
||||
billId,
|
||||
trx,
|
||||
}: IAllocatedLandedCostDeletedPayload) => {
|
||||
await this.billLandedCostGLEntries.revertLandedCostGLEntries(
|
||||
tenantId,
|
||||
oldBillLandedCost.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import { IBill, IBillLandedCostTransaction } from '@/interfaces';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import { mergeLocatedWithBillEntries } from './utils';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostInventoryTransactions {
|
||||
@Inject()
|
||||
public inventoryService: InventoryService;
|
||||
|
||||
/**
|
||||
* Records inventory transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillLandedCostTransaction} billLandedCost
|
||||
* @param {IBill} bill -
|
||||
*/
|
||||
public recordInventoryTransactions = async (
|
||||
tenantId: number,
|
||||
billLandedCost: IBillLandedCostTransaction,
|
||||
bill: IBill,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Retrieve the merged allocated entries with bill entries.
|
||||
const allocateEntries = mergeLocatedWithBillEntries(
|
||||
billLandedCost.allocateEntries,
|
||||
bill.entries
|
||||
);
|
||||
// Mappes the allocate cost entries to inventory transactions.
|
||||
const inventoryTransactions = allocateEntries.map((allocateEntry) => ({
|
||||
date: bill.billDate,
|
||||
itemId: allocateEntry.entry.itemId,
|
||||
direction: 'IN',
|
||||
quantity: null,
|
||||
rate: allocateEntry.cost,
|
||||
transactionType: 'LandedCost',
|
||||
transactionId: billLandedCost.id,
|
||||
entryId: allocateEntry.entryId,
|
||||
}));
|
||||
// Writes inventory transactions.
|
||||
return this.inventoryService.recordInventoryTransactions(
|
||||
tenantId,
|
||||
inventoryTransactions,
|
||||
false,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the inventory transaction.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} landedCostId - Landed cost id.
|
||||
* @param {Knex.Transaction} trx - Knex transactions.
|
||||
* @returns
|
||||
*/
|
||||
public removeInventoryTransactions = (
|
||||
tenantId: number,
|
||||
landedCostId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
return this.inventoryService.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
landedCostId,
|
||||
'LandedCost',
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IAllocatedLandedCostCreatedPayload,
|
||||
IAllocatedLandedCostDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import LandedCostInventoryTransactions from './LandedCostInventoryTransactions';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostInventoryTransactionsSubscriber {
|
||||
@Inject()
|
||||
landedCostInventory: LandedCostInventoryTransactions;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.billLandedCost.onCreated,
|
||||
this.writeInventoryTransactionsOnceCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.billLandedCost.onDeleted,
|
||||
this.revertInventoryTransactionsOnceDeleted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes inventory transactions of the landed cost transaction once created.
|
||||
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||
*/
|
||||
private writeInventoryTransactionsOnceCreated = async ({
|
||||
billLandedCost,
|
||||
tenantId,
|
||||
trx,
|
||||
bill,
|
||||
}: IAllocatedLandedCostCreatedPayload) => {
|
||||
// Records the inventory transactions.
|
||||
await this.landedCostInventory.recordInventoryTransactions(
|
||||
tenantId,
|
||||
billLandedCost,
|
||||
bill,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts inventory transactions of the landed cost transaction once deleted.
|
||||
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||
*/
|
||||
private revertInventoryTransactionsOnceDeleted = async ({
|
||||
tenantId,
|
||||
oldBillLandedCost,
|
||||
trx,
|
||||
}: IAllocatedLandedCostDeletedPayload) => {
|
||||
// Removes the inventory transactions.
|
||||
await this.landedCostInventory.removeInventoryTransactions(
|
||||
tenantId,
|
||||
oldBillLandedCost.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import TransactionLandedCost from './TransctionLandedCost';
|
||||
import { CONFIG } from './utils';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostSyncCostTransactions {
|
||||
@Inject()
|
||||
transactionLandedCost: TransactionLandedCost;
|
||||
|
||||
/**
|
||||
* Allocate the landed cost amount to cost transactions.
|
||||
* @param {number} tenantId -
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
*/
|
||||
public incrementLandedCostAmount = async (
|
||||
tenantId: number,
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
transactionEntryId: number,
|
||||
amount: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const Model = this.transactionLandedCost.getModel(
|
||||
tenantId,
|
||||
transactionType
|
||||
);
|
||||
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||
|
||||
// Increment the landed cost transaction amount.
|
||||
await Model.query(trx)
|
||||
.where('id', transactionId)
|
||||
.increment('allocatedCostAmount', amount);
|
||||
|
||||
// Increment the landed cost entry.
|
||||
await Model.relatedQuery(relation, trx)
|
||||
.for(transactionId)
|
||||
.where('id', transactionEntryId)
|
||||
.increment('allocatedCostAmount', amount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the landed cost amount to cost transaction.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} transactionType - Transaction type.
|
||||
* @param {number} transactionId - Transaction id.
|
||||
* @param {number} transactionEntryId - Transaction entry id.
|
||||
* @param {number} amount - Amount
|
||||
*/
|
||||
public revertLandedCostAmount = async (
|
||||
tenantId: number,
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
transactionEntryId: number,
|
||||
amount: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const Model = this.transactionLandedCost.getModel(
|
||||
tenantId,
|
||||
transactionType
|
||||
);
|
||||
const relation = CONFIG.COST_TYPES[transactionType].entries;
|
||||
|
||||
// Decrement the allocate cost amount of cost transaction.
|
||||
await Model.query(trx)
|
||||
.where('id', transactionId)
|
||||
.decrement('allocatedCostAmount', amount);
|
||||
|
||||
// Decrement the allocated cost amount cost transaction entry.
|
||||
await Model.relatedQuery(relation, trx)
|
||||
.for(transactionId)
|
||||
.where('id', transactionEntryId)
|
||||
.decrement('allocatedCostAmount', amount);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IAllocatedLandedCostCreatedPayload,
|
||||
IAllocatedLandedCostDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import LandedCostSyncCostTransactions from './LandedCostSyncCostTransactions';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostSyncCostTransactionsSubscriber {
|
||||
@Inject()
|
||||
landedCostSyncCostTransaction: LandedCostSyncCostTransactions;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.billLandedCost.onCreated,
|
||||
this.incrementCostTransactionsOnceCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.billLandedCost.onDeleted,
|
||||
this.decrementCostTransactionsOnceDeleted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment cost transactions once the landed cost allocated.
|
||||
* @param {IAllocatedLandedCostCreatedPayload} payload -
|
||||
*/
|
||||
private incrementCostTransactionsOnceCreated = async ({
|
||||
tenantId,
|
||||
billLandedCost,
|
||||
trx,
|
||||
}: IAllocatedLandedCostCreatedPayload) => {
|
||||
// Increment landed cost amount on transaction and entry.
|
||||
await this.landedCostSyncCostTransaction.incrementLandedCostAmount(
|
||||
tenantId,
|
||||
billLandedCost.fromTransactionType,
|
||||
billLandedCost.fromTransactionId,
|
||||
billLandedCost.fromTransactionEntryId,
|
||||
billLandedCost.amount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrement cost transactions once the allocated landed cost reverted.
|
||||
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||
*/
|
||||
private decrementCostTransactionsOnceDeleted = async ({
|
||||
oldBillLandedCost,
|
||||
tenantId,
|
||||
trx,
|
||||
}: IAllocatedLandedCostDeletedPayload) => {
|
||||
// Reverts the landed cost amount to the cost transaction.
|
||||
await this.landedCostSyncCostTransaction.revertLandedCostAmount(
|
||||
tenantId,
|
||||
oldBillLandedCost.fromTransactionType,
|
||||
oldBillLandedCost.fromTransactionId,
|
||||
oldBillLandedCost.fromTransactionEntryId,
|
||||
oldBillLandedCost.amount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ref } from 'objection';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
ILandedCostTransactionsQueryDTO,
|
||||
ILandedCostTransaction,
|
||||
ILandedCostTransactionDOJO,
|
||||
ILandedCostTransactionEntry,
|
||||
ILandedCostTransactionEntryDOJO,
|
||||
} from '@/interfaces';
|
||||
import TransactionLandedCost from './TransctionLandedCost';
|
||||
import BillsService from '../Bills';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostTranasctions {
|
||||
@Inject()
|
||||
transactionLandedCost: TransactionLandedCost;
|
||||
|
||||
@Inject()
|
||||
billsService: BillsService;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the landed costs based on the given query.
|
||||
* @param {number} tenantId
|
||||
* @param {ILandedCostTransactionsQueryDTO} query
|
||||
* @returns {Promise<ILandedCostTransaction[]>}
|
||||
*/
|
||||
public getLandedCostTransactions = async (
|
||||
tenantId: number,
|
||||
query: ILandedCostTransactionsQueryDTO
|
||||
): Promise<ILandedCostTransaction[]> => {
|
||||
const { transactionType } = query;
|
||||
const Model = this.transactionLandedCost.getModel(
|
||||
tenantId,
|
||||
query.transactionType
|
||||
);
|
||||
// Retrieve the model entities.
|
||||
const transactions = await Model.query().onBuild((q) => {
|
||||
q.where('allocated_cost_amount', '<', ref('landed_cost_amount'));
|
||||
|
||||
if (query.transactionType === 'Bill') {
|
||||
q.withGraphFetched('entries.item');
|
||||
} else if (query.transactionType === 'Expense') {
|
||||
q.withGraphFetched('categories.expenseAccount');
|
||||
}
|
||||
});
|
||||
const transformLandedCost =
|
||||
this.transactionLandedCost.transformToLandedCost(transactionType);
|
||||
|
||||
return R.compose(
|
||||
this.transformLandedCostTransactions,
|
||||
R.map(transformLandedCost)
|
||||
)(transactions);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transactions
|
||||
* @returns
|
||||
*/
|
||||
public transformLandedCostTransactions = (
|
||||
transactions: ILandedCostTransaction[]
|
||||
) => {
|
||||
return R.map(this.transformLandedCostTransaction)(transactions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the landed cost transaction.
|
||||
* @param {ILandedCostTransaction} transaction
|
||||
*/
|
||||
public transformLandedCostTransaction = (
|
||||
transaction: ILandedCostTransaction
|
||||
): ILandedCostTransactionDOJO => {
|
||||
const { currencyCode } = transaction;
|
||||
|
||||
// Formatted transaction amount.
|
||||
const formattedAmount = formatNumber(transaction.amount, { currencyCode });
|
||||
|
||||
// Formatted transaction unallocated cost amount.
|
||||
const formattedUnallocatedCostAmount = formatNumber(
|
||||
transaction.unallocatedCostAmount,
|
||||
{ currencyCode }
|
||||
);
|
||||
// Formatted transaction allocated cost amount.
|
||||
const formattedAllocatedCostAmount = formatNumber(
|
||||
transaction.allocatedCostAmount,
|
||||
{ currencyCode }
|
||||
);
|
||||
|
||||
return {
|
||||
...transaction,
|
||||
formattedAmount,
|
||||
formattedUnallocatedCostAmount,
|
||||
formattedAllocatedCostAmount,
|
||||
entries: R.map(this.transformLandedCostEntry(transaction))(
|
||||
transaction.entries
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ILandedCostTransaction} transaction
|
||||
* @param {ILandedCostTransactionEntry} entry
|
||||
* @returns {ILandedCostTransactionEntryDOJO}
|
||||
*/
|
||||
public transformLandedCostEntry = R.curry(
|
||||
(
|
||||
transaction: ILandedCostTransaction,
|
||||
entry: ILandedCostTransactionEntry
|
||||
): ILandedCostTransactionEntryDOJO => {
|
||||
const { currencyCode } = transaction;
|
||||
|
||||
// Formatted entry amount.
|
||||
const formattedAmount = formatNumber(entry.amount, { currencyCode });
|
||||
|
||||
// Formatted entry unallocated cost amount.
|
||||
const formattedUnallocatedCostAmount = formatNumber(
|
||||
entry.unallocatedCostAmount,
|
||||
{ currencyCode }
|
||||
);
|
||||
// Formatted entry allocated cost amount.
|
||||
const formattedAllocatedCostAmount = formatNumber(
|
||||
entry.allocatedCostAmount,
|
||||
{ currencyCode }
|
||||
);
|
||||
return {
|
||||
...entry,
|
||||
formattedAmount,
|
||||
formattedUnallocatedCostAmount,
|
||||
formattedAllocatedCostAmount,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import Knex from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import BaseLandedCost from './BaseLandedCost';
|
||||
import { IAllocatedLandedCostDeletedPayload } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class RevertAllocatedLandedCost extends BaseLandedCost {
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Deletes the allocated landed cost.
|
||||
* ==================================
|
||||
* - Delete bill landed cost transaction with associated allocate entries.
|
||||
* - Delete the associated inventory transactions.
|
||||
* - Decrement allocated amount of landed cost transaction and entry.
|
||||
* - Revert journal entries.
|
||||
* ----------------------------------
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} landedCostId - Landed cost id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public deleteAllocatedLandedCost = async (
|
||||
tenantId: number,
|
||||
landedCostId: number
|
||||
): Promise<{
|
||||
landedCostId: number;
|
||||
}> => {
|
||||
// Retrieves the bill landed cost.
|
||||
const oldBillLandedCost = await this.getBillLandedCostOrThrowError(
|
||||
tenantId,
|
||||
landedCostId
|
||||
);
|
||||
// Deletes landed cost with associated transactions.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Delete landed cost transaction with assocaited locate entries.
|
||||
await this.deleteLandedCost(tenantId, landedCostId, trx);
|
||||
|
||||
// Triggers the event `onBillLandedCostCreated`.
|
||||
await this.eventPublisher.emitAsync(events.billLandedCost.onDeleted, {
|
||||
tenantId,
|
||||
oldBillLandedCost: oldBillLandedCost,
|
||||
billId: oldBillLandedCost.billId,
|
||||
trx,
|
||||
} as IAllocatedLandedCostDeletedPayload);
|
||||
|
||||
return { landedCostId };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the landed cost transaction with assocaited allocate entries.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} landedCostId - Landed cost id.
|
||||
*/
|
||||
public deleteLandedCost = async (
|
||||
tenantId: number,
|
||||
landedCostId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const { BillLandedCost, BillLandedCostEntry } =
|
||||
this.tenancy.models(tenantId);
|
||||
|
||||
// Deletes the bill landed cost allocated entries associated to landed cost.
|
||||
await BillLandedCostEntry.query(trx)
|
||||
.where('bill_located_cost_id', landedCostId)
|
||||
.delete();
|
||||
|
||||
// Delete the bill landed cost from the storage.
|
||||
await BillLandedCost.query(trx).where('id', landedCostId).delete();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import { Model } from 'objection';
|
||||
import {
|
||||
IBill,
|
||||
IExpense,
|
||||
ILandedCostTransaction,
|
||||
ILandedCostTransactionEntry,
|
||||
} from '@/interfaces';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import BillLandedCost from './BillLandedCost';
|
||||
import ExpenseLandedCost from './ExpenseLandedCost';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ERRORS } from './utils';
|
||||
|
||||
@Service()
|
||||
export default class TransactionLandedCost {
|
||||
@Inject()
|
||||
billLandedCost: BillLandedCost;
|
||||
|
||||
@Inject()
|
||||
expenseLandedCost: ExpenseLandedCost;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the cost transaction code model.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} transactionType - Transaction type.
|
||||
* @returns
|
||||
*/
|
||||
public getModel = (tenantId: number, transactionType: string): Model => {
|
||||
const Models = this.tenancy.models(tenantId);
|
||||
const Model = Models[transactionType];
|
||||
|
||||
if (!Model) {
|
||||
throw new ServiceError(ERRORS.COST_TYPE_UNDEFINED);
|
||||
}
|
||||
return Model;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mappes the given expense or bill transaction to landed cost transaction.
|
||||
* @param {string} transactionType - Transaction type.
|
||||
* @param {IBill|IExpense} transaction - Expense or bill transaction.
|
||||
* @returns {ILandedCostTransaction}
|
||||
*/
|
||||
public transformToLandedCost = R.curry(
|
||||
(
|
||||
transactionType: string,
|
||||
transaction: IBill | IExpense
|
||||
): ILandedCostTransaction => {
|
||||
return R.compose(
|
||||
R.when(
|
||||
R.always(transactionType === 'Bill'),
|
||||
this.billLandedCost.transformToLandedCost
|
||||
),
|
||||
R.when(
|
||||
R.always(transactionType === 'Expense'),
|
||||
this.expenseLandedCost.transformToLandedCost
|
||||
)
|
||||
)(transaction);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Transformes the given expense or bill entry to landed cost transaction entry.
|
||||
* @param {string} transactionType
|
||||
* @param {} transactionEntry
|
||||
* @returns {ILandedCostTransactionEntry}
|
||||
*/
|
||||
public transformToLandedCostEntry = (
|
||||
transactionType: 'Bill' | 'Expense',
|
||||
transactionEntry
|
||||
): ILandedCostTransactionEntry => {
|
||||
return R.compose(
|
||||
R.when(
|
||||
R.always(transactionType === 'Bill'),
|
||||
this.billLandedCost.transformToLandedCostEntry
|
||||
),
|
||||
R.when(
|
||||
R.always(transactionType === 'Expense'),
|
||||
this.expenseLandedCost.transformToLandedCostEntry
|
||||
)
|
||||
)(transactionEntry);
|
||||
};
|
||||
}
|
||||
46
packages/server/src/services/Purchases/LandedCost/utils.ts
Normal file
46
packages/server/src/services/Purchases/LandedCost/utils.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { IItemEntry, IBillLandedCostTransactionEntry } from '@/interfaces';
|
||||
import { transformToMap } from 'utils';
|
||||
|
||||
export const ERRORS = {
|
||||
COST_TYPE_UNDEFINED: 'COST_TYPE_UNDEFINED',
|
||||
LANDED_COST_ITEMS_IDS_NOT_FOUND: 'LANDED_COST_ITEMS_IDS_NOT_FOUND',
|
||||
COST_TRANSACTION_HAS_NO_ENOUGH_TO_LOCATE:
|
||||
'COST_TRANSACTION_HAS_NO_ENOUGH_TO_LOCATE',
|
||||
BILL_LANDED_COST_NOT_FOUND: 'BILL_LANDED_COST_NOT_FOUND',
|
||||
COST_ENTRY_ID_NOT_FOUND: 'COST_ENTRY_ID_NOT_FOUND',
|
||||
LANDED_COST_TRANSACTION_NOT_FOUND: 'LANDED_COST_TRANSACTION_NOT_FOUND',
|
||||
LANDED_COST_ENTRY_NOT_FOUND: 'LANDED_COST_ENTRY_NOT_FOUND',
|
||||
COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT:
|
||||
'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT',
|
||||
ALLOCATE_COST_SHOULD_NOT_BE_BILL: 'ALLOCATE_COST_SHOULD_NOT_BE_BILL',
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges item entry to bill located landed cost entry.
|
||||
* @param {IBillLandedCostTransactionEntry[]} locatedEntries -
|
||||
* @param {IItemEntry[]} billEntries -
|
||||
* @returns {(IBillLandedCostTransactionEntry & { entry: IItemEntry })[]}
|
||||
*/
|
||||
export const mergeLocatedWithBillEntries = (
|
||||
locatedEntries: IBillLandedCostTransactionEntry[],
|
||||
billEntries: IItemEntry[]
|
||||
): (IBillLandedCostTransactionEntry & { entry: IItemEntry })[] => {
|
||||
const billEntriesByEntryId = transformToMap(billEntries, 'id');
|
||||
|
||||
return locatedEntries.map((entry) => ({
|
||||
...entry,
|
||||
entry: billEntriesByEntryId.get(entry.entryId),
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
export const CONFIG = {
|
||||
COST_TYPES: {
|
||||
Expense: {
|
||||
entries: 'categories',
|
||||
},
|
||||
Bill: {
|
||||
entries: 'entries',
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user