From 2e70cd727c9eb5730bef2ac30c468c39ba5f7ccb Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Thu, 11 Dec 2025 15:29:01 +0700 Subject: [PATCH 1/4] feat (encounter): create-with-patient wip --- .../domain/main-entities/encounter/dto.go | 5 + .../main-handler/encounter/handler.go | 26 ++++ .../interface/main-handler/main-handler.go | 1 + .../use-case/main-use-case/encounter/case.go | 143 ++++++++++++++++++ .../encounter/middleware-runner.go | 19 +++ .../main-use-case/encounter/tycovar.go | 7 + .../use-case/main-use-case/patient/case.go | 27 ++-- 7 files changed, 218 insertions(+), 10 deletions(-) diff --git a/internal/domain/main-entities/encounter/dto.go b/internal/domain/main-entities/encounter/dto.go index 324501e4..9265978b 100644 --- a/internal/domain/main-entities/encounter/dto.go +++ b/internal/domain/main-entities/encounter/dto.go @@ -297,3 +297,8 @@ func ToResponseList(data []Encounter) []ResponseDto { } return resp } + +type CreateWithPatientDto struct { + Encounter CreateDto `json:"encounter"` + Patient ep.CreateDto `json:"patient"` +} diff --git a/internal/interface/main-handler/encounter/handler.go b/internal/interface/main-handler/encounter/handler.go index 7ac31393..c6a02ba9 100644 --- a/internal/interface/main-handler/encounter/handler.go +++ b/internal/interface/main-handler/encounter/handler.go @@ -312,3 +312,29 @@ func (obj myBase) CancelSwitchUnit(w http.ResponseWriter, r *http.Request) { res, err := u.CancelSwitchUnit(dto) rw.DataResponse(w, res, err) } + +func (obj myBase) CreateWithPatient(w http.ResponseWriter, r *http.Request) { + authInfo, err := pa.GetAuthInfo(r) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil) + } + + dto := e.CreateWithPatientDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + + // validate SubClass + if err := verifyClassCode(dto.Encounter); err != nil { + rw.DataResponse(w, nil, d.FieldError{ + Code: dataValidationFail, + Message: err.Error(), + }) + return + } + + dto.Encounter.AuthInfo = *authInfo + dto.Patient.AuthInfo = *authInfo + res, err := u.CreateWithPatient(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index f6f6bfce..c47ae37d 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -197,6 +197,7 @@ func SetRoutes() http.Handler { "PATCH /{id}/req-switch-unit": encounter.O.RequestSwitchUnit, "PATCH /{id}/approve-switch-unit": encounter.O.ApproveSwitchUnit, "PATCH /{id}/cancel-switch-unit": encounter.O.CancelSwitchUnit, + "POST /create-with-patient": encounter.O.CreateWithPatient, }) 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 62a7c992..3b966ef9 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -22,12 +22,14 @@ import ( edc "simrs-vx/internal/domain/main-entities/death-cause" e "simrs-vx/internal/domain/main-entities/encounter" eir "simrs-vx/internal/domain/main-entities/internal-reference" + ep "simrs-vx/internal/domain/main-entities/patient" es "simrs-vx/internal/domain/main-entities/soapi" esync "simrs-vx/internal/domain/sync-entities/log" uv "simrs-vx/internal/use-case/bpjs-use-case/vclaim-reference" udc "simrs-vx/internal/use-case/main-use-case/death-cause" uir "simrs-vx/internal/use-case/main-use-case/internal-reference" + up "simrs-vx/internal/use-case/main-use-case/patient" us "simrs-vx/internal/use-case/main-use-case/soapi" ) @@ -1075,3 +1077,144 @@ func validateAuth(a auth.AuthInfo, roleAllowed []string, action string, event *p return nil } + +func CreateWithPatient(input e.CreateWithPatientDto) (*d.Data, error) { + var ( + data e.Encounter + recentSoapiDataforCopy []es.CreateDto + err error + ) + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + roleAllowed := []string{string(erg.EPCReg)} + err = validateAuth(input.Encounter.AuthInfo, roleAllowed, "create-encounter", &event) + if err != nil { + return nil, err + } + + // validate rehab by bpjs + if input.Encounter.RefTypeCode == ere.RTCBpjs && + input.Encounter.Class_Code == ere.ECAmbulatory && + ere.AmbulatoryClassCode(*input.Encounter.SubClass_Code) == ere.ACCRehab { + // get latest rehab data + recentRehabData, err := getLatestRehabData(input.Encounter, &event) + if err != nil { + return nil, err + } + + // If recentRehabData is nil, then visitMode_Code = "adm" + if recentRehabData != nil { + // If recentRehabData is not nil, determine the visitMode_Code: + // If the mode is "series", verify whether the visit count still remains + // and whether the series has not expired. + // If visitMode is "series", then get encounterAdm + input.Encounter.VisitMode_Code, input.Encounter.RecentEncounterAdm, err = determineVisitMode(recentRehabData, input.Encounter, &event) + if err != nil { + return nil, err + } + } else { + input.Encounter.VisitMode_Code = ere.VMCAdm + } + + // When visitMode_Code is "series", load the associated SOAPI record to copy its values. + if input.Encounter.VisitMode_Code == ere.VMCSeries { + // get data soapi + recentSoapiDataforCopy, err = getSoapiEncounterAdm(*input.Encounter.RecentEncounterAdm, &event) + if err != nil { + return nil, err + } + } + } + + // check if patient is new in the hospital + input.Encounter.NewStatus, err = identifyPatientStatus(input.Encounter) + if err != nil { + return nil, err + } + input.Encounter.Adm_Employee_Id = input.Encounter.AuthInfo.Employee_Id + + mwRunner := newMiddlewareRunner(&event, input.Encounter.Sync) + + err = dg.I.Transaction(func(tx *gorm.DB) error { + // create patient + var patientId uint + patientData, err := up.Create(input.Patient, tx) + if err != nil { + return err + } + + if patientData != nil { + patientId = patientData.Data.(*ep.Patient).Id + } + + // create encounter + input.Encounter.Patient_Id = &patientId + if resData, err := CreateData(input.Encounter, &event, tx); err != nil { + return err + } else { + data = *resData + input.Encounter.Id = data.Id + } + + // insert ambulatory/emergency/inpatient + err = insertdataClassCode(input.Encounter, recentSoapiDataforCopy, &event, tx) + if err != nil { + return err + } + + // insert vclaimReference + if vr := input.Encounter.VclaimReference; vr != nil { + t, _ := time.Parse("2006-01-02", vr.TglRujukan) + _, err = uv.CreateData(ev.CreateDto{ + Encounter_Id: &data.Id, + Date: &t, + SrcCode: input.Encounter.Ref_Number, + SrcName: input.Encounter.RefSource_Name, + Number: &vr.NoSep}, &event, tx) + if err != nil { + return err + } + } + + dataEncounter, err := ReadDetailData(e.ReadDetailDto{ + Id: data.Id, + Includes: "Adm_Employee.User,Adm_Employee.Person," + + "Patient.Person.Relatives," + + "Patient.Person.VclaimMember,VclaimReference," + + "Patient.Person.Contacts,Patient.Person.Addresses"}, + &event, tx) + if err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateWithPatientMiddleware(createWithPatientPreMw, dataEncounter); err != nil { + return err + } + + return nil + }) + + if err = runLogMiddleware(err, input, mwRunner); err != nil { + return nil, err + } + + pl.SetLogInfo(&event, nil, "complete") + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} diff --git a/internal/use-case/main-use-case/encounter/middleware-runner.go b/internal/use-case/main-use-case/encounter/middleware-runner.go index da62fdb6..3ee19355 100644 --- a/internal/use-case/main-use-case/encounter/middleware-runner.go +++ b/internal/use-case/main-use-case/encounter/middleware-runner.go @@ -259,3 +259,22 @@ func (me *middlewareRunner) RunCancelSwitchUnitMiddleware(middleware cancelSwitc func (me *middlewareRunner) setMwType(mwType pu.MWType) { me.MwType = mwType } + +func (me *middlewareRunner) RunCreateWithPatientMiddleware(middlewares []createWithPatientMw, input *e.Encounter) error { + if !me.SyncOn { + return nil + } + + for _, middleware := range middlewares { + logData := pu.GetLogData(input, nil) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} diff --git a/internal/use-case/main-use-case/encounter/tycovar.go b/internal/use-case/main-use-case/encounter/tycovar.go index 2b268378..376baed8 100644 --- a/internal/use-case/main-use-case/encounter/tycovar.go +++ b/internal/use-case/main-use-case/encounter/tycovar.go @@ -74,6 +74,11 @@ type cancelSwitchUnitMw struct { Func func(input *e.ApproveCancelUnitDto) error } +type createWithPatientMw struct { + Name string + Func func(input *e.Encounter) error +} + type UpdateMw = updateMw type DeleteMw = deleteMw @@ -94,3 +99,5 @@ var updatestatusEncounter []updateStatusMw var requestSwitchEncounter requestSwitchUnitMw var approveSwitchEncounter approveSwitchUnitMw var cancelSwitchEncounter cancelSwitchUnitMw +var createWithPatientPreMw []createWithPatientMw +var createWithPatientPostMw []createWithPatientMw diff --git a/internal/use-case/main-use-case/patient/case.go b/internal/use-case/main-use-case/patient/case.go index 5180137b..ec518fe4 100644 --- a/internal/use-case/main-use-case/patient/case.go +++ b/internal/use-case/main-use-case/patient/case.go @@ -27,7 +27,7 @@ import ( const source = "patient" -func Create(input e.CreateDto) (*d.Data, error) { +func Create(input e.CreateDto, dbx ...*gorm.DB) (*d.Data, error) { data := e.Patient{} event := pl.Event{ @@ -35,6 +35,13 @@ func Create(input e.CreateDto) (*d.Data, error) { Source: source, } + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + // Start log pl.SetLogInfo(&event, input, "started", "create") mwRunner := newMiddlewareRunner(&event, input.Sync) @@ -51,7 +58,7 @@ func Create(input e.CreateDto) (*d.Data, error) { input.RegisteredBy_User_Name = &input.AuthInfo.User_Name - err := dg.I.Transaction(func(tx *gorm.DB) error { + err := tx.Transaction(func(tx1 *gorm.DB) error { mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { @@ -65,14 +72,14 @@ func Create(input e.CreateDto) (*d.Data, error) { } input.Number = nomr - if person_id, err := upe.CreateOrUpdatePerson(input.Person, &event, tx); err != nil { + if person_id, err := upe.CreateOrUpdatePerson(input.Person, &event, tx1); err != nil { return err } else { input.Person_Id = person_id } if input.Person.VclaimMember_CardNumber != nil && input.Person.ResidentIdentityNumber != nil { - if err := uvm.CreateOrUpdateData(evm.CreateDto{CardNumber: input.Person.VclaimMember_CardNumber, Person_Id: input.Person_Id}, &event, tx); err != nil { + if err := uvm.CreateOrUpdateData(evm.CreateDto{CardNumber: input.Person.VclaimMember_CardNumber, Person_Id: input.Person_Id}, &event, tx1); err != nil { return err } } @@ -80,38 +87,38 @@ func Create(input e.CreateDto) (*d.Data, error) { for idx := range input.PersonAddresses { input.PersonAddresses[idx].Person_Id = *input.Person_Id } - if err := upa.CreateOrUpdateBatch(input.PersonAddresses, &event, tx); err != nil { + if err := upa.CreateOrUpdateBatch(input.PersonAddresses, &event, tx1); err != nil { return err } for idx := range input.PersonContacts { input.PersonContacts[idx].Person_Id = *input.Person_Id } - if err := upc.CreateOrUpdateBatch(input.PersonContacts, &event, tx); err != nil { + if err := upc.CreateOrUpdateBatch(input.PersonContacts, &event, tx1); err != nil { return err } for idx := range input.PersonRelatives { input.PersonRelatives[idx].Person_Id = *input.Person_Id } - if err := upr.CreateOrUpdateBatch(input.PersonRelatives, &event, tx); err != nil { + if err := upr.CreateOrUpdateBatch(input.PersonRelatives, &event, tx1); err != nil { return err } for idx := range input.PersonInsurances { input.PersonInsurances[idx].Person_Id = *input.Person_Id } - if err := upi.CreateOrUpdateBatch(input.PersonInsurances, &event, tx); err != nil { + if err := upi.CreateOrUpdateBatch(input.PersonInsurances, &event, tx1); err != nil { return err } - if resData, err := CreateData(input, &event, tx); err != nil { + if resData, err := CreateData(input, &event, tx1); err != nil { return err } else { data = *resData } - dataPatient, err := ReadDetailData(e.ReadDetailDto{Id: uint16(data.Id)}, &event, tx) + dataPatient, err := ReadDetailData(e.ReadDetailDto{Id: uint16(data.Id)}, &event, tx1) if err != nil { return err } From 4214bd00bd41a0453b78d12f5443c1857aca9423 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Thu, 11 Dec 2025 15:40:07 +0700 Subject: [PATCH 2/4] feat/dockerize: added assets --- Dockerfile-main-api | 1 + Dockerfile-simgos-sync-api | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile-main-api b/Dockerfile-main-api index 0c75acfa..ea038869 100644 --- a/Dockerfile-main-api +++ b/Dockerfile-main-api @@ -7,6 +7,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o ./cmd/main-api/main-api ./cmd/main-api/ FROM alpine:latest WORKDIR /app +COPY --from=builder /src/assets . COPY --from=builder /src/cmd/main-api/main-api . COPY --from=builder /src/cmd/main-api/config.yml . EXPOSE 8000 diff --git a/Dockerfile-simgos-sync-api b/Dockerfile-simgos-sync-api index 35660a2f..efb675db 100644 --- a/Dockerfile-simgos-sync-api +++ b/Dockerfile-simgos-sync-api @@ -7,6 +7,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o ./cmd/simgos-sync-api/simgos-sync-api . FROM alpine:latest WORKDIR /app +COPY --from=builder /src/assets . COPY --from=builder /src/cmd/simgos-sync-api/simgos-sync-api . COPY --from=builder /src/cmd/simgos-sync-api/config.yml . EXPOSE 8000 From 4a62316548a22a9ea16f0b64c834532530f1a1b9 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Thu, 11 Dec 2025 15:51:55 +0700 Subject: [PATCH 3/4] feat (encounter): create-with-patient done, tested with positif case only --- internal/use-case/main-use-case/encounter/case.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/use-case/main-use-case/encounter/case.go b/internal/use-case/main-use-case/encounter/case.go index 3b966ef9..b4001156 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -1151,7 +1151,7 @@ func CreateWithPatient(input e.CreateWithPatientDto) (*d.Data, error) { } if patientData != nil { - patientId = patientData.Data.(*ep.Patient).Id + patientId = patientData.Data.(ep.ResponseDto).Id } // create encounter From c7f2876e6e0e43fefbe54d477cb123c47b068805 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Thu, 11 Dec 2025 16:43:14 +0700 Subject: [PATCH 4/4] dev: chore, moved Dockerfile* to example --- .gitignore | 2 ++ Dockerfile-main-api => Dockerfile-main-api-example | 2 +- Dockerfile-simgos-sync-api => Dockerfile-sync-api-example | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) rename Dockerfile-main-api => Dockerfile-main-api-example (96%) rename Dockerfile-simgos-sync-api => Dockerfile-sync-api-example (97%) diff --git a/.gitignore b/.gitignore index 7800ac94..4c10eb38 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ go.work.sum # env file .env config.yml +Dockerfile-main-api +Dockerfile-sync-api **/atlas.hcl !**/atlas.hcl.example diff --git a/Dockerfile-main-api b/Dockerfile-main-api-example similarity index 96% rename from Dockerfile-main-api rename to Dockerfile-main-api-example index ea038869..cb8839b9 100644 --- a/Dockerfile-main-api +++ b/Dockerfile-main-api-example @@ -10,5 +10,5 @@ WORKDIR /app COPY --from=builder /src/assets . COPY --from=builder /src/cmd/main-api/main-api . COPY --from=builder /src/cmd/main-api/config.yml . -EXPOSE 8000 +EXPOSE 8010 CMD ["./main-api"] diff --git a/Dockerfile-simgos-sync-api b/Dockerfile-sync-api-example similarity index 97% rename from Dockerfile-simgos-sync-api rename to Dockerfile-sync-api-example index efb675db..5b32d522 100644 --- a/Dockerfile-simgos-sync-api +++ b/Dockerfile-sync-api-example @@ -10,5 +10,5 @@ WORKDIR /app COPY --from=builder /src/assets . COPY --from=builder /src/cmd/simgos-sync-api/simgos-sync-api . COPY --from=builder /src/cmd/simgos-sync-api/config.yml . -EXPOSE 8000 +EXPOSE 8011 CMD ["./simgos-sync-api"]