refactor(nestjs): wip

This commit is contained in:
Ahmed Bouhuolia
2025-05-28 21:32:48 +02:00
parent c51347d3ec
commit 66a2261e50
32 changed files with 206 additions and 70 deletions

View File

@@ -89,6 +89,7 @@ import { CurrenciesModule } from '../Currencies/Currencies.module';
import { MiscellaneousModule } from '../Miscellaneous/Miscellaneous.module';
import { UsersModule } from '../UsersModule/Users.module';
import { ContactsModule } from '../Contacts/Contacts.module';
import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
@Module({
imports: [
@@ -186,6 +187,7 @@ import { ContactsModule } from '../Contacts/Contacts.module';
BankingTransactionsExcludeModule,
BankingTransactionsRegonizeModule,
BankingMatchingModule,
BankingPlaidModule,
TransactionsLockingModule,
SettingsModule,
FeaturesModule,

View File

@@ -0,0 +1,22 @@
import { Body, Controller, Post } from '@nestjs/common';
import { PlaidApplication } from './PlaidApplication';
import { PlaidItemDto } from './dtos/PlaidItem.dto';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller('banking/plaid')
@ApiTags('banking-plaid')
export class BankingPlaidController {
constructor(private readonly plaidApplication: PlaidApplication) {}
@Post('link-token')
@ApiOperation({ summary: 'Get Plaid link token' })
getLinkToken() {
return this.plaidApplication.getLinkToken();
}
@Post('exchange-token')
@ApiOperation({ summary: 'Exchange Plaid access token' })
exchangeToken(@Body() itemDTO: PlaidItemDto) {
return this.plaidApplication.exchangeToken(itemDTO);
}
}

View File

@@ -15,6 +15,7 @@ import { PlaidItemService } from './command/PlaidItem';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
import { SystemPlaidItem } from './models/SystemPlaidItem';
import { BankingPlaidController } from './BankingPlaid.controller';
const models = [RegisterTenancyModel(PlaidItem)];
@@ -38,5 +39,6 @@ const models = [RegisterTenancyModel(PlaidItem)];
TenancyContext,
],
exports: [...models],
controllers: [BankingPlaidController]
})
export class BankingPlaidModule {}

View File

