From fcbec59a1767772c331e6f4b186e2301dd0e1760 Mon Sep 17 00:00:00 2001 From: vanilia Date: Sun, 9 Nov 2025 22:25:55 +0700 Subject: [PATCH] add switch poly and spprove --- .../domain/main-entities/encounter/dto.go | 47 +- .../main-handler/encounter/handler.go | 54 +- .../encounter/request-validation.go | 88 +-- .../interface/main-handler/main-handler.go | 24 +- .../use-case/main-use-case/encounter/case.go | 398 +++++++------ .../main-use-case/encounter/helper.go | 529 ++++++++++++------ .../use-case/main-use-case/encounter/lib.go | 101 +++- .../internal-reference/helper.go | 4 + 8 files changed, 836 insertions(+), 409 deletions(-) diff --git a/internal/domain/main-entities/encounter/dto.go b/internal/domain/main-entities/encounter/dto.go index 6782b18c..966a53d3 100644 --- a/internal/domain/main-entities/encounter/dto.go +++ b/internal/domain/main-entities/encounter/dto.go @@ -1,6 +1,13 @@ package encounter import ( + eam "simrs-vx/internal/domain/main-entities/ambulatory" + edc "simrs-vx/internal/domain/main-entities/death-cause" + eem "simrs-vx/internal/domain/main-entities/emergency" + eip "simrs-vx/internal/domain/main-entities/inpatient" + eir "simrs-vx/internal/domain/main-entities/internal-reference" + er "simrs-vx/internal/domain/main-entities/rehab/base" + // std "time" @@ -19,7 +26,6 @@ import ( ea "simrs-vx/internal/domain/main-entities/appointment" ed "simrs-vx/internal/domain/main-entities/doctor" ee "simrs-vx/internal/domain/main-entities/employee" - eir "simrs-vx/internal/domain/main-entities/internal-reference" ep "simrs-vx/internal/domain/main-entities/patient" es "simrs-vx/internal/domain/main-entities/specialist" ess "simrs-vx/internal/domain/main-entities/subspecialist" @@ -48,8 +54,10 @@ type CreateDto struct { Appointment_Id *uint `json:"appointment_id"` RefTypeCode ere.RefTypeCode `json:"refTypeCode"` NewStatus bool `json:"newStatus"` - VisitMode_Code *ere.VisitModeCode `json:"visitMode_code"` // if subClass_Code is rehab - AllocatedVisitCount *int `json:"allocatedVisitCount"` // if subClass_Code is rehab and VisitMode_Code is "adm" + + Id uint `json:"-"` + RecentEncounterAdm *Encounter `json:"-"` // if subClass_Code is rehab + VisitMode_Code ere.VisitModeCode `json:"-"` // if subClass_Code is rehab pa.AuthInfo } @@ -114,7 +122,6 @@ type DischargeDto struct { AdmDischargeEducation *string `json:"admDischargeEducation"` DischargeReason *string `json:"dischargeReason"` DeathCause *string `json:"deathCause"` - InternalReferences *[]eir.CreateDto `json:"internalReferences,omitempty"` } type CheckinDto struct { @@ -125,6 +132,17 @@ type CheckinDto struct { FinishedAt *time.Time `json:"finishedAt"` } +type SwitchUnitDto struct { + Id uint `json:"id"` + PolySwitchCode *ere.PolySwitchCode `json:"polySwitchCode"` + InternalReferences *[]eir.CreateDto `json:"internalReferences" validate:"required"` +} + +type ApproveUnitDto struct { + Id uint `json:"id"` + InternalReferences_Id uint16 `json:"internalReferences_id" validate:"required"` +} + type ResponseDto struct { ecore.Main Patient_Id *uint `json:"patient_id"` @@ -159,6 +177,17 @@ type ResponseDto struct { DischargeReason *string `json:"dischargeReason"` Status_Code erc.DataStatusCode `json:"status_code"` VclaimSep *evs.VclaimSep `json:"vclaimSep,omitempty"` + StartedAt *time.Time `json:"startedAt"` + FinishedAt *time.Time `json:"finishedAt"` + Discharge_Date *time.Time `json:"discharge_date"` + InternalReferences *[]eir.InternalReference `json:"internalReferences,omitempty"` + DeathCause *edc.DeathCause `json:"deathCause,omitempty"` + NewStatus bool `json:"newStatus"` + Ambulatory *eam.Ambulatory `json:"ambulatory,omitempty"` + Emergency *eem.Emergency `json:"emergency,omitempty"` + Inpatient *eip.Inpatient `json:"inpatient,omitempty"` + Rehab *er.Basic `json:"rehab,omitempty"` + RehabChildren *[]er.Basic `json:"rehabChildren,omitempty"` } func (d Encounter) ToResponse() ResponseDto { @@ -195,6 +224,16 @@ func (d Encounter) ToResponse() ResponseDto { DischargeReason: d.DischargeReason, Status_Code: d.Status_Code, VclaimSep: d.VclaimSep, + StartedAt: d.StartedAt, + FinishedAt: d.FinishedAt, + Discharge_Date: d.Discharge_Date, + InternalReferences: d.InternalReferences, + DeathCause: d.DeathCause, + NewStatus: d.NewStatus, + Emergency: d.Emergency, + Inpatient: d.Inpatient, + Rehab: d.Rehab, + RehabChildren: d.RehabChildren, } resp.Main = d.Main return resp diff --git a/internal/interface/main-handler/encounter/handler.go b/internal/interface/main-handler/encounter/handler.go index b66abe76..22d1d8cf 100644 --- a/internal/interface/main-handler/encounter/handler.go +++ b/internal/interface/main-handler/encounter/handler.go @@ -3,18 +3,17 @@ package encounter import ( "net/http" + pa "simrs-vx/internal/lib/auth" + + d "github.com/karincake/dodol" rw "github.com/karincake/risoles" sf "github.com/karincake/semprit" // ua "github.com/karincake/tumpeng/auth/svc" + erc "simrs-vx/internal/domain/references/common" e "simrs-vx/internal/domain/main-entities/encounter" u "simrs-vx/internal/use-case/main-use-case/encounter" - - erc "simrs-vx/internal/domain/references/common" - pa "simrs-vx/internal/lib/auth" - - d "github.com/karincake/dodol" ) type myBase struct{} @@ -31,7 +30,13 @@ func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { return } - if valid := validateRequestCreate(w, dto); !valid { + + // validate SubClass + if err := verifyClassCode(dto); err != nil { + rw.DataResponse(w, nil, d.FieldError{ + Code: dataValidationFail, + Message: err.Error(), + }) return } @@ -192,3 +197,40 @@ func (obj myBase) Skip(w http.ResponseWriter, r *http.Request) { res, err := u.UpdateStatusCode(dto) rw.DataResponse(w, res, err) } + +func (obj myBase) RequestSwitchUnit(w http.ResponseWriter, r *http.Request) { + dto := e.SwitchUnitDto{} + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + + // validate request body + if valid := validateRequestSwitchUnit(w, dto); !valid { + return + } + + dto.Id = uint(id) + res, err := u.RequestSwitchUnit(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) ApproveSwitchUnit(w http.ResponseWriter, r *http.Request) { + dto := e.ApproveUnitDto{} + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + + dto.Id = uint(id) + res, err := u.ApproveSwitchUnit(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/encounter/request-validation.go b/internal/interface/main-handler/encounter/request-validation.go index d8e0619a..be370074 100644 --- a/internal/interface/main-handler/encounter/request-validation.go +++ b/internal/interface/main-handler/encounter/request-validation.go @@ -1,9 +1,14 @@ package encounter import ( + "errors" + "fmt" "net/http" e "simrs-vx/internal/domain/main-entities/encounter" ere "simrs-vx/internal/domain/references/encounter" + ua "simrs-vx/internal/use-case/main-use-case/ambulatory" + ue "simrs-vx/internal/use-case/main-use-case/emergency" + ui "simrs-vx/internal/use-case/main-use-case/inpatient" d "github.com/karincake/dodol" rw "github.com/karincake/risoles" @@ -11,6 +16,25 @@ import ( const dataValidationFail = "data-validation-fail" +func verifyClassCode(input e.CreateDto) (err error) { + switch input.Class_Code { + case ere.ECAmbulatory: + _, err = ua.CheckClassCode(input.SubClass_Code) + case ere.ECEmergency: + _, err = ue.CheckClassCode(input.SubClass_Code) + case ere.ECInpatient: + _, err = ui.CheckClassCode(input.SubClass_Code) + default: + return errors.New("invalid encounter class code") + } + + if err != nil { + return + } + + return nil +} + func validateRequestCheckout(w http.ResponseWriter, i e.DischargeDto) (valid bool) { if *i.Discharge_Method_Code == ere.DMCDeath && i.DeathCause == nil { rw.DataResponse(w, nil, d.FieldError{ @@ -19,34 +43,6 @@ func validateRequestCheckout(w http.ResponseWriter, i e.DischargeDto) (valid boo }) return } - - //case ere.DMCConsulPoly, ere.DMCConsulExecutive: - // if i.InternalReferences == nil { - // rw.DataResponse(w, nil, d.FieldError{ - // Code: dataValidationFail, - // Message: fmt.Sprintf("internalReferences required if discharge_method_code is %s", *i.Discharge_Method_Code), - // }) - // return - // } - // - // for _, v := range *i.InternalReferences { - // if v.Unit_Id == nil { - // rw.DataResponse(w, nil, d.FieldError{ - // Code: dataValidationFail, - // Message: "internalReferences.unit_id required", - // }) - // return - // } - // - // if v.Doctor_Id == nil { - // rw.DataResponse(w, nil, d.FieldError{ - // Code: dataValidationFail, - // Message: "internalReferences.doctor_id required", - // }) - // return - // } - // } - return true } @@ -62,18 +58,30 @@ func validateRequestCheckIn(w http.ResponseWriter, i e.CheckinDto) (valid bool) return true } -func validateRequestCreate(w http.ResponseWriter, i e.CreateDto) (valid bool) { - switch { - case i.Class_Code == ere.ECAmbulatory: - // field allocatedVisitCount required if ambulatory visitMode_Code is adm - if ere.AmbulatoryClassCode(*i.SubClass_Code) == ere.ACCRehab && *i.VisitMode_Code == ere.VMCAdm { - if *i.AllocatedVisitCount == 0 { - rw.DataResponse(w, nil, d.FieldError{ - Code: dataValidationFail, - Message: "allocatedVisitCount required", - }) - return - } +func validateRequestSwitchUnit(w http.ResponseWriter, i e.SwitchUnitDto) (valid bool) { + if i.InternalReferences == nil { + rw.DataResponse(w, nil, d.FieldError{ + Code: dataValidationFail, + Message: fmt.Sprintf("internalReferences required"), + }) + return + } + + for _, v := range *i.InternalReferences { + if v.Unit_Id == nil { + rw.DataResponse(w, nil, d.FieldError{ + Code: dataValidationFail, + Message: "internalReferences.unit_id required", + }) + return + } + + if v.Doctor_Id == nil { + rw.DataResponse(w, nil, d.FieldError{ + Code: dataValidationFail, + Message: "internalReferences.doctor_id required", + }) + return } } diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index 1c703b64..ee33d4a5 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -144,17 +144,19 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/material-order-item", materialorderitem.O) hk.GroupRoutes("/v1/encounter", r, auth.GuardMW, hk.MapHandlerFunc{ - "GET /": encounter.O.GetList, - "GET /{id}": encounter.O.GetDetail, - "POST /": encounter.O.Create, - "PATCH /{id}": encounter.O.Update, - "DELETE /{id}": encounter.O.Delete, - "PATCH /{id}/check-out": encounter.O.CheckOut, - "PATCH /{id}/check-in": encounter.O.CheckIn, - "PATCH /{id}/proccess": encounter.O.Process, - "PATCH /{id}/cancel": encounter.O.Cancel, - "PATCH /{id}/reject": encounter.O.Reject, - "PATCH /{id}/skip": encounter.O.Skip, + "GET /": encounter.O.GetList, + "GET /{id}": encounter.O.GetDetail, + "POST /": encounter.O.Create, + "PATCH /{id}": encounter.O.Update, + "DELETE /{id}": encounter.O.Delete, + "PATCH /{id}/check-out": encounter.O.CheckOut, + "PATCH /{id}/check-in": encounter.O.CheckIn, + "PATCH /{id}/proccess": encounter.O.Process, + "PATCH /{id}/cancel": encounter.O.Cancel, + "PATCH /{id}/reject": encounter.O.Reject, + "PATCH /{id}/skip": encounter.O.Skip, + "PATCH /{id}/req-switch-unit": encounter.O.RequestSwitchUnit, + "PATCH /{id}/approve-switch-unit": encounter.O.ApproveSwitchUnit, }) hk.GroupRoutes("/v1/mcu-order", r, auth.GuardMW, hk.MapHandlerFunc{ "GET /": mcuorder.O.GetList, diff --git a/internal/use-case/main-use-case/encounter/case.go b/internal/use-case/main-use-case/encounter/case.go index ec2ea231..9798a28b 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -2,7 +2,7 @@ package encounter import ( "errors" - authhelper "simrs-vx/internal/lib/auth" + "fmt" "strconv" "time" @@ -17,28 +17,18 @@ import ( ere "simrs-vx/internal/domain/references/encounter" eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist" - ea "simrs-vx/internal/domain/main-entities/ambulatory" - ec "simrs-vx/internal/domain/main-entities/chemo" edc "simrs-vx/internal/domain/main-entities/death-cause" - ed "simrs-vx/internal/domain/main-entities/doctor" - ee "simrs-vx/internal/domain/main-entities/emergency" eem "simrs-vx/internal/domain/main-entities/employee" e "simrs-vx/internal/domain/main-entities/encounter" - ei "simrs-vx/internal/domain/main-entities/inpatient" - er "simrs-vx/internal/domain/main-entities/rehab" + eir "simrs-vx/internal/domain/main-entities/internal-reference" erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist" es "simrs-vx/internal/domain/main-entities/soapi" uaeh "simrs-vx/internal/use-case/main-use-case/adm-employee-hist" - ua "simrs-vx/internal/use-case/main-use-case/ambulatory" - uc "simrs-vx/internal/use-case/main-use-case/chemo" udc "simrs-vx/internal/use-case/main-use-case/death-cause" - ud "simrs-vx/internal/use-case/main-use-case/doctor" - ue "simrs-vx/internal/use-case/main-use-case/emergency" uem "simrs-vx/internal/use-case/main-use-case/employee" - ui "simrs-vx/internal/use-case/main-use-case/inpatient" - ur "simrs-vx/internal/use-case/main-use-case/rehab" - us "simrs-vx/internal/use-case/main-use-case/soapi" + uir "simrs-vx/internal/use-case/main-use-case/internal-reference" + urdh "simrs-vx/internal/use-case/main-use-case/responsible-doctor-hist" ) const source = "encounter" @@ -46,8 +36,11 @@ const source = "encounter" var now = time.Now() func Create(input e.CreateDto) (*d.Data, error) { - data := e.Encounter{} - createSoapi := []es.CreateDto{} + var ( + data e.Encounter + recentSoapiDataforCopy []es.CreateDto + err error + ) event := pl.Event{ Feature: "Create", @@ -57,51 +50,36 @@ func Create(input e.CreateDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "create") - // validate SubClass - var subCode interface{} - subCode, err := verifyClassCode(input) - if err != nil { - return nil, err - } - - // verify whether the allocated visit count has not exceeded the limit - if input.Class_Code == ere.ECAmbulatory && subCode.(ere.AmbulatoryClassCode) == ere.ACCRehab && - *input.VisitMode_Code == ere.VMCSeries { - - dataEncounter, valid, err := verifyAllocatedVisitCount(input, &event) + // validate rehab bpjs + if input.RefTypeCode == ere.RTCBpjs && input.Class_Code == ere.ECAmbulatory && ere.AmbulatoryClassCode(*input.SubClass_Code) == ere.ACCRehab { + // identify visit mode from latest rehab data + recentRehabData, err := identifyVisitModeCode(input, &event) if err != nil { return nil, err } - if !valid { - 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"), + if recentRehabData != nil { + // determine VisitModeCode + input.VisitMode_Code, *input.RecentEncounterAdm, err = determineVisitMode(recentRehabData, input, &event) + if err != nil { + return nil, err } - return nil, pl.SetLogError(&event, input) + } else { + input.VisitMode_Code = ere.VMCAdm } - // get data soapi - createSoapi, err = getSoapiEncounterAdm(dataEncounter, &event) - if err != nil { - return nil, err + // if visitMode_Code is series, then get data soapi for copy + if input.VisitMode_Code == ere.VMCSeries { + // get data soapi + recentSoapiDataforCopy, err = getSoapiEncounterAdm(*input.RecentEncounterAdm, &event) + if err != nil { + return nil, err + } } } // check if patient is new in the hospital - 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 nil, err - } - if list, ok := dataPatient.Data.([]e.ResponseDto); ok { - if len(list) < 1 { - input.NewStatus = true - } - } + input.NewStatus, err = identifyPatientStatus(input) err = dg.I.Transaction(func(tx *gorm.DB) error { mwRunner := newMiddlewareRunner(&event, tx) @@ -122,72 +100,18 @@ func Create(input e.CreateDto) (*d.Data, error) { return err } else { data = *resData + input.Id = data.Id } - switch input.Class_Code { - case ere.ECAmbulatory: - subCodeAmbulatory := subCode.(ere.AmbulatoryClassCode) - ambCreate := ea.CreateDto{ - Encounter_Id: &data.Id, - Class_Code: subCodeAmbulatory, - } - _, err = ua.CreateData(ambCreate, &event, tx) - if err != nil { - return err - } - - if subCodeAmbulatory == ere.ACCChemo { - chemoCreate := ec.CreateDto{ - Encounter_Id: &data.Id, - Status_Code: erc.DVCNew, - SrcUnit_Id: input.Unit_Id, - } - _, err = uc.CreateData(chemoCreate, &event, tx) - if err != nil { - return err - } - } - - if subCodeAmbulatory == ere.ACCRehab && *input.VisitMode_Code == ere.VMCAdm { - // create data rehab - if _, err = ur.CreateData(er.CreateDto{ - Encounter_Id: &data.Id, - AllocatedVisitCount: input.AllocatedVisitCount}, &event, tx); err != nil { - return err - } - } else if subCodeAmbulatory == ere.ACCRehab && *input.VisitMode_Code == ere.VMCSeries { - // Insert Soapi - if err = us.CreateBulkData(createSoapi, data.Id, &event, tx); err != nil { - return err - } - } - - case ere.ECEmergency: - emerCreate := ee.CreateDto{ - Encounter_Id: &data.Id, - Class_Code: subCode.(ere.EmergencyClassCode), - } - _, err = ue.CreateData(emerCreate, &event, tx) - if err != nil { - return err - } - case ere.ECInpatient: - inpCreate := ei.CreateDto{ - Encounter_Id: &data.Id, - Class_Code: subCode.(ere.InpatientClassCode), - Infra_Id: input.Infra_Id, - } - _, err = ui.CreateData(inpCreate, &event, tx) - if err != nil { - return err - } - default: - return errors.New("invalid encounter class code") + // insert ambulatory/emergency/inpatient + err = insertdataClassCode(input, recentSoapiDataforCopy, &event, tx) + if err != nil { + return err } // insert adm_employee_hist if _, err := uaeh.CreateData(eaeh.CreateDto{ - Encounter_Id: &data.Main.Id, + Encounter_Id: &data.Id, Employee_Id: data.Adm_Employee_Id, StartedAt: &now}, &event, tx); err != nil { return err @@ -469,10 +393,15 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { if data.Ambulatory != nil && (data.Ambulatory.Class_Code == ere.ACCReg || data.Ambulatory.Class_Code == ere.ACCRehab) { // validate if soapi exist - err = getSoapiByTypeCode(input.Id, *data.Ambulatory, &event, "check-out") - if err != nil { + if err = getSoapiByTypeCode(data, &event, "check-out"); err != nil { return err } + + // verify and update rehabData if visit count has reached the allowed limit + if err = verifyRehabLimit(data, &event, tx); err != nil { + return err + } + } else { // chemo TBC if err := checkSoapiByDocExists(data.Id, &event, tx); err != nil { @@ -495,27 +424,22 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { } // update finishedAt in latest responsible_doctor_hist - if err = updateLatestResponsibleDoctorHist(e.CheckinDto{Id: input.Id, StartedAt: &now}, &event, tx); err != nil { + if err = updateLatestResponsibleDoctorHist(e.CheckinDto{Id: input.Id, FinishedAt: &now}, &event, tx); err != nil { return err } // update finishedAt in latest adm_employee_hist - if err = updateLatestAdmEmployeeHist(e.CheckinDto{Id: input.Id, StartedAt: &now}, &event, tx); err != nil { + if err = updateLatestAdmEmployeeHist(e.CheckinDto{Id: input.Id, FinishedAt: &now}, &event, tx); err != nil { return err } + // insert data death-cause if *input.Discharge_Method_Code == ere.DMCDeath { - // insert data death-cause if _, err = udc.CreateData(edc.CreateDto{Encounter_Id: &input.Id, Value: input.DeathCause}, &event, tx); err != nil { return err } } - //// bulk insert internal-references - //if err = createInternalReferences(input, &event, tx); err != nil { - // return err - //} - pl.SetLogInfo(&event, nil, "complete") return nil @@ -642,13 +566,13 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { // validate if soapi exist if data.Ambulatory != nil && (data.Ambulatory.Class_Code == ere.ACCReg || data.Ambulatory.Class_Code == ere.ACCRehab) { - err = getSoapiByTypeCode(input.Id, *data.Ambulatory, &event, "check-in") + err = getSoapiByTypeCode(data, &event, "check-in") if err != nil { return err } } - // Insert responsible_doctor_hist if responsible_doctor_id has changed && update latest history + // Upsert responsible_doctor_hist if responsible_doctor_id has changed if data.Responsible_Doctor_Id == nil || *input.Responsible_Doctor_Id != *data.Responsible_Doctor_Id { // upsert responsibleDoctorHist if err = upsertResponsibleDoctorHist(erdh.CreateDto{ @@ -660,7 +584,7 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { } } - // Insert adm_employee_hist if adm_employee_id has changed && update latest history + // Upsert adm_employee_hist if adm_employee_id has changed if input.Adm_Employee_Id != nil && *input.Adm_Employee_Id != *data.Adm_Employee_Id { // upsert admEmployeeHist if err = upsertAdmEmployeeHist(eaeh.CreateDto{ @@ -677,14 +601,6 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { return err } - if data.Ambulatory.Class_Code == ere.ACCRehab { - if err := updateRehabDoctor(er.UpdateDto{CreateDto: er.CreateDto{ - Encounter_Id: &data.Id, - }}, &event, tx); err != nil { - return err - } - } - pl.SetLogInfo(&event, nil, "complete") return nil @@ -704,43 +620,205 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { }, nil } -func validateForeignKey(input e.CheckinDto) error { - // validate employee_Id - if input.Adm_Employee_Id != nil { - if _, err := uem.ReadDetail(eem.ReadDetailDto{Id: uint16(*input.Adm_Employee_Id)}); err != nil { +func RequestSwitchUnit(input e.SwitchUnitDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Responsible_Doctor"} + var data *e.Encounter + var err error + + event := pl.Event{ + Feature: "RequestSwitchUnit", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "checkOut") + + unitIDs := make(map[uint16]struct{}) + doctorIDs := make(map[uint]struct{}) + for _, ref := range *input.InternalReferences { + if ref.Unit_Id != nil { + unitIDs[*ref.Unit_Id] = struct{}{} + } + if ref.Doctor_Id != nil { + doctorIDs[*ref.Doctor_Id] = struct{}{} + } + } + + // validate unit + if err = validateUnitIds(unitIDs, &event); err != nil { + return nil, err + } + + // validate doctor + if err = validateDoctorIds(doctorIDs, &event); err != nil { + return nil, err + } + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } - } - // validate doctor_id - if input.Responsible_Doctor_Id != nil { - if _, err := ud.ReadDetail(ed.ReadDetailDto{Id: uint16(*input.Responsible_Doctor_Id)}); err != nil { + //if data.IsDone() { + // event.Status = "failed" + // event.ErrInfo = pl.ErrorInfo{ + // Code: "data-state-mismatch", + // Detail: "encounter is done", + // Raw: errors.New("encounter is done"), + // } + // return pl.SetLogError(&event, input) + //} + + // verify Soapi exist for current responsible doctor + dataSoapi, err := getSoapiByResponsibleDoctor(*data, &event) + if err != nil { return err } + if len(dataSoapi) < 1 { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "missing-soapi", + Detail: fmt.Sprintf("Missing soapi from latest responsible doctor"), + } + return pl.SetLogError(&event, input) + } + + // bulk internal references + if err := uir.CreateBulkData(*input.InternalReferences, input.Id, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + return nil + }) + + if err != nil { + return nil, err } - return nil + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "requestSwitchUnit", + }, + Data: data.ToResponse(), + }, nil } -func verifyClassCode(input e.CreateDto) (subCode interface{}, err error) { - switch input.Class_Code { - case ere.ECAmbulatory: - subCode, err = ua.CheckClassCode(input.SubClass_Code) - if err != nil { - return nil, err - } - case ere.ECEmergency: - subCode, err = ue.CheckClassCode(input.SubClass_Code) - if err != nil { - return nil, err - } - case ere.ECInpatient: - subCode, err = ui.CheckClassCode(input.SubClass_Code) - if err != nil { - return nil, err - } - default: - return nil, errors.New("invalid encounter class code") +func ApproveSwitchUnit(input e.ApproveUnitDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Responsible_Doctor"} + var data *e.Encounter + var err error + + event := pl.Event{ + Feature: "ApproveSwitchUnit", + Source: source, } - return + + // Start log + pl.SetLogInfo(&event, input, "started", "approveSwitchUnit") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + // get internal reference + irData, err := uir.ReadDetailData(eir.ReadDetailDto{ + Id: input.InternalReferences_Id, Includes: "Doctor"}, &event, tx) + if err != nil { + return err + } + + if *irData.Status_Code != erc.DACNew { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-state-mismatch", + Detail: "internal references is approve", + Raw: errors.New("internal references is approve"), + } + return pl.SetLogError(&event, input) + } + + //if data.IsDone() { + // event.Status = "failed" + // event.ErrInfo = pl.ErrorInfo{ + // Code: "data-state-mismatch", + // Detail: "encounter is done", + // Raw: errors.New("encounter is done"), + // } + // return pl.SetLogError(&event, input) + //} + + // verify Soapi exist for current responsible doctor + dataSoapi, err := getSoapiByResponsibleDoctor(*data, &event) + if err != nil { + return err + } + if len(dataSoapi) < 1 { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "missing-soapi", + Detail: fmt.Sprintf("Missing soapi from latest responsible doctor"), + } + return pl.SetLogError(&event, input) + } + + // update internal reference + if err = uir.UpdateData(eir.UpdateDto{ + Id: input.InternalReferences_Id, + CreateDto: eir.CreateDto{ + Encounter_Id: &input.Id, + Doctor_Id: irData.Doctor_Id, + Unit_Id: irData.Unit_Id, + Status_Code: erc.DACApproved, + }}, irData, &event, tx); err != nil { + return err + } + + // update encounter + if err = updateEncounterApproveSwitchUnit(*irData, &event, tx); err != nil { + return err + } + + // update latest responsible doctor hist + if err = updateLatestResponsibleDoctorHist(e.CheckinDto{Id: input.Id, FinishedAt: &now}, &event, tx); err != nil { + return err + } + + // create responsible doctor based on internal reference data + if _, err = urdh.CreateData(erdh.CreateDto{ + Encounter_Id: &input.Id, + Doctor_Id: irData.Doctor_Id, + StartedAt: &now, + }, &event, tx); err != nil { + return err + } + + // update data response + data.Responsible_Doctor_Id = irData.Doctor_Id + data.Unit = irData.Unit + data.Specialist_Id = irData.Doctor.Specialist_Id + data.Subspecialist_Id = irData.Doctor.Subspecialist_Id + + pl.SetLogInfo(&event, nil, "complete") + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + Data: data.ToResponse(), + }, nil + } diff --git a/internal/use-case/main-use-case/encounter/helper.go b/internal/use-case/main-use-case/encounter/helper.go index 9c522ed6..dc825348 100644 --- a/internal/use-case/main-use-case/encounter/helper.go +++ b/internal/use-case/main-use-case/encounter/helper.go @@ -10,6 +10,8 @@ import ( "strings" "time" + authhelper "simrs-vx/internal/lib/auth" + dg "github.com/karincake/apem/db-gorm-pg" "gorm.io/gorm" @@ -23,9 +25,13 @@ import ( eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist" ea "simrs-vx/internal/domain/main-entities/ambulatory" + ec "simrs-vx/internal/domain/main-entities/chemo" 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" + eem "simrs-vx/internal/domain/main-entities/employee" 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" @@ -36,19 +42,26 @@ import ( epi "simrs-vx/internal/domain/main-entities/prescription-item" 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" eu "simrs-vx/internal/domain/main-entities/unit" // udo "simrs-vx/internal/use-case/main-use-case/device-order" - es "simrs-vx/internal/domain/main-entities/soapi" uaeh "simrs-vx/internal/use-case/main-use-case/adm-employee-hist" - uir "simrs-vx/internal/use-case/main-use-case/internal-reference" + 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" + uem "simrs-vx/internal/use-case/main-use-case/employee" + 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" 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" urdh "simrs-vx/internal/use-case/main-use-case/responsible-doctor-hist" + us "simrs-vx/internal/use-case/main-use-case/soapi" ) func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Encounter) { @@ -104,6 +117,19 @@ func setDataDischarge(src e.DischargeDto, dst *e.Encounter) { dst.FinishedAt = &now } +func setDataUpdateStatus(src e.UpdateStatusDto, dst *e.Encounter) { + dst.Status_Code = src.StatusCode +} + +func setDataCheckIn(src e.CheckinDto, dst *e.Encounter) { + if src.Adm_Employee_Id != nil { + dst.Adm_Employee_Id = src.Adm_Employee_Id + } + + dst.Responsible_Doctor_Id = src.Responsible_Doctor_Id + dst.StartedAt = src.StartedAt +} + func checkSoapiByDocExists(encounter_id uint, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, nil, "started", "checkSoapiByDocExists") var soapies []es.Soapi @@ -335,113 +361,6 @@ func getMcuOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error { return nil } -func setDataUpdateStatus(src e.UpdateStatusDto, dst *e.Encounter) { - dst.Status_Code = src.StatusCode -} - -func setDataCheckIn(src e.CheckinDto, dst *e.Encounter) { - if src.Adm_Employee_Id != nil { - dst.Adm_Employee_Id = src.Adm_Employee_Id - } - - dst.Responsible_Doctor_Id = src.Responsible_Doctor_Id - dst.StartedAt = src.StartedAt -} - -func createInternalReferences(input e.DischargeDto, event *pl.Event, tx *gorm.DB) error { - unitIDs := make(map[uint16]struct{}) - doctorIDs := make(map[uint]struct{}) - - for _, ref := range *input.InternalReferences { - if ref.Unit_Id != nil { - unitIDs[*ref.Unit_Id] = struct{}{} - } - if ref.Doctor_Id != nil { - doctorIDs[*ref.Doctor_Id] = struct{}{} - } - } - - // validate unitIds - if len(unitIDs) > 0 { - var ids []uint16 - for id := range unitIDs { - ids = append(ids, id) - } - - units, err := getUnits(ids, event, tx) - if err != nil { - return fmt.Errorf("failed to fetch units: %w", err) - } - if len(units) != len(ids) { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "data-validation-fail", - Detail: "unit_id not found", - } - return pl.SetLogError(event, nil) - } - } - - // validate doctorIds - if len(doctorIDs) > 0 { - var ids []uint - for id := range doctorIDs { - ids = append(ids, id) - } - - doctors, err := getDoctors(ids, event, tx) - if err != nil { - return fmt.Errorf("failed to fetch doctors: %w", err) - } - if len(doctors) != len(ids) { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "data-validation-fail", - Detail: "doctor_id not found", - } - return pl.SetLogError(event, nil) - } - } - - if err := uir.CreateBulkData(*input.InternalReferences, input.Id, event, tx); err != nil { - return err - } - - return nil -} - -func getUnits(unitIds []uint16, event *pl.Event, tx *gorm.DB) ([]eu.Unit, error) { - pl.SetLogInfo(event, nil, "started", "getUnits") - var units []eu.Unit - err := tx.Where("\"Id\" 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 []uint, event *pl.Event, tx *gorm.DB) ([]ed.Doctor, error) { - pl.SetLogInfo(event, nil, "started", "getDoctors") - var doctors []ed.Doctor - err := tx.Where("\"Id\" 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 upsertResponsibleDoctorHist(input erdh.CreateDto, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, nil, "started", "DBCreate") @@ -566,7 +485,7 @@ func updateLatestResponsibleDoctorHist(input e.CheckinDto, event *pl.Event, dbx result := tx. Model(&erdh.ResponsibleDoctorHist{}). Where("\"Id\" = (?)", subQuery). - Update("\"FinishedAt\"", input.StartedAt) + Update("\"FinishedAt\"", input.FinishedAt) if result.Error != nil { event.Status = "failed" @@ -607,7 +526,7 @@ func updateLatestAdmEmployeeHist(input e.CheckinDto, event *pl.Event, dbx ...*go result := tx. Model(&eaeh.AdmEmployeeHist{}). Where("\"Id\" = (?)", subQuery). - Update("\"FinishedAt\"", input.StartedAt) + Update("\"FinishedAt\"", input.FinishedAt) if result.Error != nil { event.Status = "failed" @@ -628,9 +547,7 @@ func updateLatestAdmEmployeeHist(input e.CheckinDto, event *pl.Event, dbx ...*go return nil } -func getSoapiEncounterAdm(enc e.Encounter, event *pl.Event) (dataSoapi []es.CreateDto, err error) { - var data []es.Soapi - +func getSoapiByResponsibleDoctor(enc e.Encounter, event *pl.Event) (data []es.Soapi, err error) { pl.SetLogInfo(event, enc, "started", "DBReadList") if enc.Responsible_Doctor == nil { @@ -653,21 +570,22 @@ func getSoapiEncounterAdm(enc e.Encounter, event *pl.Event) (dataSoapi []es.Crea if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ - Raw: err, + Raw: err, + Code: "read-fail", + Detail: "Database read failed", } - - 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") + 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 @@ -690,39 +608,46 @@ func getSoapiEncounterAdm(enc e.Encounter, event *pl.Event) (dataSoapi []es.Crea return } -func getSoapiByTypeCode(encounterId uint, dataAmbulatory ea.Ambulatory, event *pl.Event, mode string) (err error) { - pl.SetLogInfo(event, encounterId, "started", "DBReadList") +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 tx := dg.I. Model(&es.Soapi{}). Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\""). - Where("\"Encounter_Id\" = ?", encounterId). + Where("\"Encounter_Id\" = ?", encounter.Id). Where("\"Employee\".\"Position_Code\" = ?", erg.EPCDoc) // Set Case switch { - case dataAmbulatory.Class_Code == ere.ACCReg: + case amb.Class_Code == ere.ACCReg: tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", ercl.STCEEarlyMedic) - case dataAmbulatory.Class_Code == ere.ACCRehab && dataAmbulatory.VisitMode_Code == ere.VMCAdm: + 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 dataAmbulatory.Class_Code == ere.ACCRehab && dataAmbulatory.VisitMode_Code == ere.VMCSeries: + 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, encounterId) + return setDBError(event, err, encounter) } pl.SetLogInfo(event, nil, "complete") - return validateExistedSoapi(dataSoapi, &dataAmbulatory, event, mode) + return validateExistedSoapi(dataSoapi, encounter, event, mode) } -func validateExistedSoapi(dataSoapi []es.Soapi, dataAmbulatory *ea.Ambulatory, event *pl.Event, mode string) error { +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 @@ -731,11 +656,11 @@ func validateExistedSoapi(dataSoapi []es.Soapi, dataAmbulatory *ea.Ambulatory, e required := []ercl.SoapiTypeCode{} switch { - case dataAmbulatory.Class_Code == ere.ACCReg: + case amb.Class_Code == ere.ACCReg: required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic} - case dataAmbulatory.Class_Code == ere.ACCRehab && dataAmbulatory.VisitMode_Code == ere.VMCAdm: + case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCAdm: required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.STCFunc, ercl.STCEarlyRehab} - case dataAmbulatory.Class_Code == ere.ACCRehab && dataAmbulatory.VisitMode_Code == ere.VMCSeries: + case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCSeries: required = []ercl.SoapiTypeCode{ercl.STCEarlyRehab} } @@ -762,6 +687,231 @@ func validateExistedSoapi(dataSoapi []es.Soapi, dataAmbulatory *ea.Ambulatory, e 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 "", e.Encounter{}, err + } + + if !isQuotaValid || recentRehabData.ExpiredAt.Before(*pu.GetTimeNow()) { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "visit-limit-exceeded", + Detail: "Encounter has exceeded the allowed number of visits or expired", + Raw: errors.New("visit count exceeds allowed limit"), + } + return "", e.Encounter{}, 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 "", e.Encounter{}, 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_Id: input.Infra_Id, + } + + // 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) + + switch { + case subCode == ere.ACCChemo: + chemoCreate := ec.CreateDto{ + Encounter_Id: &input.Id, + Status_Code: erc.DVCNew, + SrcUnit_Id: input.Unit_Id, + } + + // create data chemo + _, err = uc.CreateData(chemoCreate, 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 rehab status as 'done' if exceeded. + if len(*encounterAdmData.RehabChildren) >= *encounterAdmData.Rehab.AllocatedVisitCount { + err = updateRehabStatus(er.UpdateDto{ + Id: uint16(encounterAdmData.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) error { + // validate employee_Id + if input.Adm_Employee_Id != nil { + if _, err := uem.ReadDetail(eem.ReadDetailDto{Id: uint16(*input.Adm_Employee_Id)}); err != nil { + return err + } + } + + // validate doctor_id + if input.Responsible_Doctor_Id != nil { + if _, err := ud.ReadDetail(ed.ReadDetailDto{Id: uint16(*input.Responsible_Doctor_Id)}); err != nil { + return err + } + } + + return nil +} + func setSoapiError(event *pl.Event, detail string) error { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ @@ -781,31 +931,82 @@ func setDBError(event *pl.Event, err error, ctx any) error { 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 -//} +func getUnits(unitIds []uint16, event *pl.Event) ([]eu.Unit, error) { + pl.SetLogInfo(event, nil, "started", "getUnits") + var units []eu.Unit + err := dg.I.Where("\"Id\" 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 []uint, event *pl.Event) ([]ed.Doctor, error) { + pl.SetLogInfo(event, nil, "started", "getDoctors") + var doctors []ed.Doctor + err := dg.I.Where("\"Id\" 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 validateUnitIds(unitIDs map[uint16]struct{}, event *pl.Event) error { + if len(unitIDs) > 0 { + var ids []uint16 + for id := range unitIDs { + ids = append(ids, id) + } + + units, err := getUnits(ids, event) + if err != nil { + return fmt.Errorf("failed to fetch units: %w", err) + } + if len(units) != len(ids) { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-validation-fail", + Detail: "unit_id not found", + } + return pl.SetLogError(event, nil) + } + } + + return nil +} + +func validateDoctorIds(doctorIDs map[uint]struct{}, event *pl.Event) error { + if len(doctorIDs) > 0 { + var ids []uint + for id := range doctorIDs { + ids = append(ids, id) + } + + doctors, err := getDoctors(ids, event) + if err != nil { + return fmt.Errorf("failed to fetch doctors: %w", err) + } + if len(doctors) != len(ids) { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-validation-fail", + Detail: "doctor_id not found", + } + return pl.SetLogError(event, nil) + } + } + + return nil +} diff --git a/internal/use-case/main-use-case/encounter/lib.go b/internal/use-case/main-use-case/encounter/lib.go index 88c50113..5f41bf8c 100644 --- a/internal/use-case/main-use-case/encounter/lib.go +++ b/internal/use-case/main-use-case/encounter/lib.go @@ -3,7 +3,7 @@ package encounter import ( // std "errors" - ere "simrs-vx/internal/domain/references/encounter" + eir "simrs-vx/internal/domain/main-entities/internal-reference" // external dg "github.com/karincake/apem/db-gorm-pg" gh "github.com/karincake/getuk" @@ -13,9 +13,14 @@ import ( pl "simrs-vx/pkg/logger" pu "simrs-vx/pkg/use-case-helper" + ere "simrs-vx/internal/domain/references/encounter" + e "simrs-vx/internal/domain/main-entities/encounter" + er "simrs-vx/internal/domain/main-entities/rehab" ) +const ErrorReadFailed = "Database read failed" + func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.Encounter, error) { pl.SetLogInfo(event, nil, "started", "DBCreate") @@ -249,20 +254,49 @@ func updateCheckInData(input e.CheckinDto, data *e.Encounter, event *pl.Event, d return nil } +func identifyVisitModeCode(i e.CreateDto, event *pl.Event) (recentRehabData *er.Rehab, err error) { + pl.SetLogInfo(event, nil, "started", "DBGetLatestRehab") + + var ( + tx = dg.I + ) + + err = tx. + Joins("JOIN \"Encounter\" ON \"Encounter\".\"Id\" = \"Rehab\".\"Encounter_Id\""). + Where("\"Encounter\".\"Patient_Id\" = ?", i.Patient_Id). + Order("\"CreatedAt\" DESC"). + First(&recentRehabData).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "read-recentRehab-fail", + Detail: ErrorReadFailed, + Raw: err, + } + return nil, pl.SetLogError(event, i) + } + + return +} + 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 + tx = dg.I + recentEncounterAdm e.Encounter + valid = true ) 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.ACCRehab, ere.VMCAdm). + Scopes(gh.Preload("RehabChildren,Rehab,Responsible_Doctor")). + Joins("JOIN \"Rehab\" ON \"Rehab\".\"Encounter_Id\" = \"Encounter\".\"Id\""). + Where("\"Encounter\".\"Patient_Id\" = ?", i.Patient_Id). + Where("\"Rehab\".\"VisitMode_Code\" = ?", ere.VMCAdm). Order("\"CreatedAt\" DESC"). First(&recentEncounterAdm).Error if err != nil { @@ -275,22 +309,41 @@ func verifyAllocatedVisitCount(i e.CreateDto, event *pl.Event) (e.Encounter, boo return e.Encounter{}, false, pl.SetLogError(event, i) } - err = tx. - 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.ACCRehab, 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) + // validate count rehab children + if recentEncounterAdm.RehabChildren != nil { + valid = len(*recentEncounterAdm.RehabChildren) < *recentEncounterAdm.Rehab.AllocatedVisitCount } - return recentEncounterAdm, countEncounterSeries < int64(*recentEncounterAdm.Rehab_Adm.AllocatedVisitCount), nil + return recentEncounterAdm, valid, nil +} + +func updateEncounterApproveSwitchUnit(input eir.InternalReference, event *pl.Event, dbx ...*gorm.DB) (err error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Model(&e.Encounter{}). + Where("\"Id\" = ?", input.Encounter_Id). + Updates(map[string]interface{}{ + "Responsible_Doctor_Id": input.Doctor_Id, + "Unit_Id": input.Unit_Id, + "Specialist_Id": input.Doctor.Specialist_Id, + "Subspecialist_Id": input.Doctor.Subspecialist_Id, + }).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "update-fail", + Detail: "Failed to update encounter approve switch unit", + Raw: err, + } + return pl.SetLogError(event, input) + } + + pl.SetLogInfo(event, input, "complete") + return nil } diff --git a/internal/use-case/main-use-case/internal-reference/helper.go b/internal/use-case/main-use-case/internal-reference/helper.go index dbd92f73..36c28540 100644 --- a/internal/use-case/main-use-case/internal-reference/helper.go +++ b/internal/use-case/main-use-case/internal-reference/helper.go @@ -6,6 +6,7 @@ package internal_reference import ( ir "simrs-vx/internal/domain/main-entities/internal-reference" + erc "simrs-vx/internal/domain/references/common" ) func setData[T *ir.CreateDto | *ir.UpdateDto](input T, data *ir.InternalReference) { @@ -20,16 +21,19 @@ func setData[T *ir.CreateDto | *ir.UpdateDto](input T, data *ir.InternalReferenc data.Encounter_Id = inputSrc.Encounter_Id data.Unit_Id = inputSrc.Unit_Id data.Doctor_Id = inputSrc.Doctor_Id + data.Status_Code = &inputSrc.Status_Code } func setBulkData(input []ir.CreateDto, encounterId uint) []ir.InternalReference { var data []ir.InternalReference for _, v := range input { + statusCode := erc.DACNew data = append(data, ir.InternalReference{ Encounter_Id: &encounterId, Unit_Id: v.Unit_Id, Doctor_Id: v.Doctor_Id, + Status_Code: &statusCode, }) }