overtime_apply_controller.dart 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import 'dart:math';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'overtime_model.dart';
  4. import 'overtime_api.dart';
  5. class OvertimeApplyState {
  6. final OvertimeModel overtime;
  7. final bool isSubmitting;
  8. const OvertimeApplyState({required this.overtime, this.isSubmitting = false});
  9. OvertimeApplyState copyWith({OvertimeModel? overtime, bool? isSubmitting}) =>
  10. OvertimeApplyState(
  11. overtime: overtime ?? this.overtime,
  12. isSubmitting: isSubmitting ?? this.isSubmitting,
  13. );
  14. }
  15. class OvertimeApplyController extends StateNotifier<OvertimeApplyState> {
  16. final OvertimeApi _api;
  17. OvertimeApplyController(this._api)
  18. : super(
  19. OvertimeApplyState(
  20. overtime: OvertimeModel(
  21. id: '',
  22. applicationNo: '',
  23. applicantId: '',
  24. applicantName: '',
  25. deptId: '',
  26. deptName: '',
  27. otType: 'workday',
  28. compensationType: 'comp_leave',
  29. startTime: DateTime.now(),
  30. endTime: DateTime.now().add(const Duration(hours: 3)),
  31. netOtHours: 0.0,
  32. reason: '',
  33. createTime: DateTime.now(),
  34. updateTime: DateTime.now(),
  35. ),
  36. ),
  37. );
  38. void updateType(String t) =>
  39. state = state.copyWith(overtime: state.overtime.copyWith(otType: t));
  40. void updateCompensation(String c) {
  41. final newRatio = c == 'mixed'
  42. ? (state.overtime.compLeaveRatio ?? 0.5)
  43. : null;
  44. state = state.copyWith(
  45. overtime: state.overtime.copyWith(
  46. compensationType: c,
  47. compLeaveRatio: newRatio,
  48. ),
  49. );
  50. }
  51. void updateMixRatio(double v) {
  52. final ratio = (v / 10).round() * 10 / 100.0;
  53. state = state.copyWith(
  54. overtime: state.overtime.copyWith(compLeaveRatio: ratio),
  55. );
  56. }
  57. void updateStartTime(DateTime t) {
  58. state = state.copyWith(overtime: state.overtime.copyWith(startTime: t));
  59. _recalc();
  60. }
  61. void updateEndTime(DateTime t) {
  62. state = state.copyWith(overtime: state.overtime.copyWith(endTime: t));
  63. _recalc();
  64. }
  65. void updateReason(String r) =>
  66. state = state.copyWith(overtime: state.overtime.copyWith(reason: r));
  67. void _recalc() {
  68. final net = _calculateNetHours(
  69. state.overtime.startTime,
  70. state.overtime.endTime,
  71. );
  72. state = state.copyWith(
  73. overtime: state.overtime.copyWith(netOtHours: net),
  74. );
  75. }
  76. /// 净工时 = (结束-开始) - 午餐(12:00-13:00) - 晚餐(18:00-18:30)
  77. double _calculateNetHours(DateTime start, DateTime end) {
  78. if (!end.isAfter(start)) return 0;
  79. final totalMinutes = end.difference(start).inMinutes;
  80. int deduction = 0;
  81. // 午餐盲区: 12:00-13:00
  82. final lunchS = DateTime(start.year, start.month, start.day, 12, 0);
  83. final lunchE = DateTime(start.year, start.month, start.day, 13, 0);
  84. deduction += _overlapMinutes(start, end, lunchS, lunchE);
  85. // 晚餐盲区: 18:00-18:30
  86. final dinnerS = DateTime(start.year, start.month, start.day, 18, 0);
  87. final dinnerE = DateTime(start.year, start.month, start.day, 18, 30);
  88. deduction += _overlapMinutes(start, end, dinnerS, dinnerE);
  89. return max(0, totalMinutes - deduction) / 60.0;
  90. }
  91. int _overlapMinutes(DateTime aS, DateTime aE, DateTime bS, DateTime bE) {
  92. final s = aS.isAfter(bS) ? aS : bS;
  93. final e = aE.isBefore(bE) ? aE : bE;
  94. if (!e.isAfter(s)) return 0;
  95. return e.difference(s).inMinutes;
  96. }
  97. bool validate() {
  98. final o = state.overtime;
  99. if (o.reason.trim().isEmpty) return false;
  100. if (!o.endTime.isAfter(o.startTime)) return false;
  101. if (o.netOtHours <= 0) return false;
  102. return true;
  103. }
  104. Future<bool> submit() async {
  105. if (!validate()) return false;
  106. state = state.copyWith(isSubmitting: true);
  107. try {
  108. await _api.submit(state.overtime.copyWith(status: 'pending'));
  109. return true;
  110. } catch (_) {
  111. return false;
  112. } finally {
  113. state = state.copyWith(isSubmitting: false);
  114. }
  115. }
  116. Future<bool> saveDraft() async {
  117. state = state.copyWith(isSubmitting: true);
  118. try {
  119. await _api.saveDraft(state.overtime);
  120. return true;
  121. } catch (_) {
  122. return false;
  123. } finally {
  124. state = state.copyWith(isSubmitting: false);
  125. }
  126. }
  127. }
  128. final overtimeApplyProvider = StateNotifierProvider.autoDispose
  129. .family<OvertimeApplyController, OvertimeApplyState, String?>((
  130. ref,
  131. editId,
  132. ) {
  133. return OvertimeApplyController(ref.read(overtimeApiProvider));
  134. });