const moment = require('moment-timezone'), Section = require('../models/section'), Round = require('../models/round'), Teetime = require('../models/teetime'), TempRound = require('../models/tempRound'), Device = require('../models/device'), Config = require('../models/config'), Fix = require('../models/fix'), LastFix = require('../models/lastFix'), GoalSetting = require('../models/goalSetting'), GoalTime = require('../models/goalTime'), DailyGoals = require('../models/dailyGoalSetting'), paceInfoModule = require('../modules/pace'), holetimesModule = require('../modules/holetimes'), mainModule = require('../modules/main'), User = require('../models/user'), UserNotification = require('../models/user_notification'), {absInMin} = require("./pace"), { startOfDayUtc} = require('../modules/datetime'), { secondsToHMS } = require('./datetime'), shotgun = require('./shotgun') require('../modules/extend') async function getPace(fix, round, section, target) { const config = await Config.findOne(), paceThreshold = config.paceThreshold, duration = moment.duration(fix.date - round.startTime).asSeconds(), expected = target || await getExpected(round, section), pace = Math.round(expected * round.goalTimeFraction) - duration, now = moment(); //creating notification for current location pace const existingNotification = await UserNotification.findOne({ round: round._id, _id: { $gt: round._id }, type: "Pace Notification" }); if (!existingNotification && pace < paceThreshold) { const tenMinutesAgo = moment().subtract(10, 'minutes'); if (moment(fix.date).isBetween(tenMinutesAgo, now)) { createNotification(round, fix); } } return pace; } async function createNotification(round, fix) { let usersArray = [] const users = await User.find({permissions: {$elemMatch: {name:'Pace Notification', active:true}}}).select('_id notifications'), type = 'Pace Notification', location = round.locations.at(-1), timeStamp = moment().format() users.map(user => { user.notifications.forEach(notification => { if(notification.notificationId == 12){ const active = mainModule.activeDaysCheck(timeStamp, notification.activeDays) const dismissed_at = active ? null : timeStamp usersArray.push({user: user._id, dismissed_at: dismissed_at}) } }) }) const notification = { classification: 'Notification', icon: 'marshal', type: type, title: `${type} - #${round.tag}`, body: `Tag ${round.tag} is now ${absInMin(location.pace)} min slow`, section: fix.section ?? null, fixId: fix._id, round: round._id, device: round.device, users: usersArray } UserNotification.create(notification) } async function getDefualtGoalTime(){ courseSections = await Section.find().select('-_id properties.goalTime') let accumulativeGoalTime = 0; const accgoalTime = courseSections.map(section =>{ return accumulativeGoalTime += section.properties.goalTime }) const lastAccGoalTime = Array.from(accgoalTime.values()).pop() return lastAccGoalTime; } //use futureSec = true to get the expected times for sections that havent been played yet async function getExpected(round, section, futureSec = false) { const lastLocation = round.locations.at(-1) const stopSection = await Section.findOne({ 'properties.number': { $gte: lastLocation.sectionNumber }, 'properties.trigger': 'stop', }) .sort({ 'properties.number': 1 }) .select('-_id properties.number') const sameNine = section.properties.number <= stopSection.properties.number && section.properties.number > lastLocation.sectionNumber if(round.currentNine == 1 && sameNine || futureSec) { if(section.properties.number accumulator + value.properties.goalTime, 0) } const sections = await Section.find({ 'properties.number': { $gte: round.startSection - 1, // Include tee $lt: section.properties.number }, }) .select('-_id properties.goalTime') return sections.reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) } else { const frontNineStop = await Section.findOne({ 'properties.number': { $gt: round.startSection }, 'properties.trigger': 'stop', }) .sort({ 'properties.number': 1 }) .select('-_id properties.number') const frontNineSections = await Section.find({ 'properties.number': { $gte: round.startSection - 1, // Include tee $lte: frontNineStop.properties.number } }) .select('-_id properties.goalTime') const backNineStop = await Section.findOne({ 'properties.number': { $gte: section.properties.number }, 'properties.trigger': 'stop', }) .sort({ 'properties.number': 1 }) .select('-_id properties.number') const backNineStart = await Section.findOne({ 'properties.number': { $lte: backNineStop.properties.number }, 'properties.trigger': 'start', }) .sort({ 'properties.number': -1 }) const backNineSections = await Section.find({ $or: [ { 'properties.hole': null }, // Include halfway { 'properties.number': { $gte: backNineStart.properties.number - 1, $lt: section.properties.number, } }, ], }) .select('-_id properties.goalTime') return frontNineSections.concat(backNineSections) .reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) } } async function isNextSection(round, section, fix) { if(shotgun.isShotgunRound(round)) { return await shotgun.isNextSection(round, section, fix) } const lastLocation = round.locations.at(-1) if(section.properties.number == lastLocation.sectionNumber) { // Same section return false } else if(section.properties.number == lastLocation.sectionNumber + 1 || section.properties.number == lastLocation.sectionNumber + 2) { // Next 2 sections return true } else if(round.locations.length == 1) { if(section.properties.trigger == 'start') { const duration = moment .duration(fix.date - round.startTime) .asSeconds() if(section.properties.number != round.startSection || duration > 60 * 20) { console.log( 'Trigger validation:', 'round:', round._id.toString(), 'same section:', section.properties.number == round.startSection, 'duration:', duration, ); await Round.findByIdAndUpdate(round._id, { complete: true, isIncomplete: true, reason: 'Trigger validation', }, ) if(!round.primary) { // Apply new primary round await Round.updateMany( { $and: [ { _id: { $in: round.related } }, { _id: { $ne: round.related[0] } }, ] }, { $set: { primary: round.related[0] }, $pull: { related: round._id }, } ) await Round.findByIdAndUpdate(round.related[0], { $set: { primary: null }, $pull: { related: round._id }, }, ) } else { await Round.updateMany( { _id: { $in: round.related } }, { $pull: { related: round._id } }, ) } return 'new-round' } } return false } const stopSection = await Section.findOne({ 'properties.number': { $gte: lastLocation.sectionNumber }, 'properties.trigger': 'stop', }) .sort({ 'properties.number': 1 }) .select('-_id properties.number') const sameNine = section.properties.number <= stopSection.properties.number && section.properties.number > lastLocation.sectionNumber let accumulativeGoalTime = 0 let sectionsBetween = [] const select = '-_id section properties.number properties.goalTime properties.section properties.name properties.hole properties.holeSection properties.startAddition' if(sameNine) { // Still playing same nine sectionsBetween = await Section.find({ 'properties.number': { $gte: lastLocation.sectionNumber, $lt: section.properties.number, }, }) .sort({'properties.number': 1}) .select(select) accumulativeGoalTime = sectionsBetween.reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) } else if(round.currentNine == 1) { // Skipped front 9 stop zone const startSection = await Section.findOne({ 'properties.number': { $lte: section.properties.number + 1 }, // +1 incase its a teebox 'properties.trigger': 'start', }) .sort({ 'properties.number': -1 }) .select('-_id properties.number') sectionsBetween = await Section.find({ $or: [ { 'properties.number': { $gte: lastLocation.sectionNumber, $lte: stopSection.properties.number, } }, { 'properties.hole': null }, // Include halfway ], }) .sort({'properties.number': 1}) .select(select) const sectionsBetweenBackNine = await Section.find({ 'properties.number': { $gte: startSection.properties.number - 1, // Include the teebox $lt: section.properties.number, }, }) .sort({'properties.number': 1}) .select(select) sectionsBetween = sectionsBetween.concat(sectionsBetweenBackNine) accumulativeGoalTime = sectionsBetween.reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) } else { return false } const diff = moment(fix.date).diff(moment(lastLocation.fix.date), 'seconds') let isNextSection = accumulativeGoalTime > 0 && diff > accumulativeGoalTime / 2 const fixesInBetween = await Fix.find({ _id: { $gt: lastLocation.fix, $lt: fix._id }, device: fix.device._id, sectionNumber: { $ne: null }, }).sort({ date: 1 }) if(fixesInBetween.length) { console.log('fixesInBetween:', fixesInBetween.length) if(section.properties.trigger == 'start' && isNextSection) { const r = await reOpenClosedRound(round, section, fix) if(absInMin(r.location.pace) > 90) { console.log( 'Trigger validation:', 'round:', round._id.toString(), 'pace in secs:', r.location.pace, ) await markRoundAsInvalid(round._id, 'Irregular play') return 'new-round' } } else { let sectionsInSequence = 0 let lastSectionNumber = fixesInBetween.at(-1).sectionNumber for(let i = fixesInBetween.length - 2; i >= 0; i--) { if(lastSectionNumber - 1 == fixesInBetween[i].sectionNumber) { sectionsInSequence++ lastSectionNumber = fixesInBetween[i].sectionNumber } else if(lastSectionNumber != fixesInBetween[i].sectionNumber) { break } } console.log('sectionsInSequence:', sectionsInSequence) // In sequence again if(sectionsInSequence >= 3) { if(sameNine) { return true } else if(round.isNineHole) { await reOpenClosedRound(round, section, fix) } else { markRoundAsInvalid(round._id, 'Irregular play') } } } return false } /** * Skipped section(s) * Manufacture fixes if there are not fixes in between the last fix and the new fix */ if(isNextSection) { if(!section.properties.hole) { // Halfway isNextSection = 'skipped-stop' } else if(section.properties.trigger == 'start') { if(round.currentNine == 1) { // Landed on a trigger zone await reOpenClosedRound(round, section, fix) } else { await Round.findByIdAndUpdate(round._id, { complete: true, isIncomplete: true, }, ) isNextSection = 'new-round' } } // Insert manufactured fixes const lastFix = lastLocation.fix const ratio = diff / accumulativeGoalTime let timeFrom = moment(lastFix.date) // Last known section is included because it was a start of that section // Last section of the sections skipped is excluded because it will be calculated by the actual fix const locations = [] for(let i = 1; i < sectionsBetween.length; i++) { const section = sectionsBetween[i] const goalTime = sectionsBetween[i - 1].properties.goalTime const newGT = goalTime * ratio timeFrom.add(newGT, 'seconds') if(section.properties.hole != null) { // Halfway const coos = getCenter(section.section.coordinates[0]) const fixData = { isManufactured: true, source: 'Skipped zone', device: round.device, date: timeFrom.toDate(), section: section.properties.name, sectionNumber: section.properties.number, position: { type: 'Point', coordinates: coos }, } for(let j = 0; j <= 1000; j++) { // Prevent duplicate fixes const id = mainModule.getIdByTimestamp(fixData.date, j) const found = await Fix.findById(id) if(!found) { fixData._id = id break; } } const fix = await Fix.create(fixData) const duration = moment.duration(fix.date - round.startTime).asSeconds() const location = await genLocation(round, section, fix) location.startTime = duration locations.push(location) } } await Round.updateOne( { _id: round._id }, { $push: { locations: { $each: locations } } } ) } return isNextSection } async function allSectionsForNineByStartSectionNumber(sectionNumber) { const startSection = await Section.findOne({ 'properties.number': sectionNumber - 1, // Include teebox }).select('-_id properties.number') const stopSection = await Section.findOne({ 'properties.number': { $gte: sectionNumber }, 'properties.trigger': 'stop', }) .select('-_id properties.number') .sort({ 'properties.number': 1 }) return await Section.find({ 'properties.number': { $gte: startSection.properties.number, $lte: stopSection.properties.number, }, }).sort({'properties.number': 1}) } async function reOpenClosedRound(round, section, fix) { let accumulativeGoalTime = section.properties.startAddition ? section.properties.startAddition : 0 let frontNineGoalTime = 0 const secondStartSection = await Section.findOne({ 'properties.number': { $lte: section.properties.number }, 'properties.trigger': 'start', }).sort({ 'properties.number': -1 }) const sections = [round.startSection, secondStartSection.properties.number] for(let i = 0; i < sections.length; i++) { const sec = await allSectionsForNineByStartSectionNumber(sections[i]) accumulativeGoalTime += sec.reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) if(i == 0) { frontNineGoalTime = sec.reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) } } const halfway = await Section.findOne({ 'properties.hole': null }).select('properties.goalTime') if(halfway) { accumulativeGoalTime += halfway.properties.goalTime } const duration = moment.duration(fix.date - round.startTime).asSeconds() const sectionsHH = await Section.find({ $or: [ { 'properties.hole': null }, { 'properties.number': section.properties.number - 1 }, ] }).select('properties.goalTime') const sectionHHSum = sectionsHH.reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) const location = { hole: section.properties.hole, holeSection: section.properties.holeSection, sectionNumber: section.properties.number, isProjected: fix.isProjected, fixCoordinates: fix.position.coordinates, fix: fix._id, batteryPercentage: fix.batteryPercentage, date: fix.date, pace: Math.round((frontNineGoalTime + sectionHHSum) * round.goalTimeFraction - duration), startTime: duration, playTime: duration, } await Round.updateOne( { _id: round._id }, { $set: { currentHole: section.properties.hole, currentHoleSection: section.properties.holeSection, currentSection: section.properties.number, projectedGoalTime: getProjectedTime(round, location.pace), complete: false, isNineHole: false, currentNine: 2, paceNine: round.locations.at(-1).pace, goalTime: Math.round(accumulativeGoalTime * round.goalTimeFraction), }, $push: { locations: location } } ) // Add round ID to device Device.updateOne( { _id: round.device }, { $set: { round: round._id } }, function(error) { if(error) console.log(new Date(), 'error updating device round:', round.device, error) } ) return { location } } async function getGoalTimeByStartSection(section) { const stopSection = await Section.findOne({ 'properties.trigger': 'stop', 'properties.number': { $gt: section.properties.number } }) .sort({ 'properties.number': 1 }) .select('-_id properties.number') let otherStopSection = await Section.findOne({ 'properties.trigger': 'stop', 'properties.number': { $ne: stopSection.properties.number } }) .sort({ 'properties.number': 1 }) .select('-_id properties.number') if(!otherStopSection) { otherStopSection = stopSection } const otherStartSection = await Section.findOne({ 'properties.trigger': 'start', 'properties.number': { $lt: otherStopSection.properties.number } }) .sort({ 'properties.number': -1 }) .select('-_id properties.number properties.startAddition') const startSectionsCount = await Section.countDocuments({ 'properties.trigger': 'start' }) const sections = startSectionsCount > 1 ? [section.properties.number, otherStartSection.properties.number] : [section.properties.number] let accumulativeGoalTime = otherStartSection.properties.startAddition ? otherStartSection.properties.startAddition : 0 for(let i = 0; i < sections.length; i++) { const sec = await allSectionsForNineByStartSectionNumber(sections[i]) accumulativeGoalTime += sec.reduce((accumulator, value) => accumulator + value.properties.goalTime, 0) } const halfway = await Section.findOne({ 'properties.hole': null }).select('properties.goalTime') if(halfway) { accumulativeGoalTime += halfway.properties.goalTime } return accumulativeGoalTime } async function getHoldingUp(round, location) { // is there a round in the next 2 sections ? const config = await Config.findOne().select('timezone'), related = round.related, sod = moment().tz(config.timezone).startOf('day').toDate() related.push(round._id) const delayer = await Round.aggregate([ { $match: { _id: { $nin: round.related}, startTime: { $gte: sod }, complete: false, isSecondary: false, pace: {$lt : 0} } } ]) .project({ locations: 1, lastLocation: { $arrayElemAt: ['$locations', -1] } }) .match({ 'lastLocation.date': {$lt:location.date }, 'lastLocation.sectionNumber': { $gte: round.currentSection, $lte: round.currentSection + 2 }}) .sort({"lastLocation.date" : -1}) .limit(1) if(!delayer.length) return null //Can't be a delayer if round was delayed withing the last for zones //removing for now to reintroduce later // for(let i = delayer.locations.length - 1; i >= delayer.locations.length-4; i--){ // if(delayer.locations[i].heldUp){ // return null // } // } return delayer[0] } async function getRoundHeader(round, ignorePrimary = false) { const originalRound = !ignorePrimary && round.primary ? await Round.findById(round.primary) .populate('device teeTime') : round const relatedRounds = await Round.find({_id: { $in: originalRound.related }}) .populate('device teeTime') relatedRounds.unshift(originalRound) const tags = relatedRounds.map((round, i) => { return { tag: round.teeTime && round.teeTime.groupDisplay ? round.teeTime.groupDisplay : round.device.tag, color: paceInfoModule.colorByLoc(originalRound.locations.at(-1)), isApp: round.device.app, isCart: round.device.isCart, isProblem: !ignorePrimary ? originalRound.locations.at(-1).isProblem : round.locations.at(-1).isProblem, isProjected: !ignorePrimary ? originalRound.locations.at(-1).isProjected : round.locations.at(-1).isProjected, primary: i == 0, isSecondary: i > 0, isTertiary: i > 1, } }) const holeTimes = await holetimesModule.getWheelByRound(originalRound) const location = originalRound.locations.at(-1) return { pace: paceInfoModule.paceInfo(originalRound, true), heldUp: location.heldUp, holdingUp: location.holdingUp, isAutoAssigned: round.isAutoAssigned, tags: tags, holeTimes: holeTimes, } } async function genLocation(round, section, fix, target) { return { hole: section.properties.hole, holeSection: section.properties.holeSection, sectionNumber: section.properties.number, isProjected: fix.isProjected, fixCoordinates: fix.position.coordinates, fix: fix._id, pace: await getPace(fix, round, section, target), batteryPercentage: fix.batteryPercentage, date: fix.date } } async function getRoundInfo(round, ignorePrimary = false) { const finalRound = !ignorePrimary && round.primary ? await Round.findById(round.primary) .populate([{ path: 'teeTime', populate: [{ path: 'players.player', select: 'firstname surname' }] },{ path: 'roundAhead', populate: [{ path: 'device teeTime', }], select: 'tag device locations teeTime', }]) : round const config = await Config.findOne().select('-_id timezone'), goalTime = secondsToHMS(finalRound.goalTime), currentTime = secondsToHMS(finalRound.playTime), projectedTime = secondsToHMS(finalRound.projectedGoalTime), groupAheadTime = paceInfoModule.absInMin(finalRound.locations.at(-1).paceGap), positionalGap = paceInfoModule.absInMin(finalRound.locations.at(-1).positionalGap), prefix = positionalGap == 0 ? '' : positionalGap >= 0 ? '-' : '+', players = finalRound.teeTime ? finalRound.teeTime.players.map(ele => ele.player) : null const groupAhead = !finalRound.roundAhead ? null : { isCart: finalRound.roundAhead.device.isCart, time: groupAheadTime, positionalGap: `${prefix}${positionalGap}`, tag: finalRound.roundAhead.teeTime && finalRound.roundAhead.teeTime.groupDisplay ? finalRound.roundAhead.teeTime.groupDisplay : finalRound.roundAhead.device.tag, color: paceInfoModule.colorByLoc(finalRound.roundAhead.locations.at(-1)) } let startSection = await Section.findOne({'properties.number':finalRound.startSection}) let currentSection = await Section.findOne({'properties.number':finalRound.currentSection}) return { groupAhead, currentTime, projectedTime, goalTime, players, startHole: startSection.properties.courseName?finalRound.startHole+" "+startSection.properties.courseName:finalRound.startHole, endHole: finalRound.locations.at(-1).hole, roundDate: moment.tz(finalRound.startTime, config.timezone).format("ddd, DD MMM YYYY"), startTime: moment.tz(finalRound.startTime, config.timezone).format('HH:mm:ss'), endTime: finalRound.endTime ? moment(finalRound.endTime).tz(config.timezone).format('HH:mm:ss') : null, goalTimeName: finalRound.goalName, currentHole: { number: currentSection.properties.courseName?finalRound.currentHole+" "+currentSection.properties.courseName:finalRound.currentHole, section: finalRound.currentHoleSection }, } } async function getRoundGoalTime(currentDay, roundStartTime) { return await GoalTime.findOne({ isActive: true, activeTimeDays: { $elemMatch: { days: { $in: currentDay }, startTime: { $lte: roundStartTime }, endTime: { $gte: roundStartTime }, } } }) } function getProjectedTime(round, locPace = null) { const pace = locPace !== null ? locPace : round.locations.at(-1).pace const currentGT = round.playTime + pace const leftToPlay = round.goalTime - currentGT const pacePerc = pace/currentGT return Math.round(round.goalTime - (leftToPlay * pacePerc)) } function getCenter(coord_array) { let i = 0, center = coord_array[0], plat, plng, clat, clng, mlat, mlng coord_array[0] = null coord_array.forEach(function(coord) { if(coord != null) { plat = coord[0]; plng = coord[1]; clat = center[0]; clng = center[1]; mlat = (plat + (clat * i)) / (i + 1); mlng = (plng + (clng * i)) / (i + 1); center = [mlat, mlng]; i++; } }) return [mlat, mlng]; } // shotgunDevices is an array of all the devices that are part of the shotgun. async function reRunRounds(from , to = null, shotgunDevices = null) { console.log('Re run rounds') const config = await Config.findOne().select('-_id timezone'), toId = to ? mainModule.getIdByTimestamp(to) : mainModule.getIdByTimestamp(moment(from).tz(config.timezone).endOf('day').utc().toDate()), fromId = mainModule.getIdByTimestamp(from) if(shotgunDevices && shotgunDevices.length){ //Find all rounds that have started since the shotgun start or were triggers by the device in the shotgun console.log('Shotgun Devices',shotgunDevices) const rounds = await Round.find({ $or: [ { "device": { $in: shotgunDevices } }, { _id: { $gt: fromId, $lt : toId } } ] }) await linkEventstoRound(rounds) await Round.deleteMany({ $or: [ { "device": { $in: shotgunDevices } }, { _id: { $gt: fromId, $lt : toId } } ] }) Device.updateMany( { _id: { $in: shotgunDevices } }, { round: null }, ).exec() } else{ console.log('Not a shotgun') //Find all rounds that were created over the given period const rounds = await Round.find({ _id: { $gt: fromId, $lt : toId } }) await linkEventstoRound(rounds) await Round.deleteMany({ _id: { $gt: fromId, $lt : toId } }) } // rerun rounds // last fix to have beginning of todays fix date const lastFix = await LastFix.findOne() await LastFix.updateOne( { _id: lastFix._id }, { fixId: fromId, startId: fromId, stopId: toId, pickUpId: lastFix.fixId }, { upsert: true } ) //TODO handle the rerunning of rounds separately from grinder //we will create a separate module to handel this and it will be called here } async function triggerRetroShotgun(time, deviceIds) { console.log('Trigger RetroActive') const config= await Config.findOne().select('timezone'), timezone = config.timezone, date = moment().tz(timezone).format('YYYY-MM-DD'), //convert time from course timezone to utc from = moment.tz(`${date} ${time}`, timezone).utc().toDate() //Find all rounds that have triggered before the shotgun start time and remove locations that were created after the shotgun start time. const fromId = mainModule.getIdByTimestamp(moment().tz(timezone).startOf('day').toDate()) await Round.updateMany({ _id: { $gt: fromId } }, { $pull: { locations:{ date : {$gt:from} } } }) // End removing locations reRunRounds(from, null , deviceIds) } async function linkEventstoRound(rounds){ console.log('Link rounds to events') /**** Check all these rounds for: Things we need to relink to rounds: 1.Geo Breaches 2.Marshal Interactions 3.Messaging If they have any of these save them to the TempRound collection. ****/ const roundIds = rounds.map(round => round._id) const roundsToLink = await Round.aggregate([ { $match: { _id: { $in: roundIds }}}, ]) .lookup({ from: 'interactions', localField: '_id', foreignField: 'round', as: 'interaction' }) .lookup({ from: 'geofencebreaches', localField: '_id', foreignField: 'round', as: 'breach' }) .lookup({ from: 'usernotifications', localField: '_id', foreignField: 'round', as: 'notifications' }) .match( { _id: { $in: roundIds }}) const linkedRounds = roundsToLink.filter(round => round.interaction.length || round.breach.length || round.notifications.length) if(linkedRounds.length){ console.log('Create Temp Rounds') await TempRound.deleteMany() for(let round of linkedRounds) { await TempRound.create({ round : round._id, firstFix : round.firstFix, teeTime : round.teeTime, }) } } } /* This function accepts an array of rounds, with its startTime, and returns the average onPace goal percentage It will check each rounds startTime against the Goal set for that day */ async function calcAvgOnPaceGoal(rounds) { if(rounds && rounds.length){ const config = await Config.findOne().select('timezone'), goals = await GoalSetting.findOne() if(goals){ const roundsGoalTimes = rounds.map(async round => { const roundDate = moment(round.startTime).tz(config.timezone).format("YYYY-MM-DD") const roundDay = moment(round.startTime).tz(config.timezone).format('ddd').toLowerCase() const dailyGoal = await DailyGoals.findOne({date:roundDate}) if(dailyGoal){ return dailyGoal.onPace } else { return goals[roundDay].roundsOnPace } }) const avgGoal = await Promise.all(roundsGoalTimes) return avgGoal.average() } else { return null } } else{ return null } } async function markRoundAsInvalid(_id, reason = null) { const round = await Round.findByIdAndUpdate(_id, { complete: true, isIncomplete: true, reason, }, ) await Device.findByIdAndUpdate(round.device,{round:null}) } module.exports = { getHoldingUp, isNextSection, getPace, getExpected, getRoundHeader, getRoundInfo, reOpenClosedRound, genLocation, getCenter, allSectionsForNineByStartSectionNumber, getGoalTimeByStartSection, getDefualtGoalTime, getRoundGoalTime, reRunRounds, triggerRetroShotgun, linkEventstoRound, calcAvgOnPaceGoal, getProjectedTime, markRoundAsInvalid, }