diff --git a/Frontend/Dockerfile b/Frontend/Dockerfile index 3030b62f..b8f1b607 100644 --- a/Frontend/Dockerfile +++ b/Frontend/Dockerfile @@ -35,7 +35,8 @@ COPY . /app/ WORKDIR /app RUN flutter upgrade -RUN flutter build web --release --web-renderer canvaskit -t ./lib/main_prod.dart +RUN flutter build web --release -t ./lib/main_prod.dart +# RUN flutter build web --release --web-renderer canvaskit -t ./lib/main_prod.dart # RUN cd .. diff --git a/Frontend/lib/mih_apis/mih_claim_statement_generation_api.dart b/Frontend/lib/mih_apis/mih_claim_statement_generation_api.dart new file mode 100644 index 00000000..cbc592a9 --- /dev/null +++ b/Frontend/lib/mih_apis/mih_claim_statement_generation_api.dart @@ -0,0 +1,256 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/claim_statement_file.dart'; +import 'package:flutter/material.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +import '../mih_components/mih_pop_up_messages/mih_error_message.dart'; +import '../mih_components/mih_pop_up_messages/mih_success_message.dart'; +import '../mih_env/env.dart'; + +class MIHClaimStatementGenerationApi { + final baseAPI = AppEnviroment.baseApiUrl; + + /// This function is used to generate and store a claim/ statement. + /// + /// Patameters: TBC . + /// + /// Returns TBC. + Future generateClaimStatement( + ClaimStatementGenerationArguments data, + PatientViewArguments args, + BuildContext context, + ) async { + //start loading circle + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + + var response1 = await http.post( + Uri.parse("${AppEnviroment.baseApiUrl}/minio/generate/claim-statement/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "document_type": data.document_type, + "patient_app_id": data.patient_app_id, + "patient_full_name": data.patient_full_name, + "patient_id_no": data.patient_id_no, + "has_med_aid": data.has_med_aid, + "med_aid_no": data.med_aid_no, + "med_aid_code": data.med_aid_code, + "med_aid_name": data.med_aid_name, + "med_aid_scheme": data.med_aid_scheme, + "busName": data.busName, + "busAddr": data.busAddr, + "busNo": data.busNo, + "busEmail": data.busEmail, + "provider_name": data.provider_name, + "practice_no": data.practice_no, + "vat_no": data.vat_no, + "service_date": data.service_date, + "service_desc": data.service_desc, + "service_desc_option": data.service_desc_option, + "procedure_name": data.procedure_name, + "procedure_additional_info": data.procedure_additional_info, + "icd10_code": data.icd10_code, + "amount": data.amount, + "pre_auth_no": data.pre_auth_no, + "logo_path": data.logo_path, + "sig_path": data.sig_path, + }), + ); + //print(response1.statusCode); + DateTime now = new DateTime.now(); + DateTime date = new DateTime(now.year, now.month, now.day); + String fileName = + "${data.document_type}-${data.patient_full_name}-${date.toString().substring(0, 10)}.pdf"; + if (response1.statusCode == 200) { + //Update this API + var response2 = await http.post( + Uri.parse("${AppEnviroment.baseApiUrl}/files/claim-statement/insert/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "app_id": data.patient_app_id, + "business_id": args.business!.business_id, + "file_path": "${data.patient_app_id}/claims-statements/$fileName", + "file_name": fileName + }), + ); + if (response2.statusCode == 201) { + // end loading circle + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed('/patient-manager/patient', arguments: args); + String message = + "The ${data.document_type}: $fileName has been successfully generated and added to ${data.patient_full_name}'s record. You can now access and download it for their use."; + successPopUp(message, context); + } else { + internetConnectionPopUp(context); + } + } else { + internetConnectionPopUp(context); + } + } + + /// This function is used to fetch a list of claim/ statement files for a ptient. + /// + /// Patameters: app_id . + /// + /// Returns List. + static Future> getClaimStatementFilesByPatient( + String app_id, + ) async { + //print("Patien manager page: $endpoint"); + final response = await http.get(Uri.parse( + "${AppEnviroment.baseApiUrl}/files/claim-statement/patient/$app_id")); + // print("Here"); + // print("Body: ${response.body}"); + // print("Code: ${response.statusCode}"); + // errorCode = response.statusCode.toString(); + // errorBody = response.body; + + if (response.statusCode == 200) { + //print("Here1"); + Iterable l = jsonDecode(response.body); + //print("Here2"); + List docList = List.from( + l.map((model) => ClaimStatementFile.fromJson(model))); + //print("Here3"); + //print(patientQueue); + return docList; + } else { + throw Exception( + 'failed to fatch patient claims statement files with api'); + } + } + + /// This function is used to fetch a list of claim/ statement files for a business. + /// + /// Patameters: business_id . + /// + /// Returns List. + static Future> getClaimStatementFilesByBusiness( + String business_id, + ) async { + //print("Patien manager page: $endpoint"); + final response = await http.get(Uri.parse( + "${AppEnviroment.baseApiUrl}/files/claim-statement/business/$business_id")); + // print("Here"); + // print("Body: ${response.body}"); + // print("Code: ${response.statusCode}"); + // errorCode = response.statusCode.toString(); + // errorBody = response.body; + + if (response.statusCode == 200) { + //print("Here1"); + Iterable l = jsonDecode(response.body); + //print("Here2"); + List docList = List.from( + l.map((model) => ClaimStatementFile.fromJson(model))); + //print("Here3"); + //print(patientQueue); + return docList; + } else { + throw Exception( + 'failed to fatch business claims statement files with api'); + } + } + + /// This function is used to Delete loyalty card from users mzansi wallet. + /// + /// Patameters:- + /// AppUser signedInUser, + /// int idloyalty_cards, + /// BuildContext context, + /// + /// Returns VOID (TRIGGERS NOTIGICATIOPN ON SUCCESS) + static Future deleteClaimStatementFilesByFileID( + PatientViewArguments args, + String filePath, + int fileID, + BuildContext context, + ) async { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + // delete file from minio + var response = await http.delete( + Uri.parse("${AppEnviroment.baseApiUrl}/minio/delete/file/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({"file_path": filePath}), + ); + //print("Here4"); + //print(response.statusCode); + if (response.statusCode == 200) { + //SQL delete + var response2 = await http.delete( + Uri.parse("${AppEnviroment.baseApiUrl}/files/claim-statement/delete"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({"idclaim_statement_file": fileID}), + ); + if (response2.statusCode == 200) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + //print(widget.business); + + Navigator.of(context) + .pushNamed('/patient-manager/patient', arguments: args); + + // Navigator.of(context) + // .pushNamed('/patient-profile', arguments: widget.signedInUser); + // setState(() {}); + String message = + "The File has been deleted successfully. This means it will no longer be visible on your and cannot be used for future appointments."; + successPopUp(message, context); + } else { + internetConnectionPopUp(context); + } + } else { + internetConnectionPopUp(context); + } + } + + //================== POP UPS ========================================================================== + + static void internetConnectionPopUp(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage( + errorType: "Internet Connection", + ); + }, + ); + } + + static void successPopUp(String message, BuildContext context) { + showDialog( + context: context, + builder: (context) { + return MIHSuccessMessage( + successType: "Success", + successMessage: message, + ); + }, + ); + } +} diff --git a/Frontend/lib/mih_apis/mih_icd10_code_api.dart b/Frontend/lib/mih_apis/mih_icd10_code_api.dart new file mode 100644 index 00000000..79fce4d0 --- /dev/null +++ b/Frontend/lib/mih_apis/mih_icd10_code_api.dart @@ -0,0 +1,55 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/icd10_code.dart.dart'; +import 'package:flutter/material.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +import '../mih_env/env.dart'; + +class MIHIcd10CodeApis { + final baseAPI = AppEnviroment.baseApiUrl; + + /// This function is used to fetch a list of icd 10 codes based on a search . + /// + /// Patameters: String search, BuildContext context + /// + /// Returns List. + static Future> getIcd10Codes( + String search, BuildContext context) async { + //print("Patien manager page: $endpoint"); + mihLoadingPopUp(context); + + final response = await http + .get(Uri.parse("${AppEnviroment.baseApiUrl}/icd10-codes/$search")); + // print("Here"); + // print("Body: ${response.body}"); + // print("Code: ${response.statusCode}"); + // errorCode = response.statusCode.toString(); + // errorBody = response.body; + + if (response.statusCode == 200) { + //print("Here1"); + Iterable l = jsonDecode(response.body); + //print("Here2"); + List icd10Codes = + List.from(l.map((model) => ICD10Code.fromJson(model))); + //print("Here3"); + //print(patientQueue); + Navigator.of(context).pop(); + return icd10Codes; + } else { + Navigator.of(context).pop(); + throw Exception('failed to fetch icd-10 codes with api'); + } + } + + static void mihLoadingPopUp(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + } +} diff --git a/Frontend/lib/mih_apis/mih_mzansi_wallet_apis.dart b/Frontend/lib/mih_apis/mih_mzansi_wallet_apis.dart index 3b64825c..d25df159 100644 --- a/Frontend/lib/mih_apis/mih_mzansi_wallet_apis.dart +++ b/Frontend/lib/mih_apis/mih_mzansi_wallet_apis.dart @@ -85,7 +85,7 @@ class MIHMzansiWalletApis { arguments: signedInUser, ); String message = - "The note has been deleted successfully. This means it will no longer be visible on your and cannot be used for future appointments."; + "The loyalty card has been deleted successfully. This means it will no longer be visible in your Mzansi Wallet."; successPopUp(message, context); } else { internetConnectionPopUp(context); @@ -125,7 +125,7 @@ class MIHMzansiWalletApis { // Navigator.pushNamed(context, '/patient-manager/patient', // arguments: widget.signedInUser); String message = - "Your $shop_name Loyalty Card was successfully added to you Mzansi Wallet."; + "Your $shop_name Loyalty Card was successfully added to your Mzansi Wallet."; Navigator.pop(context); Navigator.pop(context); Navigator.of(context).pushNamed( diff --git a/Frontend/lib/mih_objects/arguments.dart b/Frontend/lib/mih_objects/arguments.dart index 249fcbe4..3b924bbf 100644 --- a/Frontend/lib/mih_objects/arguments.dart +++ b/Frontend/lib/mih_objects/arguments.dart @@ -99,3 +99,61 @@ class PatientEditArguments { this.selectedPatient, ); } + +class ClaimStatementGenerationArguments { + final String document_type; + final String patient_app_id; + final String patient_full_name; + final String patient_id_no; + final String has_med_aid; + final String med_aid_no; + final String med_aid_code; + final String med_aid_name; + final String med_aid_scheme; + final String busName; + final String busAddr; + final String busNo; + final String busEmail; + final String provider_name; + final String practice_no; + final String vat_no; + final String service_date; + final String service_desc; + final String service_desc_option; + final String procedure_name; + final String procedure_additional_info; + final String icd10_code; + final String amount; + final String pre_auth_no; + final String logo_path; + final String sig_path; + + ClaimStatementGenerationArguments( + this.document_type, + this.patient_app_id, + this.patient_full_name, + this.patient_id_no, + this.has_med_aid, + this.med_aid_no, + this.med_aid_code, + this.med_aid_name, + this.med_aid_scheme, + this.busName, + this.busAddr, + this.busNo, + this.busEmail, + this.provider_name, + this.practice_no, + this.vat_no, + this.service_date, + this.service_desc, + this.service_desc_option, + this.procedure_name, + this.procedure_additional_info, + this.icd10_code, + this.amount, + this.pre_auth_no, + this.logo_path, + this.sig_path, + ); +} diff --git a/Frontend/lib/mih_objects/business.dart b/Frontend/lib/mih_objects/business.dart index 751c281c..cafded10 100644 --- a/Frontend/lib/mih_objects/business.dart +++ b/Frontend/lib/mih_objects/business.dart @@ -10,6 +10,8 @@ class Business { final String bus_email; final String app_id; final String gps_location; + final String practice_no; + final String vat_no; const Business( this.business_id, @@ -22,6 +24,8 @@ class Business { this.bus_email, this.app_id, this.gps_location, + this.practice_no, + this.vat_no, ); factory Business.fromJson(dynamic json) { @@ -36,6 +40,8 @@ class Business { json['bus_email'], json['app_id'], json['gps_location'], + json['practice_no'], + json['vat_no'], ); } } diff --git a/Frontend/lib/mih_objects/claim_statement_file.dart b/Frontend/lib/mih_objects/claim_statement_file.dart new file mode 100644 index 00000000..6845b53b --- /dev/null +++ b/Frontend/lib/mih_objects/claim_statement_file.dart @@ -0,0 +1,40 @@ +class ClaimStatementFile { + final int idclaim_statement_file; + final String app_id; + final String business_id; + final String insert_date; + final String file_path; + final String file_name; + + const ClaimStatementFile({ + required this.idclaim_statement_file, + required this.app_id, + required this.business_id, + required this.insert_date, + required this.file_path, + required this.file_name, + }); + + factory ClaimStatementFile.fromJson(Map json) { + return switch (json) { + { + "idclaim_statement_file": int idclaim_statement_file, + 'app_id': String app_id, + 'business_id': String business_id, + 'insert_date': String insert_date, + 'file_path': String file_path, + 'file_name': String file_name, + } => + ClaimStatementFile( + idclaim_statement_file: idclaim_statement_file, + app_id: app_id, + business_id: business_id, + insert_date: insert_date, + file_path: file_path, + file_name: file_name, + ), + _ => + throw const FormatException('Failed to load Claim Statement Object.'), + }; + } +} diff --git a/Frontend/lib/mih_objects/icd10_code.dart.dart b/Frontend/lib/mih_objects/icd10_code.dart.dart new file mode 100644 index 00000000..dc675090 --- /dev/null +++ b/Frontend/lib/mih_objects/icd10_code.dart.dart @@ -0,0 +1,23 @@ +class ICD10Code { + final String icd10; + final String description; + + const ICD10Code({ + required this.icd10, + required this.description, + }); + + factory ICD10Code.fromJson(Map json) { + return switch (json) { + { + "icd10": String icd10, + 'description': String description, + } => + ICD10Code( + icd10: icd10, + description: description, + ), + _ => throw const FormatException('Failed to load icd10 code object.'), + }; + } +} diff --git a/Frontend/lib/mih_packages/manage_business/business_details.dart b/Frontend/lib/mih_packages/manage_business/business_details.dart index 82265f68..a0654f30 100644 --- a/Frontend/lib/mih_packages/manage_business/business_details.dart +++ b/Frontend/lib/mih_packages/manage_business/business_details.dart @@ -48,14 +48,13 @@ class _BusinessDetailsState extends State { final contactController = TextEditingController(); final emailController = TextEditingController(); final locationController = TextEditingController(); + final practiceNoController = TextEditingController(); + final vatNoController = TextEditingController(); late PlatformFile? selectedLogo = null; late PlatformFile? selectedSignature = null; - // late Future futureBusinessUser; - // BusinessUser? businessUser; - // late Future futureBusiness; - // Business? business; + final ValueNotifier busType = ValueNotifier(""); late String business_id; late String oldLogoPath; @@ -79,32 +78,6 @@ class _BusinessDetailsState extends State { } } - // Future getBusinessUserDetails() async { - // var response = await http - // .get(Uri.parse("$baseAPI/business-user/${widget.signedInUser.app_id}")); - // if (response.statusCode == 200) { - // String body = response.body; - // var decodedData = jsonDecode(body); - // BusinessUser business_User = BusinessUser.fromJson(decodedData); - // return business_User; - // } else { - // return null; - // } - // } - - // Future getBusinessDetails() async { - // var response = await http.get( - // Uri.parse("$baseAPI/business/app_id/${widget.signedInUser.app_id}")); - // if (response.statusCode == 200) { - // String body = response.body; - // var decodedData = jsonDecode(body); - // Business business = Business.fromJson(decodedData); - // return business; - // } else { - // return null; - // } - // } - Future uploadSelectedFile( PlatformFile? file, TextEditingController controller) async { //to-do delete file when changed @@ -183,6 +156,8 @@ class _BusinessDetailsState extends State { "contact_no": contactController.text, "bus_email": emailController.text, "gps_location": locationController.text, + "practice_no": practiceNoController.text, + "vat_no": vatNoController.text, }), ); if (response.statusCode == 200) { @@ -275,6 +250,14 @@ class _BusinessDetailsState extends State { } } + void typeSelected() { + if (typeController.text.isNotEmpty) { + busType.value = typeController.text; + } else { + busType.value = ""; + } + } + @override void dispose() { nameController.dispose(); @@ -289,12 +272,15 @@ class _BusinessDetailsState extends State { contactController.dispose(); emailController.dispose(); locationController.dispose(); + practiceNoController.dispose(); + vatNoController.dispose(); _focusNode.dispose(); super.dispose(); } @override void initState() { + typeController.addListener(typeSelected); setState(() { //businessUser = results; titleController.text = widget.arguments.businessUser!.title; @@ -316,6 +302,8 @@ class _BusinessDetailsState extends State { contactController.text = widget.arguments.business!.contact_no; emailController.text = widget.arguments.business!.bus_email; locationController.text = widget.arguments.business!.gps_location; + practiceNoController.text = widget.arguments.business!.practice_no; + vatNoController.text = widget.arguments.business!.vat_no; }); super.initState(); @@ -374,6 +362,29 @@ class _BusinessDetailsState extends State { editable: true, ), const SizedBox(height: 10.0), + ValueListenableBuilder( + valueListenable: busType, + builder: + (BuildContext context, String value, Widget? child) { + return Visibility( + visible: value == "Doctors Office", + child: MIHTextField( + controller: practiceNoController, + hintText: "Practice Number", + editable: true, + required: true, + ), + ); + }, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: vatNoController, + hintText: "VAT Number", + editable: true, + required: true, + ), + const SizedBox(height: 10.0), MIHTextField( controller: contactController, hintText: "Contact Number", diff --git a/Frontend/lib/mih_packages/manage_business/profile_business_add.dart b/Frontend/lib/mih_packages/manage_business/profile_business_add.dart index fd496307..76052b74 100644 --- a/Frontend/lib/mih_packages/manage_business/profile_business_add.dart +++ b/Frontend/lib/mih_packages/manage_business/profile_business_add.dart @@ -52,10 +52,14 @@ class _ProfileBusinessAddState extends State { final contactController = TextEditingController(); final emailController = TextEditingController(); final locationController = TextEditingController(); + final practiceNoController = TextEditingController(); + final vatNoController = TextEditingController(); late PlatformFile selectedLogo; late PlatformFile selectedSignature; + final ValueNotifier busType = ValueNotifier(""); + Future uploadSelectedFile( PlatformFile file, TextEditingController controller) async { var token = await SuperTokens.getAccessToken(); @@ -125,6 +129,8 @@ class _ProfileBusinessAddState extends State { "contact_no": contactController.text, "bus_email": emailController.text, "gps_location": locationController.text, + "practice_no": practiceNoController.text, + "vat_no": vatNoController.text, }), ); if (response.statusCode == 201) { @@ -205,6 +211,14 @@ class _ProfileBusinessAddState extends State { return regex.hasMatch(text); } + void typeSelected() { + if (typeController.text.isNotEmpty) { + busType.value = typeController.text; + } else { + busType.value = ""; + } + } + MIHAction getActionButton() { return MIHAction( icon: const Icon(Icons.arrow_back), @@ -281,6 +295,28 @@ class _ProfileBusinessAddState extends State { editable: true, ), const SizedBox(height: 10.0), + ValueListenableBuilder( + valueListenable: busType, + builder: (BuildContext context, String value, Widget? child) { + return Visibility( + visible: value == "Doctors Office", + child: MIHTextField( + controller: practiceNoController, + hintText: "Practice Number", + editable: true, + required: true, + ), + ); + }, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: vatNoController, + hintText: "VAT Number", + editable: true, + required: true, + ), + const SizedBox(height: 10.0), MIHTextField( controller: contactController, hintText: "Contact Number", @@ -462,12 +498,15 @@ class _ProfileBusinessAddState extends State { contactController.dispose(); emailController.dispose(); locationController.dispose(); + practiceNoController.dispose(); + vatNoController.dispose(); _focusNode.dispose(); super.dispose(); } @override void initState() { + typeController.addListener(typeSelected); setState(() { fnameController.text = widget.signedInUser.fname; lnameController.text = widget.signedInUser.lname; @@ -489,197 +528,5 @@ class _ProfileBusinessAddState extends State { pullDownToRefresh: false, onPullDown: () async {}, ); - - // return Scaffold( - // // appBar: const MIHAppBar( - // // barTitle: "Add Business", - // // propicFile: null, - // // ), - // //drawer: MIHAppDrawer(signedInUser: widget.signedInUser), - // body: SafeArea( - // child: Stack( - // children: [ - // KeyboardListener( - // focusNode: _focusNode, - // autofocus: true, - // onKeyEvent: (event) async { - // if (event is KeyDownEvent && - // event.logicalKey == LogicalKeyboardKey.enter) { - // submitForm(); - // } - // }, - // child: SingleChildScrollView( - // padding: const EdgeInsets.all(15), - // child: Column( - // children: [ - // //const SizedBox(height: 15), - // const Text( - // "Add Business Profile", - // style: TextStyle( - // fontWeight: FontWeight.bold, - // fontSize: 25, - // ), - // ), - // const SizedBox(height: 25.0), - // MIHTextField( - // controller: regController, - // hintText: "Registration No.", - // editable: true, - // required: true, - // ), - // const SizedBox(height: 10.0), - // MIHTextField( - // controller: nameController, - // hintText: "Business Name", - // editable: true, - // required: true, - // ), - // const SizedBox(height: 10.0), - // MIHDropdownField( - // controller: typeController, - // hintText: "Business Type", - // dropdownOptions: const ["Doctors Office", "Other"], - // required: true, - // editable: true, - // ), - // const SizedBox(height: 10.0), - // MIHTextField( - // controller: contactController, - // hintText: "Contact Number", - // editable: true, - // required: true, - // ), - // const SizedBox(height: 10.0), - // MIHTextField( - // controller: emailController, - // hintText: "Email", - // editable: true, - // required: true, - // ), - // const SizedBox(height: 10.0), - // MIHFileField( - // controller: logonameController, - // hintText: "Logo", - // editable: false, - // required: true, - // onPressed: () async { - // FilePickerResult? result = - // await FilePicker.platform.pickFiles( - // type: FileType.custom, - // allowedExtensions: ['jpg', 'png', 'pdf'], - // ); - // if (result == null) return; - // final selectedFile = result.files.first; - // setState(() { - // selectedLogo = selectedFile; - // }); - // setState(() { - // logonameController.text = selectedFile.name; - // }); - // }, - // ), - // const SizedBox(height: 15.0), - // Divider( - // color: MzanziInnovationHub.of(context) - // ?.theme - // .secondaryColor(), - // ), - // //const SizedBox(height: 15.0), - // const Text( - // "My Business User", - // style: TextStyle( - // fontWeight: FontWeight.bold, - // fontSize: 25, - // ), - // ), - // const SizedBox(height: 25.0), - // MIHDropdownField( - // controller: titleController, - // hintText: "Title", - // dropdownOptions: const ["Doctor", "Assistant"], - // required: true, - // editable: true, - // ), - // const SizedBox(height: 10.0), - // MIHTextField( - // controller: fnameController, - // hintText: "Name", - // editable: false, - // required: true, - // ), - // const SizedBox(height: 10.0), - // MIHTextField( - // controller: lnameController, - // hintText: "Surname", - // editable: false, - // required: true, - // ), - // const SizedBox(height: 10.0), - // MIHFileField( - // controller: signtureController, - // hintText: "Signature", - // editable: false, - // required: true, - // onPressed: () async { - // FilePickerResult? result = - // await FilePicker.platform.pickFiles( - // type: FileType.custom, - // allowedExtensions: ['jpg', 'png', 'pdf'], - // ); - // if (result == null) return; - // final selectedFile = result.files.first; - // setState(() { - // selectedSignature = selectedFile; - // }); - // setState(() { - // signtureController.text = selectedFile.name; - // }); - // }, - // ), - // const SizedBox(height: 15.0), - // MIHDropdownField( - // controller: accessController, - // hintText: "Access", - // dropdownOptions: const ["Full", "Partial"], - // required: true, - // editable: false, - // ), - // const SizedBox(height: 30.0), - // SizedBox( - // width: 500.0, - // height: 50.0, - // child: MIHButton( - // buttonText: "Add", - // buttonColor: MzanziInnovationHub.of(context)! - // .theme - // .secondaryColor(), - // textColor: MzanziInnovationHub.of(context)! - // .theme - // .primaryColor(), - // onTap: () { - // submitForm(); - // }, - // ), - // ), - // ], - // ), - // ), - // ), - // Positioned( - // top: 10, - // left: 5, - // width: 50, - // height: 50, - // child: IconButton( - // onPressed: () { - // Navigator.of(context).pop(); - // }, - // icon: const Icon(Icons.arrow_back), - // ), - // ) - // ], - // ), - // ), - // ); } } diff --git a/Frontend/lib/mih_packages/patient_profile/Claim_Statement_Window.dart b/Frontend/lib/mih_packages/patient_profile/Claim_Statement_Window.dart new file mode 100644 index 00000000..d89a9b69 --- /dev/null +++ b/Frontend/lib/mih_packages/patient_profile/Claim_Statement_Window.dart @@ -0,0 +1,508 @@ +import 'package:Mzansi_Innovation_Hub/main.dart'; +import 'package:Mzansi_Innovation_Hub/mih_apis/mih_claim_statement_generation_api.dart'; +import 'package:Mzansi_Innovation_Hub/mih_apis/mih_icd10_code_api.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_button.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_date_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_dropdown_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_search_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_text_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_layout/mih_window.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_error_message.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/app_user.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/business.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/business_user.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/icd10_code.dart.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/patients.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/patient_profile/icd10_search_window.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class ClaimStatementWindow extends StatefulWidget { + final Patient selectedPatient; + final AppUser signedInUser; + final Business? business; + final BusinessUser? businessUser; + const ClaimStatementWindow({ + super.key, + required this.selectedPatient, + required this.signedInUser, + required this.business, + required this.businessUser, + }); + + @override + State createState() => _ClaimStatementWindowState(); +} + +class _ClaimStatementWindowState extends State { + final TextEditingController _docTypeController = TextEditingController(); + final TextEditingController _fullNameController = TextEditingController(); + final TextEditingController _idController = TextEditingController(); + final TextEditingController _medAidController = TextEditingController(); + final TextEditingController _medAidNoController = TextEditingController(); + final TextEditingController _medAidCodeController = TextEditingController(); + final TextEditingController _medAidNameController = TextEditingController(); + final TextEditingController _medAidSchemeController = TextEditingController(); + final TextEditingController _providerNameController = TextEditingController(); + final TextEditingController _practiceNoController = TextEditingController(); + final TextEditingController _vatNoController = TextEditingController(); + final TextEditingController _serviceDateController = TextEditingController(); + final TextEditingController _serviceDescController = TextEditingController(); + final TextEditingController _serviceDescOptionsController = + TextEditingController(); + final TextEditingController _prcedureNameController = TextEditingController(); + // final TextEditingController _procedureDateController = + // TextEditingController(); + final TextEditingController _proceedureAdditionalInfoController = + TextEditingController(); + final TextEditingController _icd10CodeController = TextEditingController(); + final TextEditingController _amountController = TextEditingController(); + final TextEditingController _preauthNoController = TextEditingController(); + final ValueNotifier serviceDesc = ValueNotifier(""); + final ValueNotifier medAid = ValueNotifier(""); + List icd10codeList = []; + + void icd10SearchWindow(List codeList) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => ICD10SearchWindow( + icd10CodeController: _icd10CodeController, + icd10codeList: codeList, + ), + ); + } + + Widget getWindowBody() { + return Column( + children: [ + MIHDropdownField( + controller: _docTypeController, + hintText: "Document Type", + dropdownOptions: const ["Claim", "Statement"], + required: true, + editable: true, + ), + const SizedBox(height: 10), + // Text( + // "Patient Details", + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 20, + // fontWeight: FontWeight.bold, + // color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + // ), + // ), + // Divider(color: MzanziInnovationHub.of(context)!.theme.secondaryColor()), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _fullNameController, + // hintText: "Full Name", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _idController, + // hintText: "ID No.", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _medAidController, + // hintText: "Has Medical Aid", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // ValueListenableBuilder( + // valueListenable: serviceDesc, + // builder: (BuildContext context, String value, Widget? child) { + // return Visibility( + // visible: value == "Yes", + // child: Column( + // children: [ + // MIHTextField( + // controller: _medAidNoController, + // hintText: "Medical Aid No.", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _medAidCodeController, + // hintText: "Medical Aid Code", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _medAidNameController, + // hintText: "Medical Aid Name", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _medAidSchemeController, + // hintText: "Medical Aid Scheme", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // ], + // ), + // ); + // }, + // ), + // Text( + // "Provider Details", + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 20, + // fontWeight: FontWeight.bold, + // color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + // ), + // ), + // Divider(color: MzanziInnovationHub.of(context)!.theme.secondaryColor()), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _providerNameController, + // hintText: "Provider Name", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _practiceNoController, + // hintText: "Practice No.", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + // MIHTextField( + // controller: _vatNoController, + // hintText: "VAT No.", + // editable: false, + // required: true, + // ), + // const SizedBox(height: 10), + Text( + "Service Details", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + Divider(color: MzanziInnovationHub.of(context)!.theme.secondaryColor()), + const SizedBox(height: 10), + MIHDateField( + controller: _serviceDateController, + lableText: "Date of Service", + required: true, + ), + const SizedBox(height: 10), + MIHDropdownField( + controller: _serviceDescController, + hintText: "Service Decription", + dropdownOptions: const [ + "Consultation", + "Procedure", + "Other", + ], + required: true, + editable: true, + ), + const SizedBox(height: 10), + ValueListenableBuilder( + valueListenable: serviceDesc, + builder: (BuildContext context, String value, Widget? child) { + Widget returnWidget; + switch (value) { + case 'Consultation': + returnWidget = Column( + children: [ + MIHDropdownField( + controller: _serviceDescOptionsController, + hintText: "Service Decription Options", + dropdownOptions: const [ + "General Consultation", + "Follow-Up Consultation", + "Specialist Consultation", + "Emergency Consultation", + ], + required: true, + editable: true, + ), + const SizedBox(height: 10), + ], + ); + case 'Procedure': + returnWidget = Column( + children: [ + MIHTextField( + controller: _prcedureNameController, + hintText: "Procedure Name", + editable: true, + required: true, + ), + const SizedBox(height: 10), + // MIHDateField( + // controller: _procedureDateController, + // lableText: "Procedure Date", + // required: true, + // ), + // const SizedBox(height: 10), + MIHTextField( + controller: _proceedureAdditionalInfoController, + hintText: "Additional Information", + editable: true, + required: true, + ), + const SizedBox(height: 10), + ], + ); + case 'Other': + returnWidget = Column( + children: [ + MIHTextField( + controller: _serviceDescOptionsController, + hintText: "Service Decription text", + editable: false, + required: true, + ), + const SizedBox(height: 10), + ], + ); + default: + returnWidget = const SizedBox(); + } + return returnWidget; + }, + ), + //const SizedBox(height: 10), + MIHSearchField( + controller: _icd10CodeController, + hintText: "ICD-10 Code & Description", + required: true, + editable: true, + onTap: () { + //api + MIHIcd10CodeApis.getIcd10Codes(_icd10CodeController.text, context) + .then((result) { + icd10SearchWindow(result); + }); + }, + ), + const SizedBox(height: 10), + MIHTextField( + controller: _amountController, + hintText: "Amount", + editable: true, + required: true, + ), + Text( + "Additional Infomation", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + Divider(color: MzanziInnovationHub.of(context)!.theme.secondaryColor()), + const SizedBox(height: 10), + MIHTextField( + controller: _preauthNoController, + hintText: "Pre-authorisation No.", + editable: true, + required: false, + ), + const SizedBox(height: 15), + SizedBox( + width: 500, + height: 50, + child: MIHButton( + onTap: () { + //generate document and uploud it + if (isInputValid()) { + MIHClaimStatementGenerationApi().generateClaimStatement( + ClaimStatementGenerationArguments( + _docTypeController.text, + widget.selectedPatient.app_id, + _fullNameController.text, + _idController.text, + _medAidController.text, + _medAidNoController.text, + _medAidCodeController.text, + _medAidNameController.text, + _medAidSchemeController.text, + widget.business!.Name, + "*To-Be Added*", + widget.business!.contact_no, + widget.business!.bus_email, + _providerNameController.text, + _practiceNoController.text, + _vatNoController.text, + _serviceDateController.text, + _serviceDescController.text, + _serviceDescOptionsController.text, + _prcedureNameController.text, + _proceedureAdditionalInfoController.text, + _icd10CodeController.text, + _amountController.text, + _preauthNoController.text, + widget.business!.logo_path, + widget.businessUser!.sig_path, + ), + PatientViewArguments( + widget.signedInUser, + widget.selectedPatient, + widget.businessUser, + widget.business, + "business", + ), + context); + } else { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Input Error"); + }, + ); + } + }, + buttonText: "Generate", + buttonColor: + MzanziInnovationHub.of(context)!.theme.secondaryColor(), + textColor: MzanziInnovationHub.of(context)!.theme.primaryColor(), + ), + ) + ], + ); + } + + void serviceDescriptionSelected() { + if (_serviceDescController.text.isNotEmpty) { + serviceDesc.value = _serviceDescController.text; + } else { + serviceDesc.value = ""; + } + } + + void hasMedAid() { + if (_medAidController.text.isNotEmpty) { + medAid.value = _medAidController.text; + } else { + medAid.value = ""; + } + } + + bool isInputValid() { + switch (_serviceDescController.text) { + case 'Procedure': + if (_docTypeController.text.isEmpty || + _serviceDateController.text.isEmpty || + _icd10CodeController.text.isEmpty || + _amountController.text.isEmpty || + _prcedureNameController.text.isEmpty || + _proceedureAdditionalInfoController.text.isEmpty) { + return false; + } else { + return true; + } + default: + if (_docTypeController.text.isEmpty || + _serviceDateController.text.isEmpty || + _icd10CodeController.text.isEmpty || + _amountController.text.isEmpty || + _serviceDescOptionsController.text.isEmpty) { + return false; + } else { + return true; + } + } + } + + String getUserTitle() { + if (widget.businessUser!.title == "Doctor") { + return "Dr."; + } else { + return widget.businessUser!.title; + } + } + + String getTodayDate() { + DateTime today = DateTime.now(); + return DateFormat('yyyy-MM-dd').format(today); + } + + @override + void dispose() { + _docTypeController.dispose(); + _fullNameController.dispose(); + _idController.dispose(); + _medAidController.dispose(); + _medAidNoController.dispose(); + _medAidCodeController.dispose(); + _medAidNameController.dispose(); + _medAidSchemeController.dispose(); + _providerNameController.dispose(); + _practiceNoController.dispose(); + _vatNoController.dispose(); + _serviceDateController.dispose(); + _serviceDescController.dispose(); + _serviceDescOptionsController.dispose(); + _prcedureNameController.dispose(); + // _procedureDateController.dispose(); + _proceedureAdditionalInfoController.dispose(); + _icd10CodeController.dispose(); + _preauthNoController.dispose(); + super.dispose(); + } + + @override + void initState() { + _serviceDescController.addListener(serviceDescriptionSelected); + _medAidController.addListener(hasMedAid); + _fullNameController.text = + "${widget.selectedPatient.first_name} ${widget.selectedPatient.last_name}"; + _idController.text = widget.selectedPatient.id_no; + _medAidController.text = widget.selectedPatient.medical_aid; + _medAidNameController.text = widget.selectedPatient.medical_aid_name; + _medAidCodeController.text = widget.selectedPatient.medical_aid_code; + _medAidNoController.text = widget.selectedPatient.medical_aid_no; + _medAidSchemeController.text = widget.selectedPatient.medical_aid_scheme; + _serviceDateController.text = getTodayDate(); + _providerNameController.text = + "${getUserTitle()} ${widget.signedInUser.fname} ${widget.signedInUser.lname}"; + _practiceNoController.text = widget.business!.practice_no; + _vatNoController.text = widget.business!.vat_no; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return MIHWindow( + fullscreen: false, + windowTitle: "Generate Claim/ Statement Document", + windowTools: const [], + onWindowTapClose: () { + // medicineController.clear(); + // quantityController.clear(); + // dosageController.clear(); + // timesDailyController.clear(); + // noDaysController.clear(); + // noRepeatsController.clear(); + Navigator.pop(context); + }, + windowBody: [ + getWindowBody(), + ], + ); + } +} diff --git a/Frontend/lib/mih_packages/patient_profile/builder/build_claim_statement_files_list.dart b/Frontend/lib/mih_packages/patient_profile/builder/build_claim_statement_files_list.dart new file mode 100644 index 00000000..ee548458 --- /dev/null +++ b/Frontend/lib/mih_packages/patient_profile/builder/build_claim_statement_files_list.dart @@ -0,0 +1,228 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/mih_apis/mih_claim_statement_generation_api.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/claim_statement_file.dart'; +import 'package:flutter/material.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +import '../../../main.dart'; +import '../../../mih_components/mih_layout/mih_window.dart'; +import '../../../mih_components/mih_pop_up_messages/mih_delete_message.dart'; +import '../../../mih_components/mih_pop_up_messages/mih_error_message.dart'; +import '../../../mih_components/mih_pop_up_messages/mih_success_message.dart'; +import '../../../mih_env/env.dart'; +import '../../../mih_objects/app_user.dart'; +import '../../../mih_objects/business.dart'; +import '../../../mih_objects/business_user.dart'; +import '../../../mih_objects/patients.dart'; +import 'build_file_view.dart'; + +class BuildClaimStatementFileList extends StatefulWidget { + final AppUser signedInUser; + final List files; + final Patient selectedPatient; + final Business? business; + final BusinessUser? businessUser; + final String type; + const BuildClaimStatementFileList({ + super.key, + required this.files, + required this.signedInUser, + required this.selectedPatient, + required this.business, + required this.businessUser, + required this.type, + }); + + @override + State createState() => + _BuildClaimStatementFileListState(); +} + +class _BuildClaimStatementFileListState + extends State { + int indexOn = 0; + final baseAPI = AppEnviroment.baseApiUrl; + final basefile = AppEnviroment.baseFileUrl; + String fileUrl = ""; + + Future getFileUrlApiCall(String filePath) async { + var url = "$baseAPI/minio/pull/file/${AppEnviroment.getEnv()}/$filePath"; + //print(url); + var response = await http.get(Uri.parse(url)); + // print("here1"); + //print(response.statusCode); + + if (response.statusCode == 200) { + //print("here2"); + String body = response.body; + //print(body); + //print("here3"); + var decodedData = jsonDecode(body); + //print("Dedoced: ${decodedData['minioURL']}"); + + return decodedData['minioURL']; + //AppUser u = AppUser.fromJson(decodedData); + // print(u.email); + //return "AlometThere"; + } else { + throw Exception("Error: GetUserData status code ${response.statusCode}"); + } + } + + void internetConnectionPopUp() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Internet Connection"); + }, + ); + } + + void successPopUp(String message) { + showDialog( + context: context, + builder: (context) { + return MIHSuccessMessage( + successType: "Success", + successMessage: message, + ); + }, + ); + } + + void deleteFilePopUp(String filePath, int fileID) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => MIHDeleteMessage( + deleteType: "File", + onTap: () async { + //API Call here + await MIHClaimStatementGenerationApi + .deleteClaimStatementFilesByFileID( + PatientViewArguments( + widget.signedInUser, + widget.selectedPatient, + widget.businessUser, + widget.business, + "business", + ), + filePath, + fileID, + context, + ); + }, + ), + ); + } + + void viewFilePopUp(String fileName, String filePath, int fileID, String url) { + bool hasAccessToDelete = false; + if (widget.type == "business") { + hasAccessToDelete = true; + } + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => MIHWindow( + fullscreen: true, + windowTitle: fileName, + windowBody: [ + BuildFileView( + link: url, + path: filePath, + //pdfLink: '${AppEnviroment.baseFileUrl}/mih/$filePath', + ), + const SizedBox( + height: 10, + ) + ], + windowTools: [ + Visibility( + visible: hasAccessToDelete, + child: IconButton( + onPressed: () { + deleteFilePopUp(filePath, fileID); + }, + icon: Icon( + size: 35, + Icons.delete, + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + ), + ], + onWindowTapClose: () { + Navigator.pop(context); + }, + ), + ); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (widget.files.isNotEmpty) { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (BuildContext context, int index) { + return Divider( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ); + }, + itemCount: widget.files.length, + itemBuilder: (context, index) { + return ListTile( + title: Text( + widget.files[index].file_name, + style: TextStyle( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + subtitle: Text( + widget.files[index].insert_date, + style: TextStyle( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + // trailing: Icon( + // Icons.arrow_forward, + // color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + // ), + onTap: () async { + await getFileUrlApiCall(widget.files[index].file_path) + .then((urlHere) { + //print(url); + setState(() { + fileUrl = urlHere; + }); + }); + + viewFilePopUp( + widget.files[index].file_name, + widget.files[index].file_path, + widget.files[index].idclaim_statement_file, + fileUrl); + }, + ); + }, + ); + } else { + return const Center( + child: Text( + "No Documents Available", + style: TextStyle(fontSize: 25, color: Colors.grey), + textAlign: TextAlign.center, + ), + ); + } + } +} diff --git a/Frontend/lib/mih_packages/patient_profile/builder/build_icd10_code_list.dart b/Frontend/lib/mih_packages/patient_profile/builder/build_icd10_code_list.dart new file mode 100644 index 00000000..7bce328c --- /dev/null +++ b/Frontend/lib/mih_packages/patient_profile/builder/build_icd10_code_list.dart @@ -0,0 +1,76 @@ +import 'package:Mzansi_Innovation_Hub/mih_objects/icd10_code.dart.dart'; +import 'package:flutter/material.dart'; + +import '../../../main.dart'; +import '../../../mih_env/env.dart'; + +class BuildICD10CodeList extends StatefulWidget { + final TextEditingController icd10CodeController; + final List icd10codeList; + + const BuildICD10CodeList({ + super.key, + required this.icd10CodeController, + required this.icd10codeList, + }); + + @override + State createState() => _BuildPatientsListState(); +} + +class _BuildPatientsListState extends State { + String baseAPI = AppEnviroment.baseApiUrl; + int counter = 0; + + Widget displayCode(int index) { + String title = "ICD-10 Code: ${widget.icd10codeList[index].icd10}"; + String description = + "Description: ${widget.icd10codeList[index].description}"; + return ListTile( + title: Text( + title, + style: TextStyle( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + subtitle: RichText( + text: TextSpan( + text: description, + style: DefaultTextStyle.of(context).style, + ), + ), + onTap: () { + //select code + setState(() { + widget.icd10CodeController.text = + "${widget.icd10codeList[index].icd10} - ${widget.icd10codeList[index].description}"; + }); + Navigator.of(context).pop(); + }, + ); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (BuildContext context, index) { + return Divider( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ); + }, + itemCount: widget.icd10codeList.length, + itemBuilder: (context, index) { + //final patient = widget.patients[index].id_no.contains(widget.searchString); + //print(index); + return displayCode(index); + }, + ); + } +} diff --git a/Frontend/lib/mih_packages/patient_profile/icd10_search_window.dart b/Frontend/lib/mih_packages/patient_profile/icd10_search_window.dart new file mode 100644 index 00000000..f7613aaa --- /dev/null +++ b/Frontend/lib/mih_packages/patient_profile/icd10_search_window.dart @@ -0,0 +1,58 @@ +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_text_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_layout/mih_window.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/icd10_code.dart.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/patient_profile/builder/build_icd10_code_list.dart'; +import 'package:flutter/material.dart'; + +class ICD10SearchWindow extends StatefulWidget { + final TextEditingController icd10CodeController; + final List icd10codeList; + const ICD10SearchWindow({ + super.key, + required this.icd10CodeController, + required this.icd10codeList, + }); + + @override + State createState() => _ICD10SearchWindowState(); +} + +class _ICD10SearchWindowState extends State { + Widget getWindowBody() { + return Column( + children: [ + MIHTextField( + controller: widget.icd10CodeController, + hintText: "Search Text", + editable: false, + required: false, + ), + BuildICD10CodeList( + icd10CodeController: widget.icd10CodeController, + icd10codeList: widget.icd10codeList, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return MIHWindow( + fullscreen: false, + windowTitle: "ICD-10 Search", + windowTools: const [], + onWindowTapClose: () { + // medicineController.clear(); + // quantityController.clear(); + // dosageController.clear(); + // timesDailyController.clear(); + // noDaysController.clear(); + // noRepeatsController.clear(); + Navigator.pop(context); + }, + windowBody: [ + getWindowBody(), + ], + ); + } +} diff --git a/Frontend/lib/mih_packages/patient_profile/patient_claims_statements.dart b/Frontend/lib/mih_packages/patient_profile/patient_claims_statements.dart new file mode 100644 index 00000000..b168816e --- /dev/null +++ b/Frontend/lib/mih_packages/patient_profile/patient_claims_statements.dart @@ -0,0 +1,272 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/mih_apis/mih_claim_statement_generation_api.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/claim_statement_file.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/patient_profile/Claim_Statement_Window.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/patient_profile/builder/build_claim_statement_files_list.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import '../../main.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +import '../../mih_components/mih_pop_up_messages/mih_error_message.dart'; +import '../../mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import '../../mih_components/mih_pop_up_messages/mih_success_message.dart'; +import '../../mih_env/env.dart'; +import '../../mih_objects/app_user.dart'; +import '../../mih_objects/business.dart'; +import '../../mih_objects/business_user.dart'; +import '../../mih_objects/files.dart'; +import '../../mih_objects/patients.dart'; +// import 'builder/build_files_list.dart'; + +class PatientClaimsOrStatements extends StatefulWidget { + final int patientIndex; + final Patient selectedPatient; + final AppUser signedInUser; + final Business? business; + final BusinessUser? businessUser; + final String type; + + const PatientClaimsOrStatements({ + super.key, + required this.patientIndex, + required this.selectedPatient, + required this.signedInUser, + required this.business, + required this.businessUser, + required this.type, + }); + + @override + State createState() => + _PatientClaimsOrStatementsState(); +} + +class _PatientClaimsOrStatementsState extends State { + String endpointFiles = "${AppEnviroment.baseApiUrl}/files/patients/"; + String endpointUser = "${AppEnviroment.baseApiUrl}/users/profile/"; + String endpointGenFiles = + "${AppEnviroment.baseApiUrl}/files/generate/med-cert/"; + String endpointFileUpload = "${AppEnviroment.baseApiUrl}/files/upload/file/"; + String endpointInsertFiles = "${AppEnviroment.baseApiUrl}/files/insert/"; + + final startDateController = TextEditingController(); + final endDateTextController = TextEditingController(); + final retDateTextController = TextEditingController(); + final selectedFileController = TextEditingController(); + final medicineController = TextEditingController(); + final quantityController = TextEditingController(); + final dosageController = TextEditingController(); + final timesDailyController = TextEditingController(); + final noDaysController = TextEditingController(); + final noRepeatsController = TextEditingController(); + final outputController = TextEditingController(); + + late Future> futueFiles; + late String userEmail = ""; + late PlatformFile selected; + final baseAPI = AppEnviroment.baseApiUrl; + + Future> fetchFiles() async { + final response = await http.get(Uri.parse( + "${AppEnviroment.baseApiUrl}/files/patients/${widget.selectedPatient.app_id}")); + + //print(response.statusCode); + //print(response.body); + if (response.statusCode == 200) { + Iterable l = jsonDecode(response.body); + List files = + List.from(l.map((model) => PFile.fromJson(model))); + return files; + } else { + internetConnectionPopUp(); + throw Exception('failed to load patients'); + } + } + + void successPopUp(String message) { + showDialog( + context: context, + builder: (context) { + return MIHSuccessMessage( + successType: "Success", + successMessage: message, + ); + }, + ); + } + + void messagePopUp(error) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(error), + ); + }, + ); + } + + void claimOrStatementWindow() { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => ClaimStatementWindow( + selectedPatient: widget.selectedPatient, + signedInUser: widget.signedInUser, + business: widget.business, + businessUser: widget.businessUser, + ), + ); + } + + void internetConnectionPopUp() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Internet Connection"); + }, + ); + } + + bool isMedCertFieldsFilled() { + if (startDateController.text.isEmpty || + endDateTextController.text.isEmpty || + retDateTextController.text.isEmpty) { + return false; + } else { + return true; + } + } + + bool isFileFieldsFilled() { + if (selectedFileController.text.isEmpty) { + return false; + } else { + return true; + } + } + + List setIcons() { + if (widget.type == "personal") { + return [ + Text( + "Claims/ Statements", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + // IconButton( + // onPressed: () { + // uploudFilePopUp(); + // }, + // icon: Icon( + // Icons.add, + // color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + // ), + // ) + ]; + } else { + return [ + Text( + "Claims/ Statements", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + IconButton( + onPressed: () { + // new window to input fields for claim/ statements + claimOrStatementWindow(); + }, + icon: Icon( + Icons.add, + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ) + ]; + } + } + + @override + void dispose() { + startDateController.dispose(); + endDateTextController.dispose(); + retDateTextController.dispose(); + selectedFileController.dispose(); + medicineController.dispose(); + quantityController.dispose(); + dosageController.dispose(); + timesDailyController.dispose(); + noDaysController.dispose(); + noRepeatsController.dispose(); + outputController.dispose(); + super.dispose(); + } + + @override + void initState() { + if (widget.business == null) { + futueFiles = + MIHClaimStatementGenerationApi.getClaimStatementFilesByPatient( + widget.signedInUser.app_id); + } else { + futueFiles = + MIHClaimStatementGenerationApi.getClaimStatementFilesByBusiness( + widget.business!.business_id); + } + + //patientDetails = getPatientDetails() as Patient; + //getUserDetails(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: futueFiles, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: Mihloadingcircle(), + ); + } else if (snapshot.hasData) { + final filesList = snapshot.data!; + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: setIcons(), + ), + Divider( + color: + MzanziInnovationHub.of(context)!.theme.secondaryColor()), + const SizedBox(height: 10), + //const Placeholder(), + BuildClaimStatementFileList( + files: filesList, + signedInUser: widget.signedInUser, + selectedPatient: widget.selectedPatient, + business: widget.business, + businessUser: widget.businessUser, + type: widget.type, + ), + ], + ); + } else { + return const Center( + child: Text("Error Loading Notes"), + ); + } + }, + ); + } +} diff --git a/Frontend/lib/mih_packages/patient_profile/patient_view.dart b/Frontend/lib/mih_packages/patient_profile/patient_view.dart index 73de67d2..4144506f 100644 --- a/Frontend/lib/mih_packages/patient_profile/patient_view.dart +++ b/Frontend/lib/mih_packages/patient_profile/patient_view.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:Mzansi_Innovation_Hub/mih_packages/patient_profile/patient_claims_statements.dart'; import 'package:flutter/material.dart'; import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; import '../../main.dart'; @@ -94,7 +95,7 @@ class _PatientViewState extends State { businessUser: widget.arguments.businessUser, type: widget.arguments.type, ); - } else { + } else if (index == 2) { return PatientFiles( patientIndex: widget.arguments.selectedPatient!.idpatients, selectedPatient: widget.arguments.selectedPatient!, @@ -103,6 +104,15 @@ class _PatientViewState extends State { businessUser: widget.arguments.businessUser, type: widget.arguments.type, ); + } else { + return PatientClaimsOrStatements( + patientIndex: widget.arguments.selectedPatient!.idpatients, + selectedPatient: widget.arguments.selectedPatient!, + signedInUser: widget.arguments.signedInUser, + business: widget.arguments.business, + businessUser: widget.arguments.businessUser, + type: widget.arguments.type, + ); } } @@ -221,6 +231,35 @@ class _PatientViewState extends State { ), ), ), + //============ Claims/ Statements ================ + Visibility( + visible: _selectedIndex != 3, + child: IconButton( + onPressed: () { + setState(() { + _selectedIndex = 3; + }); + }, + icon: const Icon( + Icons.file_open_outlined, + size: 35, + ), + ), + ), + Visibility( + visible: _selectedIndex == 3, + child: IconButton.filled( + onPressed: () { + setState(() { + _selectedIndex = 3; + }); + }, + icon: const Icon( + Icons.file_open_outlined, + size: 35, + ), + ), + ), ], ); } diff --git a/backend/ICD10_Codes/ICD-10_MIT_2021_Excel_16-March_2021.xls b/backend/ICD10_Codes/ICD-10_MIT_2021_Excel_16-March_2021.xls new file mode 100644 index 00000000..50ec386a Binary files /dev/null and b/backend/ICD10_Codes/ICD-10_MIT_2021_Excel_16-March_2021.xls differ diff --git a/backend/main.py b/backend/main.py index 2dda05e9..b38b7809 100644 --- a/backend/main.py +++ b/backend/main.py @@ -6,6 +6,7 @@ import routers.patients as patients import routers.patients_files as patients_files import routers.patients_notes as patients_notes import routers.patients_queue as patients_queue +import routers.claim_statement_files as claim_statement_files import routers.users as users import routers.notifications as notifications import routers.fileStorage as fileStorage @@ -15,6 +16,7 @@ import routers.business as business import routers.access_request as access_request import routers.patient_access as patient_access import routers.mzansi_wallet as mzansi_wallet +import routers.icd10_codes as icd10_codes from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware import Middleware from supertokens_python import get_all_cors_headers @@ -83,11 +85,13 @@ app.include_router(access_request.router) app.include_router(patient_access.router) app.include_router(users.router) app.include_router(fileStorage.router) +app.include_router(claim_statement_files.router) app.include_router(medicine.router) app.include_router(business_user.router) app.include_router(business.router) app.include_router(notifications.router) app.include_router(mzansi_wallet.router) +app.include_router(icd10_codes.router) # Check if server is up @app.get("/", tags=["Server Check"]) diff --git a/backend/routers/business.py b/backend/routers/business.py index 91c2ac68..087eafe6 100644 --- a/backend/routers/business.py +++ b/backend/routers/business.py @@ -25,6 +25,8 @@ class businessInsertRequest(BaseModel): contact_no: str bus_email: str gps_location: str + practice_no: str + vat_no: str class businessUpdateRequest(BaseModel): business_id: str @@ -36,6 +38,8 @@ class businessUpdateRequest(BaseModel): contact_no: str bus_email: str gps_location: str + practice_no: str + vat_no: str # Get List of all files @@ -43,7 +47,10 @@ class businessUpdateRequest(BaseModel): async def read_business_by_business_id(business_id: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) db = database.dbConnection.dbAppDataConnect() cursor = db.cursor() - query = "SELECT business.business_id, business.Name, business.type, business.registration_no, business.logo_name, business.logo_path, business.contact_no, business.bus_email, business_users.app_id, business.gps_location " + query = "SELECT business.business_id, business.Name, business.type, business.registration_no, " + query += "business.logo_name, business.logo_path, business.contact_no, business.bus_email, " + query += "business_users.app_id, business.gps_location, " + query += "practice_no, vat_no " query += "FROM business " query += "inner join business_users " query += "on business.business_id=business_users.business_id " @@ -64,6 +71,8 @@ async def read_business_by_business_id(business_id: str, session: SessionContain "bus_email": item[7], "app_id": item[8], "gps_location": item[9], + "practice_no": item[10], + "vat_no": item[11], } for item in cursor.fetchall() ] @@ -81,7 +90,10 @@ async def read_business_by_business_id(business_id: str, session: SessionContain async def read_business_by_app_id(app_id: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) db = database.dbConnection.dbAppDataConnect() cursor = db.cursor() - query = "SELECT business.business_id, business.Name, business.type, business.registration_no, business.logo_name, business.logo_path, business.contact_no, business.bus_email, business_users.app_id, business.gps_location " + query = "SELECT business.business_id, business.Name, business.type, business.registration_no, " + query += "business.logo_name, business.logo_path, business.contact_no, business.bus_email, " + query += "business_users.app_id, business.gps_location, " + query += "practice_no, vat_no " query += "FROM business " query += "inner join business_users " query += "on business.business_id=business_users.business_id " @@ -102,6 +114,8 @@ async def read_business_by_app_id(app_id: str, session: SessionContainer = Depen "bus_email": item[7], "app_id": item[8], "gps_location": item[9], + "practice_no": item[10], + "vat_no": item[11], } for item in cursor.fetchall() ] @@ -118,8 +132,8 @@ async def insert_business_details(itemRequest : businessInsertRequest, session: db = database.dbConnection.dbAppDataConnect() cursor = db.cursor() query = "insert into business " - query += "(business_id, Name, type, registration_no, logo_name, logo_path, contact_no, bus_email, gps_location) " - query += "values (%s, %s, %s, %s, %s, %s, %s, %s, %s)" + query += "(business_id, Name, type, registration_no, logo_name, logo_path, contact_no, bus_email, gps_location, practice_no, vat_no), " + query += "values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" uuidString = str(uuid.uuid1()) userData = (uuidString, itemRequest.Name, @@ -129,7 +143,9 @@ async def insert_business_details(itemRequest : businessInsertRequest, session: itemRequest.logo_path, itemRequest.contact_no, itemRequest.bus_email, - itemRequest.gps_location,) + itemRequest.gps_location, + itemRequest.practice_no, + itemRequest.vat_no) try: cursor.execute(query, userData) except Exception as error: @@ -146,7 +162,7 @@ async def Update_Business_details(itemRequest : businessUpdateRequest, session: # print(itemRequest.gps_location) cursor = db.cursor() query = "update business " - query += "set Name=%s, type=%s, registration_no=%s, logo_name=%s, logo_path=%s, contact_no=%s, bus_email=%s, gps_location=%s " + query += "set Name=%s, type=%s, registration_no=%s, logo_name=%s, logo_path=%s, contact_no=%s, bus_email=%s, gps_location=%s, practice_no=%s, vat_no=%s " query += "where business_id=%s" userData = (itemRequest.Name, itemRequest.type, @@ -156,6 +172,8 @@ async def Update_Business_details(itemRequest : businessUpdateRequest, session: itemRequest.contact_no, itemRequest.bus_email, itemRequest.gps_location, + itemRequest.practice_no, + itemRequest.vat_no, itemRequest.business_id, ) try: diff --git a/backend/routers/claim_statement_files.py b/backend/routers/claim_statement_files.py new file mode 100644 index 00000000..87104724 --- /dev/null +++ b/backend/routers/claim_statement_files.py @@ -0,0 +1,112 @@ +import mysql.connector +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +#from ..database import dbConnection +import database +from datetime import date +#SuperToken Auth from front end +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +router = APIRouter() + +class claimStatementDeleteRequest(BaseModel): + idclaim_statement_file: int + +class claimStatementInsertRequest(BaseModel): + app_id: str + business_id: str + file_path: str + file_name: str + + +# Get List of all files by patient +@router.get("/files/claim-statement/patient/{app_id}", tags=["Claim Statement Files"]) +async def read_all_claim_statement_files_by_app_id(app_id: str, session: SessionContainer = Depends(verify_session())): + db = database.dbConnection.dbPatientManagerConnect() + cursor = db.cursor() + query = "SELECT * FROM claim_statement_file where app_id = %s ORDER BY insert_date DESC" + cursor.execute(query, (app_id,)) + items = [ + { + "idclaim_statement_file": item[0], + "app_id": item[1], + "business_id": item[2], + "insert_date": item[3], + "file_path": item[4], + "file_name": item[5], + } + for item in cursor.fetchall() + ] + cursor.close() + db.close() + return items + +# Get List of all files by patient +@router.get("/files/claim-statement/business/{business_id}", tags=["Claim Statement Files"]) +async def read_all_claim_statement_files_by_business_id(business_id: str, session: SessionContainer = Depends(verify_session())): + db = database.dbConnection.dbPatientManagerConnect() + cursor = db.cursor() + query = "SELECT * FROM claim_statement_file where business_id = %s ORDER BY insert_date DESC" + cursor.execute(query, (business_id,)) + items = [ + { + "idclaim_statement_file": item[0], + "app_id": item[1], + "business_id": item[2], + "insert_date": item[3], + "file_path": item[4], + "file_name": item[5], + } + for item in cursor.fetchall() + ] + cursor.close() + db.close() + return items + +# Delete Patient note on table +@router.delete("/files/claim-statement/delete/", tags=["Claim Statement Files"]) +async def Delete_Patient_File(itemRequest : claimStatementDeleteRequest, session: SessionContainer = Depends(verify_session())): #session: SessionContainer = Depends(verify_session()) + # today = date.today() + db = database.dbConnection.dbPatientManagerConnect() + cursor = db.cursor() + query = "delete from claim_statement_file " + query += "where idclaim_statement_file=%s" + # notetData = (itemRequest.idpatient_notes) + try: + cursor.execute(query, (str(itemRequest.idclaim_statement_file),)) + except Exception as error: + raise HTTPException(status_code=404, detail="Failed to Delete Record") + #return {"query": query, "message": error} + db.commit() + cursor.close() + db.close() + return {"message": "Successfully deleted Record"} + +# Insert Patient note into table +@router.post("/files/claim-statement/insert/", tags=["Claim Statement Files"], status_code=201) +async def insert_Patient_Files(itemRequest : claimStatementInsertRequest, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) + today = date.today() + db = database.dbConnection.dbPatientManagerConnect() + cursor = db.cursor() + query = "insert into claim_statement_file " + query += "(app_id, business_id, file_path, file_name, insert_date) " + query += "values (%s, %s, %s, %s, %s)" + notetData = ( + itemRequest.app_id, + itemRequest.business_id, + itemRequest.file_path, + itemRequest.file_name, + today, + ) + try: + cursor.execute(query, notetData) + except Exception as error: + print(error) + raise HTTPException(status_code=404, detail="Failed to Create Record") + # return {"message": error} + db.commit() + cursor.close() + db.close() + return {"message": "Successfully Created file Record"} diff --git a/backend/routers/fileStorage.py b/backend/routers/fileStorage.py index cc0069c2..adda5a9d 100644 --- a/backend/routers/fileStorage.py +++ b/backend/routers/fileStorage.py @@ -71,6 +71,35 @@ class perscriptionList(BaseModel): sig_path: str data: List[perscription] +class claimStatementUploud(BaseModel): + document_type: str + patient_app_id: str + patient_full_name: str + patient_id_no: str + has_med_aid: str + med_aid_no: str + med_aid_code: str + med_aid_name: str + med_aid_scheme: str + busName: str + busAddr: str + busNo: str + busEmail: str + provider_name: str + practice_no: str + vat_no: str + service_date: str + service_desc: str + service_desc_option: str + procedure_name: str + # procedure_date: str + procedure_additional_info: str + icd10_code: str + amount: str + pre_auth_no: str + logo_path: str + sig_path: str + @router.get("/minio/pull/file/{env}/{app_id}/{folder}/{file_name}", tags=["Minio"]) async def pull_File_from_user(app_id: str, folder: str, file_name: str, env: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) path = app_id + "/" + folder + "/" + file_name @@ -93,7 +122,8 @@ async def pull_File_from_user(app_id: str, folder: str, file_name: str, env: str # return {"message": error} if(env == "Dev"): return { - "minioURL": f"http://10.0.2.2:9000/mih/{app_id}/{folder}/{file_name}", + # 10.0.2.2 + "minioURL": f"http://localhost:9000/mih/{app_id}/{folder}/{file_name}", } else: return { @@ -122,7 +152,7 @@ async def delete_File_of_user(requestItem: minioDeleteRequest, session: SessionC path = requestItem.file_path try: # uploudFile(app_id, file.filename, extension[1], content) - client = Minio_Storage.minioConnection.minioConnect("dev") + client = Minio_Storage.minioConnection.minioConnect("Prd") minioError = client.remove_object("mih", path) except Exception as error: @@ -147,6 +177,11 @@ async def upload_perscription_to_user(requestItem: perscriptionList, session: Se uploudPerscription(requestItem) return {"message": "Successfully Generated File"} +@router.post("/minio/generate/claim-statement/", tags=["Minio"]) +async def upload_perscription_to_user(requestItem: claimStatementUploud, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) + uploudClaimStatement(requestItem) + return {"message": "Successfully Generated File"} + def uploudFile(app_id, folder, fileName, extension, content): client = Minio_Storage.minioConnection.minioConnect("Dev") found = client.bucket_exists("mih") @@ -162,9 +197,8 @@ def uploudFile(app_id, folder, fileName, extension, content): part_size=10*1024*1024, content_type=f"application/{extension}") - def uploudMedCert(requestItem: medCertUploud): - client = Minio_Storage.minioConnection.minioConnect("dev") + client = Minio_Storage.minioConnection.minioConnect("Prod") generateMedCertPDF(requestItem) today = datetime.today().strftime('%Y-%m-%d') found = client.bucket_exists("mih") @@ -176,7 +210,7 @@ def uploudMedCert(requestItem: medCertUploud): client.fput_object("mih", fileName, "temp-med-cert.pdf") def generateMedCertPDF(requestItem: medCertUploud): - client = Minio_Storage.minioConnection.minioConnect("dev") + client = Minio_Storage.minioConnection.minioConnect("Prod") new_logo_path = requestItem.logo_path.replace(" ","-") new_sig_path = requestItem.sig_path.replace(" ","-") minioLogo = client.get_object("mih", new_logo_path).read() @@ -241,7 +275,7 @@ def generateMedCertPDF(requestItem: medCertUploud): myCanvas.save() def uploudPerscription(requestItem: perscriptionList): - client = Minio_Storage.minioConnection.minioConnect("dev") + client = Minio_Storage.minioConnection.minioConnect("Prod") generatePerscriptionPDF(requestItem) today = datetime.today().strftime('%Y-%m-%d') found = client.bucket_exists("mih") @@ -253,7 +287,7 @@ def uploudPerscription(requestItem: perscriptionList): client.fput_object("mih", fileName, "temp-perscription.pdf") def generatePerscriptionPDF(requestItem: perscriptionList): - client = Minio_Storage.minioConnection.minioConnect("dev") + client = Minio_Storage.minioConnection.minioConnect("Prod") new_logo_path = requestItem.logo_path.replace(" ","-") new_sig_path = requestItem.sig_path.replace(" ","-") minioLogo = client.get_object("mih", new_logo_path).read() @@ -263,7 +297,7 @@ def generatePerscriptionPDF(requestItem: perscriptionList): w,h = A4 - myCanvas = canvas.Canvas("temp-perscription.pdf", pagesize=A4) + myCanvas = canvas.Canvas("temp-claim.pdf", pagesize=A4) #Business Logo myCanvas.drawImage(imageLogo, 50, h - 125,100,100, mask='auto') @@ -341,9 +375,180 @@ def generatePerscriptionPDF(requestItem: perscriptionList): myCanvas.save() -def byteToMb(size): - sizekb = size /1000 - sizemb = sizekb / 1000 - return sizemb +def uploudClaimStatement(requestItem: claimStatementUploud): + try: + client = Minio_Storage.minioConnection.minioConnect("Prod") + print("connected") + except Exception: + print("error") + + generateClaimStatementPDF(requestItem) + today = datetime.today().strftime('%Y-%m-%d') + found = client.bucket_exists("mih") + if not found: + client.make_bucket("mih") + else: + print("Bucket already exists") + fileName = f"{requestItem.patient_app_id}/claims-statements/{requestItem.document_type}-{requestItem.patient_full_name}-{today}.pdf" + client.fput_object("mih", fileName, "temp-claim-statement.pdf") -# print(byteToMb(229574)) \ No newline at end of file +def generateClaimStatementPDF(requestItem: claimStatementUploud): + client = Minio_Storage.minioConnection.minioConnect("Prod") + # print("buckets: " + client.list_buckets) + new_logo_path = requestItem.logo_path.replace(" ","-") + new_sig_path = requestItem.sig_path.replace(" ","-") + print("Path Logo: " + new_logo_path) + minioLogo = client.get_object("mih", new_logo_path).read() + imageLogo = ImageReader(io.BytesIO(minioLogo)) + minioSig = client.get_object("mih", new_sig_path).read() + imageSig = ImageReader(io.BytesIO(minioSig)) + w,h = A4 + + + myCanvas = canvas.Canvas("temp-claim-statement.pdf", pagesize=A4) + + #Business Logo + myCanvas.drawImage(imageLogo, 50, h - 125,100,100, mask='auto') + + #Business Details + myCanvas.setFont('Helvetica-Bold', 10) + myCanvas.drawRightString(w - 50,h - 40, f"Name: {requestItem.busName}") + myCanvas.drawRightString(w - 50,h - 55, f"Address: {requestItem.busAddr}") + myCanvas.drawRightString(w - 50,h - 70, f"Practice No.: {requestItem.practice_no}") + myCanvas.drawRightString(w - 50,h - 85, f"Contact No.: {requestItem.busNo}") + myCanvas.drawRightString(w - 50,h - 100, f"Email: {requestItem.busEmail}") + myCanvas.line(50,h-150,w-50,h-150) + #Todays Date + myCanvas.setFont('Helvetica-Bold', 12) + today = datetime.today() + issueDate = today.strftime('%d-%m-%Y') + myCanvas.drawRightString(w - 50,h - 180,f"{issueDate}") + + #Title + myCanvas.setFont('Helvetica-Bold', 20) + myCanvas.drawString(w-340, h - 200, requestItem.document_type) + + #Body + # Patient Details + myCanvas.setFont('Helvetica-Bold', 14) + myCanvas.drawString(50, h-230, "Patient Details") + myCanvas.line(50,h-235,w-50,h-235) + + medAidNo = "" + medAidCode = "" + medAidNameAndScheme = "" + if(requestItem.has_med_aid == "Yes"): + medAidNo = requestItem.med_aid_no + medAidCode = requestItem.med_aid_code + medAidNameAndScheme = f"{requestItem.med_aid_name} - {requestItem.med_aid_scheme}" + else: + medAidNo = "n/a" + medAidCode = "n/a" + medAidNameAndScheme = "n/a" + preAuthNo = requestItem.pre_auth_no + if(preAuthNo == ""): + preAuthNo = "n/a" + # category + myCanvas.setFont('Helvetica-Bold', 12) + myCanvas.drawString(50, h-250, f"Patient Name:") + myCanvas.drawString(50, h-265, f"Patient ID:") + myCanvas.drawString(50, h-280, f"Medical Aid No.:") + myCanvas.drawString(50, h-295, f"Medical Aid Code:") + myCanvas.drawString(50, h-310, f"Medical Aid Scheme:") + myCanvas.drawString(50, h-325, f"Pre-Authorisation No:") + # content + myCanvas.setFont('Helvetica', 12) + myCanvas.drawString(225, h-250, f"{requestItem.patient_full_name}") + myCanvas.drawString(225, h-265, f"{requestItem.patient_id_no}") + myCanvas.drawString(225, h-280, f"{medAidNo}") + myCanvas.drawString(225, h-295, f"{medAidCode}") + myCanvas.drawString(225, h-310, f"{medAidNameAndScheme}") + myCanvas.drawString(225, h-325, f"{preAuthNo}") + #=============================================================================== + # Provide Details + myCanvas.setFont('Helvetica-Bold', 14) + myCanvas.drawString(50, h-355, "Provider Details") + myCanvas.line(50,h-360,w-50,h-360) + + myCanvas.setFont('Helvetica-Bold', 12) + myCanvas.drawString(50, h-375, f"Practice Name:") + myCanvas.drawString(50, h-390, f"Practice No.:") + myCanvas.drawString(50, h-405, f"Vat No.:") + myCanvas.drawString(50, h-420, f"Provider Name:") + + myCanvas.setFont('Helvetica', 12) + myCanvas.drawString(225, h-375, f"{requestItem.busName}") + myCanvas.drawString(225, h-390, f"{requestItem.practice_no}") + myCanvas.drawString(225, h-405, f"{requestItem.vat_no}") + myCanvas.drawString(225, h-420, f"{requestItem.provider_name}") + + #=============================================================================== + # Service Details + myCanvas.setFont('Helvetica-Bold', 14) + myCanvas.drawString(50, h-450, "Service Details") + # myCanvas.drawRightString(w - 50, h-300, "Repeat(s)") + myCanvas.drawRightString(w - 70, h-450, "Amount") + myCanvas.line(50,h-455,w-50,h-455) + + myCanvas.setFont('Helvetica-Bold', 12) + myCanvas.drawString(50, h-470, f"Service Type:") + myCanvas.drawString(50, h-485, f"Service Date:") + + myCanvas.setFont('Helvetica', 12) + myCanvas.drawString(225, h-470, f"{requestItem.service_desc}") + displayAmount = "" + if("." in requestItem.amount or "," in requestItem.amount): + displayAmount = requestItem.amount.replace(",",".") + else: + displayAmount = requestItem.amount + ".00" + myCanvas.drawRightString(w - 80, h-470, displayAmount) + myCanvas.drawString(225, h-485, f"{requestItem.service_date}") + y = 0 + if(requestItem.service_desc == "Precedure"): + myCanvas.setFont('Helvetica-Bold', 12) + myCanvas.drawString(50, h-500, f"Procedure Name:") + myCanvas.drawString(50, h-515, f"Additional Info:") + myCanvas.drawString(50, h-530, f"ICD-10 Code & Description:") + + myCanvas.setFont('Helvetica', 12) + myCanvas.drawString(225, h-500, f"{requestItem.procedure_name}") + myCanvas.drawString(225, h-515, f"{requestItem.procedure_additional_info}") + y = 530 + for line in wrap(requestItem.icd10_code, 45): + myCanvas.drawString(225, h-y, f"{line}") + y+=15 + myCanvas.line(50,h-y,w-50,h-y) + else: + myCanvas.setFont('Helvetica-Bold', 12) + myCanvas.drawString(50, h-500, f"Service Description:") + myCanvas.drawString(50, h-515, f"ICD-10 Code & Description:") + + myCanvas.setFont('Helvetica', 12) + myCanvas.drawString(225, h-500, f"{requestItem.service_desc_option}") + y = 515 + for line in wrap(requestItem.icd10_code, 45): + myCanvas.drawString(225, h-y, f"{line}") + y+=15 + # myCanvas.drawString(225, h-515, f"{requestItem.icd10_code}") + myCanvas.line(50,h-y,w-50,h-y) + #=============================================================================== + + #Signature + y=750 + myCanvas.drawImage(imageSig, 50, h-y,100,100) + myCanvas.line(50,h-y-10,200,h-y-10) + myCanvas.drawString(50, h-y-30, requestItem.provider_name.upper()) + + #QR Verification + qrText = f"{requestItem.document_type} generated on {issueDate} by {requestItem.busName} for {requestItem.patient_full_name}.\nPowered by Mzansi Innovation Hub." + qrText = qrText.replace(" ","+") + + url = f"https://api.qrserver.com/v1/create-qr-code/?data={qrText}&size=100x100" + response = requests.get(url) + image = ImageReader(io.BytesIO(response.content)) + myCanvas.drawImage(image,w-150, h-y-10,100,100) + + myCanvas.setFont('Helvetica-Bold', 15) + myCanvas.drawString(w-150,h-y-30,"Scan to verify") + + myCanvas.save() diff --git a/backend/routers/icd10_codes.py b/backend/routers/icd10_codes.py new file mode 100644 index 00000000..911951e8 --- /dev/null +++ b/backend/routers/icd10_codes.py @@ -0,0 +1,48 @@ +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +import os +import xlrd +#SuperToken Auth from front end +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +router = APIRouter() + +#get all medicines +@router.get("/icd10-codes/all", tags=["ICD10 Code"]) +async def read_all_icd10_codes(session: SessionContainer = Depends(verify_session())): + return getICD10CodesData("") + +#get all medicines by search +@router.get("/icd10-codes/{search}", tags=["ICD10 Code"]) +async def read_icd10_codes_search(search: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) + return getICD10CodesData(search) + +def getICD10CodesData(search: str): + path = os.getcwd() + #print(path) + #parentDir = os.path.abspath(os.path.join(path, os.pardir)) + filePath = os.path.join(path, "ICD10_Codes", "ICD-10_MIT_2021_Excel_16-March_2021.xls") + print(f'========================= %s ===============================',filePath) + book = xlrd.open_workbook_xls(filePath) + sh = book.sheet_by_index(0) + codeList = [] + for rx in range(1,sh.nrows): + if(str(sh.cell_value(rx, 7)).strip() != "" + and search.lower() in str(sh.cell_value(rx, 7)).strip().lower() + or search.lower() in str(sh.cell_value(rx, 8)).strip().lower()): + codeList.append({ + "icd10": str(sh.cell_value(rx, 7)).strip(), + "description": str(sh.cell_value(rx, 8)).strip(), + }) + seen = set() + codeList_noDuplicates = [] + for d in codeList: + t = tuple(d.items()) + #print(t[0][1]) + if t not in seen: + seen.add(t) + codeList_noDuplicates.append(d) + return sorted(codeList_noDuplicates, key=lambda d: d['icd10']) #qsort(medlist) + \ No newline at end of file