import 'dart:convert'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:fis_jsonrpc/services/patient.m.dart'; import 'package:get/get.dart'; import 'package:uuid/uuid.dart'; import 'package:vital_local_database/core/interface/queryable.dart'; import 'package:vitalapp/architecture/storage/text_storage.dart'; import 'package:vitalapp/database/db.dart'; import 'package:vitalapp/database/entities/defines.dart'; import 'package:vitalapp/database/entities/diagnosis.dart'; import 'package:vitalapp/database/entities/followup.dart'; import 'package:vitalapp/database/entities/patient.dart'; import 'package:vitalapp/managers/adapters/offline/patient.dart'; import 'package:vitalapp/managers/interfaces/models/common.dart'; import 'package:vitalapp/managers/interfaces/models/data_sync.dart'; import 'package:vitalapp/rpc.dart'; import 'package:vitalapp/store/store.dart'; import 'interfaces/data_sync.dart'; import 'package:fis_common/logger/logger.dart'; import 'interfaces/record_data_cache.dart'; class DataSyncManager implements IDataSyncManager { String get userCode => Store.user.userCode!; @override Future> getPatientPagedList( int pageIndex, { int pageSize = 50, List? syncStates, }) async { try { final query = db.repositories.patient.queryable.where((x) { final list = []; list.add(x.isValid.equals(true)); list.add(x.userCode.equals(Store.user.userCode)); //添加用户code if (syncStates != null && syncStates.isNotEmpty) { if (syncStates.length == 1) { list.add(x.overallSyncState.equals(syncStates[0])); } else { list.add(x.overallSyncState.inEqueals(syncStates)); } } return list; }); final count = await query.count(); final offset = pageSize * (pageIndex - 1); final entities = await query.offset(offset).limit(pageSize).toList(); return PagedDataCollection( totalCount: count, pageIndex: pageIndex, pageSize: pageSize, data: entities, ); } catch (e) { logger.e("DataSyncManager getPatientPagedList error.", e); return PagedDataCollection( totalCount: 0, pageIndex: pageIndex, pageSize: pageSize, data: [], ); } } @override Future> getPatientWaitUploadAllList() async { final waitList = await db.repositories.patient.queryable.where((x) { final list = []; list.add(x.isValid.equals(true)); list.add(x.userCode.equals(Store.user.userCode)); list.add(x.overallSyncState.equals(OfflineDataSyncState.wait)); return list; }).toList(); final failList = await db.repositories.patient.queryable.where((x) { final list = []; list.add(x.isValid.equals(true)); list.add(x.userCode.equals(Store.user.userCode)); list.add(x.overallSyncState.equals(OfflineDataSyncState.fail)); return list; }).toList(); // TODO: 暂时未支持 Where in ,暂时先分开查 return [...waitList, ...failList]; } @override Future syncPatientAllData(String patientCode) async { // TODO: 后续加上事务支持 final patientEntity = await db.repositories.patient.singleByCode(patientCode); if (patientEntity == null) { return false; } bool isSyncCompleted = false; try { final patientResult = await syncPatient(patientEntity); if (!patientResult) { return false; } if (patientEntity.diagnosisCount > 0) { final diagnosisSyncCount = await syncPatientDiagnosis(patientCode); patientEntity.diagnosisCount -= diagnosisSyncCount; } if (patientEntity.followUpCount > 0) { final followUpSyncCount = await syncPatientFollowUp(patientCode); patientEntity.followUpCount -= followUpSyncCount; } isSyncCompleted = _checkPatientSyncCompleted(patientEntity); if (isSyncCompleted) { patientEntity.overallSyncState = OfflineDataSyncState.success; } else { patientEntity.overallSyncState = OfflineDataSyncState.fail; } } catch (e) { logger.e( "DataSyncManager syncPatientAllData[during sync each] error.", e); // TODO: 后续可加上失败原因字段 patientEntity.overallSyncState = OfflineDataSyncState.fail; return false; } // 更新居民记录 try { final ret = await db.repositories.patient.update(patientEntity); return isSyncCompleted && ret > 0; } catch (e) { logger.e( "DataSyncManager syncPatientAllData[during update overall state] error.", e); return false; } } /// 校验同步是否完成 bool _checkPatientSyncCompleted(PatientEntity entity) { if (entity.examCount > 0) return false; if (entity.diagnosisCount > 0) return false; if (entity.followUpCount > 0) return false; if (entity.syncState != OfflineDataSyncState.success) return false; return true; } @override Future syncPatient(PatientEntity entity) async { try { final jsonMap = jsonDecode(entity.dataJson); final dto = PatientDTO.fromJson(jsonMap); bool syncResult = false; if (entity.syncType == OfflineDataSyncType.create) { final request = PatientDtoConverter.dto2Create(dto); request.token = Store.user.token; final code = await rpc.patient.createPatientAsync(request); syncResult = code.isNotEmpty; } else { final request = PatientDtoConverter.dto2Update(dto); request.token = Store.user.token; syncResult = await rpc.patient.updatePatientAsync(request); } entity.syncState = syncResult ? OfflineDataSyncState.success : OfflineDataSyncState.fail; await db.repositories.patient.update(entity); return syncResult; } catch (e) { logger.e("DataSyncManager syncPatient error.", e); return false; } } @override Future syncPatientDiagnosis(String patientCode) async { final list = await db.repositories.diagnosis .getNotUploadedListByPatientCode(patientCode, userCode); int count = 0; final appDataId = await _getDiagnosisAppDataId(patientCode); for (var entity in list) { final success = await _syncSingleDiagnosis(appDataId, entity); if (success) { count++; } } return count; } @override Future syncPatientFollowUp(String patientCode) async { final list = await db.repositories.followUp .queryAllListByPatient(patientCode, userCode); int count = 0; for (var entity in list) { final success = await _syncSingleFollowUp(entity); if (success) { count++; } } return count; } @override Future> getOfflinePagedList( int pageIndex, { int pageSize = 50, List? syncStates, }) async { final patientCode = Store.user.currentSelectPatientInfo!.code!; final userCode = Store.user.userCode!; final sql = _getListQuerySql(patientCode, userCode, states: []); final dbList = await db.database.query(sql); // if(dbList.isEmpty){ // return // } return PagedDataCollection( totalCount: 0, pageIndex: pageIndex, pageSize: pageSize, data: [], ); } @override Future> getOfflineList({ List? syncStates, }) async { final patientCode = Store.user.currentSelectPatientInfo!.code!; final userCode = Store.user.userCode!; final sql = _getListQuerySql(patientCode, userCode, states: syncStates ?? []); final dbList = await db.database.query(sql); if (dbList.isEmpty) { return []; } final models = dbList.map((e) => OfflineRecordModel.fromJson(e)).toList(); return models; } String _getListQuerySql( String patientCode, String userCode, { List? states, }) { final sb = StringBuffer(); sb.write('SELECT p.* '); sb.write(',COUNT(d.id) AS diagnosisCount '); sb.write(',COUNT(f.id) AS followupCount '); sb.writeln('FROM patients p '); // 联表 - 健康检测 sb.writeln('Left JOIN diagnosis d ON p.code = d.patientCode '); sb.writeln('AND d.isValid =1 AND d.userCode="$userCode" '); if (states != null && states.isNotEmpty) { _buildStatesSql(states, "d"); } // 联表 - 随访 sb.writeln('Left JOIN followup f ON p.code = f.patientCode'); sb.writeln('AND f.isValid =1 AND f.userCode="$userCode" '); if (states != null && states.isNotEmpty) { _buildStatesSql(states, "f"); } sb.writeln('WHERE 1=1 p.isValid=1 '); sb.writeln('AND p.code="$patientCode" AND p.userCode="$userCode" '); if (states != null && states.isNotEmpty) { _buildStatesSql(states, "p"); } // 根据档案维度分组 sb.writeln('GROUP BY p.code'); //'HAVING p.syncState=0 OR diagnosisCount>0 OR followupCount>0' sb.writeln( 'ORDER BY p.createTime DESC, d.createTime DESC,f.createTime DESC'); sb.writeln(';'); return sb.toString(); } static String _buildStatesSql( List states, String alias) { if (alias.length == 1) { return "AND f.syncState=${states[0].index} "; } final sb = StringBuffer("AND ( "); for (var i = 0; i < states.length; i++) { final val = states[i].index; if (i == 0) { sb.writeln("$alias.syncState=$val "); } else { sb.writeln("OR $alias.syncState=$val "); } } sb.writeln(") "); return sb.toString(); } Future _syncSingleFollowUp(FollowUpEntity entity) async { bool result = false; try { if (entity.syncType == OfflineDataSyncType.create) { final request = CreateFollowUpRequest( token: Store.user.token, key: entity.typeKey, patientCode: entity.patientCode, templateCode: entity.templateCode, followUpData: entity.dataJson, followUpTime: entity.followUpTime, nextFollowUpTime: entity.nextFollowUpTime, followUpMode: entity.mode, followUpPhotos: entity.followUpPhtots, ); final code = await rpc.followUp.createFollowUpAsync(request); result = code.isNotEmpty; if (result) { entity.code = code; // 更新真实Code } } else { final request = UpdateFollowUpRequest( token: Store.user.token, key: entity.typeKey, followUpData: entity.dataJson, followUpTime: entity.followUpTime, nextFollowUpTime: entity.nextFollowUpTime, followUpMode: entity.mode, code: entity.code, followUpPhotos: entity.followUpPhtots, ); result = await rpc.followUp.updateFollowUpAsync(request); } if (result) { entity.syncState = OfflineDataSyncState.success; } else { entity.syncState = OfflineDataSyncState.fail; } final updateRows = await db.repositories.followUp.update(entity); result = updateRows > 0; } catch (e) { logger.e( "DataSyncManager_syncSingleFollowUp error. id: ${entity.id}.", e); } return result; } Future _syncSingleDiagnosis( String appDataId, DiagnosisEntity entity) async { final currPatient = Store.user.currentSelectPatientInfo; if (currPatient == null) { return false; } bool result = false; try { final valuesMap = jsonDecode(entity.dataJson); List diagnosisItems = await Get.find() .verifyDiagnosisDataList(valuesMap); // 目前不存在更新 final request = SyncPatientAndDiagnosisDataRequest( token: Store.user.token, patientCode: currPatient.code, patientName: currPatient.patientName ?? '', patientAddress: currPatient.patientAddress ?? '', patientGender: currPatient.patientGender, permanentResidenceAddress: currPatient.permanentResidenceAddress, phone: currPatient.phone, cardNo: currPatient.cardNo, nationality: currPatient.nationality, birthday: currPatient.birthday, crowdLabels: currPatient.crowdLabels, cardType: currPatient.cardType, contractedDoctor: currPatient.contractedDoctor, appDataId: appDataId, diagnosisTime: DateTime.now(), diagnosisItems: diagnosisItems, ); result = await rpc.diagnosis.syncPatientAndDiagnosisDataAsync(request); if (result) { entity.syncState = OfflineDataSyncState.success; } else { entity.syncState = OfflineDataSyncState.fail; } final updateRows = await db.repositories.diagnosis.update(entity); result = updateRows > 0; } catch (e) { logger.e( "DataSyncManager_syncSingleDiagnosis error. id: ${entity.id}.", e); } return result; } Future _getDiagnosisAppDataId(String patientCode) async { // TODO 待封装 TextStorage cachedRecord = TextStorage( fileName: 'appDataId', directory: "patient/$patientCode", ); String? appDataId = await cachedRecord.read(); appDataId ??= const Uuid().v4().replaceAll('-', ''); return appDataId; } }