From b72f85b394d6bab985f4c20c0ac47a4367f46627 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 1 Jan 2025 14:39:25 +0200 Subject: [PATCH] refactor: e2e --- .../modules/CreditNotes/models/CreditNote.ts | 8 +- .../ManualJournals.controller.ts | 5 +- .../commands/EditManualJournal.service.ts | 17 +- .../commands/ManualJournalGL.ts | 4 +- .../SaleEstimates/SaleEstimates.controller.ts | 18 +-- .../commands/ApproveSaleEstimate.service.ts | 5 +- .../commands/RejectSaleEstimate.service.ts | 2 +- .../SaleEstimates/models/SaleEstimate.ts | 146 +++++++++++------- .../writeoff/SaleInvoiceWriteoffGL.ts | 2 +- .../SaleInvoices/models/SaleInvoice.ts | 8 + .../TransactionItemEntry/models/ItemEntry.ts | 7 +- .../server-nest/test/expenses.e2e-spec.ts | 20 ++- .../test/manual-journal.e2e-spec.ts | 6 +- .../test/sale-estimates.e2e-spec.ts | 116 +++++++++----- 14 files changed, 227 insertions(+), 137 deletions(-) diff --git a/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts b/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts index 5f0cdca8e..25364f7ba 100644 --- a/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts +++ b/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts @@ -286,12 +286,16 @@ export class CreditNote extends BaseModel { const { AccountTransaction, } = require('../../Accounts/models/AccountTransaction.model'); - const { ItemEntry } = require('../../Items/models/ItemEntry'); + const { + ItemEntry, + } = require('../../TransactionItemEntry/models/ItemEntry'); const { Customer } = require('../../Customers/models/Customer'); const { Branch } = require('../../Branches/models/Branch.model'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); - const { PdfTemplateModel } = require('../../PdfTemplate/models/PdfTemplate'); + const { + PdfTemplateModel, + } = require('../../PdfTemplate/models/PdfTemplate'); return { /** diff --git a/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts b/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts index 28b6342b6..e8b364af8 100644 --- a/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts +++ b/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts @@ -3,6 +3,8 @@ import { Controller, Delete, Get, + HttpCode, + HttpStatus, Param, Post, Put, @@ -37,7 +39,8 @@ export class ManualJournalsController { return this.manualJournalsApplication.deleteManualJournal(manualJournalId); } - @Post(':id/publish') + @Put(':id/publish') + @HttpCode(HttpStatus.OK) public publishManualJournal(@Param('id') manualJournalId: number) { return this.manualJournalsApplication.publishManualJournal(manualJournalId); } diff --git a/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts b/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts index cb34d55ca..23ce868bc 100644 --- a/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts +++ b/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts @@ -26,9 +26,8 @@ export class EditManualJournal { /** * Authorize the manual journal editing. - * @param {number} tenantId - * @param {number} manualJournalId - * @param {IManualJournalDTO} manualJournalDTO + * @param {number} manualJournalId - Manual journal id. + * @param {IManualJournalDTO} manualJournalDTO - Manual journal DTO. */ private authorize = async ( manualJournalId: number, @@ -81,10 +80,8 @@ export class EditManualJournal { /** * Edits jouranl entries. - * @param {number} tenantId - * @param {number} manualJournalId - * @param {IMakeJournalDTO} manualJournalDTO - * @param {ISystemUser} authorizedUser + * @param {number} manualJournalId - Manual journal id. + * @param {IMakeJournalDTO} manualJournalDTO - Manual journal DTO. */ public async editJournalEntries( manualJournalId: number, @@ -94,7 +91,8 @@ export class EditManualJournal { oldManualJournal: ManualJournal; }> { // Validates the manual journal existance on the storage. - const oldManualJournal = await ManualJournal.query() + const oldManualJournal = await this.manualJournalModel + .query() .findById(manualJournalId) .throwIfNotFound(); @@ -121,7 +119,8 @@ export class EditManualJournal { ...manualJournalObj, }); // Retrieve the given manual journal with associated entries after modifications. - const manualJournal = await this.manualJournalModel.query(trx) + const manualJournal = await this.manualJournalModel + .query(trx) .findById(manualJournalId) .withGraphFetched('entries'); diff --git a/packages/server-nest/src/modules/ManualJournals/commands/ManualJournalGL.ts b/packages/server-nest/src/modules/ManualJournals/commands/ManualJournalGL.ts index f88ed828c..21ea2e9d1 100644 --- a/packages/server-nest/src/modules/ManualJournals/commands/ManualJournalGL.ts +++ b/packages/server-nest/src/modules/ManualJournals/commands/ManualJournalGL.ts @@ -75,6 +75,8 @@ export class ManualJournalGL { * @returns {ILedgerEntry[]} */ public getManualJournalGLEntries = (): ILedgerEntry[] => { - return this.manualJournal.entries.map(this.getManualJournalEntry).flat(); + return this.manualJournal.entries + .map((entry) => this.getManualJournalEntry(entry)) + .flat(); }; } diff --git a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts index 5f2a32c05..3b3db294a 100644 --- a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts +++ b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts @@ -18,7 +18,7 @@ import { import { SaleEstimate } from './models/SaleEstimate'; import { PublicRoute } from '../Auth/Jwt.guard'; -@Controller('sales/estimates') +@Controller('sale-estimates') @PublicRoute() export class SaleEstimatesController { /** @@ -53,9 +53,9 @@ export class SaleEstimatesController { return this.saleEstimatesApplication.deleteSaleEstimate(estimateId); } - @Get(':id') - public getSaleEstimate(@Param('id', ParseIntPipe) estimateId: number) { - return this.saleEstimatesApplication.getSaleEstimate(estimateId); + @Get('state') + public getSaleEstimateState() { + return this.saleEstimatesApplication.getSaleEstimateState(); } // @Get() @@ -70,14 +70,14 @@ export class SaleEstimatesController { return this.saleEstimatesApplication.deliverSaleEstimate(saleEstimateId); } - @Post(':id/approve') + @Put(':id/approve') public approveSaleEstimate( @Param('id', ParseIntPipe) saleEstimateId: number, ): Promise { return this.saleEstimatesApplication.approveSaleEstimate(saleEstimateId); } - @Post(':id/reject') + @Put(':id/reject') public rejectSaleEstimate( @Param('id', ParseIntPipe) saleEstimateId: number, ): Promise { @@ -125,8 +125,8 @@ export class SaleEstimatesController { return this.saleEstimatesApplication.getSaleEstimateMail(saleEstimateId); } - @Get('state') - public getSaleEstimateState() { - return this.saleEstimatesApplication.getSaleEstimateState(); + @Get(':id') + public getSaleEstimate(@Param('id', ParseIntPipe) estimateId: number) { + return this.saleEstimatesApplication.getSaleEstimate(estimateId); } } diff --git a/packages/server-nest/src/modules/SaleEstimates/commands/ApproveSaleEstimate.service.ts b/packages/server-nest/src/modules/SaleEstimates/commands/ApproveSaleEstimate.service.ts index 107d846b9..721a1bd89 100644 --- a/packages/server-nest/src/modules/SaleEstimates/commands/ApproveSaleEstimate.service.ts +++ b/packages/server-nest/src/modules/SaleEstimates/commands/ApproveSaleEstimate.service.ts @@ -5,7 +5,7 @@ import { } from '../types/SaleEstimates.types'; import { ERRORS } from '../constants'; import { Knex } from 'knex'; -import moment from 'moment'; +import * as moment from 'moment'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { SaleEstimate } from '../models/SaleEstimate'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -52,8 +52,7 @@ export class ApproveSaleEstimateService { // Update estimate as approved. const saleEstimate = await this.saleEstimateModel .query(trx) - .where('id', saleEstimateId) - .patchAndFetch({ + .patchAndFetchById(saleEstimateId, { approvedAt: moment().toMySqlDateTime(), rejectedAt: null, }); diff --git a/packages/server-nest/src/modules/SaleEstimates/commands/RejectSaleEstimate.service.ts b/packages/server-nest/src/modules/SaleEstimates/commands/RejectSaleEstimate.service.ts index b5f07c9f5..37dd9c9bf 100644 --- a/packages/server-nest/src/modules/SaleEstimates/commands/RejectSaleEstimate.service.ts +++ b/packages/server-nest/src/modules/SaleEstimates/commands/RejectSaleEstimate.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import moment from 'moment'; +import * as moment from 'moment'; import { Knex } from 'knex'; import { SaleEstimate } from '../models/SaleEstimate'; import { EventEmitter2 } from '@nestjs/event-emitter'; diff --git a/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts b/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts index 5fee91e86..99b0e14de 100644 --- a/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts +++ b/packages/server-nest/src/modules/SaleEstimates/models/SaleEstimate.ts @@ -206,65 +206,95 @@ export class SaleEstimate extends BaseModel { /** * Relationship mapping. */ - // static get relationMappings() { - // return { - // customer: { - // relation: Model.BelongsToOneRelation, - // modelClass: this.customerModel, - // join: { - // from: 'sales_estimates.customerId', - // to: 'contacts.id', - // }, - // filter(query) { - // query.where('contact_service', 'customer'); - // }, - // }, - // entries: { - // relation: Model.HasManyRelation, - // modelClass: this.itemEntryModel, - // join: { - // from: 'sales_estimates.id', - // to: 'items_entries.referenceId', - // }, - // filter(builder) { - // builder.where('reference_type', 'SaleEstimate'); - // builder.orderBy('index', 'ASC'); - // }, - // }, - // branch: { - // relation: Model.BelongsToOneRelation, - // modelClass: this.branchModel, - // join: { - // from: 'sales_estimates.branchId', - // to: 'branches.id', - // }, - // }, - // warehouse: { - // relation: Model.BelongsToOneRelation, - // modelClass: this.warehouseModel, - // join: { - // from: 'sales_estimates.warehouseId', - // to: 'warehouses.id', - // }, - // }, - // attachments: { - // relation: Model.ManyToManyRelation, - // modelClass: this.documentModel, - // join: { - // from: 'sales_estimates.id', - // through: { - // from: 'document_links.modelId', - // to: 'document_links.documentId', - // }, - // to: 'documents.id', - // }, - // filter(query) { - // query.where('model_ref', 'SaleEstimate'); - // }, - // }, - // }; - // } + static get relationMappings() { + const { ItemEntry } = require('../../TransactionItemEntry/models/ItemEntry'); + const { Customer } = require('../../Customers/models/Customer'); + const { Branch } = require('../../Branches/models/Branch.model'); + const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); + const { Document } = require('../../ChromiumlyTenancy/models/Document'); + const { PdfTemplateModel } = require('../../PdfTemplate/models/PdfTemplate'); + return { + customer: { + relation: Model.BelongsToOneRelation, + modelClass: Customer, + join: { + from: 'sales_estimates.customerId', + to: 'contacts.id', + }, + filter(query) { + query.where('contact_service', 'customer'); + }, + }, + entries: { + relation: Model.HasManyRelation, + modelClass: ItemEntry, + join: { + from: 'sales_estimates.id', + to: 'items_entries.referenceId', + }, + filter(builder) { + builder.where('reference_type', 'SaleEstimate'); + builder.orderBy('index', 'ASC'); + }, + }, + + /** + * Sale estimate may belongs to branch. + */ + branch: { + relation: Model.BelongsToOneRelation, + modelClass: Branch, + join: { + from: 'sales_estimates.branchId', + to: 'branches.id', + }, + }, + + /** + * Sale estimate may has associated warehouse. + */ + warehouse: { + relation: Model.BelongsToOneRelation, + modelClass: Warehouse, + join: { + from: 'sales_estimates.warehouseId', + to: 'warehouses.id', + }, + }, + + /** + * Sale estimate transaction may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document, + join: { + from: 'sales_estimates.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'SaleEstimate'); + }, + }, + + /** + * Sale estimate may belongs to pdf branding template. + */ + pdfTemplate: { + relation: Model.BelongsToOneRelation, + modelClass: PdfTemplateModel, + join: { + from: 'sales_estimates.pdfTemplateId', + to: 'pdf_templates.id', + }, + }, + }; + } /** * Model settings. */ diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/writeoff/SaleInvoiceWriteoffGL.ts b/packages/server-nest/src/modules/SaleInvoices/commands/writeoff/SaleInvoiceWriteoffGL.ts index 1cba043d2..f7e7a7dd8 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/writeoff/SaleInvoiceWriteoffGL.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/writeoff/SaleInvoiceWriteoffGL.ts @@ -56,7 +56,7 @@ export class SaleInvoiceWriteoffGL { return { ...commontEntry, - credit: this.saleInvoiceModel.localWrittenoffAmount, + credit: this.saleInvoiceModel.writtenoffAmountLocal, accountId: this.ARAccountId, contactId: this.saleInvoiceModel.customerId, debit: 0, diff --git a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts index 78fbce8f1..00731259e 100644 --- a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts +++ b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts @@ -295,6 +295,14 @@ export class SaleInvoice extends BaseModel { return Math.max(dueDateMoment.diff(dateMoment, 'days'), 0); } + /** + * Written-off amount in local currency. + * @returns {number} + */ + get writtenoffAmountLocal() { + return this.writtenoffAmount * this.exchangeRate; + } + /** * Retrieve the overdue days in number. * @return {number|null} diff --git a/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts b/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts index 20216df57..85280fe9d 100644 --- a/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts +++ b/packages/server-nest/src/modules/TransactionItemEntry/models/ItemEntry.ts @@ -38,7 +38,6 @@ export class ItemEntry extends BaseModel { item: Item; allocatedCostEntries: BillLandedCostEntry[]; - /** * Table name. * @returns {string} @@ -180,8 +179,8 @@ export class ItemEntry extends BaseModel { const { Bill } = require('../../Bills/models/Bill'); const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt'); const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate'); - const { Expense } = require('../../Expenses/models/Expense.model'); - const { TaxRate } = require('../../TaxRates/models/TaxRate.model'); + const { TaxRateModel } = require('../../TaxRates/models/TaxRate.model'); + // const { Expense } = require('../../Expenses/models/Expense.model'); // const ProjectTask = require('models/Task'); return { @@ -282,7 +281,7 @@ export class ItemEntry extends BaseModel { */ tax: { relation: Model.HasOneRelation, - modelClass: TaxRate, + modelClass: TaxRateModel, join: { from: 'items_entries.taxRateId', to: 'tax_rates.id', diff --git a/packages/server-nest/test/expenses.e2e-spec.ts b/packages/server-nest/test/expenses.e2e-spec.ts index 74078ebc8..221505f3d 100644 --- a/packages/server-nest/test/expenses.e2e-spec.ts +++ b/packages/server-nest/test/expenses.e2e-spec.ts @@ -16,10 +16,7 @@ const makeExpenseRequest = () => ({ description: faker.lorem.sentence(), }, ], - // currencyCode: faker.finance.currencyCode(), - // userId: faker.number.int({ min: 1, max: 100 }), - // payeeId: faker.number.int({ min: 1, max: 100 }), - // branchId: faker.number.int({ min: 1, max: 100 }), + branchId: 1, }); describe('Expenses (e2e)', () => { @@ -31,6 +28,21 @@ describe('Expenses (e2e)', () => { .expect(201); }); + it('/expenses/:id (PUT)', async () => { + const response = await request(app.getHttpServer()) + .post('/expenses') + .set('organization-id', '4064541lv40nhca') + .send(makeExpenseRequest()); + + const expenseId = response.body.id; + + return request(app.getHttpServer()) + .put(`/expenses/${expenseId}`) + .set('organization-id', '4064541lv40nhca') + .send(makeExpenseRequest()) + .expect(200); + }); + it('/expenses/:id (GET)', async () => { const response = await request(app.getHttpServer()) .post('/expenses') diff --git a/packages/server-nest/test/manual-journal.e2e-spec.ts b/packages/server-nest/test/manual-journal.e2e-spec.ts index 6aa8b6b8c..5ac8ebf7c 100644 --- a/packages/server-nest/test/manual-journal.e2e-spec.ts +++ b/packages/server-nest/test/manual-journal.e2e-spec.ts @@ -23,7 +23,7 @@ const makeManualJournalRequest = () => ({ ], }); -describe('Manual Journals (e2e)', () => { +describe.only('Manual Journals (e2e)', () => { it('/manual-journals (POST)', () => { return request(app.getHttpServer()) .post('/manual-journals') @@ -78,7 +78,7 @@ describe('Manual Journals (e2e)', () => { .expect(200); }); - it('/manual-journals/:id/publish (POST)', async () => { + it('/manual-journals/:id/publish (PUT)', async () => { const response = await request(app.getHttpServer()) .post('/manual-journals') .set('organization-id', '4064541lv40nhca') @@ -87,7 +87,7 @@ describe('Manual Journals (e2e)', () => { const journalId = response.body.id; return request(app.getHttpServer()) - .post(`/manual-journals/${journalId}/publish`) + .put(`/manual-journals/${journalId}/publish`) .set('organization-id', '4064541lv40nhca') .send() .expect(200); diff --git a/packages/server-nest/test/sale-estimates.e2e-spec.ts b/packages/server-nest/test/sale-estimates.e2e-spec.ts index 93eb8a3a9..c05fcac90 100644 --- a/packages/server-nest/test/sale-estimates.e2e-spec.ts +++ b/packages/server-nest/test/sale-estimates.e2e-spec.ts @@ -2,59 +2,93 @@ import * as request from 'supertest'; import { faker } from '@faker-js/faker'; import { app } from './init-app-test'; +const makeEstimateRequest = () => ({ + customerId: 2, + estimateDate: '2022-02-02', + expirationDate: '2020-03-02', + delivered: false, + estimateNumber: faker.string.uuid(), + discount: 100, + discountType: 'amount', + entries: [ + { + index: 1, + itemId: 1001, + quantity: 3, + rate: 1000, + description: "It's description here.", + }, + ], +}); + describe('Sale Estimates (e2e)', () => { - it('/sales/estimates (POST)', async () => { + it('/sale-estimates (POST)', async () => { return request(app.getHttpServer()) - .post('/sales/estimates') + .post('/sale-estimates') .set('organization-id', '4064541lv40nhca') - .send({ - customerId: 2, - estimateDate: '2022-02-02', - expirationDate: '2020-03-02', - delivered: false, - estimateNumber: faker.string.uuid(), - discount: 100, - discountType: 'amount', - entries: [ - { - index: 1, - itemId: 1001, - quantity: 3, - rate: 1000, - description: "It's description here.", - }, - ], - }) + .send(makeEstimateRequest()) .expect(201); }); - it('/sales/estimates (DELETE)', async () => { + it('/sale-estimates (DELETE)', async () => { const response = await request(app.getHttpServer()) - .post('/sales/estimates') + .post('/sale-estimates') .set('organization-id', '4064541lv40nhca') - .send({ - customerId: 2, - estimateDate: '2022-02-02', - expirationDate: '2020-03-02', - delivered: false, - estimateNumber: faker.string.uuid(), - discount: 100, - discountType: 'amount', - entries: [ - { - index: 1, - itemId: 1001, - quantity: 3, - rate: 1000, - description: "It's description here.", - }, - ], - }); + .send(makeEstimateRequest()); const estimateId = response.body.id; return request(app.getHttpServer()) - .delete(`/sales/estimates/${estimateId}`) + .delete(`/sale-estimates/${estimateId}`) + .set('organization-id', '4064541lv40nhca') + .expect(200); + }); + + it('/sale-estimates/state (GET)', async () => { + return request(app.getHttpServer()) + .get('/sale-estimates/state') + .set('organization-id', '4064541lv40nhca') + .expect(200); + }); + + it('/sale-estimates/:id (GET)', async () => { + const response = await request(app.getHttpServer()) + .post('/sale-estimates') + .set('organization-id', '4064541lv40nhca') + .send(makeEstimateRequest()); + + const estimateId = response.body.id; + + return request(app.getHttpServer()) + .get(`/sale-estimates/${estimateId}`) + .set('organization-id', '4064541lv40nhca') + .expect(200); + }); + + it('/sale-estimates/:id/approve (PUT)', async () => { + const response = await request(app.getHttpServer()) + .post('/sale-estimates') + .set('organization-id', '4064541lv40nhca') + .send({ ...makeEstimateRequest(), delivered: true }); + + const estimateId = response.body.id; + + return request(app.getHttpServer()) + .put(`/sale-estimates/${estimateId}/approve`) + .set('organization-id', '4064541lv40nhca') + .expect(200); + }); + + it('/sale-estimates/:id/reject (PUT)', async () => { + const response = await request(app.getHttpServer()) + .post('/sale-estimates') + .set('organization-id', '4064541lv40nhca') + .send({ ...makeEstimateRequest(), delivered: true }); + + const estimateId = response.body.id; + + return request(app.getHttpServer()) + .put(`/sale-estimates/${estimateId}/reject`) .set('organization-id', '4064541lv40nhca') .expect(200); });