feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,122 @@
import { Knex } from 'knex';
import {
IPaymentReceivedCreateDTO,
IPaymentReceivedCreatedPayload,
IPaymentReceivedCreatingPayload,
} from '../types/PaymentReceived.types';
import { PaymentReceivedValidators } from './PaymentReceivedValidators.service';
import { PaymentReceiveDTOTransformer } from './PaymentReceivedDTOTransformer';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PaymentReceived } from '../models/PaymentReceived';
import { events } from '@/common/events/events';
import { Customer } from '@/modules/Customers/models/Customer';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreatePaymentReceivedDto } from '../dtos/PaymentReceived.dto';
@Injectable()
export class CreatePaymentReceivedService {
constructor(
private validators: PaymentReceivedValidators,
private eventPublisher: EventEmitter2,
private uow: UnitOfWork,
private transformer: PaymentReceiveDTOTransformer,
private tenancyContext: TenancyContext,
@Inject(PaymentReceived.name)
private paymentReceived: TenantModelProxy<typeof PaymentReceived>,
@Inject(Customer.name)
private customer: TenantModelProxy<typeof Customer>,
) {}
/**
* Creates a new payment receive and store it to the storage
* with associated invoices payment and journal transactions.
* @param {IPaymentReceivedCreateDTO} paymentReceiveDTO - Payment receive create DTO.
* @param {Knex.Transaction} trx - Database transaction.
*/
public async createPaymentReceived(
paymentReceiveDTO: CreatePaymentReceivedDto,
trx?: Knex.Transaction,
) {
const tenant = await this.tenancyContext.getTenant(true);
// Validate customer existance.
const paymentCustomer = await this.customer()
.query()
.findById(paymentReceiveDTO.customerId)
.throwIfNotFound();
// Transformes the payment receive DTO to model.
const paymentReceiveObj = await this.transformCreateDTOToModel(
paymentCustomer,
paymentReceiveDTO,
);
// Validate payment receive number uniquiness.
await this.validators.validatePaymentReceiveNoExistance(
paymentReceiveObj.paymentReceiveNo,
);
// Validate the deposit account existance and type.
const depositAccount = await this.validators.getDepositAccountOrThrowError(
paymentReceiveDTO.depositAccountId,
);
// Validate payment receive invoices IDs existance.
await this.validators.validateInvoicesIDsExistance(
paymentReceiveDTO.customerId,
paymentReceiveDTO.entries,
);
// Validate invoice payment amount.
await this.validators.validateInvoicesPaymentsAmount(
paymentReceiveDTO.entries,
);
// Validates the payment account currency code.
this.validators.validatePaymentAccountCurrency(
depositAccount.currencyCode,
paymentCustomer.currencyCode,
tenant?.metadata.baseCurrency,
);
// Creates a payment receive transaction under UOW envirment.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onPaymentReceiveCreating` event.
await this.eventPublisher.emitAsync(events.paymentReceive.onCreating, {
trx,
paymentReceiveDTO,
} as IPaymentReceivedCreatingPayload);
// Inserts the payment receive transaction.
const paymentReceive = await this.paymentReceived()
.query(trx)
.insertGraphAndFetch({
...paymentReceiveObj,
});
// Triggers `onPaymentReceiveCreated` event.
await this.eventPublisher.emitAsync(events.paymentReceive.onCreated, {
paymentReceive,
paymentReceiveId: paymentReceive.id,
paymentReceiveDTO,
trx,
} as IPaymentReceivedCreatedPayload);
return paymentReceive;
}, trx);
}
/**
* Transform the create payment receive DTO.
* @param {ICustomer} customer
* @param {IPaymentReceivedCreateDTO} paymentReceiveDTO
* @returns
*/
private transformCreateDTOToModel = async (
customer: Customer,
paymentReceiveDTO: IPaymentReceivedCreateDTO,
) => {
return this.transformer.transformPaymentReceiveDTOToModel(
customer,
paymentReceiveDTO,
);
};
}

View File

@@ -0,0 +1,84 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { PaymentReceived } from '../models/PaymentReceived';
import { PaymentReceivedEntry } from '../models/PaymentReceivedEntry';
import {
IPaymentReceivedDeletingPayload,
IPaymentReceivedDeletedPayload,
} from '../types/PaymentReceived.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeletePaymentReceivedService {
/**
* @param {EventEmitter2} eventPublisher - Event emitter.
* @param {UnitOfWork} uow - Unit of work.
* @param {typeof PaymentReceived} paymentReceiveModel - Payment received model.
* @param {typeof PaymentReceivedEntry} paymentReceiveEntryModel - Payment received entry model.
*/
constructor(
private eventPublisher: EventEmitter2,
private uow: UnitOfWork,
@Inject(PaymentReceived.name)
private paymentReceiveModel: TenantModelProxy<typeof PaymentReceived>,
@Inject(PaymentReceivedEntry.name)
private paymentReceiveEntryModel: TenantModelProxy<
typeof PaymentReceivedEntry
>,
) {}
/**
* Deletes the given payment receive with associated entries
* and journal transactions.
* -----
* - Deletes the payment receive transaction.
* - Deletes the payment receive associated entries.
* - Deletes the payment receive associated journal transactions.
* - Revert the customer balance.
* - Revert the payment amount of the associated invoices.
* @async
* @param {Integer} paymentReceiveId - Payment receive id.
* @param {IPaymentReceived} paymentReceive - Payment receive object.
*/
public async deletePaymentReceive(paymentReceiveId: number) {
// Retreive payment receive or throw not found service error.
const oldPaymentReceive = await this.paymentReceiveModel()
.query()
.withGraphFetched('entries')
.findById(paymentReceiveId)
.throwIfNotFound();
// Delete payment receive transaction and associate transactions under UOW env.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onPaymentReceiveDeleting` event.
await this.eventPublisher.emitAsync(events.paymentReceive.onDeleting, {
oldPaymentReceive,
trx,
} as IPaymentReceivedDeletingPayload);
// Deletes the payment receive associated entries.
await this.paymentReceiveEntryModel()
.query(trx)
.where('payment_receive_id', paymentReceiveId)
.delete();
// Deletes the payment receive transaction.
await this.paymentReceiveModel()
.query(trx)
.findById(paymentReceiveId)
.delete();
// Triggers `onPaymentReceiveDeleted` event.
await this.eventPublisher.emitAsync(events.paymentReceive.onDeleted, {
paymentReceiveId,
oldPaymentReceive,
trx,
} as IPaymentReceivedDeletedPayload);
});
}
}

