From 71ae9ab9a5a37af4b1d1721c140935f8ea06f396 Mon Sep 17 00:00:00 2001 From: xxcxy Date: Wed, 2 Jun 2021 19:25:48 +0800 Subject: [PATCH] Payments - Batch Endpoints --- app-constants.js | 9 +- config/default.js | 3 +- data/demo-data.json | 36 +- ...coder-bookings-api.postman_collection.json | 320 ++++++++++++++---- docs/swagger.yaml | 174 +++++++++- ...-26-work-period-payment-table-migration.js | 38 +++ src/bootstrap.js | 19 +- .../WorkPeriodPaymentController.js | 13 +- src/models/WorkPeriodPayment.js | 10 +- src/routes/WorkPeriodPaymentRoutes.js | 8 + src/services/InterviewService.js | 4 +- src/services/ResourceBookingService.js | 1 - src/services/WorkPeriodPaymentService.js | 197 ++++++++--- test/unit/ResourceBookingService.test.js | 4 + test/unit/WorkPeriodPaymentService.test.js | 23 +- test/unit/common/WorkPeriodPaymentData.js | 5 +- 16 files changed, 672 insertions(+), 192 deletions(-) create mode 100644 migrations/2021-05-26-work-period-payment-table-migration.js diff --git a/app-constants.js b/app-constants.js index 534e46de..7433499a 100644 --- a/app-constants.js +++ b/app-constants.js @@ -76,9 +76,10 @@ const ChallengeStatus = { COMPLETED: 'Completed' } -const PaymentProcessingSwitch = { - ON: 'ON', - OFF: 'OFF' +const WorkPeriodPaymentStatus = { + COMPLETED: 'completed', + CANCELLED: 'cancelled', + SCHEDULED: 'scheduled' } module.exports = { @@ -87,5 +88,5 @@ module.exports = { Scopes, Interviews, ChallengeStatus, - PaymentProcessingSwitch + WorkPeriodPaymentStatus } diff --git a/config/default.js b/config/default.js index 2b5ca7ba..9b4d6e81 100644 --- a/config/default.js +++ b/config/default.js @@ -160,7 +160,6 @@ module.exports = { ROLE_ID_SUBMITTER: process.env.ROLE_ID_SUBMITTER || '732339e7-8e30-49d7-9198-cccf9451e221', TYPE_ID_TASK: process.env.TYPE_ID_TASK || 'ecd58c69-238f-43a4-a4bb-d172719b9f31', DEFAULT_TIMELINE_TEMPLATE_ID: process.env.DEFAULT_TIMELINE_TEMPLATE_ID || '53a307ce-b4b3-4d6f-b9a1-3741a58f77e6', - DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825', + DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825' - PAYMENT_PROCESSING_SWITCH: process.env.PAYMENT_PROCESSING_SWITCH || 'OFF' } diff --git a/data/demo-data.json b/data/demo-data.json index e0733443..e7fbe36e 100644 --- a/data/demo-data.json +++ b/data/demo-data.json @@ -182,12 +182,12 @@ { "id": "077aa2ca-5b60-4ad9-a965-1b37e08a5046", "jobCandidateId": "881a19de-2b0c-4bb9-b36a-4cb5e223bdb5", - "googleCalendarId": null, + "calendarEventId": null, "customMessage": null, - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 1, "startTimestamp": null, - "attendeesList": null, + "guestEmails": null, "status": "Completed", "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", "updatedBy": null, @@ -211,12 +211,12 @@ { "id": "b1f7ba76-640f-47e2-9463-59e51b51ec60", "jobCandidateId": "827ee401-df04-42e1-abbe-7b97ce7937ff", - "googleCalendarId": "dummyId", + "calendarEventId": "dummyId", "customMessage": "This is a custom message", - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 2, "startTimestamp": null, - "attendeesList": null, + "guestEmails": null, "status": "Scheduling", "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", "updatedBy": null, @@ -226,12 +226,12 @@ { "id": "3144fa65-ea1a-4bec-81b0-7cb1c8845826", "jobCandidateId": "827ee401-df04-42e1-abbe-7b97ce7937ff", - "googleCalendarId": null, + "calendarEventId": null, "customMessage": null, - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 1, "startTimestamp": null, - "attendeesList": null, + "guestEmails": null, "status": "Completed", "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", "updatedBy": null, @@ -255,12 +255,12 @@ { "id": "976d23a9-5710-453f-99d9-f57a588bb610", "jobCandidateId": "a4ea7bcf-5b99-4381-b99c-a9bd05d83a36", - "googleCalendarId": "dummyId", + "calendarEventId": "dummyId", "customMessage": "This is a custom message", - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 3, "startTimestamp": null, - "attendeesList": [ + "guestEmails": [ "attendee1@yopmail.com", "attendee2@yopmail.com" ], @@ -273,12 +273,12 @@ { "id": "a23e1bf2-1084-4cfe-a0d8-d83bc6fec655", "jobCandidateId": "a4ea7bcf-5b99-4381-b99c-a9bd05d83a36", - "googleCalendarId": "dummyId", + "calendarEventId": "dummyId", "customMessage": "This is a custom message", - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 2, "startTimestamp": null, - "attendeesList": [ + "guestEmails": [ "attendee1@yopmail.com", "attendee2@yopmail.com" ], @@ -291,12 +291,12 @@ { "id": "9efd72c3-1dc7-4ce2-9869-8cca81d0adeb", "jobCandidateId": "a4ea7bcf-5b99-4381-b99c-a9bd05d83a36", - "googleCalendarId": null, + "calendarEventId": null, "customMessage": null, - "xaiTemplate": "interview-30", + "templateUrl": "interview-30", "round": 1, "startTimestamp": null, - "attendeesList": null, + "guestEmails": null, "status": "Completed", "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", "updatedBy": null, diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index a0518c50..4c1e6fca 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "58b277bb-0d1d-4bbf-919f-c5951ba0e1c0", + "_postman_id": "2252a54a-8d60-4855-acd1-51138f7edc70", "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -14749,6 +14749,55 @@ }, "response": [] }, + { + "name": "create work period2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () {\r", + " pm.response.to.have.status(200);\r", + " if(pm.response.status === \"OK\"){\r", + " const response = pm.response.json()\r", + " pm.environment.set(\"workPeriodId2\", response.id);\r", + " }\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"resourceBookingId\": \"{{resourceBookingId}}\",\r\n \"startDate\": \"2021-03-14\",\r\n \"endDate\": \"2021-03-20\",\r\n \"daysWorked\": 2,\r\n \"memberRate\": 13.13,\r\n \"customerRate\": 13.13,\r\n \"paymentStatus\": \"pending\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-periods", + "host": [ + "{{URL}}" + ], + "path": [ + "work-periods" + ] + } + }, + "response": [] + }, { "name": "create work period with m2m", "event": [ @@ -14830,7 +14879,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -14850,7 +14899,7 @@ "response": [] }, { - "name": "create work period payment with m2m create", + "name": "create multiple work period payments with boooking manager", "event": [ { "listen": "test", @@ -14858,10 +14907,6 @@ "exec": [ "pm.test('Status code is 200', function () {\r", " pm.response.to.have.status(200);\r", - " if(pm.response.status === \"OK\"){\r", - " const response = pm.response.json()\r", - " pm.environment.set(\"workPeriodPaymentIdCreatedByM2M\", response.id);\r", - " }\r", "});" ], "type": "text/javascript" @@ -14874,12 +14919,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_m2m_create_work_period_payment}}" + "value": "Bearer {{token_bookingManager}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodIdCreatedByM2M}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "[{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600\r\n},{\r\n \"workPeriodId\": \"{{workPeriodId2}}\",\r\n \"amount\": 900\r\n}]", "options": { "raw": { "language": "json" @@ -14899,16 +14944,14 @@ "response": [] }, { - "name": "create work period payment with connect user", + "name": "create query work period payments with boooking manager", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test('Status code is 403', function () {\r", - " pm.response.to.have.status(403);\r", - " const response = pm.response.json()\r", - " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", + "pm.test('Status code is 200', function () {\r", + " pm.response.to.have.status(200);\r", "});" ], "type": "text/javascript" @@ -14921,12 +14964,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_connectUser}}" + "value": "Bearer {{token_bookingManager}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\"query\": { \"workPeriods.paymentStatus\": \"pending\" } }", "options": { "raw": { "language": "json" @@ -14934,28 +14977,31 @@ } }, "url": { - "raw": "{{URL}}/work-period-payments", + "raw": "{{URL}}/work-period-payments/query", "host": [ "{{URL}}" ], "path": [ - "work-period-payments" + "work-period-payments", + "query" ] } }, "response": [] }, { - "name": "create work period payment with member", + "name": "create work period payment with m2m create", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test('Status code is 403', function () {\r", - " pm.response.to.have.status(403);\r", - " const response = pm.response.json()\r", - " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", + "pm.test('Status code is 200', function () {\r", + " pm.response.to.have.status(200);\r", + " if(pm.response.status === \"OK\"){\r", + " const response = pm.response.json()\r", + " pm.environment.set(\"workPeriodPaymentIdCreatedByM2M\", response.id);\r", + " }\r", "});" ], "type": "text/javascript" @@ -14968,12 +15014,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_member}}" + "value": "Bearer {{token_m2m_create_work_period_payment}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodIdCreatedByM2M}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -14993,16 +15039,16 @@ "response": [] }, { - "name": "create work period payment with user id not exist", + "name": "create work period payment with connect user", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test('Status code is 400', function () {\r", - " pm.response.to.have.status(400);\r", + "pm.test('Status code is 403', function () {\r", + " pm.response.to.have.status(403);\r", " const response = pm.response.json()\r", - " pm.expect(response.message).to.eq(\"Bad Request\")\r", + " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", "});" ], "type": "text/javascript" @@ -15015,12 +15061,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_userId_not_exist}}" + "value": "Bearer {{token_connectUser}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -15040,16 +15086,16 @@ "response": [] }, { - "name": "create work period payment with invalid token", + "name": "create work period payment with member", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test('Status code is 401', function () {\r", - " pm.response.to.have.status(401);\r", + "pm.test('Status code is 403', function () {\r", + " pm.response.to.have.status(403);\r", " const response = pm.response.json()\r", - " pm.expect(response.message).to.eq(\"Invalid Token.\")\r", + " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", "});" ], "type": "text/javascript" @@ -15062,12 +15108,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer invalid_token" + "value": "Bearer {{token_member}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -15087,7 +15133,7 @@ "response": [] }, { - "name": "create work period payment with missing workPeriodId", + "name": "create work period payment with user id not exist", "event": [ { "listen": "test", @@ -15096,7 +15142,7 @@ "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(\"\\\"workPeriodPayment.workPeriodId\\\" is required\")\r", + " pm.expect(response.message).to.eq(\"Bad Request\")\r", "});" ], "type": "text/javascript" @@ -15109,12 +15155,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_bookingManager}}" + "value": "Bearer {{token_userId_not_exist}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -15134,16 +15180,16 @@ "response": [] }, { - "name": "create work period payment with invalid workPeriodId 1", + "name": "create work period payment with invalid token", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test('Status code is 400', function () {\r", - " pm.response.to.have.status(400);\r", + "pm.test('Status code is 401', function () {\r", + " pm.response.to.have.status(401);\r", " const response = pm.response.json()\r", - " pm.expect(response.message).to.eq(\"\\\"workPeriodPayment.workPeriodId\\\" must be a valid GUID\")\r", + " pm.expect(response.message).to.eq(\"Invalid Token.\")\r", "});" ], "type": "text/javascript" @@ -15156,12 +15202,12 @@ { "key": "Authorization", "type": "text", - "value": "Bearer {{token_bookingManager}}" + "value": "Bearer invalid_token" } ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"aaa-bb-c\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -15181,7 +15227,7 @@ "response": [] }, { - "name": "create work period payment with invalid workPeriodId 2", + "name": "create work period payment with missing workPeriodId", "event": [ { "listen": "test", @@ -15190,7 +15236,7 @@ "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(\"\\\"workPeriodPayment.workPeriodId\\\" must be a string\")\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayment.workPeriodId\\\" is required\")\r", "});" ], "type": "text/javascript" @@ -15208,7 +15254,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": 123,\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -15228,7 +15274,7 @@ "response": [] }, { - "name": "create work period payment with invalid amount 1", + "name": "create work period payment with invalid workPeriodId 1", "event": [ { "listen": "test", @@ -15237,7 +15283,7 @@ "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(\"\\\"workPeriodPayment.amount\\\" must be a number\")\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayment.workPeriodId\\\" must be a valid GUID\")\r", "});" ], "type": "text/javascript" @@ -15255,7 +15301,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": \"abc\",\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"aaa-bb-c\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -15275,7 +15321,7 @@ "response": [] }, { - "name": "create work period payment with invalid amount 2", + "name": "create work period payment with invalid workPeriodId 2", "event": [ { "listen": "test", @@ -15284,7 +15330,7 @@ "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(\"\\\"workPeriodPayment.amount\\\" must be greater than 0\")\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayment.workPeriodId\\\" must be a string\")\r", "});" ], "type": "text/javascript" @@ -15302,7 +15348,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 0,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": 123,\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -15322,7 +15368,7 @@ "response": [] }, { - "name": "create work period payment with invalid status 1", + "name": "create work period payment with invalid amount 1", "event": [ { "listen": "test", @@ -15331,7 +15377,7 @@ "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(\"\\\"workPeriodPayment.status\\\" must be one of [completed, cancelled]\")\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayment.amount\\\" must be a number\")\r", "});" ], "type": "text/javascript" @@ -15349,7 +15395,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 1200,\r\n \"status\": 123\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": \"abc\"\r\n}", "options": { "raw": { "language": "json" @@ -15369,7 +15415,7 @@ "response": [] }, { - "name": "create work period payment with invalid status 2", + "name": "create work period payment with invalid amount 2", "event": [ { "listen": "test", @@ -15378,7 +15424,7 @@ "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(\"\\\"workPeriodPayment.status\\\" must be one of [completed, cancelled]\")\r", + " pm.expect(response.message).to.eq(\"\\\"workPeriodPayment.amount\\\" must be greater than 0\")\r", "});" ], "type": "text/javascript" @@ -15396,7 +15442,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 1200,\r\n \"status\": \"invalid-status\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId}}\",\r\n \"amount\": 0\r\n}", "options": { "raw": { "language": "json" @@ -22198,7 +22244,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId_created_by_administrator}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId_created_by_administrator}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -22217,6 +22263,52 @@ }, "response": [] }, + { + "name": "✔ create multiple work period payment with administrator", + "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": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_administrator}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\"query\": { \"workPeriods.paymentStatus\": \"pending\" } }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-period-payments/query", + "host": [ + "{{URL}}" + ], + "path": [ + "work-period-payments", + "query" + ] + } + }, + "response": [] + }, { "name": "✔ get work period payment with administrator", "event": [ @@ -24431,7 +24523,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId_created_for_member}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId_created_for_member}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -24450,6 +24542,54 @@ }, "response": [] }, + { + "name": "✘ create query work period payment with member", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 403', function () {\r", + " pm.response.to.have.status(403);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_member_tester1234}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\"query\": { \"workPeriods.paymentStatus\": \"pending\" } }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-period-payments/query", + "host": [ + "{{URL}}" + ], + "path": [ + "work-period-payments", + "query" + ] + } + }, + "response": [] + }, { "name": "✘ get work period payment with member", "event": [ @@ -26664,7 +26804,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId_created_for_connect_manager}}\",\r\n \"amount\": 600,\r\n \"status\": \"completed\"\r\n}", + "raw": "{\r\n \"workPeriodId\": \"{{workPeriodId_created_for_connect_manager}}\",\r\n \"amount\": 600\r\n}", "options": { "raw": { "language": "json" @@ -26683,6 +26823,54 @@ }, "response": [] }, + { + "name": "✘ create query work period payment with connect manager", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 403', function () {\r", + " pm.response.to.have.status(403);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\"query\": { \"workPeriods.paymentStatus\": \"pending\" } }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/work-period-payments/query", + "host": [ + "{{URL}}" + ], + "path": [ + "work-period-payments", + "query" + ] + } + }, + "response": [] + }, { "name": "✘ get work period payment with connect manager", "event": [ @@ -26900,4 +27088,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a0b6064b..9382de52 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2280,14 +2280,24 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/WorkPeriodPaymentRequestBody" + oneOf: + - $ref: "#/components/schemas/WorkPeriodPaymentCreateRequestBody" + - type: array + items: + $ref: "#/components/schemas/WorkPeriodPaymentCreateRequestBody" responses: "200": description: OK content: application/json: schema: - $ref: "#/components/schemas/WorkPeriodPayment" + oneOf: + - $ref: "#/components/schemas/WorkPeriodPayment" + - type: array + items: + oneOf: + - $ref: "#/components/schemas/WorkPeriodPayment" + - $ref: "#/components/schemas/WorkPeriodPaymentCreatedError" "400": description: Bad request content: @@ -2380,7 +2390,7 @@ paths: required: false schema: type: string - enum: ["completed", "cancelled"] + enum: ["completed", "scheduled", "cancelled"] description: The payment status. responses: "200": @@ -2444,6 +2454,59 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /work-period-payments/query: + post: + tags: + - WorkPeriodPayments + description: | + Create Multiple Work Period Payments for all the pages at once. + + **Authorization** Topcoder token with write Work period payment scope is allowed + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/WorkPeriodPaymentQueryCreateRequestBody" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/WorkPeriodPaymentQueryCreateResult" + "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" + "404": + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /work-period-payments/{id}: get: tags: @@ -4211,7 +4274,7 @@ components: description: "The amount to be paid." status: type: string - enum: ["completed", "cancelled"] + enum: ["completed", "scheduled", "cancelled"] description: "The payment status." billingAccountId: type: integer @@ -4233,6 +4296,28 @@ components: type: string format: uuid description: "The user Id who updated the work period payment last time.(Will get the user info from the token)" + WorkPeriodPaymentCreatedError: + required: + - workPeriodId + properties: + workPeriodId: + type: string + format: uuid + description: "The work period id." + amount: + type: integer + example: 2 + description: "The amount to be paid." + error: + type: object + properties: + message: + type: string + description: "The error message" + code: + type: integer + example: 429 + description: "HTTP code of error" WorkPeriodPaymentRequestBody: required: - workPeriodId @@ -4247,8 +4332,85 @@ components: description: "The amount to be paid." status: type: string - enum: ["completed", "cancelled"] + enum: ["completed", "scheduled", "cancelled"] description: "The payment status." + WorkPeriodPaymentCreateRequestBody: + required: + - workPeriodId + properties: + workPeriodId: + type: string + format: uuid + description: "The work period id." + amount: + type: integer + example: 2 + description: "The amount to be paid." + WorkPeriodPaymentQueryCreateRequestBody: + properties: + status: + type: string + enum: ["placed", "in-progress", "completed"] + description: The resource booking status. + startDate: + type: string + format: date + description: The resource booking start date. + endDate: + type: string + format: date + description: The resource booking end date. + rateType: + type: string + enum: ["hourly", "daily", "weekly", "monthly"] + description: The resource booking rate type. + jobId: + type: string + format: uuid + description: The job id. + userId: + type: string + format: uuid + description: The user id. + projectId: + type: integer + description: The project id. + projectIds: + oneOf: + - type: string + description: comma separated project ids. + - type: array + items: + type: integer + workPeriods.paymentStatus: + type: string + enum: ["pending", "partially-completed", "completed", "cancelled"] + workPeriods.startDate: + type: string + format: date + pattern: '^\d{4}-\d{2}-\d{2}$' + description: The work period start date. + workPeriods.endDate: + type: string + format: date + pattern: '^\d{4}-\d{2}-\d{2}$' + description: The work period end date. + workPeriods.userHandle: + type: string + description: The user handle. + WorkPeriodPaymentQueryCreateResult: + properties: + total: + type: integer + description: The total Work Periods found. + totalSuccess: + type: integer + description: The total payments scheduled successfully. + totalError: + type: integer + description: The total payments which failed to get scheduled. + query: + $ref: "#/components/schemas/WorkPeriodPaymentQueryCreateRequestBody" WorkPeriodPaymentPatchRequestBody: properties: workPeriodId: @@ -4261,7 +4423,7 @@ components: description: "The amount to be paid." status: type: string - enum: ["completed", "cancelled"] + enum: ["completed", "scheduled", "cancelled"] description: "The payment status." CheckRun: type: object diff --git a/migrations/2021-05-26-work-period-payment-table-migration.js b/migrations/2021-05-26-work-period-payment-table-migration.js new file mode 100644 index 00000000..5ee8ef70 --- /dev/null +++ b/migrations/2021-05-26-work-period-payment-table-migration.js @@ -0,0 +1,38 @@ +'use strict'; +const config = require('config') + +/** + * Migrate work_period_payments challenge_id - from not null to allow null. + * enum_work_period_payments_status from completed, cancelled to completed, canceled, scheduled. + */ +module.exports = { + up: async (queryInterface, Sequelize) => { + const table = { tableName: 'work_period_payments', schema: config.DB_SCHEMA_NAME } + await Promise.all([ + queryInterface.changeColumn(table, 'challenge_id', { type: Sequelize.UUID }), + queryInterface.sequelize.query(`ALTER TYPE ${config.DB_SCHEMA_NAME}.enum_work_period_payments_status ADD VALUE 'scheduled'`) + ]) + }, + + down: async (queryInterface, Sequelize) => { + const table = { tableName: 'work_period_payments', schema: config.DB_SCHEMA_NAME } + await Promise.all([ + queryInterface.changeColumn(table, 'challenge_id', { type: Sequelize.UUID, allowNull: false }), + queryInterface.sequelize.query(` + DELETE + FROM + pg_enum + WHERE + enumlabel = 'scheduled' AND + enumtypid = ( + SELECT + oid + FROM + pg_type + WHERE + typname = 'enum_work_period_payments_status' + ) + `) + ]) + } +}; diff --git a/src/bootstrap.js b/src/bootstrap.js index 2999f131..9ca05431 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -2,10 +2,8 @@ const fs = require('fs') const Joi = require('joi') const path = require('path') const _ = require('lodash') -const { Interviews } = require('../app-constants') +const { Interviews, WorkPeriodPaymentStatus } = require('../app-constants') const logger = require('./common/logger') -const constants = require('../app-constants') -const config = require('config') const allowedInterviewStatuses = _.values(Interviews.Status) const allowedXAITemplate = _.keys(Interviews.XaiTemplate) @@ -16,12 +14,12 @@ Joi.rateType = () => Joi.string().valid('hourly', 'daily', 'weekly', 'monthly') Joi.jobStatus = () => Joi.string().valid('sourcing', 'in-review', 'assigned', 'closed', 'cancelled') Joi.resourceBookingStatus = () => Joi.string().valid('placed', 'closed', 'cancelled') Joi.workload = () => Joi.string().valid('full-time', 'fractional') -Joi.jobCandidateStatus = () => Joi.string().valid('open', 'placed', 'selected', 'client rejected - screening', 'client rejected - interview', 'rejected - other', 'cancelled', 'interview', 'topcoder-rejected', 'applied','rejected-pre-screen','skills-test','skills-test','phone-screen','job-closed') +Joi.jobCandidateStatus = () => Joi.string().valid('open', 'placed', 'selected', 'client rejected - screening', 'client rejected - interview', 'rejected - other', 'cancelled', 'interview', 'topcoder-rejected', 'applied', 'rejected-pre-screen', 'skills-test', 'skills-test', 'phone-screen', 'job-closed') Joi.title = () => Joi.string().max(128) Joi.paymentStatus = () => Joi.string().valid('pending', 'partially-completed', 'completed', 'cancelled') Joi.xaiTemplate = () => Joi.string().valid(...allowedXAITemplate) Joi.interviewStatus = () => Joi.string().valid(...allowedInterviewStatuses) -Joi.workPeriodPaymentStatus = () => Joi.string().valid('completed', 'cancelled') +Joi.workPeriodPaymentStatus = () => Joi.string().valid(..._.values(WorkPeriodPaymentStatus)) // Empty string is not allowed by Joi by default and must be enabled with allow(''). // See https://joi.dev/api/?v=17.3.0#string fro details why it's like this. // In many cases we would like to allow empty string to make it easier to create UI for editing data. @@ -45,14 +43,3 @@ function buildServices (dir) { } buildServices(path.join(__dirname, 'services')) - -// validate some configurable parameters for the app -const paymentProcessingSwitchSchema = Joi.string().label('PAYMENT_PROCESSING_SWITCH').valid( - ...Object.values(constants.PaymentProcessingSwitch) -) -try { - Joi.attempt(config.PAYMENT_PROCESSING_SWITCH, paymentProcessingSwitchSchema) -} catch (err) { - console.error(err.message) - process.exit(1) -} diff --git a/src/controllers/WorkPeriodPaymentController.js b/src/controllers/WorkPeriodPaymentController.js index 93f5c046..4bba2385 100644 --- a/src/controllers/WorkPeriodPaymentController.js +++ b/src/controllers/WorkPeriodPaymentController.js @@ -3,7 +3,6 @@ */ const service = require('../services/WorkPeriodPaymentService') const helper = require('../common/helper') -const config = require('config') /** * Get workPeriodPayment by id @@ -20,7 +19,7 @@ async function getWorkPeriodPayment (req, res) { * @param res the response */ async function createWorkPeriodPayment (req, res) { - res.send(await service.createWorkPeriodPayment(req.authUser, req.body, { paymentProcessingSwitch: config.PAYMENT_PROCESSING_SWITCH })) + res.send(await service.createWorkPeriodPayment(req.authUser, req.body)) } /** @@ -52,9 +51,19 @@ async function searchWorkPeriodPayments (req, res) { res.send(result.result) } +/** + * Create all query workPeriodPayments + * @param req the request + * @param res the response + */ +async function createQueryWorkPeriodPayments (req, res) { + res.send(await service.createQueryWorkPeriodPayments(req.authUser, req.body)) +} + module.exports = { getWorkPeriodPayment, createWorkPeriodPayment, + createQueryWorkPeriodPayments, partiallyUpdateWorkPeriodPayment, fullyUpdateWorkPeriodPayment, searchWorkPeriodPayments diff --git a/src/models/WorkPeriodPayment.js b/src/models/WorkPeriodPayment.js index 3683faf0..7db484ea 100644 --- a/src/models/WorkPeriodPayment.js +++ b/src/models/WorkPeriodPayment.js @@ -1,6 +1,8 @@ const { Sequelize, Model } = require('sequelize') +const _ = require('lodash') const config = require('config') const errors = require('../common/errors') +const { WorkPeriodPaymentStatus } = require('../../app-constants') module.exports = (sequelize) => { class WorkPeriodPayment extends Model { @@ -44,17 +46,13 @@ module.exports = (sequelize) => { }, challengeId: { field: 'challenge_id', - type: Sequelize.UUID, - allowNull: false + type: Sequelize.UUID }, amount: { type: Sequelize.DOUBLE }, status: { - type: Sequelize.ENUM( - 'completed', - 'cancelled' - ), + type: Sequelize.ENUM(_.values(WorkPeriodPaymentStatus)), allowNull: false }, billingAccountId: { diff --git a/src/routes/WorkPeriodPaymentRoutes.js b/src/routes/WorkPeriodPaymentRoutes.js index dcc284eb..3b6f6ba9 100644 --- a/src/routes/WorkPeriodPaymentRoutes.js +++ b/src/routes/WorkPeriodPaymentRoutes.js @@ -18,6 +18,14 @@ module.exports = { scopes: [constants.Scopes.READ_WORK_PERIOD_PAYMENT, constants.Scopes.ALL_WORK_PERIOD_PAYMENT] } }, + '/work-period-payments/query': { + post: { + controller: 'WorkPeriodPaymentController', + method: 'createQueryWorkPeriodPayments', + auth: 'jwt', + scopes: [constants.Scopes.CREATE_WORK_PERIOD_PAYMENT, constants.Scopes.ALL_WORK_PERIOD_PAYMENT] + } + }, '/work-period-payments/:id': { get: { controller: 'WorkPeriodPaymentController', diff --git a/src/services/InterviewService.js b/src/services/InterviewService.js index 10a065f4..a69a788c 100644 --- a/src/services/InterviewService.js +++ b/src/services/InterviewService.js @@ -241,8 +241,8 @@ async function requestInterview (currentUser, jobCandidateId, interview) { const guestMembers = await helper.getMemberDetailsByEmails(interview.guestEmails) interview.hostName = `${hostMembers[0].firstName} ${hostMembers[0].lastName}` interview.guestNames = _.map(interview.guestEmails, (guestEmail) => { - var foundGuestMember = _.find(guestMembers, function(guestMember) { return guestEmail == guestMember.email }); - return (foundGuestMember != undefined) ? `${foundGuestMember.firstName} ${foundGuestMember.lastName}` : guestEmail.split("@")[0] + var foundGuestMember = _.find(guestMembers, function (guestMember) { return guestEmail === guestMember.email }) + return (foundGuestMember !== undefined) ? `${foundGuestMember.firstName} ${foundGuestMember.lastName}` : guestEmail.split('@')[0] }) try { diff --git a/src/services/ResourceBookingService.js b/src/services/ResourceBookingService.js index f5c40206..f1758c89 100644 --- a/src/services/ResourceBookingService.js +++ b/src/services/ResourceBookingService.js @@ -472,7 +472,6 @@ async function searchResourceBookings (currentUser, criteria, options = { return criteria.sortOrder = 'desc' } try { - throw new Error('fallback to DB') const esQuery = { index: config.get('esConfig.ES_INDEX_RESOURCE_BOOKING'), _source_includes: queryOpt.include, diff --git a/src/services/WorkPeriodPaymentService.js b/src/services/WorkPeriodPaymentService.js index c196f88e..59f63720 100644 --- a/src/services/WorkPeriodPaymentService.js +++ b/src/services/WorkPeriodPaymentService.js @@ -3,18 +3,17 @@ */ const _ = require('lodash') -const Joi = require('joi') +const Joi = require('joi').extend(require('@joi/date')) const config = require('config') const HttpStatus = require('http-status-codes') const { Op } = require('sequelize') const uuid = require('uuid') -const moment = require('moment') const helper = require('../common/helper') const logger = require('../common/logger') const errors = require('../common/errors') -const constants = require('../../app-constants') const models = require('../models') -const PaymentService = require('./PaymentService') +const { WorkPeriodPaymentStatus } = require('../../app-constants') +const { searchResourceBookings } = require('./ResourceBookingService') const WorkPeriodPayment = models.WorkPeriodPayment const esClient = helper.getESClient() @@ -32,6 +31,75 @@ async function _checkUserPermissionForCRUWorkPeriodPayment (currentUser) { } } +/** + * Create single workPeriodPayment + * @param {Object} workPeriodPayment the workPeriodPayment to be created + * @param {String} createdBy the authUser id + * @returns {Object} the created workPeriodPayment + */ +async function _createSingleWorkPeriodPayment (workPeriodPayment, createdBy) { + const correspondingWorkPeriod = await helper.ensureWorkPeriodById(workPeriodPayment.workPeriodId) // ensure work period exists + + // get billingAccountId from corresponding resource booking + const correspondingResourceBooking = await helper.ensureResourceBookingById(correspondingWorkPeriod.resourceBookingId) + + return _createSingleWorkPeriodPaymentWithWorkPeriodAndResourceBooking(workPeriodPayment, createdBy, correspondingWorkPeriod, correspondingResourceBooking) +} + +/** + * Create single workPeriodPayment + * @param {Object} workPeriodPayment the workPeriodPayment to be created + * @param {String} createdBy the authUser id + * @param {Object} correspondingWorkPeriod the workPeriod + * @param {Object} correspondingResourceBooking the resourceBooking + * @returns {Object} the created workPeriodPayment + */ +async function _createSingleWorkPeriodPaymentWithWorkPeriodAndResourceBooking (workPeriodPayment, createdBy, correspondingWorkPeriod, correspondingResourceBooking) { + if (!correspondingResourceBooking.billingAccountId) { + throw new errors.ConflictError(`id: ${correspondingWorkPeriod.resourceBookingId} "ResourceBooking" Billing account is not assigned to the resource booking`) + } + workPeriodPayment.billingAccountId = correspondingResourceBooking.billingAccountId + workPeriodPayment.id = uuid.v4() + workPeriodPayment.status = WorkPeriodPaymentStatus.SCHEDULED + workPeriodPayment.createdBy = createdBy + + // set workPeriodPayment amount + if (_.isNil(workPeriodPayment.amount)) { + const memberRate = correspondingWorkPeriod.memberRate || correspondingResourceBooking.memberRate + if (_.isNil(memberRate)) { + throw new errors.BadRequestError(`Can't find a member rate in work period: ${workPeriodPayment.workPeriodId} to calculate the amount`) + } + let daysWorked = 0 + if (correspondingWorkPeriod.daysWorked) { + daysWorked = correspondingWorkPeriod.daysWorked + } else { + const matchDW = _.find(helper.extractWorkPeriods(correspondingResourceBooking.startDate, correspondingResourceBooking.endDate), { startDate: correspondingWorkPeriod.startDate }) + if (matchDW) { + daysWorked = matchDW.daysWorked + } + } + if (daysWorked === 0) { + workPeriodPayment.amount = 0 + } else { + workPeriodPayment.amount = _.round(memberRate * 5 / daysWorked, 2) + } + } + + let created = null + try { + created = await WorkPeriodPayment.create(workPeriodPayment) + } catch (err) { + if (!_.isUndefined(err.original)) { + throw new errors.BadRequestError(err.original.detail) + } else { + throw err + } + } + + await helper.postEvent(config.TAAS_WORK_PERIOD_PAYMENT_CREATE_TOPIC, created.toJSON()) + return created.dataValues +} + /** * Get workPeriodPayment by id * @param {Object} currentUser the user who perform this operation. @@ -101,58 +169,39 @@ getWorkPeriodPayment.schema = Joi.object().keys({ * Create workPeriodPayment * @param {Object} currentUser the user who perform this operation * @param {Object} workPeriodPayment the workPeriodPayment to be created - * @param {Object} options the extra options to control the function * @returns {Object} the created workPeriodPayment */ -async function createWorkPeriodPayment (currentUser, workPeriodPayment, options = { paymentProcessingSwitch: 'OFF' }) { +async function createWorkPeriodPayment (currentUser, workPeriodPayment) { // check permission await _checkUserPermissionForCRUWorkPeriodPayment(currentUser) + const createdBy = await helper.getUserId(currentUser.userId) - const { projectId, userHandle, endDate, resourceBookingId } = await helper.ensureWorkPeriodById(workPeriodPayment.workPeriodId) // ensure work period exists - - // get billingAccountId from corresponding resource booking - const correspondingResourceBooking = await helper.ensureResourceBookingById(resourceBookingId) - if (!correspondingResourceBooking.billingAccountId) { - throw new errors.ConflictError(`id: ${resourceBookingId} "ResourceBooking" Billing account is not assigned to the resource booking`) - } - workPeriodPayment.billingAccountId = correspondingResourceBooking.billingAccountId - - const paymentChallenge = options.paymentProcessingSwitch === constants.PaymentProcessingSwitch.ON ? (await PaymentService.createPayment({ - projectId, - userHandle, - amount: workPeriodPayment.amount, - name: `TaaS Payment - ${userHandle} - Week Ending ${moment(endDate).format('D/M/YYYY')}`, - description: `TaaS Payment - ${userHandle} - Week Ending ${moment(endDate).format('D/M/YYYY')}`, - billingAccountId: correspondingResourceBooking.billingAccountId - })) : ({ id: '00000000-0000-0000-0000-000000000000' }) - - workPeriodPayment.id = uuid.v4() - workPeriodPayment.challengeId = paymentChallenge.id - workPeriodPayment.createdBy = await helper.getUserId(currentUser.userId) - - let created = null - try { - created = await WorkPeriodPayment.create(workPeriodPayment) - } catch (err) { - if (!_.isUndefined(err.original)) { - throw new errors.BadRequestError(err.original.detail) - } else { - throw err + 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(wp, { error: { message: e.message, code: e.httpStatus } })) + } } + return result + } else { + return await _createSingleWorkPeriodPayment(workPeriodPayment, createdBy) } - - await helper.postEvent(config.TAAS_WORK_PERIOD_PAYMENT_CREATE_TOPIC, created.toJSON()) - return created.dataValues } +const singleCreateWorkPeriodPaymentSchema = Joi.object().keys({ + workPeriodId: Joi.string().uuid().required(), + amount: Joi.number().greater(0).allow(null) +}) createWorkPeriodPayment.schema = Joi.object().keys({ currentUser: Joi.object().required(), - workPeriodPayment: Joi.object().keys({ - workPeriodId: Joi.string().uuid().required(), - amount: Joi.number().greater(0).allow(null), - status: Joi.workPeriodPaymentStatus().default('completed') - }).required(), - options: Joi.object() + workPeriodPayment: Joi.alternatives().try( + singleCreateWorkPeriodPaymentSchema.required(), + Joi.array().min(1).items(singleCreateWorkPeriodPaymentSchema).required() + ).required() }).required() /** @@ -358,9 +407,67 @@ searchWorkPeriodPayments.schema = Joi.object().keys({ options: Joi.object() }).required() +/** + * Create all query workPeriodPayments + * @param {Object} currentUser the user who perform this operation. + * @param {Object} criteria the query criteria + * @returns {Object} the process result + */ +async function createQueryWorkPeriodPayments (currentUser, criteria) { + // check permission + await _checkUserPermissionForCRUWorkPeriodPayment(currentUser) + const createdBy = await helper.getUserId(currentUser.userId) + const query = criteria.query + + const fields = _.join(_.uniq(_.concat( + ['id', 'billingAccountId', 'memberRate', 'startDate', 'endDate', 'workPeriods.id', 'workPeriods.resourceBookingId', 'workPeriods.memberRate', 'workPeriods.daysWorked', 'workPeriods.startDate'], + _.map(_.keys(query), k => k === 'projectIds' ? 'projectId' : k)) + ), ',') + const searchResult = await searchResourceBookings(currentUser, _.extend({ fields, page: 1 }, query), { returnAll: true }) + + const wpArray = _.flatMap(searchResult.result, 'workPeriods') + const resourceBookingMap = _.fromPairs(_.map(searchResult.result, rb => [rb.id, rb])) + const result = { total: wpArray.length, query, totalSuccess: 0, totalError: 0 } + + for (const wp of wpArray) { + try { + await _createSingleWorkPeriodPaymentWithWorkPeriodAndResourceBooking({ workPeriodId: wp.id }, createdBy, wp, resourceBookingMap[wp.resourceBookingId]) + result.totalSuccess++ + } catch (err) { + logger.logFullError(err, { component: 'WorkPeriodPaymentService', context: 'createQueryWorkPeriodPayments' }) + result.totalError++ + } + } + return result +} + +createQueryWorkPeriodPayments.schema = Joi.object().keys({ + currentUser: Joi.object().required(), + criteria: Joi.object().keys({ + query: Joi.object().keys({ + status: Joi.resourceBookingStatus(), + startDate: Joi.date().format('YYYY-MM-DD'), + endDate: Joi.date().format('YYYY-MM-DD'), + rateType: Joi.rateType(), + jobId: Joi.string().uuid(), + userId: Joi.string().uuid(), + projectId: Joi.number().integer(), + projectIds: Joi.alternatives( + Joi.string(), + Joi.array().items(Joi.number().integer()) + ), + 'workPeriods.paymentStatus': Joi.paymentStatus(), + 'workPeriods.startDate': Joi.date().format('YYYY-MM-DD'), + 'workPeriods.endDate': Joi.date().format('YYYY-MM-DD'), + 'workPeriods.userHandle': Joi.string() + }).required() + }).required() +}).required() + module.exports = { getWorkPeriodPayment, createWorkPeriodPayment, + createQueryWorkPeriodPayments, partiallyUpdateWorkPeriodPayment, fullyUpdateWorkPeriodPayment, searchWorkPeriodPayments diff --git a/test/unit/ResourceBookingService.test.js b/test/unit/ResourceBookingService.test.js index 64de1900..862ef357 100644 --- a/test/unit/ResourceBookingService.test.js +++ b/test/unit/ResourceBookingService.test.js @@ -455,9 +455,13 @@ describe('resourceBooking service test', () => { const stubResourceBookingFindAll = sinon.stub(ResourceBooking, 'findAll').callsFake(async () => { return data.resourceBookingFindAll }) + const stubResourceBookingCount = sinon.stub(ResourceBooking, 'count').callsFake(async () => { + return data.resourceBookingFindAll.length + }) const result = await service.searchResourceBookings(commonData.userWithManagePermission, data.criteria) expect(esClientSearch.calledOnce).to.be.true expect(stubResourceBookingFindAll.calledOnce).to.be.true + expect(stubResourceBookingCount.calledOnce).to.be.true expect(result).to.deep.eq(data.result) }) it('T26:Fail to search resource booking with not allowed fields', async () => { diff --git a/test/unit/WorkPeriodPaymentService.test.js b/test/unit/WorkPeriodPaymentService.test.js index 5e90b072..ecc11186 100644 --- a/test/unit/WorkPeriodPaymentService.test.js +++ b/test/unit/WorkPeriodPaymentService.test.js @@ -5,7 +5,6 @@ const expect = require('chai').expect const sinon = require('sinon') const models = require('../../src/models') const service = require('../../src/services/WorkPeriodPaymentService') -const paymentService = require('../../src/services/PaymentService') const commonData = require('./common/CommonData') const testData = require('./common/WorkPeriodPaymentData') const helper = require('../../src/common/helper') @@ -25,32 +24,25 @@ describe('workPeriod service test', () => { let stubEnsureWorkPeriodById let stubEnsureResourceBookingById let stubCreateWorkPeriodPayment - let stubCreatePayment beforeEach(async () => { stubGetUserId = sinon.stub(helper, 'getUserId').callsFake(async () => testData.workPeriodPayment01.getUserIdResponse) stubEnsureWorkPeriodById = sinon.stub(helper, 'ensureWorkPeriodById').callsFake(async () => testData.workPeriodPayment01.ensureWorkPeriodByIdResponse) stubEnsureResourceBookingById = sinon.stub(helper, 'ensureResourceBookingById').callsFake(async () => testData.workPeriodPayment01.ensureResourceBookingByIdResponse) stubCreateWorkPeriodPayment = sinon.stub(models.WorkPeriodPayment, 'create').callsFake(() => testData.workPeriodPayment01.response) - stubCreatePayment = sinon.stub(paymentService, 'createPayment').callsFake(async () => testData.workPeriodPayment01.createPaymentResponse) }) it('create work period success', async () => { - const response = await service.createWorkPeriodPayment(commonData.currentUser, testData.workPeriodPayment01.request, { paymentProcessingSwitch: 'ON' }) + const response = await service.createWorkPeriodPayment(commonData.currentUser, testData.workPeriodPayment01.request) expect(stubGetUserId.calledOnce).to.be.true expect(stubEnsureWorkPeriodById.calledOnce).to.be.true expect(stubEnsureResourceBookingById.calledOnce).to.be.true - expect(stubCreatePayment.calledOnce).to.be.true expect(stubCreateWorkPeriodPayment.calledOnce).to.be.true expect(response).to.eql(testData.workPeriodPayment01.response.dataValues) }) it('create work period success - billingAccountId is set', async () => { - await service.createWorkPeriodPayment(commonData.currentUser, testData.workPeriodPayment01.request, { paymentProcessingSwitch: 'ON' }) - expect(stubCreatePayment.calledOnce).to.be.true - expect(stubCreatePayment.args[0][0]).to.include({ - billingAccountId: testData.workPeriodPayment01.ensureResourceBookingByIdResponse.billingAccountId - }) + await service.createWorkPeriodPayment(commonData.currentUser, testData.workPeriodPayment01.request) expect(stubCreateWorkPeriodPayment.calledOnce).to.be.true expect(stubCreateWorkPeriodPayment.args[0][0]).to.include({ billingAccountId: testData.workPeriodPayment01.ensureResourceBookingByIdResponse.billingAccountId @@ -67,16 +59,5 @@ describe('workPeriod service test', () => { expect(err.message).to.include('"ResourceBooking" Billing account is not assigned to the resource booking') } }) - - describe('when PAYMENT_PROCESSING_SWITCH is ON/OFF', async () => { - it('do not create payment if PAYMENT_PROCESSING_SWITCH is OFF', async () => { - await service.createWorkPeriodPayment(commonData.currentUser, testData.workPeriodPayment01.request, { paymentProcessingSwitch: 'OFF' }) - expect(stubCreatePayment.calledOnce).to.be.false - }) - it('create payment if PAYMENT_PROCESSING_SWITCH is ON', async () => { - await service.createWorkPeriodPayment(commonData.currentUser, testData.workPeriodPayment01.request, { paymentProcessingSwitch: 'ON' }) - expect(stubCreatePayment.calledOnce).to.be.true - }) - }) }) }) diff --git a/test/unit/common/WorkPeriodPaymentData.js b/test/unit/common/WorkPeriodPaymentData.js index d94d9280..6e321b62 100644 --- a/test/unit/common/WorkPeriodPaymentData.js +++ b/test/unit/common/WorkPeriodPaymentData.js @@ -1,14 +1,13 @@ const workPeriodPayment01 = { request: { workPeriodId: '467b4df7-ced4-41b9-9710-b83808cddaf4', - amount: 600, - status: 'completed' + amount: 600 }, response: { dataValues: { workPeriodId: '467b4df7-ced4-41b9-9710-b83808cddaf4', amount: 600, - status: 'completed', + status: 'scheduled', id: '01971e6f-0f09-4a2a-bc2e-2adac0f00622', challengeId: '00000000-0000-0000-0000-000000000000', createdBy: '57646ff9-1cd3-4d3c-88ba-eb09a395366c',