From 0e982d8ab4f67d9fe4b9e755e306b2276098e88f Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Wed, 5 Nov 2025 20:54:16 +0700 Subject: [PATCH 01/29] feat/trx-orders: added AntibioticSrcCategory flow --- .../antibiotic-src-category/handler.go | 71 +++++ .../interface/main-handler/main-handler.go | 3 + .../antibiotic-src-category/case.go | 276 ++++++++++++++++++ .../antibiotic-src-category/helper.go | 22 ++ .../antibiotic-src-category/lib.go | 140 +++++++++ .../middleware-runner.go | 103 +++++++ .../antibiotic-src-category/middleware.go | 9 + .../antibiotic-src-category/tycovar.go | 44 +++ 8 files changed, 668 insertions(+) create mode 100644 internal/interface/main-handler/antibiotic-src-category/handler.go create mode 100644 internal/use-case/main-use-case/antibiotic-src-category/case.go create mode 100644 internal/use-case/main-use-case/antibiotic-src-category/helper.go create mode 100644 internal/use-case/main-use-case/antibiotic-src-category/lib.go create mode 100644 internal/use-case/main-use-case/antibiotic-src-category/middleware-runner.go create mode 100644 internal/use-case/main-use-case/antibiotic-src-category/middleware.go create mode 100644 internal/use-case/main-use-case/antibiotic-src-category/tycovar.go diff --git a/internal/interface/main-handler/antibiotic-src-category/handler.go b/internal/interface/main-handler/antibiotic-src-category/handler.go new file mode 100644 index 00000000..7dde9c66 --- /dev/null +++ b/internal/interface/main-handler/antibiotic-src-category/handler.go @@ -0,0 +1,71 @@ +package mcusrccategory + +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/mcu-src-category" + u "simrs-vx/internal/use-case/main-use-case/mcu-src-category" +) + +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{} + 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/main-handler.go b/internal/interface/main-handler/main-handler.go index bedf3b71..ffbdd9ab 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -64,6 +64,7 @@ import ( zlc "simrs-vx/pkg/zerolog-ctx" /******************** sources ********************/ + antibioticsrccat "simrs-vx/internal/interface/main-handler/antibiotic-src-category" device "simrs-vx/internal/interface/main-handler/device" diagnosesrc "simrs-vx/internal/interface/main-handler/diagnose-src" division "simrs-vx/internal/interface/main-handler/division" @@ -133,11 +134,13 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/counter", counter.O) hc.RegCrud(r, "/v1/medicine-mix", medicicinemix.O) hc.RegCrud(r, "/v1/medicine-mix-item", medicicinemixitem.O) + hc.RegCrud(r, "/v1/antibiotic-src-category", antibioticsrccat.O) hc.RegCrud(r, "/v1/soapi", auth.GuardMW, soapi.O) hc.RegCrud(r, "/v1/adime", auth.GuardMW, adime.O) hc.RegCrud(r, "/v1/sbar", auth.GuardMW, sbar.O) hc.RegCrud(r, "/v1/prescription-item", prescriptionitem.O) hc.RegCrud(r, "/v1/device-order-item", deviceorderitem.O) + hc.RegCrud(r, "/v1/material-order-item", materialorderitem.O) hk.GroupRoutes("/v1/encounter", r, auth.GuardMW, hk.MapHandlerFunc{ diff --git a/internal/use-case/main-use-case/antibiotic-src-category/case.go b/internal/use-case/main-use-case/antibiotic-src-category/case.go new file mode 100644 index 00000000..2eaceb0c --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src-category/case.go @@ -0,0 +1,276 @@ +package antibioticsrccategory + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src-category" + "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" +) + +const source = "antibiotic-src-category" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.AntibioticSrcCategory{} + + 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.AntibioticSrcCategory + var dataList []e.AntibioticSrcCategory + 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.AntibioticSrcCategory + 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.AntibioticSrcCategory + 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.AntibioticSrcCategory + 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/antibiotic-src-category/helper.go b/internal/use-case/main-use-case/antibiotic-src-category/helper.go new file mode 100644 index 00000000..7bbe8d80 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src-category/helper.go @@ -0,0 +1,22 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package antibioticsrccategory + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src-category" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.AntibioticSrcCategory) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.Code = inputSrc.Code + data.Name = inputSrc.Name +} diff --git a/internal/use-case/main-use-case/antibiotic-src-category/lib.go b/internal/use-case/main-use-case/antibiotic-src-category/lib.go new file mode 100644 index 00000000..31f2eee1 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src-category/lib.go @@ -0,0 +1,140 @@ +package antibioticsrccategory + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src-category" + + 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.AntibioticSrcCategory, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.AntibioticSrcCategory{} + 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.AntibioticSrcCategory, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.AntibioticSrcCategory{} + 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.AntibioticSrcCategory{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Scopes(gh.Sort(input.Sort)) + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, plh.HandleListError(input, event, err) + } + + meta.Count = int(count) + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + + pl.SetLogInfo(event, nil, "complete") + return data, &meta, nil +} + +func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e.AntibioticSrcCategory, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.AntibioticSrcCategory{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.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.AntibioticSrcCategory, 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.AntibioticSrcCategory, 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/antibiotic-src-category/middleware-runner.go b/internal/use-case/main-use-case/antibiotic-src-category/middleware-runner.go new file mode 100644 index 00000000..6ab4e880 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src-category/middleware-runner.go @@ -0,0 +1,103 @@ +package antibioticsrccategory + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src-category" + 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.AntibioticSrcCategory) 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.AntibioticSrcCategory) 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.AntibioticSrcCategory) 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.AntibioticSrcCategory) 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.AntibioticSrcCategory) 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/antibiotic-src-category/middleware.go b/internal/use-case/main-use-case/antibiotic-src-category/middleware.go new file mode 100644 index 00000000..55091880 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src-category/middleware.go @@ -0,0 +1,9 @@ +package antibioticsrccategory + +// 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/antibiotic-src-category/tycovar.go b/internal/use-case/main-use-case/antibiotic-src-category/tycovar.go new file mode 100644 index 00000000..0980e65a --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src-category/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 antibioticsrccategory + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/antibiotic-src-category" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.AntibioticSrcCategory, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.AntibioticSrcCategory, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.AntibioticSrcCategory, 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 From 7037ceae558b674cbdf64364579823995ecacc9f Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Thu, 6 Nov 2025 12:28:53 +0700 Subject: [PATCH 02/29] feat/trx-orders: added AntibioticSrc flow --- .../main-handler/antibiotic-src/handler.go | 71 +++++ .../interface/main-handler/main-handler.go | 2 + .../main-use-case/antibiotic-src/case.go | 280 ++++++++++++++++++ .../main-use-case/antibiotic-src/helper.go | 30 ++ .../main-use-case/antibiotic-src/lib.go | 140 +++++++++ .../antibiotic-src/middleware-runner.go | 111 +++++++ .../main-use-case/antibiotic-src/tycovar.go | 44 +++ 7 files changed, 678 insertions(+) create mode 100644 internal/interface/main-handler/antibiotic-src/handler.go create mode 100644 internal/use-case/main-use-case/antibiotic-src/case.go create mode 100644 internal/use-case/main-use-case/antibiotic-src/helper.go create mode 100644 internal/use-case/main-use-case/antibiotic-src/lib.go create mode 100644 internal/use-case/main-use-case/antibiotic-src/middleware-runner.go create mode 100644 internal/use-case/main-use-case/antibiotic-src/tycovar.go diff --git a/internal/interface/main-handler/antibiotic-src/handler.go b/internal/interface/main-handler/antibiotic-src/handler.go new file mode 100644 index 00000000..506472f8 --- /dev/null +++ b/internal/interface/main-handler/antibiotic-src/handler.go @@ -0,0 +1,71 @@ +package antibioticsrc + +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/antibiotic-src" + u "simrs-vx/internal/use-case/main-use-case/antibiotic-src" +) + +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{} + 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) +} \ No newline at end of file diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index ffbdd9ab..1948447d 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -64,6 +64,7 @@ import ( zlc "simrs-vx/pkg/zerolog-ctx" /******************** sources ********************/ + antibioticsrc "simrs-vx/internal/interface/main-handler/antibiotic-src" antibioticsrccat "simrs-vx/internal/interface/main-handler/antibiotic-src-category" device "simrs-vx/internal/interface/main-handler/device" diagnosesrc "simrs-vx/internal/interface/main-handler/diagnose-src" @@ -135,6 +136,7 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/medicine-mix", medicicinemix.O) hc.RegCrud(r, "/v1/medicine-mix-item", medicicinemixitem.O) hc.RegCrud(r, "/v1/antibiotic-src-category", antibioticsrccat.O) + hc.RegCrud(r, "/v1/antibiotic-src", antibioticsrc.O) hc.RegCrud(r, "/v1/soapi", auth.GuardMW, soapi.O) hc.RegCrud(r, "/v1/adime", auth.GuardMW, adime.O) hc.RegCrud(r, "/v1/sbar", auth.GuardMW, sbar.O) diff --git a/internal/use-case/main-use-case/antibiotic-src/case.go b/internal/use-case/main-use-case/antibiotic-src/case.go new file mode 100644 index 00000000..e1ff1c44 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src/case.go @@ -0,0 +1,280 @@ +package antibioticsrc + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src" + "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" +) + +const source = "antibiotic-src" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.AntibioticSrc{} + + 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 err := createItem(&input, &event, tx); 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.AntibioticSrc + var dataList []e.AntibioticSrc + 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.AntibioticSrc + 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.AntibioticSrc + 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.AntibioticSrc + 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 + +} \ No newline at end of file diff --git a/internal/use-case/main-use-case/antibiotic-src/helper.go b/internal/use-case/main-use-case/antibiotic-src/helper.go new file mode 100644 index 00000000..69dfb986 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src/helper.go @@ -0,0 +1,30 @@ +package antibioticsrc + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src" + + pl "simrs-vx/pkg/logger" + + "gorm.io/gorm" +) + +func setData(input any, data *e.AntibioticSrc) { + switch input.(type) { + case *e.CreateDto: + data.Code = input.(*e.CreateDto).Code + data.Name = input.(*e.CreateDto).Name + data.AntibioticSrcCategory_Code = input.(*e.CreateDto).AntibioticSrcCategory_Code + // data.Item_Id = input.(*e.CreateDto).Item_Id + case *e.UpdateDto: + data.Code = input.(*e.UpdateDto).Code + data.Name = input.(*e.UpdateDto).Name + data.AntibioticSrcCategory_Code = input.(*e.UpdateDto).AntibioticSrcCategory_Code + // data.Item_Id = input.(*e.UpdateDto).Item_Id + } +} + +func createItem(input *e.CreateDto, event *pl.Event, tx *gorm.DB) error { + // For antibiotic-src, we don't need to create items since Item_Id is commented out + // This function is kept for consistency with the pattern but does nothing + return nil +} diff --git a/internal/use-case/main-use-case/antibiotic-src/lib.go b/internal/use-case/main-use-case/antibiotic-src/lib.go new file mode 100644 index 00000000..24f91815 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src/lib.go @@ -0,0 +1,140 @@ +package antibioticsrc + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src" + + 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.AntibioticSrc, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.AntibioticSrc{} + 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.AntibioticSrc, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.AntibioticSrc{} + 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.AntibioticSrc{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Scopes(gh.Sort(input.Sort)) + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, plh.HandleListError(input, event, err) + } + + meta.Count = int(count) + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + + pl.SetLogInfo(event, nil, "complete") + return data, &meta, nil +} + +func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e.AntibioticSrc, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.AntibioticSrc{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.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.AntibioticSrc, 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.AntibioticSrc, 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 +} \ No newline at end of file diff --git a/internal/use-case/main-use-case/antibiotic-src/middleware-runner.go b/internal/use-case/main-use-case/antibiotic-src/middleware-runner.go new file mode 100644 index 00000000..2a939f32 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src/middleware-runner.go @@ -0,0 +1,111 @@ +package antibioticsrc + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-src" + 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.AntibioticSrc) 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.AntibioticSrc) 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.AntibioticSrc) 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.AntibioticSrc) 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.AntibioticSrc) 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 +} + +// 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/antibiotic-src/tycovar.go b/internal/use-case/main-use-case/antibiotic-src/tycovar.go new file mode 100644 index 00000000..94e36fd4 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-src/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 antibioticsrc + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/antibiotic-src" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.AntibioticSrc, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.AntibioticSrc, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.AntibioticSrc, 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 From 13b0ea571eff3a91f526bf4806cf050d2b377961 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Thu, 6 Nov 2025 12:49:10 +0700 Subject: [PATCH 03/29] feat/trx-orders: added antibiotic-in-use flow --- .../main-entities/antibiotic-in-use/dto.go | 4 +- .../main-use-case/antibiotic-in-use/case.go | 389 ++++++++++++++++++ .../main-use-case/antibiotic-in-use/helper.go | 56 +++ .../main-use-case/antibiotic-in-use/lib.go | 140 +++++++ .../antibiotic-in-use/middleware-runner.go | 103 +++++ .../antibiotic-in-use/middleware.go | 9 + .../antibiotic-in-use/tycovar.go | 44 ++ 7 files changed, 744 insertions(+), 1 deletion(-) create mode 100644 internal/use-case/main-use-case/antibiotic-in-use/case.go create mode 100644 internal/use-case/main-use-case/antibiotic-in-use/helper.go create mode 100644 internal/use-case/main-use-case/antibiotic-in-use/lib.go create mode 100644 internal/use-case/main-use-case/antibiotic-in-use/middleware-runner.go create mode 100644 internal/use-case/main-use-case/antibiotic-in-use/middleware.go create mode 100644 internal/use-case/main-use-case/antibiotic-in-use/tycovar.go diff --git a/internal/domain/main-entities/antibiotic-in-use/dto.go b/internal/domain/main-entities/antibiotic-in-use/dto.go index 7fbce2dd..1c6ee63f 100644 --- a/internal/domain/main-entities/antibiotic-in-use/dto.go +++ b/internal/domain/main-entities/antibiotic-in-use/dto.go @@ -24,9 +24,11 @@ type FilterDto struct { AntibioticSrc_Id *uint `json:"mcu-src-id"` Result *string `json:"result"` Status_Code erc.DataStatusCode `json:"status-code"` + Includes string `json:"includes"` } type ReadDetailDto struct { - Id uint `json:"id"` + Id uint `json:"id"` + Includes string `json:"includes"` } type UpdateDto struct { diff --git a/internal/use-case/main-use-case/antibiotic-in-use/case.go b/internal/use-case/main-use-case/antibiotic-in-use/case.go new file mode 100644 index 00000000..20d95639 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-in-use/case.go @@ -0,0 +1,389 @@ +package antibioticinuse + +import ( + "errors" + "strconv" + + e "simrs-vx/internal/domain/main-entities/antibiotic-in-use" + + ue "simrs-vx/internal/use-case/main-use-case/encounter" + umo "simrs-vx/internal/use-case/main-use-case/mcu-order" + + 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" +) + +const source = "antibiotic-in-use" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.AntibioticInUse{} + + 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 + } + + // check if encounter is done + if ue.IsDone(*input.McuOrder_Id, &event, tx) { + return errors.New("encounter is already done") + } + + // check if mcu-order is completed + if umo.IsCompleted(*input.McuOrder_Id, &event, tx) { + return errors.New("mcu-order is already completed") + } + + // if !input.AuthInfo.IsDoctor() { + // 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) + // } + + // doctor_id, err := ud.GetIdByUserId(&input.AuthInfo.User_Id, &event, tx) + // if err != nil { + // return err + // } + + // verify the mcu-order belongs to the same doctor + // rdDto := e.ReadDetailDto{Id: *input.McuOrder_Id} + // if err := ValidateMcuOrderDoctor(rdDto, doctor_id, &event, tx); 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.AntibioticInUse + var dataList []e.AntibioticInUse + 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.AntibioticInUse + 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.AntibioticInUse + 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 + } + + // if data.IsCompleted() { + // return errors.New("data already completed") + // } + + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + return err + } + + // if !input.AuthInfo.IsDoctor() { + // 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) + // } + + // doctor_id, err := ud.GetIdByUserId(&input.AuthInfo.User_Id, &event, tx) + // if err != nil { + // return err + // } + + // verify the mcu-order belongs to the same doctor + // if err := ValidateMcuOrderDoctor(e.ReadDetailDto{Id: *data.McuOrder_Id}, doctor_id, &event, tx); 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.AntibioticInUse + 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 SetSchedule(input e.SetScheduleDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.AntibioticInUse + var err error + + event := pl.Event{ + Feature: "SetSchedule", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "setSchedule") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + data, err = ReadDetailData(rdDto, &event, tx) + if err != nil { + return err + } + // if data.IsCompleted() { + // return errors.New("data already completed") + // } + + // data.ExaminationDate = input.ExaminationDate + // 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 + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "setSchedule", + }, + Data: data.ToResponse(), + }, nil +} diff --git a/internal/use-case/main-use-case/antibiotic-in-use/helper.go b/internal/use-case/main-use-case/antibiotic-in-use/helper.go new file mode 100644 index 00000000..8f81510e --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-in-use/helper.go @@ -0,0 +1,56 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package antibioticinuse + +import ( + "errors" + + e "simrs-vx/internal/domain/main-entities/antibiotic-in-use" + emo "simrs-vx/internal/domain/main-entities/mcu-order" + + pl "simrs-vx/pkg/logger" + + "gorm.io/gorm" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.AntibioticInUse) { + var inputSrc *e.CreateDto + if inputT, ok := any(input).(*e.CreateDto); ok { + inputSrc = inputT + // data.Status_Code = erc.DSCNew + } else { + inputTemp := any(input).(*e.UpdateDto) + inputSrc = &inputTemp.CreateDto + } + + data.McuOrder_Id = inputSrc.McuOrder_Id + data.AntibioticSrc_Id = inputSrc.AntibioticSrc_Id + // data.ExaminationDate = inputSrc.ExaminationDate +} + +func ValidateMcuOrderDoctor(input e.ReadDetailDto, doctorId uint, event *pl.Event, tx *gorm.DB) error { + pl.SetLogInfo(event, input, "started", "ValidateMcuOrderDoctor") + + var mcuOrder emo.McuOrder + if err := tx.First(&mcuOrder, input.Id).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return errors.New("mcu-order not found") + } + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-get-fail", + Detail: "Failed to get mcu-order", + Raw: err, + } + return pl.SetLogError(event, input) + } + + if mcuOrder.Doctor_Id == nil || *mcuOrder.Doctor_Id != doctorId { + return errors.New("doctor is not authorized for this mcu-order") + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} diff --git a/internal/use-case/main-use-case/antibiotic-in-use/lib.go b/internal/use-case/main-use-case/antibiotic-in-use/lib.go new file mode 100644 index 00000000..e6b2d13d --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-in-use/lib.go @@ -0,0 +1,140 @@ +package antibioticinuse + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-in-use" + + 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.AntibioticInUse, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.AntibioticInUse{} + 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.AntibioticInUse, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.AntibioticInUse{} + 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.AntibioticInUse{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Order("\"CreatedAt\" DESC") + + if err := tx.Find(&data).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, &meta, nil + } + return nil, nil, plh.HandleListError(input, event, err) + } + + meta.Count = int(count) + meta.PageNumber = pagination.PageNumber + meta.PageSize = pagination.PageSize + + pl.SetLogInfo(event, nil, "complete") + return data, &meta, nil +} + +func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e.AntibioticInUse, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.AntibioticInUse{} + + 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.AntibioticInUse, 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.AntibioticInUse, 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 +} \ No newline at end of file diff --git a/internal/use-case/main-use-case/antibiotic-in-use/middleware-runner.go b/internal/use-case/main-use-case/antibiotic-in-use/middleware-runner.go new file mode 100644 index 00000000..de906bf8 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-in-use/middleware-runner.go @@ -0,0 +1,103 @@ +package antibioticinuse + +import ( + e "simrs-vx/internal/domain/main-entities/antibiotic-in-use" + 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.AntibioticInUse) 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.AntibioticInUse) 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.AntibioticInUse) 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.AntibioticInUse) 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.AntibioticInUse) 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 +} \ No newline at end of file diff --git a/internal/use-case/main-use-case/antibiotic-in-use/middleware.go b/internal/use-case/main-use-case/antibiotic-in-use/middleware.go new file mode 100644 index 00000000..f437fa0c --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-in-use/middleware.go @@ -0,0 +1,9 @@ +package antibioticinuse + +// example of middleware +// func init() { +// createPreMw = append(createPreMw, +// CreateMw{Name: "modif-input", Func: pm.ModifInput}, +// CreateMw{Name: "check-data", Func: pm.CheckData}, +// ) +// } \ No newline at end of file diff --git a/internal/use-case/main-use-case/antibiotic-in-use/tycovar.go b/internal/use-case/main-use-case/antibiotic-in-use/tycovar.go new file mode 100644 index 00000000..f35c0065 --- /dev/null +++ b/internal/use-case/main-use-case/antibiotic-in-use/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 antibioticinuse + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/antibiotic-in-use" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.AntibioticInUse, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.AntibioticInUse, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.AntibioticInUse, 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 \ No newline at end of file From 7c600dce5f917d48c85686fcd315fc2f11c724fe Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Thu, 20 Nov 2025 13:39:51 +0700 Subject: [PATCH 04/29] feat/things-munaja: device and mcu order --- .../main-entities/device-order-item/dto.go | 1 + .../domain/main-entities/mcu-order/dto.go | 5 +- .../domain/main-entities/mcu-order/entity.go | 6 ++ .../interface/main-handler/main-handler.go | 1 + .../main-handler/mcu-order/handler.go | 12 ++++ .../main-use-case/device-order-item/lib.go | 4 +- .../use-case/main-use-case/mcu-order/case.go | 59 +++++++++++++++++++ 7 files changed, 85 insertions(+), 3 deletions(-) diff --git a/internal/domain/main-entities/device-order-item/dto.go b/internal/domain/main-entities/device-order-item/dto.go index 4464c91d..3d31c66d 100644 --- a/internal/domain/main-entities/device-order-item/dto.go +++ b/internal/domain/main-entities/device-order-item/dto.go @@ -15,6 +15,7 @@ type CreateDto struct { type ReadListDto struct { FilterDto Includes string `json:"includes"` + Sort string `json:"sort"` Pagination ecore.Pagination } diff --git a/internal/domain/main-entities/mcu-order/dto.go b/internal/domain/main-entities/mcu-order/dto.go index 594f72d7..2835ce2c 100644 --- a/internal/domain/main-entities/mcu-order/dto.go +++ b/internal/domain/main-entities/mcu-order/dto.go @@ -17,6 +17,7 @@ import ( // internal - domain - main-entities ed "simrs-vx/internal/domain/main-entities/doctor" ee "simrs-vx/internal/domain/main-entities/encounter" + emoib "simrs-vx/internal/domain/main-entities/mcu-order-item/base" ) type CreateDto struct { @@ -85,7 +86,8 @@ type ResponseDto struct { ExaminationDate *time.Time `json:"examinationDate"` Number uint8 `json:"number"` Temperature float64 `json:"temperature"` - UrgencyLevel_Code ercl.McuUrgencyLevelCode `json:"urgencyLevel_code""` + UrgencyLevel_Code ercl.McuUrgencyLevelCode `json:"urgencyLevel_code"` + Items []*emoib.McuOrderItem `json:"items"` } func (d McuOrder) ToResponse() ResponseDto { @@ -100,6 +102,7 @@ func (d McuOrder) ToResponse() ResponseDto { Number: d.Number, Temperature: d.Temperature, UrgencyLevel_Code: d.UrgencyLevel_Code, + Items: d.Items, } resp.Main = d.Main return resp diff --git a/internal/domain/main-entities/mcu-order/entity.go b/internal/domain/main-entities/mcu-order/entity.go index b9e5d0d7..81fb7495 100644 --- a/internal/domain/main-entities/mcu-order/entity.go +++ b/internal/domain/main-entities/mcu-order/entity.go @@ -4,6 +4,7 @@ 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" + emoib "simrs-vx/internal/domain/main-entities/mcu-order-item/base" "time" ercl "simrs-vx/internal/domain/references/clinical" @@ -23,6 +24,11 @@ type McuOrder struct { Temperature float64 `json:"temperature"` UrgencyLevel_Code ercl.McuUrgencyLevelCode `json:"urgencyLevel_code" gorm:"not null;size:15"` Scope_Code ercl.McuScopeCode `json:"scope_code" gorm:"index;size:10"` + Items []*emoib.McuOrderItem `json:"items" gorm:"foreignKey:McuOrder_Id;references:Id"` +} + +func (d McuOrder) IsNotNew() bool { + return d.Status_Code != erc.DSCNew } func (d McuOrder) IsCompleted() bool { diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index aa6f76fb..6fcecac3 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -173,6 +173,7 @@ func SetRoutes() http.Handler { "POST /": mcuorder.O.Create, "PATCH /{id}": mcuorder.O.Update, "DELETE /{id}": mcuorder.O.Delete, + "PATCH /{id}/submit": mcuorder.O.Submit, "PATCH /{id}/complete": mcuorder.O.Complete, "PATCH /{id}/set-schedule": mcuorder.O.SetSchedule, }) diff --git a/internal/interface/main-handler/mcu-order/handler.go b/internal/interface/main-handler/mcu-order/handler.go index ac4aca9f..bbbd1c56 100644 --- a/internal/interface/main-handler/mcu-order/handler.go +++ b/internal/interface/main-handler/mcu-order/handler.go @@ -85,6 +85,18 @@ func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { rw.DataResponse(w, res, err) } +func (obj myBase) Submit(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.ReadDetailDto{} + dto.Id = uint(id) + res, err := u.Submit(dto) + rw.DataResponse(w, res, err) +} + func (obj myBase) Complete(w http.ResponseWriter, r *http.Request) { id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { diff --git a/internal/use-case/main-use-case/device-order-item/lib.go b/internal/use-case/main-use-case/device-order-item/lib.go index 70ccc5d7..84838096 100644 --- a/internal/use-case/main-use-case/device-order-item/lib.go +++ b/internal/use-case/main-use-case/device-order-item/lib.go @@ -52,8 +52,8 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.De Scopes(gh.Preload(input.Includes)). Scopes(gh.Filter(input.FilterDto)). Count(&count). - Scopes(gh.Paginate(input, &pagination)). - Order("\"CreatedAt\" DESC") + Scopes(gh.Sort(input.Sort)). + Scopes(gh.Paginate(input, &pagination)) if err := tx.Find(&data).Error; err != nil { if err == gorm.ErrRecordNotFound { diff --git a/internal/use-case/main-use-case/mcu-order/case.go b/internal/use-case/main-use-case/mcu-order/case.go index 38f13088..6d7c4fed 100644 --- a/internal/use-case/main-use-case/mcu-order/case.go +++ b/internal/use-case/main-use-case/mcu-order/case.go @@ -324,6 +324,65 @@ func Delete(input e.DeleteDto) (*d.Data, error) { } +func Submit(input e.ReadDetailDto) (*d.Data, error) { + var data *e.McuOrder + var err error + + event := pl.Event{ + Feature: "Process", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "process") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + data, err = ReadDetailData(input, &event, tx) + if err != nil { + return err + } + + if data.IsNotNew() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-state-mismatch", + Detail: "prescription is not in new state", + Raw: errors.New("prescription is not in new state"), + } + return pl.SetLogError(&event, input) + } + + data.Status_Code = erc.DSCSubmited + 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 + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "submited", + }, + Data: data.ToResponse(), + }, nil + +} + func Complete(input e.ReadDetailDto) (*d.Data, error) { var data *e.McuOrder var err error From 46d0c17669b467cee217ea327ac75b48229062c2 Mon Sep 17 00:00:00 2001 From: vanilia Date: Thu, 20 Nov 2025 14:16:08 +0700 Subject: [PATCH 05/29] encounter mapping --- internal/domain/references/person/person.go | 3 + .../domain/simgos-entities/patient/entity.go | 2 +- .../interface/main-handler/patient/handler.go | 8 + .../use-case/main-use-case/patient/case.go | 14 ++ .../simgos-sync-use-case/encounter/case.go | 197 ++++++++++++++++++ .../simgos-sync-use-case/encounter/helper.go | 74 +++++++ .../simgos-sync-use-case/encounter/lib.go | 188 +++++++++++++++++ .../encounter/middleware-runner.go | 104 +++++++++ .../encounter/middleware.go | 9 + .../simgos-sync-use-case/encounter/tycovar.go | 44 ++++ .../simgos-sync-use-case/patient/case.go | 2 +- .../simgos-sync-use-case/patient/helper.go | 109 ++++++---- 12 files changed, 714 insertions(+), 40 deletions(-) create mode 100644 internal/use-case/simgos-sync-use-case/encounter/case.go create mode 100644 internal/use-case/simgos-sync-use-case/encounter/helper.go create mode 100644 internal/use-case/simgos-sync-use-case/encounter/lib.go create mode 100644 internal/use-case/simgos-sync-use-case/encounter/middleware-runner.go create mode 100644 internal/use-case/simgos-sync-use-case/encounter/middleware.go create mode 100644 internal/use-case/simgos-sync-use-case/encounter/tycovar.go diff --git a/internal/domain/references/person/person.go b/internal/domain/references/person/person.go index 1648b578..dfd35179 100644 --- a/internal/domain/references/person/person.go +++ b/internal/domain/references/person/person.go @@ -112,6 +112,9 @@ const ( RCNephew RelationshipCode = "nephew" // Keponakan RCGdChild RelationshipCode = "gd-child" // Cucu RCOther RelationshipCode = "other" // Lainnya + RCFriend RelationshipCode = "friend" // Teman + RCSpouse RelationshipCode = "spouse" // Pasangan (Suami / Istri) + RCSelf RelationshipCode = "self" // Diri Sendiri ALTCIdn AddressLocationTypeCode = "identity" // Sesuai Identitas ALTCDom AddressLocationTypeCode = "domicile" // Sesuai Domisili diff --git a/internal/domain/simgos-entities/patient/entity.go b/internal/domain/simgos-entities/patient/entity.go index 20b7fee6..67958d56 100644 --- a/internal/domain/simgos-entities/patient/entity.go +++ b/internal/domain/simgos-entities/patient/entity.go @@ -65,7 +65,7 @@ type MPasien struct { NamaIbu string `json:"nama_ibu" gorm:"column:nama_ibu"` PendidikanAyah string `json:"pendidikan_ayah" gorm:"column:pendidikan_ayah"` PendidikanIbu string `json:"pendidikan_ibu" gorm:"column:pendidikan_ibu"` - StIdentitasOrtu string `json:"st_identitas_ortu" gorm:"column:st_identitas_ortu"` + StIdentitasOrtu uint `json:"st_identitas_ortu" gorm:"column:st_identitas_ortu"` NomrBaru *string `json:"nomr_baru" gorm:"column:nomr_baru"` KtpFile *string `json:"ktp_file" gorm:"column:ktp_file"` KkFile *string `json:"kk_file" gorm:"column:kk_file"` diff --git a/internal/interface/main-handler/patient/handler.go b/internal/interface/main-handler/patient/handler.go index 9fd5fdce..41fca562 100644 --- a/internal/interface/main-handler/patient/handler.go +++ b/internal/interface/main-handler/patient/handler.go @@ -2,6 +2,7 @@ package patient import ( "net/http" + pa "simrs-vx/internal/lib/auth" rw "github.com/karincake/risoles" sf "github.com/karincake/semprit" @@ -19,10 +20,17 @@ 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/patient/case.go b/internal/use-case/main-use-case/patient/case.go index 1a8437e5..0ed93bbe 100644 --- a/internal/use-case/main-use-case/patient/case.go +++ b/internal/use-case/main-use-case/patient/case.go @@ -1,6 +1,7 @@ package patient import ( + "errors" "strconv" pl "simrs-vx/pkg/logger" @@ -38,6 +39,19 @@ func Create(input e.CreateDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "create") mwRunner := newMiddlewareRunner(&event) + // check if user has employee position + if !input.AuthInfo.HasEmployeePosition() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "auth-forbidden", + Detail: "user has no employee position", + Raw: errors.New("authentication failed"), + } + return nil, pl.SetLogError(&event, input) + } + + input.RegisteredBy_User_Name = &input.AuthInfo.User_Name + err := dg.I.Transaction(func(tx *gorm.DB) error { mwRunner.setMwType(pu.MWTPre) // Run pre-middleware diff --git a/internal/use-case/simgos-sync-use-case/encounter/case.go b/internal/use-case/simgos-sync-use-case/encounter/case.go new file mode 100644 index 00000000..57650594 --- /dev/null +++ b/internal/use-case/simgos-sync-use-case/encounter/case.go @@ -0,0 +1,197 @@ +package encounter + +import ( + pl "simrs-vx/pkg/logger" + + d "github.com/karincake/dodol" + "gorm.io/gorm" + + db "simrs-vx/pkg/dualtrx-helper" + + e "simrs-vx/internal/domain/main-entities/division" + esimgos "simrs-vx/internal/domain/simgos-entities/division" + esync "simrs-vx/internal/domain/sync-entities/division" + elog "simrs-vx/internal/domain/sync-entities/log" +) + +const source = "division" + +func Create(input e.CreateDto) (*d.Data, error) { + var ( + sgData *esimgos.MUnit + syncLink *esync.DivisionLink + err error + ) + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err = db.WithDualTx(func(tx *db.Dualtx) error { + // STEP 1: Insert to simgos + sgData, err = CreateSimgosData(input, &event, tx.Simgos) + if err != nil { + return err + } + + // STEP 2: Insert to Link + syncLink, err = CreateLinkData(*input.Id, sgData.KodeUnit, &event, tx.Sync) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + if syncLink != nil { + go func() { _ = DeleteLinkData(syncLink, &event) }() + } + return nil, err + } + + pl.SetLogInfo(&event, nil, "complete") + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + }, nil +} + +func CreateSimxLog(input elog.SimxLogDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + tx := db.NewTx() + err := tx.Sync.Transaction(func(tx *gorm.DB) error { + // Insert to Log + if err := CreateLogData(input, &event, tx); err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + pl.SetLogInfo(&event, nil, "complete") + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + }, nil +} + +func Update(input e.UpdateDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Update", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "update") + + // STEP 1: Get Link + syncLink, err := ReadDetailLinkData(*input.Id, &event) + if err != nil { + return nil, err + } + + tx := db.NewTx() + err = tx.Simgos.Transaction(func(tx *gorm.DB) error { + // Step 2: Update Simgos + if err = UpdateSimgosData(input, syncLink, &event, tx); err != nil { + return err + } + + return nil + }) + + pl.SetLogInfo(&event, nil, "complete") + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + }, nil +} + +func Delete(input e.DeleteDto) (*d.Data, error) { + var isLinkDeleted bool + + event := pl.Event{ + Feature: "Delete", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "delete") + + // STEP 1: Get Link + syncLink, err := ReadDetailLinkData(*input.Id, &event) + if err != nil { + return nil, err + } + + // STEP 2: Get Simgos + sgData, err := ReadDetailSimgosData(uint16(syncLink.Simgos_Id), &event) + if err != nil { + return nil, err + } + + err = db.WithDualTx(func(tx *db.Dualtx) error { + // STEP 3: Delete Simgos + err = HardDeleteSimgosData(sgData, &event, tx.Simgos) + if err != nil { + return err + } + + // STEP 4: Delete Link + err = DeleteLinkData(syncLink, &event, tx.Sync) + if err != nil { + return err + } + + isLinkDeleted = true + return nil + }) + + if err != nil { + if isLinkDeleted { + go func() { + _, _ = CreateLinkData(uint(*input.Id), sgData.KodeUnit, &event) + }() + } + return nil, err + } + + pl.SetLogInfo(&event, nil, "complete") + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "deleted", + }, + }, nil + +} diff --git a/internal/use-case/simgos-sync-use-case/encounter/helper.go b/internal/use-case/simgos-sync-use-case/encounter/helper.go new file mode 100644 index 00000000..6fb47ac9 --- /dev/null +++ b/internal/use-case/simgos-sync-use-case/encounter/helper.go @@ -0,0 +1,74 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package encounter + +import ( + "encoding/json" + "strconv" + + erc "simrs-vx/internal/domain/references/common" + ere "simrs-vx/internal/domain/references/encounter" + + e "simrs-vx/internal/domain/main-entities/encounter" + esimgos "simrs-vx/internal/domain/simgos-entities/encounter" + esync "simrs-vx/internal/domain/sync-entities/encounter" + esyncLog "simrs-vx/internal/domain/sync-entities/encounter" +) + +func setDataSimgos(input *e.Encounter) (data esimgos.TPendaftaran) { + data.Nomr = *input.Patient.Number + data.Tglreg = input.RegisteredAt + + kddokter, _ := strconv.Atoi(*input.Appointment_Doctor_Code) + data.Kddokter = uint(kddokter) + + kdpoly, _ := strconv.Atoi(*input.Unit_Code) + data.Kdpoly = uint(kdpoly) + + setKdrujuk(input.RefType_Code, data) + + return +} + +func setKdrujuk(input *ere.RefTypeCode, data esimgos.TPendaftaran) { + switch *input { + case ere.RTCGov: + data.Kdrujuk = 1 + case ere.RTCPrivate: + data.Kdrujuk = 2 + case ere.RTCBpjs: + data.Kdrujuk = 3 + default: + data.Kdrujuk = 0 + } +} + +func setDataSimxLog(input *esyncLog.SimxLogDto) (data esync.DivisionSimxLog) { + // encode to JSON + jsonData, _ := json.MarshalIndent(input.Payload, "", " ") + jsonString := string(jsonData) + + var status erc.ProcessStatusCode + if input.IsSuccess { + status = erc.PSCSuccess + } else { + status = erc.PSCFailed + if input.ErrMessage != nil { + data.ErrMessage = input.ErrMessage + } + } + + data.Value = &jsonString + data.Date = &now + data.Status = status + + return +} + +func setDataSimxLink(simxId, simgosId uint) (data esync.DivisionLink) { + data.Simx_Id = simxId + data.Simgos_Id = simgosId + return +} diff --git a/internal/use-case/simgos-sync-use-case/encounter/lib.go b/internal/use-case/simgos-sync-use-case/encounter/lib.go new file mode 100644 index 00000000..f713ca6c --- /dev/null +++ b/internal/use-case/simgos-sync-use-case/encounter/lib.go @@ -0,0 +1,188 @@ +package encounter + +import ( + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + "time" + + dg "github.com/karincake/apem/db-gorm-pg" + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/division" + esimgos "simrs-vx/internal/domain/simgos-entities/division" + esync "simrs-vx/internal/domain/sync-entities/division" + esynclog "simrs-vx/internal/domain/sync-entities/log" +) + +var now = time.Now() + +func CreateSimgosData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*esimgos.MUnit, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := setDataSimgos(&input) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.IS["simrs"] + } + + if err := tx.Create(&data).Error; err != nil { + return nil, plh.HandleCreateError(input, event, err) + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func ReadDetailSimgosData(simgosId uint16, event *pl.Event) (*esimgos.MUnit, error) { + pl.SetLogInfo(event, simgosId, "started", "DBReadDetail") + data := esimgos.MUnit{} + + var tx = dg.IS["simrs"] + + if err := tx. + Where("\"kode_unit\" = ?", simgosId). + First(&data).Error; err != nil { + if processedErr := pu.HandleReadError(err, event, source, simgosId, data); processedErr != nil { + return nil, processedErr + } + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func UpdateSimgosData(input e.UpdateDto, dataSimgos *esync.DivisionLink, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, input, "started", "DBUpdate") + + data := setDataSimgos(&input) + data.KodeUnit = dataSimgos.Simgos_Id + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.IS["simrs"] + } + + 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 HardDeleteSimgosData(data *esimgos.MUnit, 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.IS["simrs"] + } + + if err := tx. + Delete(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-delete-fail", + Detail: "Database delete failed", + Raw: err, + } + return pl.SetLogError(event, data) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} + +func CreateLinkData(simxId, simgosId uint, event *pl.Event, dbx ...*gorm.DB) (*esync.DivisionLink, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + data := setDataSimxLink(simxId, simgosId) + + 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(data, event, err) + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func ReadDetailLinkData(simxId uint16, event *pl.Event) (*esync.DivisionLink, error) { + pl.SetLogInfo(event, simxId, "started", "DBReadDetail") + data := esync.DivisionLink{} + + var tx = dg.I + + if err := tx. + Where("\"Simx_Id\" = ?", simxId). + First(&data).Error; err != nil { + if processedErr := pu.HandleReadError(err, event, source, simxId, data); processedErr != nil { + return nil, processedErr + } + } + + pl.SetLogInfo(event, nil, "complete") + return &data, nil +} + +func DeleteLinkData(data *esync.DivisionLink, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, data, "started", "DBDelete") + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.Delete(&data).Error; err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-delete-fail", + Detail: "Database delete failed", + Raw: err, + } + return pl.SetLogError(event, data) + } + + pl.SetLogInfo(event, nil, "complete") + return nil +} + +func CreateLogData(input esynclog.SimxLogDto, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, nil, "started", "DBCreate") + data := setDataSimxLog(&input) + + 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/simgos-sync-use-case/encounter/middleware-runner.go b/internal/use-case/simgos-sync-use-case/encounter/middleware-runner.go new file mode 100644 index 00000000..83e0252c --- /dev/null +++ b/internal/use-case/simgos-sync-use-case/encounter/middleware-runner.go @@ -0,0 +1,104 @@ +package encounter + +import ( + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/encounter" +) + +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.Encounter) 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.Encounter) 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.Encounter) 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.Encounter) 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.Encounter) 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/simgos-sync-use-case/encounter/middleware.go b/internal/use-case/simgos-sync-use-case/encounter/middleware.go new file mode 100644 index 00000000..8a047d33 --- /dev/null +++ b/internal/use-case/simgos-sync-use-case/encounter/middleware.go @@ -0,0 +1,9 @@ +package encounter + +// 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/simgos-sync-use-case/encounter/tycovar.go b/internal/use-case/simgos-sync-use-case/encounter/tycovar.go new file mode 100644 index 00000000..17890a1b --- /dev/null +++ b/internal/use-case/simgos-sync-use-case/encounter/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 encounter + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/encounter" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.Encounter, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.Encounter, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.Encounter, 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/simgos-sync-use-case/patient/case.go b/internal/use-case/simgos-sync-use-case/patient/case.go index d403a8ac..104e79e9 100644 --- a/internal/use-case/simgos-sync-use-case/patient/case.go +++ b/internal/use-case/simgos-sync-use-case/patient/case.go @@ -211,7 +211,7 @@ func GenerateNomr() (*d.Data, error) { // Start log pl.SetLogInfo(&event, nil, "started", "create") - norm, err := generateNomrPatient() + norm, err := generateNomrPatient("12") if err != nil { return nil, err } diff --git a/internal/use-case/simgos-sync-use-case/patient/helper.go b/internal/use-case/simgos-sync-use-case/patient/helper.go index 0bffcc9b..8ab4c326 100644 --- a/internal/use-case/simgos-sync-use-case/patient/helper.go +++ b/internal/use-case/simgos-sync-use-case/patient/helper.go @@ -39,21 +39,33 @@ func setDataSimgos(input *e.Patient) (data esimgos.MPasien) { if input.Person != nil && input.Person.Relatives != nil { mapRelative(input.Person.Relatives, &data) + } else { + data.PenanggungjawabHubungan = "DIRI SENDIRI" + data.PenanggungjawabNama = input.Person.Name + data.PenanggungjawabPhone = data.Notelp + data.PenanggungjawabAlamat = data.Alamat } data.Nip = input.RegisteredBy_User_Name data.Tgldaftar = input.RegisteredAt data.Nomr = *input.Number + nomrBaru, _ := generateNomrPatient("00") + data.NomrBaru = &nomrBaru + return } -func generateNomrPatient() (string, error) { - const prefix = "12" // fixed starting prefix (same as $awal_rm) in simgos +func generateNomrPatient(prefix string) (string, error) { const maxSuffix = 999999 + column := "nomr" + if prefix == "00" { + column = "nomr_baru" + } + type Row struct { - Nomr string `gorm:"column:nomr"` + Value string `gorm:"column:value"` } var r Row @@ -61,31 +73,35 @@ func generateNomrPatient() (string, error) { // Lock rows for this prefix → prevents race condition if err := dg.IS["simrs"]. Table("m_pasien"). - Select("nomr"). - Where("nomr LIKE ?", prefix+"%"). - Order("nomr DESC"). + Select(fmt.Sprintf("%s AS value", column)). + Where(fmt.Sprintf("%s LIKE ?", column), prefix+"%"). + Order(fmt.Sprintf("%s DESC", column)). Clauses(clause.Locking{Strength: "UPDATE"}). Limit(1). Scan(&r).Error; err != nil { return "", err } - // No existing NOMR → start fresh - if r.Nomr == "" { + // No existing number → start at 000001 + if r.Value == "" || len(r.Value) < len(prefix) { return prefix + "000001", nil } - suffix := r.Nomr[len(prefix):] // extract numeric part - num, _ := strconv.Atoi(suffix) - - // 3. If suffix reaches 999999 → increment the prefix - if num == maxSuffix { - p, _ := strconv.Atoi(prefix) - p++ - return fmt.Sprintf("%d000001", p), nil + suffix := r.Value[len(prefix):] + num, err := strconv.Atoi(suffix) + if err != nil { + // data corrupt, reset nomor + return prefix + "000001", nil } - // 4. Normal increment + // If reached 999999 → increment prefix + if num >= maxSuffix { + p, _ := strconv.Atoi(prefix) + p++ + return fmt.Sprintf("%02d000001", p), nil + } + + // Normal increment return prefix + fmt.Sprintf("%06d", num+1), nil } @@ -262,30 +278,47 @@ func mapRelative(relative *[]epr.PersonRelative, data *esimgos.MPasien) { return } - r := (*relative)[0] - data.PenanggungjawabNama = *r.Name + for _, r := range *relative { + if r.Responsible { + data.PenanggungjawabNama = *r.Name - switch r.Relationship_Code { - case erp.RCMother, erp.RCFather: - data.PenanggungjawabHubungan = "ORANG TUA" - switch { - case r.Relationship_Code == erp.RCMother: - data.NamaIbu = *r.Name - eduCode, _ := setEducation(r.Education_Code) - data.PendidikanIbu = strconv.Itoa(int(eduCode)) - case r.Relationship_Code == erp.RCFather: - data.NamaAyah = *r.Name - eduCode, _ := setEducation(r.Education_Code) - data.PendidikanAyah = strconv.Itoa(int(eduCode)) + switch r.Relationship_Code { + case erp.RCMother, erp.RCFather: + data.PenanggungjawabHubungan = "ORANG TUA" + switch { + case r.Relationship_Code == erp.RCMother: + data.NamaIbu = *r.Name + eduCode, _ := setEducation(r.Education_Code) + data.PendidikanIbu = strconv.Itoa(int(eduCode)) + case r.Relationship_Code == erp.RCFather: + data.NamaAyah = *r.Name + eduCode, _ := setEducation(r.Education_Code) + data.PendidikanAyah = strconv.Itoa(int(eduCode)) + } + case erp.RCChild: + data.PenanggungjawabHubungan = "ANAK" + case erp.RCSibling: + data.PenanggungjawabHubungan = "SAUDARA" + case erp.RCUncle, erp.RCAunt, + erp.RCGdMother, erp.RCGdFather, + erp.RCNephew, erp.RCGdChild: + data.PenanggungjawabHubungan = "KELUARGA LAIN" + case erp.RCFriend: + data.PenanggungjawabHubungan = "TEMAN" + case erp.RCSpouse: + data.PenanggungjawabHubungan = "SUAMI/ISTRI" + case erp.RCSelf: + data.PenanggungjawabHubungan = "DIRI SENDIRI" + default: + data.PenanggungjawabHubungan = "LAINNYA ..." + } + + data.PenanggungjawabAlamat = *r.Address + data.PenanggungjawabPhone = *r.PhoneNumber + + break } - case erp.RCChild: - data.PenanggungjawabHubungan = "ANAK" - default: - data.PenanggungjawabHubungan = "LAINNYA" } - - data.PenanggungjawabAlamat = *r.Address - data.PenanggungjawabPhone = *r.PhoneNumber } func setDataSimxLog(input *esyncLog.SimxLogDto) (data esync.PatientSimxLog) { From dd5f6f78e34eb493747e26da8c746e0b09741358 Mon Sep 17 00:00:00 2001 From: vanilia Date: Fri, 21 Nov 2025 10:21:21 +0700 Subject: [PATCH 06/29] add cud sync encounter --- .../domain/main-entities/encounter/entity.go | 2 +- .../domain/references/encounter/encounter.go | 1 + .../simgos-entities/encounter/entity.go | 2 +- .../use-case/main-use-case/encounter/case.go | 198 +++++++++++------- .../encounter/middleware-runner.go | 71 +++++-- .../main-use-case/encounter/tycovar.go | 30 ++- .../use-case/main-use-case/patient/case.go | 2 - .../simgos-sync-use-case/encounter/case.go | 24 +-- .../simgos-sync-use-case/encounter/helper.go | 141 ++++++++++++- .../simgos-sync-use-case/encounter/lib.go | 28 +-- 10 files changed, 364 insertions(+), 135 deletions(-) diff --git a/internal/domain/main-entities/encounter/entity.go b/internal/domain/main-entities/encounter/entity.go index c188bb1a..4d814ed0 100644 --- a/internal/domain/main-entities/encounter/entity.go +++ b/internal/domain/main-entities/encounter/entity.go @@ -40,7 +40,7 @@ type Encounter struct { VisitDate time.Time `json:"visitDate"` StartedAt *time.Time `json:"startedAt"` FinishedAt *time.Time `json:"finishedAt"` - PaymentMethod_Code erc.PaymentMethodCode `json:"paymentMethod_code" gorm:"size:10"` + PaymentMethod_Code ere.AllPaymentMethodCode `json:"paymentMethod_code" gorm:"size:10"` InsuranceCompany_Code *string `json:"insuranceCompany_code"` InsuranceCompany *ei.InsuranceCompany `json:"insuranceCompany,omitempty" gorm:"foreignKey:InsuranceCompany_Code;references:Code"` Member_Number *string `json:"memberNumber" gorm:"unique;size:20"` diff --git a/internal/domain/references/encounter/encounter.go b/internal/domain/references/encounter/encounter.go index 532e1625..66025373 100644 --- a/internal/domain/references/encounter/encounter.go +++ b/internal/domain/references/encounter/encounter.go @@ -99,6 +99,7 @@ const ( APMCJkmm AllPaymentMethodCode = "jkmm" // JKMM APMCSpm AllPaymentMethodCode = "spm" // SPM APMCPks AllPaymentMethodCode = "pks" // PKS + APMCUmum AllPaymentMethodCode = "umum" SRTCInternal SEPRefTypeCode = "internal" // Rujukan Internal SRTCExternal SEPRefTypeCode = "external" // Faskes Lain diff --git a/internal/domain/simgos-entities/encounter/entity.go b/internal/domain/simgos-entities/encounter/entity.go index 16ba873e..cd8f538d 100644 --- a/internal/domain/simgos-entities/encounter/entity.go +++ b/internal/domain/simgos-entities/encounter/entity.go @@ -16,7 +16,7 @@ type TPendaftaran struct { KeteranganStatus *uint `json:"keterangan_status" gorm:"column:keterangan_status"` Pasienbaru uint `json:"pasienbaru" gorm:"column:pasienbaru"` Nip string `json:"nip" gorm:"column:nip"` - Idxdaftar uint `json:"idxdaftar" gorm:"column:idxdaftar"` + Idxdaftar uint `json:"idxdaftar" gorm:"primaryKey;autoIncrement;column:idxdaftar"` Masukpoly *time.Time `json:"masukpoly" gorm:"column:masukpoly"` Keluarpoly *time.Time `json:"keluarpoly" gorm:"column:keluarpoly"` Ketrujuk string `json:"ketrujuk" gorm:"column:ketrujuk"` diff --git a/internal/use-case/main-use-case/encounter/case.go b/internal/use-case/main-use-case/encounter/case.go index e3d564ff..37e08bc5 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -3,7 +3,6 @@ package encounter import ( "errors" "fmt" - us "simrs-vx/internal/use-case/main-use-case/soapi" "strconv" "time" @@ -24,11 +23,13 @@ import ( eir "simrs-vx/internal/domain/main-entities/internal-reference" erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist" es "simrs-vx/internal/domain/main-entities/soapi" + esync "simrs-vx/internal/domain/sync-entities/log" uaeh "simrs-vx/internal/use-case/main-use-case/adm-employee-hist" udc "simrs-vx/internal/use-case/main-use-case/death-cause" uir "simrs-vx/internal/use-case/main-use-case/internal-reference" urdh "simrs-vx/internal/use-case/main-use-case/responsible-doctor-hist" + us "simrs-vx/internal/use-case/main-use-case/soapi" ) const source = "encounter" @@ -50,34 +51,6 @@ func Create(input e.CreateDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "create") - // validate rehab bpjs - if input.RefTypeCode == ere.RTCBpjs && input.Class_Code == ere.ECAmbulatory && ere.AmbulatoryClassCode(*input.SubClass_Code) == ere.ACCRehab { - // get latest rehab data - recentRehabData, err := getLatestRehabData(input, &event) - if err != nil { - return nil, err - } - - if recentRehabData != nil { - // determine VisitModeCode - input.VisitMode_Code, input.RecentEncounterAdm, err = determineVisitMode(recentRehabData, input, &event) - if err != nil { - return nil, err - } - } else { - input.VisitMode_Code = ere.VMCAdm - } - - // if visitMode_Code is series, then get data soapi for copy - if input.VisitMode_Code == ere.VMCSeries { - // get data soapi - recentSoapiDataforCopy, err = getSoapiEncounterAdm(*input.RecentEncounterAdm, &event) - if err != nil { - return nil, err - } - } - } - // check if user has employee position if !input.AuthInfo.HasEmployeePosition() { event.Status = "failed" @@ -102,17 +75,44 @@ func Create(input e.CreateDto) (*d.Data, error) { input.Adm_Employee_Id = input.AuthInfo.Employee_Id } - // check if patient is new in the hospital - input.NewStatus, err = identifyPatientStatus(input) - - 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 + // validate rehab by bpjs + if input.RefTypeCode == ere.RTCBpjs && + input.Class_Code == ere.ECAmbulatory && + ere.AmbulatoryClassCode(*input.SubClass_Code) == ere.ACCRehab { + // get latest rehab data + recentRehabData, err := getLatestRehabData(input, &event) + if err != nil { + return nil, err } + // If recentRehabData is nil, then visitMode_Code = "adm" + if recentRehabData != nil { + // If recentRehabData is not nil, determine the visitMode_Code: + // If the mode is "series", verify whether the visit count still remains + // and whether the series has not expired. + input.VisitMode_Code, input.RecentEncounterAdm, err = determineVisitMode(recentRehabData, input, &event) + if err != nil { + return nil, err + } + } else { + input.VisitMode_Code = ere.VMCAdm + } + + // When visitMode_Code is "series", load the associated SOAPI record to copy its values. + if input.VisitMode_Code == ere.VMCSeries { + // get data soapi + recentSoapiDataforCopy, err = getSoapiEncounterAdm(*input.RecentEncounterAdm, &event) + if err != nil { + return nil, err + } + } + } + + // check if patient is new in the hospital + input.NewStatus, err = identifyPatientStatus(input) + mwRunner := newMiddlewareRunner(&event) + + err = dg.I.Transaction(func(tx *gorm.DB) error { // create encounter if resData, err := CreateData(input, &event, tx); err != nil { return err @@ -128,28 +128,36 @@ func Create(input e.CreateDto) (*d.Data, error) { } // insert adm_employee_hist - if _, err := uaeh.CreateData(eaeh.CreateDto{ + if _, err = uaeh.CreateData(eaeh.CreateDto{ Encounter_Id: &data.Id, Employee_Id: data.Adm_Employee_Id, StartedAt: &now}, &event, tx); err != nil { return err } - mwRunner.setMwType(pu.MWTPost) - // Run post-middleware - if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + dataEncounter, err := ReadDetailData(e.ReadDetailDto{ + Id: uint16(data.Id), + Includes: "Adm_Employee.User,Patient.Person.Relatives,Patient.Person.VclaimMember"}, + &event, tx) + if err != nil { return err } - pl.SetLogInfo(&event, nil, "complete") + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, dataEncounter); err != nil { + return err + } return nil }) - if err != nil { + if err = runLogMiddleware(err, input, mwRunner); err != nil { return nil, err } + pl.SetLogInfo(&event, nil, "complete") + return &d.Data{ Meta: d.II{ "source": source, @@ -175,7 +183,7 @@ func ReadList(input e.ReadListDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "readList") err = dg.I.Transaction(func(tx *gorm.DB) error { - mwRunner := newMiddlewareRunner(&event, tx) + mwRunner := newMiddlewareRunner(&event) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { @@ -226,7 +234,7 @@ func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "readDetail") err = dg.I.Transaction(func(tx *gorm.DB) error { - mwRunner := newMiddlewareRunner(&event, tx) + mwRunner := newMiddlewareRunner(&event) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { @@ -261,7 +269,7 @@ func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { } func Update(input e.UpdateDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id)} + rdDto := e.ReadDetailDto{Id: input.Id} var data *e.Encounter var err error @@ -272,6 +280,7 @@ func Update(input e.UpdateDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "update") + mwRunner := newMiddlewareRunner(&event) err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") @@ -289,32 +298,33 @@ func Update(input e.UpdateDto) (*d.Data, error) { return pl.SetLogError(&event, input) } - 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") + dataEncounter, err := ReadDetailData(e.ReadDetailDto{ + Id: uint16(data.Id), + Includes: "Adm_Employee.User,Patient.Person.Relatives,Patient.Person.VclaimMember"}, + &event, tx) + if err != nil { + return err + } - mwRunner.setMwType(pu.MWTPost) + mwRunner.setMwType(pu.MWTPre) // Run post-middleware - if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + if err := mwRunner.RunUpdateMiddleware(updatePreMw, dataEncounter); err != nil { return err } return nil }) - if err != nil { + if err = runLogMiddleware(err, input, mwRunner); err != nil { return nil, err } + pl.SetLogInfo(&event, nil, "complete") + return &d.Data{ Meta: d.IS{ "source": source, @@ -327,7 +337,7 @@ func Update(input e.UpdateDto) (*d.Data, error) { } func Delete(input e.DeleteDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id)} + rdDto := e.ReadDetailDto{Id: input.Id} var data *e.Encounter var err error @@ -361,36 +371,33 @@ func Delete(input e.DeleteDto) (*d.Data, error) { return nil, pl.SetLogError(&event, input) } + mwRunner := newMiddlewareRunner(&event) + 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 { + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunDeleteMiddleware(deletePreMw, &input); err != nil { return err } return nil }) - if err != nil { + if err = runLogMiddleware(err, input, mwRunner); err != nil { return nil, err } + pl.SetLogInfo(&event, nil, "complete") + return &d.Data{ Meta: d.IS{ "source": source, @@ -581,12 +588,12 @@ func UpdateStatusCode(input e.UpdateStatusDto) (*d.Data, error) { } } - mwRunner := newMiddlewareRunner(&event, tx) - mwRunner.setMwType(pu.MWTPre) - // Run pre-middleware - if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { - return err - } + //mwRunner := newMiddlewareRunner(&event) + //mwRunner.setMwType(pu.MWTPre) + //// Run pre-middleware + //if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { + // return err + //} if err := UpdateStatusData(input, data, &event, tx); err != nil { return err @@ -594,11 +601,11 @@ func UpdateStatusCode(input e.UpdateStatusDto) (*d.Data, error) { pl.SetLogInfo(&event, nil, "complete") - mwRunner.setMwType(pu.MWTPost) - // Run post-middleware - if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { - return err - } + //mwRunner.setMwType(pu.MWTPost) + //// Run post-middleware + //if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { + // return err + //} return nil }) @@ -915,3 +922,32 @@ func ApproveSwitchUnit(input e.ApproveUnitDto) (*d.Data, error) { }, nil } + +func runLogMiddleware(err error, input any, mwRunner *middlewareRunner) error { + var errMsg string + inputLog := esync.SimxLogDto{ + Payload: input, + Method: erc.CCCreate, + } + + if err != nil { + // Run log-middleware + errMsg = err.Error() + inputLog.ErrMessage = &errMsg + inputLog.IsSuccess = false + + // create log failed + if errMiddleware := mwRunner.RunCreateLogMiddleware(createSimxLogMw, &inputLog); errMiddleware != nil { + return errMiddleware + } + return err + } + + // create log success + inputLog.IsSuccess = true + if err = mwRunner.RunCreateLogMiddleware(createSimxLogMw, &inputLog); err != nil { + return err + } + + return nil +} diff --git a/internal/use-case/main-use-case/encounter/middleware-runner.go b/internal/use-case/main-use-case/encounter/middleware-runner.go index 1448a7af..b030d286 100644 --- a/internal/use-case/main-use-case/encounter/middleware-runner.go +++ b/internal/use-case/main-use-case/encounter/middleware-runner.go @@ -1,35 +1,64 @@ package encounter import ( - e "simrs-vx/internal/domain/main-entities/encounter" pl "simrs-vx/pkg/logger" pu "simrs-vx/pkg/use-case-helper" "gorm.io/gorm" + + sync "simrs-vx/internal/infra/sync-cfg" + + e "simrs-vx/internal/domain/main-entities/encounter" + esync "simrs-vx/internal/domain/sync-entities/log" ) type middlewareRunner struct { Event *pl.Event Tx *gorm.DB MwType pu.MWType + SyncOn bool } // NewMiddlewareExecutor creates a new middleware executor -func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner { +func newMiddlewareRunner(event *pl.Event) *middlewareRunner { return &middlewareRunner{ - Event: event, - Tx: tx, + Event: event, + SyncOn: sync.O.Enable, } } // ExecuteCreateMiddleware executes create middleware -func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.Encounter) error { +func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.Encounter) error { + if !me.SyncOn { + return nil + } + for _, middleware := range middlewares { - logData := pu.GetLogData(input, data) + logData := pu.GetLogData(input, nil) pl.SetLogInfo(me.Event, logData, "started", middleware.Name) - if err := middleware.Func(input, data, me.Tx); err != nil { + if err := middleware.Func(input); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + } + return nil +} + +// ExecuteCreateMiddleware executes create middleware +func (me *middlewareRunner) RunCreateLogMiddleware(middlewares []createLogMw, input *esync.SimxLogDto) error { + if !me.SyncOn { + return nil + } + + for _, middleware := range middlewares { + logData := pu.GetLogData(input, nil) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input); err != nil { return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) } @@ -39,6 +68,10 @@ func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e } func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.Encounter) error { + if !me.SyncOn { + return nil + } + for _, middleware := range middlewares { logData := pu.GetLogData(input, data) @@ -54,6 +87,10 @@ func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, inpu } func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.Encounter) error { + if !me.SyncOn { + return nil + } + for _, middleware := range middlewares { logData := pu.GetLogData(input, data) @@ -68,13 +105,17 @@ func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, return nil } -func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.Encounter) error { +func (me *middlewareRunner) RunUpdateMiddleware(middlewares []updateMw, input *e.Encounter) error { + if !me.SyncOn { + return nil + } + for _, middleware := range middlewares { - logData := pu.GetLogData(input, data) + logData := pu.GetLogData(input, nil) pl.SetLogInfo(me.Event, logData, "started", middleware.Name) - if err := middleware.Func(input, data, me.Tx); err != nil { + if err := middleware.Func(input); err != nil { return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) } @@ -83,13 +124,17 @@ func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, inpu return nil } -func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.Encounter) error { +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []deleteMw, input *e.DeleteDto) error { + if !me.SyncOn { + return nil + } + for _, middleware := range middlewares { - logData := pu.GetLogData(input, data) + logData := pu.GetLogData(input, nil) pl.SetLogInfo(me.Event, logData, "started", middleware.Name) - if err := middleware.Func(input, data, me.Tx); err != nil { + if err := middleware.Func(input); err != nil { return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) } diff --git a/internal/use-case/main-use-case/encounter/tycovar.go b/internal/use-case/main-use-case/encounter/tycovar.go index 17890a1b..f618bd19 100644 --- a/internal/use-case/main-use-case/encounter/tycovar.go +++ b/internal/use-case/main-use-case/encounter/tycovar.go @@ -12,13 +12,28 @@ import ( "gorm.io/gorm" e "simrs-vx/internal/domain/main-entities/encounter" + elog "simrs-vx/internal/domain/sync-entities/log" ) type createMw struct { Name string - Func func(input *e.CreateDto, data *e.Encounter, tx *gorm.DB) error + Func func(input *e.Encounter) error } +type createLogMw struct { + Name string + Func func(input *elog.SimxLogDto) error +} + +type updateMw struct { + Name string + Func func(input *e.Encounter) error +} + +type deleteMw struct { + Name string + Func func(input *e.DeleteDto) error +} type readListMw struct { Name string Func func(input *e.ReadListDto, data *e.Encounter, tx *gorm.DB) error @@ -29,16 +44,17 @@ type readDetailMw struct { Func func(input *e.ReadDetailDto, data *e.Encounter, tx *gorm.DB) error } -type UpdateMw = readDetailMw -type DeleteMw = readDetailMw +type UpdateMw = updateMw +type DeleteMw = deleteMw -var createPreMw []createMw // preprocess middleware -var createPostMw []createMw // postprocess middleware +var createPreMw []createMw // preprocess middleware +var createPostMw []createMw // postprocess middleware +var createSimxLogMw []createLogMw var readListPreMw []readListMw // .. var readListPostMw []readListMw // .. var readDetailPreMw []readDetailMw var readDetailPostMw []readDetailMw -var updatePreMw []readDetailMw +var updatePreMw []updateMw var updatePostMw []readDetailMw -var deletePreMw []readDetailMw +var deletePreMw []deleteMw var deletePostMw []readDetailMw diff --git a/internal/use-case/main-use-case/patient/case.go b/internal/use-case/main-use-case/patient/case.go index 0ed93bbe..24aa4394 100644 --- a/internal/use-case/main-use-case/patient/case.go +++ b/internal/use-case/main-use-case/patient/case.go @@ -351,8 +351,6 @@ func Delete(input e.DeleteDto) (*d.Data, error) { return err } - input.Id = data.Id - // Delete Patient if err := DeleteData(data, &event, tx); err != nil { return err diff --git a/internal/use-case/simgos-sync-use-case/encounter/case.go b/internal/use-case/simgos-sync-use-case/encounter/case.go index 57650594..591dae36 100644 --- a/internal/use-case/simgos-sync-use-case/encounter/case.go +++ b/internal/use-case/simgos-sync-use-case/encounter/case.go @@ -8,18 +8,18 @@ import ( db "simrs-vx/pkg/dualtrx-helper" - e "simrs-vx/internal/domain/main-entities/division" - esimgos "simrs-vx/internal/domain/simgos-entities/division" - esync "simrs-vx/internal/domain/sync-entities/division" + e "simrs-vx/internal/domain/main-entities/encounter" + esimgos "simrs-vx/internal/domain/simgos-entities/encounter" + esync "simrs-vx/internal/domain/sync-entities/encounter" elog "simrs-vx/internal/domain/sync-entities/log" ) -const source = "division" +const source = "encounter" -func Create(input e.CreateDto) (*d.Data, error) { +func Create(input e.Encounter) (*d.Data, error) { var ( - sgData *esimgos.MUnit - syncLink *esync.DivisionLink + sgData *esimgos.TPendaftaran + syncLink *esync.EncounterLink err error ) @@ -39,7 +39,7 @@ func Create(input e.CreateDto) (*d.Data, error) { } // STEP 2: Insert to Link - syncLink, err = CreateLinkData(*input.Id, sgData.KodeUnit, &event, tx.Sync) + syncLink, err = CreateLinkData(input.Id, sgData.Idxdaftar, &event, tx.Sync) if err != nil { return err } @@ -99,7 +99,7 @@ func CreateSimxLog(input elog.SimxLogDto) (*d.Data, error) { }, nil } -func Update(input e.UpdateDto) (*d.Data, error) { +func Update(input e.Encounter) (*d.Data, error) { event := pl.Event{ Feature: "Update", Source: source, @@ -109,7 +109,7 @@ func Update(input e.UpdateDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "update") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(*input.Id, &event) + syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) if err != nil { return nil, err } @@ -147,7 +147,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "delete") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(*input.Id, &event) + syncLink, err := ReadDetailLinkData(input.Id, &event) if err != nil { return nil, err } @@ -178,7 +178,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { if err != nil { if isLinkDeleted { go func() { - _, _ = CreateLinkData(uint(*input.Id), sgData.KodeUnit, &event) + _, _ = CreateLinkData(uint(input.Id), sgData.Idxdaftar, &event) }() } return nil, err diff --git a/internal/use-case/simgos-sync-use-case/encounter/helper.go b/internal/use-case/simgos-sync-use-case/encounter/helper.go index 6fb47ac9..b9daa7de 100644 --- a/internal/use-case/simgos-sync-use-case/encounter/helper.go +++ b/internal/use-case/simgos-sync-use-case/encounter/helper.go @@ -6,6 +6,8 @@ package encounter import ( "encoding/json" + epr "simrs-vx/internal/domain/main-entities/person-relative" + erp "simrs-vx/internal/domain/references/person" "strconv" erc "simrs-vx/internal/domain/references/common" @@ -14,21 +16,68 @@ import ( e "simrs-vx/internal/domain/main-entities/encounter" esimgos "simrs-vx/internal/domain/simgos-entities/encounter" esync "simrs-vx/internal/domain/sync-entities/encounter" - esyncLog "simrs-vx/internal/domain/sync-entities/encounter" + esyncLog "simrs-vx/internal/domain/sync-entities/log" ) func setDataSimgos(input *e.Encounter) (data esimgos.TPendaftaran) { data.Nomr = *input.Patient.Number data.Tglreg = input.RegisteredAt + // set kddokter kddokter, _ := strconv.Atoi(*input.Appointment_Doctor_Code) data.Kddokter = uint(kddokter) + // set kdpoly kdpoly, _ := strconv.Atoi(*input.Unit_Code) data.Kdpoly = uint(kdpoly) + // set kdrujuk setKdrujuk(input.RefType_Code, data) - + + // set kdcarabayar + setKdcarabayar(input.PaymentMethod_Code, data) + + // TODO: shift + + // set status + setStatus(input.Discharge_Method_Code, data) + if input.Status_Code == erc.DSCCancel { + data.Status = 11 + } + + // set pasienbaru + if input.NewStatus { + data.Pasienbaru = 1 + } + + if emp := input.Adm_Employee; emp != nil { + if user := emp.User; user != nil { + data.Nip = user.Name + } + } + + data.Masukpoly = input.StartedAt + data.Keluarpoly = input.FinishedAt + + if input.RefSource_Name != nil { + data.Ketrujuk = *input.RefSource_Name + } else { + data.Ketrujuk = "TR" + } + + // TODO: preload person.relative, person.vclaimMember + if p := input.Patient.Person; p != nil { + mapRelative(p.Relatives, data) + + if bpjs := p.VclaimMember; bpjs != nil { + data.Nokartu = *bpjs.CardNumber + } + } + + data.Jamreg = input.RegisteredAt + + // TODO: norujukan, tglrujukan, dokumen_pendukung sep_file + return } @@ -45,7 +94,91 @@ func setKdrujuk(input *ere.RefTypeCode, data esimgos.TPendaftaran) { } } -func setDataSimxLog(input *esyncLog.SimxLogDto) (data esync.DivisionSimxLog) { +func setKdcarabayar(input ere.AllPaymentMethodCode, data esimgos.TPendaftaran) { + switch input { + case ere.APMCPks: + data.Kdcarabayar = 12 + case ere.APMCJkn: + data.Kdcarabayar = 5 + case ere.APMCJkmm: + data.Kdcarabayar = 9 + case ere.APMCSpm: + data.Kdcarabayar = 10 + default: + data.Kdcarabayar = 1 + } +} + +func setStatus(code *ere.DischargeMethodCode, data esimgos.TPendaftaran) { + switch *code { + case ere.DMCHome: + data.Status = 1 + case ere.DMCInpatient: + data.Status = 2 + case ere.DMCDeathOnArrival: + data.Status = 3 + case ere.DMCDeath: + data.Status = 8 + case ere.DMCHomeReq: + data.Status = 7 + case ere.DMCConsulPoly: + data.Status = 5 + case ere.DMCExtRef: + data.Status = 6 + case ere.DMCConsulChDay: + data.Status = 12 + case ere.DMCEmergency: + data.Status = 10 + case ere.DMCEmergencyCovid: + data.Status = 13 + case ere.DMCConsulExecutive: + data.Status = 15 + case ere.DMCConsulBack: + data.Status = 100 + default: + data.Status = 0 + } +} + +func mapRelative(relative *[]epr.PersonRelative, data esimgos.TPendaftaran) { + if relative == nil || len(*relative) == 0 { + return + } + + for _, r := range *relative { + if r.Responsible { + data.PenanggungjawabNama = *r.Name + + switch r.Relationship_Code { + case erp.RCMother, erp.RCFather: + data.PenanggungjawabHubungan = "ORANG TUA" + case erp.RCChild: + data.PenanggungjawabHubungan = "ANAK" + case erp.RCSibling: + data.PenanggungjawabHubungan = "SAUDARA" + case erp.RCUncle, erp.RCAunt, + erp.RCGdMother, erp.RCGdFather, + erp.RCNephew, erp.RCGdChild: + data.PenanggungjawabHubungan = "KELUARGA LAIN" + case erp.RCFriend: + data.PenanggungjawabHubungan = "TEMAN" + case erp.RCSpouse: + data.PenanggungjawabHubungan = "SUAMI/ISTRI" + case erp.RCSelf: + data.PenanggungjawabHubungan = "DIRI SENDIRI" + default: + data.PenanggungjawabHubungan = "LAINNYA ..." + } + + data.PenanggungjawabAlamat = *r.Address + data.PenanggungjawabPhone = *r.PhoneNumber + + break + } + } +} + +func setDataSimxLog(input *esyncLog.SimxLogDto) (data esync.EncounterSimxLog) { // encode to JSON jsonData, _ := json.MarshalIndent(input.Payload, "", " ") jsonString := string(jsonData) @@ -67,7 +200,7 @@ func setDataSimxLog(input *esyncLog.SimxLogDto) (data esync.DivisionSimxLog) { return } -func setDataSimxLink(simxId, simgosId uint) (data esync.DivisionLink) { +func setDataSimxLink(simxId, simgosId uint) (data esync.EncounterLink) { data.Simx_Id = simxId data.Simgos_Id = simgosId return diff --git a/internal/use-case/simgos-sync-use-case/encounter/lib.go b/internal/use-case/simgos-sync-use-case/encounter/lib.go index f713ca6c..9ccf433c 100644 --- a/internal/use-case/simgos-sync-use-case/encounter/lib.go +++ b/internal/use-case/simgos-sync-use-case/encounter/lib.go @@ -9,15 +9,15 @@ import ( dg "github.com/karincake/apem/db-gorm-pg" "gorm.io/gorm" - e "simrs-vx/internal/domain/main-entities/division" - esimgos "simrs-vx/internal/domain/simgos-entities/division" - esync "simrs-vx/internal/domain/sync-entities/division" + e "simrs-vx/internal/domain/main-entities/encounter" + esimgos "simrs-vx/internal/domain/simgos-entities/encounter" + esync "simrs-vx/internal/domain/sync-entities/encounter" esynclog "simrs-vx/internal/domain/sync-entities/log" ) var now = time.Now() -func CreateSimgosData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*esimgos.MUnit, error) { +func CreateSimgosData(input e.Encounter, event *pl.Event, dbx ...*gorm.DB) (*esimgos.TPendaftaran, error) { pl.SetLogInfo(event, nil, "started", "DBCreate") data := setDataSimgos(&input) @@ -37,14 +37,14 @@ func CreateSimgosData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*esi return &data, nil } -func ReadDetailSimgosData(simgosId uint16, event *pl.Event) (*esimgos.MUnit, error) { +func ReadDetailSimgosData(simgosId uint16, event *pl.Event) (*esimgos.TPendaftaran, error) { pl.SetLogInfo(event, simgosId, "started", "DBReadDetail") - data := esimgos.MUnit{} + data := esimgos.TPendaftaran{} var tx = dg.IS["simrs"] if err := tx. - Where("\"kode_unit\" = ?", simgosId). + Where("\"idxdaftar\" = ?", simgosId). First(&data).Error; err != nil { if processedErr := pu.HandleReadError(err, event, source, simgosId, data); processedErr != nil { return nil, processedErr @@ -55,11 +55,11 @@ func ReadDetailSimgosData(simgosId uint16, event *pl.Event) (*esimgos.MUnit, err return &data, nil } -func UpdateSimgosData(input e.UpdateDto, dataSimgos *esync.DivisionLink, event *pl.Event, dbx ...*gorm.DB) error { +func UpdateSimgosData(input e.Encounter, dataSimgos *esync.EncounterLink, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, input, "started", "DBUpdate") data := setDataSimgos(&input) - data.KodeUnit = dataSimgos.Simgos_Id + data.Idxdaftar = dataSimgos.Simgos_Id var tx *gorm.DB if len(dbx) > 0 { @@ -82,7 +82,7 @@ func UpdateSimgosData(input e.UpdateDto, dataSimgos *esync.DivisionLink, event * return nil } -func HardDeleteSimgosData(data *esimgos.MUnit, event *pl.Event, dbx ...*gorm.DB) error { +func HardDeleteSimgosData(data *esimgos.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBDelete") var tx *gorm.DB @@ -107,7 +107,7 @@ func HardDeleteSimgosData(data *esimgos.MUnit, event *pl.Event, dbx ...*gorm.DB) return nil } -func CreateLinkData(simxId, simgosId uint, event *pl.Event, dbx ...*gorm.DB) (*esync.DivisionLink, error) { +func CreateLinkData(simxId, simgosId uint, event *pl.Event, dbx ...*gorm.DB) (*esync.EncounterLink, error) { pl.SetLogInfo(event, nil, "started", "DBCreate") data := setDataSimxLink(simxId, simgosId) @@ -126,9 +126,9 @@ func CreateLinkData(simxId, simgosId uint, event *pl.Event, dbx ...*gorm.DB) (*e return &data, nil } -func ReadDetailLinkData(simxId uint16, event *pl.Event) (*esync.DivisionLink, error) { +func ReadDetailLinkData(simxId uint16, event *pl.Event) (*esync.EncounterLink, error) { pl.SetLogInfo(event, simxId, "started", "DBReadDetail") - data := esync.DivisionLink{} + data := esync.EncounterLink{} var tx = dg.I @@ -144,7 +144,7 @@ func ReadDetailLinkData(simxId uint16, event *pl.Event) (*esync.DivisionLink, er return &data, nil } -func DeleteLinkData(data *esync.DivisionLink, event *pl.Event, dbx ...*gorm.DB) error { +func DeleteLinkData(data *esync.EncounterLink, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBDelete") var tx *gorm.DB From c997c2472eb9107bb320917daea466320f272970 Mon Sep 17 00:00:00 2001 From: vanilia Date: Fri, 21 Nov 2025 20:21:28 +0700 Subject: [PATCH 07/29] add vclaim reference --- .../domain/main-entities/encounter/dto.go | 57 ++-- .../bpjs-use-case/vclaim-reference/case.go | 277 ++++++++++++++++++ .../bpjs-use-case/vclaim-reference/helper.go | 25 ++ .../bpjs-use-case/vclaim-reference/lib.go | 140 +++++++++ .../vclaim-reference/middleware-runner.go | 103 +++++++ .../vclaim-reference/middleware.go | 9 + .../bpjs-use-case/vclaim-reference/tycovar.go | 44 +++ .../simgos-sync-use-case/encounter/helper.go | 90 +++++- 8 files changed, 709 insertions(+), 36 deletions(-) create mode 100644 internal/use-case/bpjs-use-case/vclaim-reference/case.go create mode 100644 internal/use-case/bpjs-use-case/vclaim-reference/helper.go create mode 100644 internal/use-case/bpjs-use-case/vclaim-reference/lib.go create mode 100644 internal/use-case/bpjs-use-case/vclaim-reference/middleware-runner.go create mode 100644 internal/use-case/bpjs-use-case/vclaim-reference/middleware.go create mode 100644 internal/use-case/bpjs-use-case/vclaim-reference/tycovar.go diff --git a/internal/domain/main-entities/encounter/dto.go b/internal/domain/main-entities/encounter/dto.go index f43a1de9..e2fe33ac 100644 --- a/internal/domain/main-entities/encounter/dto.go +++ b/internal/domain/main-entities/encounter/dto.go @@ -34,27 +34,28 @@ import ( ) type CreateDto struct { - Patient_Id *uint `json:"patient_id"` - 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 - Infra_Code *string `json:"infra_code"` // for inpatient - Unit_Code *string `json:"unit_code"` - Specialist_Code *string `json:"specialist_code"` - Subspecialist_Code *string `json:"subspecialist_code"` - VisitDate time.Time `json:"visitDate"` - PaymentMethod_Code erc.PaymentMethodCode `json:"paymentMethod_code" gorm:"size:10"` - InsuranceCompany_Code *string `json:"insuranceCompany_code"` - Member_Number *string `json:"member_number" validate:"maxLength=20"` - Ref_Number *string `json:"ref_number" validate:"maxLength=20"` - Trx_Number *string `json:"trx_number" validate:"maxLength=20"` - Appointment_Doctor_Code *string `json:"appointment_doctor_code"` - Adm_Employee_Id *uint `json:"-"` - Responsible_Doctor_Code *string `json:"responsible_doctor_code"` - RefSource_Name *string `json:"refSource_name" validate:"maxLength=100"` - Appointment_Id *uint `json:"appointment_id"` - RefTypeCode ere.RefTypeCode `json:"refTypeCode"` - NewStatus bool `json:"newStatus"` + Patient_Id *uint `json:"patient_id"` + 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 + Infra_Code *string `json:"infra_code"` // for inpatient + Unit_Code *string `json:"unit_code"` + Specialist_Code *string `json:"specialist_code"` + Subspecialist_Code *string `json:"subspecialist_code"` + VisitDate time.Time `json:"visitDate"` + PaymentMethod_Code ere.AllPaymentMethodCode `json:"paymentMethod_code" gorm:"size:10"` + InsuranceCompany_Code *string `json:"insuranceCompany_code"` + Member_Number *string `json:"member_number" validate:"maxLength=20"` + Ref_Number *string `json:"ref_number" validate:"maxLength=20"` + Trx_Number *string `json:"trx_number" validate:"maxLength=20"` + Appointment_Doctor_Code *string `json:"appointment_doctor_code"` + Adm_Employee_Id *uint `json:"-"` + Responsible_Doctor_Code *string `json:"responsible_doctor_code"` + RefSource_Name *string `json:"refSource_name" validate:"maxLength=100"` + Appointment_Id *uint `json:"appointment_id"` + RefTypeCode ere.RefTypeCode `json:"refTypeCode"` + NewStatus bool `json:"newStatus"` + VclaimReference *TRujukan `json:"vclaimReference"` Id uint `json:"-"` RecentEncounterAdm *Encounter `json:"-"` // if subClass_Code is rehab @@ -63,6 +64,18 @@ type CreateDto struct { pa.AuthInfo } +type TRujukan struct { + NoSep string `json:"noSep"` + TglRujukan string `json:"tglRujukan"` + PpkDirujuk string `json:"ppkDirujuk"` + JnsPelayanan string `json:"jnsPelayanan"` + Catatan string `json:"catatan"` + DiagRujukan string `json:"diagRujukan"` + TipeRujukan string `json:"tipeRujukan"` + PoliRujukan string `json:"poliRujukan"` + User string `json:"user"` +} + type ReadListDto struct { FilterDto Includes string `json:"includes"` @@ -161,7 +174,7 @@ type ResponseDto struct { Subspecialist *ess.Subspecialist `json:"subspecialist,omitempty"` Unit *eu.Unit `json:"unit,omitempty"` VisitDate time.Time `json:"visitDate"` - PaymentMethod_Code erc.PaymentMethodCode `json:"paymentMethod_code"` + PaymentMethod_Code ere.AllPaymentMethodCode `json:"paymentMethod_code"` InsuranceCompany_Code *string `json:"insuranceCompany_code"` Member_Number *string `json:"member_number"` Ref_Number *string `json:"ref_number"` diff --git a/internal/use-case/bpjs-use-case/vclaim-reference/case.go b/internal/use-case/bpjs-use-case/vclaim-reference/case.go new file mode 100644 index 00000000..bfbfea34 --- /dev/null +++ b/internal/use-case/bpjs-use-case/vclaim-reference/case.go @@ -0,0 +1,277 @@ +package vclaimreference + +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/bpjs-entities/vclaim-reference" +) + +const source = "vclaim-reference" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.VclaimReference{} + + 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.VclaimReference + var dataList []e.VclaimReference + 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.VclaimReference + 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.VclaimReference + 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.VclaimReference + 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/bpjs-use-case/vclaim-reference/helper.go b/internal/use-case/bpjs-use-case/vclaim-reference/helper.go new file mode 100644 index 00000000..9127921f --- /dev/null +++ b/internal/use-case/bpjs-use-case/vclaim-reference/helper.go @@ -0,0 +1,25 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package vclaimreference + +import ( + e "simrs-vx/internal/domain/bpjs-entities/vclaim-reference" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.VclaimReference) { + 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.Date = inputSrc.Date + data.SrcCode = inputSrc.SrcCode + data.SrcName = inputSrc.SrcName + data.Number = inputSrc.Number +} diff --git a/internal/use-case/bpjs-use-case/vclaim-reference/lib.go b/internal/use-case/bpjs-use-case/vclaim-reference/lib.go new file mode 100644 index 00000000..3f784edf --- /dev/null +++ b/internal/use-case/bpjs-use-case/vclaim-reference/lib.go @@ -0,0 +1,140 @@ +package vclaimreference + +import ( + plh "simrs-vx/pkg/lib-helper" + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors" + dg "github.com/karincake/apem/db-gorm-pg" + gh "github.com/karincake/getuk" + "gorm.io/gorm" + + e "simrs-vx/internal/domain/bpjs-entities/vclaim-reference" +) + +func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.VclaimReference, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.VclaimReference{} + + 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.VclaimReference, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.VclaimReference{} + 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.VclaimReference{}). + Scopes(gh.Preload(input.Includes)). + Scopes(gh.Filter(input.FilterDto)). + Count(&count). + Scopes(gh.Paginate(input, &pagination)). + Scopes(gh.Sort(input.Sort)) + + 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.VclaimReference, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.VclaimReference{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.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.VclaimReference, 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.VclaimReference, 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/bpjs-use-case/vclaim-reference/middleware-runner.go b/internal/use-case/bpjs-use-case/vclaim-reference/middleware-runner.go new file mode 100644 index 00000000..c8ad1e8f --- /dev/null +++ b/internal/use-case/bpjs-use-case/vclaim-reference/middleware-runner.go @@ -0,0 +1,103 @@ +package vclaimreference + +import ( + e "simrs-vx/internal/domain/bpjs-entities/vclaim-reference" + 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.VclaimReference) 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.VclaimReference) 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.VclaimReference) 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.VclaimReference) 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.VclaimReference) 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/bpjs-use-case/vclaim-reference/middleware.go b/internal/use-case/bpjs-use-case/vclaim-reference/middleware.go new file mode 100644 index 00000000..defdfa84 --- /dev/null +++ b/internal/use-case/bpjs-use-case/vclaim-reference/middleware.go @@ -0,0 +1,9 @@ +package vclaimreference + +// 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/bpjs-use-case/vclaim-reference/tycovar.go b/internal/use-case/bpjs-use-case/vclaim-reference/tycovar.go new file mode 100644 index 00000000..29add547 --- /dev/null +++ b/internal/use-case/bpjs-use-case/vclaim-reference/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 vclaimreference + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/bpjs-entities/vclaim-reference" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.VclaimReference, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.VclaimReference, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.VclaimReference, 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/simgos-sync-use-case/encounter/helper.go b/internal/use-case/simgos-sync-use-case/encounter/helper.go index b9daa7de..586dbd81 100644 --- a/internal/use-case/simgos-sync-use-case/encounter/helper.go +++ b/internal/use-case/simgos-sync-use-case/encounter/helper.go @@ -17,9 +17,12 @@ import ( esimgos "simrs-vx/internal/domain/simgos-entities/encounter" esync "simrs-vx/internal/domain/sync-entities/encounter" esyncLog "simrs-vx/internal/domain/sync-entities/log" + + dg "github.com/karincake/apem/db-gorm-pg" + "gorm.io/gorm/clause" ) -func setDataSimgos(input *e.Encounter) (data esimgos.TPendaftaran) { +func setDataCreate(input *e.Encounter) (data esimgos.TPendaftaran, err error) { data.Nomr = *input.Patient.Number data.Tglreg = input.RegisteredAt @@ -39,33 +42,28 @@ func setDataSimgos(input *e.Encounter) (data esimgos.TPendaftaran) { // TODO: shift - // set status - setStatus(input.Discharge_Method_Code, data) - if input.Status_Code == erc.DSCCancel { - data.Status = 11 - } - // set pasienbaru if input.NewStatus { data.Pasienbaru = 1 } + // set nip if emp := input.Adm_Employee; emp != nil { if user := emp.User; user != nil { data.Nip = user.Name } } - data.Masukpoly = input.StartedAt - data.Keluarpoly = input.FinishedAt - - if input.RefSource_Name != nil { - data.Ketrujuk = *input.RefSource_Name + if ref := input.VclaimReference; ref != nil { + data.Ketrujuk = *ref.SrcCode + data.Norujukan = *ref.Number + data.Tglrujukan = ref.Date + data.Strujukan = 1 } else { data.Ketrujuk = "TR" + data.Strujukan = 0 } - // TODO: preload person.relative, person.vclaimMember if p := input.Patient.Person; p != nil { mapRelative(p.Relatives, data) @@ -76,7 +74,54 @@ func setDataSimgos(input *e.Encounter) (data esimgos.TPendaftaran) { data.Jamreg = input.RegisteredAt - // TODO: norujukan, tglrujukan, dokumen_pendukung sep_file + // default kelas daftar + data.KelasDaftar = 3 + + // TODO: st_asal_masuk + + // set noAntrian + defaultFromSimx := "rehab" + data.NoAntrian = &defaultFromSimx + + // set no_kunjung + noKunjung, err := generateNoKunjung() + if err != nil { + return esimgos.TPendaftaran{}, err + } + + data.NoKunjung = uint(noKunjung) + + return +} + +func setDataUpdate(input *e.Encounter, data esimgos.TPendaftaran) { + // set kddokter + kddokter, _ := strconv.Atoi(*input.Responsible_Doctor_Code) + data.Kddokter = uint(kddokter) + + // set kdpoly + kdpoly, _ := strconv.Atoi(*input.Unit_Code) + data.Kdpoly = uint(kdpoly) + + // set status + setStatus(input.Discharge_Method_Code, data) + if input.Status_Code == erc.DSCCancel { + data.Status = 11 + } + + data.Masukpoly = input.StartedAt + data.Keluarpoly = input.FinishedAt + + if p := input.Patient.Person; p != nil { + mapRelative(p.Relatives, data) + } + + // set petugas_klinik + if emp := input.Adm_Employee; emp != nil { + if user := emp.User; user != nil { + data.PetugasKlinik = user.Name + } + } return } @@ -205,3 +250,20 @@ func setDataSimxLink(simxId, simgosId uint) (data esync.EncounterLink) { data.Simgos_Id = simgosId return } + +func generateNoKunjung() (int, error) { + var lastNumber int + + // Lock rows for this prefix → prevents race condition + if err := dg.IS["simrs"]. + Table("t_pendaftaran"). + Select("COALESCE(MAX(no_kunjung),0)"). + Where("tglreg = CURRENT_DATE"). + Order("no_kunjung DESC"). + Clauses(clause.Locking{Strength: "UPDATE"}). + Scan(&lastNumber).Error; err != nil { + return 0, err + } + + return lastNumber + 1, nil +} From 8051552d701ebd2e1558f465b532dff865b8df05 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Sun, 23 Nov 2025 18:37:28 +0700 Subject: [PATCH 08/29] feat/things-munaja: adjust employee - person --- internal/domain/main-entities/employee/dto.go | 29 +++++++++++-------- .../person-address/base/entity.go | 22 ++++++++++++++ .../main-entities/person-address/entity.go | 17 ++--------- .../person-contact/base/entity.go | 13 +++++++++ .../main-entities/person-contact/entity.go | 8 ++--- .../domain/main-entities/person/entity.go | 2 +- internal/domain/main-entities/soapi/dto.go | 2 +- .../references/organization/organization.go | 12 ++++---- internal/lib/auth/tycovar.go | 24 +++++++-------- 9 files changed, 77 insertions(+), 52 deletions(-) create mode 100644 internal/domain/main-entities/person-address/base/entity.go create mode 100644 internal/domain/main-entities/person-contact/base/entity.go diff --git a/internal/domain/main-entities/employee/dto.go b/internal/domain/main-entities/employee/dto.go index 85b7008a..5b2c674d 100644 --- a/internal/domain/main-entities/employee/dto.go +++ b/internal/domain/main-entities/employee/dto.go @@ -4,6 +4,7 @@ import ( ecore "simrs-vx/internal/domain/base-entities/core" ep "simrs-vx/internal/domain/main-entities/person" eu "simrs-vx/internal/domain/main-entities/user" + "time" erc "simrs-vx/internal/domain/references/common" erg "simrs-vx/internal/domain/references/organization" @@ -55,22 +56,26 @@ type MetaDto struct { type ResponseDto struct { ecore.Main - User_Id *uint `json:"user_id"` - User *eu.User `json:"user,omitempty"` - Person_Id *uint `json:"person_id"` - Person *ep.Person `json:"person,omitempty"` - Number *string `json:"number"` - Status_Code erc.ActiveStatusCode `json:"status_code"` + User_Id *uint `json:"user_id"` + User *eu.User `json:"user,omitempty"` + Person_Id *uint `json:"person_id"` + Person *ep.Person `json:"person,omitempty"` + Number *string `json:"number"` + Position_Code *erg.EmployeePositionCode `json:"position_code"` + Contract_ExpiredDate *time.Time `json:"contract_expiredDate"` + Status_Code erc.ActiveStatusCode `json:"status_code"` } func (d Employee) ToResponse() ResponseDto { resp := ResponseDto{ - User_Id: d.User_Id, - User: d.User, - Person_Id: d.Person_Id, - Person: d.Person, - Number: d.Number, - Status_Code: d.Status_Code, + User_Id: d.User_Id, + User: d.User, + Person_Id: d.Person_Id, + Person: d.Person, + Number: d.Number, + Position_Code: d.Position_Code, + Contract_ExpiredDate: d.Contract_ExpiredDate, + Status_Code: d.Status_Code, } resp.Main = d.Main return resp diff --git a/internal/domain/main-entities/person-address/base/entity.go b/internal/domain/main-entities/person-address/base/entity.go new file mode 100644 index 00000000..65d71912 --- /dev/null +++ b/internal/domain/main-entities/person-address/base/entity.go @@ -0,0 +1,22 @@ +package personaddress + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + epr "simrs-vx/internal/domain/main-entities/postal-region" + ev "simrs-vx/internal/domain/main-entities/village" + + erp "simrs-vx/internal/domain/references/person" +) + +type PersonAddress struct { + ecore.Main // adjust this according to the needs + Person_Id uint `json:"person_id"` + Address string `json:"address" gorm:"size:150"` + LocationType_Code erp.AddressLocationTypeCode `json:"locationType_code" gorm:"size:10"` + Rt string `json:"rt" gorm:"size:2"` + Rw string `json:"rw" gorm:"size:2"` + PostalRegion_Code *string `json:"postalRegion_code" gorm:"size:6"` + PostalRegion *epr.PostalRegion `json:"postalRegion,omitempty" gorm:"foreignKey:PostalRegion_Code;references:Code"` + Village_Code *string `json:"village_code" gorm:"size:10"` + Village *ev.Village `json:"village,omitempty" gorm:"foreignKey:Village_Code;references:Code"` +} diff --git a/internal/domain/main-entities/person-address/entity.go b/internal/domain/main-entities/person-address/entity.go index 65d71912..f53e2859 100644 --- a/internal/domain/main-entities/person-address/entity.go +++ b/internal/domain/main-entities/person-address/entity.go @@ -1,22 +1,9 @@ package personaddress import ( - ecore "simrs-vx/internal/domain/base-entities/core" - epr "simrs-vx/internal/domain/main-entities/postal-region" - ev "simrs-vx/internal/domain/main-entities/village" - - erp "simrs-vx/internal/domain/references/person" + eb "simrs-vx/internal/domain/main-entities/person-address/base" ) type PersonAddress struct { - ecore.Main // adjust this according to the needs - Person_Id uint `json:"person_id"` - Address string `json:"address" gorm:"size:150"` - LocationType_Code erp.AddressLocationTypeCode `json:"locationType_code" gorm:"size:10"` - Rt string `json:"rt" gorm:"size:2"` - Rw string `json:"rw" gorm:"size:2"` - PostalRegion_Code *string `json:"postalRegion_code" gorm:"size:6"` - PostalRegion *epr.PostalRegion `json:"postalRegion,omitempty" gorm:"foreignKey:PostalRegion_Code;references:Code"` - Village_Code *string `json:"village_code" gorm:"size:10"` - Village *ev.Village `json:"village,omitempty" gorm:"foreignKey:Village_Code;references:Code"` + eb.PersonAddress } diff --git a/internal/domain/main-entities/person-contact/base/entity.go b/internal/domain/main-entities/person-contact/base/entity.go new file mode 100644 index 00000000..b39aa53e --- /dev/null +++ b/internal/domain/main-entities/person-contact/base/entity.go @@ -0,0 +1,13 @@ +package personcontact + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + erp "simrs-vx/internal/domain/references/person" +) + +type PersonContact struct { + ecore.Main // adjust this according to the needs + Person_Id uint `json:"person_id"` + Type_Code erp.ContactTypeCode `json:"type_code" gorm:"size:15"` + Value string `json:"value" gorm:"size:100"` +} diff --git a/internal/domain/main-entities/person-contact/entity.go b/internal/domain/main-entities/person-contact/entity.go index b39aa53e..3ba257c4 100644 --- a/internal/domain/main-entities/person-contact/entity.go +++ b/internal/domain/main-entities/person-contact/entity.go @@ -1,13 +1,9 @@ package personcontact import ( - ecore "simrs-vx/internal/domain/base-entities/core" - erp "simrs-vx/internal/domain/references/person" + eb "simrs-vx/internal/domain/main-entities/person-contact/base" ) type PersonContact struct { - ecore.Main // adjust this according to the needs - Person_Id uint `json:"person_id"` - Type_Code erp.ContactTypeCode `json:"type_code" gorm:"size:15"` - Value string `json:"value" gorm:"size:100"` + eb.PersonContact } diff --git a/internal/domain/main-entities/person/entity.go b/internal/domain/main-entities/person/entity.go index d6e6744a..f36ec83a 100644 --- a/internal/domain/main-entities/person/entity.go +++ b/internal/domain/main-entities/person/entity.go @@ -34,7 +34,7 @@ type Person struct { Ocupation_Code *erp.OcupationCode `json:"occupation_code" gorm:"size:15"` Ocupation_Name *string `json:"occupation_name" gorm:"size:50"` MaritalStatus_Code *erp.MaritalStatusCode `json:"maritalStatus_code" gorm:"size:10"` - Nationality *string `json:"nationality": gorm:"size:50"` + Nationality *string `json:"nationality" gorm:"size:50"` Ethnic_Code *string `json:"ethnic_code" gorm:"size:20"` Ethnic *ee.Ethnic `json:"ethnic,omitempty" gorm:"foreignKey:Ethnic_Code;references:Code"` Language_Code *string `json:"language_code" gorm:"size:10"` diff --git a/internal/domain/main-entities/soapi/dto.go b/internal/domain/main-entities/soapi/dto.go index b8f1b827..3edc18e3 100644 --- a/internal/domain/main-entities/soapi/dto.go +++ b/internal/domain/main-entities/soapi/dto.go @@ -32,7 +32,7 @@ type FilterDto struct { Encounter_Id *uint `json:"encounter-id"` Employee_Id *uint `json:"employee-id"` Time *time.Time `json:"time"` - TypeCode erc.SoapiTypeCode `json:"typeCode"` + TypeCode erc.SoapiTypeCode `json:"type-code"` Value *string `json:"value"` } diff --git a/internal/domain/references/organization/organization.go b/internal/domain/references/organization/organization.go index 09b19dce..e12531cd 100644 --- a/internal/domain/references/organization/organization.go +++ b/internal/domain/references/organization/organization.go @@ -18,14 +18,16 @@ const ( EPCReg EmployeePositionCode = "reg" // Admisi/Pendaftaran EPCNur EmployeePositionCode = "nur" // Perawat EPCDoc EmployeePositionCode = "doc" // Dokter - EPCNut EmployeePositionCode = "nut" // Ahli gizi EPCMwi EmployeePositionCode = "miw" // Bidan + EPCThr EmployeePositionCode = "thr" // Terapis + EPCNut EmployeePositionCode = "nut" // Ahli gizi EPCLab EmployeePositionCode = "lab" // Laboran EPCPha EmployeePositionCode = "pha" // Farmasi - EPCPay EmployeePositionCode = "pay" // Pembayaran - EPCHur EmployeePositionCode = "hue" // Sumber Daya Manusia - EPCGea EmployeePositionCode = "gea" // Bagian Umum - EPCMan EmployeePositionCode = "man" // Manajemen + EPCNom EmployeePositionCode = "nom" // Non Medic + // EPCPay EmployeePositionCode = "pay" // Pembayaran + // EPCHur EmployeePositionCode = "hue" // Sumber Daya Manusia + // EPCGea EmployeePositionCode = "gea" // Bagian Umum + // EPCMan EmployeePositionCode = "man" // Manajemen IPCSpecialist = "specialist-intern" IPCNurse = "nurse-intern" diff --git a/internal/lib/auth/tycovar.go b/internal/lib/auth/tycovar.go index 70a6fec2..117d0c73 100644 --- a/internal/lib/auth/tycovar.go +++ b/internal/lib/auth/tycovar.go @@ -65,19 +65,19 @@ func (a AuthInfo) IsPharmacist() bool { return *a.Employee_Position_Code == string(ero.EPCPha) } -func (a AuthInfo) IsPayment() bool { - if a.Employee_Position_Code == nil { - return false - } - return *a.Employee_Position_Code == string(ero.EPCPay) -} +// func (a AuthInfo) IsPayment() bool { +// if a.Employee_Position_Code == nil { +// return false +// } +// return *a.Employee_Position_Code == string(ero.EPCPay) +// } -func (a AuthInfo) IsManagement() bool { - if a.Employee_Position_Code == nil { - return false - } - return *a.Employee_Position_Code == string(ero.EPCMan) -} +// func (a AuthInfo) IsManagement() bool { +// if a.Employee_Position_Code == nil { +// return false +// } +// return *a.Employee_Position_Code == string(ero.EPCMan) +// } func (a AuthInfo) IsSpecialistIntern() bool { if a.Intern_Position_Code == nil { From 8c53dfb0dec039a5c336a6b0ae1830170e8a805c Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Mon, 24 Nov 2025 05:08:26 +0700 Subject: [PATCH 09/29] feat/things-munaja: added unit on login --- internal/use-case/main-use-case/authentication/helper.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/use-case/main-use-case/authentication/helper.go b/internal/use-case/main-use-case/authentication/helper.go index c0a3bd0f..7af3afa5 100644 --- a/internal/use-case/main-use-case/authentication/helper.go +++ b/internal/use-case/main-use-case/authentication/helper.go @@ -226,6 +226,10 @@ func populateRoles(user *eu.User, input eu.LoginDto, atClaims jwt.MapClaims, out outputData["doctor_code"] = doctor.Code // specialist + if doctor.Unit_Code != nil { + atClaims["unit_code"] = doctor.Unit_Code + outputData["unit_code"] = doctor.Unit_Code + } if doctor.Specialist_Code != nil { atClaims["specialist_code"] = doctor.Specialist_Code outputData["specialist_code"] = doctor.Specialist_Code From 157ea603a471cce0ed83811674d23cb20ef9c1ff Mon Sep 17 00:00:00 2001 From: vanilia Date: Mon, 24 Nov 2025 10:17:37 +0700 Subject: [PATCH 10/29] set checkin checkout --- .../domain/main-entities/encounter/dto.go | 4 + .../main-handler/encounter/handler.go | 33 ++- .../simgos-sync-handler/encounter/handler.go | 63 ++++++ .../simgos-sync-handler.go | 2 + .../use-case/main-use-case/encounter/case.go | 200 ++++++++++++------ .../main-use-case/encounter/helper.go | 2 +- .../encounter/middleware-runner.go | 36 ++++ .../main-use-case/encounter/middleware.go | 22 +- .../main-use-case/encounter/tycovar.go | 12 ++ .../simgos-sync-plugin/encounter/plugin.go | 167 +++++++++++++++ .../simgos-sync-use-case/encounter/case.go | 102 ++++++++- .../simgos-sync-use-case/encounter/helper.go | 118 +++++++---- .../simgos-sync-use-case/encounter/lib.go | 69 +++++- 13 files changed, 694 insertions(+), 136 deletions(-) create mode 100644 internal/interface/simgos-sync-handler/encounter/handler.go create mode 100644 internal/use-case/simgos-sync-plugin/encounter/plugin.go diff --git a/internal/domain/main-entities/encounter/dto.go b/internal/domain/main-entities/encounter/dto.go index e2fe33ac..849a5834 100644 --- a/internal/domain/main-entities/encounter/dto.go +++ b/internal/domain/main-entities/encounter/dto.go @@ -140,6 +140,8 @@ type DischargeDto struct { AdmDischargeEducation *string `json:"admDischargeEducation"` DischargeReason *string `json:"dischargeReason"` DeathCause *string `json:"deathCause"` + + pa.AuthInfo } type CheckinDto struct { @@ -148,6 +150,8 @@ type CheckinDto struct { Adm_Employee_Id *uint `json:"adm_employee_id"` StartedAt *time.Time `json:"startedAt"` FinishedAt *time.Time `json:"finishedAt"` + + pa.AuthInfo } type SwitchUnitDto struct { diff --git a/internal/interface/main-handler/encounter/handler.go b/internal/interface/main-handler/encounter/handler.go index c8fcfa1a..0c87b7b2 100644 --- a/internal/interface/main-handler/encounter/handler.go +++ b/internal/interface/main-handler/encounter/handler.go @@ -70,6 +70,11 @@ func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { } func (obj myBase) Update(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) + } + id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { return @@ -80,29 +85,39 @@ func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { return } dto.Id = uint16(id) + dto.AuthInfo = *authInfo + 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 - } authInfo, err := pa.GetAuthInfo(r) if err != nil { rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil) } + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + dto := e.DeleteDto{} dto.Id = uint16(id) dto.AuthInfo = *authInfo + res, err := u.Delete(dto) rw.DataResponse(w, res, err) } func (obj myBase) CheckOut(w http.ResponseWriter, r *http.Request) { dto := e.DischargeDto{} + + authInfo, err := pa.GetAuthInfo(r) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil) + } + id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { return @@ -118,12 +133,20 @@ func (obj myBase) CheckOut(w http.ResponseWriter, r *http.Request) { } dto.Id = uint(id) + dto.AuthInfo = *authInfo + res, err := u.CheckOut(dto) rw.DataResponse(w, res, err) } func (obj myBase) CheckIn(w http.ResponseWriter, r *http.Request) { dto := e.CheckinDto{} + + authInfo, err := pa.GetAuthInfo(r) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil) + } + id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { return @@ -139,6 +162,8 @@ func (obj myBase) CheckIn(w http.ResponseWriter, r *http.Request) { } dto.Id = uint(id) + dto.AuthInfo = *authInfo + res, err := u.CheckIn(dto) rw.DataResponse(w, res, err) } diff --git a/internal/interface/simgos-sync-handler/encounter/handler.go b/internal/interface/simgos-sync-handler/encounter/handler.go new file mode 100644 index 00000000..41ce9415 --- /dev/null +++ b/internal/interface/simgos-sync-handler/encounter/handler.go @@ -0,0 +1,63 @@ +package encounter + +import ( + "net/http" + + rw "github.com/karincake/risoles" + // ua "github.com/karincake/tumpeng/auth/svc" + + e "simrs-vx/internal/domain/main-entities/encounter" + esync "simrs-vx/internal/domain/sync-entities/log" + u "simrs-vx/internal/use-case/simgos-sync-use-case/encounter" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.Encounter{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) CreateLog(w http.ResponseWriter, r *http.Request) { + dto := esync.SimxLogDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.CreateSimxLog(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.Encounter{} + 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 = uint16(id) + + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/simgos-sync-handler/simgos-sync-handler.go b/internal/interface/simgos-sync-handler/simgos-sync-handler.go index 2a67b7e9..b5b65507 100644 --- a/internal/interface/simgos-sync-handler/simgos-sync-handler.go +++ b/internal/interface/simgos-sync-handler/simgos-sync-handler.go @@ -19,6 +19,7 @@ import ( /******************** internal ********************/ "simrs-vx/internal/interface/main-handler/home" division "simrs-vx/internal/interface/simgos-sync-handler/division" + encounter "simrs-vx/internal/interface/simgos-sync-handler/encounter" installation "simrs-vx/internal/interface/simgos-sync-handler/installation" patient "simrs-vx/internal/interface/simgos-sync-handler/patient" specialist "simrs-vx/internal/interface/simgos-sync-handler/specialist" @@ -47,6 +48,7 @@ func SetRoutes() http.Handler { hc.SyncCrud(r, prefix+"/v1/subspecialist", subspecialist.O) hc.SyncCrud(r, prefix+"/v1/patient", patient.O) r.HandleFunc(fmt.Sprintf("GET %s/v1/patient-nomr-generator", prefix), patient.O.GenerateNomr) + hc.SyncCrud(r, prefix+"/v1/encounter", encounter.O) return cmw.SetCors(handlerlogger.SetLog(r)) } diff --git a/internal/use-case/main-use-case/encounter/case.go b/internal/use-case/main-use-case/encounter/case.go index 37e08bc5..45a85a00 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -3,6 +3,7 @@ package encounter import ( "errors" "fmt" + "simrs-vx/internal/lib/auth" "strconv" "time" @@ -17,6 +18,7 @@ import ( ere "simrs-vx/internal/domain/references/encounter" erg "simrs-vx/internal/domain/references/organization" + ev "simrs-vx/internal/domain/bpjs-entities/vclaim-reference" eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist" edc "simrs-vx/internal/domain/main-entities/death-cause" e "simrs-vx/internal/domain/main-entities/encounter" @@ -25,6 +27,7 @@ import ( es "simrs-vx/internal/domain/main-entities/soapi" esync "simrs-vx/internal/domain/sync-entities/log" + uv "simrs-vx/internal/use-case/bpjs-use-case/vclaim-reference" uaeh "simrs-vx/internal/use-case/main-use-case/adm-employee-hist" udc "simrs-vx/internal/use-case/main-use-case/death-cause" uir "simrs-vx/internal/use-case/main-use-case/internal-reference" @@ -51,28 +54,10 @@ func Create(input e.CreateDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "create") - // check if user has employee position - if !input.AuthInfo.HasEmployeePosition() { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "auth-forbidden", - Detail: "user has no employee position", - Raw: errors.New("authentication failed"), - } - return nil, pl.SetLogError(&event, input) - } - - // check only user with registration position is allowed to create encounter - if input.AuthInfo.Employee_Position_Code != nil && *input.AuthInfo.Employee_Position_Code != string(erg.EPCReg) { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "auth-forbidden", - Detail: "user position is not allowed, only user with registration position is allowed to create encounter", - Raw: errors.New("authentication failed"), - } - return nil, pl.SetLogError(&event, input) - } else { - input.Adm_Employee_Id = input.AuthInfo.Employee_Id + roleAllowed := []string{string(erg.EPCReg)} + err = validateAuth(input.AuthInfo, roleAllowed, "create-encounter", &event) + if err != nil { + return nil, err } // validate rehab by bpjs @@ -90,6 +75,7 @@ func Create(input e.CreateDto) (*d.Data, error) { // If recentRehabData is not nil, determine the visitMode_Code: // If the mode is "series", verify whether the visit count still remains // and whether the series has not expired. + // If visitMode is "series", then get encounterAdm input.VisitMode_Code, input.RecentEncounterAdm, err = determineVisitMode(recentRehabData, input, &event) if err != nil { return nil, err @@ -110,6 +96,8 @@ func Create(input e.CreateDto) (*d.Data, error) { // check if patient is new in the hospital input.NewStatus, err = identifyPatientStatus(input) + input.Adm_Employee_Id = input.AuthInfo.Employee_Id + mwRunner := newMiddlewareRunner(&event) err = dg.I.Transaction(func(tx *gorm.DB) error { @@ -127,7 +115,19 @@ func Create(input e.CreateDto) (*d.Data, error) { return err } - // insert adm_employee_hist + // insert vclaimReference + if vr := input.VclaimReference; vr != nil { + t, _ := time.Parse("2006-01-02", vr.TglRujukan) + + _, err = uv.CreateData(ev.CreateDto{ + Encounter_Id: &data.Id, + Date: &t, + SrcCode: input.Ref_Number, + SrcName: input.RefSource_Name, + Number: &vr.NoSep}, &event, tx) + } + + // insert adm_employee_hist (employee with role reg for create) if _, err = uaeh.CreateData(eaeh.CreateDto{ Encounter_Id: &data.Id, Employee_Id: data.Adm_Employee_Id, @@ -137,7 +137,7 @@ func Create(input e.CreateDto) (*d.Data, error) { dataEncounter, err := ReadDetailData(e.ReadDetailDto{ Id: uint16(data.Id), - Includes: "Adm_Employee.User,Patient.Person.Relatives,Patient.Person.VclaimMember"}, + Includes: "Adm_Employee.User,Patient.Person.Relatives,Patient.Person.VclaimMember,VclaimReference"}, &event, tx) if err != nil { return err @@ -280,6 +280,14 @@ func Update(input e.UpdateDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "update") + + roleAllowed := []string{string(erg.EPCReg)} + err = validateAuth(input.AuthInfo, roleAllowed, "update-encounter", &event) + if err != nil { + return nil, err + } + input.Adm_Employee_Id = input.AuthInfo.Employee_Id + mwRunner := newMiddlewareRunner(&event) err = dg.I.Transaction(func(tx *gorm.DB) error { @@ -349,26 +357,10 @@ func Delete(input e.DeleteDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "delete") - // check if user has employee position - if !input.AuthInfo.HasEmployeePosition() { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "auth-forbidden", - Detail: "user has no employee position", - Raw: errors.New("authentication failed"), - } - return nil, pl.SetLogError(&event, input) - } - - // check only user with registration position is allowed to create encounter - if input.AuthInfo.Employee_Position_Code != nil && *input.AuthInfo.Employee_Position_Code != string(erg.EPCReg) { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "auth-forbidden", - Detail: "user position is not allowed, only user with registration position is allowed to create encounter", - Raw: errors.New("authentication failed"), - } - return nil, pl.SetLogError(&event, input) + roleAllowed := []string{string(erg.EPCReg)} + err = validateAuth(input.AuthInfo, roleAllowed, "delete-encounter", &event) + if err != nil { + return nil, err } mwRunner := newMiddlewareRunner(&event) @@ -379,6 +371,33 @@ func Delete(input e.DeleteDto) (*d.Data, error) { return err } + if data.IsDone() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "data-state-mismatch", + Detail: "encounter is done", + Raw: errors.New("encounter is done"), + } + return pl.SetLogError(&event, input) + } + + // Prevent delete if soapi exist + dataSoapi, _, err := us.ReadListData(es.ReadListDto{ + FilterDto: es.FilterDto{Encounter_Id: &data.Id}}, &event, tx) + if err != nil { + return err + } + + if len(dataSoapi) > 0 { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "cancel-not-allowed", + Detail: "encounter can't be deleted because SOAPI already exists", + Raw: errors.New("encounter has SOAPI records"), + } + return pl.SetLogError(&event, input) + } + if err := DeleteData(data, &event, tx); err != nil { return err } @@ -422,6 +441,14 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "checkOut") + roleAllowed := []string{string(erg.EPCNur), string(erg.EPCDoc)} + err = validateAuth(input.AuthInfo, roleAllowed, "checkOut-encounter", &event) + if err != nil { + return nil, err + } + + mwRunner := newMiddlewareRunner(&event) + err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { @@ -445,7 +472,7 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { } if data.Ambulatory.Class_Code == ere.ACCRehab { - // verify and update rehabData if visit count has reached the allowed limit + // verify and update rehabData to DONE if visit count has reached the allowed limit if err = verifyRehabLimit(data, &event, tx); err != nil { return err } @@ -488,15 +515,21 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { } } - pl.SetLogInfo(&event, nil, "complete") + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCheckoutMiddleware(checkoutEncounter, &input); err != nil { + return err + } return nil }) - if err != nil { + if err = runLogMiddleware(err, input, mwRunner); err != nil { return nil, err } + pl.SetLogInfo(&event, nil, "complete") + return &d.Data{ Meta: d.IS{ "source": source, @@ -568,11 +601,8 @@ func UpdateStatusCode(input e.UpdateStatusDto) (*d.Data, error) { } // Prevent cancellation if soapi exist - encounterId := uint(input.Id) dataSoapi, _, err := us.ReadListData(es.ReadListDto{ - FilterDto: es.FilterDto{ - Encounter_Id: &encounterId, - }}, &event, tx) + FilterDto: es.FilterDto{Encounter_Id: &data.Id}}, &event, tx) if err != nil { return err } @@ -588,25 +618,12 @@ func UpdateStatusCode(input e.UpdateStatusDto) (*d.Data, error) { } } - //mwRunner := newMiddlewareRunner(&event) - //mwRunner.setMwType(pu.MWTPre) - //// Run pre-middleware - //if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { - // return err - //} - if err := UpdateStatusData(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 }) @@ -638,6 +655,13 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "checkIn") + roleAllowed := []string{string(erg.EPCNur), string(erg.EPCDoc)} + err = validateAuth(input.AuthInfo, roleAllowed, "checkIn-encounter", &event) + if err != nil { + return nil, err + } + input.Adm_Employee_Id = input.AuthInfo.Employee_Id + // validate foreign key if err := validateForeignKey(input); err != nil { return nil, err @@ -648,6 +672,8 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { input.StartedAt = &now } + mwRunner := newMiddlewareRunner(&event) + err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { @@ -685,7 +711,7 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { } // Upsert adm_employee_hist if adm_employee_id has changed - if input.Adm_Employee_Id != nil && *input.Adm_Employee_Id != *data.Adm_Employee_Id { + if *input.Adm_Employee_Id != *data.Adm_Employee_Id { // upsert admEmployeeHist if err = upsertAdmEmployeeHist(eaeh.CreateDto{ Encounter_Id: &data.Id, @@ -701,15 +727,29 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { return err } - pl.SetLogInfo(&event, nil, "complete") + dataEncounter, err := ReadDetailData(e.ReadDetailDto{ + Id: uint16(data.Id), + Includes: "Adm_Employee.User"}, + &event, tx) + if err != nil { + return err + } + + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCheckinMiddleware(checkinEncounterMw, dataEncounter); err != nil { + return err + } return nil }) - if err != nil { + if err = runLogMiddleware(err, input, mwRunner); err != nil { return nil, err } + pl.SetLogInfo(&event, nil, "complete") + return &d.Data{ Meta: d.IS{ "source": source, @@ -951,3 +991,29 @@ func runLogMiddleware(err error, input any, mwRunner *middlewareRunner) error { return nil } + +func validateAuth(a auth.AuthInfo, roleAllowed []string, action string, event *pl.Event) error { + // check if user has employee position + if !a.HasEmployeePosition() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "auth-forbidden", + Detail: "missing employee position", + Raw: errors.New("authentication failed"), + } + return pl.SetLogError(event, a) + } + + // check only roleAllowed position is allowed to create encounter + if a.Employee_Position_Code != nil && !pu.Contains(roleAllowed, *a.Employee_Position_Code) { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "auth-forbidden", + Detail: fmt.Sprintf("position '%s' is not allowed to %s", *a.Employee_Position_Code, action), + Raw: errors.New("authentication failed"), + } + return pl.SetLogError(event, a) + } + + return nil +} diff --git a/internal/use-case/main-use-case/encounter/helper.go b/internal/use-case/main-use-case/encounter/helper.go index 1de331b8..3b215c36 100644 --- a/internal/use-case/main-use-case/encounter/helper.go +++ b/internal/use-case/main-use-case/encounter/helper.go @@ -617,7 +617,7 @@ func getSoapiByTypeCode(encounter *e.Encounter, event *pl.Event, mode string) (e rehab = encounter.Rehab ) - // Set Query for get data Soapi + // Set Query for get data Soapi by doc tx := dg.I. Model(&es.Soapi{}). Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\""). diff --git a/internal/use-case/main-use-case/encounter/middleware-runner.go b/internal/use-case/main-use-case/encounter/middleware-runner.go index b030d286..78558cd0 100644 --- a/internal/use-case/main-use-case/encounter/middleware-runner.go +++ b/internal/use-case/main-use-case/encounter/middleware-runner.go @@ -143,6 +143,42 @@ func (me *middlewareRunner) RunDeleteMiddleware(middlewares []deleteMw, input *e return nil } +func (me *middlewareRunner) RunCheckinMiddleware(middleware checkinMw, input *e.Encounter) error { + if !me.SyncOn { + return nil + } + + logData := pu.GetLogData(input, nil) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + + return nil +} + +func (me *middlewareRunner) RunCheckoutMiddleware(middleware checkoutMw, input *e.DischargeDto) error { + if !me.SyncOn { + return nil + } + + logData := pu.GetLogData(input, nil) + + pl.SetLogInfo(me.Event, logData, "started", middleware.Name) + + if err := middleware.Func(input); err != nil { + return pu.HandleMiddlewareError(me.Event, string(me.MwType), middleware.Name, logData, err) + } + + pl.SetLogInfo(me.Event, nil, "complete") + + return nil +} + func (me *middlewareRunner) setMwType(mwType pu.MWType) { me.MwType = mwType } diff --git a/internal/use-case/main-use-case/encounter/middleware.go b/internal/use-case/main-use-case/encounter/middleware.go index 8a047d33..bb5fab3f 100644 --- a/internal/use-case/main-use-case/encounter/middleware.go +++ b/internal/use-case/main-use-case/encounter/middleware.go @@ -1,9 +1,17 @@ package encounter -// example of middleware -// func init() { -// createPreMw = append(createPreMw, -// CreateMw{Name: "modif-input", Func: pm.ModifInput}, -// CreateMw{Name: "check-data", Func: pm.CheckData}, -// ) -// } +import plugin "simrs-vx/internal/use-case/simgos-sync-plugin/encounter" + +func init() { + createPreMw = append(createPreMw, + createMw{Name: "sync-update-encounter", Func: plugin.Create}) + + createSimxLogMw = append(createSimxLogMw, + createLogMw{Name: "create-sync-log", Func: plugin.CreateLog}) + + updatePreMw = append(updatePreMw, + updateMw{Name: "sync-update-patient", Func: plugin.Update}) + + deletePreMw = append(deletePreMw, + deleteMw{Name: "sync-delete-patient", Func: plugin.Delete}) +} diff --git a/internal/use-case/main-use-case/encounter/tycovar.go b/internal/use-case/main-use-case/encounter/tycovar.go index f618bd19..f2309211 100644 --- a/internal/use-case/main-use-case/encounter/tycovar.go +++ b/internal/use-case/main-use-case/encounter/tycovar.go @@ -44,6 +44,16 @@ type readDetailMw struct { Func func(input *e.ReadDetailDto, data *e.Encounter, tx *gorm.DB) error } +type checkinMw struct { + Name string + Func func(input *e.Encounter) error +} + +type checkoutMw struct { + Name string + Func func(input *e.DischargeDto) error +} + type UpdateMw = updateMw type DeleteMw = deleteMw @@ -58,3 +68,5 @@ var updatePreMw []updateMw var updatePostMw []readDetailMw var deletePreMw []deleteMw var deletePostMw []readDetailMw +var checkinEncounterMw checkinMw +var checkoutEncounter checkoutMw diff --git a/internal/use-case/simgos-sync-plugin/encounter/plugin.go b/internal/use-case/simgos-sync-plugin/encounter/plugin.go new file mode 100644 index 00000000..70d3e8be --- /dev/null +++ b/internal/use-case/simgos-sync-plugin/encounter/plugin.go @@ -0,0 +1,167 @@ +package encounter + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + d "github.com/karincake/dodol" + + sync "simrs-vx/internal/infra/sync-cfg" + + e "simrs-vx/internal/domain/main-entities/encounter" + elog "simrs-vx/internal/domain/sync-entities/log" +) + +func Create(input *e.Encounter) error { + endpoint := getPrefixEndpoint() + + jsonData, err := json.Marshal(input) + if err != nil { + return fmt.Errorf("failed to encode JSON: %w", err) + } + + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsonData)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + errors := d.FieldError{} + _ = json.Unmarshal(bodyBytes, &errors) + + return fmt.Errorf(errors.Message) + } + + return nil +} + +func CreateLog(input *elog.SimxLogDto) error { + prefixEndpoint := getPrefixEndpoint() + endpoint := prefixEndpoint + "/log" + + jsonData, err := json.Marshal(input) + if err != nil { + return fmt.Errorf("failed to encode JSON: %w", err) + } + + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsonData)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + errors := d.FieldError{} + _ = json.Unmarshal(bodyBytes, &errors) + + return fmt.Errorf(errors.Message) + } + + return nil +} + +func Update(input *e.Encounter) error { + prefixEndpoint := getPrefixEndpoint() + endpoint := fmt.Sprintf("%s/%v", prefixEndpoint, input.Id) + + jsonData, err := json.Marshal(input) + if err != nil { + return fmt.Errorf("failed to encode JSON: %w", err) + } + + req, err := http.NewRequest("PATCH", endpoint, bytes.NewReader(jsonData)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + errors := d.FieldError{} + _ = json.Unmarshal(bodyBytes, &errors) + + return fmt.Errorf(errors.Message) + } + + return nil +} + +func Delete(input *e.DeleteDto) error { + prefixEndpoint := getPrefixEndpoint() + endpoint := fmt.Sprintf("%s/%v", prefixEndpoint, input.Id) + + jsonData, err := json.Marshal(input) + if err != nil { + return fmt.Errorf("failed to encode JSON: %w", err) + } + + req, err := http.NewRequest("DELETE", endpoint, bytes.NewReader(jsonData)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + errors := d.FieldError{} + _ = json.Unmarshal(bodyBytes, &errors) + + return fmt.Errorf(errors.Message) + } + + return nil +} + +func getPrefixEndpoint() string { + return fmt.Sprintf("%s%s/v1/encounter", sync.O.Host, sync.O.Prefix) +} diff --git a/internal/use-case/simgos-sync-use-case/encounter/case.go b/internal/use-case/simgos-sync-use-case/encounter/case.go index 591dae36..50a822ed 100644 --- a/internal/use-case/simgos-sync-use-case/encounter/case.go +++ b/internal/use-case/simgos-sync-use-case/encounter/case.go @@ -33,7 +33,7 @@ func Create(input e.Encounter) (*d.Data, error) { err = db.WithDualTx(func(tx *db.Dualtx) error { // STEP 1: Insert to simgos - sgData, err = CreateSimgosData(input, &event, tx.Simgos) + sgData, err = CreateTPendaftaranData(input, &event, tx.Simgos) if err != nil { return err } @@ -114,10 +114,16 @@ func Update(input e.Encounter) (*d.Data, error) { return nil, err } + // STEP 2: Get Simgos + simgos, err := ReadEncounterDetailData(uint16(syncLink.Simgos_Id), &event) + if err != nil { + return nil, err + } + tx := db.NewTx() err = tx.Simgos.Transaction(func(tx *gorm.DB) error { // Step 2: Update Simgos - if err = UpdateSimgosData(input, syncLink, &event, tx); err != nil { + if err = UpdateTPendaftaranData(input, *simgos, &event, tx); err != nil { return err } @@ -153,14 +159,14 @@ func Delete(input e.DeleteDto) (*d.Data, error) { } // STEP 2: Get Simgos - sgData, err := ReadDetailSimgosData(uint16(syncLink.Simgos_Id), &event) + sgData, err := ReadEncounterDetailData(uint16(syncLink.Simgos_Id), &event) if err != nil { return nil, err } err = db.WithDualTx(func(tx *db.Dualtx) error { // STEP 3: Delete Simgos - err = HardDeleteSimgosData(sgData, &event, tx.Simgos) + err = HardDeleteTPendaftaranData(sgData, &event, tx.Simgos) if err != nil { return err } @@ -195,3 +201,91 @@ func Delete(input e.DeleteDto) (*d.Data, error) { }, nil } + +func CheckIn(input e.Encounter) (*d.Data, error) { + event := pl.Event{ + Feature: "CheckIn", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "check-in") + + // STEP 1: Get Link + syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + if err != nil { + return nil, err + } + + // STEP 2: Get Simgos + simgos, err := ReadEncounterDetailData(uint16(syncLink.Simgos_Id), &event) + if err != nil { + return nil, err + } + + tx := db.NewTx() + err = tx.Simgos.Transaction(func(tx *gorm.DB) error { + // Step 2: Update Simgos + if err = UpdateCheckinData(input, *simgos, &event, tx); err != nil { + return err + } + + // TODO: insert t_pemeriksaan_hist + + return nil + }) + + pl.SetLogInfo(&event, nil, "complete") + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + }, nil +} + +func CheckOut(input e.DischargeDto) (*d.Data, error) { + event := pl.Event{ + Feature: "CheckIn", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "check-in") + + // STEP 1: Get Link + syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + if err != nil { + return nil, err + } + + // STEP 2: Get Simgos + simgos, err := ReadEncounterDetailData(uint16(syncLink.Simgos_Id), &event) + if err != nil { + return nil, err + } + + tx := db.NewTx() + err = tx.Simgos.Transaction(func(tx *gorm.DB) error { + // Step 2: Update Simgos + if err = UpdateCheckoutData(input, *simgos, &event, tx); err != nil { + return err + } + + // TODO: insert t_pemeriksaan_hist + + return nil + }) + + pl.SetLogInfo(&event, nil, "complete") + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "updated", + }, + }, nil +} diff --git a/internal/use-case/simgos-sync-use-case/encounter/helper.go b/internal/use-case/simgos-sync-use-case/encounter/helper.go index 586dbd81..4672daa5 100644 --- a/internal/use-case/simgos-sync-use-case/encounter/helper.go +++ b/internal/use-case/simgos-sync-use-case/encounter/helper.go @@ -7,11 +7,10 @@ package encounter import ( "encoding/json" epr "simrs-vx/internal/domain/main-entities/person-relative" - erp "simrs-vx/internal/domain/references/person" - "strconv" - erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" + erp "simrs-vx/internal/domain/references/person" + "strconv" e "simrs-vx/internal/domain/main-entities/encounter" esimgos "simrs-vx/internal/domain/simgos-entities/encounter" @@ -22,17 +21,47 @@ import ( "gorm.io/gorm/clause" ) -func setDataCreate(input *e.Encounter) (data esimgos.TPendaftaran, err error) { +func setDataCreateTPendaftaran(input *e.Encounter) (data esimgos.TPendaftaran, err error) { data.Nomr = *input.Patient.Number + + // set pasienbaru + if input.NewStatus { + data.Pasienbaru = 1 + } + + // default kelas daftar + data.KelasDaftar = 3 + + // set noAntrian + defaultFromSimx := "rehab" + data.NoAntrian = &defaultFromSimx + + // set no_kunjung + noKunjung, err := generateNoKunjung() + if err != nil { + return esimgos.TPendaftaran{}, err + } + data.NoKunjung = uint(noKunjung) + + setDataTPendaftaran(input, data) + + return +} + +func setDataTPendaftaran(input *e.Encounter, data esimgos.TPendaftaran) { data.Tglreg = input.RegisteredAt // set kddokter - kddokter, _ := strconv.Atoi(*input.Appointment_Doctor_Code) - data.Kddokter = uint(kddokter) + if d := input.Appointment_Doctor_Code; d != nil { + kddokter, _ := strconv.Atoi(*input.Appointment_Doctor_Code) + data.Kddokter = uint(kddokter) + } // set kdpoly - kdpoly, _ := strconv.Atoi(*input.Unit_Code) - data.Kdpoly = uint(kdpoly) + if p := input.Unit_Code; p != nil { + kdpoly, _ := strconv.Atoi(*input.Unit_Code) + data.Kdpoly = uint(kdpoly) + } // set kdrujuk setKdrujuk(input.RefType_Code, data) @@ -42,11 +71,6 @@ func setDataCreate(input *e.Encounter) (data esimgos.TPendaftaran, err error) { // TODO: shift - // set pasienbaru - if input.NewStatus { - data.Pasienbaru = 1 - } - // set nip if emp := input.Adm_Employee; emp != nil { if user := emp.User; user != nil { @@ -74,54 +98,40 @@ func setDataCreate(input *e.Encounter) (data esimgos.TPendaftaran, err error) { data.Jamreg = input.RegisteredAt - // default kelas daftar - data.KelasDaftar = 3 - // TODO: st_asal_masuk - // set noAntrian - defaultFromSimx := "rehab" - data.NoAntrian = &defaultFromSimx - - // set no_kunjung - noKunjung, err := generateNoKunjung() - if err != nil { - return esimgos.TPendaftaran{}, err - } - - data.NoKunjung = uint(noKunjung) - return } -func setDataUpdate(input *e.Encounter, data esimgos.TPendaftaran) { +func setDataCheckIn(input e.Encounter, data esimgos.TPendaftaran) { // set kddokter kddokter, _ := strconv.Atoi(*input.Responsible_Doctor_Code) data.Kddokter = uint(kddokter) - // set kdpoly - kdpoly, _ := strconv.Atoi(*input.Unit_Code) - data.Kdpoly = uint(kdpoly) - - // set status - setStatus(input.Discharge_Method_Code, data) - if input.Status_Code == erc.DSCCancel { - data.Status = 11 + // set petugas_klinik + if data.PetugasKlinik != "" { + if a := input.Adm_Employee; a != nil { + if u := a.User; u != nil { + data.PetugasKlinik = u.Name + } + } } data.Masukpoly = input.StartedAt - data.Keluarpoly = input.FinishedAt + return +} - if p := input.Patient.Person; p != nil { - mapRelative(p.Relatives, data) - } +func setDataCheckOut(input e.DischargeDto, data esimgos.TPendaftaran) { + data.Keluarpoly = &now - // set petugas_klinik - if emp := input.Adm_Employee; emp != nil { - if user := emp.User; user != nil { - data.PetugasKlinik = user.Name - } + // set status + setStatus(input.Discharge_Method_Code, data) + if data.Status == 8 || data.Status == 3 { + data.SebabMati1 = input.DeathCause } + //if input.Status_Code == erc.DSCCancel { + // data.Status = 11 + //} return } @@ -162,18 +172,34 @@ func setStatus(code *ere.DischargeMethodCode, data esimgos.TPendaftaran) { data.Status = 2 case ere.DMCDeathOnArrival: data.Status = 3 + data.StMeninggal = func(u uint) *uint { return &u }(1) + data.DtMeninggal = &now case ere.DMCDeath: data.Status = 8 + data.StMeninggal = func(u uint) *uint { return &u }(1) + data.DtMeninggal = &now case ere.DMCHomeReq: data.Status = 7 case ere.DMCConsulPoly: data.Status = 5 + data.PoliNameHfis = nil + data.SpesialisIdHfis = nil + data.DokterNameHfis = nil + data.DokterIdHfis = nil case ere.DMCExtRef: data.Status = 6 case ere.DMCConsulChDay: data.Status = 12 + data.PoliNameHfis = nil + data.SpesialisIdHfis = nil + data.DokterNameHfis = nil + data.DokterIdHfis = nil case ere.DMCEmergency: data.Status = 10 + data.PoliNameHfis = nil + data.SpesialisIdHfis = nil + data.DokterNameHfis = nil + data.DokterIdHfis = nil case ere.DMCEmergencyCovid: data.Status = 13 case ere.DMCConsulExecutive: diff --git a/internal/use-case/simgos-sync-use-case/encounter/lib.go b/internal/use-case/simgos-sync-use-case/encounter/lib.go index 9ccf433c..f4c0023f 100644 --- a/internal/use-case/simgos-sync-use-case/encounter/lib.go +++ b/internal/use-case/simgos-sync-use-case/encounter/lib.go @@ -17,10 +17,13 @@ import ( var now = time.Now() -func CreateSimgosData(input e.Encounter, event *pl.Event, dbx ...*gorm.DB) (*esimgos.TPendaftaran, error) { +func CreateTPendaftaranData(input e.Encounter, event *pl.Event, dbx ...*gorm.DB) (*esimgos.TPendaftaran, error) { pl.SetLogInfo(event, nil, "started", "DBCreate") - data := setDataSimgos(&input) + data, err := setDataCreateTPendaftaran(&input) + if err != nil { + return nil, plh.HandleCreateError(input, event, err) + } var tx *gorm.DB if len(dbx) > 0 { @@ -37,7 +40,7 @@ func CreateSimgosData(input e.Encounter, event *pl.Event, dbx ...*gorm.DB) (*esi return &data, nil } -func ReadDetailSimgosData(simgosId uint16, event *pl.Event) (*esimgos.TPendaftaran, error) { +func ReadEncounterDetailData(simgosId uint16, event *pl.Event) (*esimgos.TPendaftaran, error) { pl.SetLogInfo(event, simgosId, "started", "DBReadDetail") data := esimgos.TPendaftaran{} @@ -55,11 +58,10 @@ func ReadDetailSimgosData(simgosId uint16, event *pl.Event) (*esimgos.TPendaftar return &data, nil } -func UpdateSimgosData(input e.Encounter, dataSimgos *esync.EncounterLink, event *pl.Event, dbx ...*gorm.DB) error { +func UpdateTPendaftaranData(input e.Encounter, data esimgos.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, input, "started", "DBUpdate") - data := setDataSimgos(&input) - data.Idxdaftar = dataSimgos.Simgos_Id + setDataTPendaftaran(&input, data) var tx *gorm.DB if len(dbx) > 0 { @@ -82,7 +84,7 @@ func UpdateSimgosData(input e.Encounter, dataSimgos *esync.EncounterLink, event return nil } -func HardDeleteSimgosData(data *esimgos.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error { +func HardDeleteTPendaftaranData(data *esimgos.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, data, "started", "DBDelete") var tx *gorm.DB @@ -107,6 +109,59 @@ func HardDeleteSimgosData(data *esimgos.TPendaftaran, event *pl.Event, dbx ...*g return nil } +func UpdateCheckinData(input e.Encounter, data esimgos.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, input, "started", "DBUpdate") + + setDataCheckIn(input, data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.IS["simrs"] + } + + 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 UpdateCheckoutData(input e.DischargeDto, data esimgos.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error { + pl.SetLogInfo(event, input, "started", "DBUpdate") + + setDataCheckOut(input, data) + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.IS["simrs"] + } + + 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 CreateTPemeriksaanHist func CreateLinkData(simxId, simgosId uint, event *pl.Event, dbx ...*gorm.DB) (*esync.EncounterLink, error) { pl.SetLogInfo(event, nil, "started", "DBCreate") data := setDataSimxLink(simxId, simgosId) From f54a2b5b758231ebfc1775809d01632d620a2f31 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Mon, 24 Nov 2025 11:37:57 +0700 Subject: [PATCH 11/29] feat/trx-orders: fix doctor_code + id type --- .../antibiotic-src-category/handler.go | 21 +++++++++++-------- .../main-use-case/antibiotic-in-use/helper.go | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/internal/interface/main-handler/antibiotic-src-category/handler.go b/internal/interface/main-handler/antibiotic-src-category/handler.go index 7dde9c66..cc4766af 100644 --- a/internal/interface/main-handler/antibiotic-src-category/handler.go +++ b/internal/interface/main-handler/antibiotic-src-category/handler.go @@ -33,39 +33,42 @@ func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { } func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { - id := rw.ValidateInt(w, "id", r.PathValue("id")) - if id <= 0 { + intId := rw.ValidateInt(w, "id", r.PathValue("id")) + if intId <= 0 { return } + id := uint16(intId) dto := e.ReadDetailDto{} - dto.Id = uint16(id) + dto.Id = &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 { + intId := rw.ValidateInt(w, "id", r.PathValue("id")) + if intId <= 0 { return } + id := uint16(intId) dto := e.UpdateDto{} if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { return } - dto.Id = uint16(id) + dto.Id = &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 { + intId := rw.ValidateInt(w, "id", r.PathValue("id")) + if intId <= 0 { return } + id := uint16(intId) dto := e.DeleteDto{} - dto.Id = uint16(id) + dto.Id = &id res, err := u.Delete(dto) rw.DataResponse(w, res, err) } diff --git a/internal/use-case/main-use-case/antibiotic-in-use/helper.go b/internal/use-case/main-use-case/antibiotic-in-use/helper.go index 8f81510e..20741ce1 100644 --- a/internal/use-case/main-use-case/antibiotic-in-use/helper.go +++ b/internal/use-case/main-use-case/antibiotic-in-use/helper.go @@ -30,7 +30,7 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.AntibioticInUse) { // data.ExaminationDate = inputSrc.ExaminationDate } -func ValidateMcuOrderDoctor(input e.ReadDetailDto, doctorId uint, event *pl.Event, tx *gorm.DB) error { +func ValidateMcuOrderDoctor(input e.ReadDetailDto, doctorCode string, event *pl.Event, tx *gorm.DB) error { pl.SetLogInfo(event, input, "started", "ValidateMcuOrderDoctor") var mcuOrder emo.McuOrder @@ -47,7 +47,7 @@ func ValidateMcuOrderDoctor(input e.ReadDetailDto, doctorId uint, event *pl.Even return pl.SetLogError(event, input) } - if mcuOrder.Doctor_Id == nil || *mcuOrder.Doctor_Id != doctorId { + if mcuOrder.Doctor_Code == nil || *mcuOrder.Doctor_Code != doctorCode { return errors.New("doctor is not authorized for this mcu-order") } From da88637fe9ce23e618b3c3f19ecab82a4b518b00 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 24 Nov 2025 12:21:17 +0700 Subject: [PATCH 12/29] feat (resume): wip --- assets/docs/resume.html | 432 ++++++++++++++++++ .../use-case/main-use-case/resume/case.go | 295 ++++++++++++ .../use-case/main-use-case/resume/helper.go | 22 + internal/use-case/main-use-case/resume/lib.go | 140 ++++++ .../main-use-case/resume/middleware-runner.go | 103 +++++ .../main-use-case/resume/middleware.go | 9 + .../use-case/main-use-case/resume/tycovar.go | 44 ++ .../main-use-case/unit-position/case.go | 2 +- 8 files changed, 1046 insertions(+), 1 deletion(-) create mode 100644 assets/docs/resume.html create mode 100644 internal/use-case/main-use-case/resume/case.go create mode 100644 internal/use-case/main-use-case/resume/helper.go create mode 100644 internal/use-case/main-use-case/resume/lib.go create mode 100644 internal/use-case/main-use-case/resume/middleware-runner.go create mode 100644 internal/use-case/main-use-case/resume/middleware.go create mode 100644 internal/use-case/main-use-case/resume/tycovar.go diff --git a/assets/docs/resume.html b/assets/docs/resume.html new file mode 100644 index 00000000..fdf47a2d --- /dev/null +++ b/assets/docs/resume.html @@ -0,0 +1,432 @@ + + + + + General Consent + + + + + + + + + + +
+ logo + +
+ PEMERINTAH PROVINSI JAWA TIMUR +
+
+ RUMAH SAKIT UMUM DAERAH Dr. SAIFUL ANWAR +
+
+ TERAKREDITASI KARS VERSI 2012 TINGKAT PARIPURNA +
+
+ Jl. Jaksa Agung Suprapto No. 2 MALANG 65111 +
+
Telp. (0341) 362101, Fax. (0341) 362110
+
Email: rsu-drsaifulanwar@jatimprov.go.id
+
Website: www.rsudsaifulanwar.jatimprov.go.id
+
+ logo +
+ +
+ +
+ IDENTITAS PASIEN +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ No. RM + : {{ .Repalcable }} + Kelas + : {{ .Repalcable }}
+ NIK + : {{ .Repalcable }} + Ruang/Klinik + : {{ .Repalcable }}
+ Nama + : {{ .Repalcable }} + DPJP + : {{ .Repalcable }}
+ Tempat/Tanggal Lahir + : {{ .Repalcable }}
+ Jenis Kelamin + : {{ .Repalcable }}
+ No. Telepon + : {{ .Repalcable }}
+ +
+

+ RESUME MEDIS +

+

Medical Discharge Summary

+ + +

(Diisi oleh Dokter Penanggung Jawab Pelayanan)

+ + + + + + + + + + + + + + + + + + + + + +
+ Tanggal Masuk + : {{ .Repalcable }}
+ Tanggal Keluar + : {{ .Repalcable }}
+ Diagnosis Masuk + : {{ .Repalcable }}
+ Diagnosis Keluar + : {{ .Repalcable }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ No + + Diagnosa + + Dasar Diagnosa + + Kode ICD-10 +
+ 1 + {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ 2 + {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ 3 + {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ + + + + + + +

Kajian Awal Medis

+

(Diisi oleh Dokter Penanggung Jawab Pelayanan)

+ + + + + + + + + + + + + + + + + +
+ Keluhan Utama + : {{ .Repalcable }}
+ Riwayat Penyakit + : {{ .Repalcable }}
+ Pemeriksaan Fisik & Keadaan Umum + : {{ .Repalcable }}
+ +

Pemeriksaan Penunjang

+ + + + + + +
+ Pemeriksaan + : {{ .Repalcable }}
+ +

Tindakan Medis

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ No + + Tindakan + + Dasar Tindakan + + Kode ICD 9-CM +
+ 1 + {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ 2 + {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ 3 + {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ + + + + + + +
+ Keluhan Utama + : {{ .Repalcable }}
+ + + + + +

Konsultasi

+ + + + + + + + + + + + + + + + + + + + + +
+ No + + Konsultasi + + Jawaban Konsultasi +
+ 1 + {{ .Repalcable }}{{ .Repalcable }}
+ 2 + {{ .Repalcable }}{{ .Repalcable }}
+ 3 + {{ .Repalcable }}{{ .Repalcable }}
+ +

Terapi

+ + + + + + +
+ Alergi + : + Replace +
+ + + +

Obat yang diberikan

+ + + + + + + + + + + + + + + + + +
+ Terapi (Farmakologi & Non Farmakologi) Selawa Perawatan + + Terapi (Farmakologi & Non Farmakologi) Selawa Pulang +
{{ .Repalcable }}{{ .Repalcable }}
{{ .Repalcable }}{{ .Repalcable }}
{{ .Repalcable }}{{ .Repalcable }}
+ + + + + + +

Kondisi saat Pulang

+ + + + + + +
+ Keluhan Utama + : {{ .Repalcable }}
+ +
+ + + \ No newline at end of file diff --git a/internal/use-case/main-use-case/resume/case.go b/internal/use-case/main-use-case/resume/case.go new file mode 100644 index 00000000..79a2adf1 --- /dev/null +++ b/internal/use-case/main-use-case/resume/case.go @@ -0,0 +1,295 @@ +package resume + +import ( + "errors" + "strconv" + + // main entities + e "simrs-vx/internal/domain/main-entities/resume" + + ue "simrs-vx/internal/use-case/main-use-case/encounter" + + 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 = "resume" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.Resume{} + + 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 + } + + // check if encounter is done + if ue.IsDone(*input.Encounter_Id, &event, tx) { + return errors.New("encounter is already done") + } + + 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.Resume + var dataList []e.Resume + 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.Resume + 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.Resume + 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 + } + + // check if encounter is done + if ue.IsDone(*input.Encounter_Id, &event, tx) { + return errors.New("encounter is already done") + } + + if err := UpdateData(input, data, &event, tx); err != nil { + return err + } + + // Get Updated Data + if data, err = ReadDetailData(rdDto, &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: uint(input.Id)} + var data *e.Resume + 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/resume/helper.go b/internal/use-case/main-use-case/resume/helper.go new file mode 100644 index 00000000..18ff757c --- /dev/null +++ b/internal/use-case/main-use-case/resume/helper.go @@ -0,0 +1,22 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package resume + +import ( + e "simrs-vx/internal/domain/main-entities/resume" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Resume) { + 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.Value = inputSrc.Value +} diff --git a/internal/use-case/main-use-case/resume/lib.go b/internal/use-case/main-use-case/resume/lib.go new file mode 100644 index 00000000..16bb1afd --- /dev/null +++ b/internal/use-case/main-use-case/resume/lib.go @@ -0,0 +1,140 @@ +package resume + +import ( + "errors" + e "simrs-vx/internal/domain/main-entities/resume" + 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.Resume, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.Resume{} + 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.Resume, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.Resume{} + 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.Resume{}). + 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.Resume, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.Resume{} + + var tx *gorm.DB + if len(dbx) > 0 { + tx = dbx[0] + } else { + tx = dg.I + } + + if err := tx.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.Resume, 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.Resume, 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/resume/middleware-runner.go b/internal/use-case/main-use-case/resume/middleware-runner.go new file mode 100644 index 00000000..e7f4cade --- /dev/null +++ b/internal/use-case/main-use-case/resume/middleware-runner.go @@ -0,0 +1,103 @@ +package resume + +import ( + e "simrs-vx/internal/domain/main-entities/resume" + 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.Resume) 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.Resume) 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.Resume) 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.Resume) 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.Resume) 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/resume/middleware.go b/internal/use-case/main-use-case/resume/middleware.go new file mode 100644 index 00000000..dc0aa8c9 --- /dev/null +++ b/internal/use-case/main-use-case/resume/middleware.go @@ -0,0 +1,9 @@ +package resume + +// 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/resume/tycovar.go b/internal/use-case/main-use-case/resume/tycovar.go new file mode 100644 index 00000000..13f36d2a --- /dev/null +++ b/internal/use-case/main-use-case/resume/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 resume + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/resume" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.Resume, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.Resume, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.Resume, 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/unit-position/case.go b/internal/use-case/main-use-case/unit-position/case.go index 47ea67cd..c472e90c 100644 --- a/internal/use-case/main-use-case/unit-position/case.go +++ b/internal/use-case/main-use-case/unit-position/case.go @@ -290,7 +290,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { func validateForeignKey(input e.CreateDto) error { // validate installation_id - if _, err := uu.ReadDetail(eu.ReadDetailDto{Code: &input.Code}); err != nil { + if _, err := uu.ReadDetail(eu.ReadDetailDto{Code: input.Unit_Code}); err != nil { return err } From 8ea916261466dd0b0488c4a662c210093102e6c0 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 24 Nov 2025 12:28:15 +0700 Subject: [PATCH 13/29] fix (unit-position): wrong validation unit_code --- internal/use-case/main-use-case/unit-position/case.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/use-case/main-use-case/unit-position/case.go b/internal/use-case/main-use-case/unit-position/case.go index 47ea67cd..c472e90c 100644 --- a/internal/use-case/main-use-case/unit-position/case.go +++ b/internal/use-case/main-use-case/unit-position/case.go @@ -290,7 +290,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { func validateForeignKey(input e.CreateDto) error { // validate installation_id - if _, err := uu.ReadDetail(eu.ReadDetailDto{Code: &input.Code}); err != nil { + if _, err := uu.ReadDetail(eu.ReadDetailDto{Code: input.Unit_Code}); err != nil { return err } From 5322e986e4c7b4cbd19f03c686033915feaf1f86 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 24 Nov 2025 17:13:40 +0700 Subject: [PATCH 14/29] feat (resume): add verify and validate, base value structure --- .../domain/main-entities/encounter/entity.go | 7 + internal/domain/main-entities/resume/dto.go | 119 ++++++++++++++++- .../domain/main-entities/resume/entity.go | 12 ++ .../interface/main-handler/main-handler.go | 10 ++ .../interface/main-handler/resume/handler.go | 121 ++++++++++++++++++ .../use-case/main-use-case/resume/case.go | 97 ++++++++++++++ .../use-case/main-use-case/resume/helper.go | 2 + 7 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 internal/interface/main-handler/resume/handler.go diff --git a/internal/domain/main-entities/encounter/entity.go b/internal/domain/main-entities/encounter/entity.go index 61496f9e..f8f0c66f 100644 --- a/internal/domain/main-entities/encounter/entity.go +++ b/internal/domain/main-entities/encounter/entity.go @@ -82,3 +82,10 @@ type Encounter struct { func (d Encounter) IsDone() bool { return d.Status_Code == erc.DSCDone } + +func (d Encounter) IsSameResponsibleDoctor(input *string) bool { + if input == nil { + return false + } + return d.Responsible_Doctor_Code == input +} diff --git a/internal/domain/main-entities/resume/dto.go b/internal/domain/main-entities/resume/dto.go index ea9485d7..bf505d20 100644 --- a/internal/domain/main-entities/resume/dto.go +++ b/internal/domain/main-entities/resume/dto.go @@ -2,11 +2,19 @@ package resume import ( ecore "simrs-vx/internal/domain/base-entities/core" + "time" + + erc "simrs-vx/internal/domain/references/common" + + pa "simrs-vx/internal/lib/auth" ) type CreateDto struct { - Encounter_Id *uint `json:"encounter_id"` - Value *string `json:"value"` + Encounter_Id *uint `json:"encounter_id"` + Value *string `json:"value"` + Status_Code erc.DataVerifiedCode `json:"status_code"` + + pa.AuthInfo } type ReadListDto struct { @@ -16,7 +24,8 @@ type ReadListDto struct { } type FilterDto struct { - Encounter_Id *uint `json:"encounter-id"` + Encounter_Id *uint `json:"encounter-id"` + Doctor_Code *string `json:"doctor-code"` } type ReadDetailDto struct { @@ -30,6 +39,8 @@ type UpdateDto struct { type DeleteDto struct { Id uint `json:"id"` + + pa.AuthInfo } type MetaDto struct { @@ -40,16 +51,20 @@ type MetaDto struct { type ResponseDto struct { ecore.Main - Encounter_Id *uint `json:"encounter_id"` - Value *string `json:"value"` - FileUrl *string `json:"fileUrl"` + Encounter_Id *uint `json:"encounter_id"` + Doctor_Code *string `json:"doctor_code"` + Value *string `json:"value"` + FileUrl *string `json:"fileUrl"` + Status_Code erc.DataVerifiedCode `json:"status_code"` } func (d Resume) ToResponse() ResponseDto { resp := ResponseDto{ Encounter_Id: d.Encounter_Id, + Doctor_Code: d.Doctor_Code, Value: d.Value, FileUrl: d.FileUrl, + Status_Code: d.Status_Code, } resp.Main = d.Main return resp @@ -62,3 +77,95 @@ func ToResponseList(data []Resume) []ResponseDto { } return resp } + +// ValueDto is for resume value +type ValueDto struct { + Assessment Assessment `json:"assessment"` + Diagnosis Diagnosis `json:"diagnosis"` + Action Action `json:"action"` + Consultation Consultation `json:"consultation"` + Supporting SupportingExaminations `json:"supporting"` + Pharmacy PharmacyData `json:"pharmacy"` + Discharge DischargeCondition `json:"discharge"` + National NationalProgram `json:"national"` + Management Management `json:"management"` +} + +type Assessment struct { + StartedAt *time.Time `json:"startedAt` + FinishedAt *time.Time `json:"finishedAt` + Doctor_Code string `json:"doctor_code` + DiagnosisIn string `json:"diagnosesIn` + AmbulatoryIndication string `json:"ambulatoryIndication"` + MainComplaint string `json:"mainComplaint"` + PhysicalExamination string `json:"physicalExamination"` + MedicalHistory string `json:"medicalHistory"` + MedicalDiagnosis string `json:"medicalDiagnosis"` +} + +type Diagnosis struct { + PrimaryDiagnosis DiagnosisEntry `json:"primaryDiagnosis"` + SecondaryDiagnoses []DiagnosisEntry `json:"secondaryDiagnoses"` +} + +type DiagnosisEntry struct { + Diagnosis string `json:"diagnosis"` + ICD10 string `json:"icd_10"` + Basis string `json:"basis"` // Clinical basis of diagnosis / dasar diagnosa +} + +type Action struct { + PrimaryAction ActionEntry `json:"primaryAction"` + AdditionalActions []ActionEntry `json:"additionalActions"` + MedicalActions string `json:"medicalActions"` // free-text: "Tindakan Medis" +} + +type ActionEntry struct { + Action string `json:"action"` // Tindakan + ICD9 string `json:"icd_9"` // ICD-9 + Basis string `json:"basis"` // Dasar Tindakan +} + +type Consultation struct { + Consultations []ConsultationEntry `json:"consultations"` +} + +type ConsultationEntry struct { + Consultation string `json:"consultation"` // Konsultasi + ConsultationAnswer string `json:"consultationAnswer"` // Jawaban Konsultasi +} + +type SupportingExaminations struct { + Notes string `json:"notes"` // Free-text list of lab/imaging results +} + +type PharmacyData struct { + AllergySpecialConditions string `json:"allergySpecialConditions"` // Kelainan Khusus Alergi + OtherConditions string `json:"otherConditions"` // Kelainan Lain + TherapyDuringCare string `json:"therapyDuringCare"` // Terapi selama dirawat + TherapyAtDischarge string `json:"therapyAtDischarge"` // Terapi waktu pulang + FollowUpInstructions string `json:"followUpInstructions"` // Edukasi / Anjuran / Follow-up +} + +type DischargeCondition struct { + BloodPressureSystolic float64 `json:"bloodPressureSystolic"` // Tekanan Darah Sistol (mmHg) + BloodPressureDiastolic float64 `json:"bloodPressureDiastolic"` // Tekanan Darah Diastol (mmHg) + RespirationRate float64 `json:"respirationRate"` // Pernafasan (kali/menit) + HeartRate float64 `json:"heartRate"` // Denyut Jantung (kali/menit) + BodyTemperature float64 `json:"bodyTemperature"` // Suhu Tubuh (°C) + + ConsciousnessLevel string `json:"consciousnessLevel"` // Tingkat Kesadaran + PainScale int `json:"painScale"` // Skala Nyeri (0–10) +} + +type NationalProgram struct { + ProgramService string `json:"programService"` // e.g. "Antenatal Care" + ProgramServiceStatus string `json:"programServiceStatus"` // e.g. "Suspected" +} + +type Management struct { + NationalProgramService string `json:"nationalProgramService"` // e.g. selected program + FollowUpManagement string `json:"followUpManagement"` // e.g. further management plan + ConditionOnDischarge string `json:"conditionOnDischarge"` // e.g. "Stable" + DischargeMethod string `json:"dischargeMethod"` // e.g. "Discharged with Doctor's Approval" +} diff --git a/internal/domain/main-entities/resume/entity.go b/internal/domain/main-entities/resume/entity.go index 70050166..93a1d8cd 100644 --- a/internal/domain/main-entities/resume/entity.go +++ b/internal/domain/main-entities/resume/entity.go @@ -16,3 +16,15 @@ type Resume struct { FileUrl *string `json:"fileUrl" gorm:"size:1024"` Status_Code erc.DataVerifiedCode `json:"status_code" gorm:"not null;size:10"` } + +func (d Resume) IsNew() bool { + return d.Status_Code == erc.DVCNew +} + +func (d Resume) IsVerified() bool { + return d.Status_Code == erc.DVCVerified +} + +func (d Resume) IsValidated() bool { + return d.Status_Code == erc.DVCValidated +} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index 56811194..1dc4c893 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -34,6 +34,7 @@ import ( prescription "simrs-vx/internal/interface/main-handler/prescription" prescriptionitem "simrs-vx/internal/interface/main-handler/prescription-item" responsibledoctorhist "simrs-vx/internal/interface/main-handler/responsible-doctor-hist" + resume "simrs-vx/internal/interface/main-handler/resume" sbar "simrs-vx/internal/interface/main-handler/sbar" soapi "simrs-vx/internal/interface/main-handler/soapi" uploadfile "simrs-vx/internal/interface/main-handler/upload-file" @@ -288,6 +289,15 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/encounter-document", encounterdocument.O) hc.RegCrud(r, "/v1/general-consent", generalconsent.O) r.HandleFunc("POST /v1/generate-file", generatefile.Generate) + hk.GroupRoutes("/v1/resume", r, auth.GuardMW, hk.MapHandlerFunc{ + "POST /": resume.Create, + "GET /": resume.GetList, + "GET /{id}": resume.GetDetail, + "PATCH /{id}": resume.Update, + "DELETE /{id}": resume.Delete, + "PATCH /{id}/verify": resume.Verify, + "PATCH /{id}/validate": resume.Validate, + }) /******************** actor ********************/ hc.RegCrud(r, "/v1/person", person.O) diff --git a/internal/interface/main-handler/resume/handler.go b/internal/interface/main-handler/resume/handler.go new file mode 100644 index 00000000..5574bc7a --- /dev/null +++ b/internal/interface/main-handler/resume/handler.go @@ -0,0 +1,121 @@ +package resume + +import ( + "net/http" + + d "github.com/karincake/dodol" + rw "github.com/karincake/risoles" + sf "github.com/karincake/semprit" + + // ua "github.com/karincake/tumpeng/auth/svc" + + e "simrs-vx/internal/domain/main-entities/resume" + u "simrs-vx/internal/use-case/main-use-case/resume" + + erc "simrs-vx/internal/domain/references/common" + + pa "simrs-vx/internal/lib/auth" +) + +func 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) +} + +func 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 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 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 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) +} + +func Verify(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) + } + 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) + dto.Status_Code = erc.DVCVerified + dto.AuthInfo = *authInfo + res, err := u.UpdateStatusCode(dto) + rw.DataResponse(w, res, err) +} + +func Validate(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) + } + 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) + dto.Status_Code = erc.DVCValidated + dto.AuthInfo = *authInfo + res, err := u.UpdateStatusCode(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/use-case/main-use-case/resume/case.go b/internal/use-case/main-use-case/resume/case.go index 79a2adf1..cbc472f9 100644 --- a/internal/use-case/main-use-case/resume/case.go +++ b/internal/use-case/main-use-case/resume/case.go @@ -4,7 +4,10 @@ import ( "errors" "strconv" + erc "simrs-vx/internal/domain/references/common" + // main entities + ee "simrs-vx/internal/domain/main-entities/encounter" e "simrs-vx/internal/domain/main-entities/resume" ue "simrs-vx/internal/use-case/main-use-case/encounter" @@ -31,6 +34,10 @@ func Create(input e.CreateDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "create") err := dg.I.Transaction(func(tx *gorm.DB) error { + if !input.AuthInfo.IsDoctor() { + return errors.New("user is not a doctor") + } + mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware @@ -43,6 +50,7 @@ func Create(input e.CreateDto) (*d.Data, error) { return errors.New("encounter is already done") } + input.Status_Code = erc.DVCNew if resData, err := CreateData(input, &event, tx); err != nil { return err } else { @@ -188,6 +196,10 @@ func Update(input e.UpdateDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "update") err = dg.I.Transaction(func(tx *gorm.DB) error { + if !input.AuthInfo.IsDoctor() { + return errors.New("user is not a doctor") + } + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err @@ -254,6 +266,10 @@ func Delete(input e.DeleteDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "delete") err = dg.I.Transaction(func(tx *gorm.DB) error { + if !input.AuthInfo.IsDoctor() { + return errors.New("user is not a doctor") + } + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err @@ -293,3 +309,84 @@ func Delete(input e.DeleteDto) (*d.Data, error) { }, nil } + +func UpdateStatusCode(input e.UpdateDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: input.Id} + var data *e.Resume + var err error + + event := pl.Event{ + Feature: "UpdateStatusCode", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "updateStatusCode") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + if !input.AuthInfo.IsDoctor() { + return errors.New("user is not a doctor") + } + + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + enc, err := ue.ReadDetailData(ee.ReadDetailDto{Id: uint16(*data.Encounter_Id)}, &event, tx) + if err != nil { + return err + } + + // check if encounter is done + if enc.IsDone() { + return errors.New("encounter is already done") + } + + switch input.Status_Code { + case erc.DVCValidated: + if !enc.IsSameResponsibleDoctor(input.AuthInfo.Doctor_Code) { + return errors.New("validation doctor is not the same as encounter responsible doctor") + } + + if data.IsNew() { + return errors.New("resume need to be verified first") + } + if data.IsValidated() { + return errors.New("resume already validated") + } + data.Status_Code = erc.DVCValidated + err = tx.Save(&data).Error + if err != nil { + return err + } + case erc.DVCVerified: + if data.IsValidated() { + return errors.New("resume already validated") + } + if data.IsVerified() { + return errors.New("resume already verified") + } + data.Status_Code = erc.DVCVerified + err = tx.Save(&data).Error + if 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 +} diff --git a/internal/use-case/main-use-case/resume/helper.go b/internal/use-case/main-use-case/resume/helper.go index 18ff757c..edd45222 100644 --- a/internal/use-case/main-use-case/resume/helper.go +++ b/internal/use-case/main-use-case/resume/helper.go @@ -18,5 +18,7 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Resume) { } data.Encounter_Id = inputSrc.Encounter_Id + data.Doctor_Code = inputSrc.AuthInfo.Doctor_Code data.Value = inputSrc.Value + data.Status_Code = inputSrc.Status_Code } From ca0fee997eb5cb671898688cad179083c99a1a46 Mon Sep 17 00:00:00 2001 From: vanilia Date: Tue, 25 Nov 2025 14:10:01 +0700 Subject: [PATCH 15/29] fix conflict --- README-DB.md | 4 ++-- cmd/main-api/config.yml-example | 2 +- .../new/division/handler.go | 3 ++- .../new/encounter/handler.go | 3 ++- .../new/installation/handler.go | 3 ++- .../new/patient/handler.go | 3 ++- .../new/specialist/handler.go | 3 ++- .../new/subspecialist/handler.go | 2 +- .../simgos-sync-handler/new/unit/handler.go | 3 ++- .../simgos-sync-handler.go | 19 ++++++++++--------- 10 files changed, 26 insertions(+), 19 deletions(-) diff --git a/README-DB.md b/README-DB.md index b99cf08a..c361ca31 100644 --- a/README-DB.md +++ b/README-DB.md @@ -15,7 +15,7 @@
Example: 1. `User_Id` represents the `User` object and the `Id` attribute 2. `BestProduct_Code` represents the `BestProduct` object and the `Code` attribute - 3. When more than one column has a similar definition, a `prefixnew` can be added to indicate the column's purpose. + 3. When more than one column has a similar definition, a `prefix` can be added to indicate the column's purpose.
Example: 1. `Admin_User_Id` represents the `Admin` of the `User` object and the `Id` attribute 2. `Operator_User_Id` refers to the `Operator` of the `User` object and the `Id` column. @@ -25,7 +25,7 @@
Example: 1. `user_id` represents the `User` object and the `Id` attribute. 2. `bestProduct_code` represents the `bestProduct` object and the `code` attribute. - 3. When more than one column has a similar definition, a `prefixnew` can be added to indicate the column's purpose. + 3. When more than one column has a similar definition, a `prefix` can be added to indicate the column's purpose.
Example: 1. `admin_user_id` refers to the `admin` of the `user` object and `id` attribute 2. `operator_user_id` refers to the `operator` of the `user` object, column `id` diff --git a/cmd/main-api/config.yml-example b/cmd/main-api/config.yml-example index 3f6224ad..30351abf 100644 --- a/cmd/main-api/config.yml-example +++ b/cmd/main-api/config.yml-example @@ -71,7 +71,7 @@ bpjsCfg: syncUrlCfg: enable: false host: - prefixnew: new-to-old + prefix: new-to-old docsCfg: path: ../../assets/docs/ \ No newline at end of file diff --git a/internal/interface/simgos-sync-handler/new/division/handler.go b/internal/interface/simgos-sync-handler/new/division/handler.go index 75059cd8..28920d21 100644 --- a/internal/interface/simgos-sync-handler/new/division/handler.go +++ b/internal/interface/simgos-sync-handler/new/division/handler.go @@ -2,13 +2,14 @@ package division import ( "net/http" - u "simrs-vx/internal/use-case/simgos-sync-use-case/new/division" rw "github.com/karincake/risoles" // ua "github.com/karincake/tumpeng/auth/svc" e "simrs-vx/internal/domain/main-entities/division" esync "simrs-vx/internal/domain/sync-entities/log" + + u "simrs-vx/internal/use-case/simgos-sync-use-case/new/division" ) type myBase struct{} diff --git a/internal/interface/simgos-sync-handler/new/encounter/handler.go b/internal/interface/simgos-sync-handler/new/encounter/handler.go index 1f03c38c..e6b0515a 100644 --- a/internal/interface/simgos-sync-handler/new/encounter/handler.go +++ b/internal/interface/simgos-sync-handler/new/encounter/handler.go @@ -2,13 +2,14 @@ package encounter import ( "net/http" - u "simrs-vx/internal/use-case/simgos-sync-use-case/new/encounter" rw "github.com/karincake/risoles" // ua "github.com/karincake/tumpeng/auth/svc" e "simrs-vx/internal/domain/main-entities/encounter" esync "simrs-vx/internal/domain/sync-entities/log" + + u "simrs-vx/internal/use-case/simgos-sync-use-case/new/encounter" ) type myBase struct{} diff --git a/internal/interface/simgos-sync-handler/new/installation/handler.go b/internal/interface/simgos-sync-handler/new/installation/handler.go index eb5fe09d..3a1eca93 100644 --- a/internal/interface/simgos-sync-handler/new/installation/handler.go +++ b/internal/interface/simgos-sync-handler/new/installation/handler.go @@ -2,13 +2,14 @@ package installation import ( "net/http" - u "simrs-vx/internal/use-case/simgos-sync-use-case/new/installation" rw "github.com/karincake/risoles" // ua "github.com/karincake/tumpeng/auth/svc" e "simrs-vx/internal/domain/main-entities/installation" esync "simrs-vx/internal/domain/sync-entities/log" + + u "simrs-vx/internal/use-case/simgos-sync-use-case/new/installation" ) type myBase struct{} diff --git a/internal/interface/simgos-sync-handler/new/patient/handler.go b/internal/interface/simgos-sync-handler/new/patient/handler.go index d3e6d591..f3c3951b 100644 --- a/internal/interface/simgos-sync-handler/new/patient/handler.go +++ b/internal/interface/simgos-sync-handler/new/patient/handler.go @@ -2,13 +2,14 @@ package patient import ( "net/http" - u "simrs-vx/internal/use-case/simgos-sync-use-case/new/patient" rw "github.com/karincake/risoles" // ua "github.com/karincake/tumpeng/auth/svc" e "simrs-vx/internal/domain/main-entities/patient" esync "simrs-vx/internal/domain/sync-entities/log" + + u "simrs-vx/internal/use-case/simgos-sync-use-case/new/patient" ) type myBase struct{} diff --git a/internal/interface/simgos-sync-handler/new/specialist/handler.go b/internal/interface/simgos-sync-handler/new/specialist/handler.go index b1a31dbb..2afe3f13 100644 --- a/internal/interface/simgos-sync-handler/new/specialist/handler.go +++ b/internal/interface/simgos-sync-handler/new/specialist/handler.go @@ -2,13 +2,14 @@ package specialist import ( "net/http" - u "simrs-vx/internal/use-case/simgos-sync-use-case/new/specialist" rw "github.com/karincake/risoles" // ua "github.com/karincake/tumpeng/auth/svc" e "simrs-vx/internal/domain/main-entities/specialist" esync "simrs-vx/internal/domain/sync-entities/log" + + u "simrs-vx/internal/use-case/simgos-sync-use-case/new/specialist" ) type myBase struct{} diff --git a/internal/interface/simgos-sync-handler/new/subspecialist/handler.go b/internal/interface/simgos-sync-handler/new/subspecialist/handler.go index 4cf6dd13..0bfd2226 100644 --- a/internal/interface/simgos-sync-handler/new/subspecialist/handler.go +++ b/internal/interface/simgos-sync-handler/new/subspecialist/handler.go @@ -2,13 +2,13 @@ package subspecialist import ( "net/http" - u "simrs-vx/internal/use-case/simgos-sync-use-case/new/subspecialist" rw "github.com/karincake/risoles" // ua "github.com/karincake/tumpeng/auth/svc" e "simrs-vx/internal/domain/main-entities/subspecialist" esync "simrs-vx/internal/domain/sync-entities/log" + u "simrs-vx/internal/use-case/simgos-sync-use-case/new/subspecialist" ) type myBase struct{} diff --git a/internal/interface/simgos-sync-handler/new/unit/handler.go b/internal/interface/simgos-sync-handler/new/unit/handler.go index 66479db2..95808728 100644 --- a/internal/interface/simgos-sync-handler/new/unit/handler.go +++ b/internal/interface/simgos-sync-handler/new/unit/handler.go @@ -2,13 +2,14 @@ package unit import ( "net/http" - u "simrs-vx/internal/use-case/simgos-sync-use-case/new/unit" rw "github.com/karincake/risoles" // ua "github.com/karincake/tumpeng/auth/svc" e "simrs-vx/internal/domain/main-entities/unit" esync "simrs-vx/internal/domain/sync-entities/log" + + u "simrs-vx/internal/use-case/simgos-sync-use-case/new/unit" ) type myBase struct{} diff --git a/internal/interface/simgos-sync-handler/simgos-sync-handler.go b/internal/interface/simgos-sync-handler/simgos-sync-handler.go index a82e81e6..0fc0d5b3 100644 --- a/internal/interface/simgos-sync-handler/simgos-sync-handler.go +++ b/internal/interface/simgos-sync-handler/simgos-sync-handler.go @@ -2,20 +2,14 @@ package simgossynchandler import ( "net/http" - "simrs-vx/internal/interface/simgos-sync-handler/new/division" - "simrs-vx/internal/interface/simgos-sync-handler/new/encounter" - "simrs-vx/internal/interface/simgos-sync-handler/new/installation" - "simrs-vx/internal/interface/simgos-sync-handler/new/patient" - "simrs-vx/internal/interface/simgos-sync-handler/new/specialist" - "simrs-vx/internal/interface/simgos-sync-handler/new/subspecialist" - "simrs-vx/internal/interface/simgos-sync-handler/new/unit" - hc "simrs-vx/pkg/handler-crud-helper" /******************** infra ********************/ gs "simrs-vx/internal/infra/gorm-setting" simgosdb "simrs-vx/internal/infra/simgos-db" + /******************** pkg ********************/ cmw "simrs-vx/pkg/cors-manager-mw" + hc "simrs-vx/pkg/handler-crud-helper" lh "simrs-vx/pkg/lang-helper" handlerlogger "simrs-vx/pkg/middleware/handler-logger" zlc "simrs-vx/pkg/zerolog-ctx" @@ -26,6 +20,13 @@ import ( /******************** internal ********************/ "simrs-vx/internal/interface/main-handler/home" + "simrs-vx/internal/interface/simgos-sync-handler/new/division" + "simrs-vx/internal/interface/simgos-sync-handler/new/encounter" + "simrs-vx/internal/interface/simgos-sync-handler/new/installation" + "simrs-vx/internal/interface/simgos-sync-handler/new/patient" + "simrs-vx/internal/interface/simgos-sync-handler/new/specialist" + "simrs-vx/internal/interface/simgos-sync-handler/new/subspecialist" + "simrs-vx/internal/interface/simgos-sync-handler/new/unit" ) func SetRoutes() http.Handler { @@ -56,7 +57,7 @@ func SetRoutes() http.Handler { }) hk.GroupRoutes(prefixnew+"/v1/encounter", r, hk.MapHandlerFunc{ "POST /": encounter.O.Create, - "POST /log": encounter.O.CreateLog, + "POST /log": encounter.O.CreateLog,git ad "PATCH /{id}": encounter.O.Update, "DELETE /{id}": encounter.O.Delete, "PATCH /{id}/checkin": encounter.O.Checkin, From 782d272d91249c3a0a36a76d163a980c92e79c85 Mon Sep 17 00:00:00 2001 From: vanilia Date: Tue, 25 Nov 2025 14:10:25 +0700 Subject: [PATCH 16/29] fix conflict --- internal/interface/simgos-sync-handler/simgos-sync-handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/interface/simgos-sync-handler/simgos-sync-handler.go b/internal/interface/simgos-sync-handler/simgos-sync-handler.go index 0fc0d5b3..ccfa6cda 100644 --- a/internal/interface/simgos-sync-handler/simgos-sync-handler.go +++ b/internal/interface/simgos-sync-handler/simgos-sync-handler.go @@ -57,7 +57,7 @@ func SetRoutes() http.Handler { }) hk.GroupRoutes(prefixnew+"/v1/encounter", r, hk.MapHandlerFunc{ "POST /": encounter.O.Create, - "POST /log": encounter.O.CreateLog,git ad + "POST /log": encounter.O.CreateLog, "PATCH /{id}": encounter.O.Update, "DELETE /{id}": encounter.O.Delete, "PATCH /{id}/checkin": encounter.O.Checkin, From 63f9d5512bd4760e2687518a3cffbcd21f53bd0a Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 25 Nov 2025 14:31:03 +0700 Subject: [PATCH 17/29] resume wip --- assets/docs/resume.html | 8 +++--- .../main-use-case/generate-file/tycovar.go | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/assets/docs/resume.html b/assets/docs/resume.html index fdf47a2d..8cdca672 100644 --- a/assets/docs/resume.html +++ b/assets/docs/resume.html @@ -82,7 +82,7 @@ : {{ .Repalcable }} - Kelas + Ruang/Klinik : {{ .Repalcable }} @@ -94,7 +94,7 @@ : {{ .Repalcable }} - Ruang/Klinik + Kelas : {{ .Repalcable }} @@ -112,7 +112,7 @@ {{ .Repalcable }} - + @@ -324,7 +324,7 @@ diff --git a/internal/use-case/main-use-case/generate-file/tycovar.go b/internal/use-case/main-use-case/generate-file/tycovar.go index 2f6a0ced..2bbe9fbf 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -7,6 +7,8 @@ import ( erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" + er "simrs-vx/internal/domain/main-entities/resume" + pu "simrs-vx/pkg/use-case-helper" ) @@ -32,6 +34,31 @@ type ControlLetterPDF struct { PrintDate string } +type ResumePDF struct { + MedicalRecord string + NIK string + Name string + BirthPlaceDate string + Gender string + Phone string + Class string + Unit string + Doctor_Name string + StartedDate string + FinishedDate string + DiagnosisIn string + DiagnosisOut string + Diagnosis []er.DiagnosisEntry + MainComplaint string + MedicalHistory string + PhysicalExamination string + SupportingExamination string + MedicalActions []er.ActionEntry + MedicalAction string + Consultations []er.ConsultationEntry + Allergy string +} + type GenerateDto struct { EntityType_Code ere.EntityTypeCode `json:"entityType_code" validate:"required"` Ref_Id *string `json:"ref_id" validate:"required"` From 8cf750c63f651cfd62e53b2f16d494e5aee73b1c Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 25 Nov 2025 15:52:07 +0700 Subject: [PATCH 18/29] wip --- .../domain/main-entities/person/entity.go | 60 ++++++ internal/domain/main-entities/resume/dto.go | 3 +- .../main-use-case/generate-file/case.go | 128 +---------- .../main-use-case/generate-file/helper.go | 203 ++++++++++++++++++ .../main-use-case/generate-file/tycovar.go | 4 +- internal/use-case/main-use-case/resume/lib.go | 2 +- pkg/conv-helper/conv-helper.go | 26 ++- pkg/use-case-helper/use-case-helper.go | 10 + 8 files changed, 305 insertions(+), 131 deletions(-) diff --git a/internal/domain/main-entities/person/entity.go b/internal/domain/main-entities/person/entity.go index 8d7f79a8..3c7d8629 100644 --- a/internal/domain/main-entities/person/entity.go +++ b/internal/domain/main-entities/person/entity.go @@ -10,6 +10,7 @@ import ( epi "simrs-vx/internal/domain/main-entities/person-insurance" epr "simrs-vx/internal/domain/main-entities/person-relative" er "simrs-vx/internal/domain/main-entities/regency" + "strings" erp "simrs-vx/internal/domain/references/person" @@ -58,3 +59,62 @@ func (d Person) IsSameResidentIdentityNumber(input *string) bool { } return d.ResidentIdentityNumber == input } + +func (d Person) GenderString() string { + if d.Gender_Code == nil { + return "" + } + switch *d.Gender_Code { + case erp.GCMale: + return "Laki-laki(L)" + case erp.GCFemale: + return "Perempuan(P)" + default: + return "" + } +} + +func (d Person) GetPhoneNumber() string { + if d.Contacts == nil { + return "" + } + for _, c := range *d.Contacts { + if c.Type_Code == erp.CTPhone || c.Type_Code == erp.CTMPhone { + return c.Value + } + } + return "" +} + +func (d Person) FullName() string { + name := strings.TrimSpace(d.Name) + if name == "" { + return "" + } + + parts := []string{} + + // Front title (dr., drs., etc) + if d.FrontTitle != nil { + ft := strings.TrimSpace(*d.FrontTitle) + if ft != "" { + parts = append(parts, ft) + } + } + + // Name (always included) + parts = append(parts, name) + + // Join front title + name + full := strings.Join(parts, " ") + + // End title → attach with comma + if d.EndTitle != nil { + et := strings.TrimSpace(*d.EndTitle) + if et != "" { + full = full + ", " + et + } + } + + return full +} diff --git a/internal/domain/main-entities/resume/dto.go b/internal/domain/main-entities/resume/dto.go index bf505d20..884b8e32 100644 --- a/internal/domain/main-entities/resume/dto.go +++ b/internal/domain/main-entities/resume/dto.go @@ -29,7 +29,8 @@ type FilterDto struct { } type ReadDetailDto struct { - Id uint `json:"id"` + Id uint `json:"id"` + Includes string `json:"includes"` } type UpdateDto struct { diff --git a/internal/use-case/main-use-case/generate-file/case.go b/internal/use-case/main-use-case/generate-file/case.go index 39cb2f02..7c657640 100644 --- a/internal/use-case/main-use-case/generate-file/case.go +++ b/internal/use-case/main-use-case/generate-file/case.go @@ -1,26 +1,14 @@ package generatefile import ( - "encoding/json" "errors" - evs "simrs-vx/internal/domain/bpjs-entities/vclaim-sep" - evscl "simrs-vx/internal/domain/bpjs-entities/vclaim-sep-control-letter" - egc "simrs-vx/internal/domain/main-entities/general-consent" - - uvs "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep" - uvscl "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep-control-letter" - ugc "simrs-vx/internal/use-case/main-use-case/general-consent" - - pc "simrs-vx/pkg/conv-helper" 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" - erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" ) @@ -32,133 +20,37 @@ func Generate(input GenerateDto) (*d.Data, error) { Source: source, } - var response ResponseDto + var ( + response *ResponseDto + err error + ) // Start log pl.SetLogInfo(&event, input, "started", "create") - err := dg.I.Transaction(func(tx *gorm.DB) error { + err = dg.I.Transaction(func(tx *gorm.DB) error { switch input.Type_Code { // general-consent case ere.DTCGC: - // get value from general consent by ref_id - gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id))}, &event) + response, err = generateGC(input, event, tx) if err != nil { return err } - if gc.FileUrl != nil { - if err := removeFile(string(input.EntityType_Code), *gc.FileUrl); err != nil { - return err - } - } - - // map template data - templateData := GeneralConsentPDF{} - if gc.Value != nil { - err := json.Unmarshal([]byte(*gc.Value), &templateData) - if err != nil { - event.ErrInfo = pl.ErrorInfo{ - Code: "data-unmarshal-fail", - Detail: err.Error(), - Raw: err, - } - return err - } - } else { - return errors.New("no value in this general consent") - } - - input.FormatType = erc.DFTCPDF - input.TemplateName = TDNGC - input.Encounter_Id = gc.Encounter_Id - templateData.Date = pu.FormatIndonesianDate(gc.CreatedAt) - - // generate file - urlPub, err := generateFile(input, templateData) - if err != nil { - return err - } - - gc.FileUrl = &urlPub - if err := dg.I.Save(&gc).Error; err != nil { - return err - } - - response = ResponseDto{ - FileUrl: urlPub, - } - // control-letter case ere.DTCVSCL: - // get value from control letter by ref_id - cl, err := uvscl.ReadDetailData(evscl.ReadDetailDto{Number: input.Ref_Id}, &event) - if err != nil { - if !pu.IsDataNotFoundError(err) { - return err - } - } - - if cl != nil && cl.FileUrl != nil { - if err := removeFile(string(input.EntityType_Code), *cl.FileUrl); err != nil { - return err - } - } - - // map template data - clData := VclaimControlLetter{} - if input.Data != nil { - err := json.Unmarshal([]byte(*input.Data), &clData) - if err != nil { - event.ErrInfo = pl.ErrorInfo{ - Code: "data-unmarshal-fail", - Detail: err.Error(), - Raw: err, - } - return err - } - } else { - return errors.New("there is no data to be used") - } - - if cl == nil { - createCL := evscl.CreateDto{ - VclaimSep_Number: &clData.VclaimSep.Number, - Number: &clData.Number, - Value: input.Data, - } - if cl, err = uvscl.CreateData(createCL, &event, tx); err != nil { - return err - } - - } - // get encounter id by vclaim sep number - vs, err := uvs.ReadDetailData(evs.ReadDetailDto{Number: &clData.VclaimSep.Number}, &event) + response, err = generateCL(input, event, tx) if err != nil { return err } - input.FormatType = erc.DFTCPDF - input.TemplateName = TDNCL - input.Encounter_Id = vs.Encounter_Id - input.UseA5Lanscape = true - - templateData := clData.generateTemplateData() - // generate file - urlPub, err := generateFile(input, templateData) + // resume + case ere.DTCResume: + response, err = generateResume(input, event, tx) if err != nil { return err } - cl.FileUrl = &urlPub - if err := tx.Save(&cl).Error; err != nil { - return err - } - - response = ResponseDto{ - FileUrl: urlPub, - } - default: return errors.New("invalid type code") } diff --git a/internal/use-case/main-use-case/generate-file/helper.go b/internal/use-case/main-use-case/generate-file/helper.go index 8cd3543f..e42fc13e 100644 --- a/internal/use-case/main-use-case/generate-file/helper.go +++ b/internal/use-case/main-use-case/generate-file/helper.go @@ -1,19 +1,36 @@ package generatefile import ( + "encoding/json" "errors" "fmt" "mime" "path/filepath" "time" + evs "simrs-vx/internal/domain/bpjs-entities/vclaim-sep" + evscl "simrs-vx/internal/domain/bpjs-entities/vclaim-sep-control-letter" + ee "simrs-vx/internal/domain/main-entities/encounter" + egc "simrs-vx/internal/domain/main-entities/general-consent" + er "simrs-vx/internal/domain/main-entities/resume" + + uvs "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep" + uvscl "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep-control-letter" + ue "simrs-vx/internal/use-case/main-use-case/encounter" + ugc "simrs-vx/internal/use-case/main-use-case/general-consent" + ur "simrs-vx/internal/use-case/main-use-case/resume" + erc "simrs-vx/internal/domain/references/common" docscfg "simrs-vx/internal/infra/docs-cfg" + pc "simrs-vx/pkg/conv-helper" pf "simrs-vx/pkg/file-helper" + pl "simrs-vx/pkg/logger" pm "simrs-vx/pkg/minio-helper" pp "simrs-vx/pkg/pdf-helper" pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" ) // generate temporary file, upload to minio, generate public url, delete temporary file @@ -93,3 +110,189 @@ func removeFile(bucket, fileUrl string) error { } return nil } + +// generate general consent +func generateGC(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) { + // get value from general consent by ref_id + gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id))}, &event) + if err != nil { + return nil, err + } + + if gc.FileUrl != nil { + if err := removeFile(string(input.EntityType_Code), *gc.FileUrl); err != nil { + return nil, err + } + } + + // map template data + templateData := GeneralConsentPDF{} + if gc.Value != nil { + err := json.Unmarshal([]byte(*gc.Value), &templateData) + if err != nil { + event.ErrInfo = pl.ErrorInfo{ + Code: "data-unmarshal-fail", + Detail: err.Error(), + Raw: err, + } + return nil, err + } + } else { + return nil, errors.New("no value in this general consent") + } + + input.FormatType = erc.DFTCPDF + input.TemplateName = TDNGC + input.Encounter_Id = gc.Encounter_Id + templateData.Date = pu.FormatIndonesianDate(gc.CreatedAt) + + // generate file + urlPub, err := generateFile(input, templateData) + if err != nil { + return nil, err + } + + gc.FileUrl = &urlPub + if err := tx.Save(&gc).Error; err != nil { + return nil, err + } + + response := ResponseDto{ + FileUrl: urlPub, + } + + return &response, nil +} + +func generateCL(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) { + // get value from control letter by ref_id + cl, err := uvscl.ReadDetailData(evscl.ReadDetailDto{Number: input.Ref_Id}, &event) + if err != nil { + if !pu.IsDataNotFoundError(err) { + return nil, err + } + } + + if cl != nil && cl.FileUrl != nil { + if err := removeFile(string(input.EntityType_Code), *cl.FileUrl); err != nil { + return nil, err + } + } + + // map template data + clData := VclaimControlLetter{} + if input.Data != nil { + err := json.Unmarshal([]byte(*input.Data), &clData) + if err != nil { + event.ErrInfo = pl.ErrorInfo{ + Code: "data-unmarshal-fail", + Detail: err.Error(), + Raw: err, + } + return nil, err + } + } else { + return nil, errors.New("there is no data to be used") + } + + if cl == nil { + createCL := evscl.CreateDto{ + VclaimSep_Number: &clData.VclaimSep.Number, + Number: &clData.Number, + Value: input.Data, + } + if cl, err = uvscl.CreateData(createCL, &event, tx); err != nil { + return nil, err + } + + } + // get encounter id by vclaim sep number + vs, err := uvs.ReadDetailData(evs.ReadDetailDto{Number: &clData.VclaimSep.Number}, &event) + if err != nil { + return nil, err + } + + input.FormatType = erc.DFTCPDF + input.TemplateName = TDNCL + input.Encounter_Id = vs.Encounter_Id + input.UseA5Lanscape = true + + templateData := clData.generateTemplateData() + // generate file + urlPub, err := generateFile(input, templateData) + if err != nil { + return nil, err + } + + cl.FileUrl = &urlPub + if err := tx.Save(&cl).Error; err != nil { + return nil, err + } + + response := ResponseDto{ + FileUrl: urlPub, + } + + return &response, nil +} + +func generateResume(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) { + // get value from resume by ref_id + includes := "Doctor.Employee.Person" + r, err := ur.ReadDetailData(er.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id)), Includes: includes}, &event) + if err != nil { + return nil, err + } + + if r.FileUrl != nil { + if err := removeFile(string(input.EntityType_Code), *r.FileUrl); err != nil { + return nil, err + } + } + + response := ResponseDto{ + FileUrl: "", + } + + return &response, nil +} + +func generateResumeTemplateData(resume er.Resume, event pl.Event, tx *gorm.DB) (*ResumePDF, error) { + // get encounter + includes := "Patient,Patient.Person,Patient.Person.BirthRegency,Patient.Person.Contacts" + encounter, err := ue.ReadDetailData(ee.ReadDetailDto{Id: uint16(*resume.Encounter_Id), Includes: includes}, &event) + if err != nil { + return nil, err + } + fmt.Println(encounter) + + // map template data + rData := er.ValueDto{} + if resume.Value != nil { + err := json.Unmarshal([]byte(*resume.Value), &rData) + if err != nil { + event.ErrInfo = pl.ErrorInfo{ + Code: "data-unmarshal-fail", + Detail: err.Error(), + Raw: err, + } + return nil, err + } + } else { + return nil, errors.New("there is no data to be used") + } + templateData := ResumePDF{} + templateData.MedicalRecord = *encounter.Patient.Number + templateData.NIK = *encounter.Patient.Person.ResidentIdentityNumber + templateData.Name = encounter.Patient.Person.Name + templateData.BirthPlaceDate = pu.FormatPlaceAndDate(encounter.Patient.Person.BirthRegency.Name, *encounter.Patient.Person.BirthDate) + templateData.Gender = encounter.Patient.Person.GenderString() + templateData.Phone = encounter.Patient.Person.GetPhoneNumber() + // templateData.Class = get from vclaim sep eg. III + // templateData.Unit = get from vclaim sep eg. R.KERINCI - KLS 3 + templateData.Doctor_Name = resume.Doctor.Employee.Person.FullName() + templateData.StartedAt = *pc.TimeToStringWithFormat(encounter.StartedAt, "") + templateData.FinishedAt = *pc.TimeToStringWithFormat(encounter.FinishedAt, "") + + return &templateData, nil +} diff --git a/internal/use-case/main-use-case/generate-file/tycovar.go b/internal/use-case/main-use-case/generate-file/tycovar.go index 2bbe9fbf..1dee32ff 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -44,8 +44,8 @@ type ResumePDF struct { Class string Unit string Doctor_Name string - StartedDate string - FinishedDate string + StartedAt string + FinishedAt string DiagnosisIn string DiagnosisOut string Diagnosis []er.DiagnosisEntry diff --git a/internal/use-case/main-use-case/resume/lib.go b/internal/use-case/main-use-case/resume/lib.go index 16bb1afd..02a2e27a 100644 --- a/internal/use-case/main-use-case/resume/lib.go +++ b/internal/use-case/main-use-case/resume/lib.go @@ -81,7 +81,7 @@ func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e tx = dg.I } - if err := tx.First(&data, input.Id).Error; err != nil { + 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 } diff --git a/pkg/conv-helper/conv-helper.go b/pkg/conv-helper/conv-helper.go index 03063c74..9b9bc44d 100644 --- a/pkg/conv-helper/conv-helper.go +++ b/pkg/conv-helper/conv-helper.go @@ -8,6 +8,8 @@ import ( "gorm.io/gorm" ) +const StandartDateTimeFormat = "2006-01-02 15:04:05" + // check string pointer, if nil return default value func StrConvDefault(f *string, def string) string { if f == nil { @@ -96,20 +98,12 @@ func BoolToString(f *bool) *string { return &t } -func TimeToString(f *time.Time) *string { - if f == nil { - return nil - } - t := f.Format("2006-01-02 15:04:05") - return &t -} - // Handling gorm.DeletedAt func DeletedAtToString(deletedAt *gorm.DeletedAt) *string { if deletedAt == nil || !deletedAt.Valid { return nil } - return TimeToString(&deletedAt.Time) + return TimeToStringWithFormat(&deletedAt.Time, "") } func BoolToFloat64(b bool) float64 { @@ -151,3 +145,17 @@ func StringToUint64(s string) *uint64 { } return &u } + +func TimeToStringWithFormat(t *time.Time, format string) *string { + result := "" + if t == nil || t.IsZero() { + return &result + } + + if format == "" { + format = StandartDateTimeFormat + } + + result = t.Format(format) + return &result +} diff --git a/pkg/use-case-helper/use-case-helper.go b/pkg/use-case-helper/use-case-helper.go index 019f9773..7d0ec17f 100644 --- a/pkg/use-case-helper/use-case-helper.go +++ b/pkg/use-case-helper/use-case-helper.go @@ -208,3 +208,13 @@ func GetLastTwoPathSegments(s string) string { // fallback: return entire string if less than 2 segments return strings.Trim(path, "/") } + +func FormatPlaceAndDate(place string, t time.Time) string { + // Ensure place is uppercase + place = strings.ToUpper(strings.TrimSpace(place)) + + // Format date: DD-MM-YYYY + dateStr := t.Format("02-01-2006") + + return place + ", " + dateStr +} From 4109752121b22fd6220e6d2715873c4ff6128328 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 25 Nov 2025 15:53:24 +0700 Subject: [PATCH 19/29] fix (doctor): wrong code passed --- internal/use-case/main-use-case/doctor/helper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/use-case/main-use-case/doctor/helper.go b/internal/use-case/main-use-case/doctor/helper.go index 344d1c0e..9ddc2027 100644 --- a/internal/use-case/main-use-case/doctor/helper.go +++ b/internal/use-case/main-use-case/doctor/helper.go @@ -22,6 +22,6 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Doctor) { data.IHS_Number = inputSrc.IHS_Number data.SIP_Number = inputSrc.SIP_Number data.Unit_Code = inputSrc.Unit_Code - data.Specialist_Code = inputSrc.Code - data.Subspecialist_Code = inputSrc.Code + data.Specialist_Code = inputSrc.Specialist_Code + data.Subspecialist_Code = inputSrc.Subspecialist_Code } From 04312afceeaeb76bdd8c26d550461ffd6630dd85 Mon Sep 17 00:00:00 2001 From: vanilia Date: Tue, 25 Nov 2025 19:47:47 +0700 Subject: [PATCH 20/29] testing --- .../domain/main-entities/encounter/dto.go | 22 ++--- .../domain/references/encounter/encounter.go | 8 +- .../simgos-entities/m-polihfis/entity.go | 2 +- .../domain/simgos-entities/m-poly/entity.go | 2 +- .../simgos-entities/t-pendaftaran/entity.go | 1 - .../main-handler/encounter/handler.go | 14 +-- .../new/encounter/handler.go | 2 +- .../use-case/main-use-case/doctor/helper.go | 4 +- .../main-use-case/edu-assessment/case.go | 2 +- .../use-case/main-use-case/encounter/case.go | 21 ++-- .../main-use-case/encounter/helper.go | 3 +- .../main-use-case/therapy-protocol/case.go | 2 +- internal/use-case/main-use-case/unit/case.go | 16 +++- .../new/encounter/case.go | 54 +++++------ .../new/encounter/helper.go | 95 ++++++++++++------- .../simgos-sync-use-case/new/encounter/lib.go | 8 +- .../new/patient/helper.go | 15 +-- .../new/specialist/helper.go | 2 +- .../new/specialist/lib.go | 9 +- .../simgos-sync-use-case/new/unit/helper.go | 3 + 20 files changed, 165 insertions(+), 120 deletions(-) diff --git a/internal/domain/main-entities/encounter/dto.go b/internal/domain/main-entities/encounter/dto.go index 849a5834..d4eeda3f 100644 --- a/internal/domain/main-entities/encounter/dto.go +++ b/internal/domain/main-entities/encounter/dto.go @@ -65,15 +65,15 @@ type CreateDto struct { } type TRujukan struct { - NoSep string `json:"noSep"` + NoSep string `json:"noSep"` // Surat Eligibilitas Peserta TglRujukan string `json:"tglRujukan"` - PpkDirujuk string `json:"ppkDirujuk"` - JnsPelayanan string `json:"jnsPelayanan"` + PpkDirujuk string `json:"ppkDirujuk"` // PPK (Provider Pelayanan Kesehatan) -> kode RS tujuan + JnsPelayanan string `json:"jnsPelayanan"` // Jenis pelayanan yang dimintakan; 1 = RawatInap; 2 = RawatJalan Catatan string `json:"catatan"` - DiagRujukan string `json:"diagRujukan"` - TipeRujukan string `json:"tipeRujukan"` - PoliRujukan string `json:"poliRujukan"` - User string `json:"user"` + DiagRujukan string `json:"diagRujukan"` // Kode Diagnosa + TipeRujukan string `json:"tipeRujukan"` // 0 = Rujukan Penuh; 1 = Rujukan Partial (hanya untuk tindakan tertentu); 2 = Balik (dikembalikan ke faskes asal) + PoliRujukan string `json:"poliRujukan"` // Kode Poli Tujuan + User string `json:"user"` // user yang membuat rujukan } type ReadListDto struct { @@ -105,24 +105,24 @@ type FilterDto struct { } type ReadDetailDto struct { - Id uint16 `json:"id"` + Id uint `json:"id"` Includes string `json:"includes"` } type UpdateDto struct { - Id uint16 `json:"id"` + Id uint `json:"id"` CreateDto } type UpdateStatusDto struct { - Id uint16 `json:"id"` + Id uint `json:"id"` StatusCode erc.DataStatusCode `json:"status_code"` pa.AuthInfo } type DeleteDto struct { - Id uint16 `json:"id"` + Id uint `json:"id"` pa.AuthInfo } diff --git a/internal/domain/references/encounter/encounter.go b/internal/domain/references/encounter/encounter.go index ca600246..a1fc3b31 100644 --- a/internal/domain/references/encounter/encounter.go +++ b/internal/domain/references/encounter/encounter.go @@ -95,10 +95,10 @@ const ( RTCPrivate RefTypeCode = "private" // Swasta RTCBpjs RefTypeCode = "bpjs" // BPJS - APMCJkn AllPaymentMethodCode = "jkn" // JKN - APMCJkmm AllPaymentMethodCode = "jkmm" // JKMM - APMCSpm AllPaymentMethodCode = "spm" // SPM - APMCPks AllPaymentMethodCode = "pks" // PKS + APMCJkn AllPaymentMethodCode = "jkn" // JKN -> Jaminan Kesehatan Nasional -> BPJS Kesehatan + APMCJkmm AllPaymentMethodCode = "jkmm" // JKMM -> Jaminan Kesehatan Masyarakat Miskin -> Dibiayai oleh APBD daerah + APMCSpm AllPaymentMethodCode = "spm" // SPM -> Surat Pernyataan Miskin + APMCPks AllPaymentMethodCode = "pks" // PKS -> Perjanjian Kerjasama -> Pembayaran melalui instansi atau perusahaan yang memiliki kontrak dengan rumah sakit APMCUmum AllPaymentMethodCode = "umum" SRTCInternal SEPRefTypeCode = "internal" // Rujukan Internal diff --git a/internal/domain/simgos-entities/m-polihfis/entity.go b/internal/domain/simgos-entities/m-polihfis/entity.go index b9eb7b56..8bed236d 100644 --- a/internal/domain/simgos-entities/m-polihfis/entity.go +++ b/internal/domain/simgos-entities/m-polihfis/entity.go @@ -1,4 +1,4 @@ -package division +package m_polihfis type MPolihfis struct { Id uint `json:"id" gorm:"primaryKey;autoIncrement;column:id"` diff --git a/internal/domain/simgos-entities/m-poly/entity.go b/internal/domain/simgos-entities/m-poly/entity.go index 52fe20eb..aff4bc1e 100644 --- a/internal/domain/simgos-entities/m-poly/entity.go +++ b/internal/domain/simgos-entities/m-poly/entity.go @@ -1,7 +1,7 @@ package m_poly type MPoly struct { - Kode uint `json:"kode" gorm:"primaryKey;autoIncrement;column:kode"` + Kode uint `json:"kode" gorm:"primaryKey;column:kode"` Nama string `json:"nama" gorm:"column:nama"` Jenispoly uint `json:"jenispoly" gorm:"column:jenispoly"` } diff --git a/internal/domain/simgos-entities/t-pendaftaran/entity.go b/internal/domain/simgos-entities/t-pendaftaran/entity.go index 682aabeb..08222653 100644 --- a/internal/domain/simgos-entities/t-pendaftaran/entity.go +++ b/internal/domain/simgos-entities/t-pendaftaran/entity.go @@ -82,7 +82,6 @@ type TPendaftaran struct { DokterNameHfis *string `json:"dokter_name_hfis" gorm:"column:dokter_name_hfis"` DokterIdHfis *string `json:"dokter_id_hfis" gorm:"column:dokter_id_hfis"` StatusBridging *string `json:"status_bridging" gorm:"column:status_bridging"` - NoSpri *string `json:"no_spri" gorm:"column:no_spri"` } func (TPendaftaran) TableName() string { diff --git a/internal/interface/main-handler/encounter/handler.go b/internal/interface/main-handler/encounter/handler.go index 0c87b7b2..688d6904 100644 --- a/internal/interface/main-handler/encounter/handler.go +++ b/internal/interface/main-handler/encounter/handler.go @@ -64,7 +64,7 @@ func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { } dto := e.ReadDetailDto{} sf.UrlQueryParam(&dto, *r.URL) - dto.Id = uint16(id) + dto.Id = uint(id) res, err := u.ReadDetail(dto) rw.DataResponse(w, res, err) } @@ -84,7 +84,7 @@ func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { return } - dto.Id = uint16(id) + dto.Id = uint(id) dto.AuthInfo = *authInfo res, err := u.Update(dto) @@ -103,7 +103,7 @@ func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { } dto := e.DeleteDto{} - dto.Id = uint16(id) + dto.Id = uint(id) dto.AuthInfo = *authInfo res, err := u.Delete(dto) @@ -175,7 +175,7 @@ func (obj myBase) Process(w http.ResponseWriter, r *http.Request) { } dto := e.UpdateStatusDto{ - Id: uint16(id), + Id: uint(id), StatusCode: erc.DSCProcess, } @@ -195,7 +195,7 @@ func (obj myBase) Cancel(w http.ResponseWriter, r *http.Request) { } dto := e.UpdateStatusDto{ - Id: uint16(id), + Id: uint(id), StatusCode: erc.DSCCancel, } @@ -211,7 +211,7 @@ func (obj myBase) Reject(w http.ResponseWriter, r *http.Request) { } dto := e.UpdateStatusDto{ - Id: uint16(id), + Id: uint(id), StatusCode: erc.DSCRejected, } @@ -226,7 +226,7 @@ func (obj myBase) Skip(w http.ResponseWriter, r *http.Request) { } dto := e.UpdateStatusDto{ - Id: uint16(id), + Id: uint(id), StatusCode: erc.DSCSkipped, } diff --git a/internal/interface/simgos-sync-handler/new/encounter/handler.go b/internal/interface/simgos-sync-handler/new/encounter/handler.go index e6b0515a..bec44cd4 100644 --- a/internal/interface/simgos-sync-handler/new/encounter/handler.go +++ b/internal/interface/simgos-sync-handler/new/encounter/handler.go @@ -51,7 +51,7 @@ func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { } dto := e.DeleteDto{} - dto.Id = uint16(id) + dto.Id = uint(id) res, err := u.Delete(dto) rw.DataResponse(w, res, err) diff --git a/internal/use-case/main-use-case/doctor/helper.go b/internal/use-case/main-use-case/doctor/helper.go index 344d1c0e..9ddc2027 100644 --- a/internal/use-case/main-use-case/doctor/helper.go +++ b/internal/use-case/main-use-case/doctor/helper.go @@ -22,6 +22,6 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Doctor) { data.IHS_Number = inputSrc.IHS_Number data.SIP_Number = inputSrc.SIP_Number data.Unit_Code = inputSrc.Unit_Code - data.Specialist_Code = inputSrc.Code - data.Subspecialist_Code = inputSrc.Code + data.Specialist_Code = inputSrc.Specialist_Code + data.Subspecialist_Code = inputSrc.Subspecialist_Code } diff --git a/internal/use-case/main-use-case/edu-assessment/case.go b/internal/use-case/main-use-case/edu-assessment/case.go index 230f0679..525274ca 100644 --- a/internal/use-case/main-use-case/edu-assessment/case.go +++ b/internal/use-case/main-use-case/edu-assessment/case.go @@ -37,7 +37,7 @@ func Create(input e.CreateDto) (*d.Data, error) { } // validate encounter_id - _, err := ue.ReadDetail(ee.ReadDetailDto{Id: uint16(*input.Encounter_Id)}) + _, err := ue.ReadDetail(ee.ReadDetailDto{Id: *input.Encounter_Id}) if err != nil { return err } diff --git a/internal/use-case/main-use-case/encounter/case.go b/internal/use-case/main-use-case/encounter/case.go index 25d74859..2a8ee1ba 100644 --- a/internal/use-case/main-use-case/encounter/case.go +++ b/internal/use-case/main-use-case/encounter/case.go @@ -118,7 +118,6 @@ func Create(input e.CreateDto) (*d.Data, error) { // insert vclaimReference if vr := input.VclaimReference; vr != nil { t, _ := time.Parse("2006-01-02", vr.TglRujukan) - _, err = uv.CreateData(ev.CreateDto{ Encounter_Id: &data.Id, Date: &t, @@ -136,8 +135,10 @@ func Create(input e.CreateDto) (*d.Data, error) { } dataEncounter, err := ReadDetailData(e.ReadDetailDto{ - Id: uint16(data.Id), - Includes: "Adm_Employee.User,Patient.Person.Relatives,Patient.Person.VclaimMember,VclaimReference"}, + Id: data.Id, + Includes: "Adm_Employee.User,Patient.Person.Relatives," + + "Patient.Person.VclaimMember,VclaimReference," + + "Patient.Person.Contacts,Patient.Person.Addresses"}, &event, tx) if err != nil { return err @@ -311,7 +312,7 @@ func Update(input e.UpdateDto) (*d.Data, error) { } dataEncounter, err := ReadDetailData(e.ReadDetailDto{ - Id: uint16(data.Id), + Id: data.Id, Includes: "Adm_Employee.User,Patient.Person.Relatives,Patient.Person.VclaimMember"}, &event, tx) if err != nil { @@ -429,7 +430,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { } func CheckOut(input e.DischargeDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Ambulatory,Rehab"} + rdDto := e.ReadDetailDto{Id: input.Id, Includes: "Ambulatory,Rehab"} var data *e.Encounter var err error @@ -516,7 +517,7 @@ func CheckOut(input e.DischargeDto) (*d.Data, error) { } dataEncounter, err := ReadDetailData(e.ReadDetailDto{ - Id: uint16(data.Id), + Id: data.Id, Includes: "DeathCause"}, &event, tx) if err != nil { @@ -658,7 +659,7 @@ func UpdateStatusCode(input e.UpdateStatusDto) (*d.Data, error) { } func CheckIn(input e.CheckinDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Rehab,Ambulatory"} + rdDto := e.ReadDetailDto{Id: input.Id, Includes: "Rehab,Ambulatory"} var data *e.Encounter var err error @@ -743,7 +744,7 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { } dataEncounter, err := ReadDetailData(e.ReadDetailDto{ - Id: uint16(data.Id), + Id: data.Id, Includes: "Adm_Employee.User"}, &event, tx) if err != nil { @@ -776,7 +777,7 @@ func CheckIn(input e.CheckinDto) (*d.Data, error) { } func RequestSwitchUnit(input e.SwitchUnitDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Responsible_Doctor"} + rdDto := e.ReadDetailDto{Id: input.Id, Includes: "Responsible_Doctor"} var data *e.Encounter var err error @@ -865,7 +866,7 @@ func RequestSwitchUnit(input e.SwitchUnitDto) (*d.Data, error) { } func ApproveSwitchUnit(input e.ApproveUnitDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Responsible_Doctor"} + rdDto := e.ReadDetailDto{Id: input.Id, Includes: "Responsible_Doctor"} var data *e.Encounter var err error diff --git a/internal/use-case/main-use-case/encounter/helper.go b/internal/use-case/main-use-case/encounter/helper.go index 3b215c36..73b053e5 100644 --- a/internal/use-case/main-use-case/encounter/helper.go +++ b/internal/use-case/main-use-case/encounter/helper.go @@ -68,6 +68,7 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Encounter) { var inputSrc *e.CreateDto if inputT, ok := any(input).(*e.CreateDto); ok { inputSrc = inputT + data.Status_Code = erc.DSCNew } else { inputTemp := any(input).(*e.UpdateDto) inputSrc = &inputTemp.CreateDto @@ -90,7 +91,6 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Encounter) { data.Responsible_Doctor_Code = inputSrc.Responsible_Doctor_Code data.RefSource_Name = inputSrc.RefSource_Name data.Appointment_Id = inputSrc.Appointment_Id - data.Status_Code = erc.DSCProcess data.RefType_Code = &inputSrc.RefTypeCode data.NewStatus = inputSrc.NewStatus } @@ -556,6 +556,7 @@ func getSoapiByResponsibleDoctor(enc e.Encounter, event *pl.Event) (data []es.So Code: "no responsible-doctor found", Detail: "Encounter does not have responsible-doctor", } + return nil, pl.SetLogError(event, enc) } err = dg.I. diff --git a/internal/use-case/main-use-case/therapy-protocol/case.go b/internal/use-case/main-use-case/therapy-protocol/case.go index ed4f3a21..a332d675 100644 --- a/internal/use-case/main-use-case/therapy-protocol/case.go +++ b/internal/use-case/main-use-case/therapy-protocol/case.go @@ -359,7 +359,7 @@ func Verify(input e.VerifyDto) (*d.Data, error) { 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 { + if _, err := ue.ReadDetail(ee.ReadDetailDto{Id: *input.Encounter_Id}); err != nil { return err } } diff --git a/internal/use-case/main-use-case/unit/case.go b/internal/use-case/main-use-case/unit/case.go index 1db6fb79..c73c2c1d 100644 --- a/internal/use-case/main-use-case/unit/case.go +++ b/internal/use-case/main-use-case/unit/case.go @@ -1,6 +1,7 @@ package unit import ( + "errors" e "simrs-vx/internal/domain/main-entities/unit" erc "simrs-vx/internal/domain/references/common" esync "simrs-vx/internal/domain/sync-entities/log" @@ -27,9 +28,22 @@ func Create(input e.CreateDto) (*d.Data, error) { // Start log pl.SetLogInfo(&event, input, "started", "create") + + // validate unit_code + _, err := strconv.Atoi(input.Code) + if err != nil { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "invalid_code_format", + Detail: "unit_code must be a valid integer", + Raw: errors.New("invalid unit_code format"), + } + return nil, pl.SetLogError(&event, input) + } + mwRunner := newMiddlewareRunner(&event) - err := dg.I.Transaction(func(tx *gorm.DB) error { + err = dg.I.Transaction(func(tx *gorm.DB) error { if resData, err := CreateData(input, &event, tx); err != nil { return err } else { diff --git a/internal/use-case/simgos-sync-use-case/new/encounter/case.go b/internal/use-case/simgos-sync-use-case/new/encounter/case.go index 6b4f7ab4..f9f48c11 100644 --- a/internal/use-case/simgos-sync-use-case/new/encounter/case.go +++ b/internal/use-case/simgos-sync-use-case/new/encounter/case.go @@ -12,8 +12,6 @@ import ( etp "simrs-vx/internal/domain/simgos-entities/t-pendaftaran" esync "simrs-vx/internal/domain/sync-entities/encounter" elog "simrs-vx/internal/domain/sync-entities/log" - - up "simrs-vx/internal/use-case/simgos-sync-use-case/new/patient" ) const source = "encounter" @@ -46,14 +44,8 @@ func Create(input e.Encounter) (*d.Data, error) { return err } - // STEP: 3 Get MPasien - dataPatient, err := up.ReadDetailSimgosByNomrData(sgData.Nomr, &event, tx.Simgos) - if err != nil { - return err - } - - // STEP 4: Update MPasien - err = updatePatient(*dataPatient, &event, tx.Simgos) + // STEP 3: Update MPasien + err = updatePatientCaraBayar(*sgData, &event, tx.Simgos) if err != nil { return err } @@ -123,13 +115,13 @@ func Update(input e.Encounter) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "update") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + syncLink, err := ReadDetailLinkData(input.Id, &event) if err != nil { return nil, err } // STEP 2: Get Simgos - simgos, err := ReadTPendaftaranDetailData(uint16(syncLink.Simgos_Id), &event) + simgos, err := ReadTPendaftaranDetailData(syncLink.Simgos_Id, &event) if err != nil { return nil, err } @@ -137,7 +129,7 @@ func Update(input e.Encounter) (*d.Data, error) { tx := db.NewTx() err = tx.Simgos.Transaction(func(tx *gorm.DB) error { // Step 2: Update Simgos - if err = UpdateTPendaftaranData(input, *simgos, "update", &event, tx); err != nil { + if err = UpdateTPendaftaranData(input, simgos, "update", &event, tx); err != nil { return err } @@ -173,7 +165,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { } // STEP 2: Get Simgos - sgData, err := ReadTPendaftaranDetailData(uint16(syncLink.Simgos_Id), &event) + sgData, err := ReadTPendaftaranDetailData(syncLink.Simgos_Id, &event) if err != nil { return nil, err } @@ -226,13 +218,13 @@ func CheckIn(input e.Encounter) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "check-in") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + syncLink, err := ReadDetailLinkData(input.Id, &event) if err != nil { return nil, err } // STEP 2: Get Simgos - simgos, err := ReadTPendaftaranDetailData(uint16(syncLink.Simgos_Id), &event) + simgos, err := ReadTPendaftaranDetailData(syncLink.Simgos_Id, &event) if err != nil { return nil, err } @@ -240,7 +232,7 @@ func CheckIn(input e.Encounter) (*d.Data, error) { tx := db.NewTx() err = tx.Simgos.Transaction(func(tx *gorm.DB) error { // Step 2: Update Simgos - if err = UpdateTPendaftaranData(input, *simgos, "check-in", &event, tx); err != nil { + if err = UpdateTPendaftaranData(input, simgos, "check-in", &event, tx); err != nil { return err } @@ -268,13 +260,13 @@ func CheckOut(input e.Encounter) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "check-out") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + syncLink, err := ReadDetailLinkData(input.Id, &event) if err != nil { return nil, err } // STEP 2: Get Simgos - simgos, err := ReadTPendaftaranDetailData(uint16(syncLink.Simgos_Id), &event) + simgos, err := ReadTPendaftaranDetailData(syncLink.Simgos_Id, &event) if err != nil { return nil, err } @@ -282,7 +274,7 @@ func CheckOut(input e.Encounter) (*d.Data, error) { tx := db.NewTx() err = tx.Simgos.Transaction(func(tx *gorm.DB) error { // Step 2: Update Simgos - if err = UpdateTPendaftaranData(input, *simgos, "check-out", &event, tx); err != nil { + if err = UpdateTPendaftaranData(input, simgos, "check-out", &event, tx); err != nil { return err } @@ -310,13 +302,13 @@ func Cancel(input e.Encounter) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "cancel") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + syncLink, err := ReadDetailLinkData(input.Id, &event) if err != nil { return nil, err } // STEP 2: Get Simgos - simgos, err := ReadTPendaftaranDetailData(uint16(syncLink.Simgos_Id), &event) + simgos, err := ReadTPendaftaranDetailData(syncLink.Simgos_Id, &event) if err != nil { return nil, err } @@ -324,7 +316,7 @@ func Cancel(input e.Encounter) (*d.Data, error) { tx := db.NewTx() err = tx.Simgos.Transaction(func(tx *gorm.DB) error { // Step 2: Update Simgos - if err = UpdateTPendaftaranData(input, *simgos, "update-status", &event, tx); err != nil { + if err = UpdateTPendaftaranData(input, simgos, "update-status", &event, tx); err != nil { return err } @@ -352,13 +344,13 @@ func RequestSwitchUnit(input e.Encounter) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "request-switch-unit") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + syncLink, err := ReadDetailLinkData(input.Id, &event) if err != nil { return nil, err } // STEP 2: Get Simgos - tpendaftaran, err := ReadTPendaftaranDetailData(uint16(syncLink.Simgos_Id), &event) + tpendaftaran, err := ReadTPendaftaranDetailData(syncLink.Simgos_Id, &event) if err != nil { return nil, err } @@ -366,12 +358,12 @@ func RequestSwitchUnit(input e.Encounter) (*d.Data, error) { tx := db.NewTx() err = tx.Simgos.Transaction(func(tx *gorm.DB) error { // Step 2: Update TPendaftaran - if err = UpdateTPendaftaranData(input, *tpendaftaran, "update-status", &event, tx); err != nil { + if err = UpdateTPendaftaranData(input, tpendaftaran, "update-status", &event, tx); err != nil { return err } // Step 3: Insert TPemeriksaanHist - if _, err := CreateBulkTPemeriksaanHistData(input, *tpendaftaran, &event, tx); err != nil { + if _, err := CreateBulkTPemeriksaanHistData(input, tpendaftaran, &event, tx); err != nil { return err } @@ -399,13 +391,13 @@ func ApproveSwitchUnit(input e.Encounter) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "request-switch-unit") // STEP 1: Get Link - syncLink, err := ReadDetailLinkData(uint16(input.Id), &event) + syncLink, err := ReadDetailLinkData(input.Id, &event) if err != nil { return nil, err } // STEP 2: Get Simgos - tpendaftaran, err := ReadTPendaftaranDetailData(uint16(syncLink.Simgos_Id), &event) + tpendaftaran, err := ReadTPendaftaranDetailData(syncLink.Simgos_Id, &event) if err != nil { return nil, err } @@ -413,12 +405,12 @@ func ApproveSwitchUnit(input e.Encounter) (*d.Data, error) { tx := db.NewTx() err = tx.Simgos.Transaction(func(tx *gorm.DB) error { // Step 2: Update TPendaftaran - if err = UpdateTPendaftaranData(input, *tpendaftaran, "update-status", &event, tx); err != nil { + if err = UpdateTPendaftaranData(input, tpendaftaran, "update-status", &event, tx); err != nil { return err } // Step 3: Insert TPemeriksaanHist - if _, err := CreateBulkTPemeriksaanHistData(input, *tpendaftaran, &event, tx); err != nil { + if _, err := CreateBulkTPemeriksaanHistData(input, tpendaftaran, &event, tx); err != nil { return err } diff --git a/internal/use-case/simgos-sync-use-case/new/encounter/helper.go b/internal/use-case/simgos-sync-use-case/new/encounter/helper.go index a64a0c4e..fe1d5b42 100644 --- a/internal/use-case/simgos-sync-use-case/new/encounter/helper.go +++ b/internal/use-case/simgos-sync-use-case/new/encounter/helper.go @@ -6,7 +6,7 @@ package encounter import ( "encoding/json" - epr "simrs-vx/internal/domain/main-entities/person-relative" + epa "simrs-vx/internal/domain/main-entities/person-address" erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" erp "simrs-vx/internal/domain/references/person" @@ -14,6 +14,8 @@ import ( "strconv" e "simrs-vx/internal/domain/main-entities/encounter" + epc "simrs-vx/internal/domain/main-entities/person-contact" + epr "simrs-vx/internal/domain/main-entities/person-relative" ep "simrs-vx/internal/domain/simgos-entities/m-pasien" etph "simrs-vx/internal/domain/simgos-entities/t-pemeriksaan-hist" etp "simrs-vx/internal/domain/simgos-entities/t-pendaftaran" @@ -47,20 +49,29 @@ func setDataCreateTPendaftaran(input *e.Encounter) (data etp.TPendaftaran, err e } data.NoKunjung = uint(noKunjung) - setDataTPendaftaran(input, data) + if p := input.Patient.Person; p != nil { + mapRelative(p.Relatives, &data) + + if data.PenanggungjawabNama == "" { + data.PenanggungjawabHubungan = "DIRI SENDIRI" + data.PenanggungjawabNama = p.Name + mapContact(p.Contacts, &data) + mapAddress(p.Addresses, &data) + } + + if bpjs := p.VclaimMember; bpjs != nil { + data.Nokartu = *bpjs.CardNumber + } + } + + setDataTPendaftaran(input, &data) return } -func setDataTPendaftaran(input *e.Encounter, data etp.TPendaftaran) { +func setDataTPendaftaran(input *e.Encounter, data *etp.TPendaftaran) { data.Tglreg = input.RegisteredAt - // set kddokter - if d := input.Appointment_Doctor_Code; d != nil { - kddokter, _ := strconv.Atoi(*input.Appointment_Doctor_Code) - data.Kddokter = uint(kddokter) - } - // set kdpoly if p := input.Unit_Code; p != nil { kdpoly, _ := strconv.Atoi(*input.Unit_Code) @@ -82,22 +93,18 @@ func setDataTPendaftaran(input *e.Encounter, data etp.TPendaftaran) { } } - if ref := input.VclaimReference; ref != nil { - data.Ketrujuk = *ref.SrcCode - data.Norujukan = *ref.Number - data.Tglrujukan = ref.Date + // set data if payment_method bpjs + if input.RefSource_Name != nil { + data.Ketrujuk = *input.RefSource_Name data.Strujukan = 1 } else { data.Ketrujuk = "TR" data.Strujukan = 0 } - if p := input.Patient.Person; p != nil { - mapRelative(p.Relatives, data) - - if bpjs := p.VclaimMember; bpjs != nil { - data.Nokartu = *bpjs.CardNumber - } + if ref := input.VclaimReference; ref != nil { + data.Norujukan = *ref.Number + data.Tglrujukan = ref.Date } data.Jamreg = input.RegisteredAt @@ -107,7 +114,7 @@ func setDataTPendaftaran(input *e.Encounter, data etp.TPendaftaran) { return } -func setDataCheckIn(input e.Encounter, data etp.TPendaftaran) { +func setDataCheckIn(input e.Encounter, data *etp.TPendaftaran) { // set kddokter kddokter, _ := strconv.Atoi(*input.Responsible_Doctor_Code) data.Kddokter = uint(kddokter) @@ -125,7 +132,7 @@ func setDataCheckIn(input e.Encounter, data etp.TPendaftaran) { return } -func setDataRequestSwitchUnit(input e.Encounter, data etp.TPendaftaran) (hist []etph.TPemeriksaanHist) { +func setDataRequestSwitchUnit(input e.Encounter, data *etp.TPendaftaran) (hist []etph.TPemeriksaanHist) { if enc := input.InternalReferences; enc != nil { for i, ref := range *enc { hist = append(hist, etph.TPemeriksaanHist{ @@ -151,7 +158,7 @@ func setDataRequestSwitchUnit(input e.Encounter, data etp.TPendaftaran) (hist [] return } -func setDataCheckOut(input e.Encounter, data etp.TPendaftaran) { +func setDataCheckOut(input e.Encounter, data *etp.TPendaftaran) { data.Keluarpoly = input.Discharge_Date // set status @@ -169,13 +176,13 @@ func setDataCheckOut(input e.Encounter, data etp.TPendaftaran) { return } -func setDataUpdateStatus(input e.Encounter, data etp.TPendaftaran) { +func setDataUpdateStatus(input e.Encounter, data *etp.TPendaftaran) { if input.Status_Code == erc.DSCCancel { data.Status = 11 } } -func setKdrujuk(input *ere.RefTypeCode, data etp.TPendaftaran) { +func setKdrujuk(input *ere.RefTypeCode, data *etp.TPendaftaran) { switch *input { case ere.RTCGov: data.Kdrujuk = 1 @@ -188,7 +195,7 @@ func setKdrujuk(input *ere.RefTypeCode, data etp.TPendaftaran) { } } -func setKdcarabayar(input ere.AllPaymentMethodCode, data etp.TPendaftaran) { +func setKdcarabayar(input ere.AllPaymentMethodCode, data *etp.TPendaftaran) { switch input { case ere.APMCPks: data.Kdcarabayar = 12 @@ -203,7 +210,7 @@ func setKdcarabayar(input ere.AllPaymentMethodCode, data etp.TPendaftaran) { } } -func setStatus(code *ere.DischargeMethodCode, data etp.TPendaftaran) { +func setStatus(code *ere.DischargeMethodCode, data *etp.TPendaftaran) { switch *code { case ere.DMCHome: data.Status = 1 @@ -250,7 +257,7 @@ func setStatus(code *ere.DischargeMethodCode, data etp.TPendaftaran) { } } -func mapRelative(relative *[]epr.PersonRelative, data etp.TPendaftaran) { +func mapRelative(relative *[]epr.PersonRelative, data *etp.TPendaftaran) { if relative == nil || len(*relative) == 0 { return } @@ -322,10 +329,11 @@ func generateNoKunjung() (int, error) { // Lock rows for this prefix → prevents race condition if err := dg.IS["simrs"]. Table("t_pendaftaran"). - Select("COALESCE(MAX(no_kunjung),0)"). + Select("no_kunjung"). Where("tglreg = CURRENT_DATE"). Order("no_kunjung DESC"). Clauses(clause.Locking{Strength: "UPDATE"}). + Limit(1). Scan(&lastNumber).Error; err != nil { return 0, err } @@ -339,7 +347,7 @@ func stringtouint(v string) *uint { return &point } -func updatePatient(patient ep.MPasien, event *pl.Event, dbx ...*gorm.DB) error { +func updatePatientCaraBayar(input etp.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, nil, "started", "DBUpdate") var tx *gorm.DB @@ -350,9 +358,9 @@ func updatePatient(patient ep.MPasien, event *pl.Event, dbx ...*gorm.DB) error { } if err := tx.Model(&ep.MPasien{}). - Where("\"nomr\" = ?", patient.Nomr). + Where("\"nomr\" = ?", input.Nomr). Updates(map[string]interface{}{ - "kdcarabayar": patient.Kdcarabayar, + "kdcarabayar": input.Kdcarabayar, }).Error; err != nil { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ @@ -360,9 +368,30 @@ func updatePatient(patient ep.MPasien, event *pl.Event, dbx ...*gorm.DB) error { Detail: "Failed to update patient", Raw: err, } - return pl.SetLogError(event, patient) + return pl.SetLogError(event, input) } - pl.SetLogInfo(event, patient, "complete") + pl.SetLogInfo(event, input, "complete") return nil } + +func mapContact(contact *[]epc.PersonContact, data *etp.TPendaftaran) { + if contact == nil || len(*contact) == 0 { + return + } + + for _, c := range *contact { + if c.Type_Code == erp.CTPhone || c.Type_Code == erp.CTMPhone { + data.PenanggungjawabPhone = c.Value + break + } + } +} + +func mapAddress(addresses *[]epa.PersonAddress, data *etp.TPendaftaran) { + if addresses == nil || len(*addresses) == 0 { + return + } + a := (*addresses)[0] + data.PenanggungjawabAlamat = a.Address +} diff --git a/internal/use-case/simgos-sync-use-case/new/encounter/lib.go b/internal/use-case/simgos-sync-use-case/new/encounter/lib.go index 064131e5..3312193d 100644 --- a/internal/use-case/simgos-sync-use-case/new/encounter/lib.go +++ b/internal/use-case/simgos-sync-use-case/new/encounter/lib.go @@ -41,7 +41,7 @@ func CreateTPendaftaranData(input e.Encounter, event *pl.Event, dbx ...*gorm.DB) return &data, nil } -func ReadTPendaftaranDetailData(simgosId uint16, event *pl.Event) (*etp.TPendaftaran, error) { +func ReadTPendaftaranDetailData(simgosId uint, event *pl.Event) (*etp.TPendaftaran, error) { pl.SetLogInfo(event, simgosId, "started", "DBReadDetail") data := etp.TPendaftaran{} @@ -59,7 +59,7 @@ func ReadTPendaftaranDetailData(simgosId uint16, event *pl.Event) (*etp.TPendaft return &data, nil } -func UpdateTPendaftaranData(input e.Encounter, data etp.TPendaftaran, method string, event *pl.Event, dbx ...*gorm.DB) error { +func UpdateTPendaftaranData(input e.Encounter, data *etp.TPendaftaran, method string, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, input, "started", "DBUpdate") switch method { @@ -138,7 +138,7 @@ func CreateLinkData(simxId, simgosId uint, event *pl.Event, dbx ...*gorm.DB) (*e return &data, nil } -func ReadDetailLinkData(simxId uint16, event *pl.Event) (*esync.EncounterLink, error) { +func ReadDetailLinkData(simxId uint, event *pl.Event) (*esync.EncounterLink, error) { pl.SetLogInfo(event, simxId, "started", "DBReadDetail") data := esync.EncounterLink{} @@ -199,7 +199,7 @@ func CreateLogData(input esynclog.SimxLogDto, event *pl.Event, dbx ...*gorm.DB) return nil } -func CreateBulkTPemeriksaanHistData(input e.Encounter, data etp.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) (hist []etph.TPemeriksaanHist, err error) { +func CreateBulkTPemeriksaanHistData(input e.Encounter, data *etp.TPendaftaran, event *pl.Event, dbx ...*gorm.DB) (hist []etph.TPemeriksaanHist, err error) { pl.SetLogInfo(event, nil, "started", "DBCreate") hist = setDataRequestSwitchUnit(input, data) diff --git a/internal/use-case/simgos-sync-use-case/new/patient/helper.go b/internal/use-case/simgos-sync-use-case/new/patient/helper.go index 14f516d5..532574b2 100644 --- a/internal/use-case/simgos-sync-use-case/new/patient/helper.go +++ b/internal/use-case/simgos-sync-use-case/new/patient/helper.go @@ -37,12 +37,15 @@ func setDataSimgos(input *e.Patient) (data esimgos.MPasien) { mapContact(input.Person.Contacts, &data) } - data.PenanggungjawabHubungan = "DIRI SENDIRI" - data.PenanggungjawabNama = input.Person.Name - data.PenanggungjawabPhone = data.Notelp - data.PenanggungjawabAlamat = data.Alamat - if input.Person != nil || input.Person.Relatives != nil { - mapRelative(input.Person.Relatives, &data) + if input.Person != nil { + data.PenanggungjawabHubungan = "DIRI SENDIRI" + data.PenanggungjawabNama = input.Person.Name + data.PenanggungjawabPhone = data.Notelp + data.PenanggungjawabAlamat = data.Alamat + + if input.Person.Relatives != nil { + mapRelative(input.Person.Relatives, &data) + } } data.Nip = input.RegisteredBy_User_Name diff --git a/internal/use-case/simgos-sync-use-case/new/specialist/helper.go b/internal/use-case/simgos-sync-use-case/new/specialist/helper.go index 4a155496..adce079f 100644 --- a/internal/use-case/simgos-sync-use-case/new/specialist/helper.go +++ b/internal/use-case/simgos-sync-use-case/new/specialist/helper.go @@ -14,7 +14,7 @@ import ( esync "simrs-vx/internal/domain/sync-entities/specialist" ) -func setDataSimgos[T *e.CreateDto | *e.UpdateDto](input T) (data esimgos.MPolihfis) { +func setDataSimgos[T *e.CreateDto | *e.UpdateDto](input T, data *esimgos.MPolihfis) { var inputSrc *e.CreateDto if inputT, ok := any(input).(*e.CreateDto); ok { inputSrc = inputT diff --git a/internal/use-case/simgos-sync-use-case/new/specialist/lib.go b/internal/use-case/simgos-sync-use-case/new/specialist/lib.go index c796af73..0aee2ceb 100644 --- a/internal/use-case/simgos-sync-use-case/new/specialist/lib.go +++ b/internal/use-case/simgos-sync-use-case/new/specialist/lib.go @@ -20,7 +20,8 @@ var now = time.Now() func CreateSimgosData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*esimgos.MPolihfis, error) { pl.SetLogInfo(event, nil, "started", "DBCreate") - data := setDataSimgos(&input) + data := esimgos.MPolihfis{} + setDataSimgos(&input, &data) var tx *gorm.DB if len(dbx) > 0 { @@ -29,7 +30,7 @@ func CreateSimgosData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*esi tx = dg.IS["simrs"] } - if err := tx.Create(&data).Error; err != nil { + if err := tx.Debug().Create(&data).Error; err != nil { return nil, plh.HandleCreateError(input, event, err) } @@ -58,7 +59,9 @@ func ReadDetailSimgosData(simgosId uint16, event *pl.Event) (*esimgos.MPolihfis, func UpdateSimgosData(input e.UpdateDto, dataSimgos *esync.SpecialistLink, event *pl.Event, dbx ...*gorm.DB) error { pl.SetLogInfo(event, input, "started", "DBUpdate") - data := setDataSimgos(&input) + data := esimgos.MPolihfis{} + + setDataSimgos(&input, &data) data.Id = dataSimgos.Simgos_Id var tx *gorm.DB diff --git a/internal/use-case/simgos-sync-use-case/new/unit/helper.go b/internal/use-case/simgos-sync-use-case/new/unit/helper.go index 667bb7f0..d7dfb1f6 100644 --- a/internal/use-case/simgos-sync-use-case/new/unit/helper.go +++ b/internal/use-case/simgos-sync-use-case/new/unit/helper.go @@ -7,6 +7,7 @@ package unit import ( "encoding/json" erc "simrs-vx/internal/domain/references/common" + "strconv" e "simrs-vx/internal/domain/main-entities/unit" @@ -27,6 +28,8 @@ func setDataSimgos[T *e.CreateDto | *e.UpdateDto](input T) (data esimgos.MPoly) data.Nama = inputSrc.Name data.Jenispoly = 0 + kodePoly, _ := strconv.Atoi(inputSrc.Code) + data.Kode = uint(kodePoly) return } From 8360da06c861556b416692e42e7f973ab7c5a2ed Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Tue, 25 Nov 2025 21:04:28 +0700 Subject: [PATCH 21/29] dev: hotfix, soaoi minor filtering --- internal/domain/main-entities/soapi/dto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/domain/main-entities/soapi/dto.go b/internal/domain/main-entities/soapi/dto.go index 3edc18e3..94094df7 100644 --- a/internal/domain/main-entities/soapi/dto.go +++ b/internal/domain/main-entities/soapi/dto.go @@ -62,7 +62,7 @@ type ResponseDto struct { Employee_Id *uint `json:"employee_id"` Employee *eem.Employee `json:"employee,omitempty"` Time *time.Time `json:"time"` - TypeCode erc.SoapiTypeCode `json:"typeCode"` + TypeCode erc.SoapiTypeCode `json:"type-code"` Value *string `json:"value"` } From 04add70ac5018780f859b1fd5356a6336e88abf2 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Wed, 26 Nov 2025 17:23:48 +0700 Subject: [PATCH 22/29] feat (generate-file): resume table is done --- assets/docs/resume.html | 531 +++++++++++++----- internal/domain/main-entities/resume/dto.go | 10 +- .../interface/main-handler/resume/handler.go | 10 + .../main-use-case/generate-file/helper.go | 41 +- .../main-use-case/generate-file/tycovar.go | 13 + 5 files changed, 452 insertions(+), 153 deletions(-) diff --git a/assets/docs/resume.html b/assets/docs/resume.html index 8cdca672..db66314d 100644 --- a/assets/docs/resume.html +++ b/assets/docs/resume.html @@ -7,26 +7,40 @@ body { padding: 2rem 3rem; } + table { width: 100%; border-collapse: collapse } + td { vertical-align: top; } - .border-table, .border-table td, .border-table th { + + .border-table, + .border-table td, + .border-table th { border: 1px solid black; } - .section-title { + + .section-title { margin-top: 1rem; margin-bottom: 0rem; } - .data-label { + + .data-label { width: 9rem; } - .data-colon { - width: 0.1234rem; + + .data-colon { + width: 15px; padding-left: 10px; } + + .description-label { + font-size: 0.7rem; + margin: 0; + } + @@ -60,7 +74,7 @@
- Keluhan Utama + Tindakan Medis : {{ .Repalcable }}

- +
: - {{ .Repalcable }} + {{ .MedicalRecord }} Ruang/Klinik : - {{ .Repalcable }} + {{ .Unit }} NIK : - {{ .Repalcable }} + {{ .NIK }} Kelas : - {{ .Repalcable }} + {{ .Class }} Nama : - {{ .Repalcable }} + {{ .Name }} DPJP : - {{ .Repalcable }} + {{ .Doctor_Name }} - + @@ -121,21 +135,21 @@ Tempat/Tanggal Lahir : - {{ .Repalcable }} + {{ .BirthPlaceDate }} Jenis Kelamin : - {{ .Repalcable }} + {{ .Gender }} No. Telepon : - {{ .Repalcable }} + {{ .Phone }} @@ -165,32 +179,33 @@ Tanggal Masuk : - {{ .Repalcable }} + {{ .StartedAt }} Tanggal Keluar : - {{ .Repalcable }} + {{ .FinishedAt }} Diagnosis Masuk : - {{ .Repalcable }} + {{ .DiagnosisIn }} Diagnosis Keluar : - {{ .Repalcable }} + {{ .DiagnosisOut }} - +
+ - - - - - - - - - - - - - - - - - - -
No @@ -205,31 +220,46 @@ Kode ICD-10
- 1 - {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
- 2 - {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
- 3 - {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ + + {{ $dataLen := len .Diagnosis }} + {{ if gt $dataLen 0 }} + + {{ range $index, $diagnosis := .Diagnosis }} + + {{ if eq $index 0 }}1{{ else if eq $index 1 }}2{{ else if eq $index 2 }}3{{ else if eq $index 3 }}4{{ else if eq $index 4 }}5{{ else }}{{ $index }}{{ end }} + {{ $diagnosis.DiagnosisName }} + {{ $diagnosis.Basis }} + {{ $diagnosis.ICD10 }} + + {{ end }} + + + {{ if eq $dataLen 1 }} + + 2 + + + + + {{ end }} + {{ else }} + + + 1 + + + + + + 2 + + + + + {{ end }} + + @@ -237,50 +267,54 @@ -

Kajian Awal Medis

+

Kajian Awal Medis

(Diisi oleh Dokter Penanggung Jawab Pelayanan)

- + ">(Diisi oleh Dokter Penanggung Jawab Pelayanan)

+ - + - + - +
Keluhan Utama : {{ .Repalcable }}{{ .MainComplaint }}
Riwayat Penyakit : {{ .Repalcable }}{{ .MedicalHistory }}
Pemeriksaan Fisik & Keadaan Umum : {{ .Repalcable }}{{ .PhysicalExamination }}
-

Pemeriksaan Penunjang

+

Pemeriksaan Penunjang

- +
Pemeriksaan : {{ .Repalcable }}{{ .SupportingExamination }}
-

Tindakan Medis

- +
+ + +

Tindakan Medis

+ + - - - - - - - - - - - - - - - - - - -
No @@ -295,31 +329,47 @@ Kode ICD 9-CM
- 1 - {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
- 2 - {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
- 3 - {{ .Repalcable }}{{ .Repalcable }}{{ .Repalcable }}
+ + + {{ $dataLen := len .MedicalActions }} + {{ if gt $dataLen 0 }} + + {{ range $index, $action := .MedicalActions }} + + {{ if eq $index 0 }}1{{ else if eq $index 1 }}2{{ else if eq $index 2 }}3{{ else if eq $index 3 }}4{{ else if eq $index 4 }}5{{ else }}{{ $index }}{{ end }} + {{ $action.Action }} + {{ $action.Basis }} + {{ $action.ICD9 }} + + {{ end }} + + + {{ if eq $dataLen 1 }} + + 2 + + + + + {{ end }} + {{ else }} + + + 1 + + + + + + 2 + + + + + {{ end }} + + + @@ -327,51 +377,53 @@ Tindakan Medis - +
: {{ .Repalcable }}{{ .MedicalAction }}
+ + +
+

Konsultasi

+
+ + + + + + + + + + {{ $dataLen := len .Consultations }} + {{ if gt $dataLen 0 }} + + {{ range $index, $consultation := .Consultations }} + + + + + + {{ end }} + + + {{ if eq $dataLen 1 }} + + {{ end }} + {{ else }} + + + + {{ end }} + +
NoKonsultasiJawaban Konsultasi
{{ if eq $index 0 }}1{{ else if eq $index 1 }}2{{ else if eq $index 2 }}3{{ else if eq $index 3 }}4{{ else if eq $index 4 }}5{{ else }}{{ $index }}{{ end }}{{ $consultation.Consultation }}{{ $consultation.ConsultationAnswer }}
2
1
2
+ + -

Konsultasi

- - - - - - - - - - - - - - - - - - - - - -
- No - - Konsultasi - - Jawaban Konsultasi -
- 1 - {{ .Repalcable }}{{ .Repalcable }}
- 2 - {{ .Repalcable }}{{ .Repalcable }}
- 3 - {{ .Repalcable }}{{ .Repalcable }}
- -

Terapi

+

Terapi

@@ -379,54 +431,241 @@ : - Replace + {{ .Allergy}}
- -

Obat yang diberikan

- +
- - - - - - - - - - - - - - + +
- Terapi (Farmakologi & Non Farmakologi) Selawa Perawatan - - Terapi (Farmakologi & Non Farmakologi) Selawa Pulang -
{{ .Repalcable }}{{ .Repalcable }}
{{ .Repalcable }}{{ .Repalcable }}
{{ .Repalcable }}{{ .Repalcable }} +

Obat yang diberikan

+ + + + + + + + + + + + + + + + + +
+ Terapi (Farmakologi & Non Farmakologi) Selawa Perawatan + + Terapi (Farmakologi & Non Farmakologi) Selawa Pulang +
{{ .NIK }}{{ .NIK }}
{{ .NIK }}{{ .NIK }}
{{ .NIK }}{{ .NIK }}
+
+ -

Kondisi saat Pulang

+

Kondisi saat Pulang

- + - + + + + + + + + + + + + + + + + + + + + +
- Keluhan Utama - Kesadaran : {{ .Repalcable }}{{ .ConsciousnessLevel }}
+ Tanda Vital +

Vital sign

+
:
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ Tekanan Darah +

Blood Pressure

+
: {{ .BloodPressure }} mmHg +
+ Suhu +

Temperatur

+
: {{ .BodyTemperature }} C +
+ Nadi +

Pulse

+
: {{ .HeartRate }} x/Menit
+
+ + + + + + + + + + + + + + +
+ Frekuensi Nafas +

Respiratory rate

+
: {{ .RespirationRate }}
+ Skala Nyeri +

Pain Scale

+
: {{ .PainScale }}
+
+ +
Keadaan Keluar: {{ .ConditionOnDischarge }}
Cara Keluar +

Patient discharge of hospital

+
:{{ .DischargeMethod }}
+ + + + + + + + + + + + + + + + + + + + + + + +
+

INSTRUKSI UNTUK TINDAK LANJUT

+

Follow up Consultation to

+
+

Kontrol Ke

+

Follow up Consultation to

+
+ + + + + + + + + + + + + + + + +
Fasyankes: {{ .NIK }}
Tanggal: {{ .NIK }}
Klinik: {{ .NIK }}
+
+

Dalam keadaan darurat dapat menghubungi

+

In case of emergencycontact

+
+

IGD

+

(Accident & Emergency Instalation)

+
+ + + + + + +
Telepon +

Phone

+
: (0341) 362101
+
+

EDUKASI & RENCANA TINDAK LANJUT

+

Follow up plan (if necessary)

+
+

+

(bila diperlukan)

+
+ + + + + + + + + + + + + +
+ Malang, {{ .Date }}
+ Dokter Penanggung Jawab Pelayanan +
+ ({{ .Doctor_Name }}) +
+
+

*) Data dapat berubah setelah pasien pulang sesuai prosedur

+ \ No newline at end of file diff --git a/internal/domain/main-entities/resume/dto.go b/internal/domain/main-entities/resume/dto.go index 884b8e32..76e959e5 100644 --- a/internal/domain/main-entities/resume/dto.go +++ b/internal/domain/main-entities/resume/dto.go @@ -96,7 +96,7 @@ type Assessment struct { StartedAt *time.Time `json:"startedAt` FinishedAt *time.Time `json:"finishedAt` Doctor_Code string `json:"doctor_code` - DiagnosisIn string `json:"diagnosesIn` + DiagnosisIn string `json:"diagnosisIn` AmbulatoryIndication string `json:"ambulatoryIndication"` MainComplaint string `json:"mainComplaint"` PhysicalExamination string `json:"physicalExamination"` @@ -106,13 +106,13 @@ type Assessment struct { type Diagnosis struct { PrimaryDiagnosis DiagnosisEntry `json:"primaryDiagnosis"` - SecondaryDiagnoses []DiagnosisEntry `json:"secondaryDiagnoses"` + SecondaryDiagnosis []DiagnosisEntry `json:"secondaryDiagnosis"` } type DiagnosisEntry struct { - Diagnosis string `json:"diagnosis"` - ICD10 string `json:"icd_10"` - Basis string `json:"basis"` // Clinical basis of diagnosis / dasar diagnosa + DiagnosisName string `json:"diagnosis"` + ICD10 string `json:"icd_10"` + Basis string `json:"basis"` // Clinical basis of diagnosis / dasar diagnosa } type Action struct { diff --git a/internal/interface/main-handler/resume/handler.go b/internal/interface/main-handler/resume/handler.go index 5574bc7a..9a59f5fd 100644 --- a/internal/interface/main-handler/resume/handler.go +++ b/internal/interface/main-handler/resume/handler.go @@ -52,6 +52,10 @@ func GetDetail(w http.ResponseWriter, r *http.Request) { } func Update(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) + } id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { return @@ -62,11 +66,16 @@ func Update(w http.ResponseWriter, r *http.Request) { return } dto.Id = uint(id) + dto.AuthInfo = *authInfo res, err := u.Update(dto) rw.DataResponse(w, res, err) } func Delete(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) + } id := rw.ValidateInt(w, "id", r.PathValue("id")) if id <= 0 { return @@ -74,6 +83,7 @@ func Delete(w http.ResponseWriter, r *http.Request) { dto := e.DeleteDto{} dto.Id = uint(id) + dto.AuthInfo = *authInfo res, err := u.Delete(dto) rw.DataResponse(w, res, err) } diff --git a/internal/use-case/main-use-case/generate-file/helper.go b/internal/use-case/main-use-case/generate-file/helper.go index e42fc13e..02800c3a 100644 --- a/internal/use-case/main-use-case/generate-file/helper.go +++ b/internal/use-case/main-use-case/generate-file/helper.go @@ -250,8 +250,28 @@ func generateResume(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDt } } + templateData, err := generateResumeTemplateData(*r, event, tx) + if err != nil { + return nil, err + } + + input.FormatType = erc.DFTCPDF + input.TemplateName = TDNR + input.Encounter_Id = r.Encounter_Id + + // generate file + urlPub, err := generateFile(input, templateData) + if err != nil { + return nil, err + } + + r.FileUrl = &urlPub + if err := tx.Save(&r).Error; err != nil { + return nil, err + } + response := ResponseDto{ - FileUrl: "", + FileUrl: urlPub, } return &response, nil @@ -264,7 +284,6 @@ func generateResumeTemplateData(resume er.Resume, event pl.Event, tx *gorm.DB) ( if err != nil { return nil, err } - fmt.Println(encounter) // map template data rData := er.ValueDto{} @@ -293,6 +312,24 @@ func generateResumeTemplateData(resume er.Resume, event pl.Event, tx *gorm.DB) ( templateData.Doctor_Name = resume.Doctor.Employee.Person.FullName() templateData.StartedAt = *pc.TimeToStringWithFormat(encounter.StartedAt, "") templateData.FinishedAt = *pc.TimeToStringWithFormat(encounter.FinishedAt, "") + templateData.DiagnosisIn = rData.Assessment.DiagnosisIn + templateData.Diagnosis = rData.Diagnosis.SecondaryDiagnosis + templateData.MainComplaint = rData.Assessment.MainComplaint + templateData.MedicalHistory = rData.Assessment.MedicalHistory + templateData.PhysicalExamination = rData.Assessment.PhysicalExamination + templateData.SupportingExamination = rData.Supporting.Notes + templateData.MedicalActions = rData.Action.AdditionalActions + templateData.MedicalAction = rData.Action.MedicalActions + templateData.Consultations = rData.Consultation.Consultations + templateData.Allergy = rData.Pharmacy.AllergySpecialConditions + templateData.ConsciousnessLevel = rData.Discharge.ConsciousnessLevel + templateData.BloodPressure = fmt.Sprintf("%.0f/%.0f", rData.Discharge.BloodPressureSystolic, rData.Discharge.BloodPressureDiastolic) + templateData.BodyTemperature = rData.Discharge.BodyTemperature + templateData.RespirationRate = rData.Discharge.RespirationRate + templateData.HeartRate = rData.Discharge.HeartRate + templateData.PainScale = rData.Discharge.PainScale + templateData.ConditionOnDischarge = rData.Management.ConditionOnDischarge + templateData.DischargeMethod = rData.Management.DischargeMethod return &templateData, nil } diff --git a/internal/use-case/main-use-case/generate-file/tycovar.go b/internal/use-case/main-use-case/generate-file/tycovar.go index 1dee32ff..d1d1ed6a 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -57,6 +57,18 @@ type ResumePDF struct { MedicalAction string Consultations []er.ConsultationEntry Allergy string + ConsciousnessLevel string + BloodPressure string + BodyTemperature float64 + RespirationRate float64 + HeartRate float64 + PainScale int + ConditionOnDischarge string + DischargeMethod string + // fasyankes + // date + // klinik + Date string } type GenerateDto struct { @@ -110,6 +122,7 @@ type TemplateDocsName string const ( TDNGC TemplateDocsName = "general-consent.html" TDNCL TemplateDocsName = "control-letter.html" + TDNR TemplateDocsName = "resume.html" ) func (v VclaimControlLetter) generateTemplateData() ControlLetterPDF { From f13a45119b75446a075e13b8696f60508ac12c73 Mon Sep 17 00:00:00 2001 From: vanilia Date: Thu, 27 Nov 2025 11:43:42 +0700 Subject: [PATCH 23/29] fix import sequential --- internal/use-case/simgos-sync-use-case/new/encounter/helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/use-case/simgos-sync-use-case/new/encounter/helper.go b/internal/use-case/simgos-sync-use-case/new/encounter/helper.go index 1d48ea97..bbbc8687 100644 --- a/internal/use-case/simgos-sync-use-case/new/encounter/helper.go +++ b/internal/use-case/simgos-sync-use-case/new/encounter/helper.go @@ -6,7 +6,6 @@ package encounter import ( "encoding/json" - epa "simrs-vx/internal/domain/main-entities/person-address" erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" erp "simrs-vx/internal/domain/references/person" @@ -14,6 +13,7 @@ import ( "strconv" e "simrs-vx/internal/domain/main-entities/encounter" + epa "simrs-vx/internal/domain/main-entities/person-address" epc "simrs-vx/internal/domain/main-entities/person-contact" epr "simrs-vx/internal/domain/main-entities/person-relative" ep "simrs-vx/internal/domain/simgos-entities/m-pasien" From 7c454a7c0d2c0803f3244b9b2217eb3b6442daba Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 27 Nov 2025 12:04:52 +0700 Subject: [PATCH 24/29] migration from server --- cmd/main-migration/migrations/atlas.sum | 114 ++++++++++++------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 363efe04..449ff1d4 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:U9vdficFramvwnH40psSuRxzUuq6EPJwLcr9MvVTq7s= +h1:Y8m8OmsumTaAfXfYYXHlZS0+OqbAqFfM3a55nmibOUE= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -75,59 +75,59 @@ h1:U9vdficFramvwnH40psSuRxzUuq6EPJwLcr9MvVTq7s= 20251106040137.sql h1:ppcqkVoT0o9jZcjI/TN7LuaPxXhJQhnIXEJtloP/46o= 20251106041333.sql h1:2JkxyelQ/EeB+boL5bfpnzefw32ttEGKvKchtQjWmAU= 20251106042006.sql h1:ruppYa1kAJQUU3ufQBbKGMcXrGbGJJiRPclT+dNc/YQ= -20251106050412.sql h1:1002KYtHd8AwrQTMewbs/PPHDylHDghigE/3S7PVdMA= -20251106063418.sql h1:jPW/gBnbFl4RO39lQ0ZMDtYA6xbhyD6CgQupT50HmaY= -20251106071906.sql h1:leYGKxR3EQn794aOehf0sd/ZPmOnvBMZPy5/anGmRB4= -20251106073157.sql h1:KASMzjjjk5UB7Zj8lCRtM1utc4ZnDjlnpZbtTe3vONE= -20251106074218.sql h1:Z5q5deOvLaZDPhiVTN9st3/s56RepBa2YOyrMXBdj4A= -20251106081846.sql h1:P+VsWwhGt60adDIZuE/Aa38JVp/yX1rnsdpXpxASodw= -20251106082844.sql h1:Dmi5A8i9frQZvdXYPwc7f8CisZtBH8liSXq1rI6z1iM= -20251106090021.sql h1:4JwdKgO8T46YhyWVJUxpRIwudBDlG8QN1brSOYmgQ20= -20251106144745.sql h1:nqnQCzGrVJaq8ilOEOGXeRUL1dolj+OPWKuP8A92FRA= -20251107012049.sql h1:Pff4UqltGS3clSlGr0qq8CQM56L29wyxY0FC/N/YAhU= -20251107064812.sql h1:GB9a0ZfMYTIoGNmKUG+XcYUsTnRMFfT4/dAD71uCPc4= -20251107064937.sql h1:IC5pw1Ifj30hiE6dr5NMHXaSHoQI+vRd40N5ABgBHRI= -20251107071420.sql h1:9NO3iyLEXEtWa2kSRjM/8LyzuVIk6pdFL2SuheWjB08= -20251107074318.sql h1:7fHbSRrdjOmHh/xwnjCLwoiB5cW5zeH+uxLV0vZbkIA= -20251107075050.sql h1:np+3uTOnU9QNtK7Knaw8eRMhkyB9AwrtSNHphOBxbHY= -20251107080604.sql h1:cXDBLPJDVWLTG6yEJqkJsOQ7p7VYxLM2SY+mwO8qSHo= -20251107081830.sql h1:/S7OQZo4ZnK80t28g/JyiOTZtmWG/dP5Wg2zXNMQ/iE= -20251107091033.sql h1:/cbkF1nO/IjNSIfDJJx456KJtQ9rWFXOBFAkR/M2xiE= -20251107091209.sql h1:jrLQOUeV8ji2fg0pnEcs1bw4ANUxzTSMXC/rrHLIY+M= -20251107091541.sql h1:6UqbhQQRmzA2+eKu5lIvkwOkk+lH70QLZC8Pjpjcq68= -20251110012217.sql h1:C9HpX0iyHzKjyNv/5DSAn2MCHj6MX4p5UQ/NrY7QD0w= -20251110012306.sql h1:J54yb27d30LBbYp9n1P66gFVRlxPguKu0kxmWIBBG8g= -20251110052049.sql h1:232T2x8xTczJl9nk4jxJpZXhoOGYthhxjJ7nK8Jd8vg= -20251110062042.sql h1:WnfVUXrzYoj8qdkkjO9/JQQ8agGd4GfSHQdMjo7LDAg= -20251110063202.sql h1:hSzGfwVMWa6q3vwIQZUkxKgBNCzHjB+6GKy54zfV+oQ= -20251110063633.sql h1:/VpofIAqNS1CnazEnpW/+evbzn9Kew3xDW48r57M+Xg= -20251110085551.sql h1:bFZwSmfvVbTUr/enWB82WqjG88gpqcZ6s45btUvO0uo= -20251110091516.sql h1:KkJMwPQuaZQhiqnKrNQrgP12gw9rV8T3P2o3mtGTcvY= -20251110091948.sql h1:I4odAYrJdvNf1jPw6ppDC0XdI7v6vKBACg/ABwUgA7I= -20251110092729.sql h1:l1out8soEmVP6dNjaIOtGYo6QDcoJZRI8X1sjZ5ZGmo= -20251110093522.sql h1:nsz8jCxGjEdr/bz9g+4ozfZzIP803xONjVmucad1GMc= -20251110100258.sql h1:IBqt1VZj5WjQ+l9aAFGHOCCBtzb03KlLLihFLut7itg= -20251110100545.sql h1:6/LV7751iyKxE2xI6vO1zly+aHUwxXD/IBwLcVpKxqM= -20251110155448.sql h1:kFPobJB+cpflsXBAWUwy3lohuWvrb/VRlXnhJWl7i3Y= -20251111072601.sql h1:ch8F+yVhsSM5xY+TwMLY3PxdLa4Wuhtj76oyw79R7Js= -20251111073546.sql h1:cCv0NPscADAOBahRVqtDWFs6G2t7n+4a+RwlF8vk/c4= -20251111074148.sql h1:70TsV83u1gQ5TktI13K7NQiyCCa35Td2aR6CNtKUa4U= -20251111074652.sql h1:ddfQ/sRKMezPM75xBFTGytUQX5AwZ3znrJVpg73gKPA= -20251111082257.sql h1:ZsdLY1ROouos0l3oS0lkeSiuKLEUGbVvBhpcM2AVhkw= -20251111111017.sql h1:qrJ93dNtQwcuAvpsP/lAK/H63C4cinXrsVaPmWsTqkU= -20251113101344.sql h1:xaOZvAUP1fFfnO+syEFOzJUIg5lTfBe5AWHPbBWuCLA= -20251113120533.sql h1:f3/U1Ve2yF2zSMhkt+xtwF8wUYfUKYwgbNeGfE37EW4= -20251114062746.sql h1:FInLaEFQByESEwFJKuKnuUSTKmcDpi3ZXaxkKwz2+D8= -20251117005942.sql h1:wD3BWrUSmo1HlW16V3lkaBkJvbAZ0fNk77te7J9NhOc= -20251117075427.sql h1:TqU9VKZa3I8YNXUGQWY3WVBYN+1FvyyaKy0hB1jgAho= -20251118074929.sql h1:p1KsWqCuR1JXA/jVO5BmOhCcaQ8clT7t0YRszAhPFbg= -20251119063438.sql h1:NVGM0X/LHD37EaPl8SNzkNiZDJ7AB1QR+LLwLh6WRdg= -20251119065730.sql h1:U5lzk1WvMB0bw3kwckou7jkEt4bwdYItwHr2Vxqe7w4= -20251119072302.sql h1:qCuI2WMEMF/XNbjV+RXPjBnuCKLu1Fia+mR9HiLWBIs= -20251119072450.sql h1:Xg+bTwqGyKPNFEQhJylvpz1wifdfmDJvcAq6vmNf0Ng= -20251120005512.sql h1:Ek6qpacAI/qVuTYxKno+uJyzn7s5z9pf3t7VA8gTzm4= -20251120074415.sql h1:NNUeJVA03EeBHJhHqPXEZoDv/PnC6yK1/cRhmukyaJo= -20251121033803.sql h1:/vfvFX/3pzSCIHnSbMUT9EMBDykOpVkvyfeTEle9Vas= -20251124071457.sql h1:qg2dhCL9YwD13xnfJ175lW/p6MGfzFKaBqd908FByRc= -20251125125303.sql h1:4JSFv1Pmhbe9tqpLXgO63OwYnGsalStgUXKVWPyc1YE= -20251126064057.sql h1:+lByIo0zAsHVSFzQGVMuymFQajyl7fRNqgs9d/76y+E= +20251106050412.sql h1:MiEMJ1HCFYnalKuq3Z38xJeogfBAMqsTv2sG4EF8dDw= +20251106063418.sql h1:y3veDJPjKekOWLCZek/LgQwXPRhZtOppTfUXiqoL95s= +20251106071906.sql h1:/TUZA3XpMY23qEJXdkTwlzrNMvSSl6JJniPcgAttBaw= +20251106073157.sql h1:78txeibJ602DMD7huD618ZSMt6phSRzDNPTlo0PGyrc= +20251106074218.sql h1:8Xz7WywrtUnSxOHhlal53gG9rE7r86LFUt5zBFe/mIs= +20251106081846.sql h1:jp91Bf5bxGXMiUB1VIuN6y768vb2iWwow44WfCE5J5k= +20251106082844.sql h1:RHYzRO4G1fSWwf+xc/3QezZ/Iil67cZPIgNpNz3TNhQ= +20251106090021.sql h1:dFDk6mq+zjbYWmfWIrHf9DiKvvoXHjrr0++zssMTWP8= +20251106144745.sql h1:aHcr23iBFqCHer5D/SsPMXBCLjGqUYvWYfRU8jSJgIw= +20251107012049.sql h1:hu/7NHhnAkT4xK0RNtqmMDdH1Bo5EZbl7itDRjiCT+g= +20251107064812.sql h1:sfCXDQYnMf0ddrQ9oYljWJLLSt9NJjJV6o8VS3p7aZE= +20251107064937.sql h1:DlYGJ9LZFwZyR7jBP5zaGB128aIc4HAixBKPYCz9EkY= +20251107071420.sql h1:ynCdZAd2utLl+FhtWZwtahNXgIVOvuk3s/rOq7lfXA4= +20251107074318.sql h1:WE9cPhibWtZ0dbu1VEGirTeY6ijFYGMNhHdBtM32kOc= +20251107075050.sql h1:8tvneruqdynDOaJK1+0z4CH7YXZStZpGdqwIeOMLik4= +20251107080604.sql h1:8c4jd4Tql7tcdhbI9NS0tgvN+ADu9FnCf8wMUbmW7A0= +20251107081830.sql h1:SAAe3lmsm9vGXuSEsDdl7ad0EAxP5CMmFRDEgp9M7yY= +20251107091033.sql h1:JLdX/u7GUdBfjrPrMSNAqc8HtSoj0YA9iW9Vc6FJZdw= +20251107091209.sql h1:CzhYtwAwT+GHrbqcagnJE+v3mbl/rObf1IJaLCKlzrs= +20251107091541.sql h1:+3ZyWJTftDY2JeWThXuIxGWpUBnyMPyOyY4jBjdWYJI= +20251110012217.sql h1:f4Z8TuGc+XMSJ+Ekn4/PeHRE2FlHWkc5gKPJB0hAX7c= +20251110012306.sql h1:ENPyI6Kjdk6qKJQb0yJ6MCTDPAmO1WD/uhKuCSl+jYo= +20251110052049.sql h1:OrQ0acnyoQLKnTitZfnBcVr5jDslF59OFLaqT7SpdVs= +20251110062042.sql h1:9KwldQt0NpVPR86L0T4hlkfHAGau+7CiZYgu5rF+yhg= +20251110063202.sql h1:A117DuZmZ6U0jWHA3DISnr+yvBjKIr1ObrUr047YezQ= +20251110063633.sql h1:qTiC0F19JnhUIXF4LGJQ21jEV6kKGyhTr1x2kimFqPQ= +20251110085551.sql h1:HZcJM0RSC6HBaUSjKBE8MgDG8Vn9f3LmwA/OnT9Cp7I= +20251110091516.sql h1:W3AQhQLgirEWuCObbLl+Prdrbq6k6EEY1xcoWsmbog4= +20251110091948.sql h1:3tsITMrZr/T+L4wqUMz8sHS229jCJl4T0Nu3dMccxH8= +20251110092729.sql h1:uU+k88RH/e0Ns4/SmJl03RVYPscBAPuiLfxR6CJqaf0= +20251110093522.sql h1:O7upSj8VNjzvroL4IU59bfxKATOkAVGBArcUbVNq9aM= +20251110100258.sql h1://JSArUMNI3/gAtYDx2VN94C198SFW0ANjgs+p6eCRM= +20251110100545.sql h1:ENPOqeJYRpMI4e8VCKwaQgaql8se6pIidAhG2cjskBg= +20251110155448.sql h1:VW9KE0jxWy4flZ28sdXmhY6JWd6eKAX/cVL8KWRVii4= +20251111072601.sql h1:99WWzGjcDDFNC2cHRfPu4MBLWNrgPMY5HAYUbmIcVRA= +20251111073546.sql h1:iIGwFNfUgStdczw/PXTH3SD84xAyxrbZICofc3M8TMk= +20251111074148.sql h1:mzkezSKwCV2bTZ/0BHiTCX5qAy4uHpwar1xzwAH15Ko= +20251111074652.sql h1:lYh4/3BHV24pgPC0pP4RUKb+XtL/6AsZGPItRpnzBiQ= +20251111082257.sql h1:+cIZ1lf8HYGSL6t6U88plLKk8nOO1YVdV7h3YOM84ds= +20251111111017.sql h1:xzlhR2xLfOj4ddYlesS+bpEeDrxiAf5ILNOspsbZjBU= +20251113101344.sql h1:vSzThY3p6XYTj0dJ2uFVkHmlytyrFAnGE58CJLx364I= +20251113120533.sql h1:lfjgdqRGo/EohrVw8sWlFbDjh3ASTsfQ6pr3qjafqvc= +20251114062746.sql h1:6DLYjfJ60PkAABZTXvOwSE+xrU25oyoX7gpYlBnA9cw= +20251117005942.sql h1:0XoJKca8IOaB9QKFVF/qPY7jKcIgh6m/LODQuE06SAs= +20251117075427.sql h1:LhY2urosfoSu1/vkHmgsNP4JA4DuWBs9gL59yMqcF8M= +20251118074929.sql h1:3RWBD6BziQVw6WSfthgoVuhTELHER9NrIuZm4hY/F1A= +20251119063438.sql h1:EVTG3ZrHjCmM95YPASZTRPiwHrG6e33A2vO4hLSAaBQ= +20251119065730.sql h1:s98wnZOCW6NbnwDS3H53XIQ60u6B9bDwBLNvy5+Rn64= +20251119072302.sql h1:ZL+VHekLYqgNtOFKlj02WHrAk11dtTxQkmyTKE8IOzY= +20251119072450.sql h1:SiJy2vpSvoPFw4J1SW7HjGSJzrqR62NsjRYAeabV+kc= +20251120005512.sql h1:hJUaWXYJ3HiSquBTw8OKhymjA4O43ehAMGfiOY8W87Q= +20251120074415.sql h1:dNmcqZHqfZwi/wtYvO/pSFgImN2scXL0p/7g0+HLp0s= +20251121033803.sql h1:onlddYGo+tQdGaEdJPqdsx3sncGnwEvqMSCksF6vjjI= +20251124071457.sql h1:ikQLIXFikUbkUd55uJBmEvqLGEC9t0db+Om4TQsWP7M= +20251125125303.sql h1:OLKbNPO36AHtIDursW9AeBvf60sahQKC1iOjHmpx0MA= +20251126064057.sql h1:6al3PTWbw/WGiBcrRrVWppOMLtG+eRaH/qSLbnmh1kQ= From abfbc568b03efcda6c77768a0924996d382346f4 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Thu, 27 Nov 2025 13:30:45 +0700 Subject: [PATCH 25/29] feat (generate-file) hopefully resume.html doesnt make any trouble --- assets/docs/resume.html | 94 +++++++++++++------ internal/domain/main-entities/resume/dto.go | 10 ++ .../main-use-case/generate-file/helper.go | 2 + .../main-use-case/generate-file/tycovar.go | 1 + 4 files changed, 78 insertions(+), 29 deletions(-) diff --git a/assets/docs/resume.html b/assets/docs/resume.html index db66314d..4ad209ff 100644 --- a/assets/docs/resume.html +++ b/assets/docs/resume.html @@ -438,35 +438,55 @@ + + + + + + {{ end }} + {{ else }} + + + + + + + + + + {{ end }} + +
+

Obat yang diberikan

+ + + + + + + + + {{ $dataLen := len .Medications }} + + {{ if gt $dataLen 0 }} + + {{ range .Medications }} - + + + {{ end }} -
+ Terapi (Farmakologi & Non Farmakologi) Selama Perawatan + + Terapi (Farmakologi & Non Farmakologi) Waktu Pulang +
-

Obat yang diberikan

- - - - - - - - - - - - - - - - - -
- Terapi (Farmakologi & Non Farmakologi) Selawa Perawatan - - Terapi (Farmakologi & Non Farmakologi) Selawa Pulang -
{{ .NIK }}{{ .NIK }}
{{ .NIK }}{{ .NIK }}
{{ .NIK }}{{ .NIK }}
-
{{ .DuringTreatment }}{{ .AtDischarge }}
+ + {{ if eq $dataLen 1 }} +
  
  
  
+ + + @@ -501,7 +521,11 @@

Blood Pressure

: + {{ if and .BloodPressure (ne .BloodPressure "0/0") (ne .BloodPressure "") }} {{ .BloodPressure }} mmHg + {{ else }} + + {{ end }} @@ -512,7 +536,11 @@ : - {{ .BodyTemperature }} C + {{ if ne .BodyTemperature 0.0 }} + {{ .BodyTemperature }} C + {{ else }} + + {{ end }} @@ -521,7 +549,11 @@

Pulse

: - {{ .HeartRate }} x/Menit + {{ if ne .HeartRate 0.0 }} + {{ .HeartRate }} x/Menit + {{ else }} + + {{ end }} @@ -535,7 +567,11 @@ : - {{ .RespirationRate }} + {{ if ne .RespirationRate 0.0 }} + {{ .RespirationRate }} x/Menit + {{ else }} + + {{ end }} diff --git a/internal/domain/main-entities/resume/dto.go b/internal/domain/main-entities/resume/dto.go index 76e959e5..e7ddf09b 100644 --- a/internal/domain/main-entities/resume/dto.go +++ b/internal/domain/main-entities/resume/dto.go @@ -90,6 +90,7 @@ type ValueDto struct { Discharge DischargeCondition `json:"discharge"` National NationalProgram `json:"national"` Management Management `json:"management"` + Medication Medication `json:"medication"` } type Assessment struct { @@ -170,3 +171,12 @@ type Management struct { ConditionOnDischarge string `json:"conditionOnDischarge"` // e.g. "Stable" DischargeMethod string `json:"dischargeMethod"` // e.g. "Discharged with Doctor's Approval" } + +type MedicationEntry struct { + DuringTreatment string `json:"duringTreatment"` + AtDischarge string `json:"atDischarge"` +} + +type Medication struct { + Medications []MedicationEntry `json:"medications"` +} diff --git a/internal/use-case/main-use-case/generate-file/helper.go b/internal/use-case/main-use-case/generate-file/helper.go index 02800c3a..1f0d0bfd 100644 --- a/internal/use-case/main-use-case/generate-file/helper.go +++ b/internal/use-case/main-use-case/generate-file/helper.go @@ -330,6 +330,8 @@ func generateResumeTemplateData(resume er.Resume, event pl.Event, tx *gorm.DB) ( templateData.PainScale = rData.Discharge.PainScale templateData.ConditionOnDischarge = rData.Management.ConditionOnDischarge templateData.DischargeMethod = rData.Management.DischargeMethod + templateData.Medications = rData.Medication.Medications + templateData.Date = pu.GetTimeNow().Format("02-01-2006") return &templateData, nil } diff --git a/internal/use-case/main-use-case/generate-file/tycovar.go b/internal/use-case/main-use-case/generate-file/tycovar.go index d1d1ed6a..a242f476 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -65,6 +65,7 @@ type ResumePDF struct { PainScale int ConditionOnDischarge string DischargeMethod string + Medications []er.MedicationEntry // fasyankes // date // klinik From 2a393015635f30b428241495ce0ef32ed99fe54e Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Fri, 28 Nov 2025 16:10:35 +0700 Subject: [PATCH 26/29] feat (vclaim-sep): create, read, delete done --- cmd/bpjs-api/config.yml-example | 3 - cmd/main-api/config.yml-example | 3 - .../domain/bpjs-entities/vclaim-sep/dto.go | 141 +++++++++++++++++- .../bpjs-entities/vclaim-sep/entitiy.go | 1 + .../domain/main-entities/encounter/entity.go | 2 +- .../interface/bpjs-handler/bpjs-handler.go | 14 +- .../interface/main-handler/main-handler.go | 48 +++++- .../member/handler.go | 0 .../monitoring/handler.go | 0 .../reference/handler.go | 0 .../interface/main-handler/resume/handler.go | 6 - .../vclaim-sep-hist/handler.go | 0 .../vclaim-sep-print/handler.go | 0 .../vclaim-sep/handler.go | 56 ++++--- .../use-case/bpjs-plugin/vclaim-sep/plugin.go | 81 +++++++++- .../bpjs-plugin/vclaim-sep/tycovar.go | 2 + .../use-case/bpjs-use-case/vclaim-sep/case.go | 22 ++- .../use-case/bpjs-use-case/vclaim-sep/lib.go | 3 + .../vclaim-sep/middleware-runner.go | 2 +- .../bpjs-use-case/vclaim-sep/middleware.go | 7 +- .../bpjs-use-case/vclaim-sep/tycovar.go | 10 +- .../main-use-case/generate-file/helper.go | 4 + .../use-case/main-use-case/resume/case.go | 9 +- .../use-case/main-use-case/resume/helper.go | 4 +- 24 files changed, 343 insertions(+), 75 deletions(-) rename internal/interface/{bpjs-handler => main-handler}/member/handler.go (100%) rename internal/interface/{bpjs-handler => main-handler}/monitoring/handler.go (100%) rename internal/interface/{bpjs-handler => main-handler}/reference/handler.go (100%) rename internal/interface/{bpjs-handler => main-handler}/vclaim-sep-hist/handler.go (100%) rename internal/interface/{bpjs-handler => main-handler}/vclaim-sep-print/handler.go (100%) rename internal/interface/{bpjs-handler => main-handler}/vclaim-sep/handler.go (55%) diff --git a/cmd/bpjs-api/config.yml-example b/cmd/bpjs-api/config.yml-example index 60b1644c..dcea2f07 100644 --- a/cmd/bpjs-api/config.yml-example +++ b/cmd/bpjs-api/config.yml-example @@ -57,9 +57,6 @@ corsCfg: satuSehatCfg: host: localhost:8200 -bpjsCfg: - host: localhost:8200 - corsCfg: allowedOrigins: - http://example.com diff --git a/cmd/main-api/config.yml-example b/cmd/main-api/config.yml-example index 30351abf..3423b5d1 100644 --- a/cmd/main-api/config.yml-example +++ b/cmd/main-api/config.yml-example @@ -57,9 +57,6 @@ corsCfg: satuSehatCfg: host: localhost:8200 -bpjsCfg: - host: localhost:8200 - corsCfg: allowedOrigins: - http://example.com diff --git a/internal/domain/bpjs-entities/vclaim-sep/dto.go b/internal/domain/bpjs-entities/vclaim-sep/dto.go index 8a174688..2f4475b5 100644 --- a/internal/domain/bpjs-entities/vclaim-sep/dto.go +++ b/internal/domain/bpjs-entities/vclaim-sep/dto.go @@ -1,6 +1,7 @@ package vclaimsep import ( + "encoding/json" ecore "simrs-vx/internal/domain/base-entities/core" evsh "simrs-vx/internal/domain/bpjs-entities/vclaim-sep-hist" ) @@ -8,7 +9,7 @@ import ( type CreateDto struct { Encounter_Id *uint `json:"encounter_id"` Number *string `json:"number" validate:"maxLength=19"` - RequestPayload []byte `json:"requestPayload" validate:"maxLength=1024"` + RequestPayload string `json:"requestPayload" validate:"maxLength=1024"` VclaimSepHist evsh.CreateDto } @@ -25,8 +26,9 @@ type FilterDto struct { } type ReadDetailDto struct { - Id uint `json:"id"` - Number *string `json:"number"` + Id uint `json:"id"` + Number *string `json:"number"` + Encounter_Id *uint `json:"encounter_id"` } type UpdateDto struct { @@ -47,14 +49,16 @@ type MetaDto struct { type ResponseDto struct { ecore.Main - Encounter_Id *uint `json:"encounter_id"` - Number *string `json:"number"` + Encounter_Id *uint `json:"encounter_id"` + Number *string `json:"number"` + Detail *SepDetail `json:"detail,omitempty"` } func (d VclaimSep) ToResponse() ResponseDto { resp := ResponseDto{ Encounter_Id: d.Encounter_Id, Number: d.Number, + Detail: d.Detail, } resp.Main = d.Main return resp @@ -71,3 +75,130 @@ func ToResponseList(data []VclaimSep) []ResponseDto { func (c CreateDto) IsMessageSuccess() bool { return c.VclaimSepHist.Message == "Sukses" } + +func (c CreateDto) RequestPayloadIntoJson() ([]byte, error) { + payload := map[string]interface{}{} + err := json.Unmarshal([]byte(c.RequestPayload), &payload) + if err != nil { + return nil, err + } + return json.Marshal(payload) +} + +func (c CreateDto) RequestPayloadWithEncounterId() ([]byte, error) { + payload := map[string]interface{}{} + + err := json.Unmarshal([]byte(c.RequestPayload), &payload) + if err != nil { + return nil, err + } + + payload["encounter_id"] = c.Encounter_Id + + return json.Marshal(payload) +} + +type SepResponse struct { + MetaData MetaData `json:"metaData"` + Response *SepDetail `json:"response"` +} + +type MetaData struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type SepDetail struct { + AssestmenPel AssestmenPel `json:"assestmenPel"` + Catatan string `json:"catatan"` + Cob string `json:"cob"` + Diagnosa string `json:"diagnosa"` + Dpjp Dpjp `json:"dpjp"` + ESep string `json:"eSEP"` + FlagProcedure FlagProcedure `json:"flagProcedure"` + Informasi any `json:"informasi"` // null = interface{} + JnsPelayanan string `json:"jnsPelayanan"` + Katarak string `json:"katarak"` + KdPenunjang KdPenunjang `json:"kdPenunjang"` + KdStatusKecelakaan string `json:"kdStatusKecelakaan"` + KelasRawat string `json:"kelasRawat"` + KlsRawat KlsRawat `json:"klsRawat"` + Kontrol Kontrol `json:"kontrol"` + LokasiKejadian LokasiKejadian `json:"lokasiKejadian"` + NmStatusKecelakaan string `json:"nmstatusKecelakaan"` + NoRujukan string `json:"noRujukan"` + NoSep string `json:"noSep"` + Penjamin any `json:"penjamin"` // null + Peserta Peserta `json:"peserta"` + Poli string `json:"poli"` + PoliEksekutif string `json:"poliEksekutif"` + TglSep string `json:"tglSep"` + TujuanKunj TujuanKunj `json:"tujuanKunj"` +} + +type AssestmenPel struct { + Kode string `json:"kode"` + Nama string `json:"nama"` +} + +type Dpjp struct { + KdDPJP string `json:"kdDPJP"` + NmDPJP string `json:"nmDPJP"` +} + +type FlagProcedure struct { + Kode string `json:"kode"` + Nama string `json:"nama"` +} + +type KdPenunjang struct { + Kode string `json:"kode"` + Nama string `json:"nama"` +} + +type KlsRawat struct { + KlsRawatHak string `json:"klsRawatHak"` + KlsRawatNaik *string `json:"klsRawatNaik"` // nullable + Pembiayaan *string `json:"pembiayaan"` // nullable + PenanggungJawab *string `json:"penanggungJawab"` // nullable +} + +type Kontrol struct { + KdDokter *string `json:"kdDokter"` // null + NmDokter *string `json:"nmDokter"` // null + NoSurat *string `json:"noSurat"` // null +} + +type LokasiKejadian struct { + KdKab *string `json:"kdKab"` + KdKec *string `json:"kdKec"` + KdProp *string `json:"kdProp"` + KetKejadian *string `json:"ketKejadian"` + Lokasi *string `json:"lokasi"` + TglKejadian *string `json:"tglKejadian"` +} + +type Peserta struct { + Asuransi any `json:"asuransi"` + HakKelas string `json:"hakKelas"` + JnsPeserta string `json:"jnsPeserta"` + Kelamin string `json:"kelamin"` + Nama string `json:"nama"` + NoKartu string `json:"noKartu"` + NoMR string `json:"noMr"` + TglLahir string `json:"tglLahir"` +} + +type TujuanKunj struct { + Kode string `json:"kode"` + Nama string `json:"nama"` +} + +type SepDeleteRequest struct { + Request struct { + TSep struct { + NoSep string `json:"noSep"` + User string `json:"user"` + } `json:"t_sep"` + } `json:"request"` +} diff --git a/internal/domain/bpjs-entities/vclaim-sep/entitiy.go b/internal/domain/bpjs-entities/vclaim-sep/entitiy.go index f175e4cb..6948d640 100644 --- a/internal/domain/bpjs-entities/vclaim-sep/entitiy.go +++ b/internal/domain/bpjs-entities/vclaim-sep/entitiy.go @@ -12,4 +12,5 @@ type VclaimSep struct { Number *string `json:"number" gorm:"unique;size:19"` Prints []*evsp.VclaimSepPrint `json:"prints,omitempty" gorm:"foreignKey:VclaimSep_Number;references:Number"` ControlLetters []*evscl.VclaimSepControlLetter `json:"controlLetters,omitempty" gorm:"foreignKey:VclaimSep_Number;references:Number"` + Detail *SepDetail `json:"detail,omitempty" gorm:"-"` } diff --git a/internal/domain/main-entities/encounter/entity.go b/internal/domain/main-entities/encounter/entity.go index a3908149..5584819b 100644 --- a/internal/domain/main-entities/encounter/entity.go +++ b/internal/domain/main-entities/encounter/entity.go @@ -87,5 +87,5 @@ func (d Encounter) IsSameResponsibleDoctor(input *string) bool { if input == nil { return false } - return d.Responsible_Doctor_Code == input + return *d.Responsible_Doctor_Code == *input } diff --git a/internal/interface/bpjs-handler/bpjs-handler.go b/internal/interface/bpjs-handler/bpjs-handler.go index 175bb667..4f000664 100644 --- a/internal/interface/bpjs-handler/bpjs-handler.go +++ b/internal/interface/bpjs-handler/bpjs-handler.go @@ -4,12 +4,12 @@ import ( "net/http" /******************** main / transaction ********************/ - member "simrs-vx/internal/interface/bpjs-handler/member" - monitoring "simrs-vx/internal/interface/bpjs-handler/monitoring" - reference "simrs-vx/internal/interface/bpjs-handler/reference" - vclaimsep "simrs-vx/internal/interface/bpjs-handler/vclaim-sep" - vclaimsephist "simrs-vx/internal/interface/bpjs-handler/vclaim-sep-hist" - vclaimsepprint "simrs-vx/internal/interface/bpjs-handler/vclaim-sep-print" + member "simrs-vx/internal/interface/main-handler/member" + monitoring "simrs-vx/internal/interface/main-handler/monitoring" + reference "simrs-vx/internal/interface/main-handler/reference" + vclaimsep "simrs-vx/internal/interface/main-handler/vclaim-sep" + vclaimsephist "simrs-vx/internal/interface/main-handler/vclaim-sep-hist" + vclaimsepprint "simrs-vx/internal/interface/main-handler/vclaim-sep-print" /******************** actor ********************/ @@ -49,7 +49,7 @@ func SetRoutes() http.Handler { hk.GroupRoutes("/v1/vclaim-sep", r, hk.MapHandlerFunc{ "POST /": vclaimsep.O.Create, - "PATCH /{id}": vclaimsep.O.Update, + "PATCH /{id}": vclaimsep.O.GetDetail, "DELETE /{id}": vclaimsep.O.Delete, }) diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index a4f6e382..d374a8a8 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -120,9 +120,17 @@ import ( regency "simrs-vx/internal/interface/main-handler/regency" village "simrs-vx/internal/interface/main-handler/village" - ///// Internal + /******************** Internal ********************/ validation "simrs-vx/internal/interface/main-handler/helper/validation" "simrs-vx/internal/interface/main-handler/home" + + /******************** BPJS ********************/ + member "simrs-vx/internal/interface/main-handler/member" + monitoring "simrs-vx/internal/interface/main-handler/monitoring" + reference "simrs-vx/internal/interface/main-handler/reference" + vclaimsep "simrs-vx/internal/interface/main-handler/vclaim-sep" + vclaimsephist "simrs-vx/internal/interface/main-handler/vclaim-sep-hist" + vclaimsepprint "simrs-vx/internal/interface/main-handler/vclaim-sep-print" ) // One place route to relatively easier to manage, ESPECIALLY in tracking @@ -138,6 +146,7 @@ func SetRoutes() http.Handler { a.RegisterExtCall(validation.RegisterValidation) a.RegisterExtCall(simgossync.SetConfig) a.RegisterExtCall(docscfg.ParseCfg) + a.RegisterExtCall(ibpjs.SetConfig) r := http.NewServeMux() @@ -371,6 +380,43 @@ func SetRoutes() http.Handler { hc.RegCrudByCode(r, "/v1/province", province.O) hc.RegCrudByCode(r, "/v1/postal-region", postalregion.O) + /******************** BPJS ********************/ + hk.GroupRoutes("/v1/vclaim-sep", r, hk.MapHandlerFunc{ + "POST /": vclaimsep.O.Create, + "GET /{id}": vclaimsep.O.GetDetail, + "DELETE /{id}": vclaimsep.O.Delete, + }) + + hk.GroupRoutes("/v1/vclaim-sep-hist", r, hk.MapHandlerFunc{ + "GET /": vclaimsephist.O.GetList, + }) + + hk.GroupRoutes("/v1/vclaim-sep-print", r, hk.MapHandlerFunc{ + "POST /": vclaimsepprint.O.Create, + }) + + hk.GroupRoutes("/v1/reference", r, hk.MapHandlerFunc{ + "GET /province": reference.GetListProvince, + "GET /regency/{provinceCode}": reference.GetListCities, + "GET /district/{regencyCode}": reference.GetListDistrict, + "GET /diagnose/{keyword}": reference.GetListDiagnose, + "GET /diagnose-prb": reference.GetListDiagnosePrb, + "GET /medicine-prb/{keyword}": reference.GetListMedicinePrb, + "GET /unit/{unitCode}": reference.GetListUnit, + "GET /healthcare/{healthcare}/{healthcareType}": reference.GetListHealthcare, + "GET /responsible-doctor/{serviceType}/{serviceDate}/{specialistCode}": reference.GetListDoctor, + }) + + hk.GroupRoutes("/v1/member", r, hk.MapHandlerFunc{ + "GET /bpjs/{cardNumber}/{sepDate}": member.GetListByBpjsNumber, + "GET /nik/{nik}/{sepDate}": member.GetListByNik, + }) + + hk.GroupRoutes("/v1/monitoring", r, hk.MapHandlerFunc{ + "GET /visit/{date}/{serviceType}": monitoring.GetListVisit, + "GET /hist/{cardNumber}/{startDate}/{endDate}": monitoring.GetListHist, + }) + ///// return cmw.SetCors(handlerlogger.SetLog(r)) } diff --git a/internal/interface/bpjs-handler/member/handler.go b/internal/interface/main-handler/member/handler.go similarity index 100% rename from internal/interface/bpjs-handler/member/handler.go rename to internal/interface/main-handler/member/handler.go diff --git a/internal/interface/bpjs-handler/monitoring/handler.go b/internal/interface/main-handler/monitoring/handler.go similarity index 100% rename from internal/interface/bpjs-handler/monitoring/handler.go rename to internal/interface/main-handler/monitoring/handler.go diff --git a/internal/interface/bpjs-handler/reference/handler.go b/internal/interface/main-handler/reference/handler.go similarity index 100% rename from internal/interface/bpjs-handler/reference/handler.go rename to internal/interface/main-handler/reference/handler.go diff --git a/internal/interface/main-handler/resume/handler.go b/internal/interface/main-handler/resume/handler.go index 9a59f5fd..24ac0b08 100644 --- a/internal/interface/main-handler/resume/handler.go +++ b/internal/interface/main-handler/resume/handler.go @@ -99,9 +99,6 @@ func Verify(w http.ResponseWriter, r *http.Request) { } dto := e.UpdateDto{} - if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { - return - } dto.Id = uint(id) dto.Status_Code = erc.DVCVerified dto.AuthInfo = *authInfo @@ -120,9 +117,6 @@ func Validate(w http.ResponseWriter, r *http.Request) { } dto := e.UpdateDto{} - if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { - return - } dto.Id = uint(id) dto.Status_Code = erc.DVCValidated dto.AuthInfo = *authInfo diff --git a/internal/interface/bpjs-handler/vclaim-sep-hist/handler.go b/internal/interface/main-handler/vclaim-sep-hist/handler.go similarity index 100% rename from internal/interface/bpjs-handler/vclaim-sep-hist/handler.go rename to internal/interface/main-handler/vclaim-sep-hist/handler.go diff --git a/internal/interface/bpjs-handler/vclaim-sep-print/handler.go b/internal/interface/main-handler/vclaim-sep-print/handler.go similarity index 100% rename from internal/interface/bpjs-handler/vclaim-sep-print/handler.go rename to internal/interface/main-handler/vclaim-sep-print/handler.go diff --git a/internal/interface/bpjs-handler/vclaim-sep/handler.go b/internal/interface/main-handler/vclaim-sep/handler.go similarity index 55% rename from internal/interface/bpjs-handler/vclaim-sep/handler.go rename to internal/interface/main-handler/vclaim-sep/handler.go index d40c76e9..ed11e257 100644 --- a/internal/interface/bpjs-handler/vclaim-sep/handler.go +++ b/internal/interface/main-handler/vclaim-sep/handler.go @@ -1,7 +1,6 @@ package vclaimsep import ( - "io" "net/http" rw "github.com/karincake/risoles" @@ -16,14 +15,9 @@ var O myBase func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { dto := e.CreateDto{} - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "failed to read body", http.StatusBadRequest) + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { return } - defer r.Body.Close() - - dto.RequestPayload = body res, err := u.Create(dto) rw.DataResponse(w, res, err) } @@ -35,39 +29,39 @@ func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { // 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 { +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{} + dto.Id = uint(id) + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +// func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { +// number := rw.ValidateString(w, "number", r.PathValue("number")) +// if number != "" { // return // } -// dto := e.ReadDetailDto{} -// dto.Id = uint(id) -// res, err := u.ReadDetail(dto) + +// dto := e.UpdateDto{} +// if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { +// return +// } +// dto.Number = &number +// res, err := u.Update(dto) // rw.DataResponse(w, res, err) // } -func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { - number := rw.ValidateString(w, "number", r.PathValue("number")) - if number != "" { - return - } - - dto := e.UpdateDto{} - if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { - return - } - dto.Number = &number - res, err := u.Update(dto) - rw.DataResponse(w, res, err) -} - func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { - number := rw.ValidateString(w, "number", r.PathValue("number")) - if number != "" { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { return } dto := e.DeleteDto{} - dto.Number = &number + dto.Id = uint(id) res, err := u.Delete(dto) rw.DataResponse(w, res, err) } diff --git a/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go b/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go index fd18763f..70097b74 100644 --- a/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go +++ b/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go @@ -14,7 +14,11 @@ import ( ) func CreateSep(input *e.CreateDto, data *e.VclaimSep, tx *gorm.DB) error { - req, err := http.NewRequest("POST", ibpjs.O.BaseUrl+"/sep", bytes.NewReader(input.RequestPayload)) + payload, err := input.RequestPayloadIntoJson() + if err != nil { + return err + } + req, err := http.NewRequest("POST", ibpjs.O.BaseUrl+"sep", bytes.NewBuffer(payload)) if err != nil { return err } @@ -43,7 +47,11 @@ func CreateSep(input *e.CreateDto, data *e.VclaimSep, tx *gorm.DB) error { } // Save request/response details in DTO for further use - input.VclaimSepHist.RequestPayload = string(input.RequestPayload) + reqPayload, err := input.RequestPayloadWithEncounterId() + if err != nil { + return err + } + input.VclaimSepHist.RequestPayload = string(reqPayload) input.VclaimSepHist.ResponseBody = string(body) input.VclaimSepHist.Message = msg input.Number = func() *string { @@ -56,3 +64,72 @@ func CreateSep(input *e.CreateDto, data *e.VclaimSep, tx *gorm.DB) error { return nil } + +func ReadDetailSep(input *e.ReadDetailDto, data *e.VclaimSep, tx *gorm.DB) error { + endpoint := fmt.Sprintf("sep/%s", *data.Number) + req, err := http.NewRequest("GET", ibpjs.O.BaseUrl+endpoint, nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + var detail e.SepResponse + if err := json.Unmarshal(body, &detail); err != nil { + return fmt.Errorf("failed to parse response JSON: %w", err) + } + + data.Detail = detail.Response + + return nil +} + +func DeleteSep(input *e.DeleteDto, data *e.VclaimSep, tx *gorm.DB) error { + payload := e.SepDeleteRequest{} + payload.Request.TSep.NoSep = *data.Number + payload.Request.TSep.User = "Coba Ws" + + jsonPayload, err := json.Marshal(payload) + if err != nil { + return err + } + + req, err := http.NewRequest("DELETE", ibpjs.O.BaseUrl+"sep", bytes.NewBuffer(jsonPayload)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + var detail e.SepResponse + if err := json.Unmarshal(body, &detail); err != nil { + return fmt.Errorf("failed to parse response JSON: %w", err) + } + + if detail.MetaData.Message == SepNotFound { + return fmt.Errorf("sep with number %s not found", *data.Number) + } + + return nil +} diff --git a/internal/use-case/bpjs-plugin/vclaim-sep/tycovar.go b/internal/use-case/bpjs-plugin/vclaim-sep/tycovar.go index f057726e..7c19c44c 100644 --- a/internal/use-case/bpjs-plugin/vclaim-sep/tycovar.go +++ b/internal/use-case/bpjs-plugin/vclaim-sep/tycovar.go @@ -10,3 +10,5 @@ type vclaimResponse struct { } `json:"sep"` } `json:"response"` } + +const SepNotFound = "Data Tidak Ditemukan" diff --git a/internal/use-case/bpjs-use-case/vclaim-sep/case.go b/internal/use-case/bpjs-use-case/vclaim-sep/case.go index ac72d648..1d0e4aab 100644 --- a/internal/use-case/bpjs-use-case/vclaim-sep/case.go +++ b/internal/use-case/bpjs-use-case/vclaim-sep/case.go @@ -1,6 +1,7 @@ package vclaimsep import ( + "fmt" "strconv" e "simrs-vx/internal/domain/bpjs-entities/vclaim-sep" @@ -31,6 +32,17 @@ func Create(input e.CreateDto) (*d.Data, error) { pl.SetLogInfo(&event, input, "started", "create") err := dg.I.Transaction(func(tx *gorm.DB) error { + rDto := e.ReadDetailDto{Encounter_Id: input.Encounter_Id} + if vSep, err := ReadDetailData(rDto, &event, tx); err != nil { + if !pu.IsDataNotFoundError(err) { + return err + } + + if vSep != nil { + return fmt.Errorf("sep with encounter id %d already exist with number %s", input.Encounter_Id, *vSep.Number) + } + } + mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware @@ -38,12 +50,12 @@ func Create(input e.CreateDto) (*d.Data, error) { return err } - if _, err := uvsh.CreateData(input.VclaimSepHist, &event, tx); err != nil { + if _, err := uvsh.CreateData(input.VclaimSepHist, &event); err != nil { return err } if !input.IsMessageSuccess() { - return nil + return fmt.Errorf("%s", input.VclaimSepHist.Message) } if resData, err := CreateData(input, &event, tx); err != nil { @@ -234,7 +246,7 @@ func Update(input e.UpdateDto) (*d.Data, error) { } func Delete(input e.DeleteDto) (*d.Data, error) { - rdDto := e.ReadDetailDto{Number: input.Number} + rdDto := e.ReadDetailDto{Id: input.Id} var data *e.VclaimSep var err error @@ -255,7 +267,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware - if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { + if err := mwRunner.RunDeleteMiddleware(deletePreMw, &e.DeleteDto{}, data); err != nil { return err } @@ -265,7 +277,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { mwRunner.setMwType(pu.MWTPost) // Run post-middleware - if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { + if err := mwRunner.RunDeleteMiddleware(deletePostMw, &e.DeleteDto{}, data); err != nil { return err } diff --git a/internal/use-case/bpjs-use-case/vclaim-sep/lib.go b/internal/use-case/bpjs-use-case/vclaim-sep/lib.go index 817c357c..82da0d45 100644 --- a/internal/use-case/bpjs-use-case/vclaim-sep/lib.go +++ b/internal/use-case/bpjs-use-case/vclaim-sep/lib.go @@ -87,6 +87,9 @@ func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e if input.Id != 0 { tx = tx.Where("\"Id\" = ?", input.Id) } + if input.Encounter_Id != nil { + tx = tx.Where("\"Encounter_Id\" = ?", *input.Encounter_Id) + } if err := tx.First(&data).Error; err != nil { if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { diff --git a/internal/use-case/bpjs-use-case/vclaim-sep/middleware-runner.go b/internal/use-case/bpjs-use-case/vclaim-sep/middleware-runner.go index dee35276..8e8522a6 100644 --- a/internal/use-case/bpjs-use-case/vclaim-sep/middleware-runner.go +++ b/internal/use-case/bpjs-use-case/vclaim-sep/middleware-runner.go @@ -83,7 +83,7 @@ func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, inpu return nil } -func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.VclaimSep) error { +func (me *middlewareRunner) RunDeleteMiddleware(middlewares []deleteMw, input *e.DeleteDto, data *e.VclaimSep) error { for _, middleware := range middlewares { logData := pu.GetLogData(input, data) diff --git a/internal/use-case/bpjs-use-case/vclaim-sep/middleware.go b/internal/use-case/bpjs-use-case/vclaim-sep/middleware.go index 1ad0a853..08caba00 100644 --- a/internal/use-case/bpjs-use-case/vclaim-sep/middleware.go +++ b/internal/use-case/bpjs-use-case/vclaim-sep/middleware.go @@ -9,5 +9,10 @@ func init() { createPreMw = append(createPreMw, createMw{Name: "create-sep", Func: pvs.CreateSep}, ) - + readDetailPostMw = append(readDetailPostMw, + readDetailMw{Name: "read-detail-sep", Func: pvs.ReadDetailSep}, + ) + deletePreMw = append(deletePreMw, + deleteMw{Name: "delete-sep", Func: pvs.DeleteSep}, + ) } diff --git a/internal/use-case/bpjs-use-case/vclaim-sep/tycovar.go b/internal/use-case/bpjs-use-case/vclaim-sep/tycovar.go index 932bc355..be3b5d11 100644 --- a/internal/use-case/bpjs-use-case/vclaim-sep/tycovar.go +++ b/internal/use-case/bpjs-use-case/vclaim-sep/tycovar.go @@ -29,8 +29,12 @@ type readDetailMw struct { Func func(input *e.ReadDetailDto, data *e.VclaimSep, tx *gorm.DB) error } +type deleteMw struct { + Name string + Func func(input *e.DeleteDto, data *e.VclaimSep, tx *gorm.DB) error +} + type UpdateMw = readDetailMw -type DeleteMw = readDetailMw var createPreMw []createMw // preprocess middleware var createPostMw []createMw // postprocess middleware @@ -40,5 +44,5 @@ var readDetailPreMw []readDetailMw var readDetailPostMw []readDetailMw var updatePreMw []readDetailMw var updatePostMw []readDetailMw -var deletePreMw []readDetailMw -var deletePostMw []readDetailMw +var deletePreMw []deleteMw +var deletePostMw []deleteMw diff --git a/internal/use-case/main-use-case/generate-file/helper.go b/internal/use-case/main-use-case/generate-file/helper.go index 1f0d0bfd..3a6c3480 100644 --- a/internal/use-case/main-use-case/generate-file/helper.go +++ b/internal/use-case/main-use-case/generate-file/helper.go @@ -244,6 +244,10 @@ func generateResume(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDt return nil, err } + if !r.IsValidated() { + return nil, errors.New("resume is not validated") + } + if r.FileUrl != nil { if err := removeFile(string(input.EntityType_Code), *r.FileUrl); err != nil { return nil, err diff --git a/internal/use-case/main-use-case/resume/case.go b/internal/use-case/main-use-case/resume/case.go index cbc472f9..eab70280 100644 --- a/internal/use-case/main-use-case/resume/case.go +++ b/internal/use-case/main-use-case/resume/case.go @@ -50,7 +50,6 @@ func Create(input e.CreateDto) (*d.Data, error) { return errors.New("encounter is already done") } - input.Status_Code = erc.DVCNew if resData, err := CreateData(input, &event, tx); err != nil { return err } else { @@ -345,16 +344,16 @@ func UpdateStatusCode(input e.UpdateDto) (*d.Data, error) { switch input.Status_Code { case erc.DVCValidated: - if !enc.IsSameResponsibleDoctor(input.AuthInfo.Doctor_Code) { - return errors.New("validation doctor is not the same as encounter responsible doctor") - } - if data.IsNew() { return errors.New("resume need to be verified first") } if data.IsValidated() { return errors.New("resume already validated") } + if !enc.IsSameResponsibleDoctor(input.AuthInfo.Doctor_Code) { + return errors.New("validation doctor is not the same as encounter responsible doctor") + } + data.Status_Code = erc.DVCValidated err = tx.Save(&data).Error if err != nil { diff --git a/internal/use-case/main-use-case/resume/helper.go b/internal/use-case/main-use-case/resume/helper.go index edd45222..794ae214 100644 --- a/internal/use-case/main-use-case/resume/helper.go +++ b/internal/use-case/main-use-case/resume/helper.go @@ -6,12 +6,15 @@ package resume import ( e "simrs-vx/internal/domain/main-entities/resume" + + erc "simrs-vx/internal/domain/references/common" ) func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Resume) { var inputSrc *e.CreateDto if inputT, ok := any(input).(*e.CreateDto); ok { inputSrc = inputT + data.Status_Code = erc.DVCNew } else { inputTemp := any(input).(*e.UpdateDto) inputSrc = &inputTemp.CreateDto @@ -20,5 +23,4 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Resume) { data.Encounter_Id = inputSrc.Encounter_Id data.Doctor_Code = inputSrc.AuthInfo.Doctor_Code data.Value = inputSrc.Value - data.Status_Code = inputSrc.Status_Code } From 2a979f60a6ba1fe314114efc831cbc9cf752a4fa Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 1 Dec 2025 12:43:23 +0700 Subject: [PATCH 27/29] feat (vclaim/bpjs/jkn): add several endpoint --- .../domain/bpjs-entities/control-plan/dto.go | 7 ++ .../bpjs-entities/control-plan/entity.go | 22 +++++ internal/domain/bpjs-entities/referral/dto.go | 5 + .../domain/bpjs-entities/referral/entity.go | 96 +++++++++++++++++++ .../main-handler/control-plan/handler.go | 32 +++++++ .../interface/main-handler/main-handler.go | 16 +++- .../interface/main-handler/member/handler.go | 8 +- .../main-handler/monitoring/handler.go | 10 +- .../main-handler/reference/handler.go | 20 ++-- .../main-handler/referral/handler.go | 22 +++++ .../main-handler/vclaim-sep/handler.go | 6 +- .../bpjs-plugin/control-plan/helper.go | 11 +++ .../bpjs-plugin/control-plan/plugin.go | 37 +++++++ .../use-case/bpjs-plugin/referral/helper.go | 11 +++ .../use-case/bpjs-plugin/referral/plugin.go | 37 +++++++ .../use-case/bpjs-plugin/vclaim-sep/plugin.go | 4 +- .../bpjs-use-case/control-plan/case.go | 50 ++++++++++ .../control-plan/middleware-runner.go | 42 ++++++++ .../bpjs-use-case/control-plan/middleware.go | 12 +++ .../bpjs-use-case/control-plan/tycovar.go | 18 ++++ .../use-case/bpjs-use-case/referral/case.go | 50 ++++++++++ .../referral/middleware-runner.go | 42 ++++++++ .../bpjs-use-case/referral/middleware.go | 12 +++ .../bpjs-use-case/referral/tycovar.go | 18 ++++ 24 files changed, 561 insertions(+), 27 deletions(-) create mode 100644 internal/domain/bpjs-entities/control-plan/dto.go create mode 100644 internal/domain/bpjs-entities/control-plan/entity.go create mode 100644 internal/domain/bpjs-entities/referral/dto.go create mode 100644 internal/domain/bpjs-entities/referral/entity.go create mode 100644 internal/interface/main-handler/control-plan/handler.go create mode 100644 internal/interface/main-handler/referral/handler.go create mode 100644 internal/use-case/bpjs-plugin/control-plan/helper.go create mode 100644 internal/use-case/bpjs-plugin/control-plan/plugin.go create mode 100644 internal/use-case/bpjs-plugin/referral/helper.go create mode 100644 internal/use-case/bpjs-plugin/referral/plugin.go create mode 100644 internal/use-case/bpjs-use-case/control-plan/case.go create mode 100644 internal/use-case/bpjs-use-case/control-plan/middleware-runner.go create mode 100644 internal/use-case/bpjs-use-case/control-plan/middleware.go create mode 100644 internal/use-case/bpjs-use-case/control-plan/tycovar.go create mode 100644 internal/use-case/bpjs-use-case/referral/case.go create mode 100644 internal/use-case/bpjs-use-case/referral/middleware-runner.go create mode 100644 internal/use-case/bpjs-use-case/referral/middleware.go create mode 100644 internal/use-case/bpjs-use-case/referral/tycovar.go diff --git a/internal/domain/bpjs-entities/control-plan/dto.go b/internal/domain/bpjs-entities/control-plan/dto.go new file mode 100644 index 00000000..2290707d --- /dev/null +++ b/internal/domain/bpjs-entities/control-plan/dto.go @@ -0,0 +1,7 @@ +package controlplan + +type ReadListDto struct { + PathValue1 string `json:"-"` // jenis kontrol + PathValue2 string `json:"-"` // kode poli + PathValue3 string `json:"-"` // tanggal +} diff --git a/internal/domain/bpjs-entities/control-plan/entity.go b/internal/domain/bpjs-entities/control-plan/entity.go new file mode 100644 index 00000000..1a07379a --- /dev/null +++ b/internal/domain/bpjs-entities/control-plan/entity.go @@ -0,0 +1,22 @@ +package controlplan + +type Response struct { + MetaData MetaData `json:"metaData"` + Response *ResponseItems `json:"response"` // nullable +} + +type MetaData struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type ResponseItems struct { + List []DoctorSchedule `json:"list"` +} + +type DoctorSchedule struct { + JadwalPraktek string `json:"jadwalPraktek"` + Kapasitas string `json:"kapasitas"` + KodeDokter string `json:"kodeDokter"` + NamaDokter string `json:"namaDokter"` +} diff --git a/internal/domain/bpjs-entities/referral/dto.go b/internal/domain/bpjs-entities/referral/dto.go new file mode 100644 index 00000000..16113d24 --- /dev/null +++ b/internal/domain/bpjs-entities/referral/dto.go @@ -0,0 +1,5 @@ +package referral + +type ReadDetailDto struct { + Number *string `json:"number"` // nomor rujukan +} diff --git a/internal/domain/bpjs-entities/referral/entity.go b/internal/domain/bpjs-entities/referral/entity.go new file mode 100644 index 00000000..8977ee31 --- /dev/null +++ b/internal/domain/bpjs-entities/referral/entity.go @@ -0,0 +1,96 @@ +package referral + +type Response struct { + MetaData MetaData `json:"metaData"` + Response *ReferralDetail `json:"response"` // nullable +} + +type MetaData struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type ReferralDetail struct { + AsalFaskes string `json:"asalFaskes"` + Rujukan RujukanDetail `json:"rujukan"` +} + +type RujukanDetail struct { + Diagnosa Diagnosa `json:"diagnosa"` + Keluhan *string `json:"keluhan"` // nullable + NoKunjungan string `json:"noKunjungan"` + Pelayanan Pelayanan `json:"pelayanan"` + Peserta Peserta `json:"peserta"` + PoliRujukan Poli `json:"poliRujukan"` + ProvPerujuk Provider `json:"provPerujuk"` + TglKunjungan string `json:"tglKunjungan"` +} + +type Diagnosa struct { + Kode string `json:"kode"` + Nama string `json:"nama"` +} + +type Pelayanan struct { + Kode string `json:"kode"` + Nama string `json:"nama"` +} + +type Peserta struct { + Cob Cob `json:"cob"` + HakKelas KeteranganKode `json:"hakKelas"` + Informasi Informasi `json:"informasi"` + JenisPeserta KeteranganKode `json:"jenisPeserta"` + Mr Mr `json:"mr"` + Nama string `json:"nama"` + Nik string `json:"nik"` + NoKartu string `json:"noKartu"` + Pisa string `json:"pisa"` + ProvUmum Provider `json:"provUmum"` + Sex string `json:"sex"` + StatusPeserta KeteranganKode `json:"statusPeserta"` + TglCetakKartu string `json:"tglCetakKartu"` + TglLahir string `json:"tglLahir"` + TglTAT string `json:"tglTAT"` + TglTMT string `json:"tglTMT"` + Umur Umur `json:"umur"` +} + +type Cob struct { + NmAsuransi *string `json:"nmAsuransi"` // nullable + NoAsuransi *string `json:"noAsuransi"` + TglTAT *string `json:"tglTAT"` + TglTMT *string `json:"tglTMT"` +} + +type KeteranganKode struct { + Keterangan string `json:"keterangan"` + Kode string `json:"kode"` +} + +type Informasi struct { + Dinsos *string `json:"dinsos"` + ESep *string `json:"eSEP"` + NoSKTM *string `json:"noSKTM"` + ProlanisPRB *string `json:"prolanisPRB"` +} + +type Mr struct { + NoMR string `json:"noMR"` + NoTelepon string `json:"noTelepon"` +} + +type Provider struct { + Kode string `json:"kdProvider" json:"kode"` // API sometimes uses different keys + NmProvider string `json:"nmProvider" json:"nama"` +} + +type Poli struct { + Kode string `json:"kode"` + Nama string `json:"nama"` +} + +type Umur struct { + UmurSaatPelayanan string `json:"umurSaatPelayanan"` + UmurSekarang string `json:"umurSekarang"` +} diff --git a/internal/interface/main-handler/control-plan/handler.go b/internal/interface/main-handler/control-plan/handler.go new file mode 100644 index 00000000..382af58d --- /dev/null +++ b/internal/interface/main-handler/control-plan/handler.go @@ -0,0 +1,32 @@ +package controlplan + +import ( + "net/http" + + e "simrs-vx/internal/domain/bpjs-entities/control-plan" + u "simrs-vx/internal/use-case/bpjs-use-case/control-plan" + + d "github.com/karincake/dodol" + rw "github.com/karincake/risoles" +) + +func GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + pValue1 := rw.ValidateString(w, "controlType", r.PathValue("controlType")) + if pValue1 == "" { + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "controlType is required"}, nil) + } + pValue2 := rw.ValidateString(w, "polyCode", r.PathValue("polyCode")) + if pValue2 == "" { + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "polyCode is required"}, nil) + } + pValue3 := rw.ValidateString(w, "date", r.PathValue("date")) + if pValue3 == "" { + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "date is required"}, nil) + } + dto.PathValue1 = pValue1 + dto.PathValue2 = pValue2 + dto.PathValue3 = pValue3 + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index d374a8a8..362a5e8a 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -125,9 +125,11 @@ import ( "simrs-vx/internal/interface/main-handler/home" /******************** BPJS ********************/ + controlplan "simrs-vx/internal/interface/main-handler/control-plan" member "simrs-vx/internal/interface/main-handler/member" monitoring "simrs-vx/internal/interface/main-handler/monitoring" reference "simrs-vx/internal/interface/main-handler/reference" + referral "simrs-vx/internal/interface/main-handler/referral" vclaimsep "simrs-vx/internal/interface/main-handler/vclaim-sep" vclaimsephist "simrs-vx/internal/interface/main-handler/vclaim-sep-hist" vclaimsepprint "simrs-vx/internal/interface/main-handler/vclaim-sep-print" @@ -382,9 +384,9 @@ func SetRoutes() http.Handler { /******************** BPJS ********************/ hk.GroupRoutes("/v1/vclaim-sep", r, hk.MapHandlerFunc{ - "POST /": vclaimsep.O.Create, - "GET /{id}": vclaimsep.O.GetDetail, - "DELETE /{id}": vclaimsep.O.Delete, + "POST /": vclaimsep.O.Create, + "GET /{number}": vclaimsep.O.GetDetail, + "DELETE /{number}": vclaimsep.O.Delete, }) hk.GroupRoutes("/v1/vclaim-sep-hist", r, hk.MapHandlerFunc{ @@ -417,6 +419,14 @@ func SetRoutes() http.Handler { "GET /hist/{cardNumber}/{startDate}/{endDate}": monitoring.GetListHist, }) + hk.GroupRoutes("/v1/control-plan", r, hk.MapHandlerFunc{ + "GET /{controlType}/{polyCode}/{date}": controlplan.GetList, + }) + + hk.GroupRoutes("/v1/referral", r, hk.MapHandlerFunc{ + "GET /{number}": referral.GetList, + }) + ///// return cmw.SetCors(handlerlogger.SetLog(r)) } diff --git a/internal/interface/main-handler/member/handler.go b/internal/interface/main-handler/member/handler.go index 80881ee3..3b4c3ba1 100644 --- a/internal/interface/main-handler/member/handler.go +++ b/internal/interface/main-handler/member/handler.go @@ -14,11 +14,11 @@ func GetListByBpjsNumber(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue1 := rw.ValidateString(w, "cardNumber", r.PathValue("cardNumber")) if pValue1 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "cardNumber is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "cardNumber is required"}, nil) } pValue2 := rw.ValidateString(w, "sepDate", r.PathValue("sepDate")) if pValue2 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "sepDate is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "sepDate is required"}, nil) } dto.ReferenceType = e.RTBpjs dto.PathValue1 = pValue1 @@ -31,11 +31,11 @@ func GetListByNik(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue1 := rw.ValidateString(w, "nik", r.PathValue("nik")) if pValue1 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "nik is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "nik is required"}, nil) } pValue2 := rw.ValidateString(w, "sepDate", r.PathValue("sepDate")) if pValue2 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "sepDate is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "sepDate is required"}, nil) } dto.ReferenceType = e.RTNik dto.PathValue1 = pValue1 diff --git a/internal/interface/main-handler/monitoring/handler.go b/internal/interface/main-handler/monitoring/handler.go index cf82a5b9..aa8acdf4 100644 --- a/internal/interface/main-handler/monitoring/handler.go +++ b/internal/interface/main-handler/monitoring/handler.go @@ -14,11 +14,11 @@ func GetListVisit(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue1 := rw.ValidateString(w, "date", r.PathValue("date")) if pValue1 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "date is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "date is required"}, nil) } pValue2 := rw.ValidateString(w, "serviceType", r.PathValue("serviceType")) if pValue2 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "serviceType is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "serviceType is required"}, nil) } dto.ReferenceType = e.RTVisit dto.PathValue1 = pValue1 @@ -31,15 +31,15 @@ func GetListHist(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue1 := rw.ValidateString(w, "cardNumber", r.PathValue("cardNumber")) if pValue1 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "cardNumber is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "cardNumber is required"}, nil) } pValue2 := rw.ValidateString(w, "startDate", r.PathValue("startDate")) if pValue2 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "startDate is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "startDate is required"}, nil) } pValue3 := rw.ValidateString(w, "endDate", r.PathValue("endDate")) if pValue3 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "endDate is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "endDate is required"}, nil) } dto.ReferenceType = e.RTHist dto.PathValue1 = pValue1 diff --git a/internal/interface/main-handler/reference/handler.go b/internal/interface/main-handler/reference/handler.go index fb972af1..b9948eb3 100644 --- a/internal/interface/main-handler/reference/handler.go +++ b/internal/interface/main-handler/reference/handler.go @@ -21,7 +21,7 @@ func GetListCities(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue := rw.ValidateString(w, "provinceCode", r.PathValue("provinceCode")) if pValue == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "provinceCode is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "provinceCode is required"}, nil) } dto.ReferenceType = e.RTCities dto.PathValue1 = pValue @@ -33,7 +33,7 @@ func GetListDistrict(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue := rw.ValidateString(w, "regencyCode", r.PathValue("regencyCode")) if pValue == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "regencyCode is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "regencyCode is required"}, nil) } dto.ReferenceType = e.RTDistrict dto.PathValue1 = pValue @@ -45,7 +45,7 @@ func GetListDiagnose(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue := rw.ValidateString(w, "keyword", r.PathValue("keyword")) if pValue == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "keyword is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "keyword is required"}, nil) } dto.ReferenceType = e.RTDiagnose dto.PathValue1 = pValue @@ -64,7 +64,7 @@ func GetListMedicinePrb(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue := rw.ValidateString(w, "keyword", r.PathValue("keyword")) if pValue == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "keyword is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "keyword is required"}, nil) } dto.ReferenceType = e.RTMedicinePrb dto.PathValue1 = pValue @@ -76,7 +76,7 @@ func GetListUnit(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue := rw.ValidateString(w, "unitCode", r.PathValue("unitCode")) if pValue == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "unitCode is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "unitCode is required"}, nil) } dto.ReferenceType = e.RTUnit dto.PathValue1 = pValue @@ -88,11 +88,11 @@ func GetListHealthcare(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue1 := rw.ValidateString(w, "healthcare", r.PathValue("healthcare")) if pValue1 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "healthcare is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "healthcare is required"}, nil) } pValue2 := rw.ValidateString(w, "healthcareType", r.PathValue("healthcareType")) if pValue2 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "healthcareType is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "healthcareType is required"}, nil) } dto.ReferenceType = e.RTHealthcare dto.PathValue1 = pValue1 @@ -105,15 +105,15 @@ func GetListDoctor(w http.ResponseWriter, r *http.Request) { dto := e.ReadListDto{} pValue1 := rw.ValidateString(w, "serviceType", r.PathValue("serviceType")) if pValue1 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "serviceType is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "serviceType is required"}, nil) } pValue2 := rw.ValidateString(w, "serviceDate", r.PathValue("serviceDate")) if pValue2 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "serviceDate is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "serviceDate is required"}, nil) } pValue3 := rw.ValidateString(w, "specialistCode", r.PathValue("specialistCode")) if pValue3 == "" { - rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": "specialistCode is required"}, nil) + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "specialistCode is required"}, nil) } dto.PathValue1 = pValue1 dto.PathValue2 = pValue2 diff --git a/internal/interface/main-handler/referral/handler.go b/internal/interface/main-handler/referral/handler.go new file mode 100644 index 00000000..94b66a78 --- /dev/null +++ b/internal/interface/main-handler/referral/handler.go @@ -0,0 +1,22 @@ +package referral + +import ( + "net/http" + + e "simrs-vx/internal/domain/bpjs-entities/referral" + u "simrs-vx/internal/use-case/bpjs-use-case/referral" + + d "github.com/karincake/dodol" + rw "github.com/karincake/risoles" +) + +func GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadDetailDto{} + number := rw.ValidateString(w, "number", r.PathValue("number")) + if number == "" { + rw.WriteJSON(w, http.StatusBadRequest, d.IS{"message": "number is required"}, nil) + } + dto.Number = &number + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/vclaim-sep/handler.go b/internal/interface/main-handler/vclaim-sep/handler.go index ed11e257..1529e2d8 100644 --- a/internal/interface/main-handler/vclaim-sep/handler.go +++ b/internal/interface/main-handler/vclaim-sep/handler.go @@ -30,12 +30,12 @@ func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { // } func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { - id := rw.ValidateInt(w, "id", r.PathValue("id")) - if id <= 0 { + number := rw.ValidateString(w, "number", r.PathValue("number")) + if number <= "" { return } dto := e.ReadDetailDto{} - dto.Id = uint(id) + dto.Number = &number res, err := u.ReadDetail(dto) rw.DataResponse(w, res, err) } diff --git a/internal/use-case/bpjs-plugin/control-plan/helper.go b/internal/use-case/bpjs-plugin/control-plan/helper.go new file mode 100644 index 00000000..15af7238 --- /dev/null +++ b/internal/use-case/bpjs-plugin/control-plan/helper.go @@ -0,0 +1,11 @@ +package controlplan + +import ( + "fmt" + e "simrs-vx/internal/domain/bpjs-entities/control-plan" + ibpjs "simrs-vx/internal/infra/bpjs" +) + +func endpointMapper(input *e.ReadListDto) string { + return fmt.Sprintf("%sRencanaKontrol/jadwalDokter?jeniskontrol=%s&kodepoli=%s&tanggalkontrol=%s", ibpjs.O.BaseUrl, input.PathValue1, input.PathValue2, input.PathValue3) +} diff --git a/internal/use-case/bpjs-plugin/control-plan/plugin.go b/internal/use-case/bpjs-plugin/control-plan/plugin.go new file mode 100644 index 00000000..276750ca --- /dev/null +++ b/internal/use-case/bpjs-plugin/control-plan/plugin.go @@ -0,0 +1,37 @@ +package controlplan + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + e "simrs-vx/internal/domain/bpjs-entities/control-plan" + + "gorm.io/gorm" +) + +func ReadList(input *e.ReadListDto, data *e.Response, tx *gorm.DB) error { + endpoint := endpointMapper(input) + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if err := json.Unmarshal(body, &data); err != nil { + return fmt.Errorf("failed to parse response JSON: %w", err) + } + + return nil +} diff --git a/internal/use-case/bpjs-plugin/referral/helper.go b/internal/use-case/bpjs-plugin/referral/helper.go new file mode 100644 index 00000000..169834be --- /dev/null +++ b/internal/use-case/bpjs-plugin/referral/helper.go @@ -0,0 +1,11 @@ +package referral + +import ( + "fmt" + e "simrs-vx/internal/domain/bpjs-entities/referral" + ibpjs "simrs-vx/internal/infra/bpjs" +) + +func endpointMapper(input *e.ReadDetailDto) string { + return fmt.Sprintf("%sRujukan/RS/%s", ibpjs.O.BaseUrl, *input.Number) +} diff --git a/internal/use-case/bpjs-plugin/referral/plugin.go b/internal/use-case/bpjs-plugin/referral/plugin.go new file mode 100644 index 00000000..225181e4 --- /dev/null +++ b/internal/use-case/bpjs-plugin/referral/plugin.go @@ -0,0 +1,37 @@ +package referral + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + e "simrs-vx/internal/domain/bpjs-entities/referral" + + "gorm.io/gorm" +) + +func ReadDetail(input *e.ReadDetailDto, data *e.Response, tx *gorm.DB) error { + endpoint := endpointMapper(input) + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if err := json.Unmarshal(body, &data); err != nil { + return fmt.Errorf("failed to parse response JSON: %w", err) + } + + return nil +} diff --git a/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go b/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go index 70097b74..4588029f 100644 --- a/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go +++ b/internal/use-case/bpjs-plugin/vclaim-sep/plugin.go @@ -66,7 +66,7 @@ func CreateSep(input *e.CreateDto, data *e.VclaimSep, tx *gorm.DB) error { } func ReadDetailSep(input *e.ReadDetailDto, data *e.VclaimSep, tx *gorm.DB) error { - endpoint := fmt.Sprintf("sep/%s", *data.Number) + endpoint := fmt.Sprintf("sep/%s", input.Number) req, err := http.NewRequest("GET", ibpjs.O.BaseUrl+endpoint, nil) if err != nil { return err @@ -96,7 +96,7 @@ func ReadDetailSep(input *e.ReadDetailDto, data *e.VclaimSep, tx *gorm.DB) error func DeleteSep(input *e.DeleteDto, data *e.VclaimSep, tx *gorm.DB) error { payload := e.SepDeleteRequest{} - payload.Request.TSep.NoSep = *data.Number + payload.Request.TSep.NoSep = *input.Number payload.Request.TSep.User = "Coba Ws" jsonPayload, err := json.Marshal(payload) diff --git a/internal/use-case/bpjs-use-case/control-plan/case.go b/internal/use-case/bpjs-use-case/control-plan/case.go new file mode 100644 index 00000000..764ea2ea --- /dev/null +++ b/internal/use-case/bpjs-use-case/control-plan/case.go @@ -0,0 +1,50 @@ +package controlplan + +import ( + e "simrs-vx/internal/domain/bpjs-entities/control-plan" + + dg "github.com/karincake/apem/db-gorm-pg" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +const source = "control-plan" + +func ReadList(input e.ReadListDto) (*e.Response, error) { + var data e.Response + 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 + } + + 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 &data, nil +} diff --git a/internal/use-case/bpjs-use-case/control-plan/middleware-runner.go b/internal/use-case/bpjs-use-case/control-plan/middleware-runner.go new file mode 100644 index 00000000..4c3db3d8 --- /dev/null +++ b/internal/use-case/bpjs-use-case/control-plan/middleware-runner.go @@ -0,0 +1,42 @@ +package controlplan + +import ( + e "simrs-vx/internal/domain/bpjs-entities/control-plan" + 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, + } +} + +func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.Response) 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/bpjs-use-case/control-plan/middleware.go b/internal/use-case/bpjs-use-case/control-plan/middleware.go new file mode 100644 index 00000000..3478bb19 --- /dev/null +++ b/internal/use-case/bpjs-use-case/control-plan/middleware.go @@ -0,0 +1,12 @@ +package controlplan + +import ( + pcp "simrs-vx/internal/use-case/bpjs-plugin/control-plan" +) + +func init() { + readListPreMw = append(readListPreMw, + readListMw{Name: "readList-control-plan", Func: pcp.ReadList}, + ) + +} diff --git a/internal/use-case/bpjs-use-case/control-plan/tycovar.go b/internal/use-case/bpjs-use-case/control-plan/tycovar.go new file mode 100644 index 00000000..ea2b39a9 --- /dev/null +++ b/internal/use-case/bpjs-use-case/control-plan/tycovar.go @@ -0,0 +1,18 @@ +/* +member is peserta +*/ +package controlplan + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/bpjs-entities/control-plan" +) + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.Response, tx *gorm.DB) error +} + +var readListPreMw []readListMw // .. +var readListPostMw []readListMw // .. diff --git a/internal/use-case/bpjs-use-case/referral/case.go b/internal/use-case/bpjs-use-case/referral/case.go new file mode 100644 index 00000000..8818dc58 --- /dev/null +++ b/internal/use-case/bpjs-use-case/referral/case.go @@ -0,0 +1,50 @@ +package referral + +import ( + e "simrs-vx/internal/domain/bpjs-entities/referral" + + dg "github.com/karincake/apem/db-gorm-pg" + + pl "simrs-vx/pkg/logger" + pu "simrs-vx/pkg/use-case-helper" + + "gorm.io/gorm" +) + +const source = "referral" + +func ReadDetail(input e.ReadDetailDto) (*e.Response, error) { + var data e.Response + 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 + } + + 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 &data, nil +} diff --git a/internal/use-case/bpjs-use-case/referral/middleware-runner.go b/internal/use-case/bpjs-use-case/referral/middleware-runner.go new file mode 100644 index 00000000..10c26241 --- /dev/null +++ b/internal/use-case/bpjs-use-case/referral/middleware-runner.go @@ -0,0 +1,42 @@ +package referral + +import ( + e "simrs-vx/internal/domain/bpjs-entities/referral" + 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, + } +} + +func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.Response) 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/bpjs-use-case/referral/middleware.go b/internal/use-case/bpjs-use-case/referral/middleware.go new file mode 100644 index 00000000..0296ae5a --- /dev/null +++ b/internal/use-case/bpjs-use-case/referral/middleware.go @@ -0,0 +1,12 @@ +package referral + +import ( + pr "simrs-vx/internal/use-case/bpjs-plugin/referral" +) + +func init() { + readDetailPreMw = append(readDetailPreMw, + readDetailMw{Name: "read-detail-referral", Func: pr.ReadDetail}, + ) + +} diff --git a/internal/use-case/bpjs-use-case/referral/tycovar.go b/internal/use-case/bpjs-use-case/referral/tycovar.go new file mode 100644 index 00000000..b1950977 --- /dev/null +++ b/internal/use-case/bpjs-use-case/referral/tycovar.go @@ -0,0 +1,18 @@ +/* +member is peserta +*/ +package referral + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/bpjs-entities/referral" +) + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.Response, tx *gorm.DB) error +} + +var readDetailPreMw []readDetailMw +var readDetailPostMw []readDetailMw From c3fae2ec52a48c68c83ac00945692ade377f0ba7 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 1 Dec 2025 12:47:57 +0700 Subject: [PATCH 28/29] fix encounter id on read detail --- internal/use-case/main-use-case/generate-file/helper.go | 2 +- internal/use-case/main-use-case/resume/case.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/use-case/main-use-case/generate-file/helper.go b/internal/use-case/main-use-case/generate-file/helper.go index 3a6c3480..6edd34d8 100644 --- a/internal/use-case/main-use-case/generate-file/helper.go +++ b/internal/use-case/main-use-case/generate-file/helper.go @@ -284,7 +284,7 @@ func generateResume(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDt func generateResumeTemplateData(resume er.Resume, event pl.Event, tx *gorm.DB) (*ResumePDF, error) { // get encounter includes := "Patient,Patient.Person,Patient.Person.BirthRegency,Patient.Person.Contacts" - encounter, err := ue.ReadDetailData(ee.ReadDetailDto{Id: uint16(*resume.Encounter_Id), Includes: includes}, &event) + encounter, err := ue.ReadDetailData(ee.ReadDetailDto{Id: *resume.Encounter_Id, Includes: includes}, &event) if err != nil { return nil, err } diff --git a/internal/use-case/main-use-case/resume/case.go b/internal/use-case/main-use-case/resume/case.go index eab70280..8f771794 100644 --- a/internal/use-case/main-use-case/resume/case.go +++ b/internal/use-case/main-use-case/resume/case.go @@ -332,7 +332,7 @@ func UpdateStatusCode(input e.UpdateDto) (*d.Data, error) { return err } - enc, err := ue.ReadDetailData(ee.ReadDetailDto{Id: uint16(*data.Encounter_Id)}, &event, tx) + enc, err := ue.ReadDetailData(ee.ReadDetailDto{Id: *data.Encounter_Id}, &event, tx) if err != nil { return err } From ece6a0b7a5d6e7c613c3357b1f574470f3eb7275 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 1 Dec 2025 15:14:20 +0700 Subject: [PATCH 29/29] add screening --- .../migrations/20251201081333.sql | 13 ++ cmd/main-migration/migrations/atlas.sum | 115 +++++++++--------- .../domain/main-entities/screening/dto.go | 86 +++++++++++++ .../domain/main-entities/screening/entity.go | 17 +++ .../domain/references/clinical/clinical.go | 4 + internal/interface/migration/main-entities.go | 2 + 6 files changed, 180 insertions(+), 57 deletions(-) create mode 100644 cmd/main-migration/migrations/20251201081333.sql create mode 100644 internal/domain/main-entities/screening/dto.go create mode 100644 internal/domain/main-entities/screening/entity.go diff --git a/cmd/main-migration/migrations/20251201081333.sql b/cmd/main-migration/migrations/20251201081333.sql new file mode 100644 index 00000000..876b32b7 --- /dev/null +++ b/cmd/main-migration/migrations/20251201081333.sql @@ -0,0 +1,13 @@ +-- Create "Screening" table +CREATE TABLE "public"."Screening" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Encounter_Id" bigint NULL, + "Employee_Id" bigint NULL, + "Type" text NULL, + "Value" text NULL, + PRIMARY KEY ("Id"), + CONSTRAINT "fk_Screening_Employee" FOREIGN KEY ("Employee_Id") REFERENCES "public"."Employee" ("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 449ff1d4..5753c626 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:Y8m8OmsumTaAfXfYYXHlZS0+OqbAqFfM3a55nmibOUE= +h1:6lWgIJo0vUBd7Bo4B3YMvyDpJsOTVzVfqRQ03ad9r1s= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -75,59 +75,60 @@ h1:Y8m8OmsumTaAfXfYYXHlZS0+OqbAqFfM3a55nmibOUE= 20251106040137.sql h1:ppcqkVoT0o9jZcjI/TN7LuaPxXhJQhnIXEJtloP/46o= 20251106041333.sql h1:2JkxyelQ/EeB+boL5bfpnzefw32ttEGKvKchtQjWmAU= 20251106042006.sql h1:ruppYa1kAJQUU3ufQBbKGMcXrGbGJJiRPclT+dNc/YQ= -20251106050412.sql h1:MiEMJ1HCFYnalKuq3Z38xJeogfBAMqsTv2sG4EF8dDw= -20251106063418.sql h1:y3veDJPjKekOWLCZek/LgQwXPRhZtOppTfUXiqoL95s= -20251106071906.sql h1:/TUZA3XpMY23qEJXdkTwlzrNMvSSl6JJniPcgAttBaw= -20251106073157.sql h1:78txeibJ602DMD7huD618ZSMt6phSRzDNPTlo0PGyrc= -20251106074218.sql h1:8Xz7WywrtUnSxOHhlal53gG9rE7r86LFUt5zBFe/mIs= -20251106081846.sql h1:jp91Bf5bxGXMiUB1VIuN6y768vb2iWwow44WfCE5J5k= -20251106082844.sql h1:RHYzRO4G1fSWwf+xc/3QezZ/Iil67cZPIgNpNz3TNhQ= -20251106090021.sql h1:dFDk6mq+zjbYWmfWIrHf9DiKvvoXHjrr0++zssMTWP8= -20251106144745.sql h1:aHcr23iBFqCHer5D/SsPMXBCLjGqUYvWYfRU8jSJgIw= -20251107012049.sql h1:hu/7NHhnAkT4xK0RNtqmMDdH1Bo5EZbl7itDRjiCT+g= -20251107064812.sql h1:sfCXDQYnMf0ddrQ9oYljWJLLSt9NJjJV6o8VS3p7aZE= -20251107064937.sql h1:DlYGJ9LZFwZyR7jBP5zaGB128aIc4HAixBKPYCz9EkY= -20251107071420.sql h1:ynCdZAd2utLl+FhtWZwtahNXgIVOvuk3s/rOq7lfXA4= -20251107074318.sql h1:WE9cPhibWtZ0dbu1VEGirTeY6ijFYGMNhHdBtM32kOc= -20251107075050.sql h1:8tvneruqdynDOaJK1+0z4CH7YXZStZpGdqwIeOMLik4= -20251107080604.sql h1:8c4jd4Tql7tcdhbI9NS0tgvN+ADu9FnCf8wMUbmW7A0= -20251107081830.sql h1:SAAe3lmsm9vGXuSEsDdl7ad0EAxP5CMmFRDEgp9M7yY= -20251107091033.sql h1:JLdX/u7GUdBfjrPrMSNAqc8HtSoj0YA9iW9Vc6FJZdw= -20251107091209.sql h1:CzhYtwAwT+GHrbqcagnJE+v3mbl/rObf1IJaLCKlzrs= -20251107091541.sql h1:+3ZyWJTftDY2JeWThXuIxGWpUBnyMPyOyY4jBjdWYJI= -20251110012217.sql h1:f4Z8TuGc+XMSJ+Ekn4/PeHRE2FlHWkc5gKPJB0hAX7c= -20251110012306.sql h1:ENPyI6Kjdk6qKJQb0yJ6MCTDPAmO1WD/uhKuCSl+jYo= -20251110052049.sql h1:OrQ0acnyoQLKnTitZfnBcVr5jDslF59OFLaqT7SpdVs= -20251110062042.sql h1:9KwldQt0NpVPR86L0T4hlkfHAGau+7CiZYgu5rF+yhg= -20251110063202.sql h1:A117DuZmZ6U0jWHA3DISnr+yvBjKIr1ObrUr047YezQ= -20251110063633.sql h1:qTiC0F19JnhUIXF4LGJQ21jEV6kKGyhTr1x2kimFqPQ= -20251110085551.sql h1:HZcJM0RSC6HBaUSjKBE8MgDG8Vn9f3LmwA/OnT9Cp7I= -20251110091516.sql h1:W3AQhQLgirEWuCObbLl+Prdrbq6k6EEY1xcoWsmbog4= -20251110091948.sql h1:3tsITMrZr/T+L4wqUMz8sHS229jCJl4T0Nu3dMccxH8= -20251110092729.sql h1:uU+k88RH/e0Ns4/SmJl03RVYPscBAPuiLfxR6CJqaf0= -20251110093522.sql h1:O7upSj8VNjzvroL4IU59bfxKATOkAVGBArcUbVNq9aM= -20251110100258.sql h1://JSArUMNI3/gAtYDx2VN94C198SFW0ANjgs+p6eCRM= -20251110100545.sql h1:ENPOqeJYRpMI4e8VCKwaQgaql8se6pIidAhG2cjskBg= -20251110155448.sql h1:VW9KE0jxWy4flZ28sdXmhY6JWd6eKAX/cVL8KWRVii4= -20251111072601.sql h1:99WWzGjcDDFNC2cHRfPu4MBLWNrgPMY5HAYUbmIcVRA= -20251111073546.sql h1:iIGwFNfUgStdczw/PXTH3SD84xAyxrbZICofc3M8TMk= -20251111074148.sql h1:mzkezSKwCV2bTZ/0BHiTCX5qAy4uHpwar1xzwAH15Ko= -20251111074652.sql h1:lYh4/3BHV24pgPC0pP4RUKb+XtL/6AsZGPItRpnzBiQ= -20251111082257.sql h1:+cIZ1lf8HYGSL6t6U88plLKk8nOO1YVdV7h3YOM84ds= -20251111111017.sql h1:xzlhR2xLfOj4ddYlesS+bpEeDrxiAf5ILNOspsbZjBU= -20251113101344.sql h1:vSzThY3p6XYTj0dJ2uFVkHmlytyrFAnGE58CJLx364I= -20251113120533.sql h1:lfjgdqRGo/EohrVw8sWlFbDjh3ASTsfQ6pr3qjafqvc= -20251114062746.sql h1:6DLYjfJ60PkAABZTXvOwSE+xrU25oyoX7gpYlBnA9cw= -20251117005942.sql h1:0XoJKca8IOaB9QKFVF/qPY7jKcIgh6m/LODQuE06SAs= -20251117075427.sql h1:LhY2urosfoSu1/vkHmgsNP4JA4DuWBs9gL59yMqcF8M= -20251118074929.sql h1:3RWBD6BziQVw6WSfthgoVuhTELHER9NrIuZm4hY/F1A= -20251119063438.sql h1:EVTG3ZrHjCmM95YPASZTRPiwHrG6e33A2vO4hLSAaBQ= -20251119065730.sql h1:s98wnZOCW6NbnwDS3H53XIQ60u6B9bDwBLNvy5+Rn64= -20251119072302.sql h1:ZL+VHekLYqgNtOFKlj02WHrAk11dtTxQkmyTKE8IOzY= -20251119072450.sql h1:SiJy2vpSvoPFw4J1SW7HjGSJzrqR62NsjRYAeabV+kc= -20251120005512.sql h1:hJUaWXYJ3HiSquBTw8OKhymjA4O43ehAMGfiOY8W87Q= -20251120074415.sql h1:dNmcqZHqfZwi/wtYvO/pSFgImN2scXL0p/7g0+HLp0s= -20251121033803.sql h1:onlddYGo+tQdGaEdJPqdsx3sncGnwEvqMSCksF6vjjI= -20251124071457.sql h1:ikQLIXFikUbkUd55uJBmEvqLGEC9t0db+Om4TQsWP7M= -20251125125303.sql h1:OLKbNPO36AHtIDursW9AeBvf60sahQKC1iOjHmpx0MA= -20251126064057.sql h1:6al3PTWbw/WGiBcrRrVWppOMLtG+eRaH/qSLbnmh1kQ= +20251106050412.sql h1:1002KYtHd8AwrQTMewbs/PPHDylHDghigE/3S7PVdMA= +20251106063418.sql h1:jPW/gBnbFl4RO39lQ0ZMDtYA6xbhyD6CgQupT50HmaY= +20251106071906.sql h1:leYGKxR3EQn794aOehf0sd/ZPmOnvBMZPy5/anGmRB4= +20251106073157.sql h1:KASMzjjjk5UB7Zj8lCRtM1utc4ZnDjlnpZbtTe3vONE= +20251106074218.sql h1:Z5q5deOvLaZDPhiVTN9st3/s56RepBa2YOyrMXBdj4A= +20251106081846.sql h1:P+VsWwhGt60adDIZuE/Aa38JVp/yX1rnsdpXpxASodw= +20251106082844.sql h1:Dmi5A8i9frQZvdXYPwc7f8CisZtBH8liSXq1rI6z1iM= +20251106090021.sql h1:4JwdKgO8T46YhyWVJUxpRIwudBDlG8QN1brSOYmgQ20= +20251106144745.sql h1:nqnQCzGrVJaq8ilOEOGXeRUL1dolj+OPWKuP8A92FRA= +20251107012049.sql h1:Pff4UqltGS3clSlGr0qq8CQM56L29wyxY0FC/N/YAhU= +20251107064812.sql h1:GB9a0ZfMYTIoGNmKUG+XcYUsTnRMFfT4/dAD71uCPc4= +20251107064937.sql h1:IC5pw1Ifj30hiE6dr5NMHXaSHoQI+vRd40N5ABgBHRI= +20251107071420.sql h1:9NO3iyLEXEtWa2kSRjM/8LyzuVIk6pdFL2SuheWjB08= +20251107074318.sql h1:7fHbSRrdjOmHh/xwnjCLwoiB5cW5zeH+uxLV0vZbkIA= +20251107075050.sql h1:np+3uTOnU9QNtK7Knaw8eRMhkyB9AwrtSNHphOBxbHY= +20251107080604.sql h1:cXDBLPJDVWLTG6yEJqkJsOQ7p7VYxLM2SY+mwO8qSHo= +20251107081830.sql h1:/S7OQZo4ZnK80t28g/JyiOTZtmWG/dP5Wg2zXNMQ/iE= +20251107091033.sql h1:/cbkF1nO/IjNSIfDJJx456KJtQ9rWFXOBFAkR/M2xiE= +20251107091209.sql h1:jrLQOUeV8ji2fg0pnEcs1bw4ANUxzTSMXC/rrHLIY+M= +20251107091541.sql h1:6UqbhQQRmzA2+eKu5lIvkwOkk+lH70QLZC8Pjpjcq68= +20251110012217.sql h1:C9HpX0iyHzKjyNv/5DSAn2MCHj6MX4p5UQ/NrY7QD0w= +20251110012306.sql h1:J54yb27d30LBbYp9n1P66gFVRlxPguKu0kxmWIBBG8g= +20251110052049.sql h1:232T2x8xTczJl9nk4jxJpZXhoOGYthhxjJ7nK8Jd8vg= +20251110062042.sql h1:WnfVUXrzYoj8qdkkjO9/JQQ8agGd4GfSHQdMjo7LDAg= +20251110063202.sql h1:hSzGfwVMWa6q3vwIQZUkxKgBNCzHjB+6GKy54zfV+oQ= +20251110063633.sql h1:/VpofIAqNS1CnazEnpW/+evbzn9Kew3xDW48r57M+Xg= +20251110085551.sql h1:bFZwSmfvVbTUr/enWB82WqjG88gpqcZ6s45btUvO0uo= +20251110091516.sql h1:KkJMwPQuaZQhiqnKrNQrgP12gw9rV8T3P2o3mtGTcvY= +20251110091948.sql h1:I4odAYrJdvNf1jPw6ppDC0XdI7v6vKBACg/ABwUgA7I= +20251110092729.sql h1:l1out8soEmVP6dNjaIOtGYo6QDcoJZRI8X1sjZ5ZGmo= +20251110093522.sql h1:nsz8jCxGjEdr/bz9g+4ozfZzIP803xONjVmucad1GMc= +20251110100258.sql h1:IBqt1VZj5WjQ+l9aAFGHOCCBtzb03KlLLihFLut7itg= +20251110100545.sql h1:6/LV7751iyKxE2xI6vO1zly+aHUwxXD/IBwLcVpKxqM= +20251110155448.sql h1:kFPobJB+cpflsXBAWUwy3lohuWvrb/VRlXnhJWl7i3Y= +20251111072601.sql h1:ch8F+yVhsSM5xY+TwMLY3PxdLa4Wuhtj76oyw79R7Js= +20251111073546.sql h1:cCv0NPscADAOBahRVqtDWFs6G2t7n+4a+RwlF8vk/c4= +20251111074148.sql h1:70TsV83u1gQ5TktI13K7NQiyCCa35Td2aR6CNtKUa4U= +20251111074652.sql h1:ddfQ/sRKMezPM75xBFTGytUQX5AwZ3znrJVpg73gKPA= +20251111082257.sql h1:ZsdLY1ROouos0l3oS0lkeSiuKLEUGbVvBhpcM2AVhkw= +20251111111017.sql h1:qrJ93dNtQwcuAvpsP/lAK/H63C4cinXrsVaPmWsTqkU= +20251113101344.sql h1:xaOZvAUP1fFfnO+syEFOzJUIg5lTfBe5AWHPbBWuCLA= +20251113120533.sql h1:f3/U1Ve2yF2zSMhkt+xtwF8wUYfUKYwgbNeGfE37EW4= +20251114062746.sql h1:FInLaEFQByESEwFJKuKnuUSTKmcDpi3ZXaxkKwz2+D8= +20251117005942.sql h1:wD3BWrUSmo1HlW16V3lkaBkJvbAZ0fNk77te7J9NhOc= +20251117075427.sql h1:TqU9VKZa3I8YNXUGQWY3WVBYN+1FvyyaKy0hB1jgAho= +20251118074929.sql h1:p1KsWqCuR1JXA/jVO5BmOhCcaQ8clT7t0YRszAhPFbg= +20251119063438.sql h1:NVGM0X/LHD37EaPl8SNzkNiZDJ7AB1QR+LLwLh6WRdg= +20251119065730.sql h1:U5lzk1WvMB0bw3kwckou7jkEt4bwdYItwHr2Vxqe7w4= +20251119072302.sql h1:qCuI2WMEMF/XNbjV+RXPjBnuCKLu1Fia+mR9HiLWBIs= +20251119072450.sql h1:Xg+bTwqGyKPNFEQhJylvpz1wifdfmDJvcAq6vmNf0Ng= +20251120005512.sql h1:Ek6qpacAI/qVuTYxKno+uJyzn7s5z9pf3t7VA8gTzm4= +20251120074415.sql h1:NNUeJVA03EeBHJhHqPXEZoDv/PnC6yK1/cRhmukyaJo= +20251121033803.sql h1:/vfvFX/3pzSCIHnSbMUT9EMBDykOpVkvyfeTEle9Vas= +20251124071457.sql h1:qg2dhCL9YwD13xnfJ175lW/p6MGfzFKaBqd908FByRc= +20251125125303.sql h1:4JSFv1Pmhbe9tqpLXgO63OwYnGsalStgUXKVWPyc1YE= +20251126064057.sql h1:vAdhz5Nn/gGJy0UKZAEldeXv8HpHtJU/t8ygDVIbTsU= +20251201081333.sql h1:1946fD7iMnf18FjYzVNoHu6suWSDMfjaF2R7jx5gjo0= diff --git a/internal/domain/main-entities/screening/dto.go b/internal/domain/main-entities/screening/dto.go new file mode 100644 index 00000000..5dfed2c7 --- /dev/null +++ b/internal/domain/main-entities/screening/dto.go @@ -0,0 +1,86 @@ +package screening + +import ( + // std + + // internal - lib + pa "simrs-vx/internal/lib/auth" + + // internal - domain - base-entities + ecore "simrs-vx/internal/domain/base-entities/core" + + // internal - domain - main-entities + eem "simrs-vx/internal/domain/main-entities/employee" + + erc "simrs-vx/internal/domain/references/clinical" +) + +type CreateDto struct { + Encounter_Id *uint `json:"encounter_id"` + Employee_Id *uint `json:"-"` + Type erc.ScreeningFormTypeCode `json:"type"` + Value *string `json:"value"` + + pa.AuthInfo +} + +type ReadListDto struct { + FilterDto + Includes string `json:"includes"` + Pagination ecore.Pagination +} + +type FilterDto struct { + Encounter_Id *uint `json:"encounter-id"` + Employee_Id *uint `json:"employee-id"` + Type erc.ScreeningFormTypeCode `json:"type"` + Value *string `json:"value"` +} + +type ReadDetailDto struct { + Id uint16 `json:"id"` +} + +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"` + Employee_Id *uint `json:"employee_id"` + Employee *eem.Employee `json:"employee,omitempty"` + Type erc.ScreeningFormTypeCode `json:"type"` + Value *string `json:"value"` +} + +func (d Screening) ToResponse() ResponseDto { + resp := ResponseDto{ + Encounter_Id: d.Encounter_Id, + Employee_Id: d.Employee_Id, + Employee: d.Employee, + Type: d.Type, + Value: d.Value, + } + resp.Main = d.Main + return resp +} + +func ToResponseList(data []Screening) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/screening/entity.go b/internal/domain/main-entities/screening/entity.go new file mode 100644 index 00000000..ebdb735b --- /dev/null +++ b/internal/domain/main-entities/screening/entity.go @@ -0,0 +1,17 @@ +package screening + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + eem "simrs-vx/internal/domain/main-entities/employee" + + erc "simrs-vx/internal/domain/references/clinical" +) + +type Screening struct { + ecore.Main // adjust this according to the needs + Encounter_Id *uint `json:"encounter_id"` + Employee_Id *uint `json:"employee_id"` + Employee *eem.Employee `json:"employee,omitempty" gorm:"foreignKey:Employee_Id;references:Id"` + Type erc.ScreeningFormTypeCode `json:"type"` + Value *string `json:"value"` +} diff --git a/internal/domain/references/clinical/clinical.go b/internal/domain/references/clinical/clinical.go index 30a4fe8a..4755eea1 100644 --- a/internal/domain/references/clinical/clinical.go +++ b/internal/domain/references/clinical/clinical.go @@ -22,6 +22,7 @@ type ( LearnMethodCode string LangClassCode string TranslatorSrcCode string + ScreeningFormTypeCode string ) const ( @@ -187,6 +188,9 @@ const ( TSCTeam TranslatorSrcCode = "team" // Tim Penerjemah TSCFamily TranslatorSrcCode = "family" // Keluarga + + SFTCA ScreeningFormTypeCode = "form-a" // Formu A + SFTCB ScreeningFormTypeCode = "form-b" // Formu B ) type Soapi struct { diff --git a/internal/interface/migration/main-entities.go b/internal/interface/migration/main-entities.go index 0b4a62fd..a59e9b2e 100644 --- a/internal/interface/migration/main-entities.go +++ b/internal/interface/migration/main-entities.go @@ -85,6 +85,7 @@ import ( resume "simrs-vx/internal/domain/main-entities/resume" room "simrs-vx/internal/domain/main-entities/room" sbar "simrs-vx/internal/domain/main-entities/sbar" + screening "simrs-vx/internal/domain/main-entities/screening" soapi "simrs-vx/internal/domain/main-entities/soapi" specialist "simrs-vx/internal/domain/main-entities/specialist" specialistintern "simrs-vx/internal/domain/main-entities/specialist-intern" @@ -217,5 +218,6 @@ func getMainEntities() []any { &vclaimsepcontrolletter.VclaimSepControlLetter{}, &resume.Resume{}, &vclaimreference.VclaimReference{}, + &screening.Screening{}, } }