data_sync.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import 'dart:convert';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:get/get.dart';
  4. import 'package:uuid/uuid.dart';
  5. import 'package:vital_local_database/core/interface/queryable.dart';
  6. import 'package:vitalapp/architecture/storage/text_storage.dart';
  7. import 'package:vitalapp/database/db.dart';
  8. import 'package:vitalapp/database/entities/defines.dart';
  9. import 'package:vitalapp/database/entities/diagnosis.dart';
  10. import 'package:vitalapp/database/entities/followup.dart';
  11. import 'package:vitalapp/database/entities/patient.dart';
  12. import 'package:vitalapp/managers/adapters/offline/patient.dart';
  13. import 'package:vitalapp/managers/interfaces/models/common.dart';
  14. import 'package:vitalapp/managers/interfaces/models/data_sync.dart';
  15. import 'package:vitalapp/rpc.dart';
  16. import 'package:vitalapp/store/store.dart';
  17. import 'interfaces/data_sync.dart';
  18. import 'package:fis_common/logger/logger.dart';
  19. import 'interfaces/record_data_cache.dart';
  20. class DataSyncManager implements IDataSyncManager {
  21. String get userCode => Store.user.userCode!;
  22. @override
  23. Future<PagedDataCollection<PatientEntity>> getPatientPagedList(
  24. int pageIndex, {
  25. int pageSize = 50,
  26. List<OfflineDataSyncState>? syncStates,
  27. }) async {
  28. try {
  29. final query = db.repositories.patient.queryable.where((x) {
  30. final list = <IDbColumnCondition>[];
  31. list.add(x.isValid.equals(true));
  32. list.add(x.userCode.equals(Store.user.userCode)); //添加用户code
  33. if (syncStates != null && syncStates.isNotEmpty) {
  34. if (syncStates.length == 1) {
  35. list.add(x.overallSyncState.equals(syncStates[0]));
  36. } else {
  37. list.add(x.overallSyncState.inEqueals(syncStates));
  38. }
  39. }
  40. return list;
  41. });
  42. final count = await query.count();
  43. final offset = pageSize * (pageIndex - 1);
  44. final entities = await query.offset(offset).limit(pageSize).toList();
  45. return PagedDataCollection<PatientEntity>(
  46. totalCount: count,
  47. pageIndex: pageIndex,
  48. pageSize: pageSize,
  49. data: entities,
  50. );
  51. } catch (e) {
  52. logger.e("DataSyncManager getPatientPagedList error.", e);
  53. return PagedDataCollection<PatientEntity>(
  54. totalCount: 0,
  55. pageIndex: pageIndex,
  56. pageSize: pageSize,
  57. data: [],
  58. );
  59. }
  60. }
  61. @override
  62. Future<List<PatientEntity>> getPatientWaitUploadAllList() async {
  63. final waitList = await db.repositories.patient.queryable.where((x) {
  64. final list = <IDbColumnCondition>[];
  65. list.add(x.isValid.equals(true));
  66. list.add(x.userCode.equals(Store.user.userCode));
  67. list.add(x.overallSyncState.equals(OfflineDataSyncState.wait));
  68. return list;
  69. }).toList();
  70. final failList = await db.repositories.patient.queryable.where((x) {
  71. final list = <IDbColumnCondition>[];
  72. list.add(x.isValid.equals(true));
  73. list.add(x.userCode.equals(Store.user.userCode));
  74. list.add(x.overallSyncState.equals(OfflineDataSyncState.fail));
  75. return list;
  76. }).toList();
  77. // TODO: 暂时未支持 Where in ,暂时先分开查
  78. return [...waitList, ...failList];
  79. }
  80. @override
  81. Future<bool> syncPatientAllData(String patientCode) async {
  82. // TODO: 后续加上事务支持
  83. final patientEntity =
  84. await db.repositories.patient.singleByCode(patientCode);
  85. if (patientEntity == null) {
  86. return false;
  87. }
  88. bool isSyncCompleted = false;
  89. try {
  90. final patientResult = await syncPatient(patientEntity);
  91. if (!patientResult) {
  92. return false;
  93. }
  94. if (patientEntity.diagnosisCount > 0) {
  95. final diagnosisSyncCount = await syncPatientDiagnosis(patientCode);
  96. patientEntity.diagnosisCount -= diagnosisSyncCount;
  97. }
  98. if (patientEntity.followUpCount > 0) {
  99. final followUpSyncCount = await syncPatientFollowUp(patientCode);
  100. patientEntity.followUpCount -= followUpSyncCount;
  101. }
  102. isSyncCompleted = _checkPatientSyncCompleted(patientEntity);
  103. if (isSyncCompleted) {
  104. patientEntity.overallSyncState = OfflineDataSyncState.success;
  105. } else {
  106. patientEntity.overallSyncState = OfflineDataSyncState.fail;
  107. }
  108. } catch (e) {
  109. logger.e(
  110. "DataSyncManager syncPatientAllData[during sync each] error.", e);
  111. // TODO: 后续可加上失败原因字段
  112. patientEntity.overallSyncState = OfflineDataSyncState.fail;
  113. return false;
  114. }
  115. // 更新居民记录
  116. try {
  117. final ret = await db.repositories.patient.update(patientEntity);
  118. return isSyncCompleted && ret > 0;
  119. } catch (e) {
  120. logger.e(
  121. "DataSyncManager syncPatientAllData[during update overall state] error.",
  122. e);
  123. return false;
  124. }
  125. }
  126. /// 校验同步是否完成
  127. bool _checkPatientSyncCompleted(PatientEntity entity) {
  128. if (entity.examCount > 0) return false;
  129. if (entity.diagnosisCount > 0) return false;
  130. if (entity.followUpCount > 0) return false;
  131. if (entity.syncState != OfflineDataSyncState.success) return false;
  132. return true;
  133. }
  134. @override
  135. Future<bool> syncPatient(PatientEntity entity) async {
  136. try {
  137. final jsonMap = jsonDecode(entity.dataJson);
  138. final dto = PatientDTO.fromJson(jsonMap);
  139. bool syncResult = false;
  140. if (entity.syncType == OfflineDataSyncType.create) {
  141. final request = PatientDtoConverter.dto2Create(dto);
  142. request.token = Store.user.token;
  143. final code = await rpc.vitalPatient.createPatientAsync(request);
  144. syncResult = code.isNotEmpty;
  145. if (syncResult) {
  146. entity.code = code;
  147. }
  148. } else {
  149. final request = PatientDtoConverter.dto2Update(dto);
  150. request.token = Store.user.token;
  151. syncResult = await rpc.patient.updatePatientAsync(request);
  152. }
  153. if (syncResult) {
  154. syncResult = await _syncPatientExt(entity);
  155. }
  156. entity.syncState =
  157. syncResult ? OfflineDataSyncState.success : OfflineDataSyncState.fail;
  158. await db.repositories.patient.update(entity);
  159. return syncResult;
  160. } catch (e) {
  161. logger.e("DataSyncManager syncPatient error.", e);
  162. return false;
  163. }
  164. }
  165. @override
  166. Future<int> syncPatientDiagnosis(String patientCode) async {
  167. final list = await db.repositories.diagnosis
  168. .getNotUploadedListByPatientCode(patientCode, userCode);
  169. int count = 0;
  170. for (var entity in list) {
  171. final success = await _syncSingleDiagnosis(entity);
  172. if (success) {
  173. count++;
  174. }
  175. }
  176. return count;
  177. }
  178. @override
  179. Future<int> syncPatientFollowUp(String patientCode) async {
  180. final list = await db.repositories.followUp
  181. .queryAllListByPatient(patientCode, userCode);
  182. int count = 0;
  183. for (var entity in list) {
  184. final success = await _syncSingleFollowUp(entity);
  185. if (success) {
  186. count++;
  187. }
  188. }
  189. return count;
  190. }
  191. @override
  192. Future<PagedDataCollection<OfflineRecordModel>> getOfflinePagedList(
  193. int pageIndex, {
  194. int pageSize = 50,
  195. List<OfflineDataSyncState>? syncStates,
  196. }) async {
  197. final patientCode = Store.user.currentSelectPatientInfo!.code!;
  198. final userCode = Store.user.userCode!;
  199. final sql = _getListQuerySql(patientCode, userCode, states: []);
  200. final dbList = await db.database.query(sql);
  201. // if(dbList.isEmpty){
  202. // return
  203. // }
  204. return PagedDataCollection<OfflineRecordModel>(
  205. totalCount: 0,
  206. pageIndex: pageIndex,
  207. pageSize: pageSize,
  208. data: [],
  209. );
  210. }
  211. @override
  212. Future<List<OfflineRecordModel>> getOfflineList({
  213. List<OfflineDataSyncState>? syncStates,
  214. }) async {
  215. final patientCode = Store.user.currentSelectPatientInfo!.code!;
  216. final userCode = Store.user.userCode!;
  217. final sql =
  218. _getListQuerySql(patientCode, userCode, states: syncStates ?? []);
  219. final dbList = await db.database.query(sql);
  220. if (dbList.isEmpty) {
  221. return [];
  222. }
  223. final models = dbList.map((e) => OfflineRecordModel.fromJson(e)).toList();
  224. return models;
  225. }
  226. String _getListQuerySql(
  227. String patientCode,
  228. String userCode, {
  229. List<OfflineDataSyncState>? states,
  230. }) {
  231. final sb = StringBuffer();
  232. sb.write('SELECT p.* ');
  233. sb.write(',COUNT(d.id) AS diagnosisCount ');
  234. sb.write(',COUNT(f.id) AS followupCount ');
  235. sb.writeln('FROM patients p ');
  236. // 联表 - 健康检测
  237. sb.writeln('Left JOIN diagnosis d ON p.code = d.patientCode ');
  238. sb.writeln('AND d.isValid =1 AND d.userCode="$userCode" ');
  239. if (states != null && states.isNotEmpty) {
  240. _buildStatesSql(states, "d");
  241. }
  242. // 联表 - 随访
  243. sb.writeln('Left JOIN followup f ON p.code = f.patientCode');
  244. sb.writeln('AND f.isValid =1 AND f.userCode="$userCode" ');
  245. if (states != null && states.isNotEmpty) {
  246. _buildStatesSql(states, "f");
  247. }
  248. sb.writeln('WHERE 1=1 p.isValid=1 ');
  249. sb.writeln('AND p.code="$patientCode" AND p.userCode="$userCode" ');
  250. if (states != null && states.isNotEmpty) {
  251. _buildStatesSql(states, "p");
  252. }
  253. // 根据档案维度分组
  254. sb.writeln('GROUP BY p.code');
  255. //'HAVING p.syncState=0 OR diagnosisCount>0 OR followupCount>0'
  256. sb.writeln(
  257. 'ORDER BY p.createTime DESC, d.createTime DESC,f.createTime DESC');
  258. sb.writeln(';');
  259. return sb.toString();
  260. }
  261. static String _buildStatesSql(
  262. List<OfflineDataSyncState> states, String alias) {
  263. if (alias.length == 1) {
  264. return "AND f.syncState=${states[0].index} ";
  265. }
  266. final sb = StringBuffer("AND ( ");
  267. for (var i = 0; i < states.length; i++) {
  268. final val = states[i].index;
  269. if (i == 0) {
  270. sb.writeln("$alias.syncState=$val ");
  271. } else {
  272. sb.writeln("OR $alias.syncState=$val ");
  273. }
  274. }
  275. sb.writeln(") ");
  276. return sb.toString();
  277. }
  278. Future<bool> _syncSingleFollowUp(FollowUpEntity entity) async {
  279. bool result = false;
  280. try {
  281. if (entity.syncType == OfflineDataSyncType.create) {
  282. final request = CreateFollowUpRequest(
  283. token: Store.user.token,
  284. key: entity.typeKey,
  285. patientCode: entity.patientCode,
  286. templateCode: entity.templateCode,
  287. followUpData: entity.dataJson,
  288. followUpTime: entity.followUpTime,
  289. nextFollowUpTime: entity.nextFollowUpTime,
  290. followUpMode: entity.mode,
  291. followUpPhotos: entity.followUpPhtots,
  292. );
  293. final code = await rpc.vitalFollowUp.createFollowUpAsync(request);
  294. result = code.isNotEmpty;
  295. if (result) {
  296. entity.code = code; // 更新真实Code
  297. }
  298. } else {
  299. final request = UpdateFollowUpRequest(
  300. token: Store.user.token,
  301. key: entity.typeKey,
  302. followUpData: entity.dataJson,
  303. followUpTime: entity.followUpTime,
  304. nextFollowUpTime: entity.nextFollowUpTime,
  305. followUpMode: entity.mode,
  306. code: entity.code,
  307. followUpPhotos: entity.followUpPhtots,
  308. );
  309. result = await rpc.vitalFollowUp.updateFollowUpAsync(request);
  310. }
  311. if (result) {
  312. entity.syncState = OfflineDataSyncState.success;
  313. } else {
  314. entity.syncState = OfflineDataSyncState.fail;
  315. }
  316. final updateRows = await db.repositories.followUp.update(entity);
  317. result = updateRows > 0;
  318. } catch (e) {
  319. logger.e(
  320. "DataSyncManager_syncSingleFollowUp error. id: ${entity.id}.", e);
  321. }
  322. return result;
  323. }
  324. Future<bool> _syncSingleDiagnosis(DiagnosisEntity entity) async {
  325. final currPatient = Store.user.currentSelectPatientInfo;
  326. if (currPatient == null) {
  327. return false;
  328. }
  329. bool result = false;
  330. try {
  331. final valuesMap = jsonDecode(entity.dataJson);
  332. List<DiagnosisItem> diagnosisItems =
  333. await Get.find<IRecordDataCacheManager>()
  334. .verifyDiagnosisDataList(valuesMap);
  335. // 目前不存在更新
  336. final request = SyncPatientAndDiagnosisDataRequest(
  337. token: Store.user.token,
  338. patientCode: currPatient.code,
  339. patientName: currPatient.patientName ?? '',
  340. patientAddress: currPatient.patientAddress ?? '',
  341. patientGender: currPatient.patientGender,
  342. permanentResidenceAddress: currPatient.permanentResidenceAddress,
  343. phone: currPatient.phone,
  344. cardNo: currPatient.cardNo,
  345. nationality: currPatient.nationality,
  346. birthday: currPatient.birthday,
  347. crowdLabels: currPatient.crowdLabels,
  348. cardType: currPatient.cardType,
  349. contractedDoctor: currPatient.contractedDoctor,
  350. appDataId: entity.code,
  351. diagnosisTime: DateTime.now(),
  352. diagnosisItems: diagnosisItems,
  353. );
  354. result =
  355. await rpc.vitalDiagnosis.syncPatientAndDiagnosisDataAsync(request);
  356. if (result) {
  357. entity.syncState = OfflineDataSyncState.success;
  358. } else {
  359. entity.syncState = OfflineDataSyncState.fail;
  360. }
  361. final updateRows = await db.repositories.diagnosis.update(entity);
  362. result = updateRows > 0;
  363. } catch (e) {
  364. logger.e(
  365. "DataSyncManager_syncSingleDiagnosis error. id: ${entity.id}.", e);
  366. }
  367. return result;
  368. }
  369. Future<bool> _syncPatientExt(PatientEntity entity) async {
  370. if (entity.extJson == null || entity.extJson!.isEmpty) {
  371. return true;
  372. }
  373. if (entity.extCode == null || entity.extCode!.startsWith("mock")) {
  374. final extCode =
  375. await rpc.vitalPatientExtension.createPatientExtensionAsync(
  376. CreatePatientExtensionRequest(
  377. token: Store.user.token,
  378. key: "PatientHealthInfo",
  379. patientCode: entity.code,
  380. extensionData: entity.extJson,
  381. ),
  382. );
  383. if (extCode.isNotEmpty) {
  384. entity.extCode = extCode;
  385. return true;
  386. } else {
  387. return false;
  388. }
  389. } else {
  390. final result =
  391. await rpc.vitalPatientExtension.updatePatientExtensionAsync(
  392. UpdatePatientExtensionRequest(
  393. code: entity.extCode,
  394. token: Store.user.token,
  395. key: "PatientHealthInfo",
  396. patientCode: entity.code,
  397. extensionData: entity.extJson,
  398. ),
  399. );
  400. return result;
  401. }
  402. }
  403. }