View File

@@ -0,0 +1,164 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
IPaymentReceivedEditDTO,
IPaymentReceivedEditedPayload,
IPaymentReceivedEditingPayload,
} from '../types/PaymentReceived.types';
import { PaymentReceiveDTOTransformer } from './PaymentReceivedDTOTransformer';
import { PaymentReceivedValidators } from './PaymentReceivedValidators.service';
import { PaymentReceived } from '../models/PaymentReceived';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { Customer } from '@/modules/Customers/models/Customer';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditPaymentReceivedDto } from '../dtos/PaymentReceived.dto';
@Injectable()
export class EditPaymentReceivedService {
constructor(
private readonly transformer: PaymentReceiveDTOTransformer,
private readonly validators: PaymentReceivedValidators,
private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly tenancyContext: TenancyContext,
@Inject(PaymentReceived.name)
private readonly paymentReceiveModel: TenantModelProxy<
typeof PaymentReceived
>,
@Inject(Customer.name)
private readonly customerModel: TenantModelProxy<typeof Customer>,
) {}
/**
* Edit details the given payment receive with associated entries.
* ------
* - Update the payment receive transactions.
* - Insert the new payment receive entries.
* - Update the given payment receive entries.
* - Delete the not presented payment receive entries.
* - Re-insert the journal transactions and update the different accounts balance.
* - Update the different customer balances.
* - Update the different invoice payment amount.
* @async
* @param {number} paymentReceiveId -
* @param {IPaymentReceivedEditDTO} paymentReceiveDTO -
*/
public async editPaymentReceive(
paymentReceiveId: number,
paymentReceiveDTO: EditPaymentReceivedDto,
) {
const tenant = await this.tenancyContext.getTenant(true);
// Validate the payment receive existance.
const oldPaymentReceive = await this.paymentReceiveModel()
.query()
.withGraphFetched('entries')
.findById(paymentReceiveId)
.throwIfNotFound();
// Validates the payment existance.
this.validators.validatePaymentExistance(oldPaymentReceive);
// Validate customer existance.
const customer = await this.customerModel()
.query()
.findById(paymentReceiveDTO.customerId)
.throwIfNotFound();
// Transformes the payment receive DTO to model.
const paymentReceiveObj = await this.transformEditDTOToModel(
customer,
paymentReceiveDTO,
oldPaymentReceive,
);
// Validate customer whether modified.
this.validators.validateCustomerNotModified(
paymentReceiveDTO,
oldPaymentReceive,
);
// Validate payment receive number uniquiness.
if (paymentReceiveDTO.paymentReceiveNo) {
await this.validators.validatePaymentReceiveNoExistance(
paymentReceiveDTO.paymentReceiveNo,
paymentReceiveId,
);
}
// Validate the deposit account existance and type.
const depositAccount = await this.validators.getDepositAccountOrThrowError(
paymentReceiveDTO.depositAccountId,
);
// Validate the entries ids existance on payment receive type.
await this.validators.validateEntriesIdsExistance(
paymentReceiveId,
paymentReceiveDTO.entries,
);
// Validate payment receive invoices IDs existance and associated
// to the given customer id.
await this.validators.validateInvoicesIDsExistance(
oldPaymentReceive.customerId,
paymentReceiveDTO.entries,
);
// Validate invoice payment amount.
await this.validators.validateInvoicesPaymentsAmount(
paymentReceiveDTO.entries,
oldPaymentReceive.entries,
);
// Validates the payment account currency code.
this.validators.validatePaymentAccountCurrency(
depositAccount.currencyCode,
customer.currencyCode,
tenant?.metadata.baseCurrency,
);
// Creates payment receive transaction under UOW envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onPaymentReceiveEditing` event.
await this.eventPublisher.emitAsync(events.paymentReceive.onEditing, {
trx,
oldPaymentReceive,
paymentReceiveDTO,
} as IPaymentReceivedEditingPayload);
// Update the payment receive transaction.
const paymentReceive = await this.paymentReceiveModel()
.query(trx)
.upsertGraphAndFetch({
id: paymentReceiveId,
...paymentReceiveObj,
});
// Triggers `onPaymentReceiveEdited` event.
await this.eventPublisher.emitAsync(events.paymentReceive.onEdited, {
paymentReceiveId,
paymentReceive,
oldPaymentReceive,
paymentReceiveDTO,
trx,
} as IPaymentReceivedEditedPayload);
return paymentReceive;
});
}
/**
* Transform the edit payment receive DTO.
* @param {ICustomer} customer
* @param {IPaymentReceivedEditDTO} paymentReceiveDTO
* @param {IPaymentReceived} oldPaymentReceive
* @returns
*/
private transformEditDTOToModel = async (
customer: Customer,
paymentReceiveDTO: EditPaymentReceivedDto,
oldPaymentReceive: PaymentReceived,
) => {
return this.transformer.transformPaymentReceiveDTOToModel(
customer,
paymentReceiveDTO,
oldPaymentReceive,
);
};
}

View File

