Skip to content

Commit

Permalink
feat(journal): only edit editable transactions
Browse files Browse the repository at this point in the history
This commit implements highlighting and edit mode for transaction groups
in the journal ui-grid.  It also ensures that only editable transactions
(those marked with _editing) are allowed to receive edits.

Finally, an editing flag is visible outside of the ui-grid to show that
there are currently transactions open for editing.
  • Loading branch information
Jonathan Niles committed Aug 4, 2016
1 parent 7ad17bd commit 94edba8
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 31 deletions.
214 changes: 201 additions & 13 deletions client/src/js/services/grid/TransactionService.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
angular.module('bhima.services')
.service('TransactionService', TransactionService);

TransactionService.$inject = ['util'];
TransactionService.$inject = ['util', 'uiGridConstants'];

/**
* Transactions Service
Expand All @@ -10,62 +10,250 @@ TransactionService.$inject = ['util'];
* and providing a number of utility methods for manipulating and framing this
* information.
*/
function TransactionService(util) {
function TransactionService(util, uiGridConstants) {

// convert arguments to an array
function toArray(args) {
return Array.prototype.slice.call(args);
}

/**
* @function indexBy
*
* @description
* This function maps transaction row indexes to transaction record_uuids.
*
* @private
*/
function indexBy(array, property) {
return array.reduce(function (aggregate, row, index) {
var key = row.entity[property];

// header rows do not have keys
if (!key) { return aggregate; }

aggregate[key] = aggregate[key] || [];
aggregate[key].push(index);

return aggregate;
}, {});
}

/**
* @constructor
*/
function Transactions(gridOptions) {
this.gridOptions = gridOptions;
var cachedGridApiCallback = gridOptions.onRegisterApi;

// a mapping of record_uuid -> array indices in the rendered row set
this.transactionIndices = {};

// these arrays store the transactions ids currently being highlighted or
// edited.
this._highlights = [];
this._edits = [];

util.after(gridOptions, 'onRegisterApi', function onRegisterApi(api) {
this.gridApi = api;

// on each row rendering, recompute the transaction row indexes
api.core.on.rowsRendered(null, createTransactionIndexMap.bind(this));
}.bind(this));
}

/**
* @function createTransactionIndexMap
*
* @description
* Creates the transaction index mapping for easy lookup of transactions.
* This is an internal method that is called on every rowRendered() call.
*
* @private
*/
function createTransactionIndexMap() {
var rows = this.gridApi.grid.rows;
this.transactionIndices = indexBy(rows, 'record_uuid');
}

/**
* @function getTransactionRows
*
* @description
* Efficiently return transaction rows (but not header rows) in a transaction
* given the record_uuid using the transaction index mapping.
*
* @param {String} uuid - the record_uuid of the transaction
* @return {Array} rows - the rows in the grid used to render that transaction.
*/
Transactions.prototype.getTransactionRows = function getTransactionRows(uuid) {
var array = this.gridApi.grid.rows;
var indices = this.transactionIndices[uuid];

// return an array of transaction rows. These are bound to the grid, despite
// being a new array.
return indices.map(function (index) {
return array[index];
});
};


/**
* @method validate
*
* @description
* A method to validate individual transactions by their record uuid. If no records
* uuids are passed in, it will validate all transactions in the grid.
*/
Transactions.prototype.validate = function validate() {
// noop()
Transactions.prototype.validate = function validate(uuid) {

// if no uuid is passed in, call with all uuids indexed
if (!uuid) {
this.validate.apply(this, Object.keys(this.indexBy));
}

var rows = this.getTransactionRows(uuid);

// @TODO - custom logic to validate that debits/credits balance, etc

return true;
};

// helper functions to get parent row (group header) from a child row
function getParentNode(row) {
return row.treeNode.parentRow;
}

// helper functions to get child row record_uuid from the first child row.
// Note: if we want to support multiple groups, this should take the last
// child to avoid getting another group header.
function getChildRecordUuid(row) {
var child = row.treeNode.children[0].row;
return child.entity.record_uuid;
}

/**
* @function setPropertyOnTransaction
*
* @description
* This function toggles a single property on the transaction rows. It must
* be called with `.call()` or `.bind()` to properly set the `this` variable.
*
* @param {String} uuid - the transaction's record_uuid
* @param {String} property - the property to set on each transaction row
* @param {Any} value - any value to set on the transaction
*
* @private
*/
function setPropertyOnTransaction(uuid, property, value) {

var rows = this.getTransactionRows(uuid);

// loop through all rows to set the transactions
rows.forEach(function (row) {
if (row.entity.record_uuid === uuid) {
row[property] = value;

// set the transaction property with the same record
var parent = getParentNode(row);
parent[property] = value;
}
});

// make sure the grid updates with the changes
this.gridApi.core.notifyDataChange(uiGridConstants.dataChange.COLUMN);
}

/**
* @method highlight
*
* @description
* This function sets the _hasHighlight property on all transactions matching
* the provided uuid. This should
* the provided uuid. This is useful for scrolling transactions into view.
*
* @param {String} uuid - the record_uuid of the transaction to highlight.
* @param {Boolean} unsetPreviousHighlight - boolean to determine whether
* previous highlights should persist.
*/
Transactions.prototype.highlight = function highlight(uuid, unsetPreviousHighlight) {
// noop()

// remove the _editing property on all transactions
if (unsetPreviousHighlight) {
this._highlights.forEach(function (uuid) {
setPropertyOnTransaction.call(this, uuid, '_hasHighlight', false);
}.bind(this));
}

// set highlight on the provided transaction
setPropertyOnTransaction.call(this, uuid, '_hasHighlight', true);
};

Transactions.prototype.scrollIntoView = function scrollIntoView(uuid) {
var rows = this.getTransactionRows(uuid);
var lastRowInView = rows[rows.length - 1];
this.gridApi.core.scrollTo(lastRowInView.entity);
};

/**
* @method invalidate
*
* @param {String} uuid - the record_uuid of the transaction to invalidate.
*/
Transactions.prototype.invalidate = function invalidate(uuid) {
setPropertyOnTransaction.call(this, uuid, '_invalid', true);
};

/**
* @method edit
*
* @description
* This function sets the _editing property on all transactions matching the
* provided uuid.
* provided uuid. It also scrolls the transaction into view if necessary.
*
* @param {String|Object} uuid - either a row or a record_uuid of the
* transaction to be edited. If a row is passed in, the record_uuid is
* inferred from the child rows.
*/
Transactions.prototype.edit = function edit(uuid) {
// noop()

// if a row is passed in, resolve the row to the child record_uuid
if (angular.isObject(uuid)) {
uuid = getChildRecordUuid(uuid);
}

setPropertyOnTransaction.call(this, uuid, '_editing', true);
this.scrollIntoView(uuid);
this._edits.push(uuid);
};

/**
* @method save
*
* @description
* This function unsets the _editing property on all transactions matching the
* provided uuid. It validates the transaction before any other action is taken.
* This function saves all transactions by
*/
Transactions.prototype.save = function save(uuid) {
// noop()
Transactions.prototype.save = function save() {
// @TODO validate()

// remove the _editing property on all transactions
this._edits.forEach(function (uuid) {
setPropertyOnTransaction.call(this, uuid, '_editing', false);
}.bind(this));

// set the edits length to 0
this._edits.length = 0;
};

/**
* @method isEditing
*
* @description
* Returns a boolean indicating if a transaction is being edited or not.
*
* @returns {Boolean} - True if there is a transaction being edited, false
* otherwise.
*/
Transactions.prototype.isEditing = function isEditing() {
return this._edits.length > 0;
};

/**
Expand Down
50 changes: 50 additions & 0 deletions client/src/less/bhima-bootstrap.less
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,53 @@ input[type=number] {-moz-appearance: textfield;}
/* expert mode */
/* @padding-base-vertical: 3px; */
/* @padding-base-horizontal: 6px; */

/* badges additions */
.badge.badge-primary {
background-color: @label-primary-bg;
border-radius: @border-radius-base;
}

.badge.badge-danger {
background-color: @label-danger-bg;
border-radius: @border-radius-base;
}

.badge.badge-warning {
background-color: @label-warning-bg;
border-radius: @border-radius-base;
}

.badge.badge-success {
background-color: @label-success-bg;
border-radius: @border-radius-base;
}

.badge.badge-info {
background-color: @label-info-bg;
border-radius: @border-radius-base;
}

/** transaction things */

.ui-grid-row .ui-grid-cell.transaction-edit {
background-color: @state-info-bg;
border-color: @btn-primary-border;
color: @state-info-text;
}

.ui-grid-row .ui-grid-cell.transaction-edit.transaction-edit-header {
background-color: @btn-primary-bg;
border-color: @btn-primary-border;
color: @btn-primary-color;
}

.ui-grid-row .ui-grid-cell.transaction-highlight {
border-color: @state-warning-border;
}

.ui-grid-row .ui-grid-cell.transaction-highlight.transaction-highlight-header {
background-color: @state-warning-bg;
border-color: @state-warning-border;
color: @state-warning-text;
}
9 changes: 8 additions & 1 deletion client/src/partials/journal/journal.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
<div class="bhima-title">
<ol class="headercrumb">
<li class="static">{{ "TREE.FINANCE" | translate }}</li>
<li class="title">{{ "POSTING_JOURNAL.TITLE" | translate }}</li>
<li class="title">
{{ "POSTING_JOURNAL.TITLE" | translate }}

<span class="badge badge-primary text-uppercase" ng-show="JournalCtrl.transactions.isEditing()">
<i class="fa fa-moon-o"></i>
Edit Mode
</span>
</li>
</ol>

<div class="toolbar">
Expand Down
29 changes: 19 additions & 10 deletions client/src/partials/journal/journal.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Confi
vm.gridOptions = {
enableColumnMenus : false,
authenticateEdits : true,
appScopeProvider : vm
appScopeProvider : vm,
rowTemplate: '/partials/journal/templates/transaction.row.html',
cellEditableCondition: function ($scope) {
return $scope.row._editing;
}
};

// Initialise each of the journal utilities, providing them access to the journal
Expand All @@ -57,16 +61,20 @@ function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Confi
transactions = new Transactions(vm.gridOptions);
editors = new Editors(vm.gridOptions);

window.transactions = transactions;
vm.transactions = transactions;

vm.loading = true;
Journal.read()
.then(function (records) {
vm.gridOptions.data = records;
})
.catch(function (error) {
vm.hasError = true;
Notify.errorHandler(error);
})
.finally(toggleLoadingIndicator);
.then(function (records) {
vm.gridOptions.data = records;
})
.catch(function (error) {
vm.hasError = true;
Notify.errorHandler(error);
})
.finally(toggleLoadingIndicator);


/**
* @function toggleLoadingIndicator
Expand Down Expand Up @@ -127,7 +135,8 @@ function JournalController(Journal, Sorting, Grouping, Filtering, Columns, Confi

{ field : 'reference_uuid', displayName : 'TABLE.COLUMNS.REFERENCE', headerCellFilter: 'translate', visible: false },
{ field : 'record_uuid', displayName : 'TABLE.COLUMNS.RECORD', headerCellFilter: 'translate', visible: false },
{ field : 'user', displayName : 'TABLE.COLUMNS.RESPONSIBLE', headerCellFilter: 'translate', visible: false, enableCellEdit: false }
{ field : 'user', displayName : 'TABLE.COLUMNS.RESPONSIBLE', headerCellFilter: 'translate', visible: false, enableCellEdit: false },
{ field : 'actions', displayName : '', headerCellFilter: 'translate', visible: true, enableCellEdit: false, cellTemplate: '/partials/journal/templates/actions.cell.html' }
];

vm.gridOptions.columnDefs = columns;
Expand Down
Loading

0 comments on commit 94edba8

Please sign in to comment.