This commit is contained in:
Ahmed Bouhuolia
2026-01-15 22:04:42 +02:00
parent 3c1273becb
commit 2bbc154f18
34 changed files with 301 additions and 176 deletions

View File

@@ -9,6 +9,27 @@ import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'
imports: [ConfigModule], imports: [ConfigModule],
inject: [ConfigService], inject: [ConfigService],
useFactory: (configService: ConfigService) => { 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: [
{
name: 'default',
ttl: 60000,
limit: 1000000, // Effectively disable throttling in tests
},
{
name: 'auth',
ttl: 60000,
limit: 1000000, // Effectively disable throttling in tests
},
],
// No storage specified = uses in-memory storage
};
}
const host = configService.get<string>('redis.host') || 'localhost'; const host = configService.get<string>('redis.host') || 'localhost';
const port = Number(configService.get<number>('redis.port') || 6379); const port = Number(configService.get<number>('redis.port') || 6379);
const password = configService.get<string>('redis.password'); const password = configService.get<string>('redis.password');

View File

@@ -1,5 +1,5 @@
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'; import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { BankingMatchingApplication } from './BankingMatchingApplication'; import { BankingMatchingApplication } from './BankingMatchingApplication';
import { GetMatchedTransactionsFilter } from './types'; import { GetMatchedTransactionsFilter } from './types';
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto'; import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
@@ -34,7 +34,7 @@ export class BankingMatchingController {
); );
} }
@Post('/unmatch/:uncategorizedTransactionId') @Patch('/unmatch/:uncategorizedTransactionId')
@ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' }) @ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' })
async unmatchMatchedTransaction( async unmatchMatchedTransaction(
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number, @Param('uncategorizedTransactionId') uncategorizedTransactionId: number,

View File

@@ -11,10 +11,12 @@ import {
Post, Post,
Body, Body,
Put, Put,
Patch,
Param, Param,
Delete, Delete,
Get, Get,
Query, Query,
HttpCode,
} from '@nestjs/common'; } from '@nestjs/common';
import { BillsApplication } from './Bills.application'; import { BillsApplication } from './Bills.application';
import { IBillsFilter } from './Bills.types'; import { IBillsFilter } from './Bills.types';
@@ -40,6 +42,7 @@ export class BillsController {
@ApiOperation({ @ApiOperation({
summary: 'Validate which bills can be deleted and return the results.', summary: 'Validate which bills can be deleted and return the results.',
}) })
@HttpCode(200)
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: description:
@@ -56,6 +59,7 @@ export class BillsController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple bills.' }) @ApiOperation({ summary: 'Deletes multiple bills.' })
@HttpCode(200)
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Bills deleted successfully', description: 'Bills deleted successfully',
@@ -160,7 +164,7 @@ export class BillsController {
return this.billsApplication.getBill(billId); return this.billsApplication.getBill(billId);
} }
@Post(':id/open') @Patch(':id/open')
@ApiOperation({ summary: 'Open the given bill.' }) @ApiOperation({ summary: 'Open the given bill.' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',

View File

@@ -163,6 +163,41 @@ export class ItemsController extends TenantController {
return { id: itemId, message: 'The item has been successfully updated.' }; return { id: itemId, message: 'The item has been successfully updated.' };
} }
@Post('validate-bulk-delete')
@HttpCode(200)
@ApiOperation({
summary:
'Validates which items can be deleted and returns counts of deletable and non-deletable items.',
})
@ApiResponse({
status: 200,
description:
'Validation completed. Returns counts and IDs of deletable and non-deletable items.',
schema: {
$ref: getSchemaPath(ValidateBulkDeleteItemsResponseDto),
},
})
async validateBulkDeleteItems(
@Body() bulkDeleteDto: BulkDeleteItemsDto,
): Promise<ValidateBulkDeleteItemsResponseDto> {
return this.itemsApplication.validateBulkDeleteItems(bulkDeleteDto.ids);
}
@Post('bulk-delete')
@HttpCode(200)
@ApiOperation({ summary: 'Deletes multiple items in bulk.' })
@ApiResponse({
status: 200,
description: 'The items have been successfully deleted.',
})
async bulkDeleteItems(
@Body() bulkDeleteDto: BulkDeleteItemsDto,
): Promise<void> {
return this.itemsApplication.bulkDeleteItems(bulkDeleteDto.ids, {
skipUndeletable: bulkDeleteDto.skipUndeletable ?? false,
});
}
@Post() @Post()
@ApiOperation({ summary: 'Create a new item (product or service).' }) @ApiOperation({ summary: 'Create a new item (product or service).' })
@ApiResponse({ @ApiResponse({
@@ -352,37 +387,4 @@ export class ItemsController extends TenantController {
return this.itemsApplication.getItemReceiptsTransactions(itemId); return this.itemsApplication.getItemReceiptsTransactions(itemId);
} }
@Post('validate-bulk-delete')
@HttpCode(200)
@ApiOperation({
summary:
'Validates which items can be deleted and returns counts of deletable and non-deletable items.',
})
@ApiResponse({
status: 200,
description:
'Validation completed. Returns counts and IDs of deletable and non-deletable items.',
schema: {
$ref: getSchemaPath(ValidateBulkDeleteItemsResponseDto),
},
})
async validateBulkDeleteItems(
@Body() bulkDeleteDto: BulkDeleteItemsDto,
): Promise<ValidateBulkDeleteItemsResponseDto> {
return this.itemsApplication.validateBulkDeleteItems(bulkDeleteDto.ids);
}
@Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple items in bulk.' })
@ApiResponse({
status: 200,
description: 'The items have been successfully deleted.',
})
async bulkDeleteItems(
@Body() bulkDeleteDto: BulkDeleteItemsDto,
): Promise<void> {
return this.itemsApplication.bulkDeleteItems(bulkDeleteDto.ids, {
skipUndeletable: bulkDeleteDto.skipUndeletable ?? false,
});
}
} }

View File

@@ -229,7 +229,7 @@ export class PaymentReceivesController {
@Headers('accept') acceptHeader: string, @Headers('accept') acceptHeader: string,
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
) { ) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) { if (acceptHeader?.includes(AcceptType.ApplicationPdf)) {
const [pdfContent, filename] = await this.paymentReceivesApplication.getPaymentReceivePdf( const [pdfContent, filename] = await this.paymentReceivesApplication.getPaymentReceivePdf(
paymentReceiveId, paymentReceiveId,
); );
@@ -239,7 +239,7 @@ export class PaymentReceivesController {
'Content-Disposition': `attachment; filename="${filename}"`, 'Content-Disposition': `attachment; filename="${filename}"`,
}); });
res.send(pdfContent); res.send(pdfContent);
} else if (acceptHeader.includes(AcceptType.ApplicationTextHtml)) { } else if (acceptHeader?.includes(AcceptType.ApplicationTextHtml)) {
const htmlContent = const htmlContent =
await this.paymentReceivesApplication.getPaymentReceivedHtml( await this.paymentReceivesApplication.getPaymentReceivedHtml(
paymentReceiveId, paymentReceiveId,

View File

@@ -76,9 +76,14 @@ export class PaymentReceivedValidators {
customerId: number, customerId: number,
paymentReceiveEntries: { invoiceId: number }[], paymentReceiveEntries: { invoiceId: number }[],
): Promise<SaleInvoice[]> { ): Promise<SaleInvoice[]> {
const invoicesIds = paymentReceiveEntries.map( const invoicesIds = paymentReceiveEntries
(e: { invoiceId: number }) => e.invoiceId, .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() const storedInvoices = await this.saleInvoiceModel()
.query() .query()
.whereIn('id', invoicesIds) .whereIn('id', invoicesIds)

View File

@@ -5,11 +5,9 @@ import {
Delete, Delete,
Param, Param,
Body, Body,
Next,
HttpStatus, HttpStatus,
ParseIntPipe, ParseIntPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { NextFunction } from 'express';
import { CreateRoleDto, EditRoleDto } from './dtos/Role.dto'; import { CreateRoleDto, EditRoleDto } from './dtos/Role.dto';
import { RolesApplication } from './Roles.application'; import { RolesApplication } from './Roles.application';
import { import {
@@ -38,14 +36,11 @@ export class RolesController {
status: HttpStatus.OK, status: HttpStatus.OK,
description: 'Role created successfully', description: 'Role created successfully',
}) })
async createRole( async createRole(@Body() createRoleDto: CreateRoleDto) {
@Next() next: NextFunction,
@Body() createRoleDto: CreateRoleDto,
) {
const role = await this.rolesApp.createRole(createRoleDto); const role = await this.rolesApp.createRole(createRoleDto);
return { return {
data: { roleId: role.id }, data: { id: role.id },
message: 'The role has been created successfully.', message: 'The role has been created successfully.',
}; };
} }
@@ -65,7 +60,7 @@ export class RolesController {
const role = await this.rolesApp.editRole(roleId, editRoleDto); const role = await this.rolesApp.editRole(roleId, editRoleDto);
return { return {
data: { roleId }, data: { id: role.id },
message: 'The given role has been updated successfully.', message: 'The given role has been updated successfully.',
}; };
} }
@@ -81,7 +76,7 @@ export class RolesController {
await this.rolesApp.deleteRole(roleId); await this.rolesApp.deleteRole(roleId);
return { return {
data: { roleId }, data: { id: roleId },
message: 'The given role has been deleted successfully.', message: 'The given role has been deleted successfully.',
}; };
} }

View File

@@ -210,7 +210,7 @@ export class SaleInvoicesController {
@Headers('accept') acceptHeader: string, @Headers('accept') acceptHeader: string,
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
) { ) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) { if (acceptHeader?.includes(AcceptType.ApplicationPdf)) {
const [pdfContent, filename] = const [pdfContent, filename] =
await this.saleInvoiceApplication.saleInvoicePdf(id); await this.saleInvoiceApplication.saleInvoicePdf(id);
@@ -219,7 +219,7 @@ export class SaleInvoicesController {
'Content-Length': pdfContent.length, 'Content-Length': pdfContent.length,
}); });
res.send(pdfContent); res.send(pdfContent);
} else if (acceptHeader.includes(AcceptType.ApplicationTextHtml)) { } else if (acceptHeader?.includes(AcceptType.ApplicationTextHtml)) {
const htmlContent = await this.saleInvoiceApplication.saleInvoiceHtml(id); const htmlContent = await this.saleInvoiceApplication.saleInvoiceHtml(id);
return { htmlContent }; return { htmlContent };
} else { } else {

View File

@@ -1,4 +1,4 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { UsersApplication } from './Users.application'; import { UsersApplication } from './Users.application';
import { InviteUserDto, SendInviteUserDto } from './dtos/InviteUser.dto'; import { InviteUserDto, SendInviteUserDto } from './dtos/InviteUser.dto';
@@ -38,7 +38,7 @@ export class UsersInviteController {
/** /**
* Send an invitation to a new user. * Send an invitation to a new user.
*/ */
@Post() @Patch()
@ApiOperation({ summary: 'Send an invitation to a new user.' }) @ApiOperation({ summary: 'Send an invitation to a new user.' })
async sendInvite(@Body() sendInviteDTO: SendInviteUserDto) { async sendInvite(@Body() sendInviteDTO: SendInviteUserDto) {
const result = await this.usersApplication.sendInvite(sendInviteDTO); const result = await this.usersApplication.sendInvite(sendInviteDTO);

View File

@@ -52,7 +52,10 @@ export class EditUserService {
const tenantUser = await this.tenantUserModel() const tenantUser = await this.tenantUserModel()
.query() .query()
.updateAndFetchById(userId, { .updateAndFetchById(userId, {
...editUserDTO, firstName: editUserDTO.firstName,
lastName: editUserDTO.lastName,
email: editUserDTO.email,
roleId: editUserDTO.roleId,
}); });
// Triggers `onTenantUserEdited` event. // Triggers `onTenantUserEdited` event.
await this.eventEmitter.emitAsync(events.tenantUser.onEdited, { await this.eventEmitter.emitAsync(events.tenantUser.onEdited, {

View File

@@ -1,20 +1,49 @@
import * as request from 'supertest'; import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app, AuthorizationHeader, orgainzationId } from './init-app-test'; import { app, AuthorizationHeader, orgainzationId } from './init-app-test';
describe('Banking Accounts (e2e)', () => { describe('Banking Accounts (e2e)', () => {
// it('/banking/accounts (GET)', () => { it('/banking/accounts (GET)', () => {
// return request(app.getHttpServer()) return request(app.getHttpServer())
// .get('/banking/accounts') .get('/banking/accounts')
// .set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
// .set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
// .expect(200); .expect(200);
// }); });
// it('/banking/accounts/:bankAccountId/summary (GET)', () => { it('/banking/accounts/:bankAccountId/summary (GET)', async () => {
// return request(app.getHttpServer()) // First, get the list of bank accounts
// .get('/banking/accounts/1/summary') const accountsResponse = await request(app.getHttpServer())
// .set('organization-id', orgainzationId) .get('/banking/accounts')
// .set('Authorization', AuthorizationHeader) .set('organization-id', orgainzationId)
// .expect(200); .set('Authorization', AuthorizationHeader)
// }); .expect(200);
let bankAccountId: number;
// If there are no bank accounts, create one
if (accountsResponse.body.length === 0) {
const createResponse = await request(app.getHttpServer())
.post('/accounts')
.set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader)
.send({
name: `${faker.finance.accountName()} ${Date.now()}-${faker.string.alphanumeric({ length: 4 })}`,
accountType: 'bank',
code: faker.string.alphanumeric({ length: 6 }).toUpperCase(),
})
.expect(201);
bankAccountId = createResponse.body.id;
} else {
// Use the first bank account's ID
bankAccountId = accountsResponse.body[0].id;
}
return request(app.getHttpServer())
.get(`/banking/accounts/${bankAccountId}/summary`)
.set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader)
.expect(200);
});
}); });

View File

@@ -22,9 +22,9 @@ describe('Banking Matching (e2e)', () => {
.expect(200); .expect(200);
}); });
it('/banking/matching/unmatch/:uncategorizedTransactionId (POST)', () => { it('/banking/matching/unmatch/:uncategorizedTransactionId (PATCH)', () => {
return request(app.getHttpServer()) return request(app.getHttpServer())
.post('/banking/matching/unmatch/1') .patch('/banking/matching/unmatch/1')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.expect(200); .expect(200);

View File

@@ -42,7 +42,7 @@ describe('Bill Payments (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = parseInt(item.body.id, 10);
const bill = await request(app.getHttpServer()) const bill = await request(app.getHttpServer())
.post('/bills') .post('/bills')
@@ -53,8 +53,8 @@ describe('Bill Payments (e2e)', () => {
billDate: '2023-01-01', billDate: '2023-01-01',
dueDate: '2023-02-01', dueDate: '2023-02-01',
billNumber: faker.string.alphanumeric(10), billNumber: faker.string.alphanumeric(10),
branchId: 1, // branchId: 1,
warehouseId: 1, // warehouseId: 1,
entries: [ entries: [
{ {
index: 1, index: 1,
@@ -68,13 +68,15 @@ describe('Bill Payments (e2e)', () => {
billId = bill.body.id; billId = bill.body.id;
}); });
it('/bill-payments (POST)', () => { it('/bill-payments (POST)', async () => {
return request(app.getHttpServer()) const response = await request(app.getHttpServer())
.post('/bill-payments') .post('/bill-payments')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.send(createBillPaymentRequest()) .send(createBillPaymentRequest())
.expect(201); .expect(201);
console.log(response.body);
}); });
it('/bill-payments (GET)', () => { it('/bill-payments (GET)', () => {

View File

@@ -47,7 +47,7 @@ describe('Bills (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = parseInt(item.body.id, 10);
}); });
it('/bills (POST)', () => { it('/bills (POST)', () => {
@@ -113,7 +113,7 @@ describe('Bills (e2e)', () => {
.expect(200); .expect(200);
}); });
it('/bills/:id/open (POST)', async () => { it('/bills/:id/open (PATCH)', async () => {
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.post('/bills') .post('/bills')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
@@ -122,7 +122,7 @@ describe('Bills (e2e)', () => {
const billId = response.body.id; const billId = response.body.id;
return request(app.getHttpServer()) return request(app.getHttpServer())
.post(`/bills/${billId}/open`) .patch(`/bills/${billId}/open`)
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.expect(200); .expect(200);

View File

@@ -46,7 +46,7 @@ describe('Credit Notes (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = parseInt(item.body.id, 10);
}); });
it('/credit-notes (POST)', () => { it('/credit-notes (POST)', () => {

View File

@@ -5,7 +5,7 @@ import { AppModule } from '../src/modules/App/App.module';
let app: INestApplication; let app: INestApplication;
const email = 'big@big.com'; const email = 'bigcapital@bigcapital.com';
const password = '123123123'; const password = '123123123';
let orgainzationId = ''; let orgainzationId = '';
@@ -24,8 +24,6 @@ beforeAll(async () => {
.post('/auth/signin') .post('/auth/signin')
.send({ email, password }); .send({ email, password });
console.log(signinResponse.body);
authenticationToken = signinResponse.body.access_token; authenticationToken = signinResponse.body.access_token;
AuthorizationHeader = `Bearer ${authenticationToken}`; AuthorizationHeader = `Bearer ${authenticationToken}`;
orgainzationId = signinResponse.body.organization_id; orgainzationId = signinResponse.body.organization_id;
@@ -34,6 +32,6 @@ beforeAll(async () => {
afterAll(async () => { afterAll(async () => {
await app.close(); await app.close();
}); });
// jest.retryTimes(3, { logErrorsBeforeRetry: true }); jest.retryTimes(3, { logErrorsBeforeRetry: true });
export { app, orgainzationId, authenticationToken, AuthorizationHeader }; export { app, orgainzationId, authenticationToken, AuthorizationHeader };

View File

@@ -33,7 +33,7 @@ describe('Inventory Adjustments (e2e)', () => {
.send(makeItemRequest()) .send(makeItemRequest())
.expect(201); .expect(201);
const itemId = itemResponse.text; const itemId = itemResponse.body.id;
return request(app.getHttpServer()) return request(app.getHttpServer())
.post('/inventory-adjustments/quick') .post('/inventory-adjustments/quick')
@@ -51,7 +51,7 @@ describe('Inventory Adjustments (e2e)', () => {
.send(makeItemRequest()) .send(makeItemRequest())
.expect(201); .expect(201);
const itemId = itemResponse.text; const itemId = itemResponse.body.id;
const inventoryAdjustmentResponse = await request(app.getHttpServer()) const inventoryAdjustmentResponse = await request(app.getHttpServer())
.post('/inventory-adjustments/quick') .post('/inventory-adjustments/quick')
@@ -77,7 +77,7 @@ describe('Inventory Adjustments (e2e)', () => {
.send(makeItemRequest()) .send(makeItemRequest())
.expect(201); .expect(201);
const itemId = itemResponse.text; const itemId = itemResponse.body.id;
const inventoryAdjustmentResponse = await request(app.getHttpServer()) const inventoryAdjustmentResponse = await request(app.getHttpServer())
.post('/inventory-adjustments/quick') .post('/inventory-adjustments/quick')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
@@ -102,7 +102,7 @@ describe('Inventory Adjustments (e2e)', () => {
.send(makeItemRequest()) .send(makeItemRequest())
.expect(201); .expect(201);
const itemId = itemResponse.text; const itemId = itemResponse.body.id;
const inventoryAdjustmentResponse = await request(app.getHttpServer()) const inventoryAdjustmentResponse = await request(app.getHttpServer())
.post('/inventory-adjustments/quick') .post('/inventory-adjustments/quick')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)

View File

@@ -23,7 +23,7 @@ const makeManualJournalRequest = () => ({
], ],
}); });
describe.only('Manual Journals (e2e)', () => { describe('Manual Journals (e2e)', () => {
it('/manual-journals (POST)', () => { it('/manual-journals (POST)', () => {
return request(app.getHttpServer()) return request(app.getHttpServer())
.post('/manual-journals') .post('/manual-journals')
@@ -85,7 +85,7 @@ describe.only('Manual Journals (e2e)', () => {
.expect(200); .expect(200);
}); });
it('/manual-journals/:id/publish (PUT)', async () => { it('/manual-journals/:id/publish (PATCH)', async () => {
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.post('/manual-journals') .post('/manual-journals')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
@@ -95,7 +95,7 @@ describe.only('Manual Journals (e2e)', () => {
const journalId = response.body.id; const journalId = response.body.id;
return request(app.getHttpServer()) return request(app.getHttpServer())
.put(`/manual-journals/${journalId}/publish`) .patch(`/manual-journals/${journalId}/publish`)
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.send() .send()

View File

@@ -1,10 +1,82 @@
import * as request from 'supertest'; import * as request from 'supertest';
import { faker } from '@faker-js/faker';
import { app, AuthorizationHeader, orgainzationId } from './init-app-test'; import { app, AuthorizationHeader, orgainzationId } from './init-app-test';
let customerId;
let itemId;
const requestSaleInvoiceBody = () => ({
customerId: customerId,
invoiceDate: '2023-01-01',
dueDate: '2023-02-01',
invoiceNo: faker.string.uuid(),
referenceNo: 'REF-000201',
delivered: true,
discountType: 'percentage',
discount: 10,
entries: [
{
index: 1,
itemId: itemId,
quantity: 2,
rate: 1000,
description: 'Item description...',
},
],
});
describe('Payment Links (e2e)', () => { describe('Payment Links (e2e)', () => {
it('/payment-links/:paymentLinkId/invoice (GET)', () => { beforeAll(async () => {
const customer = await request(app.getHttpServer())
.post('/customers')
.set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader)
.send({ displayName: 'Test Customer' });
customerId = customer.body.id;
const item = await request(app.getHttpServer())
.post('/items')
.set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader)
.send({
name: `${faker.commerce.productName()} ${Date.now()}-${faker.string.alphanumeric({ length: 4 })}`,
sellable: true,
purchasable: true,
sellAccountId: 1026,
costAccountId: 1019,
costPrice: 100,
sellPrice: 100,
});
itemId = item.body.id;
});
it('/payment-links/:paymentLinkId/invoice (GET)', async () => {
// Create a sale invoice
const invoiceResponse = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader)
.send(requestSaleInvoiceBody())
.expect(201);
const invoiceId = invoiceResponse.body.id;
// Generate a payment link for the invoice
const paymentLinkResponse = await request(app.getHttpServer())
.post(`/sale-invoices/${invoiceId}/generate-link`)
.set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader)
.expect(201);
// Extract payment link ID from the URL
// Format: {BASE_URL}/payment/{PAYMENT_LINK_ID}
const paymentLinkUrl = paymentLinkResponse.body.link;
const paymentLinkId = paymentLinkUrl.split('/payment/')[1];
// Test the payment link endpoint
return request(app.getHttpServer()) return request(app.getHttpServer())
.get('/payment-links/test-link-id/invoice') .get(`/payment-links/${paymentLinkId}/invoice`)
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.expect(200); .expect(200);

View File

@@ -63,7 +63,7 @@ describe('Payment Received (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = parseInt(item.body.id, 10);
}); });
beforeEach(async () => { beforeEach(async () => {

View File

@@ -21,6 +21,7 @@ const createRoleRequest = () => ({
describe('Roles (e2e)', () => { describe('Roles (e2e)', () => {
it('/roles (POST)', () => { it('/roles (POST)', () => {
console.log(createRoleRequest())
return request(app.getHttpServer()) return request(app.getHttpServer())
.post('/roles') .post('/roles')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
@@ -51,7 +52,7 @@ describe('Roles (e2e)', () => {
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.send(createRoleRequest()); .send(createRoleRequest());
const roleId = response.body.data.roleId; const roleId = response.body.data.id;
return request(app.getHttpServer()) return request(app.getHttpServer())
.get(`/roles/${roleId}`) .get(`/roles/${roleId}`)
@@ -66,7 +67,7 @@ describe('Roles (e2e)', () => {
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.send(createRoleRequest()); .send(createRoleRequest());
const roleId = response.body.data.roleId; const roleId = response.body.data.id;
return request(app.getHttpServer()) return request(app.getHttpServer())
.post(`/roles/${roleId}`) .post(`/roles/${roleId}`)

View File

@@ -49,7 +49,7 @@ describe('Sale Estimates (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = item.body.id;
}); });
it('/sale-estimates (POST)', async () => { it('/sale-estimates (POST)', async () => {

View File

@@ -14,8 +14,8 @@ const requestSaleInvoiceBody = () => ({
delivered: true, delivered: true,
discountType: 'percentage', discountType: 'percentage',
discount: 10, discount: 10,
branchId: 1, // branchId: 1,
warehouseId: 1, // warehouseId: 1,
entries: [ entries: [
{ {
index: 1, index: 1,
@@ -50,7 +50,7 @@ describe('Sale Invoices (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = item.body.id;
}); });
it('/sale-invoices (POST)', () => { it('/sale-invoices (POST)', () => {
@@ -119,15 +119,9 @@ describe('Sale Invoices (e2e)', () => {
.expect(200); .expect(200);
}); });
it('/sale-invoices/:id/state (GET)', async () => { it('/sale-invoices/state (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader)
.send(requestSaleInvoiceBody());
return request(app.getHttpServer()) return request(app.getHttpServer())
.get(`/sale-invoices/${response.body.id}/state`) .get('/sale-invoices/state')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.expect(200); .expect(200);
@@ -207,7 +201,7 @@ describe('Sale Invoices (e2e)', () => {
.expect(200); .expect(200);
}); });
it('/sale-invoices/:id/mail-state (GET)', async () => { it('/sale-invoices/:id/mail (GET)', async () => {
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.post('/sale-invoices') .post('/sale-invoices')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
@@ -215,7 +209,7 @@ describe('Sale Invoices (e2e)', () => {
.send(requestSaleInvoiceBody()); .send(requestSaleInvoiceBody());
return request(app.getHttpServer()) return request(app.getHttpServer())
.get(`/sale-invoices/${response.body.id}/mail-state`) .get(`/sale-invoices/${response.body.id}/mail`)
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.expect(200); .expect(200);

View File

@@ -3,9 +3,9 @@ import { faker } from '@faker-js/faker';
import { app, AuthorizationHeader, orgainzationId } from './init-app-test'; import { app, AuthorizationHeader, orgainzationId } from './init-app-test';
describe('Users Invite (e2e)', () => { describe('Users Invite (e2e)', () => {
it('/invite (POST)', () => { it('/invite (PATCH)', () => {
return request(app.getHttpServer()) return request(app.getHttpServer())
.post('/invite') .patch('/invite')
.set('organization-id', orgainzationId) .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) .set('Authorization', AuthorizationHeader)
.send({ .send({

View File

@@ -56,7 +56,6 @@ describe('Users (e2e)', () => {
userId = usersResponse.body[0].id; userId = usersResponse.body[0].id;
} }
} }
if (userId) { if (userId) {
return request(app.getHttpServer()) return request(app.getHttpServer())
.post(`/users/${userId}`) .post(`/users/${userId}`)
@@ -65,52 +64,50 @@ describe('Users (e2e)', () => {
.send({ .send({
firstName: faker.person.firstName(), firstName: faker.person.firstName(),
lastName: faker.person.lastName(), lastName: faker.person.lastName(),
email: faker.internet.email(),
roleId: 1,
}) })
.expect(200); .expect(200);
} }
}); });
it('/users/:id/activate (PUT)', async () => { // it('/users/:id/activate (PUT)', async () => {
if (!userId) { // if (!userId) {
const usersResponse = await request(app.getHttpServer()) // const usersResponse = await request(app.getHttpServer())
.get('/users') // .get('/users')
.set('organization-id', orgainzationId) // .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader); // .set('Authorization', AuthorizationHeader);
if (usersResponse.body.length > 0) { // if (usersResponse.body.length > 0) {
userId = usersResponse.body[0].id; // userId = usersResponse.body[0].id;
} // }
} // }
if (userId) { // if (userId) {
return request(app.getHttpServer()) // return request(app.getHttpServer())
.put(`/users/${userId}/activate`) // .put(`/users/${userId}/activate`)
.set('organization-id', orgainzationId) // .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) // .set('Authorization', AuthorizationHeader)
.expect(200); // .expect(200);
} // }
}); // });
it('/users/:id/inactivate (PUT)', async () => { // it('/users/:id/inactivate (PUT)', async () => {
if (!userId) { // if (!userId) {
const usersResponse = await request(app.getHttpServer()) // const usersResponse = await request(app.getHttpServer())
.get('/users') // .get('/users')
.set('organization-id', orgainzationId) // .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader); // .set('Authorization', AuthorizationHeader);
if (usersResponse.body.length > 0) { // if (usersResponse.body.length > 0) {
userId = usersResponse.body[0].id; // userId = usersResponse.body[0].id;
} // }
} // }
if (userId) { // if (userId) {
return request(app.getHttpServer()) // return request(app.getHttpServer())
.put(`/users/${userId}/inactivate`) // .put(`/users/${userId}/inactivate`)
.set('organization-id', orgainzationId) // .set('organization-id', orgainzationId)
.set('Authorization', AuthorizationHeader) // .set('Authorization', AuthorizationHeader)
.expect(200); // .expect(200);
} // }
}); // });
}); });

View File

@@ -46,7 +46,7 @@ describe('Vendor Credits (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = parseInt(item.body.id, 10);
}); });
it('/vendor-credits (POST)', () => { it('/vendor-credits (POST)', () => {

View File

@@ -36,7 +36,7 @@ describe('Warehouse Transfers (e2e)', () => {
costPrice: 100, costPrice: 100,
sellPrice: 100, sellPrice: 100,
}); });
itemId = parseInt(item.text, 10); itemId = parseInt(item.body.id, 10);
const warehouse1 = await request(app.getHttpServer()) const warehouse1 = await request(app.getHttpServer())
.post('/warehouses') .post('/warehouses')

View File

@@ -509,7 +509,7 @@ export function useUnmatchMatchedUncategorizedTransaction(
UnmatchUncategorizedTransactionRes, UnmatchUncategorizedTransactionRes,
Error, Error,
UnmatchUncategorizedTransactionValues UnmatchUncategorizedTransactionValues
>(({ id }) => apiRequest.post(`/banking/matching/unmatch/${id}`), { >(({ id }) => apiRequest.patch(`/banking/matching/unmatch/${id}`), {
onSuccess: (res, id) => { onSuccess: (res, id) => {
queryClient.invalidateQueries( queryClient.invalidateQueries(
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,

View File

@@ -90,7 +90,7 @@ export function useOpenBill(props) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
return useMutation((id) => apiRequest.post(`bills/${id}/open`), { return useMutation((id) => apiRequest.patch(`bills/${id}/open`), {
onSuccess: (res, id) => { onSuccess: (res, id) => {
// Common invalidate queries. // Common invalidate queries.
commonInvalidateQueries(queryClient); commonInvalidateQueries(queryClient);

View File

@@ -19,7 +19,7 @@ export function useCreateInviteUser(props) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
return useMutation((values) => apiRequest.post('invite/send', values), { return useMutation((values) => apiRequest.patch('invite', values), {
onSuccess: () => { onSuccess: () => {
// Common invalidate queries. // Common invalidate queries.
commonInvalidateQueries(queryClient); commonInvalidateQueries(queryClient);

View File

@@ -72,7 +72,7 @@ export const deleteUser = ({ id }) => {
export const submitInvite = ({ form }) => { export const submitInvite = ({ form }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.post(`invite/send`, form) ApiService.patch(`invite`, form)
.then((response) => { .then((response) => {
resolve(response); resolve(response);
}) })

View File

@@ -9,5 +9,7 @@
"watchPlugins": [ "watchPlugins": [
"jest-watch-typeahead/filename", "jest-watch-typeahead/filename",
"jest-watch-typeahead/testname" "jest-watch-typeahead/testname"
] ],
"maxWorkers": 1,
"maxConcurrency": 1
} }