fast_check_in_service.dart 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:geolocation/models/position.dart';
  4. import 'package:get/get.dart';
  5. import 'package:o2oa_all_platform/common/extension/index.dart';
  6. import '../api/index.dart';
  7. import '../models/index.dart';
  8. import '../utils/index.dart';
  9. import '../widgets/index.dart';
  10. import 'shared_preference_service.dart';
  11. /// 极速打卡功能
  12. class FastCheckInService {
  13. // 单例
  14. static final FastCheckInService instance = FastCheckInService._internal();
  15. factory FastCheckInService() => instance;
  16. FastCheckInService._internal();
  17. // 定时器
  18. Timer? _timer;
  19. /// geolocator 定位
  20. GeolocatorHelper? _geoHelper;
  21. /// 当前定位信息
  22. GeoPosition? _myPosition;
  23. // 上班是否开启极速打卡
  24. bool _onDutyFastCheckInEnable = false;
  25. // 下班是否开启极速打卡
  26. bool _offDutyFastCheckInEnable = false;
  27. // 是否正在运行中
  28. bool _isRunning = false;
  29. // 运行时间 超过 2 分钟就结束
  30. int _runningTime = 0;
  31. // 工作地点
  32. final List<WorkPlaceList> _workplaceList = [];
  33. WorkPlaceList? _nearLeastWorkplace; // 最近的打卡地点
  34. bool _isInCheckInPositionRange = false; // 最近的打卡地点是否在打卡范围内
  35. AttendanceV2Record? _currentCheckItem; // 当前打卡对象
  36. bool _isPosting = false;
  37. /// 开始运行极速打卡
  38. void start() async {
  39. if (!Platform.isAndroid && !Platform.isIOS) return;
  40. if (_isRunning) return;
  41. OLogger.i('开始极速打卡!');
  42. _isRunning = true;
  43. final config = await AttendanceAssembleControlService.to.config();
  44. if (config != null &&
  45. (config.onDutyFastCheckInEnable == true ||
  46. config.offDutyFastCheckInEnable == true)) {
  47. _onDutyFastCheckInEnable = config.onDutyFastCheckInEnable ?? false;
  48. _offDutyFastCheckInEnable = config.offDutyFastCheckInEnable ?? false;
  49. _startTimer();
  50. _loadPreCheckInData();
  51. } else {
  52. OLogger.d('没有配置极速打卡!');
  53. _isRunning = false;
  54. }
  55. }
  56. /// 结束运行极速打卡
  57. void stop() {
  58. OLogger.i('结束极速打卡!');
  59. _timer?.cancel();
  60. _timer = null;
  61. _geoHelper?.stopLocation();
  62. _geoHelper = null;
  63. _myPosition = null;
  64. _nearLeastWorkplace = null;
  65. _currentCheckItem = null;
  66. _runningTime = 0;
  67. _isRunning = false;
  68. }
  69. /// 开始定位
  70. void _startLocation() async {
  71. // 初始化定位程序
  72. _geoHelper = GeolocatorHelper(callback: (position){
  73. _myPosition = position;
  74. _calNearestWorkplace();
  75. });
  76. _geoHelper?.startLocation();
  77. }
  78. /// 开始计时 2 分钟 没有运行结束就 stop
  79. void _startTimer() {
  80. /// 间隔5秒
  81. _timer = Timer.periodic(const Duration(milliseconds: 5000), (timer) {
  82. _runningTime += 5000;
  83. if (_runningTime >= 2 * 60 * 1000) {
  84. stop();
  85. }
  86. });
  87. }
  88. /// 获取打卡初始化数据
  89. /// 先判断数据业务 除了 固定班制 排班制 其他不进行自动打卡
  90. void _loadPreCheckInData() async {
  91. final data = await AttendanceAssembleControlService.to.loadPreCheckInData();
  92. if (data == null) {
  93. stop();
  94. return;
  95. }
  96. bool needCheck = data.canCheckIn ?? false; //今天是否还需要打卡
  97. if (needCheck) {
  98. // 打卡记录
  99. final checkItemList = data.checkItemList ?? [];
  100. // 是否最后一条已经打卡过的数据
  101. _currentCheckItem = checkItemList
  102. .firstWhereOrNull((element) => element.checkInResult == 'PreCheckIn');
  103. // 固定班制 排班制 并且有班次 id 才能进行极速打卡
  104. if (_currentCheckItem != null &&
  105. _currentCheckItem?.shiftId?.isNotEmpty == true &&
  106. (_currentCheckItem?.groupCheckType == "1" ||
  107. _currentCheckItem?.groupCheckType == "3")) {
  108. needCheck = true;
  109. } else {
  110. needCheck = false;
  111. }
  112. }
  113. // 如果不能打卡了 就结束
  114. if (!needCheck) {
  115. OLogger.i("今天无需打卡, 全部停止。");
  116. stop();
  117. } else {
  118. // 开始启动定位
  119. _startLocation();
  120. // 开始查询工作场所数据
  121. if (data.workPlaceList != null && data.workPlaceList!.isNotEmpty) {
  122. _workplaceList
  123. ..clear()
  124. ..addAll(data.workPlaceList!);
  125. // 计算最近的打卡地点
  126. _calNearestWorkplace();
  127. }
  128. _tryCheckIn();
  129. }
  130. }
  131. /// 尝试极速打卡
  132. void _tryCheckIn() {
  133. if (_myPosition == null || _workplaceList.isEmpty) {
  134. OLogger.e('可能还未定位到。。。。');
  135. return;
  136. }
  137. if (_nearLeastWorkplace != null &&
  138. _currentCheckItem != null &&
  139. _isInCheckInPositionRange) {
  140. // 上班打卡
  141. if ((_currentCheckItem?.checkInType == 'OnDuty' &&
  142. _onDutyFastCheckInEnable) ||
  143. (_currentCheckItem?.checkInType == 'OffDuty' &&
  144. _offDutyFastCheckInEnable)) {
  145. // 是否在打卡限制时间内
  146. final dutyTime = _currentCheckItem?.preDutyTime ?? '';
  147. final preBeforeTime = _currentCheckItem?.preDutyTimeBeforeLimit ?? '';
  148. final preAfterTime = _currentCheckItem?.preDutyTimeAfterLimit ?? '';
  149. if (!_checkLimitTime(_currentCheckItem?.checkInType ?? '', dutyTime,
  150. preBeforeTime, preAfterTime)) {
  151. OLogger.e("不在限制时间内!!!!");
  152. return;
  153. }
  154. _postCheckIn(_currentCheckItem!, _nearLeastWorkplace!.id!);
  155. } else {
  156. OLogger.i("当前打卡类型:${_currentCheckItem?.checkInType} 不允许极速打卡!");
  157. stop();
  158. }
  159. }
  160. }
  161. // 打卡
  162. void _postCheckIn(AttendanceV2Record record, String workPlaceId) async {
  163. if (_isPosting) return;
  164. _isPosting = true;
  165. var post = CheckPost(
  166. recordId: record.id,
  167. checkInType: record.checkInType,
  168. workPlaceId: workPlaceId,
  169. fieldWork: false,
  170. signDescription: '',
  171. latitude: '${_myPosition!.latitude}',
  172. longitude: '${_myPosition!.longitude}',
  173. recordAddress: '${_myPosition!.address}',
  174. sourceType: 'FAST_CHECK');
  175. var deviceType = SharedPreferenceService.to
  176. .getString(SharedPreferenceService.currentDeviceTypeKey);
  177. post.sourceDevice = deviceType;
  178. final iddata =
  179. await AttendanceAssembleControlService.to.checkInPostV2(post);
  180. if (iddata != null) {
  181. OLogger.i('极速打卡成功!');
  182. // 发送通知
  183. O2OverlayEntryDialog.instance.openFastCheckInDialog(
  184. 'attendance_fast_checkin_notify_title'.tr,
  185. 'attendance_fast_checkin_notify_content'
  186. .trArgs([(iddata.recordDate ?? '')]));
  187. stop();
  188. }
  189. _isPosting = false;
  190. }
  191. /// 是否有打卡时间限制
  192. bool _checkLimitTime(String checkInType, String dutyTime,
  193. String preDutyTimeBeforeLimit, String preDutyTimeAfterLimit) {
  194. final now = DateTime.now();
  195. final today = now.ymd();
  196. final dutyTimeDate = DateTime.tryParse('$today $dutyTime:00');
  197. if (dutyTimeDate == null) {
  198. OLogger.e('时间解析出差,$today $dutyTime');
  199. return false;
  200. }
  201. // 极速打卡开始时间
  202. var fastCheckInBeforeLimit = dutyTimeDate;
  203. if (checkInType == 'OnDuty') {
  204. //上班前一个小时
  205. fastCheckInBeforeLimit = dutyTimeDate.addMinutes(-60);
  206. }
  207. // 极速打卡结束时间
  208. var fastCheckInAfterLimit = dutyTimeDate.addMinutes(60); // 下班后1个小时
  209. if (checkInType == 'OnDuty') {
  210. fastCheckInAfterLimit = dutyTimeDate;
  211. }
  212. if (preDutyTimeBeforeLimit.isNotEmpty) {
  213. final beforeTime = DateTime.tryParse("$today $preDutyTimeBeforeLimit:00");
  214. if (beforeTime != null && fastCheckInBeforeLimit.isBefore(beforeTime)) {
  215. fastCheckInBeforeLimit = beforeTime;
  216. }
  217. }
  218. if (preDutyTimeAfterLimit.isNotEmpty) {
  219. final afterTime = DateTime.tryParse("$today $preDutyTimeAfterLimit:00");
  220. if (afterTime != null && fastCheckInAfterLimit.isAfter(afterTime)) {
  221. fastCheckInAfterLimit = afterTime;
  222. }
  223. }
  224. OLogger.d(
  225. "打卡时间,$dutyTimeDate 极速打卡开始时间:$fastCheckInBeforeLimit 极速打卡结束时间: $fastCheckInAfterLimit");
  226. if (fastCheckInBeforeLimit.isBefore(now) &&
  227. fastCheckInAfterLimit.isAfter(now)) {
  228. return true;
  229. }
  230. return false;
  231. }
  232. /// 计算最近的打卡地点
  233. void _calNearestWorkplace() async {
  234. if (_myPosition == null) {
  235. OLogger.e('还没有定位成功!');
  236. return;
  237. }
  238. _isInCheckInPositionRange = false;
  239. if (_workplaceList.isNotEmpty) {
  240. for (final workplace in _workplaceList) {
  241. OLogger.d('工作地点: ${workplace.latitude} ${workplace.longitude}');
  242. double startLatitude = _myPosition?.latitude ?? 0;
  243. double startLongitude = _myPosition?.longitude ?? 0;
  244. final lngLat = workplace.getLngLat();
  245. double endLatitude = lngLat[1];
  246. double endLongitude = lngLat[0];
  247. if (startLatitude != 0 && startLongitude != 0 && endLatitude != 0 && endLongitude != 0) {
  248. OLogger.d('坐标 workplace endLatitude:$endLatitude endLongitude:$endLongitude myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  249. final distance = _geoHelper?.distanceInMeters(startLatitude, startLongitude, endLatitude, endLongitude) ?? 0;
  250. int range = workplace.errorRange ?? 100; // 默认 100 米
  251. OLogger.d('距离计算:$distance $range');
  252. if (distance != 0 && distance <= range) {
  253. // 找到了范围内的打卡地点
  254. _nearLeastWorkplace = workplace;
  255. _isInCheckInPositionRange = true;
  256. _tryCheckIn();
  257. break;
  258. }
  259. } else {
  260. OLogger.e(' 错误的坐标 workplace endLatitude:$endLatitude endLongitude:$endLongitude myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  261. }
  262. }
  263. OLogger.d('找到最近的打卡点? ${_nearLeastWorkplace?.placeName ?? '无'}');
  264. }
  265. }
  266. }