feat (generate-file): general-consent pdf done

This commit is contained in:
dpurbosakti
2025-11-18 14:44:19 +07:00
parent 6ab240b8d5
commit 0d340553b3
8 changed files with 299 additions and 23 deletions
+64 -18
View File
@@ -125,11 +125,11 @@
>
<br /><span style="margin-left: 10px"
>c) Anggota keluarga saya :
{{ if eq (len .FamilyMembers) 0 }}
{{ if eq (len .PatientRelatives_Name) 0 }}
..........................................
{{ else }}
<ul style="margin:0; padding-left:20px;">
{{ range $i, $name := .FamilyMembers }}
<ul style="margin:0; padding-left:40px;">
{{ range $i, $name := .PatientRelatives_Name }}
{{ if lt $i 2 }}
<li>{{ $name }}</li>
{{ end }}
@@ -247,7 +247,7 @@
<table style="width: 100%; margin-top: 40px; text-align: center">
<tr>
<td>
Malang, ............................................................
Malang, {{ .Date }}
</td>
</tr>
</table>
@@ -260,20 +260,66 @@
border-collapse: collapse;
"
>
<tr>
<td>
Pasien/keluarga/<br>penanggung jawab<br /><br /><br />.......................................<br />{{ .Responsible_Name }}
</td>
<td>
Pemberi Informasi<br /><br /><br />.......................................<br />{{ .InformationGivenBy_Name }}
</td>
<td>
Saksi I<br /><br /><br />.......................................<br /> {{ .Witness1_Name }}
</td>
<td>
Saksi II<br /><br /><br />.......................................<br /> {{ .Witness2_Name }}
</td>
</tr>
<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_Name }}
</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;">
{{ .InformationGivenBy_Name }}
</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_Name }}
</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_Name }}
</div>
</td>
</tr>
</table>
</body>
</html>
@@ -5,7 +5,7 @@ import (
)
type CreateDto struct {
Encounter_Id *uint `json:"-"`
Encounter_Id *uint `json:"encounter_id"`
Value *string `json:"value"`
}
@@ -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
}
@@ -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)
@@ -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
}
@@ -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 {
+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
}
+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, "/")
}