Skip to content

Commit 5e31b5b

Browse files
author
Vikas Agarwal
committed
feat: git#636-Need details of billing account for the project for the purpose of invoicing
1 parent 83bab6c commit 5e31b5b

File tree

8 files changed

+104
-4
lines changed

8 files changed

+104
-4
lines changed

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ export const M2M_SCOPES = {
276276
WRITE: 'write:projects',
277277
READ_USER_BILLING_ACCOUNTS: 'read:user-billing-accounts',
278278
WRITE_PROJECTS_BILLING_ACCOUNTS: 'write:projects-billing-accounts',
279+
READ_PROJECT_BILLING_ACCOUNT_DETAILS: 'read:project-billing-account-details',
279280
},
280281
PROJECT_MEMBERS: {
281282
ALL: 'all:project-members',

src/permissions/constants.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,14 @@ const SCOPES_PROJECTS_WRITE = [
9696
*/
9797
const SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS = [
9898
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
99-
M2M_SCOPES.READ_USER_BILLING_ACCOUNTS,
99+
M2M_SCOPES.PROJECTS.READ_USER_BILLING_ACCOUNTS,
100+
];
101+
102+
/**
103+
* M2M scopes to "read" available Billing Accounts for the project
104+
*/
105+
const SCOPES_PROJECTS_READ_BILLING_ACCOUNT_DETAILS = [
106+
M2M_SCOPES.PROJECTS.READ_PROJECT_BILLING_ACCOUNT_DETAILS,
100107
];
101108

102109
/**
@@ -277,6 +284,18 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
277284
scopes: SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS,
278285
},
279286

287+
/*
288+
* Project Invite
289+
*/
290+
READ_PROJECT_BILLING_ACCOUNT_DETAILS: {
291+
meta: {
292+
title: 'Read details of billing accounts - only allowed to m2m calls',
293+
group: 'Project Billing Accounts',
294+
description: 'Who can view the details of the Billing Account attached to the project',
295+
},
296+
scopes: SCOPES_PROJECTS_READ_BILLING_ACCOUNT_DETAILS,
297+
},
298+
280299
/*
281300
* Project Member
282301
*/

src/permissions/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ module.exports = () => {
2424
PERMISSION.READ_AVL_PROJECT_BILLING_ACCOUNTS,
2525
]));
2626

27+
Authorizer.setPolicy('projectBillingAccount.view', generalPermission([
28+
PERMISSION.READ_PROJECT_BILLING_ACCOUNT_DETAILS,
29+
]));
30+
2731
Authorizer.setPolicy('projectMember.create', generalPermission([
2832
PERMISSION.CREATE_PROJECT_MEMBER_OWN,
2933
PERMISSION.CREATE_PROJECT_MEMBER_NOT_OWN,

src/routes/billingAccounts/get.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import _ from 'lodash';
2+
import validate from 'express-validation';
3+
import Joi from 'joi';
4+
import { middleware as tcMiddleware } from 'tc-core-library-js';
5+
import SalesforceService from '../../services/salesforceService';
6+
import models from '../../models';
7+
8+
/**
9+
* API to get project attachments.
10+
*
11+
*/
12+
13+
const permissions = tcMiddleware.permissions;
14+
15+
const schema = {
16+
params: {
17+
projectId: Joi.number().integer().positive().required(),
18+
},
19+
};
20+
21+
module.exports = [
22+
validate(schema),
23+
permissions('projectBillingAccount.view'),
24+
async (req, res, next) => {
25+
const projectId = _.parseInt(req.params.projectId);
26+
try {
27+
const project = await models.Project.findOne({
28+
where: { id: projectId },
29+
attributes: ['id', 'billingAccountId'],
30+
raw: true,
31+
});
32+
const billingAccountId = project.billingAccountId;
33+
if (!billingAccountId) {
34+
const err = new Error('Billing Account not found');
35+
err.status = 404;
36+
throw err;
37+
}
38+
const { accessToken, instanceUrl } = await SalesforceService.authenticate();
39+
// eslint-disable-next-line
40+
const sql = `SELECT TopCoder_Billing_Account_Id__c, Mark_Up__c from Topcoder_Billing_Account__c tba where TopCoder_Billing_Account_Id__c='${billingAccountId}'`;
41+
req.log.debug(sql);
42+
const billingAccount = await SalesforceService.queryBillingAccount(sql, accessToken, instanceUrl, req.log);
43+
res.json(billingAccount);
44+
} catch (error) {
45+
req.log.error(error);
46+
next(error);
47+
}
48+
},
49+
];

src/routes/billingAccounts/list.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = [
2929
const sql = `SELECT Topcoder_Billing_Account__r.id, Topcoder_Billing_Account__r.TopCoder_Billing_Account_Id__c, Topcoder_Billing_Account__r.Billing_Account_Name__c, Topcoder_Billing_Account__r.Start_Date__c, Topcoder_Billing_Account__r.End_Date__c from Topcoder_Billing_Account_Resource__c tbar where UserID__c='${userId}'`;
3030
// and Topcoder_Billing_Account__r.TC_Connect_Project_ID__c='${projectId}'
3131
req.log.debug(sql);
32-
const billingAccounts = await SalesforceService.query(sql, accessToken, instanceUrl, req.log);
32+
const billingAccounts = await SalesforceService.queryUserBillingAccounts(sql, accessToken, instanceUrl, req.log);
3333
res.json(billingAccounts);
3434
} catch (error) {
3535
req.log.error(error);

src/routes/billingAccounts/list.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('Project Billing Accounts list', () => {
7171
accessToken: 'mock',
7272
instanceUrl: 'mock_url',
7373
}));
74-
salesforceQuery = sinon.stub(SalesforceService, 'query', () => Promise.resolve(billingAccountsData));
74+
salesforceQuery = sinon.stub(SalesforceService, 'queryUserBillingAccounts', () => Promise.resolve(billingAccountsData));
7575
done();
7676
});
7777
});

