controller.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  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 '../../../../common/api/index.dart';
  7. import '../../../../common/models/index.dart';
  8. import '../../../../common/services/index.dart';
  9. import '../../../../common/utils/index.dart';
  10. import '../../../../common/widgets/index.dart';
  11. import 'index.dart';
  12. class OldCheckInController extends GetxController {
  13. OldCheckInController();
  14. final state = OldCheckInState();
  15. Record? lastRecord;
  16. // 外勤打卡,需要输入说明
  17. final TextEditingController outsideCheckInDescInputController =
  18. TextEditingController();
  19. // 定时器
  20. Timer? _timer;
  21. GeolocatorHelper? _geoHelper;
  22. /// 当前定位信息
  23. GeoPosition? myPosition;
  24. // 工作地点
  25. List<WorkplaceInfo> workplaceList = [];
  26. WorkplaceInfo? nearLeastWorkplace; // 最近的打卡地点
  27. /// 在 onInit() 之后调用 1 帧。这是进入的理想场所
  28. @override
  29. void onReady() {
  30. /// 循环执行
  31. /// 间隔1秒
  32. _timer = Timer.periodic(const Duration(milliseconds: 1000), (timer) {
  33. ///定时任务
  34. _updateTimePerSecond();
  35. });
  36. // 初始化定位程序
  37. _geoHelper = GeolocatorHelper(callback: (position){
  38. myPosition = position;
  39. _calNearestWorkplace();
  40. });
  41. _geoHelper?.startLocation();
  42. // 初始化数据
  43. _loadAllWorkPalce();
  44. _loadRecordList();
  45. super.onReady();
  46. }
  47. /// 在 [onDelete] 方法之前调用。
  48. @override
  49. void onClose() {
  50. // 取消定时器
  51. _timer?.cancel();
  52. _geoHelper?.stopLocation();
  53. super.onClose();
  54. }
  55. _loadAllWorkPalce() async {
  56. final result = await AttendanceAssembleControlService.to.listAllAttendanceWorkPlace();
  57. if (result != null) {
  58. workplaceList.clear();
  59. workplaceList.addAll(result);
  60. }
  61. }
  62. _loadRecordList() async {
  63. final result = await AttendanceAssembleControlService.to.listMyRecords();
  64. if (result != null) {
  65. final rList = result.records ?? [];
  66. if (rList.isNotEmpty) {
  67. lastRecord = rList.last;
  68. }
  69. state.recordList.clear();
  70. var unCheckNumber = 0;
  71. final list = result.scheduleInfos?.map((s) {
  72. //是否已打卡
  73. final record = rList.firstWhereOrNull((element) => element.checkinType == s.checkinType);
  74. if (record != null) {
  75. s.checkinStatus = 'attendance_record_check_time'.tr;
  76. s.checkinTime = record.signTime;
  77. s.recordId = record.id;
  78. if (lastRecord != null && lastRecord?.id == record.id) {
  79. s.lastRecord = record;
  80. }
  81. unCheckNumber = 0; //清零
  82. }else{
  83. s.checkinStatus = 'attendance_uncheckin'.tr;
  84. unCheckNumber++;
  85. }
  86. return s;
  87. }).toList();
  88. state.recordList.addAll(list??[]);
  89. state.todayNeedCheck = (unCheckNumber > 0) ;
  90. } else {
  91. state.todayNeedCheck = false;
  92. }
  93. }
  94. /// 查找最近的打卡地点
  95. void _calNearestWorkplace() async {
  96. if (myPosition != null && workplaceList.isNotEmpty) {
  97. double minDistance = -1.0;
  98. for (var workplace in workplaceList) {
  99. OLogger.d('工作地点: ${workplace.latitude} ${workplace.longitude}');
  100. double startLatitude = myPosition?.latitude ?? 0;
  101. double startLongitude = myPosition?.longitude ?? 0;
  102. double endLatitude = double.tryParse(workplace.latitude ?? '0') ?? 0;
  103. double endLongitude = double.tryParse(workplace.longitude ?? '0') ?? 0;
  104. if (startLatitude != 0 && startLongitude != 0 && endLatitude != 0 && endLongitude != 0) {
  105. OLogger.d('坐标 workplace endLatitude:$endLatitude endLongitude:$endLongitude myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  106. final wgs84 = BaiduLocationTransformHelper.bd09towgs84(endLongitude, endLatitude);
  107. final wgs84Lng = wgs84[0];
  108. final wgs84Lat = wgs84[1];
  109. OLogger.d('转化后的gps 坐标 workplace wgs84Lat:$wgs84Lat wgs84Lng:$wgs84Lng myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  110. final distance = _geoHelper?.distanceInMeters(startLatitude, startLongitude, wgs84Lat, wgs84Lng) ?? 0;
  111. int range = workplace.errorRange ?? 100; // 默认 100 米
  112. OLogger.d('距离计算:$distance $range');
  113. if (minDistance == -1.0) {
  114. minDistance = distance;
  115. nearLeastWorkplace = workplace;
  116. } else {
  117. if (minDistance > distance) {
  118. minDistance = distance;
  119. nearLeastWorkplace = workplace;
  120. }
  121. }
  122. } else {
  123. OLogger.e(' 错误的坐标 workplace endLatitude:$endLatitude endLongitude:$endLongitude myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  124. }
  125. }
  126. OLogger.d('找到最近的打卡点? ${nearLeastWorkplace?.placeName ?? '无'}');
  127. _checkIsInWorkplace();
  128. } else {
  129. OLogger.e('还没有定位成功,或没有查询到工作列表!');
  130. }
  131. }
  132. /// 检查当前定位是否在打卡地点的范围内
  133. void _checkIsInWorkplace() async {
  134. if (myPosition != null && nearLeastWorkplace != null) {
  135. double startLatitude = myPosition?.latitude ?? 0;
  136. double startLongitude = myPosition?.longitude ?? 0;
  137. double endLatitude = double.tryParse(nearLeastWorkplace?.latitude ?? '0') ?? 0;
  138. double endLongitude = double.tryParse(nearLeastWorkplace?.longitude ?? '0') ?? 0;
  139. if (startLatitude != 0 && startLongitude != 0 && endLatitude != 0 && endLongitude != 0) {
  140. OLogger.d('坐标 workplace endLatitude:$endLatitude endLongitude:$endLongitude myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  141. final wgs84 = BaiduLocationTransformHelper.bd09towgs84(endLongitude, endLatitude);
  142. final wgs84Lng = wgs84[0];
  143. final wgs84Lat = wgs84[1];
  144. OLogger.d('转化后的gps 坐标 workplace wgs84Lat:$wgs84Lat wgs84Lng:$wgs84Lng myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  145. final distance = _geoHelper?.distanceInMeters(startLatitude, startLongitude, wgs84Lat, wgs84Lng) ?? 0;
  146. int range = nearLeastWorkplace?.errorRange ?? 100; // 默认 100 米
  147. if (distance != 0 && distance <= range) {
  148. state.currentAddress = nearLeastWorkplace!.placeName;
  149. state.isInCheckInPositionRange = true;
  150. } else {
  151. state.currentAddress = 'attendance_checkin_not_in_workplace_error'.tr;
  152. state.isInCheckInPositionRange = false;
  153. }
  154. } else {
  155. OLogger.e(' 错误的坐标 workplace endLatitude:$endLatitude endLongitude:$endLongitude myPosition startLatitude: $startLatitude startLongitude: $startLongitude');
  156. }
  157. }
  158. }
  159. /// 每秒更新一次时间
  160. void _updateTimePerSecond() {
  161. state.currentTime = DateTime.now().hms();
  162. }
  163. ///
  164. /// 打卡
  165. ///
  166. void clickCheckIn() {
  167. OLogger.d('点击打卡');
  168. if (!state.todayNeedCheck) {
  169. OLogger.e('今日不需要打卡了,已经完成!');
  170. return;
  171. }
  172. _checkinClickOrUpdate(recordType: _calCheckType());
  173. }
  174. void clickUpdateRecord(Feature info) {
  175. final context = Get.context;
  176. if(context == null) {
  177. return;
  178. }
  179. O2UI.showConfirm(context, 'attendance_update_confirm_content'.tr, okPressed: (){
  180. _checkinClickOrUpdate(recordType: info.checkinType, recordId: info.lastRecord?.id);
  181. });
  182. }
  183. String _calCheckType() {
  184. final checkItem = state.recordList.firstWhereOrNull((element) => element.checkinStatus == 'attendance_uncheckin'.tr);
  185. if (checkItem != null) {
  186. return checkItem.checkinType ?? '';
  187. }
  188. return '';
  189. }
  190. ///
  191. /// 打卡处理
  192. ///
  193. /// @param recordId 更新打卡的id
  194. /// @param recordType 更新打卡的类型
  195. void _checkinClickOrUpdate({String? recordId, String? recordType}) {
  196. if (myPosition == null) {
  197. Loading.toast('attendance_checkin_need_location'.tr);
  198. return;
  199. }
  200. if (state.isInCheckInPositionRange && nearLeastWorkplace != null) {
  201. // 正常打卡
  202. OLogger.d('正常打卡!');
  203. // 计算checkin type
  204. try {
  205. String checkType = recordType ?? '';
  206. String id = recordId ?? '';
  207. _postCheckIn('${myPosition!.latitude}', '${myPosition!.longitude}',
  208. '${myPosition!.address}', checkType,
  209. workPlaceId: nearLeastWorkplace!.id ?? '',
  210. workAddress: nearLeastWorkplace!.placeName ?? '',
  211. id: id);
  212. } catch (e) {
  213. OLogger.e(e);
  214. Loading.toast('attendance_checkin_type_empty'.tr);
  215. }
  216. } else {
  217. // 外勤打开
  218. OLogger.d('外勤打卡');
  219. _outsideCheckIn(recordId: recordId, recordType: recordType);
  220. }
  221. }
  222. /// 外勤打卡
  223. void _outsideCheckIn({String? recordId, String? recordType}) async {
  224. final context = Get.context;
  225. if(context == null) {
  226. return;
  227. }
  228. // 确认外勤打卡 dialog
  229. var result = await O2UI.showCustomDialog(
  230. context,
  231. 'attendance_checkin_outside_dialog_title'.tr,
  232. TextField(
  233. controller: outsideCheckInDescInputController,
  234. maxLines: 1,
  235. style: Theme.of(context).textTheme.bodyMedium,
  236. keyboardType: TextInputType.text,
  237. textInputAction: TextInputAction.done,
  238. decoration: InputDecoration(
  239. hintText: 'attendance_checkin_outside_dialog_hint'.tr,
  240. ),
  241. ));
  242. OLogger.d('外勤打卡,返回结果$result');
  243. if (result != null && result == O2DialogAction.positive) {
  244. var desc = outsideCheckInDescInputController.text;
  245. // 计算checkin type
  246. try {
  247. _postCheckIn('${myPosition!.latitude}', '${myPosition!.longitude}',
  248. '${myPosition!.address}', recordType ?? '',
  249. signDesc: desc, isExternal: true, id: recordId?? '');
  250. } catch (e) {
  251. OLogger.e(e);
  252. Loading.toast('attendance_checkin_type_empty'.tr);
  253. }
  254. }
  255. }
  256. ///
  257. /// 提交打卡
  258. ///
  259. void _postCheckIn(
  260. String latitude, String longitude, String addrStr, String checkType,
  261. {String workPlaceId = '',
  262. String signDesc = '',
  263. String id = '',
  264. bool isExternal = false,
  265. String workAddress = ''}) async {
  266. if (isExternal) {
  267. // 外勤打开
  268. if (signDesc.isEmpty) {
  269. Loading.toast('attendance_outside_need_desc'.tr);
  270. return;
  271. }
  272. }
  273. Loading.show();
  274. final now = DateTime.now();
  275. var post = MobileCheckPost(
  276. latitude: latitude,
  277. longitude: longitude,
  278. recordAddress: addrStr,
  279. workAddress: workPlaceId,
  280. checkin_type: checkType,
  281. isExternal: isExternal,
  282. description: signDesc,
  283. signTime: now.hms(),
  284. recordDateString: now.ymd()
  285. );
  286. if (id.isNotEmpty) {
  287. post.id = id;
  288. }
  289. var deviceType = SharedPreferenceService.to.getString(SharedPreferenceService.currentDeviceTypeKey);
  290. post.optMachineType = deviceType;
  291. var iddata = await AttendanceAssembleControlService.to.checkInPost(post);
  292. if (iddata != null) {
  293. Loading.dismiss();
  294. _loadRecordList();
  295. }
  296. }
  297. }