diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index 35244bee..04ab034f 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "87477d86-2d08-40b6-93c6-99a394193e28", + "_postman_id": "0bd597ba-4bc2-4ea1-be33-45776b80c1ce", "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -11647,6 +11647,11 @@ "key": "projectIds", "value": "111, 16705", "disabled": true + }, + { + "key": "billingAccountId", + "value": "0", + "disabled": true } ] } @@ -11725,6 +11730,11 @@ "key": "status", "value": "assigned", "disabled": true + }, + { + "key": "billingAccountId", + "value": "0", + "disabled": true } ] } @@ -11797,6 +11807,11 @@ { "key": "sortOrder", "value": "desc" + }, + { + "key": "billingAccountId", + "value": "0", + "disabled": true } ] } @@ -11867,6 +11882,11 @@ { "key": "sortOrder", "value": "desc" + }, + { + "key": "billingAccountId", + "value": "0", + "disabled": true } ] } @@ -12537,6 +12557,278 @@ }, "response": [] }, + { + "name": "search resource bookings with parameters 13", + "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": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "url": { + "raw": "{{URL}}/resourceBookings?fields=id,startDate,endDate,billingAccountId,workPeriods&billingAccountId=80000071&workPeriods.startDate=2021-01-03&workPeriods.isFirstWeek=true", + "host": [ + "{{URL}}" + ], + "path": [ + "resourceBookings" + ], + "query": [ + { + "key": "fields", + "value": "id,startDate,endDate,billingAccountId,workPeriods" + }, + { + "key": "billingAccountId", + "value": "80000071" + }, + { + "key": "workPeriods.startDate", + "value": "2021-01-03" + }, + { + "key": "workPeriods.isFirstWeek", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "search resource bookings with parameters 14", + "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": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "url": { + "raw": "{{URL}}/resourceBookings?fields=id,startDate,endDate,billingAccountId,workPeriods&billingAccountId=80000071&workPeriods.startDate=2021-02-07&workPeriods.isLastWeek=true", + "host": [ + "{{URL}}" + ], + "path": [ + "resourceBookings" + ], + "query": [ + { + "key": "fields", + "value": "id,startDate,endDate,billingAccountId,workPeriods" + }, + { + "key": "billingAccountId", + "value": "80000071" + }, + { + "key": "workPeriods.startDate", + "value": "2021-02-07" + }, + { + "key": "workPeriods.isLastWeek", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "search resource bookings with parameters 15", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () {\r", + " pm.response.to.have.status(400);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"Cannot filter by \\\"isLastWeek\\\" without \\\"startDate\\\"\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "url": { + "raw": "{{URL}}/resourceBookings?fields=id,startDate,endDate,billingAccountId,workPeriods&billingAccountId=80000071&workPeriods.isLastWeek=true", + "host": [ + "{{URL}}" + ], + "path": [ + "resourceBookings" + ], + "query": [ + { + "key": "fields", + "value": "id,startDate,endDate,billingAccountId,workPeriods" + }, + { + "key": "billingAccountId", + "value": "80000071" + }, + { + "key": "workPeriods.isLastWeek", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "search resource bookings with parameters 16", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () {\r", + " pm.response.to.have.status(400);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"Cannot filter by \\\"isFirstWeek\\\" without \\\"startDate\\\"\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "url": { + "raw": "{{URL}}/resourceBookings?fields=id,startDate,endDate,billingAccountId,workPeriods&billingAccountId=80000071&workPeriods.isFirstWeek=true", + "host": [ + "{{URL}}" + ], + "path": [ + "resourceBookings" + ], + "query": [ + { + "key": "fields", + "value": "id,startDate,endDate,billingAccountId,workPeriods" + }, + { + "key": "billingAccountId", + "value": "80000071" + }, + { + "key": "workPeriods.isFirstWeek", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "search resource bookings with parameters 17", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () {\r", + " pm.response.to.have.status(400);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"Cannot filter by both \\\"isFirstWeek\\\" and \\\"isLastWeek\\\" set to \\\"true\\\"\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "url": { + "raw": "{{URL}}/resourceBookings?fields=id,startDate,endDate,billingAccountId,workPeriods&billingAccountId=80000071&workPeriods.startDate=2021-02-07&workPeriods.isLastWeek=true&workPeriods.isFirstWeek=true", + "host": [ + "{{URL}}" + ], + "path": [ + "resourceBookings" + ], + "query": [ + { + "key": "fields", + "value": "id,startDate,endDate,billingAccountId,workPeriods" + }, + { + "key": "billingAccountId", + "value": "80000071" + }, + { + "key": "workPeriods.startDate", + "value": "2021-02-07" + }, + { + "key": "workPeriods.isLastWeek", + "value": "true" + }, + { + "key": "workPeriods.isFirstWeek", + "value": "true" + } + ] + } + }, + "response": [] + }, { "name": "put resource booking with booking manager", "event": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 48d1efb0..6bec35a5 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1506,6 +1506,12 @@ paths: schema: type: string description: comma separated project ids. + - in: query + name: billingAccountId + required: false + schema: + type: integer + description: billing account id. 0 represents null value. - in: query name: workPeriods.paymentStatus required: false @@ -1527,7 +1533,7 @@ paths: type: string format: date pattern: '^\d{4}-\d{2}-\d{2}$' - description: The work period start date. + description: The work period start date. Should be Sunday. - in: query name: workPeriods.endDate required: false @@ -1535,13 +1541,33 @@ paths: type: string format: date pattern: '^\d{4}-\d{2}-\d{2}$' - description: The work period end date. + description: The work period end date. Should be Saturday. - in: query name: workPeriods.userHandle required: false schema: type: string description: The user handle. + - in: query + name: workPeriods.isFirstWeek + required: false + schema: + type: boolean + default: false + description: | + the week which matches workPeriods.startDate is the first one in the RB. + workPeriods.startDate is required. + only one of workPeriods.isFirstWeek and workPeriods.isLastWeek is allowed. + - in: query + name: workPeriods.isLastWeek + required: false + schema: + type: boolean + default: false + description: | + the week which matches workPeriods.startDate is the last one in the RB. + workPeriods.startDate is required. + only one of workPeriods.isFirstWeek and workPeriods.isLastWeek is allowed. - in: query name: workPeriods.payments.status required: false diff --git a/src/common/helper.js b/src/common/helper.js index 0a8ffac5..7f9625be 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -177,7 +177,7 @@ esIndexPropertyMapping[config.get('esConfig.ES_INDEX_RESOURCE_BOOKING')] = { memberRate: { type: 'float' }, customerRate: { type: 'float' }, rateType: { type: 'keyword' }, - billingAccountId: { type: 'integer' }, + billingAccountId: { type: 'integer', null_value: 0 }, workPeriods: { type: 'nested', properties: { diff --git a/src/services/ResourceBookingService.js b/src/services/ResourceBookingService.js index 45f5cac1..7c5d0a59 100644 --- a/src/services/ResourceBookingService.js +++ b/src/services/ResourceBookingService.js @@ -109,7 +109,7 @@ function _checkCriteriaAndGetFields (currentUser, criteria) { // Check if any WorkPeriodPayment field will be returned result.withWorkPeriodPayments = result.allWorkPeriodPayments || result.fieldsWPP.length > 0 // Extract the filters from criteria parameter - let filters = _.filter(Object.keys(criteria), key => _.indexOf(['fromDb', 'fields', 'page', 'perPage', 'sortBy', 'sortOrder', 'jobIds'], key) === -1) + let filters = _.filter(Object.keys(criteria), key => _.indexOf(['fromDb', 'fields', 'page', 'perPage', 'sortBy', 'sortOrder', 'jobIds', 'workPeriods.isFirstWeek', 'workPeriods.isLastWeek'], key) === -1) filters = _.map(filters, f => { if (f === 'projectIds') { return 'projectId' @@ -590,7 +590,7 @@ async function searchResourceBookings (currentUser, criteria, options) { } esQuery.body.sort.push(sort) // Apply ResourceBooking filters - _.each(_.pick(criteria, ['status', 'startDate', 'endDate', 'rateType', 'projectId', 'jobId', 'userId']), (value, key) => { + _.each(_.pick(criteria, ['status', 'startDate', 'endDate', 'rateType', 'projectId', 'jobId', 'userId', 'billingAccountId']), (value, key) => { esQuery.body.query.bool.must.push({ term: { [key]: { @@ -615,6 +615,15 @@ async function searchResourceBookings (currentUser, criteria, options) { } }) } + if (criteria['workPeriods.isFirstWeek']) { + esQuery.body.query.bool.must.push({ + range: { startDate: { gte: criteria['workPeriods.startDate'] } } + }) + } else if (criteria['workPeriods.isLastWeek']) { + esQuery.body.query.bool.must.push({ + range: { endDate: { lte: moment(criteria['workPeriods.startDate']).add(6, 'day').format('YYYY-MM-DD') } } + }) + } // Apply WorkPeriod and WorkPeriodPayment filters const workPeriodFilters = _.pick(criteria, ['workPeriods.paymentStatus', 'workPeriods.startDate', 'workPeriods.endDate', 'workPeriods.userHandle']) const workPeriodPaymentFilters = _.pick(criteria, ['workPeriods.payments.status', 'workPeriods.payments.days']) @@ -716,12 +725,20 @@ async function searchResourceBookings (currentUser, criteria, options) { _.each(_.pick(criteria, ['status', 'startDate', 'endDate', 'rateType', 'projectId', 'jobId', 'userId']), (value, key) => { filter[Op.and].push({ [key]: value }) }) + if (!_.isUndefined(criteria.billingAccountId)) { + filter[Op.and].push({ billingAccountId: criteria.billingAccountId === 0 ? null : criteria.billingAccountId }) + } if (criteria.projectIds) { filter[Op.and].push({ projectId: criteria.projectIds }) } if (criteria.jobIds && criteria.jobIds.length > 0) { filter[Op.and].push({ id: criteria.jobIds }) } + if (criteria['workPeriods.isFirstWeek']) { + filter[Op.and].push({ startDate: { [Op.gte]: criteria['workPeriods.startDate'] } }) + } else if (criteria['workPeriods.isLastWeek']) { + filter[Op.and].push({ endDate: { [Op.lte]: moment(criteria['workPeriods.startDate']).add(6, 'day').format('YYYY-MM-DD') } }) + } const queryCriteria = { where: filter, offset: ((page - 1) * perPage), @@ -850,13 +867,48 @@ searchResourceBookings.schema = Joi.object().keys({ Joi.string(), Joi.array().items(Joi.number().integer()) ), + billingAccountId: Joi.number().integer(), 'workPeriods.paymentStatus': Joi.alternatives( Joi.string(), Joi.array().items(Joi.paymentStatus()) ), - 'workPeriods.startDate': Joi.date().format('YYYY-MM-DD'), - 'workPeriods.endDate': Joi.date().format('YYYY-MM-DD'), + 'workPeriods.startDate': Joi.date().format('YYYY-MM-DD').custom((value, helpers) => { + const date = new Date(value) + const weekDay = date.getDay() + if (weekDay !== 0) { + return helpers.message('workPeriods.startDate should be always Sunday') + } + return value + }), + 'workPeriods.endDate': Joi.date().format('YYYY-MM-DD').custom((value, helpers) => { + const date = new Date(value) + const weekDay = date.getDay() + if (weekDay !== 6) { + return helpers.message('workPeriods.endDate should be always Saturday') + } + return value + }), 'workPeriods.userHandle': Joi.string(), + 'workPeriods.isFirstWeek': Joi.when(Joi.ref('workPeriods.startDate', { separator: false }), { + is: Joi.exist(), + then: Joi.boolean().default(false), + otherwise: Joi.boolean().valid(false).messages({ + 'any.only': 'Cannot filter by "isFirstWeek" without "startDate"' + }) + }), + 'workPeriods.isLastWeek': Joi.boolean().when(Joi.ref('workPeriods.startDate', { separator: false }), { + is: Joi.exist(), + then: Joi.when(Joi.ref('workPeriods.isFirstWeek', { separator: false }), { + is: false, + then: Joi.boolean().default(false), + otherwise: Joi.boolean().valid(false).messages({ + 'any.only': 'Cannot filter by both "isFirstWeek" and "isLastWeek" set to "true"' + }) + }), + otherwise: Joi.boolean().valid(false).messages({ + 'any.only': 'Cannot filter by "isLastWeek" without "startDate"' + }) + }), 'workPeriods.payments.status': Joi.workPeriodPaymentStatus(), 'workPeriods.payments.days': Joi.number().integer().min(0).max(5) }).required(), diff --git a/test/unit/common/ResourceBookingData.js b/test/unit/common/ResourceBookingData.js index 1f9535a1..0dd6aa74 100644 --- a/test/unit/common/ResourceBookingData.js +++ b/test/unit/common/ResourceBookingData.js @@ -1500,7 +1500,7 @@ const T30 = { } } const T31 = { - criteria: { 'workPeriods.startDate': '2021-05-10' }, + criteria: { 'workPeriods.startDate': '2021-05-09' }, error: { httpStatus: 400, message: 'Can not filter or sort by some field which is not included in fields'