Skip to content

Commit

Permalink
refactor(stock): improve find modals
Browse files Browse the repository at this point in the history
Consolidates code related to finding depots and services for the stock
exit modal.  The logic is all contained in a single file now so that we
have uniform error handling between the two different modals.
  • Loading branch information
jniles committed Nov 22, 2020
1 parent 7602017 commit a02f94b
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 101 deletions.
4 changes: 2 additions & 2 deletions client/src/i18n/en/stock.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@
"DOES_REQUISITION_EXIST_HELP":"Click on this option if you want to exit stock based on a requisition slip",
"EDIT_REQUISITION": "Edit",
"NEW_REQUISITION": "New requisition",
"NOT_FOR_SERVICE":"The requisition slip is not for services",
"NOT_FOR_DEPOT":"The requisition slip is not for depots",
"NOT_FOR_SERVICE":"The requisition slip is not for the selected service",
"NOT_FOR_DEPOT":"The requisition slip is not for the selected depot",
"NOT_FOR_THE_SAME_DEPOT":"Exit on a requisition slip can not be made on the same depot",
"OTHER_REQUISITION": "Other requisition",
"RECEIVER": "Beneficiary",
Expand Down
4 changes: 2 additions & 2 deletions client/src/i18n/fr/stock.json
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@
"DOES_REQUISITION_EXIST_HELP": "Cliquez sur cette option si vous voulez effectuer une sortie de stock à partir d'un bon de requisition",
"EDIT_REQUISITION" : "Editer",
"NEW_REQUISITION" : "Nouvelle réquisition",
"NOT_FOR_SERVICE":"Le bon de requisition ne concerne pas les services",
"NOT_FOR_DEPOT":"Le bon de requisition ne concerne pas les depots",
"NOT_FOR_SERVICE":"Le bon de requisition ne concerne pas le service en cours",
"NOT_FOR_DEPOT":"Le bon de requisition ne concerne pas le depot en cours",
"NOT_FOR_THE_SAME_DEPOT":"La sortie sur bon de requisition ne peut être effectué sur un même dépôt",
"OTHER_REQUISITION" : "Autre réquisition",
"RECEIVER" : "Bénéficiaire",
Expand Down
97 changes: 97 additions & 0 deletions client/src/modules/stock/exit/modals/RequisitionHelper.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
angular.module('bhima.services')
.service('RequisitionHelperService', RequisitionHelperService);

RequisitionHelperService.$inject = ['StockService'];

/**
* @function RequisitionHelperService
*
* @description
* This function combines shared options from the FindDepot and FindService modals
* into a single file to not repeat code.
*
*/
function RequisitionHelperService(Stock) {

const service = this;

/**
* @function lookupRequisitionByReference
*
* @description
* This function looks up the requisition by the reference and asserts that
* the requisition is available for use. If it passes checks, the requisition
* API is queried again with the balance check.
*/
function lookupRequisitionByReference(reference) {
return Stock.stockRequisition.read(null, { reference })
.then(([requisition]) => {

// check that the requisition actually exists
if (!requisition || !requisition.uuid) {
const err = new Error('REQUISITION.VOUCHER_NOT_FOUND');
err.label = 'label label-warning';
throw err;
}

// check that the requisition is available to use
const statuses = ['done', 'completed', 'excessive'];
if (statuses.includes(requisition.status_key)) {
const err = new Error('REQUISITION.ALREADY_USED');
err.label = 'label label-success';
throw err;
}

// check that the requisition hasn't been canceled
if (requisition.status_key === 'cancelled') {
const err = new Error('REQUISITION.CANCELLED');
err.label = 'label label-danger';
throw err;
}

return Stock.stockRequisition.read(requisition.uuid, { balance : true });
});
}

/**
* @function isRequisitionForDepot
*
* @description
* Checks if the requisition is for the correct depot and sets the appropriate
* error flags if not.
*/
function isRequisitionForDepot(requisition, depot) {
if (depot.uuid !== requisition.depot_uuid) {
const err = new Error('REQUISITION.NOT_FOR_DEPOT');
err.label = 'label label-warning';
throw err;
}
return requisition;
}

/**
* @function isRequisitionForService
*
* @description
* Checks if the requisition is for the correct service and sets the appropriate
* error flags if not.
*/
function isRequisitionForService(requisition, reqService) {
if (!reqService || !reqService.uuid) {
const err = new Error('REQUISITION.NOT_FOR_SERVICE');
err.label = 'label label-warning';
throw err;
}
return requisition;
}

// bind functions/properties to service
Object.assign(service, {
lookupRequisitionByReference,
isRequisitionForDepot,
isRequisitionForService,
});

return service;

}
10 changes: 7 additions & 3 deletions client/src/modules/stock/exit/modals/findDepot.modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
novalidate>

