Files
flutterApp/lib/features/devices/domain/entities/device_schedule_plan_entity.dart

144 lines
5.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// 定时作业计划(设备定时任务)业务实体。
///
/// 只表达业务概念(设备/路线/周期规则/生效区间),不含任何后端字段名与
/// 报文格式;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);
}