Skip to content

Commit

Permalink
additional calculation for the next available date for each RSE
Browse files Browse the repository at this point in the history
  • Loading branch information
markdturner committed Sep 18, 2024
1 parent 9d86f11 commit fddb3b9
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 52 deletions.
197 changes: 151 additions & 46 deletions src/api/rse/services/rse.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,175 @@
*/

const { createCoreService } = require("@strapi/strapi").factories
const { DateTime, Interval } = require("luxon")


module.exports = createCoreService('api::rse.rse')
/*
module.exports = createCoreService("api::rse.rse", ({ strapi }) => ({
module.exports = createCoreService('api::rse.rse', () => ({
async find(...args) {
let { results, pagination } = await super.find(...args)

let populate = {
assignments: true,
capacities: true
}

// If populate is not set, populate assignments and capacities
if(args[0].populate && !args[0].populate.isArray && args[0].populate.assignments && args[0].populate.capacities) {
args[0].populate = ['assignments', 'capacities']
}
else {
if(!args[0] || !args[0].populate.isArray) {
args[0].populate = []
}

if(!args[0].populate.includes('assignments')) {
populate.assignments = false
args[0].populate.push('assignments')
}

if(!args[0].populate.includes('capacities')) {
populate.capacities = false
args[0].populate.push('capacities')
}
}

const { results, pagination } = await super.find(...args)

let rses = results
results = []

rses.forEach((rse, index) => {

const year = args[0].filters.year ? args[0].filters.year.$eq : DateTime.now().year
// Sort assignments by start date
rse.assignments = rse.assignments.sort((a, b) => DateTime.fromISO(a.start) - DateTime.fromISO(b.start))

const startDate = DateTime.fromISO(`${year}-08-01`),
endDate = DateTime.fromISO(`${(Number(year)+1)}-07-31`)
// Only look at assignments that end in the future
const assignments = rse.assignments.filter(assignment => { return DateTime.fromISO(assignment.end) >= DateTime.now() }),
endDates = assignments.reduce((dates, assignment) => { dates.push(DateTime.fromISO(assignment.end)); return dates }, []).sort((a, b) => a - b)

// Filter for use when checking if an object with a date range overlaps with the year
const dateRangeFilter = {
$or: [
{
start: {
$between: [startDate.toISODate(), endDate.toISODate() ]
}
},
{
end: {
$between: [startDate.toISODate(), endDate.toISODate() ]
}
},
{
start: {
$lt: startDate.toISODate()
},
end: {
$gt: endDate.toISODate()
if(!rse.active || endDates.length === 0) {
rse.nextAvailableDate = null
rse.nextAvailableFTE = null
}
else {
// Loop over end dates
for(const date of endDates) {

let assignments = [],
capacities = []

// Find assignments that are concurrent with the end date
rse.assignments.forEach((assignment) => {
const period = Interval.fromDateTimes(DateTime.fromISO(assignment.start), DateTime.fromISO(assignment.end))
period.contains(date.plus({ days: 1})) ? assignments.push(assignment) : null
})

// Find capacities that are concurrent with the end date
rse.capacities.forEach((capacity) => {
// If end date is null, set it to 20 years in the future
capacity.end = capacity.end ? capacity.end : DateTime.now().plus({ years: 20}).toISODate()
const period = Interval.fromDateTimes(DateTime.fromISO(capacity.start), DateTime.fromISO(capacity.end))
period.contains(date.plus({ days: 1})) ? capacities.push(capacity) : null
})

// Reduce down total assigned time and capacity time
const assigned = assignments.reduce((total, assignment) => total + assignment.fte, 0),
capacity = capacities.reduce((total, capacity) => total + capacity.capacity, 0)

// If there is capacity available, set the next available date and FTE
if(capacity - assigned > 0) {
rse.nextAvailableDate = date.plus({ days: 1 }).toISODate()
rse.nextAvailableFTE = capacity - assigned
break
}
}
]
}

// cleanup if assignments and capacities are not requested
if(!populate.assignments) { delete results[index].assignments }
if(!populate.capacities) { delete results[index].capacities }
})

return { results, pagination }
},
async findOne(entityId, params) {

let populate = {
assignments: true,
capacities: true
}

const yearFilter = {
year: { $eq: year }
// If populate is not set, populate assignments and capacities
if(params.populate && !params.populate.isArray && params.populate.assignments && params.populate.capacities) {
params.populate = ['assignments', 'capacities']
}
else {
if(!params.populate || !params.populate.isArray) {
params.populate = []
}

if(!params.populate.includes('assignments')) {
populate.assignments = false
params.populate.push('assignments')
}

if(!params.populate.includes('capacities')) {
populate.capacities = false
params.populate.push('capacities')
}
}

const result = await super.findOne(entityId, params)

let rse = result

let assignments = await strapi.service("api::assignment.assignment").find({filters: dateRangeFilter, populate: { rse: { fields: ['id'] }, project: { fields: ['name'] } } }),
capacities = await strapi.service("api::capacity.capacity").find({filters: dateRangeFilter}),
holidays = await fetchBankHolidays(year),
leave = await strapi.service("api::timesheet.timesheet").findLeave({filters: yearFilter}),
timesheets = await strapi.service("api::timesheet.timesheet").find({filters: yearFilter})
// Sort assignments by start date
rse.assignments = rse.assignments.sort((a, b) => DateTime.fromISO(a.start) - DateTime.fromISO(b.start))

for await (const rse of rses) {
// Only look at assignments that end in the future
const assignments = rse.assignments.filter(assignment => { return DateTime.fromISO(assignment.end) >= DateTime.now() }),
endDates = assignments.reduce((dates, assignment) => { dates.push(DateTime.fromISO(assignment.end)); return dates }, []).sort((a, b) => a - b)

rse.calendar = createCalendar(rse, holidays, leave.data, assignments.results, capacities.results, timesheets.data, startDate, endDate)
const nextAvailable = rse.calendar.find(day => (day.utilisation.unallocated > 0 && DateTime.fromISO(day.date) >= DateTime.now()))
if(!rse.active || endDates.length === 0) {
rse.nextAvailableDate = null
rse.nextAvailableFTE = null
}
else {
// Loop over end dates
for(const date of endDates) {

let assignments = [],
capacities = []

// Find assignments that are concurrent with the end date
rse.assignments.forEach((assignment) => {
const period = Interval.fromDateTimes(DateTime.fromISO(assignment.start), DateTime.fromISO(assignment.end))
period.contains(date.plus({ days: 1})) ? assignments.push(assignment) : null
})

rse.nextAvailableDate = nextAvailable ? nextAvailable.date : null
rse.nextAvailableFTE = nextAvailable ? nextAvailable.utilisation.unallocated : 0
// Find capacities that are concurrent with the end date
rse.capacities.forEach((capacity) => {
// If end date is null, set it to 20 years in the future
capacity.end = capacity.end ? capacity.end : DateTime.now().plus({ years: 20}).toISODate()
const period = Interval.fromDateTimes(DateTime.fromISO(capacity.start), DateTime.fromISO(capacity.end))
period.contains(date.plus({ days: 1})) ? capacities.push(capacity) : null
})

results.push(rse)
// Reduce down total assigned time and capacity time
const assigned = assignments.reduce((total, assignment) => total + assignment.fte, 0),
capacity = capacities.reduce((total, capacity) => total + capacity.capacity, 0)

// If there is capacity available, set the next available date and FTE
if(capacity - assigned > 0) {
rse.nextAvailableDate = date.plus({ days: 1 }).toISODate()
rse.nextAvailableFTE = capacity - assigned
break
}
}
}

return { data: results, meta: pagination }
// cleanup if assignments and capacities are not requested
if(!populate.assignments) { delete result.assignments }
if(!populate.capacities) { delete result.capacities }

return result
}
}))
*/
}))
10 changes: 5 additions & 5 deletions src/api/timesheet/services/timesheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ function createCalendar(rse, holidays, leave, assignments, capacities, timesheet
leave: leaveDay ? {
type: leaveDay.TYPE,
durationCode: leaveDay.DURATION,
duration: leaveDay.DURATION === 'Y' ? 7.4 : 3.7,
duration: leaveDay.DURATION === 'Y' ? 7.26 : 3.63,
status: leaveDay.STATUS
} : null,
assignments: currentAssignments.map(({ rse, ...assignment }) => assignment),
Expand Down Expand Up @@ -476,10 +476,10 @@ module.exports = ({ strapi }) => ({
summary.days.assigned.push(dailyAssignments.reduce((total, assignment) => total + (assignment.fte / 100), 0).toFixed(1))
summary.days.leave.push((dailyTimesheetSummary.leave).toFixed(1))
summary.days.sickness.push((dailyTimesheetSummary.sickness).toFixed(1))
summary.days.recorded.push((dailyTimesheetSummary.recorded.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.4).toFixed(1))
summary.days.billable.push((dailyTimesheetSummary.billable.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.4).toFixed(1))
summary.days.nonBillable.push((dailyTimesheetSummary.nonBillable.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.4).toFixed(1))
summary.days.volunteered.push((dailyTimesheetSummary.volunteered.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.4).toFixed(1))
summary.days.recorded.push((dailyTimesheetSummary.recorded.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.26).toFixed(1))
summary.days.billable.push((dailyTimesheetSummary.billable.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.26).toFixed(1))
summary.days.nonBillable.push((dailyTimesheetSummary.nonBillable.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.26).toFixed(1))
summary.days.volunteered.push((dailyTimesheetSummary.volunteered.reduce((total, entry) => total + entry.timeInterval.duration, 0) / 60 / 60 / 7.26).toFixed(1))
}
else {
summary.days.capacity.push(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"x-generation-date": "2024-09-07T22:21:39.958Z"
"x-generation-date": "2024-09-18T08:16:42.272Z"
},
"x-strapi-config": {
"path": "/documentation",
Expand Down

0 comments on commit fddb3b9

Please sign in to comment.