Skip to content

Commit

Permalink
feat(journal): implement filtering server-side
Browse files Browse the repository at this point in the history
This commit implements filtering on the posting journal in the server
side.  All visible columns are supported, including min and max date.
  • Loading branch information
Jonathan Niles authored and sfount committed Nov 15, 2016
1 parent d53701f commit 49f88fd
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 61 deletions.
2 changes: 2 additions & 0 deletions client/src/js/services/System.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ function SystemService($http, util) {
console.log('Connection was open.');
}

/*
// set up event stream
var source = new EventSource(baseUrl.concat('/stream'));
source.addEventListener('open', handleOpenEvent, false);
source.addEventListener('message', handleServerSentEvent, false);
source.addEventListener('error', handleErrorEvent, false);
*/

return service;
}
195 changes: 147 additions & 48 deletions server/controllers/finance/journal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,36 @@
*
* @module finance/journal/
*
* @description This module is responsible for handling CRUD operations
* @description
* This module is responsible for handling CRUD operations
* against the `posting journal` table.
*
* @requires q
* @requires lodash
* @requires node-uuid
* @requires lib/db
* @requires lib/errors/NotFound
* @requires lib/errors/BadRequest
*/

'use strict';

// npm deps
const q = require('q');
const _ = require('lodash');
const uuid = require('node-uuid');

// module dependencies
const db = require('../../../lib/db');
const uuid = require('node-uuid');
const NotFound = require('../../../lib/errors/NotFound');
const _ = require('lodash');
const util = require('../../../lib/util');
const NotFound = require('../../../lib/errors/NotFound');
const BadRequest = require('../../../lib/errors/BadRequest');

// expose to the api
exports.list = list;
exports.getTransaction = getTransaction;
exports.reverse = reverse;
exports.journalEntryList = journalEntryList;
exports.find = find;

