Skip to content

Commit

Permalink
Add stock entry typeahead support for selecting existing lots
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcameron authored and jniles committed Feb 20, 2021
1 parent fb81c88 commit eb908c1
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 27 deletions.
2 changes: 1 addition & 1 deletion client/src/i18n/en/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
"VIEW_ACCOUNT_STATEMENT": "View in Account Statement",
"VIEW_CREDIT_NOTE": "View Credit Note",
"VIEW_INVOICE": "View Invoices",
"VIEW_ARTICLE_IN_STOCK": "View article in stock",
"VIEW_ARTICLE_IN_STOCK": "View Article in Stock",
"VIEW_PATIENT": "View Patient",
"VIEW_PAYMENTS": "View Cash Payments",
"VIEW_RECEIPT": "View Receipt",
Expand Down
1 change: 1 addition & 0 deletions client/src/i18n/en/stock.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@
"ASSIGNMENT_HISTORIC":"Assignment History",
"ASSIGNMENT_CURRENT":"Current",
"NO_ASSIGNMENT":"No Assignment",
"LOT_LABEL_EXPIRES": "{{label}} (expires: {{expdate}})",
"INCLUDE_EXHAUSTED_LOTS":"Include exhausted lots",
"INCLUDE_ONLY_EXHAUSTED_LOTS": "Include only exhausted lots",
"CLICK_TO_SEE":"Click here to see lots",
Expand Down
1 change: 1 addition & 0 deletions client/src/i18n/fr/stock.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@
"ASSIGNMENT_HISTORIC":"Historique d'assignation",
"ASSIGNMENT_CURRENT":"En cours",
"NO_ASSIGNMENT":"Aucune assignation",
"LOT_LABEL_EXPIRES": "{{label}} (expirira le {{expdate}})",
"INCLUDE_EXHAUSTED_LOTS":"Inclure les lots de stocks épuisés",
"INCLUDE_ONLY_EXHAUSTED_LOTS": "Inclure uniquement les lots de stocks épuisé",
"CLICK_TO_SEE":"Cliquez ici pour voir les lots",
Expand Down
23 changes: 17 additions & 6 deletions client/src/js/services/LotService.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,29 @@ function LotService(Api, $http, util) {

lots.read = (uuid) => {
return Api.read.call(lots, uuid)
.then(res => {
.then((res) => {
res.expiration_date = new Date(res.expiration_date);
return res;
});
};

lots.candidates = (params) => {
return $http.get(`/lots_candidates/${params.inventory_uuid}`)
.then((res) => {
res.data.forEach((lot) => {
lot.expiration_date = new Date(lot.expiration_date);
});
return res;
})
.then(util.unwrapHttpResponse);
};

lots.dupes = (params) => {
return $http.get('/lot/dupes', { params })
.then(res => {
res.data.forEach((row) => {
row.entry_date = new Date(row.entry_date);
row.expiration_date = new Date(row.expiration_date);
return $http.get('/lots_dupes', { params })
.then((res) => {
res.data.forEach((lot) => {
lot.entry_date = new Date(lot.entry_date);
lot.expiration_date = new Date(lot.expiration_date);
});
return res;
})
Expand Down
4 changes: 2 additions & 2 deletions client/src/js/services/StockService.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ function StockService(Api, StockFilterer, HttpCache, util, Periods) {
* @function processLotsFromStore
*
* @description
* This function loops through the store's contents mapping them into a flat array
* of lots.
* This function loops through the store's contents mapping them into a flat
* array of lots.
*
* @returns {Array} - lots in an array.
*/
Expand Down
20 changes: 13 additions & 7 deletions client/src/modules/stock/entry/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ angular.module('bhima.controllers')
// dependencies injections
StockEntryController.$inject = [
'InventoryService', 'NotifyService', 'SessionService', 'util',
'bhConstants', 'ReceiptModal', 'PurchaseOrderService', 'StockFormService',
'StockService', 'StockModalService', 'uiGridConstants', 'Store',
'uuid', '$translate',
'bhConstants', 'ReceiptModal', 'PurchaseOrderService',
'StockFormService', 'StockService', 'StockModalService', 'LotService',
'uiGridConstants', 'Store', 'uuid', '$translate',
];

/**
Expand All @@ -17,7 +17,7 @@ StockEntryController.$inject = [
*/
function StockEntryController(
Inventory, Notify, Session, util, bhConstants, ReceiptModal, Purchase,
StockForm, Stock, StockModal, uiGridConstants, Store, Uuid, $translate,
StockForm, Stock, StockModal, Lots, uiGridConstants, Store, Uuid, $translate,
) {
// variables
let inventoryStore;
Expand All @@ -43,6 +43,7 @@ function StockEntryController(
vm.submit = submit;
vm.reset = reset;
vm.onDateChange = onDateChange;

vm.gridOptions = {
appScopeProvider : vm,
enableSorting : false,
Expand Down Expand Up @@ -423,7 +424,7 @@ function StockEntryController(
* @description [grid] pop up a modal for defining lots for each row in the grid
*/
function setLots(stockLine) {
// Additionnal information for an inventory Group
// Additional information for an inventory Group
const inventory = inventoryStore.get(stockLine.inventory_uuid);
stockLine.tracking_expiration = inventory.tracking_expiration;
stockLine.unique_item = inventory.unique_item;
Expand All @@ -436,7 +437,7 @@ function StockEntryController(
if (!res) { return; }
stockLine.lots = res.lots;
stockLine.quantity = res.quantity;
stockLine.unit_cost = res.unit_cost; // integration and donation price is defined in the lot modal
stockLine.unit_cost = res.unit_cost; // integration and donation price are defined in the lot modal
vm.hasValidInput = hasValidInput();
})
.catch(Notify.handleError);
Expand Down Expand Up @@ -505,7 +506,6 @@ function StockEntryController(
.catch(Notify.handleError);
}


/**
* @method submitIntegration
* @description prepare the stock movement and send data to the server as new stock integration
Expand Down Expand Up @@ -605,6 +605,12 @@ function StockEntryController(
line.unit = inventory.unit;
line.tracking_expiration = inventory.tracking_expiration;
setInitialized(line);

// Store the candidate lots for this inventory code
Lots.candidates({ inventory_uuid : line.inventory_uuid })
.then((lots) => {
line.candidateLots = lots;
});
}

startup();
Expand Down
37 changes: 33 additions & 4 deletions client/src/modules/stock/entry/modals/lots.modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ angular.module('bhima.controllers')

StockDefineLotsModalController.$inject = [
'appcache', '$uibModalInstance', 'uiGridConstants', 'data',
'SessionService', 'bhConstants', 'StockEntryModalForm', 'focus',
'SessionService', 'bhConstants', 'StockEntryModalForm',
'$translate', 'focus',
];

function StockDefineLotsModalController(
AppCache, Instance, uiGridConstants, Data, Session, bhConstants, EntryForm, Focus,
AppCache, Instance, uiGridConstants, Data, Session, bhConstants, EntryForm,
$translate, Focus,
) {
const vm = this;

Expand All @@ -30,7 +32,6 @@ function StockDefineLotsModalController(
vm.enterprise = Session.enterprise;
vm.stockLine = angular.copy(Data.stockLine);
vm.entryType = Data.entry_type;

vm.isTransfer = (vm.entryType === 'transfer_reception');

// exposing method to the view
Expand All @@ -43,6 +44,8 @@ function StockDefineLotsModalController(
vm.onChangeUnitCost = onChangeUnitCost;
vm.onDateChange = onDateChange;

vm.onSelectLot = onSelectLot;

vm.isCostEditable = (vm.entryType !== 'transfer_reception');

const cols = [{
Expand Down Expand Up @@ -85,11 +88,13 @@ function StockDefineLotsModalController(
vm.gridOptions = {
appScopeProvider : vm,
enableSorting : false,
enableColumnResize : true,
enableColumnMenus : false,
showColumnFooter : true,
fastWatch : true,
flatEntityAccess : true,
data : vm.form.rows,
minRowsToShow : 4,
columnDefs : cols,
onRegisterApi,
};
Expand All @@ -115,9 +120,12 @@ function StockDefineLotsModalController(
* if the fast insert option is enable do this :
* - add new row automatically on blur
* - set the focus in the new row
* @param {string} rowLot the row.entity.lot string
* @param {string} rowLot the row.entity.lot string/object
*/
function onLotBlur(rowLot) {
// NOTE: rowLot will be an object if an existed lot was
// selected from the typeahead. Otherwise it will
// be the lot name string that was typed in.
if (vm.enableFastInsert && rowLot) {

const emptyLotRow = getFirstEmptyLot();
Expand Down Expand Up @@ -170,6 +178,27 @@ function StockDefineLotsModalController(
}
}

/**
* @method onSelectLot
*
* @description
* Updates the expiration field based on the date in
* the corresponding candidate lot.
*
* NOTE: This function is only called when a lot is selected
* from the typeahead (which is created from the list
* valid candidate lots for this inventory item). So
* the 'find()' below should always work.
*
* @param {object} entity the row.entity.lot object (being displayed)
* @param {object} item the active typeahead model object
*/
function onSelectLot(entity, item) {
const lot = vm.stockLine.candidateLots.find(l => l.uuid === item.uuid);
entity.expiration_date = new Date(lot.expiration_date);
onChanges();
}

function cancel() {
saveSetting();
Instance.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<bh-date-picker
date="row.entity.expiration_date"
on-change="grid.appScope.onDateChange(date, row.entity)"
ng-disabled="row.entity._initialized"
required="true">
</bh-date-picker>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
class="form-control"
ng-model="row.entity.lot"
ng-model-options="{ 'debounce' : 0 }"
ng-change="grid.appScope.onChanges()"
bh-blur="grid.appScope.onLotBlur(row.entity.lot)"
typeahead-editable="true"
typeahead-append-to-body="true"
uib-typeahead="lot as lot.label for lot in grid.appScope.stockLine.candidateLots | filter:$viewValue | limitTo:8"
typeahead-template-url="/modules/stock/entry/modals/templates/selectLot.tmpl.html"
typeahead-select-on-blur="false"
typeahead-select-on-exact="true"
typeahead-on-select="grid.appScope.onSelectLot(row.entity, $item)"
ng-blur="grid.appScope.onLotBlur(row.entity.lot)"
required>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<a id="lot-uuid-{{ match.model.uuid }}" href>
<span ng-bind-html="match.model.label | uibTypeaheadHighlight:query"></span>
<span style="margin-left: 1em">[</span>
<span translate>INVENTORY.EXPIRES</span><span> </span>
<span ng-bind-html="match.model.expiration_date | date"></span>
<span>]</span>
</a>
3 changes: 2 additions & 1 deletion server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,8 @@ exports.configure = function configure(app) {
app.put('/lots/:uuid', lots.update);
app.get('/lots/:uuid/assignments/:depot_uuid', lots.assignments);
app.post('/lots/:uuid/merge', lots.merge);
app.get('/lot/dupes/:label?/:entry_date?/:expiration_date?/:initial_quantity?/:inventory_uuid?', lots.getDupes);
app.get('/lots_candidates/:inventory_uuid', lots.getCandidates);
app.get('/lots_dupes/:label?/:entry_date?/:expiration_date?/:initial_quantity?/:inventory_uuid?', lots.getDupes);

// API for Account Reference Type routes crud
app.get('/account_reference_type', accountReferenceType.list);
Expand Down
27 changes: 26 additions & 1 deletion server/controllers/stock/lots.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ exports.update = update;
exports.details = details;
exports.assignments = assignments;
exports.getLotTags = getLotTags;
exports.getCandidates = getCandidates;
exports.getDupes = getDupes;
exports.merge = merge;

Expand Down Expand Up @@ -96,7 +97,31 @@ async function update(req, res, next) {
}

/**
* GET /lot_dupes/:label?/:inventory_uuid?/:initial_quantity?/:entry_date?/:expiration_date?
* GET /lots_candidates/:inventory_uuid
*
* @description
* Returns all lots with the that inventory_uuid
*/
function getCandidates(req, res, next) {
const inventoryUuid = db.bid(req.params.inventory_uuid);

const query = `
SELECT BUID(l.uuid) AS uuid, l.label, l.description, l.expiration_date
FROM lot l
WHERE l.inventory_uuid = ?
ORDER BY label, expiration_date
`;

return db.exec(query, [inventoryUuid])
.then(rows => {
res.status(200).json(rows);
})
.catch(next)
.done();
}

/**
* GET /lots_dupes/:label?/:inventory_uuid?/:initial_quantity?/:entry_date?/:expiration_date?
*
* @description
* Returns all lots with the given label or matching field(s)
Expand Down
6 changes: 3 additions & 3 deletions test/integration/mergeLots.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,15 @@ describe('Test merging lots', () => {
// ===========================================================================
// Verify we have created the lots, tags, etc
it('Verify we created lot1 and its dupes (lot3, lot4, lot5)', () => {
return agent.get('/lot/dupes')
return agent.get('/lots_dupes')
.query({ label : lot1Label, inventory_uuid : vitamineUuid })
.then((res) => {
helpers.api.listed(res, 4); // 4 = the lot itself and 3 dupes
})
.catch(helpers.handler);
});
it('Verify we created lot2', () => {
return agent.get('/lot/dupes')
return agent.get('/lots_dupes')
.query({ label : lot2Label, inventory_uuid : vitamineUuid })
.then((res) => {
helpers.api.listed(res, 1);
Expand Down Expand Up @@ -177,7 +177,7 @@ describe('Test merging lots', () => {
});

it(`Verify the 'find duplicate lots' query works`, () => {
return agent.get('/lot/dupes')
return agent.get('/lots_dupes')
.query({ find_dupes : 1 })
.then((res) => {
helpers.api.listed(res, 1); // all dupes can be merged into one
Expand Down

0 comments on commit eb908c1

Please sign in to comment.