Implement recurring day of month and day of week

This commit is contained in:
Louis Lam 2022-10-15 18:49:09 +08:00
parent 0b8d4cdaac
commit d8a676abb6
4 changed files with 154 additions and 60 deletions

View File

@ -112,6 +112,40 @@ class Maintenance extends BeanModel {
return this.toPublicJSON(timezone); return this.toPublicJSON(timezone);
} }
getDayOfWeekList() {
log.debug("timeslot", "List: " + this.weekdays);
return JSON.parse(this.weekdays).sort(function (a, b) {
return a - b;
});
}
getDayOfMonthList() {
return JSON.parse(this.days_of_month).sort(function (a, b) {
return a - b;
});
}
getStartDateTime() {
let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm");
log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
// Start Time
let startTimeSecond = dayjs.utc(this.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
log.debug("timeslot", "startTime: " + startTimeSecond);
// Bake StartDate + StartTime = Start DateTime
return dayjs.utc(this.start_date).add(startTimeSecond, "second");
}
getDuration() {
let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
// Add 24hours if it is across day
if (duration < 0) {
duration += 24 * 3600;
}
return duration;
}
static jsonToBean(bean, obj) { static jsonToBean(bean, obj) {
if (obj.id) { if (obj.id) {
bean.id = obj.id; bean.id = obj.id;

View File

@ -40,6 +40,7 @@ class MaintenanceTimeslot extends BeanModel {
if (maintenance.strategy === "manual") { if (maintenance.strategy === "manual") {
log.debug("maintenance", "No need to generate timeslot for manual type"); log.debug("maintenance", "No need to generate timeslot for manual type");
} else if (maintenance.strategy === "single") { } else if (maintenance.strategy === "single") {
let bean = R.dispense("maintenance_timeslot"); let bean = R.dispense("maintenance_timeslot");
bean.maintenance_id = maintenance.id; bean.maintenance_id = maintenance.id;
@ -47,74 +48,131 @@ class MaintenanceTimeslot extends BeanModel {
bean.end_date = maintenance.end_date; bean.end_date = maintenance.end_date;
bean.generated_next = true; bean.generated_next = true;
return await R.store(bean); return await R.store(bean);
} else if (maintenance.strategy === "recurring-interval") {
let bean = R.dispense("maintenance_timeslot");
} else if (maintenance.strategy === "recurring-interval") {
// Prevent dead loop, in case interval_day is not set // Prevent dead loop, in case interval_day is not set
if (!maintenance.interval_day || maintenance.interval_day <= 0) { if (!maintenance.interval_day || maintenance.interval_day <= 0) {
maintenance.interval_day = 1; maintenance.interval_day = 1;
} }
let startOfTheDay = dayjs.utc(maintenance.start_date).format("HH:mm"); return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
log.debug("timeslot", "startOfTheDay: " + startOfTheDay); return startDateTime.add(maintenance.interval_day, "day");
});
// Start Time
let startTimeSecond = dayjs.utc(maintenance.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
log.debug("timeslot", "startTime: " + startTimeSecond);
// Duration
let duration = dayjs.utc(maintenance.end_time, "HH:mm").diff(dayjs.utc(maintenance.start_time, "HH:mm"), "second");
// Add 24hours if it is across day
if (duration < 0) {
duration += 24 * 3600;
}
// Bake StartDate + StartTime = Start DateTime
let startDateTime = dayjs.utc(maintenance.start_date).add(startTimeSecond, "second");
let endDateTime;
// Keep generating from the first possible date, until it is ok
while (true) {
log.debug("timeslot", "startDateTime: " + startDateTime.format());
// Handling out of effective date range
if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
log.debug("timeslot", "Out of effective date range");
return null;
}
endDateTime = startDateTime.add(duration, "second");
// If endDateTime is out of effective date range, use the end datetime from effective date range
if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
endDateTime = dayjs.utc(maintenance.end_date);
}
// If minDate is set, the endDateTime must be bigger than it.
// And the endDateTime must be bigger current time
if (
(!minDate || endDateTime.diff(minDate) > 0) &&
endDateTime.diff(dayjs()) > 0
) {
break;
}
startDateTime = startDateTime.add(maintenance.interval_day, "day");
}
bean.maintenance_id = maintenance.id;
bean.start_date = localToUTC(startDateTime);
bean.end_date = localToUTC(endDateTime);
bean.generated_next = false;
return await R.store(bean);
} else if (maintenance.strategy === "recurring-weekday") { } else if (maintenance.strategy === "recurring-weekday") {
// TODO let dayOfWeekList = maintenance.getDayOfWeekList();
if (dayOfWeekList.length <= 0) {
log.debug("timeslot", "No weekdays selected?");
return null;
}
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
while (true) {
startDateTime = startDateTime.add(1, "day");
log.debug("timeslot", "nextDateTime: " + startDateTime);
let day = startDateTime.local().day();
log.debug("timeslot", "nextDateTime.day(): " + day);
if (dayOfWeekList.includes(day)) {
return startDateTime;
}
}
});
} else if (maintenance.strategy === "recurring-day-of-month") { } else if (maintenance.strategy === "recurring-day-of-month") {
// TODO let dayOfMonthList = maintenance.getDayOfMonthList();
if (dayOfMonthList.length <= 0) {
log.debug("timeslot", "No day selected?");
return null;
}
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
while (true) {
startDateTime = startDateTime.add(1, "day");
let day = parseInt(startDateTime.local().format("D"));
log.debug("timeslot", "day: " + day);
// Check 1-31
if (dayOfMonthList.includes(day)) {
return startDateTime;
}
// Check "lastDay1","lastDay2"...
let daysInMonth = startDateTime.daysInMonth();
let lastDayList = [];
// Small first, e.g. 28 > 29 > 30 > 31
for (let i = 4; i >= 1; i--) {
if (dayOfMonthList.includes("lastDay" + i)) {
lastDayList.push(daysInMonth - i + 1);
}
}
log.debug("timeslot", "lastDayList: " + lastDayList);
if (lastDayList.includes(day)) {
return startDateTime;
}
}
});
} else { } else {
throw new Error("Unknown maintenance strategy"); throw new Error("Unknown maintenance strategy");
} }
} }
/**
* Generate a next timeslot for all recurring types
* @param maintenance
* @param minDate
* @param nextDayCallback The logic how to get the next possible day
* @returns {Promise<null|MaintenanceTimeslot>}
*/
static async handleRecurringType(maintenance, minDate, nextDayCallback) {
let bean = R.dispense("maintenance_timeslot");
let duration = maintenance.getDuration();
let startDateTime = maintenance.getStartDateTime();
let endDateTime;
// Keep generating from the first possible date, until it is ok
while (true) {
log.debug("timeslot", "startDateTime: " + startDateTime.format());
// Handling out of effective date range
if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
log.debug("timeslot", "Out of effective date range");
return null;
}
endDateTime = startDateTime.add(duration, "second");
// If endDateTime is out of effective date range, use the end datetime from effective date range
if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
endDateTime = dayjs.utc(maintenance.end_date);
}
// If minDate is set, the endDateTime must be bigger than it.
// And the endDateTime must be bigger current time
if (
(!minDate || endDateTime.diff(minDate) > 0) &&
endDateTime.diff(dayjs()) > 0
) {
break;
}
startDateTime = nextDayCallback(startDateTime);
}
bean.maintenance_id = maintenance.id;
bean.start_date = localToUTC(startDateTime);
bean.end_date = localToUTC(endDateTime);
bean.generated_next = false;
return await R.store(bean);
}
} }
module.exports = MaintenanceTimeslot; module.exports = MaintenanceTimeslot;

