From da88637fe9ce23e618b3c3f19ecab82a4b518b00 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 24 Nov 2025 12:21:17 +0700 Subject: [PATCH 01/13] 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 5322e986e4c7b4cbd19f03c686033915feaf1f86 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 24 Nov 2025 17:13:40 +0700 Subject: [PATCH 02/13] 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 63f9d5512bd4760e2687518a3cffbcd21f53bd0a Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 25 Nov 2025 14:31:03 +0700 Subject: [PATCH 03/13] 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 04/13] 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 04add70ac5018780f859b1fd5356a6336e88abf2 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Wed, 26 Nov 2025 17:23:48 +0700 Subject: [PATCH 05/13] 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 abfbc568b03efcda6c77768a0924996d382346f4 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Thu, 27 Nov 2025 13:30:45 +0700 Subject: [PATCH 06/13] 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 07/13] 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 08/13] 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 09/13] 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 10/13] 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{}, } } From 8306e1217e2db071f433d52fa7ce1090d13a9104 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Mon, 1 Dec 2025 17:47:59 +0700 Subject: [PATCH 11/13] feat/action-report: done --- .../migrations/20251201104439.sql | 19 ++ cmd/main-migration/migrations/atlas.sum | 5 +- .../domain/main-entities/action-report/dto.go | 89 ++++++ .../main-entities/action-report/entity.go | 45 +++ .../domain/references/clinical/clinical.go | 66 ++++ .../references/organization/organization.go | 5 + .../main-handler/action-report/handler.go | 80 +++++ .../interface/main-handler/main-handler.go | 2 + internal/interface/migration/main-entities.go | 2 + .../main-use-case/action-report/case.go | 295 ++++++++++++++++++ .../main-use-case/action-report/helper.go | 28 ++ .../main-use-case/action-report/lib.go | 140 +++++++++ .../action-report/middleware-runner.go | 103 ++++++ .../main-use-case/action-report/middleware.go | 9 + .../main-use-case/action-report/tycovar.go | 44 +++ 15 files changed, 930 insertions(+), 2 deletions(-) create mode 100644 cmd/main-migration/migrations/20251201104439.sql create mode 100644 internal/domain/main-entities/action-report/dto.go create mode 100644 internal/domain/main-entities/action-report/entity.go create mode 100644 internal/interface/main-handler/action-report/handler.go create mode 100644 internal/use-case/main-use-case/action-report/case.go create mode 100644 internal/use-case/main-use-case/action-report/helper.go create mode 100644 internal/use-case/main-use-case/action-report/lib.go create mode 100644 internal/use-case/main-use-case/action-report/middleware-runner.go create mode 100644 internal/use-case/main-use-case/action-report/middleware.go create mode 100644 internal/use-case/main-use-case/action-report/tycovar.go diff --git a/cmd/main-migration/migrations/20251201104439.sql b/cmd/main-migration/migrations/20251201104439.sql new file mode 100644 index 00000000..50f60b6b --- /dev/null +++ b/cmd/main-migration/migrations/20251201104439.sql @@ -0,0 +1,19 @@ +-- Create "ActionReport" table +CREATE TABLE "public"."ActionReport" ( + "Id" bigserial NOT NULL, + "CreatedAt" timestamptz NULL, + "UpdatedAt" timestamptz NULL, + "DeletedAt" timestamptz NULL, + "Encounter_Id" bigint NULL, + "Date" character varying(20) NOT NULL, + "Doctor_Code" character varying(10) NULL, + "Operator_Employe_Id" bigint NULL, + "Assistant_Employe_Id" bigint NULL, + "Instrumentor_Employe_Id" bigint NULL, + "Diagnose" character varying(1024) NULL, + "Procedures" character varying(10240) NULL, + "Nurse_Code" character varying(10) NULL, + "Value" text NULL, + PRIMARY KEY ("Id"), + CONSTRAINT "fk_ActionReport_Encounter" FOREIGN KEY ("Encounter_Id") REFERENCES "public"."Encounter" ("Id") ON UPDATE NO ACTION ON DELETE NO ACTION +); diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index 5753c626..c02dc2b8 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:6lWgIJo0vUBd7Bo4B3YMvyDpJsOTVzVfqRQ03ad9r1s= +h1:oIkzjGilKCqqfLGlvMc2I2WwJ8zQgdMeTWy25k1uaLI= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -131,4 +131,5 @@ h1:6lWgIJo0vUBd7Bo4B3YMvyDpJsOTVzVfqRQ03ad9r1s= 20251124071457.sql h1:qg2dhCL9YwD13xnfJ175lW/p6MGfzFKaBqd908FByRc= 20251125125303.sql h1:4JSFv1Pmhbe9tqpLXgO63OwYnGsalStgUXKVWPyc1YE= 20251126064057.sql h1:vAdhz5Nn/gGJy0UKZAEldeXv8HpHtJU/t8ygDVIbTsU= -20251201081333.sql h1:1946fD7iMnf18FjYzVNoHu6suWSDMfjaF2R7jx5gjo0= +20251201081333.sql h1:PoC8ADRdwDuohDTB74yW/DaB42igdYa4B6humbrEJBk= +20251201104439.sql h1:EIBP67qeQWEYMdMOM/gouXzkAM4R3vSkmuw4Fe8usOs= diff --git a/internal/domain/main-entities/action-report/dto.go b/internal/domain/main-entities/action-report/dto.go new file mode 100644 index 00000000..1edb4a73 --- /dev/null +++ b/internal/domain/main-entities/action-report/dto.go @@ -0,0 +1,89 @@ +package actionreport + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + ee "simrs-vx/internal/domain/main-entities/encounter" + + pa "simrs-vx/internal/lib/auth" +) + +type CreateDto struct { + Encounter_Id uint64 `json:"encounter_id"` + Date string `json:"date"` + Doctor_Code string `json:"doctor_code"` + Operator_Employe_Id *uint `json:"operator_employe_id"` + Assistant_Employe_Id *uint `json:"assistant_employe_id"` + Instrumentor_Employe_Id *uint `json:"instrumentor_employe_id"` + Diagnose *string `json:"diagnose"` + Nurse_Code *string `json:"nurse_code"` + 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"` +} + +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 uint64 `json:"encounter_id"` + Encounter *ee.Encounter `json:"encounter,omitempty"` + Date string `json:"date"` + Doctor_Code string `json:"doctor_code"` + Operator_Employe_Id *uint `json:"operator_employe_id"` + Assistant_Employe_Id *uint `json:"assistant_employe_id"` + Instrumentor_Employe_Id *uint `json:"instrumentor_employe_id"` + Diagnose *string `json:"diagnose"` + Nurse_Code *string `json:"nurse_code"` + Value *string `json:"value"` +} + +func (d ActionReport) ToResponse() ResponseDto { + resp := ResponseDto{ + Encounter_Id: d.Encounter_Id, + Encounter: d.Encounter, + Date: d.Date, + Doctor_Code: d.Doctor_Code, + Operator_Employe_Id: d.Operator_Employe_Id, + Assistant_Employe_Id: d.Assistant_Employe_Id, + Instrumentor_Employe_Id: d.Instrumentor_Employe_Id, + Nurse_Code: d.Nurse_Code, + Value: &d.Value, + } + resp.Main = d.Main + return resp +} + +func ToResponseList(data []ActionReport) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/action-report/entity.go b/internal/domain/main-entities/action-report/entity.go new file mode 100644 index 00000000..3437136e --- /dev/null +++ b/internal/domain/main-entities/action-report/entity.go @@ -0,0 +1,45 @@ +package actionreport + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" + ee "simrs-vx/internal/domain/main-entities/encounter" +) + +type ActionReport struct { + ecore.Main // adjust this according to the needs + Encounter_Id uint64 `json:"encounter_id" gorm:"foreignKey"` + Encounter *ee.Encounter `json:"doctor,omitempty" gorm:"foreignKey:Encounter_Id;references:Id"` + Date string `json:"date" gorm:"not null;size:20"` + Doctor_Code string `json:"doctor_code" gorm:"size:10"` + Operator_Employe_Id *uint `json:"operator_employe_id"` + Assistant_Employe_Id *uint `json:"assistant_employe_id"` + Instrumentor_Employe_Id *uint `json:"instrumentor_employe_id"` + Diagnose *string `json:"diagnose" gorm:"size:1024"` + Procedures *string `json:"procedures" gorm:"size:10240"` + Nurse_Code *string `json:"nurse_code" gorm:"size:10"` + + Value string `json:"value"` + // SurgerySize_Code *string `json:"surgerySize_code" gorm:"size:10"` + // Billing_Code *string `json:"billing_code" gorm:"size:10"` + // SurgerySystem_Code *string `json:"surgerySystem_code" gorm:"size:10"` + // StartAt *string `json:"startAt" gorm:"size:20"` + // EndAt *string `json:"endAt" gorm:"size:20"` + // AnesthesiaStartAt *string `json:"anesthesiaStartAt" gorm:"size:20"` + // AnesthesiaEndAt *string `json:"anesthesiaEndAt" gorm:"size:20"` + // SurgeryType_Code *string `json:"surgeryType_code" gorm:"size:10"` + // SurgeryStage_Code *string `json:"surgeryStage_code" gorm:"size:10"` + // BornMortality_Code *string `json:"bornMortality_code" gorm:"size:10"` + // BornLocation_Code *string `json:"bornLocation_code" gorm:"size:10"` + // Weight *string `json:"weight" gorm:"size:10"` + // BornNotes *string `json:"bornNotes" gorm:"size:1024"` + // Description *string `json:"notes" gorm:"size:1024"` + // BleedingAmount *uint16 `json:"bleedingAmount" gorm:"size:10"` + // BloodInType_Code *string `json:"bloodInType_code" gorm:"size:10"` + // BloodInAmount *uint16 `json:"bloodInAmount" gorm:"size:10"` + // Brand *string `json:"brand" gorm:"size:100"` + // ImplantName *string `json:"implantName" gorm:"size:100"` + // ImplantRegisterNumber *string `json:"implantRegisterNumber" gorm:"size:100"` + // ImplantCompanionName *string `json:"implantCompanionName" gorm:"size:100"` + // SpecimentDest_Code *string `json:"specimentDest" gorm:"size:100"` + // TissueInfo *string `json:"tissueInfo" gorm:"size:100"` +} diff --git a/internal/domain/references/clinical/clinical.go b/internal/domain/references/clinical/clinical.go index 4755eea1..7a3a2ec1 100644 --- a/internal/domain/references/clinical/clinical.go +++ b/internal/domain/references/clinical/clinical.go @@ -23,6 +23,13 @@ type ( LangClassCode string TranslatorSrcCode string ScreeningFormTypeCode string + SurgerySizeCode string + SurgerySystemCode string + SurgeryTypeCode string + SurgeryStageCode string + BornMortalityCode string + BornLocationCode string + SpecimentDestCode string ) const ( @@ -191,6 +198,38 @@ const ( SFTCA ScreeningFormTypeCode = "form-a" // Formu A SFTCB ScreeningFormTypeCode = "form-b" // Formu B + + SSCSmall SurgerySizeCode = "" + SSCMedium SurgerySizeCode = "" + SSCLarge SurgerySizeCode = "" + SSCSpecial SurgerySizeCode = "" + + SSyCCito SurgerySystemCode = "" + SSyCUrgent SurgerySystemCode = "" + SSyCEfective SurgerySystemCode = "" + SSyCSpecial SurgerySystemCode = "" + + STCClean SurgeryTypeCode = "" + STCCleanCtm SurgeryTypeCode = "" + STCUncleanCtm SurgeryTypeCode = "" + STCUnclean SurgeryTypeCode = "" + + SStCFirst SurgeryStageCode = "" + SStCRepeat SurgeryStageCode = "" + + BMCAlive BornMortalityCode = "" + BMCDead BornMortalityCode = "" + + BLCExtMiw BornLocationCode = "" + BLCExtDoc BornLocationCode = "" + BLCTradMiw BornLocationCode = "" + BLCLocalMed BornLocationCode = "" + BLCExtParamedic BornLocationCode = "" + + SDCAp SpecimentDestCode = "" + SDCMicro SpecimentDestCode = "" + SDCLab SpecimentDestCode = "" + SDCNone SpecimentDestCode = "" ) type Soapi struct { @@ -315,3 +354,30 @@ type HeadToToe struct { Neuron string `json:"neuron,omitempty"` BodyOthers string `json:"body-others,omitempty"` } + +type RecordAction struct { + Procedures []string `json:"procedures"` + SurgerySize_Code *string `json:"surgerySize_code"` + Billing_Code *string `json:"billing_code"` + SurgerySystem_Code *string `json:"surgerySystem_code"` + StartAt *string `json:"startAt"` + EndAt *string `json:"endAt"` + AnesthesiaStartAt *string `json:"anesthesiaStartAt"` + AnesthesiaEndAt *string `json:"anesthesiaEndAt"` + SurgeryType_Code *string `json:"surgeryType_code"` + SurgeryStage_Code *string `json:"surgeryStage_code"` + BornMortality_Code *string `json:"bornMortality_code"` + BornLocation_Code *string `json:"bornLocation_code"` + Weight *string `json:"weight"` + BornNotes *string `json:"bornNotes" gorm:"size:1024"` + Description *string `json:"notes" gorm:"size:1024"` + BleedingAmount *uint16 `json:"bleedingAmount"` + BloodInType_Code *string `json:"bloodInType_code"` + BloodInAmount *uint16 `json:"bloodInAmount"` + Brand *string `json:"brand" gorm:"size:100"` + ImplantName *string `json:"implantName" gorm:"size:100"` + ImplantRegisterNumber *string `json:"implantRegisterNumber" gorm:"size:100"` + ImplantCompanionName *string `json:"implantCompanionName" gorm:"size:100"` + SpecimentDest_Code *string `json:"specimentDest" gorm:"size:100"` + TissueInfo []string `json:"tissueInfo" gorm:"size:100"` +} diff --git a/internal/domain/references/organization/organization.go b/internal/domain/references/organization/organization.go index e12531cd..5b2b4343 100644 --- a/internal/domain/references/organization/organization.go +++ b/internal/domain/references/organization/organization.go @@ -8,6 +8,7 @@ type ( InfraGroupCode string UnitTypeCode string DoctorFeeTypeCode string + ActionBillingCode string ) const ( @@ -62,4 +63,8 @@ const ( DFTCInp DoctorFeeTypeCode = "inpatient" // Rawat Inap DFTCEme DoctorFeeTypeCode = "emergency" // Darurat DFTCReh DoctorFeeTypeCode = "medic-rehab" // Rehab Medik + + ABCGeneral ActionBillingCode = "general" + ABCRegional ActionBillingCode = "regional" + ABCLocal ActionBillingCode = "local" ) diff --git a/internal/interface/main-handler/action-report/handler.go b/internal/interface/main-handler/action-report/handler.go new file mode 100644 index 00000000..0fdd1851 --- /dev/null +++ b/internal/interface/main-handler/action-report/handler.go @@ -0,0 +1,80 @@ +package soapi + +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/action-report" + u "simrs-vx/internal/use-case/main-use-case/action-report" + + pa "simrs-vx/internal/lib/auth" + + d "github.com/karincake/dodol" +) + +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) +} + +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 646bf053..dd025d0e 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -4,6 +4,7 @@ import ( "net/http" /******************** main / transaction ********************/ + actionreport "simrs-vx/internal/interface/main-handler/action-report" adime "simrs-vx/internal/interface/main-handler/adime" admemployeehist "simrs-vx/internal/interface/main-handler/adm-employee-hist" ambulancetransportrequest "simrs-vx/internal/interface/main-handler/ambulance-transport-req" @@ -171,6 +172,7 @@ func SetRoutes() http.Handler { 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/action-report", auth.GuardMW, actionreport.O) hc.RegCrud(r, "/v1/material-order-item", materialorderitem.O) hk.GroupRoutes("/v1/encounter", r, auth.GuardMW, hk.MapHandlerFunc{ diff --git a/internal/interface/migration/main-entities.go b/internal/interface/migration/main-entities.go index a59e9b2e..bed0c18c 100644 --- a/internal/interface/migration/main-entities.go +++ b/internal/interface/migration/main-entities.go @@ -1,6 +1,7 @@ package migration import ( + actionreport "simrs-vx/internal/domain/main-entities/action-report" adime "simrs-vx/internal/domain/main-entities/adime" admemployeehist "simrs-vx/internal/domain/main-entities/adm-employee-hist" ambulancetransportreq "simrs-vx/internal/domain/main-entities/ambulance-transport-req" @@ -219,5 +220,6 @@ func getMainEntities() []any { &resume.Resume{}, &vclaimreference.VclaimReference{}, &screening.Screening{}, + &actionreport.ActionReport{}, } } diff --git a/internal/use-case/main-use-case/action-report/case.go b/internal/use-case/main-use-case/action-report/case.go new file mode 100644 index 00000000..2ae0f268 --- /dev/null +++ b/internal/use-case/main-use-case/action-report/case.go @@ -0,0 +1,295 @@ +package actionreport + +import ( + "errors" + "strconv" + + e "simrs-vx/internal/domain/main-entities/action-report" + + 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 = "action-report" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.ActionReport{} + + 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 !input.AuthInfo.IsDoctor() && !input.AuthInfo.IsNurse() { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "auth-forbidden", + Detail: "user position is not allowed", + Raw: errors.New("authentication failed"), + } + return pl.SetLogError(&event, input) + } + + if input.AuthInfo.Employee_Id != nil { + v := uint(*input.AuthInfo.Employee_Id) + input.Employee_Id = &v + } else { + input.Employee_Id = nil + } + + 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.ActionReport + var dataList []e.ActionReport + 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.ActionReport + 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.ActionReport + 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.ActionReport + 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/action-report/helper.go b/internal/use-case/main-use-case/action-report/helper.go new file mode 100644 index 00000000..efe4532e --- /dev/null +++ b/internal/use-case/main-use-case/action-report/helper.go @@ -0,0 +1,28 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package actionreport + +import ( + e "simrs-vx/internal/domain/main-entities/action-report" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.ActionReport) { + 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.Doctor_Code = inputSrc.Doctor_Code + data.Operator_Employe_Id = inputSrc.Operator_Employe_Id + data.Assistant_Employe_Id = inputSrc.Assistant_Employe_Id + data.Instrumentor_Employe_Id = inputSrc.Instrumentor_Employe_Id + data.Nurse_Code = inputSrc.Nurse_Code + data.Value = inputSrc.Value +} diff --git a/internal/use-case/main-use-case/action-report/lib.go b/internal/use-case/main-use-case/action-report/lib.go new file mode 100644 index 00000000..5a272cce --- /dev/null +++ b/internal/use-case/main-use-case/action-report/lib.go @@ -0,0 +1,140 @@ +package actionreport + +import ( + e "simrs-vx/internal/domain/main-entities/action-report" + + 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.ActionReport, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.ActionReport{} + 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.ActionReport, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.ActionReport{} + 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.ActionReport{}). + 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.ActionReport, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.ActionReport{} + + 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.ActionReport, 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.ActionReport, 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/action-report/middleware-runner.go b/internal/use-case/main-use-case/action-report/middleware-runner.go new file mode 100644 index 00000000..e2544169 --- /dev/null +++ b/internal/use-case/main-use-case/action-report/middleware-runner.go @@ -0,0 +1,103 @@ +package actionreport + +import ( + e "simrs-vx/internal/domain/main-entities/action-report" + 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.ActionReport) 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.ActionReport) 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.ActionReport) 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.ActionReport) 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.ActionReport) 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/action-report/middleware.go b/internal/use-case/main-use-case/action-report/middleware.go new file mode 100644 index 00000000..50254de2 --- /dev/null +++ b/internal/use-case/main-use-case/action-report/middleware.go @@ -0,0 +1,9 @@ +package actionreport + +// 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/action-report/tycovar.go b/internal/use-case/main-use-case/action-report/tycovar.go new file mode 100644 index 00000000..9eebfabd --- /dev/null +++ b/internal/use-case/main-use-case/action-report/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 actionreport + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/action-report" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.ActionReport, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.ActionReport, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.ActionReport, 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 50db75e014b64cdda8a3df5176d61697ef2b2eb3 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 1 Dec 2025 15:14:20 +0700 Subject: [PATCH 12/13] 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..b4c4fe05 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:Y8m8OmsumTaAfXfYYXHlZS0+OqbAqFfM3a55nmibOUE= +h1:fkwkWe2PSYaTNxdTEmwe747+bhXV+kOcly/gC7dq/Q8= 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:PoC8ADRdwDuohDTB74yW/DaB42igdYa4B6humbrEJBk= 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{}, } } From 4f6bc8da314809802b07305f851c8e2ebd59f12f Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Mon, 1 Dec 2025 18:02:20 +0700 Subject: [PATCH 13/13] feat/action-report: migrated + finishing --- cmd/main-migration/migrations/atlas.sum | 7 ++----- internal/domain/main-entities/mcu-order-item/entity.go | 4 +++- internal/domain/references/clinical/clinical.go | 6 ------ 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index bdc2efaf..8f71fcc5 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,8 +1,4 @@ -<<<<<<< HEAD -h1:oIkzjGilKCqqfLGlvMc2I2WwJ8zQgdMeTWy25k1uaLI= -======= -h1:fkwkWe2PSYaTNxdTEmwe747+bhXV+kOcly/gC7dq/Q8= ->>>>>>> migration +h1:NUsCmkoiBso9yZsv4HWrn3+woqMcFRFtgXKln5rY4pQ= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -136,3 +132,4 @@ h1:fkwkWe2PSYaTNxdTEmwe747+bhXV+kOcly/gC7dq/Q8= 20251125125303.sql h1:4JSFv1Pmhbe9tqpLXgO63OwYnGsalStgUXKVWPyc1YE= 20251126064057.sql h1:vAdhz5Nn/gGJy0UKZAEldeXv8HpHtJU/t8ygDVIbTsU= 20251201081333.sql h1:PoC8ADRdwDuohDTB74yW/DaB42igdYa4B6humbrEJBk= +20251201104439.sql h1:tpqdrOf9d2aGwZshqm62nG6SXnfVaO/g6A7z0efPS14= diff --git a/internal/domain/main-entities/mcu-order-item/entity.go b/internal/domain/main-entities/mcu-order-item/entity.go index 48a57bac..7d4d757d 100644 --- a/internal/domain/main-entities/mcu-order-item/entity.go +++ b/internal/domain/main-entities/mcu-order-item/entity.go @@ -4,12 +4,14 @@ import ( emo "simrs-vx/internal/domain/main-entities/mcu-order" emoib "simrs-vx/internal/domain/main-entities/mcu-order-item/base" + emosi "simrs-vx/internal/domain/main-entities/mcu-order-sub-item/base" erc "simrs-vx/internal/domain/references/common" ) type McuOrderItem struct { emoib.McuOrderItem - McuOrder *emo.McuOrder `json:"mcuOrder,omitempty" gorm:"foreignKey:McuOrder_Id;references:Id"` + McuOrder *emo.McuOrder `json:"mcuOrder,omitempty" gorm:"foreignKey:McuOrder_Id;references:Id"` + Items []*emosi.McuOrderSubItem `json:"items" gorm:"foreignKey:McuOrderItem_Id;references:Id"` } func (d McuOrderItem) IsCompleted() bool { diff --git a/internal/domain/references/clinical/clinical.go b/internal/domain/references/clinical/clinical.go index f6c1b572..7a3a2ec1 100644 --- a/internal/domain/references/clinical/clinical.go +++ b/internal/domain/references/clinical/clinical.go @@ -23,7 +23,6 @@ type ( LangClassCode string TranslatorSrcCode string ScreeningFormTypeCode string -<<<<<<< HEAD SurgerySizeCode string SurgerySystemCode string SurgeryTypeCode string @@ -31,8 +30,6 @@ type ( BornMortalityCode string BornLocationCode string SpecimentDestCode string -======= ->>>>>>> migration ) const ( @@ -201,7 +198,6 @@ const ( SFTCA ScreeningFormTypeCode = "form-a" // Formu A SFTCB ScreeningFormTypeCode = "form-b" // Formu B -<<<<<<< HEAD SSCSmall SurgerySizeCode = "" SSCMedium SurgerySizeCode = "" @@ -234,8 +230,6 @@ const ( SDCMicro SpecimentDestCode = "" SDCLab SpecimentDestCode = "" SDCNone SpecimentDestCode = "" -======= ->>>>>>> migration ) type Soapi struct {