import * as R from 'ramda'; import { Knex } from 'knex'; import { ISaleInvoice, IItemEntry, ILedgerEntry, AccountNormal, ILedger, } from '@/interfaces'; import { Service, Inject } from 'typedi'; import Ledger from '@/services/Accounting/Ledger'; import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import ItemsEntriesService from '@/services/Items/ItemsEntriesService'; @Service() export class SaleInvoiceGLEntries { @Inject() private tenancy: HasTenancyService; @Inject() private ledegrRepository: LedgerStorageService; @Inject() private itemsEntriesService: ItemsEntriesService; /** * Writes a sale invoice GL entries. * @param {number} tenantId - Tenant id. * @param {number} saleInvoiceId - Sale invoice id. * @param {Knex.Transaction} trx */ public writeInvoiceGLEntries = async ( tenantId: number, saleInvoiceId: number, trx?: Knex.Transaction ) => { const { SaleInvoice } = this.tenancy.models(tenantId); const { accountRepository } = this.tenancy.repositories(tenantId); const saleInvoice = await SaleInvoice.query(trx) .findById(saleInvoiceId) .withGraphFetched('entries.item'); // Find or create the A/R account. const ARAccount = await accountRepository.findOrCreateAccountReceivable( saleInvoice.currencyCode, {}, trx ); // Find or create tax payable account. const taxPayableAccount = await accountRepository.findOrCreateTaxPayable( {}, trx ); // Retrieves the ledger of the invoice. const ledger = this.getInvoiceGLedger( saleInvoice, ARAccount.id, taxPayableAccount.id ); // Commits the ledger entries to the storage as UOW. await this.ledegrRepository.commit(tenantId, ledger, trx); }; /** * Rewrites the given invoice GL entries. * @param {number} tenantId * @param {number} saleInvoiceId * @param {Knex.Transaction} trx */ public rewritesInvoiceGLEntries = async ( tenantId: number, saleInvoiceId: number, trx?: Knex.Transaction ) => { // Reverts the invoice GL entries. await this.revertInvoiceGLEntries(tenantId, saleInvoiceId, trx); // Writes the invoice GL entries. await this.writeInvoiceGLEntries(tenantId, saleInvoiceId, trx); }; /** * Reverts the given invoice GL entries. * @param {number} tenantId * @param {number} saleInvoiceId * @param {Knex.Transaction} trx */ public revertInvoiceGLEntries = async ( tenantId: number, saleInvoiceId: number, trx?: Knex.Transaction ) => { await this.ledegrRepository.deleteByReference( tenantId, saleInvoiceId, 'SaleInvoice', trx ); }; /** * Retrieves the given invoice ledger. * @param {ISaleInvoice} saleInvoice * @param {number} ARAccountId * @returns {ILedger} */ public getInvoiceGLedger = ( saleInvoice: ISaleInvoice, ARAccountId: number, taxPayableAccountId: number ): ILedger => { const entries = this.getInvoiceGLEntries( saleInvoice, ARAccountId, taxPayableAccountId ); return new Ledger(entries); }; /** * Retrieves the invoice GL common entry. * @param {ISaleInvoice} saleInvoice * @returns {Partial} */ private getInvoiceGLCommonEntry = ( saleInvoice: ISaleInvoice ): Partial => ({ credit: 0, debit: 0, currencyCode: saleInvoice.currencyCode, exchangeRate: saleInvoice.exchangeRate, transactionType: 'SaleInvoice', transactionId: saleInvoice.id, date: saleInvoice.invoiceDate, userId: saleInvoice.userId, transactionNumber: saleInvoice.invoiceNo, referenceNumber: saleInvoice.referenceNo, createdAt: saleInvoice.createdAt, indexGroup: 10, branchId: saleInvoice.branchId, }); /** * Retrieve receivable entry of the given invoice. * @param {ISaleInvoice} saleInvoice * @param {number} ARAccountId * @returns {ILedgerEntry} */ private getInvoiceReceivableEntry = ( saleInvoice: ISaleInvoice, ARAccountId: number ): ILedgerEntry => { const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice); return { ...commonEntry, debit: saleInvoice.totalLocal, accountId: ARAccountId, contactId: saleInvoice.customerId, accountNormal: AccountNormal.DEBIT, index: 1, } as ILedgerEntry; }; /** * Retrieve item income entry of the given invoice. * @param {ISaleInvoice} saleInvoice - * @param {IItemEntry} entry - * @param {number} index - * @returns {ILedgerEntry} */ private getInvoiceItemEntry = R.curry( ( saleInvoice: ISaleInvoice, entry: IItemEntry, index: number ): ILedgerEntry => { const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice); const localAmount = entry.amountExludingTax * saleInvoice.exchangeRate; return { ...commonEntry, credit: localAmount, accountId: entry.sellAccountId, note: entry.description, index: index + 2, itemId: entry.itemId, itemQuantity: entry.quantity, accountNormal: AccountNormal.CREDIT, projectId: entry.projectId || saleInvoice.projectId, taxRateId: entry.taxRateId, taxRate: entry.taxRate, }; } ); /** * Retreives the GL entry of tax payable. * @param {ISaleInvoice} saleInvoice - * @param {number} taxPayableAccountId - * @returns {ILedgerEntry} */ private getInvoiceTaxEntry = R.curry( ( saleInvoice: ISaleInvoice, taxPayableAccountId: number, entry: IItemEntry, index: number ): ILedgerEntry => { const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice); return { ...commonEntry, credit: entry.taxAmount, accountId: taxPayableAccountId, index: index + 1, indexGroup: 30, accountNormal: AccountNormal.CREDIT, taxRateId: entry.taxRateId, taxRate: entry.taxRate, }; } ); /** * Retrieves the invoice tax GL entries. * @param {ISaleInvoice} saleInvoice * @param {number} taxPayableAccountId * @returns {ILedgerEntry[]} */ private getInvoiceTaxEntries = ( saleInvoice: ISaleInvoice, taxPayableAccountId: number ): ILedgerEntry[] => { // Retrieves the non-zero tax entries. const nonZeroTaxEntries = this.itemsEntriesService.getNonZeroEntries( saleInvoice.entries ); const transformTaxEntry = this.getInvoiceTaxEntry( saleInvoice, taxPayableAccountId ); // Transforms the non-zero tax entries to GL entries. return nonZeroTaxEntries.map(transformTaxEntry); }; /** * Retrieves the invoice GL entries. * @param {ISaleInvoice} saleInvoice * @param {number} ARAccountId * @returns {ILedgerEntry[]} */ public getInvoiceGLEntries = ( saleInvoice: ISaleInvoice, ARAccountId: number, taxPayableAccountId: number ): ILedgerEntry[] => { const receivableEntry = this.getInvoiceReceivableEntry( saleInvoice, ARAccountId ); const transformItemEntry = this.getInvoiceItemEntry(saleInvoice); const creditEntries = saleInvoice.entries.map(transformItemEntry); const taxEntries = this.getInvoiceTaxEntries( saleInvoice, taxPayableAccountId ); return [receivableEntry, ...creditEntries, ...taxEntries]; }; }