From 81791a0be692b3c3093953bfcff487c80d865c73 Mon Sep 17 00:00:00 2001 From: lomamech Date: Sat, 17 Jan 2026 13:07:48 +0100 Subject: [PATCH] Add automated price revaluation due to currency appreciation and depreciation --- client/src/i18n/en/form.json | 5 + client/src/i18n/fr/form.json | 5 + client/src/js/services/ModalService.js | 18 +++ .../configuration/groups/groups.html | 7 ++ .../inventory/configuration/groups/groups.js | 8 ++ .../configuration/groups/groups.service.js | 6 + .../groups/modals/coefficient.modal.js | 29 +++++ .../groups/modals/coefficient.tmpl.html | 54 +++++++++ package-lock.json | 31 +----- server/config/routes.js | 2 + server/controllers/inventory/index.js | 73 ++++++++++++- .../controllers/inventory/inventory/core.js | 2 +- test/integration/inventory/group.js | 103 ++++++++++++++++++ test/integration/inventory/import.js | 1 - 14 files changed, 310 insertions(+), 34 deletions(-) create mode 100644 client/src/modules/inventory/configuration/groups/modals/coefficient.modal.js create mode 100644 client/src/modules/inventory/configuration/groups/modals/coefficient.tmpl.html diff --git a/client/src/i18n/en/form.json b/client/src/i18n/en/form.json index d78a3b6e58..f65c5e5bcc 100644 --- a/client/src/i18n/en/form.json +++ b/client/src/i18n/en/form.json @@ -247,6 +247,7 @@ "PAYMENT_PERIOD": "Payment period", "PER": "per", "PREVIOUS": "Previous", + "PRICING_COEFFICIENT_ADMINISTRATION": "Pricing Coefficient Administration", "RECORD_SAME": "No changes have been made to the form", "REQUISITION": { "CANCEL": "Cancel the inventory requisition request", @@ -658,6 +659,7 @@ "NB_SPOUSE": "Spouse Count", "NET_SALARY": "Net Salary", "NEW_EXCHANGE_RATE": "New Exchange Rate", + "NEW_VALUE": "Updated Coefficient", "NO_DEBTOR_GROUPS": "No debtor groups", "NO_DISTRIBUTABLE": "Not distributable", "NO_FISCALYEAR_REGISTERED": "There are no fiscal years registered so far! Click 'Create a Fiscal Year' to add your first fiscal year", @@ -673,6 +675,7 @@ "NUMBER_SUBMISSIONS": "Number of submissions", "OFF_DAYS": "Off Days", "OF": "Of", + "OLD_VALUE": "Previous Coefficient", "ON_DELIVERY": "On Delivery", "ON": "On", "ON_PURCHASE": "On Purchase", @@ -1024,7 +1027,9 @@ "MANUFACTURER_MODEL": "Enter Manufacturer model", "MAX_DEBT": "Overdraft Limit (Max Debt)", "NAME": "Enter a name", + "NEW_VALUE": "eg. current exchange rate", "NOTES": "Enter any additional information", + "OLD_VALUE": "eg. previous exchange rate", "PACKAGING": "Enter count per package", "PATIENT_ID": "Enter Patient ID", "PATIENT_NAME": "Enter a Patient Name", diff --git a/client/src/i18n/fr/form.json b/client/src/i18n/fr/form.json index 5dbdfa9223..b70b0068b4 100644 --- a/client/src/i18n/fr/form.json +++ b/client/src/i18n/fr/form.json @@ -247,6 +247,7 @@ "PAYMENT_PERIOD": "Période de paiement", "PER": "par", "PREVIOUS": "Précédent", + "PRICING_COEFFICIENT_ADMINISTRATION": "Gestion des coefficients tarifaires", "RECORD_SAME": "Aucun changement n'a été apporté sur le formulaire", "REQUISITION": { "CANCEL": "Annuler la demande de réquisition des stocks", @@ -661,6 +662,7 @@ "NB_SPOUSE": "Nombre epoux (se)", "NET_SALARY": "Salaire Net", "NEW_EXCHANGE_RATE": "Nouveau taux d'echange", + "NEW_VALUE": "Nouveau Coefficient", "NO_DEBTOR_GROUPS": "Aucun Groupes Débiteurs", "NO_DISTRIBUTABLE": "Non distribuable", "NO_FISCALYEAR_REGISTERED": "Il n'y a aucune année fiscale enregistré jusque là! Cliquez sur 'Créer L'année Fiscale' pour ajouter votre première année fiscale", @@ -675,6 +677,7 @@ "NUMBER_SUBMISSIONS": "Nombre de soumissions", "OF": "De", "OFF_DAYS": "jours fériés", + "OLD_VALUE": "Ancien Coefficient", "ON_DELIVERY": "Lors de la delivarance", "ON_PURCHASE": "Lors de l'achat", "ON": "Sur", @@ -1023,7 +1026,9 @@ "MANUFACTURER_MODEL": "Entrer le modèle", "MAX_DEBT": "Entrez la limite de découvert (dette maximale)", "NAME": "Entrer le nom", + "NEW_VALUE": "Par exemple nouveau taux de Change", "NOTES": "Commentaire", + "OLD_VALUE": "Par exemple l'ancien taux de change", "ORIGIN": "Origine", "PACKAGING": "Entrer le Conditionnement du paquet", "PASSWORD": "Entrer le mot de passe", diff --git a/client/src/js/services/ModalService.js b/client/src/js/services/ModalService.js index ee77f5011a..cbecfe05d3 100644 --- a/client/src/js/services/ModalService.js +++ b/client/src/js/services/ModalService.js @@ -39,6 +39,8 @@ function ModalService(Modal) { // inventory group action : add or edit service.openInventoryGroupActions = openInventoryGroupActions; + service.openCoefficientAdministration = openCoefficientAdministration; + // inventory type action : add or edit service.openInventoryTypeActions = openInventoryTypeActions; @@ -187,6 +189,22 @@ function ModalService(Modal) { return instance.result; } + /** + * Coefficient Administration Modal + */ + function openCoefficientAdministration(request) { + const params = angular.extend(modalParameters, { + templateUrl : 'modules/inventory/configuration/groups/modals/coefficient.tmpl.html', + controller : 'CoefficientActionsModalController', + controllerAs : '$ctrl', + size : 'xs', + resolve : { data : () => request }, + }); + + const instance = Modal.open(params); + return instance.result; + } + /** Inventory Types Modal for actions */ function openInventoryTypeActions(request) { diff --git a/client/src/modules/inventory/configuration/groups/groups.html b/client/src/modules/inventory/configuration/groups/groups.html index f13e825bd8..5448f0493a 100644 --- a/client/src/modules/inventory/configuration/groups/groups.html +++ b/client/src/modules/inventory/configuration/groups/groups.html @@ -6,6 +6,13 @@ INVENTORY.LIST_GROUP ({{ GroupsCtrl.groupList.length }}) + + + + + FORM.BUTTONS.SUBMIT + + + diff --git a/package-lock.json b/package-lock.json index 636615e128..21b5cd4c93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -182,7 +182,6 @@ "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -234,7 +233,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -278,7 +276,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1207,7 +1204,6 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -1508,7 +1504,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.10.0.tgz", "integrity": "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2" }, @@ -1768,7 +1763,6 @@ "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.1.2.tgz", "integrity": "sha512-pYcg+cxVd9E9poC7AJf7ZrlQQrwAV6KVkiPvlbLJX5km+pBWrUOGQhdd87oIGc7/5iVQ7qSiAdl9QD+l55yegg==", "license": "MIT", - "peer": true, "engines": { "node": ">=4.0.0" } @@ -1804,7 +1798,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1875,8 +1868,7 @@ "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz", "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==", "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward.", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/angular-animate": { "version": "1.8.3", @@ -2461,7 +2453,6 @@ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "license": "Apache-2.0", - "peer": true, "peerDependencies": { "bare-abort-controller": "*" }, @@ -2752,7 +2743,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3054,7 +3044,6 @@ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -3130,7 +3119,6 @@ "integrity": "sha512-ikaUhQvQWchRYj2K54itFp3nrcxaFRpSDQxDlRzSn9aWgu9Pi7lD8yFxTso4WnQ39+WZ69oB/qOvqp+isJIIWA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4.0.0" }, @@ -3197,7 +3185,6 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", - "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -4396,8 +4383,7 @@ "version": "0.0.1534754", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1534754.tgz", "integrity": "sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/dezalgo": { "version": "1.0.4", @@ -5011,7 +4997,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5138,7 +5123,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5597,7 +5581,6 @@ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", "license": "MIT", - "peer": true, "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.7", @@ -8744,7 +8727,6 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -8925,7 +8907,6 @@ "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -8965,7 +8946,6 @@ "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "chai": "*", "karma": ">=0.10.9" @@ -9947,7 +9927,6 @@ "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", @@ -11597,7 +11576,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -12092,7 +12070,6 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -12344,7 +12321,6 @@ "integrity": "sha512-Sdpl/zsYOsagZ4ICoZJPGZw8d9gZmK5DcxVal11dXi/1/t2eIXHjCf5NfmhDg5XnG9Nye+yo/LqMzIxie2rHTw==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@puppeteer/browsers": "2.11.0", "chromium-bidi": "12.0.1", @@ -12563,7 +12539,6 @@ "resolved": "https://registry.npmjs.org/redis/-/redis-5.10.0.tgz", "integrity": "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==", "license": "MIT", - "peer": true, "dependencies": { "@redis/bloom": "5.10.0", "@redis/client": "5.10.0", @@ -14809,7 +14784,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15076,7 +15050,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/server/config/routes.js b/server/config/routes.js index 59f0598ab9..36c22ae1c1 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -366,6 +366,8 @@ exports.configure = function configure(app) { app.get('/inventory/log/:uuid', inventory.logs); app.get('/inventory/download/log/:uuid', inventory.logDownLoad); + app.post('/inventory/coefficient_setting', inventory.coefficientSetting); + /** Inventory Group API endpoints */ app.post('/inventory/groups', inventory.createInventoryGroups); app.get('/inventory/groups', inventory.listInventoryGroups); diff --git a/server/controllers/inventory/index.js b/server/controllers/inventory/index.js index 1be9dfc9ca..9dc0d43268 100644 --- a/server/controllers/inventory/index.js +++ b/server/controllers/inventory/index.js @@ -58,6 +58,7 @@ exports.deleteInventoryUnits = deleteInventoryUnits; exports.deleteInventory = deleteInventory; exports.getInventoryUnitCosts = getInventoryUnitCosts; +exports.coefficientSetting = coefficientSetting; // expose routes for import exports.importing = importing; @@ -139,10 +140,9 @@ async function logDownLoad(req, res, next) { try { const { lang } = req.query; const rows = await core.inventoryLog(req.params.uuid); - // inventory columns + // inventory columns const dictionary = util.loadDictionary(lang); - const inventory = await core.getItemsMetadata({ uuid : req.params.uuid }); const lines = [ @@ -150,6 +150,7 @@ async function logDownLoad(req, res, next) { ]; lines.push({ + // eslint-disable-next-line you-dont-need-lodash-underscore/get column1 : _.get(dictionary, 'FORM.LABELS.INVENTORY'), column2 : inventory[0].label || '', column3 : '', @@ -157,9 +158,12 @@ async function logDownLoad(req, res, next) { lines.push({ column1 : '', column2 : '', column3 : '' }); rows.forEach(r => { - const text = JSON.parse(r.text); + const { text } = r; + lines.push({ + // eslint-disable-next-line you-dont-need-lodash-underscore/get column1 : _.get(dictionary, 'FORM.LABELS.USER'), + // eslint-disable-next-line you-dont-need-lodash-underscore/get column2 : _.get(dictionary, 'FORM.LABELS.DATE'), column3 : '', }); @@ -168,13 +172,16 @@ async function logDownLoad(req, res, next) { lines.push({ column1 : '', + // eslint-disable-next-line you-dont-need-lodash-underscore/get column2 : _.get(dictionary, 'FORM.LABELS.FROM'), + // eslint-disable-next-line you-dont-need-lodash-underscore/get column3 : _.get(dictionary, 'FORM.LABELS.TO'), }); const currentchanges = Object.keys(text.current); currentchanges.forEach(cc => { const line2 = { + // eslint-disable-next-line you-dont-need-lodash-underscore/get column1 : _.get(dictionary, core.inventoryColsMap[cc]), column2 : text.last[cc], column3 : text.current[cc], @@ -347,7 +354,67 @@ function createInventoryGroups(req, res, next) { .catch((error) => { core.errorHandler(error, req, res, next); }); +} + +async function coefficientSetting(req, res, next) { + try { + const { old, new : newValue } = req.body; + + // Validate input + if (old === undefined || newValue === undefined) { + return res.status(400).json({ + error : '"old" and "new" values must be provided.', + }); + } + + if (typeof old !== 'number' || typeof newValue !== 'number') { + return res.status(400).json({ + error : '"old" and "new" must be numbers.', + }); + } + + if (old === 0) { + return res.status(400).json({ + error : '"old" value cannot be zero. Anomaly detected.', + }); + } + + // Retrieve the smallest monetary unit for rounding + const minMonetaryUnit = req.session.enterprise.min_monentary_unit; + + // Calculate the coefficient + const coefficient = newValue / old; + + // Prepare transactions + const transactions = []; + + // Fetch all unlocked inventory items + const query = ` + SELECT BUID(inv.uuid) AS inventory_uuid, inv.price + FROM inventory AS inv + WHERE inv.locked = 0; + `; + const items = await db.exec(query); + + items.forEach(item => { + // Calculate the new price and round to the nearest monetary unit + const itemPrice = Math.floor((item.price * coefficient) / minMonetaryUnit) * minMonetaryUnit; + + const updateData = { + price : itemPrice, + note : `Coefficient Setting: ${coefficient} (1): ${item.price} (2): ${itemPrice}`, + }; + + transactions.push(core.updateItemsMetadata(updateData, item.inventory_uuid, req.session)); + }); + // Execute all updates in parallel + const results = await Promise.all(transactions); + + return res.status(201).json(results); // <-- 'return' added + } catch (error) { + return next(error); // <-- 'return' added + } } /** diff --git a/server/controllers/inventory/inventory/core.js b/server/controllers/inventory/inventory/core.js index 673a070c93..17309ff47d 100644 --- a/server/controllers/inventory/inventory/core.js +++ b/server/controllers/inventory/inventory/core.js @@ -370,7 +370,7 @@ async function getItemsMetadataById(uid, query = {}) { i.stock_max, i.created_at AS timestamp, i.type_id, i.unit_id, i.unit_weight, i.unit_volume, ig.sales_account, i.default_quantity, i.delay, i.purchase_interval, i.importance, i.last_purchase, i.num_purchase, i.manufacturer_brand, i.manufacturer_model, - i.is_count_per_container, ig.tracking_consumption, ig.tracking_expiration + i.is_count_per_container, ig.tracking_consumption, ig.tracking_expiration, i.note FROM inventory AS i JOIN inventory_type AS it JOIN inventory_unit AS iu JOIN inventory_group AS ig ON i.type_id = it.id diff --git a/test/integration/inventory/group.js b/test/integration/inventory/group.js index 5d72e7d4ca..c1fdd4ff93 100644 --- a/test/integration/inventory/group.js +++ b/test/integration/inventory/group.js @@ -70,4 +70,107 @@ describe('test/integration/inventory/groups The inventory groups API', () => { }) .catch(helpers.handler); }); + + it('should update all unlocked inventory items with the correct coefficient', async () => { + // Arrange: define old and new values + const payload = { + old : 1000, + new : 2000, + }; + + const minMonentaryUnit = 0.01; // simulate session value + const coefficient = payload.new / payload.old; + + // Act: call the endpoint + const res = await agent.post('/inventory/coefficient_setting').send(payload); + + // Assert: status should be 201 + expect(res.status).to.equal(201); + + // Assert: response should be an array of updated items + expect(res.body).to.be.an('array'); + res.body.forEach(item => { + expect(item).to.have.property('price'); + expect(item).to.have.property('note'); + + // Extract old price from note to verify calculation + const regex = /\(1\)\s*([\d.]+)\s*\(2\)\s*([\d.]+)/; + const match = item.note.match(regex); + + function round(num, decimals = 2) { + return Math.round(num * 10 ** decimals) / 10 ** decimals; + } + + if (match) { + const oldPrice = parseFloat(match[1], 10); + const expectedPrice = Math.floor((oldPrice * coefficient) / minMonentaryUnit) * minMonentaryUnit; + + expect(round(item.price, 2)).to.equal(round(expectedPrice, 2)); + } + }); + }); + + it('should return 500 if there is an internal error', async () => { + // Arrange: send invalid payload to trigger an error + const payload = { old : 0, new : 2000 }; // division by zero + + // Act + const res = await agent.post('/inventory/coefficient_setting').send(payload); + + // Assert + expect(res.status).to.be.oneOf([400, 500]); + }); + + it('should return 400 if required fields are missing', async () => { + const payload = {}; // missing old/new + + const res = await agent.post('/inventory/coefficient_setting').send(payload); + + expect(res.status).to.equal(400); + expect(res.body).to.have.property('error'); + }); + + it('should restore all unlocked inventory items to their old values', async () => { + // Arrange: define old and new values + // 'old' is the current value (after first update) + // 'new' is the value to restore + const payload = { + old : 2000, + new : 1000, + }; + + const minMonentaryUnit = 0.01; // simulate session value + const coefficient = payload.new / payload.old; // calculate the coefficient to restore prices + + // Act: call the endpoint to restore values + const res = await agent.post('/inventory/coefficient_setting').send(payload); + + // Assert: status should be 201 Created + expect(res.status).to.equal(201); + + // Assert: response should be an array of updated items + expect(res.body).to.be.an('array'); + + res.body.forEach(item => { + expect(item).to.have.property('price'); + expect(item).to.have.property('note'); + + // Extract old price from note to verify calculation + const regex = /\(1\)\s*([\d.]+)\s*\(2\)\s*([\d.]+)/; + const match = item.note.match(regex); + + // Helper function to round numbers to 2 decimals + function round(num, decimals = 2) { + return Math.round(num * 10 ** decimals) / 10 ** decimals; + } + + if (match) { + const oldPrice = parseFloat(match[1], 10); // original price before any update + const expectedPrice = Math.floor((oldPrice * coefficient) / minMonentaryUnit) * minMonentaryUnit; + + // Assert that the restored price matches the expected value + expect(round(item.price, 2)).to.equal(round(expectedPrice, 2)); + } + }); + }); }); diff --git a/test/integration/inventory/import.js b/test/integration/inventory/import.js index b279c68aaa..7f20571c30 100644 --- a/test/integration/inventory/import.js +++ b/test/integration/inventory/import.js @@ -38,7 +38,6 @@ describe('test/integration/inventory/import The inventory import API', () => { return agent.get('/inventory/metadata') .then(res => { totalInventoriesBeforeImport = res.body.length; - // import inventories from a csv file return agent.post('/inventory/import') /**