Skip to content

Commit

Permalink
feat(price list) import items from csv file
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremielodi committed Oct 17, 2019
1 parent 8ffaa79 commit 8c97480
Show file tree
Hide file tree
Showing 17 changed files with 331 additions and 39 deletions.
38 changes: 24 additions & 14 deletions client/src/i18n/en/price_list.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
{"PRICE_LIST":{"ADD_ITEMS":"Add an Item",
"ADD_PRICE_LIST":"Add a Price List",
"HELP_TXT_1":"Select an action on the left to edit price list properties",
"HELP_TXT_2":"Edit the price list's metadata",
"HELP_TXT_3":"Remove the price list",
"ITEMS":"Items",
"NEW_PRICE_LIST":"New Price List",
"NO_RECORDS":"No Records",
"NONE":"Apply no price list",
"PRICE_LIST_ITEMS":"Price list items",
"TITLE":"Price List Management",
"UNABLE_TO_DELETE":"Price list cannot be deleted - it may currently be assigned to patient / debtor groups",
"UPDATE":"Update a Price List",
"ERRORS":{"HAS_NEGATIVE_PRICE":"You cannot assign a negative price to an item. Please either assign a positive percentage, a negative percentage, or a positive value."}}}
{
"PRICE_LIST": {
"ADD_ITEMS": "Add an Item",
"ADD_PRICE_LIST": "Add a Price List",
"HELP_TXT_1": "Select an action on the left to edit price list properties",
"HELP_TXT_2": "Edit the price list's metadata",
"HELP_TXT_3": "Remove the price list",
"ITEMS": "Items",
"NEW_PRICE_LIST": "New Price List",
"NO_RECORDS": "No Records",
"NONE": "Apply no price list",
"PRICE_LIST_ITEMS": "Price list items",
"TITLE": "Price List Management",
"UNABLE_TO_DELETE": "Price list cannot be deleted - it may currently be assigned to patient / debtor groups",
"UPDATE": "Update a Price List",
"ERRORS": {
"HAS_NEGATIVE_PRICE": "You cannot assign a negative price to an item. Please either assign a positive percentage, a negative percentage, or a positive value."
},
"IMPORT": {
"PRICE_LIST_ITEMS_DESCRIPTION": "You are about to import inventories's prices for this price list from a csv file",
"PRICE_LIST_ITEMS": "Click here for downloading price list items template file"
}
}
}
38 changes: 24 additions & 14 deletions client/src/i18n/fr/price_list.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
{"PRICE_LIST":{"ADD_ITEMS":"Ajouter un Item",
"ADD_PRICE_LIST":"Ajouter une Liste",
"HELP_TXT_1":"Choisir une action à gauche pour editer la liste de prix",
"HELP_TXT_2":"Éditer les metadonnées de la Liste",
"HELP_TXT_3":"Effacer la Liste",
"ITEMS":"Items",
"NEW_PRICE_LIST":"Nouvelle Liste des Prix",
"NO_RECORDS":"Pas d'enregistrement",
"NONE":"Appliquer aucune liste de prix",
"PRICE_LIST_ITEMS":"Eléments de la liste des prix",
"TITLE":"Liste des Prix",
"UNABLE_TO_DELETE":"La liste de prix ne peut être supprimer - car elle peut être courament assignée à un groupe de patient / Groupes Débiteurs",
"UPDATE":"Mettre à jour une liste de prix",
"ERRORS":{"HAS_NEGATIVE_PRICE":"Vous ne pouvez pas assigner une valeur negatif."}}}
{
"PRICE_LIST": {
"ADD_ITEMS": "Ajouter un Item",
"ADD_PRICE_LIST": "Ajouter une Liste",
"HELP_TXT_1": "Choisir une action à gauche pour editer la liste de prix",
"HELP_TXT_2": "Éditer les metadonnées de la Liste",
"HELP_TXT_3": "Effacer la Liste",
"ITEMS": "Items",
"NEW_PRICE_LIST": "Nouvelle Liste des Prix",
"NO_RECORDS": "Pas d'enregistrement",
"NONE": "Appliquer aucune liste de prix",
"PRICE_LIST_ITEMS": "Eléments de la liste des prix",
"TITLE": "Liste des Prix",
"UNABLE_TO_DELETE": "La liste de prix ne peut être supprimer - car elle peut être courament assignée à un groupe de patient / Groupes Débiteurs",
"UPDATE": "Mettre à jour une liste de prix",
"ERRORS": {
"HAS_NEGATIVE_PRICE": "Vous ne pouvez pas assigner une valeur negatif."
},
"IMPORT": {
"PRICE_LIST_ITEMS_DESCRIPTION": "Vous êtes sur le point d'importer les prix des inventaires pour cette liste des prix à partir d'un fichier CSV",
"PRICE_LIST_ITEMS": "Cliquez ici pour télécharger le fichier modèle pour l'importation des prix"
}
}
}
47 changes: 47 additions & 0 deletions client/src/modules/prices/modal/import.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<form name="ActionForm" bh-submit="ModalCtrl.submit(ActionForm)" novalidate>
<div class="modal-header">
<ol class="headercrumb">
<li class="static" translate>FORM.LABELS.PRICE_LIST</li>
<li class="title text-capitalize">
{{ModalCtrl.priceList.description}}
</li>
</ol>
</div>
<div class="modal-body" data-import-modal>
<div class="alert alert-info">
<p translate>PRICE_LIST.IMPORT.PRICE_LIST_ITEMS_DESCRIPTION</p>
<a ng-click="ModalCtrl.downloadTemplate()" href>
<i class="fa fa-cloud-download"></i>
<span translate>PRICE_LIST.IMPORT.PRICE_LIST_ITEMS</span>
</a>
</div>

