From 8e2a5a04cd68bfca9690aa322a559cce1f550303 Mon Sep 17 00:00:00 2001 From: Cagdas U Date: Tue, 27 Apr 2021 23:08:01 +0300 Subject: [PATCH] feat(interview-scheduler): frontend integration 1. Update x.ai template names. 2. Update resourceBooking & jobCandidate statuses. 3. Add migrations for the above updates. 4. Update `interview-invitation` email template. --- app-constants.js | 5 +- config/default.js | 2 +- config/email_template.config.js | 26 +++- data/demo-data.json | 30 ++-- ...coder-bookings-api.postman_collection.json | 144 +++++++++--------- docs/swagger.yaml | 50 +++--- ...esource-booking-placed-status-migration.js | 18 +++ ...7-job-candidate-placed-status-migration.js | 18 +++ ...job-candidate-selected-status-migration.js | 18 +++ ...ndidate-rejected-other-status-migration.js | 22 +++ scripts/recruit-crm-job-import/index.js | 2 +- src/bootstrap.js | 6 +- src/common/helper.js | 5 + src/eventHandlers/InterviewEventHandler.js | 55 ++++--- .../ResourceBookingEventHandler.js | 56 +++---- src/services/InterviewService.js | 10 +- src/services/PaymentService.js | 4 +- src/services/ResourceBookingService.js | 2 +- src/services/TeamService.js | 12 +- 19 files changed, 310 insertions(+), 175 deletions(-) create mode 100644 migrations/2021-04-27-172352-resource-booking-placed-status-migration.js create mode 100644 migrations/2021-04-27-172407-job-candidate-placed-status-migration.js create mode 100644 migrations/2021-04-27-172422-job-candidate-selected-status-migration.js create mode 100644 migrations/2021-04-27-172437-job-candidate-rejected-other-status-migration.js diff --git a/app-constants.js b/app-constants.js index 71f9dce1..37217f99 100644 --- a/app-constants.js +++ b/app-constants.js @@ -62,9 +62,10 @@ const Interviews = { Completed: 'Completed', Cancelled: 'Cancelled' }, + // key: template name in x.ai, value: duration XaiTemplate: { - '30MinInterview': '30-min-interview', - '60MinInterview': '60-min-interview' + '30-minutes': 30, + '60-minutes': 60 } } diff --git a/config/default.js b/config/default.js index 8db522af..56ad8263 100644 --- a/config/default.js +++ b/config/default.js @@ -153,7 +153,7 @@ module.exports = { // SendGrid email template ID for interview invitation INTERVIEW_INVITATION_SENDGRID_TEMPLATE_ID: process.env.INTERVIEW_INVITATION_SENDGRID_TEMPLATE_ID, // The sender (aka `from`) email for invitation. - INTERVIEW_INVITATION_SENDER_EMAIL: process.env.INTERVIEW_INVITATION_SENDER_EMAIL, + INTERVIEW_INVITATION_SENDER_EMAIL: process.env.INTERVIEW_INVITATION_SENDER_EMAIL || 'scheduler@topcoder.com', // the URL where TaaS App is hosted TAAS_APP_URL: process.env.TAAS_APP_URL || 'https://platform.topcoder-dev.com/taas/myteams', // environment variables for Payment Service diff --git a/config/email_template.config.js b/config/email_template.config.js index 7de8ad80..01775ad8 100644 --- a/config/email_template.config.js +++ b/config/email_template.config.js @@ -63,17 +63,31 @@ module.exports = { /* Request interview for a job candidate * - * - interviewType: the x.ai interview type. Example: "30-min-interview" + * - interviewType: the x.ai interview type. Example: "30-minutes" + * - interviewRound: the round of the interview. Example: 2 + * - interviewDuration: duration of the interview, in minutes. Example: 30 + * - interviewerList: The list of interviewer email addresses. Example: "first@attendee.com, second@attendee.com" + * - candidateId: the id of the jobCandidate. Example: "cc562545-7b75-48bf-87e7-50b3c57e41b1" * - candidateName: Full name of candidate. Example: "John Doe" * - jobName: The title of the job. Example: "TaaS API Misc Updates" - * - customMessage: if it's needed, a custom message can be added to the end of email. Example: "I would like to invite you for an interview..." * * Template (defined in SendGrid): - * Subject: '/{{interviewType}} tech interview with {{candidateName}} for {{jobName}} is requested by the Customer' + * Subject: '{{interviewType}} tech interview with {{candidateName}} for {{jobName}} is requested by the Customer' * Body: - * 'The customer has requested /{{interviewType}} with {{candidateName}} for {{jobName}}.' - * + 'In a few minutes you will receive an invitation from our scheduling tool. Please proceed with the invitation to agree on timing.' - * + '

{{customMessage}}' + * 'Hello! + *

+ * Congratulations, you have been selected to participate in a Topcoder Gig Work Interview! + *

+ * Please monitor your email for a response to this where you can coordinate your availability. + *