@@ -0,0 +1,85 @@
import * as R from 'ramda';
import { Inject, Injectable } from '@nestjs/common';
import { omit, sumBy } from 'lodash';
import * as composeAsync from 'async/compose';
import {
IPaymentReceivedCreateDTO,
IPaymentReceivedEditDTO,
} from '../types/PaymentReceived.types';
import { PaymentReceivedValidators } from './PaymentReceivedValidators.service';
import { PaymentReceivedIncrement } from './PaymentReceivedIncrement.service';
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { BrandingTemplateDTOTransformer } from '@/modules/PdfTemplate/BrandingTemplateDTOTransformer';
import { PaymentReceived } from '../models/PaymentReceived';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { Customer } from '@/modules/Customers/models/Customer';
import { formatDateFields } from '@/utils/format-date-fields';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class PaymentReceiveDTOTransformer {
constructor(
private readonly validators: PaymentReceivedValidators,
private readonly increments: PaymentReceivedIncrement,
private readonly branchDTOTransform: BranchTransactionDTOTransformer,
private readonly brandingTemplatesTransformer: BrandingTemplateDTOTransformer,
@Inject(PaymentReceived.name)
private readonly paymentReceivedModel: TenantModelProxy<
typeof PaymentReceived
>,
) {}
/**
* Transformes the create payment receive DTO to model object.
* @param {IPaymentReceivedCreateDTO|IPaymentReceivedEditDTO} paymentReceiveDTO - Payment receive DTO.
* @param {IPaymentReceived} oldPaymentReceive -
* @return {IPaymentReceived}
*/
public async transformPaymentReceiveDTOToModel(
customer: Customer,
paymentReceiveDTO: IPaymentReceivedCreateDTO | IPaymentReceivedEditDTO,
oldPaymentReceive?: PaymentReceived,
): Promise<PaymentReceived> {
const amount =
paymentReceiveDTO.amount ??
sumBy(paymentReceiveDTO.entries, 'paymentAmount');
// Retreive the next invoice number.
const autoNextNumber = await this.increments.getNextPaymentReceiveNumber();
// Retrieve the next payment receive number.
const paymentReceiveNo =
paymentReceiveDTO.paymentReceiveNo ||
oldPaymentReceive?.paymentReceiveNo ||
autoNextNumber;
this.validators.validatePaymentNoRequire(paymentReceiveNo);
const entries = R.compose(
// Associate the default index to each item entry line.
assocItemEntriesDefaultIndex,
)(paymentReceiveDTO.entries);
const initialDTO = {
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
'paymentDate',
]),
amount,
currencyCode: customer.currencyCode,
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
entries,
};
const asyncDto = await composeAsync(
this.branchDTOTransform.transformDTO<PaymentReceived>,
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
'SaleInvoice',
),
)(initialDTO);
return asyncDto;
}
}

View File

@@ -0,0 +1,196 @@
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { PaymentReceived } from '../models/PaymentReceived';
import { sumBy } from 'lodash';
import { AccountNormal } from '@/interfaces/Account';
import { Ledger } from '@/modules/Ledger/Ledger';
export class PaymentReceivedGL {
private readonly paymentReceived: PaymentReceived;
private ARAccountId: number;
private exchangeGainOrLossAccountId: number;
private baseCurrencyCode: string;
/**
* Constructor method.
* @param {PaymentReceived} paymentReceived - Payment received.
*/
constructor(paymentReceived: PaymentReceived) {
this.paymentReceived = paymentReceived;
}
/**
* Sets the A/R account ID.
* @param {number} ARAccountId - A/R account ID.
* @returns {PaymentReceivedGL}
*/
setARAccountId(ARAccountId: number) {
this.ARAccountId = ARAccountId;
return this;
}
/**
* Sets the exchange gain/loss account ID.
* @param {number} exchangeGainOrLossAccountId - Exchange gain/loss account ID.
* @returns {PaymentReceivedGL}
*/
setExchangeGainOrLossAccountId(exchangeGainOrLossAccountId: number) {
this.exchangeGainOrLossAccountId = exchangeGainOrLossAccountId;
return this;
}
/**
* Sets the base currency code.
* @param {string} baseCurrencyCode - Base currency code.
* @returns {PaymentReceivedGL}
*/
setBaseCurrencyCode(baseCurrencyCode: string) {
this.baseCurrencyCode = baseCurrencyCode;
return this;
}
/**
* Calculates the payment total exchange gain/loss.
* @param {IBillPayment} paymentReceive - Payment receive with entries.
* @returns {number}
*/
private paymentExGainOrLoss = (): number => {
return sumBy(this.paymentReceived.entries, (entry) => {
const paymentLocalAmount =
entry.paymentAmount * this.paymentReceived.exchangeRate;
const invoicePayment = entry.paymentAmount * entry.invoice.exchangeRate;
return paymentLocalAmount - invoicePayment;
});
};
/**
* Retrieves the common entry of payment receive.
*/
private get paymentReceiveCommonEntry() {
return {
debit: 0,
credit: 0,
currencyCode: this.paymentReceived.currencyCode,
exchangeRate: this.paymentReceived.exchangeRate,
transactionId: this.paymentReceived.id,
transactionType: 'PaymentReceive',
transactionNumber: this.paymentReceived.paymentReceiveNo,
referenceNumber: this.paymentReceived.referenceNo,
date: this.paymentReceived.paymentDate,
userId: this.paymentReceived.userId,
createdAt: this.paymentReceived.createdAt,
branchId: this.paymentReceived.branchId,
};
}
/**
* Retrieves the payment exchange gain/loss entry.
* @param {IPaymentReceived} paymentReceive -
* @param {number} ARAccountId -
* @param {number} exchangeGainOrLossAccountId -
* @param {string} baseCurrencyCode -
* @returns {ILedgerEntry[]}
*/
private get paymentExchangeGainLossEntry(): ILedgerEntry[] {
const commonJournal = this.paymentReceiveCommonEntry;
const gainOrLoss = this.paymentExGainOrLoss();
const absGainOrLoss = Math.abs(gainOrLoss);
return gainOrLoss
? [
{
...commonJournal,
currencyCode: this.baseCurrencyCode,
exchangeRate: 1,
debit: gainOrLoss > 0 ? absGainOrLoss : 0,
credit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: this.ARAccountId,
contactId: this.paymentReceived.customerId,
index: 3,
accountNormal: AccountNormal.CREDIT,
},
{
...commonJournal,
currencyCode: this.baseCurrencyCode,
exchangeRate: 1,
credit: gainOrLoss > 0 ? absGainOrLoss : 0,
debit: gainOrLoss < 0 ? absGainOrLoss : 0,
accountId: this.exchangeGainOrLossAccountId,
index: 3,
accountNormal: AccountNormal.DEBIT,
},
]
: [];
}
/**
* Retrieves the payment deposit GL entry.
* @param {IPaymentReceived} paymentReceive
* @returns {ILedgerEntry}
*/
private get paymentDepositGLEntry(): ILedgerEntry {
const commonJournal = this.paymentReceiveCommonEntry;
return {
...commonJournal,
debit: this.paymentReceived.localAmount,
accountId: this.paymentReceived.depositAccountId,
index: 2,
accountNormal: AccountNormal.DEBIT,
};
}
/**
* Retrieves the payment receivable entry.
* @param {IPaymentReceived} paymentReceive
* @param {number} ARAccountId
* @returns {ILedgerEntry}
*/
private get paymentReceivableEntry(): ILedgerEntry {
const commonJournal = this.paymentReceiveCommonEntry;
return {
...commonJournal,
credit: this.paymentReceived.localAmount,
contactId: this.paymentReceived.customerId,
accountId: this.ARAccountId,
index: 1,
accountNormal: AccountNormal.DEBIT,
};
}
/**
* Records payment receive journal transactions.
*
* Invoice payment journals.
* --------
* - Account receivable -> Debit
* - Payment account [current asset] -> Credit
* @returns {Promise<ILedgerEntry>}
*/
public GLEntries(): ILedgerEntry[] {
// Retrieve the payment deposit entry.
const paymentDepositEntry = this.paymentDepositGLEntry;
// Retrieves the A/R entry.
const receivableEntry = this.paymentReceivableEntry;
// Exchange gain/loss entries.
const gainLossEntries = this.paymentExchangeGainLossEntry;
return [paymentDepositEntry, receivableEntry, ...gainLossEntries];
}
/**
* Retrieves the payment receive ledger.
* @returns {Ledger}
*/
public getLedger = (): Ledger => {
return new Ledger(this.GLEntries());
};
}

