Skip to content

Commit

Permalink
Merge #1760
Browse files Browse the repository at this point in the history
1760: refactor: update invoice registry to latest standards r=sfount
This PR updates the Invoice Registry to the latest standard.  It should help developers of other registries figure out how to update their registries as well.

~~**Note**: The 5K+ line change is because I've committed a package-lock.json file that comes by default with npm v5+.  You can find it in the [release notes](https://github.com/npm/npm/releases/tag/v5.0.0).  If you would prefer me to remove it and us to ignore it, that's cool.~~ I've removed the `package-lock.json` as it appears to cause issues with previous NPM versions.

For reference of what changed, all components of #1749 were tackled here.

#### Code Changes
 1. I've refined the `FilterParser` implementation on the server by turning off `autoParseStatements` (see #1454).  Every filter must be specified.  I've also changed cashReference comparison statement to convert the binary uuid in JS, since this is what most other modules do.
 2. I've migrated the filters to using the client `FilterService`.
 3. I've ensured the filter modal to latest standards.  This includes using `bhClear` (see #1702).
 4. The end-to-end tests have been updated to reflect these changes.  They now use a combined `SearchModal` service, shared with the Cash Payments Registry.  It provides simple control of the filters.
 5. Two options for downloading have been added: PDF and CSV.  For uniformity, they both use the server to serve files to an HTML `<a href download>` tag.  If needed, I can try to implement the CSV rendering with the Exporter service ... but I worry about the difference in UX.  I'll look to review for guidance.
 6. I've implemented the GridState service on this grid.  It works as the Journal's works.
 7. I've implemented the Grid Column service.  It also uses the GridState as described above... however, there is a bug in the Grid Column service reported in #1759.

#### A Note on End to End Tests
I've combined the Cash Payment Registry's `SearchModal` page object with the Invoice Registry's `SearchModal`.  Basically, any search form should be able to leverage this page object without writing their own massive test suite.  It prefers convention over configuration though, so you'll have to name your controllers `$ctrl`, your search parameters `searchQueries` (as done in the Journal and Cash Payments registry) and your default search paramters `defaultQueries`.

#### Screenshots
Pics or it didn't happen, right?