+ * Interviewee: {{candidateName}}
+ * Interviewer(s): {{interviewerList}}
+ * Interview Length: {{interviewDuration}} minutes + *

+ * /{{interviewType}} + *

+ * Topcoder Info:
+ * Note: "id: {{candidateId}}, round: {{interviewRound}}"' * * Note, that the template should be defined in SendGrid. * The subject & body above (identical to actual SendGrid template) is for reference purposes. diff --git a/data/demo-data.json b/data/demo-data.json index 2947b5d3..5736c839 100644 --- a/data/demo-data.json +++ b/data/demo-data.json @@ -269,7 +269,7 @@ "id": "5191a860-4327-4c50-b76b-84beba04519b", "jobId": "a5b3bf94-a8bf-4c7e-b685-70a29a4d7d6e", "userId": "79ce2a3e-7679-48cf-8ac9-0a8ca4c4b463", - "status": "shortlist", + "status": "selected", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -282,7 +282,7 @@ "id": "e6d9635c-b122-4f69-9285-09fb1ab30106", "jobId": "a5b3bf94-a8bf-4c7e-b685-70a29a4d7d6e", "userId": "98ec2c16-442e-4b61-8ad1-66123ee37d3c", - "status": "rejected", + "status": "rejected - other", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -347,7 +347,7 @@ "id": "85d6649e-2682-4904-9480-a77b72fef27d", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", "userId": "213d2dd9-1fc3-4eda-ad97-2d56e2a84a1e", - "status": "selected", + "status": "placed", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -360,7 +360,7 @@ "id": "922dfce3-4e06-4387-9fdb-64f70675e86b", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", "userId": "dd5adacb-444d-4992-8b7b-0c349be598db", - "status": "selected", + "status": "placed", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -373,7 +373,7 @@ "id": "c26c38e2-a47d-405b-abc6-fe62a739561c", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", "userId": "6d0509c7-5f12-4d84-9a19-8e80ef7ddd66", - "status": "selected", + "status": "placed", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -386,7 +386,7 @@ "id": "7bef2b37-e1ee-4638-bfc1-c911787ac955", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", "userId": "f65e2104-2987-4136-839d-ee4632f0b2e5", - "status": "selected", + "status": "placed", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -399,7 +399,7 @@ "id": "e9716139-1f40-4bf1-9f8a-77ae4bcc621e", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", "userId": "e5e667ad-0950-43c2-8d1d-6e83ad7d1c7e", - "status": "selected", + "status": "placed", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -412,7 +412,7 @@ "id": "a1731d01-eac9-4eff-8e5a-8a3c99bc66e0", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", "userId": "bef43122-426b-4b2b-acdd-9b5b3bd1c0bf", - "status": "selected", + "status": "placed", "externalId": null, "resume": null, "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -425,7 +425,7 @@ "id": "25787cb2-d876-4883-b533-d5e628d213ce", "jobId": "1324da27-9d7d-47d8-a04e-9fb3f35a67fa", "userId": "95e7970f-12b4-43b7-ab35-38c34bf033c7", - "status": "open", + "status": "interview", "externalId": "88774631", "resume": "http://example.com", "createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c", @@ -468,7 +468,7 @@ "projectId": 111, "userId": "213d2dd9-1fc3-4eda-ad97-2d56e2a84a1e", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", - "status": "assigned", + "status": "placed", "startDate": "2021-01-25", "endDate": "2021-01-31", "memberRate": 1000, @@ -500,7 +500,7 @@ "projectId": 111, "userId": "6d0509c7-5f12-4d84-9a19-8e80ef7ddd66", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", - "status": "assigned", + "status": "placed", "startDate": "2021-02-27", "endDate": "2021-03-15", "memberRate": 2000, @@ -532,7 +532,7 @@ "projectId": 111, "userId": "dd5adacb-444d-4992-8b7b-0c349be598db", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", - "status": "assigned", + "status": "placed", "startDate": "2021-03-18", "endDate": "2021-05-28", "memberRate": 800, @@ -548,7 +548,7 @@ "projectId": 111, "userId": "f65e2104-2987-4136-839d-ee4632f0b2e5", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", - "status": "assigned", + "status": "placed", "startDate": "2000-03-27", "endDate": "2000-04-27", "memberRate": 3000, @@ -564,7 +564,7 @@ "projectId": 111, "userId": "bef43122-426b-4b2b-acdd-9b5b3bd1c0bf", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", - "status": "assigned", + "status": "placed", "startDate": "2020-04-27", "endDate": "2020-05-27", "memberRate": 0, @@ -612,7 +612,7 @@ "projectId": 111, "userId": "e5e667ad-0950-43c2-8d1d-6e83ad7d1c7e", "jobId": "2d5e2a52-e0dd-4cd9-8f4c-7cffa43951d0", - "status": "assigned", + "status": "placed", "startDate": "2021-07-27", "endDate": "2021-09-27", "memberRate": 1700, diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index f40394d5..704ad61d 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "f26164a2-4129-4e0a-89fb-75c6756b4a79", + "_postman_id": "059739c9-5726-44b6-876d-6d87940c9aff", "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -2088,7 +2088,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2153,7 +2153,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2218,7 +2218,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2283,7 +2283,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2348,7 +2348,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2413,7 +2413,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2476,7 +2476,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2539,7 +2539,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true }, { @@ -2763,7 +2763,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", + "raw": "{\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2796,7 +2796,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", + "raw": "{\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2829,7 +2829,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", + "raw": "{\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2862,7 +2862,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", + "raw": "{\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2895,7 +2895,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", + "raw": "{\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2928,7 +2928,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", + "raw": "{\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -3217,7 +3217,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"status\": \"Completed\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"status\": \"Completed\"\r\n}", "options": { "raw": { "language": "json" @@ -3270,7 +3270,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"attendeesList\": [\"attendee1@yopmail.com\", \"attendee2@yopmail.com\"],\r\n \"status\": \"Scheduling\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"attendeesList\": [\"attendee1@yopmail.com\", \"attendee2@yopmail.com\"],\r\n \"status\": \"Scheduling\"\r\n}", "options": { "raw": { "language": "json" @@ -3321,7 +3321,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", "options": { "raw": { "language": "json" @@ -3373,7 +3373,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -3422,7 +3422,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"status\": \"xxxx\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"status\": \"xxxx\"\r\n}", "options": { "raw": { "language": "json" @@ -3472,7 +3472,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"attendeesList\": [\"attendee1@yopmail.com\", \"attendee2@yopmail.com\"]\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"attendeesList\": [\"attendee1@yopmail.com\", \"attendee2@yopmail.com\"]\r\n}", "options": { "raw": { "language": "json" @@ -3521,7 +3521,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"attendeesList\": \"asddd\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"attendeesList\": \"asddd\"\r\n}", "options": { "raw": { "language": "json" @@ -3570,7 +3570,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"attendeesList\": [\"asdas\"]\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"attendeesList\": [\"asdas\"]\r\n}", "options": { "raw": { "language": "json" @@ -3619,7 +3619,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"round\": 1\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"round\": 1\r\n}", "options": { "raw": { "language": "json" @@ -3668,7 +3668,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"startTimestamp\": \"2021-04-17\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"startTimestamp\": \"2021-04-17\"\r\n}", "options": { "raw": { "language": "json" @@ -3748,7 +3748,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(\"\\\"interview.xaiTemplate\\\" must be one of [30-min-interview, 60-min-interview]\")\r", + " pm.expect(response.message).to.eq(\"\\\"interview.xaiTemplate\\\" must be one of [30-minutes, 60-minutes]\")\r", "});" ], "type": "text/javascript" @@ -3815,7 +3815,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -3864,7 +3864,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -3911,7 +3911,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -3958,7 +3958,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4005,7 +4005,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4052,7 +4052,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4101,7 +4101,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4150,7 +4150,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4199,7 +4199,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4248,7 +4248,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4297,7 +4297,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4340,7 +4340,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -4389,7 +4389,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\"\r\n}", "options": { "raw": { "language": "json" @@ -5312,7 +5312,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(\"\\\"data.xaiTemplate\\\" must be one of [30-min-interview, 60-min-interview]\")\r", + " pm.expect(response.message).to.eq(\"\\\"data.xaiTemplate\\\" must be one of [30-minutes, 60-minutes]\")\r", "});" ], "type": "text/javascript" @@ -8467,7 +8467,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-26\",\r\n \"endDate\": \"2020-11-29\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-26\",\r\n \"endDate\": \"2020-11-29\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -8603,7 +8603,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobIdCreatedByM2M}}\",\r\n \"startDate\": \"2020-12-27\",\r\n \"endDate\": \"2021-01-10\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobIdCreatedByM2M}}\",\r\n \"startDate\": \"2020-12-27\",\r\n \"endDate\": \"2021-01-10\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -8741,7 +8741,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -8789,7 +8789,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -8837,7 +8837,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -8885,7 +8885,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{jobId}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -8931,7 +8931,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-30\",\r\n \"endDate\": \"2020-11-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-30\",\r\n \"endDate\": \"2020-11-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -8977,7 +8977,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-12-30\",\r\n \"endDate\": \"2021-02-10\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-12-30\",\r\n \"endDate\": \"2021-02-10\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -9025,7 +9025,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -9073,7 +9073,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -9121,7 +9121,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-28\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -9169,7 +9169,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -19214,7 +19214,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true } ] @@ -19235,7 +19235,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"placed\"\r\n}", "options": { "raw": { "language": "json" @@ -19268,7 +19268,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\r\n \"status\": \"selected\"\r\n}", "options": { "raw": { "language": "json" @@ -19449,7 +19449,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", "options": { "raw": { "language": "json" @@ -19805,7 +19805,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{projectId}},\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"jobId\": \"{{job_id_created_by_administrator}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -19838,7 +19838,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -21158,7 +21158,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true } ] @@ -21179,7 +21179,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"placed\"\r\n}", "options": { "raw": { "language": "json" @@ -21212,7 +21212,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\r\n \"status\": \"selected\"\r\n}", "options": { "raw": { "language": "json" @@ -21389,7 +21389,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", "options": { "raw": { "language": "json" @@ -21440,7 +21440,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", "options": { "raw": { "language": "json" @@ -21845,7 +21845,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{project_id_16718}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_member}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -21878,7 +21878,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -23313,7 +23313,7 @@ }, { "key": "status", - "value": "shortlist", + "value": "selected", "disabled": true } ] @@ -23334,7 +23334,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"status\": \"placed\"\r\n}", "options": { "raw": { "language": "json" @@ -23367,7 +23367,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\r\n \"status\": \"selected\"\r\n}", "options": { "raw": { "language": "json" @@ -23544,7 +23544,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", "options": { "raw": { "language": "json" @@ -23595,7 +23595,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"xaiTemplate\": \"30-min-interview\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", + "raw": "{\r\n \"xaiTemplate\": \"30-minutes\",\r\n \"googleCalendarId\": \"dummyId\",\r\n \"customMessage\": \"This is a custom message\",\r\n \"status\": \"Scheduling\"\r\n}", "options": { "raw": { "language": "json" @@ -23996,7 +23996,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"projectId\": {{project_id_16843}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"assigned\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"projectId\": {{project_id_16843}},\r\n \"userId\": \"fe38eed1-af73-41fd-85a2-ac4da1ff09a3\",\r\n \"jobId\": \"{{job_id_created_by_connect_manager}}\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"status\": \"placed\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" @@ -24029,7 +24029,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"assigned\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", + "raw": "{\r\n \"status\": \"placed\",\r\n \"startDate\": \"2020-09-27\",\r\n \"endDate\": \"2020-09-27\",\r\n \"memberRate\": 13.23,\r\n \"customerRate\": 13,\r\n \"rateType\": \"hourly\",\r\n \"billingAccountId\": 80000071\r\n}", "options": { "raw": { "language": "json" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d96dd455..93b67407 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -593,9 +593,11 @@ paths: enum: [ "open", + "placed", "selected", - "shortlist", - "rejected", + "client rejected - screening", + "client rejected - interview", + "rejected - other", "cancelled", "interview", "topcoder-rejected", @@ -1305,7 +1307,7 @@ paths: required: false schema: type: string - enum: ["assigned", "in-progress", "completed"] + enum: ["placed", "in-progress", "completed"] description: The status. - in: query name: startDate @@ -3283,9 +3285,11 @@ components: enum: [ "open", + "placed", "selected", - "shortlist", - "rejected", + "client rejected - screening", + "client rejected - interview", + "rejected - other", "cancelled", "interview", "topcoder-rejected", @@ -3392,7 +3396,7 @@ components: description: "The user id." status: type: string - enum: ["open", "selected", "shortlist", "rejected", "cancelled"] + enum: ["open", "placed", "selected", "client rejected - screening", "client rejected - interview", "rejected - other", "cancelled", "interview", "topcoder-rejected"] description: "The job candidate status." default: open externalId: @@ -3410,9 +3414,11 @@ components: enum: [ "open", + "placed", "selected", - "shortlist", - "rejected", + "client rejected - screening", + "client rejected - interview", + "rejected - other", "cancelled", "interview", "topcoder-rejected", @@ -3449,8 +3455,8 @@ components: description: "The google calendar id." xaiTemplate: type: string - example: "30-min-interview" - enum: ["30-min-interview", "60-min-interview"] + example: "30-minutes" + enum: ["30-minutes", "60-minutes"] description: "The x.ai template name" customMessage: type: string @@ -3504,8 +3510,8 @@ components: description: "The custom message." xaiTemplate: type: string - enum: ["30-min-interview", "60-min-interview"] - example: "30-min-interview" + enum: ["30-minutes", "60-minutes"] + example: "30-minutes" description: "The x.ai template name" attendeesList: type: array @@ -3529,8 +3535,8 @@ components: description: "The custom message." xaiTemplate: type: string - enum: ["30-min-interview", "60-min-interview"] - example: "30-min-interview" + enum: ["30-minutes", "60-minutes"] + example: "30-minutes" description: "The x.ai template name" attendeesList: type: array @@ -3617,7 +3623,7 @@ components: description: "The external id." status: type: string - enum: ["assigned", "closed", "cancelled"] + enum: ["placed", "closed", "cancelled"] description: "The job status." startDate: type: string @@ -3684,7 +3690,7 @@ components: description: "The job id." status: type: string - enum: ["assigned", "closed", "cancelled"] + enum: ["placed", "closed", "cancelled"] description: "The job status." default: sourcing startDate: @@ -3719,7 +3725,7 @@ components: properties: status: type: string - enum: ["assigned", "closed", "cancelled"] + enum: ["placed", "closed", "cancelled"] startDate: type: string format: date @@ -4338,14 +4344,20 @@ components: type: string format: url description: "The link for the resume that can be downloaded" + interviews: + type: array + items: + $ref: "#/components/schemas/Interview" status: type: string enum: [ "open", + "placed", "selected", - "shortlist", - "rejected", + "client rejected - screening", + "client rejected - interview", + "rejected - other", "cancelled", "interview", "topcoder-rejected", diff --git a/migrations/2021-04-27-172352-resource-booking-placed-status-migration.js b/migrations/2021-04-27-172352-resource-booking-placed-status-migration.js new file mode 100644 index 00000000..70ae3c6c --- /dev/null +++ b/migrations/2021-04-27-172352-resource-booking-placed-status-migration.js @@ -0,0 +1,18 @@ +'use strict'; + +const config = require('config') + +/** + * Migrate ResourceBooking status - from assigned to placed. + */ +module.exports = { + up: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.resource_bookings` + await queryInterface.sequelize.query(`UPDATE ${tableName} SET status = 'placed' WHERE status = 'assigned'`) + }, + + down: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.resource_bookings` + await queryInterface.sequelize.query(`UPDATE ${tableName} SET status = 'assigned' WHERE status = 'placed'`) + } +}; diff --git a/migrations/2021-04-27-172407-job-candidate-placed-status-migration.js b/migrations/2021-04-27-172407-job-candidate-placed-status-migration.js new file mode 100644 index 00000000..611184ee --- /dev/null +++ b/migrations/2021-04-27-172407-job-candidate-placed-status-migration.js @@ -0,0 +1,18 @@ +'use strict'; + +const config = require('config') + +/** + * Migrate JobCandidate status - from selected to placed. + */ +module.exports = { + up: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.job_candidates` + await queryInterface.sequelize.query(`UPDATE ${tableName} SET status = 'placed' WHERE status = 'selected'`) + }, + + down: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.job_candidates` + await queryInterface.sequelize.query(`UPDATE ${tableName} SET status = 'selected' WHERE status = 'placed'`) + } +}; diff --git a/migrations/2021-04-27-172422-job-candidate-selected-status-migration.js b/migrations/2021-04-27-172422-job-candidate-selected-status-migration.js new file mode 100644 index 00000000..3ba8ec06 --- /dev/null +++ b/migrations/2021-04-27-172422-job-candidate-selected-status-migration.js @@ -0,0 +1,18 @@ +'use strict'; + +const config = require('config') + +/** + * Migrate JobCandidate status - from shortlist to selected. + */ +module.exports = { + up: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.job_candidates` + await queryInterface.sequelize.query(`UPDATE ${tableName} SET status = 'selected' WHERE status = 'shortlist'`) + }, + + down: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.job_candidates` + await queryInterface.sequelize.query(`UPDATE ${tableName} SET status = 'shortlist' WHERE status = 'selected'`) + } +}; diff --git a/migrations/2021-04-27-172437-job-candidate-rejected-other-status-migration.js b/migrations/2021-04-27-172437-job-candidate-rejected-other-status-migration.js new file mode 100644 index 00000000..9e8a2480 --- /dev/null +++ b/migrations/2021-04-27-172437-job-candidate-rejected-other-status-migration.js @@ -0,0 +1,22 @@ +'use strict'; + +const config = require('config') + +/** + * Migrate JobCandidate status - from rejected to rejected - other. + */ +module.exports = { + up: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.job_candidates` + await queryInterface.sequelize.query( + `UPDATE ${tableName} SET status = 'rejected - other' WHERE status = 'rejected'` + ) + }, + + down: async (queryInterface, Sequelize) => { + const tableName = `${config.DB_SCHEMA_NAME}.job_candidates` + await queryInterface.sequelize.query( + `UPDATE ${tableName} SET status = 'rejected' WHERE status = 'rejected - other'` + ) + } +}; diff --git a/scripts/recruit-crm-job-import/index.js b/scripts/recruit-crm-job-import/index.js index c82254ab..596ab680 100644 --- a/scripts/recruit-crm-job-import/index.js +++ b/scripts/recruit-crm-job-import/index.js @@ -89,7 +89,7 @@ async function processJob (job, info = []) { data.resourceBookingId = result.id } // update the resourceBooking based on startDate and endDate - const resourceBookingStatus = dateFNS.isBefore(data.endDate, dateFNS.startOfToday()) ? 'closed' : 'assigned' + const resourceBookingStatus = dateFNS.isBefore(data.endDate, dateFNS.startOfToday()) ? 'closed' : 'placed' logger.debug(`resourceBookingId: ${data.resourceBookingId} status: ${resourceBookingStatus}`) await helper.updateResourceBookingStatus(data.resourceBookingId, resourceBookingStatus) info.push({ text: `id: ${data.resourceBookingId} status: ${resourceBookingStatus} resource booking updated`, tag: 'resource_booking_status_updated' }) diff --git a/src/bootstrap.js b/src/bootstrap.js index 19e93973..6a364e8a 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -8,15 +8,15 @@ const constants = require('../app-constants') const config = require('config') const allowedInterviewStatuses = _.values(Interviews.Status) -const allowedXAITemplate = _.values(Interviews.XaiTemplate) +const allowedXAITemplate = _.keys(Interviews.XaiTemplate) Joi.page = () => Joi.number().integer().min(1).default(1) Joi.perPage = () => Joi.number().integer().min(1).default(20) 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('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', 'selected', 'shortlist', 'rejected', 'cancelled', 'interview', 'topcoder-rejected') +Joi.jobCandidateStatus = () => Joi.string().valid('open', 'placed', 'selected', 'client rejected - screening', 'client rejected - interview', 'rejected - other', 'cancelled', 'interview', 'topcoder-rejected') Joi.title = () => Joi.string().max(128) Joi.paymentStatus = () => Joi.string().valid('pending', 'partially-completed', 'completed', 'cancelled') Joi.xaiTemplate = () => Joi.string().valid(...allowedXAITemplate) diff --git a/src/common/helper.js b/src/common/helper.js index b65f6ba7..ec55e2d8 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -679,6 +679,11 @@ function encodeQueryString (queryObj, nesting = '') { * @returns {Array} the users found */ async function listUsersByExternalId (externalId) { + // return empty list if externalId is null or undefined + if (!!externalId !== true) { + return [] + } + const token = await getM2MUbahnToken() const q = { enrich: true, diff --git a/src/eventHandlers/InterviewEventHandler.js b/src/eventHandlers/InterviewEventHandler.js index ee503ee5..af7ce400 100644 --- a/src/eventHandlers/InterviewEventHandler.js +++ b/src/eventHandlers/InterviewEventHandler.js @@ -3,8 +3,9 @@ */ const models = require('../models') -const logger = require('../common/logger') +// const logger = require('../common/logger') const helper = require('../common/helper') +const { Interviews } = require('../../app-constants') const teamService = require('../services/TeamService') /** @@ -15,27 +16,45 @@ const teamService = require('../services/TeamService') */ async function sendInvitationEmail (payload) { const interview = payload.value + // get job candidate user details const jobCandidate = await models.JobCandidate.findById(interview.jobCandidateId) const jobCandidateUser = await helper.getUserById(jobCandidate.userId, true) - const jobCandidateUserEmail = helper.getUserAttributeValue(jobCandidateUser, 'email') + // const jobCandidateUserEmail = helper.getUserAttributeValue(jobCandidateUser, 'email') // get customer details const job = await jobCandidate.getJob() - const customerUser = await helper.getUserByExternalId(job.externalId, true) - const customerUserEmail = helper.getUserAttributeValue(customerUser, 'email') - if (jobCandidateUserEmail && customerUserEmail) { - teamService.sendEmail({}, { - template: 'interview-invitation', - recipients: [jobCandidateUserEmail, customerUserEmail], - cc: interview.attendeesList, - data: { - interviewType: interview.xaiTemplate, - jobName: job.title, - candidateName: `${jobCandidateUser.firstName} ${jobCandidateUser.lastName}`, - customMessage: interview.customMessage - } - }) - } else { + // const customerUser = await helper.getUserByExternalId(job.externalId, true) + // const customerUserEmail = helper.getUserAttributeValue(customerUser, 'email') + + // TODO: remove mock addresses & switch back to the old implementation once API gets fixed + // Both emails will be undefined since TC API doesn't return attributes, + // this is a workaround to skip check/condition & log the payload + // it will post the event nevertheless (with mocked candidate&customer address), so you can see on the logs as kafka event + // and verify the payload content + const customerMockEmail = 'testcustomer@yopmail.com' + const candidateMockEmail = 'testuserforemail@yopmail.com' + + // if (jobCandidateUserEmail && customerUserEmail) { + const interviewerList = interview.attendeesList + // ? [customerUserEmail, ...interview.attendeesList].join(', ') // "customer@mail.com, first@attendee.com, second@attendee.com..." + // : customerUserEmail + ? [customerMockEmail, ...interview.attendeesList].join(', ') // "customer@mail.com, first@attendee.com, second@attendee.com..." + : customerMockEmail + teamService.sendEmail({}, { + template: 'interview-invitation', + recipients: [candidateMockEmail, customerMockEmail], + cc: interview.attendeesList, + data: { + interviewType: interview.xaiTemplate, + interviewRound: interview.round, + interviewDuration: Interviews.XaiTemplate[interview.xaiTemplate], + interviewerList, + jobName: job.title, + candidateName: `${jobCandidateUser.firstName} ${jobCandidateUser.lastName}`, + candidateId: interview.jobCandidateId + } + }) + /* } else { // one (or both) of the emails are missing due to some reason // for e.g. some users' externalIds may be set to null or similar // log error @@ -44,7 +63,7 @@ async function sendInvitationEmail (payload) { context: 'sendInvitationEmail', message: 'Couldn\'t sent invitation emails. Insufficient details.' }) - } + } */ } /** diff --git a/src/eventHandlers/ResourceBookingEventHandler.js b/src/eventHandlers/ResourceBookingEventHandler.js index 95947436..7bf821df 100644 --- a/src/eventHandlers/ResourceBookingEventHandler.js +++ b/src/eventHandlers/ResourceBookingEventHandler.js @@ -13,26 +13,26 @@ const WorkPeriodService = require('../services/WorkPeriodService') const WorkPeriod = models.WorkPeriod /** - * When ResourceBooking's status is changed to `assigned` + * When ResourceBooking's status is changed to `placed` * the corresponding JobCandidate record (with the same userId and jobId) - * should be updated with status `selected` + * should be updated with status `placed` * * @param {Object} payload the event payload * @returns {undefined} */ -async function selectJobCandidate (payload) { +async function placeJobCandidate (payload) { if (_.get(payload, 'options.oldValue') && payload.value.status === payload.options.oldValue.status) { logger.debug({ component: 'ResourceBookingEventHandler', - context: 'selectJobCandidate', + context: 'placeJobCandidate', message: 'status not changed' }) return } - if (payload.value.status !== 'assigned') { + if (payload.value.status !== 'placed') { logger.debug({ component: 'ResourceBookingEventHandler', - context: 'selectJobCandidate', + context: 'placeJobCandidate', message: `not interested resource booking - status: ${payload.value.status}` }) return @@ -41,7 +41,7 @@ async function selectJobCandidate (payload) { if (!resourceBooking.jobId) { logger.debug({ component: 'ResourceBookingEventHandler', - context: 'selectJobCandidate', + context: 'placeJobCandidate', message: `id: ${resourceBooking.id} resource booking without jobId - ignored` }) return @@ -51,42 +51,42 @@ async function selectJobCandidate (payload) { jobId: resourceBooking.jobId, userId: resourceBooking.userId, status: { - [Op.not]: 'selected' + [Op.not]: 'placed' } } }) await Promise.all(candidates.map(candidate => JobCandidateService.partiallyUpdateJobCandidate( helper.getAuditM2Muser(), candidate.id, - { status: 'selected' } + { status: 'placed' } ).then(result => { logger.info({ component: 'ResourceBookingEventHandler', - context: 'selectJobCandidate', + context: 'placeJobCandidate', message: `id: ${result.id} candidate got selected.` }) }))) } /** - * Update the status of the Job to assigned when it positions requirement is fullfilled. + * Update the status of the Job to placed when it positions requirement is fullfilled. * * @param {Object} payload the event payload * @returns {undefined} */ -async function assignJob (payload) { +async function placeJob (payload) { if (_.get(payload, 'options.oldValue') && payload.value.status === payload.options.oldValue.status) { logger.debug({ component: 'ResourceBookingEventHandler', - context: 'assignJob', + context: 'placeJob', message: 'status not changed' }) return } - if (payload.value.status !== 'assigned') { + if (payload.value.status !== 'placed') { logger.debug({ component: 'ResourceBookingEventHandler', - context: 'assignJob', + context: 'placeJob', message: `not interested resource booking - status: ${payload.value.status}` }) return @@ -95,34 +95,34 @@ async function assignJob (payload) { if (!resourceBooking.jobId) { logger.debug({ component: 'ResourceBookingEventHandler', - context: 'assignJob', + context: 'placeJob', message: `id: ${resourceBooking.id} resource booking without jobId - ignored` }) return } const job = await models.Job.findById(resourceBooking.jobId) - if (job.status === 'assigned') { + if (job.status === 'placed') { logger.debug({ component: 'ResourceBookingEventHandler', - context: 'assignJob', - message: `job with projectId ${job.projectId} is already assigned` + context: 'placeJob', + message: `job with projectId ${job.projectId} is already placed` }) return } const resourceBookings = await models.ResourceBooking.findAll({ where: { jobId: job.id, - status: 'assigned' + status: 'placed' } }) logger.debug({ component: 'ResourceBookingEventHandler', - context: 'assignJob', - message: `the number of assigned resource bookings is ${resourceBookings.length} - the numPositions of the job is ${job.numPositions}` + context: 'placeJob', + message: `the number of placed resource bookings is ${resourceBookings.length} - the numPositions of the job is ${job.numPositions}` }) if (job.numPositions === resourceBookings.length) { - await JobService.partiallyUpdateJob(helper.getAuditM2Muser(), job.id, { status: 'assigned' }) - logger.info({ component: 'ResourceBookingEventHandler', context: 'assignJob', message: `job ${job.id} is assigned` }) + await JobService.partiallyUpdateJob(helper.getAuditM2Muser(), job.id, { status: 'placed' }) + logger.info({ component: 'ResourceBookingEventHandler', context: 'placeJob', message: `job ${job.id} is placed` }) } } @@ -294,8 +294,8 @@ async function _deleteWorkPeriods (workPeriods) { * @returns {undefined} */ async function processCreate (payload) { - await selectJobCandidate(payload) - await assignJob(payload) + await placeJobCandidate(payload) + await placeJob(payload) await createWorkPeriods(payload) } @@ -306,8 +306,8 @@ async function processCreate (payload) { * @returns {undefined} */ async function processUpdate (payload) { - await selectJobCandidate(payload) - await assignJob(payload) + await placeJobCandidate(payload) + await placeJob(payload) await updateWorkPeriods(payload) } diff --git a/src/services/InterviewService.js b/src/services/InterviewService.js index 2afad72e..d4f67576 100644 --- a/src/services/InterviewService.js +++ b/src/services/InterviewService.js @@ -125,10 +125,18 @@ async function requestInterview (currentUser, jobCandidateId, interview) { }) interview.round = round + 1 - // create try { + // create the interview const created = await Interview.create(interview) await helper.postEvent(config.TAAS_INTERVIEW_REQUEST_TOPIC, created.toJSON()) + // update jobCandidate.status to Interview + const [, affectedRows] = await models.JobCandidate.update( + { status: 'interview' }, + { where: { id: created.jobCandidateId }, returning: true } + ) + const updatedJobCandidate = _.omit(_.get(affectedRows, '0.dataValues'), 'deletedAt') + await helper.postEvent(config.TAAS_JOB_CANDIDATE_UPDATE_TOPIC, updatedJobCandidate) + // return created interview return created.dataValues } catch (err) { // gracefully handle if one of the common sequelize errors diff --git a/src/services/PaymentService.js b/src/services/PaymentService.js index 15c6ee7c..7d714f75 100644 --- a/src/services/PaymentService.js +++ b/src/services/PaymentService.js @@ -79,13 +79,13 @@ async function createChallenge (challenge, token) { pureV5Task: true }, tags: ['Other'], - startDate: new Date(), + startDate: new Date() } if (challenge.billingAccountId) { body.billing = { billingAccountId: challenge.billingAccountId, - markup: 0, // for TaaS payments we always use 0 markup + markup: 0 // for TaaS payments we always use 0 markup } } try { diff --git a/src/services/ResourceBookingService.js b/src/services/ResourceBookingService.js index 4fef4e46..3693f22c 100644 --- a/src/services/ResourceBookingService.js +++ b/src/services/ResourceBookingService.js @@ -159,7 +159,7 @@ async function createResourceBooking (currentUser, resourceBooking) { createResourceBooking.schema = Joi.object().keys({ currentUser: Joi.object().required(), resourceBooking: Joi.object().keys({ - status: Joi.resourceBookingStatus().default('assigned'), + status: Joi.resourceBookingStatus().default('placed'), projectId: Joi.number().integer().required(), userId: Joi.string().uuid().required(), jobId: Joi.string().uuid().allow(null), diff --git a/src/services/TeamService.js b/src/services/TeamService.js index 459e6228..8eb6714c 100644 --- a/src/services/TeamService.js +++ b/src/services/TeamService.js @@ -23,13 +23,13 @@ const emailTemplates = _.mapValues(emailTemplateConfig, (template) => { }) /** - * Function to get assigned resource bookings with specific projectIds + * Function to get placed resource bookings with specific projectIds * @param {Object} currentUser the user who perform this operation. * @param {Array} projectIds project ids * @returns the request result */ -async function _getAssignedResourceBookingsByProjectIds (currentUser, projectIds) { - const criteria = { status: 'assigned', projectIds } +async function _getPlacedResourceBookingsByProjectIds (currentUser, projectIds) { + const criteria = { status: 'placed', projectIds } const { result } = await ResourceBookingService.searchResourceBookings(currentUser, criteria, { returnAll: true }) return result } @@ -95,8 +95,8 @@ searchTeams.schema = Joi.object().keys({ */ async function getTeamDetail (currentUser, projects, isSearch = true) { const projectIds = _.map(projects, 'id') - // Get all assigned resourceBookings filtered by projectIds - const resourceBookings = await _getAssignedResourceBookingsByProjectIds(currentUser, projectIds) + // Get all placed resourceBookings filtered by projectIds + const resourceBookings = await _getPlacedResourceBookingsByProjectIds(currentUser, projectIds) // Get all jobs filtered by projectIds const jobs = await _getJobsByProjectIds(currentUser, projectIds) @@ -285,7 +285,7 @@ async function getTeamJob (currentUser, id, jobId) { const photoURLMap = _.groupBy(members, 'handleLower') result.candidates = _.map(job.candidates, candidate => { - const candidateData = _.pick(candidate, ['status', 'resume', 'userId', 'id']) + const candidateData = _.pick(candidate, ['status', 'resume', 'userId', 'interviews', 'id']) const userData = userMap[candidate.userId][0] // attach user data to the candidate Object.assign(candidateData, _.pick(userData, ['handle', 'firstName', 'lastName', 'skills']))