diff --git a/config/default.js b/config/default.js index e23770d8..4727e58e 100644 --- a/config/default.js +++ b/config/default.js @@ -92,6 +92,7 @@ module.exports = { UPDATE: process.env.SCOPE_CHALLENGES_UPDATE || "update:challenges", DELETE: process.env.SCOPE_CHALLENGES_DELETE || "delete:challenges", ALL: process.env.SCOPE_CHALLENGES_ALL || "all:challenges", + PAYMENT: process.env.SCOPE_PAYMENT || "create:payments", }, DEFAULT_CONFIDENTIALITY_TYPE: process.env.DEFAULT_CONFIDENTIALITY_TYPE || "public", @@ -129,7 +130,8 @@ module.exports = { GRPC_CHALLENGE_SERVER_HOST: process.env.GRPC_DOMAIN_CHALLENGE_SERVER_HOST || "localhost", GRPC_CHALLENGE_SERVER_PORT: process.env.GRPC_DOMAIN_CHALLENGE_SERVER_PORT || 8888, GRPC_ACL_SERVER_HOST: process.env.GRPC_ACL_SERVER_HOST || "localhost", - GRPC_ACL_SERVER_PORT: process.env.GRPC_ACL_SERVER_PORT || 8889, + GRPC_ACL_SERVER_PORT: process.env.GRPC_ACL_SERVER_PORT || 40020, - SKIP_PROJECT_ID_BY_TIMLINE_TEMPLATE_ID: process.env.SKIP_PROJECT_ID_BY_TIMLINE_TEMPLATE_ID || '517e76b0-8824-4e72-9b48-a1ebde1793a8' + SKIP_PROJECT_ID_BY_TIMLINE_TEMPLATE_ID: + process.env.SKIP_PROJECT_ID_BY_TIMLINE_TEMPLATE_ID || "517e76b0-8824-4e72-9b48-a1ebde1793a8", }; diff --git a/src/controllers/ChallengeController.js b/src/controllers/ChallengeController.js index 38768dc1..2d726578 100644 --- a/src/controllers/ChallengeController.js +++ b/src/controllers/ChallengeController.js @@ -104,6 +104,26 @@ async function updateChallenge(req, res) { res.send(result); } +/** + * Update Legacy Payout (Updates informixoltp:payment_detail) + * This has no effect other than to keep DW in sync for looker with + * Updates that happen in Wallet + */ +async function updateLegacyPayout(req, res) { + logger.debug( + `updateLegacyPayout User: ${JSON.stringify(req.authUser)} - ChallengeID: ${ + req.params.challengeId + } - Body: ${JSON.stringify(req.body)}` + ); + const result = await service.updateLegacyPayout( + req, + req.authUser, + req.params.challengeId, + req.body + ); + res.send(result); +} + /** * Delete challenge * @param {Object} req the request @@ -152,6 +172,7 @@ module.exports = { createChallenge, getChallenge, updateChallenge, + updateLegacyPayout, deleteChallenge, getChallengeStatistics, sendNotifications, diff --git a/src/routes.js b/src/routes.js index 8d46e69d..8adb7d2a 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,7 +4,7 @@ const constants = require("../app-constants"); const { - SCOPES: { READ, CREATE, UPDATE, DELETE, ALL }, + SCOPES: { PAYMENT, READ, CREATE, UPDATE, DELETE, ALL }, } = require("config"); module.exports = { @@ -112,6 +112,14 @@ module.exports = { scopes: [UPDATE, ALL], }, }, + "/challenges/:challengeId/legacy-payment": { + patch: { + controller: "ChallengeController", + method: "updateLegacyPayout", + auth: "jwt", + scopes: [PAYMENT], + }, + }, "/challenges/:challengeId/statistics": { get: { controller: "ChallengeController", diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 46a30f7c..d0d9ef24 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -2172,6 +2172,13 @@ updateChallenge.schema = { ) .optional(), overview: Joi.any().forbidden(), + v5Payout: Joi.object().keys({ + userId: Joi.number().integer().positive().required(), + amount: Joi.number().allow(null), + status: Joi.string().allow(null), + datePaid: Joi.string().allow(null), + releaseDate: Joi.string().allow(null), + }), }) .unknown(true) .required(), @@ -2498,6 +2505,132 @@ async function indexChallengeAndPostToKafka(updatedChallenge, track, type) { }); } +async function updateLegacyPayout(currentUser, challengeId, data) { + const challenge = await challengeDomain.lookup(getLookupCriteria("id", challengeId)); + const { v5Payout } = data; + + // SQL qurey to fetch the payment and payment_detail record + let sql = `SELECT * FROM informixoltp:payment p + INNER JOIN informixoltp:payment_detail pd ON p.most_recent_detail_id = pd.payment_detail_id + WHERE p.user_id = ${v5Payout.userId} AND`; + + if (challenge.legacyId != null) { + sql += ` pd.component_project_id = ${challenge.legacyId}`; + } else { + sql += ` pd.jira_issue_id = \'${challengeId}\'`; + } + + sql += " ORDER BY pd.payment_detail_id ASC"; + + console.log("Fetch legacy payment detail: ", sql); + + const result = await aclQueryDomain.rawQuery({ sql }); + let updateClauses = [`date_modified = current`]; + + const statusMap = { + Paid: 53, + OnHold: 55, + OnHoldAdmin: 55, + Owed: 56, + Cancelled: 65, + EnteredIntoPaymentSystem: 70, + }; + + if (v5Payout.status != null) { + updateClauses.push(`payment_status_id = ${statusMap[v5Payout.status]}`); + if (v5Payout.status === "Paid") { + updateClauses.push(`date_paid = '${v5Payout.datePaid}'`); + } else { + updateClauses.push("date_paid = null"); + } + } + + if (v5Payout.releaseDate != null) { + updateClauses.push(`date_due = '${v5Payout.releaseDate}'`); + } + + const paymentDetailIds = result.rows.map( + (row) => row.fields.find((field) => field.key === "payment_detail_id").value + ); + + if (v5Payout.amount != null) { + updateClauses.push(`total_amount = ${v5Payout.amount}`); + if (paymentDetailIds.length === 1) { + updateClauses.push(`net_amount = ${v5Payout.amount}`); + updateClauses.push(`gross_amount = ${v5Payout.amount}`); + } + } + + if (paymentDetailIds.length === 0) { + return { + success: false, + message: "No payment detail record found", + }; + } + + const whereClause = [`payment_detail_id IN (${paymentDetailIds.join(",")})`]; + + const updateQuery = `UPDATE informixoltp:payment_detail SET ${updateClauses.join( + ", " + )} WHERE ${whereClause.join(" AND ")}`; + + console.log("Update Clauses", updateClauses); + console.log("Update Query", updateQuery); + + await aclQueryDomain.rawQuery({ sql: updateQuery }); + + if (v5Payout.amount != null) { + if (paymentDetailIds.length > 1) { + const amountInCents = v5Payout.amount * 100; + + const split1Cents = Math.round(amountInCents * 0.75); + const split2Cents = amountInCents - split1Cents; + + const split1Dollars = Number((split1Cents / 100).toFixed(2)); + const split2Dollars = Number((split2Cents / 100).toFixed(2)); + + const paymentUpdateQueries = paymentDetailIds.map((paymentDetailId, index) => { + let amt = 0; + if (index === 0) { + amt = split1Dollars; + } + if (index === 1) { + amt = split2Dollars; + } + + return `UPDATE informixoltp:payment_detail SET date_modified = CURRENT, net_amount = ${amt}, gross_amount = ${amt} WHERE payment_detail_id = ${paymentDetailId}`; + }); + + console.log("Payment Update Queries", paymentUpdateQueries); + + await Promise.all( + paymentUpdateQueries.map((query) => aclQueryDomain.rawQuery({ sql: query })) + ); + } + } + + return { + success: true, + message: "Successfully updated legacy payout", + }; +} +updateLegacyPayout.schema = { + currentUser: Joi.any(), + challengeId: Joi.id(), + data: Joi.object() + .keys({ + v5Payout: Joi.object().keys({ + userId: Joi.number().integer().positive().required(), + amount: Joi.number().allow(null), + status: Joi.string().allow(null), + datePaid: Joi.string().allow(null), + releaseDate: Joi.string().allow(null), + }), + }) + .unknown(true) + .required(), +}; + /** * Get SRM Schedule * @param {Object} criteria the criteria @@ -2562,6 +2695,7 @@ module.exports = { getChallenge, updateChallenge, deleteChallenge, + updateLegacyPayout, getChallengeStatistics, sendNotifications, advancePhase,