144 lines
5.0 KiB
Dart
144 lines
5.0 KiB
Dart
/// 定时作业计划(设备定时任务)业务实体。
|
||
///
|
||
/// 只表达业务概念(设备/路线/周期规则/生效区间),不含任何后端字段名与
|
||
/// 报文格式;entity → JSON 的组装封在 data 层(DeviceTaskDatasource)。
|
||
/// 对应接口:POST /iot/deviceTask/save(按天/周/月循环策略)。
|
||
library;
|
||
|
||
/// 循环策略:每天 / 每周指定星期 / 每月指定几号
|
||
enum WorkScheduleType { day, week, month }
|
||
|
||
/// 星期几(domain 自定义,不绑定 intl;value 与 DateTime.weekday 同口径 1-7)
|
||
enum Weekday { monday, tuesday, wednesday, thursday, friday, saturday, sunday }
|
||
|
||
extension WeekdayX on Weekday {
|
||
/// 1-7,周一=1,与 [DateTime.weekday] 对齐
|
||
int get value => index + 1;
|
||
}
|
||
|
||
/// 星期选择项(周一~周日),提交时由数据层映射为后端枚举字面量
|
||
const List<Weekday> kWorkScheduleWeekdays = <Weekday>[
|
||
Weekday.monday,
|
||
Weekday.tuesday,
|
||
Weekday.wednesday,
|
||
Weekday.thursday,
|
||
Weekday.friday,
|
||
Weekday.saturday,
|
||
Weekday.sunday,
|
||
];
|
||
|
||
/// 一条待创建的定时作业计划。
|
||
///
|
||
/// 时间拆成「日期 + 时分」:
|
||
/// - [startDate] / [endDate]:方案生效区间(仅日期部分有效,
|
||
/// 提交时格式化为开始日 00:00:00 ~ 结束日 23:59:59)
|
||
/// - [dayHour] / [dayMinute]:每日执行时刻
|
||
class DeviceSchedulePlanEntity {
|
||
const DeviceSchedulePlanEntity({
|
||
required this.deviceId,
|
||
required this.orgId,
|
||
required this.planName,
|
||
required this.routeId,
|
||
required this.siteId,
|
||
required this.type,
|
||
this.days = const <Weekday>[],
|
||
this.dates = const <int>[],
|
||
this.dayHour = 8,
|
||
this.dayMinute = 0,
|
||
required this.startDate,
|
||
required this.endDate,
|
||
});
|
||
|
||
/// 设备 SN(如 MC700-xxx,与任务下发接口同一口径)
|
||
final String deviceId;
|
||
|
||
/// 所属组织 ID(与场站同源,取自 SiteCubit 选中场站的 orgId)
|
||
final int orgId;
|
||
|
||
/// 定时任务方案名称
|
||
final String planName;
|
||
|
||
/// 作业路线 ID(路线库一条作业记录的主键,A 环境 = workRecord.id)
|
||
final int routeId;
|
||
|
||
/// 站点 ID(当前选中场站)
|
||
final int siteId;
|
||
|
||
final WorkScheduleType type;
|
||
|
||
/// type=week:选中星期集合(不能为空)
|
||
final List<Weekday> days;
|
||
|
||
/// type=month:每月执行日期集合,取值 1-31(不能为空;小月缺该日则当月跳过)
|
||
final List<int> dates;
|
||
|
||
/// 每日执行时刻
|
||
final int dayHour;
|
||
final int dayMinute;
|
||
|
||
/// 生效区间(仅日期部分有效)
|
||
final DateTime startDate;
|
||
final DateTime endDate;
|
||
|
||
/// 提交前的完整校验,返回 null = 通过。
|
||
///
|
||
/// 与后端入参校验规则逐条对齐,本地能拦的先拦(省一次失败往返):
|
||
/// 1. 必填项(设备/名称/路线/场站/组织);
|
||
/// 2. days / dates 数组非空(按周期类型);
|
||
/// 3. 结束时间必须晚于开始时间;
|
||
/// 4. 业务强校验:生效区间内必须存在至少一个满足周期规则、
|
||
/// 且执行时刻落在区间内的可执行日期;否则后端拒绝保存。
|
||
String? getValidationError() {
|
||
if (deviceId.isEmpty) return '请选择执行设备';
|
||
if (planName.trim().isEmpty) return '请填写方案名称';
|
||
if (routeId <= 0) return '请选择作业路线';
|
||
if (siteId <= 0 || orgId <= 0) return '请先在首页选择场站';
|
||
if (type == WorkScheduleType.week && days.isEmpty) {
|
||
return '按周执行需至少选择一个星期';
|
||
}
|
||
if (type == WorkScheduleType.month && dates.isEmpty) {
|
||
return '按月执行需至少选择一个日期';
|
||
}
|
||
if (dates.any((d) => d < 1 || d > 31)) {
|
||
return '每月执行日期取值范围为 1-31';
|
||
}
|
||
if (dayHour < 0 || dayHour > 23 || dayMinute < 0 || dayMinute > 59) {
|
||
return '执行时刻不合法';
|
||
}
|
||
if (_dayOf(endDate).isBefore(_dayOf(startDate))) {
|
||
return '结束日期必须晚于开始日期';
|
||
}
|
||
if (!hasExecutableTime) {
|
||
return '生效区间内没有满足周期规则的可执行日期,任务不会触发';
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// 业务强校验:逐天扫描 [startDate, endDate],
|
||
/// 存在「命中周期规则 且 执行时刻落在 [开始日 00:00, 结束日 24:00) 内」的一天即可执行。
|
||
/// 示例:有效期 11-19 ~ 11-20、week · days=[monday],区间内没有周一 → false。
|
||
bool get hasExecutableTime {
|
||
DateTime day = _dayOf(startDate);
|
||
final DateTime last = _dayOf(endDate);
|
||
while (!day.isAfter(last)) {
|
||
if (_matches(day)) {
|
||
// startTime 固定当天 00:00:00、finishTime 当天 23:59:59,
|
||
// 任意时刻的执行点在区间内等价于「日期落在 [start, end] 闭区间」
|
||
return true;
|
||
}
|
||
day = day.add(const Duration(days: 1));
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool _matches(DateTime day) => switch (type) {
|
||
WorkScheduleType.day => true,
|
||
WorkScheduleType.week => days.contains(
|
||
kWorkScheduleWeekdays[day.weekday - 1],
|
||
),
|
||
WorkScheduleType.month => dates.contains(day.day),
|
||
};
|
||
|
||
static DateTime _dayOf(DateTime d) => DateTime(d.year, d.month, d.day);
|
||
}
|