<div class="form-group"
ng-class="{ 'has-error' : ModalCtrl.noSelectedFile }">
<label translate>ACCOUNT.IMPORT.LOAD_FROM_FILE</label>
<input
id="import-input"
accept=".csv"
class="form-control"
type="file"
name="file"
ng-model="ModalCtrl.file"
ngf-select="ModalCtrl.select(ModalCtrl.file)">
<div class="help-block" data-error-message ng-show="ModalCtrl.noSelectedFile">
<i class="fa fa-warning"></i> <span translate>INVENTORY.NO_FILE_SELECTED</span>
</div>
</div>

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

<bh-loading-button
disabled="!ModalCtrl.file"
loading-state="ActionForm.$loading">
</bh-loading-button>
</div>
</form>
58 changes: 58 additions & 0 deletions client/src/modules/prices/modal/import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
angular.module('bhima.controllers')
.controller('ImportPriceListModalController', ImportPriceListModalController);

ImportPriceListModalController.$inject = [
'data', '$uibModalInstance', 'InventoryService',
'Upload', 'NotifyService', 'PriceListService',
];

function ImportPriceListModalController(data, Instance, Inventory, Upload, Notify, PriceList) {
const vm = this;

vm.downloadTemplate = Inventory.downloadInventoriesTemplate;
vm.cancel = Instance.close;
vm.priceList = data;
vm.select = (file) => {
vm.noSelectedFile = !file;
};

vm.downloadTemplate = PriceList.downloadTemplate;

vm.submit = () => {
// send data only when a file is selected
if (!vm.file) {
vm.noSelectedFile = true;
return;
}

uploadFile(vm.file);
};

/** upload the file to server */
function uploadFile(file) {
vm.uploadState = 'uploading';

const params = {
url : '/prices/item/import',
data : { file, pricelist_uuid : data.uuid },
};

// upload the file to the server
Upload.upload(params)
.then(handleSuccess, Notify.handleError, handleProgress);

// success upload handler
function handleSuccess() {
vm.uploadState = 'uploaded';
Notify.success('INVENTORY.UPLOAD_SUCCESS');
Instance.close();
}

// progress handler
function handleProgress(evt) {
file.progress = Math.min(100, parseInt((100.0 * evt.loaded) / evt.total, 10));
vm.progressStyle = { width : String(file.progress).concat('%') };
}

}
}
1 change: 1 addition & 0 deletions client/src/modules/prices/prices.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<i class="fa fa-print"></i> <span translate>FORM.BUTTONS.PRINT</span>
</a>
</li>

<li role="menuitem">
<a data-method="export" id="export" href="/prices/download/list?{{PriceListCtrl.download('csv')}}" translate-attr="{'download': 'DOWNLOADS.CSV'}" >
<i class="fa fa-file-excel-o"></i> <span translate>DOWNLOADS.CSV</span>
Expand Down
17 changes: 17 additions & 0 deletions client/src/modules/prices/prices.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function PriceListController(
vm.download = download;
vm.openColumnConfigModal = openColumnConfigModal;
vm.toggleInlineFilter = toggleInlineFilter;
vm.ImportList = ImportList;
// set price list items
vm.addItem = addItem;
// delete a price list
Expand Down Expand Up @@ -172,6 +173,22 @@ function PriceListController(
});
}

// Add pricelist Item in a modal
function ImportList(pricelist) {
return $uibModal.open({
templateUrl : 'modules/prices/modal/import.html',
controller : 'ImportPriceListModalController as ModalCtrl',
keyboard : false,
backdrop : 'static',
size : 'md',
resolve : {
data : function dataProvider() {
return pricelist || {};
},
},
});
}

