refactor: e2e

This commit is contained in:
Ahmed Bouhuolia
2025-01-01 14:39:25 +02:00
parent 8bacf3a001
commit b72f85b394
14 changed files with 227 additions and 137 deletions

View File

@@ -286,12 +286,16 @@ export class CreditNote extends BaseModel {
const { const {
AccountTransaction, AccountTransaction,
} = require('../../Accounts/models/AccountTransaction.model'); } = require('../../Accounts/models/AccountTransaction.model');
const { ItemEntry } = require('../../Items/models/ItemEntry'); const {
ItemEntry,
} = require('../../TransactionItemEntry/models/ItemEntry');
const { Customer } = require('../../Customers/models/Customer'); const { Customer } = require('../../Customers/models/Customer');
const { Branch } = require('../../Branches/models/Branch.model'); const { Branch } = require('../../Branches/models/Branch.model');
const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
const { PdfTemplateModel } = require('../../PdfTemplate/models/PdfTemplate'); const {
PdfTemplateModel,
} = require('../../PdfTemplate/models/PdfTemplate');
return { return {
/** /**

View File

@@ -3,6 +3,8 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
HttpCode,
HttpStatus,
Param, Param,
Post, Post,
Put, Put,
@@ -37,7 +39,8 @@ export class ManualJournalsController {
return this.manualJournalsApplication.deleteManualJournal(manualJournalId); return this.manualJournalsApplication.deleteManualJournal(manualJournalId);
} }
@Post(':id/publish') @Put(':id/publish')
@HttpCode(HttpStatus.OK)
public publishManualJournal(@Param('id') manualJournalId: number) { public publishManualJournal(@Param('id') manualJournalId: number) {
return this.manualJournalsApplication.publishManualJournal(manualJournalId); return this.manualJournalsApplication.publishManualJournal(manualJournalId);
} }

View File

@@ -26,9 +26,8 @@ export class EditManualJournal {
/** /**
* Authorize the manual journal editing. * Authorize the manual journal editing.
* @param {number} tenantId * @param {number} manualJournalId - Manual journal id.
* @param {number} manualJournalId * @param {IManualJournalDTO} manualJournalDTO - Manual journal DTO.
* @param {IManualJournalDTO} manualJournalDTO
*/ */
private authorize = async ( private authorize = async (
manualJournalId: number, manualJournalId: number,
@@ -81,10 +80,8 @@ export class EditManualJournal {
/** /**
* Edits jouranl entries. * Edits jouranl entries.
* @param {number} tenantId * @param {number} manualJournalId - Manual journal id.
* @param {number} manualJournalId * @param {IMakeJournalDTO} manualJournalDTO - Manual journal DTO.
* @param {IMakeJournalDTO} manualJournalDTO
* @param {ISystemUser} authorizedUser
*/ */
public async editJournalEntries( public async editJournalEntries(
manualJournalId: number, manualJournalId: number,
@@ -94,7 +91,8 @@ export class EditManualJournal {
oldManualJournal: ManualJournal; oldManualJournal: ManualJournal;
}> { }> {
// Validates the manual journal existance on the storage. // Validates the manual journal existance on the storage.
const oldManualJournal = await ManualJournal.query() const oldManualJournal = await this.manualJournalModel
.query()
.findById(manualJournalId) .findById(manualJournalId)
.throwIfNotFound(); .throwIfNotFound();
@@ -121,7 +119,8 @@ export class EditManualJournal {
...manualJournalObj, ...manualJournalObj,
}); });
// Retrieve the given manual journal with associated entries after modifications. // 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) .findById(manualJournalId)
.withGraphFetched('entries'); .withGraphFetched('entries');

View File

@@ -75,6 +75,8 @@ export class ManualJournalGL {
* @returns {ILedgerEntry[]} * @returns {ILedgerEntry[]}
*/ */
public getManualJournalGLEntries = (): ILedgerEntry[] => { public getManualJournalGLEntries = (): ILedgerEntry[] => {
return this.manualJournal.entries.map(this.getManualJournalEntry).flat(); return this.manualJournal.entries
.map((entry) => this.getManualJournalEntry(entry))
.flat();
}; };
} }

View File

@@ -18,7 +18,7 @@ import {
import { SaleEstimate } from './models/SaleEstimate'; import { SaleEstimate } from './models/SaleEstimate';
import { PublicRoute } from '../Auth/Jwt.guard'; import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('sales/estimates') @Controller('sale-estimates')
@PublicRoute() @PublicRoute()
export class SaleEstimatesController { export class SaleEstimatesController {
/** /**
@@ -53,9 +53,9 @@ export class SaleEstimatesController {
return this.saleEstimatesApplication.deleteSaleEstimate(estimateId); return this.saleEstimatesApplication.deleteSaleEstimate(estimateId);
} }
@Get(':id') @Get('state')
public getSaleEstimate(@Param('id', ParseIntPipe) estimateId: number) { public getSaleEstimateState() {
return this.saleEstimatesApplication.getSaleEstimate(estimateId); return this.saleEstimatesApplication.getSaleEstimateState();
} }
// @Get() // @Get()
@@ -70,14 +70,14 @@ export class SaleEstimatesController {
return this.saleEstimatesApplication.deliverSaleEstimate(saleEstimateId); return this.saleEstimatesApplication.deliverSaleEstimate(saleEstimateId);
} }
@Post(':id/approve') @Put(':id/approve')
public approveSaleEstimate( public approveSaleEstimate(
@Param('id', ParseIntPipe) saleEstimateId: number, @Param('id', ParseIntPipe) saleEstimateId: number,
): Promise<void> { ): Promise<void> {
return this.saleEstimatesApplication.approveSaleEstimate(saleEstimateId); return this.saleEstimatesApplication.approveSaleEstimate(saleEstimateId);
} }
@Post(':id/reject') @Put(':id/reject')
public rejectSaleEstimate( public rejectSaleEstimate(
@Param('id', ParseIntPipe) saleEstimateId: number, @Param('id', ParseIntPipe) saleEstimateId: number,
): Promise<void> { ): Promise<void> {
@@ -125,8 +125,8 @@ export class SaleEstimatesController {
return this.saleEstimatesApplication.getSaleEstimateMail(saleEstimateId); return this.saleEstimatesApplication.getSaleEstimateMail(saleEstimateId);
} }
@Get('state') @Get(':id')
public getSaleEstimateState() { public getSaleEstimate(@Param('id', ParseIntPipe) estimateId: number) {
return this.saleEstimatesApplication.getSaleEstimateState(); return this.saleEstimatesApplication.getSaleEstimate(estimateId);
} }
} }

View File

@@ -5,7 +5,7 @@ import {
} from '../types/SaleEstimates.types'; } from '../types/SaleEstimates.types';
import { ERRORS } from '../constants'; import { ERRORS } from '../constants';
import { Knex } from 'knex'; import { Knex } from 'knex';
import moment from 'moment'; import * as moment from 'moment';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { SaleEstimate } from '../models/SaleEstimate'; import { SaleEstimate } from '../models/SaleEstimate';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
@@ -52,8 +52,7 @@ export class ApproveSaleEstimateService {
// Update estimate as approved. // Update estimate as approved.
const saleEstimate = await this.saleEstimateModel const saleEstimate = await this.saleEstimateModel
.query(trx) .query(trx)
.where('id', saleEstimateId) .patchAndFetchById(saleEstimateId, {
.patchAndFetch({
approvedAt: moment().toMySqlDateTime(), approvedAt: moment().toMySqlDateTime(),
rejectedAt: null, rejectedAt: null,
}); });

View File

@@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import moment from 'moment'; import * as moment from 'moment';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { SaleEstimate } from '../models/SaleEstimate'; import { SaleEstimate } from '../models/SaleEstimate';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';

View File

@@ -206,65 +206,95 @@ export class SaleEstimate extends BaseModel {
/** /**
* Relationship mapping. * Relationship mapping.
*/ */
// static get relationMappings() { static get relationMappings() {
// return { const { ItemEntry } = require('../../TransactionItemEntry/models/ItemEntry');
// customer: { const { Customer } = require('../../Customers/models/Customer');
// relation: Model.BelongsToOneRelation, const { Branch } = require('../../Branches/models/Branch.model');
// modelClass: this.customerModel, const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
// join: { const { Document } = require('../../ChromiumlyTenancy/models/Document');
// from: 'sales_estimates.customerId', const { PdfTemplateModel } = require('../../PdfTemplate/models/PdfTemplate');
// 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');
// },
// },
// };
// }
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. * Model settings.
*/ */

View File

@@ -56,7 +56,7 @@ export class SaleInvoiceWriteoffGL {
return { return {
...commontEntry, ...commontEntry,
credit: this.saleInvoiceModel.localWrittenoffAmount, credit: this.saleInvoiceModel.writtenoffAmountLocal,
accountId: this.ARAccountId, accountId: this.ARAccountId,
contactId: this.saleInvoiceModel.customerId, contactId: this.saleInvoiceModel.customerId,
debit: 0, debit: 0,

View File

@@ -295,6 +295,14 @@ export class SaleInvoice extends BaseModel {
return Math.max(dueDateMoment.diff(dateMoment, 'days'), 0); 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. * Retrieve the overdue days in number.
* @return {number|null} * @return {number|null}

View File

@@ -38,7 +38,6 @@ export class ItemEntry extends BaseModel {
item: Item; item: Item;
allocatedCostEntries: BillLandedCostEntry[]; allocatedCostEntries: BillLandedCostEntry[];
/** /**
* Table name. * Table name.
* @returns {string} * @returns {string}
@@ -180,8 +179,8 @@ export class ItemEntry extends BaseModel {
const { Bill } = require('../../Bills/models/Bill'); const { Bill } = require('../../Bills/models/Bill');
const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt'); const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate'); const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate');
const { Expense } = require('../../Expenses/models/Expense.model'); const { TaxRateModel } = require('../../TaxRates/models/TaxRate.model');
const { TaxRate } = require('../../TaxRates/models/TaxRate.model'); // const { Expense } = require('../../Expenses/models/Expense.model');
// const ProjectTask = require('models/Task'); // const ProjectTask = require('models/Task');
return { return {
@@ -282,7 +281,7 @@ export class ItemEntry extends BaseModel {
*/ */
tax: { tax: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: TaxRate, modelClass: TaxRateModel,
join: { join: {
from: 'items_entries.taxRateId', from: 'items_entries.taxRateId',
to: 'tax_rates.id', to: 'tax_rates.id',

View File

@@ -16,10 +16,7 @@ const makeExpenseRequest = () => ({
description: faker.lorem.sentence(), description: faker.lorem.sentence(),
}, },
], ],
// currencyCode: faker.finance.currencyCode(), branchId: 1,
// userId: faker.number.int({ min: 1, max: 100 }),
// payeeId: faker.number.int({ min: 1, max: 100 }),
// branchId: faker.number.int({ min: 1, max: 100 }),
}); });
describe('Expenses (e2e)', () => { describe('Expenses (e2e)', () => {
@@ -31,6 +28,21 @@ describe('Expenses (e2e)', () => {
.expect(201); .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 () => { it('/expenses/:id (GET)', async () => {
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.post('/expenses') .post('/expenses')

View File

@@ -23,7 +23,7 @@ const makeManualJournalRequest = () => ({
], ],
}); });
describe('Manual Journals (e2e)', () => { describe.only('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')
@@ -78,7 +78,7 @@ describe('Manual Journals (e2e)', () => {
.expect(200); .expect(200);
}); });
it('/manual-journals/:id/publish (POST)', async () => { it('/manual-journals/:id/publish (PUT)', async () => {
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.post('/manual-journals') .post('/manual-journals')
.set('organization-id', '4064541lv40nhca') .set('organization-id', '4064541lv40nhca')
@@ -87,7 +87,7 @@ describe('Manual Journals (e2e)', () => {
const journalId = response.body.id; const journalId = response.body.id;
return request(app.getHttpServer()) return request(app.getHttpServer())
.post(`/manual-journals/${journalId}/publish`) .put(`/manual-journals/${journalId}/publish`)
.set('organization-id', '4064541lv40nhca') .set('organization-id', '4064541lv40nhca')
.send() .send()
.expect(200); .expect(200);

View File

@@ -2,59 +2,93 @@ import * as request from 'supertest';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import { app } from './init-app-test'; 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)', () => { describe('Sale Estimates (e2e)', () => {
it('/sales/estimates (POST)', async () => { it('/sale-estimates (POST)', async () => {
return request(app.getHttpServer()) return request(app.getHttpServer())
.post('/sales/estimates') .post('/sale-estimates')
.set('organization-id', '4064541lv40nhca') .set('organization-id', '4064541lv40nhca')
.send({ .send(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.",
},
],
})
.expect(201); .expect(201);
}); });
it('/sales/estimates (DELETE)', async () => { it('/sale-estimates (DELETE)', async () => {
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.post('/sales/estimates') .post('/sale-estimates')
.set('organization-id', '4064541lv40nhca') .set('organization-id', '4064541lv40nhca')
.send({ .send(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.",
},
],
});
const estimateId = response.body.id; const estimateId = response.body.id;
return request(app.getHttpServer()) 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') .set('organization-id', '4064541lv40nhca')
.expect(200); .expect(200);
}); });