Skip to content

Commit

Permalink
Bugfixes and multi-contract per org investigation.
Browse files Browse the repository at this point in the history
  • Loading branch information
jlabusch committed Jul 23, 2018
1 parent c88f6c9 commit df75dd0
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 79 deletions.
10 changes: 7 additions & 3 deletions api/lib/data_store.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var util = require('./util'),
sqlite3 = require('sqlite3').verbose();

const DEBUG = true;
const DEBUG = false;

'use strict';

Expand Down Expand Up @@ -83,10 +83,14 @@ let sql = {
dump: {
contracts: util.trim `
SELECT c.id as contract_id,c.org_id,c.start_date,c.end_date,
cs.system_id,
b.id as budget_id,b.base_hours,b.base_hours_spent,b.sla_quote_hours,b.additional_hours
cs.system_id
FROM contracts c
LEFT JOIN contract_system_link cs ON cs.contract_id=c.id
`,
budgets: util.trim `
SELECT c.id as contract_id,
b.id as budget_id,b.base_hours,b.base_hours_spent,b.sla_quote_hours,b.additional_hours
FROM contracts c
LEFT JOIN contract_budget_link cb ON cb.contract_id=c.id
LEFT JOIN budgets b ON b.id=cb.budget_id
`,
Expand Down
8 changes: 4 additions & 4 deletions api/lib/data_sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ function process_wrms_data(resolve, reject, contract, wr_rows){
timesheet_buckets = {},
timesheet_budgets = {};

// We only reach this point if there are no live quotes.
// We only reach this point if there are no live quotes; add up the timesheets.
for (; iw < wr_rows.length && wr_rows[iw].request_id === wr.request_id; ++iw){

let iw_hours = wr_rows[iw].timesheet_hours,
Expand Down Expand Up @@ -335,8 +335,8 @@ function process_wrms_data(resolve, reject, contract, wr_rows){
try{
await sqlite_promise(
store.dbs.syncing,
'UPDATE budgets SET base_hours_spent=? WHERE id=?',
b.base_hours_spent + n,
'UPDATE budgets SET base_hours_spent=base_hours_spent + ? WHERE id=?',
n,
b.id
);
}catch(err){
Expand Down Expand Up @@ -451,7 +451,7 @@ function select_best_budget(contract, period){
store.dbs.syncing.all(
// TODO FIXME: figure out the syntax of prepared statement with placeholder
// containing % and using ESCAPE clause. Right now this is an easy sqli vector.
`SELECT id,base_hours,base_hours_spent,sla_quote_hours,additional_hours FROM budgets WHERE id LIKE '${contract.name} %'`,
`SELECT id,base_hours,base_hours_spent,sla_quote_hours,additional_hours FROM budgets WHERE id LIKE '${contract.name.replace(/'/, '')} %'`,
(err, budgets) => {
if (err){
reject(err);
Expand Down
7 changes: 2 additions & 5 deletions api/lib/data_sync_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,10 @@ function match_non_monthly_budget_name(id, ctx){
let from = new Date(m[1]),
to = new Date(m[2]);

// force "to" to be the end of the month
to.setMonth(to.getMonth()+1);
to.setDate(-1);

let pdate = new Date(ctx.period);

return pdate > from && pdate < to;
// Budget names are [start,end)
return pdate >= from && pdate < to;
}
return false;
}
Expand Down
24 changes: 17 additions & 7 deletions api/lib/espo.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,28 +161,38 @@ function query_espo_accounts(context){
// type: "Service Level Agreement"
function merge_espo_data(context){
let active = {};

util.log_debug(__filename, '==================== Raw contracts: ', DEBUG);
context.contracts.list.forEach(c => {
util.log_debug(__filename, JSON.stringify(c), DEBUG);
if (c.status === 'Active' && c.type === 'Service Level Agreement'){
active[c.accountId] = {
if (!active[c.accountId]){
active[c.accountId] = [];
}
active[c.accountId].push({
name: c.name,
org_name: c.accountName,
type: (c.sLAFrequency || 'unknown').toLowerCase().trim(),
hours: (c.sLAHours || 0),
start_date: util.date_fmt(new Date(c.startDate)),
end_date: util.date_fmt(new Date(c.endrenewalDate)),
systems: (c.systemID ? c.systemID.split(/,\s*/).map(n => parseInt(n)).filter(n => n !== null && !isNaN(n)) : [])
};
});
}
});

let result = [];

util.log_debug(__filename, '==================== Raw accounts: ', DEBUG);
context.accounts.list.forEach(a => {
util.log_debug(__filename, JSON.stringify(a), DEBUG);
if (active[a.id]){
active[a.id].org_id = a.orgID;
context.accounts.list.forEach(arr => {
util.log_debug(__filename, JSON.stringify(arr), DEBUG);
if (active[arr.id]){
active[arr.id].forEach(a => {
a.org_id = arr.orgID;
result.push(a);
});
}
});
return Object.values(active);
return Object.values(result);
}

3 changes: 2 additions & 1 deletion api/lib/get_customer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ module.exports = function(req, res, next, ctx){
FROM contracts c
JOIN contract_system_link cs ON c.id=cs.contract_id
WHERE c.org_id=?
AND cs.system_id IN (${ctx.sys.join(',')})`,
AND cs.system_id IN (${ctx.sys.join(',')})
GROUP BY c.id`,
ctx.org,
handler(data => {
if (!Array.isArray(data) || data.length < 1){
Expand Down
8 changes: 6 additions & 2 deletions api/lib/get_customer_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ module.exports = function(req, res, next, ctx){
store.query(
util.trim `SELECT c.id,
c.org_name,
c.org_id
c.org_id,
s.system_id
FROM contracts c
JOIN contract_system_link s ON c.id=s.contract_id
ORDER BY c.org_name,c.id`,
handler(data => {
if (!Array.isArray(data)){
Expand All @@ -18,7 +20,9 @@ module.exports = function(req, res, next, ctx){
let r = {};

data.forEach(row => {
r[row.id] = row.org_id;
let o = r[row.id] || {org_id: row.org_id, systems: []};
o.systems.push(row.system_id);
r[row.id] = o;
})

return r;
Expand Down
3 changes: 2 additions & 1 deletion api/lib/get_sla_hours.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module.exports = function(req, res, next, ctx){
JOIN contracts c ON c.id=cb.contract_id
JOIN contract_system_link cs ON cs.contract_id=c.id
WHERE c.org_id=?
AND cs.system_id IN (${ctx.sys.join(',')})`,
AND cs.system_id IN (${ctx.sys.join(',')})
GROUP BY b.id`,
ctx.org,
handler(data => {
if (!Array.isArray(data) || data.length < 1){
Expand Down
2 changes: 2 additions & 0 deletions api/lib/get_sla_unquoted.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ module.exports = function(req, res, next, ctx){
handler(data => {
let r = {result: [{wr: "None", result: 0}]};

util.log_debug(__filename, 'raw data: ' + JSON.stringify(data, null, 2));

if (Array.isArray(data) && data.length > 0){
// Compress the list to one element per WR
let wrs = {};
Expand Down
4 changes: 3 additions & 1 deletion api/lib/get_users.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ module.exports = query.prepare(
next(r);
},
(key, ctx, next, error) => {
if (!util.get_org(ctx) ||
// TODO: decide where to fetch tokens from now that the config no longer exists.
if (true ||
!util.get_org(ctx) ||
!util.get_org(ctx).users ||
!util.get_org(ctx).users.hostname ||
!util.get_org(ctx).users.token)
Expand Down
71 changes: 22 additions & 49 deletions api/lib/org_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,44 @@ var util = require('./util');

var orgs = {};

const DEBUG = false;

exports.__raw = function(){ return orgs }

exports.add_org = function(contract){
util.log_debug(__filename, `add_org(${JSON.stringify(contract)})`);
util.log_debug(__filename, `add_org(${JSON.stringify(contract)})`, DEBUG);

orgs[contract.name] = JSON.parse(JSON.stringify(contract));
}

exports.add_system = function(contract, system){
util.log_debug(__filename, `add_system(${JSON.stringify(contract)}, ${system})`);
util.log_debug(__filename, `add_system(${JSON.stringify(contract)}, ${system})`, DEBUG);

let o = orgs[contract.name];

if (o){
let s = o.systems || [];
s.push(system);
orgs[contract.name].systems = s;
if (!s.includes(system)){
s.push(system);
}
o.systems = s;
}else{
throw new Error(`add_system(${system}) called before add_org(${contract.name})`);
}
}

function get_org_by_key(field, val){
// Succeed if either argument is null, or if the arrays match exactly.
function systems_match(a, b){
return !Array.isArray(a) ||
!Array.isArray(b) ||
a.sort().join(',') === b.sort().join(',');
}

function get_org_by_key(field, val, systems){
let o = null;

Object.values(orgs).forEach(org => {
if (org[field] === val){
if (org[field] === val && systems_match(systems, org.systems)){
o = org;
}
});
Expand All @@ -45,8 +56,6 @@ function get_org_by_key(field, val){
//
// Returns contracts.* or null
exports.get_org = function(id){
util.log_debug(__filename, 'get_org(' + JSON.stringify({id:id}) + ')');

if (id === undefined){
throw new Error('get_org() with no ID specified');
}
Expand All @@ -55,53 +64,17 @@ exports.get_org = function(id){
n = parseInt(id); // in case it's a bare number

if (id.org || !isNaN(n)){
o = get_org_by_key('org_id', id.org || n);
// Numeric lookups may need to be disambiguated by system
o = get_org_by_key('org_id', id.org || n, id.systems);
}else{
// Name lookups are already unique... Unless someone has messed up in the CRM,
// in which case we'll just return the first match
o = get_org_by_key('name', id);
}

return o;
}

util.log_debug(__filename, 'get_org(' + JSON.stringify({id:id}) + ') => ' + JSON.stringify(o), DEBUG);

/*
var config = require('config'),
fs = require('fs');
var __orgs = (function(cfg){
let o = JSON.parse(JSON.stringify(cfg));
Object.keys(o).forEach(name => {
o[name].name = name;
o[ o[name].id ] = o[name];
});
return o;
})(config.get('orgs'));
exports.get_org = function(id){
if (id === undefined){
return null;
}
return __orgs[id.org ? id.org : id];
}

exports.get_all_orgs = function(){
// not using __orgs because it doubles up ID and name keys
let o = JSON.parse(JSON.stringify(config.get('orgs')));
return Object.keys(o).map(name => {
return o[name].id;
});
}

exports.get_all_systems = function(){
let arr = [];
// not using __orgs because it doubles up ID and name keys
let o = JSON.parse(JSON.stringify(config.get('orgs')));
Object.keys(o).forEach(name => {
let s = o[name].default_system;
if (s && s.match(/^[0-9,]+$/)){
arr = arr.concat(s.split(/,/));
}
});
return arr;
}
*/
2 changes: 2 additions & 0 deletions frontend/proxy/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from . import views

# TODO: to support multiple active contracts per client, the "dashboard" and "api" URLs need
# to match (?P<systems>[0-9,]+) between <client> and <month>.
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^dashboard/(?P<client>[_a-z0-9A-Z ]+)/$', views.DashboardView.as_view(), name='dashboard'),
Expand Down
3 changes: 1 addition & 2 deletions frontend/proxy/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ def get(self, request, item, client, month):

if not request.user.is_superuser:
# If not admin, cannot view any months earlier than July 2017.
# TODO: fix this properly with a database for the client SLA
# dates instead of hard coding
# TODO: respect the SLA dates in the CRM instead.
if month_dt < min_dt:
month = min_dt.strftime("%Y-%m")

Expand Down
15 changes: 11 additions & 4 deletions frontend/static/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ URI_EXT = '__vendor/default/2017-7';

var tile_exists = {};

function mkuri(org){
// TODO: if we need to support multiple active contracts per client, then
// after making the django proxy changes, insert org.systems.join(',')
// instead of "default" here:
return org.org_id + "/default/" + PERIOD;
}

query('/customer_list', function(err, data){
if (err){
console.log('customer_list: ' + err);
Expand All @@ -15,7 +22,7 @@ query('/customer_list', function(err, data){
}
}

const org = {name: name, org_id: data[name]};
const org = {name: name, org_id: data[name].org_id, systems: data[name].systems};

if (!tile_exists[name]){
draw_tile(org, i, 'blue');
Expand All @@ -26,7 +33,7 @@ query('/customer_list', function(err, data){
handle_hours(org, i, after_fetches),
undefined,
0,
org.org_id + "/default/" + PERIOD
mkuri(org)
);
});
});
Expand Down Expand Up @@ -64,7 +71,7 @@ function handle_hours(org, i, next){
handle_count(org, i, color, next),
undefined,
0,
org.org_id + "/default/" + PERIOD
mkuri(org)
);
};
}
Expand Down Expand Up @@ -93,7 +100,7 @@ function draw_tile(org, i, color, count){
'<a href="/dashboard/' + org.org_id + '/">' +
'<div class="index-tile ' + color + '">' +
'<span class="tile-count">' + count + '</span>' +
'<span class="tile-name">' + org.name + '</span>' +
'<span class="tile-name">' + org.name.replace(/ SLA /, ' ').replace(/\d\d\d\d\s?-\s?/, '') + '</span>' +
'</div>' +
'</a>'
);
Expand Down

0 comments on commit df75dd0

Please sign in to comment.