/**
* Looks up a transaction by record_uuid.
Expand Down Expand Up @@ -49,7 +60,7 @@ function lookupTransaction(record_uuid) {
JOIN user u ON u.id = p.user_id
WHERE p.record_uuid = ?
ORDER BY p.trans_date DESC;
`;
`;

return db.exec(sql, [ db.bid(record_uuid) ])
.then(function (rows) {
Expand All @@ -64,27 +75,31 @@ function lookupTransaction(record_uuid) {
}

/**
* GET /journal
* Getting data from the posting journal
* @function find
*
* @description
* This function filters the posting journal by query parameters passed in via
* the options object. If no query parameters are provided, the method will
* return all items in the posting journal
*/
function list(req, res, next) {
function find(options) {
// remove the limit first thing, if it exists
let limit = Number(options.limit);
delete options.limit;

journalEntryList(req.query)
.then(rows => {
res.status(200).json(rows);
})
.catch(next);
}
// support flexible queries by keeping a growing list of conditions and
// statements
let conditions = {
statements: [],
parameters: []
};

/**
* @method journalEntryList
* @description return a list of journal entries
*/
function journalEntryList(params) {
let uuids = [];
let conditions = [];
let whereTransactions;
let whereDates;
// if nothing is passed in as an option, throw an error
if (_.isEmpty(options)) {
return q.reject(
new BadRequest('The request requires at least one parameter.', 'ERRORS.PARAMETERS_REQUIRED')
);
}

let sql = `
SELECT BUID(p.uuid) AS uuid, p.project_id, p.fiscal_year_id, p.period_id,
Expand All @@ -105,47 +120,131 @@ function journalEntryList(params) {
LEFT JOIN entity_map em ON em.uuid = p.entity_uuid
LEFT JOIN document_map dm1 ON dm1.uuid = p.record_uuid
LEFT JOIN document_map dm2 ON dm2.uuid = p.reference_uuid
WHERE
SQL_CONDITIONS
ORDER BY p.trans_date DESC
`;

// get only transactions given
if (params && params.uuids && params.uuids.length) {
uuids = params.uuids.map(uuid => {
return db.bid(uuid);
});
whereTransactions = ' p.uuid IN (?) ';
// filter on a record uuid
if (options.record_uuid) {
const recordUuid = db.bid(options.record_uuid);
conditions.statements.push('p.record_uuid = ?');
conditions.parameters.push(recordUuid);
delete options.record_uuid;
}

// filter on reference uuid
if (options.reference_uuid) {
const referenceUuid = db.bid(options.reference_uuid);
conditions.statements.push('p.reference_uuid = ?');
conditions.parameters.push(referenceUuid);
delete options.reference_uuid;
}

// TODO - will this have SQL injection?
if (options.description) {
conditions.statements.push(`p.description LIKE "%${options.description}%"`);
delete options.description;
}

// dates given
if (params && params.dateFrom && params.dateTo) {
params.dateFrom = new Date(params.dateFrom);
params.dateTo = new Date(params.dateTo);
whereDates = ' DATE(p.trans_date) BETWEEN DATE(?) AND DATE(?) ';
// filter on uuid
if (options.uuid) {
const id = db.bid(options.uuid);
conditions.statements.push('p.uuid = ?');
conditions.parameters.push(id);
delete options.uuid;
}

if (whereDates && whereTransactions) {
sql += ' WHERE ' + whereDates + ' AND ' + whereTransactions;
conditions.push(params.dateFrom, params.dateTo, uuids);
// filter on min date
if (options.dateFrom) {
conditions.statements.push('DATE(p.trans_date) >= ?');
conditions.parameters.push(new Date(options.dateFrom));
delete options.dateFrom;
}

// filter on max date
if (options.dateTo) {
conditions.statements.push('DATE(p.trans_date) <= ?');
conditions.parameters.push(new Date(options.dateTo));
delete options.dateTo;
}

if (options.comment) {
conditions.statements.push(`p.comment LIKE "%${options.comment}%"`);
delete options.comment;
}

// this accounts for currency_id, user_id, trans_id, account_id, etc ..

// assign query parameters as needed
let { statements, parameters } = util.parseQueryStringToSQL(options, 'p');
conditions.statements = _.concat(conditions.statements, statements);
conditions.parameters = _.concat(conditions.parameters, parameters);

} else if (whereDates && !whereTransactions) {
sql += ' WHERE ' + whereDates;
conditions.push(params.dateFrom, params.dateTo);
sql = sql.replace('SQL_CONDITIONS', conditions.statements.join(' AND '));

} else if (!whereDates && whereTransactions) {
sql += ' WHERE ' + whereTransactions;
conditions.push(uuids);
// finally, apply the LIMIT query
if (!isNaN(limit)) {
sql += ' LIMIT ?;';
conditions.parameters.push(limit);
}

sql += ' ORDER BY p.trans_date DESC;';
return db.exec(sql, conditions);
return db.exec(sql, conditions.parameters);
}

/**
* GET /journal
* Getting data from the posting journal
*/
function list(req, res, next) {

let promise;

// TODO - clean this up a bit. We should only use a single column definition
// for both this and find()
if (_.isEmpty(req.query)) {
let sql = `
SELECT BUID(p.uuid) AS uuid, p.project_id, p.fiscal_year_id, p.period_id,
p.trans_id, p.trans_date, BUID(p.record_uuid) AS record_uuid,
dm1.text AS hrRecord, p.description, p.account_id, p.debit, p.credit,
p.debit_equiv, p.credit_equiv, p.currency_id, c.name AS currencyName,
BUID(p.entity_uuid) AS entity_uuid, em.text AS hrEntity,
BUID(p.reference_uuid) AS reference_uuid, dm2.text AS hrReference,
p.comment, p.origin_id, p.user_id, p.cc_id, p.pc_id, pro.abbr,
pro.name AS project_name, per.start_date AS period_start,
per.end_date AS period_end, a.number AS account_number, u.display_name
FROM posting_journal p
JOIN project pro ON pro.id = p.project_id
JOIN period per ON per.id = p.period_id
JOIN account a ON a.id = p.account_id
JOIN user u ON u.id = p.user_id
JOIN currency c ON c.id = p.currency_id
LEFT JOIN entity_map em ON em.uuid = p.entity_uuid
LEFT JOIN document_map dm1 ON dm1.uuid = p.record_uuid
LEFT JOIN document_map dm2 ON dm2.uuid = p.reference_uuid
ORDER BY p.trans_date DESC
`;

promise = db.exec(sql);
} else {
promise = find(req.query);
}

promise
.then(rows => {
res.status(200).json(rows);
})
.catch(next)
.done();
}

/**
* GET /journal/:record_uuid
* send back a set of lines which have the same record_uuid the which provided by the user
*/
function getTransaction (req, res, next){
function getTransaction (req, res, next) {
lookupTransaction(req.params.record_uuid)
.then(function (transaction) {
.then(transaction => {
res.status(200).json(transaction);
})
.catch(next)
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/medical/patients/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ function lookupByDebtorUuid(debtorUuid) {
p.father_name, p.mother_name, p.religion, p.marital_status, p.profession, p.employer, p.spouse,
p.spouse_profession, p.spouse_employer, p.notes, p.avatar, proj.abbr, d.text,
dg.account_id, BUID(dg.price_list_uuid) AS price_list_uuid, dg.is_convention, BUID(dg.uuid) as debtor_group_uuid,
dg.locked, dg.name as debtor_group_name, u.username, a.number
dg.locked, dg.name as debtor_group_name, u.username, a.number
FROM patient AS p JOIN project AS proj JOIN debtor AS d JOIN debtor_group AS dg JOIN user AS u JOIN account AS a
ON p.debtor_uuid = d.uuid AND d.group_uuid = dg.uuid AND p.project_id = proj.id AND p.user_id = u.id AND a.id = dg.account_id
WHERE p.debtor_uuid = ?;
Expand Down
20 changes: 20 additions & 0 deletions server/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const q = require('q');

/** The query string conditions builder */
module.exports.queryCondition = queryCondition;
module.exports.parseQueryStringToSQL = parseQueryStringToSQL;
module.exports.take = take;
module.exports.isTrueString = isTrueString;
module.exports.isFalsy = isFalsy;
Expand Down Expand Up @@ -68,6 +69,25 @@ function queryCondition(sql, params, excludeWhere, dateConditon) {
return { query: sql, conditions : conditions };
}

// prefix is a string
function parseQueryStringToSQL(options, tablePrefix) {
let conditions = {
statements: [],
parameters: []
};

tablePrefix = tablePrefix || '';

let escapedKeys = _.mapKeys(options, (value, key) => tablePrefix.concat('.', key));

_.forEach(escapedKeys, (value, key) => {
conditions.statements.push(`${key} = ?`);
conditions.parameters.push(value);
});

return conditions;
}


/**
* @function take
Expand Down
2 changes: 1 addition & 1 deletion server/models/test/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ INSERT INTO `posting_journal` VALUES
(HUID(UUID()),1,1,16,'TRANS4','2016-01-09 17:04:27',@second_voucher,'description x',3627,200.0000,0.0000,200.0000,0.0000,2,NULL,NULL,NULL,'Sample voucher data two',1,2,1,NULL),
(HUID(UUID()),1,1,16,'TRANS4','2016-01-09 17:04:27',@second_voucher,'description x',3628,0.0000,200.0000,0.0000,200.0000,2,NULL,NULL,NULL,'Sample voucher data two',1,2,1,NULL),
(HUID(UUID()),1,1,16,'TRANS5','2016-01-09 17:04:27',@third_voucher,'description x',3627,300.0000,0.0000,300.0000,0.0000,2,NULL,'D',NULL,'Sample voucher data three',1,2,1,NULL),
(HUID(UUID()),1,1,16,'TRANS5','2016-02-09 17:04:27',@third_voucher,'description x',3628,0.0000,300.0000,0.0000,300.0000,2,NULL,NULL,NULL,'Sample voucher data three',1,2,1,NULL);
(HUID(UUID()),1,1,16,'TRANS5','2016-02-09 17:04:27',@third_voucher,'unique',3628,0.0000,300.0000,0.0000,300.0000,2,NULL,NULL,NULL,'Sample voucher data three',1,2,1,NULL);

-- zones des santes SNIS
INSERT INTO `mod_snis_zs` VALUES
Expand Down
Loading

0 comments on commit 49f88fd

Please sign in to comment.