package encounter import ( // std "errors" "fmt" erc "simrs-vx/internal/domain/references/clinical" ere "simrs-vx/internal/domain/references/encounter" ero "simrs-vx/internal/domain/references/organization" "strings" // external dg "github.com/karincake/apem/db-gorm-pg" gh "github.com/karincake/getuk" "gorm.io/gorm" // pkg plh "simrs-vx/pkg/lib-helper" pl "simrs-vx/pkg/logger" pu "simrs-vx/pkg/use-case-helper" // internal eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist" ea "simrs-vx/internal/domain/main-entities/ambulatory" e "simrs-vx/internal/domain/main-entities/encounter" er "simrs-vx/internal/domain/main-entities/rehab" erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist" es "simrs-vx/internal/domain/main-entities/soapi" ) func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.Encounter, error) { pl.SetLogInfo(event, nil, "started", "DBCreate") data := e.Encounter{} setData(&input, &data) var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } if err := tx.Create(&data).Error; err != nil { return nil, plh.HandleCreateError(input, event, err) } pl.SetLogInfo(event, nil, "complete") return &data, nil } func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.Encounter, *e.MetaDto, error) { if input.AuthInfo.User_Id == 0 { return nil, nil, plh.HandleListError(input, event, errors.New("user_id is required")) } pl.SetLogInfo(event, input, "started", "DBReadList") data := []e.Encounter{} pagination := gh.Pagination{} count := int64(0) meta := e.MetaDto{} var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } tx = tx.Model(&e.Encounter{}) if input.AuthInfo.Doctor_Id != nil { tx.Where("\"Responsible_Doctor_Id\" = ?", *input.AuthInfo.Doctor_Id) } tx.Scopes(gh.Preload(input.Includes)). Scopes(gh.Filter(input.FilterDto)). Count(&count). Scopes(gh.Paginate(input, &pagination)). Order("\"CreatedAt\" DESC") if err := tx.Find(&data).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, &meta, nil } return nil, nil, plh.HandleListError(input, event, err) } meta.Count = int(count) meta.PageNumber = pagination.PageNumber meta.PageSize = pagination.PageSize pl.SetLogInfo(event, nil, "complete") return data, &meta, nil } func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e.Encounter, error) { pl.SetLogInfo(event, input, "started", "DBReadDetail") data := e.Encounter{} var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } if err := tx.Scopes(gh.Preload(input.Includes)).First(&data, input.Id).Error; err != nil { if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { return nil, processedErr } } pl.SetLogInfo(event, nil, "complete") return &data, nil } func UpdateData(input e.UpdateDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBUpdate") setDataUpdate(input, data) var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } if err := tx.Save(&data).Error; err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-update-fail", Detail: "Database update failed", Raw: err, } return pl.SetLogError(event, input) } pl.SetLogInfo(event, nil, "complete") return nil } func DeleteData(data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBDelete") var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } if err := tx.Delete(&data).Error; err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-delete-fail", Detail: "Database delete failed", Raw: err, } return pl.SetLogError(event, data) } pl.SetLogInfo(event, nil, "complete") return nil } func updateDischargeData(input e.DischargeDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBUpdateDischarge") setDataDischarge(input, data) var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } if err := tx.Save(&data).Error; err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-update-fail", Detail: "Database update failed", Raw: err, } return pl.SetLogError(event, input) } pl.SetLogInfo(event, nil, "complete") return nil } func IsDone(encounter_id uint, event *pl.Event, dbx ...*gorm.DB) bool { pl.SetLogInfo(event, nil, "started", "DBIsDone") var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } var data e.Encounter if err := tx.Where("\"Id\" = ?", encounter_id).First(&data).Error; err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-get-fail", Detail: "Database get failed", Raw: err, } return false } pl.SetLogInfo(event, nil, "complete") return data.IsDone() } func UpdateStatusData(input e.UpdateStatusDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBUpdate") setDataUpdateStatus(input, data) var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } if err := tx.Save(&data).Error; err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-update-fail", Detail: "Database update failed", Raw: err, } return pl.SetLogError(event, input) } pl.SetLogInfo(event, nil, "complete") return nil } func updateCheckInData(input e.CheckinDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBUpdate") setDataCheckIn(input, data) var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } if err := tx.Save(&data).Error; err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-update-fail", Detail: "Database update failed", Raw: err, } return pl.SetLogError(event, input) } pl.SetLogInfo(event, nil, "complete") return nil } func updateLatestResponsibleDoctorHist(input e.CheckinDto, 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 } subQuery := tx. Select("\"Id\""). Model(&erdh.ResponsibleDoctorHist{}). Where("\"Encounter_Id\" = ?", input.Id). Order("\"CreatedAt\" DESC"). Limit(1) result := tx. Model(&erdh.ResponsibleDoctorHist{}). Where("\"Id\" = (?)", subQuery). Update("\"FinishedAt\"", input.StartedAt) 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) } if result.RowsAffected == 0 { pl.SetLogInfo(event, input, "no previous data found to update") return nil } pl.SetLogInfo(event, nil, "complete") return nil } func updateLatestAdmEmployeeHist(input e.CheckinDto, 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 } subQuery := tx. Select("\"Id\""). Model(&eaeh.AdmEmployeeHist{}). Where("\"Encounter_Id\" = ?", input.Id). Order("\"CreatedAt\" DESC"). Limit(1) result := tx. Model(&eaeh.AdmEmployeeHist{}). Where("\"Id\" = (?)", subQuery). Update("\"FinishedAt\"", input.StartedAt) 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) } if result.RowsAffected == 0 { pl.SetLogInfo(event, input, "no previous data found to update") return nil } pl.SetLogInfo(event, nil, "complete") return nil } func verifyAllocatedVisitCount(i e.CreateDto, event *pl.Event) (e.Encounter, bool, error) { pl.SetLogInfo(event, nil, "started", "DBGetRecentEncounterAdm") var ( tx = dg.I recentEncounterAdm e.Encounter countEncounterSeries int64 ) err := tx. Scopes(gh.Preload("Rehab,Responsible_Doctor")). Joins("JOIN \"Ambulatory\" ON \"Ambulatory\".\"Encounter_Id\" = \"Encounter\".\"Id\""). Where("\"Patient_Id\" = ?", i.Patient_Id). Where("\"Ambulatory\".\"Class_Code\" = ? AND \"Ambulatory\".\"VisitMode_Code\" = ?", ere.ACCRme, ere.VMCAdm). Order("\"CreatedAt\" DESC"). First(&recentEncounterAdm).Error if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "read-recentEncounter-fail", Detail: "Database read failed", Raw: err, } return e.Encounter{}, false, pl.SetLogError(event, i) } err = tx. Debug(). Model(&e.Encounter{}). Joins("JOIN \"Ambulatory\" ON \"Ambulatory\".\"Encounter_Id\" = \"Encounter\".\"Id\""). Where("\"Patient_Id\" = ?", i.Patient_Id). Where("\"Ambulatory\".\"Class_Code\" = ? AND \"Ambulatory\".\"VisitMode_Code\" = ?", ere.ACCRme, ere.VMCSeries). Where("\"Encounter\".\"CreatedAt\" > ?", recentEncounterAdm.CreatedAt). Count(&countEncounterSeries).Error if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "read-countEncounter-fail", Detail: "Database read failed", Raw: err, } return e.Encounter{}, false, pl.SetLogError(event, i) } return recentEncounterAdm, countEncounterSeries < int64(*recentEncounterAdm.Rehab.AllocatedVisitCount), nil } func getSoapiEncounterAdm(enc e.Encounter, event *pl.Event) (dataSoapi []es.CreateDto, err error) { var data []es.Soapi 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", } } err = dg.I.Debug(). Model(&es.Soapi{}). Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\""). Where("\"Encounter_Id\" = ?", enc.Id). Where("\"Employee\".\"Position_Code\" = ?", ero.EPCDoc). Where("\"Soapi\".\"TypeCode\" IN ?", []erc.SoapiTypeCode{erc.STCEEarlyMedic, erc.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, } if errors.Is(err, gorm.ErrRecordNotFound) { event.ErrInfo.Code = "data-not-found" event.ErrInfo.Detail = "Data not found" return nil, pl.SetLogError(event, enc) } event.ErrInfo.Code = "read-fail" event.ErrInfo.Detail = "Database read failed" return nil, pl.SetLogError(event, enc) } pl.SetLogInfo(event, nil, "complete") 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: fmt.Sprintf("Missing required Soapi types"), } return nil, pl.SetLogError(event, enc) } return } func getSoapiByTypeCode(encounterId uint, event *pl.Event, mode string) (err error) { pl.SetLogInfo(event, encounterId, "started", "DBReadList") var ( dataAmbulatory ea.Ambulatory dataSoapi []es.Soapi ) // Get Data Ambulatory if err = dg.I. Where("\"Encounter_Id\" = ?", encounterId). First(&dataAmbulatory).Error; err != nil { return setDBError(event, err, encounterId) } // Set Query for get data Soapi tx := dg.I. Model(&es.Soapi{}). Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\""). Where("\"Encounter_Id\" = ?", encounterId). Where("\"Employee\".\"Position_Code\" = ?", ero.EPCDoc) // Set Case switch { case dataAmbulatory.Class_Code == ere.ACCReg: tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", erc.STCEEarlyMedic) case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCAdm: tx = tx.Where("\"Soapi\".\"TypeCode\" IN ?", []erc.SoapiTypeCode{erc.STCEEarlyMedic, erc.STCFunc, erc.STCEarlyRehab}) case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCSeries: tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", erc.STCEarlyRehab) } if err = tx.Find(&dataSoapi).Error; err != nil { return setDBError(event, err, encounterId) } pl.SetLogInfo(event, nil, "complete") return validateExistedSoapi(dataSoapi, &dataAmbulatory, event, mode) } func validateExistedSoapi(dataSoapi []es.Soapi, dataAmbulatory *ea.Ambulatory, event *pl.Event, mode string) error { typeExist := make(map[erc.SoapiTypeCode]bool) for _, s := range dataSoapi { typeExist[s.TypeCode] = true } required := []erc.SoapiTypeCode{} switch { case dataAmbulatory.Class_Code == ere.ACCReg: required = []erc.SoapiTypeCode{erc.STCEEarlyMedic} case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCAdm: required = []erc.SoapiTypeCode{erc.STCEEarlyMedic, erc.STCFunc, erc.STCEarlyRehab} case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCSeries: required = []erc.SoapiTypeCode{erc.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 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 updateRehabDoctor(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("\"Encounter_Id\" = (?)", input.Encounter_Id). Update("\"Doctor_Id\"", input.Doctor_Id) 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 }