This commit is contained in:
Ahmed Bouhuolia
2026-01-16 00:23:16 +02:00
parent 2bbc154f18
commit c21301061f
15 changed files with 87 additions and 45 deletions

View File

@@ -11,7 +11,7 @@ import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'
useFactory: (configService: ConfigService) => {
// Use in-memory storage with very high limits for test environment
const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
if (isTest) {
return {
throttlers: [
@@ -35,10 +35,11 @@ import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'
const password = configService.get<string>('redis.password');
const db = configService.get<number>('redis.db');
const globalTtl = configService.get<number>('throttle.global.ttl');
const globalLimit = configService.get<number>('throttle.global.limit');
const authTtl = configService.get<number>('throttle.auth.ttl');
const authLimit = configService.get<number>('throttle.auth.limit');
// Ensure we always have valid numbers with fallback defaults
const globalTtl = configService.get<number>('throttle.global.ttl') ?? 60000;
const globalLimit = configService.get<number>('throttle.global.limit') ?? 100;
const authTtl = configService.get<number>('throttle.auth.ttl') ?? 60000;
const authLimit = configService.get<number>('throttle.auth.limit') ?? 10;
return {
throttlers: [

View File

@@ -4,8 +4,11 @@ import { TransactionsByReferenceRepository } from './TransactionsByReferenceRepo
import { TransactionsByReferenceService } from './TransactionsByReference.service';
import { TransactionsByReferenceController } from './TransactionsByReference.controller';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
import { AccountsModule } from '@/modules/Accounts/Accounts.module';
@Module({
imports: [FinancialSheetCommonModule, AccountsModule],
providers: [
TransactionsByReferenceRepository,
TransactionsByReferenceApplication,
@@ -14,4 +17,4 @@ import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
],
controllers: [TransactionsByReferenceController],
})
export class TransactionsByReferenceModule {}
export class TransactionsByReferenceModule { }

View File

@@ -1,6 +1,6 @@
import { Controller, Get, Query } from '@nestjs/common';
import { TransactionsByReferenceApplication } from './TransactionsByReferenceApplication';
import { ITransactionsByReferenceQuery } from './TransactionsByReference.types';
import { TransactionsByReferenceQueryDto } from './TransactionsByReferenceQuery.dto';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
@Controller('reports/transactions-by-reference')
@@ -8,13 +8,13 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
export class TransactionsByReferenceController {
constructor(
private readonly transactionsByReferenceApp: TransactionsByReferenceApplication,
) {}
) { }
@Get()
@ApiResponse({ status: 200, description: 'Transactions by reference' })
@ApiOperation({ summary: 'Get transactions by reference' })
async getTransactionsByReference(
@Query() query: ITransactionsByReferenceQuery,
@Query() query: TransactionsByReferenceQueryDto,
) {
const data = await this.transactionsByReferenceApp.getTransactions(query);

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import {
ITransactionsByReferencePojo,
ITransactionsByReferenceQuery,
@@ -13,7 +13,7 @@ export class TransactionsByReferenceService {
constructor(
private readonly repository: TransactionsByReferenceRepository,
private readonly tenancyContext: TenancyContext
) {}
) { }
/**
* Retrieve accounts transactions by given reference id and type.
@@ -23,6 +23,12 @@ export class TransactionsByReferenceService {
public async getTransactionsByReference(
query: ITransactionsByReferenceQuery
): Promise<ITransactionsByReferencePojo> {
// Validate referenceId is a valid positive number
const referenceId = Number(query.referenceId);
if (isNaN(referenceId) || referenceId <= 0) {
throw new BadRequestException('referenceId must be a valid positive number');
}
const filter = {
...getTransactionsByReferenceQuery(),
...query,
@@ -31,7 +37,7 @@ export class TransactionsByReferenceService {
// Retrieve the accounts transactions of the given reference.
const transactions = await this.repository.getTransactions(
Number(filter.referenceId),
referenceId,
filter.referenceType
);
// Transactions by reference report.

View File

@@ -0,0 +1,22 @@
import { IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class TransactionsByReferenceQueryDto {
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The type of the reference (e.g., SaleInvoice, Bill, etc.)',
example: 'SaleInvoice',
required: true,
})
referenceType: string;
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The ID of the reference',
example: '1',
required: true,
})
referenceId: string;
}

View File

@@ -47,18 +47,18 @@ export class TransactionsByReference extends FinancialSheet {
debit: this.getAmountMeta(transaction.debit, { money: false }),
// @ts-ignore
// referenceTypeFormatted: transaction.referenceTypeFormatted,
referenceTypeFormatted: '',
// formattedReferenceType: transaction.referenceTypeFormatted,
formattedReferenceType: '',
referenceType: transaction.referenceType,
referenceId: transaction.referenceId,
contactId: transaction.contactId,
contactType: transaction.contactType,
contactTypeFormatted: transaction.contactType,
formattedContactType: transaction.contactType || '',
accountName: transaction.account.name,
accountCode: transaction.account.code,
accountName: transaction.account?.name || '',
accountCode: transaction.account?.code || '',
accountId: transaction.accountId,
};
};

View File

@@ -32,7 +32,7 @@ export class PaymentReceivedValidators {
@Inject(Account.name)
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
) { }
/**
* Validates the payment existance.
@@ -79,11 +79,11 @@ export class PaymentReceivedValidators {
const invoicesIds = paymentReceiveEntries
.map((e: { invoiceId: number }) => e.invoiceId)
.filter((id): id is number => id !== undefined && id !== null);
if (invoicesIds.length === 0) {
throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND);
}
const storedInvoices = await this.saleInvoiceModel()
.query()
.whereIn('id', invoicesIds)

View File

@@ -1,6 +1,7 @@
import {
Controller,
Post,
Put,
Get,
Delete,
Param,
@@ -45,7 +46,7 @@ export class RolesController {
};
}
@Post(':id')
@Put(':id')
@ApiOperation({ summary: 'Edit an existing role' })
@ApiParam({ name: 'id', description: 'Role ID' })
@ApiBody({ type: EditRoleDto })

View File

@@ -17,7 +17,7 @@ export class EditRoleService {
@Inject(Role.name)
private readonly roleModel: TenantModelProxy<typeof Role>,
) {}
) { }
/**
* Edits details of the given role on the storage.
@@ -30,7 +30,13 @@ export class EditRoleService {
// Retrieve the given role or throw not found serice error.
const oldRole = await this.roleModel().query().findById(roleId);
const permissions = editRoleDTO.permissions;
// Transform permissions: map permissionId to id for Objection.js upsertGraph
const permissions = editRoleDTO.permissions.map((perm) => ({
id: perm.permissionId,
subject: perm.subject,
ability: perm.ability,
value: perm.value,
}));
// Updates the role on the storage.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {

View File

@@ -38,7 +38,7 @@ export class CommandRolePermissionDto {
value: boolean;
}
export class CreateRolePermissionDto extends CommandRolePermissionDto {}
export class CreateRolePermissionDto extends CommandRolePermissionDto { }
export class EditRolePermissionDto extends CommandRolePermissionDto {
@IsNumber()
@IsNotEmpty()
@@ -83,9 +83,9 @@ export class EditRoleDto extends CommandRoleDto {
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => CommandRolePermissionDto)
@Type(() => EditRolePermissionDto)
@ApiProperty({
type: [CommandRolePermissionDto],
type: [EditRolePermissionDto],
description: 'The permissions of the role',
})
permissions: Array<EditRolePermissionDto>;

View File

@@ -168,7 +168,7 @@ export class SaleReceiptsController {
@Headers('accept') acceptHeader: string,
@Res({ passthrough: true }) res: Response,
) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
if (acceptHeader?.includes(AcceptType.ApplicationPdf)) {
const [pdfContent] =
await this.saleReceiptApplication.getSaleReceiptPdf(id);
@@ -177,7 +177,7 @@ export class SaleReceiptsController {
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
} else if (acceptHeader.includes(AcceptType.ApplicationTextHtml)) {
} else if (acceptHeader?.includes(AcceptType.ApplicationTextHtml)) {
const htmlContent =
await this.saleReceiptApplication.getSaleReceiptHtml(id);

View File

@@ -38,7 +38,7 @@ export class CreateSaleReceipt {
@Inject(Customer.name)
private readonly customerModel: TenantModelProxy<typeof Customer>,
) {}
) { }
/**
* Creates a new sale receipt with associated entries.
@@ -89,7 +89,7 @@ export class CreateSaleReceipt {
// Inserts the sale receipt graph to the storage.
const saleReceipt = await this.saleReceiptModel()
.query()
.query(trx)
.upsertGraph({
...saleReceiptObj,
});