mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
refactor: GL entries
This commit is contained in:
@@ -20,6 +20,8 @@ import { SaleReceiptIncrement } from './commands/SaleReceiptIncrement.service';
|
||||
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
|
||||
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
|
||||
import { SaleReceiptsController } from './SaleReceipts.controller';
|
||||
import { SaleReceiptGLEntriesSubscriber } from './subscribers/SaleReceiptGLEntriesSubscriber';
|
||||
import { SaleReceiptGLEntries } from './ledger/SaleReceiptGLEntries';
|
||||
|
||||
@Module({
|
||||
controllers: [SaleReceiptsController],
|
||||
@@ -46,6 +48,8 @@ import { SaleReceiptsController } from './SaleReceipts.controller';
|
||||
SaleReceiptDTOTransformer,
|
||||
SaleReceiptBrandingTemplate,
|
||||
SaleReceiptIncrement,
|
||||
SaleReceiptGLEntries,
|
||||
SaleReceiptGLEntriesSubscriber
|
||||
],
|
||||
})
|
||||
export class SaleReceiptsModule {}
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
// 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 saleReceipt = await SaleReceipt.query(trx)
|
||||
// .findById(saleReceiptId)
|
||||
// .withGraphFetched('entries.item');
|
||||
|
||||
// // Retrieve the income entries ledger.
|
||||
// const incomeLedger = this.getIncomeEntriesLedger(saleReceipt);
|
||||
|
||||
// // 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): Ledger => {
|
||||
// const entries = this.getIncomeGLEntries(saleReceipt);
|
||||
|
||||
// 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.localAmount,
|
||||
// accountId: saleReceipt.depositAccountId,
|
||||
// index: 1,
|
||||
// accountNormal: AccountNormal.DEBIT,
|
||||
// };
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Retrieves the income GL entries.
|
||||
// * @param {ISaleReceipt} saleReceipt -
|
||||
// * @returns {ILedgerEntry[]}
|
||||
// */
|
||||
// private getIncomeGLEntries = (saleReceipt: ISaleReceipt): ILedgerEntry[] => {
|
||||
// const getItemEntry = this.getReceiptIncomeItemEntry(saleReceipt);
|
||||
|
||||
// const creditEntries = saleReceipt.entries.map(getItemEntry);
|
||||
// const depositEntry = this.getReceiptDepositEntry(saleReceipt);
|
||||
|
||||
// return [depositEntry, ...creditEntries];
|
||||
// };
|
||||
// }
|
||||
@@ -0,0 +1,167 @@
|
||||
import * as R from 'ramda';
|
||||
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { AccountNormal } from '@/modules/Accounts/Accounts.types';
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { SaleReceipt } from '../models/SaleReceipt';
|
||||
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
|
||||
|
||||
export class SaleReceiptGL {
|
||||
private saleReceipt: SaleReceipt;
|
||||
private discountAccountId: number;
|
||||
private otherChargesAccountId: number;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {SaleReceipt} saleReceipt - Sale receipt.
|
||||
*/
|
||||
constructor(saleReceipt: SaleReceipt) {
|
||||
this.saleReceipt = saleReceipt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the discount account id.
|
||||
* @param {number} discountAccountId - Discount account id.
|
||||
*/
|
||||
setDiscountAccountId(discountAccountId: number) {
|
||||
this.discountAccountId = discountAccountId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the other charges account id.
|
||||
* @param {number} otherChargesAccountId - Other charges account id.
|
||||
*/
|
||||
setOtherChargesAccountId(otherChargesAccountId: number) {
|
||||
this.otherChargesAccountId = otherChargesAccountId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the income GL common entry.
|
||||
*/
|
||||
private getIncomeGLCommonEntry = () => {
|
||||
return {
|
||||
currencyCode: this.saleReceipt.currencyCode,
|
||||
exchangeRate: this.saleReceipt.exchangeRate,
|
||||
|
||||
transactionType: 'SaleReceipt',
|
||||
transactionId: this.saleReceipt.id,
|
||||
|
||||
date: this.saleReceipt.receiptDate,
|
||||
|
||||
transactionNumber: this.saleReceipt.receiptNumber,
|
||||
referenceNumber: this.saleReceipt.referenceNo,
|
||||
|
||||
createdAt: this.saleReceipt.createdAt,
|
||||
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
|
||||
userId: this.saleReceipt.userId,
|
||||
branchId: this.saleReceipt.branchId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve receipt income item G/L entry.
|
||||
* @param {ItemEntry} entry - Item entry.
|
||||
* @param {number} index - Index.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getReceiptIncomeItemEntry = R.curry(
|
||||
(entry: ItemEntry, index: number): ILedgerEntry => {
|
||||
const commonEntry = this.getIncomeGLCommonEntry();
|
||||
const totalLocal =
|
||||
entry.totalExcludingTax * this.saleReceipt.exchangeRate;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: totalLocal,
|
||||
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.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getReceiptDepositEntry = (): ILedgerEntry => {
|
||||
const commonEntry = this.getIncomeGLCommonEntry();
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: this.saleReceipt.totalLocal,
|
||||
accountId: this.saleReceipt.depositAccountId,
|
||||
index: 1,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the discount GL entry.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getDiscountEntry = (): ILedgerEntry => {
|
||||
const commonEntry = this.getIncomeGLCommonEntry();
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: this.saleReceipt.discountAmountLocal,
|
||||
accountId: this.discountAccountId,
|
||||
index: 1,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the adjustment GL entry.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getAdjustmentEntry = (): ILedgerEntry => {
|
||||
const commonEntry = this.getIncomeGLCommonEntry();
|
||||
const adjustmentAmount = Math.abs(this.saleReceipt.adjustmentLocal);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: this.saleReceipt.adjustmentLocal < 0 ? adjustmentAmount : 0,
|
||||
credit: this.saleReceipt.adjustmentLocal > 0 ? adjustmentAmount : 0,
|
||||
accountId: this.otherChargesAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
index: 1,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the income GL entries.
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getIncomeGLEntries = (): ILedgerEntry[] => {
|
||||
const getItemEntry = this.getReceiptIncomeItemEntry;
|
||||
|
||||
const creditEntries = this.saleReceipt.entries.map((e, index) =>
|
||||
getItemEntry(e, index),
|
||||
);
|
||||
const depositEntry = this.getReceiptDepositEntry();
|
||||
const discountEntry = this.getDiscountEntry();
|
||||
const adjustmentEntry = this.getAdjustmentEntry();
|
||||
|
||||
return [depositEntry, ...creditEntries, discountEntry, adjustmentEntry];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the income GL ledger.
|
||||
* @returns {ILedger}
|
||||
*/
|
||||
public getIncomeLedger = (): ILedger => {
|
||||
const entries = this.getIncomeGLEntries();
|
||||
|
||||
return new Ledger(entries);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
|
||||
import { SaleReceipt } from '../models/SaleReceipt';
|
||||
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||
import { SaleReceiptGL } from './SaleReceiptGL';
|
||||
|
||||
@Injectable()
|
||||
export class SaleReceiptGLEntries {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
|
||||
@Inject(SaleReceipt.name)
|
||||
private readonly saleReceiptModel: typeof SaleReceipt,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates income GL entries.
|
||||
* @param {number} saleReceiptId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public writeIncomeGLEntries = async (
|
||||
saleReceiptId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const saleReceipt = await this.saleReceiptModel.query(trx)
|
||||
.findById(saleReceiptId)
|
||||
.withGraphFetched('entries.item');
|
||||
|
||||
// Find or create the discount expense account.
|
||||
const discountAccount = await this.accountRepository.findOrCreateDiscountAccount(
|
||||
{},
|
||||
trx
|
||||
);
|
||||
// Find or create the other charges account.
|
||||
const otherChargesAccount =
|
||||
await this.accountRepository.findOrCreateOtherChargesAccount({}, trx);
|
||||
|
||||
// Retrieves the income ledger.
|
||||
const incomeLedger = new SaleReceiptGL(saleReceipt)
|
||||
.setDiscountAccountId(discountAccount.id)
|
||||
.setOtherChargesAccountId(otherChargesAccount.id)
|
||||
.getIncomeLedger();
|
||||
|
||||
// Commits the ledger entries to the storage.
|
||||
await this.ledgerStorage.commit(incomeLedger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the receipt GL entries.
|
||||
* @param {number} saleReceiptId - Sale receipt id.
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public revertReceiptGLEntries = async (
|
||||
saleReceiptId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
await this.ledgerStorage.deleteByReference(
|
||||
saleReceiptId,
|
||||
'SaleReceipt',
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites the receipt GL entries.
|
||||
* @param {number} saleReceiptId
|
||||
* @param {Knex.Transaction} trx
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public rewriteReceiptGLEntries = async (
|
||||
saleReceiptId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
// Reverts the receipt GL entries.
|
||||
await this.revertReceiptGLEntries(saleReceiptId, trx);
|
||||
|
||||
// Writes the income GL entries.
|
||||
await this.writeIncomeGLEntries(saleReceiptId, trx);
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,11 @@ import { Model, mixin } from 'objection';
|
||||
// import { DEFAULT_VIEWS } from '@/services/Sales/Receipts/constants';
|
||||
// import ModelSearchable from './ModelSearchable';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
|
||||
import { Customer } from '@/modules/Customers/models/Customer';
|
||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||
import { Branch } from '@/modules/Branches/models/Branch.model';
|
||||
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
|
||||
|
||||
export class SaleReceipt extends BaseModel {
|
||||
amount: number;
|
||||
@@ -27,6 +32,12 @@ export class SaleReceipt extends BaseModel {
|
||||
createdAt: Date;
|
||||
updatedAt: Date | null;
|
||||
|
||||
customer!: Customer;
|
||||
entries!: ItemEntry[];
|
||||
transactions!: AccountTransaction[];
|
||||
branch!: Branch;
|
||||
warehouse!: Warehouse;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
ISaleReceiptCreatedPayload,
|
||||
ISaleReceiptEditedPayload,
|
||||
ISaleReceiptEventDeletedPayload,
|
||||
} from '../types/SaleReceipts.types';
|
||||
import { SaleReceiptGLEntries } from '../ledger/SaleReceiptGLEntries';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class SaleReceiptGLEntriesSubscriber {
|
||||
constructor(private readonly saleReceiptGLEntries: SaleReceiptGLEntries) {}
|
||||
|
||||
/**
|
||||
* Handles writing sale receipt income journal entries once created.
|
||||
* @param {ISaleReceiptCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onCreated)
|
||||
@OnEvent(events.saleReceipt.onClosed)
|
||||
public async handleWriteReceiptIncomeJournalEntrieOnCreate({
|
||||
saleReceiptId,
|
||||
saleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptCreatedPayload) {
|
||||
// Can't continue if the sale receipt is not closed yet.
|
||||
if (!saleReceipt.closedAt) return null;
|
||||
|
||||
// Writes the sale receipt income journal entries.
|
||||
await this.saleReceiptGLEntries.writeIncomeGLEntries(saleReceiptId, trx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles sale receipt revert jouranl entries once be deleted.
|
||||
* @param {ISaleReceiptEventDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onDeleted)
|
||||
public async handleRevertReceiptJournalEntriesOnDeleted({
|
||||
saleReceiptId,
|
||||
trx,
|
||||
}: ISaleReceiptEventDeletedPayload) {
|
||||
await this.saleReceiptGLEntries.revertReceiptGLEntries(saleReceiptId, trx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing sale receipt income journal entries once be edited.
|
||||
* @param {ISaleReceiptEditedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onEdited)
|
||||
public async handleWriteReceiptIncomeJournalEntrieOnEdited({
|
||||
saleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptEditedPayload) {
|
||||
// Can't continue if the sale receipt is not closed yet.
|
||||
if (!saleReceipt.closedAt) return null;
|
||||
|
||||
// Writes the sale receipt income journal entries.
|
||||
await this.saleReceiptGLEntries.rewriteReceiptGLEntries(
|
||||
saleReceipt.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user