Merge branch 'dev' of https://github.com/dikstub-rssa/simrs-be into feat/sync-from-simx-160

# Conflicts:
#	internal/use-case/main-use-case/generate-file/tycovar.go
This commit is contained in:
vanilia
2025-11-19 16:13:30 +07:00
36 changed files with 1702 additions and 238 deletions
+325
View File
@@ -0,0 +1,325 @@
<html>
<head>
<meta charset="UTF-8" />
<title>General Consent</title>
</head>
<body
style="font-family: Arial, sans-serif; font-size: 12px; line-height: 1.4"
>
<table style="width: 100%; border-collapse: collapse">
<tr>
<td style="text-align: center">
<img src="logo-1.png" alt="logo" style="height: 70px" />
</td>
<td style="text-align: center">
<div style="font-size: 14px; font-weight: bold">
PEMERINTAH PROVINSI JAWA TIMUR
</div>
<div style="font-size: 14px; font-weight: bold">
RUMAH SAKIT UMUM DAERAH Dr. SAIFUL ANWAR
</div>
<div style="font-size: 13px">
TERAKREDITASI KARS VERSI 2012 TINGKAT PARIPURNA
</div>
<div style="margin-top: 8px">
Jl. Jaksa Agung Suprapto No. 2 MALANG 65111
</div>
<div>Telp. (0341) 362101, Fax. (0341) 362110</div>
<div>Email: rsu-drsaifulanwar@jatimprov.go.id</div>
<div>Website: www.rsudsaifulanwar.jatimprov.go.id</div>
</td>
<td style="text-align: center">
<img src="logo-rssa.png" alt="logo" style="height: 70px" />
</td>
</tr>
</table>
<hr style="margin: 20px 0" />
<div
style="
text-align: center;
font-weight: bold;
font-size: 14px;
margin-bottom: 5px;
"
>
FORMULIR PEMBERIAN INFORMASI DAN PERSETUJUAN UMUM
</div>
<div style="text-align: center; font-size: 12px; margin-bottom: 20px">
(GENERAL CONSENT)
</div>
<table style="width: 100%; border-collapse: collapse">
<tr>
<td style="width: 3%; vertical-align: top; padding: 6px 4px">1.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Hak dan Kewajiban sebagai pasien :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">2.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Persetujuan Pelayanan :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">3.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Akses Informasi Kesehatan :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">4.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Rahasia Kedokteran :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">5.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Membuka Rahasia Kedokteran :</b>
Saya setuju untuk membuka rahasia kedokteran terkait dengan kondisi
kesehatan, asuhan dan pengobatan yang saya terima kepada:
<br /><span style="margin-left: 10px"
>a) Dokter dan tenaga kesehatan lain yang turut merawat/memberikan
asuhan kepada saya;</span
>
<br /><span style="margin-left: 10px"
>b) Perusahaan asuransi kesehatan atau perusahaan lainnya atau pihak
lain yang menjamin pembiayaan saya;</span
>
<br /><span style="margin-left: 10px"
>c) Anggota keluarga saya :
{{ if eq (len .Relatives) 0 }}
..........................................
{{ else }}
<ul style="margin:0; padding-left:40px;">
{{ range $i, $name := .Relatives }}
{{ if lt $i 2 }}
<li>{{ $name }}</li>
{{ end }}
{{ end }}
</ul>
{{ end }}</span
>
<br />Saya memahami bahwa pembukaan rahasia ini hanya sejauh yang
diperlukan untuk tujuan perawatan, pembiayaan atau administrasi yang
terkait.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">6.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Privasi :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">7.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Barang Pribadi :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">8.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Pengajuan Keluhan :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">9.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Kewajiban Pembayaran :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">10.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Rumah Sakit Pendidikan :</b>
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).
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">11.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Selama Dalam Perawatan :</b>
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.
</td>
</tr>
<tr>
<td style="vertical-align: top; padding: 6px 4px">12.</td>
<td style="vertical-align: top; padding: 6px 4px">
<b>Penegasan Kepercayaan :</b>
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.
</td>
</tr>
</table>
<br /><br />
<div style="margin-top: 20px">
Saya menyetujui setiap pernyataan dalam formulir ini dan menandatangani
tanpa paksaan.
</div>
<table style="width: 100%; margin-top: 40px; text-align: center">
<tr>
<td>
Malang, {{ .Date }}
</td>
</tr>
</table>
<table
style="
width: 100%;
margin-top: 40px;
text-align: center;
border-collapse: collapse;
"
>
<tr style="height:160px; vertical-align:top;">
<td style="text-align:center; padding:0 10px;">
<div style="margin-top:10px; height:45px;">
Pasien/keluarga/<br>penanggung jawab
</div>
<div style="margin-top:20px; margin-bottom:20px;">
.......................................
</div>
<div style="height:20px;">
{{ .Responsible }}
</div>
</td>
<td style="text-align:center; padding:0 10px;">
<div style="margin-top:10px; height:45px;">
Pemberi Informasi
</div>
<div style="margin-top:20px; margin-bottom:20px;">
.......................................
</div>
<div style="height:20px;">
{{ .Informant }}
</div>
</td>
<td style="text-align:center; padding:0 10px;">
<div style="margin-top:10px; height:45px;">
Saksi I
</div>
<div style="margin-top:20px; margin-bottom:20px;">
.......................................
</div>
<div style="height:20px;">
{{ .Witness1 }}
</div>
</td>
<td style="text-align:center; padding:0 10px;">
<div style="margin-top:10px; height:45px;">
Saksi II
</div>
<div style="margin-top:20px; margin-bottom:20px;">
.......................................
</div>
<div style="height:20px;">
{{ .Witness2 }}
</div>
</td>
</tr>
</table>
</body>
</html>
+4 -1
View File
@@ -71,4 +71,7 @@ bpjsCfg:
syncUrlCfg:
enable: false
host:
prefix: new-to-old
prefix: new-to-old
docsCfg:
path: ../../assets/docs/
+1
View File
@@ -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
+2
View File
@@ -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=
@@ -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"`
@@ -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"`
@@ -0,0 +1,64 @@
package generalconsent
import (
ecore "simrs-vx/internal/domain/base-entities/core"
)
type CreateDto struct {
Encounter_Id *uint `json:"encounter_id"`
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
}
@@ -1,4 +1,4 @@
package general_consent
package generalconsent
import (
"simrs-vx/internal/domain/base-entities/core"
+2 -2
View File
@@ -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"
)
@@ -69,7 +69,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:"-"`
@@ -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 {
@@ -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)
}
@@ -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)
}
+17
View File
@@ -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
}
@@ -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)
}
@@ -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)
}
@@ -17,6 +17,8 @@ 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"
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"
@@ -34,7 +36,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"
@@ -57,6 +59,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 +134,7 @@ func SetRoutes() http.Handler {
a.RegisterExtCall(ibpjs.SetConfig)
a.RegisterExtCall(validation.RegisterValidation)
a.RegisterExtCall(simgossync.SetConfig)
a.RegisterExtCall(docscfg.ParseCfg)
r := http.NewServeMux()
@@ -274,8 +278,10 @@ 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)
hc.RegCrud(r, "/v1/general-consent", generalconsent.O)
r.HandleFunc("POST /v1/generate-file", generatefile.Generate)
/******************** actor ********************/
hc.RegCrud(r, "/v1/person", person.O)
@@ -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,
@@ -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 == "":
@@ -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
}
@@ -0,0 +1,295 @@
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"
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
}
// check if encounter is done
if ue.IsDone(*input.Encounter_Id, &event, tx) {
return errors.New("encounter is already done")
}
if resData, err := CreateData(input, &event, tx); err != nil {
return err
} else {
data = *resData
}
mwRunner.setMwType(pu.MWTPost)
// Run post-middleware
if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil {
return err
}
pl.SetLogInfo(&event, nil, "complete")
return nil
})
if err != nil {
return nil, err
}
return &d.Data{
Meta: d.II{
"source": source,
"structure": "single-data",
"status": "created",
},
Data: data.ToResponse(),
}, nil
}
func ReadList(input e.ReadListDto) (*d.Data, error) {
var data *e.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
}
// check if encounter is done
if ue.IsDone(*input.Encounter_Id, &event, tx) {
return errors.New("encounter is already done")
}
if err := UpdateData(input, data, &event, tx); err != nil {
return err
}
// Get Updated Data
if data, err = ReadDetailData(rdDto, &event, tx); err != nil {
return err
}
pl.SetLogInfo(&event, nil, "complete")
mwRunner.setMwType(pu.MWTPost)
// Run post-middleware
if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return &d.Data{
Meta: d.IS{
"source": source,
"structure": "single-data",
"status": "updated",
},
Data: data.ToResponse(),
}, nil
}
func Delete(input e.DeleteDto) (*d.Data, error) {
rdDto := e.ReadDetailDto{Id: uint(input.Id)}
var data *e.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
}
@@ -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
}
@@ -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
}
@@ -1,12 +1,11 @@
package upload
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"
e "simrs-vx/internal/domain/main-entities/upload"
)
type middlewareRunner struct {
@@ -24,7 +23,7 @@ func newMiddlewareRunner(event *pl.Event, tx *gorm.DB) *middlewareRunner {
}
// ExecuteCreateMiddleware executes create middleware
func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data interface{}) error {
func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e.CreateDto, data *e.GeneralConsent) error {
for _, middleware := range middlewares {
logData := pu.GetLogData(input, data)
@@ -39,7 +38,7 @@ func (me *middlewareRunner) RunCreateMiddleware(middlewares []createMw, input *e
return nil
}
func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data interface{}) error {
func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, input *e.ReadListDto, data *e.GeneralConsent) error {
for _, middleware := range middlewares {
logData := pu.GetLogData(input, data)
@@ -54,7 +53,7 @@ func (me *middlewareRunner) RunReadListMiddleware(middlewares []readListMw, inpu
return nil
}
func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data interface{}) error {
func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.GeneralConsent) error {
for _, middleware := range middlewares {
logData := pu.GetLogData(input, data)
@@ -69,7 +68,7 @@ func (me *middlewareRunner) RunReadDetailMiddleware(middlewares []readDetailMw,
return nil
}
func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data interface{}) error {
func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.GeneralConsent) error {
for _, middleware := range middlewares {
logData := pu.GetLogData(input, data)
@@ -84,7 +83,7 @@ func (me *middlewareRunner) RunUpdateMiddleware(middlewares []readDetailMw, inpu
return nil
}
func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data interface{}) error {
func (me *middlewareRunner) RunDeleteMiddleware(middlewares []readDetailMw, input *e.ReadDetailDto, data *e.GeneralConsent) error {
for _, middleware := range middlewares {
logData := pu.GetLogData(input, data)
@@ -1,4 +1,4 @@
package upload
package generalconsent
// example of middleware
// func init() {
@@ -6,27 +6,27 @@ 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
package generalconsent
import (
"gorm.io/gorm"
e "simrs-vx/internal/domain/main-entities/upload"
e "simrs-vx/internal/domain/main-entities/general-consent"
)
type createMw struct {
Name string
Func func(input *e.CreateDto, data interface{}, tx *gorm.DB) error
Func func(input *e.CreateDto, data *e.GeneralConsent, tx *gorm.DB) error
}
type readListMw struct {
Name string
Func func(input *e.ReadListDto, data interface{}, tx *gorm.DB) error
Func func(input *e.ReadListDto, data *e.GeneralConsent, tx *gorm.DB) error
}
type readDetailMw struct {
Name string
Func func(input *e.ReadDetailDto, data interface{}, tx *gorm.DB) error
Func func(input *e.ReadDetailDto, data *e.GeneralConsent, tx *gorm.DB) error
}
type UpdateMw = readDetailMw
@@ -0,0 +1,94 @@
package generatefile
import (
"encoding/json"
"errors"
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"
pu "simrs-vx/pkg/use-case-helper"
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"
)
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(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 {
err := json.Unmarshal([]byte(*gc.Value), &templateData)
if err != nil {
event.ErrInfo = pl.ErrorInfo{
Code: "data-unmarshal-fail",
Detail: err.Error(),
Raw: err,
}
return nil, err
}
} else {
return nil, errors.New("no value in this general consent")
}
input.FormatType = erc.DFTCPDF
input.TemplateName = TDNGC
input.Encounter_Id = gc.Encounter_Id
templateData.Date = pu.FormatIndonesianDate(gc.CreatedAt)
// generate file
urlPub, err := generateFile(input, templateData)
if err != nil {
return nil, err
}
gc.FileUrl = &urlPub
if err := 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")
}
}
@@ -0,0 +1,94 @@
package generatefile
import (
"errors"
"fmt"
"mime"
"path/filepath"
"time"
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("./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)
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("%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,
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
}
func removeFile(bucket, fileUrl string) error {
fPath := pu.GetLastTwoPathSegments(fileUrl)
err := pm.I.RemoveObject(bucket, fPath)
if err != nil {
return err
}
return nil
}
@@ -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
}
@@ -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]
@@ -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"`
@@ -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
}
+153
View File
@@ -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
}
+33
View File
@@ -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
}
+131
View File
@@ -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", "<br>", -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
}
+1 -1
View File
@@ -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) {
+42
View File
@@ -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, "/")
}