const _ = require('lodash') const moment = require('moment-immutable') const { getWeeks, formatDate, parseDate } = require('../dates') const { StaffMember, Workday, Terminal, Labor, sequelize } = require('../database') const { Op } = require('sequelize') const overtime = require('../overtime') const list = async (req, res) => { const terminalKey = req.params.terminal const terminal = await Terminal.findOne({where: {key: terminalKey}}) if (!terminal) return res.status(404).end() const workdays = await Workday.findAll({where: { terminalId: terminal.id }}) let workweeks = _.groupBy(workdays, d => formatDate(moment(d.date).startOf('week'))) // fill workweeks getWeeks(10).forEach(week => { const w = formatDate(week) if (!workweeks[w]) workweeks[w] = [] }) // fill workdays workweeks = _.chain(workweeks) .toPairs() .map(([w, days]) => { days = _.chain(days) .map(d => [moment(d.date).weekday(), d]) .fromPairs() .value() days = _.range(0, 7).map(d => days[d] || null) .map(day => day && { hours: day.hours, laborCost: day.laborCost }) return { workweek: w, workdays: days } }) .sortBy(x => x.workweek) .reverse() .value() res.status(200).send(workweeks) } const get = async (req, res) => { const terminalKey = req.params.terminal const terminal = await Terminal.findOne({where: {key: terminalKey}}) const week = parseDate(req.params.week) const workdays = await Workday.findAll({ where: { terminalId: terminal.id, date: { [Op.gte]: week, [Op.lte]: moment(week).endOf('week') } }, order: [ 'date' ] }) const labor = await Labor.findAll({ where: { workdayId: { [Op.in]: workdays.map(x => x.id) } } }) const laborByWorkday = _.groupBy(labor, x => x.workdayId) const extraStaffMembers = _.chain(labor) .map(x => x.staffMemberId) .filter(x => x) .uniq() .value() const staffMembers = await StaffMember.findAll({ where: { [Op.or]: [ { terminalId: terminal.id }, { id: { [Op.in]: extraStaffMembers } } ] }, order: [ 'name' ] }) const staffMembersById = _.chain(staffMembers).map(x => [x.id, x]).fromPairs().value() // Fill in empty days const workdaysByKey = _.chain(workdays).map(wd => [formatDate(wd.date), wd]).fromPairs().value() const allWorkdays = [] for (let day = week, i = 0; i < 7; i++, day = day.add(1, 'day')) { const wd = workdaysByKey[formatDate(day)] allWorkdays[i] = wd ? wd.toJSON() : {} } const workdaysWithLabor = allWorkdays.map(wd => { const labor = laborByWorkday[wd.id] || [] const laborByStaffMember = _.chain(labor).map(l => [l.staffMemberId, l]).fromPairs().value() // Map from staffMembers to preserve sorting wd.labor = staffMembers .map(sm => laborByStaffMember[sm.id] || {staffMemberId: sm.id}) .map(sm => ({ staffMemberId: sm.staffMemberId, hours: sm.hours })) // Restore any staffMembers that are no longer assigned this terminal labor.forEach(l => { if (!staffMembersById[l.staffMemberId]) { wd.labor.push(l) } }) return wd }) res.status(200).send({ workdays: allWorkdays }) } const patch = async (req, res) => { const transaction = await sequelize.transaction() try { const terminalKey = req.params.terminal const terminal = await Terminal.findOne({where: {key: terminalKey}}) const week = parseDate(req.params.week) const workdays = await Workday.findAll({ where: { terminalId: terminal.id, date: { [Op.gte]: week, [Op.lt]: moment(week).endOf('week') } }, order: [ 'date' ] }) const labor = await Labor.findAll({ where: { workdayId: { [Op.in]: workdays.map(x => x.id) } } }) const laborByWorkday = _.groupBy(labor, x => x.workdayId) const extraStaffMembers = _.chain(labor) .map(x => x.staffMemberId) .filter(x => x) .uniq() .value() const staffMembers = await StaffMember.findAll({ where: { [Op.or]: [ { terminalId: terminal.id }, { id: { [Op.in]: extraStaffMembers } } ] }, order: [ 'name' ] }) const staffMembersById = _.chain(staffMembers).map(x => [x.id, x]).fromPairs().value() // Fill in empty days const workdaysByKey = _.chain(workdays).map(wd => [formatDate(wd.date), wd]).fromPairs().value() const allWorkdays = [] for (let day = week, i = 0; i < 7; i++, day = day.add(1, 'day')) { const wd = workdaysByKey[formatDate(day)] allWorkdays[i] = wd || Workday.build({ terminalId: terminal.id, date: week.add(i, 'day') }) } const workdaysWithLabor = allWorkdays.map(wd => { const labor = laborByWorkday[wd.id] || [] const laborByStaffMember = _.chain(labor).map(l => [l.staffMemberId, l]).fromPairs().value() // Map from staffMembers to preserve sorting wd.labor = staffMembers.map(sm => laborByStaffMember[sm.id] || Labor.build({staffMemberId: sm.id, workdayId: wd.id})) // Restore any staffMembers that are no longer assigned this terminal labor.forEach(l => { if (!staffMembersById[l.staffMemberId]) { wd.labor.push(l) } }) return wd }) // Update with model const model = req.body await Promise.all(model.workdays.map(async (modelWorkday, i) => { const modelLaborById = _.chain(modelWorkday.labor).map(l => [l.staffMemberId, l]).fromPairs().value() const workday = allWorkdays[i] workday.labor.forEach(labor => { const modelLabor = modelLaborById[labor.staffMemberId] const staffMember = staffMembersById[labor.staffMemberId] const priorHours = model.workdays .slice(0, i) .map(wd => wd.labor.find(l => l.staffMemberId === labor.staffMemberId).hours) .reduce((a, b) => a + b, 0) const hours = overtime(modelLabor.hours || 0, priorHours, terminalKey) labor.hours = modelLabor.hours || 0 labor.regularHours = hours.regularHours labor.overtimeHours = hours.overtimeHours labor.doubletimeHours = hours.doubletimeHours labor.laborCost = (staffMember.wage * labor.regularHours || 0) + (staffMember.wage * labor.overtimeHours * 1.5 || 0) + (staffMember.wage * labor.doubletimeHours * 2.0 || 0) + (staffMember.salary / 2080 * labor.hours || 0) }) workday.hours = workday.labor.map(l => l.hours || 0).reduce((a, b) => a + b, 0) workday.regularHours = workday.labor.map(l => l.regularHours || 0).reduce((a, b) => a + b, 0) workday.overtimeHours = workday.labor.map(l => l.overtimeHours || 0).reduce((a, b) => a + b, 0) workday.doubletimeHours = workday.labor.map(l => l.doubletimeHours || 0).reduce((a, b) => a + b, 0) workday.laborCost = workday.labor.map(l => l.laborCost || 0).reduce((a, b) => a + b, 0) if (workday.hours || !workday.isNewRecord) { await workday.save({ transaction }) } await Promise.all(workday.labor.map(async labor => { if (labor.hours || !labor.isNewRecord) { await labor.save({ transaction }) } })) })) await transaction.commit() res.status(200).end() } catch (err) { await transaction.rollback() throw err } } module.exports = { list, get, patch }