controller.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. import 'dart:convert';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:get/get.dart';
  5. import 'package:uuid/uuid.dart';
  6. import 'package:vitalapp/architecture/defines.dart';
  7. import 'package:vitalapp/architecture/storage/text_storage.dart';
  8. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  9. import 'package:vitalapp/architecture/values/features.dart';
  10. import 'package:vitalapp/components/alert_dialog.dart';
  11. import 'package:vitalapp/global.dart';
  12. import 'package:vitalapp/helper/id_card_helper.dart';
  13. import 'package:vitalapp/managers/interfaces/device.dart';
  14. import 'package:vitalapp/managers/interfaces/models/device.dart';
  15. import 'package:vitalapp/managers/interfaces/patient.dart';
  16. import 'package:vitalapp/managers/interfaces/permission.dart';
  17. import 'package:vitalapp/managers/interfaces/registration.dart';
  18. import 'package:vitalapp/pages/controllers/blue_location_mixin.dart';
  19. import 'package:vitalapp/pages/controllers/crowd_labels.dart';
  20. import 'package:vitalapp/pages/controllers/home_nav_mixin.dart';
  21. import 'package:vitalapp/pages/home/controller.dart';
  22. import 'package:vitalapp/pages/patient/bluetooth_card_reader/view.dart';
  23. import 'package:vitalapp/pages/patient/card_reader/index.dart';
  24. import 'package:vitalapp/pages/patient/create/state.dart';
  25. import 'package:vitalapp/pages/patient/create/widgets/face_result_dialog.dart';
  26. import 'package:vitalapp/routes/routes.dart';
  27. import 'package:vitalapp/pages/patient_info/controller.dart';
  28. import 'package:vitalapp/pages/patient_info/state.dart';
  29. import 'package:vitalapp/rpc.dart';
  30. import 'package:vitalapp/store/store.dart';
  31. import 'package:vnote_device_plugin/consts/types.dart';
  32. import 'package:fis_common/logger/logger.dart';
  33. import '../../facial_recognition/index.dart';
  34. import '../../id_card_scan/index.dart';
  35. class CreatePatientController extends FControllerBase
  36. with HomeNavMixin, BluetoothAndLocationMixin {
  37. final _patientManager = Get.find<IPatientManager>();
  38. final crowdLabelsController = Get.find<CrowdLabelsController>();
  39. final _deviceManager = Get.find<IDeviceManager>();
  40. final patientInfomationController = Get.find<PatientInfomationController>();
  41. PatientInfomationState get patientInfomationState =>
  42. patientInfomationController.state;
  43. final state = CreatePatientState();
  44. bool isClearPrompt = true;
  45. @override
  46. void onReady() {
  47. final params = Get.parameters;
  48. if (params.containsKey("from")) {
  49. if (params["from"] == "list") {
  50. state.isCreateOnly = true;
  51. }
  52. }
  53. isClearPrompt = true;
  54. if (Routes.parameters.containsKey('patientInfo')) {
  55. if (Routes.parameters['patientInfo'] != null) {
  56. ///由于PatientBaseDTO中没有headImage字段,所以这里单独解析这个json,尝试获取人脸图像并绑定
  57. var jsonString = Routes.parameters['patientInfo'];
  58. // 解析 JSON 字符串并转换为 Map
  59. final parsedJson = json.decode(jsonString);
  60. //获取“isVital”字段的值
  61. final bool? isVital = parsedJson['isVital'];
  62. if (isVital != null) {
  63. state.isVital = isVital;
  64. }
  65. // 获取 'headImage' 字段的值
  66. final String? headImageUrl = parsedJson['headImage'];
  67. if (headImageUrl != null) {
  68. state.headImage = headImageUrl;
  69. } else {
  70. PatientBaseDTO patientInfo = PatientBaseDTO.fromJson(
  71. jsonDecode(
  72. Routes.parameters['patientInfo']!,
  73. ),
  74. );
  75. patientInfoParse(patientInfo);
  76. }
  77. }
  78. }
  79. return super.onReady();
  80. }
  81. /// 存为档案,调整到档案详情
  82. void saveAndBack() async {
  83. final validateMsg = await _validateForm();
  84. if (validateMsg != null) {
  85. toast(validateMsg);
  86. return null;
  87. }
  88. setBusy("正在保存...");
  89. await Future.delayed(const Duration(milliseconds: 500));
  90. final code = await _submitForm();
  91. if (code == null || code.isEmpty) {
  92. busy = false;
  93. logger.e('CreatePatientController saveAndBack code is null');
  94. PromptBox.toast("保存失败");
  95. return;
  96. }
  97. isClearPrompt = false;
  98. // await queryIsNeedFaceInput(); //询问是否录入人脸
  99. if (state.headImage.isNotEmpty) {
  100. await rpc.vitalPatient.savePatientBaseByFaceImageAsync(
  101. SavePatientBaseByFaceImageRequest(
  102. cardNo: code,
  103. token: Store.user.token,
  104. image: state.headImage,
  105. ),
  106. );
  107. ///TODO(Loki):提交体检记录
  108. _submitRegiterInfo();
  109. } else if (kIsOnline &&
  110. Store.user.hasFeature(FeatureKeys.FaceRecognition)) {
  111. var photoUrl = await getPatientPhoto(patientInfomationState.cardNo);
  112. if (photoUrl.isEmpty) {
  113. busy = false;
  114. await onFaceEntryClicked(); //不询问直接录入
  115. setBusy("正在保存...");
  116. }
  117. }
  118. Future.delayed(
  119. ///这个800ms不能移除,移除后会导致建档失败
  120. const Duration(milliseconds: 800),
  121. () async {
  122. final switchReult = await _switchCurrentPatient(code);
  123. // 重置状态
  124. state.reset();
  125. busy = false;
  126. if (switchReult) {
  127. PromptBox.toast("保存成功");
  128. Future.delayed(const Duration(milliseconds: 500));
  129. Get.find<HomeController>().switchNavByName("/patient/detail");
  130. }
  131. },
  132. );
  133. }
  134. /// 询问是否需要人脸录入
  135. Future<void> queryIsNeedFaceInput() async {
  136. /// 拥有人脸权限并且不是身份证扫码建档
  137. if (Store.user.hasFeature(FeatureKeys.FaceRecognition) && kIsOnline) {
  138. var photoUrl = await getPatientPhoto(patientInfomationState.cardNo);
  139. if (photoUrl.isNotEmpty) {
  140. await Get.dialog(VAlertDialog(
  141. contentPadding: const EdgeInsets.fromLTRB(20, 1, 20, 1),
  142. title: '提示',
  143. content: Container(
  144. margin: const EdgeInsets.only(bottom: 20),
  145. child: Column(
  146. mainAxisSize: MainAxisSize.min,
  147. children: [
  148. Image.network(photoUrl,
  149. width: 300, height: 220, fit: BoxFit.cover,
  150. loadingBuilder: (context, child, progress) {
  151. if (progress == null ||
  152. progress.cumulativeBytesLoaded ==
  153. progress.expectedTotalBytes) {
  154. return Image.network(
  155. photoUrl,
  156. width: 300,
  157. height: 220,
  158. fit: BoxFit.cover,
  159. );
  160. }
  161. return const CircularProgressIndicator(
  162. color: Colors.blueAccent);
  163. }),
  164. const Text("该居民已采集过人脸,是否重新采集?",
  165. style: TextStyle(fontSize: 20),
  166. textAlign: TextAlign.center),
  167. ],
  168. ),
  169. ),
  170. confirmLabel: '重新采集',
  171. cancelLabel: '跳过',
  172. showCancel: true,
  173. onConfirm: () async {
  174. await onFaceEntryClicked();
  175. Get.back();
  176. },
  177. onCanceled: () {
  178. Get.back();
  179. },
  180. ));
  181. } else {
  182. await Get.dialog(VAlertDialog(
  183. contentPadding: const EdgeInsets.fromLTRB(20, 1, 20, 1),
  184. title: '提示',
  185. content: Container(
  186. margin: const EdgeInsets.only(bottom: 20),
  187. child: const Text(
  188. '请采集人像,完成后可以使用人脸识别功能',
  189. style: TextStyle(fontSize: 20),
  190. textAlign: TextAlign.center,
  191. ),
  192. ),
  193. confirmLabel: '采集',
  194. cancelLabel: '暂不采集',
  195. showCancel: true,
  196. onConfirm: () async {
  197. await onFaceEntryClicked();
  198. Get.back();
  199. },
  200. onCanceled: () {
  201. Get.back();
  202. },
  203. ));
  204. }
  205. }
  206. }
  207. Future<DeviceModel?> getDevice(String type) async {
  208. List<DeviceModel> devices = await _deviceManager.getDeviceList();
  209. return devices.firstWhereOrNull((element) => element.type == type);
  210. }
  211. /// 点击读卡事件
  212. void onReadCardClicked() async {
  213. final DeviceModel? device = await getDevice(DeviceTypes.IC_READER);
  214. CardReaderResult? result;
  215. if (device != null) {
  216. final envPassed = await checkDeviceConnectEnv();
  217. if (envPassed) {
  218. result = await Get.dialog<CardReaderResult>(
  219. const BluetoothCardReaderDialog(),
  220. barrierDismissible: false,
  221. );
  222. }
  223. } else {
  224. result = await Get.dialog<CardReaderResult>(
  225. const CardReaderDialog(),
  226. barrierDismissible: false,
  227. );
  228. }
  229. if (result != null && result.success) {
  230. PromptBox.toast("读取成功");
  231. final isCardNoChanged = patientInfomationState.cardNo != result.cardNo;
  232. // patientInfomationState.cardNo = result.cardNo; // 回填身份证号
  233. final passed =
  234. await patientInfomationController.onCardNoChanged(result.cardNo);
  235. if (!passed) {
  236. return;
  237. }
  238. if (isCardNoChanged) {
  239. patientInfomationState.phoneNo = "";
  240. patientInfomationState.emergencyPhone = "";
  241. }
  242. patientInfomationState.name = result.name; // 回填姓名
  243. patientInfomationState.gender = result.gender; // 回填性别
  244. patientInfomationState.nation = result.nation; // 回填民族
  245. patientInfomationState.birthday = result.birthday; // 回填出生日期
  246. patientInfomationState.permanentResidenceAddress =
  247. result.address; // 回填户籍地址
  248. if (patientInfomationState.isSyncAddresses) {
  249. patientInfomationState.address = result.address; // 回填现住地址
  250. }
  251. if (passed) {
  252. // 缓存填充
  253. patientInfomationController.checkOfflinePatientExists(result.cardNo);
  254. }
  255. } else {
  256. print("读卡取消");
  257. }
  258. }
  259. void onIdCardScanClicked() async {
  260. var verifyResult = await _verifyCameraPermissions();
  261. if (!verifyResult) {
  262. return;
  263. }
  264. final IdCardScanResult? result = await Get.to<IdCardScanResult>(
  265. () => const IdCardScanPage(),
  266. );
  267. if (result != null && result.success) {
  268. PromptBox.toast("身份证信息识别成功");
  269. PatientBaseDTO patientInfo = result.patientBaseDTO;
  270. logger.i(
  271. "CreatePatientController onReadCardClickedToDetail patientInfo${jsonEncode(patientInfo.toJson())}");
  272. await patientInfoParse(patientInfo);
  273. } else {
  274. print("识别取消");
  275. }
  276. }
  277. Future<void> patientInfoParse(PatientBaseDTO patientInfo) async {
  278. final isCardNoChanged = patientInfomationState.cardNo != patientInfo.cardNo;
  279. // patientInfomationState.cardNo = patientInfo.cardNo ?? ""; // 回填身份证号
  280. final passed = await patientInfomationController
  281. .onCardNoChanged(patientInfo.cardNo ?? "");
  282. if (!passed) {
  283. return;
  284. }
  285. if (isCardNoChanged) {
  286. patientInfomationState.phoneNo = "";
  287. patientInfomationState.emergencyPhone = "";
  288. }
  289. patientInfomationState.name = patientInfo.patientName ?? ""; // 回填姓名
  290. patientInfomationState.gender = patientInfo.patientGender; // 回填性别
  291. patientInfomationState.nation = patientInfo.nationality ?? ""; // 回填民族
  292. patientInfomationState.birthday = patientInfo.birthday; // 回填出生日期
  293. patientInfomationState.permanentResidenceAddress =
  294. patientInfo.patientAddress ?? ""; // 回填户籍地址
  295. if (patientInfomationState.isSyncAddresses) {
  296. patientInfomationState.address =
  297. patientInfo.patientAddress ?? ""; // 回填现住地址
  298. }
  299. if (passed) {
  300. // 缓存填充
  301. patientInfomationController
  302. .checkOfflinePatientExists(patientInfo.cardNo ?? "");
  303. }
  304. }
  305. Future<bool> _verifyCameraPermissions() async {
  306. IPermissionManager permissionManager = Get.find<IPermissionManager>();
  307. var isCameraPermissions =
  308. await permissionManager.requestCameraPermissions();
  309. if (!isCameraPermissions) {
  310. await Get.dialog(
  311. VAlertDialog(
  312. title: "提示",
  313. width: 420,
  314. content: Container(
  315. height: 32,
  316. padding: const EdgeInsets.symmetric(horizontal: 24),
  317. alignment: Alignment.center,
  318. child: const Text(
  319. "未授予相机权限,前去设置",
  320. style: TextStyle(fontSize: 20),
  321. ),
  322. ),
  323. showCancel: false,
  324. onConfirm: () async {
  325. Get.back();
  326. await permissionManager.openAppSettingsAsync();
  327. },
  328. ),
  329. barrierDismissible: false,
  330. barrierColor: Colors.black.withOpacity(.4),
  331. );
  332. }
  333. return isCameraPermissions;
  334. }
  335. /// 点击人脸识别
  336. void onFaceIdLoginClicked() async {
  337. var verifyResult = await _verifyCameraPermissions();
  338. if (!verifyResult) {
  339. return;
  340. }
  341. final FaceRecognitionResult? result = await Get.to<FaceRecognitionResult>(
  342. () => const FacialRecognitionPage(
  343. mode: FacialRecognitionMode.faceRecognition,
  344. ),
  345. );
  346. if (result != null && result.success) {
  347. final patient = result.patientInfo;
  348. final hasConfirmed = await FaceResultDialog.show(patient, true);
  349. if (hasConfirmed &&
  350. patient.createdOrgCode == Store.user.organizationCode) {
  351. await _checkinPatient(patient, false);
  352. }
  353. } else {
  354. print("识别取消");
  355. }
  356. }
  357. Future<bool> _switchCurrentPatient(String cardNo) async {
  358. PatientDTO? patientInfoDto = await _patientManager.getDetail(cardNo);
  359. if (patientInfoDto != null) {
  360. await _patientManager.switchCurrentPatient(patientInfoDto);
  361. logger.i(
  362. 'create居民 当前居民是:${patientInfoDto.patientName} 居民code:${patientInfoDto.code}');
  363. return true;
  364. } else {
  365. PromptBox.toast("切换居民失败!");
  366. return false;
  367. }
  368. }
  369. /// 点击录入人脸
  370. Future<void> onFaceEntryClicked() async {
  371. final PatientDTO patientInfo = PatientDTO(
  372. patientName: patientInfomationState.name,
  373. phone: patientInfomationState.phoneNo,
  374. emergencyPhone: patientInfomationState.emergencyPhone,
  375. cardNo: patientInfomationState.cardNo,
  376. nationality: patientInfomationState.nation,
  377. birthday: patientInfomationState.birthday,
  378. cardType: patientInfomationState.cardType,
  379. patientGender: patientInfomationState.gender ?? GenderEnum.Unspecified,
  380. code: patientInfomationState.cardNo,
  381. );
  382. bool? result = await Get.to<bool>(
  383. () => FacialRecognitionPage(
  384. mode: FacialRecognitionMode.faceInput,
  385. patientInfo: patientInfo,
  386. ),
  387. );
  388. if (result != null && result) {
  389. PromptBox.toast('人脸数据存入成功');
  390. }
  391. }
  392. /// 处理 “同户籍地址” 勾选变更事件
  393. void onSyncAddressCheckChanged(bool isChecked) {
  394. patientInfomationState.isSyncAddresses = isChecked;
  395. if (isChecked) {
  396. // 同步户籍地址到现住地址
  397. patientInfomationState.address =
  398. patientInfomationState.permanentResidenceAddress;
  399. } else {
  400. patientInfomationState.address = "";
  401. }
  402. }
  403. /// 处理户籍地址变更
  404. void onCensusRegisterChanged(String value) {
  405. patientInfomationState.permanentResidenceAddress = value;
  406. if (patientInfomationState.isSyncAddresses) {
  407. patientInfomationState.address = value;
  408. }
  409. }
  410. Future<String> getPatientPhoto(String patientCode) async {
  411. final dto = await _patientManager.getDetail(patientCode);
  412. if (dto != null && dto.photos != null && dto.photos!.isNotEmpty) {
  413. return dto.photos![0];
  414. }
  415. return "";
  416. }
  417. Future<String?> _submitForm() async {
  418. final request = CreatePatientRequest2(
  419. patientName: patientInfomationState.name,
  420. phone: patientInfomationState.phoneNo,
  421. emergencyName: patientInfomationState.emergencyName,
  422. emergencyPhone: patientInfomationState.emergencyPhone,
  423. patientGender: patientInfomationState.gender ?? GenderEnum.Unspecified,
  424. nationality: patientInfomationState.nation,
  425. birthday: patientInfomationState.birthday?.toUtc(),
  426. cardType: patientInfomationState.cardType,
  427. cardNo: patientInfomationState.cardNo,
  428. patientAddress: patientInfomationState.address,
  429. permanentResidenceAddress:
  430. patientInfomationState.permanentResidenceAddress,
  431. crowdLabels:
  432. _patientManager.crowdLabelsConvert(patientInfomationState.labels),
  433. );
  434. logger.i("create CreatePatientRequest2 ${jsonEncode(request.toJson())}");
  435. final result = await _patientManager.create(request);
  436. if (result != null) {
  437. var request = GetPatientExtensionByCodeAndKeyRequest(
  438. token: Store.user.token,
  439. patientCode: patientInfomationState.cardNo,
  440. key: "PatientHealthInfo",
  441. );
  442. var result = await _patientManager
  443. .getPatientExtensionDetailByCodeAndKeyAsync(request);
  444. var healthCode = result?.code ?? '';
  445. _patientManager.createPatientExtension(
  446. patientInfomationState.cardNo,
  447. patientInfomationState.healthInfo,
  448. patientInfomationState.detailInfo,
  449. healthCode,
  450. );
  451. }
  452. return result;
  453. }
  454. bool isNumeric(String str) {
  455. if (str.isEmpty) {
  456. return false;
  457. }
  458. return double.tryParse(str) != null;
  459. }
  460. bool isAlphaNumeric(String str) {
  461. final RegExp alphaNumericRegExp = RegExp(r'^[a-zA-Z0-9]+$');
  462. return alphaNumericRegExp.hasMatch(str);
  463. }
  464. bool isAlphaNumericChineseWithSpace(String str) {
  465. final RegExp alphaNumericChineseWithSpaceRegExp =
  466. RegExp(r'^[a-zA-Z0-9\u4e00-\u9fa5\s]+$');
  467. return alphaNumericChineseWithSpaceRegExp.hasMatch(str);
  468. }
  469. Future<String?> _validateForm() async {
  470. patientInfomationController.state.name;
  471. if (patientInfomationState.name.isEmpty) {
  472. return "请填写姓名";
  473. }
  474. if (patientInfomationState.cardNo.isEmpty) {
  475. return "请填写证件号";
  476. }
  477. if (patientInfomationState.phoneNo.isNotEmpty) {
  478. if (patientInfomationState.phoneNo.length != 11 ||
  479. !isNumeric(patientInfomationState.phoneNo)) {
  480. return "请填写正确的联系电话";
  481. }
  482. }
  483. if (patientInfomationState.emergencyPhone.isNotEmpty) {
  484. if (patientInfomationState.emergencyPhone.length != 11 ||
  485. !isNumeric(patientInfomationState.emergencyPhone)) {
  486. return "请填写正确的联系人电话";
  487. }
  488. }
  489. if (patientInfomationState.cardType == CardTypeEnum.Identity) {
  490. bool isNotIDCard =
  491. IdCardHelper.validateIDCard(patientInfomationState.cardNo);
  492. if (!isNotIDCard) {
  493. return "请填写正确的证件号";
  494. }
  495. }
  496. if (patientInfomationState.cardType == CardTypeEnum.SocialInsurance) {
  497. if (patientInfomationState.cardNo.length != 18 ||
  498. !isNumeric(patientInfomationState.cardNo)) {
  499. return "请填写正确的社保号";
  500. }
  501. }
  502. if (patientInfomationState.cardType == CardTypeEnum.Passport) {
  503. if (patientInfomationState.cardNo.length != 9 ||
  504. !isAlphaNumeric(patientInfomationState.cardNo)) {
  505. return "请填写正确的护照号";
  506. }
  507. }
  508. return null;
  509. }
  510. /// 切换当前登记居民
  511. Future<void> _checkinPatient(
  512. PatientBaseDTO patient,
  513. bool isNeedConfirm,
  514. ) async {
  515. final patientDTO = PatientDTO(
  516. code: patient.cardNo,
  517. cardNo: patient.cardNo,
  518. patientName: patient.patientName,
  519. nationality: patient.nationality,
  520. patientGender: patient.patientGender,
  521. birthday: patient.birthday,
  522. patientAddress: patient.patientAddress,
  523. );
  524. await _switchCurrentPatient(patientDTO.code!);
  525. Get.find<HomeController>()
  526. .switchNavByName("/patient/detail", null, isNeedConfirm);
  527. }
  528. Future<void> setGender(String cardNo) async {
  529. if (cardNo.isNotEmpty && cardNo.length > 17) {
  530. var sexNum = cardNo.substring(16, 17);
  531. if (sexNum.isNotEmpty && RegExp(r'^\d+$').hasMatch(sexNum)) {
  532. if (int.parse(sexNum) % 2 == 1) {
  533. patientInfomationState.gender = GenderEnum.Male;
  534. } else {
  535. patientInfomationState.gender = GenderEnum.Female;
  536. }
  537. } else {
  538. patientInfomationState.gender = GenderEnum.Unknown;
  539. }
  540. }
  541. }
  542. void _submitRegiterInfo() async {
  543. Get.find<IRegistrationManager>().addRegiterInfoAsync(
  544. request: AddRegiterInfoRequest(
  545. token: Store.user.token,
  546. code: await saveCachedBatchNumber(patientInfomationState.cardNo),
  547. iDCardNo: patientInfomationState.cardNo,
  548. phone: patientInfomationState.phoneNo,
  549. name: patientInfomationState.name,
  550. sex: patientInfomationState.genderDesc,
  551. age: patientInfomationState.age,
  552. birthday: patientInfomationState.birthday,
  553. adress: patientInfomationState.address,
  554. ///TODO(Loki):这里projectType这个页面无法区分,且这个字段接口中不可为空,故而先用一个默认值
  555. projectType: state.isVital
  556. ? VitalProjectTypeEnum.VinnoHealth
  557. : VitalProjectTypeEnum.VitalStation,
  558. nationality: patientInfomationState.nation,
  559. permanentResidenceAddress:
  560. patientInfomationState.permanentResidenceAddress,
  561. ),
  562. );
  563. }
  564. Future<String?> saveCachedBatchNumber(String? cardNo) async {
  565. String batchNumber = const Uuid().v4().replaceAll('-', '');
  566. TextStorage cachedRecord = TextStorage(
  567. fileName: 'batchNumber',
  568. directory: "patient/${cardNo}",
  569. );
  570. await cachedRecord.save(batchNumber);
  571. return batchNumber;
  572. }
  573. }