mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
IPaymentReceivedCreateDTO,
|
||||
IPaymentReceivedEditDTO,
|
||||
IPaymentsReceivedFilter,
|
||||
PaymentReceiveMailOptsDTO,
|
||||
} from './types/PaymentReceived.types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreatePaymentReceivedService } from './commands/CreatePaymentReceived.serivce';
|
||||
import { EditPaymentReceivedService } from './commands/EditPaymentReceived.service';
|
||||
import { DeletePaymentReceivedService } from './commands/DeletePaymentReceived.service';
|
||||
import { GetPaymentReceivedService } from './queries/GetPaymentReceived.service';
|
||||
import { GetPaymentReceivedInvoices } from './queries/GetPaymentReceivedInvoices.service';
|
||||
import { GetPaymentReceivedPdfService } from './queries/GetPaymentReceivedPdf.service';
|
||||
import { GetPaymentReceivedStateService } from './queries/GetPaymentReceivedState.service';
|
||||
import { GetPaymentsReceivedService } from './queries/GetPaymentsReceived.service';
|
||||
import { SendPaymentReceiveMailNotification } from './commands/PaymentReceivedMailNotification';
|
||||
import {
|
||||
CreatePaymentReceivedDto,
|
||||
EditPaymentReceivedDto,
|
||||
} from './dtos/PaymentReceived.dto';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentReceivesApplication {
|
||||
constructor(
|
||||
private createPaymentReceivedService: CreatePaymentReceivedService,
|
||||
private editPaymentReceivedService: EditPaymentReceivedService,
|
||||
private deletePaymentReceivedService: DeletePaymentReceivedService,
|
||||
private getPaymentsReceivedService: GetPaymentsReceivedService,
|
||||
private getPaymentReceivedService: GetPaymentReceivedService,
|
||||
private getPaymentReceiveInvoicesService: GetPaymentReceivedInvoices,
|
||||
private sendPaymentReceiveMailNotification: SendPaymentReceiveMailNotification,
|
||||
private getPaymentReceivePdfService: GetPaymentReceivedPdfService,
|
||||
private getPaymentReceivedStateService: GetPaymentReceivedStateService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new payment receive.
|
||||
* @param {CreatePaymentReceivedDto} paymentReceiveDTO
|
||||
* @returns
|
||||
*/
|
||||
public createPaymentReceived(paymentReceiveDTO: CreatePaymentReceivedDto) {
|
||||
return this.createPaymentReceivedService.createPaymentReceived(
|
||||
paymentReceiveDTO,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details the given payment receive with associated entries.
|
||||
* @param {number} paymentReceiveId - Payment receive id.
|
||||
* @param {EditPaymentReceivedDto} paymentReceiveDTO - Payment receive data.
|
||||
* @returns
|
||||
*/
|
||||
public editPaymentReceive(
|
||||
paymentReceiveId: number,
|
||||
paymentReceiveDTO: EditPaymentReceivedDto,
|
||||
) {
|
||||
return this.editPaymentReceivedService.editPaymentReceive(
|
||||
paymentReceiveId,
|
||||
paymentReceiveDTO,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given payment receive.
|
||||
* @param {number} paymentReceiveId - Payment received id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deletePaymentReceive(paymentReceiveId: number) {
|
||||
return this.deletePaymentReceivedService.deletePaymentReceive(
|
||||
paymentReceiveId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment receives paginated and filterable.
|
||||
* @param {number} tenantId
|
||||
* @param {IPaymentsReceivedFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
public async getPaymentsReceived(filterDTO: IPaymentsReceivedFilter) {
|
||||
return this.getPaymentsReceivedService.getPaymentReceives(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given payment receive.
|
||||
* @param {number} paymentReceiveId
|
||||
* @returns {Promise<IPaymentReceived>}
|
||||
*/
|
||||
public async getPaymentReceive(paymentReceiveId: number) {
|
||||
return this.getPaymentReceivedService.getPaymentReceive(paymentReceiveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves associated sale invoices of the given payment receive.
|
||||
* @param {number} paymentReceiveId
|
||||
* @returns
|
||||
*/
|
||||
public getPaymentReceiveInvoices(paymentReceiveId: number) {
|
||||
return this.getPaymentReceiveInvoicesService.getPaymentReceiveInvoices(
|
||||
paymentReceiveId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify customer via mail about payment receive details.
|
||||
* @param {number} paymentReceiveId
|
||||
* @param {PaymentReceiveMailOptsDTO} messageOpts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public notifyPaymentByMail(
|
||||
paymentReceiveId: number,
|
||||
messageOpts: PaymentReceiveMailOptsDTO,
|
||||
): Promise<void> {
|
||||
return this.sendPaymentReceiveMailNotification.triggerMail(
|
||||
paymentReceiveId,
|
||||
messageOpts,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default mail options of the given payment transaction.
|
||||
* @param {number} paymentReceiveId - Payment receive id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public getPaymentMailOptions(paymentReceiveId: number) {
|
||||
return this.sendPaymentReceiveMailNotification.getMailOptions(
|
||||
paymentReceiveId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve pdf content of the given payment receive.
|
||||
* @param {number} tenantId
|
||||
* @param {PaymentReceive} paymentReceive
|
||||
* @returns
|
||||
*/
|
||||
public getPaymentReceivePdf(paymentReceiveId: number) {
|
||||
return this.getPaymentReceivePdfService.getPaymentReceivePdf(
|
||||
paymentReceiveId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the create/edit initial state of the payment received.
|
||||
* @returns {Promise<IPaymentReceivedState>}
|
||||
*/
|
||||
public getPaymentReceivedState() {
|
||||
return this.getPaymentReceivedStateService.getPaymentReceivedState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { PaymentReceivesApplication } from './PaymentReceived.application';
|
||||
import {
|
||||
IPaymentReceivedCreateDTO,
|
||||
IPaymentReceivedEditDTO,
|
||||
IPaymentsReceivedFilter,
|
||||
PaymentReceiveMailOptsDTO,
|
||||
} from './types/PaymentReceived.types';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@Controller('payments-received')
|
||||
@ApiTags('payments-received')
|
||||
export class PaymentReceivesController {
|
||||
constructor(private paymentReceivesApplication: PaymentReceivesApplication) {}
|
||||
|
||||
@Post(':id/mail')
|
||||
@HttpCode(200)
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The payment receive mail has been successfully sent.',
|
||||
})
|
||||
public sendPaymentReceiveMail(
|
||||
@Param('id', ParseIntPipe) paymentReceiveId: number,
|
||||
@Body() messageOpts: PaymentReceiveMailOptsDTO,
|
||||
) {
|
||||
return this.paymentReceivesApplication.notifyPaymentByMail(
|
||||
paymentReceiveId,
|
||||
messageOpts,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id/mail')
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description:
|
||||
'The payment receive mail options have been successfully retrieved.',
|
||||
})
|
||||
public getPaymentReceiveMailOptions(
|
||||
@Param('id', ParseIntPipe) paymentReceiveId: number,
|
||||
) {
|
||||
return this.paymentReceivesApplication.getPaymentMailOptions(
|
||||
paymentReceiveId,
|
||||
);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new payment received.' })
|
||||
public createPaymentReceived(
|
||||
@Body() paymentReceiveDTO: IPaymentReceivedCreateDTO,
|
||||
) {
|
||||
return this.paymentReceivesApplication.createPaymentReceived(
|
||||
paymentReceiveDTO,
|
||||
);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: 'Edit the given payment received.' })
|
||||
public editPaymentReceive(
|
||||
@Param('id', ParseIntPipe) paymentReceiveId: number,
|
||||
@Body() paymentReceiveDTO: IPaymentReceivedEditDTO,
|
||||
) {
|
||||
return this.paymentReceivesApplication.editPaymentReceive(
|
||||
paymentReceiveId,
|
||||
paymentReceiveDTO,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete the given payment received.' })
|
||||
public deletePaymentReceive(
|
||||
@Param('id', ParseIntPipe) paymentReceiveId: number,
|
||||
) {
|
||||
return this.paymentReceivesApplication.deletePaymentReceive(
|
||||
paymentReceiveId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Retrieves the payment received list.' })
|
||||
public getPaymentsReceived(@Query() filterDTO: IPaymentsReceivedFilter) {
|
||||
return this.paymentReceivesApplication.getPaymentsReceived(filterDTO);
|
||||
}
|
||||
|
||||
@Get('state')
|
||||
@ApiOperation({ summary: 'Retrieves the payment received state.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The payment received state has been successfully retrieved.',
|
||||
})
|
||||
public getPaymentReceivedState() {
|
||||
return this.paymentReceivesApplication.getPaymentReceivedState();
|
||||
}
|
||||
|
||||
@Get(':id/invoices')
|
||||
@ApiOperation({ summary: 'Retrieves the payment received invoices.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description:
|
||||
'The payment received invoices have been successfully retrieved.',
|
||||
})
|
||||
public getPaymentReceiveInvoices(
|
||||
@Param('id', ParseIntPipe) paymentReceiveId: number,
|
||||
) {
|
||||
return this.paymentReceivesApplication.getPaymentReceiveInvoices(
|
||||
paymentReceiveId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Retrieves the payment received details.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description:
|
||||
'The payment received details have been successfully retrieved.',
|
||||
})
|
||||
public getPaymentReceive(
|
||||
@Param('id', ParseIntPipe) paymentReceiveId: number,
|
||||
) {
|
||||
return this.paymentReceivesApplication.getPaymentReceive(paymentReceiveId);
|
||||
}
|
||||
|
||||
@Get(':id/pdf')
|
||||
@ApiOperation({ summary: 'Retrieves the payment received pdf.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The payment received pdf has been successfully retrieved.',
|
||||
})
|
||||
public getPaymentReceivePdf(
|
||||
@Param('id', ParseIntPipe) paymentReceivedId: number,
|
||||
) {
|
||||
return this.paymentReceivesApplication.getPaymentReceivePdf(
|
||||
paymentReceivedId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PaymentReceivesController } from './PaymentsReceived.controller';
|
||||
import { PaymentReceivesApplication } from './PaymentReceived.application';
|
||||
import { CreatePaymentReceivedService } from './commands/CreatePaymentReceived.serivce';
|
||||
import { DeletePaymentReceivedService } from './commands/DeletePaymentReceived.service';
|
||||
import { EditPaymentReceivedService } from './commands/EditPaymentReceived.service';
|
||||
import { GetPaymentReceivedStateService } from './queries/GetPaymentReceivedState.service';
|
||||
import { GetPaymentReceivedService } from './queries/GetPaymentReceived.service';
|
||||
import { GetPaymentReceivedInvoices } from './queries/GetPaymentReceivedInvoices.service';
|
||||
import { GetPaymentReceivedPdfService } from './queries/GetPaymentReceivedPdf.service';
|
||||
import { PaymentReceivedValidators } from './commands/PaymentReceivedValidators.service';
|
||||
import { PaymentReceiveDTOTransformer } from './commands/PaymentReceivedDTOTransformer';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
|
||||
import { TemplateInjectableModule } from '../TemplateInjectable/TemplateInjectable.module';
|
||||
import { PaymentReceivedBrandingTemplate } from './queries/PaymentReceivedBrandingTemplate.service';
|
||||
import { PaymentReceivedIncrement } from './commands/PaymentReceivedIncrement.service';
|
||||
import { BranchesModule } from '../Branches/Branches.module';
|
||||
import { WarehousesModule } from '../Warehouses/Warehouses.module';
|
||||
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
|
||||
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
|
||||
import { PaymentReceivedAutoIncrementSubscriber } from './subscribers/PaymentReceivedAutoIncrementSubscriber';
|
||||
import { PaymentReceivedGLEntriesSubscriber } from './subscribers/PaymentReceivedGLEntriesSubscriber';
|
||||
import { PaymentReceivedGLEntries } from './commands/PaymentReceivedGLEntries';
|
||||
import { PaymentReceivedSyncInvoicesSubscriber } from './subscribers/PaymentReceivedSyncInvoices';
|
||||
import { PaymentReceivedInvoiceSync } from './commands/PaymentReceivedInvoiceSync.service';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
import { SendPaymentReceiveMailNotification } from './commands/PaymentReceivedMailNotification';
|
||||
import { GetPaymentsReceivedService } from './queries/GetPaymentsReceived.service';
|
||||
import { MailNotificationModule } from '../MailNotification/MailNotification.module';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { MailModule } from '../Mail/Mail.module';
|
||||
import { SendPaymentReceivedMailProcessor } from './processors/PaymentReceivedMailNotification.processor';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants';
|
||||
|
||||
@Module({
|
||||
controllers: [PaymentReceivesController],
|
||||
providers: [
|
||||
PaymentReceivesApplication,
|
||||
CreatePaymentReceivedService,
|
||||
DeletePaymentReceivedService,
|
||||
EditPaymentReceivedService,
|
||||
GetPaymentReceivedStateService,
|
||||
GetPaymentReceivedService,
|
||||
GetPaymentReceivedInvoices,
|
||||
GetPaymentReceivedPdfService,
|
||||
PaymentReceivedValidators,
|
||||
PaymentReceiveDTOTransformer,
|
||||
PaymentReceivedBrandingTemplate,
|
||||
PaymentReceivedIncrement,
|
||||
PaymentReceivedGLEntries,
|
||||
TenancyContext,
|
||||
PaymentReceivedInvoiceSync,
|
||||
PaymentReceivedAutoIncrementSubscriber,
|
||||
PaymentReceivedGLEntriesSubscriber,
|
||||
PaymentReceivedSyncInvoicesSubscriber,
|
||||
GetPaymentsReceivedService,
|
||||
SendPaymentReceiveMailNotification,
|
||||
SendPaymentReceivedMailProcessor,
|
||||
],
|
||||
exports: [
|
||||
PaymentReceivesApplication,
|
||||
CreatePaymentReceivedService,
|
||||
PaymentReceivedGLEntries,
|
||||
],
|
||||
imports: [
|
||||
ChromiumlyTenancyModule,
|
||||
TemplateInjectableModule,
|
||||
BranchesModule,
|
||||
WarehousesModule,
|
||||
PdfTemplatesModule,
|
||||
AutoIncrementOrdersModule,
|
||||
LedgerModule,
|
||||
AccountsModule,
|
||||
MailNotificationModule,
|
||||
DynamicListModule,
|
||||
MailModule,
|
||||
BullModule.registerQueue({ name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE }),
|
||||
],
|
||||
})
|
||||
export class PaymentsReceivedModule {}
|
||||
@@ -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,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
@@ -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,
|
||||
// };
|
||||
// };
|
||||
// }
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
@@ -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;
|
||||
// }
|
||||
// }
|
||||
101
packages/server/src/modules/PaymentReceived/constants.ts
Normal file
101
packages/server/src/modules/PaymentReceived/constants.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
export const SEND_PAYMENT_RECEIVED_MAIL_QUEUE =
|
||||
'SEND_PAYMENT_RECEIVED_MAIL_QUEUE';
|
||||
export const SEND_PAYMENT_RECEIVED_MAIL_JOB = 'SEND_PAYMENT_RECEIVED_MAIL_JOB';
|
||||
|
||||
export const DEFAULT_PAYMENT_MAIL_SUBJECT =
|
||||
'Payment Received for {Customer Name} from {Company Name}';
|
||||
export const DEFAULT_PAYMENT_MAIL_CONTENT = `
|
||||
<p>Dear {Customer Name}</p>
|
||||
<p>Thank you for your payment. It was a pleasure doing business with you. We look forward to work together again!</p>
|
||||
<p>
|
||||
Payment Date : <strong>{Payment Date}</strong><br />
|
||||
Amount : <strong>{Payment Amount}</strong></br />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i>Regards</i><br />
|
||||
<i>{Company Name}</i>
|
||||
</p>
|
||||
`;
|
||||
|
||||
export const ERRORS = {
|
||||
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
|
||||
PAYMENT_RECEIVE_NOT_EXISTS: 'PAYMENT_RECEIVE_NOT_EXISTS',
|
||||
DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND',
|
||||
DEPOSIT_ACCOUNT_INVALID_TYPE: 'DEPOSIT_ACCOUNT_INVALID_TYPE',
|
||||
INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT',
|
||||
INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND',
|
||||
ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS',
|
||||
INVOICES_NOT_DELIVERED_YET: 'INVOICES_NOT_DELIVERED_YET',
|
||||
PAYMENT_RECEIVE_NO_IS_REQUIRED: 'PAYMENT_RECEIVE_NO_IS_REQUIRED',
|
||||
PAYMENT_RECEIVE_NO_REQUIRED: 'PAYMENT_RECEIVE_NO_REQUIRED',
|
||||
PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE: 'PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE',
|
||||
CUSTOMER_HAS_PAYMENT_RECEIVES: 'CUSTOMER_HAS_PAYMENT_RECEIVES',
|
||||
PAYMENT_ACCOUNT_CURRENCY_INVALID: 'PAYMENT_ACCOUNT_CURRENCY_INVALID',
|
||||
NO_INVOICE_CUSTOMER_EMAIL_ADDR: 'NO_INVOICE_CUSTOMER_EMAIL_ADDR',
|
||||
};
|
||||
|
||||
export const DEFAULT_VIEWS = [];
|
||||
|
||||
export const PaymentsReceiveSampleData = [
|
||||
{
|
||||
Customer: 'Randall Kohler',
|
||||
'Payment Date': '2024-10-10',
|
||||
'Payment Receive No.': 'PAY-0001',
|
||||
'Reference No.': 'REF-0001',
|
||||
'Deposit Account': 'Petty Cash',
|
||||
'Exchange Rate': '',
|
||||
Statement: 'Totam optio quisquam qui.',
|
||||
Invoice: 'INV-00001',
|
||||
'Payment Amount': 850,
|
||||
},
|
||||
];
|
||||
|
||||
export const defaultPaymentReceivedPdfTemplateAttributes = {
|
||||
// # Colors
|
||||
primaryColor: '#000',
|
||||
secondaryColor: '#000',
|
||||
|
||||
// # Company logo
|
||||
showCompanyLogo: true,
|
||||
companyLogoUri: '',
|
||||
|
||||
// # Company name
|
||||
companyName: 'Bigcapital Technology, Inc.',
|
||||
|
||||
// # Customer address
|
||||
showCustomerAddress: true,
|
||||
customerAddress: '',
|
||||
|
||||
// # Company address
|
||||
showCompanyAddress: true,
|
||||
companyAddress: '',
|
||||
billedToLabel: 'Billed To',
|
||||
|
||||
// Total
|
||||
total: '$1000.00',
|
||||
totalLabel: 'Total',
|
||||
showTotal: true,
|
||||
|
||||
// Subtotal
|
||||
subtotal: '1000/00',
|
||||
subtotalLabel: 'Subtotal',
|
||||
showSubtotal: true,
|
||||
|
||||
lines: [
|
||||
{
|
||||
invoiceNumber: 'INV-00001',
|
||||
invoiceAmount: '$1000.00',
|
||||
paidAmount: '$1000.00',
|
||||
},
|
||||
],
|
||||
// Payment received number
|
||||
showPaymentReceivedNumber: true,
|
||||
paymentReceivedNumberLabel: 'Payment Number',
|
||||
paymentReceivedNumebr: '346D3D40-0001',
|
||||
|
||||
// Payment date.
|
||||
paymentReceivedDate: 'September 3, 2024',
|
||||
showPaymentReceivedDate: true,
|
||||
paymentReceivedDateLabel: 'Payment Date',
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsNotEmpty, ValidateNested } from 'class-validator';
|
||||
import { IsString } from 'class-validator';
|
||||
import { IsDateString, IsNumber, IsOptional } from 'class-validator';
|
||||
import { IsInt } from 'class-validator';
|
||||
|
||||
export class PaymentReceivedEntryDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
id?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
index?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
paymentReceiveId?: number;
|
||||
|
||||
@IsInt()
|
||||
invoiceId: number;
|
||||
|
||||
@IsNumber()
|
||||
paymentAmount: number;
|
||||
}
|
||||
|
||||
export class CommandPaymentReceivedDto {
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ description: 'The id of the customer', example: 1 })
|
||||
customerId: number;
|
||||
|
||||
@IsDateString()
|
||||
@ApiProperty({
|
||||
description: 'The payment date of the payment received',
|
||||
example: '2021-01-01',
|
||||
})
|
||||
paymentDate: Date | string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: 'The amount of the payment received',
|
||||
example: 100,
|
||||
})
|
||||
amount?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: 'The exchange rate of the payment received',
|
||||
example: 1,
|
||||
})
|
||||
exchangeRate?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: 'The reference number of the payment received',
|
||||
example: '123456',
|
||||
})
|
||||
referenceNo?: string;
|
||||
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
description: 'The id of the deposit account',
|
||||
example: 1,
|
||||
})
|
||||
depositAccountId: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: 'The payment receive number of the payment received',
|
||||
example: '123456',
|
||||
})
|
||||
paymentReceiveNo?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: 'The statement of the payment received',
|
||||
example: '123456',
|
||||
})
|
||||
statement?: string;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => PaymentReceivedEntryDto)
|
||||
@ApiProperty({
|
||||
description: 'The entries of the payment received',
|
||||
example: [{ invoiceId: 1, paymentAmount: 100 }],
|
||||
})
|
||||
entries: PaymentReceivedEntryDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@ApiProperty({
|
||||
description: 'The id of the branch',
|
||||
example: 1,
|
||||
})
|
||||
branchId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AttachmentLinkDto)
|
||||
@ApiProperty({
|
||||
description: 'The attachments of the payment received',
|
||||
example: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'bill',
|
||||
},
|
||||
],
|
||||
})
|
||||
attachments?: AttachmentLinkDto[];
|
||||
}
|
||||
|
||||
export class CreatePaymentReceivedDto extends CommandPaymentReceivedDto {}
|
||||
export class EditPaymentReceivedDto extends CommandPaymentReceivedDto {}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { Model } from 'objection';
|
||||
import { PaymentReceivedEntry } from './PaymentReceivedEntry';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
export class PaymentReceived extends TenantBaseModel {
|
||||
customerId: number;
|
||||
paymentDate: string;
|
||||
amount: number;
|
||||
currencyCode: string;
|
||||
referenceNo: string;
|
||||
depositAccountId: number;
|
||||
paymentReceiveNo: string;
|
||||
exchangeRate: number;
|
||||
statement: string;
|
||||
|
||||
userId: number;
|
||||
branchId: number;
|
||||
pdfTemplateId: number;
|
||||
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
entries?: PaymentReceivedEntry[];
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'payment_receives';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localAmount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment receive amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resourcable model.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { PaymentReceivedEntry } = require('./PaymentReceivedEntry');
|
||||
const {
|
||||
AccountTransaction,
|
||||
} = require('../../Accounts/models/AccountTransaction.model');
|
||||
const { Customer } = require('../../Customers/models/Customer');
|
||||
const { Account } = require('../../Accounts/models/Account.model');
|
||||
const { Branch } = require('../../Branches/models/Branch.model');
|
||||
// const Document = require('../../Documents/models/Document');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer,
|
||||
join: {
|
||||
from: 'payment_receives.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.where('contact_service', 'customer');
|
||||
},
|
||||
},
|
||||
|
||||
depositAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'payment_receives.depositAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: PaymentReceivedEntry,
|
||||
join: {
|
||||
from: 'payment_receives.id',
|
||||
to: 'payment_receives_entries.paymentReceiveId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction,
|
||||
join: {
|
||||
from: 'payment_receives.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter: (builder) => {
|
||||
builder.where('reference_type', 'PaymentReceive');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Payment receive may belongs to branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch,
|
||||
join: {
|
||||
from: 'payment_receives.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Payment transaction may has many attached attachments.
|
||||
*/
|
||||
// attachments: {
|
||||
// relation: Model.ManyToManyRelation,
|
||||
// modelClass: Document.default,
|
||||
// join: {
|
||||
// from: 'payment_receives.id',
|
||||
// through: {
|
||||
// from: 'document_links.modelId',
|
||||
// to: 'document_links.documentId',
|
||||
// },
|
||||
// to: 'documents.id',
|
||||
// },
|
||||
// filter(query) {
|
||||
// query.where('model_ref', 'PaymentReceive');
|
||||
// },
|
||||
// },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
// static get meta() {
|
||||
// return PaymentReceiveSettings;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve the default custom views, roles and columns.
|
||||
// */
|
||||
// static get defaultViews() {
|
||||
// return DEFAULT_VIEWS;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'payment_receive_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
|
||||
import { Model } from 'objection';
|
||||
|
||||
export class PaymentReceivedEntry extends BaseModel {
|
||||
paymentReceiveId: number;
|
||||
invoiceId: number;
|
||||
paymentAmount: number;
|
||||
index: number;
|
||||
|
||||
invoice?: SaleInvoice;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'payment_receives_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { PaymentReceived } = require('./PaymentReceived');
|
||||
const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
|
||||
|
||||
return {
|
||||
/**
|
||||
*/
|
||||
payment: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PaymentReceived,
|
||||
join: {
|
||||
from: 'payment_receives_entries.paymentReceiveId',
|
||||
to: 'payment_receives.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The payment receive entry have have sale invoice.
|
||||
*/
|
||||
invoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice,
|
||||
join: {
|
||||
from: 'payment_receives_entries.invoiceId',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { JOB_REF, Process, Processor } from '@nestjs/bull';
|
||||
import { Job } from 'bull';
|
||||
import {
|
||||
SEND_PAYMENT_RECEIVED_MAIL_JOB,
|
||||
SEND_PAYMENT_RECEIVED_MAIL_QUEUE,
|
||||
} from '../constants';
|
||||
import { Inject, Scope } from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { SendPaymentReceiveMailNotification } from '../commands/PaymentReceivedMailNotification';
|
||||
import { SendPaymentReceivedMailPayload } from '../types/PaymentReceived.types';
|
||||
|
||||
@Processor({
|
||||
name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class SendPaymentReceivedMailProcessor {
|
||||
constructor(
|
||||
private readonly sendPaymentReceivedMail: SendPaymentReceiveMailNotification,
|
||||
private readonly clsService: ClsService,
|
||||
|
||||
@Inject(JOB_REF)
|
||||
private readonly jobRef: Job<SendPaymentReceivedMailPayload>,
|
||||
) {}
|
||||
|
||||
@Process(SEND_PAYMENT_RECEIVED_MAIL_JOB)
|
||||
async handleSendMail() {
|
||||
const { messageOptions, paymentReceivedId, organizationId, userId } =
|
||||
this.jobRef.data;
|
||||
|
||||
this.clsService.set('organizationId', organizationId);
|
||||
this.clsService.set('userId', userId);
|
||||
|
||||
try {
|
||||
await this.sendPaymentReceivedMail.sendMail(
|
||||
paymentReceivedId,
|
||||
messageOptions,
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ERRORS } from '../constants';
|
||||
import { PaymentReceiveTransfromer } from './PaymentReceivedTransformer';
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
|
||||
import { ServiceError } from '../../Items/ServiceError';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetPaymentReceivedService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(PaymentReceived.name)
|
||||
private readonly paymentReceiveModel: TenantModelProxy<
|
||||
typeof PaymentReceived
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve payment receive details.
|
||||
* @param {number} paymentReceiveId - Payment receive id.
|
||||
* @return {Promise<IPaymentReceived>}
|
||||
*/
|
||||
public async getPaymentReceive(
|
||||
paymentReceiveId: number,
|
||||
): Promise<PaymentReceived> {
|
||||
const paymentReceive = await this.paymentReceiveModel()
|
||||
.query()
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('depositAccount')
|
||||
.withGraphFetched('entries.invoice')
|
||||
.withGraphFetched('transactions')
|
||||
.withGraphFetched('branch')
|
||||
.findById(paymentReceiveId);
|
||||
|
||||
if (!paymentReceive) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
|
||||
}
|
||||
return this.transformer.transform(
|
||||
paymentReceive,
|
||||
new PaymentReceiveTransfromer(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { PaymentReceivedValidators } from '../commands/PaymentReceivedValidators.service';
|
||||
import { SaleInvoice } from '../../SaleInvoices/models/SaleInvoice';
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetPaymentReceivedInvoices {
|
||||
constructor(
|
||||
@Inject(PaymentReceived.name)
|
||||
private paymentReceiveModel: TenantModelProxy<typeof PaymentReceived>,
|
||||
|
||||
@Inject(SaleInvoice.name)
|
||||
private saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
|
||||
|
||||
private validators: PaymentReceivedValidators,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve sale invoices that associated to the given payment receive.
|
||||
* @param {number} paymentReceiveId - Payment receive id.
|
||||
* @return {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public async getPaymentReceiveInvoices(paymentReceiveId: number) {
|
||||
const paymentReceive = await this.paymentReceiveModel()
|
||||
.query()
|
||||
.findById(paymentReceiveId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Validates the payment receive existence.
|
||||
this.validators.validatePaymentExistance(paymentReceive);
|
||||
|
||||
const paymentReceiveInvoicesIds = paymentReceive.entries.map(
|
||||
(entry) => entry.invoiceId,
|
||||
);
|
||||
const saleInvoices = await this.saleInvoiceModel()
|
||||
.query()
|
||||
.whereIn('id', paymentReceiveInvoicesIds);
|
||||
|
||||
return saleInvoices;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { GetPaymentReceivedService } from './GetPaymentReceived.service';
|
||||
import { PaymentReceivedBrandingTemplate } from './PaymentReceivedBrandingTemplate.service';
|
||||
import { transformPaymentReceivedToPdfTemplate } from '../utils';
|
||||
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
|
||||
import { ChromiumlyTenancy } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { TemplateInjectable } from '@/modules/TemplateInjectable/TemplateInjectable.service';
|
||||
import { PaymentReceivedPdfTemplateAttributes } from '../types/PaymentReceived.types';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetPaymentReceivedPdfService {
|
||||
constructor(
|
||||
private chromiumlyTenancy: ChromiumlyTenancy,
|
||||
private templateInjectable: TemplateInjectable,
|
||||
private getPaymentService: GetPaymentReceivedService,
|
||||
private paymentBrandingTemplateService: PaymentReceivedBrandingTemplate,
|
||||
private eventPublisher: EventEmitter2,
|
||||
|
||||
@Inject(PaymentReceived.name)
|
||||
private paymentReceiveModel: TenantModelProxy<typeof PaymentReceived>,
|
||||
|
||||
@Inject(PdfTemplateModel.name)
|
||||
private pdfTemplateModel: TenantModelProxy<typeof PdfTemplateModel>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* @param {number} tenantId -
|
||||
* @param {IPaymentReceived} paymentReceive -
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
async getPaymentReceivePdf(
|
||||
paymentReceivedId: number,
|
||||
): Promise<[Buffer, string]> {
|
||||
const brandingAttributes =
|
||||
await this.getPaymentBrandingAttributes(paymentReceivedId);
|
||||
|
||||
const htmlContent = await this.templateInjectable.render(
|
||||
'modules/payment-receive-standard',
|
||||
brandingAttributes,
|
||||
);
|
||||
const filename = await this.getPaymentReceivedFilename(paymentReceivedId);
|
||||
// Converts the given html content to pdf document.
|
||||
const content =
|
||||
await this.chromiumlyTenancy.convertHtmlContent(htmlContent);
|
||||
const eventPayload = { paymentReceivedId };
|
||||
|
||||
// Triggers the `onCreditNotePdfViewed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.paymentReceive.onPdfViewed,
|
||||
eventPayload,
|
||||
);
|
||||
return [content, filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename of the given payment.
|
||||
* @param {number} tenantId
|
||||
* @param {number} paymentReceivedId
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
private async getPaymentReceivedFilename(
|
||||
paymentReceivedId: number,
|
||||
): Promise<string> {
|
||||
const payment = await this.paymentReceiveModel()
|
||||
.query()
|
||||
.findById(paymentReceivedId);
|
||||
|
||||
return `Payment-${payment.paymentReceiveNo}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given payment received branding attributes.
|
||||
* @param {number} paymentReceivedId - Payment received identifier.
|
||||
* @returns {Promise<PaymentReceivedPdfTemplateAttributes>}
|
||||
*/
|
||||
async getPaymentBrandingAttributes(
|
||||
paymentReceivedId: number,
|
||||
): Promise<PaymentReceivedPdfTemplateAttributes> {
|
||||
const paymentReceived =
|
||||
await this.getPaymentService.getPaymentReceive(paymentReceivedId);
|
||||
|
||||
const templateId =
|
||||
paymentReceived?.pdfTemplateId ??
|
||||
(
|
||||
await this.pdfTemplateModel().query().findOne({
|
||||
resource: 'PaymentReceive',
|
||||
default: true,
|
||||
})
|
||||
)?.id;
|
||||
|
||||
const brandingTemplate =
|
||||
await this.paymentBrandingTemplateService.getPaymentReceivedPdfTemplate(
|
||||
templateId,
|
||||
);
|
||||
|
||||
return {
|
||||
...brandingTemplate.attributes,
|
||||
...transformPaymentReceivedToPdfTemplate(paymentReceived),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IPaymentReceivedState } from '../types/PaymentReceived.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetPaymentReceivedStateService {
|
||||
constructor(
|
||||
@Inject(PdfTemplateModel.name)
|
||||
private pdfTemplateModel: TenantModelProxy<typeof PdfTemplateModel>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the create/edit initial state of the payment received.
|
||||
* @returns {Promise<IPaymentReceivedState>} - A promise resolving to the payment received state.
|
||||
*/
|
||||
public async getPaymentReceivedState(): Promise<IPaymentReceivedState> {
|
||||
const defaultPdfTemplate = await this.pdfTemplateModel()
|
||||
.query()
|
||||
.findOne({ resource: 'PaymentReceive' })
|
||||
.modify('default');
|
||||
|
||||
return {
|
||||
defaultTemplateId: defaultPdfTemplate?.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import * as R from 'ramda';
|
||||
import { PaymentReceiveTransfromer } from './PaymentReceivedTransformer';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
import { IPaymentsReceivedFilter } from '../types/PaymentReceived.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetPaymentsReceivedService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(PaymentReceived.name)
|
||||
private readonly paymentReceivedModel: TenantModelProxy<
|
||||
typeof PaymentReceived
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve payment receives paginated and filterable list.
|
||||
* @param {IPaymentsReceivedFilter} filterDTO
|
||||
*/
|
||||
public async getPaymentReceives(filterDTO: IPaymentsReceivedFilter): Promise<{
|
||||
paymentReceives: PaymentReceived[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
PaymentReceived,
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await this.paymentReceivedModel()
|
||||
.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('depositAccount');
|
||||
|
||||
dynamicList.buildQuery()(builder);
|
||||
filterDTO?.filterQuery && filterDTO.filterQuery(builder as any);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transformer the payment receives models to POJO.
|
||||
const transformedPayments = await this.transformer.transform(
|
||||
results,
|
||||
new PaymentReceiveTransfromer(),
|
||||
);
|
||||
return {
|
||||
paymentReceives: transformedPayments,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses payments receive list filter DTO.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { defaultPaymentReceivedPdfTemplateAttributes } from '../constants';
|
||||
import { GetPdfTemplateService } from '../../PdfTemplate/queries/GetPdfTemplate.service';
|
||||
import { GetOrganizationBrandingAttributesService } from '../../PdfTemplate/queries/GetOrganizationBrandingAttributes.service';
|
||||
import { mergePdfTemplateWithDefaultAttributes } from '../../SaleInvoices/utils';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentReceivedBrandingTemplate {
|
||||
constructor(
|
||||
private readonly getPdfTemplateService: GetPdfTemplateService,
|
||||
private readonly getOrgBrandingAttributes: GetOrganizationBrandingAttributesService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the payment received pdf template.
|
||||
* @param {number} paymentTemplateId
|
||||
* @returns
|
||||
*/
|
||||
public async getPaymentReceivedPdfTemplate(paymentTemplateId: number) {
|
||||
const template = await this.getPdfTemplateService.getPdfTemplate(
|
||||
paymentTemplateId
|
||||
);
|
||||
// Retrieves the organization branding attributes.
|
||||
const commonOrgBrandingAttrs =
|
||||
await this.getOrgBrandingAttributes.getOrganizationBrandingAttributes();
|
||||
|
||||
// Merges the default branding attributes with common organization branding attrs.
|
||||
const organizationBrandingAttrs = {
|
||||
...defaultPaymentReceivedPdfTemplateAttributes,
|
||||
...commonOrgBrandingAttrs,
|
||||
};
|
||||
const brandingTemplateAttrs = {
|
||||
...template.attributes,
|
||||
companyLogoUri: template.companyLogoUri,
|
||||
};
|
||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||
brandingTemplateAttrs,
|
||||
organizationBrandingAttrs
|
||||
);
|
||||
return {
|
||||
...template,
|
||||
attributes,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { SaleInvoiceTransformer } from "@/modules/SaleInvoices/queries/SaleInvoice.transformer";
|
||||
import { Transformer } from "@/modules/Transformer/Transformer";
|
||||
|
||||
|
||||
export class PaymentReceivedEntryTransfromer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to payment receive entry object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['paymentAmountFormatted', 'invoice'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the payment amount formatted.
|
||||
* @param entry
|
||||
* @returns {string}
|
||||
*/
|
||||
protected paymentAmountFormatted(entry) {
|
||||
return this.formatNumber(entry.paymentAmount, { money: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives the transformed invoice.
|
||||
*/
|
||||
protected invoice(entry) {
|
||||
return this.item(entry.invoice, new SaleInvoiceTransformer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Transformer } from '../../Transformer/Transformer';
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { PaymentReceivedEntry } from '../models/PaymentReceivedEntry';
|
||||
import { PaymentReceivedEntryTransfromer } from './PaymentReceivedEntryTransformer';
|
||||
|
||||
export class PaymentReceiveTransfromer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to payment receive object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'subtotalFormatted',
|
||||
'formattedPaymentDate',
|
||||
'formattedCreatedAt',
|
||||
'formattedAmount',
|
||||
'formattedExchangeRate',
|
||||
'entries',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted payment receive date.
|
||||
* @param {PaymentReceived} invoice
|
||||
* @returns {String}
|
||||
*/
|
||||
protected formattedPaymentDate = (payment: PaymentReceived): string => {
|
||||
return this.formatDate(payment.paymentDate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted created at date.
|
||||
* @param {PaymentReceived} payment
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCreatedAt = (payment: PaymentReceived): string => {
|
||||
return this.formatDate(payment.createdAt);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the formatted payment subtotal.
|
||||
* @param {PaymentReceived} payment
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalFormatted = (payment: PaymentReceived): string => {
|
||||
return this.formatNumber(payment.amount, {
|
||||
currencyCode: payment.currencyCode,
|
||||
money: false,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted payment amount.
|
||||
* @param {PaymentReceived} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (payment: PaymentReceived): string => {
|
||||
return this.formatNumber(payment.amount, {
|
||||
currencyCode: payment.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the formatted exchange rate.
|
||||
* @param {PaymentReceived} payment
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedExchangeRate = (payment: PaymentReceived): string => {
|
||||
return this.formatNumber(payment.exchangeRate, { money: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the payment entries.
|
||||
* @param {PaymentReceived} payment
|
||||
* @returns {IPaymentReceivedEntry[]}
|
||||
*/
|
||||
protected entries = (payment: PaymentReceived): PaymentReceivedEntry[] => {
|
||||
return this.item(payment.entries, new PaymentReceivedEntryTransfromer());
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { omit } from 'lodash';
|
||||
import { IPaymentReceivePageEntry } from '../types/PaymentReceived.types';
|
||||
import { ERRORS } from '../constants';
|
||||
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
/**
|
||||
* Payment receives edit/new pages service.
|
||||
*/
|
||||
@Injectable()
|
||||
export class PaymentsReceivedPagesService {
|
||||
constructor(
|
||||
@Inject(SaleInvoice.name)
|
||||
private readonly saleInvoice: TenantModelProxy<typeof SaleInvoice>,
|
||||
|
||||
@Inject(PaymentReceived.name)
|
||||
private readonly paymentReceived: TenantModelProxy<typeof PaymentReceived>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrive page invoices entries from the given sale invoices models.
|
||||
* @param {ISaleInvoice[]} invoices - Invoices.
|
||||
* @return {IPaymentReceivePageEntry}
|
||||
*/
|
||||
private invoiceToPageEntry(invoice: SaleInvoice): IPaymentReceivePageEntry {
|
||||
return {
|
||||
entryType: 'invoice',
|
||||
invoiceId: invoice.id,
|
||||
invoiceNo: invoice.invoiceNo,
|
||||
amount: invoice.balance,
|
||||
dueAmount: invoice.dueAmount,
|
||||
paymentAmount: invoice.paymentAmount,
|
||||
totalPaymentAmount: invoice.paymentAmount,
|
||||
currencyCode: invoice.currencyCode,
|
||||
date: invoice.invoiceDate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment receive new page receivable entries.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} vendorId - Vendor id.
|
||||
* @return {IPaymentReceivePageEntry[]}
|
||||
*/
|
||||
public async getNewPageEntries(tenantId: number, customerId: number) {
|
||||
// Retrieve due invoices.
|
||||
const entries = await this.saleInvoice()
|
||||
.query()
|
||||
.modify('delivered')
|
||||
.modify('dueInvoices')
|
||||
.where('customer_id', customerId)
|
||||
.orderBy('invoice_date', 'ASC');
|
||||
|
||||
return entries.map(this.invoiceToPageEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the payment receive details of the given id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Integer} paymentReceiveId - Payment receive id.
|
||||
*/
|
||||
public async getPaymentReceiveEditPage(
|
||||
tenantId: number,
|
||||
paymentReceiveId: number,
|
||||
): Promise<{
|
||||
paymentReceive: Omit<PaymentReceived, 'entries'>;
|
||||
entries: IPaymentReceivePageEntry[];
|
||||
}> {
|
||||
// Retrieve payment receive.
|
||||
const paymentReceive = await this.paymentReceived()
|
||||
.query()
|
||||
.findById(paymentReceiveId)
|
||||
.withGraphFetched('entries.invoice')
|
||||
.withGraphFetched('attachments');
|
||||
|
||||
// Throw not found the payment receive.
|
||||
if (!paymentReceive) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
|
||||
}
|
||||
const paymentEntries = paymentReceive.entries.map((entry) => ({
|
||||
...this.invoiceToPageEntry(entry.invoice),
|
||||
dueAmount: entry.invoice.dueAmount + entry.paymentAmount,
|
||||
paymentAmount: entry.paymentAmount,
|
||||
index: entry.index,
|
||||
}));
|
||||
// Retrieves all receivable bills that associated to the payment receive transaction.
|
||||
const restReceivableInvoices = await this.saleInvoice()
|
||||
.query()
|
||||
.modify('delivered')
|
||||
.modify('dueInvoices')
|
||||
.where('customer_id', paymentReceive.customerId)
|
||||
.whereNotIn(
|
||||
'id',
|
||||
paymentReceive.entries.map((entry) => entry.invoiceId),
|
||||
)
|
||||
.orderBy('invoice_date', 'ASC');
|
||||
|
||||
const restReceivableEntries = restReceivableInvoices.map(
|
||||
this.invoiceToPageEntry,
|
||||
);
|
||||
const entries = [...paymentEntries, ...restReceivableEntries];
|
||||
|
||||
return {
|
||||
paymentReceive: omit(paymentReceive, ['entries']),
|
||||
entries,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { PaymentReceivedIncrement } from '../commands/PaymentReceivedIncrement.service';
|
||||
import { IPaymentReceivedCreatedPayload } from '../types/PaymentReceived.types';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentReceivedAutoIncrementSubscriber {
|
||||
constructor(private readonly paymentIncrement: PaymentReceivedIncrement) {}
|
||||
|
||||
/**
|
||||
* Handles increment next number of payment receive once be created.
|
||||
* @param {IPaymentReceivedCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onCreated)
|
||||
private async handlePaymentNextNumberIncrement({}: IPaymentReceivedCreatedPayload) {
|
||||
await this.paymentIncrement.incrementNextPaymentReceiveNumber();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
IPaymentReceivedCreatedPayload,
|
||||
IPaymentReceivedDeletedPayload,
|
||||
IPaymentReceivedEditedPayload,
|
||||
} from '../types/PaymentReceived.types';
|
||||
import { PaymentReceivedGLEntries } from '../commands/PaymentReceivedGLEntries';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentReceivedGLEntriesSubscriber {
|
||||
/**
|
||||
* @param {PaymentReceivedGLEntries} paymentReceivedGLEntries -
|
||||
*/
|
||||
constructor(
|
||||
private readonly paymentReceivedGLEntries: PaymentReceivedGLEntries,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle journal entries writing once the payment receive created.
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onCreated)
|
||||
private async handleWriteJournalEntriesOnceCreated({
|
||||
paymentReceiveId,
|
||||
trx,
|
||||
}: IPaymentReceivedCreatedPayload) {
|
||||
await this.paymentReceivedGLEntries.writePaymentGLEntries(
|
||||
paymentReceiveId,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle journal entries writing once the payment receive edited.
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onEdited)
|
||||
private async handleOverwriteJournalEntriesOnceEdited({
|
||||
paymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedEditedPayload) {
|
||||
await this.paymentReceivedGLEntries.rewritePaymentGLEntries(
|
||||
paymentReceive.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles revert journal entries once deleted.
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onDeleted)
|
||||
private async handleRevertJournalEntriesOnceDeleted({
|
||||
paymentReceiveId,
|
||||
trx,
|
||||
}: IPaymentReceivedDeletedPayload) {
|
||||
await this.paymentReceivedGLEntries.revertPaymentGLEntries(
|
||||
paymentReceiveId,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { PaymentReceiveNotifyBySms } from '@/services/Sales/PaymentReceived/PaymentReceivedSmsNotify';
|
||||
// import { IPaymentReceivedCreatedPayload } from '@/interfaces';
|
||||
// import { runAfterTransaction } from '@/services/UnitOfWork/TransactionsHooks';
|
||||
|
||||
// @Service()
|
||||
// export default class SendSmsNotificationPaymentReceive {
|
||||
// @Inject()
|
||||
// private paymentReceiveSmsNotify: PaymentReceiveNotifyBySms;
|
||||
|
||||
// /**
|
||||
// * Attach events.
|
||||
// */
|
||||
// public attach(bus) {
|
||||
// bus.subscribe(
|
||||
// events.paymentReceive.onCreated,
|
||||
// this.handleNotifyViaSmsOncePaymentPublish
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Handles send SMS notification after payment transaction creation.
|
||||
// */
|
||||
// private handleNotifyViaSmsOncePaymentPublish = ({
|
||||
// tenantId,
|
||||
// paymentReceiveId,
|
||||
// trx,
|
||||
// }: IPaymentReceivedCreatedPayload) => {
|
||||
// // Notify via Sms after transactions complete running.
|
||||
// runAfterTransaction(trx, async () => {
|
||||
// try {
|
||||
// await this.paymentReceiveSmsNotify.notifyViaSmsNotificationAfterCreation(
|
||||
// tenantId,
|
||||
// paymentReceiveId
|
||||
// );
|
||||
// } catch (error) { }
|
||||
// });
|
||||
// };
|
||||
// }
|
||||
@@ -0,0 +1,27 @@
|
||||
// import { Container } from 'typedi';
|
||||
// import { On, EventSubscriber } from 'event-dispatch';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { PaymentReceiveNotifyBySms } from './PaymentReceivedSmsNotify';
|
||||
|
||||
// @EventSubscriber()
|
||||
// export default class SendSmsNotificationPaymentReceive {
|
||||
// paymentReceiveNotifyBySms: PaymentReceiveNotifyBySms;
|
||||
|
||||
// constructor() {
|
||||
// this.paymentReceiveNotifyBySms = Container.get(PaymentReceiveNotifyBySms);
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// */
|
||||
// @On(events.paymentReceive.onNotifySms)
|
||||
// async sendSmsNotificationOnceInvoiceNotify({
|
||||
// paymentReceive,
|
||||
// customer,
|
||||
// }) {
|
||||
// await this.paymentReceiveNotifyBySms.sendSmsNotification(
|
||||
// paymentReceive,
|
||||
// customer
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
import {
|
||||
IPaymentReceivedCreatedPayload,
|
||||
IPaymentReceivedDeletedPayload,
|
||||
IPaymentReceivedEditedPayload,
|
||||
} from '../types/PaymentReceived.types';
|
||||
import { PaymentReceivedInvoiceSync } from '../commands/PaymentReceivedInvoiceSync.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentReceivedSyncInvoicesSubscriber {
|
||||
/**
|
||||
* @param {PaymentReceivedInvoiceSync} paymentSyncInvoice -
|
||||
*/
|
||||
constructor(
|
||||
private readonly paymentSyncInvoice: PaymentReceivedInvoiceSync,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle sale invoice increment/decrement payment amount
|
||||
* once created, edited or deleted.
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onCreated)
|
||||
private async handleInvoiceIncrementPaymentOnceCreated({
|
||||
paymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedCreatedPayload) {
|
||||
await this.paymentSyncInvoice.saveChangeInvoicePaymentAmount(
|
||||
paymentReceive.entries,
|
||||
null,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle sale invoice increment/decrement payment amount once edited.
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onEdited)
|
||||
private async handleInvoiceIncrementPaymentOnceEdited({
|
||||
paymentReceive,
|
||||
oldPaymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedEditedPayload) {
|
||||
await this.paymentSyncInvoice.saveChangeInvoicePaymentAmount(
|
||||
paymentReceive.entries,
|
||||
oldPaymentReceive?.entries || null,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle revert invoices payment amount once payment receive deleted.
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onDeleted)
|
||||
private async handleInvoiceDecrementPaymentAmount({
|
||||
paymentReceiveId,
|
||||
oldPaymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedDeletedPayload) {
|
||||
await this.paymentSyncInvoice.saveChangeInvoicePaymentAmount(
|
||||
oldPaymentReceive.entries.map((entry) => ({
|
||||
...entry,
|
||||
paymentAmount: 0,
|
||||
})),
|
||||
oldPaymentReceive.entries,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types';
|
||||
import { Knex } from 'knex';
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||
import {
|
||||
CommonMailOptions,
|
||||
CommonMailOptionsDTO,
|
||||
} from '@/modules/MailNotification/MailNotification.types';
|
||||
import { TenantJobPayload } from '@/interfaces/Tenant';
|
||||
import { EditPaymentReceivedDto } from '../dtos/PaymentReceived.dto';
|
||||
|
||||
|
||||
export interface IPaymentReceivedEntryDTO {
|
||||
id?: number;
|
||||
index?: number;
|
||||
paymentReceiveId?: number;
|
||||
invoiceId: number;
|
||||
paymentAmount: number;
|
||||
}
|
||||
|
||||
|
||||
export interface IPaymentReceivedCreateDTO {
|
||||
customerId: number;
|
||||
paymentDate: Date | string;
|
||||
amount?: number;
|
||||
exchangeRate?: number;
|
||||
referenceNo?: string;
|
||||
depositAccountId: number;
|
||||
paymentReceiveNo?: string;
|
||||
statement?: string;
|
||||
entries: IPaymentReceivedEntryDTO[];
|
||||
|
||||
branchId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedEditDTO {
|
||||
customerId: number;
|
||||
paymentDate: Date;
|
||||
amount: number;
|
||||
exchangeRate: number;
|
||||
referenceNo: string;
|
||||
depositAccountId: number;
|
||||
paymentReceiveNo?: string;
|
||||
statement: string;
|
||||
entries: IPaymentReceivedEntryDTO[];
|
||||
branchId?: number;
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
}
|
||||
|
||||
export interface IPaymentsReceivedFilter extends IDynamicListFilter {
|
||||
stringifiedFilterRoles?: string;
|
||||
filterQuery?: (trx: Knex.Transaction) => void;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivePageEntry {
|
||||
invoiceId: number;
|
||||
entryType: string;
|
||||
invoiceNo: string;
|
||||
dueAmount: number;
|
||||
amount: number;
|
||||
totalPaymentAmount: number;
|
||||
paymentAmount: number;
|
||||
currencyCode: string;
|
||||
date: Date | string;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedEditPage {
|
||||
paymentReceive: PaymentReceived;
|
||||
entries: IPaymentReceivePageEntry[];
|
||||
}
|
||||
|
||||
export interface IPaymentsReceivedService {
|
||||
validateCustomerHasNoPayments(
|
||||
tenantId: number,
|
||||
customerId: number,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedSmsDetails {
|
||||
customerName: string;
|
||||
customerPhoneNumber: string;
|
||||
smsMessage: string;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedCreatingPayload {
|
||||
tenantId: number;
|
||||
paymentReceiveDTO: IPaymentReceivedCreateDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedCreatedPayload {
|
||||
// tenantId: number;
|
||||
paymentReceive: PaymentReceived;
|
||||
paymentReceiveId: number;
|
||||
// authorizedUser: ISystemUser;
|
||||
paymentReceiveDTO: IPaymentReceivedCreateDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedEditedPayload {
|
||||
paymentReceiveId: number;
|
||||
paymentReceive: PaymentReceived;
|
||||
oldPaymentReceive: PaymentReceived;
|
||||
paymentReceiveDTO: EditPaymentReceivedDto;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedEditingPayload {
|
||||
oldPaymentReceive: PaymentReceived;
|
||||
paymentReceiveDTO: IPaymentReceivedEditDTO;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedDeletingPayload {
|
||||
oldPaymentReceive: PaymentReceived;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IPaymentReceivedDeletedPayload {
|
||||
paymentReceiveId: number;
|
||||
oldPaymentReceive: PaymentReceived;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum PaymentReceiveAction {
|
||||
Create = 'Create',
|
||||
Edit = 'Edit',
|
||||
Delete = 'Delete',
|
||||
View = 'View',
|
||||
NotifyBySms = 'NotifyBySms',
|
||||
}
|
||||
|
||||
// export type IPaymentReceiveGLCommonEntry = Pick<
|
||||
// ILedgerEntry,
|
||||
// | 'debit'
|
||||
// | 'credit'
|
||||
// | 'currencyCode'
|
||||
// | 'exchangeRate'
|
||||
// | 'transactionId'
|
||||
// | 'transactionType'
|
||||
// | 'transactionNumber'
|
||||
// | 'referenceNumber'
|
||||
// | 'date'
|
||||
// | 'userId'
|
||||
// | 'createdAt'
|
||||
// | 'branchId'
|
||||
// >;
|
||||
|
||||
export interface PaymentReceiveMailOpts extends CommonMailOptions {}
|
||||
export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {}
|
||||
export interface PaymentReceiveMailPresendEvent {
|
||||
paymentReceivedId: number;
|
||||
messageOptions: PaymentReceiveMailOptsDTO;
|
||||
}
|
||||
|
||||
export interface PaymentReceivedPdfLineItem {
|
||||
item: string;
|
||||
description: string;
|
||||
rate: string;
|
||||
quantity: string;
|
||||
total: string;
|
||||
}
|
||||
|
||||
export interface PaymentReceivedPdfTax {
|
||||
label: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export interface PaymentReceivedPdfTemplateAttributes {
|
||||
primaryColor: string;
|
||||
secondaryColor: string;
|
||||
showCompanyLogo: boolean;
|
||||
companyLogo: string;
|
||||
companyName: string;
|
||||
|
||||
// Customer Address
|
||||
showCustomerAddress: boolean;
|
||||
customerAddress: string;
|
||||
|
||||
// Company address
|
||||
showCompanyAddress: boolean;
|
||||
companyAddress: string;
|
||||
billedToLabel: string;
|
||||
|
||||
total: string;
|
||||
totalLabel: string;
|
||||
showTotal: boolean;
|
||||
|
||||
subtotal: string;
|
||||
subtotalLabel: string;
|
||||
showSubtotal: boolean;
|
||||
|
||||
lines: Array<{
|
||||
invoiceNumber: string;
|
||||
invoiceAmount: string;
|
||||
paidAmount: string;
|
||||
}>;
|
||||
|
||||
showPaymentReceivedNumber: boolean;
|
||||
paymentReceivedNumberLabel: string;
|
||||
paymentReceivedNumebr: string;
|
||||
|
||||
paymentReceivedDate: string;
|
||||
showPaymentReceivedDate: boolean;
|
||||
paymentReceivedDateLabel: string;
|
||||
}
|
||||
|
||||
export interface IPaymentReceivedState {
|
||||
defaultTemplateId: number;
|
||||
}
|
||||
|
||||
export interface SendPaymentReceivedMailPayload extends TenantJobPayload {
|
||||
paymentReceivedId: number;
|
||||
messageOptions: PaymentReceiveMailOptsDTO;
|
||||
}
|
||||
33
packages/server/src/modules/PaymentReceived/utils.ts
Normal file
33
packages/server/src/modules/PaymentReceived/utils.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// @ts-nocheck
|
||||
import { PaymentReceived } from './models/PaymentReceived';
|
||||
import {
|
||||
PaymentReceivedPdfTemplateAttributes,
|
||||
} from './types/PaymentReceived.types';
|
||||
import { contactAddressTextFormat } from '@/utils/address-text-format';
|
||||
|
||||
export const transformPaymentReceivedToPdfTemplate = (
|
||||
payment: PaymentReceived
|
||||
): Partial<PaymentReceivedPdfTemplateAttributes> => {
|
||||
return {
|
||||
total: payment.formattedAmount,
|
||||
subtotal: payment.subtotalFormatted,
|
||||
paymentReceivedNumebr: payment.paymentReceiveNo,
|
||||
paymentReceivedDate: payment.formattedPaymentDate,
|
||||
customerName: payment.customer.displayName,
|
||||
lines: payment.entries.map((entry) => ({
|
||||
invoiceNumber: entry.invoice.invoiceNo,
|
||||
invoiceAmount: entry.invoice.totalFormatted,
|
||||
paidAmount: entry.paymentAmountFormatted,
|
||||
})),
|
||||
customerAddress: contactAddressTextFormat(payment.customer),
|
||||
};
|
||||
};
|
||||
|
||||
export const transformPaymentReceivedToMailDataArgs = (payment: any) => {
|
||||
return {
|
||||
'Customer Name': payment.customer.displayName,
|
||||
'Payment Number': payment.paymentReceiveNo,
|
||||
'Payment Date': payment.formattedPaymentDate,
|
||||
'Payment Amount': payment.formattedAmount,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user