View File

@@ -0,0 +1,114 @@
import { Knex } from 'knex';
import { PaymentReceivedGL } from './PaymentReceivedGL';
import { PaymentReceived } from '../models/PaymentReceived';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
import { Ledger } from '@/modules/Ledger/Ledger';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
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';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class PaymentReceivedGLEntries {
constructor(
private readonly ledgerStorage: LedgerStorageService,
private readonly accountRepository: AccountRepository,
private readonly tenancyContext: TenancyContext,
@Inject(PaymentReceived.name)
private readonly paymentReceivedModel: TenantModelProxy<
typeof PaymentReceived
>,
) {}
/**
* Writes payment GL entries to the storage.
* @param {number} paymentReceiveId - Payment received id.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public writePaymentGLEntries = async (
paymentReceiveId: number,
trx?: Knex.Transaction,
): Promise<void> => {
// Retrieves the given tenant metadata.
const tenantMeta = await this.tenancyContext.getTenantMetadata();
// Retrieves the payment receive with associated entries.
const paymentReceive = await this.paymentReceivedModel()
.query(trx)
.findById(paymentReceiveId)
.withGraphFetched('entries.invoice');
// Retrives the payment receive ledger.
const ledger = await this.getPaymentReceiveGLedger(
paymentReceive,
tenantMeta.baseCurrency,
);
// Commit the ledger entries to the storage.
await this.ledgerStorage.commit(ledger, trx);
};
/**
* Reverts the given payment receive GL entries.
* @param {number} paymentReceiveId - Payment received id.
* @param {Knex.Transaction} trx - Knex transaction.
*/
public revertPaymentGLEntries = async (
paymentReceiveId: number,
trx?: Knex.Transaction,
) => {
await this.ledgerStorage.deleteByReference(
paymentReceiveId,
'PaymentReceive',
trx,
);
};
/**
* Rewrites the given payment receive GL entries.
* @param {number} paymentReceiveId - Payment received id.
* @param {Knex.Transaction} trx - Knex transaction.
*/
public rewritePaymentGLEntries = async (
paymentReceiveId: number,
trx?: Knex.Transaction,
) => {
// Reverts the payment GL entries.
await this.revertPaymentGLEntries(paymentReceiveId, trx);
// Writes the payment GL entries.
await this.writePaymentGLEntries(paymentReceiveId, trx);
};
/**
* Retrieves the payment receive general ledger.
* @param {IPaymentReceived} paymentReceive - Payment received.
* @param {string} baseCurrencyCode - Base currency code.
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Ledger}
*/
public getPaymentReceiveGLedger = async (
paymentReceive: PaymentReceived,
baseCurrencyCode: string,
): Promise<Ledger> => {
// Retrieve the A/R account of the given currency.
const receivableAccount =
await this.accountRepository.findOrCreateAccountReceivable(
paymentReceive.currencyCode,
);
// 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)
.setBaseCurrencyCode(baseCurrencyCode);
return paymentReceivedGL.getLedger();
};
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
import { AutoIncrementOrdersService } from '@/modules/AutoIncrementOrders/AutoIncrementOrders.service';
@Injectable()
export class PaymentReceivedIncrement {
/**
* @param {AutoIncrementOrdersService} autoIncrementOrdersService - Auto increment orders service.
*/
constructor(
private readonly autoIncrementOrdersService: AutoIncrementOrdersService,
) {}
/**
* Retrieve the next unique payment receive number.
* @return {Promise<string>}
*/
public getNextPaymentReceiveNumber(): Promise<string> {
return this.autoIncrementOrdersService.getNextTransactionNumber(
'payment_receives',
);
}
/**
* Increment the payment receive next number.
*/
public incrementNextPaymentReceiveNumber() {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
'payment_receives',
);
}
}

View File

@@ -0,0 +1,47 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { SaleInvoice } from '../../SaleInvoices/models/SaleInvoice';
import { IPaymentReceivedEntryDTO } from '../types/PaymentReceived.types';
import { entriesAmountDiff } from '@/utils/entries-amount-diff';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class PaymentReceivedInvoiceSync {
constructor(
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
) {}
/**
* Saves difference changing between old and new invoice payment amount.
* @param {Array} paymentReceiveEntries
* @param {Array} newPaymentReceiveEntries
* @return {Promise<void>}
*/
public async saveChangeInvoicePaymentAmount(
newPaymentReceiveEntries: IPaymentReceivedEntryDTO[],
oldPaymentReceiveEntries?: IPaymentReceivedEntryDTO[],
trx?: Knex.Transaction,
): Promise<void> {
const opers: Promise<void>[] = [];
const diffEntries = entriesAmountDiff(
newPaymentReceiveEntries,
oldPaymentReceiveEntries,
'paymentAmount',
'invoiceId',
);
diffEntries.forEach((diffEntry: any) => {
if (diffEntry.paymentAmount === 0) {
return;
}
const oper = this.saleInvoiceModel().changePaymentAmount(
diffEntry.invoiceId,
diffEntry.paymentAmount,
trx,
);
opers.push(oper);
});
await Promise.all([...opers]);
}
}

