From 0c6bbba64798c61bc64f09ade9de40e949e997a0 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 3 Mar 2021 13:34:18 +0200 Subject: [PATCH 1/3] feat(items): filter auto-complete results. feat(contacts): filter auto-complete results. --- .../src/api/controllers/Contacts/Contacts.ts | 26 +++++--- server/src/api/controllers/Items.ts | 63 ++++++++++--------- server/src/interfaces/Contact.ts | 7 ++- server/src/interfaces/Item.ts | 8 ++- server/src/models/Contact.js | 6 ++ .../src/services/Contacts/ContactsService.ts | 2 +- server/src/services/Items/ItemsService.ts | 8 +-- 7 files changed, 70 insertions(+), 50 deletions(-) diff --git a/server/src/api/controllers/Contacts/Contacts.ts b/server/src/api/controllers/Contacts/Contacts.ts index 7ff3ccade..5a6d79866 100644 --- a/server/src/api/controllers/Contacts/Contacts.ts +++ b/server/src/api/controllers/Contacts/Contacts.ts @@ -1,34 +1,38 @@ import { check, param, query, body, ValidationChain } from 'express-validator'; import { Router, Request, Response, NextFunction } from 'express'; -import { Inject } from 'typedi'; +import { Inject, Service } from 'typedi'; import BaseController from 'api/controllers/BaseController'; import ContactsService from 'services/Contacts/ContactsService'; +import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { DATATYPES_LENGTH } from 'data/DataTypes'; -import { Service } from 'typedi'; @Service() export default class ContactsController extends BaseController { @Inject() contactsService: ContactsService; + @Inject() + dynamicListService: DynamicListingService; + /** * Express router. */ router() { const router = Router(); + router.get( + '/auto-complete', + [...this.autocompleteQuerySchema], + this.validationResult, + this.asyncMiddleware(this.autocompleteContacts.bind(this)), + this.dynamicListService.handlerErrorsToResponse + ); router.get( '/:id', [param('id').exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.getContact.bind(this)) ); - router.get( - '/auto-complete', - [...this.autocompleteQuerySchema], - this.validationResult, - this.asyncMiddleware(this.autocompleteContacts.bind(this)) - ); return router; } @@ -79,11 +83,13 @@ export default class ContactsController extends BaseController { const filter = { filterRoles: [], sortOrder: 'asc', - columnSortBy: 'created_at', + columnSortBy: 'display_name', limit: 10, ...this.matchedQueryData(req), }; - + if (filter.stringifiedFilterRoles) { + filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles); + } try { const contacts = await this.contactsService.autocompleteContacts( tenantId, diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index a18f55c41..b029e8447 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -216,38 +216,11 @@ export default class ItemsController extends BaseController { query('stringified_filter_roles').optional().isJSON(), query('limit').optional().isNumeric().toInt(), + + query('keyword').optional().isString().trim().escape(), ]; } - /** - * Auto-complete list. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async autocompleteList(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; - const filter = { - filterRoles: [], - sortOrder: 'asc', - columnSortBy: 'created_at', - limit: 10, - ...this.matchedQueryData(req), - }; - - try { - const items = await this.itemsService.autocompleteItems( - tenantId, - filter - ); - return res.status(200).send({ - items, - }); - } catch (error) { - next(error); - } - } - /** * Stores the given item details to the storage. * @param {Request} req @@ -410,6 +383,38 @@ export default class ItemsController extends BaseController { } } + /** + * Auto-complete list. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + async autocompleteList(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + const filter = { + filterRoles: [], + sortOrder: 'asc', + columnSortBy: 'name', + limit: 10, + keyword: '', + ...this.matchedQueryData(req), + }; + if (filter.stringifiedFilterRoles) { + filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles); + } + try { + const items = await this.itemsService.autocompleteItems( + tenantId, + filter + ); + return res.status(200).send({ + items, + }); + } catch (error) { + next(error); + } + } + /** * Deletes items in bulk. * @param {Request} req diff --git a/server/src/interfaces/Contact.ts b/server/src/interfaces/Contact.ts index a8519819c..6a6c0e279 100644 --- a/server/src/interfaces/Contact.ts +++ b/server/src/interfaces/Contact.ts @@ -1,7 +1,5 @@ -// Contact Interfaces. - -import { IDynamicListFilter } from "./DynamicFilter"; +import { IFilterRole } from "./DynamicFilter"; // ---------------------------------- export interface IContactAddress { @@ -208,6 +206,9 @@ export interface ICustomersFilter extends IDynamicListFilter { export interface IContactsAutoCompleteFilter { limit: number, keyword: string, + filterRoles?: IFilterRole[]; + columnSortBy: string; + sortOrder: string; } export interface IContactAutoCompleteItem { diff --git a/server/src/interfaces/Item.ts b/server/src/interfaces/Item.ts index 27b26b743..ed2c0d147 100644 --- a/server/src/interfaces/Item.ts +++ b/server/src/interfaces/Item.ts @@ -1,4 +1,4 @@ -import { IDynamicListFilter } from 'interfaces/DynamicFilter'; +import { IFilterRole } from 'interfaces/DynamicFilter'; export interface IItem{ id: number, @@ -72,7 +72,7 @@ export interface IItemsService { itemsList(tenantId: number, itemsFilter: IItemsFilter): Promise<{items: IItem[]}>; } -export interface IItemsFilter extends IDynamicListFilter { +export interface IItemsFilter extends IDynamicListFilterDTO { stringifiedFilterRoles?: string, page: number, pageSize: number, @@ -81,5 +81,7 @@ export interface IItemsFilter extends IDynamicListFilter { export interface IItemsAutoCompleteFilter { limit: number, keyword: string, - + filterRoles?: IFilterRole[]; + columnSortBy: string; + sortOrder: string; } \ No newline at end of file diff --git a/server/src/models/Contact.js b/server/src/models/Contact.js index fb4953ef5..fe5166490 100644 --- a/server/src/models/Contact.js +++ b/server/src/models/Contact.js @@ -97,6 +97,12 @@ export default class Contact extends TenantModel { static get fields() { return { + contact_service: { + column: 'contact_service', + }, + display_name: { + column: 'display_name', + }, created_at: { column: 'created_at', } diff --git a/server/src/services/Contacts/ContactsService.ts b/server/src/services/Contacts/ContactsService.ts index e152fa137..938af5f46 100644 --- a/server/src/services/Contacts/ContactsService.ts +++ b/server/src/services/Contacts/ContactsService.ts @@ -201,7 +201,7 @@ export default class ContactsService { // Retrieve contacts list by the given query. const contacts = await Contact.query().onBuild((builder) => { if (contactsFilter.keyword) { - builder.where('display_name', 'LIKE', contactsFilter.keyword); + builder.where('display_name', 'LIKE', `%${contactsFilter.keyword}%`); } dynamicList.buildQuery()(builder); builder.limit(contactsFilter.limit); diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 8b9b76044..d3046fe8c 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -523,14 +523,14 @@ export default class ItemsService implements IItemsService { dynamicFilter.buildQuery()(builder); builder.limit(itemsFilter.limit); - }); - // const autocompleteItems = this.transformAutoCompleteItems(items); + if (itemsFilter.keyword) { + builder.where('name', 'LIKE', `%${itemsFilter.keyword}%`); + } + }); return items; } - // transformAutoCompleteItems(item) - /** * Validates the given item or items have no associated invoices or bills. * @param {number} tenantId - Tenant id. From 9fef91b96504081a5778b2db6e7ce547902541b9 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 3 Mar 2021 14:05:58 +0200 Subject: [PATCH 2/3] feat(saleReceipts): fix schedule compute inventory items cost. --- .../api/controllers/Sales/SalesInvoices.ts | 4 +- .../api/controllers/Sales/SalesReceipts.ts | 133 ++++++++++-------- .../services/Accounting/JournalCommands.ts | 2 + server/src/services/Sales/SalesReceipts.ts | 14 +- 4 files changed, 76 insertions(+), 77 deletions(-) diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index 8db65f6c6..06fd7d010 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -7,7 +7,7 @@ import SaleInvoiceService from 'services/Sales/SalesInvoices'; import ItemsService from 'services/Items/ItemsService'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { ServiceError } from 'exceptions'; -import { ISaleInvoiceDTO, ISalesInvoicesFilter } from 'interfaces'; +import { ISaleInvoiceDTO, ISaleInvoiceCreateDTO } from 'interfaces'; @Service() export default class SaleInvoicesController extends BaseController { @@ -153,7 +153,7 @@ export default class SaleInvoicesController extends BaseController { */ async newSaleInvoice(req: Request, res: Response, next: NextFunction) { const { tenantId, user } = req; - const saleInvoiceDTO: ISaleInvoiceDTO = this.matchedBodyData(req); + const saleInvoiceDTO: ISaleInvoiceCreateDTO = this.matchedBodyData(req); try { // Creates a new sale invoice with associated entries. diff --git a/server/src/api/controllers/Sales/SalesReceipts.ts b/server/src/api/controllers/Sales/SalesReceipts.ts index cec72b5ab..f8cf3658d 100644 --- a/server/src/api/controllers/Sales/SalesReceipts.ts +++ b/server/src/api/controllers/Sales/SalesReceipts.ts @@ -9,7 +9,7 @@ import { ServiceError } from 'exceptions'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; @Service() -export default class SalesReceiptsController extends BaseController{ +export default class SalesReceiptsController extends BaseController { @Inject() saleReceiptService: SaleReceiptService; @@ -24,36 +24,35 @@ export default class SalesReceiptsController extends BaseController{ router.post( '/:id/close', - [ - ...this.specificReceiptValidationSchema, - ], + [...this.specificReceiptValidationSchema], this.validationResult, asyncMiddleware(this.closeSaleReceipt.bind(this)), - this.handleServiceErrors, - ) + this.handleServiceErrors + ); router.post( - '/:id', [ + '/:id', + [ ...this.specificReceiptValidationSchema, ...this.salesReceiptsValidationSchema, ], this.validationResult, asyncMiddleware(this.editSaleReceipt.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.post( '/', this.salesReceiptsValidationSchema, this.validationResult, asyncMiddleware(this.newSaleReceipt.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.delete( '/:id', this.specificReceiptValidationSchema, this.validationResult, asyncMiddleware(this.deleteSaleReceipt.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.get( '/', @@ -61,15 +60,14 @@ export default class SalesReceiptsController extends BaseController{ this.validationResult, asyncMiddleware(this.getSalesReceipts.bind(this)), this.handleServiceErrors, - this.dynamicListService.handlerErrorsToResponse, + this.dynamicListService.handlerErrorsToResponse ); router.get( - '/:id', [ - ...this.specificReceiptValidationSchema, - ], + '/:id', + [...this.specificReceiptValidationSchema], this.validationResult, asyncMiddleware(this.getSaleReceipt.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); return router; } @@ -94,8 +92,14 @@ export default class SalesReceiptsController extends BaseController{ check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.quantity').exists().isNumeric().toInt(), check('entries.*.rate').exists().isNumeric().toInt(), - check('entries.*.discount').optional({ nullable: true }).isNumeric().toInt(), - check('entries.*.description').optional({ nullable: true }).trim().escape(), + check('entries.*.discount') + .optional({ nullable: true }) + .isNumeric() + .toInt(), + check('entries.*.description') + .optional({ nullable: true }) + .trim() + .escape(), check('receipt_message').optional().trim().escape(), check('statement').optional().trim().escape(), @@ -106,9 +110,7 @@ export default class SalesReceiptsController extends BaseController{ * Specific sale receipt validation schema. */ get specificReceiptValidationSchema() { - return [ - param('id').exists().isNumeric().toInt() - ]; + return [param('id').exists().isNumeric().toInt()]; } /** @@ -121,14 +123,14 @@ export default class SalesReceiptsController extends BaseController{ query('column_sort_by').optional(), query('sort_order').optional().isIn(['desc', 'asc']), query('page').optional().isNumeric().toInt(), - query('page_size').optional().isNumeric().toInt(), + query('page_size').optional().isNumeric().toInt(), ]; } /** * Creates a new receipt. - * @param {Request} req - * @param {Response} res + * @param {Request} req + * @param {Response} res */ async newSaleReceipt(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; @@ -136,11 +138,10 @@ export default class SalesReceiptsController extends BaseController{ try { // Store the given sale receipt details with associated entries. - const storedSaleReceipt = await this.saleReceiptService - .createSaleReceipt( - tenantId, - saleReceiptDTO, - ); + const storedSaleReceipt = await this.saleReceiptService.createSaleReceipt( + tenantId, + saleReceiptDTO + ); return res.status(200).send({ id: storedSaleReceipt.id, message: 'Sale receipt has been created successfully.', @@ -152,8 +153,8 @@ export default class SalesReceiptsController extends BaseController{ /** * Deletes the sale receipt with associated entries and journal transactions. - * @param {Request} req - * @param {Response} res + * @param {Request} req + * @param {Response} res */ async deleteSaleReceipt(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; @@ -162,7 +163,7 @@ export default class SalesReceiptsController extends BaseController{ try { // Deletes the sale receipt. await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId); - + return res.status(200).send({ id: saleReceiptId, message: 'Sale receipt has been deleted successfully.', @@ -175,8 +176,8 @@ export default class SalesReceiptsController extends BaseController{ /** * Edit the sale receipt details with associated entries and re-write * journal transaction on the same date. - * @param {Request} req - - * @param {Response} res - + * @param {Request} req - + * @param {Response} res - */ async editSaleReceipt(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; @@ -188,7 +189,7 @@ export default class SalesReceiptsController extends BaseController{ await this.saleReceiptService.editSaleReceipt( tenantId, saleReceiptId, - saleReceipt, + saleReceipt ); return res.status(200).send({ id: saleReceiptId, @@ -201,9 +202,9 @@ export default class SalesReceiptsController extends BaseController{ /** * Marks the given the sale receipt as closed. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async closeSaleReceipt(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; @@ -211,10 +212,7 @@ export default class SalesReceiptsController extends BaseController{ try { // Update the given sale receipt details. - await this.saleReceiptService.closeSaleReceipt( - tenantId, - saleReceiptId, - ); + await this.saleReceiptService.closeSaleReceipt(tenantId, saleReceiptId); return res.status(200).send({ id: saleReceiptId, message: 'Sale receipt has been closed successfully.', @@ -226,7 +224,7 @@ export default class SalesReceiptsController extends BaseController{ /** * Listing sales receipts. - * @param {Request} req + * @param {Request} req * @param {Response} res */ async getSalesReceipts(req: Request, res: Response, next: NextFunction) { @@ -244,8 +242,11 @@ export default class SalesReceiptsController extends BaseController{ } try { - const { salesReceipts, pagination, filterMeta } = await this.saleReceiptService - .salesReceiptsList(tenantId, filter); + const { + salesReceipts, + pagination, + filterMeta, + } = await this.saleReceiptService.salesReceiptsList(tenantId, filter); return res.status(200).send({ sale_receipts: salesReceipts, @@ -259,20 +260,23 @@ export default class SalesReceiptsController extends BaseController{ /** * Retrieve the sale receipt with associated entries. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ - async getSaleReceipt(req: Request, res: Response, next: NextFunction) { + async getSaleReceipt(req: Request, res: Response, next: NextFunction) { const { id: saleReceiptId } = req.params; const { tenantId } = req; try { - const saleReceipt = await this.saleReceiptService.getSaleReceipt(tenantId, saleReceiptId); + const saleReceipt = await this.saleReceiptService.getSaleReceipt( + tenantId, + saleReceiptId + ); return res.status(200).send({ sale_receipt: saleReceipt, - }) + }); } catch (error) { next(error); } @@ -280,41 +284,46 @@ export default class SalesReceiptsController extends BaseController{ /** * Handles service errors. - * @param {Error} error - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Error} error + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ - handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) { + handleServiceErrors( + error: Error, + req: Request, + res: Response, + next: NextFunction + ) { if (error instanceof ServiceError) { if (error.errorType === 'SALE_RECEIPT_NOT_FOUND') { return res.boom.badRequest(null, { errors: [{ type: 'SALE_RECEIPT_NOT_FOUND', code: 100 }], - }) + }); } if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_FOUND') { return res.boom.badRequest(null, { errors: [{ type: 'DEPOSIT_ACCOUNT_NOT_FOUND', code: 200 }], - }) + }); } if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET') { return res.boom.badRequest(null, { errors: [{ type: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET', code: 300 }], - }) + }); } if (error.errorType === 'ITEMS_NOT_FOUND') { return res.boom.badRequest(null, { - errors: [{ type: 'ITEMS_NOT_FOUND', code: 400, }], + errors: [{ type: 'ITEMS_NOT_FOUND', code: 400 }], }); } if (error.errorType === 'ENTRIES_IDS_NOT_FOUND') { return res.boom.badRequest(null, { - errors: [{ type: 'ENTRIES_IDS_NOT_FOUND', code: 500, }], + errors: [{ type: 'ENTRIES_IDS_NOT_FOUND', code: 500 }], }); } if (error.errorType === 'NOT_SELL_ABLE_ITEMS') { return res.boom.badRequest(null, { - errors: [{ type: 'NOT_SELL_ABLE_ITEMS', code: 600, }], + errors: [{ type: 'NOT_SELL_ABLE_ITEMS', code: 600 }], }); } if (error.errorType === 'SALE.RECEIPT.NOT.FOUND') { @@ -340,4 +349,4 @@ export default class SalesReceiptsController extends BaseController{ } next(error); } -}; +} diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index 5a3135fa5..f9f7fff83 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -417,6 +417,8 @@ export default class JournalCommands { referenceId: saleReceipt.id, date: saleReceipt.receiptDate, userId: saleReceipt.userId, + transactionNumber: saleReceipt.receiptNumber, + referenceNumber: saleReceipt.referenceNo, }; // XXX Debit - Deposit account. const depositEntry = new JournalEntry({ diff --git a/server/src/services/Sales/SalesReceipts.ts b/server/src/services/Sales/SalesReceipts.ts index 27bc61f9f..d412f3a8e 100644 --- a/server/src/services/Sales/SalesReceipts.ts +++ b/server/src/services/Sales/SalesReceipts.ts @@ -181,19 +181,16 @@ export default class SalesReceiptService { tenantId, saleReceiptDTO.depositAccountId ); - // Validate items IDs existance on the storage. await this.itemsEntriesService.validateItemsIdsExistance( tenantId, saleReceiptDTO.entries ); - // Validate the sellable items. await this.itemsEntriesService.validateNonSellableEntriesItems( tenantId, saleReceiptDTO.entries ); - // Validate sale receipt number uniuqiness. if (saleReceiptDTO.receiptNumber) { await this.validateReceiptNumberUnique( @@ -460,7 +457,7 @@ export default class SalesReceiptService { saleReceipt: ISaleReceipt, override?: boolean ): Promise { - await this.inventoryService.recordInventoryTransactionsFromItemsEntries( + return this.inventoryService.recordInventoryTransactionsFromItemsEntries( tenantId, saleReceipt.id, 'SaleReceipt', @@ -468,15 +465,6 @@ export default class SalesReceiptService { 'OUT', override, ); - // Triggers `onInventoryTransactionsCreated` event. - this.eventDispatcher.dispatch( - events.saleReceipt.onInventoryTransactionsCreated, - { - tenantId, - saleReceipt, - saleReceiptId: saleReceipt.id, - } - ); } /** From 4f98db4c4b00150b9cb55f78bc04244cdb5e7ca7 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 3 Mar 2021 14:25:16 +0200 Subject: [PATCH 3/3] feat(items): item type can not changing in inventory type. --- server/src/api/controllers/Items.ts | 9 ++++++ server/src/services/Items/ItemsService.ts | 38 ++++++++++++++++++++++- server/src/services/Items/constants.ts | 2 +- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index b029e8447..d6a083723 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -538,6 +538,15 @@ export default class ItemsController extends BaseController { ], }); } + if (error.errorType === 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE') { + return res.status(400).send({ + errors: [{ + type: 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE', + message: 'Cannot change inventory item type', + code: 340, + }], + }); + } } next(error); } diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index d3046fe8c..d36724ffc 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -227,6 +227,37 @@ export default class ItemsService implements IItemsService { }; } + /** + * + * @param {IItemDTO} itemDTO - Item DTO. + * @param {IItem} oldItem - + */ + private transformEditItemDTOToModel(itemDTO: IItemDTO, oldItem: IItem) { + return { + ...itemDTO, + ...(itemDTO.type === 'inventory' && oldItem.type !== 'inventory' + ? { + quantityOnHand: 0, + } + : {}), + }; + } + + /** + * Validate item type in edit item mode, cannot change item inventory type. + * @param {IItemDTO} itemDTO + * @param {IItem} oldItem + */ + private validateEditItemInventoryType(itemDTO: IItemDTO, oldItem: IItem) { + if ( + itemDTO.type && + oldItem.type === 'inventory' && + itemDTO.type !== oldItem.type + ) { + throw new ServiceError(ERRORS.ITEM_CANNOT_CHANGE_INVENTORY_TYPE); + } + } + /** * Creates a new item. * @param {number} tenantId DTO @@ -288,6 +319,11 @@ export default class ItemsService implements IItemsService { // Validates the given item existance on the storage. const oldItem = await this.getItemOrThrowError(tenantId, itemId); + this.validateEditItemInventoryType(itemDTO, oldItem); + + // Transform the edit item DTO to model. + const itemModel = this.transformEditItemDTOToModel(itemDTO, oldItem); + // Validate whether the given item name already exists on the storage. await this.validateItemNameUniquiness(tenantId, itemDTO.name, itemId); @@ -318,7 +354,7 @@ export default class ItemsService implements IItemsService { } const newItem = await Item.query().patchAndFetchById(itemId, { - ...itemDTO, + ...itemModel, }); this.logger.info('[items] item edited successfully.', { tenantId, diff --git a/server/src/services/Items/constants.ts b/server/src/services/Items/constants.ts index 927137e8a..0bafbe384 100644 --- a/server/src/services/Items/constants.ts +++ b/server/src/services/Items/constants.ts @@ -1,4 +1,3 @@ - export const ERRORS = { NOT_FOUND: 'NOT_FOUND', ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND', @@ -18,4 +17,5 @@ export const ERRORS = { ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', + ITEM_CANNOT_CHANGE_INVENTORY_TYPE: 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE', };