**New User Interface**
![invoiceregistrynewui](https://cloud.githubusercontent.com/assets/896472/26746668/ef0bcfa8-47e8-11e7-8bd7-819f9a4e1dea.png)

**New Menu Demo**
![invoiceregistrydropdownmenu](https://cloud.githubusercontent.com/assets/896472/26746674/f928966a-47e8-11e7-9026-2fe054b33142.png)

**Download as CSV Option**
![invoiceregistrydownloadascsv png](https://cloud.githubusercontent.com/assets/896472/26746727/42f4b8e6-47e9-11e7-8e63-fabd55f071fa.png)

**Column Configuration Modal**
![invoiceregistrycolumnconfigurationmodal](https://cloud.githubusercontent.com/assets/896472/26746763/7fb6e542-47e9-11e7-8669-2eb8536517f8.png)

**Column Changes Saved in Save State**
![invoiceregistrycolumnconfigurationsaved](https://cloud.githubusercontent.com/assets/896472/26746774/9109f7da-47e9-11e7-98b6-d01faf5ca512.png)

Closes #1749.

----
Thank you for contributing!

Before submitting this pull request, please verify that you have:
 - [x] Run your code through ESLint.  [Check out our styleguide](https://github.com/IMA-WorldHealth/bhima-2.X/blob/master/docs/STYLEGUIDE.md).
 - [x] Run integration tests.
 - [x] Run end-to-end tests.
 - [x] Accurately described the changes your are making in this pull request.

For a more detailed checklist, [see the official review checklist](https://docs.google.com/document/d/1nupLVLRXgSZJQo_acLgrwvPnN8RukfSiwRhSToj81uU/pub) that this PR will be evaluated against.

Ensuring that the above checkboxes are completed will help speed the review process and help build a stronger application.  Thanks!
  • Loading branch information
bors[bot] committed Jun 5, 2017
2 parents fedcce7 + 5aeb188 commit 9ca7d36
Show file tree
Hide file tree
Showing 19 changed files with 552 additions and 492 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
.idea/*
*~

# ignore lock file until it works with npm < version 5
package-lock.json

# ignore all compressed files
*.gz, *.xz, *.zip

Expand Down
6 changes: 6 additions & 0 deletions client/src/css/ui-grid.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
height : calc(100vh - 65px);
}

/* this is full height minus the header crumb and filters*/
.grid-full-height-with-filters {
width: 100%;
height : calc(100vh - 106px);
}

.grid-journal {
width : 100%;
height : calc(100vh - 108px);
Expand Down
2 changes: 1 addition & 1 deletion client/src/i18n/en/downloads.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{"DOWNLOADS":{"JSON":"Download as JSON",
"PDF":"Download as PDF",
"CSV":"Download as CSV"}}
"CSV":"Download as CSV"}}
16 changes: 11 additions & 5 deletions client/src/less/bhima-bootstrap.less
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
@ima-light-orange: #fdba64;
@ima-light-blue: #6fbdee;

/** @todo move all structure code into less with configurable variables */
/**
* Application structure variables - these widths are used to determine how much
/** @todo move all structure code into less with configurable variables */ /** * Application structure variables - these widths are used to determine how much
* space the tree navigation takes up in its `expanded` and `collapsed` states.
*/
@expanded-offset: 260px;
Expand Down Expand Up @@ -266,6 +264,14 @@ input[type=number] {-moz-appearance: textfield;}

/* large button as menu */
.bg-ima-dark-blue { background: @ima-dark-blue; }
.bg-ima-dark-blue .value,
.bg-ima-dark-blue .uilabel
.bg-ima-dark-blue .value,
.bg-ima-dark-blue .uilabel
{ color: white !important; }

/* experimental usage of the search-modal */
.search-modal {
overflow : auto;
max-height:600px;
padding:0px;
}

4 changes: 1 addition & 3 deletions client/src/modules/cash/payments/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ function CashPaymentRegistryController(
vm.filter = filter;

// global variables
// vm.filters = { lang: Languages.key };
vm.filtersFmt = [];
vm.gridOptions = {};
vm.enterprise = Session.enterprise;
vm.bhConstants = bhConstants;
Expand Down Expand Up @@ -137,7 +135,7 @@ function CashPaymentRegistryController(
toggleLoadingIndicator();

var request = Cash.read(null, filters);

request.then(function (rows) {
rows.forEach(function (row) {
var hasCreditNote = row.reversed;
Expand Down
6 changes: 5 additions & 1 deletion client/src/modules/cash/payments/templates/search.modal.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
angular.module('bhima.controllers')
.controller('SearchCashPaymentModalController', SearchCashPaymentModalController);

// dependencies injections
SearchCashPaymentModalController.$inject = [
'CashboxService', 'NotifyService', '$uibModalInstance', 'filters', 'Store', 'PeriodService', 'util', 'DebtorGroupService',
];

/**
* Search Cash Payment controller
*
* @description
* This controller powers the Invoice Search modal. Invoices are passed in from the registry as
* POJO and are attached to the view. They are modified here and returned to the parent controller
* as a POJO.
*/
function SearchCashPaymentModalController(Cashboxes, Notify, Instance, filters, Store, Periods, util, DebtorGroups) {
var vm = this;
Expand Down
140 changes: 84 additions & 56 deletions client/src/modules/invoices/patientInvoice.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@ angular.module('bhima.services')
.service('PatientInvoiceService', PatientInvoiceService);

PatientInvoiceService.$inject = [
'$uibModal', 'util', 'SessionService', 'PrototypeApiService', 'DepricatedFilterService'
'$uibModal', 'SessionService', 'PrototypeApiService', 'FilterService', 'appcache', 'PeriodService',
'$httpParamSerializer', 'LanguageService',
];

/**
* @module
* Patient Invoice Service
*
* Allows direct querying of the /invoices API. Normally this should be done
* through the PatientService, but for queries not tied to particular patients,
* this service is particularly useful.
* @description
* This service wraps the /invoices URL and all CRUD on the underlying tables
* takes place through this service.
*/
function PatientInvoiceService(Modal, util, Session, Api, Filters) {
function PatientInvoiceService(Modal, Session, Api, Filters, AppCache, Periods, $httpParamSerializer, Languages) {
var service = new Api('/invoices/');

var filter = new Filters();
var invoiceFilters = new Filters();
var filterCache = new AppCache('cash-filters');

service.create = create;
service.openSearchModal = openSearchModal;
service.formatFilterParameters = formatFilterParameters;
service.openCreditNoteModal = openCreditNoteModal;
service.balance = balance;
service.filters = invoiceFilters;

/**
* @method create
Expand Down Expand Up @@ -55,7 +58,7 @@ function PatientInvoiceService(Modal, util, Session, Api, Filters) {

cp.description = description;

return Api.create.call(this, { invoice: cp });
return Api.create.call(this, { invoice : cp });
}

/**
Expand Down Expand Up @@ -83,7 +86,6 @@ function PatientInvoiceService(Modal, util, Session, Api, Filters) {
delete item._invalid;
delete item._initialised;
delete item._hasPriceList;

return item;
}

Expand All @@ -95,14 +97,14 @@ function PatientInvoiceService(Modal, util, Session, Api, Filters) {
animation : false,
keyboard : false,
backdrop : 'static',
controller : 'InvoiceRegistrySearchModalController as ModalCtrl',
resolve: {
filters : function filtersProvider() { return filters; }
}
controller : 'InvoiceRegistrySearchModalController as $ctrl',
resolve : {
filters : function filtersProvider() { return filters; },
},
}).result;
}

//open a dialog box to Cancel Credit Note
// open a dialog box to Cancel Credit Note
function openCreditNoteModal(invoice) {
return Modal.open({
templateUrl : 'modules/invoices/registry/modalCreditNote.html',
Expand All @@ -115,49 +117,75 @@ function PatientInvoiceService(Modal, util, Session, Api, Filters) {
}, true).result;
}

/**
* This function prepares the headers for invoice properties which were filtered,
* Special treatment occurs when processing data related to the date
* @todo - this might be better in its own service
*/
function formatFilterParameters(params) {
var columns = [
{ field: 'service_id', displayName: 'FORM.LABELS.SERVICE' },
{ field: 'user_id', displayName: 'FORM.LABELS.USER' },
{ field: 'reference', displayName: 'FORM.LABELS.REFERENCE' },
{ field: 'debtor_uuid', displayName: 'FORM.LABELS.CLIENT' },
{ field : 'patientReference', displayName: 'FORM.LABELS.REFERENCE_PATIENT'},
{ field: 'billingDateFrom', displayName: 'FORM.LABELS.DATE', comparitor: '>', ngFilter:'date' },
{ field: 'billingDateTo', displayName: 'FORM.LABELS.DATE', comparitor: '<', ngFilter:'date' },
{ field: 'reversed', displayName : 'FORM.INFO.CREDIT_NOTE' },
{ field: 'defaultPeriod', displayName : 'TABLE.COLUMNS.PERIOD', ngFilter : 'translate' },
{ field: 'cash_uuid', displayName : 'FORM.INFO.PAYMENT' },
{ field: 'defaultPeriod', displayName : 'TABLE.COLUMNS.PERIOD', ngFilter : 'translate' },
{ field: 'debtor_group_uuid', displayName: 'FORM.LABELS.DEBTOR_GROUP' }
];

// returns columns from filters
return columns.filter(function (column) {
var LIMIT_UUID_LENGTH = 6;
var value = params[column.field];

if (angular.isDefined(value)) {
column.value = value;

if (column.field === 'cash_uuid' || column.field === 'debtor_group_uuid') {
column.value = column.value.slice(0, LIMIT_UUID_LENGTH).concat('...');
}

// @FIXME tempoarary hack for default period
if (column.field === 'defaultPeriod') {
column.value = filter.lookupPeriod(value).label;
}
return true;
} else {
return false;
}
});
invoiceFilters.registerDefaultFilters([
{ key : 'period', label : 'TABLE.COLUMNS.PERIOD', valueFilter : 'translate' },
{ key : 'custom_period_start', label : 'PERIODS.START', valueFilter : 'date' },
{ key : 'custom_period_end', label : 'PERIODS.END', valueFilter : 'date' },
{ key : 'limit', label : 'FORM.LABELS.LIMIT' }]);

invoiceFilters.registerCustomFilters([
{ key : 'service_id', label : 'FORM.LABELS.SERVICE' },
{ key : 'user_id', label : 'FORM.LABELS.USER' },
{ key : 'reference', label : 'FORM.LABELS.REFERENCE' },
{ key : 'debtor_uuid', label : 'FORM.LABELS.CLIENT' },
{ key : 'patientReference', label : 'FORM.LABELS.REFERENCE_PATIENT' },
{ key : 'billingDateFrom', label : 'FORM.LABELS.DATE', comparitor : '>', valueFilter : 'date' },
{ key : 'billingDateTo', label : 'FORM.LABELS.DATE', comparitor : '<', valueFilter : 'date' },
{ key : 'reversed', label : 'FORM.INFO.CREDIT_NOTE' },
{ key : 'defaultPeriod', label : 'TABLE.COLUMNS.PERIOD', valueFilter : 'translate' },
{ key : 'cash_uuid', label : 'FORM.INFO.PAYMENT' },
]);

if (filterCache.filters) {
// load cached filter definition if it exists
invoiceFilters.loadCache(filterCache.filters);
}

// once the cache has been loaded - ensure that default filters are provided appropriate values
assignDefaultFilters();

function assignDefaultFilters() {
// get the keys of filters already assigned - on initial load this will be empty
var assignedKeys = Object.keys(invoiceFilters.formatHTTP());

// assign default period filter
var periodDefined =
service.util.arrayIncludes(assignedKeys, ['period', 'custom_period_start', 'custom_period_end']);

if (!periodDefined) {
invoiceFilters.assignFilters(Periods.defaultFilters());
}

// assign default limit filter
if (assignedKeys.indexOf('limit') === -1) {
invoiceFilters.assignFilter('limit', 100);
}
}

service.removeFilter = function removeFilter(key) {
invoiceFilters.resetFilterState(key);
};

// load filters from cache
service.cacheFilters = function cacheFilters() {
filterCache.filters = invoiceFilters.formatCache();
};

service.loadCachedFilters = function loadCachedFilters() {
invoiceFilters.loadCache(filterCache.filters || {});
};

// downloads a type of report based on the
service.download = function download(type) {
var filterOpts = invoiceFilters.formatHTTP();
var defaultOpts = { renderer : type, lang : Languages.key };

// combine options
var options = angular.merge(defaultOpts, filterOpts);

// return serialized options
return $httpParamSerializer(options);
};

return service;
}
Loading

0 comments on commit 9ca7d36

Please sign in to comment.