Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
889b0cec4b fix(server): sale receipt cost gl entries 2026-01-25 22:20:28 +02:00
40 changed files with 810 additions and 843 deletions

View File

@@ -17,7 +17,7 @@ export class BillBranchValidateSubscriber {
* Validate branch existance on bill creating. * Validate branch existance on bill creating.
* @param {IBillCreatingPayload} payload * @param {IBillCreatingPayload} payload
*/ */
@OnEvent(events.bill.onCreating, { suppressErrors: false }) @OnEvent(events.bill.onCreating)
async validateBranchExistanceOnBillCreating({ async validateBranchExistanceOnBillCreating({
billDTO, billDTO,
}: IBillCreatingPayload) { }: IBillCreatingPayload) {
@@ -30,7 +30,7 @@ export class BillBranchValidateSubscriber {
* Validate branch existance once bill editing. * Validate branch existance once bill editing.
* @param {IBillEditingPayload} payload * @param {IBillEditingPayload} payload
*/ */
@OnEvent(events.bill.onEditing, { suppressErrors: false }) @OnEvent(events.bill.onEditing)
async validateBranchExistanceOnBillEditing({ billDTO }: IBillEditingPayload) { async validateBranchExistanceOnBillEditing({ billDTO }: IBillEditingPayload) {
await this.validateBranchExistance.validateTransactionBranchWhenActive( await this.validateBranchExistance.validateTransactionBranchWhenActive(
billDTO.branchId, billDTO.branchId,

View File

@@ -14,7 +14,7 @@ export class CashflowBranchDTOValidatorSubscriber {
* Validate branch existance once cashflow transaction creating. * Validate branch existance once cashflow transaction creating.
* @param {ICommandCashflowCreatingPayload} payload * @param {ICommandCashflowCreatingPayload} payload
*/ */
@OnEvent(events.cashflow.onTransactionCreating, { suppressErrors: false }) @OnEvent(events.cashflow.onTransactionCreating)
async validateBranchExistanceOnCashflowTransactionCreating({ async validateBranchExistanceOnCashflowTransactionCreating({
newTransactionDTO, newTransactionDTO,
}: ICommandCashflowCreatingPayload) { }: ICommandCashflowCreatingPayload) {

View File

@@ -15,13 +15,13 @@ import {
export class ContactBranchValidateSubscriber { export class ContactBranchValidateSubscriber {
constructor( constructor(
private readonly validateBranchExistance: ValidateBranchExistance, private readonly validateBranchExistance: ValidateBranchExistance,
) {} ) { }
/** /**
* Validate branch existance on customer creating. * Validate branch existance on customer creating.
* @param {ICustomerEventCreatingPayload} payload * @param {ICustomerEventCreatingPayload} payload
*/ */
@OnEvent(events.customers.onCreating, { suppressErrors: false }) @OnEvent(events.customers.onCreating)
async validateBranchExistanceOnCustomerCreating({ async validateBranchExistanceOnCustomerCreating({
customerDTO, customerDTO,
}: ICustomerEventCreatingPayload) { }: ICustomerEventCreatingPayload) {
@@ -37,7 +37,7 @@ export class ContactBranchValidateSubscriber {
* Validate branch existance once customer opening balance editing. * Validate branch existance once customer opening balance editing.
* @param {ICustomerOpeningBalanceEditingPayload} payload * @param {ICustomerOpeningBalanceEditingPayload} payload
*/ */
@OnEvent(events.customers.onOpeningBalanceChanging, { suppressErrors: false }) @OnEvent(events.customers.onOpeningBalanceChanging)
async validateBranchExistanceOnCustomerOpeningBalanceEditing({ async validateBranchExistanceOnCustomerOpeningBalanceEditing({
openingBalanceEditDTO, openingBalanceEditDTO,
}: ICustomerOpeningBalanceEditingPayload) { }: ICustomerOpeningBalanceEditingPayload) {
@@ -52,7 +52,7 @@ export class ContactBranchValidateSubscriber {
* Validates the branch existance on vendor creating. * Validates the branch existance on vendor creating.
* @param {IVendorEventCreatingPayload} payload * @param {IVendorEventCreatingPayload} payload
*/ */
@OnEvent(events.vendors.onCreating, { suppressErrors: false }) @OnEvent(events.vendors.onCreating)
async validateBranchExistanceonVendorCreating({ async validateBranchExistanceonVendorCreating({
vendorDTO, vendorDTO,
}: IVendorEventCreatingPayload) { }: IVendorEventCreatingPayload) {
@@ -68,7 +68,7 @@ export class ContactBranchValidateSubscriber {
* Validate branch existance once the vendor opening balance editing. * Validate branch existance once the vendor opening balance editing.
* @param {IVendorOpeningBalanceEditingPayload} payload * @param {IVendorOpeningBalanceEditingPayload} payload
*/ */
@OnEvent(events.vendors.onOpeningBalanceChanging, { suppressErrors: false }) @OnEvent(events.vendors.onOpeningBalanceChanging)
async validateBranchExistanceOnVendorOpeningBalanceEditing({ async validateBranchExistanceOnVendorOpeningBalanceEditing({
openingBalanceEditDTO, openingBalanceEditDTO,
}: IVendorOpeningBalanceEditingPayload) { }: IVendorOpeningBalanceEditingPayload) {

View File

@@ -15,7 +15,7 @@ export class CreditNoteBranchValidateSubscriber {
* Validate branch existance on credit note creating. * Validate branch existance on credit note creating.
* @param {ICreditNoteCreatingPayload} payload * @param {ICreditNoteCreatingPayload} payload
*/ */
@OnEvent(events.creditNote.onCreating, { suppressErrors: false }) @OnEvent(events.creditNote.onCreating)
async validateBranchExistanceOnCreditCreating({ async validateBranchExistanceOnCreditCreating({
creditNoteDTO, creditNoteDTO,
}: ICreditNoteCreatingPayload) { }: ICreditNoteCreatingPayload) {
@@ -28,7 +28,7 @@ export class CreditNoteBranchValidateSubscriber {
* Validate branch existance once credit note editing. * Validate branch existance once credit note editing.
* @param {ICreditNoteEditingPayload} payload * @param {ICreditNoteEditingPayload} payload
*/ */
@OnEvent(events.creditNote.onEditing, { suppressErrors: false }) @OnEvent(events.creditNote.onEditing)
async validateBranchExistanceOnCreditEditing({ async validateBranchExistanceOnCreditEditing({
creditNoteEditDTO, creditNoteEditDTO,
}: ICreditNoteEditingPayload) { }: ICreditNoteEditingPayload) {

View File

@@ -14,7 +14,7 @@ export class CreditNoteRefundBranchValidateSubscriber {
* Validate branch existance on refund credit note creating. * Validate branch existance on refund credit note creating.
* @param {IRefundCreditNoteCreatingPayload} payload * @param {IRefundCreditNoteCreatingPayload} payload
*/ */
@OnEvent(events.creditNote.onRefundCreating, { suppressErrors: false }) @OnEvent(events.creditNote.onRefundCreating)
async validateBranchExistanceOnCreditRefundCreating({ async validateBranchExistanceOnCreditRefundCreating({
newCreditNoteDTO, newCreditNoteDTO,
}: IRefundCreditNoteCreatingPayload) { }: IRefundCreditNoteCreatingPayload) {

View File

@@ -16,7 +16,7 @@ export class ExpenseBranchValidateSubscriber {
* Validate branch existance once expense transaction creating. * Validate branch existance once expense transaction creating.
* @param {IExpenseCreatingPayload} payload * @param {IExpenseCreatingPayload} payload
*/ */
@OnEvent(events.expenses.onCreating, { suppressErrors: false }) @OnEvent(events.expenses.onCreating)
async validateBranchExistanceOnExpenseCreating({ async validateBranchExistanceOnExpenseCreating({
expenseDTO, expenseDTO,
}: IExpenseCreatingPayload) { }: IExpenseCreatingPayload) {
@@ -29,7 +29,7 @@ export class ExpenseBranchValidateSubscriber {
* Validate branch existance once expense transaction editing. * Validate branch existance once expense transaction editing.
* @param {IExpenseEventEditingPayload} payload * @param {IExpenseEventEditingPayload} payload
*/ */
@OnEvent(events.expenses.onEditing, { suppressErrors: false }) @OnEvent(events.expenses.onEditing)
async validateBranchExistanceOnExpenseEditing({ async validateBranchExistanceOnExpenseEditing({
expenseDTO, expenseDTO,
}: IExpenseEventEditingPayload) { }: IExpenseEventEditingPayload) {

View File

@@ -14,7 +14,7 @@ export class InventoryAdjustmentBranchValidateSubscriber {
* Validate branch existance on inventory adjustment creating. * Validate branch existance on inventory adjustment creating.
* @param {IInventoryAdjustmentCreatingPayload} payload * @param {IInventoryAdjustmentCreatingPayload} payload
*/ */
@OnEvent(events.inventoryAdjustment.onQuickCreating, { suppressErrors: false }) @OnEvent(events.inventoryAdjustment.onQuickCreating)
async validateBranchExistanceOnInventoryCreating({ async validateBranchExistanceOnInventoryCreating({
quickAdjustmentDTO, quickAdjustmentDTO,
}: IInventoryAdjustmentCreatingPayload) { }: IInventoryAdjustmentCreatingPayload) {

View File

@@ -17,7 +17,7 @@ export class InvoiceBranchValidateSubscriber {
* Validate branch existance on invoice creating. * Validate branch existance on invoice creating.
* @param {ISaleInvoiceCreatingPayload} payload * @param {ISaleInvoiceCreatingPayload} payload
*/ */
@OnEvent(events.saleInvoice.onCreating, { suppressErrors: false }) @OnEvent(events.saleInvoice.onCreating)
async validateBranchExistanceOnInvoiceCreating({ async validateBranchExistanceOnInvoiceCreating({
saleInvoiceDTO, saleInvoiceDTO,
}: ISaleInvoiceCreatingPaylaod) { }: ISaleInvoiceCreatingPaylaod) {
@@ -30,7 +30,7 @@ export class InvoiceBranchValidateSubscriber {
* Validate branch existance once invoice editing. * Validate branch existance once invoice editing.
* @param {ISaleInvoiceEditingPayload} payload * @param {ISaleInvoiceEditingPayload} payload
*/ */
@OnEvent(events.saleInvoice.onEditing, { suppressErrors: false }) @OnEvent(events.saleInvoice.onEditing)
async validateBranchExistanceOnInvoiceEditing({ async validateBranchExistanceOnInvoiceEditing({
saleInvoiceDTO, saleInvoiceDTO,
}: ISaleInvoiceEditingPayload) { }: ISaleInvoiceEditingPayload) {

View File

@@ -17,7 +17,7 @@ export class PaymentMadeBranchValidateSubscriber {
* Validate branch existance on estimate creating. * Validate branch existance on estimate creating.
* @param {ISaleEstimateCreatedPayload} payload * @param {ISaleEstimateCreatedPayload} payload
*/ */
@OnEvent(events.billPayment.onCreating, { suppressErrors: false }) @OnEvent(events.billPayment.onCreating)
async validateBranchExistanceOnPaymentCreating({ async validateBranchExistanceOnPaymentCreating({
billPaymentDTO, billPaymentDTO,
}: IBillPaymentCreatingPayload) { }: IBillPaymentCreatingPayload) {
@@ -30,7 +30,7 @@ export class PaymentMadeBranchValidateSubscriber {
* Validate branch existance once estimate editing. * Validate branch existance once estimate editing.
* @param {ISaleEstimateEditingPayload} payload * @param {ISaleEstimateEditingPayload} payload
*/ */
@OnEvent(events.billPayment.onEditing, { suppressErrors: false }) @OnEvent(events.billPayment.onEditing)
async validateBranchExistanceOnPaymentEditing({ async validateBranchExistanceOnPaymentEditing({
billPaymentDTO, billPaymentDTO,
}: IBillPaymentEditingPayload) { }: IBillPaymentEditingPayload) {

View File

@@ -17,7 +17,7 @@ export class PaymentReceiveBranchValidateSubscriber {
* Validate branch existance on estimate creating. * Validate branch existance on estimate creating.
* @param {IPaymentReceivedCreatingPayload} payload * @param {IPaymentReceivedCreatingPayload} payload
*/ */
@OnEvent(events.paymentReceive.onCreating, { suppressErrors: false }) @OnEvent(events.paymentReceive.onCreating)
async validateBranchExistanceOnPaymentCreating({ async validateBranchExistanceOnPaymentCreating({
paymentReceiveDTO, paymentReceiveDTO,
}: IPaymentReceivedCreatingPayload) { }: IPaymentReceivedCreatingPayload) {
@@ -30,7 +30,7 @@ export class PaymentReceiveBranchValidateSubscriber {
* Validate branch existance once estimate editing. * Validate branch existance once estimate editing.
* @param {IPaymentReceivedEditingPayload} payload * @param {IPaymentReceivedEditingPayload} payload
*/ */
@OnEvent(events.paymentReceive.onEditing, { suppressErrors: false }) @OnEvent(events.paymentReceive.onEditing)
async validateBranchExistanceOnPaymentEditing({ async validateBranchExistanceOnPaymentEditing({
paymentReceiveDTO, paymentReceiveDTO,
}: IPaymentReceivedEditingPayload) { }: IPaymentReceivedEditingPayload) {

View File

@@ -17,7 +17,7 @@ export class SaleEstimateBranchValidateSubscriber {
* Validate branch existance on estimate creating. * Validate branch existance on estimate creating.
* @param {ISaleEstimateCreatedPayload} payload * @param {ISaleEstimateCreatedPayload} payload
*/ */
@OnEvent(events.saleEstimate.onCreating, { suppressErrors: false }) @OnEvent(events.saleEstimate.onCreating)
async validateBranchExistanceOnEstimateCreating({ async validateBranchExistanceOnEstimateCreating({
estimateDTO, estimateDTO,
}: ISaleEstimateCreatingPayload) { }: ISaleEstimateCreatingPayload) {
@@ -30,7 +30,7 @@ export class SaleEstimateBranchValidateSubscriber {
* Validate branch existance once estimate editing. * Validate branch existance once estimate editing.
* @param {ISaleEstimateEditingPayload} payload * @param {ISaleEstimateEditingPayload} payload
*/ */
@OnEvent(events.saleEstimate.onEditing, { suppressErrors: false }) @OnEvent(events.saleEstimate.onEditing)
async validateBranchExistanceOnEstimateEditing({ async validateBranchExistanceOnEstimateEditing({
estimateDTO, estimateDTO,
}: ISaleEstimateEditingPayload) { }: ISaleEstimateEditingPayload) {

View File

@@ -17,7 +17,7 @@ export class SaleReceiptBranchValidateSubscriber {
* Validate branch existance on estimate creating. * Validate branch existance on estimate creating.
* @param {ISaleReceiptCreatingPayload} payload * @param {ISaleReceiptCreatingPayload} payload
*/ */
@OnEvent(events.saleReceipt.onCreating, { suppressErrors: false }) @OnEvent(events.saleReceipt.onCreating)
async validateBranchExistanceOnInvoiceCreating({ async validateBranchExistanceOnInvoiceCreating({
saleReceiptDTO, saleReceiptDTO,
}: ISaleReceiptCreatingPayload) { }: ISaleReceiptCreatingPayload) {
@@ -30,7 +30,7 @@ export class SaleReceiptBranchValidateSubscriber {
* Validate branch existance once estimate editing. * Validate branch existance once estimate editing.
* @param {ISaleReceiptEditingPayload} payload * @param {ISaleReceiptEditingPayload} payload
*/ */
@OnEvent(events.saleReceipt.onEditing, { suppressErrors: false }) @OnEvent(events.saleReceipt.onEditing)
async validateBranchExistanceOnInvoiceEditing({ async validateBranchExistanceOnInvoiceEditing({
saleReceiptDTO, saleReceiptDTO,
}: ISaleReceiptEditingPayload) { }: ISaleReceiptEditingPayload) {

View File

@@ -17,7 +17,7 @@ export class VendorCreditBranchValidateSubscriber {
* Validate branch existance on estimate creating. * Validate branch existance on estimate creating.
* @param {ISaleEstimateCreatedPayload} payload * @param {ISaleEstimateCreatedPayload} payload
*/ */
@OnEvent(events.vendorCredit.onCreating, { suppressErrors: false }) @OnEvent(events.vendorCredit.onCreating)
async validateBranchExistanceOnCreditCreating({ async validateBranchExistanceOnCreditCreating({
vendorCreditCreateDTO, vendorCreditCreateDTO,
}: IVendorCreditCreatingPayload) { }: IVendorCreditCreatingPayload) {
@@ -30,7 +30,7 @@ export class VendorCreditBranchValidateSubscriber {
* Validate branch existance once estimate editing. * Validate branch existance once estimate editing.
* @param {ISaleEstimateEditingPayload} payload * @param {ISaleEstimateEditingPayload} payload
*/ */
@OnEvent(events.vendorCredit.onEditing, { suppressErrors: false }) @OnEvent(events.vendorCredit.onEditing)
async validateBranchExistanceOnCreditEditing({ async validateBranchExistanceOnCreditEditing({
vendorCreditDTO, vendorCreditDTO,
}: IVendorCreditEditingPayload) { }: IVendorCreditEditingPayload) {

View File

@@ -14,7 +14,7 @@ export class VendorCreditRefundBranchValidateSubscriber {
* Validate branch existance on refund credit note creating. * Validate branch existance on refund credit note creating.
* @param {IRefundVendorCreditCreatingPayload} payload * @param {IRefundVendorCreditCreatingPayload} payload
*/ */
@OnEvent(events.vendorCredit.onRefundCreating, { suppressErrors: false }) @OnEvent(events.vendorCredit.onRefundCreating)
async validateBranchExistanceOnCreditRefundCreating({ async validateBranchExistanceOnCreditRefundCreating({
refundVendorCreditDTO, refundVendorCreditDTO,
}: IRefundVendorCreditCreatingPayload) { }: IRefundVendorCreditCreatingPayload) {

View File

@@ -1,103 +1,117 @@
import { Injectable } from '@nestjs/common'; // import { Service, Inject } from 'typedi';
import { AccountNormal } from '@/interfaces/Account'; // import { AccountNormal, ICustomer, ILedgerEntry } from '@/interfaces';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; // import Ledger from '@/services/Accounting/Ledger';
import { Ledger } from '@/modules/Ledger/Ledger';
import { Customer } from './models/Customer';
@Injectable() // @Service()
export class CustomerGLEntries { // export class CustomerGLEntries {
/** // /**
* Retrieves the customer opening balance common entry attributes. // * Retrieves the customer opening balance common entry attributes.
*/ // * @param {ICustomer} customer
private getCustomerOpeningGLCommonEntry = (customer: Customer) => { // */
return { // private getCustomerOpeningGLCommonEntry = (customer: ICustomer) => {
exchangeRate: customer.openingBalanceExchangeRate, // return {
currencyCode: customer.currencyCode, // exchangeRate: customer.openingBalanceExchangeRate,
// currencyCode: customer.currencyCode,
transactionType: 'CustomerOpeningBalance', // transactionType: 'CustomerOpeningBalance',
transactionId: customer.id, // transactionId: customer.id,
date: customer.openingBalanceAt, // date: customer.openingBalanceAt,
contactId: customer.id, // userId: customer.userId,
// contactId: customer.id,
credit: 0, // credit: 0,
debit: 0, // debit: 0,
branchId: customer.openingBalanceBranchId, // branchId: customer.openingBalanceBranchId,
}; // };
}; // };
/** // /**
* Retrieves the customer opening GL credit entry. // * Retrieves the customer opening GL credit entry.
*/ // * @param {number} ARAccountId
private getCustomerOpeningGLCreditEntry = ( // * @param {ICustomer} customer
ARAccountId: number, // * @returns {ILedgerEntry}
customer: Customer // */
): ILedgerEntry => { // private getCustomerOpeningGLCreditEntry = (
const commonEntry = this.getCustomerOpeningGLCommonEntry(customer); // ARAccountId: number,
// customer: ICustomer
// ): ILedgerEntry => {
// const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
return { // return {
...commonEntry, // ...commonEntry,
credit: 0, // credit: 0,
debit: customer.localOpeningBalance, // debit: customer.localOpeningBalance,
accountId: ARAccountId, // accountId: ARAccountId,
accountNormal: AccountNormal.DEBIT, // accountNormal: AccountNormal.DEBIT,
index: 1, // index: 1,
}; // };
}; // };
/** // /**
* Retrieves the customer opening GL debit entry. // * Retrieves the customer opening GL debit entry.
*/ // * @param {number} incomeAccountId
private getCustomerOpeningGLDebitEntry = ( // * @param {ICustomer} customer
incomeAccountId: number, // * @returns {ILedgerEntry}
customer: Customer // */
): ILedgerEntry => { // private getCustomerOpeningGLDebitEntry = (
const commonEntry = this.getCustomerOpeningGLCommonEntry(customer); // incomeAccountId: number,
// customer: ICustomer
// ): ILedgerEntry => {
// const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
return { // return {
...commonEntry, // ...commonEntry,
credit: customer.localOpeningBalance, // credit: customer.localOpeningBalance,
debit: 0, // debit: 0,
accountId: incomeAccountId, // accountId: incomeAccountId,
accountNormal: AccountNormal.CREDIT, // accountNormal: AccountNormal.CREDIT,
index: 2, // index: 2,
}; // };
}; // };
/** // /**
* Retrieves the customer opening GL entries. // * Retrieves the customer opening GL entries.
*/ // * @param {number} ARAccountId
public getCustomerOpeningGLEntries = ( // * @param {number} incomeAccountId
ARAccountId: number, // * @param {ICustomer} customer
incomeAccountId: number, // * @returns {ILedgerEntry[]}
customer: Customer // */
) => { // public getCustomerOpeningGLEntries = (
const debitEntry = this.getCustomerOpeningGLDebitEntry( // ARAccountId: number,
incomeAccountId, // incomeAccountId: number,
customer // customer: ICustomer
); // ) => {
const creditEntry = this.getCustomerOpeningGLCreditEntry( // const debitEntry = this.getCustomerOpeningGLDebitEntry(
ARAccountId, // incomeAccountId,
customer // customer
); // );
return [debitEntry, creditEntry]; // const creditEntry = this.getCustomerOpeningGLCreditEntry(
}; // ARAccountId,
// customer
// );
// return [debitEntry, creditEntry];
// };
/** // /**
* Retrieves the customer opening balance ledger. // * Retrieves the customer opening balance ledger.
*/ // * @param {number} ARAccountId
public getCustomerOpeningLedger = ( // * @param {number} incomeAccountId
ARAccountId: number, // * @param {ICustomer} customer
incomeAccountId: number, // * @returns {ILedger}
customer: Customer // */
) => { // public getCustomerOpeningLedger = (
const entries = this.getCustomerOpeningGLEntries( // ARAccountId: number,
ARAccountId, // incomeAccountId: number,
incomeAccountId, // customer: ICustomer
customer // ) => {
); // const entries = this.getCustomerOpeningGLEntries(
return new Ledger(entries); // ARAccountId,
}; // incomeAccountId,
} // customer
// );
// return new Ledger(entries);
// };
// }

View File

@@ -1,84 +1,90 @@
import { Knex } from 'knex'; // import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common'; // import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service'; // import HasTenancyService from '@/services/Tenancy/TenancyService';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository'; // import { Service, Inject } from 'typedi';
import { CustomerGLEntries } from './CustomerGLEntries'; // import { CustomerGLEntries } from './CustomerGLEntries';
import { Customer } from './models/Customer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Account } from '../Accounts/models/Account.model';
@Injectable() // @Service()
export class CustomerGLEntriesStorage { // export class CustomerGLEntriesStorage {
constructor( // @Inject()
private readonly ledgerStorage: LedgerStorageService, // private tenancy: HasTenancyService;
private readonly accountRepository: AccountRepository,
private readonly customerGLEntries: CustomerGLEntries,
@Inject(Account.name) // @Inject()
private readonly accountModel: TenantModelProxy<typeof Account>, // private ledegrRepository: LedgerStorageService;
@Inject(Customer.name) // @Inject()
private readonly customerModel: TenantModelProxy<typeof Customer>, // private customerGLEntries: CustomerGLEntries;
) { }
/** // /**
* Customer opening balance journals. // * Customer opening balance journals.
*/ // * @param {number} tenantId
public writeCustomerOpeningBalance = async ( // * @param {number} customerId
customerId: number, // * @param {Knex.Transaction} trx
trx?: Knex.Transaction, // */
) => { // public writeCustomerOpeningBalance = async (
const customer = await this.customerModel() // tenantId: number,
.query(trx) // customerId: number,
.findById(customerId); // trx?: Knex.Transaction
// ) => {
// const { Customer } = this.tenancy.models(tenantId);
// const { accountRepository } = this.tenancy.repositories(tenantId);
// Finds the income account. // const customer = await Customer.query(trx).findById(customerId);
const incomeAccount = await this.accountModel()
.query(trx)
.findOne({ slug: 'other-income' });
// Find or create the A/R account. // // Finds the income account.
const ARAccount = // const incomeAccount = await accountRepository.findOne({
await this.accountRepository.findOrCreateAccountReceivable( // slug: 'other-income',
customer.currencyCode, // });
{}, // // Find or create the A/R account.
trx, // const ARAccount = await accountRepository.findOrCreateAccountReceivable(
); // customer.currencyCode,
// Retrieves the customer opening balance ledger. // {},
const ledger = this.customerGLEntries.getCustomerOpeningLedger( // trx
ARAccount.id, // );
incomeAccount.id, // // Retrieves the customer opening balance ledger.
customer, // const ledger = this.customerGLEntries.getCustomerOpeningLedger(
); // ARAccount.id,
// Commits the ledger entries to the storage. // incomeAccount.id,
await this.ledgerStorage.commit(ledger, trx); // customer
}; // );
// // Commits the ledger entries to the storage.
// await this.ledegrRepository.commit(tenantId, ledger, trx);
// };
/** // /**
* Reverts the customer opening balance GL entries. // * Reverts the customer opening balance GL entries.
*/ // * @param {number} tenantId
public revertCustomerOpeningBalance = async ( // * @param {number} customerId
customerId: number, // * @param {Knex.Transaction} trx
trx?: Knex.Transaction, // */
) => { // public revertCustomerOpeningBalance = async (
await this.ledgerStorage.deleteByReference( // tenantId: number,
customerId, // customerId: number,
'CustomerOpeningBalance', // trx?: Knex.Transaction
trx, // ) => {
); // await this.ledegrRepository.deleteByReference(
}; // tenantId,
// customerId,
// 'CustomerOpeningBalance',
// trx
// );
// };
/** // /**
* Writes the customer opening balance GL entries. // * Writes the customer opening balance GL entries.
*/ // * @param {number} tenantId
public rewriteCustomerOpeningBalance = async ( // * @param {number} customerId
customerId: number, // * @param {Knex.Transaction} trx
trx?: Knex.Transaction, // */
) => { // public rewriteCustomerOpeningBalance = async (
// Reverts the customer opening balance entries. // tenantId: number,
await this.revertCustomerOpeningBalance(customerId, trx); // customerId: number,
// trx?: Knex.Transaction
// ) => {
// // Reverts the customer opening balance entries.
// await this.revertCustomerOpeningBalance(tenantId, customerId, trx);
// Write the customer opening balance entries. // // Write the customer opening balance entries.
await this.writeCustomerOpeningBalance(customerId, trx); // await this.writeCustomerOpeningBalance(tenantId, customerId, trx);
}; // };
} // }

View File

@@ -9,7 +9,10 @@ import {
Query, Query,
} from '@nestjs/common'; } from '@nestjs/common';
import { CustomersApplication } from './CustomersApplication.service'; import { CustomersApplication } from './CustomersApplication.service';
import { CustomerOpeningBalanceEditDto } from './dtos/CustomerOpeningBalanceEdit.dto'; import {
ICustomerOpeningBalanceEditDTO,
ICustomersFilter,
} from './types/Customers.types';
import { import {
ApiOperation, ApiOperation,
ApiResponse, ApiResponse,
@@ -103,7 +106,7 @@ export class CustomersController {
}) })
editOpeningBalance( editOpeningBalance(
@Param('id') customerId: number, @Param('id') customerId: number,
@Body() openingBalanceDTO: CustomerOpeningBalanceEditDto, @Body() openingBalanceDTO: ICustomerOpeningBalanceEditDTO,
) { ) {
return this.customersApplication.editOpeningBalance( return this.customersApplication.editOpeningBalance(
customerId, customerId,

View File

@@ -18,19 +18,9 @@ import { GetCustomers } from './queries/GetCustomers.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { BulkDeleteCustomersService } from './BulkDeleteCustomers.service'; import { BulkDeleteCustomersService } from './BulkDeleteCustomers.service';
import { ValidateBulkDeleteCustomersService } from './ValidateBulkDeleteCustomers.service'; import { ValidateBulkDeleteCustomersService } from './ValidateBulkDeleteCustomers.service';
import { LedgerModule } from '../Ledger/Ledger.module';
import { AccountsModule } from '../Accounts/Accounts.module';
import { CustomerGLEntries } from './CustomerGLEntries';
import { CustomerGLEntriesStorage } from './CustomerGLEntriesStorage';
import { CustomerWriteGLOpeningBalanceSubscriber } from './subscribers/CustomerGLEntriesSubscriber';
@Module({ @Module({
imports: [ imports: [TenancyDatabaseModule, DynamicListModule],
TenancyDatabaseModule,
DynamicListModule,
LedgerModule,
AccountsModule,
],
controllers: [CustomersController], controllers: [CustomersController],
providers: [ providers: [
ActivateCustomer, ActivateCustomer,
@@ -51,9 +41,6 @@ import { CustomerWriteGLOpeningBalanceSubscriber } from './subscribers/CustomerG
GetCustomers, GetCustomers,
BulkDeleteCustomersService, BulkDeleteCustomersService,
ValidateBulkDeleteCustomersService, ValidateBulkDeleteCustomersService,
CustomerGLEntries,
CustomerGLEntriesStorage,
CustomerWriteGLOpeningBalanceSubscriber,
], ],
}) })
export class CustomersModule {} export class CustomersModule {}

View File

@@ -4,7 +4,10 @@ import { CreateCustomer } from './commands/CreateCustomer.service';
import { EditCustomer } from './commands/EditCustomer.service'; import { EditCustomer } from './commands/EditCustomer.service';
import { DeleteCustomer } from './commands/DeleteCustomer.service'; import { DeleteCustomer } from './commands/DeleteCustomer.service';
import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service'; import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service';
import { CustomerOpeningBalanceEditDto } from './dtos/CustomerOpeningBalanceEdit.dto'; import {
ICustomerOpeningBalanceEditDTO,
ICustomersFilter,
} from './types/Customers.types';
import { CreateCustomerDto } from './dtos/CreateCustomer.dto'; import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
import { EditCustomerDto } from './dtos/EditCustomer.dto'; import { EditCustomerDto } from './dtos/EditCustomer.dto';
import { GetCustomers } from './queries/GetCustomers.service'; import { GetCustomers } from './queries/GetCustomers.service';
@@ -15,12 +18,12 @@ import { ValidateBulkDeleteCustomersService } from './ValidateBulkDeleteCustomer
@Injectable() @Injectable()
export class CustomersApplication { export class CustomersApplication {
constructor( constructor(
private readonly getCustomerService: GetCustomerService, private getCustomerService: GetCustomerService,
private readonly createCustomerService: CreateCustomer, private createCustomerService: CreateCustomer,
private readonly editCustomerService: EditCustomer, private editCustomerService: EditCustomer,
private readonly deleteCustomerService: DeleteCustomer, private deleteCustomerService: DeleteCustomer,
private readonly editOpeningBalanceService: EditOpeningBalanceCustomer, private editOpeningBalanceService: EditOpeningBalanceCustomer,
private readonly getCustomersService: GetCustomers, private getCustomersService: GetCustomers,
private readonly bulkDeleteCustomersService: BulkDeleteCustomersService, private readonly bulkDeleteCustomersService: BulkDeleteCustomersService,
private readonly validateBulkDeleteCustomersService: ValidateBulkDeleteCustomersService, private readonly validateBulkDeleteCustomersService: ValidateBulkDeleteCustomersService,
) {} ) {}
@@ -69,7 +72,7 @@ export class CustomersApplication {
*/ */
public editOpeningBalance = ( public editOpeningBalance = (
customerId: number, customerId: number,
openingBalanceEditDTO: CustomerOpeningBalanceEditDto, openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO,
) => { ) => {
return this.editOpeningBalanceService.changeOpeningBalance( return this.editOpeningBalanceService.changeOpeningBalance(
customerId, customerId,
@@ -79,7 +82,7 @@ export class CustomersApplication {
/** /**
* Retrieve customers paginated list. * Retrieve customers paginated list.
* @param {GetCustomersQueryDto} filter - Cusotmers filter. * @param {ICustomersFilter} filter - Cusotmers filter.
*/ */
public getCustomers = (filterDTO: GetCustomersQueryDto) => { public getCustomers = (filterDTO: GetCustomersQueryDto) => {
return this.getCustomersService.getCustomersList(filterDTO); return this.getCustomersService.getCustomersList(filterDTO);

View File

@@ -31,7 +31,7 @@ export class CreateCustomer {
/** /**
* Creates a new customer. * Creates a new customer.
* @param {CreateCustomerDto} customerDTO * @param {ICustomerNewDTO} customerDTO
* @return {Promise<ICustomer>} * @return {Promise<ICustomer>}
*/ */
public async createCustomer( public async createCustomer(

View File

@@ -1,10 +1,10 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { import {
ICustomerOpeningBalanceEditDTO,
ICustomerOpeningBalanceEditedPayload, ICustomerOpeningBalanceEditedPayload,
ICustomerOpeningBalanceEditingPayload, ICustomerOpeningBalanceEditingPayload,
} from '../types/Customers.types'; } from '../types/Customers.types';
import { CustomerOpeningBalanceEditDto } from '../dtos/CustomerOpeningBalanceEdit.dto';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Customer } from '../models/Customer'; import { Customer } from '../models/Customer';
@@ -29,11 +29,11 @@ export class EditOpeningBalanceCustomer {
/** /**
* Changes the opening balance of the given customer. * Changes the opening balance of the given customer.
* @param {number} customerId - Customer ID. * @param {number} customerId - Customer ID.
* @param {CustomerOpeningBalanceEditDto} openingBalanceEditDTO * @param {ICustomerOpeningBalanceEditDTO} openingBalanceEditDTO
*/ */
public async changeOpeningBalance( public async changeOpeningBalance(
customerId: number, customerId: number,
openingBalanceEditDTO: CustomerOpeningBalanceEditDto, openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO,
): Promise<Customer> { ): Promise<Customer> {
// Retrieves the old customer or throw not found error. // Retrieves the old customer or throw not found error.
const oldCustomer = await this.customerModel() const oldCustomer = await this.customerModel()

View File

@@ -4,7 +4,6 @@ import {
IsNotEmpty, IsNotEmpty,
IsNumber, IsNumber,
IsString, IsString,
ValidateIf,
} from 'class-validator'; } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, ToNumber } from '@/common/decorators/Validators'; import { IsOptional, ToNumber } from '@/common/decorators/Validators';
@@ -41,11 +40,10 @@ export class CreateCustomerDto extends ContactAddressDto {
@ApiProperty({ @ApiProperty({
required: false, required: false,
description: 'Opening balance date (required when openingBalance is provided)', description: 'Opening balance date',
example: '2024-01-01', example: '2024-01-01',
}) })
@ValidateIf((o) => o.openingBalance != null) @IsOptional()
@IsNotEmpty({ message: 'openingBalanceAt is required when openingBalance is provided' })
@IsString() @IsString()
openingBalanceAt?: string; openingBalanceAt?: string;

View File

@@ -1,44 +0,0 @@
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
export class CustomerOpeningBalanceEditDto {
@ApiProperty({
required: true,
description: 'Opening balance',
example: 5000.0,
})
@IsNumber()
@IsNotEmpty()
@ToNumber()
openingBalance: number;
@ApiProperty({
required: false,
description: 'Opening balance date',
example: '2024-01-01',
})
@IsOptional()
@IsString()
openingBalanceAt?: string;
@ApiProperty({
required: false,
description: 'Opening balance exchange rate',
example: 1.0,
})
@IsOptional()
@IsNumber()
@ToNumber()
openingBalanceExchangeRate?: number;
@ApiProperty({
required: false,
description: 'Opening balance branch ID',
example: 101,
})
@IsOptional()
@IsNumber()
@ToNumber()
openingBalanceBranchId?: number;
}

View File

@@ -1,63 +1,91 @@
import { Injectable } from '@nestjs/common'; // import { Service, Inject } from 'typedi';
import { OnEvent } from '@nestjs/event-emitter'; // import {
import { // ICustomerEventCreatedPayload,
ICustomerEventCreatedPayload, // ICustomerEventDeletedPayload,
ICustomerEventDeletedPayload, // ICustomerOpeningBalanceEditedPayload,
ICustomerOpeningBalanceEditedPayload, // } from '@/interfaces';
} from '../types/Customers.types'; // import events from '@/subscribers/events';
import { events } from '@/common/events/events'; // import { CustomerGLEntriesStorage } from '../CustomerGLEntriesStorage';
import { CustomerGLEntriesStorage } from '../CustomerGLEntriesStorage';
@Injectable() // @Service()
export class CustomerWriteGLOpeningBalanceSubscriber { // export class CustomerWriteGLOpeningBalanceSubscriber {
constructor(private readonly customerGLEntries: CustomerGLEntriesStorage) { } // @Inject()
// private customerGLEntries: CustomerGLEntriesStorage;
/** // /**
* Handles the writing opening balance journal entries once the customer created. // * Attaches events with handlers.
*/ // */
@OnEvent(events.customers.onCreated) // public attach(bus) {
public async handleWriteOpenBalanceEntries({ // bus.subscribe(
customer, // events.customers.onCreated,
trx, // this.handleWriteOpenBalanceEntries
}: ICustomerEventCreatedPayload) { // );
// Writes the customer opening balance journal entries. // bus.subscribe(
if (customer.openingBalance) { // events.customers.onDeleted,
await this.customerGLEntries.writeCustomerOpeningBalance( // this.handleRevertOpeningBalanceEntries
customer.id, // );
trx, // bus.subscribe(
); // events.customers.onOpeningBalanceChanged,
} // this.handleRewriteOpeningEntriesOnChanged
} // );
// }
/** // /**
* Handles the deleting opening balance journal entries once the customer deleted. // * Handles the writing opening balance journal entries once the customer created.
*/ // * @param {ICustomerEventCreatedPayload} payload -
@OnEvent(events.customers.onDeleted) // */
public async handleRevertOpeningBalanceEntries({ // private handleWriteOpenBalanceEntries = async ({
customerId, // tenantId,
trx, // customer,
}: ICustomerEventDeletedPayload) { // trx,
await this.customerGLEntries.revertCustomerOpeningBalance(customerId, trx); // }: ICustomerEventCreatedPayload) => {
} // // Writes the customer opening balance journal entries.
// if (customer.openingBalance) {
// await this.customerGLEntries.writeCustomerOpeningBalance(
// tenantId,
// customer.id,
// trx
// );
// }
// };
/** // /**
* Handles the rewrite opening balance entries once opening balance changed. // * Handles the deleting opeing balance journal entrise once the customer deleted.
*/ // * @param {ICustomerEventDeletedPayload} payload -
@OnEvent(events.customers.onOpeningBalanceChanged) // */
public async handleRewriteOpeningEntriesOnChanged({ // private handleRevertOpeningBalanceEntries = async ({
customer, // tenantId,
trx, // customerId,
}: ICustomerOpeningBalanceEditedPayload) { // trx,
if (customer.openingBalance) { // }: ICustomerEventDeletedPayload) => {
await this.customerGLEntries.rewriteCustomerOpeningBalance( // await this.customerGLEntries.revertCustomerOpeningBalance(
customer.id, // tenantId,
trx, // customerId,
); // trx
} else { // );
await this.customerGLEntries.revertCustomerOpeningBalance( // };
customer.id,
trx, // /**
); // * Handles the rewrite opening balance entries once opening balnace changed.
} // * @param {ICustomerOpeningBalanceEditedPayload} payload -
} // */
} // private handleRewriteOpeningEntriesOnChanged = async ({
// tenantId,
// customer,
// trx,
// }: ICustomerOpeningBalanceEditedPayload) => {
// if (customer.openingBalance) {
// await this.customerGLEntries.rewriteCustomerOpeningBalance(
// tenantId,
// customer.id,
// trx
// );
// } else {
// await this.customerGLEntries.revertCustomerOpeningBalance(
// tenantId,
// customer.id,
// trx
// );
// }
// };
// }

View File

@@ -4,7 +4,6 @@ import { IContactAddressDTO } from '@/modules/Contacts/types/Contacts.types';
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { CreateCustomerDto } from '../dtos/CreateCustomer.dto'; import { CreateCustomerDto } from '../dtos/CreateCustomer.dto';
import { CustomerOpeningBalanceEditDto } from '../dtos/CustomerOpeningBalanceEdit.dto';
import { EditCustomerDto } from '../dtos/EditCustomer.dto'; import { EditCustomerDto } from '../dtos/EditCustomer.dto';
// Customer Interfaces. // Customer Interfaces.
@@ -114,16 +113,23 @@ export enum VendorAction {
View = 'View', View = 'View',
} }
export interface ICustomerOpeningBalanceEditDTO {
openingBalance: number;
openingBalanceAt: Date | string;
openingBalanceExchangeRate: number;
openingBalanceBranchId?: number;
}
export interface ICustomerOpeningBalanceEditingPayload { export interface ICustomerOpeningBalanceEditingPayload {
oldCustomer: Customer; oldCustomer: Customer;
openingBalanceEditDTO: CustomerOpeningBalanceEditDto; openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO;
trx?: Knex.Transaction; trx?: Knex.Transaction;
} }
export interface ICustomerOpeningBalanceEditedPayload { export interface ICustomerOpeningBalanceEditedPayload {
customer: Customer; customer: Customer;
oldCustomer: Customer; oldCustomer: Customer;
openingBalanceEditDTO: CustomerOpeningBalanceEditDto; openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -0,0 +1,143 @@
import * as R from 'ramda';
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { InventoryCostLotTracker } from '../InventoryCost/models/InventoryCostLotTracker';
import { LedgerStorageService } from '../Ledger/LedgerStorage.service';
import { groupInventoryTransactionsByTypeId } from '../InventoryCost/utils';
import { Ledger } from '../Ledger/Ledger';
import { AccountNormal } from '@/interfaces/Account';
import { ILedgerEntry } from '../Ledger/types/Ledger.types';
import { increment } from '@/utils/increment';
@Injectable()
export class SaleReceiptCostGLEntries {
constructor(
private readonly ledgerStorage: LedgerStorageService,
@Inject(InventoryCostLotTracker.name)
private readonly inventoryCostLotTracker: TenantModelProxy<
typeof InventoryCostLotTracker
>,
) {}
/**
* Writes journal entries from sales receipts.
* @param {Date} startingDate - Starting date.
* @param {Knex.Transaction} trx - Transaction.
*/
public writeInventoryCostJournalEntries = async (
startingDate: Date,
trx?: Knex.Transaction,
): Promise<void> => {
const inventoryCostLotTrans = await this.inventoryCostLotTracker()
.query()
.where('direction', 'OUT')
.where('transaction_type', 'SaleReceipt')
.where('cost', '>', 0)
.modify('filterDateRange', startingDate)
.orderBy('date', 'ASC')
.withGraphFetched('receipt')
.withGraphFetched('item')
.withGraphFetched('itemEntry');
const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans);
await this.ledgerStorage.commit(ledger, trx);
};
/**
* Retrieves the inventory cost lots ledger.
*/
private getInventoryCostLotsLedger = (
inventoryCostLots: InventoryCostLotTracker[],
) => {
const inventoryTransactions =
groupInventoryTransactionsByTypeId(inventoryCostLots);
const entries = inventoryTransactions
.map(this.getSaleReceiptCostGLEntries)
.flat();
return new Ledger(entries);
};
/**
* Builds the common GL entry fields for a sale receipt cost.
*/
private getReceiptCostGLCommonEntry = (
inventoryCostLot: InventoryCostLotTracker,
) => {
return {
currencyCode: inventoryCostLot.receipt.currencyCode,
exchangeRate: inventoryCostLot.receipt.exchangeRate,
transactionType: inventoryCostLot.transactionType,
transactionId: inventoryCostLot.transactionId,
transactionNumber: inventoryCostLot.receipt.receiptNumber,
referenceNumber: inventoryCostLot.receipt.referenceNo,
date: inventoryCostLot.date,
indexGroup: 20,
costable: true,
createdAt: inventoryCostLot.createdAt,
debit: 0,
credit: 0,
branchId: inventoryCostLot.receipt.branchId,
};
};
/**
* Retrieves the inventory cost GL entry for a single lot.
*/
private getInventoryCostGLEntry = R.curry(
(
getIndexIncrement: () => number,
inventoryCostLot: InventoryCostLotTracker,
): ILedgerEntry[] => {
const commonEntry = this.getReceiptCostGLCommonEntry(inventoryCostLot);
const costAccountId =
inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId;
const description = inventoryCostLot.itemEntry?.description || null;
const costEntry = {
...commonEntry,
debit: inventoryCostLot.cost,
accountId: costAccountId,
accountNormal: AccountNormal.DEBIT,
itemId: inventoryCostLot.itemId,
note: description,
index: getIndexIncrement(),
};
const inventoryEntry = {
...commonEntry,
credit: inventoryCostLot.cost,
accountId: inventoryCostLot.item.inventoryAccountId,
accountNormal: AccountNormal.DEBIT,
itemId: inventoryCostLot.itemId,
note: description,
index: getIndexIncrement(),
};
return [costEntry, inventoryEntry];
},
);
/**
* Builds GL entries for a group of sale receipt cost lots.
* - Cost of goods sold -> Debit
* - Inventory assets -> Credit
*/
public getSaleReceiptCostGLEntries = (
inventoryCostLots: InventoryCostLotTracker[],
): ILedgerEntry[] => {
const getIndexIncrement = increment(0);
const getInventoryLotEntry =
this.getInventoryCostGLEntry(getIndexIncrement);
return inventoryCostLots.map((t) => getInventoryLotEntry(t)).flat();
};
}

View File

@@ -40,6 +40,8 @@ import { SaleReceiptsImportable } from './commands/SaleReceiptsImportable';
import { GetSaleReceiptMailStateService } from './queries/GetSaleReceiptMailState.service'; import { GetSaleReceiptMailStateService } from './queries/GetSaleReceiptMailState.service';
import { GetSaleReceiptMailTemplateService } from './queries/GetSaleReceiptMailTemplate.service'; import { GetSaleReceiptMailTemplateService } from './queries/GetSaleReceiptMailTemplate.service';
import { SaleReceiptAutoIncrementSubscriber } from './subscribers/SaleReceiptAutoIncrementSubscriber'; import { SaleReceiptAutoIncrementSubscriber } from './subscribers/SaleReceiptAutoIncrementSubscriber';
import { SaleReceiptCostGLEntriesSubscriber } from './subscribers/SaleReceiptCostGLEntriesSubscriber';
import { SaleReceiptCostGLEntries } from './SaleReceiptCostGLEntries';
import { BulkDeleteSaleReceiptsService } from './BulkDeleteSaleReceipts.service'; import { BulkDeleteSaleReceiptsService } from './BulkDeleteSaleReceipts.service';
import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleReceipts.service'; import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleReceipts.service';
@@ -87,6 +89,8 @@ import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleR
GetSaleReceiptMailStateService, GetSaleReceiptMailStateService,
GetSaleReceiptMailTemplateService, GetSaleReceiptMailTemplateService,
SaleReceiptAutoIncrementSubscriber, SaleReceiptAutoIncrementSubscriber,
SaleReceiptCostGLEntries,
SaleReceiptCostGLEntriesSubscriber,
BulkDeleteSaleReceiptsService, BulkDeleteSaleReceiptsService,
ValidateBulkDeleteSaleReceiptsService, ValidateBulkDeleteSaleReceiptsService,
], ],

View File

@@ -1,148 +0,0 @@
// import { Service, Inject } from 'typedi';
// import * as R from 'ramda';
// import { Knex } from 'knex';
// import { AccountNormal, IInventoryLotCost, ILedgerEntry } from '@/interfaces';
// import { increment } from 'utils';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import Ledger from '@/services/Accounting/Ledger';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import { groupInventoryTransactionsByTypeId } from '../../Inventory/utils';
// @Service()
// export class SaleReceiptCostGLEntries {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledgerStorage: LedgerStorageService;
// /**
// * Writes journal entries from sales invoices.
// * @param {number} tenantId - The tenant id.
// * @param {Date} startingDate - Starting date.
// * @param {boolean} override
// */
// public writeInventoryCostJournalEntries = async (
// tenantId: number,
// startingDate: Date,
// trx?: Knex.Transaction
// ): Promise<void> => {
// const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
// const inventoryCostLotTrans = await InventoryCostLotTracker.query()
// .where('direction', 'OUT')
// .where('transaction_type', 'SaleReceipt')
// .where('cost', '>', 0)
// .modify('filterDateRange', startingDate)
// .orderBy('date', 'ASC')
// .withGraphFetched('receipt')
// .withGraphFetched('item');
// const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans);
// // Commit the ledger to the storage.
// await this.ledgerStorage.commit(tenantId, ledger, trx);
// };
// /**
// * Retrieves the inventory cost lots ledger.
// * @param {} inventoryCostLots
// * @returns {Ledger}
// */
// private getInventoryCostLotsLedger = (
// inventoryCostLots: IInventoryLotCost[]
// ) => {
// // Groups the inventory cost lots transactions.
// const inventoryTransactions =
// groupInventoryTransactionsByTypeId(inventoryCostLots);
// //
// const entries = inventoryTransactions
// .map(this.getSaleInvoiceCostGLEntries)
// .flat();
// return new Ledger(entries);
// };
// /**
// *
// * @param {IInventoryLotCost} inventoryCostLot
// * @returns {}
// */
// private getInvoiceCostGLCommonEntry = (
// inventoryCostLot: IInventoryLotCost
// ) => {
// return {
// currencyCode: inventoryCostLot.receipt.currencyCode,
// exchangeRate: inventoryCostLot.receipt.exchangeRate,
// transactionType: inventoryCostLot.transactionType,
// transactionId: inventoryCostLot.transactionId,
// date: inventoryCostLot.date,
// indexGroup: 20,
// costable: true,
// createdAt: inventoryCostLot.createdAt,
// debit: 0,
// credit: 0,
// branchId: inventoryCostLot.receipt.branchId,
// };
// };
// /**
// * Retrieves the inventory cost GL entry.
// * @param {IInventoryLotCost} inventoryLotCost
// * @returns {ILedgerEntry[]}
// */
// private getInventoryCostGLEntry = R.curry(
// (
// getIndexIncrement,
// inventoryCostLot: IInventoryLotCost
// ): ILedgerEntry[] => {
// const commonEntry = this.getInvoiceCostGLCommonEntry(inventoryCostLot);
// const costAccountId =
// inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId;
// // XXX Debit - Cost account.
// const costEntry = {
// ...commonEntry,
// debit: inventoryCostLot.cost,
// accountId: costAccountId,
// accountNormal: AccountNormal.DEBIT,
// itemId: inventoryCostLot.itemId,
// index: getIndexIncrement(),
// };
// // XXX Credit - Inventory account.
// const inventoryEntry = {
// ...commonEntry,
// credit: inventoryCostLot.cost,
// accountId: inventoryCostLot.item.inventoryAccountId,
// accountNormal: AccountNormal.DEBIT,
// itemId: inventoryCostLot.itemId,
// index: getIndexIncrement(),
// };
// return [costEntry, inventoryEntry];
// }
// );
// /**
// * Writes journal entries for given sale invoice.
// * -------
// * - Cost of goods sold -> Debit -> YYYY
// * - Inventory assets -> Credit -> YYYY
// * --------
// * @param {ISaleInvoice} saleInvoice
// * @param {JournalPoster} journal
// */
// public getSaleInvoiceCostGLEntries = (
// inventoryCostLots: IInventoryLotCost[]
// ): ILedgerEntry[] => {
// const getIndexIncrement = increment(0);
// const getInventoryLotEntry =
// this.getInventoryCostGLEntry(getIndexIncrement);
// return inventoryCostLots.map(getInventoryLotEntry).flat();
// };
// }

View File

@@ -1,36 +1,26 @@
// import { Inject, Service } from 'typedi'; import { Injectable } from '@nestjs/common';
// import events from '@/subscribers/events'; import { OnEvent } from '@nestjs/event-emitter';
// import { IInventoryCostLotsGLEntriesWriteEvent } from '@/interfaces'; import { events } from '@/common/events/events';
// import { SaleReceiptCostGLEntries } from '../SaleReceiptCostGLEntries'; import { IInventoryCostLotsGLEntriesWriteEvent } from '@/modules/InventoryCost/types/InventoryCost.types';
import { SaleReceiptCostGLEntries } from '../SaleReceiptCostGLEntries';
// @Service() @Injectable()
// export class SaleReceiptCostGLEntriesSubscriber { export class SaleReceiptCostGLEntriesSubscriber {
// @Inject() constructor(
// private saleReceiptCostEntries: SaleReceiptCostGLEntries; private readonly saleReceiptCostEntries: SaleReceiptCostGLEntries,
) {}
// /** /**
// * Attaches events. * Writes the receipts cost GL entries once the inventory cost lots are written.
// */ */
// public attach(bus) { @OnEvent(events.inventory.onCostLotsGLEntriesWrite)
// bus.subscribe( async writeReceiptsCostEntriesOnCostLotsWritten({
// events.inventory.onCostLotsGLEntriesWrite, trx,
// this.writeJournalEntriesOnceWriteoffCreate startingDate,
// ); }: IInventoryCostLotsGLEntriesWriteEvent) {
// } await this.saleReceiptCostEntries.writeInventoryCostJournalEntries(
startingDate,
// /** trx,
// * Writes the receipts cost GL entries once the inventory cost lots be written. );
// * @param {IInventoryCostLotsGLEntriesWriteEvent} }
// */ }
// private writeJournalEntriesOnceWriteoffCreate = async ({
// trx,
// startingDate,
// tenantId,
// }: IInventoryCostLotsGLEntriesWriteEvent) => {
// await this.saleReceiptCostEntries.writeInventoryCostJournalEntries(
// tenantId,
// startingDate,
// trx
// );
// };
// }

View File

@@ -1,116 +1,115 @@
import { Injectable } from '@nestjs/common'; // import { Service } from 'typedi';
import { AccountNormal } from '@/interfaces/Account'; // import { IVendor, AccountNormal, ILedgerEntry } from '@/interfaces';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; // import Ledger from '@/services/Accounting/Ledger';
import { Ledger } from '@/modules/Ledger/Ledger';
import { Vendor } from './models/Vendor';
@Injectable() // @Service()
export class VendorGLEntries { // export class VendorGLEntries {
/** // /**
* Retrieves the opening balance GL common entry. // * Retrieves the opening balance GL common entry.
* @param {Vendor} vendor - // * @param {IVendor} vendor -
*/ // */
private getOpeningBalanceGLCommonEntry = (vendor: Vendor) => { // private getOpeningBalanceGLCommonEntry = (vendor: IVendor) => {
return { // return {
exchangeRate: vendor.openingBalanceExchangeRate, // exchangeRate: vendor.openingBalanceExchangeRate,
currencyCode: vendor.currencyCode, // currencyCode: vendor.currencyCode,
transactionType: 'VendorOpeningBalance', // transactionType: 'VendorOpeningBalance',
transactionId: vendor.id, // transactionId: vendor.id,
date: vendor.openingBalanceAt, // date: vendor.openingBalanceAt,
contactId: vendor.id, // userId: vendor.userId,
// contactId: vendor.id,
credit: 0, // credit: 0,
debit: 0, // debit: 0,
branchId: vendor.openingBalanceBranchId, // branchId: vendor.openingBalanceBranchId,
}; // };
}; // };
/** // /**
* Retrieves the opening balance GL debit entry. // * Retrieves the opening balance GL debit entry.
* @param {number} costAccountId - // * @param {number} costAccountId -
* @param {Vendor} vendor // * @param {IVendor} vendor
* @returns {ILedgerEntry} // * @returns {ILedgerEntry}
*/ // */
private getOpeningBalanceGLDebitEntry = ( // private getOpeningBalanceGLDebitEntry = (
costAccountId: number, // costAccountId: number,
vendor: Vendor // vendor: IVendor
): ILedgerEntry => { // ): ILedgerEntry => {
const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor); // const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor);
return { // return {
...commonEntry, // ...commonEntry,
accountId: costAccountId, // accountId: costAccountId,
accountNormal: AccountNormal.DEBIT, // accountNormal: AccountNormal.DEBIT,
debit: vendor.localOpeningBalance, // debit: vendor.localOpeningBalance,
credit: 0, // credit: 0,
index: 2, // index: 2,
}; // };
}; // };
/** // /**
* Retrieves the opening balance GL credit entry. // * Retrieves the opening balance GL credit entry.
* @param {number} APAccountId // * @param {number} APAccountId
* @param {Vendor} vendor // * @param {IVendor} vendor
* @returns {ILedgerEntry} // * @returns {ILedgerEntry}
*/ // */
private getOpeningBalanceGLCreditEntry = ( // private getOpeningBalanceGLCreditEntry = (
APAccountId: number, // APAccountId: number,
vendor: Vendor // vendor: IVendor
): ILedgerEntry => { // ): ILedgerEntry => {
const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor); // const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor);
return { // return {
...commonEntry, // ...commonEntry,
accountId: APAccountId, // accountId: APAccountId,
accountNormal: AccountNormal.CREDIT, // accountNormal: AccountNormal.CREDIT,
credit: vendor.localOpeningBalance, // credit: vendor.localOpeningBalance,
index: 1, // index: 1,
}; // };
}; // };
/** // /**
* Retrieves the opening balance GL entries. // * Retrieves the opening balance GL entries.
* @param {number} APAccountId // * @param {number} APAccountId
* @param {number} costAccountId - // * @param {number} costAccountId -
* @param {Vendor} vendor // * @param {IVendor} vendor
* @returns {ILedgerEntry[]} // * @returns {ILedgerEntry[]}
*/ // */
public getOpeningBalanceGLEntries = ( // public getOpeningBalanceGLEntries = (
APAccountId: number, // APAccountId: number,
costAccountId: number, // costAccountId: number,
vendor: Vendor // vendor: IVendor
): ILedgerEntry[] => { // ): ILedgerEntry[] => {
const debitEntry = this.getOpeningBalanceGLDebitEntry( // const debitEntry = this.getOpeningBalanceGLDebitEntry(
costAccountId, // costAccountId,
vendor // vendor
); // );
const creditEntry = this.getOpeningBalanceGLCreditEntry( // const creditEntry = this.getOpeningBalanceGLCreditEntry(
APAccountId, // APAccountId,
vendor // vendor
); // );
return [debitEntry, creditEntry]; // return [debitEntry, creditEntry];
}; // };
/** // /**
* Retrieves the opening balance ledger. // * Retrieves the opening balance ledger.
* @param {number} APAccountId // * @param {number} APAccountId
* @param {number} costAccountId - // * @param {number} costAccountId -
* @param {Vendor} vendor // * @param {IVendor} vendor
* @returns {Ledger} // * @returns {Ledger}
*/ // */
public getOpeningBalanceLedger = ( // public getOpeningBalanceLedger = (
APAccountId: number, // APAccountId: number,
costAccountId: number, // costAccountId: number,
vendor: Vendor // vendor: IVendor
) => { // ) => {
const entries = this.getOpeningBalanceGLEntries( // const entries = this.getOpeningBalanceGLEntries(
APAccountId, // APAccountId,
costAccountId, // costAccountId,
vendor // vendor
); // );
return new Ledger(entries); // return new Ledger(entries);
}; // };
} // }

View File

@@ -1,86 +1,88 @@
import { Knex } from 'knex'; // import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common'; // import { Service, Inject } from 'typedi';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service'; // import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository'; // import HasTenancyService from '@/services/Tenancy/TenancyService';
import { VendorGLEntries } from './VendorGLEntries'; // import { VendorGLEntries } from './VendorGLEntries';
import { Vendor } from './models/Vendor';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() // @Service()
export class VendorGLEntriesStorage { // export class VendorGLEntriesStorage {
constructor( // @Inject()
private readonly ledgerStorage: LedgerStorageService, // private tenancy: HasTenancyService;
private readonly accountRepository: AccountRepository,
private readonly vendorGLEntries: VendorGLEntries,
@Inject(Vendor.name) // @Inject()
private readonly vendorModel: TenantModelProxy<typeof Vendor>, // private ledegrRepository: LedgerStorageService;
) { }
/** // @Inject()
* Vendor opening balance journals. // private vendorGLEntries: VendorGLEntries;
* @param {number} vendorId
* @param {Knex.Transaction} trx
*/
public writeVendorOpeningBalance = async (
vendorId: number,
trx?: Knex.Transaction,
) => {
const vendor = await this.vendorModel()
.query(trx)
.findById(vendorId);
// Finds the expense account. // /**
const expenseAccount = await this.accountRepository.findOrCreateOtherExpensesAccount( // * Vendor opening balance journals.
{}, // * @param {number} tenantId
trx, // * @param {number} vendorId
); // * @param {Knex.Transaction} trx
// Find or create the A/P account. // */
const APAccount = // public writeVendorOpeningBalance = async (
await this.accountRepository.findOrCreateAccountsPayable( // tenantId: number,
vendor.currencyCode, // vendorId: number,
{}, // trx?: Knex.Transaction
trx, // ) => {
); // const { Vendor } = this.tenancy.models(tenantId);
// Retrieves the vendor opening balance ledger. // const { accountRepository } = this.tenancy.repositories(tenantId);
const ledger = this.vendorGLEntries.getOpeningBalanceLedger(
APAccount.id,
expenseAccount.id,
vendor,
);
// Commits the ledger entries to the storage.
await this.ledgerStorage.commit(ledger, trx);
};
/** // const vendor = await Vendor.query(trx).findById(vendorId);
* Reverts the vendor opening balance GL entries.
* @param {number} vendorId
* @param {Knex.Transaction} trx
*/
public revertVendorOpeningBalance = async (
vendorId: number,
trx?: Knex.Transaction,
) => {
await this.ledgerStorage.deleteByReference(
vendorId,
'VendorOpeningBalance',
trx,
);
};
/** // // Finds the expense account.
* Writes the vendor opening balance GL entries. // const expenseAccount = await accountRepository.findOne({
* @param {number} vendorId // slug: 'other-expenses',
* @param {Knex.Transaction} trx // });
*/ // // Find or create the A/P account.
public rewriteVendorOpeningBalance = async ( // const APAccount = await accountRepository.findOrCreateAccountsPayable(
vendorId: number, // vendor.currencyCode,
trx?: Knex.Transaction, // {},
) => { // trx
// Reverts the vendor opening balance entries first. // );
await this.revertVendorOpeningBalance(vendorId, trx); // // Retrieves the vendor opening balance ledger.
// const ledger = this.vendorGLEntries.getOpeningBalanceLedger(
// APAccount.id,
// expenseAccount.id,
// vendor
// );
// // Commits the ledger entries to the storage.
// await this.ledegrRepository.commit(tenantId, ledger, trx);
// };
// Write the vendor opening balance entries. // /**
await this.writeVendorOpeningBalance(vendorId, trx); // * Reverts the vendor opening balance GL entries.
}; // * @param {number} tenantId
} // * @param {number} vendorId
// * @param {Knex.Transaction} trx
// */
// public revertVendorOpeningBalance = async (
// tenantId: number,
// vendorId: number,
// trx?: Knex.Transaction
// ) => {
// await this.ledegrRepository.deleteByReference(
// tenantId,
// vendorId,
// 'VendorOpeningBalance',
// trx
// );
// };
// /**
// * Writes the vendor opening balance GL entries.
// * @param {number} tenantId
// * @param {number} vendorId
// * @param {Knex.Transaction} trx
// */
// public rewriteVendorOpeningBalance = async (
// tenantId: number,
// vendorId: number,
// trx?: Knex.Transaction
// ) => {
// await this.writeVendorOpeningBalance(tenantId, vendorId, trx);
// await this.revertVendorOpeningBalance(tenantId, vendorId, trx);
// };
// }

View File

@@ -9,7 +9,10 @@ import {
Query, Query,
} from '@nestjs/common'; } from '@nestjs/common';
import { VendorsApplication } from './VendorsApplication.service'; import { VendorsApplication } from './VendorsApplication.service';
import { VendorOpeningBalanceEditDto } from './dtos/VendorOpeningBalanceEdit.dto'; import {
IVendorOpeningBalanceEditDTO,
IVendorsFilter,
} from './types/Vendors.types';
import { import {
ApiOperation, ApiOperation,
ApiResponse, ApiResponse,
@@ -65,7 +68,7 @@ export class VendorsController {
@ApiOperation({ summary: 'Edit the given vendor opening balance.' }) @ApiOperation({ summary: 'Edit the given vendor opening balance.' })
editOpeningBalance( editOpeningBalance(
@Param('id') vendorId: number, @Param('id') vendorId: number,
@Body() openingBalanceDTO: VendorOpeningBalanceEditDto, @Body() openingBalanceDTO: IVendorOpeningBalanceEditDTO,
) { ) {
return this.vendorsApplication.editOpeningBalance( return this.vendorsApplication.editOpeningBalance(
vendorId, vendorId,

View File

@@ -18,14 +18,9 @@ import { VendorsExportable } from './VendorsExportable';
import { VendorsImportable } from './VendorsImportable'; import { VendorsImportable } from './VendorsImportable';
import { BulkDeleteVendorsService } from './BulkDeleteVendors.service'; import { BulkDeleteVendorsService } from './BulkDeleteVendors.service';
import { ValidateBulkDeleteVendorsService } from './ValidateBulkDeleteVendors.service'; import { ValidateBulkDeleteVendorsService } from './ValidateBulkDeleteVendors.service';
import { LedgerModule } from '../Ledger/Ledger.module';
import { AccountsModule } from '../Accounts/Accounts.module';
import { VendorGLEntries } from './VendorGLEntries';
import { VendorGLEntriesStorage } from './VendorGLEntriesStorage';
import { VendorsWriteGLOpeningSubscriber } from './subscribers/VendorGLEntriesSubscriber';
@Module({ @Module({
imports: [TenancyDatabaseModule, DynamicListModule, LedgerModule, AccountsModule], imports: [TenancyDatabaseModule, DynamicListModule],
controllers: [VendorsController], controllers: [VendorsController],
providers: [ providers: [
ActivateVendorService, ActivateVendorService,
@@ -43,10 +38,7 @@ import { VendorsWriteGLOpeningSubscriber } from './subscribers/VendorGLEntriesSu
TransformerInjectable, TransformerInjectable,
TenancyContext, TenancyContext,
VendorsExportable, VendorsExportable,
VendorsImportable, VendorsImportable
VendorGLEntries,
VendorGLEntriesStorage,
VendorsWriteGLOpeningSubscriber,
], ],
}) })
export class VendorsModule { } export class VendorsModule {}

View File

@@ -5,7 +5,10 @@ import { EditVendorService } from './commands/EditVendor.service';
import { DeleteVendorService } from './commands/DeleteVendor.service'; import { DeleteVendorService } from './commands/DeleteVendor.service';
import { EditOpeningBalanceVendorService } from './commands/EditOpeningBalanceVendor.service'; import { EditOpeningBalanceVendorService } from './commands/EditOpeningBalanceVendor.service';
import { GetVendorService } from './queries/GetVendor'; import { GetVendorService } from './queries/GetVendor';
import { VendorOpeningBalanceEditDto } from './dtos/VendorOpeningBalanceEdit.dto'; import {
IVendorOpeningBalanceEditDTO,
IVendorsFilter,
} from './types/Vendors.types';
import { GetVendorsService } from './queries/GetVendors.service'; import { GetVendorsService } from './queries/GetVendors.service';
import { CreateVendorDto } from './dtos/CreateVendor.dto'; import { CreateVendorDto } from './dtos/CreateVendor.dto';
import { EditVendorDto } from './dtos/EditVendor.dto'; import { EditVendorDto } from './dtos/EditVendor.dto';
@@ -55,14 +58,14 @@ export class VendorsApplication {
} }
/** /**
* Changes the opening balance of the given vendor. * Changes the opening balance of the given customer.
* @param {number} vendorId * @param {number} vendorId
* @param {VendorOpeningBalanceEditDto} openingBalanceEditDTO * @param {IVendorOpeningBalanceEditDTO} openingBalanceEditDTO
* @returns {Promise<IVendor>} * @returns {Promise<IVendor>}
*/ */
public editOpeningBalance( public editOpeningBalance(
vendorId: number, vendorId: number,
openingBalanceEditDTO: VendorOpeningBalanceEditDto, openingBalanceEditDTO: IVendorOpeningBalanceEditDTO,
) { ) {
return this.editOpeningBalanceService.editOpeningBalance( return this.editOpeningBalanceService.editOpeningBalance(
vendorId, vendorId,
@@ -92,7 +95,10 @@ export class VendorsApplication {
vendorIds: number[], vendorIds: number[],
options?: { skipUndeletable?: boolean }, options?: { skipUndeletable?: boolean },
) { ) {
return this.bulkDeleteVendorsService.bulkDeleteVendors(vendorIds, options); return this.bulkDeleteVendorsService.bulkDeleteVendors(
vendorIds,
options,
);
} }
public validateBulkDeleteVendors(vendorIds: number[]) { public validateBulkDeleteVendors(vendorIds: number[]) {

View File

@@ -2,10 +2,10 @@ import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { import {
IVendorOpeningBalanceEditDTO,
IVendorOpeningBalanceEditedPayload, IVendorOpeningBalanceEditedPayload,
IVendorOpeningBalanceEditingPayload, IVendorOpeningBalanceEditingPayload,
} from '../types/Vendors.types'; } from '../types/Vendors.types';
import { VendorOpeningBalanceEditDto } from '../dtos/VendorOpeningBalanceEdit.dto';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Vendor } from '../models/Vendor'; import { Vendor } from '../models/Vendor';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
@@ -29,12 +29,12 @@ export class EditOpeningBalanceVendorService {
/** /**
* Changes the opening balance of the given customer. * Changes the opening balance of the given customer.
* @param {number} vendorId * @param {number} vendorId
* @param {VendorOpeningBalanceEditDto} openingBalanceEditDTO * @param {IVendorOpeningBalanceEditDTO} openingBalanceEditDTO
* @returns {Promise<IVendor>} * @returns {Promise<IVendor>}
*/ */
public async editOpeningBalance( public async editOpeningBalance(
vendorId: number, vendorId: number,
openingBalanceEditDTO: VendorOpeningBalanceEditDto, openingBalanceEditDTO: IVendorOpeningBalanceEditDTO,
) { ) {
// Retrieves the old vendor or throw not found error. // Retrieves the old vendor or throw not found error.
const oldVendor = await this.vendorModel() const oldVendor = await this.vendorModel()

View File

@@ -2,13 +2,11 @@ import { ApiProperty } from '@nestjs/swagger';
import { import {
IsISO8601, IsISO8601,
IsInt, IsInt,
IsNotEmpty,
IsNumber, IsNumber,
Min, Min,
IsBoolean, IsBoolean,
IsEmail, IsEmail,
IsString, IsString,
ValidateIf,
} from 'class-validator'; } from 'class-validator';
import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto'; import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto';
import { IsOptional, ToNumber } from '@/common/decorators/Validators'; import { IsOptional, ToNumber } from '@/common/decorators/Validators';
@@ -32,12 +30,8 @@ export class CreateVendorDto extends ContactAddressDto {
@ToNumber() @ToNumber()
openingBalanceExchangeRate?: number; openingBalanceExchangeRate?: number;
@ApiProperty({ @ApiProperty({ required: false, description: 'Date of the opening balance' })
required: false, @IsOptional()
description: 'Date of the opening balance (required when openingBalance is provided)',
})
@ValidateIf((o) => o.openingBalance != null)
@IsNotEmpty({ message: 'openingBalanceAt is required when openingBalance is provided' })
@IsISO8601() @IsISO8601()
openingBalanceAt?: Date; openingBalanceAt?: Date;

View File

@@ -1,44 +0,0 @@
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
export class VendorOpeningBalanceEditDto {
@ApiProperty({
required: true,
description: 'Opening balance',
example: 5000.0,
})
@IsNumber()
@IsNotEmpty()
@ToNumber()
openingBalance: number;
@ApiProperty({
required: false,
description: 'Opening balance date',
example: '2024-01-01',
})
@IsOptional()
@IsString()
openingBalanceAt?: string;
@ApiProperty({
required: false,
description: 'Opening balance exchange rate',
example: 1.0,
})
@IsOptional()
@IsNumber()
@ToNumber()
openingBalanceExchangeRate?: number;
@ApiProperty({
required: false,
description: 'Opening balance branch ID',
example: 101,
})
@IsOptional()
@IsNumber()
@ToNumber()
openingBalanceBranchId?: number;
}

View File

@@ -36,7 +36,6 @@ export class Vendor extends TenantBaseModel {
openingBalance: number; openingBalance: number;
openingBalanceExchangeRate: number; openingBalanceExchangeRate: number;
openingBalanceAt: Date | string; openingBalanceAt: Date | string;
openingBalanceBranchId?: number;
salutation: string; salutation: string;
firstName: string; firstName: string;

View File

@@ -1,71 +1,91 @@
import { Injectable } from '@nestjs/common'; // import { Inject, Service } from 'typedi';
import { OnEvent } from '@nestjs/event-emitter'; // import events from '@/subscribers/events';
import { events } from '@/common/events/events'; // import { VendorGLEntriesStorage } from '../VendorGLEntriesStorage';
import { VendorGLEntriesStorage } from '../VendorGLEntriesStorage'; // import {
import { // IVendorEventCreatedPayload,
IVendorEventCreatedPayload, // IVendorEventDeletedPayload,
IVendorEventDeletedPayload, // IVendorOpeningBalanceEditedPayload,
IVendorOpeningBalanceEditedPayload, // } from '@/interfaces';
} from '../types/Vendors.types';
@Injectable() // @Service()
export class VendorsWriteGLOpeningSubscriber { // export class VendorsWriteGLOpeningSubscriber {
constructor( // @Inject()
private readonly vendorGLEntriesStorage: VendorGLEntriesStorage, // private vendorGLEntriesStorage: VendorGLEntriesStorage;
) {}
/** // /**
* Writes the open balance journal entries once the vendor created. // * Constructor method.
* @param {IVendorEventCreatedPayload} payload - // */
*/ // public attach(bus) {
@OnEvent(events.vendors.onCreated) // bus.subscribe(
public async handleWriteOpeningBalanceEntries({ // events.vendors.onCreated,
vendor, // this.handleWriteOpeningBalanceEntries
trx, // );
}: IVendorEventCreatedPayload) { // bus.subscribe(
// Writes the vendor opening balance journal entries. // events.vendors.onDeleted,
if (vendor.openingBalance) { // this.handleRevertOpeningBalanceEntries
await this.vendorGLEntriesStorage.writeVendorOpeningBalance( // );
vendor.id, // bus.subscribe(
trx, // events.vendors.onOpeningBalanceChanged,
); // this.handleRewriteOpeningEntriesOnChanged
} // );
} // }
/** // /**
* Revert the opening balance journal entries once the vendor deleted. // * Writes the open balance journal entries once the vendor created.
* @param {IVendorEventDeletedPayload} payload - // * @param {IVendorEventCreatedPayload} payload -
*/ // */
@OnEvent(events.vendors.onDeleted) // private handleWriteOpeningBalanceEntries = async ({
public async handleRevertOpeningBalanceEntries({ // tenantId,
vendorId, // vendor,
trx, // trx,
}: IVendorEventDeletedPayload) { // }: IVendorEventCreatedPayload) => {
await this.vendorGLEntriesStorage.revertVendorOpeningBalance( // // Writes the vendor opening balance journal entries.
vendorId, // if (vendor.openingBalance) {
trx, // await this.vendorGLEntriesStorage.writeVendorOpeningBalance(
); // tenantId,
} // vendor.id,
// trx
// );
// }
// };
/** // /**
* Handles the rewrite opening balance entries once opening balance changed. // * Revert the opening balance journal entries once the vendor deleted.
* @param {IVendorOpeningBalanceEditedPayload} payload - // * @param {IVendorEventDeletedPayload} payload -
*/ // */
@OnEvent(events.vendors.onOpeningBalanceChanged) // private handleRevertOpeningBalanceEntries = async ({
public async handleRewriteOpeningEntriesOnChanged({ // tenantId,
vendor, // vendorId,
trx, // trx,
}: IVendorOpeningBalanceEditedPayload) { // }: IVendorEventDeletedPayload) => {
if (vendor.openingBalance) { // await this.vendorGLEntriesStorage.revertVendorOpeningBalance(
await this.vendorGLEntriesStorage.rewriteVendorOpeningBalance( // tenantId,
vendor.id, // vendorId,
trx, // trx
); // );
} else { // };
await this.vendorGLEntriesStorage.revertVendorOpeningBalance(
vendor.id, // /**
trx, // * Handles the rewrite opening balance entries once opening balnace changed.
); // * @param {ICustomerOpeningBalanceEditedPayload} payload -
} // */
} // private handleRewriteOpeningEntriesOnChanged = async ({
} // tenantId,
// vendor,
// trx,
// }: IVendorOpeningBalanceEditedPayload) => {
// if (vendor.openingBalance) {
// await this.vendorGLEntriesStorage.rewriteVendorOpeningBalance(
// tenantId,
// vendor.id,
// trx
// );
// } else {
// await this.vendorGLEntriesStorage.revertVendorOpeningBalance(
// tenantId,
// vendor.id,
// trx
// );
// }
// };
// }

View File

@@ -7,7 +7,6 @@ import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/Dynam
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { CreateVendorDto } from '../dtos/CreateVendor.dto'; import { CreateVendorDto } from '../dtos/CreateVendor.dto';
import { EditVendorDto } from '../dtos/EditVendor.dto'; import { EditVendorDto } from '../dtos/EditVendor.dto';
import { VendorOpeningBalanceEditDto } from '../dtos/VendorOpeningBalanceEdit.dto';
// ---------------------------------- // ----------------------------------
export interface IVendorNewDTO extends IContactAddressDTO { export interface IVendorNewDTO extends IContactAddressDTO {
@@ -93,16 +92,23 @@ export interface IVendorEventEditedPayload {
trx?: Knex.Transaction; trx?: Knex.Transaction;
} }
export interface IVendorOpeningBalanceEditDTO {
openingBalance: number;
openingBalanceAt: Date | string;
openingBalanceExchangeRate: number;
openingBalanceBranchId?: number;
}
export interface IVendorOpeningBalanceEditingPayload { export interface IVendorOpeningBalanceEditingPayload {
oldVendor: Vendor; oldVendor: Vendor;
openingBalanceEditDTO: VendorOpeningBalanceEditDto; openingBalanceEditDTO: IVendorOpeningBalanceEditDTO;
trx?: Knex.Transaction; trx?: Knex.Transaction;
} }
export interface IVendorOpeningBalanceEditedPayload { export interface IVendorOpeningBalanceEditedPayload {
vendor: Vendor; vendor: Vendor;
oldVendor: Vendor; oldVendor: Vendor;
openingBalanceEditDTO: VendorOpeningBalanceEditDto; openingBalanceEditDTO: IVendorOpeningBalanceEditDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }