/* DESCRIPTION: Any functions that are used internally by the use-case */ package encounter import ( "errors" "fmt" "strings" "time" authhelper "simrs-vx/internal/lib/auth" dg "github.com/karincake/apem/db-gorm-pg" "gorm.io/gorm" pl "simrs-vx/pkg/logger" pu "simrs-vx/pkg/use-case-helper" ercl "simrs-vx/internal/domain/references/clinical" erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" erg "simrs-vx/internal/domain/references/organization" ea "simrs-vx/internal/domain/main-entities/ambulatory" ec "simrs-vx/internal/domain/main-entities/chemo" ecpl "simrs-vx/internal/domain/main-entities/chemo-plan" ecp "simrs-vx/internal/domain/main-entities/chemo-protocol" edo "simrs-vx/internal/domain/main-entities/device-order" ed "simrs-vx/internal/domain/main-entities/doctor" ee "simrs-vx/internal/domain/main-entities/emergency" e "simrs-vx/internal/domain/main-entities/encounter" ei "simrs-vx/internal/domain/main-entities/inpatient" emo "simrs-vx/internal/domain/main-entities/material-order" emco "simrs-vx/internal/domain/main-entities/mcu-order" em "simrs-vx/internal/domain/main-entities/medication" emei "simrs-vx/internal/domain/main-entities/medication-item" emi "simrs-vx/internal/domain/main-entities/medicine-mix" emmi "simrs-vx/internal/domain/main-entities/medicine-mix-item" en "simrs-vx/internal/domain/main-entities/nurse" ep "simrs-vx/internal/domain/main-entities/prescription" epi "simrs-vx/internal/domain/main-entities/prescription-item" er "simrs-vx/internal/domain/main-entities/rehab" es "simrs-vx/internal/domain/main-entities/soapi" esp "simrs-vx/internal/domain/main-entities/specialist" ua "simrs-vx/internal/use-case/main-use-case/ambulatory" uc "simrs-vx/internal/use-case/main-use-case/chemo" ud "simrs-vx/internal/use-case/main-use-case/doctor" ue "simrs-vx/internal/use-case/main-use-case/emergency" ui "simrs-vx/internal/use-case/main-use-case/inpatient" um "simrs-vx/internal/use-case/main-use-case/medication" umei "simrs-vx/internal/use-case/main-use-case/medication-item" umi "simrs-vx/internal/use-case/main-use-case/medicine-mix" ummi "simrs-vx/internal/use-case/main-use-case/medicine-mix-item" _ "simrs-vx/internal/use-case/main-use-case/nurse" un "simrs-vx/internal/use-case/main-use-case/nurse" up "simrs-vx/internal/use-case/main-use-case/prescription" upi "simrs-vx/internal/use-case/main-use-case/prescription-item" ur "simrs-vx/internal/use-case/main-use-case/rehab" us "simrs-vx/internal/use-case/main-use-case/soapi" ) func setDataCreate(input *e.CreateDto, data *e.Encounter) { data.Patient_Id = input.Patient_Id data.RegisteredAt = input.RegisteredAt data.Class_Code = input.Class_Code data.Specialist_Code = input.Specialist_Code data.Specialist_Code = input.Specialist_Code data.Subspecialist_Code = input.Subspecialist_Code data.VisitDate = input.VisitDate data.PaymentMethod_Code = input.PaymentMethod_Code data.InsuranceCompany_Code = input.InsuranceCompany_Code data.Member_Number = input.Member_Number data.RefType_Code = &input.RefTypeCode data.Ref_Number = input.Ref_Number data.Trx_Number = input.Trx_Number data.Appointment_Doctor_Code = input.Appointment_Doctor_Code data.Adm_Employee_Id = input.Adm_Employee_Id data.RefSource_Name = input.RefSource_Name data.Appointment_Id = input.Appointment_Id data.Status_Code = erc.DSCNew data.NewStatus = input.NewStatus } func setDataUpdate(src e.UpdateDto, dst *e.Encounter) { dst.Specialist_Code = src.Specialist_Code dst.Specialist_Code = src.Specialist_Code dst.Subspecialist_Code = src.Subspecialist_Code dst.VisitDate = src.VisitDate dst.Appointment_Doctor_Code = src.Appointment_Doctor_Code dst.Adm_Employee_Id = src.Adm_Employee_Id dst.Appointment_Id = src.Appointment_Id dst.RefSource_Name = src.RefSource_Name dst.Trx_Number = src.Trx_Number dst.Ref_Number = src.Ref_Number dst.Member_Number = src.Member_Number } func setDataUpdateFromSource(input *e.UpdateDto, data *e.Encounter) { data.Patient_Id = input.Patient_Id data.RegisteredAt = input.RegisteredAt data.Specialist_Code = input.Specialist_Code data.Specialist_Code = input.Specialist_Code data.Subspecialist_Code = input.Subspecialist_Code data.VisitDate = input.VisitDate data.StartedAt = input.StartedAt data.FinishedAt = input.FinishedAt data.PaymentMethod_Code = input.PaymentMethod_Code data.InsuranceCompany_Code = input.InsuranceCompany_Code data.Member_Number = input.Member_Number data.RefType_Code = &input.RefTypeCode data.Ref_Number = input.Ref_Number data.Trx_Number = input.Trx_Number data.Appointment_Doctor_Code = input.Appointment_Doctor_Code data.Adm_Employee_Id = input.Adm_Employee_Id data.Responsible_Doctor_Code = input.Responsible_Doctor_Code data.Discharge_Method_Code = input.Discharge_Method_Code data.RefSource_Name = input.RefSource_Name data.Appointment_Id = input.Appointment_Id data.EarlyEducation = input.EarlyEducation data.MedicalDischargeEducation = input.MedicalDischargeEducation data.AdmDischargeEducation = input.AdmDischargeEducation data.DischargeReason = input.DischargeReason data.Status_Code = input.Status_Code data.Discharge_Date = input.Discharge_Date data.NewStatus = input.NewStatus data.Responsible_Nurse_Code = input.Responsible_Nurse_Code } func setDataDischarge(src e.DischargeDto, dst *e.Encounter) { dst.Discharge_Method_Code = src.Discharge_Method_Code dst.EarlyEducation = src.EarlyEducation dst.MedicalDischargeEducation = src.MedicalDischargeEducation dst.AdmDischargeEducation = src.AdmDischargeEducation dst.DischargeReason = src.DischargeReason dst.Status_Code = erc.DSCDone now := time.Now() dst.Discharge_Date = &now dst.FinishedAt = &now } func setDataCheckIn(src e.CheckinDto, dst *e.Encounter) { dst.Responsible_Nurse_Code = src.Responsible_Nurse_Code dst.Responsible_Doctor_Code = src.Responsible_Doctor_Code dst.StartedAt = src.StartedAt dst.Status_Code = erc.DSCProcess } func setDischargeMethodCode(code ere.PolySwitchCode) *ere.DischargeMethodCode { var dcm ere.DischargeMethodCode switch code { case ere.PSCConsulPoly: dcm = ere.DMCConsulPoly case ere.PSCConsulExecutive: dcm = ere.DMCConsulExecutive } return &dcm } func checkSoapiByDocExists(encounter_id uint, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "checkSoapiByDocExists") var soapies []es.Soapi err := tx. Preload("Employee"). Preload("Employee.User"). Where("\"Encounter_Id\" = ?", encounter_id).Find(&soapies).Error if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-get-fail", Detail: "get soapi failed", Raw: err, } return pl.SetLogError(event, nil) } if len(soapies) == 0 { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-notFound", Detail: "no soapi found for encounter", Raw: errors.New("soapi not found"), } return pl.SetLogError(event, nil) } for _, s := range soapies { if s.Employee != nil && *s.Employee.Position_Code == erg.EPCDoc { return nil } } event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-update-fail", Detail: "no soapi written by a doctor found", Raw: errors.New("all soapi employees are not doctors"), } return pl.SetLogError(event, nil) } func createMedication(encounter_id uint, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "createMedication") prescription, err := up.ReadDetailData(ep.ReadDetailDto{Encounter_Id: &encounter_id}, event, tx) if err != nil { return err } prescriptionItem, _, err := upi.ReadListData(epi.ReadListDto{ FilterDto: epi.FilterDto{Prescription_Id: &prescription.Id}, Includes: "medicineMix,medicineMix-MixItems"}, event, tx) if err != nil { return err } if len(prescriptionItem) == 0 { return nil } medicationCreate := em.CreateDto{ Encounter_Id: &encounter_id, IssuedAt: pu.GetTimeNow(), Status_Code: erc.DSCNew, } medication, err := um.CreateData(medicationCreate, event, tx) if err != nil { return err } for _, prescriptionItem := range prescriptionItem { if prescriptionItem.IsMix { medMix_id, err := createMedicineMixAndItem(*prescriptionItem.MedicineMix, event, tx) if err != nil { return err } prescriptionItem.MedicineMix_Id = medMix_id } err := createMedicationItem(medication.Id, prescriptionItem, event, tx) if err != nil { return err } } return nil } func createMedicineMixAndItem(input emi.MedicineMix, event *pl.Event, tx *gorm.DB) (*uint, error) { pl.SetLogInfo(event, nil, "started", "createMedicineMix") medicineMixCreate := emi.CreateDto{ Name: input.Name, Uom_Code: input.Uom_Code, } medicineMix, err := umi.CreateData(medicineMixCreate, event, tx) if err != nil { return nil, err } // recreate medicineMixItem with new medicineMix_id to keep medMixItem remain the same for prescriptionItem that is created for _, medicineMixItem := range input.MixItems { medicineMixItemCreate := emmi.CreateDto{ MedicineMix_Id: &medicineMix.Id, Medicine_Code: medicineMixItem.Medicine_Code, Dose: medicineMixItem.Dose, } _, err := ummi.CreateData(medicineMixItemCreate, event, tx) if err != nil { return nil, err } } return &medicineMix.Id, nil } func createMedicationItem(medication_id uint, input epi.PrescriptionItem, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "createMedicationItem") medicationItemCreate := emei.CreateDto{ Medication_Id: &medication_id, IsMix: input.IsMix, Medicine_Code: input.Medicine_Code, MedicineMix_Id: input.MedicineMix_Id, Usage: input.Usage, Interval: input.Interval, IntervalUnit_Code: input.IntervalUnit_Code, Quantity: input.Quantity, } _, err := umei.CreateData(medicationItemCreate, event, tx) if err != nil { return err } return nil } func checkNewOrdersExist(encounter_id uint, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "CheckNewOrdersExist") var errs []string if err := getDeviceOrders(encounter_id, event, tx); err != nil { errs = append(errs, err.Error()) } if err := getMaterialOrders(encounter_id, event, tx); err != nil { errs = append(errs, err.Error()) } if err := getMcuOrders(encounter_id, event, tx); err != nil { errs = append(errs, err.Error()) } if len(errs) > 0 { return fmt.Errorf("encounter has open orders: %s", strings.Join(errs, "; ")) } return nil } func getDeviceOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "getDeviceOrders") var orders []edo.DeviceOrder err := tx.Where("\"Encounter_Id\" = ? AND \"Status_Code\" = ?", encounter_id, erc.DSCNew).Find(&orders).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil } event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-get-fail", Detail: "get device order failed", Raw: err, } return pl.SetLogError(event, nil) } if len(orders) > 0 { return fmt.Errorf("encounter has %d device orders", len(orders)) } return nil } func getMaterialOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "getMaterialOrders") var orders []emo.MaterialOrder err := tx.Where("\"Encounter_Id\" = ? AND \"Status_Code\" = ?", encounter_id, erc.DSCNew).Find(&orders).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil } event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-get-fail", Detail: "get material order failed", Raw: err, } return pl.SetLogError(event, nil) } if len(orders) > 0 { return fmt.Errorf("encounter has %d material orders", len(orders)) } return nil } func getMcuOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "getMcuOrders") var orders []emco.McuOrder err := tx.Where("\"Encounter_Id\" = ? AND \"Status_Code\" = ?", encounter_id, erc.DSCNew).Find(&orders).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil } event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-get-fail", Detail: "get mcu order failed", Raw: err, } return pl.SetLogError(event, nil) } if len(orders) > 0 { return fmt.Errorf("encounter has %d mcu orders", len(orders)) } return nil } func getSoapiByResponsibleDoctor(enc e.Encounter, event *pl.Event) (data []es.Soapi, err error) { pl.SetLogInfo(event, enc, "started", "DBReadList") if enc.Responsible_Doctor == nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "no responsible-doctor found", Detail: "Encounter does not have responsible-doctor", } return nil, pl.SetLogError(event, enc) } err = dg.I. Model(&es.Soapi{}). Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\""). Where("\"Encounter_Id\" = ?", enc.Id). Where("\"Employee\".\"Position_Code\" = ?", erg.EPCDoc). Where("\"Soapi\".\"TypeCode\" IN ?", []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.STCFunc}). Where("\"Soapi\".\"Employee_Id\" = ?", *enc.Responsible_Doctor.Employee_Id). Find(&data).Error if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Raw: err, Code: "read-fail", Detail: "Database read failed", } return nil, pl.SetLogError(event, enc) } pl.SetLogInfo(event, nil, "complete") return } func getSoapiEncounterAdm(enc e.Encounter, event *pl.Event) (dataSoapi []es.CreateDto, err error) { data, err := getSoapiByResponsibleDoctor(enc, event) if err != nil { return nil, err } for _, s := range data { // set data soapi for copy dataSoapi = append(dataSoapi, es.CreateDto{ Employee_Id: s.Employee_Id, Time: s.Time, TypeCode: s.TypeCode, Value: s.Value, }) } if len(dataSoapi) < 2 { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "missing-soapi", Detail: "Missing required Soapi types", } return nil, pl.SetLogError(event, enc) } return } func getSoapiByTypeCode(encounter *e.Encounter, event *pl.Event, mode string) (err error) { pl.SetLogInfo(event, encounter, "started", "DBReadList") var ( dataSoapi []es.Soapi amb = encounter.Ambulatory rehab = encounter.Rehab ) // Set Query for get data Soapi by doc tx := dg.I. Model(&es.Soapi{}). Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\""). Where("\"Encounter_Id\" = ?", encounter.Id). Where("\"Employee\".\"Position_Code\" = ?", erg.EPCDoc) // Set Case switch { case amb.Class_Code == ere.ACCReg: tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", ercl.STCEEarlyMedic) case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCAdm: tx = tx.Where("\"Soapi\".\"TypeCode\" IN ?", []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.STCFunc, ercl.STCEarlyRehab}) case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCSeries: tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", ercl.STCEarlyRehab) } if err = tx.Find(&dataSoapi).Error; err != nil { return setDBError(event, err, encounter) } pl.SetLogInfo(event, nil, "complete") return validateExistedSoapi(dataSoapi, encounter, event, mode) } func validateExistedSoapi(dataSoapi []es.Soapi, dataEncounter *e.Encounter, event *pl.Event, mode string) error { var ( amb = dataEncounter.Ambulatory rehab = dataEncounter.Rehab ) typeExist := make(map[ercl.SoapiTypeCode]bool) for _, s := range dataSoapi { typeExist[s.TypeCode] = true } required := []ercl.SoapiTypeCode{} switch { case amb.Class_Code == ere.ACCReg: required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic} case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCAdm: required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.STCFunc, ercl.STCEarlyRehab} case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCSeries: required = []ercl.SoapiTypeCode{ercl.STCEarlyRehab} } var missing, existing []string for _, code := range required { if typeExist[code] { existing = append(existing, string(code)) } else { missing = append(missing, string(code)) } } switch mode { case "check-in": if len(existing) > 0 { return setSoapiError(event, fmt.Sprintf("Soapi type(s) %s exist, can't check-in", strings.Join(existing, ", "))) } case "check-out": if len(missing) > 0 { return setSoapiError(event, fmt.Sprintf("Soapi type(s) %s not found, can't check-out", strings.Join(missing, ", "))) } } return nil } func identifyPatientStatus(input e.CreateDto) (isNewPatient bool, err error) { dataPatient, err := ReadList(e.ReadListDto{ FilterDto: e.FilterDto{Patient_Id: input.Patient_Id}, AuthInfo: authhelper.AuthInfo{User_Id: input.User_Id}}) if err != nil { return } if list, ok := dataPatient.Data.([]e.ResponseDto); ok { if len(list) < 1 { isNewPatient = true } } return } func determineVisitMode(recentRehabData *er.Rehab, input e.CreateDto, event *pl.Event) (ere.VisitModeCode, *e.Encounter, error) { var ( visitModeCode ere.VisitModeCode recentAdmEncounterData e.Encounter isQuotaValid bool err error ) switch *recentRehabData.Status_Code { case erc.DSCProcess: visitModeCode = ere.VMCSeries // verify whether the allocated visit count has not exceeded the limit recentAdmEncounterData, isQuotaValid, err = verifyAllocatedVisitCount(input, event) if err != nil { return "", nil, err } if !isQuotaValid { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "visit-limit-exceeded", Detail: "Encounter has exceeded the allowed number of visits", Raw: errors.New("visit count exceeds allowed limit"), } return "", nil, pl.SetLogError(event, input) } if recentRehabData.ExpiredAt != nil && recentRehabData.ExpiredAt.Before(*pu.GetTimeNow()) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "visit-limit-exceeded", Detail: "Encounter period has expired", Raw: errors.New("encounter expired"), } return "", nil, pl.SetLogError(event, input) } case erc.DSCDone: visitModeCode = ere.VMCAdm default: event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "invalid-status", Detail: fmt.Sprintf("Unknown rehab status: %v", *recentRehabData.Status_Code), Raw: errors.New("unsupported rehab status"), } return "", nil, pl.SetLogError(event, input) } return visitModeCode, &recentAdmEncounterData, nil } func insertdataClassCode(input e.CreateDto, soapiData []es.CreateDto, event *pl.Event, tx *gorm.DB) (err error) { switch input.Class_Code { case ere.ECAmbulatory: subCode := ere.AmbulatoryClassCode(*input.SubClass_Code) ambCreate := ea.CreateDto{ Encounter_Id: &input.Id, Class_Code: subCode, } // create data Ambulatory _, err = ua.CreateData(ambCreate, event, tx) if err != nil { return err } // insert chemo/rehab err = insertDataSubClassAmbulatory(input, soapiData, event, tx) if err != nil { return err } case ere.ECEmergency: subCode := ere.EmergencyClassCode(*input.SubClass_Code) emerCreate := ee.CreateDto{ Encounter_Id: &input.Id, Class_Code: subCode, } // create data emergency _, err = ue.CreateData(emerCreate, event, tx) if err != nil { return err } case ere.ECInpatient: subCode := ere.InpatientClassCode(*input.SubClass_Code) inpCreate := ei.CreateDto{ Encounter_Id: &input.Id, Class_Code: subCode, Infra_Code: input.Infra_Code, } // create data inpatient _, err = ui.CreateData(inpCreate, event, tx) if err != nil { return err } default: return errors.New("invalid encounter class code") } return } func insertDataSubClassAmbulatory(input e.CreateDto, soapiData []es.CreateDto, event *pl.Event, tx *gorm.DB) (err error) { subCode := ere.AmbulatoryClassCode(*input.SubClass_Code) var chemoPlan *ecpl.ChemoPlan switch { case subCode == ere.ACCChemo: // validate if encounter Chemo valid chemoPlan, err = validateChemo(*input.Patient_Id, event) if err != nil { return err } if chemoPlan == nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-not-found", Detail: "chemo plan not found", } return pl.SetLogError(event, input) } chemoCreate := ec.CreateDto{ Encounter_Id: &input.Id, Status_Code: erc.DVCVerified, Specialist_Code: input.Specialist_Code, Class_Code: ere.CCCAct, } // create data chemo _, err = uc.CreateData(chemoCreate, event, tx) if err != nil { return err } // set chemo-plan to planned chemoPlan.Encounter_Id = &input.Id err = updateChemoPlan(chemoPlan, event, tx) if err != nil { return err } case subCode == ere.ACCRehab: rehabData := er.CreateDto{ Encounter_Id: &input.Id, VisitMode_Code: input.VisitMode_Code, Status_Code: erc.DSCProcess, } // if visitMode_code is series, then bulk insert soapi if input.VisitMode_Code == ere.VMCSeries { rehabData.Parent_Encounter_Id = &input.RecentEncounterAdm.Id // Insert Soapi if err = us.CreateBulkData(soapiData, input.Id, event, tx); err != nil { return err } } // create rehab if _, err = ur.CreateData(rehabData, event, tx); err != nil { return err } } return } func verifyRehabLimit(data *e.Encounter, event *pl.Event, tx *gorm.DB) error { // get data encounter adm encounterAdmData, _, err := verifyAllocatedVisitCount(e.CreateDto{Patient_Id: data.Patient_Id}, event) if err != nil { return err } // Check if the visit count has reached the allowed limit // Mark latest rehab status as 'done' if exceeded. if len(*encounterAdmData.RehabChildren) >= *encounterAdmData.Rehab.AllocatedVisitCount { err = updateRehabStatus(er.UpdateDto{ Id: uint16(data.Rehab.Id), CreateDto: er.CreateDto{Status_Code: erc.DSCDone}}, event, tx) if err != nil { return err } } return nil } func updateRehabStatus(input er.UpdateDto, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, "started", "DBUpdate") var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } result := tx. Model(&er.Rehab{}). Where("\"Id\" = (?)", input.Id). Update("\"Status_Code\"", input.Status_Code) if result.Error != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-update-fail", Detail: "Database update failed", Raw: result.Error, } return pl.SetLogError(event, input) } pl.SetLogInfo(event, nil, "complete") return nil } func validateForeignKey(input e.CheckinDto, event *pl.Event) error { // validate employee_Id if input.Responsible_Nurse_Code != nil { if _, err := un.ReadDetailData(en.ReadDetailDto{Code: input.Responsible_Nurse_Code}, event); err != nil { return err } } // validate doctor_Code if input.Responsible_Doctor_Code != nil { if _, err := ud.ReadDetailData(ed.ReadDetailDto{Code: input.Responsible_Doctor_Code}, event); err != nil { return err } } return nil } func setSoapiError(event *pl.Event, detail string) error { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "invalid-soapi-state", Detail: detail, } return pl.SetLogError(event, detail) } func setDBError(event *pl.Event, err error, ctx any) error { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Raw: err, Code: "read-fail", Detail: "Database read failed", } return pl.SetLogError(event, ctx) } func getSpecialists(unitIds []string, event *pl.Event) ([]esp.Specialist, error) { pl.SetLogInfo(event, nil, "started", "getSpecialists") var units []esp.Specialist err := dg.I.Where("\"Code\" IN ?", unitIds).Find(&units).Error if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-get-fail", Detail: "get units", Raw: err, } return nil, pl.SetLogError(event, nil) } return units, nil } func getDoctors(doctorIds []string, event *pl.Event) ([]ed.Doctor, error) { pl.SetLogInfo(event, nil, "started", "getDoctors") var doctors []ed.Doctor err := dg.I.Where("\"Code\" IN ?", doctorIds).Find(&doctors).Error if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-get-fail", Detail: "get doctors", Raw: err, } return nil, pl.SetLogError(event, nil) } return doctors, nil } func validateSpecialistCodes(speCodes map[string]struct{}, event *pl.Event) error { if len(speCodes) > 0 { var codes []string for code := range speCodes { codes = append(codes, code) } specialists, err := getSpecialists(codes, event) if err != nil { return fmt.Errorf("failed to fetch units: %w", err) } if len(specialists) != len(codes) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-validation-fail", Detail: "unit_code not found", } return pl.SetLogError(event, nil) } } return nil } func validateDoctorCodes(doctorCodes map[string]struct{}, event *pl.Event) error { if len(doctorCodes) > 0 { var codes []string for code := range doctorCodes { codes = append(codes, code) } doctors, err := getDoctors(codes, event) if err != nil { return fmt.Errorf("failed to fetch doctors: %w", err) } if len(doctors) != len(codes) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-validation-fail", Detail: "doctor_code not found", } return pl.SetLogError(event, nil) } } return nil } func updateChemoPlan(data *ecpl.ChemoPlan, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, nil, "started", "getChemoFromEncounter") var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } err := tx.Model(&data).Updates(map[string]interface{}{ `"Status"`: ere.SPCPlanned, `"Encounter_Id"`: data.Encounter_Id}).Error if err != nil { return err } pl.SetLogInfo(event, nil, "complete") return nil } func getChemoProtocol(patientId uint, event *pl.Event) (*ecp.ChemoProtocol, error) { pl.SetLogInfo(event, nil, "started", "getChemoProtocol") data := ecp.ChemoProtocol{} var tx = dg.I tx = tx.Model(&ecp.ChemoProtocol{}). Joins(`LEFT JOIN "ChemoPlan" cp ON cp."Protocol_Id" = "ChemoProtocol"."Id"`). Where(`"ChemoProtocol"."Patient_Id" = ?`, patientId). Preload("ChemoPlans", func(db *gorm.DB) *gorm.DB { return db. Where(`"Status" IS NULL OR "Status" = ?`, ere.SPCSchedule). Order(`"Id" ASC`). Limit(1) }). Order(`"CreatedAt" DESC`). First(&data) if err := tx.Error; err != nil { return nil, err } pl.SetLogInfo(event, nil, "complete") return &data, nil } func validateChemo(patientId uint, event *pl.Event) (*ecpl.ChemoPlan, error) { // get chemo adm chemoAdm, err := getChemoAdm(patientId, event) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-not-found", Detail: "patient doesn't have active chemo", } return nil, pl.SetLogError(event, patientId) } return nil, err } // validate is chemo verified if chemoAdm.Status_Code != erc.DVCVerified { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-not-match", Detail: fmt.Sprintf("chemo not yet verified"), } return nil, pl.SetLogError(event, chemoAdm) } // get chemo protocol chemoProtocol, err := getChemoProtocol(patientId, event) if err != nil { return nil, err } if chemoProtocol.Status_Code != erc.DVCVerified { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-not-match", Detail: fmt.Sprintf("protocol chemo not yet verified"), } return nil, pl.SetLogError(event, chemoProtocol) } if now.Before(*chemoProtocol.StartDate) || now.After(*chemoProtocol.EndDate) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "invalid-date-range", Detail: "chemo cannot be performed because the current date is outside the allowed treatment window.", } return nil, pl.SetLogError(event, chemoProtocol) } if chemoProtocol.ChemoPlans == nil || len(*chemoProtocol.ChemoPlans) == 0 { return nil, nil } // Return the first chemo plan return &(*chemoProtocol.ChemoPlans)[0], nil } func getChemoAdm(patientId uint, event *pl.Event) (*ec.Chemo, error) { pl.SetLogInfo(event, nil, "started", "getChemoProtocol") data := ec.Chemo{} var tx = dg.I tx = tx.Model(&ec.Chemo{}). Joins(`LEFT JOIN "Encounter" e ON e."Id" = "Chemo"."Encounter_Id"`). Where(`e."Patient_Id" = ? AND "Chemo"."Class_Code" = ?`, patientId, ere.CCCAdm). Order(`"CreatedAt" DESC`). First(&data) if err := tx.Error; err != nil { return nil, err } pl.SetLogInfo(event, nil, "complete") return &data, nil }