import 'dart:math'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'overtime_model.dart'; import 'overtime_api.dart'; class OvertimeApplyState { final OvertimeModel overtime; final bool isSubmitting; const OvertimeApplyState({required this.overtime, this.isSubmitting = false}); OvertimeApplyState copyWith({OvertimeModel? overtime, bool? isSubmitting}) => OvertimeApplyState( overtime: overtime ?? this.overtime, isSubmitting: isSubmitting ?? this.isSubmitting, ); } class OvertimeApplyController extends StateNotifier { final OvertimeApi _api; OvertimeApplyController(this._api) : super( OvertimeApplyState( overtime: OvertimeModel( id: '', applicationNo: '', applicantId: '', applicantName: '', deptId: '', deptName: '', otType: 'workday', compensationType: 'comp_leave', startTime: DateTime.now(), endTime: DateTime.now().add(const Duration(hours: 3)), netOtHours: 0.0, reason: '', createTime: DateTime.now(), updateTime: DateTime.now(), ), ), ); void updateType(String t) => state = state.copyWith(overtime: state.overtime.copyWith(otType: t)); void updateCompensation(String c) { final newRatio = c == 'mixed' ? (state.overtime.compLeaveRatio ?? 0.5) : null; state = state.copyWith( overtime: state.overtime.copyWith( compensationType: c, compLeaveRatio: newRatio, ), ); } void updateMixRatio(double v) { final ratio = (v / 10).round() * 10 / 100.0; state = state.copyWith( overtime: state.overtime.copyWith(compLeaveRatio: ratio), ); } void updateStartTime(DateTime t) { state = state.copyWith(overtime: state.overtime.copyWith(startTime: t)); _recalc(); } void updateEndTime(DateTime t) { state = state.copyWith(overtime: state.overtime.copyWith(endTime: t)); _recalc(); } void updateReason(String r) => state = state.copyWith(overtime: state.overtime.copyWith(reason: r)); void _recalc() { final net = _calculateNetHours( state.overtime.startTime, state.overtime.endTime, ); state = state.copyWith( overtime: state.overtime.copyWith(netOtHours: net), ); } /// 净工时 = (结束-开始) - 午餐(12:00-13:00) - 晚餐(18:00-18:30) double _calculateNetHours(DateTime start, DateTime end) { if (!end.isAfter(start)) return 0; final totalMinutes = end.difference(start).inMinutes; int deduction = 0; // 午餐盲区: 12:00-13:00 final lunchS = DateTime(start.year, start.month, start.day, 12, 0); final lunchE = DateTime(start.year, start.month, start.day, 13, 0); deduction += _overlapMinutes(start, end, lunchS, lunchE); // 晚餐盲区: 18:00-18:30 final dinnerS = DateTime(start.year, start.month, start.day, 18, 0); final dinnerE = DateTime(start.year, start.month, start.day, 18, 30); deduction += _overlapMinutes(start, end, dinnerS, dinnerE); return max(0, totalMinutes - deduction) / 60.0; } int _overlapMinutes(DateTime aS, DateTime aE, DateTime bS, DateTime bE) { final s = aS.isAfter(bS) ? aS : bS; final e = aE.isBefore(bE) ? aE : bE; if (!e.isAfter(s)) return 0; return e.difference(s).inMinutes; } bool validate() { final o = state.overtime; if (o.reason.trim().isEmpty) return false; if (!o.endTime.isAfter(o.startTime)) return false; if (o.netOtHours <= 0) return false; return true; } Future submit() async { if (!validate()) return false; state = state.copyWith(isSubmitting: true); try { await _api.submit(state.overtime.copyWith(status: 'pending')); return true; } catch (_) { return false; } finally { state = state.copyWith(isSubmitting: false); } } Future saveDraft() async { state = state.copyWith(isSubmitting: true); try { await _api.saveDraft(state.overtime); return true; } catch (_) { return false; } finally { state = state.copyWith(isSubmitting: false); } } } final overtimeApplyProvider = StateNotifierProvider.autoDispose .family(( ref, editId, ) { return OvertimeApplyController(ref.read(overtimeApiProvider)); });