diff --git a/cmd/main-migration/migrations/20251104084135.sql b/cmd/main-migration/migrations/20251104084135.sql new file mode 100644 index 00000000..8c50d914 --- /dev/null +++ b/cmd/main-migration/migrations/20251104084135.sql @@ -0,0 +1,17 @@ +-- Create "ChemoProtocol" table +CREATE TABLE "public"."ChemoProtocol" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Encounter_Id" bigint NULL, + "Patient_Weight" numeric NULL, + "Patient_Height" numeric NULL, + "Diagnoses" text NULL, + "Duration" bigint NULL, + "DurationUnit_Code" character varying(10) NULL, + "StartDate" timestamptz NULL, + "EndDate" timestamptz NULL, + PRIMARY KEY ("Id"), + CONSTRAINT "fk_ChemoProtocol_Encounter" FOREIGN KEY ("Encounter_Id") REFERENCES "public"."Encounter" ("Id") ON UPDATE NO ACTION ON DELETE NO ACTION +); diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 7be07b61..40b66b02 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:2J5umJFoWClM/F8kKNyZFoDqrV1aL8PimwTG6Mx6syo= +h1:drtrRtMhlNYK0c9wV3CUkJvXwWgrD8xGPPJy9wlcvNA= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -67,4 +67,5 @@ h1:2J5umJFoWClM/F8kKNyZFoDqrV1aL8PimwTG6Mx6syo= 20251103081637.sql h1:tf3BcwTeIw+oxMEisKDDfyKnBfalTLs8b0PJA8JWYxY= 20251104042334.sql h1:7PDMWOhmJywolAPKFZ14XaDBeMvcxShaXFN2IemNtzk= 20251104043530.sql h1:qvYVp3ysPf27f1BcoRNCFGovxuVE12lg9d6Xzda6zWU= -20251104080952.sql h1:+SMRSBPjP0qeLlaiIdff7hUnW2+1l1GJOWjP7fWPiGA= +20251104080952.sql h1:avghpv1n3yaCDR/TA0X+hgxDGoLBQGu/GJUwj4VT/Ic= +20251104084135.sql h1:Y4coFrHgDXd/DM8ihEy+qMkOSrO8M4SI4shRCJIiBBA= diff --git a/internal/domain/main-entities/adm-employee-hist/dto.go b/internal/domain/main-entities/adm-employee-hist/dto.go index 9a5d838f..a3500060 100644 --- a/internal/domain/main-entities/adm-employee-hist/dto.go +++ b/internal/domain/main-entities/adm-employee-hist/dto.go @@ -28,9 +28,8 @@ type FilterDto struct { } type ReadDetailDto struct { - Id uint16 `json:"id"` - Code *string `json:"code"` - Includes string `json:"includes"` + Id uint16 `json:"id"` + Includes string `json:"includes"` } type UpdateDto struct { diff --git a/internal/domain/main-entities/ambulatory/dto.go b/internal/domain/main-entities/ambulatory/dto.go index 9b6bc314..f580467a 100644 --- a/internal/domain/main-entities/ambulatory/dto.go +++ b/internal/domain/main-entities/ambulatory/dto.go @@ -6,8 +6,9 @@ import ( ) type CreateDto struct { - Encounter_Id *uint `json:"encounter_id"` - Class_Code ere.AmbulatoryClassCode `json:"class_code" validate:"maxLength=10"` + Encounter_Id *uint `json:"encounter_id"` + Class_Code ere.AmbulatoryClassCode `json:"class_code" validate:"maxLength=10"` + VisitMode_Code ere.VisitModeCode `json:"visitMode_code"` } type ReadListDto struct { @@ -42,12 +43,17 @@ type MetaDto struct { type ResponseDto struct { ecore.Main - Encounter_Id *uint `json:"encounter_id"` - Class_Code ere.AmbulatoryClassCode `json:"class_code"` + Encounter_Id *uint `json:"encounter_id"` + Class_Code ere.AmbulatoryClassCode `json:"class_code"` + VisitMode_Code ere.VisitModeCode `json:"visitMode_code"` } func (d Ambulatory) ToResponse() ResponseDto { - resp := ResponseDto{} + resp := ResponseDto{ + Encounter_Id: d.Encounter_Id, + Class_Code: d.Class_Code, + VisitMode_Code: d.VisitMode_Code, + } resp.Main = d.Main return resp } diff --git a/internal/domain/main-entities/chemo-protocol/dto.go b/internal/domain/main-entities/chemo-protocol/dto.go new file mode 100644 index 00000000..04ea0023 --- /dev/null +++ b/internal/domain/main-entities/chemo-protocol/dto.go @@ -0,0 +1,91 @@ +package chemo_protocol + +import ( + // std + "time" + + // internal - domain - references + erc "simrs-vx/internal/domain/references/common" + + // internal - domain - main-entities + ecore "simrs-vx/internal/domain/base-entities/core" + ee "simrs-vx/internal/domain/main-entities/encounter" +) + +type CreateDto struct { + Encounter_Id *uint `json:"encounter_id"` + Patient_Weight *float32 `json:"patient_weight"` + Patient_Height *float32 `json:"patient_height"` + Diagnoses *string `json:"diagnoses"` + Duration *uint `json:"duration"` + DurationUnit_Code *erc.TimeUnitCode `json:"durationUnit_code"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` +} + +type ReadListDto struct { + FilterDto + Includes string `json:"includes"` + Pagination ecore.Pagination +} + +type FilterDto struct { + Encounter_Id *uint `json:"encounter-id"` +} + +type ReadDetailDto struct { + Id uint16 `json:"id"` + Includes string `json:"includes"` +} + +type UpdateDto struct { + Id uint16 `json:"id"` + CreateDto +} + +type DeleteDto struct { + Id uint16 `json:"id"` +} + +type MetaDto struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + Count int `json:"count"` +} + +type ResponseDto struct { + ecore.Main + Encounter_Id *uint `json:"encounter_id"` + Encounter *ee.Encounter `json:"encounter,omitempty"` + Patient_Weight *float32 `json:"patient_weight"` + Patient_Height *float32 `json:"patient_height"` + Diagnoses *string `json:"diagnoses"` + Duration *uint `json:"duration"` + DurationUnit_Code *erc.TimeUnitCode `json:"durationUnit_code"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` +} + +func (d ChemoProtocol) ToResponse() ResponseDto { + resp := ResponseDto{ + Encounter_Id: d.Encounter_Id, + Encounter: d.Encounter, + Patient_Weight: d.Patient_Weight, + Patient_Height: d.Patient_Height, + Diagnoses: d.Diagnoses, + Duration: d.Duration, + DurationUnit_Code: d.DurationUnit_Code, + StartDate: d.StartDate, + EndDate: d.EndDate, + } + resp.Main = d.Main + return resp +} + +func ToResponseList(data []ChemoProtocol) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/chemo-protocol/entity.go b/internal/domain/main-entities/chemo-protocol/entity.go new file mode 100644 index 00000000..e1508e91 --- /dev/null +++ b/internal/domain/main-entities/chemo-protocol/entity.go @@ -0,0 +1,21 @@ +package chemo_protocol + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + ee "simrs-vx/internal/domain/main-entities/encounter" + "simrs-vx/internal/domain/references/common" + "time" +) + +type ChemoProtocol struct { + ecore.Main + Encounter_Id *uint `json:"encounter_id"` + Encounter *ee.Encounter `json:"encounter,omitempty" gorm:"foreignKey:Encounter_Id;references:Id"` + Patient_Weight *float32 `json:"patient_weight"` + Patient_Height *float32 `json:"patient_height"` + Diagnoses *string `json:"diagnoses"` + Duration *uint `json:"duration"` + DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code" gorm:"size:10"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` +} diff --git a/internal/domain/main-entities/chemo/dto.go b/internal/domain/main-entities/chemo/dto.go index 8317b259..653b5409 100644 --- a/internal/domain/main-entities/chemo/dto.go +++ b/internal/domain/main-entities/chemo/dto.go @@ -52,6 +52,8 @@ type DeleteDto struct { type VerifyDto struct { Id uint16 `json:"id"` Status_Code erc.DataVerifiedCode `json:"status_code"` + Bed *string `json:"bed" validate:"required"` + Needs *string `json:"needs" validate:"required"` pa.AuthInfo } diff --git a/internal/domain/main-entities/encounter/dto.go b/internal/domain/main-entities/encounter/dto.go index 89f12caf..6782b18c 100644 --- a/internal/domain/main-entities/encounter/dto.go +++ b/internal/domain/main-entities/encounter/dto.go @@ -48,6 +48,8 @@ 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" pa.AuthInfo } diff --git a/internal/domain/main-entities/rehab/dto.go b/internal/domain/main-entities/rehab/dto.go new file mode 100644 index 00000000..0cec61d8 --- /dev/null +++ b/internal/domain/main-entities/rehab/dto.go @@ -0,0 +1,70 @@ +package rehab + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + ed "simrs-vx/internal/domain/main-entities/doctor" +) + +type CreateDto struct { + Encounter_Id *uint `json:"encounter_id"` + Doctor_Id *uint `json:"doctor_id"` + AllocatedVisitCount *int `json:"allocatedVisitCount"` +} + +type ReadListDto struct { + FilterDto + Includes string `json:"includes"` + Pagination ecore.Pagination +} + +type FilterDto struct { + Encounter_Id *uint `json:"encounter-id"` + Doctor_Id *uint `json:"doctor-id"` +} + +type ReadDetailDto struct { + Id uint16 `json:"id"` + Includes string `json:"includes"` +} + +type UpdateDto struct { + Id uint16 `json:"id"` + CreateDto +} + +type DeleteDto struct { + Id uint16 `json:"id"` +} + +type MetaDto struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + Count int `json:"count"` +} + +type ResponseDto struct { + ecore.Main + Encounter_Id *uint `json:"encounter_id"` + Doctor_Id *uint `json:"doctor_id"` + Doctor *ed.Doctor `json:"doctor,omitempty"` + AllocatedVisitCount *int `json:"allocatedVisitCount"` +} + +func (d Rehab) ToResponse() ResponseDto { + resp := ResponseDto{ + Encounter_Id: d.Encounter_Id, + Doctor_Id: d.Doctor_Id, + Doctor: d.Doctor, + AllocatedVisitCount: d.AllocatedVisitCount, + } + resp.Main = d.Main + return resp +} + +func ToResponseList(data []Rehab) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/responsible-doctor-hist/dto.go b/internal/domain/main-entities/responsible-doctor-hist/dto.go index 342faffa..68c8dd21 100644 --- a/internal/domain/main-entities/responsible-doctor-hist/dto.go +++ b/internal/domain/main-entities/responsible-doctor-hist/dto.go @@ -28,9 +28,8 @@ type FilterDto struct { } type ReadDetailDto struct { - Id uint16 `json:"id"` - Code *string `json:"code"` - Includes string `json:"includes"` + Id uint16 `json:"id"` + Includes string `json:"includes"` } type UpdateDto struct { diff --git a/internal/domain/main-entities/therapy-protocol/dto.go b/internal/domain/main-entities/therapy-protocol/dto.go new file mode 100644 index 00000000..f7a5f2e4 --- /dev/null +++ b/internal/domain/main-entities/therapy-protocol/dto.go @@ -0,0 +1,107 @@ +package therapy_protocol + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/encounter" + "simrs-vx/internal/domain/references/common" +) + +type CreateDto struct { + Encounter_Id *uint `json:"encounter_id"` + Doctor_Id *uint `json:"doctor_id"` + Anamnesis *string `json:"anamnesis" validate:"maxLength=2048"` + MedicalDiagnoses *string `json:"medicalDiagnoses"` + FunctionDiagnoses *string `json:"functionDiagnoses"` + Procedures *string `json:"procedures"` + SupportingExams *string `json:"supportingExams" validate:"maxLength=2048"` + Instruction *string `json:"instruction" validate:"maxLength=2048"` + Evaluation *string `json:"evaluation" validate:"maxLength=2048"` + WorkCauseStatus *string `json:"workCauseStatus"` + Frequency *uint `json:"frequency"` + IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_code" validate:"maxLength=10"` + Duration *uint `json:"duration"` + DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code" validate:"maxLength=10"` +} + +type ReadListDto struct { + FilterDto + Includes string `json:"includes"` + Pagination ecore.Pagination +} + +type FilterDto struct { + Encounter_Id *uint `json:"encounter-id"` + Doctor_Id *uint `json:"doctor-id"` +} + +type ReadDetailDto struct { + Id uint `json:"id"` + Includes string `json:"includes"` +} + +type UpdateDto struct { + Id uint `json:"id"` + CreateDto +} + +type DeleteDto struct { + Id uint `json:"id"` +} + +type MetaDto struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + Count int `json:"count"` +} + +type ResponseDto struct { + ecore.Main + Encounter_Id *uint `json:"encounter_id"` + Encounter *ee.Encounter `json:"encounter,omitempty"` + Doctor_Id *uint `json:"doctor_id"` + Doctor *ed.Doctor `json:"doctor,omitempty"` + Anamnesis *string `json:"anamnesis"` + MedicalDiagnoses *string `json:"medicalDiagnoses"` + FunctionDiagnoses *string `json:"functionDiagnoses"` + Procedures *string `json:"procedures"` + SupportingExams *string `json:"supportingExams"` + Instruction *string `json:"instruction"` + Evaluation *string `json:"evaluation"` + WorkCauseStatus *string `json:"workCauseStatus"` + Frequency *uint `json:"frequency"` + IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_code"` + Duration *uint `json:"duration"` + DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code"` +} + +func (d TherapyProtocol) ToResponse() ResponseDto { + resp := ResponseDto{ + Encounter_Id: d.Encounter_Id, + Encounter: d.Encounter, + Doctor_Id: d.Doctor_Id, + Doctor: d.Doctor, + Anamnesis: d.Anamnesis, + MedicalDiagnoses: d.MedicalDiagnoses, + FunctionDiagnoses: d.FunctionDiagnoses, + Procedures: d.Procedures, + SupportingExams: d.SupportingExams, + Instruction: d.Instruction, + Evaluation: d.Evaluation, + WorkCauseStatus: d.WorkCauseStatus, + Frequency: d.Frequency, + IntervalUnit_Code: d.IntervalUnit_Code, + Duration: d.Duration, + DurationUnit_Code: d.DurationUnit_Code, + } + resp.Main = d.Main + return resp +} + +func ToResponseList(data []TherapyProtocol) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/therapy-protocol/entity.go b/internal/domain/main-entities/therapy-protocol/entity.go index 99b5b59d..f5ec318a 100644 --- a/internal/domain/main-entities/therapy-protocol/entity.go +++ b/internal/domain/main-entities/therapy-protocol/entity.go @@ -25,7 +25,7 @@ type TherapyProtocol struct { Evaluation *string `json:"evaluation" gorm:"size:2048"` WorkCauseStatus *string `json:"workCauseStatus" gorm:"size:2048"` Frequency *uint `json:"frequency"` - IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_Code" gorm:"size:10"` + IntervalUnit_Code *common.TimeUnitCode `json:"intervalUnit_code" gorm:"size:10"` Duration *uint `json:"duration"` - DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_Code" gorm:"size:10"` + DurationUnit_Code *common.TimeUnitCode `json:"durationUnit_code" gorm:"size:10"` } diff --git a/internal/interface/main-handler/chemo-protocol/handler.go b/internal/interface/main-handler/chemo-protocol/handler.go new file mode 100644 index 00000000..60d9db4d --- /dev/null +++ b/internal/interface/main-handler/chemo-protocol/handler.go @@ -0,0 +1,72 @@ +package chemo_protocol + +import ( + "net/http" + + rw "github.com/karincake/risoles" + sf "github.com/karincake/semprit" + + // ua "github.com/karincake/tumpeng/auth/svc" + e "simrs-vx/internal/domain/main-entities/chemo-protocol" + u "simrs-vx/internal/use-case/main-use-case/chemo-protocol" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.CreateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + sf.UrlQueryParam(&dto, *r.URL) + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + dto := e.ReadDetailDto{} + sf.UrlQueryParam(&dto, *r.URL) + + dto.Id = uint16(id) + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.UpdateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Id = uint16(id) + res, err := u.Update(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.DeleteDto{} + dto.Id = uint16(id) + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/chemo/handler.go b/internal/interface/main-handler/chemo/handler.go index 90552020..18435479 100644 --- a/internal/interface/main-handler/chemo/handler.go +++ b/internal/interface/main-handler/chemo/handler.go @@ -84,11 +84,16 @@ func (obj myBase) Verify(w http.ResponseWriter, r *http.Request) { } dto := e.VerifyDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Id = uint16(id) authInfo, err := pa.GetAuthInfo(r) if err != nil { rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil) } + dto.AuthInfo = *authInfo dto.Status_Code = erc.DVCVerified res, err := u.Verify(dto) diff --git a/internal/interface/main-handler/encounter/handler.go b/internal/interface/main-handler/encounter/handler.go index ed8b1fbc..b66abe76 100644 --- a/internal/interface/main-handler/encounter/handler.go +++ b/internal/interface/main-handler/encounter/handler.go @@ -26,10 +26,15 @@ func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { if err != nil { rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil) } + dto := e.CreateDto{} if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { return } + if valid := validateRequestCreate(w, dto); !valid { + return + } + dto.AuthInfo = *authInfo res, err := u.Create(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 025298fc..b7d31e71 100644 --- a/internal/interface/main-handler/encounter/request-validation.go +++ b/internal/interface/main-handler/encounter/request-validation.go @@ -63,3 +63,21 @@ 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 + } + } + } + + return true +} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index baea92da..1c703b64 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -9,6 +9,7 @@ import ( ambulancetransportrequest "simrs-vx/internal/interface/main-handler/ambulance-transport-req" auth "simrs-vx/internal/interface/main-handler/authentication" chemo "simrs-vx/internal/interface/main-handler/chemo" + chemoprotocol "simrs-vx/internal/interface/main-handler/chemo-protocol" consultation "simrs-vx/internal/interface/main-handler/consultation" controlletter "simrs-vx/internal/interface/main-handler/control-letter" counter "simrs-vx/internal/interface/main-handler/counter" @@ -94,6 +95,7 @@ import ( specialistposition "simrs-vx/internal/interface/main-handler/specialist-position" subspecialist "simrs-vx/internal/interface/main-handler/subspecialist" subspecialistposition "simrs-vx/internal/interface/main-handler/subspecialist-position" + therapyprotocol "simrs-vx/internal/interface/main-handler/therapy-protocol" unit "simrs-vx/internal/interface/main-handler/unit" unitposition "simrs-vx/internal/interface/main-handler/unit-position" uom "simrs-vx/internal/interface/main-handler/uom" @@ -257,6 +259,8 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/ambulance-transport-req", ambulancetransportrequest.O) hc.RegCrud(r, "/v1/responsible-doctor-hist", responsibledoctorhist.O) hc.RegCrud(r, "/v1/adm-employee-hist", admemployeehist.O) + hc.RegCrud(r, "/v1/therapy-protocol", therapyprotocol.O) + hc.RegCrud(r, "/v1/chemo-protocol", chemoprotocol.O) /******************** actor ********************/ hc.RegCrud(r, "/v1/person", person.O) diff --git a/internal/interface/main-handler/therapy-protocol/handler.go b/internal/interface/main-handler/therapy-protocol/handler.go new file mode 100644 index 00000000..2d9efc3b --- /dev/null +++ b/internal/interface/main-handler/therapy-protocol/handler.go @@ -0,0 +1,73 @@ +package therapy_protocol + +import ( + "net/http" + + rw "github.com/karincake/risoles" + sf "github.com/karincake/semprit" + + // ua "github.com/karincake/tumpeng/auth/svc" + + e "simrs-vx/internal/domain/main-entities/therapy-protocol" + u "simrs-vx/internal/use-case/main-use-case/therapy-protocol" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.CreateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + sf.UrlQueryParam(&dto, *r.URL) + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + dto := e.ReadDetailDto{} + + sf.UrlQueryParam(&dto, *r.URL) + dto.Id = uint(id) + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.UpdateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Id = uint(id) + res, err := u.Update(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.DeleteDto{} + dto.Id = uint(id) + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/migration/main-entities.go b/internal/interface/migration/main-entities.go index fac22ea6..0b3a9970 100644 --- a/internal/interface/migration/main-entities.go +++ b/internal/interface/migration/main-entities.go @@ -7,6 +7,7 @@ import ( ambulatory "simrs-vx/internal/domain/main-entities/ambulatory" appointment "simrs-vx/internal/domain/main-entities/appointment" chemo "simrs-vx/internal/domain/main-entities/chemo" + chemoprotocol "simrs-vx/internal/domain/main-entities/chemo-protocol" consultation "simrs-vx/internal/domain/main-entities/consultation" controlletter "simrs-vx/internal/domain/main-entities/control-letter" counter "simrs-vx/internal/domain/main-entities/counter" @@ -197,5 +198,6 @@ func getMainEntities() []any { &vclaimmember.VclaimMember{}, &controlletter.ControlLetter{}, &rehab.Rehab{}, + &chemoprotocol.ChemoProtocol{}, } } diff --git a/internal/use-case/main-use-case/adm-employee-hist/lib.go b/internal/use-case/main-use-case/adm-employee-hist/lib.go index dc03b28a..6bdcbe22 100644 --- a/internal/use-case/main-use-case/adm-employee-hist/lib.go +++ b/internal/use-case/main-use-case/adm-employee-hist/lib.go @@ -75,29 +75,18 @@ func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e pl.SetLogInfo(event, input, "started", "DBReadDetail") data := e.AdmEmployeeHist{} - var tx, getData *gorm.DB + var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } - switch { - case input.Id != 0: - getData = tx.First(&data, input.Id) - case input.Code != nil && *input.Code != "": - getData = tx.Where("code = ?", *input.Code).First(&data) - default: - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "data-read-detail-fail", - Detail: "either Id or Code must be provided", - } + if err := tx. + Scopes(gh.Preload(input.Includes)). + First(&data, input.Id). + Error; err != nil { - return nil, pl.SetLogError(event, nil) - } - - if err := getData.Error; err != nil { if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { return nil, processedErr } diff --git a/internal/use-case/main-use-case/ambulatory/helper.go b/internal/use-case/main-use-case/ambulatory/helper.go index 912211c0..a8a00e0d 100644 --- a/internal/use-case/main-use-case/ambulatory/helper.go +++ b/internal/use-case/main-use-case/ambulatory/helper.go @@ -22,6 +22,7 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Ambulatory) { data.Encounter_Id = inputSrc.Encounter_Id data.Class_Code = inputSrc.Class_Code + data.VisitMode_Code = inputSrc.VisitMode_Code } func CheckClassCode(input *string) (ere.AmbulatoryClassCode, error) { diff --git a/internal/use-case/main-use-case/chemo-protocol/case.go b/internal/use-case/main-use-case/chemo-protocol/case.go new file mode 100644 index 00000000..4e007d28 --- /dev/null +++ b/internal/use-case/main-use-case/chemo-protocol/case.go @@ -0,0 +1,277 @@ +package chemo_protocol + +import ( + "strconv" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/chemo-protocol" +) + +const source = "chemo-protocol" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.ChemoProtocol{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.ChemoProtocol + var dataList []e.ChemoProtocol + var metaList *e.MetaDto + var err error + + event := pl.Event{ + Feature: "ReadList", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readList") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { + return err + } + + if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "list-data", + "status": "fetched", + "page_number": strconv.Itoa(metaList.PageNumber), + "page_size": strconv.Itoa(metaList.PageSize), + "record_totalCount": strconv.Itoa(metaList.Count), + "record_currentCount": strconv.Itoa(len(dataList)), + }, + Data: e.ToResponseList(dataList), + }, nil +} + +func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { + var data *e.ChemoProtocol + var err error + + event := pl.Event{ + Feature: "ReadDetail", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readDetail") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { + return err + } + + if data, err = ReadDetailData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "fetched", + }, + Data: data.ToResponse(), + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.ChemoProtocol + var err error + + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + 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 + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + 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 + +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.ChemoProtocol + var err error + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + 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 + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := DeleteData(data, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + Data: data.ToResponse(), + }, nil + +} diff --git a/internal/use-case/main-use-case/chemo-protocol/helper.go b/internal/use-case/main-use-case/chemo-protocol/helper.go new file mode 100644 index 00000000..885e6fbb --- /dev/null +++ b/internal/use-case/main-use-case/chemo-protocol/helper.go @@ -0,0 +1,28 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package chemo_protocol + +import ( + e "simrs-vx/internal/domain/main-entities/chemo-protocol" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.ChemoProtocol) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.Encounter_Id = inputSrc.Encounter_Id + data.Patient_Weight = inputSrc.Patient_Weight + data.Patient_Height = inputSrc.Patient_Height + data.Diagnoses = inputSrc.Diagnoses + data.Duration = inputSrc.Duration + data.DurationUnit_Code = inputSrc.DurationUnit_Code + data.StartDate = inputSrc.StartDate + data.EndDate = inputSrc.EndDate +} diff --git a/internal/use-case/main-use-case/chemo-protocol/lib.go b/internal/use-case/main-use-case/chemo-protocol/lib.go new file mode 100644 index 00000000..22e8808c --- /dev/null +++ b/internal/use-case/main-use-case/chemo-protocol/lib.go @@ -0,0 +1,147 @@ +package chemo_protocol + +import ( + "errors" + + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + + e "simrs-vx/internal/domain/main-entities/chemo-protocol" +) + +func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.ChemoProtocol, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.ChemoProtocol{} + 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.ChemoProtocol, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.ChemoProtocol{} + 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.ChemoProtocol{}). + 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 errors.Is(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.ChemoProtocol, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.ChemoProtocol{} + + 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.ChemoProtocol, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBUpdate") + setData(&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.ChemoProtocol, 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 +} diff --git a/internal/use-case/main-use-case/chemo-protocol/middleware-runner.go b/internal/use-case/main-use-case/chemo-protocol/middleware-runner.go new file mode 100644 index 00000000..7c932d7b --- /dev/null +++ b/internal/use-case/main-use-case/chemo-protocol/middleware-runner.go @@ -0,0 +1,103 @@ +package chemo_protocol + +import ( + e "simrs-vx/internal/domain/main-entities/chemo-protocol" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +type middlewareRunner struct { + Event *pl.Event + Tx *gorm.DB + MwType pu.MWType +} + +// NewMiddlewareExecutor creates a new middleware executor +func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { + return &middlewareRunner{ + Event: event, + Tx: tx, + } +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.ChemoProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.ChemoProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.ChemoProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.ChemoProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.ChemoProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) setMwType(mwType pu.MWType) { + me.MwType = mwType +} diff --git a/internal/use-case/main-use-case/chemo-protocol/middleware.go b/internal/use-case/main-use-case/chemo-protocol/middleware.go new file mode 100644 index 00000000..a438fd1c --- /dev/null +++ b/internal/use-case/main-use-case/chemo-protocol/middleware.go @@ -0,0 +1,9 @@ +package chemo_protocol + +// example of middleware +// func init() { +// createPreMw = append(createPreMw, +// CreateMw{Name: "modif-input", Func: pm.ModifInput}, +// CreateMw{Name: "check-data", Func: pm.CheckData}, +// ) +// } diff --git a/internal/use-case/main-use-case/chemo-protocol/tycovar.go b/internal/use-case/main-use-case/chemo-protocol/tycovar.go new file mode 100644 index 00000000..f4822996 --- /dev/null +++ b/internal/use-case/main-use-case/chemo-protocol/tycovar.go @@ -0,0 +1,44 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package chemo_protocol + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/chemo-protocol" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.ChemoProtocol, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.ChemoProtocol, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.ChemoProtocol, tx *gorm.DB) error +} + +type UpdateMw = readDetailMw +type DeleteMw = readDetailMw + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var updatePreMw []readDetailMw +var updatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw diff --git a/internal/use-case/main-use-case/chemo/case.go b/internal/use-case/main-use-case/chemo/case.go index 541faa04..f7b3c7d3 100644 --- a/internal/use-case/main-use-case/chemo/case.go +++ b/internal/use-case/main-use-case/chemo/case.go @@ -311,6 +311,8 @@ func Verify(input e.VerifyDto) (*d.Data, error) { data.VerifiedAt = pu.GetTimeNow() data.Status_Code = input.Status_Code data.VerifiedBy_User_Id = &input.AuthInfo.User_Id + data.Bed = input.Bed + data.Needs = input.Needs err = tx.Save(&data).Error if err != nil { diff --git a/internal/use-case/main-use-case/encounter/case.go b/internal/use-case/main-use-case/encounter/case.go index 829cf86c..ef70d9c2 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -25,6 +25,7 @@ import ( 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" erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist" es "simrs-vx/internal/domain/main-entities/soapi" @@ -36,7 +37,7 @@ import ( 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" - urdh "simrs-vx/internal/use-case/main-use-case/responsible-doctor-hist" + ur "simrs-vx/internal/use-case/main-use-case/rehab" us "simrs-vx/internal/use-case/main-use-case/soapi" ) @@ -46,6 +47,7 @@ var now = time.Now() func Create(input e.CreateDto) (*d.Data, error) { data := e.Encounter{} + createSoapi := []es.CreateDto{} event := pl.Event{ Feature: "Create", @@ -55,6 +57,39 @@ 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) + 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"), + } + return nil, pl.SetLogError(&event, input) + } + + // get data soapi + createSoapi, err = getSoapiEncounterAdm(dataEncounter, &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}, @@ -62,7 +97,6 @@ func Create(input e.CreateDto) (*d.Data, error) { if err != nil { return nil, err } - if list, ok := dataPatient.Data.([]e.ResponseDto); ok { if len(list) < 1 { input.NewStatus = true @@ -83,6 +117,7 @@ func Create(input e.CreateDto) (*d.Data, error) { input.Adm_Employee_Id = &emp.Id } + // create encounter if resData, err := CreateData(input, &event, tx); err != nil { return err } else { @@ -91,52 +126,57 @@ func Create(input e.CreateDto) (*d.Data, error) { switch input.Class_Code { case ere.ECAmbulatory: - subCode, err := ua.CheckClassCode(input.SubClass_Code) - if err != nil { - return err - } + subCodeAmbulatory := subCode.(ere.AmbulatoryClassCode) ambCreate := ea.CreateDto{ - Encounter_Id: &data.Id, - Class_Code: subCode, + Encounter_Id: &data.Id, + Class_Code: subCodeAmbulatory, + VisitMode_Code: *input.VisitMode_Code, } _, err = ua.CreateData(ambCreate, &event, tx) if err != nil { return err } - if subCode == ere.ACCChemo { + 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 } } - case ere.ECEmergency: - subCode, err := ue.CheckClassCode(input.SubClass_Code) - 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, + Doctor_Id: input.Appointment_Doctor_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, + Class_Code: subCode.(ere.EmergencyClassCode), } _, err = ue.CreateData(emerCreate, &event, tx) if err != nil { return err } case ere.ECInpatient: - subCode, err := ui.CheckClassCode(input.SubClass_Code) - if err != nil { - return err - } inpCreate := ei.CreateDto{ Encounter_Id: &data.Id, - Class_Code: subCode, + Class_Code: subCode.(ere.InpatientClassCode), Infra_Id: input.Infra_Id, } _, err = ui.CreateData(inpCreate, &event, tx) @@ -148,11 +188,10 @@ func Create(input e.CreateDto) (*d.Data, error) { } // insert adm_employee_hist - if _, err := uaeh.Create(eaeh.CreateDto{ + if _, err := uaeh.CreateData(eaeh.CreateDto{ Encounter_Id: &data.Main.Id, Employee_Id: data.Adm_Employee_Id, - StartedAt: &now, - }); err != nil { + StartedAt: &now}, &event, tx); err != nil { return err } @@ -402,7 +441,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { } func CheckOut(input e.DischargeDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id)} + rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Ambulatory"} var data *e.Encounter var err error @@ -430,8 +469,17 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { return pl.SetLogError(&event, input) } - if err := checkSoapiByDocExists(data.Id, &event, tx); err != nil { - return err + 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 { + return err + } + } else { + // chemo TBC + if err := checkSoapiByDocExists(data.Id, &event, tx); err != nil { + return err + } } if err := updateDischargeData(input, data, &event, tx); err != nil { @@ -557,7 +605,7 @@ func UpdateStatusCode(input e.UpdateStatusDto) (*d.Data, error) { } func CheckIn(input e.CheckinDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id)} + rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Rehab,Ambulatory"} var data *e.Encounter var err error @@ -579,24 +627,6 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { input.StartedAt = &now } - // validate if soapi exist - dataSoapi, err := us.ReadList(es.ReadListDto{FilterDto: es.FilterDto{Encounter_Id: &input.Id}}) - if err != nil { - return nil, err - } - - if list, ok := dataSoapi.Data.([]es.ResponseDto); ok { - if len(list) > 0 { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "data-state-mismatch", - Detail: "soapi already exist", - Raw: errors.New("soapi already exist"), - } - return nil, pl.SetLogError(&event, input) - } - } - err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { @@ -613,36 +643,34 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { return pl.SetLogError(&event, input) } - // Insert responsible_doctor_hist if responsible_doctor_id has changed && update latest history - if data.Responsible_Doctor_Id == nil || *input.Responsible_Doctor_Id != *data.Responsible_Doctor_Id { - // update finishedAt in latest responsible_doctor_hist - if err = updateLatestResponsibleDoctorHist(input, &event, tx); err != nil { + // 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") + if err != nil { return err } + } - //insert responsible_doctor_hist - if _, err = urdh.Create(erdh.CreateDto{ - Encounter_Id: &input.Id, + // Insert responsible_doctor_hist if responsible_doctor_id has changed && update latest history + if data.Responsible_Doctor_Id == nil || *input.Responsible_Doctor_Id != *data.Responsible_Doctor_Id { + // upsert responsibleDoctorHist + if err = upsertResponsibleDoctorHist(erdh.CreateDto{ + Encounter_Id: &data.Id, Doctor_Id: input.Responsible_Doctor_Id, StartedAt: input.StartedAt, - }); err != nil { + }, &event, tx); err != nil { return err } } // Insert adm_employee_hist if adm_employee_id has changed && update latest history if input.Adm_Employee_Id != nil && *input.Adm_Employee_Id != *data.Adm_Employee_Id { - // update finishedAt in latest adm_employee_hist - if err = updateLatestAdmEmployeeHist(input, &event, tx); err != nil { - return err - } - - // insert responsible_doctor_hist - if _, err = uaeh.Create(eaeh.CreateDto{ - Encounter_Id: &input.Id, + // upsert admEmployeeHist + if err = upsertAdmEmployeeHist(eaeh.CreateDto{ + Encounter_Id: &data.Id, Employee_Id: input.Adm_Employee_Id, StartedAt: input.StartedAt, - }); err != nil { + }, &event, tx); err != nil { return err } } @@ -652,6 +680,15 @@ 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, + Doctor_Id: input.Responsible_Doctor_Id, + }}, &event, tx); err != nil { + return err + } + } + pl.SetLogInfo(&event, nil, "complete") return nil @@ -688,3 +725,26 @@ func validateForeignKey(input e.CheckinDto) error { return 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") + } + return +} diff --git a/internal/use-case/main-use-case/encounter/helper.go b/internal/use-case/main-use-case/encounter/helper.go index c5eb155e..67cef58b 100644 --- a/internal/use-case/main-use-case/encounter/helper.go +++ b/internal/use-case/main-use-case/encounter/helper.go @@ -7,17 +7,25 @@ package encounter import ( "errors" "fmt" - uir "simrs-vx/internal/use-case/main-use-case/internal-reference" "strings" "time" + 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" + + eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist" + ea "simrs-vx/internal/domain/main-entities/ambulatory" edo "simrs-vx/internal/domain/main-entities/device-order" ed "simrs-vx/internal/domain/main-entities/doctor" + e "simrs-vx/internal/domain/main-entities/encounter" 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" @@ -26,20 +34,21 @@ import ( emmi "simrs-vx/internal/domain/main-entities/medicine-mix-item" 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" + erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist" 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" 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" - - e "simrs-vx/internal/domain/main-entities/encounter" - erc "simrs-vx/internal/domain/references/common" - erg "simrs-vx/internal/domain/references/organization" + urdh "simrs-vx/internal/use-case/main-use-case/responsible-doctor-hist" ) func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Encounter) { @@ -432,3 +441,371 @@ func getDoctors(doctorIds []uint, event *pl.Event, tx *gorm.DB) ([]ed.Doctor, er } return doctors, nil } + +func upsertResponsibleDoctorHist(input erdh.CreateDto, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + var latest erdh.ResponsibleDoctorHist + err := tx. + Where("\"Encounter_Id\" = ?", input.Encounter_Id). + Order("\"CreatedAt\" DESC"). + Limit(1). + First(&latest).Error + + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + // Insert + if _, err = urdh.CreateData(input, event, tx); err != nil { + return err + } + case err != nil: + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "read-fail", + Detail: "Failed to read responsible doctor history", + Raw: err, + } + return pl.SetLogError(event, input) + default: + // Update + if err := tx.Model(&latest).Updates(map[string]interface{}{ + "Doctor_Id": input.Doctor_Id, + "StartedAt": input.StartedAt, + "UpdatedAt": time.Now(), + }).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "update-fail", + Detail: "Failed to update responsible doctor history", + Raw: err, + } + return pl.SetLogError(event, input) + } + } + pl.SetLogInfo(event, input, "complete") + return nil +} + +func upsertAdmEmployeeHist(input eaeh.CreateDto, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + var latest eaeh.AdmEmployeeHist + err := tx. + Where("\"Encounter_Id\" = ?", input.Encounter_Id). + Order("\"CreatedAt\" DESC"). + Limit(1). + First(&latest).Error + + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + // Insert + if _, err = uaeh.CreateData(input, event, tx); err != nil { + return err + } + case err != nil: + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "read-fail", + Detail: "Failed to read responsible doctor history", + Raw: err, + } + return pl.SetLogError(event, input) + + default: + // Update + if err := tx.Model(&latest).Updates(map[string]interface{}{ + "Employee_Id": input.Employee_Id, + "StartedAt": input.StartedAt, + "UpdatedAt": time.Now(), + }).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "update-fail", + Detail: "Failed to update responsible doctor history", + Raw: err, + } + return pl.SetLogError(event, input) + } + } + pl.SetLogInfo(event, input, "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 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. + 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, + } + + 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, dataAmbulatory ea.Ambulatory, event *pl.Event, mode string) (err error) { + pl.SetLogInfo(event, encounterId, "started", "DBReadList") + + var ( + dataSoapi []es.Soapi + ) + + // 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\" = ?", erg.EPCDoc) + + // Set Case + switch { + case dataAmbulatory.Class_Code == ere.ACCReg: + tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", ercl.STCEEarlyMedic) + case dataAmbulatory.Class_Code == ere.ACCRehab && dataAmbulatory.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: + tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", ercl.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[ercl.SoapiTypeCode]bool) + for _, s := range dataSoapi { + typeExist[s.TypeCode] = true + } + + required := []ercl.SoapiTypeCode{} + + switch { + case dataAmbulatory.Class_Code == ere.ACCReg: + required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic} + case dataAmbulatory.Class_Code == ere.ACCRehab && dataAmbulatory.VisitMode_Code == ere.VMCAdm: + required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.STCFunc, ercl.STCEarlyRehab} + case dataAmbulatory.Class_Code == ere.ACCRehab && dataAmbulatory.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 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 +} diff --git a/internal/use-case/main-use-case/encounter/lib.go b/internal/use-case/main-use-case/encounter/lib.go index 3327bcfa..0c51a8d6 100644 --- a/internal/use-case/main-use-case/encounter/lib.go +++ b/internal/use-case/main-use-case/encounter/lib.go @@ -3,20 +3,17 @@ package encounter import ( // std "errors" + ere "simrs-vx/internal/domain/references/encounter" // 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" e "simrs-vx/internal/domain/main-entities/encounter" - erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist" ) func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.Encounter, error) { @@ -252,84 +249,48 @@ func updateCheckInData(input e.CheckinDto, data *e.Encounter, event *pl.Event, d return nil } -func updateLatestResponsibleDoctorHist(input e.CheckinDto, event *pl.Event, dbx ...*gorm.DB) error { - pl.SetLogInfo(event, "started", "DBUpdate") +func verifyAllocatedVisitCount(i e.CreateDto, event *pl.Event) (e.Encounter, bool, error) { + pl.SetLogInfo(event, nil, "started", "DBGetRecentEncounterAdm") - var tx *gorm.DB - if len(dbx) > 0 { - tx = dbx[0] - } else { - tx = dg.I - } + var ( + tx = dg.I + recentEncounterAdm e.Encounter + countEncounterSeries int64 + ) - subQuery := tx. - Select("\"Id\""). - Model(&erdh.ResponsibleDoctorHist{}). - Where("\"Encounter_Id\" = ?", input.Id). + 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). Order("\"CreatedAt\" DESC"). - Limit(1) - - result := tx. - Model(&erdh.ResponsibleDoctorHist{}). - Where("\"Id\" = (?)", subQuery). - Update("\"FinishedAt\"", input.StartedAt) - - if result.Error != nil { + First(&recentEncounterAdm).Error + if err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ - Code: "data-update-fail", - Detail: "Database update failed", - Raw: result.Error, + Code: "read-recentEncounter-fail", + Detail: "Database read failed", + Raw: err, } - return pl.SetLogError(event, input) + return e.Encounter{}, false, pl.SetLogError(event, i) } - 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 { + 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: "data-update-fail", - Detail: "Database update failed", - Raw: result.Error, + Code: "read-countEncounter-fail", + Detail: "Database read failed", + Raw: err, } - return pl.SetLogError(event, input) + return e.Encounter{}, false, pl.SetLogError(event, i) } - if result.RowsAffected == 0 { - pl.SetLogInfo(event, input, "no previous data found to update") - return nil - } - - pl.SetLogInfo(event, nil, "complete") - return nil + return recentEncounterAdm, countEncounterSeries < int64(*recentEncounterAdm.Rehab.AllocatedVisitCount), nil } diff --git a/internal/use-case/main-use-case/rehab/case.go b/internal/use-case/main-use-case/rehab/case.go new file mode 100644 index 00000000..0d7a243e --- /dev/null +++ b/internal/use-case/main-use-case/rehab/case.go @@ -0,0 +1,275 @@ +package rehab + +import ( + e "simrs-vx/internal/domain/main-entities/rehab" + "strconv" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + "gorm.io/gorm" +) + +const source = "rehab" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.Rehab{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.Rehab + var dataList []e.Rehab + var metaList *e.MetaDto + var err error + + event := pl.Event{ + Feature: "ReadList", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readList") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { + return err + } + + if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "list-data", + "status": "fetched", + "page_number": strconv.Itoa(metaList.PageNumber), + "page_size": strconv.Itoa(metaList.PageSize), + "record_totalCount": strconv.Itoa(metaList.Count), + "record_currentCount": strconv.Itoa(len(dataList)), + }, + Data: e.ToResponseList(dataList), + }, nil +} + +func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { + var data *e.Rehab + var err error + + event := pl.Event{ + Feature: "ReadDetail", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readDetail") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { + return err + } + + if data, err = ReadDetailData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "fetched", + }, + Data: data.ToResponse(), + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.Rehab + var err error + + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + 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 + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + 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 + +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.Rehab + var err error + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + 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 + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := DeleteData(data, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + Data: data.ToResponse(), + }, nil + +} diff --git a/internal/use-case/main-use-case/rehab/helper.go b/internal/use-case/main-use-case/rehab/helper.go new file mode 100644 index 00000000..5c06ca0a --- /dev/null +++ b/internal/use-case/main-use-case/rehab/helper.go @@ -0,0 +1,23 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package rehab + +import ( + e "simrs-vx/internal/domain/main-entities/rehab" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Rehab) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.Encounter_Id = inputSrc.Encounter_Id + data.Doctor_Id = inputSrc.Doctor_Id + data.AllocatedVisitCount = inputSrc.AllocatedVisitCount +} diff --git a/internal/use-case/main-use-case/rehab/lib.go b/internal/use-case/main-use-case/rehab/lib.go new file mode 100644 index 00000000..faee1b35 --- /dev/null +++ b/internal/use-case/main-use-case/rehab/lib.go @@ -0,0 +1,144 @@ +package rehab + +import ( + "errors" + e "simrs-vx/internal/domain/main-entities/rehab" + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + "gorm.io/gorm" +) + +func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.Rehab, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.Rehab{} + 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.Rehab, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.Rehab{} + 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.Rehab{}). + 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 errors.Is(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.Rehab, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.Rehab{} + + 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.Rehab, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBUpdate") + setData(&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.Rehab, 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 +} diff --git a/internal/use-case/main-use-case/rehab/middleware-runner.go b/internal/use-case/main-use-case/rehab/middleware-runner.go new file mode 100644 index 00000000..8c0af77a --- /dev/null +++ b/internal/use-case/main-use-case/rehab/middleware-runner.go @@ -0,0 +1,103 @@ +package rehab + +import ( + e "simrs-vx/internal/domain/main-entities/rehab" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +type middlewareRunner struct { + Event *pl.Event + Tx *gorm.DB + MwType pu.MWType +} + +// NewMiddlewareExecutor creates a new middleware executor +func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { + return &middlewareRunner{ + Event: event, + Tx: tx, + } +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.Rehab) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.Rehab) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.Rehab) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.Rehab) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.Rehab) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) setMwType(mwType pu.MWType) { + me.MwType = mwType +} diff --git a/internal/use-case/main-use-case/rehab/middleware.go b/internal/use-case/main-use-case/rehab/middleware.go new file mode 100644 index 00000000..dadf6b9b --- /dev/null +++ b/internal/use-case/main-use-case/rehab/middleware.go @@ -0,0 +1,9 @@ +package rehab + +// example of middleware +// func init() { +// createPreMw = append(createPreMw, +// CreateMw{Name: "modif-input", Func: pm.ModifInput}, +// CreateMw{Name: "check-data", Func: pm.CheckData}, +// ) +// } diff --git a/internal/use-case/main-use-case/rehab/tycovar.go b/internal/use-case/main-use-case/rehab/tycovar.go new file mode 100644 index 00000000..4899ec44 --- /dev/null +++ b/internal/use-case/main-use-case/rehab/tycovar.go @@ -0,0 +1,44 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package rehab + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/rehab" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.Rehab, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.Rehab, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.Rehab, tx *gorm.DB) error +} + +type UpdateMw = readDetailMw +type DeleteMw = readDetailMw + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var updatePreMw []readDetailMw +var updatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw diff --git a/internal/use-case/main-use-case/responsible-doctor-hist/lib.go b/internal/use-case/main-use-case/responsible-doctor-hist/lib.go index d91294fe..3f035d5e 100644 --- a/internal/use-case/main-use-case/responsible-doctor-hist/lib.go +++ b/internal/use-case/main-use-case/responsible-doctor-hist/lib.go @@ -75,29 +75,18 @@ func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e pl.SetLogInfo(event, input, "started", "DBReadDetail") data := e.ResponsibleDoctorHist{} - var tx, getData *gorm.DB + var tx *gorm.DB if len(dbx) > 0 { tx = dbx[0] } else { tx = dg.I } - switch { - case input.Id != 0: - getData = tx.First(&data, input.Id) - case input.Code != nil && *input.Code != "": - getData = tx.Where("code = ?", *input.Code).First(&data) - default: - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "data-read-detail-fail", - Detail: "either Id or Code must be provided", - } + if err := tx. + Scopes(gh.Preload(input.Includes)). + First(&data, input.Id). + Error; err != nil { - return nil, pl.SetLogError(event, nil) - } - - if err := getData.Error; err != nil { if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { return nil, processedErr } diff --git a/internal/use-case/main-use-case/soapi/helper.go b/internal/use-case/main-use-case/soapi/helper.go index 5a94848a..ae396270 100644 --- a/internal/use-case/main-use-case/soapi/helper.go +++ b/internal/use-case/main-use-case/soapi/helper.go @@ -23,3 +23,19 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Soapi) { data.TypeCode = inputSrc.TypeCode data.Value = inputSrc.Value } + +func setBulkData(input []e.CreateDto, encounterId uint) []e.Soapi { + var data []e.Soapi + + for _, v := range input { + data = append(data, e.Soapi{ + Encounter_Id: &encounterId, + Employee_Id: v.Employee_Id, + Time: v.Time, + TypeCode: v.TypeCode, + Value: v.Value, + }) + } + + return data +} diff --git a/internal/use-case/main-use-case/soapi/lib.go b/internal/use-case/main-use-case/soapi/lib.go index 7e551505..57bb5d23 100644 --- a/internal/use-case/main-use-case/soapi/lib.go +++ b/internal/use-case/main-use-case/soapi/lib.go @@ -138,3 +138,23 @@ func DeleteData(data *e.Soapi, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, nil, "complete") return nil } + +func CreateBulkData(input []e.CreateDto, encounterId uint, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := setBulkData(input, encounterId) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Create(&data).Error; err != nil { + return plh.HandleCreateError(input, event, err) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} diff --git a/internal/use-case/main-use-case/therapy-protocol/case.go b/internal/use-case/main-use-case/therapy-protocol/case.go new file mode 100644 index 00000000..93cf05a4 --- /dev/null +++ b/internal/use-case/main-use-case/therapy-protocol/case.go @@ -0,0 +1,310 @@ +package therapy_protocol + +import ( + "strconv" + + "gorm.io/gorm" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/encounter" + e "simrs-vx/internal/domain/main-entities/therapy-protocol" + + ud "simrs-vx/internal/use-case/main-use-case/doctor" + ue "simrs-vx/internal/use-case/main-use-case/encounter" +) + +const source = "therapy-protocol" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.TherapyProtocol{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + // validate foreign key + if err := validateForeignKey(input); err != nil { + return nil, err + } + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.TherapyProtocol + var dataList []e.TherapyProtocol + var metaList *e.MetaDto + var err error + + event := pl.Event{ + Feature: "ReadList", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readList") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { + return err + } + + if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "list-data", + "status": "fetched", + "page_number": strconv.Itoa(metaList.PageNumber), + "page_size": strconv.Itoa(metaList.PageSize), + "record_totalCount": strconv.Itoa(metaList.Count), + "record_currentCount": strconv.Itoa(len(dataList)), + }, + Data: e.ToResponseList(dataList), + }, nil +} + +func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { + var data *e.TherapyProtocol + var err error + + event := pl.Event{ + Feature: "ReadDetail", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "readDetail") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { + return err + } + + if data, err = ReadDetailData(input, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "fetched", + }, + Data: data.ToResponse(), + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.TherapyProtocol + var err error + + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + // validate foreign key + if err := validateForeignKey(input.CreateDto); 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 + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + 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 + +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.TherapyProtocol + var err error + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + 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 + } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + if err := DeleteData(data, &event, tx); err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + Data: data.ToResponse(), + }, nil + +} + +func validateForeignKey(input e.CreateDto) error { + // validate encounter + if input.Encounter_Id != nil { + if _, err := ue.ReadDetail(ee.ReadDetailDto{Id: uint16(*input.Encounter_Id)}); err != nil { + return err + } + } + + // validate doctor_id + if input.Doctor_Id != nil { + if _, err := ud.ReadDetail(ed.ReadDetailDto{Id: uint16(*input.Doctor_Id)}); err != nil { + return err + } + } + + return nil +} diff --git a/internal/use-case/main-use-case/therapy-protocol/helper.go b/internal/use-case/main-use-case/therapy-protocol/helper.go new file mode 100644 index 00000000..12e48747 --- /dev/null +++ b/internal/use-case/main-use-case/therapy-protocol/helper.go @@ -0,0 +1,34 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package therapy_protocol + +import ( + e "simrs-vx/internal/domain/main-entities/therapy-protocol" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.TherapyProtocol) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.Encounter_Id = inputSrc.Encounter_Id + data.Doctor_Id = inputSrc.Doctor_Id + data.Anamnesis = inputSrc.Anamnesis + data.MedicalDiagnoses = inputSrc.MedicalDiagnoses + data.FunctionDiagnoses = inputSrc.FunctionDiagnoses + data.Procedures = inputSrc.Procedures + data.SupportingExams = inputSrc.SupportingExams + data.Instruction = inputSrc.Instruction + data.Evaluation = inputSrc.Evaluation + data.WorkCauseStatus = inputSrc.WorkCauseStatus + data.Frequency = inputSrc.Frequency + data.IntervalUnit_Code = inputSrc.IntervalUnit_Code + data.Duration = inputSrc.Duration + data.DurationUnit_Code = inputSrc.DurationUnit_Code +} diff --git a/internal/use-case/main-use-case/therapy-protocol/lib.go b/internal/use-case/main-use-case/therapy-protocol/lib.go new file mode 100644 index 00000000..550ef327 --- /dev/null +++ b/internal/use-case/main-use-case/therapy-protocol/lib.go @@ -0,0 +1,145 @@ +package therapy_protocol + +import ( + "errors" + e "simrs-vx/internal/domain/main-entities/therapy-protocol" + + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + "gorm.io/gorm" +) + +func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.TherapyProtocol, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.TherapyProtocol{} + 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.TherapyProtocol, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.TherapyProtocol{} + 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.TherapyProtocol{}). + 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 errors.Is(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.TherapyProtocol, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.TherapyProtocol{} + + 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.TherapyProtocol, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBUpdate") + setData(&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.TherapyProtocol, 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 +} diff --git a/internal/use-case/main-use-case/therapy-protocol/middleware-runner.go b/internal/use-case/main-use-case/therapy-protocol/middleware-runner.go new file mode 100644 index 00000000..4391b63a --- /dev/null +++ b/internal/use-case/main-use-case/therapy-protocol/middleware-runner.go @@ -0,0 +1,103 @@ +package therapy_protocol + +import ( + e "simrs-vx/internal/domain/main-entities/therapy-protocol" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +type middlewareRunner struct { + Event *pl.Event + Tx *gorm.DB + MwType pu.MWType +} + +// NewMiddlewareExecutor creates a new middleware executor +func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { + return &middlewareRunner{ + Event: event, + Tx: tx, + } +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.TherapyProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.TherapyProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.TherapyProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.TherapyProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.TherapyProtocol) error { + for _, middleware := range middlewares { + logData := pu.GetLogData(input, data) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input, data, me.Tx); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +func (me *middlewareRunner) setMwType(mwType pu.MWType) { + me.MwType = mwType +} diff --git a/internal/use-case/main-use-case/therapy-protocol/middleware.go b/internal/use-case/main-use-case/therapy-protocol/middleware.go new file mode 100644 index 00000000..a2f3626f --- /dev/null +++ b/internal/use-case/main-use-case/therapy-protocol/middleware.go @@ -0,0 +1,9 @@ +package therapy_protocol + +// example of middleware +// func init() { +// createPreMw = append(createPreMw, +// CreateMw{Name: "modif-input", Func: pm.ModifInput}, +// CreateMw{Name: "check-data", Func: pm.CheckData}, +// ) +// } diff --git a/internal/use-case/main-use-case/therapy-protocol/tycovar.go b/internal/use-case/main-use-case/therapy-protocol/tycovar.go new file mode 100644 index 00000000..a027e334 --- /dev/null +++ b/internal/use-case/main-use-case/therapy-protocol/tycovar.go @@ -0,0 +1,44 @@ +/* +DESCRIPTION: +A sample, part of the package that contains type, constants, and/or variables. + +In this sample it also provides type and variable regarding the needs of the +middleware to separate from main use-case which has the basic CRUD +functionality. The purpose of this is to make the code more maintainable. +*/ +package therapy_protocol + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/therapy-protocol" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.TherapyProtocol, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.TherapyProtocol, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.TherapyProtocol, tx *gorm.DB) error +} + +type UpdateMw = readDetailMw +type DeleteMw = readDetailMw + +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw +var updatePreMw []readDetailMw +var updatePostMw []readDetailMw +var deletePreMw []readDetailMw +var deletePostMw []readDetailMw