From 9cf28432491b7089b91a96584b20ccd71eae132e Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 17 Nov 2025 14:33:49 +0700 Subject: [PATCH 1/9] add general consent template --- assets/docs/general-consent.html | 273 +++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 assets/docs/general-consent.html diff --git a/assets/docs/general-consent.html b/assets/docs/general-consent.html new file mode 100644 index 00000000..e311cb54 --- /dev/null +++ b/assets/docs/general-consent.html @@ -0,0 +1,273 @@ + + + + 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 +
+ +
+ +
+ FORMULIR PEMBERIAN INFORMASI DAN PERSETUJUAN UMUM +
+
+ (GENERAL CONSENT) +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1. + Hak dan Kewajiban sebagai pasien : + Dengan menandatangani dokumen ini saya mengakui bahwa pada proses + pendaftaran untuk mendapatkan perawatan di RSUD Dr. Saiful Anwar telah + mendapatkan informasi tentang hak dan kewajiban saya sebagai pasien + (melalui leaflet/banner dan atau petugas). Saya berhak mendapatkan + pelayanan kesehatan sesuai standar, mendapatkan informasi yang cukup + tentang keadaan kesehatan, rencana tindakan, manfaat, risiko, + alternatif tindakan, serta biaya yang akan timbul. Saya berkewajiban + memberikan informasi kesehatan yang jujur dan lengkap kepada tenaga + kesehatan, mematuhi aturan rumah sakit, serta memenuhi kewajiban + pembayaran sesuai ketentuan yang berlaku. +
2. + Persetujuan Pelayanan : + Saya menyetujui dan memberikan persetujuan untuk dirawat di RSUD Dr. + Saiful Anwar dan dengan ini saya meminta dan memberikan kuasa kepada + RSUD Dr. Saiful Anwar, dokter dan perawat serta tenaga kesehatan + lainnya untuk memberikan asuhan keperawatan, pemeriksaan fisik yang + dilakukan oleh dokter dan perawat dan melakukan prosedur diagnostik + radiologi dan/atau terapi dan tata laksana sesuai pertimbangan dokter + yang diperlukan atau disarankan pada perawatan saya. Hal ini mencakup + seluruh pemeriksaan dan prosedur diagnostik rutin termasuk X-ray, + pemberian dan/atau tindakan medis serta penyuntikan (intramuskular, + intravena dan prosedur invasif lainnya), produk farmasi dan + obat-obatan, pemasangan alat kesehatan (kecuali yang membutuhkan + persetujuan khusus/tertulis) dan pengambilan darah untuk pemeriksaan + laboratorium atau pemeriksaan patologi. +
3. + Akses Informasi Kesehatan : + Saya memberi kuasa kepada setiap dan seluruh orang yang merawat saya + untuk memeriksa dan/atau memberitahukan informasi kesehatan saya + kepada pemberi kesehatan lain yang turut merawat saya selama di rumah + sakit ini, sesuai kebutuhan pelayanan. +
4. + Rahasia Kedokteran : + Saya setuju RSUD Dr. Saiful Anwar Malang wajib menjamin kerahasiaan + informasi medis saya baik untuk kepentingan perawatan dan pengobatan, + pendidikan maupun penelitian, sesuai ketentuan yang berlaku. +
5. + Membuka Rahasia Kedokteran : + Saya setuju untuk membuka rahasia kedokteran terkait dengan kondisi + kesehatan, asuhan dan pengobatan yang saya terima kepada: +
a) Dokter dan tenaga kesehatan lain yang turut merawat/memberikan + asuhan kepada saya; +
b) Perusahaan asuransi kesehatan atau perusahaan lainnya atau pihak + lain yang menjamin pembiayaan saya; +
c) Anggota keluarga saya : + .......................................... +
Saya memahami bahwa pembukaan rahasia ini hanya sejauh yang + diperlukan untuk tujuan perawatan, pembiayaan atau administrasi yang + terkait. +
6. + Privasi : + Saya memberi kuasa kepada RSUD Dr. Saiful Anwar Malang untuk menjaga + privasi dan kerahasiaan penyakit saya selama dalam perawatan, serta + membatasi akses terhadap informasi yang tidak berkepentingan. +
7. + Barang Pribadi : + Saya setuju untuk tidak membawa barang-barang berharga yang tidak + diperlukan (seperti perhiasan, elektronik, dll) selama dalam perawatan + di RSUD Dr. Saiful Anwar. Jika saya tetap membawa dan terjadi + kehilangan, kerusakan atau pencurian, maka RSUD Dr. Saiful Anwar tidak + bertanggung jawab atas hal tersebut, kecuali bila ada perjanjian + tertulis yang menyatakan lain. +
8. + Pengajuan Keluhan : + Saya menyatakan bahwa saya telah menerima informasi tentang adanya + tata cara mengajukan dan mengatasi keluhan terkait pelayanan medik + yang diberikan terhadap diri saya. Saya setuju untuk mengikuti tata + cara pengajuan keluhan sesuai prosedur yang ada di rumah sakit. +
9. + Kewajiban Pembayaran : + Saya menyatakan setuju, baik sebagai wali ataupun sebagai pasien, + bahwa sesuai pertimbangan pelayanan yang diberikan kepada pasien, maka + saya wajib untuk membayar total biaya pelayanan sesuai acuan biaya dan + ketentuan RSUD Dr. Saiful Anwar Malang dengan jaminan atau pribadi. + Apabila asuransi kesehatan swasta atau program pemerintah menanggung + pembiayaan saya, saya memberi wewenang kepada rumah sakit untuk + memberi tagihan dari semua pelayanan dan tindakan medis yang + diberikan. Tanggungan Asuransi saya mungkin menyatakan bahwa sebagian + pembayaran tetap menjadi tanggung jawab pribadi saya atau tidak + ditanggung oleh asuransi, maka rumah sakit berwenang memberi tagihan + untuk biaya yang tidak ditanggung oleh asuransi dan saya bertanggung + jawab untuk membayarnya. Apabila saya tidak memberikan persetujuan, + atau dikemudian hari mencabut persetujuan saya untuk melepaskan + rahasia kedokteran saya kepada perusahaan asuransi yang saya tentukan, + maka saya pribadi bertanggung jawab untuk membayar semua pelayanan dan + tindakan medis dari RSUD Dr. Saiful Anwar Malang. +
10. + Rumah Sakit Pendidikan : + Saya mengetahui bahwa RSUD Dr. Saiful Anwar merupakan rumah sakit + pendidikan yang menjadi tempat praktik klinik bagi mahasiswa + kedokteran dan profesi-profesi kesehatan lainnya, karena itu mereka + mungkin berpartisipasi dan atau terlibat dalam perawatan saya dan saya + menyetujui bahwa mereka berpartisipasi dalam perawatan saya sepanjang + di bawah supervisi dokter penanggung jawab pasien (DPJP). +
11. + Selama Dalam Perawatan : + Selama dalam perawatan saya dan keluarga saya akan mematuhi ketentuan + untuk tidak mengambil, menyimpan, mengedarkan gambar/video dokumen dan + aktivitas pelayanan selama di RS tanpa seizin rumah sakit. +
12. + Penegasan Kepercayaan : + Melalui dokumen ini, saya menegaskan kembali bahwa saya mempercayakan + kepada semua tenaga kesehatan rumah sakit untuk memberikan perawatan, + diagnostik dan terapi kepada saya sebagai pasien rawat inap atau rawat + jalan atau Instalasi Gawat Darurat (IGD), termasuk semua pemeriksaan + penunjang yang dibutuhkan untuk pengobatan dan tindakan yang + diperlukan. +
+ +

+ +
+ Saya menyetujui setiap pernyataan dalam formulir ini dan menandatangani + tanpa paksaan. +
+ + + + + +
+ Malang, ............................................................ +
+ + + + + + + + +
+ Pasien/Keluarga


.......................................
Nama + terang +
+ Pemberi Informasi


.......................................
Nama + terang +
+ Saksi I


.......................................
Nama + terang +
+ Saksi II


