From e4358034d94f5613df3075370bc41012d5801ef8 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 9 Sep 2025 13:41:06 +0700 Subject: [PATCH] feat (encounter): create and checkout + checking soapi done --- .../domain/main-entities/encounter/dto.go | 1 - internal/domain/main-entities/soapi/dto.go | 6 ++- .../{authentication.go => handler.go} | 16 ++---- .../interface/main-handler/main-handler.go | 4 +- .../interface/main-handler/soapi/handler.go | 9 ++++ .../main-use-case/ambulatory/helper.go | 16 ++++++ .../main-use-case/authentication/case.go | 27 +++++----- .../main-use-case/authentication/tycovar.go | 9 ---- .../main-use-case/emergency/helper.go | 16 ++++++ .../use-case/main-use-case/encounter/case.go | 43 +++++++-------- .../main-use-case/encounter/helper.go | 44 ++++++++++++---- .../main-use-case/inpatient/helper.go | 16 ++++++ internal/use-case/main-use-case/soapi/case.go | 22 +++++++- pkg/auth-helper/auth-helper.go | 15 ++++++ pkg/auth-helper/tycovar.go | 52 +++++++++++++++++++ 15 files changed, 225 insertions(+), 71 deletions(-) rename internal/interface/main-handler/authentication/{authentication.go => handler.go} (83%) create mode 100644 pkg/auth-helper/auth-helper.go create mode 100644 pkg/auth-helper/tycovar.go diff --git a/internal/domain/main-entities/encounter/dto.go b/internal/domain/main-entities/encounter/dto.go index d4d02929..32472324 100644 --- a/internal/domain/main-entities/encounter/dto.go +++ b/internal/domain/main-entities/encounter/dto.go @@ -14,7 +14,6 @@ import ( type CreateDto struct { Patient_Id *uint `json:"patient_id"` - Patient *ep.Patient `json:"patient,omitempty"` RegisteredAt *time.Time `json:"registeredAt"` Class_Code ere.EncounterClassCode `json:"class_code" validate:"maxLength=10"` SubClass_Code *string `json:"subClass_code" validate:"maxLength=10"` // for sub diff --git a/internal/domain/main-entities/soapi/dto.go b/internal/domain/main-entities/soapi/dto.go index d6282b00..6aac522e 100644 --- a/internal/domain/main-entities/soapi/dto.go +++ b/internal/domain/main-entities/soapi/dto.go @@ -4,14 +4,18 @@ import ( ecore "simrs-vx/internal/domain/base-entities/core" eem "simrs-vx/internal/domain/main-entities/employee" ee "simrs-vx/internal/domain/main-entities/encounter" + + pa "simrs-vx/pkg/auth-helper" "time" ) type CreateDto struct { Encounter_Id *uint `json:"encounter_id"` - Employee_Id *uint `json:"employee_id"` + Employee_Id *uint `json:"-"` Time *time.Time `json:"time"` Value *string `json:"value"` + + pa.AuthInfo } type ReadListDto struct { diff --git a/internal/interface/main-handler/authentication/authentication.go b/internal/interface/main-handler/authentication/handler.go similarity index 83% rename from internal/interface/main-handler/authentication/authentication.go rename to internal/interface/main-handler/authentication/handler.go index 37b84346..01d4fb89 100644 --- a/internal/interface/main-handler/authentication/authentication.go +++ b/internal/interface/main-handler/authentication/handler.go @@ -9,16 +9,10 @@ import ( m "simrs-vx/internal/domain/main-entities/user" s "simrs-vx/internal/use-case/main-use-case/authentication" + + pa "simrs-vx/pkg/auth-helper" ) -type authKey string - -const akInfo authKey = "authInfo" - -type AuthKey struct{} - -// var Position m.Position - func Login(w http.ResponseWriter, r *http.Request) { var input m.LoginDto if !(rw.ValidateStructByIOR(w, r.Body, &input)) { @@ -35,12 +29,12 @@ func Login(w http.ResponseWriter, r *http.Request) { } func Logout(w http.ResponseWriter, r *http.Request) { - ctxVal := r.Context().Value(AuthKey{}) + ctxVal := r.Context().Value(pa.AuthKey{}) if ctxVal == nil { rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "logout skiped. the request is done wihtout authorization."}, nil) return } - authInfo := ctxVal.(*s.AuthInfo) + authInfo := ctxVal.(*pa.AuthInfo) s.RevokeToken(authInfo.Uuid) rw.WriteJSON(w, http.StatusOK, d.IS{"message": "logged out"}, nil) } @@ -52,7 +46,7 @@ func GuardMW(next http.Handler) http.Handler { rw.WriteJSON(w, http.StatusUnauthorized, err.(d.FieldError), nil) return } - ctx := context.WithValue(r.Context(), AuthKey{}, accessDetail) + ctx := context.WithValue(r.Context(), pa.AuthKey{}, accessDetail) next.ServeHTTP(w, r.WithContext(ctx)) }) } diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index 67fdb67a..553f2ac0 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -125,10 +125,10 @@ func SetRoutes() http.Handler { "POST /": encounter.O.Create, "PATCH /{id}": encounter.O.Update, "DELETE /{id}": encounter.O.Delete, - "PATCH /{id}/checkOut": encounter.O.CheckOut, + "PATCH /{id}/checkout": encounter.O.CheckOut, }) - hc.RegCrud(r, "/v1/soapi", soapi.O) + hc.RegCrud(r, "/v1/soapi", auth.GuardMW, soapi.O) hc.RegCrud(r, "/v1/adime", adime.O) hc.RegCrud(r, "/v1/sbar", sbar.O) hc.RegCrud(r, "/v1/person", person.O) diff --git a/internal/interface/main-handler/soapi/handler.go b/internal/interface/main-handler/soapi/handler.go index a7b59bba..f5446b82 100644 --- a/internal/interface/main-handler/soapi/handler.go +++ b/internal/interface/main-handler/soapi/handler.go @@ -10,6 +10,10 @@ import ( e "simrs-vx/internal/domain/main-entities/soapi" u "simrs-vx/internal/use-case/main-use-case/soapi" + + pa "simrs-vx/pkg/auth-helper" + + d "github.com/karincake/dodol" ) type myBase struct{} @@ -17,10 +21,15 @@ type myBase struct{} var O myBase func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + authInfo, err := pa.GetAuthInfo(r) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil) + } dto := e.CreateDto{} if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { return } + dto.AuthInfo = *authInfo res, err := u.Create(dto) rw.DataResponse(w, res, err) } diff --git a/internal/use-case/main-use-case/ambulatory/helper.go b/internal/use-case/main-use-case/ambulatory/helper.go index 497f0a0b..39361d9d 100644 --- a/internal/use-case/main-use-case/ambulatory/helper.go +++ b/internal/use-case/main-use-case/ambulatory/helper.go @@ -5,7 +5,10 @@ Any functions that are used internally by the use-case package ambulatory import ( + "errors" e "simrs-vx/internal/domain/main-entities/ambulatory" + + ere "simrs-vx/internal/domain/references/encounter" ) func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Ambulatory) { @@ -20,3 +23,16 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Ambulatory) { data.Encounter_Id = inputSrc.Encounter_Id data.Class_Code = inputSrc.Class_Code } + +func CheckClassCode(input *string) (ere.AmbulatoryClassCode, error) { + if input != nil { + subCode := ere.AmbulatoryClassCode(*input) + switch subCode { + case ere.ACCReg, ere.ACCRme: + return subCode, nil + default: + return "", errors.New("unknown sub class code") + } + } + return "", errors.New("sub class code is nil") +} diff --git a/internal/use-case/main-use-case/authentication/case.go b/internal/use-case/main-use-case/authentication/case.go index 70925827..0c1f4884 100644 --- a/internal/use-case/main-use-case/authentication/case.go +++ b/internal/use-case/main-use-case/authentication/case.go @@ -16,10 +16,11 @@ import ( a "github.com/karincake/apem" ms "github.com/karincake/apem/ms-redis" + pa "simrs-vx/pkg/auth-helper" el "simrs-vx/pkg/logger" p "simrs-vx/pkg/password" - mu "simrs-vx/internal/domain/main-entities/user" + eu "simrs-vx/internal/domain/main-entities/user" erc "simrs-vx/internal/domain/references/common" ) @@ -31,9 +32,9 @@ func init() { // Generates token and store in redis at one place // just return the error code -func GenToken(input mu.LoginDto) (*d.Data, error) { +func GenToken(input eu.LoginDto) (*d.Data, error) { // Get User - user := &mu.User{Name: input.Name} + user := &eu.User{Name: input.Name} // if input.Position_Code != "" { // user.Position_Code = input.Position_Code // } @@ -94,7 +95,7 @@ func GenToken(input mu.LoginDto) (*d.Data, error) { atClaims["user_id"] = user.Id atClaims["user_name"] = user.Name // atClaims["user_email"] = user.Email - // atClaims["user_position_code"] = user.Position_Code + atClaims["user_position_code"] = user.Position_Code // atClaims["user_ref_id"] = user.Ref_Id atClaims["exp"] = atExpires atClaims["uuid"] = aUuid @@ -129,7 +130,7 @@ func GenToken(input mu.LoginDto) (*d.Data, error) { "user_id": strconv.Itoa(int(user.Id)), "user_name": user.Name, // "user_email": user.Email, - // "user_position_code": user.Position_Code, + "user_position_code": user.Position_Code, // "user_ref_id": user.Ref_Id, "accessToken": ats, }, @@ -167,7 +168,7 @@ func VerifyToken(r *http.Request, tokenType TokenType) (data *jwt.Token, errCode return token, "", "" } -func ExtractToken(r *http.Request, tokenType TokenType) (data *AuthInfo, err error) { +func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err error) { token, errCode, errDetail := VerifyToken(r, tokenType) if errCode != "" { return nil, d.FieldError{Code: errCode, Message: el.GenMessage(errCode, errDetail)} @@ -196,17 +197,17 @@ func ExtractToken(r *http.Request, tokenType TokenType) (data *AuthInfo, err err // tmp := v.(float64) // ref_id = int(tmp) // } - // position_code := "" - // if v, exist := claims["user_position_code"]; exist && v != nil { - // position_code = v.(string) - // } - data = &AuthInfo{ + position_code := "" + if v, exist := claims["user_position_code"]; exist && v != nil { + position_code = v.(string) + } + data = &pa.AuthInfo{ Uuid: accessUuid, - User_Id: int(user_id), + User_Id: uint(user_id), User_Name: user_name, // User_Email: user_email, // User_Ref_Id: ref_id, - // User_Position_Code: position_code, + User_Position_Code: position_code, } return } diff --git a/internal/use-case/main-use-case/authentication/tycovar.go b/internal/use-case/main-use-case/authentication/tycovar.go index d4d5b5ea..c1b11420 100644 --- a/internal/use-case/main-use-case/authentication/tycovar.go +++ b/internal/use-case/main-use-case/authentication/tycovar.go @@ -5,15 +5,6 @@ type TokenType string const AccessToken = "Access" const RefreshToken = "Refresh" -type AuthInfo struct { - Uuid string - User_Id int - User_Name string - // User_Email string - // User_Ref_Id int - // User_Position_Code string -} - type AuthCfg struct { AtSecretKey string `yaml:"atSecretKey"` RtSecretKey string `yaml:"rtSecretKey"` diff --git a/internal/use-case/main-use-case/emergency/helper.go b/internal/use-case/main-use-case/emergency/helper.go index b3564c4e..00cde658 100644 --- a/internal/use-case/main-use-case/emergency/helper.go +++ b/internal/use-case/main-use-case/emergency/helper.go @@ -5,7 +5,10 @@ Any functions that are used internally by the use-case package emergency import ( + "errors" e "simrs-vx/internal/domain/main-entities/emergency" + + ere "simrs-vx/internal/domain/references/encounter" ) func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Emergency) { @@ -20,3 +23,16 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Emergency) { data.Encounter_Id = inputSrc.Encounter_Id data.Class_Code = inputSrc.Class_Code } + +func CheckClassCode(input *string) (ere.EmergencyClassCode, error) { + if input != nil { + subCode := ere.EmergencyClassCode(*input) + switch subCode { + case ere.ECCEmg, ere.ECCEon: + return subCode, nil + default: + return "", errors.New("unknown sub class code") + } + } + return "", errors.New("sub class code is nil") +} diff --git a/internal/use-case/main-use-case/encounter/case.go b/internal/use-case/main-use-case/encounter/case.go index cfb70c81..0101f38d 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -53,45 +53,42 @@ 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 + } ambCreate := ea.CreateDto{ Encounter_Id: &data.Id, - Class_Code: func() ere.AmbulatoryClassCode { - if input.SubClass_Code != nil { - return ere.AmbulatoryClassCode(*input.SubClass_Code) - } - return "" - }(), + Class_Code: subCode, } - _, err := ua.CreateData(ambCreate, &event, tx) + _, err = ua.CreateData(ambCreate, &event, tx) if err != nil { return err } case ere.ECEmergency: + subCode, err := ue.CheckClassCode(input.SubClass_Code) + if err != nil { + return err + } emerCreate := ee.CreateDto{ Encounter_Id: &data.Id, - Class_Code: func() ere.EmergencyClassCode { - if input.SubClass_Code != nil { - return ere.EmergencyClassCode(*input.SubClass_Code) - } - return "" - }(), + Class_Code: subCode, } - _, err := ue.CreateData(emerCreate, &event, tx) + _, 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: func() ere.InpatientClassCode { - if input.SubClass_Code != nil { - return ere.InpatientClassCode(*input.SubClass_Code) - } - return "" - }(), - Infra_Id: input.Infra_Id, + Class_Code: subCode, + Infra_Id: input.Infra_Id, } - _, err := ui.CreateData(inpCreate, &event, tx) + _, err = ui.CreateData(inpCreate, &event, tx) if err != nil { return err } @@ -355,7 +352,7 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { return err } - if err := checkSoapiByDocExists(data.Id, tx); err != nil { + if err := checkSoapiByDocExists(data.Id, &event, tx); err != nil { return err } diff --git a/internal/use-case/main-use-case/encounter/helper.go b/internal/use-case/main-use-case/encounter/helper.go index 5f8e33a9..3e7e4b30 100644 --- a/internal/use-case/main-use-case/encounter/helper.go +++ b/internal/use-case/main-use-case/encounter/helper.go @@ -8,6 +8,7 @@ import ( "errors" e "simrs-vx/internal/domain/main-entities/encounter" es "simrs-vx/internal/domain/main-entities/soapi" + pl "simrs-vx/pkg/logger" ero "simrs-vx/internal/domain/references/organization" @@ -24,6 +25,7 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Encounter) { } data.Patient_Id = inputSrc.Patient_Id + data.RegisteredAt = inputSrc.RegisteredAt data.Class_Code = inputSrc.Class_Code data.Unit_Id = inputSrc.Unit_Id data.Specialist_Id = inputSrc.Specialist_Id @@ -43,22 +45,44 @@ func setDataDischarge(src e.DischargeDto, dst *e.Encounter) { dst.DischargeReason = src.DischargeReason } -func checkSoapiByDocExists(encounter_id uint, tx *gorm.DB) error { - soapi := es.Soapi{} +func checkSoapiByDocExists(encounter_id uint, event *pl.Event, tx *gorm.DB) error { + pl.SetLogInfo(event, nil, "started", "checkSoapiByDocExists") + var soapies []es.Soapi err := tx. Preload("Employee"). Preload("Employee.User"). - Where("\"Encounter_Id\" = ?", encounter_id).First(&soapi).Error + Where("\"Encounter_Id\" = ?", encounter_id).Find(&soapies).Error if err != nil { - return err + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-get-fail", + Detail: "get soapi failed", + Raw: err, + } + return pl.SetLogError(event, nil) } - if soapi.Employee == nil || soapi.Employee.User == nil { - return errors.New("employee not found") - } - if soapi.Employee.User.Position_Code != ero.UPCDoc { - return errors.New("employee is not a doctor") + if len(soapies) == 0 { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-notFound", + Detail: "no soapi found for encounter", + Raw: errors.New("soapi not found"), + } + return pl.SetLogError(event, nil) } - return nil + for _, s := range soapies { + if s.Employee != nil && s.Employee.User != nil && s.Employee.User.Position_Code == ero.UPCDoc { + return nil + } + } + + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-update-fail", + Detail: "no soapi written by a doctor found", + Raw: errors.New("all soapi employees are not doctors"), + } + return pl.SetLogError(event, nil) } diff --git a/internal/use-case/main-use-case/inpatient/helper.go b/internal/use-case/main-use-case/inpatient/helper.go index 75f38c7e..45014af1 100644 --- a/internal/use-case/main-use-case/inpatient/helper.go +++ b/internal/use-case/main-use-case/inpatient/helper.go @@ -5,7 +5,10 @@ Any functions that are used internally by the use-case package inpatient import ( + "errors" e "simrs-vx/internal/domain/main-entities/inpatient" + + ere "simrs-vx/internal/domain/references/encounter" ) func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Inpatient) { @@ -20,3 +23,16 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Inpatient) { data.Encounter_Id = inputSrc.Encounter_Id data.Class_Code = inputSrc.Class_Code } + +func CheckClassCode(input *string) (ere.InpatientClassCode, error) { + if input != nil { + subCode := ere.InpatientClassCode(*input) + switch subCode { + case ere.ICCHCU, ere.ICCICU, ere.ICCVK, ere.ICCIp: + return subCode, nil + default: + return "", errors.New("unknown sub class code") + } + } + return "", errors.New("sub class code is nil") +} diff --git a/internal/use-case/main-use-case/soapi/case.go b/internal/use-case/main-use-case/soapi/case.go index b30da92f..46e64e71 100644 --- a/internal/use-case/main-use-case/soapi/case.go +++ b/internal/use-case/main-use-case/soapi/case.go @@ -1,9 +1,14 @@ package soapi import ( - e "simrs-vx/internal/domain/main-entities/soapi" + "errors" "strconv" + ee "simrs-vx/internal/domain/main-entities/employee" + e "simrs-vx/internal/domain/main-entities/soapi" + + ue "simrs-vx/internal/use-case/main-use-case/employee" + dg "github.com/karincake/apem/db-gorm-pg" d "github.com/karincake/dodol" @@ -34,6 +39,21 @@ func Create(input e.CreateDto) (*d.Data, error) { return err } + if !input.AuthInfo.IsDoctor() && !input.AuthInfo.IsNurse() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "auth-forbidden", + Detail: "user position is not allowed", + Raw: errors.New("authentication failed"), + } + return pl.SetLogError(&event, input) + } + + employee, err := ue.ReadDetailData(ee.ReadDetailDto{User_Id: &input.AuthInfo.User_Id}, &event, tx) + if err != nil { + return err + } + input.Employee_Id = &employee.Id if resData, err := CreateData(input, &event, tx); err != nil { return err } else { diff --git a/pkg/auth-helper/auth-helper.go b/pkg/auth-helper/auth-helper.go new file mode 100644 index 00000000..934d8d61 --- /dev/null +++ b/pkg/auth-helper/auth-helper.go @@ -0,0 +1,15 @@ +package authhelper + +import ( + "errors" + "net/http" +) + +func GetAuthInfo(r *http.Request) (*AuthInfo, error) { + ctxVal := r.Context().Value(AuthKey{}) + if ctxVal == nil { + return nil, errors.New("can't get auth info") + } + authInfo := ctxVal.(*AuthInfo) + return authInfo, nil +} diff --git a/pkg/auth-helper/tycovar.go b/pkg/auth-helper/tycovar.go new file mode 100644 index 00000000..7586956d --- /dev/null +++ b/pkg/auth-helper/tycovar.go @@ -0,0 +1,52 @@ +package authhelper + +import ( + ero "simrs-vx/internal/domain/references/organization" +) + +type AuthKey struct{} + +type AuthInfo struct { + Uuid string + User_Id uint + User_Name string + // User_Email string + // User_Ref_Id int + User_Position_Code string +} + +func (a AuthInfo) IsDoctor() bool { + return a.User_Position_Code == string(ero.UPCDoc) +} + +func (a AuthInfo) IsNurse() bool { + return a.User_Position_Code == string(ero.UPCNur) +} + +func (a AuthInfo) IsNutritionist() bool { + return a.User_Position_Code == string(ero.UPCNut) +} + +func (a AuthInfo) IsLaborant() bool { + return a.User_Position_Code == string(ero.UPCLab) +} + +func (a AuthInfo) IsPharmacist() bool { + return a.User_Position_Code == string(ero.UPCPha) +} + +func (a AuthInfo) IsPayment() bool { + return a.User_Position_Code == string(ero.UPCPay) +} + +func (a AuthInfo) IsPaymentVerificator() bool { + return a.User_Position_Code == string(ero.UPCPav) +} + +func (a AuthInfo) IsManagement() bool { + return a.User_Position_Code == string(ero.UPCMan) +} + +func (a AuthInfo) IsSpecialistIntern() bool { + return a.User_Position_Code == string(ero.UPCInt) +}