View File

@ -620,6 +620,7 @@ export default {
weekdayShortFri: "Fri", weekdayShortFri: "Fri",
weekdayShortSat: "Sat", weekdayShortSat: "Sat",
weekdayShortSun: "Sun", weekdayShortSun: "Sun",
dayOfWeek: "Day of Week",
dayOfMonth: "Day of Month", dayOfMonth: "Day of Month",
lastDay: "Last Day", lastDay: "Last Day",
lastDay1: "Last Day of Month", lastDay1: "Last Day of Month",

View File

@ -91,8 +91,8 @@
<option value="manual">{{ $t("strategyManual") }}</option> <option value="manual">{{ $t("strategyManual") }}</option>
<option value="single">Single Maintenance Window</option> <option value="single">Single Maintenance Window</option>
<option value="recurring-interval">{{ $t("Recurring") }} - {{ $t("recurringInterval") }}</option> <option value="recurring-interval">{{ $t("Recurring") }} - {{ $t("recurringInterval") }}</option>
<option value="recurring-weekday">{{ $t("Recurring") }} - Weekday</option> <option value="recurring-weekday">{{ $t("Recurring") }} - {{ $t("dayOfWeek") }}</option>
<option value="recurring-day-of-month">{{ $t("Recurring") }} - Day of Month</option> <option value="recurring-day-of-month">{{ $t("Recurring") }} - {{ $t("dayOfMonth") }}</option>
<option v-if="false" value="recurring-day-of-year">{{ $t("Recurring") }} - Day of Year</option> <option v-if="false" value="recurring-day-of-year">{{ $t("Recurring") }} - Day of Year</option>
</select> </select>
</div> </div>
@ -136,7 +136,7 @@
<template v-if="maintenance.strategy === 'recurring-weekday'"> <template v-if="maintenance.strategy === 'recurring-weekday'">
<div class="my-3"> <div class="my-3">
<label for="interval-day" class="form-label"> <label for="interval-day" class="form-label">
{{ $t("Weekday") }} {{ $t("dayOfWeek") }}
</label> </label>
<!-- Weekday Picker --> <!-- Weekday Picker -->
@ -201,6 +201,7 @@
:minDate="minDate" :minDate="minDate"
format="yyyy-MM-dd HH:mm:ss" format="yyyy-MM-dd HH:mm:ss"
modelType="yyyy-MM-dd HH:mm:ss" modelType="yyyy-MM-dd HH:mm:ss"
required
/> />
</div> </div>
</template> </template>
@ -297,9 +298,9 @@ export default {
value: 6, value: 6,
}, },
{ {
id: "weekday7", id: "weekday0",
langKey: "weekdayShortSun", langKey: "weekdayShortSun",
value: 7, value: 0,
}, },
], ],
}; };