From 515a9847145f6e596b52adf9a9e491dbc06cb65c Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 30 Dec 2024 15:54:53 +0200 Subject: [PATCH] refactor: migrate to Nestjs --- packages/server-nest/src/main.ts | 2 +- .../CreditNotes/CreditNotes.controller.ts | 4 +- .../CommandCreditNoteDTOTransform.service.ts | 4 +- .../modules/CreditNotes/models/CreditNote.ts | 4 +- .../src/modules/Items/models/ItemEntry.ts | 216 +++++++++--------- .../SaleInvoices/SaleInvoices.controller.ts | 4 + ...ommandSaleInvoiceDTOTransformer.service.ts | 12 + .../commands/DeliverSaleInvoice.service.ts | 2 +- .../SaleInvoices/models/SaleInvoice.ts | 34 +-- .../queries/SaleInvoice.transformer.ts | 30 +-- .../models/TaxRateTransaction.model.ts | 55 +++++ .../VendorCredit/VendorCredits.controller.ts | 24 +- .../VendorCredit/VendorCredits.module.ts | 3 + .../VendorCreditsApplication.service.ts | 11 + .../commands/OpenVendorCredit.service.ts | 4 +- .../VendorCreditDTOTransform.service.ts | 3 +- .../VendorCredit/models/VendorCredit.ts | 6 + .../VendorCreditsRefund.controller.ts | 2 + .../models/RefundVendorCredit.ts | 2 +- .../server-nest/test/credit-notes.e2e-spec.ts | 71 ++++++ .../test/sale-invoices.e2e-spec.ts | 158 +++++++++++-- .../test/vendor-credits.e2e-spec.ts | 73 ++++++ pnpm-lock.yaml | 8 +- 23 files changed, 557 insertions(+), 175 deletions(-) create mode 100644 packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts create mode 100644 packages/server-nest/test/credit-notes.e2e-spec.ts create mode 100644 packages/server-nest/test/vendor-credits.e2e-spec.ts diff --git a/packages/server-nest/src/main.ts b/packages/server-nest/src/main.ts index ac95fd186..9179f2661 100644 --- a/packages/server-nest/src/main.ts +++ b/packages/server-nest/src/main.ts @@ -1,8 +1,8 @@ import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { ClsMiddleware } from 'nestjs-cls'; -import { AppModule } from './modules/App/App.module'; import './utils/moment-mysql'; +import { AppModule } from './modules/App/App.module'; import { ServiceErrorFilter } from './common/filters/service-error.filter'; async function bootstrap() { diff --git a/packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts b/packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts index dbbba8190..3ef32fd3a 100644 --- a/packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts +++ b/packages/server-nest/src/modules/CreditNotes/CreditNotes.controller.ts @@ -4,8 +4,10 @@ import { ICreditNoteEditDTO, ICreditNoteNewDTO, } from './types/CreditNotes.types'; +import { PublicRoute } from '../Auth/Jwt.guard'; @Controller('credit-notes') +@PublicRoute() export class CreditNotesController { /** * @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) { return this.creditNoteApplication.openCreditNote(creditNoteId); } diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts index 270061a5c..b6eba29eb 100644 --- a/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts +++ b/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import moment from 'moment'; import { omit } from 'lodash'; +import * as moment from 'moment'; +import * as composeAsync from 'async/compose'; import * as R from 'ramda'; -import composeAsync from 'async/compose'; import { ERRORS } from '../constants'; import { ICreditNoteEditDTO, diff --git a/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts b/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts index 2a04e8605..88dd5171c 100644 --- a/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts +++ b/packages/server-nest/src/modules/CreditNotes/models/CreditNote.ts @@ -287,7 +287,7 @@ export class CreditNote extends BaseModel { const { Branch } = require('../../Branches/models/Branch.model'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); - const { PdfTemplate } = require('../../PdfTemplate/models/PdfTemplate'); + const { PdfTemplateModel } = require('../../PdfTemplate/models/PdfTemplate'); return { /** @@ -384,7 +384,7 @@ export class CreditNote extends BaseModel { */ pdfTemplate: { relation: Model.BelongsToOneRelation, - modelClass: PdfTemplate, + modelClass: PdfTemplateModel, join: { from: 'credit_notes.pdfTemplateId', to: 'pdf_templates.id', diff --git a/packages/server-nest/src/modules/Items/models/ItemEntry.ts b/packages/server-nest/src/modules/Items/models/ItemEntry.ts index 5485d01b5..e69388a81 100644 --- a/packages/server-nest/src/modules/Items/models/ItemEntry.ts +++ b/packages/server-nest/src/modules/Items/models/ItemEntry.ts @@ -117,121 +117,121 @@ export class ItemEntry extends BaseModel { /** * Item entry relations. */ - // static get relationMappings() { - // const Item = require('models/Item'); - // const BillLandedCostEntry = require('models/BillLandedCostEntry'); - // const SaleInvoice = require('models/SaleInvoice'); - // const Bill = require('models/Bill'); - // const SaleReceipt = require('models/SaleReceipt'); - // const SaleEstimate = require('models/SaleEstimate'); - // const ProjectTask = require('models/Task'); - // const Expense = require('models/Expense'); - // const TaxRate = require('models/TaxRate'); + static get relationMappings() { + const { Item } = require('../../Items/models/Item'); + // const BillLandedCostEntry = require('models/BillLandedCostEntry'); + const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice'); + const { Bill } = require('../../Bills/models/Bill'); + const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt'); + const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate'); + // const ProjectTask = require('models/Task'); + const { Expense } = require('../../Expenses/models/Expense.model'); + const { TaxRateModel } = require('../../TaxRates/models/TaxRate.model'); - // return { - // item: { - // relation: Model.BelongsToOneRelation, - // modelClass: Item.default, - // join: { - // from: 'items_entries.itemId', - // to: 'items.id', - // }, - // }, - // allocatedCostEntries: { - // relation: Model.HasManyRelation, - // modelClass: BillLandedCostEntry.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'bill_located_cost_entries.entryId', - // }, - // }, + return { + item: { + relation: Model.BelongsToOneRelation, + modelClass: Item, + join: { + from: 'items_entries.itemId', + to: 'items.id', + }, + }, + // allocatedCostEntries: { + // relation: Model.HasManyRelation, + // modelClass: BillLandedCostEntry, + // join: { + // from: 'items_entries.referenceId', + // to: 'bill_located_cost_entries.entryId', + // }, + // }, - // invoice: { - // relation: Model.BelongsToOneRelation, - // modelClass: SaleInvoice.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'sales_invoices.id', - // }, - // }, + invoice: { + relation: Model.BelongsToOneRelation, + modelClass: SaleInvoice, + join: { + from: 'items_entries.referenceId', + to: 'sales_invoices.id', + }, + }, - // bill: { - // relation: Model.BelongsToOneRelation, - // modelClass: Bill.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'bills.id', - // }, - // }, + bill: { + relation: Model.BelongsToOneRelation, + modelClass: Bill, + join: { + from: 'items_entries.referenceId', + to: 'bills.id', + }, + }, - // estimate: { - // relation: Model.BelongsToOneRelation, - // modelClass: SaleEstimate.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'sales_estimates.id', - // }, - // }, + estimate: { + relation: Model.BelongsToOneRelation, + modelClass: SaleEstimate, + join: { + from: 'items_entries.referenceId', + to: 'sales_estimates.id', + }, + }, - // /** - // * Sale receipt reference. - // */ - // receipt: { - // relation: Model.BelongsToOneRelation, - // modelClass: SaleReceipt.default, - // join: { - // from: 'items_entries.referenceId', - // to: 'sales_receipts.id', - // }, - // }, + /** + * Sale receipt reference. + */ + receipt: { + relation: Model.BelongsToOneRelation, + modelClass: SaleReceipt, + join: { + from: 'items_entries.referenceId', + to: 'sales_receipts.id', + }, + }, - // /** - // * Project task reference. - // */ - // projectTaskRef: { - // relation: Model.HasManyRelation, - // modelClass: ProjectTask.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'tasks.id', - // }, - // }, + /** + * Project task reference. + */ + // projectTaskRef: { + // relation: Model.HasManyRelation, + // modelClass: ProjectTask.default, + // join: { + // from: 'items_entries.projectRefId', + // to: 'tasks.id', + // }, + // }, - // /** - // * Project expense reference. - // */ - // projectExpenseRef: { - // relation: Model.HasManyRelation, - // modelClass: Expense.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'expenses_transactions.id', - // }, - // }, + /** + * Project expense reference. + */ + // projectExpenseRef: { + // relation: Model.HasManyRelation, + // modelClass: Expense.default, + // join: { + // from: 'items_entries.projectRefId', + // to: 'expenses_transactions.id', + // }, + // }, - // /** - // * Project bill reference. - // */ - // projectBillRef: { - // relation: Model.HasManyRelation, - // modelClass: Bill.default, - // join: { - // from: 'items_entries.projectRefId', - // to: 'bills.id', - // }, - // }, + /** + * Project bill reference. + */ + // projectBillRef: { + // relation: Model.HasManyRelation, + // modelClass: Bill.default, + // join: { + // from: 'items_entries.projectRefId', + // to: 'bills.id', + // }, + // }, - // /** - // * Tax rate reference. - // */ - // tax: { - // relation: Model.HasOneRelation, - // modelClass: TaxRate.default, - // join: { - // from: 'items_entries.taxRateId', - // to: 'tax_rates.id', - // }, - // }, - // }; - // } + /** + * Tax rate reference. + */ + tax: { + relation: Model.HasOneRelation, + modelClass: TaxRateModel, + join: { + from: 'items_entries.taxRateId', + to: 'tax_rates.id', + }, + }, + }; + } } diff --git a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.controller.ts b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.controller.ts index d0a121cd8..b754cc10c 100644 --- a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.controller.ts +++ b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.controller.ts @@ -3,6 +3,7 @@ import { Controller, Delete, Get, + HttpCode, Param, ParseIntPipe, Post, @@ -57,6 +58,7 @@ export class SaleInvoicesController { } @Post(':id/deliver') + @HttpCode(200) deliverSaleInvoice(@Param('id', ParseIntPipe) id: number) { return this.saleInvoiceApplication.deliverSaleInvoice(id); } @@ -67,6 +69,7 @@ export class SaleInvoicesController { } @Post(':id/writeoff') + @HttpCode(200) writeOff( @Param('id', ParseIntPipe) id: number, @Body() writeoffDTO: ISaleInvoiceWriteoffDTO, @@ -75,6 +78,7 @@ export class SaleInvoicesController { } @Post(':id/cancel-writeoff') + @HttpCode(200) cancelWrittenoff(@Param('id', ParseIntPipe) id: number) { return this.saleInvoiceApplication.cancelWrittenoff(id); } diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts b/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts index c399f5852..d5ab688f6 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/CommandSaleInvoiceDTOTransformer.service.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { omit, sumBy } from 'lodash'; import * as R from 'ramda'; import * as moment from 'moment'; +import '../../../utils/moment-mysql'; import * as composeAsync from 'async/compose'; import { ISaleInvoiceCreateDTO, @@ -23,6 +24,17 @@ import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; @Injectable() 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( private branchDTOTransform: BranchTransactionDTOTransformer, private warehouseDTOTransform: WarehouseTransactionDTOTransform, diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/DeliverSaleInvoice.service.ts b/packages/server-nest/src/modules/SaleInvoices/commands/DeliverSaleInvoice.service.ts index f2240b35a..5e78dd0e4 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/DeliverSaleInvoice.service.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/DeliverSaleInvoice.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Knex } from 'knex'; -import moment from 'moment'; +import * as moment from 'moment'; import { ISaleInvoiceDeliveringPayload, ISaleInvoiceEventDeliveredPayload, diff --git a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts index df3a7b349..d89a3ed58 100644 --- a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts +++ b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts @@ -1,6 +1,7 @@ import { mixin, Model, raw } from 'objection'; 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 ModelSetting from './ModelSetting'; // import SaleInvoiceMeta from './SaleInvoice.Settings'; @@ -8,6 +9,9 @@ import moment, { MomentInput, unitOfTime } from 'moment'; // import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants'; // import ModelSearchable from './ModelSearchable'; 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 { public taxAmountWithheld: number; @@ -39,7 +43,9 @@ export class SaleInvoice extends BaseModel { public branchId: number; public warehouseId: number; - // public taxes: TaxRateTransaction[]; + public taxes: TaxRateTransaction[]; + public entries: ItemEntry[]; + public attachments: Document[]; /** * Table name @@ -438,7 +444,7 @@ export class SaleInvoice extends BaseModel { const { Branch } = require('../../Branches/models/Branch.model'); const { Warehouse } = require('../../Warehouses/models/Warehouse.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 { MatchedBankTransaction } = require('models/MatchedBankTransaction'); const { @@ -561,17 +567,17 @@ export class SaleInvoice extends BaseModel { /** * Invoice may has associated tax rate transactions. */ - // taxes: { - // relation: Model.HasManyRelation, - // modelClass: TaxRateTransaction.default, - // join: { - // from: 'sales_invoices.id', - // to: 'tax_rate_transactions.referenceId', - // }, - // filter(builder) { - // builder.where('reference_type', 'SaleInvoice'); - // }, - // }, + taxes: { + relation: Model.HasManyRelation, + modelClass: TaxRateTransaction, + join: { + from: 'sales_invoices.id', + to: 'tax_rate_transactions.referenceId', + }, + filter(builder) { + builder.where('reference_type', 'SaleInvoice'); + }, + }, /** * Sale invoice transaction may has many attached attachments. diff --git a/packages/server-nest/src/modules/SaleInvoices/queries/SaleInvoice.transformer.ts b/packages/server-nest/src/modules/SaleInvoices/queries/SaleInvoice.transformer.ts index 54ea5b52e..ed4de2dc8 100644 --- a/packages/server-nest/src/modules/SaleInvoices/queries/SaleInvoice.transformer.ts +++ b/packages/server-nest/src/modules/SaleInvoices/queries/SaleInvoice.transformer.ts @@ -184,31 +184,31 @@ export class SaleInvoiceTransformer extends Transformer { * Retrieve the taxes lines of sale invoice. * @param {ISaleInvoice} invoice */ - // protected taxes = (invoice: SaleInvoice) => { - // return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer(), { - // subtotal: invoice.subtotal, - // isInclusiveTax: invoice.isInclusiveTax, - // currencyCode: invoice.currencyCode, - // }); - // }; + protected taxes = (invoice: SaleInvoice) => { + return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer(), { + subtotal: invoice.subtotal, + isInclusiveTax: invoice.isInclusiveTax, + currencyCode: invoice.currencyCode, + }); + }; /** * Retrieves the entries of the sale invoice. * @param {ISaleInvoice} invoice * @returns {} */ - // protected entries = (invoice: SaleInvoice) => { - // return this.item(invoice.entries, new ItemEntryTransformer(), { - // currencyCode: invoice.currencyCode, - // }); - // }; + protected entries = (invoice: SaleInvoice) => { + return this.item(invoice.entries, new ItemEntryTransformer(), { + currencyCode: invoice.currencyCode, + }); + }; /** * Retrieves the sale invoice attachments. * @param {ISaleInvoice} invoice * @returns */ - // protected attachments = (invoice: SaleInvoice) => { - // return this.item(invoice.attachments, new AttachmentTransformer()); - // }; + protected attachments = (invoice: SaleInvoice) => { + return this.item(invoice.attachments, new AttachmentTransformer()); + }; } diff --git a/packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts b/packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts new file mode 100644 index 000000000..19c162cb5 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts @@ -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', + }, + }, + }; + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts b/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts index 68fbea67b..1d69d7bb4 100644 --- a/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts +++ b/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts @@ -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 { IVendorCreditCreateDTO, IVendorCreditEditDTO } from './types/VendorCredit.types'; +import { + IVendorCreditCreateDTO, + IVendorCreditEditDTO, +} from './types/VendorCredit.types'; +import { PublicRoute } from '../Auth/Jwt.guard'; @Controller('vendor-credits') +@PublicRoute() export class VendorCreditsController { constructor( private readonly vendorCreditsApplication: VendorCreditsApplicationService, @@ -13,7 +26,12 @@ export class VendorCreditsController { return this.vendorCreditsApplication.createVendorCredit(dto); } - @Put(':id') + @Put(':id/open') + async openVendorCredit(@Param('id') vendorCreditId: number) { + return this.vendorCreditsApplication.openVendorCredit(vendorCreditId); + } + + @Put(':id') async editVendorCredit( @Param('id') vendorCreditId: number, @Body() dto: IVendorCreditEditDTO, diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts b/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts index a80915388..f841e5db0 100644 --- a/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts +++ b/packages/server-nest/src/modules/VendorCredit/VendorCredits.module.ts @@ -15,6 +15,7 @@ import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module'; import { BranchesModule } from '../Branches/Branches.module'; import { WarehousesModule } from '../Warehouses/Warehouses.module'; import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; +import { OpenVendorCreditService } from './commands/OpenVendorCredit.service'; @Module({ imports: [ @@ -35,6 +36,7 @@ import { VendorCreditsApplicationService } from './VendorCreditsApplication.serv GetRefundVendorCreditService, GetVendorCreditService, VendorCreditsApplicationService, + OpenVendorCreditService ], exports: [ CreateVendorCreditService, @@ -45,6 +47,7 @@ import { VendorCreditsApplicationService } from './VendorCreditsApplication.serv GetRefundVendorCreditService, GetVendorCreditService, VendorCreditsApplicationService, + OpenVendorCreditService ], controllers: [VendorCreditsController], }) diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts b/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts index ce674abe3..e994a4453 100644 --- a/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts +++ b/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts @@ -6,6 +6,7 @@ import { GetVendorCreditService } from './queries/GetVendorCredit.service'; import { IVendorCreditEditDTO } from './types/VendorCredit.types'; import { IVendorCreditCreateDTO } from './types/VendorCredit.types'; import { Injectable } from '@nestjs/common'; +import { OpenVendorCreditService } from './commands/OpenVendorCredit.service'; @Injectable() export class VendorCreditsApplicationService { @@ -20,6 +21,7 @@ export class VendorCreditsApplicationService { private readonly editVendorCreditService: EditVendorCreditService, private readonly deleteVendorCreditService: DeleteVendorCreditService, private readonly getVendorCreditService: GetVendorCreditService, + private readonly openVendorCreditService: OpenVendorCreditService, ) {} /** @@ -32,6 +34,15 @@ export class VendorCreditsApplicationService { return this.createVendorCreditService.newVendorCredit(dto, trx); } + /** + * Opens the given vendor credit. + * @param {number} vendorCreditId - The vendor credit id. + * @returns {Promise} The opened vendor credit. + */ + openVendorCredit(vendorCreditId: number) { + return this.openVendorCreditService.openVendorCredit(vendorCreditId); + } + /** * Edits the given vendor credit. * @param {number} vendorCreditId - The vendor credit id. diff --git a/packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts index 4e576f729..338f98df4 100644 --- a/packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts +++ b/packages/server-nest/src/modules/VendorCredit/commands/OpenVendorCredit.service.ts @@ -10,6 +10,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { VendorCredit } from '../models/VendorCredit'; import { events } from '@/common/events/events'; import { ServiceError } from '@/modules/Items/ServiceError'; +import { Knex } from 'knex'; @Injectable() export class OpenVendorCreditService { @@ -32,6 +33,7 @@ export class OpenVendorCreditService { */ public openVendorCredit = async ( vendorCreditId: number, + trx?: Knex.Transaction, ): Promise => { // Retrieve the vendor credit or throw not found service error. const oldVendorCredit = await this.vendorCreditModel @@ -75,7 +77,7 @@ export class OpenVendorCreditService { } as IVendorCreditOpenedPayload); return vendorCredit; - }); + }, trx); }; /** diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts index c77012ec3..579e3542e 100644 --- a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditDTOTransform.service.ts @@ -80,10 +80,9 @@ export class VendorCreditDTOTransformService { }), }; return R.compose( - VendorCredit.fromJson, this.branchDTOTransform.transformDTO, this.warehouseDTOTransform.transformDTO, - )(initialDTO); + )(initialDTO) as VendorCredit; }; /** diff --git a/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts b/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts index c15bd11e2..787ddbd2a 100644 --- a/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts +++ b/packages/server-nest/src/modules/VendorCredit/models/VendorCredit.ts @@ -292,6 +292,9 @@ export class VendorCredit extends BaseModel { const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); return { + /** + * Vendor credit may belongs to vendor. + */ vendor: { relation: Model.BelongsToOneRelation, modelClass: Vendor, @@ -304,6 +307,9 @@ export class VendorCredit extends BaseModel { }, }, + /** + * Vendor credit may has many item entries. + */ entries: { relation: Model.HasManyRelation, modelClass: ItemEntry, diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/VendorCreditsRefund.controller.ts b/packages/server-nest/src/modules/VendorCreditsRefund/VendorCreditsRefund.controller.ts index 4ec0c99e5..d61209302 100644 --- a/packages/server-nest/src/modules/VendorCreditsRefund/VendorCreditsRefund.controller.ts +++ b/packages/server-nest/src/modules/VendorCreditsRefund/VendorCreditsRefund.controller.ts @@ -4,8 +4,10 @@ import { Body, Controller, Delete, Param, Post } from '@nestjs/common'; import { VendorCreditsRefundApplication } from './VendorCreditsRefund.application'; import { IRefundVendorCreditDTO } from './types/VendorCreditRefund.types'; import { RefundVendorCredit } from './models/RefundVendorCredit'; +import { PublicRoute } from '../Auth/Jwt.guard'; @Controller('vendor-credits') +@PublicRoute() export class VendorCreditsRefundController { constructor( private readonly vendorCreditsRefundApplication: VendorCreditsRefundApplication, diff --git a/packages/server-nest/src/modules/VendorCreditsRefund/models/RefundVendorCredit.ts b/packages/server-nest/src/modules/VendorCreditsRefund/models/RefundVendorCredit.ts index 5d7a16760..18b1dbf1b 100644 --- a/packages/server-nest/src/modules/VendorCreditsRefund/models/RefundVendorCredit.ts +++ b/packages/server-nest/src/modules/VendorCreditsRefund/models/RefundVendorCredit.ts @@ -38,7 +38,7 @@ export class RefundVendorCredit extends BaseModel { * Relationship mapping. */ static get relationMappings() { - const { VendorCredit } = require('./VendorCredit'); + const { VendorCredit } = require('../../VendorCredit/models/VendorCredit'); const { Account } = require('../../Accounts/models/Account.model'); return { diff --git a/packages/server-nest/test/credit-notes.e2e-spec.ts b/packages/server-nest/test/credit-notes.e2e-spec.ts new file mode 100644 index 000000000..dfc4328bb --- /dev/null +++ b/packages/server-nest/test/credit-notes.e2e-spec.ts @@ -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); + }); +}); diff --git a/packages/server-nest/test/sale-invoices.e2e-spec.ts b/packages/server-nest/test/sale-invoices.e2e-spec.ts index 355b4ac4b..f38fc564b 100644 --- a/packages/server-nest/test/sale-invoices.e2e-spec.ts +++ b/packages/server-nest/test/sale-invoices.e2e-spec.ts @@ -2,32 +2,148 @@ import * as request from 'supertest'; import { faker } from '@faker-js/faker'; 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)', () => { it('/sale-invoices (POST)', () => { return request(app.getHttpServer()) .post('/sale-invoices') .set('organization-id', '4064541lv40nhca') - .send({ - 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...', - }, - ], - }) + .send(requestSaleInvoiceBody()) .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); + }); }); diff --git a/packages/server-nest/test/vendor-credits.e2e-spec.ts b/packages/server-nest/test/vendor-credits.e2e-spec.ts new file mode 100644 index 000000000..b50c12a9b --- /dev/null +++ b/packages/server-nest/test/vendor-credits.e2e-spec.ts @@ -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); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7575d058..47b900af3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -544,6 +544,9 @@ importers: axios: specifier: ^1.6.0 version: 1.7.7 + bluebird: + specifier: ^3.7.2 + version: 3.7.2 bull: specifier: ^4.16.3 version: 4.16.4 @@ -16906,7 +16909,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.47) postcss-value-parser: 4.2.0 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): resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} @@ -32291,7 +32294,7 @@ packages: peerDependencies: webpack: ^5.0.0 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): resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} @@ -34736,7 +34739,6 @@ packages: - '@swc/core' - esbuild - uglify-js - dev: false /webpack@5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0): resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==}