View File

@@ -0,0 +1,188 @@
import { Queue } from 'bullmq';
import { InjectQueue } from '@nestjs/bullmq';
import { Inject, Injectable } from '@nestjs/common';
import {
DEFAULT_PAYMENT_MAIL_CONTENT,
DEFAULT_PAYMENT_MAIL_SUBJECT,
SEND_PAYMENT_RECEIVED_MAIL_JOB,
SEND_PAYMENT_RECEIVED_MAIL_QUEUE,
} from '../constants';
import { transformPaymentReceivedToMailDataArgs } from '../utils';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification';
import { PaymentReceived } from '../models/PaymentReceived';
import { GetPaymentReceivedService } from '../queries/GetPaymentReceived.service';
import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils';
import {
PaymentReceiveMailOptsDTO,
SendPaymentReceivedMailPayload,
} from '../types/PaymentReceived.types';
import { PaymentReceiveMailOpts } from '../types/PaymentReceived.types';
import { PaymentReceiveMailPresendEvent } from '../types/PaymentReceived.types';
import { SendInvoiceMailDTO } from '@/modules/SaleInvoices/SaleInvoice.types';
import { Mail } from '@/modules/Mail/Mail';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable()
export class SendPaymentReceiveMailNotification {
constructor(
private readonly getPaymentService: GetPaymentReceivedService,
private readonly contactMailNotification: ContactMailNotification,
private readonly eventEmitter: EventEmitter2,
private readonly mailTransport: MailTransporter,
private readonly tenancyContext: TenancyContext,
@InjectQueue(SEND_PAYMENT_RECEIVED_MAIL_QUEUE)
private readonly sendPaymentMailQueue: Queue,
@Inject(PaymentReceived.name)
private readonly paymentReceiveModel: TenantModelProxy<
typeof PaymentReceived
>,
) {}
/**
* Sends the mail of the given payment receive.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {PaymentReceiveMailOptsDTO} messageDTO
* @returns {Promise<void>}
*/
public async triggerMail(
paymentReceivedId: number,
messageOptions: PaymentReceiveMailOptsDTO,
): Promise<void> {
const tenant = await this.tenancyContext.getTenant();
const user = await this.tenancyContext.getSystemUser();
const organizationId = tenant.organizationId;
const userId = user.id;
const payload = {
paymentReceivedId,
messageOptions,
userId,
organizationId,
} as SendPaymentReceivedMailPayload;
await this.sendPaymentMailQueue.add(
SEND_PAYMENT_RECEIVED_MAIL_JOB,
payload,
);
// Triggers `onPaymentReceivePreMailSend` event.
await this.eventEmitter.emitAsync(events.paymentReceive.onPreMailSend, {
paymentReceivedId,
messageOptions,
} as PaymentReceiveMailPresendEvent);
}
/**
* Retrieves the default payment mail options.
* @param {number} paymentReceiveId - Payment receive id.
* @returns {Promise<PaymentReceiveMailOpts>}
*/
public getMailOptions = async (
paymentId: number,
): Promise<PaymentReceiveMailOpts> => {
const paymentReceive = await this.paymentReceiveModel()
.query()
.findById(paymentId)
.throwIfNotFound();
const formatArgs = await this.textFormatter(paymentId);
const mailOptions =
await this.contactMailNotification.getDefaultMailOptions(
paymentReceive.customerId,
);
return {
...mailOptions,
subject: DEFAULT_PAYMENT_MAIL_SUBJECT,
message: DEFAULT_PAYMENT_MAIL_CONTENT,
...formatArgs,
};
};
/**
* Retrieves the formatted text of the given sale invoice.
* @param {number} invoiceId - Sale invoice id.
* @returns {Promise<Record<string, string>>}
*/
public textFormatter = async (
invoiceId: number,
): Promise<Record<string, string>> => {
const payment = await this.getPaymentService.getPaymentReceive(invoiceId);
return transformPaymentReceivedToMailDataArgs(payment);
};
/**
* Retrieves the formatted mail options of the given payment receive.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {SendInvoiceMailDTO} messageDTO
* @returns {Promise<PaymentReceiveMailOpts>}
*/
public getFormattedMailOptions = async (
paymentReceiveId: number,
messageDTO: SendInvoiceMailDTO,
) => {
const formatterArgs = await this.textFormatter(paymentReceiveId);
// Default message options.
const defaultMessageOpts = await this.getMailOptions(paymentReceiveId);
// Parsed message opts with default options.
const parsedMessageOpts = mergeAndValidateMailOptions(
defaultMessageOpts,
messageDTO,
);
// Formats the message options.
return this.contactMailNotification.formatMailOptions(
parsedMessageOpts,
formatterArgs,
);
};
/**
* Triggers the mail invoice.
* @param {number} tenantId
* @param {number} saleInvoiceId - Invoice id.
* @param {SendInvoiceMailDTO} messageDTO - Message options.
* @returns {Promise<void>}
*/
public async sendMail(
paymentReceiveId: number,
messageDTO: PaymentReceiveMailOptsDTO,
): Promise<void> {
// Retrieves the formatted mail options.
const formattedMessageOptions = await this.getFormattedMailOptions(
paymentReceiveId,
messageDTO,
);
const mail = new Mail()
.setSubject(formattedMessageOptions.subject)
.setTo(formattedMessageOptions.to)
.setCC(formattedMessageOptions.cc)
.setBCC(formattedMessageOptions.bcc)
.setContent(formattedMessageOptions.message);
const eventPayload = {
paymentReceiveId,
messageOptions: formattedMessageOptions,
};
// Triggers `onPaymentReceiveMailSend` event.
await this.eventEmitter.emitAsync(
events.paymentReceive.onMailSend,
eventPayload,
);
await this.mailTransport.send(mail);
// Triggers `onPaymentReceiveMailSent` event.
await this.eventEmitter.emitAsync(
events.paymentReceive.onMailSent,
eventPayload,
);
}
}

