diff --git a/client/src/i18n/en/stock.json b/client/src/i18n/en/stock.json
index 962429e32b..6d3472c8ff 100644
--- a/client/src/i18n/en/stock.json
+++ b/client/src/i18n/en/stock.json
@@ -101,7 +101,7 @@
"ITEMS" : "Items",
"LEGEND" : "Legend",
"LIFETIME" : "Lifetime",
- "LOT_LIFETIME" : "Lifetime By Lot",
+ "LOT_LIFETIME" : "Lifetime by Usage",
"CURRENT_QUANTITY" : "Current Quantity",
"RISK" : "Risk",
"RISK_QUANTITY" : "Risk Quantity",
@@ -192,6 +192,7 @@
"REQUIRES_PO" : "Requires a purchase order",
"RESPONSIBLE" : "Stock Manager",
"RISK_OF_EXPIRATION" : "Risk of expiration",
+ "RISK_OF_STOCK_OUT" : "Risk of stock out",
"ROWS" : "Rows",
"SEARCH" : "Search",
"SECURITY" : "Security Stock",
diff --git a/client/src/i18n/fr/stock.json b/client/src/i18n/fr/stock.json
index 631975dea9..e39d7d6d0e 100644
--- a/client/src/i18n/fr/stock.json
+++ b/client/src/i18n/fr/stock.json
@@ -192,6 +192,7 @@
"REQUIRES_PO" : "Nécessitant une commande d'achat",
"RESPONSIBLE" : "Gestionnaire du stock",
"RISK_OF_EXPIRATION" : "Risque de péremption",
+ "RISK_OF_STOCK_OUT" : "Risque de rupture",
"ROWS" : "Lignes",
"SEARCH" : "Chercher",
"SECURITY" : "Stock de securité",
diff --git a/client/src/js/services/LotService.js b/client/src/js/services/LotService.js
index aca888c3b5..d532987688 100644
--- a/client/src/js/services/LotService.js
+++ b/client/src/js/services/LotService.js
@@ -45,12 +45,12 @@ function LotService(Api, $http, util) {
* NOTE: Once a case is found to be true, all following cases are ignored.
* 1. If the stock is exhausted, warn about that.
* (Recall that the user can choose to display exhausted lots in Lots Registry.)
- * 2. If the Lot is expired, warn about that.
- * 3. If the Lot is near expiration, warn about that.
+ * 2. If the Lot is near expiration, warn about that.
* NOTE that this assumes the CMM and that stock exits all come from this
* lot exclusively. But the CMM is based on not only on this lot, but on
* an aggregate all lots of this inventory item, so there is no guarantee
* that this will be correct.
+ * 3. If the Lot is expired, warn about that.
* 4. If a Lot is at risk of running out, warn about that. Again this is
* based on the aggregate CMM which may not work out exactly for this
* Lot in practice.
@@ -58,10 +58,11 @@ function LotService(Api, $http, util) {
* Based on this logic, only one of the warning flags should be set to true.
*/
lots.computeLotWarningFlags = (lot) => {
- lots.exhausted = false;
- lots.expired = false;
- lots.near_expiration = false;
- lots.at_risk = false;
+ lot.exhausted = false;
+ lot.expired = false;
+ lot.near_expiration = false;
+ lot.at_risk = false;
+
if (lot.quantity <= 0) {
lot.exhausted = true;
} else if (lot.lifetime < 0) {
@@ -75,6 +76,7 @@ function LotService(Api, $http, util) {
lot.at_risk = true;
}
+ return lot;
};
return lots;
diff --git a/client/src/modules/stock/lots/registry.js b/client/src/modules/stock/lots/registry.js
index d673fc62ef..0cd0b9cdc0 100644
--- a/client/src/modules/stock/lots/registry.js
+++ b/client/src/modules/stock/lots/registry.js
@@ -22,6 +22,7 @@ function StockLotsController(
// grouping box
vm.groupingBox = LotsRegistry.groupingBox;
+
// barcode scanner
vm.openBarcodeScanner = openBarcodeScanner;
@@ -145,23 +146,46 @@ function StockLotsController(
.then((lots) => {
const current = new Date();
+ const totals = {
+ expired : 0,
+ 'at-risk-of-expiring' : 0,
+ 'at-risk' : 0,
+ 'out-of-stock' : 0,
+ };
+
lots.forEach((lot) => {
const delay = moment(new Date(lot.expiration_date)).diff(current);
lot.delay_expiration = moment.duration(delay).humanize(true);
- LotService.computeLotWarningFlags(lot);
+
+ if (lot.expired) {
+ totals.expired += 1;
+ }
+
+ if (lot.at_risk) {
+ totals['at-risk'] += 1;
+ }
+
+ if (lot.near_expiration) {
+ totals['at-risk-of-expiring'] += 1;
+ }
+
+ if (lot.exhausted) {
+ totals['out-of-stock'] += 1;
+ }
+
+ // serialize tag names for filters
+ lot.tagNames = lot.tags.map(tag => tag.name).join(',');
+ lot.tags.forEach(addColorStyle);
});
+ vm.totals = totals;
+
lots.forEach(LotsRegistry.formatLotsWithoutExpirationDate);
// FIXME(@jniles): we should do this ordering on the server via an ORDER BY
lots.sort(LotsRegistry.orderByDepot);
- // serialize tag names for filters
- vm.gridOptions.data = lots.map(lot => {
- lot.tagNames = lot.tags.map(tag => tag.name).join(',');
- lot.tags.forEach(addColorStyle);
- return lot;
- });
+ vm.gridOptions.data = lots;
vm.grouping.unfoldAllGroups();
vm.gridApi.core.notifyDataChange(uiGridConstants.dataChange.COLUMN);
diff --git a/client/src/modules/stock/lots/registry.service.js b/client/src/modules/stock/lots/registry.service.js
index a71d5dcd81..a7c8a8fe5c 100644
--- a/client/src/modules/stock/lots/registry.service.js
+++ b/client/src/modules/stock/lots/registry.service.js
@@ -92,70 +92,6 @@ function LotsRegistryService(uiGridConstants, Session) {
cellClass : 'text-right',
headerCellFilter : 'translate',
}, {
- field : 'avg_consumption',
- displayName : 'STOCK.CMM',
- headerTooltip : 'STOCK.CMM',
- cellClass : 'text-right',
- headerCellFilter : 'translate',
- type : 'number',
- }, {
- field : 'S_MONTH',
- displayName : 'STOCK.MSD',
- headerTooltip : 'STOCK.MSD',
- cellClass : 'text-right',
- headerCellFilter : 'translate',
- type : 'number',
- }, {
- field : 'lifetime',
- displayName : 'STOCK.LIFETIME',
- headerTooltip : 'STOCK.LIFETIME',
- headerCellFilter : 'translate',
- cellClass : 'text-right',
- cellTemplate : 'modules/stock/lots/templates/lifetime.cell.html',
- type : 'number',
- sort : {
- direction : uiGridConstants.ASC,
- priority : 2,
- },
- }, {
- field : 'S_LOT_LIFETIME',
- displayName : 'STOCK.LOT_LIFETIME',
- headerTooltip : 'STOCK.LOT_LIFETIME',
- headerCellFilter : 'translate',
- cellTemplate : 'modules/stock/lots/templates/lot_lifetime.cell.html',
- cellClass : 'text-right',
- type : 'number',
- }, {
- field : 'S_RISK',
- displayName : 'STOCK.RISK',
- headerTooltip : 'STOCK.RISK',
- headerCellFilter : 'translate',
- cellTemplate : 'modules/stock/lots/templates/risk.cell.html',
- cellClass : 'text-right',
- type : 'number',
- sort : {
- direction : uiGridConstants.DESC,
- priority : 3,
- },
- },
- {
- field : 'IS_IN_RISK_EXPIRATION',
- displayName : 'STOCK.STATUS.IS_IN_RISK_OF_EXPIRATION',
- headerTooltip : 'STOCK.STATUS.IS_IN_RISK_OF_EXPIRATION',
- headerCellFilter : 'translate',
- cellTemplate : 'modules/stock/lots/templates/in_risk_of_expiration.html',
- cellClass : 'text-right',
- type : 'number',
- },
- {
- field : 'S_RISK_QUANTITY',
- displayName : 'STOCK.RISK_QUANTITY',
- headerTooltip : 'STOCK.RISK_QUANTITY',
- headerCellFilter : 'translate',
- cellTemplate : 'modules/stock/lots/templates/risk_quantity.cell.html',
- type : 'number',
- },
- {
field : 'tagNames',
displayName : 'TAG.LABEL',
headerTooltip : 'TAG.LABEL',
@@ -176,18 +112,24 @@ function LotsRegistryService(uiGridConstants, Session) {
- STOCK.EXPIRATION:
+ STOCK.EXPIRED: {{grid.appScope.totals.expired}}
+
+
+
+
+ STOCK.RISK_OF_EXPIRATION: {{grid.appScope.totals['at-risk-of-expiring']}}
- STOCK.RISK_OF_EXPIRATION:
+ STOCK.RISK_OF_STOCK_OUT: {{grid.appScope.totals['at-risk']}}
- STOCK.STATUS.STOCK_OUT:
+ STOCK.STATUS.STOCK_OUT: {{grid.appScope.totals['out-of-stock']}}
+
`;
@@ -205,6 +147,8 @@ function LotsRegistryService(uiGridConstants, Session) {
delete lot.expiration_date;
delete lot.lifetime;
delete lot.S_LOT_LIFETIME;
+ delete lot.S_RISK;
+ delete lot.S_RISK_QUANTITY;
}
};
diff --git a/client/src/modules/stock/lots/templates/in_risk_of_expiration.html b/client/src/modules/stock/lots/templates/in_risk_of_expiration.html
deleted file mode 100644
index c5eba2beb8..0000000000
--- a/client/src/modules/stock/lots/templates/in_risk_of_expiration.html
+++ /dev/null
@@ -1,4 +0,0 @@
-
- FORM.LABELS.YES
- FORM.LABELS.NO
-
diff --git a/client/src/modules/stock/lots/templates/lifetime.cell.html b/client/src/modules/stock/lots/templates/lifetime.cell.html
index 30381a888e..c097a046b5 100644
--- a/client/src/modules/stock/lots/templates/lifetime.cell.html
+++ b/client/src/modules/stock/lots/templates/lifetime.cell.html
@@ -1,5 +1,5 @@
-
- {{ row.entity.lifetime }} FORM.LABELS.MONTH
+
+ {{ row.entity.lifetime }} FORM.LABELS.DAYS
diff --git a/client/src/modules/stock/lots/templates/lot_lifetime.cell.html b/client/src/modules/stock/lots/templates/lot_lifetime.cell.html
index cc63cabd6f..6e4fe2891d 100644
--- a/client/src/modules/stock/lots/templates/lot_lifetime.cell.html
+++ b/client/src/modules/stock/lots/templates/lot_lifetime.cell.html
@@ -1,5 +1,5 @@
-
- {{ row.entity.S_LOT_LIFETIME }} FORM.LABELS.MONTH
+
+ {{ row.entity.lifetime_lot}} FORM.LABELS.DAYS
diff --git a/client/src/modules/stock/lots/templates/risk.cell.html b/client/src/modules/stock/lots/templates/risk.cell.html
index fd1203f3b8..3070c14123 100644
--- a/client/src/modules/stock/lots/templates/risk.cell.html
+++ b/client/src/modules/stock/lots/templates/risk.cell.html
@@ -1,5 +1,5 @@
-
-
- {{ row.entity.S_RISK }} FORM.LABELS.MONTH
+
+
+ {{ row.entity.S_RISK }} FORM.LABELS.DAYS
diff --git a/client/src/modules/stock/lots/templates/risk_quantity.cell.html b/client/src/modules/stock/lots/templates/risk_quantity.cell.html
index c91fef5f39..e4b4b5e40b 100644
--- a/client/src/modules/stock/lots/templates/risk_quantity.cell.html
+++ b/client/src/modules/stock/lots/templates/risk_quantity.cell.html
@@ -1,3 +1,3 @@
-
+
{{ row.entity.S_RISK_QUANTITY }}
-
\ No newline at end of file
+
diff --git a/client/src/modules/stock/lots/templates/row.expired.html b/client/src/modules/stock/lots/templates/row.expired.html
index 7b230cc4f0..4f4f9b848b 100644
--- a/client/src/modules/stock/lots/templates/row.expired.html
+++ b/client/src/modules/stock/lots/templates/row.expired.html
@@ -4,8 +4,12 @@
ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.uid"
ui-grid-one-bind-id-grid="rowRenderIndex + '-' + col.uid + '-cell'"
class="ui-grid-cell"
- ng-class="{ 'ui-grid-row-header-cell': col.isRowHeader, 'at-risk-of-expiring': row.entity.near_expiration,
- 'expired': row.entity.expired, 'at-risk': row.entity.at_risk, 'out-of-stock': row.entity.exhausted,
+ ng-class="{
+ 'ui-grid-row-header-cell': col.isRowHeader,
+ 'at-risk-of-expiring': row.entity.near_expiration,
+ 'expired': row.entity.expired,
+ 'at-risk': row.entity.at_risk,
+ 'out-of-stock': row.entity.exhausted,
}"
data-vals="{{col.isRowHeader}}"
role="{{col.isRowHeader ? 'rowheader' : 'gridcell'}}"
diff --git a/package.json b/package.json
index f70d0151bc..d74925e1c0 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"angular-translate-loader-static-files": "^2.18.4",
"angular-translate-loader-url": "^2.18.4",
"angular-ui-bootstrap": "^2.5.6",
- "angular-ui-grid": "^4.9.0",
+ "angular-ui-grid": "^4.10.0",
"body-parser": "^1.18.3",
"bootstrap": "^3.3.0",
"chart.js": "^2.9.4",
@@ -106,7 +106,7 @@
"cross-env": "^7.0.3",
"csvtojson": "^2.0.8",
"debug": "^4.3.1",
- "delay": "^4.4.1",
+ "delay": "^5.0.0",
"dotenv": "^8.0.0",
"excel4node": "^1.7.0",
"express": "^4.16.4",
@@ -167,7 +167,7 @@
"gulp-template": "^5.0.0",
"gulp-typescript": "^5.0.0",
"gulp-uglify": "^3.0.1",
- "karma": "^6.0.3",
+ "karma": "^6.0.4",
"karma-chai": "^0.1.0",
"karma-chai-dom": "^1.1.0",
"karma-chai-spies": "^0.1.4",
diff --git a/server/controllers/stock/core.js b/server/controllers/stock/core.js
index 27712092d4..db1a65477c 100644
--- a/server/controllers/stock/core.js
+++ b/server/controllers/stock/core.js
@@ -231,7 +231,7 @@ async function getLotsDepot(depotUuid, params, finalClause) {
SUM(m.quantity) AS mvt_quantity,
d.text AS depot_text, l.unit_cost, l.expiration_date,
d.min_months_security_stock,
- ROUND(DATEDIFF(l.expiration_date, CURRENT_DATE()) / 30.5) AS lifetime,
+ DATEDIFF(l.expiration_date, CURRENT_DATE()) AS lifetime,
BUID(l.inventory_uuid) AS inventory_uuid, BUID(l.origin_uuid) AS origin_uuid,
i.code, i.text, BUID(m.depot_uuid) AS depot_uuid,
m.date AS entry_date, i.avg_consumption, i.purchase_interval, i.delay,
@@ -266,23 +266,22 @@ async function getLotsDepot(depotUuid, params, finalClause) {
params.average_consumption_algo,
);
- // FIXME(@jniles) - this step seems to mostly just change the ordering of lots. Can we combine
- // it with the getBulkInventoryCMM?
+ // add lot indicators to the inventory list.
let inventoriesWithLotsProcessed = computeLotIndicators(inventoriesWithManagementData);
if (_status) {
- inventoriesWithLotsProcessed = inventoriesWithLotsProcessed.filter(row => row.status === _status);
+ inventoriesWithLotsProcessed = inventoriesWithLotsProcessed.filter(lot => lot.status === _status);
}
// Since the status of a product risking expiry is only defined
// after the comparison with the CMM, reason why the filtering
// is not carried out with an SQL request
if (parseInt(params.is_expiry_risk, 10) === 1) {
- inventoriesWithLotsProcessed = inventoriesWithLotsProcessed.filter(item => (item.S_RISK < 0 && item.lifetime > 0));
+ inventoriesWithLotsProcessed = inventoriesWithLotsProcessed.filter(lot => lot.flags.near_expiration);
}
if (parseInt(params.is_expiry_risk, 10) === 0) {
- inventoriesWithLotsProcessed = inventoriesWithLotsProcessed.filter(item => (item.S_RISK >= 0 && item.lifetime > 0));
+ inventoriesWithLotsProcessed = inventoriesWithLotsProcessed.filter(lot => !lot.flags.near_expiration);
}
return inventoriesWithLotsProcessed;
@@ -647,7 +646,7 @@ async function getInventoryQuantityAndConsumption(params) {
SUM(m.quantity * IF(m.is_exit = 1, -1, 1)) AS quantity,
d.text AS depot_text, d.min_months_security_stock,
l.unit_cost, l.expiration_date,
- ROUND(DATEDIFF(l.expiration_date, CURRENT_DATE()) / 30.5) AS lifetime,
+ DATEDIFF(l.expiration_date, CURRENT_DATE()) AS lifetime,
BUID(l.inventory_uuid) AS inventory_uuid, BUID(l.origin_uuid) AS origin_uuid,
l.entry_date, BUID(i.uuid) AS inventory_uuid, i.code, i.text, BUID(m.depot_uuid) AS depot_uuid,
i.avg_consumption, i.purchase_interval, i.delay, MAX(m.created_at) AS last_movement_date,
@@ -693,16 +692,35 @@ async function getInventoryQuantityAndConsumption(params) {
* process multiple stock lots
*
* @description
+ * Computes the indicators on stock lots. Sets the following flags:
+ * 1) expired - if the expiration date is in the past
+ * 2) exhausted - if the quantity is 0.
+ * 3) near_expiration - if the expiration date is sooner than the lot's stock out date
+ * 4) at_risk - if the lot is part of an _inventory_ that is at risk of running out.
+ *
+ * Further, we add the following properties:
+ * 1) lot_lifetime - the number of days left before the stock runs out.
+ * 2) max_stock_date - the last day stock will be usable.
+ * 3) min_stock_date - the first day the stock will be consumed
+ * 4) usable_quantity_remaining - the total quantity that will be usable of the lot.
+ *
+ * Note that these indicators are only tracked if we are tracking_consumption
+ * or tracking_expiration.
+ *
+ * The algorithm for calculation at risk of expiry and at risk of stock out is
+ * FIFO by expiration. Basically, we assume that lots will be used in order of
+ * their expiration dates.
*/
function computeLotIndicators(inventories) {
const flattenLots = [];
- const inventoryByDepots = _.groupBy(inventories, 'depot_uuid');
- _.map(inventoryByDepots, (depotInventories) => {
+ const inventoryByDepots = _.groupBy(inventories, 'depot_uuid');
+ _.forEach(inventoryByDepots, (depotInventories) => {
const inventoryLots = _.groupBy(depotInventories, 'inventory_uuid');
- _.map(inventoryLots, (lots) => {
+ _.forEach(inventoryLots, (lots) => {
+
// if we don't have the default CMM (avg_consumption) use the
// defined or computed CMM for each lots
const cmm = _.max(lots.map(lot => lot.avg_consumption));
@@ -711,36 +729,84 @@ function computeLotIndicators(inventories) {
// assuming the lot with lowest quantity is consumed first
let orderedInventoryLots = _.orderBy(lots, 'quantity', 'asc');
- // order lots by ascending lifetime has a hight priority than quantity
+ // order lots by ascending lifetime has a higher priority than quantity
orderedInventoryLots = _.orderBy(orderedInventoryLots, 'lifetime', 'asc');
- // compute the lot coefficient
- let lotLifetime = 0;
- _.each(orderedInventoryLots, lot => {
+ // compute the lot coefficients
+ let runningLotLifetimes = 0;
+ const today = moment().endOf('day').toDate();
+
+ _.forEach(orderedInventoryLots, (lot) => {
if (!lot.tracking_expiration) {
lot.expiration_date = '';
}
- if (lot.tracking_consumption) {
+ lot.exhausted = lot.quantity === 0;
+ lot.expired = !lot.exhausted && (lot.expiration_date < today);
+
+ // algorithm for tracking the stock consumption by day
+ if (lot.tracking_consumption && !lot.exhausted && !lot.expired) {
// apply the same CMM to all lots and update monthly consumption
lot.avg_consumption = cmm;
lot.S_MONTH = cmm ? Math.floor(lot.quantity / cmm) : lot.quantity;
- const zeroMSD = Math.round(lot.S_MONTH) === 0;
+ // get daily average consumption
+ const consumptionPerDay = lot.avg_consumption / 30.5;
+
+ // check if the lot will expire before being used up
+ const numDaysOfStockBeforeConsumed = runningLotLifetimes + Math.ceil(lot.quantity / consumptionPerDay);
+ const stockOutDate = moment(new Date()).add(numDaysOfStockBeforeConsumed, 'days').toDate();
+
+ lot.near_expiration = lot.expiration_date <= stockOutDate;
+
+ // here, we are attempting to calculate if this quantity will be at risk of expiry
+ // given that all inventory is consummed in order of their expiration dates.
+
+ // Get the real quantity of usable stock remaining
+ // If the lot will expire before being used, use the expiration date as the max date.
+ // If the lot will be consumed by CMM before expiry, use the CMM date as the max date.
+ // We've already calculated this above - it is the near_expiration variable
+ const maxStockDate = lot.near_expiration ? lot.expiration_date : stockOutDate;
+ const numDaysOfStockLeft = moment(maxStockDate).diff(new Date(), 'day');
+
+ // calculate finally the remaining days of stock, assuming we use this stock in order
+ // maybe we will not ever get a chance to use it ... then it is 0.
+ lot.lifetime_lot = Math.max(0, numDaysOfStockLeft - runningLotLifetimes);
+ lot.min_stock_date = moment(new Date()).add(runningLotLifetimes).toDate();
+ lot.max_stock_date = maxStockDate;
+
+ // add to the running LotLifetimes so that the next product will be used after it.
+ runningLotLifetimes += lot.lifetime_lot;
+
+ // the usable quantity remaining is the minimum of the stock actually available
+ // and the amount able to be consumed in the time remaining
+ const usableStockQuantityRemaining = Math.min(lot.lifetime_lot * consumptionPerDay, lot.quantity);
+ lot.usable_quantity_remaining = usableStockQuantityRemaining;
+
+ // compute the "risk" quantities and duration. This is the amount of stock in this lot that
+ // the enterprise will lose at the current rate of consumption. We express this in
+ // both quantity and in time, despite this not corresponding to a definite time period. The
+ // time period should be understood as a _quantity_ measurement - how long the pharmacy could
+ // have run if this stock wasn't expiring.
+ lot.S_RISK_QUANTITY = Math.round(lot.quantity - usableStockQuantityRemaining);
+ // if S_RISK_QUANTITY is less than a day's stock, it is likely a rounding error. Set it to 0.
+ if (lot.S_RISK_QUANTITY < consumptionPerDay) { lot.S_RISK_QUANTITY = 0; }
+ lot.S_RISK = Math.round(lot.S_RISK_QUANTITY / consumptionPerDay);
+ }
- const numMonthsOfStockLeft = (lot.quantity / lot.CMM); // how many months of stock left
- const today = new Date();
- // if we have more months of stock than the expiration date,
- // then we'll need to label these are in risk of expiration
- const numDaysOfStockLeft = numMonthsOfStockLeft * 30.5;
- const isInRiskOfExpiration = lot.expiration_date <= moment(today).add(numDaysOfStockLeft, 'days').toDate();
- lot.IS_IN_RISK_EXPIRATION = isInRiskOfExpiration;
+ // if the inventory item is at risk of stock out, mark the stock lot "at risk". It may be that a single
+ // lot is not at risk of expiring, but the inventory is at risk of expiring
+ // TODO(@jniles): does this make sense?
+ lot.at_risk = !lot.expired && (lot.status === 'minimum_reached' || lot.status === 'security_reached');
+
+ // attach flags after computation for use on client
+ lot.flags = {
+ expired : lot.expired,
+ near_expiration : lot.near_expiration,
+ exhausted : lot.exhausted,
+ at_risk : lot.at_risk,
+ };
- lot.S_LOT_LIFETIME = zeroMSD || lot.lifetime < 0 ? 0 : lot.lifetime - lotLifetime;
- lot.S_RISK = zeroMSD ? 0 : lot.S_LOT_LIFETIME - lot.S_MONTH;
- lot.S_RISK_QUANTITY = Math.round(lot.S_RISK * lot.avg_consumption);
- lotLifetime += lot.S_LOT_LIFETIME;
- }
flattenLots.push(lot);
});
});
@@ -854,9 +920,12 @@ function getInventoryMovements(params) {
});
}
-function listStatus(req, res, next) {
+async function listStatus(req, res, next) {
const sql = `SELECT id, status_key, title_key FROM status`;
- db.exec(sql).then(status => {
+ try {
+ const status = await db.exec(sql);
res.status(200).json(status);
- }).catch(next);
+ } catch (e) {
+ next(e);
+ }
}
diff --git a/test/integration/stock/lots.js b/test/integration/stock/lots.js
index 5173c57bb5..31fd4063a3 100644
--- a/test/integration/stock/lots.js
+++ b/test/integration/stock/lots.js
@@ -29,7 +29,7 @@ describe('(/lots/) The lots HTTP API', () => {
return agent.get('/stock/lots/depots/')
.query(conditions)
.then((res) => {
- helpers.api.listed(res, 3);
+ helpers.api.listed(res, 2);
})
.catch(helpers.handler);
});
diff --git a/yarn.lock b/yarn.lock
index a70bda6390..919f7b6bb3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -598,10 +598,10 @@ angular-ui-bootstrap@^2.5.6:
resolved "https://registry.yarnpkg.com/angular-ui-bootstrap/-/angular-ui-bootstrap-2.5.6.tgz#23937322ec641a6fbee16498cc32452aa199e7c5"
integrity sha512-yzcHpPMLQl0232nDzm5P4iAFTFQ9dMw0QgFLuKYbDj9M0xJ62z0oudYD/Lvh1pWfRsukiytP4Xj6BHOSrSXP8A==
-angular-ui-grid@^4.9.0:
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/angular-ui-grid/-/angular-ui-grid-4.9.1.tgz#1b4df057a31318729915faad5eb42969d4298bb0"
- integrity sha512-kR+B22gxqm+mhkmYgtv43uY5z1a5mk5riyL7WLygkAxjy8h0IgFi/PlkHwqA3Oit5+SpzKcTAntVg8OIwaiFog==
+angular-ui-grid@^4.10.0:
+ version "4.10.0"
+ resolved "https://registry.yarnpkg.com/angular-ui-grid/-/angular-ui-grid-4.10.0.tgz#5904a366c49a0a687c6a23a48b46d2b5c57d43ce"
+ integrity sha512-y0AtYkTf41JTmWc+hyl0bXYtUnjOWHu8JIhrxCx7kn9wdbOAkcfyYhV/MT2Cp0pS4gzUI1sjL0qqRyrTtKljnQ==
dependencies:
angular ">=1.4.0 1.8.x"
@@ -2607,10 +2607,10 @@ del@^6.0.0:
rimraf "^3.0.2"
slash "^3.0.0"
-delay@^4.4.1:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/delay/-/delay-4.4.1.tgz#6e02d02946a1b6ab98b39262ced965acba2ac4d1"
- integrity sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ==
+delay@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d"
+ integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==
delayed-stream@~1.0.0:
version "1.0.0"
@@ -5488,10 +5488,10 @@ karma-ng-html2js-preprocessor@^1.0.0:
resolved "https://registry.yarnpkg.com/karma-ng-html2js-preprocessor/-/karma-ng-html2js-preprocessor-1.0.0.tgz#10d8c8cfaa4136f1c8a76d91a4cbceedebec4a31"
integrity sha1-ENjIz6pBNvHIp22RpMvO7evsSjE=
-karma@^6.0.3:
- version "6.0.3"
- resolved "https://registry.yarnpkg.com/karma/-/karma-6.0.3.tgz#a35b4371306e08e3f8e46b5753aff46eaf3eec2c"
- integrity sha512-dmiLQdsNAvnbV1G6VvUK7Cl5xpwiMisZNT8MjBtOo49jKlnZSWLxQIemuLT8sGSzvx5IGgMfMQEtf/CALiUEVQ==
+karma@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-6.0.4.tgz#5c73bb1fdecd9acf8b8d5318c48ce9c85c035f79"
+ integrity sha512-Yk451MSSV82wwThwissGvTpaHYhKDOzzzcguj+XYMmLF9ZN6QDnUmOexzYPeDcne6g0eWEkP1QttzX0N9lQybw==
dependencies:
body-parser "^1.19.0"
braces "^3.0.2"
@@ -5511,7 +5511,7 @@ karma@^6.0.3:
qjobs "^1.2.0"
range-parser "^1.2.1"
rimraf "^3.0.2"
- socket.io "^3.0.4"
+ socket.io "^3.1.0"
source-map "^0.6.1"
tmp "0.2.1"
ua-parser-js "^0.7.23"
@@ -8439,7 +8439,7 @@ socket.io-parser@~4.0.3:
component-emitter "~1.3.0"
debug "~4.3.1"
-socket.io@^3.0.4:
+socket.io@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.0.tgz#4f3accda31e95893f618090c9cb5e85d345421fb"
integrity sha512-Aqg2dlRh6xSJvRYK31ksG65q4kmBOqU4g+1ukhPcoT6wNGYoIwSYPlCPuRwOO9pgLUajojGFztl6+V2opmKcww==