Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add barcode scan for invoice to stock movement #4145

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 8 additions & 4 deletions client/src/i18n/en/invoice_registry.json
@@ -1,4 +1,8 @@
{"INVOICE_REGISTRY":{"TITLE":"Invoice Registry",
"RECEIPT_CURRENCY":"Receipt Currency",
"EXCLUDE_REVERSE":"Exclude all reversed invoices",
"INCLUDE_REVERSE":"Include only reversed invoices"}}
{
"INVOICE_REGISTRY" :{
"TITLE" : "Invoice Registry",
"RECEIPT_CURRENCY" : "Receipt Currency",
"EXCLUDE_REVERSE" : "Exclude all reversed invoices",
"INCLUDE_REVERSE" : "Include only reversed invoices"
}
}
83 changes: 67 additions & 16 deletions client/src/js/components/bhFindInvoice.js
Expand Up @@ -4,8 +4,9 @@ angular.module('bhima.components')
templateUrl : 'modules/templates/bhFindInvoice.tmpl.html',
bindings : {
patientUuid : '<?', // patient uuid - to restrict search to this patient
invoiceUuid : '<?', // if a uuid exists, pass it in here.
onSearchComplete : '&', // bind callback to call when data is available
disabled : '<', // bind disable behavior
disabled : '<?', // bind disable behavior
},
});