View File

@@ -0,0 +1,32 @@
// import Container, { Service } from 'typedi';
// import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
// @Service()
// export class PaymentReceivedMailNotificationJob {
// /**
// * Constructor method.
// */
// constructor(agenda) {
// agenda.define(
// 'payment-receive-mail-send',
// { priority: 'high', concurrency: 2 },
// this.handler
// );
// }
// /**
// * Triggers sending payment notification via mail.
// */
// private handler = async (job, done: Function) => {
// const { tenantId, paymentReceiveId, messageDTO } = job.attrs.data;
// const paymentMail = Container.get(SendPaymentReceiveMailNotification);
// try {
// await paymentMail.sendMail(tenantId, paymentReceiveId, messageDTO);
// done();
// } catch (error) {
// console.log(error);
// done(error);
// }
// };
// }

View File

@@ -0,0 +1,213 @@
// import { Service, Inject } from 'typedi';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import events from '@/subscribers/events';
// import {
// IPaymentReceivedSmsDetails,
// SMS_NOTIFICATION_KEY,
// IPaymentReceived,
// IPaymentReceivedEntry,
// } from '@/interfaces';
// import SmsNotificationsSettingsService from '@/services/Settings/SmsNotificationsSettings';
// import { formatNumber, formatSmsMessage } from 'utils';
// import { TenantMetadata } from '@/system/models';
// import SaleNotifyBySms from '../SaleNotifyBySms';
// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
// import { PaymentReceivedValidators } from './commands/PaymentReceivedValidators.service';
// @Service()
// export class PaymentReceiveNotifyBySms {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private eventPublisher: EventPublisher;
// @Inject()
// private smsNotificationsSettings: SmsNotificationsSettingsService;
// @Inject()
// private saleSmsNotification: SaleNotifyBySms;
// @Inject()
// private validators: PaymentReceivedValidators;
// /**
// * Notify customer via sms about payment receive details.
// * @param {number} tenantId - Tenant id.
// * @param {number} paymentReceiveid - Payment receive id.
// */
// public async notifyBySms(tenantId: number, paymentReceiveid: number) {
// const { PaymentReceive } = this.tenancy.models(tenantId);
// // Retrieve the payment receive or throw not found service error.
// const paymentReceive = await PaymentReceive.query()
// .findById(paymentReceiveid)
// .withGraphFetched('customer')
// .withGraphFetched('entries.invoice');
// // Validates the payment existance.
// this.validators.validatePaymentExistance(paymentReceive);
// // Validate the customer phone number.
// this.saleSmsNotification.validateCustomerPhoneNumber(
// paymentReceive.customer.personalPhone
// );
// // Triggers `onPaymentReceiveNotifySms` event.
// await this.eventPublisher.emitAsync(events.paymentReceive.onNotifySms, {
// tenantId,
// paymentReceive,
// });
// // Sends the payment receive sms notification to the given customer.
// await this.sendSmsNotification(tenantId, paymentReceive);
// // Triggers `onPaymentReceiveNotifiedSms` event.
// await this.eventPublisher.emitAsync(events.paymentReceive.onNotifiedSms, {
// tenantId,
// paymentReceive,
// });
// return paymentReceive;
// }
// /**
// * Sends the payment details sms notification of the given customer.
// * @param {number} tenantId
// * @param {IPaymentReceived} paymentReceive
// * @param {ICustomer} customer
// */
// private sendSmsNotification = async (
// tenantId: number,
// paymentReceive: IPaymentReceived
// ) => {
// const smsClient = this.tenancy.smsClient(tenantId);
// const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
// // Retrieve the formatted payment details sms notification message.
// const message = this.formattedPaymentDetailsMessage(
// tenantId,
// paymentReceive,
// tenantMetadata
// );
// // The target phone number.
// const phoneNumber = paymentReceive.customer.personalPhone;
// await smsClient.sendMessageJob(phoneNumber, message);
// };
// /**
// * Notify via SMS message after payment transaction creation.
// * @param {number} tenantId
// * @param {number} paymentReceiveId
// * @returns {Promise<void>}
// */
// public notifyViaSmsNotificationAfterCreation = async (
// tenantId: number,
// paymentReceiveId: number
// ): Promise<void> => {
// const notification = this.smsNotificationsSettings.getSmsNotificationMeta(
// tenantId,
// SMS_NOTIFICATION_KEY.PAYMENT_RECEIVE_DETAILS
// );
// // Can't continue if the sms auto-notification is not enabled.
// if (!notification.isNotificationEnabled) return;
// await this.notifyBySms(tenantId, paymentReceiveId);
// };
// /**
// * Formates the payment receive details sms message.
// * @param {number} tenantId -
// * @param {IPaymentReceived} payment -
// * @param {ICustomer} customer -
// */
// private formattedPaymentDetailsMessage = (
// tenantId: number,
// payment: IPaymentReceived,
// tenantMetadata: TenantMetadata
// ) => {
// const notification = this.smsNotificationsSettings.getSmsNotificationMeta(
// tenantId,
// SMS_NOTIFICATION_KEY.PAYMENT_RECEIVE_DETAILS
// );
// return this.formatPaymentDetailsMessage(
// notification.smsMessage,
// payment,
// tenantMetadata
// );
// };
// /**
// * Formattes the payment details sms notification messafge.
// * @param {string} smsMessage
// * @param {IPaymentReceived} payment
// * @param {ICustomer} customer
// * @param {TenantMetadata} tenantMetadata
// * @returns {string}
// */
// private formatPaymentDetailsMessage = (
// smsMessage: string,
// payment: IPaymentReceived,
// tenantMetadata: any
// ): string => {
// const invoiceNumbers = this.stringifyPaymentInvoicesNumber(payment);
// // Formattes the payment number variable.
// const formattedPaymentNumber = formatNumber(payment.amount, {
// currencyCode: payment.currencyCode,
// });
// return formatSmsMessage(smsMessage, {
// Amount: formattedPaymentNumber,
// ReferenceNumber: payment.referenceNo,
// CustomerName: payment.customer.displayName,
// PaymentNumber: payment.paymentReceiveNo,
// InvoiceNumber: invoiceNumbers,
// CompanyName: tenantMetadata.name,
// });
// };
// /**
// * Stringify payment receive invoices to numbers as string.
// * @param {IPaymentReceived} payment
// * @returns {string}
// */
// private stringifyPaymentInvoicesNumber(payment: IPaymentReceived) {
// const invoicesNumberes = payment.entries.map(
// (entry: IPaymentReceivedEntry) => entry.invoice.invoiceNo
// );
// return invoicesNumberes.join(', ');
// }
// /**
// * Retrieve the SMS details of the given invoice.
// * @param {number} tenantId - Tenant id.
// * @param {number} paymentReceiveid - Payment receive id.
// */
// public smsDetails = async (
// tenantId: number,
// paymentReceiveid: number
// ): Promise<IPaymentReceivedSmsDetails> => {
// const { PaymentReceive } = this.tenancy.models(tenantId);
// // Retrieve the payment receive or throw not found service error.
// const paymentReceive = await PaymentReceive.query()
// .findById(paymentReceiveid)
// .withGraphFetched('customer')
// .withGraphFetched('entries.invoice');
// // Current tenant metadata.
// const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
// // Retrieve the formatted sms message of payment receive details.
// const smsMessage = this.formattedPaymentDetailsMessage(
// tenantId,
// paymentReceive,
// tenantMetadata
// );
// return {
// customerName: paymentReceive.customer.displayName,
// customerPhoneNumber: paymentReceive.customer.personalPhone,
// smsMessage,
// };
// };
// }

