| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- const _ = require('lodash')
- const moment = require('moment-immutable')
- const { getWeeks, formatDate, parseDate } = require('../dates')
- const { StaffMember, Workday, Workweek, Terminal, Labor, sequelize } = require('../database')
- const { Op } = require('sequelize')
- const overtime = require('../overtime')
- const { dict } = require('@alancnet/material-framework/lib/util')
- 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] = []
- })
- // Get workweek records
- const weeks = dict(await Workweek.findAll({
- where: {
- startDate: {
- [Op.in]: _.keys(workweeks)
- }
- }
- }), ['startOfWeek'])
- // 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
- })
- const week = weeks[w]
- return {
- ...week,
- 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 workweek = (await Workweek.findOne({where: {startDate: week, terminalId: terminal.id}})) || {startDate: week}
- 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({
- workweek,
- 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 workweek = (await Workweek.findOne({where: {startDate: week, terminalId: terminal.id}})) || Workweek.build({startDate: week, terminalId: terminal.id})
- 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
- workweek.hours = 0
- workweek.regularHours = 0
- workweek.overtimeHours = 0
- workweek.doubletimeHours = 0
- workweek.laborCost = 0
- 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)
- workweek.hours += workday.hours
- workweek.regularHours += workday.regularHours
- workweek.overtimeHours += workday.overtimeHours
- workweek.doubletimeHours += workday.doubletimeHours
- workweek.laborCost += workday.laborCost
- 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 })
- }
- }))
- }))
- workweek.laborCharge = model.workweek.laborCharge
- await workweek.save({ transaction })
- await transaction.commit()
-
- res.status(200).end()
- } catch (err) {
- await transaction.rollback()
- throw err
- }
- }
- module.exports = {
- list,
- get,
- patch
- }
|