refactor: migrate to Nestjs

This commit is contained in:
Ahmed Bouhuolia
2024-12-30 15:54:53 +02:00
parent 77bbf6828d
commit 515a984714
23 changed files with 557 additions and 175 deletions

View File

@@ -1,8 +1,8 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ClsMiddleware } from 'nestjs-cls'; import { ClsMiddleware } from 'nestjs-cls';
import { AppModule } from './modules/App/App.module';
import './utils/moment-mysql'; import './utils/moment-mysql';
import { AppModule } from './modules/App/App.module';
import { ServiceErrorFilter } from './common/filters/service-error.filter'; import { ServiceErrorFilter } from './common/filters/service-error.filter';
async function bootstrap() { async function bootstrap() {

View File

@@ -4,8 +4,10 @@ import {
ICreditNoteEditDTO, ICreditNoteEditDTO,
ICreditNoteNewDTO, ICreditNoteNewDTO,
} from './types/CreditNotes.types'; } from './types/CreditNotes.types';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('credit-notes') @Controller('credit-notes')
@PublicRoute()
export class CreditNotesController { export class CreditNotesController {
/** /**
* @param {CreditNoteApplication} creditNoteApplication - The credit note application service. * @param {CreditNoteApplication} creditNoteApplication - The credit note application service.
@@ -28,7 +30,7 @@ export class CreditNotesController {
); );
} }
@Post(':id/open') @Put(':id/open')
openCreditNote(@Param('id') creditNoteId: number) { openCreditNote(@Param('id') creditNoteId: number) {
return this.creditNoteApplication.openCreditNote(creditNoteId); return this.creditNoteApplication.openCreditNote(creditNoteId);
} }

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import moment from 'moment';
import { omit } from 'lodash'; import { omit } from 'lodash';
import * as moment from 'moment';
import * as composeAsync from 'async/compose';
import * as R from 'ramda'; import * as R from 'ramda';
import composeAsync from 'async/compose';
import { ERRORS } from '../constants'; import { ERRORS } from '../constants';
import { import {
ICreditNoteEditDTO, ICreditNoteEditDTO,

View File

@@ -287,7 +287,7 @@ export class CreditNote extends BaseModel {
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 { PdfTemplate } = require('../../PdfTemplate/models/PdfTemplate'); const { PdfTemplateModel } = require('../../PdfTemplate/models/PdfTemplate');
return { return {
/** /**
@@ -384,7 +384,7 @@ export class CreditNote extends BaseModel {
*/ */
pdfTemplate: { pdfTemplate: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: PdfTemplate, modelClass: PdfTemplateModel,
join: { join: {
from: 'credit_notes.pdfTemplateId', from: 'credit_notes.pdfTemplateId',
to: 'pdf_templates.id', to: 'pdf_templates.id',

View File

@@ -117,121 +117,121 @@ export class ItemEntry extends BaseModel {
/** /**
* Item entry relations. * Item entry relations.
*/ */
// static get relationMappings() { static get relationMappings() {
// const Item = require('models/Item'); const { Item } = require('../../Items/models/Item');
// const BillLandedCostEntry = require('models/BillLandedCostEntry'); // const BillLandedCostEntry = require('models/BillLandedCostEntry');
// const SaleInvoice = require('models/SaleInvoice'); const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
// const Bill = require('models/Bill'); const { Bill } = require('../../Bills/models/Bill');
// const SaleReceipt = require('models/SaleReceipt'); const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
// const SaleEstimate = require('models/SaleEstimate'); const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate');
// const ProjectTask = require('models/Task'); // const ProjectTask = require('models/Task');
// const Expense = require('models/Expense'); const { Expense } = require('../../Expenses/models/Expense.model');
// const TaxRate = require('models/TaxRate'); const { TaxRateModel } = require('../../TaxRates/models/TaxRate.model');
// return { return {
// item: { item: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Item.default, modelClass: Item,
// join: { join: {
// from: 'items_entries.itemId', from: 'items_entries.itemId',
// to: 'items.id', to: 'items.id',
// }, },
// }, },
// allocatedCostEntries: { // allocatedCostEntries: {
// relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
// modelClass: BillLandedCostEntry.default, // modelClass: BillLandedCostEntry,
// join: { // join: {
// from: 'items_entries.referenceId', // from: 'items_entries.referenceId',
// to: 'bill_located_cost_entries.entryId', // to: 'bill_located_cost_entries.entryId',
// }, // },
// }, // },
// invoice: { invoice: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: SaleInvoice.default, modelClass: SaleInvoice,
// join: { join: {
// from: 'items_entries.referenceId', from: 'items_entries.referenceId',
// to: 'sales_invoices.id', to: 'sales_invoices.id',
// }, },
// }, },
// bill: { bill: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: Bill.default, modelClass: Bill,
// join: { join: {
// from: 'items_entries.referenceId', from: 'items_entries.referenceId',
// to: 'bills.id', to: 'bills.id',
// }, },
// }, },
// estimate: { estimate: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: SaleEstimate.default, modelClass: SaleEstimate,
// join: { join: {
// from: 'items_entries.referenceId', from: 'items_entries.referenceId',
// to: 'sales_estimates.id', to: 'sales_estimates.id',
// }, },
// }, },
// /** /**
// * Sale receipt reference. * Sale receipt reference.
// */ */
// receipt: { receipt: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: SaleReceipt.default, modelClass: SaleReceipt,
// join: { join: {
// from: 'items_entries.referenceId', from: 'items_entries.referenceId',
// to: 'sales_receipts.id', to: 'sales_receipts.id',
// }, },
// }, },
// /** /**
// * Project task reference. * Project task reference.
// */ */
// projectTaskRef: { // projectTaskRef: {
// relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
// modelClass: ProjectTask.default, // modelClass: ProjectTask.default,
// join: { // join: {
// from: 'items_entries.projectRefId', // from: 'items_entries.projectRefId',
// to: 'tasks.id', // to: 'tasks.id',
// }, // },
// }, // },
// /** /**
// * Project expense reference. * Project expense reference.
// */ */
// projectExpenseRef: { // projectExpenseRef: {
// relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
// modelClass: Expense.default, // modelClass: Expense.default,
// join: { // join: {
// from: 'items_entries.projectRefId', // from: 'items_entries.projectRefId',
// to: 'expenses_transactions.id', // to: 'expenses_transactions.id',
// }, // },
// }, // },
// /** /**
// * Project bill reference. * Project bill reference.
// */ */
// projectBillRef: { // projectBillRef: {
// relation: Model.HasManyRelation, // relation: Model.HasManyRelation,
// modelClass: Bill.default, // modelClass: Bill.default,
// join: { // join: {
// from: 'items_entries.projectRefId', // from: 'items_entries.projectRefId',
// to: 'bills.id', // to: 'bills.id',
// }, // },
// }, // },
// /** /**
// * Tax rate reference. * Tax rate reference.
// */ */
// tax: { tax: {
// relation: Model.HasOneRelation, relation: Model.HasOneRelation,
// modelClass: TaxRate.default, modelClass: TaxRateModel,
// join: { join: {
// from: 'items_entries.taxRateId', from: 'items_entries.taxRateId',
// to: 'tax_rates.id', to: 'tax_rates.id',
// }, },
// }, },
// }; };
// } }
} }

View File

@@ -3,6 +3,7 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
HttpCode,
Param, Param,
ParseIntPipe, ParseIntPipe,
Post, Post,
@@ -57,6 +58,7 @@ export class SaleInvoicesController {
} }
@Post(':id/deliver') @Post(':id/deliver')
@HttpCode(200)
deliverSaleInvoice(@Param('id', ParseIntPipe) id: number) { deliverSaleInvoice(@Param('id', ParseIntPipe) id: number) {
return this.saleInvoiceApplication.deliverSaleInvoice(id); return this.saleInvoiceApplication.deliverSaleInvoice(id);
} }
@@ -67,6 +69,7 @@ export class SaleInvoicesController {
} }
@Post(':id/writeoff') @Post(':id/writeoff')
@HttpCode(200)
writeOff( writeOff(
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() writeoffDTO: ISaleInvoiceWriteoffDTO, @Body() writeoffDTO: ISaleInvoiceWriteoffDTO,
@@ -75,6 +78,7 @@ export class SaleInvoicesController {
} }
@Post(':id/cancel-writeoff') @Post(':id/cancel-writeoff')
@HttpCode(200)
cancelWrittenoff(@Param('id', ParseIntPipe) id: number) { cancelWrittenoff(@Param('id', ParseIntPipe) id: number) {
return this.saleInvoiceApplication.cancelWrittenoff(id); return this.saleInvoiceApplication.cancelWrittenoff(id);
} }

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import * as R from 'ramda'; import * as R from 'ramda';
import * as moment from 'moment'; import * as moment from 'moment';
import '../../../utils/moment-mysql';
import * as composeAsync from 'async/compose'; import * as composeAsync from 'async/compose';
import { import {
ISaleInvoiceCreateDTO, ISaleInvoiceCreateDTO,
@@ -23,6 +24,17 @@ import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class CommandSaleInvoiceDTOTransformer { export class CommandSaleInvoiceDTOTransformer {
/**
* @param {BranchTransactionDTOTransformer} branchDTOTransform - Branch transaction DTO transformer.
* @param {WarehouseTransactionDTOTransform} warehouseDTOTransform - Warehouse transaction DTO transformer.
* @param {ItemsEntriesService} itemsEntriesService - Items entries service.
* @param {CommandSaleInvoiceValidators} validators - Command sale invoice validators.
* @param {SaleInvoiceIncrement} invoiceIncrement - Sale invoice increment.
* @param {ItemEntriesTaxTransactions} taxDTOTransformer - Item entries tax transactions.
* @param {BrandingTemplateDTOTransformer} brandingTemplatesTransformer - Branding template DTO transformer.
* @param {TenancyContext} tenancyContext - Tenancy context.
* @param {SaleInvoice} saleInvoiceModel - Sale invoice model.
*/
constructor( constructor(
private branchDTOTransform: BranchTransactionDTOTransformer, private branchDTOTransform: BranchTransactionDTOTransformer,
private warehouseDTOTransform: WarehouseTransactionDTOTransform, private warehouseDTOTransform: WarehouseTransactionDTOTransform,

View File

@@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import moment from 'moment'; import * as moment from 'moment';
import { import {
ISaleInvoiceDeliveringPayload, ISaleInvoiceDeliveringPayload,
ISaleInvoiceEventDeliveredPayload, ISaleInvoiceEventDeliveredPayload,

View File

@@ -1,6 +1,7 @@
import { mixin, Model, raw } from 'objection'; import { mixin, Model, raw } from 'objection';
import { castArray, takeWhile } from 'lodash'; import { castArray, takeWhile } from 'lodash';
import moment, { MomentInput, unitOfTime } from 'moment'; import * as moment from 'moment';
import { MomentInput, unitOfTime } from 'moment';
// import TenantModel from 'models/TenantModel'; // import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting'; // import ModelSetting from './ModelSetting';
// import SaleInvoiceMeta from './SaleInvoice.Settings'; // import SaleInvoiceMeta from './SaleInvoice.Settings';
@@ -8,6 +9,9 @@ import moment, { MomentInput, unitOfTime } from 'moment';
// import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants'; // import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { TaxRateTransaction } from '@/modules/TaxRates/models/TaxRateTransaction.model';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
export class SaleInvoice extends BaseModel { export class SaleInvoice extends BaseModel {
public taxAmountWithheld: number; public taxAmountWithheld: number;
@@ -39,7 +43,9 @@ export class SaleInvoice extends BaseModel {
public branchId: number; public branchId: number;
public warehouseId: number; public warehouseId: number;
// public taxes: TaxRateTransaction[]; public taxes: TaxRateTransaction[];
public entries: ItemEntry[];
public attachments: Document[];
/** /**
* Table name * Table name
@@ -438,7 +444,7 @@ export class SaleInvoice extends BaseModel {
const { Branch } = require('../../Branches/models/Branch.model'); const { Branch } = require('../../Branches/models/Branch.model');
const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
const { Account } = require('../../Accounts/models/Account.model'); const { Account } = require('../../Accounts/models/Account.model');
// const TaxRateTransaction = require('../../Tax'); const { TaxRateTransaction } = require('../../TaxRates/models/TaxRateTransaction.model');
const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); // const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
const { const {
@@ -561,17 +567,17 @@ export class SaleInvoice extends BaseModel {
/** /**
* Invoice may has associated tax rate transactions. * Invoice may has associated tax rate transactions.
*/ */
// taxes: { taxes: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: TaxRateTransaction.default, modelClass: TaxRateTransaction,
// join: { join: {
// from: 'sales_invoices.id', from: 'sales_invoices.id',
// to: 'tax_rate_transactions.referenceId', to: 'tax_rate_transactions.referenceId',
// }, },
// filter(builder) { filter(builder) {
// builder.where('reference_type', 'SaleInvoice'); builder.where('reference_type', 'SaleInvoice');
// }, },
// }, },
/** /**
* Sale invoice transaction may has many attached attachments. * Sale invoice transaction may has many attached attachments.

View File

@@ -184,31 +184,31 @@ export class SaleInvoiceTransformer extends Transformer {
* Retrieve the taxes lines of sale invoice. * Retrieve the taxes lines of sale invoice.
* @param {ISaleInvoice} invoice * @param {ISaleInvoice} invoice
*/ */
// protected taxes = (invoice: SaleInvoice) => { protected taxes = (invoice: SaleInvoice) => {
// return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer(), { return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer(), {
// subtotal: invoice.subtotal, subtotal: invoice.subtotal,
// isInclusiveTax: invoice.isInclusiveTax, isInclusiveTax: invoice.isInclusiveTax,
// currencyCode: invoice.currencyCode, currencyCode: invoice.currencyCode,
// }); });
// }; };
/** /**
* Retrieves the entries of the sale invoice. * Retrieves the entries of the sale invoice.
* @param {ISaleInvoice} invoice * @param {ISaleInvoice} invoice
* @returns {} * @returns {}
*/ */
// protected entries = (invoice: SaleInvoice) => { protected entries = (invoice: SaleInvoice) => {
// return this.item(invoice.entries, new ItemEntryTransformer(), { return this.item(invoice.entries, new ItemEntryTransformer(), {
// currencyCode: invoice.currencyCode, currencyCode: invoice.currencyCode,
// }); });
// }; };
/** /**
* Retrieves the sale invoice attachments. * Retrieves the sale invoice attachments.
* @param {ISaleInvoice} invoice * @param {ISaleInvoice} invoice
* @returns * @returns
*/ */
// protected attachments = (invoice: SaleInvoice) => { protected attachments = (invoice: SaleInvoice) => {
// return this.item(invoice.attachments, new AttachmentTransformer()); return this.item(invoice.attachments, new AttachmentTransformer());
// }; };
} }

View File

@@ -0,0 +1,55 @@
import { mixin, Model, raw } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model';
export class TaxRateTransaction extends BaseModel {
/**
* Table name
*/
static get tableName() {
return 'tax_rate_transactions';
}
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
/**
* Model modifiers.
*/
static get modifiers() {
return {};
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { TaxRateModel } = require('./TaxRate.model');
return {
/**
* Belongs to the tax rate.
*/
taxRate: {
relation: Model.BelongsToOneRelation,
modelClass: TaxRateModel,
join: {
from: 'tax_rate_transactions.taxRateId',
to: 'tax_rates.id',
},
},
};
}
}

View File

@@ -1,8 +1,21 @@
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; import { VendorCreditsApplicationService } from './VendorCreditsApplication.service';
import { IVendorCreditCreateDTO, IVendorCreditEditDTO } from './types/VendorCredit.types'; import {
IVendorCreditCreateDTO,
IVendorCreditEditDTO,
} from './types/VendorCredit.types';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('vendor-credits') @Controller('vendor-credits')
@PublicRoute()
export class VendorCreditsController { export class VendorCreditsController {
constructor( constructor(
private readonly vendorCreditsApplication: VendorCreditsApplicationService, private readonly vendorCreditsApplication: VendorCreditsApplicationService,
@@ -13,6 +26,11 @@ export class VendorCreditsController {
return this.vendorCreditsApplication.createVendorCredit(dto); return this.vendorCreditsApplication.createVendorCredit(dto);
} }
@Put(':id/open')
async openVendorCredit(@Param('id') vendorCreditId: number) {
return this.vendorCreditsApplication.openVendorCredit(vendorCreditId);
}
@Put(':id') @Put(':id')
async editVendorCredit( async editVendorCredit(
@Param('id') vendorCreditId: number, @Param('id') vendorCreditId: number,

View File

@@ -15,6 +15,7 @@ import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { BranchesModule } from '../Branches/Branches.module'; import { BranchesModule } from '../Branches/Branches.module';
import { WarehousesModule } from '../Warehouses/Warehouses.module'; import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; import { VendorCreditsApplicationService } from './VendorCreditsApplication.service';
import { OpenVendorCreditService } from './commands/OpenVendorCredit.service';
@Module({ @Module({
imports: [ imports: [
@@ -35,6 +36,7 @@ import { VendorCreditsApplicationService } from './VendorCreditsApplication.serv
GetRefundVendorCreditService, GetRefundVendorCreditService,
GetVendorCreditService, GetVendorCreditService,
VendorCreditsApplicationService, VendorCreditsApplicationService,
OpenVendorCreditService
], ],
exports: [ exports: [
CreateVendorCreditService, CreateVendorCreditService,
@@ -45,6 +47,7 @@ import { VendorCreditsApplicationService } from './VendorCreditsApplication.serv
GetRefundVendorCreditService, GetRefundVendorCreditService,
GetVendorCreditService, GetVendorCreditService,
VendorCreditsApplicationService, VendorCreditsApplicationService,
OpenVendorCreditService
], ],
controllers: [VendorCreditsController], controllers: [VendorCreditsController],
}) })

View File

@@ -6,6 +6,7 @@ import { GetVendorCreditService } from './queries/GetVendorCredit.service';
import { IVendorCreditEditDTO } from './types/VendorCredit.types'; import { IVendorCreditEditDTO } from './types/VendorCredit.types';
import { IVendorCreditCreateDTO } from './types/VendorCredit.types'; import { IVendorCreditCreateDTO } from './types/VendorCredit.types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OpenVendorCreditService } from './commands/OpenVendorCredit.service';
@Injectable() @Injectable()
export class VendorCreditsApplicationService { export class VendorCreditsApplicationService {
@@ -20,6 +21,7 @@ export class VendorCreditsApplicationService {
private readonly editVendorCreditService: EditVendorCreditService, private readonly editVendorCreditService: EditVendorCreditService,
private readonly deleteVendorCreditService: DeleteVendorCreditService, private readonly deleteVendorCreditService: DeleteVendorCreditService,
private readonly getVendorCreditService: GetVendorCreditService, private readonly getVendorCreditService: GetVendorCreditService,
private readonly openVendorCreditService: OpenVendorCreditService,
) {} ) {}
/** /**
@@ -32,6 +34,15 @@ export class VendorCreditsApplicationService {
return this.createVendorCreditService.newVendorCredit(dto, trx); return this.createVendorCreditService.newVendorCredit(dto, trx);
} }
/**
* Opens the given vendor credit.
* @param {number} vendorCreditId - The vendor credit id.
* @returns {Promise<VendorCredit>} The opened vendor credit.
*/
openVendorCredit(vendorCreditId: number) {
return this.openVendorCreditService.openVendorCredit(vendorCreditId);
}
/** /**
* Edits the given vendor credit. * Edits the given vendor credit.
* @param {number} vendorCreditId - The vendor credit id. * @param {number} vendorCreditId - The vendor credit id.

View File

@@ -10,6 +10,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { VendorCredit } from '../models/VendorCredit'; import { VendorCredit } from '../models/VendorCredit';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';
import { Knex } from 'knex';
@Injectable() @Injectable()
export class OpenVendorCreditService { export class OpenVendorCreditService {
@@ -32,6 +33,7 @@ export class OpenVendorCreditService {
*/ */
public openVendorCredit = async ( public openVendorCredit = async (
vendorCreditId: number, vendorCreditId: number,
trx?: Knex.Transaction,
): Promise<VendorCredit> => { ): Promise<VendorCredit> => {
// Retrieve the vendor credit or throw not found service error. // Retrieve the vendor credit or throw not found service error.
const oldVendorCredit = await this.vendorCreditModel const oldVendorCredit = await this.vendorCreditModel
@@ -75,7 +77,7 @@ export class OpenVendorCreditService {
} as IVendorCreditOpenedPayload); } as IVendorCreditOpenedPayload);
return vendorCredit; return vendorCredit;
}); }, trx);
}; };
/** /**

View File

@@ -80,10 +80,9 @@ export class VendorCreditDTOTransformService {
}), }),
}; };
return R.compose( return R.compose(
VendorCredit.fromJson<VendorCredit>,
this.branchDTOTransform.transformDTO<VendorCredit>, this.branchDTOTransform.transformDTO<VendorCredit>,
this.warehouseDTOTransform.transformDTO<VendorCredit>, this.warehouseDTOTransform.transformDTO<VendorCredit>,
)(initialDTO); )(initialDTO) as VendorCredit;
}; };
/** /**

View File

@@ -292,6 +292,9 @@ export class VendorCredit extends BaseModel {
const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
return { return {
/**
* Vendor credit may belongs to vendor.
*/
vendor: { vendor: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Vendor, modelClass: Vendor,
@@ -304,6 +307,9 @@ export class VendorCredit extends BaseModel {
}, },
}, },
/**
* Vendor credit may has many item entries.
*/
entries: { entries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: ItemEntry, modelClass: ItemEntry,

View File

@@ -4,8 +4,10 @@ import { Body, Controller, Delete, Param, Post } from '@nestjs/common';
import { VendorCreditsRefundApplication } from './VendorCreditsRefund.application'; import { VendorCreditsRefundApplication } from './VendorCreditsRefund.application';
import { IRefundVendorCreditDTO } from './types/VendorCreditRefund.types'; import { IRefundVendorCreditDTO } from './types/VendorCreditRefund.types';
import { RefundVendorCredit } from './models/RefundVendorCredit'; import { RefundVendorCredit } from './models/RefundVendorCredit';
import { PublicRoute } from '../Auth/Jwt.guard';
@Controller('vendor-credits') @Controller('vendor-credits')
@PublicRoute()
export class VendorCreditsRefundController { export class VendorCreditsRefundController {
constructor( constructor(
private readonly vendorCreditsRefundApplication: VendorCreditsRefundApplication, private readonly vendorCreditsRefundApplication: VendorCreditsRefundApplication,

View File

@@ -38,7 +38,7 @@ export class RefundVendorCredit extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const { VendorCredit } = require('./VendorCredit'); const { VendorCredit } = require('../../VendorCredit/models/VendorCredit');
const { Account } = require('../../Accounts/models/Account.model'); const { Account } = require('../../Accounts/models/Account.model');
return { return {

View File

@@ -0,0 +1,71 @@
import * as request from 'supertest';
import { app } from './init-app-test';
const requestCreditNote = () => ({
customerId: 2,
creditNoteDate: '2020-02-02',
branchId: 1,
warehouseId: 1,
entries: [
{
index: 1,
itemId: 1000,
quantity: 1,
rate: 1000,
description: "It's description here.",
},
],
discount: '100',
discountType: 'amount',
});
describe('Credit Notes (e2e)', () => {
it('/credit-notes (POST)', () => {
return request(app.getHttpServer())
.post('/credit-notes')
.set('organization-id', '4064541lv40nhca')
.send(requestCreditNote())
.expect(201);
});
it('/credit-notes/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/credit-notes')
.set('organization-id', '4064541lv40nhca')
.send(requestCreditNote());
const creditNoteId = response.body.id;
return request(app.getHttpServer())
.delete(`/credit-notes/${creditNoteId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/credit-notes/:id (PUT)', async () => {
const creditNote = requestCreditNote();
const response = await request(app.getHttpServer())
.post('/credit-notes')
.set('organization-id', '4064541lv40nhca')
.send(creditNote);
const creditNoteId = response.body.id;
return request(app.getHttpServer())
.put(`/credit-notes/${creditNoteId}`)
.set('organization-id', '4064541lv40nhca')
.send(creditNote)
.expect(200);
});
it('/credit-notes/:id/open (POST)', async () => {
const response = await request(app.getHttpServer())
.post('/credit-notes')
.set('organization-id', '4064541lv40nhca')
.send(requestCreditNote());
const creditNoteId = response.body.id;
return request(app.getHttpServer())
.put(`/credit-notes/${creditNoteId}/open`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
});

View File

@@ -2,32 +2,148 @@ 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 requestSaleInvoiceBody = () => ({
customerId: 2,
invoiceDate: '2023-01-01',
dueDate: '2023-02-01',
invoiceNo: faker.string.uuid(),
referenceNo: 'REF-000201',
delivered: true,
discountType: 'percentage',
discount: 10,
branchId: 1,
warehouseId: 1,
entries: [
{
index: 1,
itemId: 1001,
quantity: 2,
rate: 1000,
description: 'Item description...',
},
],
});
describe('Sale Invoices (e2e)', () => { describe('Sale Invoices (e2e)', () => {
it('/sale-invoices (POST)', () => { it('/sale-invoices (POST)', () => {
return request(app.getHttpServer()) return request(app.getHttpServer())
.post('/sale-invoices') .post('/sale-invoices')
.set('organization-id', '4064541lv40nhca') .set('organization-id', '4064541lv40nhca')
.send({ .send(requestSaleInvoiceBody())
customerId: 2,
invoiceDate: '2023-01-01',
dueDate: '2023-02-01',
invoiceNo: 'INV-002005',
referenceNo: 'REF-000201',
delivered: true,
discountType: 'percentage',
discount: 10,
branchId: 1,
warehouseId: 1,
entries: [
{
index: 1,
itemId: 1001,
quantity: 2,
rate: 1000,
description: 'Item description...',
},
],
})
.expect(201); .expect(201);
}); });
it('/sale-invoices/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody());
return request(app.getHttpServer())
.delete(`/sale-invoices/${response.body.id}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/sale-invoices/:id (PUT)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody());
return request(app.getHttpServer())
.put(`/sale-invoices/${response.body.id}`)
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody())
.expect(200);
});
it('/sale-invoices/:id (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody());
return request(app.getHttpServer())
.get(`/sale-invoices/${response.body.id}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/sale-invoices/:id/state (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody());
return request(app.getHttpServer())
.get(`/sale-invoices/${response.body.id}/state`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/sale-invoices/:id/payments (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody());
return request(app.getHttpServer())
.get(`/sale-invoices/${response.body.id}/payments`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/sale-invoices/:id/writeoff (POST)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody());
return request(app.getHttpServer())
.post(`/sale-invoices/${response.body.id}/writeoff`)
.set('organization-id', '4064541lv40nhca')
.send({
expenseAccountId: 1024,
date: '2023-01-01',
reason: 'Write off reason',
})
.expect(200);
});
it('/sale-invoices/:id/cancel-writeoff (POST)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send(requestSaleInvoiceBody());
await request(app.getHttpServer())
.post(`/sale-invoices/${response.body.id}/writeoff`)
.set('organization-id', '4064541lv40nhca')
.send({
expenseAccountId: 1024,
date: '2023-01-01',
reason: 'Write off reason',
});
return request(app.getHttpServer())
.post(`/sale-invoices/${response.body.id}/cancel-writeoff`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/sale-invoices/:id/deliver (PUT)', async () => {
const response = await request(app.getHttpServer())
.post('/sale-invoices')
.set('organization-id', '4064541lv40nhca')
.send({
...requestSaleInvoiceBody(),
delivered: false,
});
return request(app.getHttpServer())
.post(`/sale-invoices/${response.body.id}/deliver`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
}); });

View File

@@ -0,0 +1,73 @@
import * as request from 'supertest';
import { app } from './init-app-test';
import { faker } from '@faker-js/faker';
const requestVendorCredit = () => ({
vendorId: 3,
exchangeRate: 1,
vendorCreditNumber: faker.string.uuid(),
vendorCreditDate: '2025-01-01',
entries: [
{
index: 1,
item_id: 1000,
quantity: 1,
rate: 1000,
description: "It's description here.",
},
],
branchId: 1,
warehouseId: 1,
});
describe('Vendor Credits (e2e)', () => {
it('/vendor-credits (POST)', () => {
return request(app.getHttpServer())
.post('/vendor-credits')
.set('organization-id', '4064541lv40nhca')
.send(requestVendorCredit())
.expect(201);
});
it('/vendor-credits/:id (DELETE)', async () => {
const response = await request(app.getHttpServer())
.post('/vendor-credits')
.set('organization-id', '4064541lv40nhca')
.send(requestVendorCredit());
const vendorCreditId = response.body.id;
return request(app.getHttpServer())
.delete(`/vendor-credits/${vendorCreditId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/vendor-credits/:id/open (POST)', async () => {
const response = await request(app.getHttpServer())
.post('/vendor-credits')
.set('organization-id', '4064541lv40nhca')
.send(requestVendorCredit());
const vendorCreditId = response.body.id;
return request(app.getHttpServer())
.put(`/vendor-credits/${vendorCreditId}/open`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
it('/vendor-credits/:id (GET)', async () => {
const response = await request(app.getHttpServer())
.post('/vendor-credits')
.set('organization-id', '4064541lv40nhca')
.send(requestVendorCredit());
const vendorCreditId = response.body.id;
return request(app.getHttpServer())
.get(`/vendor-credits/${vendorCreditId}`)
.set('organization-id', '4064541lv40nhca')
.expect(200);
});
});

8
pnpm-lock.yaml generated
View File

@@ -544,6 +544,9 @@ importers:
axios: axios:
specifier: ^1.6.0 specifier: ^1.6.0
version: 1.7.7 version: 1.7.7
bluebird:
specifier: ^3.7.2
version: 3.7.2
bull: bull:
specifier: ^4.16.3 specifier: ^4.16.3
version: 4.16.4 version: 4.16.4
@@ -16906,7 +16909,7 @@ packages:
postcss-modules-values: 4.0.0(postcss@8.4.47) postcss-modules-values: 4.0.0(postcss@8.4.47)
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
semver: 7.6.2 semver: 7.6.2
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4) webpack: 5.91.0(esbuild@0.23.1)
/css-loader@6.11.0(webpack@5.96.1): /css-loader@6.11.0(webpack@5.96.1):
resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==}
@@ -32291,7 +32294,7 @@ packages:
peerDependencies: peerDependencies:
webpack: ^5.0.0 webpack: ^5.0.0
dependencies: dependencies:
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4) webpack: 5.91.0(esbuild@0.23.1)
/style-loader@3.3.4(webpack@5.96.1): /style-loader@3.3.4(webpack@5.96.1):
resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==}
@@ -34736,7 +34739,6 @@ packages:
- '@swc/core' - '@swc/core'
- esbuild - esbuild
- uglify-js - uglify-js
dev: false
/webpack@5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0): /webpack@5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0):
resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==}