.......................................
Nama + terang +
+ + From 05f54e4e341b285fcf3c1416ea73f4fdeb1c7c84 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 17 Nov 2025 15:20:24 +0700 Subject: [PATCH 2/9] refactor (upload-file) --- .../main-entities/encounter-document/dto.go | 8 +- .../encounter-document/entity.go | 4 +- internal/domain/main-entities/patient/dto.go | 4 +- .../domain/references/encounter/encounter.go | 47 ++++++++ internal/domain/references/upload/upload.go | 53 --------- .../interface/main-handler/main-handler.go | 4 +- .../{upload => upload-file}/handler.go | 13 +-- .../validate-request.go | 6 +- .../main-use-case/encounter-document/case.go | 4 +- .../main-use-case/upload-file/case.go | 76 +++++++++++++ .../{upload => upload-file}/helper.go | 21 ++-- .../main-use-case/upload-file/tycovar.go} | 59 +++------- .../use-case/main-use-case/upload/case.go | 90 --------------- .../main-use-case/upload/middleware-runner.go | 104 ------------------ .../main-use-case/upload/middleware.go | 9 -- .../use-case/main-use-case/upload/tycovar.go | 44 -------- pkg/upload-helper/upload-helper.go | 2 +- 17 files changed, 169 insertions(+), 379 deletions(-) delete mode 100644 internal/domain/references/upload/upload.go rename internal/interface/main-handler/{upload => upload-file}/handler.go (84%) rename internal/interface/main-handler/{upload => upload-file}/validate-request.go (64%) create mode 100644 internal/use-case/main-use-case/upload-file/case.go rename internal/use-case/main-use-case/{upload => upload-file}/helper.go (89%) rename internal/{domain/main-entities/upload/dto.go => use-case/main-use-case/upload-file/tycovar.go} (53%) delete mode 100644 internal/use-case/main-use-case/upload/case.go delete mode 100644 internal/use-case/main-use-case/upload/middleware-runner.go delete mode 100644 internal/use-case/main-use-case/upload/middleware.go delete mode 100644 internal/use-case/main-use-case/upload/tycovar.go diff --git a/internal/domain/main-entities/encounter-document/dto.go b/internal/domain/main-entities/encounter-document/dto.go index 4bc76994..6381413b 100644 --- a/internal/domain/main-entities/encounter-document/dto.go +++ b/internal/domain/main-entities/encounter-document/dto.go @@ -1,7 +1,7 @@ package encounter_document import ( - eru "simrs-vx/internal/domain/references/upload" + ere "simrs-vx/internal/domain/references/encounter" // internal - domain - base-entities ecore "simrs-vx/internal/domain/base-entities/core" @@ -10,7 +10,7 @@ import ( type CreateDto struct { Encounter_Id *uint `json:"encounter_id"` - Type_Code eru.DocTypeCode `json:"type_code"` + Type_Code ere.DocTypeCode `json:"type_code"` Name string `json:"name"` FilePath string `json:"filePath"` Filename string `json:"-"` @@ -25,7 +25,7 @@ type ReadListDto struct { type FilterDto struct { Encounter_Id *uint `json:"encounter-id"` - Type_Code eru.DocTypeCode `json:"type-code"` + Type_Code ere.DocTypeCode `json:"type-code"` Upload_Employee_Id *string `json:"encounter-document-employee-id"` } @@ -52,7 +52,7 @@ type MetaDto struct { type ResponseDto struct { ecore.Main Encounter_Id *uint `json:"encounter_id"` - Type_Code eru.DocTypeCode `json:"type_code"` + Type_Code ere.DocTypeCode `json:"type_code"` Name string `json:"name"` FilePath *string `json:"filePath"` FileName *string `json:"fileName"` diff --git a/internal/domain/main-entities/encounter-document/entity.go b/internal/domain/main-entities/encounter-document/entity.go index b0b040fc..61152eb4 100644 --- a/internal/domain/main-entities/encounter-document/entity.go +++ b/internal/domain/main-entities/encounter-document/entity.go @@ -3,13 +3,13 @@ package encounter_document import ( ecore "simrs-vx/internal/domain/base-entities/core" ee "simrs-vx/internal/domain/main-entities/employee" - eru "simrs-vx/internal/domain/references/upload" + ere "simrs-vx/internal/domain/references/encounter" ) type EncounterDocument struct { ecore.Main Encounter_Id *uint `json:"encounter_id"` - Type_Code eru.DocTypeCode `json:"type_code"` + Type_Code ere.DocTypeCode `json:"type_code"` Name string `json:"name"` FilePath *string `json:"filePath"` FileName *string `json:"fileName"` diff --git a/internal/domain/main-entities/patient/dto.go b/internal/domain/main-entities/patient/dto.go index ec270abf..ae387895 100644 --- a/internal/domain/main-entities/patient/dto.go +++ b/internal/domain/main-entities/patient/dto.go @@ -12,7 +12,7 @@ import ( epr "simrs-vx/internal/domain/main-entities/person-relative" erc "simrs-vx/internal/domain/references/common" - eru "simrs-vx/internal/domain/references/upload" + ere "simrs-vx/internal/domain/references/encounter" pa "simrs-vx/internal/lib/auth" ) @@ -68,7 +68,7 @@ type SearchDto struct { type UploadDto struct { Id uint `json:"-"` - Code eru.DocTypeCode `json:"-"` + Code ere.DocTypeCode `json:"-"` File multipart.File `json:"-"` FileHeader *multipart.FileHeader `json:"-"` Filename string `json:"-"` diff --git a/internal/domain/references/encounter/encounter.go b/internal/domain/references/encounter/encounter.go index dc3dd09e..532e1625 100644 --- a/internal/domain/references/encounter/encounter.go +++ b/internal/domain/references/encounter/encounter.go @@ -1,5 +1,7 @@ package encounter +import "fmt" + type ( EncounterClassCode string QueueStatusCode string @@ -18,6 +20,8 @@ type ( SEPRefTypeCode string VisitModeCode string PolySwitchCode string + DocTypeCode string + EntityTypeCode string ) const ( @@ -104,6 +108,22 @@ const ( PSCConsulPoly PolySwitchCode = "consul-poly" // Konsultasi Poliklinik Lain PSCConsulExecutive PolySwitchCode = "consul-executive" // Konsultasi Antar Dokter Eksekutif + + DTCPRN DocTypeCode = "person-resident-number" // Person Resident Number + DTCPDL DocTypeCode = "person-driver-license" // Person Driver License + DTCPP DocTypeCode = "person-passport" // Person Passport + DTCPFC DocTypeCode = "person-family-card" // Person Family Card + DTCMIR DocTypeCode = "mcu-item-result" // Mcu Item Result + DTCEnPatient DocTypeCode = "encounter-patient" + DTCEnSupport DocTypeCode = "encounter-support" + DTCEnOther DocTypeCode = "encounter-other" + DTCSEP DocTypeCode = "vclaim-sep" // SEP + DTCSIPP DocTypeCode = "vclaim-sipp" // SIPP + DTCGC DocTypeCode = "general-consent" + + ETCPerson EntityTypeCode = "person" + ETCEncounter EntityTypeCode = "encounter" + ETCMCU EntityTypeCode = "mcu" ) func (ec EncounterClassCode) Code() string { @@ -118,3 +138,30 @@ func (ec EncounterClassCode) Code() string { return "UNKNOWN" } } + +var validUploadCodesByEntity = map[EntityTypeCode][]DocTypeCode{ + ETCPerson: { + DTCPRN, DTCPDL, DTCPP, DTCPFC, + }, + ETCEncounter: { + DTCSEP, DTCSIPP, DTCEnPatient, DTCEnSupport, DTCGC, DTCEnOther, + }, + ETCMCU: { + DTCMIR, + }, +} + +func IsValidUploadCode(entity EntityTypeCode, code DocTypeCode) (bool, string) { + allowedCodes, ok := validUploadCodesByEntity[entity] + if !ok { + return false, fmt.Sprintf("unknown entityType_code: %s", entity) + } + + for _, c := range allowedCodes { + if c == code { + return true, "" + } + } + + return false, fmt.Sprintf("invalid doctype_code '%s' for entityType_code '%s'", code, entity) +} diff --git a/internal/domain/references/upload/upload.go b/internal/domain/references/upload/upload.go deleted file mode 100644 index 0817e23c..00000000 --- a/internal/domain/references/upload/upload.go +++ /dev/null @@ -1,53 +0,0 @@ -package upload - -import "fmt" - -type ( - DocTypeCode string - EntityTypeCode string -) - -const ( - DTCPRN DocTypeCode = "person-resident-number" // Person Resident Number - DTCPDL DocTypeCode = "person-driver-license" // Person Driver License - DTCPP DocTypeCode = "person-passport" // Person Passport - DTCPFC DocTypeCode = "person-family-card" // Person Family Card - DTCMIR DocTypeCode = "mcu-item-result" // Mcu Item Result - DTCEnPatient DocTypeCode = "encounter-patient" - DTCEnSupport DocTypeCode = "encounter-support" - DTCEnOther DocTypeCode = "encounter-other" - DTCSEP DocTypeCode = "vclaim-sep" // SEP - DTCSIPP DocTypeCode = "vclaim-sipp" // SIPP - DTCGC DocTypeCode = "general-consent" - - ETCPerson EntityTypeCode = "person" - ETCEncounter EntityTypeCode = "encounter" - ETCMCU EntityTypeCode = "mcu" -) - -var validUploadCodesByEntity = map[EntityTypeCode][]DocTypeCode{ - ETCPerson: { - DTCPRN, DTCPDL, DTCPP, DTCPFC, - }, - ETCEncounter: { - DTCSEP, DTCSIPP, DTCEnPatient, DTCEnSupport, DTCEnOther, - }, - ETCMCU: { - DTCMIR, - }, -} - -func IsValidUploadCode(entity EntityTypeCode, code DocTypeCode) (bool, string) { - allowedCodes, ok := validUploadCodesByEntity[entity] - if !ok { - return false, fmt.Sprintf("unknown entityType_code: %s", entity) - } - - for _, c := range allowedCodes { - if c == code { - return true, "" - } - } - - return false, fmt.Sprintf("invalid doctype_code '%s' for entityType_code '%s'", code, entity) -} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index aa6f76fb..0027fbc7 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -34,7 +34,7 @@ import ( responsibledoctorhist "simrs-vx/internal/interface/main-handler/responsible-doctor-hist" sbar "simrs-vx/internal/interface/main-handler/sbar" soapi "simrs-vx/internal/interface/main-handler/soapi" - upload "simrs-vx/internal/interface/main-handler/upload" + uploadfile "simrs-vx/internal/interface/main-handler/upload-file" /******************** actor ********************/ authpartner "simrs-vx/internal/interface/main-handler/auth-partner" @@ -274,7 +274,7 @@ func SetRoutes() http.Handler { "PATCH /{id}/reject": therapyprotocol.O.Reject, }) hc.RegCrud(r, "/v1/chemo-protocol", chemoprotocol.O) - hc.RegCrud(r, "/v1/upload", upload.O) + hc.RegCrud(r, "/v1/upload-file", uploadfile.O) hc.RegCrud(r, "/v1/encounter-document", encounterdocument.O) /******************** actor ********************/ diff --git a/internal/interface/main-handler/upload/handler.go b/internal/interface/main-handler/upload-file/handler.go similarity index 84% rename from internal/interface/main-handler/upload/handler.go rename to internal/interface/main-handler/upload-file/handler.go index 8a09c9ae..ac09fb65 100644 --- a/internal/interface/main-handler/upload/handler.go +++ b/internal/interface/main-handler/upload-file/handler.go @@ -1,4 +1,4 @@ -package upload +package uploadfile import ( "net/http" @@ -7,10 +7,9 @@ import ( d "github.com/karincake/dodol" rw "github.com/karincake/risoles" - eru "simrs-vx/internal/domain/references/upload" + ere "simrs-vx/internal/domain/references/encounter" - e "simrs-vx/internal/domain/main-entities/upload" - u "simrs-vx/internal/use-case/main-use-case/upload" + u "simrs-vx/internal/use-case/main-use-case/upload-file" ) type myBase struct{} @@ -49,10 +48,10 @@ func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { } } - dto := e.CreateDto{ - EntityType_Code: eru.EntityTypeCode(r.FormValue("entityType_code")), + dto := u.CreateDto{ + EntityType_Code: ere.EntityTypeCode(r.FormValue("entityType_code")), Ref_Id: refID, - Type_Code: eru.DocTypeCode(r.FormValue("type_code")), + Type_Code: ere.DocTypeCode(r.FormValue("type_code")), Name: r.FormValue("name"), Upload_Employee_Id: employeeId, File: file, diff --git a/internal/interface/main-handler/upload/validate-request.go b/internal/interface/main-handler/upload-file/validate-request.go similarity index 64% rename from internal/interface/main-handler/upload/validate-request.go rename to internal/interface/main-handler/upload-file/validate-request.go index 38ae492c..72161e81 100644 --- a/internal/interface/main-handler/upload/validate-request.go +++ b/internal/interface/main-handler/upload-file/validate-request.go @@ -1,10 +1,10 @@ -package upload +package uploadfile import ( - e "simrs-vx/internal/domain/main-entities/upload" + u "simrs-vx/internal/use-case/main-use-case/upload-file" ) -func validateCreate(dto e.CreateDto) string { +func validateCreate(dto u.CreateDto) string { switch { case dto.EntityType_Code == "": diff --git a/internal/use-case/main-use-case/encounter-document/case.go b/internal/use-case/main-use-case/encounter-document/case.go index 88592db7..979c6611 100644 --- a/internal/use-case/main-use-case/encounter-document/case.go +++ b/internal/use-case/main-use-case/encounter-document/case.go @@ -10,7 +10,7 @@ import ( d "github.com/karincake/dodol" "gorm.io/gorm" - eru "simrs-vx/internal/domain/references/upload" + ere "simrs-vx/internal/domain/references/encounter" e "simrs-vx/internal/domain/main-entities/encounter-document" ) @@ -244,7 +244,7 @@ func Delete(input e.DeleteDto) (*d.Data, error) { return err } - if err := removeUploadedFile(string(eru.ETCEncounter), *data.FilePath, &event); err != nil { + if err := removeUploadedFile(string(ere.ETCEncounter), *data.FilePath, &event); err != nil { return err } diff --git a/internal/use-case/main-use-case/upload-file/case.go b/internal/use-case/main-use-case/upload-file/case.go new file mode 100644 index 00000000..20d000dc --- /dev/null +++ b/internal/use-case/main-use-case/upload-file/case.go @@ -0,0 +1,76 @@ +package uploadfile + +import ( + "errors" + pl "simrs-vx/pkg/logger" + + dg "github.com/karincake/apem/db-gorm-pg" + d "github.com/karincake/dodol" + "gorm.io/gorm" + + ere "simrs-vx/internal/domain/references/encounter" +) + +const source = "upload-file" + +func Upload(input CreateDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Upload", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + var ( + err error + ) + err = dg.I.Transaction(func(tx *gorm.DB) error { + // validate entityType_Code and Type_Code + valid, msg := ere.IsValidUploadCode(input.EntityType_Code, input.Type_Code) + if !valid { + event.Status = "failed" + event.ErrInfo = pl.ErrorInfo{ + Code: "invalid-code", + Detail: msg, + Raw: errors.New(msg), + } + return pl.SetLogError(&event, input) + } + + // upload file + input.FilePath, err = uploadAndGenerateFileUrl(input, &event) + if err != nil { + return err + } + + if input.EntityType_Code == ere.ETCEncounter { + _, err = setEncounterDocument(input, &event, tx) + if err != nil { + return err + } + + } else if input.EntityType_Code == ere.ETCPerson { + _, err = setPersonAttachment(input, &event, tx) + } + + 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: d.IS{ + "fileUrl": input.FilePath, + }, + }, nil +} diff --git a/internal/use-case/main-use-case/upload/helper.go b/internal/use-case/main-use-case/upload-file/helper.go similarity index 89% rename from internal/use-case/main-use-case/upload/helper.go rename to internal/use-case/main-use-case/upload-file/helper.go index 1880676f..176de93f 100644 --- a/internal/use-case/main-use-case/upload/helper.go +++ b/internal/use-case/main-use-case/upload-file/helper.go @@ -2,13 +2,13 @@ DESCRIPTION: Any functions that are used internally by the use-case */ -package upload +package uploadfile import ( "fmt" "path/filepath" ecore "simrs-vx/internal/domain/base-entities/core" - eru "simrs-vx/internal/domain/references/upload" + ere "simrs-vx/internal/domain/references/encounter" "strings" "time" @@ -21,13 +21,12 @@ import ( ee "simrs-vx/internal/domain/main-entities/encounter-document" ep "simrs-vx/internal/domain/main-entities/person" - e "simrs-vx/internal/domain/main-entities/upload" ue "simrs-vx/internal/use-case/main-use-case/encounter-document" up "simrs-vx/internal/use-case/main-use-case/person" ) -func uploadAndGenerateFileUrl(input e.CreateDto, event *pl.Event) (string, error) { +func uploadAndGenerateFileUrl(input CreateDto, event *pl.Event) (string, error) { pl.SetLogInfo(event, input, "started", "uploadAndGenerateFileUrl") bucket := string(input.EntityType_Code) ext := strings.ToLower(filepath.Ext(input.Filename)) @@ -97,7 +96,7 @@ func updatePersonAttachment(dataPerson *ep.Person, event *pl.Event, dbx ...*gorm return nil } -func setPersonAttachment(input e.CreateDto, event *pl.Event, tx *gorm.DB) (*ep.Person, error) { +func setPersonAttachment(input CreateDto, event *pl.Event, tx *gorm.DB) (*ep.Person, error) { // get person dataPerson, err := up.ReadDetailData(ep.ReadDetailDto{Id: *input.Ref_Id}, event, tx) if err != nil { @@ -106,22 +105,22 @@ func setPersonAttachment(input e.CreateDto, event *pl.Event, tx *gorm.DB) (*ep.P var removeUrl string switch input.Type_Code { - case eru.DTCPRN: + case ere.DTCPRN: if dataPerson.ResidentIdentityFileUrl != nil { removeUrl = *dataPerson.ResidentIdentityFileUrl } dataPerson.ResidentIdentityFileUrl = &input.FilePath - case eru.DTCPDL: + case ere.DTCPDL: if dataPerson.DrivingLicenseFileUrl != nil { removeUrl = *dataPerson.DrivingLicenseFileUrl } dataPerson.DrivingLicenseFileUrl = &input.FilePath - case eru.DTCPP: + case ere.DTCPP: if dataPerson.PassportFileUrl != nil { removeUrl = *dataPerson.PassportFileUrl } dataPerson.PassportFileUrl = &input.FilePath - case eru.DTCPFC: + case ere.DTCPFC: if dataPerson.FamilyIdentityFileUrl != nil { removeUrl = *dataPerson.FamilyIdentityFileUrl } @@ -141,7 +140,7 @@ func setPersonAttachment(input e.CreateDto, event *pl.Event, tx *gorm.DB) (*ep.P return dataPerson, nil } -func setEncounterDocument(input e.CreateDto, event *pl.Event, tx *gorm.DB) (*ee.EncounterDocument, error) { +func setEncounterDocument(input CreateDto, event *pl.Event, tx *gorm.DB) (*ee.EncounterDocument, error) { data := ee.EncounterDocument{} // get EncounterDocument @@ -164,7 +163,7 @@ func setEncounterDocument(input e.CreateDto, event *pl.Event, tx *gorm.DB) (*ee. Upload_Employee_Id: input.Upload_Employee_Id, } - if input.Type_Code == eru.DTCSEP || input.Type_Code == eru.DTCSIPP { + if input.Type_Code == ere.DTCSEP || input.Type_Code == ere.DTCSIPP { if len(dataUpload) > 0 { data = dataUpload[0] diff --git a/internal/domain/main-entities/upload/dto.go b/internal/use-case/main-use-case/upload-file/tycovar.go similarity index 53% rename from internal/domain/main-entities/upload/dto.go rename to internal/use-case/main-use-case/upload-file/tycovar.go index 445b1402..a5d63454 100644 --- a/internal/domain/main-entities/upload/dto.go +++ b/internal/use-case/main-use-case/upload-file/tycovar.go @@ -1,17 +1,22 @@ -package upload +/* +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 uploadfile import ( "mime/multipart" - eru "simrs-vx/internal/domain/references/upload" - - // internal - domain - base-entities - ecore "simrs-vx/internal/domain/base-entities/core" + ere "simrs-vx/internal/domain/references/encounter" ) type CreateDto struct { - EntityType_Code eru.EntityTypeCode `form:"entityType_code"` + EntityType_Code ere.EntityTypeCode `form:"entityType_code"` Ref_Id *uint `form:"ref_id"` - Type_Code eru.DocTypeCode `form:"type_code"` + Type_Code ere.DocTypeCode `form:"type_code"` Name string `form:"name"` Upload_Employee_Id *uint `form:"upload_employee_id"` FilePath string `json:"-"` @@ -23,46 +28,10 @@ type CreateDto struct { MimeType string `json:"-"` } -type ReadListDto struct { - FilterDto - Includes string `json:"includes"` - Pagination ecore.Pagination -} - -type FilterDto struct { - EntityType_Code eru.EntityTypeCode `json:"entityType-code"` - Ref_Id *uint `json:"ref-id"` - Type_Code eru.DocTypeCode `json:"type-code"` - Name string `json:"name"` - FilePath *string `json:"filePath"` - FileName *string `json:"fileName"` - Upload_Employee_Id *string `json:"encounter-document-employee-id"` -} - -type ReadDetailDto struct { - Id uint16 `json:"id"` - Includes string `json:"includes"` -} - -type UpdateDto struct { - Id uint16 `json:"id"` - CreateDto -} - -type DeleteDto struct { - Id uint16 `json:"id"` -} - -type MetaDto struct { - PageNumber int `json:"page_number"` - PageSize int `json:"page_size"` - Count int `json:"count"` -} - type ResponseDto struct { - EntityType_Code eru.EntityTypeCode `json:"entityType_code"` + EntityType_Code ere.EntityTypeCode `json:"entityType_code"` Ref_Id *uint `json:"ref_id"` - Type_Code eru.DocTypeCode `json:"type_code"` + Type_Code ere.DocTypeCode `json:"type_code"` Name string `json:"name"` Upload_Employee_Id *uint `json:"upload_employee_id"` FilePath string `json:"filePath"` diff --git a/internal/use-case/main-use-case/upload/case.go b/internal/use-case/main-use-case/upload/case.go deleted file mode 100644 index 40ff74f6..00000000 --- a/internal/use-case/main-use-case/upload/case.go +++ /dev/null @@ -1,90 +0,0 @@ -package upload - -import ( - "errors" - 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" - - eru "simrs-vx/internal/domain/references/upload" - - e "simrs-vx/internal/domain/main-entities/upload" -) - -const source = "upload" - -func Upload(input e.CreateDto) (*d.Data, error) { - event := pl.Event{ - Feature: "Upload", - Source: source, - } - - // Start log - pl.SetLogInfo(&event, input, "started", "create") - - var data interface{} - err := dg.I.Transaction(func(tx *gorm.DB) error { - mwRunner := newMiddlewareRunner(&event, tx) - mwRunner.setMwType(pu.MWTPre) - - // Run pre-middleware - err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data) - if err != nil { - return err - } - - // validate entityType_Code and Type_Code - valid, msg := eru.IsValidUploadCode(input.EntityType_Code, input.Type_Code) - if !valid { - event.Status = "failed" - event.ErrInfo = pl.ErrorInfo{ - Code: "invalid-code", - Detail: msg, - Raw: errors.New(msg), - } - return pl.SetLogError(&event, input) - } - - // upload file - input.FilePath, err = uploadAndGenerateFileUrl(input, &event) - if err != nil { - return err - } - - if input.EntityType_Code == eru.ETCEncounter { - data, err = setEncounterDocument(input, &event, tx) - if err != nil { - return err - } - - } else if input.EntityType_Code == eru.ETCPerson { - data, err = setPersonAttachment(input, &event, tx) - } - - 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: input.ToResponse(), - }, nil -} diff --git a/internal/use-case/main-use-case/upload/middleware-runner.go b/internal/use-case/main-use-case/upload/middleware-runner.go deleted file mode 100644 index 88e3e637..00000000 --- a/internal/use-case/main-use-case/upload/middleware-runner.go +++ /dev/null @@ -1,104 +0,0 @@ -package upload - -import ( - pl "simrs-vx/pkg/logger" - pu "simrs-vx/pkg/use-case-helper" - - "gorm.io/gorm" - - e "simrs-vx/internal/domain/main-entities/upload" -) - -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 interface{}) 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 interface{}) 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 interface{}) 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 interface{}) 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 interface{}) 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/upload/middleware.go b/internal/use-case/main-use-case/upload/middleware.go deleted file mode 100644 index 63beb606..00000000 --- a/internal/use-case/main-use-case/upload/middleware.go +++ /dev/null @@ -1,9 +0,0 @@ -package upload - -// 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/upload/tycovar.go b/internal/use-case/main-use-case/upload/tycovar.go deleted file mode 100644 index 4849e215..00000000 --- a/internal/use-case/main-use-case/upload/tycovar.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -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 upload - -import ( - "gorm.io/gorm" - - e "simrs-vx/internal/domain/main-entities/upload" -) - -type createMw struct { - Name string - Func func(input *e.CreateDto, data interface{}, tx *gorm.DB) error -} - -type readListMw struct { - Name string - Func func(input *e.ReadListDto, data interface{}, tx *gorm.DB) error -} - -type readDetailMw struct { - Name string - Func func(input *e.ReadDetailDto, data interface{}, 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/pkg/upload-helper/upload-helper.go b/pkg/upload-helper/upload-helper.go index 85f5448f..0dce8fd7 100644 --- a/pkg/upload-helper/upload-helper.go +++ b/pkg/upload-helper/upload-helper.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - ere "simrs-vx/internal/domain/references/upload" + ere "simrs-vx/internal/domain/references/encounter" ) func getBucketForType(docType string) (string, error) { From e78ad5aa836787c807790ca743f69a80c25af35c Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 17 Nov 2025 15:29:46 +0700 Subject: [PATCH 3/9] add docs config parser --- internal/infra/docs-cfg/docs-cfg.go | 17 +++++++++++++++++ internal/interface/main-handler/main-handler.go | 2 ++ 2 files changed, 19 insertions(+) create mode 100644 internal/infra/docs-cfg/docs-cfg.go diff --git a/internal/infra/docs-cfg/docs-cfg.go b/internal/infra/docs-cfg/docs-cfg.go new file mode 100644 index 00000000..e6d32c92 --- /dev/null +++ b/internal/infra/docs-cfg/docs-cfg.go @@ -0,0 +1,17 @@ +package docscfg + +import a "github.com/karincake/apem" + +type DocsCfg struct { + Path string +} + +var O DocsCfg = DocsCfg{} + +func ParseCfg() { + a.ParseSingleCfg(&O) +} + +func (c DocsCfg) GetPath() string { + return c.Path +} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index 0027fbc7..649c59cc 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -57,6 +57,7 @@ import ( /******************** infra ********************/ ibpjs "simrs-vx/internal/infra/bpjs" + docscfg "simrs-vx/internal/infra/docs-cfg" gs "simrs-vx/internal/infra/gorm-setting" minio "simrs-vx/internal/infra/minio" ssdb "simrs-vx/internal/infra/ss-db" @@ -131,6 +132,7 @@ func SetRoutes() http.Handler { a.RegisterExtCall(ibpjs.SetConfig) a.RegisterExtCall(validation.RegisterValidation) a.RegisterExtCall(simgossync.SetConfig) + a.RegisterExtCall(docscfg.ParseCfg) r := http.NewServeMux() From 8073236d06ec563669b84fda0a1f210529fca79a Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 17 Nov 2025 16:20:14 +0700 Subject: [PATCH 4/9] feat (general-consent): add crud --- cmd/main-api/config.yml-example | 5 +- .../main-entities/general-consent/dto.go | 64 ++++ .../main-entities/general-consent/entity.go | 2 +- internal/domain/references/common/common.go | 6 + .../main-handler/general-consent/handler.go | 72 +++++ .../interface/main-handler/main-handler.go | 2 + .../main-use-case/general-consent/case.go | 282 ++++++++++++++++++ .../main-use-case/general-consent/helper.go | 22 ++ .../main-use-case/general-consent/lib.go | 140 +++++++++ .../general-consent/middleware-runner.go | 103 +++++++ .../general-consent/middleware.go | 9 + .../main-use-case/general-consent/tycovar.go | 44 +++ .../main-use-case/generate-file/case.go | 45 +++ .../main-use-case/generate-file/tycovar.go | 25 ++ 14 files changed, 819 insertions(+), 2 deletions(-) create mode 100644 internal/domain/main-entities/general-consent/dto.go create mode 100644 internal/interface/main-handler/general-consent/handler.go create mode 100644 internal/use-case/main-use-case/general-consent/case.go create mode 100644 internal/use-case/main-use-case/general-consent/helper.go create mode 100644 internal/use-case/main-use-case/general-consent/lib.go create mode 100644 internal/use-case/main-use-case/general-consent/middleware-runner.go create mode 100644 internal/use-case/main-use-case/general-consent/middleware.go create mode 100644 internal/use-case/main-use-case/general-consent/tycovar.go create mode 100644 internal/use-case/main-use-case/generate-file/case.go create mode 100644 internal/use-case/main-use-case/generate-file/tycovar.go diff --git a/cmd/main-api/config.yml-example b/cmd/main-api/config.yml-example index 448cff66..22fdf651 100644 --- a/cmd/main-api/config.yml-example +++ b/cmd/main-api/config.yml-example @@ -70,4 +70,7 @@ bpjsCfg: syncUrlCfg: host: - prefix: new-to-old \ No newline at end of file + prefix: new-to-old + +docsCfg: + path: ../../assets/docs/ \ No newline at end of file diff --git a/internal/domain/main-entities/general-consent/dto.go b/internal/domain/main-entities/general-consent/dto.go new file mode 100644 index 00000000..3c659f2f --- /dev/null +++ b/internal/domain/main-entities/general-consent/dto.go @@ -0,0 +1,64 @@ +package generalconsent + +import ( + ecore "simrs-vx/internal/domain/base-entities/core" +) + +type CreateDto struct { + Encounter_Id *uint `json:"-"` + Value *string `json:"value"` +} + +type ReadListDto struct { + FilterDto + Includes string `json:"includes"` + Pagination ecore.Pagination +} + +type FilterDto struct { + Encounter_Id *uint `json:"encounter-id"` +} + +type ReadDetailDto struct { + Id uint `json:"id"` +} + +type UpdateDto struct { + Id uint `json:"id"` + CreateDto +} + +type DeleteDto struct { + Id uint `json:"id"` +} + +type MetaDto struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + Count int `json:"count"` +} + +type ResponseDto struct { + ecore.Main + Encounter_Id *uint `json:"encounter_id"` + Value *string `json:"value"` + FileUrl *string `json:"fileUrl"` +} + +func (d GeneralConsent) ToResponse() ResponseDto { + resp := ResponseDto{ + Encounter_Id: d.Encounter_Id, + Value: d.Value, + FileUrl: d.FileUrl, + } + resp.Main = d.Main + return resp +} + +func ToResponseList(data []GeneralConsent) []ResponseDto { + resp := make([]ResponseDto, len(data)) + for i, u := range data { + resp[i] = u.ToResponse() + } + return resp +} diff --git a/internal/domain/main-entities/general-consent/entity.go b/internal/domain/main-entities/general-consent/entity.go index f5ce6394..bf9381ec 100644 --- a/internal/domain/main-entities/general-consent/entity.go +++ b/internal/domain/main-entities/general-consent/entity.go @@ -1,4 +1,4 @@ -package general_consent +package generalconsent import ( "simrs-vx/internal/domain/base-entities/core" diff --git a/internal/domain/references/common/common.go b/internal/domain/references/common/common.go index 5b3f675b..cc466d7b 100644 --- a/internal/domain/references/common/common.go +++ b/internal/domain/references/common/common.go @@ -17,6 +17,7 @@ type ( CrudCode string DataApprovalCode string ProcessStatusCode string + DocFormatTypeCode string ) const ( @@ -106,6 +107,11 @@ const ( PSCSuccess ProcessStatusCode = "success" PSCFailed ProcessStatusCode = "failed" + + DFTCPDF DocFormatTypeCode = "pdf" + DFTCTXLSX DocFormatTypeCode = "xlsx" + DFTCTCSV DocFormatTypeCode = "csv" + DFTCTXLS DocFormatTypeCode = "xls" ) func GetDayCodes() map[DayCode]string { diff --git a/internal/interface/main-handler/general-consent/handler.go b/internal/interface/main-handler/general-consent/handler.go new file mode 100644 index 00000000..0a2e2f6a --- /dev/null +++ b/internal/interface/main-handler/general-consent/handler.go @@ -0,0 +1,72 @@ +package generalconsent + +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/general-consent" + u "simrs-vx/internal/use-case/main-use-case/general-consent" +) + +type myBase struct{} + +var O myBase + +func (obj myBase) Create(w http.ResponseWriter, r *http.Request) { + dto := e.CreateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Create(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) { + dto := e.ReadListDto{} + sf.UrlQueryParam(&dto, *r.URL) + res, err := u.ReadList(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + dto := e.ReadDetailDto{} + sf.UrlQueryParam(&dto, *r.URL) + dto.Id = uint(id) + res, err := u.ReadDetail(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Update(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.UpdateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + dto.Id = uint(id) + res, err := u.Update(dto) + rw.DataResponse(w, res, err) +} + +func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + dto := e.DeleteDto{} + dto.Id = uint(id) + res, err := u.Delete(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index 649c59cc..8ce9fadf 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -17,6 +17,7 @@ import ( deviceorderitem "simrs-vx/internal/interface/main-handler/device-order-item" encounter "simrs-vx/internal/interface/main-handler/encounter" encounterdocument "simrs-vx/internal/interface/main-handler/encounter-document" + generalconsent "simrs-vx/internal/interface/main-handler/general-consent" internalreference "simrs-vx/internal/interface/main-handler/internal-reference" materialorder "simrs-vx/internal/interface/main-handler/material-order" materialorderitem "simrs-vx/internal/interface/main-handler/material-order-item" @@ -278,6 +279,7 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/chemo-protocol", chemoprotocol.O) hc.RegCrud(r, "/v1/upload-file", uploadfile.O) hc.RegCrud(r, "/v1/encounter-document", encounterdocument.O) + hc.RegCrud(r, "/v1/general-consent", generalconsent.O) /******************** actor ********************/ hc.RegCrud(r, "/v1/person", person.O) diff --git a/internal/use-case/main-use-case/general-consent/case.go b/internal/use-case/main-use-case/general-consent/case.go new file mode 100644 index 00000000..a8c181ef --- /dev/null +++ b/internal/use-case/main-use-case/general-consent/case.go @@ -0,0 +1,282 @@ +package generalconsent + +import ( + "strconv" + + // main entities + e "simrs-vx/internal/domain/main-entities/general-consent" + + 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 = "general-consent" + +func Create(input e.CreateDto) (*d.Data, error) { + data := e.GeneralConsent{} + + event := pl.Event{ + Feature: "Create", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + err := dg.I.Transaction(func(tx *gorm.DB) error { + mwRunner := newMiddlewareRunner(&event, tx) + mwRunner.setMwType(pu.MWTPre) + // Run pre-middleware + if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { + return err + } + + if resData, err := CreateData(input, &event, tx); err != nil { + return err + } else { + data = *resData + } + + mwRunner.setMwType(pu.MWTPost) + // Run post-middleware + if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: data.ToResponse(), + }, nil +} + +func ReadList(input e.ReadListDto) (*d.Data, error) { + var data *e.GeneralConsent + var dataList []e.GeneralConsent + 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.GeneralConsent + 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.GeneralConsent + 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 + } + + // 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.GeneralConsent + 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/general-consent/helper.go b/internal/use-case/main-use-case/general-consent/helper.go new file mode 100644 index 00000000..015d4716 --- /dev/null +++ b/internal/use-case/main-use-case/general-consent/helper.go @@ -0,0 +1,22 @@ +/* +DESCRIPTION: +Any functions that are used internally by the use-case +*/ +package generalconsent + +import ( + e "simrs-vx/internal/domain/main-entities/general-consent" +) + +func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.GeneralConsent) { + 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/general-consent/lib.go b/internal/use-case/main-use-case/general-consent/lib.go new file mode 100644 index 00000000..e365523d --- /dev/null +++ b/internal/use-case/main-use-case/general-consent/lib.go @@ -0,0 +1,140 @@ +package generalconsent + +import ( + "errors" + e "simrs-vx/internal/domain/main-entities/general-consent" + 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.GeneralConsent, error) { + pl.SetLogInfo(event, nil, "started", "DBCreate") + + data := e.GeneralConsent{} + 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.GeneralConsent, *e.MetaDto, error) { + pl.SetLogInfo(event, input, "started", "DBReadList") + data := []e.GeneralConsent{} + 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.GeneralConsent{}). + 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.GeneralConsent, error) { + pl.SetLogInfo(event, input, "started", "DBReadDetail") + data := e.GeneralConsent{} + + 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.GeneralConsent, 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.GeneralConsent, 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/general-consent/middleware-runner.go b/internal/use-case/main-use-case/general-consent/middleware-runner.go new file mode 100644 index 00000000..4dfc739a --- /dev/null +++ b/internal/use-case/main-use-case/general-consent/middleware-runner.go @@ -0,0 +1,103 @@ +package generalconsent + +import ( + e "simrs-vx/internal/domain/main-entities/general-consent" + 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.GeneralConsent) 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.GeneralConsent) 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.GeneralConsent) 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.GeneralConsent) 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.GeneralConsent) 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/general-consent/middleware.go b/internal/use-case/main-use-case/general-consent/middleware.go new file mode 100644 index 00000000..69311dfc --- /dev/null +++ b/internal/use-case/main-use-case/general-consent/middleware.go @@ -0,0 +1,9 @@ +package generalconsent + +// 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/general-consent/tycovar.go b/internal/use-case/main-use-case/general-consent/tycovar.go new file mode 100644 index 00000000..b91396b9 --- /dev/null +++ b/internal/use-case/main-use-case/general-consent/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 generalconsent + +import ( + "gorm.io/gorm" + + e "simrs-vx/internal/domain/main-entities/general-consent" +) + +type createMw struct { + Name string + Func func(input *e.CreateDto, data *e.GeneralConsent, tx *gorm.DB) error +} + +type readListMw struct { + Name string + Func func(input *e.ReadListDto, data *e.GeneralConsent, tx *gorm.DB) error +} + +type readDetailMw struct { + Name string + Func func(input *e.ReadDetailDto, data *e.GeneralConsent, 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/generate-file/case.go b/internal/use-case/main-use-case/generate-file/case.go new file mode 100644 index 00000000..72182513 --- /dev/null +++ b/internal/use-case/main-use-case/generate-file/case.go @@ -0,0 +1,45 @@ +package generatefile + +import ( + // "encoding/json" + + // egc "simrs-vx/internal/domain/main-entities/general-consent" + // ugc "simrs-vx/internal/use-case/main-use-case/general-consent" + + pl "simrs-vx/pkg/logger" + + d "github.com/karincake/dodol" + + ere "simrs-vx/internal/domain/references/encounter" +) + +const source = "generate-file" + +func Generate(input GenerateDto) (*d.Data, error) { + event := pl.Event{ + Feature: "Generate", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "create") + + switch input.Type_Code { + // general-consent + case ere.DTCGC: + // get value from general consent by ref_id + // gc, err := ugc.ReadDetailData(ugc.ReadDetailDto{Ref_Id: input.Ref_Id}, &event, nil) + // if err != nil { + // return nil, err + // } + + // map template data + // gc + // templateData := GeneralConsentPDF{} + // gcUnmarshalled := json.Unmarshal(g) + + return nil, nil + } + + return nil, 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 new file mode 100644 index 00000000..716e277d --- /dev/null +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -0,0 +1,25 @@ +package generatefile + +import ( + erc "simrs-vx/internal/domain/references/common" + ere "simrs-vx/internal/domain/references/encounter" +) + +type GeneralConsentPDF struct { + PatientRelatives_Name []string `json:"patientRelatives_name"` + Responsible_Name string `json:"responsible_name"` + InformationGivenBy_Name string `json:"informationGivenBy_name"` + Witness1_Name string `json:"witness1_name"` + Witness2_Name string `json:"witness2_name"` +} + +type GenerateDto struct { + EntityType_Code ere.EntityTypeCode `form:"entityType_code"` + Ref_Id *uint `form:"ref_id"` + Type_Code ere.DocTypeCode `form:"type_code"` + FormatType erc.DocFormatTypeCode `form:"formatType"` +} + +type ResponseDto struct { + FileUrl string `json:"fileUrl"` +} From 69a13dd37d517d1b5a6ba5073907842a7d2cadc2 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 17 Nov 2025 17:18:22 +0700 Subject: [PATCH 5/9] feat (generate-file): not tested yet --- go.mod | 1 + go.sum | 2 + .../main-handler/generate-file/handler.go | 18 +++ .../interface/main-handler/main-handler.go | 2 + .../main-use-case/generate-file/case.go | 64 +++++++-- .../main-use-case/generate-file/helper.go | 83 +++++++++++ .../main-use-case/generate-file/tycovar.go | 22 ++- pkg/file-helper/file-helper.go | 33 +++++ pkg/pdf-helper/pdf-helper.go | 131 ++++++++++++++++++ 9 files changed, 340 insertions(+), 16 deletions(-) create mode 100644 internal/interface/main-handler/generate-file/handler.go create mode 100644 internal/use-case/main-use-case/generate-file/helper.go create mode 100644 pkg/file-helper/file-helper.go create mode 100644 pkg/pdf-helper/pdf-helper.go diff --git a/go.mod b/go.mod index 9d557637..b44bb011 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( require ( ariga.io/atlas v0.36.2-0.20250806044935-5bb51a0a956e // indirect + github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-redis/redis v6.15.9+incompatible // indirect diff --git a/go.sum b/go.sum index 9681a749..da70ac51 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3 h1:vrA6+R1BMLKMTbos8jAeuBrImHPGtY4gTlcue3OIej8= +github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/internal/interface/main-handler/generate-file/handler.go b/internal/interface/main-handler/generate-file/handler.go new file mode 100644 index 00000000..c6d6c67d --- /dev/null +++ b/internal/interface/main-handler/generate-file/handler.go @@ -0,0 +1,18 @@ +package generatefile + +import ( + "net/http" + + rw "github.com/karincake/risoles" + + u "simrs-vx/internal/use-case/main-use-case/generate-file" +) + +func Generate(w http.ResponseWriter, r *http.Request) { + dto := u.GenerateDto{} + if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res { + return + } + res, err := u.Generate(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 8ce9fadf..4a49c022 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -18,6 +18,7 @@ import ( encounter "simrs-vx/internal/interface/main-handler/encounter" encounterdocument "simrs-vx/internal/interface/main-handler/encounter-document" generalconsent "simrs-vx/internal/interface/main-handler/general-consent" + generatefile "simrs-vx/internal/interface/main-handler/generate-file" internalreference "simrs-vx/internal/interface/main-handler/internal-reference" materialorder "simrs-vx/internal/interface/main-handler/material-order" materialorderitem "simrs-vx/internal/interface/main-handler/material-order-item" @@ -280,6 +281,7 @@ func SetRoutes() http.Handler { hc.RegCrud(r, "/v1/upload-file", uploadfile.O) hc.RegCrud(r, "/v1/encounter-document", encounterdocument.O) hc.RegCrud(r, "/v1/general-consent", generalconsent.O) + r.HandleFunc("POST /v1/generate-file", generatefile.Generate) /******************** actor ********************/ hc.RegCrud(r, "/v1/person", person.O) 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 72182513..c39a7edc 100644 --- a/internal/use-case/main-use-case/generate-file/case.go +++ b/internal/use-case/main-use-case/generate-file/case.go @@ -1,15 +1,18 @@ package generatefile import ( - // "encoding/json" + "encoding/json" + "errors" - // egc "simrs-vx/internal/domain/main-entities/general-consent" - // ugc "simrs-vx/internal/use-case/main-use-case/general-consent" + egc "simrs-vx/internal/domain/main-entities/general-consent" + ugc "simrs-vx/internal/use-case/main-use-case/general-consent" pl "simrs-vx/pkg/logger" + dg "github.com/karincake/apem/db-gorm-pg" d "github.com/karincake/dodol" + erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" ) @@ -28,18 +31,55 @@ func Generate(input GenerateDto) (*d.Data, error) { // general-consent case ere.DTCGC: // get value from general consent by ref_id - // gc, err := ugc.ReadDetailData(ugc.ReadDetailDto{Ref_Id: input.Ref_Id}, &event, nil) - // if err != nil { - // return nil, err - // } + gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: *input.Ref_Id}, &event, nil) + if err != nil { + return nil, err + } // map template data - // gc - // templateData := GeneralConsentPDF{} - // gcUnmarshalled := json.Unmarshal(g) + 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") + } - return nil, nil + input.FormatType = erc.DFTCPDF + input.TemplateName = TDNGC + + // generate file + urlPub, err := generateFile(input, templateData) + if err != nil { + return nil, err + } + + gc.FileUrl = &urlPub + if err := dg.I.Save(&gc).Error; err != nil { + return nil, err + } + + response := ResponseDto{ + FileUrl: urlPub, + } + + return &d.Data{ + Meta: d.II{ + "source": source, + "structure": "single-data", + "status": "created", + }, + Data: response, + }, nil + default: + return nil, errors.New("invalid type code") } - return nil, nil } diff --git a/internal/use-case/main-use-case/generate-file/helper.go b/internal/use-case/main-use-case/generate-file/helper.go new file mode 100644 index 00000000..ec377dc2 --- /dev/null +++ b/internal/use-case/main-use-case/generate-file/helper.go @@ -0,0 +1,83 @@ +package generatefile + +import ( + "errors" + "fmt" + "mime" + "path/filepath" + "time" + + erc "simrs-vx/internal/domain/references/common" + docscfg "simrs-vx/internal/infra/docs-cfg" + pf "simrs-vx/pkg/file-helper" + pm "simrs-vx/pkg/minio-helper" + pp "simrs-vx/pkg/pdf-helper" +) + +// generate temporary file, upload to minio, generate public url, delete temporary file +func generateFile(input GenerateDto, templateData any) (string, error) { + newPath, err := pf.PathToSaveFile(fmt.Sprintf("./public/%s/%d/%s", input.EntityType_Code, *input.Ref_Id, input.Type_Code)) + if err != nil { + return "", err + } + + fPath := fmt.Sprintf("%s/%s_%s.%s", newPath, input.Type_Code, time.Now().Format("20060102150405"), input.FormatType) + + templatePath := docscfg.O.GetPath() + string(input.TemplateName) + + switch input.FormatType { + case erc.DFTCPDF: + if err := generatePDF(GeneratePDFdto{ + TemplatePath: templatePath, + TemplateData: templateData, + PdfPath: fPath, + }); err != nil { + return "", err + } + case erc.DFTCTXLSX: + // TODO: generate xlsx + case erc.DFTCTCSV: + // TODO: generate csv + default: + return "", errors.New("invalid format type") + } + + bucketName := input.EntityType_Code + + objectName := fmt.Sprintf("%v/%s-%d", *input.Ref_Id, input.Type_Code, time.Now().UnixNano()) + pdfUpload := pm.UploadPathInput{ + BucketName: string(bucketName), + Name: objectName, + Path: fPath, + ContentType: mime.TypeByExtension(filepath.Ext(fPath)), + } + + // create bucket if not exist, create object in bucket + info, err := pm.I.FPutObject(pdfUpload) + if err != nil { + return "", err + } + + // generate public url + urlPub := pm.I.GenerateUrl(info.Bucket, info.Key) + if err := pf.DeleteFolder(fPath); err != nil { + return "", err + } + + return urlPub, nil +} + +func generatePDF(input GeneratePDFdto) error { + // parse template data into html template + r := pp.NewRequestPdf("") + if err := r.ParseTemplate(input.TemplatePath, input.TemplateData); err == nil { + _, err := r.GenerateByCommand(input.PdfPath, input.TemplatePath) + if err != nil { + return errors.New("generate pdf by command error : " + err.Error()) + } + } else { + return errors.New("parse template error : " + err.Error()) + } + + return 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 716e277d..17c5abc1 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -14,12 +14,26 @@ type GeneralConsentPDF struct { } type GenerateDto struct { - EntityType_Code ere.EntityTypeCode `form:"entityType_code"` - Ref_Id *uint `form:"ref_id"` - Type_Code ere.DocTypeCode `form:"type_code"` - FormatType erc.DocFormatTypeCode `form:"formatType"` + EntityType_Code ere.EntityTypeCode `json:"entityType_code"` + Ref_Id *uint `json:"ref_id"` + Type_Code ere.DocTypeCode `json:"type_code"` + FormatType erc.DocFormatTypeCode `json:"formatType"` + TemplateName TemplateDocsName `json:"-"` +} + +type GeneratePDFdto struct { + TemplatePath string + TemplateData any + PdfPath string } type ResponseDto struct { FileUrl string `json:"fileUrl"` } + +type TemplateDocsName string + +// TemplateDocsName is the name of the template file in the assets/docs folder +const ( + TDNGC TemplateDocsName = "general-consent.html" +) diff --git a/pkg/file-helper/file-helper.go b/pkg/file-helper/file-helper.go new file mode 100644 index 00000000..bd0849c7 --- /dev/null +++ b/pkg/file-helper/file-helper.go @@ -0,0 +1,33 @@ +package filehelper + +import ( + "fmt" + "os" + "path/filepath" +) + +// const DEFAULT_EXPIRY_FILES = time.Hour * 24 * 7 + +func PathAgreement(medicalNumber string) (string, error) { + outputPath := fmt.Sprintf("./public/patient/%s", medicalNumber) + err := os.MkdirAll(outputPath, os.ModePerm) + return outputPath, err +} + +func PathToSaveFile(outputPath string) (string, error) { + err := os.MkdirAll(outputPath, os.ModePerm) + return outputPath, err +} + +func RenameFile(srcPath, dstPath string) error { + return os.Rename(srcPath, dstPath) +} + +func DeleteFolder(path string) error { + return os.RemoveAll(path) +} + +func PathToUrl(fileName string) *string { + fileUrl := filepath.ToSlash(fmt.Sprintf("%c%s", os.PathSeparator, fileName)) + return &fileUrl +} diff --git a/pkg/pdf-helper/pdf-helper.go b/pkg/pdf-helper/pdf-helper.go new file mode 100644 index 00000000..597052eb --- /dev/null +++ b/pkg/pdf-helper/pdf-helper.go @@ -0,0 +1,131 @@ +package pdfhelper + +import ( + "bytes" + "html/template" + "log" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/SebastiaanKlippert/go-wkhtmltopdf" +) + +// pdf requestpdf struct +type RequestPdf struct { + body string +} + +// new request to pdf function +func NewRequestPdf(body string) *RequestPdf { + return &RequestPdf{ + body: body, + } +} + +// parsing template function +func (r *RequestPdf) ParseTemplate(templatePath string, data interface{}) error { + f := strings.Split(templatePath, "/") + fileName := f[len(f)-1] + funcs := template.FuncMap{ + "nl2br": func(text string) template.HTML { + return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "
", -1)) + }, + } + t, err := template.New(fileName).Funcs(funcs).ParseFiles(templatePath) + if err != nil { + return err + } + buf := new(bytes.Buffer) + if err = t.Execute(buf, data); err != nil { + return err + } + r.body = buf.String() + + return nil +} + +func (r *RequestPdf) GenerateByCommand(pdfPath string, templatePath string) (bool, error) { + // wkhtmltopdf -L 0 -R 0 -B 0 -s Legal --enable-local-file-access resultAntigen2.html out.pdf + t := time.Now().Unix() + + if _, err := os.Stat("cloneTemplate/"); os.IsNotExist(err) { + errDir := os.Mkdir("cloneTemplate/", 0777) + if errDir != nil { + log.Fatal(errDir) + } + } + htmlName := strconv.FormatInt(int64(t), 10) + ".html" + err := os.WriteFile("cloneTemplate/"+htmlName, []byte(r.body), 0644) + if err != nil { + panic(err) + } + + cmd := exec.Command("wkhtmltopdf", "--enable-local-file-access", "-L", "0", "-R", "0", "-B", "0", "-s", "A4", "cloneTemplate/"+htmlName, pdfPath) + + if err := cmd.Run(); err != nil { + return false, err + } + dir, err := os.Getwd() + if err != nil { + panic(err) + } + defer os.RemoveAll(dir + "/cloneTemplate") + return true, nil +} + +// generate pdf function +func (r *RequestPdf) GeneratePDF(pdfPath string) (bool, error) { + t := time.Now().Unix() + + if _, err := os.Stat("cloneTemplate/"); os.IsNotExist(err) { + errDir := os.Mkdir("cloneTemplate/", 0777) + if errDir != nil { + log.Fatal(errDir) + } + } + err1 := os.WriteFile("cloneTemplate/"+strconv.FormatInt(int64(t), 10)+".html", []byte(r.body), 0644) + if err1 != nil { + panic(err1) + } + + f, err := os.Open("cloneTemplate/" + strconv.FormatInt(int64(t), 10) + ".html") + if f != nil { + defer f.Close() + } + if err != nil { + log.Fatal(err) + } + + pdfg, err := wkhtmltopdf.NewPDFGenerator() + if err != nil { + log.Fatal(err) + } + + pdfg.AddPage(wkhtmltopdf.NewPageReader(f)) + + pdfg.PageSize.Set(wkhtmltopdf.PageSizeA4) + + pdfg.Dpi.Set(300) + + err = pdfg.Create() + if err != nil { + log.Fatal(err) + } + + err = pdfg.WriteFile(pdfPath) + if err != nil { + log.Fatal(err) + } + + dir, err := os.Getwd() + if err != nil { + panic(err) + } + + defer os.RemoveAll(dir + "/cloneTemplate") + + return true, nil +} From c8e17619d8f2860d44b137bf7b76281623251bed Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Mon, 17 Nov 2025 17:34:17 +0700 Subject: [PATCH 6/9] feat (generate-file): add validation --- internal/use-case/main-use-case/generate-file/tycovar.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 17c5abc1..b5d09ceb 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -14,9 +14,9 @@ type GeneralConsentPDF struct { } type GenerateDto struct { - EntityType_Code ere.EntityTypeCode `json:"entityType_code"` - Ref_Id *uint `json:"ref_id"` - Type_Code ere.DocTypeCode `json:"type_code"` + EntityType_Code ere.EntityTypeCode `json:"entityType_code" validate:"required"` + Ref_Id *uint `json:"ref_id" validate:"required"` + Type_Code ere.DocTypeCode `json:"type_code" validate:"required"` FormatType erc.DocFormatTypeCode `json:"formatType"` TemplateName TemplateDocsName `json:"-"` } From 6ab240b8d5e33012fa89f72fa7cd6dcc5348c8e6 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 18 Nov 2025 12:15:04 +0700 Subject: [PATCH 7/9] adjust html template --- assets/docs/general-consent.html | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/assets/docs/general-consent.html b/assets/docs/general-consent.html index e311cb54..4cedc378 100644 --- a/assets/docs/general-consent.html +++ b/assets/docs/general-consent.html @@ -125,7 +125,17 @@ >
c) Anggota keluarga saya : - .......................................... + {{ range $i, $name := .FamilyMembers }} + {{ if lt $i 2 }} +
  • {{ $name }}
  • + {{ end }} + {{ end }} + +{{ end }}
    Saya memahami bahwa pembukaan rahasia ini hanya sejauh yang diperlukan untuk tujuan perawatan, pembiayaan atau administrasi yang @@ -252,20 +262,16 @@ > - Pasien/Keluarga


    .......................................
    Nama - terang + Pasien/keluarga/
    penanggung jawab


    .......................................
    {{ .Responsible_Name }} - Pemberi Informasi


    .......................................
    Nama - terang + Pemberi Informasi


    .......................................
    {{ .InformationGivenBy_Name }} - Saksi I


    .......................................
    Nama - terang + Saksi I


    .......................................
    {{ .Witness1_Name }} - Saksi II


    .......................................
    Nama - terang + Saksi II


    .......................................
    {{ .Witness2_Name }} From 0d340553b32f6f55fa059c95786bd9bad7636197 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 18 Nov 2025 14:44:19 +0700 Subject: [PATCH 8/9] feat (generate-file): general-consent pdf done --- assets/docs/general-consent.html | 82 +++++++--- .../main-entities/general-consent/dto.go | 2 +- .../main-use-case/general-consent/case.go | 13 ++ .../main-use-case/generate-file/case.go | 11 +- .../main-use-case/generate-file/helper.go | 17 +- .../main-use-case/generate-file/tycovar.go | 2 + pkg/conv-helper/conv-helper.go | 153 ++++++++++++++++++ pkg/use-case-helper/use-case-helper.go | 42 +++++ 8 files changed, 299 insertions(+), 23 deletions(-) create mode 100644 pkg/conv-helper/conv-helper.go diff --git a/assets/docs/general-consent.html b/assets/docs/general-consent.html index 4cedc378..a3f69826 100644 --- a/assets/docs/general-consent.html +++ b/assets/docs/general-consent.html @@ -125,11 +125,11 @@ >
    c) Anggota keluarga saya : - {{ if eq (len .FamilyMembers) 0 }} + {{ if eq (len .PatientRelatives_Name) 0 }} .......................................... {{ else }} -
      - {{ range $i, $name := .FamilyMembers }} +
        + {{ range $i, $name := .PatientRelatives_Name }} {{ if lt $i 2 }}
      • {{ $name }}
      • {{ end }} @@ -247,7 +247,7 @@
        - Malang, ............................................................ + Malang, {{ .Date }}
        @@ -260,20 +260,66 @@ border-collapse: collapse; " > - - - Pasien/keluarga/
        penanggung jawab


        .......................................
        {{ .Responsible_Name }} - - - Pemberi Informasi


        .......................................
        {{ .InformationGivenBy_Name }} - - - Saksi I


        .......................................
        {{ .Witness1_Name }} - - - Saksi II


        .......................................
        {{ .Witness2_Name }} - - + + +
        + Pasien/keluarga/
        penanggung jawab +
        + +
        + ....................................... +
        + +
        + {{ .Responsible_Name }} +
        + + + +
        + Pemberi Informasi +
        + +
        + ....................................... +
        + +
        + {{ .InformationGivenBy_Name }} +
        + + + +
        + Saksi I +
        + +
        + ....................................... +
        + +
        + {{ .Witness1_Name }} +
        + + + +
        + Saksi II +
        + +
        + ....................................... +
        + +
        + {{ .Witness2_Name }} +
        + + + + + diff --git a/internal/domain/main-entities/general-consent/dto.go b/internal/domain/main-entities/general-consent/dto.go index 3c659f2f..03dd7dc9 100644 --- a/internal/domain/main-entities/general-consent/dto.go +++ b/internal/domain/main-entities/general-consent/dto.go @@ -5,7 +5,7 @@ import ( ) type CreateDto struct { - Encounter_Id *uint `json:"-"` + Encounter_Id *uint `json:"encounter_id"` Value *string `json:"value"` } diff --git a/internal/use-case/main-use-case/general-consent/case.go b/internal/use-case/main-use-case/general-consent/case.go index a8c181ef..e6dd2a61 100644 --- a/internal/use-case/main-use-case/general-consent/case.go +++ b/internal/use-case/main-use-case/general-consent/case.go @@ -1,11 +1,14 @@ package generalconsent import ( + "errors" "strconv" // main entities e "simrs-vx/internal/domain/main-entities/general-consent" + ue "simrs-vx/internal/use-case/main-use-case/encounter" + pl "simrs-vx/pkg/logger" pu "simrs-vx/pkg/use-case-helper" @@ -35,6 +38,11 @@ func Create(input e.CreateDto) (*d.Data, error) { 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 { @@ -192,6 +200,11 @@ func Update(input e.UpdateDto) (*d.Data, error) { 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 } 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 c39a7edc..1c3ca51b 100644 --- a/internal/use-case/main-use-case/generate-file/case.go +++ b/internal/use-case/main-use-case/generate-file/case.go @@ -8,6 +8,7 @@ import ( ugc "simrs-vx/internal/use-case/main-use-case/general-consent" 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" @@ -31,11 +32,17 @@ func Generate(input GenerateDto) (*d.Data, error) { // general-consent case ere.DTCGC: // get value from general consent by ref_id - gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: *input.Ref_Id}, &event, nil) + gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: *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 { @@ -54,6 +61,8 @@ func Generate(input GenerateDto) (*d.Data, error) { 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) 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 ec377dc2..9bee2212 100644 --- a/internal/use-case/main-use-case/generate-file/helper.go +++ b/internal/use-case/main-use-case/generate-file/helper.go @@ -9,19 +9,21 @@ import ( 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" pm "simrs-vx/pkg/minio-helper" pp "simrs-vx/pkg/pdf-helper" + pu "simrs-vx/pkg/use-case-helper" ) // generate temporary file, upload to minio, generate public url, delete temporary file func generateFile(input GenerateDto, templateData any) (string, error) { - newPath, err := pf.PathToSaveFile(fmt.Sprintf("./public/%s/%d/%s", input.EntityType_Code, *input.Ref_Id, input.Type_Code)) + newPath, err := pf.PathToSaveFile(fmt.Sprintf("./temporary/%s", input.EntityType_Code)) if err != nil { return "", err } - fPath := fmt.Sprintf("%s/%s_%s.%s", newPath, input.Type_Code, time.Now().Format("20060102150405"), input.FormatType) + fPath := fmt.Sprintf("%s/%s-%s.%s", newPath, input.Type_Code, time.Now().Format("20060102150405"), input.FormatType) templatePath := docscfg.O.GetPath() + string(input.TemplateName) @@ -44,7 +46,7 @@ func generateFile(input GenerateDto, templateData any) (string, error) { bucketName := input.EntityType_Code - objectName := fmt.Sprintf("%v/%s-%d", *input.Ref_Id, input.Type_Code, time.Now().UnixNano()) + objectName := fmt.Sprintf("%s/%s-%d.%s", *pc.UintToString(input.Encounter_Id), input.Type_Code, time.Now().UnixNano(), input.FormatType) pdfUpload := pm.UploadPathInput{ BucketName: string(bucketName), Name: objectName, @@ -81,3 +83,12 @@ func generatePDF(input GeneratePDFdto) error { return nil } + +func removeFile(bucket, fileUrl string) error { + fPath := pu.GetLastTwoPathSegments(fileUrl) + err := pm.I.RemoveObject(bucket, fPath) + if err != nil { + return err + } + return 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 b5d09ceb..1e16cdb8 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -11,6 +11,7 @@ type GeneralConsentPDF struct { InformationGivenBy_Name string `json:"informationGivenBy_name"` Witness1_Name string `json:"witness1_name"` Witness2_Name string `json:"witness2_name"` + Date string `json:"date"` } type GenerateDto struct { @@ -19,6 +20,7 @@ type GenerateDto struct { Type_Code ere.DocTypeCode `json:"type_code" validate:"required"` FormatType erc.DocFormatTypeCode `json:"formatType"` TemplateName TemplateDocsName `json:"-"` + Encounter_Id *uint `json:"-"` } type GeneratePDFdto struct { diff --git a/pkg/conv-helper/conv-helper.go b/pkg/conv-helper/conv-helper.go new file mode 100644 index 00000000..03063c74 --- /dev/null +++ b/pkg/conv-helper/conv-helper.go @@ -0,0 +1,153 @@ +package convhelper + +import ( + "fmt" + "strconv" + "time" + + "gorm.io/gorm" +) + +// check string pointer, if nil return default value +func StrConvDefault(f *string, def string) string { + if f == nil { + return def + } + return *f +} + +// check string pointer, if nil return empty string +func StrConvEmpty(f *string) string { + return StrConvDefault(f, "-") +} + +func ByteConvStr(f *byte) string { + if f != nil { + return strconv.Itoa(int(*f)) + } + return "" +} + +func Float32Conv(f *float32) float32 { + if f == nil { + return 0 + } + return *f +} + +func StrRelConv(b bool, d string) string { + if b { + return d + } else { + return "" + } +} + +func UintToString(f interface{}) *string { + if f == nil { + return nil + } + + var t string + switch v := f.(type) { + case uint: + t = strconv.FormatUint(uint64(v), 10) + case uint8: + t = strconv.FormatUint(uint64(v), 10) + case uint16: + t = strconv.FormatUint(uint64(v), 10) + case uint32: + t = strconv.FormatUint(uint64(v), 10) + case uint64: + t = strconv.FormatUint(v, 10) + // Handle pointer types + case *uint: + if v != nil { + t = strconv.FormatUint(uint64(*v), 10) + } + case *uint8: + if v != nil { + t = strconv.FormatUint(uint64(*v), 10) + } + case *uint16: + if v != nil { + t = strconv.FormatUint(uint64(*v), 10) + } + case *uint32: + if v != nil { + t = strconv.FormatUint(uint64(*v), 10) + } + case *uint64: + if v != nil { + t = strconv.FormatUint(*v, 10) + } + default: + return nil // Unsupported type + } + + return &t +} + +func BoolToString(f *bool) *string { + if f == nil { + return nil + } + t := strconv.FormatBool(*f) + 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) +} + +func BoolToFloat64(b bool) float64 { + if b { + return 1.0 + } + return 0.0 +} + +func Float64ToBool(f float64) bool { + return f == 1.0 +} + +func StringToBool(s string) bool { + return s == "1" +} + +func StringToFloat32(s string) float32 { + f, _ := strconv.ParseFloat(s, 32) + return float32(f) +} + +func StringToFloat64(s string) float64 { + f, _ := strconv.ParseFloat(s, 64) + return f +} + +func Float64ToString(f float64) string { + return fmt.Sprintf("%.2f", f) +} + +func StringToUint64(s string) *uint64 { + if s == "" { + return nil + } + u, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil + } + return &u +} diff --git a/pkg/use-case-helper/use-case-helper.go b/pkg/use-case-helper/use-case-helper.go index 07df05ba..019f9773 100644 --- a/pkg/use-case-helper/use-case-helper.go +++ b/pkg/use-case-helper/use-case-helper.go @@ -3,6 +3,7 @@ package usecasehelper import ( "errors" "fmt" + "net/url" "strings" "time" @@ -166,3 +167,44 @@ func index[S ~[]E, E comparable](s S, v E) int { } return -1 } + +func FormatIndonesianDate(t time.Time) string { + monthNames := [...]string{ + "", // dummy index 0 + "Januari", + "Februari", + "Maret", + "April", + "Mei", + "Juni", + "Juli", + "Agustus", + "September", + "Oktober", + "November", + "Desember", + } + + return fmt.Sprintf("%d %s %d", t.Day(), monthNames[int(t.Month())], t.Year()) +} + +func GetLastTwoPathSegments(s string) string { + u, err := url.Parse(s) + var path string + + if err == nil && u.Path != "" { + path = u.Path + } else { + path = s + } + + parts := strings.Split(strings.Trim(path, "/"), "/") + n := len(parts) + + if n >= 2 { + return parts[n-2] + "/" + parts[n-1] + } + + // fallback: return entire string if less than 2 segments + return strings.Trim(path, "/") +} From 8a95d681a06656013ebec8c5a6c758e865bfe8d3 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Tue, 18 Nov 2025 15:34:58 +0700 Subject: [PATCH 9/9] adjustment --- assets/docs/general-consent.html | 12 ++++++------ .../use-case/main-use-case/generate-file/tycovar.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/assets/docs/general-consent.html b/assets/docs/general-consent.html index a3f69826..de84a742 100644 --- a/assets/docs/general-consent.html +++ b/assets/docs/general-consent.html @@ -125,11 +125,11 @@ >
        c) Anggota keluarga saya : - {{ if eq (len .PatientRelatives_Name) 0 }} + {{ if eq (len .Relatives) 0 }} .......................................... {{ else }}
          - {{ range $i, $name := .PatientRelatives_Name }} + {{ range $i, $name := .Relatives }} {{ if lt $i 2 }}
        • {{ $name }}
        • {{ end }} @@ -271,7 +271,7 @@
          - {{ .Responsible_Name }} + {{ .Responsible }}
          @@ -285,7 +285,7 @@
          - {{ .InformationGivenBy_Name }} + {{ .Informant }}
          @@ -299,7 +299,7 @@
          - {{ .Witness1_Name }} + {{ .Witness1 }}
          @@ -313,7 +313,7 @@
          - {{ .Witness2_Name }} + {{ .Witness2 }}
          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 1e16cdb8..f9fdf689 100644 --- a/internal/use-case/main-use-case/generate-file/tycovar.go +++ b/internal/use-case/main-use-case/generate-file/tycovar.go @@ -6,12 +6,12 @@ import ( ) type GeneralConsentPDF struct { - PatientRelatives_Name []string `json:"patientRelatives_name"` - Responsible_Name string `json:"responsible_name"` - InformationGivenBy_Name string `json:"informationGivenBy_name"` - Witness1_Name string `json:"witness1_name"` - Witness2_Name string `json:"witness2_name"` - Date string `json:"date"` + Relatives []string `json:"relatives"` + Responsible string `json:"responsible"` + Informant string `json:"informant"` + Witness1 string `json:"witness1"` + Witness2 string `json:"witness2"` + Date string `json:"date"` } type GenerateDto struct {