@@ -3,6 +3,7 @@ import { PlaidItemService } from './command/PlaidItem';
import { PlaidWebooks } from './command/PlaidWebhooks';
import { Injectable } from '@nestjs/common';
import { PlaidItemDTO } from './types/BankingPlaid.types';
import { PlaidItemDto } from './dtos/PlaidItem.dto';
@Injectable()
export class PlaidApplication {
@@ -25,7 +26,7 @@ export class PlaidApplication {
* @param {PlaidItemDTO} itemDTO
* @returns
*/
public exchangeToken(itemDTO: PlaidItemDTO): Promise<void> {
public exchangeToken(itemDTO: PlaidItemDto): Promise<void> {
return this.plaidItemService.item(itemDTO);
}

View File

@@ -6,11 +6,9 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { SystemPlaidItem } from '../models/SystemPlaidItem';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import {
IPlaidItemCreatedEventPayload,
PlaidItemDTO,
} from '../types/BankingPlaid.types';
import { IPlaidItemCreatedEventPayload } from '../types/BankingPlaid.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { PlaidItemDto } from '../dtos/PlaidItem.dto';
@Injectable()
export class PlaidItemService {
@@ -33,10 +31,10 @@ export class PlaidItemService {
/**
* Exchanges the public token to get access token and item id and then creates
* a new Plaid item.
* @param {PlaidItemDTO} itemDTO - Plaid item data transfer object.
* @param {PlaidItemDto} itemDTO - Plaid item data transfer object.
* @returns {Promise<void>}
*/
public async item(itemDTO: PlaidItemDTO): Promise<void> {
public async item(itemDTO: PlaidItemDto): Promise<void> {
const { publicToken, institutionId } = itemDTO;
const tenant = await this.tenancyContext.getTenant();

View File

@@ -15,6 +15,13 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class PlaidUpdateTransactions {
/**
* Constructor method.
* @param {PlaidSyncDb} plaidSync - Plaid sync service.
* @param {UnitOfWork} uow - Unit of work.
* @param {TenantModelProxy<typeof PlaidItem>} plaidItemModel - Plaid item model.
* @param {PlaidApi} plaidClient - Plaid client.
*/
constructor(
private readonly plaidSync: PlaidSyncDb,
private readonly uow: UnitOfWork,
@@ -28,8 +35,7 @@ export class PlaidUpdateTransactions {
/**
* Handles sync the Plaid item to Bigcaptial under UOW.
* @param {number} tenantId - Tenant id.
* @param {number} plaidItemId - Plaid item id.
* @param {string} plaidItemId - Plaid item id.
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
*/
public async updateTransactions(plaidItemId: string) {
@@ -44,9 +50,9 @@ export class PlaidUpdateTransactions {
* - New bank accounts.
* - Last accounts feeds updated at.
* - Turn on the accounts feed flag.
* @param {number} tenantId - Tenant ID.
* @param {string} plaidItemId - The Plaid ID for the item.
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
*/
public async updateTransactionsWork(
plaidItemId: string,

View File

@@ -0,0 +1,11 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class PlaidItemDto {
@IsString()
@IsNotEmpty()
publicToken: string;
@IsString()
@IsNotEmpty()
institutionId: string;
}

View File

@@ -1,5 +1,5 @@
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Body, Controller, Delete, Param, Post } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
import { ICreditNoteRefundDTO } from '../CreditNotes/types/CreditNotes.types';
import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.service';
import { RefundCreditNote } from './models/RefundCreditNote';
@@ -12,6 +12,14 @@ export class CreditNoteRefundsController {
private readonly creditNotesRefundsApplication: CreditNotesRefundsApplication,
) {}
@Get(':creditNoteId/refunds')
@ApiOperation({ summary: 'Retrieve the credit note graph.' })
getCreditNoteRefunds(@Param('creditNoteId') creditNoteId: number) {
return this.creditNotesRefundsApplication.getCreditNoteRefunds(
creditNoteId,
);
}
/**
* Create a refund credit note.
* @param {number} creditNoteId - The credit note ID.

View File

@@ -6,6 +6,7 @@ import { RefundSyncCreditNoteBalanceService } from './commands/RefundSyncCreditN
import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.service';
import { CreditNoteRefundsController } from './CreditNoteRefunds.controller';
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
@Module({
imports: [forwardRef(() => CreditNotesModule)],
@@ -15,10 +16,9 @@ import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
RefundCreditNoteService,
RefundSyncCreditNoteBalanceService,
CreditNotesRefundsApplication,
GetCreditNoteRefundsService,
],
exports: [
RefundSyncCreditNoteBalanceService
],
exports: [RefundSyncCreditNoteBalanceService],
controllers: [CreditNoteRefundsController],
})
export class CreditNoteRefundsModule {}

View File

@@ -5,16 +5,27 @@ import { DeleteRefundCreditNoteService } from './commands/DeleteRefundCreditNote
import { RefundCreditNoteService } from './commands/RefundCreditNote.service';
import { RefundSyncCreditNoteBalanceService } from './commands/RefundSyncCreditNoteBalance';
import { CreditNoteRefundDto } from './dto/CreditNoteRefund.dto';
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
@Injectable()
export class CreditNotesRefundsApplication {
constructor(
private readonly createRefundCreditNoteService: CreateRefundCreditNoteService,
private readonly deleteRefundCreditNoteService: DeleteRefundCreditNoteService,
private readonly getCreditNoteRefundsService: GetCreditNoteRefundsService,
private readonly refundCreditNoteService: RefundCreditNoteService,
private readonly refundSyncCreditNoteBalanceService: RefundSyncCreditNoteBalanceService,
) {}
/**
* Retrieve the credit note graph.
* @param {number} creditNoteId - Credit note id.
* @returns {Promise<IRefundCreditNotePOJO[]>}
*/
public getCreditNoteRefunds(creditNoteId: number) {
return this.getCreditNoteRefundsService.getCreditNoteRefunds(creditNoteId);
}
/**
* Create a refund credit note.
* @param {number} creditNoteId - The credit note ID.

View File

@@ -6,7 +6,7 @@ import { IRefundCreditNotePOJO } from '../types/CreditNoteRefunds.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ListCreditNoteRefunds {
export class GetCreditNoteRefundsService {
constructor(
private readonly transformer: TransformerInjectable,
@@ -18,7 +18,7 @@ export class ListCreditNoteRefunds {
/**
* Retrieve the credit note graph.
* @param {number} creditNoteId
* @param {number} creditNoteId - Credit note id.
* @returns {Promise<IRefundCreditNotePOJO[]>}
*/
public async getCreditNoteRefunds(

View File

@@ -0,0 +1,39 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service';
@Controller('credit-notes')
@ApiTags('credit-notes-apply-invoice')
export class CreditNotesApplyInvoiceController {
constructor(
private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices,
) {}
@Get(':creditNoteId/applied-invoices')
@ApiOperation({ summary: 'Applied credit note to invoices' })
@ApiResponse({
status: 200,
description: 'Credit note successfully applied to invoices',
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
appliedCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) {
return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices(
creditNoteId,
);
}
@Post(':creditNoteId/apply-invoices')
@ApiOperation({ summary: 'Apply credit note to invoices' })
@ApiResponse({
status: 200,
description: 'Credit note successfully applied to invoices',
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
applyCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) {
return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices(
creditNoteId,
);
}
}

View File

@@ -8,6 +8,7 @@ import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.modu
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service';
import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service';
import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.controller';
@Module({
providers: [
@@ -16,12 +17,11 @@ import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteA
CreditNoteApplyToInvoices,
CreditNoteApplySyncInvoicesCreditedAmount,
CreditNoteApplySyncCredit,
// GetCreditNoteAssociatedAppliedInvoices,
// GetCreditNoteAssociatedInvoicesToApply
],
exports: [
DeleteCustomerLinkedCreditNoteService,
GetCreditNoteAssociatedAppliedInvoices,
GetCreditNoteAssociatedInvoicesToApply,
],
exports: [DeleteCustomerLinkedCreditNoteService],
imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)],
controllers: [CreditNotesApplyInvoiceController],
})
export class CreditNotesApplyInvoiceModule {}

View File

@@ -7,9 +7,16 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetCreditNoteAssociatedAppliedInvoices {
/**
* @param {TransformerInjectable} transformer - The transformer service.
* @param {TenantModelProxy<typeof CreditNoteAppliedInvoice>} creditNoteAppliedInvoiceModel - The credit note applied invoice model.
* @param {TenantModelProxy<typeof CreditNote>} creditNoteModel - The credit note model.
*/
constructor(
private readonly transformer: TransformerInjectable,
private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
@Inject(CreditNoteAppliedInvoice.name)
private readonly creditNoteAppliedInvoiceModel: TenantModelProxy<typeof CreditNoteAppliedInvoice>,
@Inject(CreditNote.name)
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
@@ -29,7 +36,7 @@ export class GetCreditNoteAssociatedAppliedInvoices {
.findById(creditNoteId)
.throwIfNotFound();
const appliedToInvoices = await this.creditNoteAppliedInvoiceModel
const appliedToInvoices = await this.creditNoteAppliedInvoiceModel()
.query()
.where('credit_note_id', creditNoteId)
.withGraphFetched('saleInvoice')

View File

@@ -10,7 +10,7 @@ export class GetCreditNoteAssociatedInvoicesToApply {
/**
* @param {TransformerInjectable} transformer - Transformer service.
* @param {GetCreditNote} getCreditNote - Get credit note service.
* @param {typeof SaleInvoice} saleInvoiceModel - Sale invoice model.
* @param {TenantModelProxy<typeof SaleInvoice>} saleInvoiceModel - Sale invoice model.
*/
constructor(
private transformer: TransformerInjectable,

View File

@@ -24,7 +24,7 @@ export class FeaturesConfigure {
},
{
name: Features.BankSyncing,
defaultValue: this.configService.get('bankSync.enabled') ?? false,
defaultValue: this.configService.get('bankfeed.enabled') ?? false,
},
];
}

View File

@@ -74,11 +74,10 @@ export class EditItemCategoryService {
);
// Creates item category under unit-of-work evnirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
//
const itemCategory = await ItemCategory.query().patchAndFetchById(
itemCategoryId,
{ ...itemCategoryObj },
);
const itemCategory = await this.itemCategoryModel()
.query(trx)
.patchAndFetchById(itemCategoryId, { ...itemCategoryObj });
// Triggers `onItemCategoryEdited` event.
await this.eventEmitter.emitAsync(events.itemCategory.onEdited, {
oldItemCategory,

View File

@@ -1,6 +1,7 @@
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { IsOptional, IsString } from 'class-validator';
import { IsString } from 'class-validator';
import { IsNotEmpty } from 'class-validator';
class CommandItemCategoryDto {
@@ -17,16 +18,19 @@ class CommandItemCategoryDto {
})
description?: string;
@ToNumber()
@IsNumber()
@IsOptional()
@ApiProperty({ example: 1, description: 'The cost account ID' })
costAccountId?: number;
@ToNumber()
@IsNumber()
@IsOptional()
@ApiProperty({ example: 1, description: 'The sell account ID' })
sellAccountId?: number;
@ToNumber()
@IsNumber()
@IsOptional()
@ApiProperty({ example: 1, description: 'The inventory account ID' })

View File

@@ -18,6 +18,7 @@ import {
CreatePaymentReceivedDto,
EditPaymentReceivedDto,
} from './dtos/PaymentReceived.dto';
import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service';
@Injectable()
export class PaymentReceivesApplication {
@@ -31,6 +32,7 @@ export class PaymentReceivesApplication {
private sendPaymentReceiveMailNotification: SendPaymentReceiveMailNotification,
private getPaymentReceivePdfService: GetPaymentReceivedPdfService,
private getPaymentReceivedStateService: GetPaymentReceivedStateService,
private paymentsReceivedPagesService: PaymentsReceivedPagesService,
) {}
/**
@@ -147,4 +149,14 @@ export class PaymentReceivesApplication {
public getPaymentReceivedState() {
return this.getPaymentReceivedStateService.getPaymentReceivedState();
}
/**
* Retrieve the payment received edit page.
* @param {number} paymentReceiveId - Payment receive id.
*/
public getPaymentReceivedEditPage(paymentReceiveId: number) {
return this.paymentsReceivedPagesService.getPaymentReceiveEditPage(
paymentReceiveId,
);
}
}

View File

@@ -44,6 +44,20 @@ export class PaymentReceivesController {
);
}
@Get(':id/edit-page')
@ApiResponse({
status: 200,
description:
'The payment received edit page has been successfully retrieved.',
})
public getPaymentReceiveEditPage(
@Param('id', ParseIntPipe) paymentReceiveId: number,
) {
return this.paymentReceivesApplication.getPaymentReceivedEditPage(
paymentReceiveId,
);
}
@Get(':id/mail')
@ApiResponse({
status: 200,

View File

@@ -36,6 +36,7 @@ import { SendPaymentReceivedMailProcessor } from './processors/PaymentReceivedMa
import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants';
import { PaymentsReceivedExportable } from './commands/PaymentsReceivedExportable';
import { PaymentsReceivedImportable } from './commands/PaymentsReceivedImportable';
import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service';
@Module({
controllers: [PaymentReceivesController],
@@ -63,6 +64,7 @@ import { PaymentsReceivedImportable } from './commands/PaymentsReceivedImportabl
SendPaymentReceivedMailProcessor,
PaymentsReceivedExportable,
PaymentsReceivedImportable,
PaymentsReceivedPagesService
],
exports: [
PaymentReceivesApplication,

View File

@@ -41,11 +41,10 @@ export class PaymentsReceivedPagesService {
/**
* 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) {
public async getNewPageEntries(customerId: number) {
// Retrieve due invoices.
const entries = await this.saleInvoice()
.query()
@@ -62,10 +61,7 @@ export class PaymentsReceivedPagesService {
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id.
*/
public async getPaymentReceiveEditPage(
tenantId: number,
paymentReceiveId: number,
): Promise<{
public async getPaymentReceiveEditPage(paymentReceiveId: number): Promise<{
paymentReceive: Omit<PaymentReceived, 'entries'>;
entries: IPaymentReceivePageEntry[];
}> {

View File

@@ -232,7 +232,7 @@ export class SaleEstimatesController {
public async getSaleEstimate(
@Param('id', ParseIntPipe) estimateId: number,
@Headers('accept') acceptHeader: string,
@Res() res: Response,
@Res({ passthrough: true }) res: Response,
) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
const pdfContent =

View File

@@ -46,6 +46,12 @@ export class SaleReceiptsController {
return this.saleReceiptApplication.getSaleReceiptMail(id);
}
@Get('state')
@ApiOperation({ summary: 'Retrieves the sale receipt state.' })
getSaleReceiptState() {
return this.saleReceiptApplication.getSaleReceiptState();
}
@Get(':id/mail')
@HttpCode(200)
@ApiOperation({ summary: 'Retrieves the sale receipt mail.' })
@@ -86,7 +92,7 @@ export class SaleReceiptsController {
required: true,
type: Number,
description: 'The sale receipt id',
})
})
async getSaleReceipt(
@Param('id', ParseIntPipe) id: number,
@Headers('accept') acceptHeader: string,
@@ -135,10 +141,4 @@ export class SaleReceiptsController {
closeSaleReceipt(@Param('id', ParseIntPipe) id: number) {
return this.saleReceiptApplication.closeSaleReceipt(id);
}
@Get('state')
@ApiOperation({ summary: 'Retrieves the sale receipt state.' })
getSaleReceiptState() {
return this.saleReceiptApplication.getSaleReceiptState();
}
}