diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index f675ea17..3c71cdb1 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "8ead1433-9679-46de-9baa-d27d59106673", + "_postman_id": "8ead1433-9679-46de-9baa-d27d59106673" "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -17231,7 +17231,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"cancelled\"\r\n}", + "raw": "{\r\n \"status\": \"cancelled\",\r\n \"days\":1,\r\n \"amount\":2,\r\n \"memberRate\":1,\r\n \"customerRate\":3,\r\n \"billingAccountId\": 23\r\n}", "options": { "raw": { "language": "json" @@ -17277,7 +17277,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"cancelled\"\r\n}", + "raw": "{\r\n \"status\": \"cancelled\",\r\n \"days\":1,\r\n \"amount\":2,\r\n \"memberRate\":1,\r\n \"customerRate\":3,\r\n \"billingAccountId\": 23\r\n}", "options": { "raw": { "language": "json" @@ -17325,7 +17325,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"cancelled\"\r\n}", + "raw": "{\r\n \"status\": \"cancelled\",\r\n \"days\":1,\r\n \"amount\":2,\r\n \"memberRate\":1,\r\n \"customerRate\":3,\r\n \"billingAccountId\": 23\r\n}", "options": { "raw": { "language": "json" @@ -17373,7 +17373,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"cancelled\"\r\n}", + "raw": "{\r\n \"status\": \"cancelled\",\r\n \"days\":1,\r\n \"amount\":2,\r\n \"memberRate\":1,\r\n \"customerRate\":3,\r\n \"billingAccountId\": 23\r\n}", "options": { "raw": { "language": "json" @@ -17421,7 +17421,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"cancelled\"\r\n}", + "raw": "{\r\n \"status\": \"cancelled\",\r\n \"days\":1,\r\n \"amount\":2,\r\n \"memberRate\":1,\r\n \"customerRate\":3,\r\n \"billingAccountId\": 23\r\n}", "options": { "raw": { "language": "json" @@ -17469,7 +17469,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"cancelled\"\r\n}", + "raw": "{\r\n \"status\": \"cancelled\",\r\n \"days\":1,\r\n \"amount\":2,\r\n \"memberRate\":1,\r\n \"customerRate\":3,\r\n \"billingAccountId\": 23\r\n}", "options": { "raw": { "language": "json" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 55f22d0c..143756d7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -4908,6 +4908,30 @@ components: type: string enum: ["scheduled", "cancelled"] description: "The payment status." + memberRate: + type: integer + format: float + example: 13 + description: "The member rate." + customerRate: + type: integer + format: float + example: 13 + description: "The customer rate." + billingAccountId: + type: integer + example: 80000071 + description: "the billing account id for payments" + days: + type: integer + minimum: 0 + example: 3 + description: "The workdays to pay" + amount: + type: integer + format: float + example: 2 + description: "The amount to be paid." CheckRun: type: object properties: diff --git a/src/services/WorkPeriodPaymentService.js b/src/services/WorkPeriodPaymentService.js index 6e78d8e0..ea003f1f 100644 --- a/src/services/WorkPeriodPaymentService.js +++ b/src/services/WorkPeriodPaymentService.js @@ -48,6 +48,38 @@ async function _createSingleWorkPeriodPayment (workPeriodPayment, createdBy) { return _createSingleWorkPeriodPaymentWithWorkPeriodAndResourceBooking(workPeriodPayment, createdBy, correspondingWorkPeriod.toJSON(), correspondingResourceBooking.toJSON()) } +/** + * update challenge + * @param {String} challengeId the challenge id + * @param {Object} data the challenge update data + * @returns {undefined} + */ +async function _updateChallenge (challengeId, data) { + const body = {} + if (data.billingAccountId) { + body.billing = { + billingAccountId: _.toString(data.billingAccountId), + markup: 0 // for TaaS payments we always use 0 markup + } + } + if (data.amount) { + body.prizeSets = [{ + type: 'placement', + prizes: [{ type: 'USD', value: data.amount }] + }] + } + + if (data.billingAccountId || data.amount) { + try { + await helper.updateChallenge(challengeId, body) + logger.debug({ component: 'WorkPeriodPaymentService', context: 'updateChallenge', message: `Challenge with id ${challengeId} is updated` }) + } catch (err) { + logger.error({ component: 'WorkPeriodPaymentService', context: 'updateChallenge', message: err.response.text }) + throw new errors.BadRequestError(`Cannot update the the challenge: ${err.response.text}`) + } + } +} + /** * Create single workPeriodPayment * @param {Object} workPeriodPayment the workPeriodPayment to be created @@ -220,7 +252,16 @@ async function updateWorkPeriodPayment (currentUser, id, data) { _checkUserPermissionForCRUWorkPeriodPayment(currentUser) const workPeriodPayment = await WorkPeriodPayment.findById(id) + const oldValue = workPeriodPayment.toJSON() + + if (oldValue.status === 'in-progress') { + const keys = _.keys(_.pick(data, ['amount', 'days', 'memberRate', 'customerRate', 'billingAccountId'])) + if (keys.length) { + throw new errors.BadRequestError(`${JSON.stringify(keys)} cannot be updated when workPeriodPayment status is in-progress`) + } + } + if (data.status === 'cancelled' && oldValue.status === 'in-progress') { throw new errors.BadRequestError('You cannot cancel a WorkPeriodPayment which is in-progress') } @@ -235,6 +276,20 @@ async function updateWorkPeriodPayment (currentUser, id, data) { throw new errors.BadRequestError('There is no available daysWorked to schedule a payment') } } + + if (data.days) { + const correspondingWorkPeriod = await helper.ensureWorkPeriodById(workPeriodPayment.workPeriodId) // ensure work period exists + const maxPossibleDays = correspondingWorkPeriod.daysWorked - correspondingWorkPeriod.daysPaid - oldValue.days + if (data.days > maxPossibleDays) { + throw new errors.BadRequestError(`Days cannot be more than not paid days which is ${maxPossibleDays}`) + } + } + + // challengeId exist and skip dummy challenge + if (oldValue.challengeId && oldValue.challengeId !== '00000000-0000-0000-0000-000000000000') { + await _updateChallenge(workPeriodPayment.challengeId, data) + } + data.updatedBy = await helper.getUserId(currentUser.userId) const updated = await workPeriodPayment.update(data) await helper.postEvent(config.TAAS_WORK_PERIOD_PAYMENT_UPDATE_TOPIC, updated.toJSON(), { oldValue: oldValue, key: `workPeriodPayment.billingAccountId:${updated.billingAccountId}` }) @@ -256,7 +311,12 @@ partiallyUpdateWorkPeriodPayment.schema = Joi.object().keys({ currentUser: Joi.object().required(), id: Joi.string().uuid().required(), data: Joi.object().keys({ - status: Joi.workPeriodPaymentUpdateStatus() + status: Joi.workPeriodPaymentUpdateStatus(), + amount: Joi.number().min(0), + days: Joi.number().integer(), + memberRate: Joi.number().positive(), + customerRate: Joi.number().positive().allow(null), + billingAccountId: Joi.number().positive().integer() }).min(1).required() }).required()