Files
simrsx-be/internal/use-case/main-use-case/encounter/helper.go
2025-12-12 13:48:34 +07:00

1019 lines
28 KiB
Go

/*
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
}