// refresh the displayed PriceList
function refreshPriceList() {
return PriceListService.read(null, { detailed : 1 }).then(data => {
Expand Down
10 changes: 9 additions & 1 deletion client/src/modules/prices/prices.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function PriceListService(Api) {
service.details = details;
service.deleteItem = deleteItem;
service.download = download;

service.downloadTemplate = downloadTemplate;
/**
* @method create
*
Expand Down Expand Up @@ -78,5 +78,13 @@ function PriceListService(Api) {
return service.$http.get(url, params)
.then(service.util.unwrapHttpResponse);
}

function downloadTemplate() {
const url = service.url.concat('download/template');
return service.$http.get(url)
.then(response => {
return service.util.download(response, 'Iventory item Template', 'csv');
});
}
return service;
}
7 changes: 7 additions & 0 deletions client/src/modules/prices/templates/action.cell.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
</a>
</li>

<li>
<a data-method="import-items" ng-click="grid.appScope.ImportList(row.entity)" href>
<i class="fa fa-upload"></i>
<span translate>FORM.BUTTONS.IMPORT</span> <span translate>FORM.LABELS.ITEMS</span>
</a>
</li>

<li>
<a data-method="edit-print" target="_blank" href="/prices/report/{{row.entity.uuid}}?{{grid.appScope.download('pdf')}}">
<i class="fa fa-print"></i>
Expand Down
2 changes: 2 additions & 0 deletions server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -578,9 +578,11 @@ exports.configure = function configure(app) {
app.get('/prices', priceList.list);
app.get('/prices/:uuid', priceList.details);
app.get('/prices/download/list', priceListPreport.downloadRegistry);
app.get('/prices/download/template', priceList.downloadTemplate);
app.get('/prices/report/:uuid', financeReports.priceList);
app.post('/prices', priceList.create);
app.post('/prices/item', priceList.createItem);
app.post('/prices/item/import', upload.middleware('csv', 'file'), priceList.importItem);
app.put('/prices/:uuid', priceList.update);
app.delete('/prices/:uuid', priceList.delete);
app.delete('/prices/item/:uuid', priceList.deleteItem);
Expand Down
54 changes: 54 additions & 0 deletions server/controllers/finance/priceList.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@
* PUT /prices/:uuid
* DELETE /prices/:uuid
*/
const path = require('path');
const db = require('../../lib/db');
const { uuid } = require('../../lib/util');

const BadRequest = require('../../lib/errors/BadRequest');
const util = require('../../lib/util');

exports.lookup = lookup;
/**
* Lists all price lists in the database
Expand Down Expand Up @@ -229,6 +233,56 @@ exports.createItem = function createItem(req, res, next) {
.done();
};


/**
* @method downloadTemplate
*
* @description send to the client the template file for price list item import
*/
exports.downloadTemplate = (req, res, next) => {
try {
const file = path.join(__dirname, '../../resources/templates/import-inventory-item-template.csv');
res.download(file);
} catch (error) {
next(error);
}
};

exports.importItem = async (req, res, next) => {
try {
if (!req.files || req.files.length === 0) {
const errorDescription = 'Expected at least one file upload but did not receive any files.';
throw new BadRequest(errorDescription, 'ERRORS.MISSING_UPLOAD_FILES');
}

const filePath = req.files[0].path;

const data = await util.formatCsvToJson(filePath);
if (!hasValidDataFormat(data)) {
throw new BadRequest('The given file has a bad data format for stock', 'ERRORS.BAD_DATA_FORMAT');
}
const priceListUuid = db.bid(req.body.pricelist_uuid);
const sql = 'CALL importPriceListItem(?,?,?,?);';
const transaction = db.transaction();
data.forEach(item => {

transaction.addQuery(sql, [priceListUuid, item.code, item.price, item.is_percentage]);
});
await transaction.execute();
res.sendStatus(200);
} catch (ex) {
next(ex);
}

};

function hasValidDataFormat(data) {
const invalids = data.filter(r => {
return (r.code && r.price);
});
return (invalids.length === data.length);
}

exports.deleteItem = function deleteItem(req, res, next) {
const priceListDeleteItemSql = `DELETE FROM price_list_item WHERE uuid = ?`;
const itemUuid = db.bid(req.params.uuid);
Expand Down
33 changes: 32 additions & 1 deletion server/models/migrations/next/migrate.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
/*
* DATABASE CHANGES FOR VERSION 1.6.0 TO 1.7.0
*/
*/
/*
*Pricelist importation
by Jeremielodi
2019-10-16
*/

DROP PROCEDURE IF EXISTS importPriceListItem;
CREATE PROCEDURE importPriceListItem (
IN _price_list_uuid BINARY(16),
IN _inventory_code VARCHAR(30),
IN _value DOUBLE,
IN _is_percentage tinyint(1)
)
BEGIN
DECLARE _inventory_uuid BINARY(16);
DECLARE isIventory tinyint(5);
DECLARE inventoryLabel VARCHAR(100);

SELECT uuid, text, count(uuid)
INTO _inventory_uuid, inventoryLabel, isIventory
FROM inventory
WHERE code = _inventory_code;

IF isIventory = 1 THEN
DELETE FROM price_list_item
WHERE price_list_uuid = _price_list_uuid AND inventory_uuid = _inventory_uuid;
INSERT INTO price_list_item(uuid, inventory_uuid, price_list_uuid, label, value, is_percentage)
VALUES(HUID(uuid()), _inventory_uuid, _price_list_uuid, inventoryLabel, _value, _is_percentage);
END IF;

END $$
Loading

0 comments on commit 8c97480

Please sign in to comment.