View File

@@ -0,0 +1,286 @@
import { Inject, Injectable } from '@nestjs/common';
import { difference, sumBy } from 'lodash';
import {
IPaymentReceivedEditDTO,
IPaymentReceivedEntryDTO,
} from '../types/PaymentReceived.types';
import { ERRORS } from '../constants';
import { PaymentReceived } from '../models/PaymentReceived';
import { PaymentReceivedEntry } from '../models/PaymentReceivedEntry';
import { Account } from '@/modules/Accounts/models/Account.model';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { ServiceError } from '@/modules/Items/ServiceError';
import { ACCOUNT_TYPE } from '@/constants/accounts';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditPaymentReceivedDto } from '../dtos/PaymentReceived.dto';
@Injectable()
export class PaymentReceivedValidators {
constructor(
@Inject(PaymentReceived.name)
private readonly paymentReceiveModel: TenantModelProxy<
typeof PaymentReceived
>,
@Inject(PaymentReceivedEntry.name)
private readonly paymentReceiveEntryModel: TenantModelProxy<
typeof PaymentReceivedEntry
>,
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
@Inject(Account.name)
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
* Validates the payment existance.
* @param {PaymentReceive | null | undefined} payment
*/
public validatePaymentExistance(payment: PaymentReceived | null | undefined) {
if (!payment) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
}
}
/**
* Validates the payment receive number existance.
* @param {number} tenantId -
* @param {string} paymentReceiveNo -
*/
public async validatePaymentReceiveNoExistance(
paymentReceiveNo: string,
notPaymentReceiveId?: number,
): Promise<void> {
const paymentReceive = await this.paymentReceiveModel()
.query()
.findOne('payment_receive_no', paymentReceiveNo)
.onBuild((builder) => {
if (notPaymentReceiveId) {
builder.whereNot('id', notPaymentReceiveId);
}
});
if (paymentReceive) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NO_EXISTS);
}
}
/**
* Validates the invoices IDs existance.
* @param {number} customerId -
* @param {IPaymentReceivedEntryDTO[]} paymentReceiveEntries -
*/
public async validateInvoicesIDsExistance(
customerId: number,
paymentReceiveEntries: { invoiceId: number }[],
): Promise<SaleInvoice[]> {
const invoicesIds = paymentReceiveEntries.map(
(e: { invoiceId: number }) => e.invoiceId,
);
const storedInvoices = await this.saleInvoiceModel()
.query()
.whereIn('id', invoicesIds)
.where('customer_id', customerId);
const storedInvoicesIds = storedInvoices.map((invoice) => invoice.id);
const notFoundInvoicesIDs = difference(invoicesIds, storedInvoicesIds);
if (notFoundInvoicesIDs.length > 0) {
throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND);
}
// Filters the not delivered invoices.
const notDeliveredInvoices = storedInvoices.filter(
(invoice) => !invoice.isDelivered,
);
if (notDeliveredInvoices.length > 0) {
throw new ServiceError(ERRORS.INVOICES_NOT_DELIVERED_YET, null, {
notDeliveredInvoices,
});
}
return storedInvoices;
}
/**
* Validates entries invoice payment amount.
* @param {IPaymentReceivedEntryDTO[]} paymentReceiveEntries
* @param {IPaymentReceivedEntry[]} oldPaymentEntries
*/
public async validateInvoicesPaymentsAmount(
paymentReceiveEntries: IPaymentReceivedEntryDTO[],
oldPaymentEntries: PaymentReceivedEntry[] = [],
) {
const invoicesIds = paymentReceiveEntries.map(
(e: IPaymentReceivedEntryDTO) => e.invoiceId,
);
const storedInvoices = await this.saleInvoiceModel()
.query()
.whereIn('id', invoicesIds);
const storedInvoicesMap = new Map(
storedInvoices.map((invoice: SaleInvoice) => {
const oldEntries = oldPaymentEntries.filter((entry) => entry.invoiceId);
const oldPaymentAmount = sumBy(oldEntries, 'paymentAmount') || 0;
return [
invoice.id,
{ ...invoice, dueAmount: invoice.dueAmount + oldPaymentAmount },
];
}),
);
const hasWrongPaymentAmount: any[] = [];
paymentReceiveEntries.forEach(
(entry: IPaymentReceivedEntryDTO, index: number) => {
const entryInvoice = storedInvoicesMap.get(entry.invoiceId);
const { dueAmount } = entryInvoice;
if (dueAmount < entry.paymentAmount) {
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
}
},
);
if (hasWrongPaymentAmount.length > 0) {
throw new ServiceError(ERRORS.INVALID_PAYMENT_AMOUNT);
}
}
/**
* Validate the payment receive number require.
* @param {IPaymentReceived} paymentReceiveObj
*/
public validatePaymentReceiveNoRequire(paymentReceiveObj: PaymentReceived) {
if (!paymentReceiveObj.paymentReceiveNo) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NO_IS_REQUIRED);
}
}
/**
* Validate the payment receive entries IDs existance.
* @param {number} paymentReceiveId
* @param {IPaymentReceivedEntryDTO[]} paymentReceiveEntries
*/
public async validateEntriesIdsExistance(
paymentReceiveId: number,
paymentReceiveEntries: IPaymentReceivedEntryDTO[],
) {
const entriesIds = paymentReceiveEntries
.filter((entry) => entry.id)
.map((entry) => entry.id);
const storedEntries = await this.paymentReceiveEntryModel()
.query()
.where('payment_receive_id', paymentReceiveId);
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
if (notFoundEntriesIds.length > 0) {
throw new ServiceError(ERRORS.ENTRIES_IDS_NOT_EXISTS);
}
}
/**
* Validates the payment receive number require.
* @param {string} paymentReceiveNo
*/
public validatePaymentNoRequire(paymentReceiveNo: string) {
if (!paymentReceiveNo) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NO_REQUIRED);
}
}
/**
* Validate the payment customer whether modified.
* @param {EditPaymentReceivedDto} paymentReceiveDTO
* @param {PaymentReceived} oldPaymentReceive
*/
public validateCustomerNotModified(
paymentReceiveDTO: EditPaymentReceivedDto,
oldPaymentReceive: PaymentReceived,
) {
if (paymentReceiveDTO.customerId !== oldPaymentReceive.customerId) {
throw new ServiceError(ERRORS.PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE);
}
}
/**
* Validates the payment account currency code. The deposit account curreny
* should be equals the customer currency code or the base currency.
* @param {string} paymentAccountCurrency
* @param {string} customerCurrency
* @param {string} baseCurrency
* @throws {ServiceError(ERRORS.PAYMENT_ACCOUNT_CURRENCY_INVALID)}
*/
public validatePaymentAccountCurrency = (
paymentAccountCurrency: string,
customerCurrency: string,
baseCurrency: string,
) => {
if (
paymentAccountCurrency !== customerCurrency &&
paymentAccountCurrency !== baseCurrency
) {
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_CURRENCY_INVALID);
}
};
/**
* Validates the payment receive existance.
* @param {number} paymentReceiveId - Payment receive id.
*/
async getPaymentReceiveOrThrowError(
paymentReceiveId: number,
): Promise<PaymentReceived> {
const paymentReceive = await this.paymentReceiveModel()
.query()
.withGraphFetched('entries')
.findById(paymentReceiveId);
if (!paymentReceive) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
}
return paymentReceive;
}
/**
* Validate the deposit account id existance.
* @param {number} depositAccountId - Deposit account id.
* @return {Promise<IAccount>}
*/
async getDepositAccountOrThrowError(
depositAccountId: number,
): Promise<Account> {
const depositAccount = await this.accountModel()
.query()
.findById(depositAccountId);
if (!depositAccount) {
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
}
// Detarmines whether the account is cash, bank or other current asset.
if (
!depositAccount.isAccountType([
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
])
) {
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_INVALID_TYPE);
}
return depositAccount;
}
/**
* Validate the given customer has no payments receives.
* @param {number} customerId - Customer id.
*/
public async validateCustomerHasNoPayments(customerId: number) {
const paymentReceives = await this.paymentReceiveModel()
.query()
.where('customer_id', customerId);
if (paymentReceives.length > 0) {
throw new ServiceError(ERRORS.CUSTOMER_HAS_PAYMENT_RECEIVES);
}
}
}

