refactor: migrate ledger writer to nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-01-01 12:11:58 +02:00
parent 3ad34ba56f
commit 505c4b28a5
45 changed files with 880 additions and 777 deletions

View File

@@ -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 = {

View File

@@ -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({

View File

@@ -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 });

View File

@@ -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],

View File

@@ -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);
}
}

View File

@@ -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<void> => {
// 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<void> => {
// 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<void> => {
// // 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<void> => {
// 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<void> => {
// 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<void> => {
await this.ledgerStorage.deleteByReference(
billPaymentId,
'BillPayment',
trx,
);
};
}

View File

@@ -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
// );
// };
// }

View File

@@ -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
);
};
}

View File

@@ -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,
) {}

View File

@@ -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],
})

View File

@@ -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';

View File

@@ -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,
};
}

View File

@@ -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,
) {}

View File

@@ -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()

View File

@@ -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,
) {}

View File

@@ -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
`)
`),
);
},
/**

View File

@@ -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,
) {}

View File

@@ -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,

View File

@@ -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,
};
}

View File

@@ -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';

View File

@@ -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

View File

@@ -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 {

View File

@@ -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',
},
},
};
}
}

View File

@@ -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,
) {}
/**

View File

@@ -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,
) {}

View File

@@ -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 {}

View File

@@ -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)

View File

@@ -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,
) {}
/**

View File

@@ -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';

View File

@@ -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: [

View File

@@ -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';

View File

@@ -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';

View File

@@ -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,
];
};

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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()

View File

@@ -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';

View File

@@ -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,
};
},

View File

@@ -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');

View File

@@ -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()

View File

@@ -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';

View File

@@ -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',
},
},
};
}
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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';