src/routes/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ router.route('/v5/projects/:projectId(\\d+)/scopeChangeRequests/:requestId(\\d+)
123123

124124
router.route('/v5/projects/:projectId(\\d+)/billingAccounts')
125125
.get(require('./billingAccounts/list'));
126+
router.route('/v5/projects/:projectId(\\d+)/billingAccount')
127+
.get(require('./billingAccounts/get'));
126128

127129
router.route('/v5/projects/:projectId(\\d+)/members')
128130
.get(require('./projectMembers/list'))

src/services/salesforceService.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class SalesforceService {
5454
* @param {Object} logger logger to be used for logging
5555
* @returns {{totalSize: Number, done: Boolean, records: Array}} the result
5656
*/
57-
static query(sql, accessToken, instanceUrl, logger) {
57+
static queryUserBillingAccounts(sql, accessToken, instanceUrl, logger) {
5858
return axios({
5959
url: `${instanceUrl}/services/data/v37.0/query?q=${sql}`,
6060
method: 'get',
@@ -77,6 +77,31 @@ class SalesforceService {
7777
return billingAccounts;
7878
});
7979
}
80+
81+
/**
82+
* Run the query statement
83+
* @param {String} sql the Saleforce sql statement
84+
* @param {String} accessToken the access token
85+
* @param {String} instanceUrl the salesforce instance url
86+
* @param {Object} logger logger to be used for logging
87+
* @returns {{totalSize: Number, done: Boolean, records: Array}} the result
88+
*/
89+
static queryBillingAccount(sql, accessToken, instanceUrl, logger) {
90+
return axios({
91+
url: `${instanceUrl}/services/data/v37.0/query?q=${sql}`,
92+
method: 'get',
93+
headers: { authorization: `Bearer ${accessToken}` },
94+
}).then((res) => {
95+
if (logger) {
96+
logger.debug(_.get(res, 'data.records', []));
97+
}
98+
const billingAccounts = _.get(res, 'data.records', []).map(o => ({
99+
tcBillingAccountId: _.get(o, 'TopCoder_Billing_Account_Id__c'),
100+
markup: _.get(o, 'Mark_Up__c'),
101+
}));
102+
return billingAccounts.length > 0 ? billingAccounts[0] : {};
103+
});
104+
}
80105
}
81106

82107
export default SalesforceService;

0 commit comments

Comments
 (0)