View File

@@ -0,0 +1,39 @@
// import { Inject, Service } from 'typedi';
// import { IAccountsStructureType, IPaymentsReceivedFilter } from '@/interfaces';
// import { Exportable } from '@/services/Export/Exportable';
// import { PaymentReceivesApplication } from './PaymentReceived.application';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
// @Service()
// export class PaymentsReceivedExportable extends Exportable {
// @Inject()
// private paymentReceivedApp: PaymentReceivesApplication;
// /**
// * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId
// * @param {IPaymentsReceivedFilter} query -
// * @returns
// */
// public exportable(tenantId: number, query: IPaymentsReceivedFilter) {
// const filterQuery = (builder) => {
// builder.withGraphFetched('entries.invoice');
// builder.withGraphFetched('branch');
// };
// const parsedQuery = {
// sortOrder: 'desc',
// columnSortBy: 'created_at',
// inactiveMode: false,
// ...query,
// structure: IAccountsStructureType.Flat,
// page: 1,
// pageSize: EXPORT_SIZE_LIMIT,
// filterQuery,
// } as IPaymentsReceivedFilter;
// return this.paymentReceivedApp
// .getPaymentReceives(tenantId, parsedQuery)
// .then((output) => output.paymentReceives);
// }
// }

View File

@@ -0,0 +1,46 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { IPaymentReceivedCreateDTO } from '@/interfaces';
// import { Importable } from '@/services/Import/Importable';
// import { CreatePaymentReceived } from './commands/CreatePaymentReceived.serivce';
// import { PaymentsReceiveSampleData } from './constants';
// @Service()
// export class PaymentsReceivedImportable extends Importable {
// @Inject()
// private createPaymentReceiveService: CreatePaymentReceived;
// /**
// * Importing to account service.
// * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO
// * @returns
// */
// public importable(
// tenantId: number,
// createPaymentDTO: IPaymentReceivedCreateDTO,
// trx?: Knex.Transaction
// ) {
// return this.createPaymentReceiveService.createPaymentReceived(
// tenantId,
// createPaymentDTO,
// {},
// trx
// );
// }
// /**
// * Concurrrency controlling of the importing process.
// * @returns {number}
// */
// public get concurrency() {
// return 1;
// }
// /**
// * Retrieves the sample data that used to download accounts sample sheet.
// */
// public sampleData(): any[] {
// return PaymentsReceiveSampleData;
// }
// }