Expand All @@ -17,7 +18,7 @@ FindInvoiceComponent.$inject = [
* The Find Invoice Component
*/
function FindInvoiceComponent(PatientInvoice, Notify, $window) {
const vm = this;
const $ctrl = this;

/* @const the enter key keycode */
const ENTER_KEY = 13;
Expand All @@ -26,26 +27,43 @@ function FindInvoiceComponent(PatientInvoice, Notify, $window) {
name : '',
};

vm.$onInit = function onInit() {
vm.disabled = vm.disabled || false;
$ctrl.$onInit = function onInit() {
$ctrl.disabled = $ctrl.disabled || false;
};

$ctrl.$onChanges = function $onChanges(changes) {
if (changes && changes.invoiceUuid && changes.invoiceUuid.currentValue) {
lookupInvoiceByUuid(changes.invoiceUuid.currentValue);
}
};

/* Expose functions and variables to the template view */
vm.search = search;
vm.onKeyPress = onKeyPress;
vm.translate = translate;
$ctrl.search = search;
$ctrl.onKeyPress = onKeyPress;
$ctrl.translate = translate;

/**
* @method search
*
* @description
* Fired when the user uses the search form to look up an invoice via its
* reference.
*/
function search(form) {
PatientInvoice.findConsumableInvoicePatient(vm.invoiceReference, vm.patientUuid)
const parameters = {
invoiceReference : $ctrl.invoiceReference,
patientUuid : $ctrl.patientUuid,
};

PatientInvoice.findConsumableInvoicePatient(parameters)
.then(invoice => {

if (!invoice.details) {
vm.invoiceFound = false;
$ctrl.invoiceFound = false;
return;
}


// trigger form validation for the invoice search input
form.$setSubmitted();

Expand All @@ -55,24 +73,57 @@ function FindInvoiceComponent(PatientInvoice, Notify, $window) {
.catch(Notify.handleError);
}

/**
* @method lookupInvoiceByUuid
*
* @description
* Fired when an invoiceUuid is passed in from outside of the component.
*/
function lookupInvoiceByUuid(invoiceUuid) {
const parameters = { invoiceUuid };
if ($ctrl.patientUuid) {
parameters.patientUuid = $ctrl.patientUuid;
}

PatientInvoice.findConsumableInvoicePatient(parameters)
.then(invoice => {

if (!invoice.details) {
$ctrl.invoiceFound = false;
return;
}

$ctrl.invoiceReference = invoice.details.reference;

// select invoice and fetch articles and services in the invoice
selectInvoice(invoice);
})
.catch(Notify.handleError);
}


/**
* @method selectInvoice
*
* @param {object} invoice The invoice object
*
* @description
* This function attaches the invoice to the controller, templates in the
* values, and calls the callback.
*/
function selectInvoice(invoice) {
vm.invoiceFound = true;
$ctrl.invoiceFound = true;

const elementId = 'search-button';
const searchButton = $window.document.getElementById(elementId);

if (invoice && typeof (invoice) === 'object') {
vm.translate.name = invoice.details.debtor_name;
vm.invoiceDescription = invoice.details.description;
vm.invoiceItems = invoice.items.map(item => `${item.text}: ${item.quantity} ${item.inventory_unit}`);
if (invoice && angular.isObject(invoice)) {
$ctrl.translate.name = invoice.details.debtor_name;
$ctrl.invoiceDescription = invoice.details.description;
$ctrl.invoiceItems = invoice.items.map(item => `${item.text}: ${item.quantity} ${item.inventory_unit}`);

// call the external function with patient
vm.onSearchComplete({ invoice });
$ctrl.onSearchComplete({ invoice });

// set focus on the search button after a search
searchButton.focus();
Expand All @@ -92,7 +143,7 @@ function FindInvoiceComponent(PatientInvoice, Notify, $window) {

// submit the find-invoice form
if (event.keyCode === ENTER_KEY) {
vm.search(form);
$ctrl.search(form);
event.preventDefault();
}
}
Expand Down
18 changes: 12 additions & 6 deletions client/src/js/components/bhLoadingButton.js
Expand Up @@ -2,15 +2,21 @@ angular.module('bhima.components')
.component('bhLoadingButton', {
transclude : true,
template :
'<button type="submit" class="btn" ng-class="$ctrl.buttonClass" ng-disabled="$ctrl.loadingState || $ctrl.disabled" data-method="submit">'
+ '<span ng-show="$ctrl.loadingState"><span class="fa fa-circle-o-notch fa-spin"></span> <span translate>FORM.INFO.LOADING</span></span>'
+ '<span ng-hide="$ctrl.loadingState" ng-transclude><span translate>FORM.BUTTONS.SUBMIT</span></span>'
+ '</button>',
`<button type="submit"
class="btn"
ng-class="$ctrl.buttonClass"
ng-disabled="$ctrl.loadingState || $ctrl.disabled"
data-method="submit">
<span ng-show="$ctrl.loadingState">
<span class="fa fa-circle-o-notch fa-spin"></span> <span translate>FORM.INFO.LOADING</span>
</span>
<span ng-hide="$ctrl.loadingState" ng-transclude><span translate>FORM.BUTTONS.SUBMIT</span></span>
</button>`.trim(),
controller : LoadingButtonController,
bindings : {
loadingState : '<',
buttonClass : '@',
disabled : '<',
buttonClass : '@?',
disabled : '<?',
},
});

Expand Down
13 changes: 7 additions & 6 deletions client/src/modules/invoices/patientInvoice.service.js
Expand Up @@ -17,7 +17,7 @@ PatientInvoiceService.$inject = [
*/
function PatientInvoiceService(
Modal, Session, Api, Filters, AppCache, Periods, $httpParamSerializer,
Languages, bhConstants, Transactions, $translate
Languages, bhConstants, Transactions, $translate,
) {
const service = new Api('/invoices/');

Expand Down Expand Up @@ -193,12 +193,13 @@ function PatientInvoiceService(
};

/**
* find an invoice with its consumable inventories for a given patient
* @function findConsumableInvoicePatient
*
* @description
* Find an invoice with its consumable inventories for a given patient
*/
function findConsumableInvoicePatient(invoiceReference, patientUuid) {
const params = { invoiceReference, patientUuid };
const url = '/invoices/consumable/';
return this.$http.get(url, { params })
function findConsumableInvoicePatient(params = {}) {
return this.$http.get('/invoices/consumable/', { params })
.then(this.util.unwrapHttpResponse);
}

Expand Down
9 changes: 8 additions & 1 deletion client/src/modules/stock/exit/modals/findPatient.modal.html
Expand Up @@ -35,19 +35,26 @@
<div ng-if="$ctrl.joinInvoice">
<bh-find-invoice
patient-uuid="$ctrl.selected.uuid"
invoice-uuid="$ctrl.scannedInvoice.uuid"
on-search-complete="$ctrl.setInvoice(invoice)"
disabled="!$ctrl.selected.uuid">
</bh-find-invoice>
</div>
</div>

<div class="form-group">
<a href="" ng-click="$ctrl.openBarcodeScanner()">
<i class="fa fa-barcode"></i> <span translate>BARCODE.SCAN</span>
</a>
</div>
</div>

<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="$ctrl.cancel()" data-method="cancel" translate>
FORM.BUTTONS.CLOSE
</button>

<bh-loading-button loading-state="FindForm.$loading" disabled="$ctrl.joinInvoice && !$ctrl.selected.invoice.details.uuid">
<bh-loading-button loading-state="FindForm.$loading">
<span translate>FORM.BUTTONS.SUBMIT</span>
</bh-loading-button>
</div>
Expand Down
37 changes: 34 additions & 3 deletions client/src/modules/stock/exit/modals/findPatient.modal.js
Expand Up @@ -3,9 +3,10 @@ angular.module('bhima.controllers')

StockFindPatientModalController.$inject = [
'$uibModalInstance', 'PatientService', 'NotifyService', 'data', 'AppCache',
'BarcodeService',
];

function StockFindPatientModalController(Instance, Patient, Notify, Data, AppCache) {
function StockFindPatientModalController(Instance, Patients, Notify, Data, AppCache, Barcodes) {
const vm = this;
const cache = new AppCache('StockFindPatient');

Expand All @@ -22,9 +23,10 @@ function StockFindPatientModalController(Instance, Patient, Notify, Data, AppCac
vm.setInvoice = setInvoice;
vm.submit = submit;
vm.cancel = cancel;
vm.openBarcodeScanner = openBarcodeScanner;

if (Data.entity_uuid) {
Patient.read(Data.entity_uuid)
Patients.read(Data.entity_uuid)
.then(patient => {
setPatient(patient);
})
Expand All @@ -37,22 +39,51 @@ function StockFindPatientModalController(Instance, Patient, Notify, Data, AppCac
});
}


// set patient
function setPatient(patient) {
vm.selected = patient;
}

function setInvoice(invoice) {
vm.selected.invoice = invoice;
vm.invoice = invoice;
}

// submit
function submit() {
vm.selected.invoice = vm.invoice;
Instance.close(vm.selected);
}

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

/**
* @function openBarcodeScanner
*
* @description
* Opens the barcode scanner component and receives the invoice from the
* modal. Sets both the patient and the invoice based on the scan.
*/
function openBarcodeScanner() {
let invoice;

Barcodes.modal()
.then(record => {
invoice = record;
return Patients.read(record.patient_uuid);
})
.then(patient => {
setPatient(patient);

// we need to wait for the bh-find-invoice component to call the setInvoice()
// since the invoice details have to be formatted in a particular way.
vm.joinInvoice = 1;
vm.scannedInvoice = invoice;
})
.catch(angular.noop);
}

}
29 changes: 15 additions & 14 deletions client/src/modules/templates/bhFindInvoice.tmpl.html
Expand Up @@ -11,32 +11,33 @@
ng-keypress="$ctrl.onKeyPress($event, FindInvoiceForm)"
ng-disabled="$ctrl.disabled">
<span class="input-group-btn">
<button
id="search-button"
class="btn btn-default"
type="button"
ng-click="$ctrl.search(FindInvoiceForm)"
<button
id="search-button"
class="btn btn-default"
type="button"
ng-click="$ctrl.search(FindInvoiceForm)"
ng-disabled="$ctrl.disabled"
translate>
FORM.BUTTONS.SEARCH
</button>
</span>
</div>
<p
ng-if="!$ctrl.invoiceFound && FindInvoiceForm.$submitted"
class="help-block"
translate="FORM.INFO.PATIENT_INVOICE_NOT_FOUND"

<p
ng-if="$ctrl.invoiceFound === false"
class="help-block"
translate="FORM.INFO.PATIENT_INVOICE_NOT_FOUND"
translate-values="{{ $ctrl.translate }}">
</p>
<p
ng-if="$ctrl.invoiceFound && FindInvoiceForm.$submitted"
class="help-block"
<p
ng-if="$ctrl.invoiceFound === true"
class="help-block"
translate="FORM.INFO.PATIENT_INVOICE_FOUND">
</p>
</div>

<div ng-if="$ctrl.invoiceFound && FindInvoiceForm.$submitted" class="alert alert-success">
{{ $ctrl.invoiceDescription }}
<div ng-if="$ctrl.invoiceFound === true && $ctrl.invoiceItems" class="alert alert-success">
<b>{{$ctrl.invoice.details.reference}}</b> {{ $ctrl.invoiceDescription }}
<ul>
<li ng-repeat="item in $ctrl.invoiceItems track by $index">{{ item }}</li>
</ul>
Expand Down