diff --git a/packages/server-nest/src/modules/Accounts/Accounts.constants.ts b/packages/server-nest/src/modules/Accounts/Accounts.constants.ts index b37dbd218..bcd1cc641 100644 --- a/packages/server-nest/src/modules/Accounts/Accounts.constants.ts +++ b/packages/server-nest/src/modules/Accounts/Accounts.constants.ts @@ -1,3 +1,14 @@ +export const OtherExpensesAccount = { + name: 'Other Expenses', + slug: 'other-expenses', + account_type: 'other-expense', + code: '40011', + description: '', + active: 1, + index: 1, + predefined: 1, +}; + export const TaxPayableAccount = { name: 'Tax Payable', slug: 'tax-payable', @@ -42,6 +53,36 @@ export const StripeClearingAccount = { predefined: true, }; +export const DiscountExpenseAccount = { + name: 'Discount', + slug: 'discount', + account_type: 'other-income', + code: '40008', + active: true, + index: 1, + predefined: true, +}; + +export const PurchaseDiscountAccount = { + name: 'Purchase Discount', + slug: 'purchase-discount', + account_type: 'other-expense', + code: '40009', + active: true, + index: 1, + predefined: true, +}; + +export const OtherChargesAccount = { + name: 'Other Charges', + slug: 'other-charges', + account_type: 'other-income', + code: '40010', + active: true, + index: 1, + predefined: true, +}; + export const SeedAccounts = [ { name: 'Bank Account', @@ -231,6 +272,7 @@ export const SeedAccounts = [ }, // Expenses + OtherExpensesAccount, { name: 'Other Expenses', slug: 'other-expenses', @@ -358,6 +400,9 @@ export const SeedAccounts = [ }, UnearnedRevenueAccount, PrepardExpenses, + DiscountExpenseAccount, + PurchaseDiscountAccount, + OtherChargesAccount, ]; export const ACCOUNT_TYPE = { diff --git a/packages/server-nest/src/modules/Accounts/Accounts.module.ts b/packages/server-nest/src/modules/Accounts/Accounts.module.ts index 86f40b5e7..60c9a847a 100644 --- a/packages/server-nest/src/modules/Accounts/Accounts.module.ts +++ b/packages/server-nest/src/modules/Accounts/Accounts.module.ts @@ -13,7 +13,6 @@ import { TransformerInjectable } from '../Transformer/TransformerInjectable.serv import { ActivateAccount } from './ActivateAccount.service'; import { GetAccountTypesService } from './GetAccountTypes.service'; import { GetAccountTransactionsService } from './GetAccountTransactions.service'; -// import { EditAccount } from './EditAccount.service'; // import { GetAccountsService } from './GetAccounts.service'; @Module({ diff --git a/packages/server-nest/src/modules/Accounts/repositories/Account.repository.ts b/packages/server-nest/src/modules/Accounts/repositories/Account.repository.ts index 0ce448ddd..3f3ef3d9f 100644 --- a/packages/server-nest/src/modules/Accounts/repositories/Account.repository.ts +++ b/packages/server-nest/src/modules/Accounts/repositories/Account.repository.ts @@ -6,7 +6,11 @@ import { Account } from '../models/Account.model'; import { I18nService } from 'nestjs-i18n'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { + DiscountExpenseAccount, + OtherChargesAccount, + OtherExpensesAccount, PrepardExpenses, + PurchaseDiscountAccount, StripeClearingAccount, TaxPayableAccount, UnearnedRevenueAccount, @@ -371,7 +375,6 @@ export class AccountRepository extends TenantRepository { currencyCode: tenantMeta.baseCurrency, ...extraAttrs, }; - let result = await this.model .query(trx) .findOne({ slug: OtherExpensesAccount.slug, ..._extraAttrs }); diff --git a/packages/server-nest/src/modules/BillPayments/BillPayments.module.ts b/packages/server-nest/src/modules/BillPayments/BillPayments.module.ts index c1cae0b90..85c704d99 100644 --- a/packages/server-nest/src/modules/BillPayments/BillPayments.module.ts +++ b/packages/server-nest/src/modules/BillPayments/BillPayments.module.ts @@ -12,8 +12,13 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { BranchTransactionDTOTransformer } from '../Branches/integrations/BranchTransactionDTOTransform'; import { BranchesSettingsService } from '../Branches/BranchesSettings'; import { BillPaymentsController } from './BillPayments.controller'; +import { BillPaymentGLEntries } from './commands/BillPaymentGLEntries'; +import { BillPaymentGLEntriesSubscriber } from './subscribers/BillPaymentGLEntriesSubscriber'; +import { LedgerModule } from '../Ledger/Ledger.module'; +import { AccountsModule } from '../Accounts/Accounts.module'; @Module({ + imports: [LedgerModule, AccountsModule], providers: [ BillPaymentsApplication, CreateBillPaymentService, @@ -27,6 +32,8 @@ import { BillPaymentsController } from './BillPayments.controller'; BranchTransactionDTOTransformer, BranchesSettingsService, TenancyContext, + BillPaymentGLEntries, + BillPaymentGLEntriesSubscriber, ], exports: [BillPaymentValidators], controllers: [BillPaymentsController], diff --git a/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGL.ts b/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGL.ts index e69de29bb..ea44edea7 100644 --- a/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGL.ts +++ b/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGL.ts @@ -0,0 +1,191 @@ +import { sumBy } from 'lodash'; +import { BillPayment } from '../models/BillPayment'; +import { AccountNormal } from '@/interfaces/Account'; +import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; +import { Ledger } from '@/modules/Ledger/Ledger'; + +export class BillPaymentGL { + private billPayment: BillPayment; + private APAccountId: number; + private gainLossAccountId: number; + private baseCurrency: string; + + constructor(billPayment: BillPayment) { + this.billPayment = billPayment; + } + + /** + * Sets the A/P account ID. + * @param {number} APAccountId - + * @returns {BillPaymentGL} + */ + setAPAccountId(APAccountId: number) { + this.APAccountId = APAccountId; + return this; + } + + /** + * Sets the gain/loss account ID. + * @param {number} gainLossAccountId - + * @returns {BillPaymentGL} + */ + setGainLossAccountId(gainLossAccountId: number) { + this.gainLossAccountId = gainLossAccountId; + return this; + } + + /** + * Sets the base currency. + * @param {string} baseCurrency - + * @returns {BillPaymentGL} + */ + setBaseCurrency(baseCurrency: string) { + this.baseCurrency = baseCurrency; + return this; + } + + /** + * Retrieves the payment common entry. + */ + private get paymentCommonEntry() { + const formattedDate = moment(this.billPayment.paymentDate).format( + 'YYYY-MM-DD', + ); + + return { + debit: 0, + credit: 0, + + exchangeRate: this.billPayment.exchangeRate, + currencyCode: this.billPayment.currencyCode, + + transactionId: this.billPayment.id, + transactionType: 'BillPayment', + + transactionNumber: this.billPayment.paymentNumber, + referenceNumber: this.billPayment.reference, + + date: formattedDate, + createdAt: this.billPayment.createdAt, + + branchId: this.billPayment.branchId, + }; + } + + /** + * Calculates the payment total exchange gain/loss. + * @param {IBillPayment} paymentReceive - Payment receive with entries. + * @returns {number} + */ + private get paymentExGainOrLoss(): number { + return sumBy(this.billPayment.entries, (entry) => { + const paymentLocalAmount = + entry.paymentAmount * this.billPayment.exchangeRate; + const invoicePayment = entry.paymentAmount * entry.bill.exchangeRate; + + return invoicePayment - paymentLocalAmount; + }); + } + + /** + * Retrieves the payment exchange gain/loss entries. + * @returns {ILedgerEntry[]} + */ + private get paymentExGainOrLossEntries(): ILedgerEntry[] { + const commonEntry = this.paymentCommonEntry; + const totalExGainOrLoss = this.paymentExGainOrLoss; + const absExGainOrLoss = Math.abs(totalExGainOrLoss); + + return totalExGainOrLoss + ? [ + { + ...commonEntry, + currencyCode: this.baseCurrency, + exchangeRate: 1, + credit: totalExGainOrLoss > 0 ? absExGainOrLoss : 0, + debit: totalExGainOrLoss < 0 ? absExGainOrLoss : 0, + accountId: this.gainLossAccountId, + index: 2, + indexGroup: 20, + accountNormal: AccountNormal.DEBIT, + }, + { + ...commonEntry, + currencyCode: this.baseCurrency, + exchangeRate: 1, + debit: totalExGainOrLoss > 0 ? absExGainOrLoss : 0, + credit: totalExGainOrLoss < 0 ? absExGainOrLoss : 0, + accountId: this.APAccountId, + index: 3, + accountNormal: AccountNormal.DEBIT, + }, + ] + : []; + } + + /** + * Retrieves the payment deposit GL entry. + * @param {IBillPayment} billPayment + * @returns {ILedgerEntry} + */ + private get paymentGLEntry(): ILedgerEntry { + const commonEntry = this.paymentCommonEntry; + + return { + ...commonEntry, + credit: this.billPayment.localAmount, + accountId: this.billPayment.paymentAccountId, + accountNormal: AccountNormal.DEBIT, + index: 2, + }; + } + + /** + * Retrieves the payment GL payable entry. + * @returns {ILedgerEntry} + */ + private get paymentGLPayableEntry(): ILedgerEntry { + const commonEntry = this.paymentCommonEntry; + + return { + ...commonEntry, + exchangeRate: this.billPayment.exchangeRate, + debit: this.billPayment.localAmount, + contactId: this.billPayment.vendorId, + accountId: this.APAccountId, + accountNormal: AccountNormal.CREDIT, + index: 1, + }; + } + + /** + * Retrieves the payment GL entries. + * @param {IBillPayment} billPayment + * @param {number} APAccountId + * @returns {ILedgerEntry[]} + */ + private get paymentGLEntries(): ILedgerEntry[] { + // Retrieves the payment deposit entry. + const paymentEntry = this.paymentGLEntry; + + // Retrieves the payment debit A/R entry. + const payableEntry = this.paymentGLPayableEntry; + + // Retrieves the exchange gain/loss entries. + const exGainLossEntries = this.paymentExGainOrLossEntries; + + return [paymentEntry, payableEntry, ...exGainLossEntries]; + }; + + /** + * Retrieves the bill payment ledger. + * @param {IBillPayment} billPayment + * @param {number} APAccountId + * @returns {Ledger} + */ + public getBillPaymentLedger(): Ledger { + const entries = this.paymentGLEntries; + + return new Ledger(entries); + } +} diff --git a/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGLEntries.ts b/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGLEntries.ts index 2ff515d7b..db91a8132 100644 --- a/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGLEntries.ts +++ b/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGLEntries.ts @@ -1,277 +1,99 @@ -// import moment from 'moment'; -// import { sumBy } from 'lodash'; -// import { Service, Inject } from 'typedi'; -// import { Knex } from 'knex'; -// import { AccountNormal, IBillPayment, ILedgerEntry } from '@/interfaces'; -// import Ledger from '@/services/Accounting/Ledger'; -// import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; -// import HasTenancyService from '@/services/Tenancy/TenancyService'; -// import { TenantMetadata } from '@/system/models'; +import { Knex } from 'knex'; +import { BillPaymentGL } from './BillPaymentGL'; +import { Inject, Injectable } from '@nestjs/common'; +import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { BillPayment } from '../models/BillPayment'; +import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; -// @Service() -// export class BillPaymentGLEntries { -// @Inject() -// private tenancy: HasTenancyService; +@Injectable() +export class BillPaymentGLEntries { + constructor( + private readonly ledgerStorage: LedgerStorageService, + private readonly accountRepository: AccountRepository, + private readonly tenancyContext: TenancyContext, -// @Inject() -// private ledgerStorage: LedgerStorageService; + @Inject(BillPayment.name) + private readonly billPaymentModel: typeof BillPayment, -// /** -// * Creates a bill payment GL entries. -// * @param {number} tenantId -// * @param {number} billPaymentId -// * @param {Knex.Transaction} trx -// */ -// public writePaymentGLEntries = async ( -// tenantId: number, -// billPaymentId: number, -// trx?: Knex.Transaction -// ): Promise => { -// const { accountRepository } = this.tenancy.repositories(tenantId); -// const { BillPayment, Account } = this.tenancy.models(tenantId); + @Inject(Account.name) + private readonly accountModel: typeof Account, + ) {} -// // Retrieves the bill payment details with associated entries. -// const payment = await BillPayment.query(trx) -// .findById(billPaymentId) -// .withGraphFetched('entries.bill'); + /** + * Creates a bill payment GL entries. + * @param {number} tenantId + * @param {number} billPaymentId + * @param {Knex.Transaction} trx + */ + public writePaymentGLEntries = async ( + billPaymentId: number, + trx?: Knex.Transaction, + ): Promise => { + // Retrieves the bill payment details with associated entries. + const payment = await this.billPaymentModel + .query(trx) + .findById(billPaymentId) + .withGraphFetched('entries.bill'); -// // Retrieves the given tenant metadata. -// const tenantMeta = await TenantMetadata.query().findOne({ tenantId }); + // Retrieves the given tenant metadata. + const tenantMeta = await this.tenancyContext.getTenantMetadata(); -// // Finds or creates a new A/P account of the given currency. -// const APAccount = await accountRepository.findOrCreateAccountsPayable( -// payment.currencyCode, -// {}, -// trx -// ); -// // Exchange gain or loss account. -// const EXGainLossAccount = await Account.query(trx).modify( -// 'findBySlug', -// 'exchange-grain-loss' -// ); -// // Retrieves the bill payment ledger. -// const ledger = this.getBillPaymentLedger( -// payment, -// APAccount.id, -// EXGainLossAccount.id, -// tenantMeta.baseCurrency -// ); -// // Commits the ledger on the storage. -// await this.ledgerStorage.commit(tenantId, ledger, trx); -// }; + // Finds or creates a new A/P account of the given currency. + const APAccount = await this.accountRepository.findOrCreateAccountsPayable( + payment.currencyCode, + {}, + trx, + ); + // Exchange gain or loss account. + const EXGainLossAccount = await this.accountModel + .query(trx) + .modify('findBySlug', 'exchange-grain-loss') + .first(); -// /** -// * Rewrites the bill payment GL entries. -// * @param {number} tenantId -// * @param {number} billPaymentId -// * @param {Knex.Transaction} trx -// */ -// public rewritePaymentGLEntries = async ( -// tenantId: number, -// billPaymentId: number, -// trx?: Knex.Transaction -// ): Promise => { -// // Revert payment GL entries. -// await this.revertPaymentGLEntries(tenantId, billPaymentId, trx); + // Retrieves the bill payment ledger. + const ledger = new BillPaymentGL(payment) + .setAPAccountId(APAccount.id) + .setGainLossAccountId(EXGainLossAccount.id) + .setBaseCurrency(tenantMeta.baseCurrency) + .getBillPaymentLedger(); -// // Write payment GL entries. -// await this.writePaymentGLEntries(tenantId, billPaymentId, trx); -// }; + // Commits the ledger on the storage. + await this.ledgerStorage.commit(ledger, trx); + }; -// /** -// * Reverts the bill payment GL entries. -// * @param {number} tenantId -// * @param {number} billPaymentId -// * @param {Knex.Transaction} trx -// */ -// public revertPaymentGLEntries = async ( -// tenantId: number, -// billPaymentId: number, -// trx?: Knex.Transaction -// ): Promise => { -// await this.ledgerStorage.deleteByReference( -// tenantId, -// billPaymentId, -// 'BillPayment', -// trx -// ); -// }; + /** + * Rewrites the bill payment GL entries. + * @param {number} tenantId + * @param {number} billPaymentId + * @param {Knex.Transaction} trx + */ + public rewritePaymentGLEntries = async ( + billPaymentId: number, + trx?: Knex.Transaction, + ): Promise => { + // Revert payment GL entries. + await this.revertPaymentGLEntries(billPaymentId, trx); -// /** -// * Retrieves the payment common entry. -// * @param {IBillPayment} billPayment -// * @returns {} -// */ -// private getPaymentCommonEntry = (billPayment: IBillPayment) => { -// const formattedDate = moment(billPayment.paymentDate).format('YYYY-MM-DD'); + // Write payment GL entries. + await this.writePaymentGLEntries(billPaymentId, trx); + }; -// return { -// debit: 0, -// credit: 0, - -// exchangeRate: billPayment.exchangeRate, -// currencyCode: billPayment.currencyCode, - -// transactionId: billPayment.id, -// transactionType: 'BillPayment', - -// transactionNumber: billPayment.paymentNumber, -// referenceNumber: billPayment.reference, - -// date: formattedDate, -// createdAt: billPayment.createdAt, - -// branchId: billPayment.branchId, -// }; -// }; - -// /** -// * Calculates the payment total exchange gain/loss. -// * @param {IBillPayment} paymentReceive - Payment receive with entries. -// * @returns {number} -// */ -// private getPaymentExGainOrLoss = (billPayment: IBillPayment): number => { -// return sumBy(billPayment.entries, (entry) => { -// const paymentLocalAmount = entry.paymentAmount * billPayment.exchangeRate; -// const invoicePayment = entry.paymentAmount * entry.bill.exchangeRate; - -// return invoicePayment - paymentLocalAmount; -// }); -// }; - -// /** -// * Retrieves the payment exchange gain/loss entries. -// * @param {IBillPayment} billPayment - -// * @param {number} APAccountId - -// * @param {number} gainLossAccountId - -// * @param {string} baseCurrency - -// * @returns {ILedgerEntry[]} -// */ -// private getPaymentExGainOrLossEntries = ( -// billPayment: IBillPayment, -// APAccountId: number, -// gainLossAccountId: number, -// baseCurrency: string -// ): ILedgerEntry[] => { -// const commonEntry = this.getPaymentCommonEntry(billPayment); -// const totalExGainOrLoss = this.getPaymentExGainOrLoss(billPayment); -// const absExGainOrLoss = Math.abs(totalExGainOrLoss); - -// return totalExGainOrLoss -// ? [ -// { -// ...commonEntry, -// currencyCode: baseCurrency, -// exchangeRate: 1, -// credit: totalExGainOrLoss > 0 ? absExGainOrLoss : 0, -// debit: totalExGainOrLoss < 0 ? absExGainOrLoss : 0, -// accountId: gainLossAccountId, -// index: 2, -// indexGroup: 20, -// accountNormal: AccountNormal.DEBIT, -// }, -// { -// ...commonEntry, -// currencyCode: baseCurrency, -// exchangeRate: 1, -// debit: totalExGainOrLoss > 0 ? absExGainOrLoss : 0, -// credit: totalExGainOrLoss < 0 ? absExGainOrLoss : 0, -// accountId: APAccountId, -// index: 3, -// accountNormal: AccountNormal.DEBIT, -// }, -// ] -// : []; -// }; - -// /** -// * Retrieves the payment deposit GL entry. -// * @param {IBillPayment} billPayment -// * @returns {ILedgerEntry} -// */ -// private getPaymentGLEntry = (billPayment: IBillPayment): ILedgerEntry => { -// const commonEntry = this.getPaymentCommonEntry(billPayment); - -// return { -// ...commonEntry, -// credit: billPayment.localAmount, -// accountId: billPayment.paymentAccountId, -// accountNormal: AccountNormal.DEBIT, -// index: 2, -// }; -// }; - -// /** -// * Retrieves the payment GL payable entry. -// * @param {IBillPayment} billPayment -// * @param {number} APAccountId -// * @returns {ILedgerEntry} -// */ -// private getPaymentGLPayableEntry = ( -// billPayment: IBillPayment, -// APAccountId: number -// ): ILedgerEntry => { -// const commonEntry = this.getPaymentCommonEntry(billPayment); - -// return { -// ...commonEntry, -// exchangeRate: billPayment.exchangeRate, -// debit: billPayment.localAmount, -// contactId: billPayment.vendorId, -// accountId: APAccountId, -// accountNormal: AccountNormal.CREDIT, -// index: 1, -// }; -// }; - -// /** -// * Retrieves the payment GL entries. -// * @param {IBillPayment} billPayment -// * @param {number} APAccountId -// * @returns {ILedgerEntry[]} -// */ -// private getPaymentGLEntries = ( -// billPayment: IBillPayment, -// APAccountId: number, -// gainLossAccountId: number, -// baseCurrency: string -// ): ILedgerEntry[] => { -// // Retrieves the payment deposit entry. -// const paymentEntry = this.getPaymentGLEntry(billPayment); - -// // Retrieves the payment debit A/R entry. -// const payableEntry = this.getPaymentGLPayableEntry( -// billPayment, -// APAccountId -// ); -// // Retrieves the exchange gain/loss entries. -// const exGainLossEntries = this.getPaymentExGainOrLossEntries( -// billPayment, -// APAccountId, -// gainLossAccountId, -// baseCurrency -// ); -// return [paymentEntry, payableEntry, ...exGainLossEntries]; -// }; - -// /** -// * Retrieves the bill payment ledger. -// * @param {IBillPayment} billPayment -// * @param {number} APAccountId -// * @returns {Ledger} -// */ -// private getBillPaymentLedger = ( -// billPayment: IBillPayment, -// APAccountId: number, -// gainLossAccountId: number, -// baseCurrency: string -// ): Ledger => { -// const entries = this.getPaymentGLEntries( -// billPayment, -// APAccountId, -// gainLossAccountId, -// baseCurrency -// ); -// return new Ledger(entries); -// }; -// } + /** + * Reverts the bill payment GL entries. + * @param {number} tenantId + * @param {number} billPaymentId + * @param {Knex.Transaction} trx + */ + public revertPaymentGLEntries = async ( + billPaymentId: number, + trx?: Knex.Transaction, + ): Promise => { + await this.ledgerStorage.deleteByReference( + billPaymentId, + 'BillPayment', + trx, + ); + }; +} diff --git a/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGLEntriesSubscriber.ts b/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGLEntriesSubscriber.ts deleted file mode 100644 index 2b4f0b6f6..000000000 --- a/packages/server-nest/src/modules/BillPayments/commands/BillPaymentGLEntriesSubscriber.ts +++ /dev/null @@ -1,76 +0,0 @@ -// import { Inject, Service } from 'typedi'; -// import events from '@/subscribers/events'; -// import { -// IBillPaymentEventCreatedPayload, -// IBillPaymentEventDeletedPayload, -// IBillPaymentEventEditedPayload, -// } from '@/interfaces'; -// import { BillPaymentGLEntries } from './BillPaymentGLEntries'; - -// @Service() -// export class PaymentWriteGLEntriesSubscriber { -// @Inject() -// private billPaymentGLEntries: BillPaymentGLEntries; - -// /** -// * Attaches events with handles. -// */ -// public attach(bus) { -// bus.subscribe(events.billPayment.onCreated, this.handleWriteJournalEntries); -// bus.subscribe( -// events.billPayment.onEdited, -// this.handleRewriteJournalEntriesOncePaymentEdited -// ); -// bus.subscribe( -// events.billPayment.onDeleted, -// this.handleRevertJournalEntries -// ); -// } - -// /** -// * Handle bill payment writing journal entries once created. -// */ -// private handleWriteJournalEntries = async ({ -// tenantId, -// billPayment, -// trx, -// }: IBillPaymentEventCreatedPayload) => { -// // Records the journal transactions after bills payment -// // and change diff account balance. -// await this.billPaymentGLEntries.writePaymentGLEntries( -// tenantId, -// billPayment.id, -// trx -// ); -// }; - -// /** -// * Handle bill payment re-writing journal entries once the payment transaction be edited. -// */ -// private handleRewriteJournalEntriesOncePaymentEdited = async ({ -// tenantId, -// billPayment, -// trx, -// }: IBillPaymentEventEditedPayload) => { -// await this.billPaymentGLEntries.rewritePaymentGLEntries( -// tenantId, -// billPayment.id, -// trx -// ); -// }; - -// /** -// * Reverts journal entries once bill payment deleted. -// */ -// private handleRevertJournalEntries = async ({ -// tenantId, -// billPaymentId, -// trx, -// }: IBillPaymentEventDeletedPayload) => { -// await this.billPaymentGLEntries.revertPaymentGLEntries( -// tenantId, -// billPaymentId, -// trx -// ); -// }; -// } diff --git a/packages/server-nest/src/modules/BillPayments/subscribers/BillPaymentGLEntriesSubscriber.ts b/packages/server-nest/src/modules/BillPayments/subscribers/BillPaymentGLEntriesSubscriber.ts new file mode 100644 index 000000000..b34153a46 --- /dev/null +++ b/packages/server-nest/src/modules/BillPayments/subscribers/BillPaymentGLEntriesSubscriber.ts @@ -0,0 +1,60 @@ +import { + IBillPaymentEventCreatedPayload, + IBillPaymentEventDeletedPayload, + IBillPaymentEventEditedPayload, +} from '../types/BillPayments.types'; +import { BillPaymentGLEntries } from '../commands/BillPaymentGLEntries'; +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; + +@Injectable() +export class BillPaymentGLEntriesSubscriber { + constructor( + private readonly billPaymentGLEntries: BillPaymentGLEntries, + ) {} + + /** + * Handle bill payment writing journal entries once created. + */ + @OnEvent(events.billPayment.onCreated) + private async handleWriteJournalEntries({ + billPayment, + trx, + }: IBillPaymentEventCreatedPayload) { + // Records the journal transactions after bills payment + // and change diff account balance. + await this.billPaymentGLEntries.writePaymentGLEntries( + billPayment.id, + trx + ); + }; + + /** + * Handle bill payment re-writing journal entries once the payment transaction be edited. + */ + @OnEvent(events.billPayment.onEdited) + private async handleRewriteJournalEntriesOncePaymentEdited({ + billPayment, + trx, + }: IBillPaymentEventEditedPayload) { + await this.billPaymentGLEntries.rewritePaymentGLEntries( + billPayment.id, + trx + ); + }; + + /** + * Reverts journal entries once bill payment deleted. + */ + @OnEvent(events.billPayment.onDeleted) + private async handleRevertJournalEntries({ + billPaymentId, + trx, + }: IBillPaymentEventDeletedPayload) { + await this.billPaymentGLEntries.revertPaymentGLEntries( + billPaymentId, + trx + ); + }; +} diff --git a/packages/server-nest/src/modules/Bills/Bills.application.ts b/packages/server-nest/src/modules/Bills/Bills.application.ts index 3272309f3..9bb3db083 100644 --- a/packages/server-nest/src/modules/Bills/Bills.application.ts +++ b/packages/server-nest/src/modules/Bills/Bills.application.ts @@ -19,10 +19,10 @@ export class BillsApplication { private createBillService: CreateBill, private editBillService: EditBillService, private getBillService: GetBill, - // private getBillsService: GetBills, private deleteBillService: DeleteBill, private getDueBillsService: GetDueBills, private openBillService: OpenBillService, + // private getBillsService: GetBills, // private getBillPaymentsService: GetBillPayments, ) {} diff --git a/packages/server-nest/src/modules/Bills/Bills.module.ts b/packages/server-nest/src/modules/Bills/Bills.module.ts index c945c71c4..5866e8187 100644 --- a/packages/server-nest/src/modules/Bills/Bills.module.ts +++ b/packages/server-nest/src/modules/Bills/Bills.module.ts @@ -18,9 +18,12 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { BillsController } from './Bills.controller'; import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module'; import { BillGLEntriesSubscriber } from './subscribers/BillGLEntriesSubscriber'; +import { BillGLEntries } from './commands/BillsGLEntries'; +import { LedgerModule } from '../Ledger/Ledger.module'; +import { AccountsModule } from '../Accounts/Accounts.module'; @Module({ - imports: [BillLandedCostsModule], + imports: [BillLandedCostsModule, LedgerModule, AccountsModule], providers: [ TenancyContext, BillsApplication, @@ -37,8 +40,9 @@ import { BillGLEntriesSubscriber } from './subscribers/BillGLEntriesSubscriber'; DeleteBill, BillDTOTransformer, BillsValidators, + BillGLEntries, ItemsEntriesService, - BillGLEntriesSubscriber + BillGLEntriesSubscriber, ], controllers: [BillsController], }) diff --git a/packages/server-nest/src/modules/Bills/commands/BillDTOTransformer.service.ts b/packages/server-nest/src/modules/Bills/commands/BillDTOTransformer.service.ts index 260013310..38719dca1 100644 --- a/packages/server-nest/src/modules/Bills/commands/BillDTOTransformer.service.ts +++ b/packages/server-nest/src/modules/Bills/commands/BillDTOTransformer.service.ts @@ -6,7 +6,7 @@ import * as composeAsync from 'async/compose'; import { formatDateFields } from '@/utils/format-date-fields'; import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform'; import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { Item } from '@/modules/Items/models/Item'; import { Vendor } from '@/modules/Vendors/models/Vendor'; import { ItemEntriesTaxTransactions } from '@/modules/TaxRates/ItemEntriesTaxTransactions.service'; diff --git a/packages/server-nest/src/modules/Bills/commands/BillsGL.ts b/packages/server-nest/src/modules/Bills/commands/BillsGL.ts index d05f0d93d..f7fa52d9a 100644 --- a/packages/server-nest/src/modules/Bills/commands/BillsGL.ts +++ b/packages/server-nest/src/modules/Bills/commands/BillsGL.ts @@ -1,6 +1,6 @@ import { sumBy } from 'lodash'; import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { Bill } from '../models/Bill'; import { AccountNormal } from '@/modules/Accounts/Accounts.types'; import { Ledger } from '@/modules/Ledger/Ledger'; @@ -85,7 +85,6 @@ export class BillGL { index: index + 1, indexGroup: 10, itemId: entry.itemId, - itemQuantity: entry.quantity, accountNormal: AccountNormal.DEBIT, }; } diff --git a/packages/server-nest/src/modules/Bills/commands/BillsGLEntries.ts b/packages/server-nest/src/modules/Bills/commands/BillsGLEntries.ts index 964f0ad3c..ec82398f8 100644 --- a/packages/server-nest/src/modules/Bills/commands/BillsGLEntries.ts +++ b/packages/server-nest/src/modules/Bills/commands/BillsGLEntries.ts @@ -2,7 +2,7 @@ import { Knex } from 'knex'; import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service'; import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository'; import { Bill } from '../models/Bill'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { BillGL } from './BillsGL'; @Injectable() @@ -15,6 +15,8 @@ export class BillGLEntries { constructor( private readonly ledgerStorage: LedgerStorageService, private readonly accountRepository: AccountRepository, + + @Inject(Bill.name) private readonly billModel: typeof Bill, ) {} diff --git a/packages/server-nest/src/modules/Bills/commands/DeleteBill.service.ts b/packages/server-nest/src/modules/Bills/commands/DeleteBill.service.ts index 3297a6e8a..558ca54a4 100644 --- a/packages/server-nest/src/modules/Bills/commands/DeleteBill.service.ts +++ b/packages/server-nest/src/modules/Bills/commands/DeleteBill.service.ts @@ -8,7 +8,7 @@ import { import { BillsValidators } from './BillsValidators.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { Bill } from '../models/Bill'; @Injectable() diff --git a/packages/server-nest/src/modules/Bills/commands/EditBill.service.ts b/packages/server-nest/src/modules/Bills/commands/EditBill.service.ts index 86ed34d8f..fa03e517d 100644 --- a/packages/server-nest/src/modules/Bills/commands/EditBill.service.ts +++ b/packages/server-nest/src/modules/Bills/commands/EditBill.service.ts @@ -24,6 +24,7 @@ export class EditBillService { private eventPublisher: EventEmitter2, private transactionLandedCostEntries: TransactionLandedCostEntriesService, private transformerDTO: BillDTOTransformer, + @Inject(Bill.name) private billModel: typeof Bill, @Inject(Vendor.name) private contactModel: typeof Vendor, ) {} diff --git a/packages/server-nest/src/modules/Bills/models/Bill.ts b/packages/server-nest/src/modules/Bills/models/Bill.ts index f087c0748..01793f558 100644 --- a/packages/server-nest/src/modules/Bills/models/Bill.ts +++ b/packages/server-nest/src/modules/Bills/models/Bill.ts @@ -1,6 +1,7 @@ import { Model, raw, mixin } from 'objection'; -import { castArray, difference } from 'lodash'; -import moment from 'moment'; +import { castArray, difference, defaultTo } from 'lodash'; +import * as moment from 'moment'; +import * as R from 'ramda'; // import TenantModel from 'models/TenantModel'; // import BillSettings from './Bill.Settings'; // import ModelSetting from './ModelSetting'; @@ -8,10 +9,11 @@ import moment from 'moment'; // import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants'; // import ModelSearchable from './ModelSearchable'; import { BaseModel } from '@/models/Model'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost'; +import { DiscountType } from '@/common/types/Discount'; -export class Bill extends BaseModel{ +export class Bill extends BaseModel { public amount: number; public paymentAmount: number; public landedCostAmount: number; @@ -33,6 +35,10 @@ export class Bill extends BaseModel{ public openedAt: Date | string; public userId: number; + public discountType: DiscountType; + public discount: number; + public adjustment: number; + public branchId: number; public warehouseId: number; public projectId: number; @@ -68,9 +74,16 @@ export class Bill extends BaseModel{ 'localAllocatedCostAmount', 'billableAmount', 'amountLocal', + + 'discountAmount', + 'discountAmountLocal', + 'discountPercentage', + + 'adjustmentLocal', + 'subtotal', 'subtotalLocal', - 'subtotalExcludingTax', + 'subtotalExludingTax', 'taxAmountWithheldLocal', 'total', 'totalLocal', @@ -119,14 +132,53 @@ export class Bill extends BaseModel{ return this.taxAmountWithheld * this.exchangeRate; } + /** + * Discount amount. + * @returns {number} + */ + get discountAmount() { + return this.discountType === DiscountType.Amount + ? this.discount + : this.subtotal * (this.discount / 100); + } + + /** + * Discount amount in local currency. + * @returns {number | null} + */ + get discountAmountLocal() { + return this.discountAmount ? this.discountAmount * this.exchangeRate : null; + } + + /** + /** + * Discount percentage. + * @returns {number | null} + */ + get discountPercentage(): number | null { + return this.discountType === DiscountType.Percentage ? this.discount : null; + } + + /** + * Adjustment amount in local currency. + * @returns {number | null} + */ + get adjustmentLocal() { + return this.adjustment ? this.adjustment * this.exchangeRate : null; + } + /** * Invoice total. (Tax included) * @returns {number} */ get total() { - return this.isInclusiveTax - ? this.subtotal - : this.subtotal + this.taxAmountWithheld; + const adjustmentAmount = defaultTo(this.adjustment, 0); + + return R.compose( + R.add(adjustmentAmount), + R.subtract(R.__, this.discountAmount), + R.when(R.always(this.isInclusiveTax), R.add(this.taxAmountWithheld)), + )(this.subtotal); } /** @@ -183,7 +235,7 @@ export class Bill extends BaseModel{ raw(`COALESCE(AMOUNT, 0) - COALESCE(PAYMENT_AMOUNT, 0) - COALESCE(CREDITED_AMOUNT, 0) > 0 - `) + `), ); }, /** diff --git a/packages/server-nest/src/modules/Bills/queries/GetBill.ts b/packages/server-nest/src/modules/Bills/queries/GetBill.ts index 0cc475d79..1fe21d5b8 100644 --- a/packages/server-nest/src/modules/Bills/queries/GetBill.ts +++ b/packages/server-nest/src/modules/Bills/queries/GetBill.ts @@ -8,6 +8,7 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab export class GetBill { constructor( @Inject(Bill.name) private billModel: typeof Bill, + private transformer: TransformerInjectable, private validators: BillsValidators, ) {} diff --git a/packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts b/packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts index caf5bdbb0..f6789bc19 100644 --- a/packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts +++ b/packages/server-nest/src/modules/CreditNotes/CreditNotes.module.ts @@ -20,6 +20,8 @@ import { CreditNoteBrandingTemplate } from './queries/CreditNoteBrandingTemplate import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module'; import { CreditNoteGLEntries } from './commands/CreditNoteGLEntries'; import { CreditNoteGLEntriesSubscriber } from './subscribers/CreditNoteGLEntriesSubscriber'; +import { LedgerModule } from '../Ledger/Ledger.module'; +import { AccountsModule } from '../Accounts/Accounts.module'; @Module({ imports: [ @@ -30,6 +32,8 @@ import { CreditNoteGLEntriesSubscriber } from './subscribers/CreditNoteGLEntries ChromiumlyTenancyModule, TemplateInjectableModule, AutoIncrementOrdersModule, + LedgerModule, + AccountsModule ], providers: [ CreateCreditNoteService, diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGL.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGL.ts index b143f0c91..24d596b94 100644 --- a/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGL.ts +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNoteGL.ts @@ -2,7 +2,7 @@ import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; import { CreditNote } from '../models/CreditNote'; import { AccountNormal } from '@/interfaces/Account'; import { Ledger } from '@/modules/Ledger/Ledger'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; export class CreditNoteGL { creditNoteModel: CreditNote; @@ -111,7 +111,6 @@ export class CreditNoteGL { note: entry.description, index: index + 2, itemId: entry.itemId, - itemQuantity: entry.quantity, accountNormal: AccountNormal.CREDIT, }; } diff --git a/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts index 993902590..def8bfc67 100644 --- a/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts +++ b/packages/server-nest/src/modules/CreditNotes/commands/DeleteCreditNote.service.ts @@ -12,7 +12,7 @@ import { } from '../../CreditNoteRefunds/models/RefundCreditNote'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { ServiceError } from '@/modules/Items/ServiceError'; import { events } from '@/common/events/events'; diff --git a/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts b/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts index c4ff4a1b8..5f0cdca8e 100644 --- a/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts +++ b/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts @@ -2,7 +2,7 @@ import { DiscountType } from '@/common/types/Discount'; import { BaseModel } from '@/models/Model'; import { Branch } from '@/modules/Branches/models/Branch.model'; import { Customer } from '@/modules/Customers/models/Customer'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model'; import { mixin, Model, raw } from 'objection'; // import TenantModel from 'models/TenantModel'; @@ -28,6 +28,8 @@ export class CreditNote extends BaseModel { public currencyCode: string; public customerId: number; + public userId: number; + public branchId: number; public warehouseId: number; @@ -37,7 +39,8 @@ export class CreditNote extends BaseModel { public branch!: Branch; public warehouse!: Warehouse; - + public createdAt!: Date | string; + public updatedAt!: Date | string; /** * Table name diff --git a/packages/server-nest/src/modules/Items/ItemTransactions.service.ts b/packages/server-nest/src/modules/Items/ItemTransactions.service.ts index 306c0caca..8c7198cc2 100644 --- a/packages/server-nest/src/modules/Items/ItemTransactions.service.ts +++ b/packages/server-nest/src/modules/Items/ItemTransactions.service.ts @@ -4,7 +4,7 @@ import { ItemEstimateTransactionTransformer } from './ItemEstimatesTransaction.t import { ItemBillTransactionTransformer } from './ItemBillsTransactions.transformer'; import { ItemReceiptTransactionTransformer } from './ItemReceiptsTransactions.transformer'; import { TransformerInjectable } from '../Transformer/TransformerInjectable.service'; -import { ItemEntry } from './models/ItemEntry'; +import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry'; @Injectable() export class ItemTransactionsService { diff --git a/packages/server-nest/src/modules/Items/models/ItemEntry.ts b/packages/server-nest/src/modules/Items/models/ItemEntry.ts deleted file mode 100644 index 042c1e74d..000000000 --- a/packages/server-nest/src/modules/Items/models/ItemEntry.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { Model } from 'objection'; -// import TenantModel from 'models/TenantModel'; -// import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate'; -import { BaseModel } from '@/models/Model'; -import { Item } from './Item'; - -export class ItemEntry extends BaseModel { - public taxRate: number; - public discount: number; - public quantity: number; - public rate: number; - public isInclusiveTax: number; - public itemId: number; - public costAccountId: number; - public taxRateId: number; - public sellAccountId: number; - public description: string; - - public landedCost!: boolean; - public allocatedCostAmount!: number; - - public item!: Item; - - /** - * Table name. - * @returns {string} - */ - static get tableName() { - return 'items_entries'; - } - - /** - * Timestamps columns. - * @returns {string[]} - */ - get timestamps() { - return ['created_at', 'updated_at']; - } - - /** - * Virtual attributes. - * @returns {string[]} - */ - static get virtualAttributes() { - return [ - 'amount', - 'taxAmount', - 'amountExludingTax', - 'amountInclusingTax', - 'total', - ]; - } - - /** - * Item entry total. - * Amount of item entry includes tax and subtracted discount amount. - * @returns {number} - */ - get total() { - return this.amountInclusingTax; - } - - /** - * Item entry amount. - * Amount of item entry that may include or exclude tax. - * @returns {number} - */ - get amount() { - return this.quantity * this.rate; - } - - /** - * Item entry amount including tax. - * @returns {number} - */ - get amountInclusingTax() { - return this.isInclusiveTax ? this.amount : this.amount + this.taxAmount; - } - - /** - * Item entry amount excluding tax. - * @returns {number} - */ - get amountExludingTax() { - return this.isInclusiveTax ? this.amount - this.taxAmount : this.amount; - } - - /** - * Discount amount. - * @returns {number} - */ - get discountAmount() { - return this.amount * (this.discount / 100); - } - - /** - * Tag rate fraction. - * @returns {number} - */ - get tagRateFraction() { - return this.taxRate / 100; - } - - /** - * Tax amount withheld. - * @returns {number} - */ - get taxAmount() { - return 0; - // return this.isInclusiveTax - // ? getInclusiveTaxAmount(this.amount, this.taxRate) - // : getExlusiveTaxAmount(this.amount, this.taxRate); - } - - static calcAmount(itemEntry) { - const { discount, quantity, rate } = itemEntry; - const total = quantity * rate; - - return discount ? total - total * discount * 0.01 : total; - } - - /** - * Item entry relations. - */ - static get relationMappings() { - const { Item } = require('../../Items/models/Item'); - // const BillLandedCostEntry = require('models/BillLandedCostEntry'); - const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice'); - const { Bill } = require('../../Bills/models/Bill'); - const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt'); - const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate'); - // const ProjectTask = require('models/Task'); - const { Expense } = require('../../Expenses/models/Expense.model'); - const { TaxRateModel } = require('../../TaxRates/models/TaxRate.model'); - - return { - item: { - relation: Model.BelongsToOneRelation, - modelClass: Item, - join: { - from: 'items_entries.itemId', - to: 'items.id', - }, - }, - // allocatedCostEntries: { - // relation: Model.HasManyRelation, - // modelClass: BillLandedCostEntry, - // join: { - // from: 'items_entries.referenceId', - // to: 'bill_located_cost_entries.entryId', - // }, - // }, - - invoice: { - relation: Model.BelongsToOneRelation, - modelClass: SaleInvoice, - join: { - from: 'items_entries.referenceId', - to: 'sales_invoices.id', - }, - }, - - bill: { - relation: Model.BelongsToOneRelation, - modelClass: Bill, - join: { - from: 'items_entries.referenceId', - to: 'bills.id', - }, - }, - - estimate: { - relation: Model.BelongsToOneRelation, - modelClass: SaleEstimate, - join: { - from: 'items_entries.referenceId', - to: 'sales_estimates.id', - }, - }, - - /** - * Sale receipt reference. - */ - receipt: { - relation: Model.BelongsToOneRelation, - modelClass: SaleReceipt, - join: { - from: 'items_entries.referenceId', - to: 'sales_receipts.id', - }, - }, - - /** - * Project task reference. - */ - // projectTaskRef: { - // relation: Model.HasManyRelation, - // modelClass: ProjectTask.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'tasks.id', - // }, - // }, - - /** - * Project expense reference. - */ - // projectExpenseRef: { - // relation: Model.HasManyRelation, - // modelClass: Expense.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'expenses_transactions.id', - // }, - // }, - - /** - * Project bill reference. - */ - // projectBillRef: { - // relation: Model.HasManyRelation, - // modelClass: Bill.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'bills.id', - // }, - // }, - - /** - * Tax rate reference. - */ - tax: { - relation: Model.HasOneRelation, - modelClass: TaxRateModel, - join: { - from: 'items_entries.taxRateId', - to: 'tax_rates.id', - }, - }, - }; - } -} diff --git a/packages/server-nest/src/modules/Ledger/LedgetAccountStorage.service.ts b/packages/server-nest/src/modules/Ledger/LedgetAccountStorage.service.ts index bdcdc3e4a..c719e4a76 100644 --- a/packages/server-nest/src/modules/Ledger/LedgetAccountStorage.service.ts +++ b/packages/server-nest/src/modules/Ledger/LedgetAccountStorage.service.ts @@ -18,12 +18,10 @@ export class LedegrAccountsStorage { */ constructor( private tenancyContext: TenancyContext, + private accountRepository: AccountRepository, @Inject(Account.name) private accountModel: typeof Account, - - @Inject(AccountRepository) - private accountRepository: AccountRepository, ) {} /** diff --git a/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts b/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts index 3dc5bc4d0..a12d52d09 100644 --- a/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts +++ b/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts @@ -14,7 +14,7 @@ import { DeletePaymentReceivedService } from './commands/DeletePaymentReceived.s import { GetPaymentReceivedService } from './queries/GetPaymentReceived.service'; import { GetPaymentReceivedInvoices } from './queries/GetPaymentReceivedInvoices.service'; // import { PaymentReceiveNotifyBySms } from './PaymentReceivedSmsNotify'; -import GetPaymentReceivedPdf from './queries/GetPaymentReceivedPdf.service'; +import { GetPaymentReceivedPdfService } from './queries/GetPaymentReceivedPdf.service'; // import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification'; import { GetPaymentReceivedStateService } from './queries/GetPaymentReceivedState.service'; @@ -29,7 +29,7 @@ export class PaymentReceivesApplication { private getPaymentReceiveInvoicesService: GetPaymentReceivedInvoices, // private paymentSmsNotify: PaymentReceiveNotifyBySms, // private paymentMailNotify: SendPaymentReceiveMailNotification, - private getPaymentReceivePdfService: GetPaymentReceivedPdf, + private getPaymentReceivePdfService: GetPaymentReceivedPdfService, private getPaymentReceivedStateService: GetPaymentReceivedStateService, ) {} diff --git a/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts b/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts index a269c5168..c2c7b4330 100644 --- a/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts +++ b/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts @@ -24,6 +24,8 @@ import { PaymentReceivedGLEntriesSubscriber } from './subscribers/PaymentReceive import { PaymentReceivedGLEntries } from './commands/PaymentReceivedGLEntries'; import { PaymentReceivedSyncInvoicesSubscriber } from './subscribers/PaymentReceivedSyncInvoices'; import { PaymentReceivedInvoiceSync } from './commands/PaymentReceivedInvoiceSync.service'; +import { LedgerModule } from '../Ledger/Ledger.module'; +import { AccountsModule } from '../Accounts/Accounts.module'; @Module({ controllers: [PaymentReceivesController], @@ -55,6 +57,8 @@ import { PaymentReceivedInvoiceSync } from './commands/PaymentReceivedInvoiceSyn WarehousesModule, PdfTemplatesModule, AutoIncrementOrdersModule, + LedgerModule, + AccountsModule ], }) export class PaymentsReceivedModule {} diff --git a/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedGLEntries.ts b/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedGLEntries.ts index c567ea3f1..401228102 100644 --- a/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedGLEntries.ts +++ b/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedGLEntries.ts @@ -7,6 +7,7 @@ import { AccountRepository } from '@/modules/Accounts/repositories/Account.repos import { Injectable } from '@nestjs/common'; import { Inject } from '@nestjs/common'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { Account } from '@/modules/Accounts/models/Account.model'; @Injectable() export class PaymentReceivedGLEntries { @@ -98,7 +99,8 @@ export class PaymentReceivedGLEntries { // Exchange gain/loss account. const exGainLossAccount = await this.accountRepository.findBySlug( 'exchange-grain-loss' - ); + ) as Account; + const paymentReceivedGL = new PaymentReceivedGL(paymentReceive) .setARAccountId(receivableAccount.id) .setExchangeGainOrLossAccountId(exGainLossAccount.id) diff --git a/packages/server-nest/src/modules/PaymentReceived/queries/PaymentReceivedBrandingTemplate.service.ts b/packages/server-nest/src/modules/PaymentReceived/queries/PaymentReceivedBrandingTemplate.service.ts index c8fa78bfc..351240a94 100644 --- a/packages/server-nest/src/modules/PaymentReceived/queries/PaymentReceivedBrandingTemplate.service.ts +++ b/packages/server-nest/src/modules/PaymentReceived/queries/PaymentReceivedBrandingTemplate.service.ts @@ -1,8 +1,7 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { defaultPaymentReceivedPdfTemplateAttributes } from '../constants'; import { GetPdfTemplateService } from '../../PdfTemplate/queries/GetPdfTemplate.service'; import { GetOrganizationBrandingAttributesService } from '../../PdfTemplate/queries/GetOrganizationBrandingAttributes.service'; -import { PdfTemplateModel } from '../../PdfTemplate/models/PdfTemplate'; import { mergePdfTemplateWithDefaultAttributes } from '../../SaleInvoices/utils'; @Injectable() @@ -10,9 +9,6 @@ export class PaymentReceivedBrandingTemplate { constructor( private readonly getPdfTemplateService: GetPdfTemplateService, private readonly getOrgBrandingAttributes: GetOrganizationBrandingAttributesService, - - @Inject(PdfTemplateModel.name) - private readonly pdfTemplateModel: typeof PdfTemplateModel, ) {} /** diff --git a/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts b/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts index 1e7d9fe0b..5fee91e86 100644 --- a/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts +++ b/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts @@ -1,7 +1,7 @@ import { BaseModel } from '@/models/Model'; import moment from 'moment'; import { Model } from 'objection'; -import { ItemEntry } from '../../Items/models/ItemEntry'; +import { ItemEntry } from '../../TransactionItemEntry/models/ItemEntry'; import { Customer } from '../../Customers/models/Customer'; import { Branch } from '../../Branches/models/Branch.model'; import { Warehouse } from '../../Warehouses/models/Warehouse.model'; diff --git a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts index 979655f58..b5019ad40 100644 --- a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts +++ b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts @@ -33,6 +33,8 @@ import { TaxRatesModule } from '../TaxRates/TaxRate.module'; import { SaleInvoicesController } from './SaleInvoices.controller'; import { InvoiceGLEntriesSubscriber } from './subscribers/InvoiceGLEntriesSubscriber'; import { SaleInvoiceGLEntries } from './ledger/InvoiceGLEntries'; +import { LedgerModule } from '../Ledger/Ledger.module'; +import { AccountsModule } from '../Accounts/Accounts.module'; @Module({ imports: [ @@ -43,6 +45,8 @@ import { SaleInvoiceGLEntries } from './ledger/InvoiceGLEntries'; BranchesModule, WarehousesModule, TaxRatesModule, + LedgerModule, + AccountsModule ], controllers: [SaleInvoicesController], providers: [ diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts b/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts index d5ab688f6..56a7a7bb9 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts @@ -12,7 +12,7 @@ import { Customer } from '@/modules/Customers/models/Customer'; import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform'; import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators.service'; import { SaleInvoiceIncrement } from './SaleInvoiceIncrement.service'; import { BrandingTemplateDTOTransformer } from '@/modules/PdfTemplate/BrandingTemplateDTOTransformer'; diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/DeleteSaleInvoice.service.ts b/packages/server-nest/src/modules/SaleInvoices/commands/DeleteSaleInvoice.service.ts index 4314ed073..9a7eed94a 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/DeleteSaleInvoice.service.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/DeleteSaleInvoice.service.ts @@ -5,7 +5,7 @@ import { ISaleInvoiceDeletedPayload, ISaleInvoiceDeletingPayload, } from '../SaleInvoice.types'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { SaleInvoice } from '../models/SaleInvoice'; import { UnlinkConvertedSaleEstimate } from '@/modules/SaleEstimates/commands/UnlinkConvertedSaleEstimate.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; diff --git a/packages/server-nest/src/modules/SaleInvoices/ledger/InvoiceGL.ts b/packages/server-nest/src/modules/SaleInvoices/ledger/InvoiceGL.ts index bc9b87d68..597c58067 100644 --- a/packages/server-nest/src/modules/SaleInvoices/ledger/InvoiceGL.ts +++ b/packages/server-nest/src/modules/SaleInvoices/ledger/InvoiceGL.ts @@ -2,7 +2,7 @@ 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 { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { Ledger } from '@/modules/Ledger/Ledger'; import { SaleInvoice } from '../models/SaleInvoice'; @@ -116,7 +116,6 @@ export class InvoiceGL { note: entry.description, index: index + 2, itemId: entry.itemId, - itemQuantity: entry.quantity, accountNormal: AccountNormal.CREDIT, taxRateId: entry.taxRateId, taxRate: entry.taxRate, @@ -149,7 +148,7 @@ export class InvoiceGL { * Retrieves the invoice discount GL entry. * @returns {ILedgerEntry} */ - private getInvoiceDiscountEntry = (): ILedgerEntry => { + private get invoiceDiscountEntry(): ILedgerEntry { const commonEntry = this.invoiceGLCommonEntry; return { @@ -165,7 +164,7 @@ export class InvoiceGL { * Retrieves the invoice adjustment GL entry. * @returns {ILedgerEntry} */ - private getAdjustmentEntry = (): ILedgerEntry => { + private get adjustmentEntry(): ILedgerEntry { const commonEntry = this.invoiceGLCommonEntry; const adjustmentAmount = Math.abs(this.saleInvoice.adjustmentLocal); @@ -184,24 +183,19 @@ export class InvoiceGL { * @returns {ILedgerEntry[]} */ public getInvoiceGLEntries = (): ILedgerEntry[] => { - const receivableEntry = this.invoiceReceivableEntry; const creditEntries = this.saleInvoice.entries.map( - this.getInvoiceItemEntry, + (entry, index) => this.getInvoiceItemEntry(entry, index), ); - const taxEntries = this.saleInvoice.entries .filter((entry) => entry.taxAmount > 0) - .map(this.getInvoiceTaxEntry); - - const discountEntry = this.getInvoiceDiscountEntry(); - const adjustmentEntry = this.getAdjustmentEntry(); + .map((entry, index) => this.getInvoiceTaxEntry(entry, index)); return [ this.invoiceReceivableEntry, ...creditEntries, ...taxEntries, - discountEntry, - adjustmentEntry, + this.invoiceDiscountEntry, + this.adjustmentEntry, ]; }; diff --git a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts index 37b0ed9f7..8fa493c54 100644 --- a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts +++ b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts @@ -1,7 +1,9 @@ -import { mixin, Model, raw } from 'objection'; -import { castArray, takeWhile } from 'lodash'; +import { Model, raw } from 'objection'; +import { castArray } from 'lodash'; import * as moment from 'moment'; +import * as R from 'ramda'; import { MomentInput, unitOfTime } from 'moment'; +import { defaultTo } from 'ramda'; // import TenantModel from 'models/TenantModel'; // import ModelSetting from './ModelSetting'; // import SaleInvoiceMeta from './SaleInvoice.Settings'; @@ -10,8 +12,9 @@ import { MomentInput, unitOfTime } from 'moment'; // import ModelSearchable from './ModelSearchable'; import { BaseModel } from '@/models/Model'; import { TaxRateTransaction } from '@/modules/TaxRates/models/TaxRateTransaction.model'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { Document } from '@/modules/ChromiumlyTenancy/models/Document'; +import { DiscountType } from '@/common/types/Discount'; export class SaleInvoice extends BaseModel { public taxAmountWithheld: number; @@ -34,6 +37,10 @@ export class SaleInvoice extends BaseModel { public writtenoffAmount: number; public writtenoffAt: Date; + public discountType: DiscountType; + public discount: number; + public adjustment: number; + public customerId: number; public invoiceNo: string; public referenceNo: string; @@ -91,10 +98,15 @@ export class SaleInvoice extends BaseModel { 'subtotalExludingTax', 'taxAmountWithheldLocal', + 'discountAmount', + 'discountAmountLocal', + 'discountPercentage', + 'total', 'totalLocal', 'writtenoffAmountLocal', + 'adjustmentLocal', ]; } @@ -149,14 +161,52 @@ export class SaleInvoice extends BaseModel { return this.taxAmountWithheld * this.exchangeRate; } + /** + * Discount amount. + * @returns {number} + */ + get discountAmount() { + return this.discountType === DiscountType.Amount + ? this.discount + : this.subtotal * (this.discount / 100); + } + + /** + * Local discount amount. + * @returns {number | null} + */ + get discountAmountLocal() { + return this.discountAmount ? this.discountAmount * this.exchangeRate : null; + } + + /** + * Discount percentage. + * @returns {number | null} + */ + get discountPercentage(): number | null { + return this.discountType === DiscountType.Percentage ? this.discount : null; + } + + /** + * Adjustment amount in local currency. + * @returns {number | null} + */ + get adjustmentLocal(): number | null { + return this.adjustment ? this.adjustment * this.exchangeRate : null; + } + /** * Invoice total. (Tax included) * @returns {number} */ get total() { - return this.isInclusiveTax - ? this.subtotal - : this.subtotal + this.taxAmountWithheld; + const adjustmentAmount = defaultTo(this.adjustment, 0); + + return R.compose( + R.add(adjustmentAmount), + R.subtract(R.__, this.discountAmount), + R.when(R.always(this.isInclusiveTax), R.add(this.taxAmountWithheld)), + )(this.subtotal); } /** @@ -445,7 +495,9 @@ export class SaleInvoice extends BaseModel { const { Branch } = require('../../Branches/models/Branch.model'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); const { Account } = require('../../Accounts/models/Account.model'); - const { TaxRateTransaction } = require('../../TaxRates/models/TaxRateTransaction.model'); + const { + TaxRateTransaction, + } = require('../../TaxRates/models/TaxRateTransaction.model'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); // const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); const { diff --git a/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts b/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts index 262f60299..71040089d 100644 --- a/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts +++ b/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts @@ -22,6 +22,8 @@ import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementO import { SaleReceiptsController } from './SaleReceipts.controller'; import { SaleReceiptGLEntriesSubscriber } from './subscribers/SaleReceiptGLEntriesSubscriber'; import { SaleReceiptGLEntries } from './ledger/SaleReceiptGLEntries'; +import { LedgerModule } from '../Ledger/Ledger.module'; +import { AccountsModule } from '../Accounts/Accounts.module'; @Module({ controllers: [SaleReceiptsController], @@ -32,7 +34,9 @@ import { SaleReceiptGLEntries } from './ledger/SaleReceiptGLEntries'; BranchesModule, WarehousesModule, PdfTemplatesModule, - AutoIncrementOrdersModule + AutoIncrementOrdersModule, + LedgerModule, + AccountsModule ], providers: [ TenancyContext, diff --git a/packages/server-nest/src/modules/SaleReceipts/commands/DeleteSaleReceipt.service.ts b/packages/server-nest/src/modules/SaleReceipts/commands/DeleteSaleReceipt.service.ts index 5f828047d..956b52b3f 100644 --- a/packages/server-nest/src/modules/SaleReceipts/commands/DeleteSaleReceipt.service.ts +++ b/packages/server-nest/src/modules/SaleReceipts/commands/DeleteSaleReceipt.service.ts @@ -8,7 +8,7 @@ import { SaleReceiptValidators } from './SaleReceiptValidators.service'; import { SaleReceipt } from '../models/SaleReceipt'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { events } from '@/common/events/events'; @Injectable() diff --git a/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptDTOTransformer.service.ts b/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptDTOTransformer.service.ts index 5f05ac367..4ea9f9ac5 100644 --- a/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptDTOTransformer.service.ts +++ b/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptDTOTransformer.service.ts @@ -9,7 +9,7 @@ import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform'; import { SaleReceiptValidators } from './SaleReceiptValidators.service'; import { BrandingTemplateDTOTransformer } from '@/modules/PdfTemplate/BrandingTemplateDTOTransformer'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { formatDateFields } from '@/utils/format-date-fields'; import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index'; import { SaleReceipt } from '../models/SaleReceipt'; diff --git a/packages/server-nest/src/modules/SaleReceipts/ledger/SaleReceiptGL.ts b/packages/server-nest/src/modules/SaleReceipts/ledger/SaleReceiptGL.ts index a3198402c..5e7efbf48 100644 --- a/packages/server-nest/src/modules/SaleReceipts/ledger/SaleReceiptGL.ts +++ b/packages/server-nest/src/modules/SaleReceipts/ledger/SaleReceiptGL.ts @@ -4,7 +4,7 @@ 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'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; export class SaleReceiptGL { private saleReceipt: SaleReceipt; @@ -82,7 +82,7 @@ export class SaleReceiptGL { note: entry.description, index: index + 2, itemId: entry.itemId, - itemQuantity: entry.quantity, + // itemQuantity: entry.quantity, accountNormal: AccountNormal.CREDIT, }; }, diff --git a/packages/server-nest/src/modules/SaleReceipts/models/SaleReceipt.ts b/packages/server-nest/src/modules/SaleReceipts/models/SaleReceipt.ts index a78f988d8..6cd98ddec 100644 --- a/packages/server-nest/src/modules/SaleReceipts/models/SaleReceipt.ts +++ b/packages/server-nest/src/modules/SaleReceipts/models/SaleReceipt.ts @@ -1,4 +1,5 @@ import { Model, mixin } from 'objection'; +import { defaultTo } from 'ramda'; // import TenantModel from 'models/TenantModel'; // import ModelSetting from './ModelSetting'; // import SaleReceiptSettings from './SaleReceipt.Settings'; @@ -6,11 +7,12 @@ 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 { ItemEntry } from '@/modules/TransactionItemEntry/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'; +import { DiscountType } from '@/common/types/Discount'; export class SaleReceipt extends BaseModel { amount: number; @@ -26,9 +28,15 @@ export class SaleReceipt extends BaseModel { statement: string; closedAt: Date | string; + discountType: DiscountType; + discount: number; + adjustment: number; + branchId: number; warehouseId: number; + userId: number; + createdAt: Date; updatedAt: Date | null; @@ -37,7 +45,7 @@ export class SaleReceipt extends BaseModel { transactions!: AccountTransaction[]; branch!: Branch; warehouse!: Warehouse; - + /** * Table name */ @@ -56,7 +64,28 @@ export class SaleReceipt extends BaseModel { * Virtual attributes. */ static get virtualAttributes() { - return ['localAmount', 'isClosed', 'isDraft']; + return [ + 'localAmount', + + 'subtotal', + 'subtotalLocal', + + 'total', + 'totalLocal', + + 'adjustment', + 'adjustmentLocal', + + 'discountAmount', + 'discountAmountLocal', + 'discountPercentage', + + 'paid', + 'paidLocal', + + 'isClosed', + 'isDraft', + ]; } /** @@ -67,6 +96,90 @@ export class SaleReceipt extends BaseModel { return this.amount * this.exchangeRate; } + /** + * Receipt subtotal. + * @returns {number} + */ + get subtotal() { + return this.amount; + } + + /** + * Receipt subtotal in local currency. + * @returns {number} + */ + get subtotalLocal() { + return this.localAmount; + } + + /** + * Discount amount. + * @returns {number} + */ + get discountAmount() { + return this.discountType === DiscountType.Amount + ? this.discount + : this.subtotal * (this.discount / 100); + } + + /** + * Discount amount in local currency. + * @returns {number | null} + */ + get discountAmountLocal() { + return this.discountAmount ? this.discountAmount * this.exchangeRate : null; + } + + /** + * Discount percentage. + * @returns {number | null} + */ + get discountPercentage(): number | null { + return this.discountType === DiscountType.Percentage ? this.discount : null; + } + + /** + * Receipt total. + * @returns {number} + */ + get total() { + const adjustmentAmount = defaultTo(this.adjustment, 0); + + return this.subtotal - this.discountAmount + adjustmentAmount; + } + + /** + * Receipt total in local currency. + * @returns {number} + */ + get totalLocal() { + return this.total * this.exchangeRate; + } + + /** + * Adjustment amount in local currency. + * @returns {number} + */ + get adjustmentLocal() { + return this.adjustment * this.exchangeRate; + } + + /** + * Receipt paid amount. + * @returns {number} + */ + get paid() { + return this.total; + } + + /** + * Receipt paid amount in local currency. + * @returns {number} + */ + get paidLocal() { + return this.paid * this.exchangeRate; + } + /** * Detarmine whether the sale receipt closed. * @return {boolean} @@ -132,8 +245,12 @@ export class SaleReceipt extends BaseModel { static get relationMappings() { const { Customer } = require('../../Customers/models/Customer'); const { Account } = require('../../Accounts/models/Account.model'); - const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model'); - const { ItemEntry } = require('../../TransactionItemEntry/models/ItemEntry'); + const { + AccountTransaction, + } = require('../../Accounts/models/AccountTransaction.model'); + const { + ItemEntry, + } = require('../../TransactionItemEntry/models/ItemEntry'); const { Branch } = require('../../Branches/models/Branch.model'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); diff --git a/packages/server-nest/src/modules/TaxRates/ItemEntriesTaxTransactions.service.ts b/packages/server-nest/src/modules/TaxRates/ItemEntriesTaxTransactions.service.ts index be8948e91..ed76c272c 100644 --- a/packages/server-nest/src/modules/TaxRates/ItemEntriesTaxTransactions.service.ts +++ b/packages/server-nest/src/modules/TaxRates/ItemEntriesTaxTransactions.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { keyBy, sumBy } from 'lodash'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { TaxRateModel } from './models/TaxRate.model'; @Injectable() diff --git a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts index 1221b0b7f..0b373f933 100644 --- a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts +++ b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts @@ -4,7 +4,7 @@ import { TENANCY_DB_CONNECTION } from '../TenancyDB/TenancyDB.constants'; import { Item } from '../../../modules/Items/models/Item'; import { Account } from '@/modules/Accounts/models/Account.model'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; import { Expense } from '@/modules/Expenses/models/Expense.model'; import { ExpenseCategory } from '@/modules/Expenses/models/ExpenseCategory.model'; diff --git a/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts b/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts index 4589db44d..20216df57 100644 --- a/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts +++ b/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts @@ -1,8 +1,12 @@ +import { DiscountType } from '@/common/types/Discount'; import { BaseModel } from '@/models/Model'; +import { BillLandedCostEntry } from '@/modules/BillLandedCosts/models/BillLandedCostEntry'; +import { Item } from '@/modules/Items/models/Item'; import { getExlusiveTaxAmount, getInclusiveTaxAmount, } from '@/modules/TaxRates/utils'; +import { Model } from 'objection'; export class ItemEntry extends BaseModel { public referenceType: string; @@ -15,14 +19,26 @@ export class ItemEntry extends BaseModel { public sellAccountId: number; public costAccountId: number; - public landedCost: boolean; - public allocatedCostAmount: number; - public taxRate: number; - public discount: number; public quantity: number; public rate: number; + + public taxRate: number; public isInclusiveTax: number; + public landedCost: boolean; + public allocatedCostAmount: number; + + public discountType: DiscountType; + public discount: number; + + public adjustment: number; + + public taxRateId: number; + + item: Item; + allocatedCostEntries: BillLandedCostEntry[]; + + /** * Table name. * @returns {string} @@ -45,10 +61,24 @@ export class ItemEntry extends BaseModel { */ static get virtualAttributes() { return [ + // Amount (qty * rate) 'amount', + 'taxAmount', - 'amountExludingTax', - 'amountInclusingTax', + + // Subtotal (qty * rate) + (tax inclusive) + 'subtotalInclusingTax', + + // Subtotal Tax Exclusive (Subtotal - Tax Amount) + 'subtotalExcludingTax', + + // Subtotal (qty * rate) + (tax inclusive) + 'subtotal', + + // Discount (Is percentage ? amount * discount : discount) + 'discountAmount', + + // Total (Subtotal - Discount) 'total', ]; } @@ -59,7 +89,15 @@ export class ItemEntry extends BaseModel { * @returns {number} */ get total() { - return this.amountInclusingTax; + return this.subtotal - this.discountAmount; + } + + /** + * Total (excluding tax). + * @returns {number} + */ + get totalExcludingTax() { + return this.subtotalExcludingTax - this.discountAmount; } /** @@ -71,19 +109,27 @@ export class ItemEntry extends BaseModel { return this.quantity * this.rate; } + /** + * Subtotal amount (tax inclusive). + * @returns {number} + */ + get subtotal() { + return this.subtotalInclusingTax; + } + /** * Item entry amount including tax. * @returns {number} */ - get amountInclusingTax() { + get subtotalInclusingTax() { return this.isInclusiveTax ? this.amount : this.amount + this.taxAmount; } /** - * Item entry amount excluding tax. + * Subtotal amount (tax exclusive). * @returns {number} */ - get amountExludingTax() { + get subtotalExcludingTax() { return this.isInclusiveTax ? this.amount - this.taxAmount : this.amount; } @@ -92,7 +138,9 @@ export class ItemEntry extends BaseModel { * @returns {number} */ get discountAmount() { - return this.amount * (this.discount / 100); + return this.discountType === DiscountType.Percentage + ? this.amount * (this.discount / 100) + : this.discount; } /** @@ -123,121 +171,123 @@ export class ItemEntry extends BaseModel { /** * Item entry relations. */ - // static get relationMappings() { - // const Item = require('models/Item'); - // const BillLandedCostEntry = require('models/BillLandedCostEntry'); - // const SaleInvoice = require('models/SaleInvoice'); - // const Bill = require('models/Bill'); - // const SaleReceipt = require('models/SaleReceipt'); - // const SaleEstimate = require('models/SaleEstimate'); - // const ProjectTask = require('models/Task'); - // const Expense = require('models/Expense'); - // const TaxRate = require('models/TaxRate'); + static get relationMappings() { + const { Item } = require('../../Items/models/Item'); + const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice'); + const { + BillLandedCostEntry, + } = require('../../BillLandedCosts/models/BillLandedCostEntry'); + const { Bill } = require('../../Bills/models/Bill'); + const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt'); + const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate'); + const { Expense } = require('../../Expenses/models/Expense.model'); + const { TaxRate } = require('../../TaxRates/models/TaxRate.model'); + // const ProjectTask = require('models/Task'); - // return { - // item: { - // relation: Model.BelongsToOneRelation, - // modelClass: Item.default, - // join: { - // from: 'items_entries.itemId', - // to: 'items.id', - // }, - // }, - // allocatedCostEntries: { - // relation: Model.HasManyRelation, - // modelClass: BillLandedCostEntry.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'bill_located_cost_entries.entryId', - // }, - // }, + return { + item: { + relation: Model.BelongsToOneRelation, + modelClass: Item, + join: { + from: 'items_entries.itemId', + to: 'items.id', + }, + }, + allocatedCostEntries: { + relation: Model.HasManyRelation, + modelClass: BillLandedCostEntry, + join: { + from: 'items_entries.referenceId', + to: 'bill_located_cost_entries.entryId', + }, + }, - // invoice: { - // relation: Model.BelongsToOneRelation, - // modelClass: SaleInvoice.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'sales_invoices.id', - // }, - // }, + invoice: { + relation: Model.BelongsToOneRelation, + modelClass: SaleInvoice, + join: { + from: 'items_entries.referenceId', + to: 'sales_invoices.id', + }, + }, - // bill: { - // relation: Model.BelongsToOneRelation, - // modelClass: Bill.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'bills.id', - // }, - // }, + bill: { + relation: Model.BelongsToOneRelation, + modelClass: Bill, + join: { + from: 'items_entries.referenceId', + to: 'bills.id', + }, + }, - // estimate: { - // relation: Model.BelongsToOneRelation, - // modelClass: SaleEstimate.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'sales_estimates.id', - // }, - // }, + estimate: { + relation: Model.BelongsToOneRelation, + modelClass: SaleEstimate, + join: { + from: 'items_entries.referenceId', + to: 'sales_estimates.id', + }, + }, - // /** - // * Sale receipt reference. - // */ - // receipt: { - // relation: Model.BelongsToOneRelation, - // modelClass: SaleReceipt.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'sales_receipts.id', - // }, - // }, + /** + * Sale receipt reference. + */ + receipt: { + relation: Model.BelongsToOneRelation, + modelClass: SaleReceipt, + join: { + from: 'items_entries.referenceId', + to: 'sales_receipts.id', + }, + }, - // /** - // * Project task reference. - // */ - // projectTaskRef: { - // relation: Model.HasManyRelation, - // modelClass: ProjectTask.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'tasks.id', - // }, - // }, + /** + * Project task reference. + */ + // projectTaskRef: { + // relation: Model.HasManyRelation, + // modelClass: ProjectTask.default, + // join: { + // from: 'items_entries.projectRefId', + // to: 'tasks.id', + // }, + // }, - // /** - // * Project expense reference. - // */ - // projectExpenseRef: { - // relation: Model.HasManyRelation, - // modelClass: Expense.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'expenses_transactions.id', - // }, - // }, + /** + * Project expense reference. + */ + // projectExpenseRef: { + // relation: Model.HasManyRelation, + // modelClass: Expense.default, + // join: { + // from: 'items_entries.projectRefId', + // to: 'expenses_transactions.id', + // }, + // }, - // /** - // * Project bill reference. - // */ - // projectBillRef: { - // relation: Model.HasManyRelation, - // modelClass: Bill.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'bills.id', - // }, - // }, + /** + * Project bill reference. + */ + // projectBillRef: { + // relation: Model.HasManyRelation, + // modelClass: Bill, + // join: { + // from: 'items_entries.projectRefId', + // to: 'bills.id', + // }, + // }, - // /** - // * Tax rate reference. - // */ - // tax: { - // relation: Model.HasOneRelation, - // modelClass: TaxRate.default, - // join: { - // from: 'items_entries.taxRateId', - // to: 'tax_rates.id', - // }, - // }, - // }; - // } + /** + * Tax rate reference. + */ + tax: { + relation: Model.HasOneRelation, + modelClass: TaxRate, + join: { + from: 'items_entries.taxRateId', + to: 'tax_rates.id', + }, + }, + }; + } } diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts b/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts index eb5b15c6c..00c7d79e3 100644 --- a/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts +++ b/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts @@ -18,6 +18,8 @@ import { VendorCreditsApplicationService } from './VendorCreditsApplication.serv import { OpenVendorCreditService } from './commands/OpenVendorCredit.service'; import { VendorCreditGlEntriesSubscriber } from './subscribers/VendorCreditGLEntriesSubscriber'; import { VendorCreditGLEntries } from './commands/VendorCreditGLEntries'; +import { LedgerModule } from '../Ledger/Ledger.module'; +import { AccountsModule } from '../Accounts/Accounts.module'; @Module({ imports: [ @@ -28,6 +30,8 @@ import { VendorCreditGLEntries } from './commands/VendorCreditGLEntries'; AutoIncrementOrdersModule, BranchesModule, WarehousesModule, + LedgerModule, + AccountsModule ], providers: [ CreateVendorCreditService, diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGL.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGL.ts index f59683b7c..e85585576 100644 --- a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGL.ts +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditGL.ts @@ -1,4 +1,4 @@ -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { VendorCredit } from '../models/VendorCredit'; import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; import { AccountNormal } from '@/interfaces/Account'; @@ -96,7 +96,7 @@ export class VendorCreditGL { credit: totalLocal, index: index + 2, itemId: entry.itemId, - itemQuantity: entry.quantity, + // itemQuantity: entry.quantity, accountId: 'inventory' === entry.item.type ? entry.item.inventoryAccountId @@ -156,6 +156,10 @@ export class VendorCreditGL { return [payableEntry, discountEntry, adjustmentEntry, ...itemsEntries]; } + /** + * Retrieves the vendor credit ledger. + * @returns {Ledger} + */ public getVendorCreditLedger(): Ledger { const entries = this.getVendorCreditGLEntries(); return new Ledger(entries); diff --git a/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts b/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts index 787ddbd2a..c090a4ce6 100644 --- a/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts +++ b/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts @@ -10,7 +10,7 @@ import { Model, raw, mixin } from 'objection'; import { Vendor } from '@/modules/Vendors/models/Vendor'; import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model'; import { Branch } from '@/modules/Branches/models/Branch.model'; -import { ItemEntry } from '@/modules/Items/models/ItemEntry'; +import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { BaseModel } from '@/models/Model'; import { DiscountType } from '@/common/types/Discount';