From 72c995ca3639e7376357e1a1dd7aa9868a307932 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Sun, 1 Aug 2021 17:36:19 +0300 Subject: [PATCH 1/2] endpoints for create/update WPP in bulk --- ...coder-bookings-api.postman_collection.json | 207 +++++++++++++++++- docs/swagger.yaml | 136 +++++++++++- .../WorkPeriodPaymentController.js | 20 ++ src/routes/WorkPeriodPaymentRoutes.js | 14 ++ src/services/WorkPeriodPaymentService.js | 122 +++++++---- test/unit/WorkPeriodPaymentService.test.js | 2 +- 6 files changed, 455 insertions(+), 46 deletions(-) diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index ce9b9b17..9aec690c 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": "7954a27f-3833-404f-9e55-6016a938c86e", "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -15940,8 +15940,12 @@ " pm.response.to.have.status(200);\r", " if(pm.response.status === \"OK\"){\r", " const response = pm.response.json()\r", - " pm.environment.set(\"workPeriodPaymentId-2\", response[0].id);\r", - " pm.environment.set(\"workPeriodPaymentId-3\", response[1].id);\r", + " if (response[0].id) {\r", + " pm.environment.set(\"workPeriodPaymentId-2\", response[0].id);\r", + " }\r", + " if (response[1].id) {\r", + " pm.environment.set(\"workPeriodPaymentId-3\", response[1].id);\r", + " }\r", " }\r", "});" ], @@ -15968,12 +15972,13 @@ } }, "url": { - "raw": "{{URL}}/work-period-payments", + "raw": "{{URL}}/work-period-payments/bulk", "host": [ "{{URL}}" ], "path": [ - "work-period-payments" + "work-period-payments", + "bulk" ] } }, @@ -16498,7 +16503,7 @@ "response": [] }, { - "name": "create work period payment with invalid days 2 Copy", + "name": "create work period payment with invalid days 3", "event": [ { "listen": "test", @@ -17680,6 +17685,196 @@ } }, "response": [] + }, + { + "name": "patch work period payment in bulk", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "body": { + "mode": "raw", + "raw": "[\r\n {\r\n \"id\": \"{{workPeriodPaymentId}}\",\r\n \"status\": \"cancelled\",\r\n \"days\": 5,\r\n \"amount\": 10,\r\n \"memberRate\": 2,\r\n \"customerRate\": null,\r\n \"billingAccountId\": 44\r\n },\r\n {\r\n \"id\": \"{{workPeriodPaymentId-2}}\",\r\n \"status\": \"scheduled\"\r\n },\r\n {\r\n \"id\": \"{{workPeriodPaymentId-3}}\",\r\n \"days\": 5,\r\n \"amount\": 10,\r\n \"memberRate\": 2,\r\n \"customerRate\": 5,\r\n \"billingAccountId\": 44\r\n },\r\n {\r\n \"id\": \"{{workPeriodPaymentIdCreatedByM2M}}\",\r\n \"days\": 3\r\n }\r\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-period-payments/bulk", + "host": [ + "{{URL}}" + ], + "path": [ + "work-period-payments", + "bulk" + ] + } + }, + "response": [] + }, + { + "name": "patch work period payment in bulk invalid parameters - 1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () {\r", + " pm.response.to.have.status(400);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayments[0].status\\\" must be one of [scheduled, cancelled]\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "body": { + "mode": "raw", + "raw": "[\r\n {\r\n \"id\": \"{{workPeriodPaymentId}}\",\r\n \"status\": \"completed\"\r\n }\r\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-period-payments/bulk", + "host": [ + "{{URL}}" + ], + "path": [ + "work-period-payments", + "bulk" + ] + } + }, + "response": [] + }, + { + "name": "patch work period payment in bulk invalid parameters - 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () {\r", + " pm.response.to.have.status(400);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayments[0].days\\\" must be less than or equal to 10\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "body": { + "mode": "raw", + "raw": "[\r\n {\r\n \"id\": \"{{workPeriodPaymentId}}\",\r\n \"days\": 11\r\n }\r\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-period-payments/bulk", + "host": [ + "{{URL}}" + ], + "path": [ + "work-period-payments", + "bulk" + ] + } + }, + "response": [] + }, + { + "name": "patch work period payment in bulk invalid parameters - 3", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () {\r", + " pm.response.to.have.status(400);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayments[0].amount\\\" must be greater than 0\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "body": { + "mode": "raw", + "raw": "[\r\n {\r\n \"id\": \"{{workPeriodPaymentId}}\",\r\n \"amount\": 0\r\n }\r\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-period-payments/bulk", + "host": [ + "{{URL}}" + ], + "path": [ + "work-period-payments", + "bulk" + ] + } + }, + "response": [] } ] }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 143756d7..241135ed 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2573,6 +2573,107 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /work-period-payments/bulk: + post: + tags: + - WorkPeriodPayments + description: | + Create Work Period Payments in Bulk. + + **Authorization** Topcoder token with write Work period payment scope is allowed + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/WorkPeriodPaymentCreateRequestBody" + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + oneOf: + - $ref: "#/components/schemas/WorkPeriodPayment" + - $ref: "#/components/schemas/WorkPeriodPaymentCreatedError" + "400": + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + patch: + tags: + - WorkPeriodPayments + description: | + Partial Update work period payments in bulk. + + **Authorization** Topcoder token with update work period payment scope is allowed + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/WorkPeriodPaymentPatchRequestBodyInBulk" + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + oneOf: + - $ref: "#/components/schemas/WorkPeriodPayment" + - $ref: "#/components/schemas/WorkPeriodPaymentUpdatedError" + "400": + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /taas-teams: get: tags: @@ -4833,10 +4934,15 @@ components: description: "The work period id." days: type: integer - minimum: 1 - maximum: 5 + minimum: 0 + maximum: 10 example: 2 description: "The workDays to be paid." + amount: + type: integer + minimum: 1 + example: 200 + description: "The amount to be paid. Required only if days value is 0, otherwise forbidden." WorkPeriodPaymentQueryCreateRequestBody: properties: status: @@ -4932,6 +5038,32 @@ components: format: float example: 2 description: "The amount to be paid." + WorkPeriodPaymentPatchRequestBodyInBulk: + allOf: + - type: object + required: + - id + properties: + id: + type: string + format: uuid + description: "The work period payment id." + - $ref: "#/components/schemas/WorkPeriodPaymentPatchRequestBody" + WorkPeriodPaymentUpdatedError: + allOf: + - $ref: "#/components/schemas/WorkPeriodPaymentPatchRequestBodyInBulk" + - type: object + properties: + error: + type: object + properties: + message: + type: string + description: "The error message" + code: + type: integer + example: 429 + description: "HTTP code of error" CheckRun: type: object properties: diff --git a/src/controllers/WorkPeriodPaymentController.js b/src/controllers/WorkPeriodPaymentController.js index 3ac5c2fe..1fc44f26 100644 --- a/src/controllers/WorkPeriodPaymentController.js +++ b/src/controllers/WorkPeriodPaymentController.js @@ -22,6 +22,24 @@ async function createWorkPeriodPayment (req, res) { res.send(await service.createWorkPeriodPayment(req.authUser, req.body)) } +/** + * Create workPeriodPayments in bulk + * @param req the request + * @param res the response + */ +async function createBulkOfWorkPeriodPayments (req, res) { + res.send(await service.createBulkOfWorkPeriodPayments(req.authUser, req.body)) +} + +/** + * Update workPeriodPayments in bulk + * @param req the request + * @param res the response + */ +async function updateBulkOfWorkPeriodPayments (req, res) { + res.send(await service.updateBulkOfWorkPeriodPayments(req.authUser, req.body)) +} + /** * Partially update workPeriodPayment by id * @param req the request @@ -54,6 +72,8 @@ async function createQueryWorkPeriodPayments (req, res) { module.exports = { getWorkPeriodPayment, createWorkPeriodPayment, + createBulkOfWorkPeriodPayments, + updateBulkOfWorkPeriodPayments, createQueryWorkPeriodPayments, partiallyUpdateWorkPeriodPayment, searchWorkPeriodPayments diff --git a/src/routes/WorkPeriodPaymentRoutes.js b/src/routes/WorkPeriodPaymentRoutes.js index 7ddd2bc5..06377a4d 100644 --- a/src/routes/WorkPeriodPaymentRoutes.js +++ b/src/routes/WorkPeriodPaymentRoutes.js @@ -18,6 +18,20 @@ module.exports = { scopes: [constants.Scopes.READ_WORK_PERIOD_PAYMENT, constants.Scopes.ALL_WORK_PERIOD_PAYMENT] } }, + '/work-period-payments/bulk': { + post: { + controller: 'WorkPeriodPaymentController', + method: 'createBulkOfWorkPeriodPayments', + auth: 'jwt', + scopes: [constants.Scopes.CREATE_WORK_PERIOD_PAYMENT, constants.Scopes.ALL_WORK_PERIOD_PAYMENT] + }, + patch: { + controller: 'WorkPeriodPaymentController', + method: 'updateBulkOfWorkPeriodPayments', + auth: 'jwt', + scopes: [constants.Scopes.UPDATE_WORK_PERIOD_PAYMENT, constants.Scopes.ALL_WORK_PERIOD_PAYMENT] + } + }, '/work-period-payments/query': { post: { controller: 'WorkPeriodPaymentController', diff --git a/src/services/WorkPeriodPaymentService.js b/src/services/WorkPeriodPaymentService.js index 6e5b8b36..ac0fbe19 100644 --- a/src/services/WorkPeriodPaymentService.js +++ b/src/services/WorkPeriodPaymentService.js @@ -13,7 +13,7 @@ const helper = require('../common/helper') const logger = require('../common/logger') const errors = require('../common/errors') const models = require('../models') -const { WorkPeriodPaymentStatus } = require('../../app-constants') +const { WorkPeriodPaymentStatus, ActiveWorkPeriodPaymentStatuses } = require('../../app-constants') const { searchResourceBookings } = require('./ResourceBookingService') const WorkPeriodPayment = models.WorkPeriodPayment @@ -200,20 +200,7 @@ async function createWorkPeriodPayment (currentUser, workPeriodPayment) { _checkUserPermissionForCRUWorkPeriodPayment(currentUser) const createdBy = await helper.getUserId(currentUser.userId) - if (_.isArray(workPeriodPayment)) { - const result = [] - for (const wp of workPeriodPayment) { - try { - const successResult = await _createSingleWorkPeriodPayment(wp, createdBy) - result.push(successResult) - } catch (e) { - result.push(_.extend(_.pick(wp, 'workPeriodId'), { error: { message: e.message, code: e.httpStatus } })) - } - } - return result - } else { - return await _createSingleWorkPeriodPayment(workPeriodPayment, createdBy) - } + return await _createSingleWorkPeriodPayment(workPeriodPayment, createdBy) } const singleCreateWorkPeriodPaymentSchema = Joi.object().keys({ @@ -226,26 +213,48 @@ const singleCreateWorkPeriodPaymentSchema = Joi.object().keys({ }), otherwise: Joi.forbidden() }) -}) +}).required() + createWorkPeriodPayment.schema = Joi.object().keys({ currentUser: Joi.object().required(), - workPeriodPayment: Joi.alternatives().try( - singleCreateWorkPeriodPaymentSchema.required(), - Joi.array().min(1).items(singleCreateWorkPeriodPaymentSchema).required() - ).required() + workPeriodPayment: singleCreateWorkPeriodPaymentSchema +}) + +/** + * Create workPeriodPayments in bulk + * @param {Object} currentUser the user who perform this operation + * @param {Array} workPeriodPayments the workPeriodPayment to be created + * @returns {Array} the created workPeriodPayments + */ +async function createBulkOfWorkPeriodPayments (currentUser, workPeriodPayments) { + // check permission + _checkUserPermissionForCRUWorkPeriodPayment(currentUser) + const createdBy = await helper.getUserId(currentUser.userId) + + const result = [] + for (const wp of workPeriodPayments) { + try { + const successResult = await _createSingleWorkPeriodPayment(wp, createdBy) + result.push(successResult) + } catch (e) { + result.push(_.extend(_.pick(wp, 'workPeriodId'), { error: { message: e.message, code: e.httpStatus } })) + } + } + return result +} + +createBulkOfWorkPeriodPayments.schema = Joi.object().keys({ + currentUser: Joi.object().required(), + workPeriodPayments: Joi.array().min(1).items(singleCreateWorkPeriodPaymentSchema).required() }).required() /** * Update workPeriodPayment - * @param {Object} currentUser the user who perform this operation * @param {String} id the workPeriod id * @param {Object} data the data to be updated * @returns {Object} the updated workPeriodPayment */ -async function updateWorkPeriodPayment (currentUser, id, data) { - // check permission - _checkUserPermissionForCRUWorkPeriodPayment(currentUser) - +async function updateWorkPeriodPayment (id, data) { const workPeriodPayment = await WorkPeriodPayment.findById(id) const oldValue = workPeriodPayment.toJSON() @@ -274,9 +283,11 @@ async function updateWorkPeriodPayment (currentUser, id, data) { if (data.days) { const correspondingWorkPeriod = await helper.ensureWorkPeriodById(workPeriodPayment.workPeriodId) // ensure work period exists - const maxPossibleDays = correspondingWorkPeriod.daysWorked - (correspondingWorkPeriod.daysPaid - oldValue.days) + const maxPossibleDays = correspondingWorkPeriod.daysWorked - (correspondingWorkPeriod.daysPaid - + (_.includes(ActiveWorkPeriodPaymentStatuses, oldValue.status) ? oldValue.days : 0)) if (data.days > maxPossibleDays) { - throw new errors.BadRequestError(`Cannot update days paid to more than ${maxPossibleDays}, otherwise total paid days (${correspondingWorkPeriod.daysPaid - oldValue.days}) would be more that total worked days (${correspondingWorkPeriod.daysWorked}) for the week.`) + throw new errors.BadRequestError(`Cannot update days paid to more than ${maxPossibleDays}, otherwise total paid days (${correspondingWorkPeriod.daysPaid - + (_.includes(ActiveWorkPeriodPaymentStatuses, oldValue.status) ? oldValue.days : 0)}) would be more that total worked days (${correspondingWorkPeriod.daysWorked}) for the week.`) } } @@ -285,7 +296,6 @@ async function updateWorkPeriodPayment (currentUser, id, data) { 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}` }) return updated.dataValues @@ -299,20 +309,56 @@ async function updateWorkPeriodPayment (currentUser, id, data) { * @returns {Object} the updated workPeriodPayment */ async function partiallyUpdateWorkPeriodPayment (currentUser, id, data) { - return updateWorkPeriodPayment(currentUser, id, data) + // check permission + _checkUserPermissionForCRUWorkPeriodPayment(currentUser) + data.updatedBy = await helper.getUserId(currentUser.userId) + return updateWorkPeriodPayment(id, data) } +const updateWorkPeriodPaymentSchema = Joi.object().keys({ + status: Joi.workPeriodPaymentUpdateStatus(), + amount: Joi.number().greater(0), + days: Joi.number().integer().min(0).max(10), + memberRate: Joi.number().positive(), + customerRate: Joi.number().positive().allow(null), + billingAccountId: Joi.number().positive().integer() +}).min(1).required() + partiallyUpdateWorkPeriodPayment.schema = Joi.object().keys({ currentUser: Joi.object().required(), id: Joi.string().uuid().required(), - data: Joi.object().keys({ - status: Joi.workPeriodPaymentUpdateStatus(), - amount: Joi.number().greater(0), - days: Joi.number().integer().min(0).max(10), - memberRate: Joi.number().positive(), - customerRate: Joi.number().positive().allow(null), - billingAccountId: Joi.number().positive().integer() - }).min(1).required() + data: updateWorkPeriodPaymentSchema +}).required() + +/** + * Partially update workPeriodPayment in bulk + * @param {Object} currentUser the user who perform this operation + * @param {Array} workPeriodPayments the workPeriodPayments data to be updated + * @returns {Array} the updated workPeriodPayment + */ +async function updateBulkOfWorkPeriodPayments (currentUser, workPeriodPayments) { + // check permission + _checkUserPermissionForCRUWorkPeriodPayment(currentUser) + const updatedBy = await helper.getUserId(currentUser.userId) + const result = [] + for (const wpp of workPeriodPayments) { + try { + const successResult = await updateWorkPeriodPayment(wpp.id, _.assign(_.omit(wpp, 'id'), { updatedBy })) + result.push(successResult) + } catch (e) { + result.push(_.assign(wpp, { error: { message: e.message, code: e.httpStatus } })) + } + } + return result +} + +updateBulkOfWorkPeriodPayments.schema = Joi.object().keys({ + currentUser: Joi.object().required(), + workPeriodPayments: Joi.array().min(1).items( + updateWorkPeriodPaymentSchema.keys({ + id: Joi.string().uuid().required() + }).min(2).required() + ).required() }).required() /** @@ -524,7 +570,9 @@ createQueryWorkPeriodPayments.schema = Joi.object().keys({ module.exports = { getWorkPeriodPayment, createWorkPeriodPayment, + createBulkOfWorkPeriodPayments, createQueryWorkPeriodPayments, partiallyUpdateWorkPeriodPayment, + updateBulkOfWorkPeriodPayments, searchWorkPeriodPayments } diff --git a/test/unit/WorkPeriodPaymentService.test.js b/test/unit/WorkPeriodPaymentService.test.js index 0b5a8aca..b860bd22 100644 --- a/test/unit/WorkPeriodPaymentService.test.js +++ b/test/unit/WorkPeriodPaymentService.test.js @@ -7,7 +7,7 @@ const commonData = require('./common/CommonData') const testData = require('./common/WorkPeriodPaymentData') const helper = require('../../src/common/helper') const busApiClient = helper.getBusApiClient() -describe('workPeriod service test', () => { +describe('workPeriodPayment service test', () => { beforeEach(() => { sinon.stub(busApiClient, 'postEvent').callsFake(async () => {}) }) From 9a85ee2fa87f0ace3eacbd834bc663a098b3924f Mon Sep 17 00:00:00 2001 From: eisbilir Date: Mon, 2 Aug 2021 10:52:52 +0300 Subject: [PATCH 2/2] fix: swagger request for bulk update --- docs/swagger.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 241135ed..65d420f1 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2638,7 +2638,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/WorkPeriodPaymentPatchRequestBodyInBulk" + type: array + items: + $ref: "#/components/schemas/WorkPeriodPaymentPatchRequestBodyInBulk" responses: "200": description: OK