diff --git a/packages/server/src/modules/App/AppThrottle.module.ts b/packages/server/src/modules/App/AppThrottle.module.ts index 76011e032..6527132dc 100644 --- a/packages/server/src/modules/App/AppThrottle.module.ts +++ b/packages/server/src/modules/App/AppThrottle.module.ts @@ -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('redis.password'); const db = configService.get('redis.db'); - const globalTtl = configService.get('throttle.global.ttl'); - const globalLimit = configService.get('throttle.global.limit'); - const authTtl = configService.get('throttle.auth.ttl'); - const authLimit = configService.get('throttle.auth.limit'); + // Ensure we always have valid numbers with fallback defaults + const globalTtl = configService.get('throttle.global.ttl') ?? 60000; + const globalLimit = configService.get('throttle.global.limit') ?? 100; + const authTtl = configService.get('throttle.auth.ttl') ?? 60000; + const authLimit = configService.get('throttle.auth.limit') ?? 10; return { throttlers: [ diff --git a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionByReference.module.ts b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionByReference.module.ts index 347878b51..26810a397 100644 --- a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionByReference.module.ts +++ b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionByReference.module.ts @@ -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 { } diff --git a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.controller.ts b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.controller.ts index 16fa41077..cad2f1cd4 100644 --- a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.controller.ts @@ -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); diff --git a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.service.ts b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.service.ts index 1d8d0daab..6ef5e9ee5 100644 --- a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.service.ts +++ b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReference.service.ts @@ -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 { + // 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. diff --git a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReferenceQuery.dto.ts b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReferenceQuery.dto.ts new file mode 100644 index 000000000..fe591c018 --- /dev/null +++ b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReferenceQuery.dto.ts @@ -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; +} diff --git a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReferenceReport.ts b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReferenceReport.ts index e1f5f8ec6..5ac840b80 100644 --- a/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReferenceReport.ts +++ b/packages/server/src/modules/FinancialStatements/modules/TransactionsByReference/TransactionsByReferenceReport.ts @@ -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, }; }; diff --git a/packages/server/src/modules/PaymentReceived/commands/PaymentReceivedValidators.service.ts b/packages/server/src/modules/PaymentReceived/commands/PaymentReceivedValidators.service.ts index c3100aff5..47c0646c5 100644 --- a/packages/server/src/modules/PaymentReceived/commands/PaymentReceivedValidators.service.ts +++ b/packages/server/src/modules/PaymentReceived/commands/PaymentReceivedValidators.service.ts @@ -32,7 +32,7 @@ export class PaymentReceivedValidators { @Inject(Account.name) private readonly accountModel: TenantModelProxy, - ) {} + ) { } /** * 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) diff --git a/packages/server/src/modules/Roles/Roles.controller.ts b/packages/server/src/modules/Roles/Roles.controller.ts index 63993fc19..ccd1e72a1 100644 --- a/packages/server/src/modules/Roles/Roles.controller.ts +++ b/packages/server/src/modules/Roles/Roles.controller.ts @@ -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 }) diff --git a/packages/server/src/modules/Roles/commands/EditRole.service.ts b/packages/server/src/modules/Roles/commands/EditRole.service.ts index a37a64fdf..a7ab3b566 100644 --- a/packages/server/src/modules/Roles/commands/EditRole.service.ts +++ b/packages/server/src/modules/Roles/commands/EditRole.service.ts @@ -17,7 +17,7 @@ export class EditRoleService { @Inject(Role.name) private readonly roleModel: TenantModelProxy, - ) {} + ) { } /** * 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) => { diff --git a/packages/server/src/modules/Roles/dtos/Role.dto.ts b/packages/server/src/modules/Roles/dtos/Role.dto.ts index a2a1090bf..02b04c876 100644 --- a/packages/server/src/modules/Roles/dtos/Role.dto.ts +++ b/packages/server/src/modules/Roles/dtos/Role.dto.ts @@ -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; diff --git a/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts b/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts index 7b73f6d76..08523bf6b 100644 --- a/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts +++ b/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts @@ -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); diff --git a/packages/server/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts b/packages/server/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts index 556bb49d7..c685a0295 100644 --- a/packages/server/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts +++ b/packages/server/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts @@ -38,7 +38,7 @@ export class CreateSaleReceipt { @Inject(Customer.name) private readonly customerModel: TenantModelProxy, - ) {} + ) { } /** * 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, }); diff --git a/packages/server/test/financial-statements.e2e-spec.ts b/packages/server/test/financial-statements.e2e-spec.ts index 0558dee28..bcde773a0 100644 --- a/packages/server/test/financial-statements.e2e-spec.ts +++ b/packages/server/test/financial-statements.e2e-spec.ts @@ -163,7 +163,11 @@ describe('Financial Statements (e2e)', () => { it('/reports/transactions-by-reference (GET)', () => { return request(app.getHttpServer()) .get('/reports/transactions-by-reference') - .query(baseQuery) + .query({ + ...baseQuery, + referenceId: '1', + referenceType: 'SaleInvoice', + }) .set('organization-id', orgainzationId) .set('Authorization', AuthorizationHeader) .expect(200); diff --git a/packages/server/test/roles.e2e-spec.ts b/packages/server/test/roles.e2e-spec.ts index d055edb8d..e60553d20 100644 --- a/packages/server/test/roles.e2e-spec.ts +++ b/packages/server/test/roles.e2e-spec.ts @@ -7,13 +7,13 @@ const createRoleRequest = () => ({ roleDescription: faker.lorem.sentence(), permissions: [ { - subject: 'items', - ability: 'read', + subject: 'Item', + ability: 'View', value: true, }, { - subject: 'items', - ability: 'create', + subject: 'Item', + ability: 'Create', value: true, }, ], @@ -27,7 +27,7 @@ describe('Roles (e2e)', () => { .set('organization-id', orgainzationId) .set('Authorization', AuthorizationHeader) .send(createRoleRequest()) - .expect(200); + .expect(201); }); it('/roles (GET)', () => { @@ -61,16 +61,16 @@ describe('Roles (e2e)', () => { .expect(200); }); - it('/roles/:id (POST)', async () => { - const response = await request(app.getHttpServer()) + it('/roles/:id (PUT)', async () => { + const createResponse = await request(app.getHttpServer()) .post('/roles') .set('organization-id', orgainzationId) .set('Authorization', AuthorizationHeader) .send(createRoleRequest()); - const roleId = response.body.data.id; + const roleId = createResponse.body.data.id; return request(app.getHttpServer()) - .post(`/roles/${roleId}`) + .put(`/roles/${roleId}`) .set('organization-id', orgainzationId) .set('Authorization', AuthorizationHeader) .send({ @@ -78,9 +78,8 @@ describe('Roles (e2e)', () => { roleDescription: faker.lorem.sentence(), permissions: [ { - permissionId: 1, - subject: 'items', - ability: 'read', + subject: 'Item', + ability: 'View', value: true, }, ], @@ -94,7 +93,7 @@ describe('Roles (e2e)', () => { .set('organization-id', orgainzationId) .set('Authorization', AuthorizationHeader) .send(createRoleRequest()); - const roleId = response.body.data.roleId; + const roleId = response.body.data.id; return request(app.getHttpServer()) .delete(`/roles/${roleId}`) diff --git a/packages/server/test/sale-receipts.e2e-spec.ts b/packages/server/test/sale-receipts.e2e-spec.ts index 508438a67..1dc6b784c 100644 --- a/packages/server/test/sale-receipts.e2e-spec.ts +++ b/packages/server/test/sale-receipts.e2e-spec.ts @@ -49,7 +49,7 @@ describe('Sale Receipts (e2e)', () => { costPrice: 100, sellPrice: 100, }); - itemId = parseInt(item.text, 10); + itemId = parseInt(item.body.id, 10); }); it('/sale-reeipts (POST)', () => {