Files
bigcapital/packages/server/src/services/Sales/Receipts/SaleReceiptGLEntries.ts
Ahmed Bouhuolia 994c441bb8 feat: add local discount and adjustment calculations to financial models and transformers
- Introduced `discountAmountLocal` and `adjustmentLocal` properties across Bill, CreditNote, SaleInvoice, SaleReceipt, and VendorCredit models to calculate amounts in local currency.
- Updated transformers for CreditNote, PurchaseInvoice, and VendorCredit to include formatted representations of local discount and adjustment amounts.
- Enhanced GL entry services to handle discount and adjustment entries for SaleReceipt and CreditNote, ensuring accurate ledger entries.
- Improved overall consistency in handling financial calculations across various models and services.
2024-12-08 18:11:03 +02:00

260 lines
6.8 KiB
TypeScript

import { Knex } from 'knex';
import { Service, Inject } from 'typedi';
import * as R from 'ramda';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import {
AccountNormal,
ILedgerEntry,
ISaleReceipt,
IItemEntry,
} from '@/interfaces';
import Ledger from '@/services/Accounting/Ledger';
@Service()
export class SaleReceiptGLEntries {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private ledgerStorage: LedgerStorageService;
/**
* Creates income GL entries.
* @param {number} tenantId
* @param {number} saleReceiptId
* @param {Knex.Transaction} trx
*/
public writeIncomeGLEntries = async (
tenantId: number,
saleReceiptId: number,
trx?: Knex.Transaction
): Promise<void> => {
const { SaleReceipt } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
const saleReceipt = await SaleReceipt.query(trx)
.findById(saleReceiptId)
.withGraphFetched('entries.item');
// Find or create the discount expense account.
const discountAccount = await accountRepository.findOrCreateDiscountAccount(
{},
trx
);
// Find or create the other charges account.
const otherChargesAccount =
await accountRepository.findOrCreateOtherChargesAccount({}, trx);
// Retrieve the income entries ledger.
const incomeLedger = this.getIncomeEntriesLedger(
saleReceipt,
discountAccount.id,
otherChargesAccount.id
);
// Commits the ledger entries to the storage.
await this.ledgerStorage.commit(tenantId, incomeLedger, trx);
};
/**
* Reverts the receipt GL entries.
* @param {number} tenantId
* @param {number} saleReceiptId
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public revertReceiptGLEntries = async (
tenantId: number,
saleReceiptId: number,
trx?: Knex.Transaction
): Promise<void> => {
await this.ledgerStorage.deleteByReference(
tenantId,
saleReceiptId,
'SaleReceipt',
trx
);
};
/**
* Rewrites the receipt GL entries.
* @param {number} tenantId
* @param {number} saleReceiptId
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public rewriteReceiptGLEntries = async (
tenantId: number,
saleReceiptId: number,
trx?: Knex.Transaction
): Promise<void> => {
// Reverts the receipt GL entries.
await this.revertReceiptGLEntries(tenantId, saleReceiptId, trx);
// Writes the income GL entries.
await this.writeIncomeGLEntries(tenantId, saleReceiptId, trx);
};
/**
* Retrieves the income GL ledger.
* @param {ISaleReceipt} saleReceipt
* @returns {Ledger}
*/
private getIncomeEntriesLedger = (
saleReceipt: ISaleReceipt,
discountAccountId: number,
otherChargesAccountId: number
): Ledger => {
const entries = this.getIncomeGLEntries(
saleReceipt,
discountAccountId,
otherChargesAccountId
);
return new Ledger(entries);
};
/**
* Retireves the income GL common entry.
* @param {ISaleReceipt} saleReceipt -
*/
private getIncomeGLCommonEntry = (saleReceipt: ISaleReceipt) => {
return {
currencyCode: saleReceipt.currencyCode,
exchangeRate: saleReceipt.exchangeRate,
transactionType: 'SaleReceipt',
transactionId: saleReceipt.id,
date: saleReceipt.receiptDate,
transactionNumber: saleReceipt.receiptNumber,
referenceNumber: saleReceipt.referenceNo,
createdAt: saleReceipt.createdAt,
credit: 0,
debit: 0,
userId: saleReceipt.userId,
branchId: saleReceipt.branchId,
};
};
/**
* Retrieve receipt income item GL entry.
* @param {ISaleReceipt} saleReceipt -
* @param {IItemEntry} entry -
* @param {number} index -
* @returns {ILedgerEntry}
*/
private getReceiptIncomeItemEntry = R.curry(
(
saleReceipt: ISaleReceipt,
entry: IItemEntry,
index: number
): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
const itemIncome = entry.amount * saleReceipt.exchangeRate;
return {
...commonEntry,
credit: itemIncome,
accountId: entry.item.sellAccountId,
note: entry.description,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
accountNormal: AccountNormal.CREDIT,
};
}
);
/**
* Retrieves the receipt deposit GL deposit entry.
* @param {ISaleReceipt} saleReceipt
* @returns {ILedgerEntry}
*/
private getReceiptDepositEntry = (
saleReceipt: ISaleReceipt
): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
return {
...commonEntry,
debit: saleReceipt.totalLocal,
accountId: saleReceipt.depositAccountId,
index: 1,
accountNormal: AccountNormal.DEBIT,
};
};
/**
* Retrieves the discount GL entry.
* @param {ISaleReceipt} saleReceipt
* @param {number} discountAccountId
* @returns {ILedgerEntry}
*/
private getDiscountEntry = (
saleReceipt: ISaleReceipt,
discountAccountId: number
): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
return {
...commonEntry,
debit: saleReceipt.discountAmount,
accountId: discountAccountId,
index: 1,
accountNormal: AccountNormal.CREDIT,
};
};
/**
* Retrieves the adjustment GL entry.
* @param {ISaleReceipt} saleReceipt
* @param {number} adjustmentAccountId
* @returns {ILedgerEntry}
*/
private getAdjustmentEntry = (
saleReceipt: ISaleReceipt,
adjustmentAccountId: number
): ILedgerEntry => {
const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
const adjustmentAmount = Math.abs(saleReceipt.adjustment);
return {
...commonEntry,
debit: saleReceipt.adjustment < 0 ? adjustmentAmount : 0,
credit: saleReceipt.adjustment > 0 ? adjustmentAmount : 0,
accountId: adjustmentAccountId,
accountNormal: AccountNormal.CREDIT,
index: 1,
};
};
/**
* Retrieves the income GL entries.
* @param {ISaleReceipt} saleReceipt -
* @returns {ILedgerEntry[]}
*/
private getIncomeGLEntries = (
saleReceipt: ISaleReceipt,
discountAccountId: number,
otherChargesAccountId: number
): ILedgerEntry[] => {
const getItemEntry = this.getReceiptIncomeItemEntry(saleReceipt);
const creditEntries = saleReceipt.entries.map(getItemEntry);
const depositEntry = this.getReceiptDepositEntry(saleReceipt);
const discountEntry = this.getDiscountEntry(saleReceipt, discountAccountId);
const adjustmentEntry = this.getAdjustmentEntry(
saleReceipt,
otherChargesAccountId
);
return [depositEntry, ...creditEntries, discountEntry, adjustmentEntry];
};
}