<div class="modal-header">
<i class="fa fa-search"></i>
<span translate>FORM.LABELS.SEARCH</span>
<span translate>STOCK.DEPOT</span>
<ol class="headercrumb">
<li class="static"> <i class="fa fa-search"></i> </li>
<li class="title">
<span translate>FORM.LABELS.SEARCH</span>
<span translate>STOCK.DEPOT</span>
</li>
</ol>
</div>

<div class="modal-body">
Expand Down
58 changes: 16 additions & 42 deletions client/src/modules/stock/exit/modals/findDepot.modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ angular.module('bhima.controllers')

StockFindDepotModalController.$inject = [
'$uibModalInstance', 'DepotService', 'NotifyService', 'data',
'StockService', 'SessionService',
'StockService', 'SessionService', 'RequisitionHelperService',
];

function StockFindDepotModalController(Instance, Depot, Notify, Data, Stock, Session) {
function StockFindDepotModalController(Instance, Depot, Notify, Data, Stock, Session, RequisitionHelpers) {
const vm = this;
const enableStrictDepotDistribution = Session.stock_settings.enable_strict_depot_distribution;

Expand Down Expand Up @@ -43,9 +43,7 @@ function StockFindDepotModalController(Instance, Depot, Notify, Data, Stock, Ses
}

function extractDepotFromCollection(depotUuid, collection) {
const idx = collection.findIndex(i => (i.uuid === depotUuid));
if (idx > -1) { collection.splice(idx, 1); }
return collection;
return collection.filter(depot => depot.uuid !== depotUuid);
}

vm.onChangeReference = reference => {
Expand All @@ -55,67 +53,43 @@ function StockFindDepotModalController(Instance, Depot, Notify, Data, Stock, Ses
// submit
function submit(form) {
if (vm.reference) {
return Stock.stockRequisition.read(null, { reference : vm.reference })
.then(requisitionDetails)
return RequisitionHelpers.lookupRequisitionByReference(vm.reference)
.then(requisition => RequisitionHelpers.isRequisitionForDepot(requisition, Data.depot))
.then(depotDetails)
.then(assignDepotRequisition)
.catch(Notify.handleError);
.catch(err => {

// bind the error flags as needed
vm.requisitionMessage = err.message;
vm.requisitionLabel = err.label;
Notify.handleError(err);
});
}

if (form.$invalid && !vm.requisition.uuid) { return null; }
return Instance.close(vm.selected);
}

function requisitionDetails([requisition]) {
if (!requisition || !requisition.uuid) {
vm.requisitionMessage = 'REQUISITION.VOUCHER_NOT_FOUND';
vm.requisitionLabel = 'label label-primary';
throw new Error('Requisition Not Found');
}

if (requisition.status_key === 'done' || requisition.status_key === 'completed'
|| requisition.status_key === 'excessive') {
vm.requisitionMessage = 'REQUISITION.ALREADY_USED';
vm.requisitionLabel = 'label label-success';
throw new Error('Requisition Already Used');
}

if (requisition.status_key === 'cancelled') {
vm.requisitionMessage = 'REQUISITION.CANCELLED';
vm.requisitionLabel = 'label label-danger';
throw new Error('Requisition Cancelled');
}

return Stock.stockRequisition.read(requisition.uuid, { balance : true });
}

function depotDetails(requisition) {
vm.requisition = requisition;

if (vm.requisition.depot_uuid !== Data.depot.uuid) {
vm.requisitionMessage = 'REQUISITION.NOT_FOR_DEPOT';
vm.requisitionLabel = 'label label-warning';
throw new Error('This requisition is not for this depot');
}

return Depot.read(null, { uuid : vm.requisition.requestor_uuid });
}

function assignDepotRequisition([depot]) {
if (Data.depot.uuid === vm.selected.uuid) {
vm.requisitionMessage = 'REQUISITION.NOT_FOR_THE_SAME_DEPOT';
vm.requisitionLabel = 'label label-danger';
throw new Error('The requisition cannot be for the same depot');
const err = new Error('REQUISITION.NOT_FOR_THE_SAME_DEPOT');
err.label = 'label label-danger';
throw err;
}

vm.selected = depot;
vm.selected.requisition = vm.requisition;

Instance.close(vm.selected);
}

// cancel
function cancel() {
Instance.close(vm.selected);
}

}
11 changes: 6 additions & 5 deletions client/src/modules/stock/exit/modals/findPatient.modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

<div class="modal-header">
<ol class="headercrumb">
<li class="static"> <i class="fa fa-search"></i> </li>
<li class="title">
<span translate>FORM.LABELS.SEARCH</span>
<span translate>FORM.LABELS.PATIENT</span>
</li>
<li class="static"> <i class="fa fa-search"></i> </li>
<li class="title">
<span translate>FORM.LABELS.SEARCH</span>
<span translate>FORM.LABELS.PATIENT</span>
</li>
</ol>
</div>

<div class="modal-body">
Expand Down
10 changes: 7 additions & 3 deletions client/src/modules/stock/exit/modals/findService.modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
novalidate>

<div class="modal-header">
<i class="fa fa-search"></i>
<span translate>FORM.LABELS.SEARCH</span>
<span translate>FORM.LABELS.SERVICE</span>
<ol class="headercrumb">
<li class="static"> <i class="fa fa-search"></i> </li>
<li class="title">
<span translate>FORM.LABELS.SEARCH</span>
<span translate>FORM.LABELS.SERVICE</span>
</li>
</ol>
</div>

<div class="modal-body">
Expand Down
58 changes: 17 additions & 41 deletions client/src/modules/stock/exit/modals/findService.modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ angular.module('bhima.controllers')

StockFindServiceModalController.$inject = [
'$uibModalInstance', 'ServiceService', 'NotifyService', 'data',
'StockService',
'StockService', 'RequisitionHelperService',
];

function StockFindServiceModalController(Instance, Service, Notify, Data, Stock) {
function StockFindServiceModalController(Instance, Service, Notify, Data, Stock, RequisitionHelpers) {
const vm = this;

vm.selected = {};

// bind methods
vm.submit = submit;
vm.cancel = cancel;
vm.clear = clear;

Service.read()
Service.read(null, { hidden : 0 })
.then(services => {
vm.services = services.filter(s => !s.hidden);
vm.services = services;
[vm.selected] = services.filter(s => s.uuid === Data.entity_uuid);
})
.catch(Notify.handleError);
Expand All @@ -32,60 +34,34 @@ function StockFindServiceModalController(Instance, Service, Notify, Data, Stock)
// submit
function submit(form) {
if (vm.reference) {
return Stock.stockRequisition.read(null, { reference : vm.reference })
.then(requisitionDetails)
return RequisitionHelpers.lookupRequisitionByReference(vm.reference)
.then(requisition => RequisitionHelpers.isRequisitionForDepot(requisition, Data.depot))
.then(serviceDetails)
.then(assignServiceRequisition)
.catch(Notify.handleError);
.catch(err => {

// bind the error flags as needed
vm.requisitionMessage = err.message;
vm.requisitionLabel = err.label;
Notify.handleError(err);
});
}

if (form.$invalid && !vm.requisition.uuid) { return null; }
return Instance.close(vm.selected);
}

function requisitionDetails([requisition]) {
if (Data.depot.uuid !== requisition.depot_uuid) {
vm.requisitionMessage = 'REQUISITION.NOT_FOR_DEPOT';
vm.requisitionLabel = 'label label-warning';
throw new Error('This requisition is not for this depot');
}

if (!requisition || !requisition.uuid) {
vm.requisitionMessage = 'REQUISITION.VOUCHER_NOT_FOUND';
vm.requisitionLabel = 'label label-warning';
throw new Error('Requisition Not Found');
}

if (requisition.status_key === 'done' || requisition.status_key === 'completed'
|| requisition.status_key === 'excessive') {
vm.requisitionMessage = 'REQUISITION.ALREADY_USED';
vm.requisitionLabel = 'label label-success';
throw new Error('Requisition Already Used');
}

if (requisition.status_key === 'cancelled') {
vm.requisitionMessage = 'REQUISITION.CANCELLED';
vm.requisitionLabel = 'label label-danger';
throw new Error('Requisition Cancelled');
}

return Stock.stockRequisition.read(requisition.uuid, { balance : true });
}

function serviceDetails(requisition) {
vm.requisition = requisition;
return Service.read(null, { uuid : vm.requisition.requestor_uuid });
}

function assignServiceRequisition([service]) {
if (!service || !service.uuid) {
vm.requisitionMessage = 'REQUISITION.NOT_FOR_SERVICE';
vm.requisitionLabel = 'label label-warning';
throw new Error('The requisition is not for services');
}
RequisitionHelpers.isRequisitionForService(vm.requisition, service);

vm.selected = service;
vm.selected.requisition = vm.requisition;

Instance.close(vm.selected);
}

Expand Down
7 changes: 4 additions & 3 deletions server/controllers/admin/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ function list(req, res, next) {
}

const params = db.convert(req.query, ['uuid']);
const filters = new FilterParser(params);
const filters = new FilterParser({ ...params, tableAlias : 's' });

filters.equals('uuid', 'uuid', 's');
filters.equals('name', 'name', 's');
filters.equals('uuid');
filters.equals('name');
filters.equals('hidden');
filters.setOrder('ORDER BY s.name');

const query = filters.applyQuery(sql);
Expand Down

0 comments on commit a02f94b

Please sign in to comment.