Skip to content

Commit

Permalink
feature(Prevent negative quantity)
Browse files Browse the repository at this point in the history
Prevent negative quantity in client side in
- All stock exit
- Inventory adjustement
- Aggregate stock consumption
  • Loading branch information
lomamech committed Apr 6, 2021
1 parent fd7b8da commit 3234ce4
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 10 deletions.
1 change: 1 addition & 0 deletions client/src/i18n/en/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"ER_BAD_FIELD_ERROR" : "Column does not exist in database.",
"ER_ROW_IS_REFERENCED" : "Cannot delete entity because entity is used in another table.",
"ER_ROW_IS_REFERENCED_2" : "Cannot delete entity because entity is used in another table.",
"ER_PREVENT_NEGATIVE_QUANTITY_IN_STOCK" : "There are stock movements, which may overconsume the quantity in stock and generate negative quantity in stock",
"ER_BAD_NULL_ERROR" : "A column was left NULL that cannot be NULL.",
"ER_PARSE_ERROR" : "Your request could not be translated into valid SQL. Please modify your request and try again.",
"ER_EMPTY_QUERY" : "Your request seems empty. Are you sure you filled everything in?",
Expand Down
2 changes: 2 additions & 0 deletions client/src/i18n/en/stock.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
"EXCESSIVE_QUANTITY": "Excessive Quantity",
"LOT_QUANTITY_OVER_GLOBAL": "Total lot quantity is more than the global quantity",
"MISSING_LOT_UNIT_COST": "A non-negative value is required for lot unit cost",
"OVERCONSUMED_INVENTORIES": "Over-consumed inventories",
"OVERCONSUMED_INVENTORIES_DESCRIPTION": "<strong>{{ text }}</strong> with batch number <strong>{{ label }}</strong>, actually only has <strong>{{ quantityAvailable }} {{ unit_type }}</strong>, consumption of <strong>{{ quantity }} {{ unit_type }}</strong> items is unrealistic",
"PLEASE_CHECK_EXPIRY_DATE": "Please check the expiration date",
"POORLY_FORMALIZED_DATE_RANGE": "Poorly formalized date range: one consumption period must not be contained in another",
"QUANTITY_CONSUMED_LOWER" : "Total quantity consumed is lower than the global quantity consumed",
Expand Down
1 change: 1 addition & 0 deletions client/src/i18n/fr/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"ER_BAD_FIELD_ERROR" : "Vous avez entré un champ qui ne peut pas être stocké dans la base de données.",
"ER_ROW_IS_REFERENCED" : "Vous ne pouvez pas supprimer cet enregistrement, car il est référencé par un autre enregistrement dans la base de données.",
"ER_ROW_IS_REFERENCED_2" : "Vous ne pouvez pas supprimer cet enregistrement, car il est référencé par un autre enregistrement dans la base de données.",
"ER_PREVENT_NEGATIVE_QUANTITY_IN_STOCK" : "Il y a des mouvements de stock, qui peuvent surconsommer la quantité en stock et générer une quantité négative en stock",
"ER_BAD_NULL_ERROR" : "Un champ a été laissé vide qui ne peut pas être vide.",
"ER_PARSE_ERROR" : "Votre demande n'a pas été comprise. Veuillez modifier votre demande et réessayer.",
"ER_EMPTY_QUERY" : "Votre demande semble vide. Êtes-vous sûr de remplir tout?",
Expand Down
4 changes: 4 additions & 0 deletions client/src/i18n/fr/stock.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@
"EXCESSIVE_QUANTITY": "Quantité excessive",
"LOT_QUANTITY_OVER_GLOBAL": "La quantité totale des lots dépasse la quantity globale fournie",
"MISSING_LOT_UNIT_COST": "Une valeur non négative est requise pour le coût unitaire du lot",
"OVERCONSUMED_INVENTORIES": "Inventaires surconsommés",
"OVERCONSUMED_INVENTORIES_DESCRIPTION": "<strong>{{ text }}</strong> avec le numéro de lot <strong>{{ label }}</strong>, ne possède en réalité que <strong>{{ quantityAvailable }} {{ unit_type }}</strong> la consommation de <strong>{{ quantity }} {{ unit_type }}</strong> n'est pas réaliste",
"PLEASE_CHECK_EXPIRY_DATE": "Veuillez vérifier la date d'expiration",
"PROBLEMATIC_ADJUSTMENT": "Ajustement problematique",
"PROBLEMATIC_ADJUSTMENT_DESCRIPTION": "<strong>{{ text }}</strong> avec le numéro de lot <strong>{{ label }}</strong>, ne possède en réalité que <strong>{{ quantityAvailable }} l'ajustement négatif de {{ old_quantity }} à {{ quantity }} générera une quantité en stock négative",
"POORLY_FORMALIZED_DATE_RANGE": "Plages des dates males formalisées: une période de consommation ne doit pas être contenue dans une autre",
"QUANTITY_CONSUMED_LOWER" : "La quantité totale consommé est inférieure à la quantity globale consommée",
"QUANTITY_CONSUMED_OVER_GLOBAL" : "La quantité totale consommé dépasse la quantity globale consommée",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div ng-if="StockCtrl.overconsumption.length" class="row">
<div class="alert alert-danger">
<i class="fa fa-warning"></i> <span translate> STOCK.ERRORS.OVERCONSUMED_INVENTORIES </span>
<ul>
<li ng-repeat="item in StockCtrl.overconsumption track by $index">
<span translate translate-values="item.textI18n" translate-sanitize-strategy="'sce'"> STOCK.ERRORS.OVERCONSUMED_INVENTORIES_DESCRIPTION </span>
</li>
</ul>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6 col-md-6">
<h3 translate>STOCK.AGGREGATED_STOCK_CONSUMPTION.TITLE</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ function StockAggregatedConsumptionController(
vm.setConsumptionByLots = setConsumptionByLots;
vm.checkValidation = checkValidation;

vm.currentInventories = [];
vm.overconsumption = [];

vm.onSelectFiscalYear = (fiscalYear) => {
setupStock();
vm.movement.fiscal_id = fiscalYear.id;
Expand All @@ -48,11 +51,13 @@ function StockAggregatedConsumptionController(

vm.movement.period_id = period.id;
loadInventories(vm.depot);
loadCurrentInventories(vm.depot);
};

vm.onChangeDepot = depot => {
vm.depot = depot;
loadInventories(vm.depot);
loadCurrentInventories(vm.depot);
};

/**
Expand Down Expand Up @@ -228,6 +233,21 @@ function StockAggregatedConsumptionController(
});
}

function loadCurrentInventories(depot, dateTo = new Date()) {
vm.loading = true;
Stock.lots.read(null, { depot_uuid : depot.uuid, dateTo })
.then(lots => {
vm.currentInventories = lots.filter(item => item.quantity > 0);

// Here we check directly if a Depot has inventories in stock available in current date
vm.emptyCurrentStock = !vm.currentInventories.length;
})
.catch(Notify.handleError)
.finally(() => {
vm.loading = false;
});
}

function checkValidation(consumptionData) {
let valid = true;

Expand All @@ -249,6 +269,37 @@ function StockAggregatedConsumptionController(
user : Session.user.display_name,
};

const checkOverconsumption = vm.Stock.store.data;

checkOverconsumption.forEach(stock => {
stock.quantityAvailable = 0;

vm.currentInventories.forEach(lot => {
if (lot.uuid === stock.uuid) {
stock.quantityAvailable = lot.quantity;
}
});
});

vm.overconsumption = checkOverconsumption.filter(
c => (c.quantity_consumed + c.quantity_lost) > c.quantityAvailable,
);

if (vm.overconsumption.length) {
vm.overconsumption.forEach(item => {
item.textI18n = {
text : item.text,
label : item.label,
quantityAvailable : item.quantityAvailable,
quantity : (item.quantity_consumed + item.quantity_lost),
};
});

Notify.danger('ERRORS.ER_PREVENT_NEGATIVE_QUANTITY_IN_STOCK');
vm.$loading = false;
return 0;
}

const formatedDescription = $translate.instant('STOCK.EXIT_AGGREGATE_CONSUMPTION', i18nKeys);

const isValid = vm.Stock.validate();
Expand Down
11 changes: 11 additions & 0 deletions client/src/modules/stock/exit/exit.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div ng-if="StockCtrl.overconsumption.length" class="row">
<div class="alert alert-danger">
<i class="fa fa-warning"></i> <span translate> STOCK.ERRORS.OVERCONSUMED_INVENTORIES </span>
<ul>
<li ng-repeat="item in StockCtrl.overconsumption track by $index">
<span translate translate-values="item.textI18n" translate-sanitize-strategy="'sce'"> STOCK.ERRORS.OVERCONSUMED_INVENTORIES_DESCRIPTION </span>
</li>
</ul>
</div>
</div>
</div>

<div class="col-xs-6 col-md-3 panel-default">
Expand Down
74 changes: 68 additions & 6 deletions client/src/modules/stock/exit/exit.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,29 @@ function StockExitController(
vm.gridApi = {};
vm.selectedLots = [];
vm.selectableInventories = [];
vm.currentInventories = [];
vm.reset = reset;
vm.ROW_ERROR_FLAG = bhConstants.grid.ROW_ERROR_FLAG;
vm.overconsumption = [];

vm.onDateChange = date => {
vm.movement.date = date;
if (vm.movement.date < new Date()) {
vm.dateMessageWarning = true;
}
loadInventories(vm.depot, date);
loadCurrentInventories(vm.depot);

vm.overconsumption = [];
checkValidity();
};

vm.onChangeDepot = depot => {
vm.depot = depot;
loadInventories(vm.depot);
loadCurrentInventories(vm.depot);

vm.overconsumption = [];
};

// bind methods
Expand Down Expand Up @@ -206,6 +214,8 @@ function StockExitController(
vm.movement.description = $translate.instant(mapExit[exitType.label].description);
vm.stockForm.store.clear();
vm.resetEntryExitTypes = false;

vm.overconsumption = [];
}

function setupStock() {
Expand Down Expand Up @@ -284,6 +294,22 @@ function StockExitController(
});
}

function loadCurrentInventories(depot, dateTo = new Date()) {
vm.loading = true;
Stock.lots.read(null, { depot_uuid : depot.uuid, dateTo })
.then(lots => {

vm.currentInventories = lots.filter(item => item.quantity > 0);

// Here we check directly if a Depot has inventories in stock available in current date
vm.emptyCurrentStock = !vm.currentInventories.length;
})
.catch(Notify.handleError)
.finally(() => {
vm.loading = false;
});
}

// on lot select
function onLotSelect(row) {
if (!row.lot || !row.lot.uuid) { return; }
Expand Down Expand Up @@ -399,12 +425,14 @@ function StockExitController(
}

function setSelectedEntity(entity) {
const uniformEntity = Stock.uniformSelectedEntity(entity);
vm.reference = uniformEntity.reference;
vm.displayName = uniformEntity.displayName;
vm.selectedEntityUuid = uniformEntity.uuid;
vm.requisition = (entity && entity.requisition) || {};
loadRequisitions(entity);
if (entity) {
const uniformEntity = Stock.uniformSelectedEntity(entity);
vm.reference = uniformEntity.reference;
vm.displayName = uniformEntity.displayName;
vm.selectedEntityUuid = uniformEntity.uuid;
vm.requisition = (entity && entity.requisition) || {};
loadRequisitions(entity);
}
}

function loadRequisitions(entity) {
Expand Down Expand Up @@ -450,6 +478,36 @@ function StockExitController(
function submit(form) {
if (form.$invalid) { return null; }

const checkOverconsumption = vm.stockForm.store.data;

checkOverconsumption.forEach(stock => {
stock.quantityAvailable = 0;

vm.currentInventories.forEach(lot => {
if (lot.uuid === stock.lot.uuid) {
stock.quantityAvailable = lot.quantity;
}
});
});

vm.overconsumption = checkOverconsumption.filter(c => c.quantity > c.quantityAvailable);

if (vm.overconsumption.length) {
vm.overconsumption.forEach(item => {
item.textI18n = {
text : item.inventory.text,
label : item.lot.label,
quantityAvailable : item.quantityAvailable,
quantity : item.quantity,
unit_type : item.inventory.unit_type,
};
});

Notify.danger('ERRORS.ER_PREVENT_NEGATIVE_QUANTITY_IN_STOCK');
vm.$loading = false;
return 0;
}

if (vm.movement.exit_type !== 'loss' && expiredLots()) {
// NOTE: This check may not be necessary, since the user cannot select
// expired lots/batches directly. But lots can also come in via
Expand Down Expand Up @@ -477,8 +535,12 @@ function StockExitController(
// Load inventories
loadInventories(vm.depot);

// Load current inventories for antedate cases
loadCurrentInventories(vm.depot);

vm.reset(form);
vm.selectedLots = [];
vm.overconsumption = [];
resetSelectedEntity();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div ng-if="StockCtrl.overconsumption.length" class="row">
<div class="alert alert-danger">
<i class="fa fa-warning"></i> <span translate> STOCK.ERRORS.PROBLEMATIC_ADJUSTMENT </span>
<ul>
<li ng-repeat="item in StockCtrl.overconsumption track by $index">
<span translate translate-values="item.textI18n" translate-sanitize-strategy="'sce'"> STOCK.ERRORS.PROBLEMATIC_ADJUSTMENT_DESCRIPTION </span>
</li>
</ul>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6 col-md-6">
<h3 translate>INVENTORY_ADJUSTMENT.TITLE</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,23 @@ function StockInventoryAdjustmentController(
vm.movement = {};
vm.stockOut = {};

vm.currentInventories = [];
vm.overconsumption = [];

vm.onDateChange = date => {
vm.movement.date = date;
loadInventories(vm.depot);
loadCurrentInventories(vm.depot);

vm.overconsumption = [];
};

vm.onChangeDepot = depot => {
vm.depot = depot;
loadInventories(vm.depot);
loadCurrentInventories(vm.depot);

vm.overconsumption = [];
};

// bind constants
Expand Down Expand Up @@ -197,6 +206,21 @@ function StockInventoryAdjustmentController(
});
}

function loadCurrentInventories(depot, dateTo = new Date()) {
vm.loading = true;
Stock.lots.read(null, { depot_uuid : depot.uuid, dateTo })
.then(lots => {
vm.currentInventories = lots.filter(item => item.quantity > 0);

// Here we check directly if a Depot has inventories in stock available in current date
vm.emptyCurrentStock = !vm.currentInventories.length;
})
.catch(Notify.handleError)
.finally(() => {
vm.loading = false;
});
}

// ================================= Submit ================================
function submit(form) {
// check stock validity
Expand All @@ -218,6 +242,36 @@ function StockInventoryAdjustmentController(
return row;
});

const checkOverconsumption = vm.Stock.store.data;

checkOverconsumption.forEach(stock => {
stock.quantityAvailable = 0;

vm.currentInventories.forEach(lot => {
if (lot.uuid === stock.uuid) {
stock.quantityAvailable = lot.quantity;
}
});
});

vm.overconsumption = checkOverconsumption.filter(c => (c.old_quantity - c.quantity) > c.quantityAvailable);

if (vm.overconsumption.length) {
vm.overconsumption.forEach(item => {
item.textI18n = {
text : item.text,
label : item.label,
old_quantity : item.old_quantity,
quantityAvailable : item.quantityAvailable,
quantity : item.quantity,
};
});

Notify.danger('ERRORS.ER_PREVENT_NEGATIVE_QUANTITY_IN_STOCK');
vm.$loading = false;
return 0;
}

movement.lots = lots.filter(lot => {
return lot.quantity !== lot.oldQuantity;
});
Expand Down
Loading

0 comments on commit 3234ce4

Please sign in to comment.