This commit is contained in:
dpurbosakti
2025-11-25 15:52:07 +07:00
parent 250b446c37
commit 8cf750c63f
8 changed files with 305 additions and 131 deletions

No files matched your search

@@ -10,6 +10,7 @@ import (
epi "simrs-vx/internal/domain/main-entities/person-insurance" epi "simrs-vx/internal/domain/main-entities/person-insurance"
epr "simrs-vx/internal/domain/main-entities/person-relative" epr "simrs-vx/internal/domain/main-entities/person-relative"
er "simrs-vx/internal/domain/main-entities/regency" er "simrs-vx/internal/domain/main-entities/regency"
"strings"
erp "simrs-vx/internal/domain/references/person" erp "simrs-vx/internal/domain/references/person"
@@ -58,3 +59,62 @@ func (d Person) IsSameResidentIdentityNumber(input *string) bool {
} }
return d.ResidentIdentityNumber == input return d.ResidentIdentityNumber == input
} }
func (d Person) GenderString() string {
if d.Gender_Code == nil {
return ""
}
switch *d.Gender_Code {
case erp.GCMale:
return "Laki-laki(L)"
case erp.GCFemale:
return "Perempuan(P)"
default:
return ""
}
}
func (d Person) GetPhoneNumber() string {
if d.Contacts == nil {
return ""
}
for _, c := range *d.Contacts {
if c.Type_Code == erp.CTPhone || c.Type_Code == erp.CTMPhone {
return c.Value
}
}
return ""
}
func (d Person) FullName() string {
name := strings.TrimSpace(d.Name)
if name == "" {
return ""
}
parts := []string{}
// Front title (dr., drs., etc)
if d.FrontTitle != nil {
ft := strings.TrimSpace(*d.FrontTitle)
if ft != "" {
parts = append(parts, ft)
}
}
// Name (always included)
parts = append(parts, name)
// Join front title + name
full := strings.Join(parts, " ")
// End title → attach with comma
if d.EndTitle != nil {
et := strings.TrimSpace(*d.EndTitle)
if et != "" {
full = full + ", " + et
}
}
return full
}
+2 -1
View File
@@ -29,7 +29,8 @@ type FilterDto struct {
} }
type ReadDetailDto struct { type ReadDetailDto struct {
Id uint `json:"id"` Id uint `json:"id"`
Includes string `json:"includes"`
} }
type UpdateDto struct { type UpdateDto struct {
@@ -1,26 +1,14 @@
package generatefile package generatefile
import ( import (
"encoding/json"
"errors" "errors"
evs "simrs-vx/internal/domain/bpjs-entities/vclaim-sep"
evscl "simrs-vx/internal/domain/bpjs-entities/vclaim-sep-control-letter"
egc "simrs-vx/internal/domain/main-entities/general-consent"
uvs "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep"
uvscl "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep-control-letter"
ugc "simrs-vx/internal/use-case/main-use-case/general-consent"
pc "simrs-vx/pkg/conv-helper"
pl "simrs-vx/pkg/logger" pl "simrs-vx/pkg/logger"
pu "simrs-vx/pkg/use-case-helper"
dg "github.com/karincake/apem/db-gorm-pg" dg "github.com/karincake/apem/db-gorm-pg"
d "github.com/karincake/dodol" d "github.com/karincake/dodol"
"gorm.io/gorm" "gorm.io/gorm"
erc "simrs-vx/internal/domain/references/common"
ere "simrs-vx/internal/domain/references/encounter" ere "simrs-vx/internal/domain/references/encounter"
) )
@@ -32,133 +20,37 @@ func Generate(input GenerateDto) (*d.Data, error) {
Source: source, Source: source,
} }
var response ResponseDto var (
response *ResponseDto
err error
)
// Start log // Start log
pl.SetLogInfo(&event, input, "started", "create") pl.SetLogInfo(&event, input, "started", "create")
err := dg.I.Transaction(func(tx *gorm.DB) error { err = dg.I.Transaction(func(tx *gorm.DB) error {
switch input.Type_Code { switch input.Type_Code {
// general-consent // general-consent
case ere.DTCGC: case ere.DTCGC:
// get value from general consent by ref_id response, err = generateGC(input, event, tx)
gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id))}, &event)
if err != nil { if err != nil {
return err return err
} }
if gc.FileUrl != nil {
if err := removeFile(string(input.EntityType_Code), *gc.FileUrl); err != nil {
return err
}
}
// map template data
templateData := GeneralConsentPDF{}
if gc.Value != nil {
err := json.Unmarshal([]byte(*gc.Value), &templateData)
if err != nil {
event.ErrInfo = pl.ErrorInfo{
Code: "data-unmarshal-fail",
Detail: err.Error(),
Raw: err,
}
return err
}
} else {
return errors.New("no value in this general consent")
}
input.FormatType = erc.DFTCPDF
input.TemplateName = TDNGC
input.Encounter_Id = gc.Encounter_Id
templateData.Date = pu.FormatIndonesianDate(gc.CreatedAt)
// generate file
urlPub, err := generateFile(input, templateData)
if err != nil {
return err
}
gc.FileUrl = &urlPub
if err := dg.I.Save(&gc).Error; err != nil {
return err
}
response = ResponseDto{
FileUrl: urlPub,
}
// control-letter // control-letter
case ere.DTCVSCL: case ere.DTCVSCL:
// get value from control letter by ref_id response, err = generateCL(input, event, tx)
cl, err := uvscl.ReadDetailData(evscl.ReadDetailDto{Number: input.Ref_Id}, &event)
if err != nil {
if !pu.IsDataNotFoundError(err) {
return err
}
}
if cl != nil && cl.FileUrl != nil {
if err := removeFile(string(input.EntityType_Code), *cl.FileUrl); err != nil {
return err
}
}
// map template data
clData := VclaimControlLetter{}
if input.Data != nil {
err := json.Unmarshal([]byte(*input.Data), &clData)
if err != nil {
event.ErrInfo = pl.ErrorInfo{
Code: "data-unmarshal-fail",
Detail: err.Error(),
Raw: err,
}
return err
}
} else {
return errors.New("there is no data to be used")
}
if cl == nil {
createCL := evscl.CreateDto{
VclaimSep_Number: &clData.VclaimSep.Number,
Number: &clData.Number,
Value: input.Data,
}
if cl, err = uvscl.CreateData(createCL, &event, tx); err != nil {
return err
}
}
// get encounter id by vclaim sep number
vs, err := uvs.ReadDetailData(evs.ReadDetailDto{Number: &clData.VclaimSep.Number}, &event)
if err != nil { if err != nil {
return err return err
} }
input.FormatType = erc.DFTCPDF // resume
input.TemplateName = TDNCL case ere.DTCResume:
input.Encounter_Id = vs.Encounter_Id response, err = generateResume(input, event, tx)
input.UseA5Lanscape = true
templateData := clData.generateTemplateData()
// generate file
urlPub, err := generateFile(input, templateData)
if err != nil { if err != nil {
return err return err
} }
cl.FileUrl = &urlPub
if err := tx.Save(&cl).Error; err != nil {
return err
}
response = ResponseDto{
FileUrl: urlPub,
}
default: default:
return errors.New("invalid type code") return errors.New("invalid type code")
} }
@@ -1,19 +1,36 @@
package generatefile package generatefile
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"mime" "mime"
"path/filepath" "path/filepath"
"time" "time"
evs "simrs-vx/internal/domain/bpjs-entities/vclaim-sep"
evscl "simrs-vx/internal/domain/bpjs-entities/vclaim-sep-control-letter"
ee "simrs-vx/internal/domain/main-entities/encounter"
egc "simrs-vx/internal/domain/main-entities/general-consent"
er "simrs-vx/internal/domain/main-entities/resume"
uvs "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep"
uvscl "simrs-vx/internal/use-case/bpjs-use-case/vclaim-sep-control-letter"
ue "simrs-vx/internal/use-case/main-use-case/encounter"
ugc "simrs-vx/internal/use-case/main-use-case/general-consent"
ur "simrs-vx/internal/use-case/main-use-case/resume"
erc "simrs-vx/internal/domain/references/common" erc "simrs-vx/internal/domain/references/common"
docscfg "simrs-vx/internal/infra/docs-cfg" docscfg "simrs-vx/internal/infra/docs-cfg"
pc "simrs-vx/pkg/conv-helper" pc "simrs-vx/pkg/conv-helper"
pf "simrs-vx/pkg/file-helper" pf "simrs-vx/pkg/file-helper"
pl "simrs-vx/pkg/logger"
pm "simrs-vx/pkg/minio-helper" pm "simrs-vx/pkg/minio-helper"
pp "simrs-vx/pkg/pdf-helper" pp "simrs-vx/pkg/pdf-helper"
pu "simrs-vx/pkg/use-case-helper" pu "simrs-vx/pkg/use-case-helper"
"gorm.io/gorm"
) )
// generate temporary file, upload to minio, generate public url, delete temporary file // generate temporary file, upload to minio, generate public url, delete temporary file
@@ -93,3 +110,189 @@ func removeFile(bucket, fileUrl string) error {
} }
return nil return nil
} }
// generate general consent
func generateGC(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) {
// get value from general consent by ref_id
gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id))}, &event)
if err != nil {
return nil, err
}
if gc.FileUrl != nil {
if err := removeFile(string(input.EntityType_Code), *gc.FileUrl); err != nil {
return nil, err
}
}
// map template data
templateData := GeneralConsentPDF{}
if gc.Value != nil {
err := json.Unmarshal([]byte(*gc.Value), &templateData)
if err != nil {
event.ErrInfo = pl.ErrorInfo{
Code: "data-unmarshal-fail",
Detail: err.Error(),
Raw: err,
}
return nil, err
}
} else {
return nil, errors.New("no value in this general consent")
}
input.FormatType = erc.DFTCPDF
input.TemplateName = TDNGC
input.Encounter_Id = gc.Encounter_Id
templateData.Date = pu.FormatIndonesianDate(gc.CreatedAt)
// generate file
urlPub, err := generateFile(input, templateData)
if err != nil {
return nil, err
}
gc.FileUrl = &urlPub
if err := tx.Save(&gc).Error; err != nil {
return nil, err
}
response := ResponseDto{
FileUrl: urlPub,
}
return &response, nil
}
func generateCL(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) {
// get value from control letter by ref_id
cl, err := uvscl.ReadDetailData(evscl.ReadDetailDto{Number: input.Ref_Id}, &event)
if err != nil {
if !pu.IsDataNotFoundError(err) {
return nil, err
}
}
if cl != nil && cl.FileUrl != nil {
if err := removeFile(string(input.EntityType_Code), *cl.FileUrl); err != nil {
return nil, err
}
}
// map template data
clData := VclaimControlLetter{}
if input.Data != nil {
err := json.Unmarshal([]byte(*input.Data), &clData)
if err != nil {
event.ErrInfo = pl.ErrorInfo{
Code: "data-unmarshal-fail",
Detail: err.Error(),
Raw: err,
}
return nil, err
}
} else {
return nil, errors.New("there is no data to be used")
}
if cl == nil {
createCL := evscl.CreateDto{
VclaimSep_Number: &clData.VclaimSep.Number,
Number: &clData.Number,
Value: input.Data,
}
if cl, err = uvscl.CreateData(createCL, &event, tx); err != nil {
return nil, err
}
}
// get encounter id by vclaim sep number
vs, err := uvs.ReadDetailData(evs.ReadDetailDto{Number: &clData.VclaimSep.Number}, &event)
if err != nil {
return nil, err
}
input.FormatType = erc.DFTCPDF
input.TemplateName = TDNCL
input.Encounter_Id = vs.Encounter_Id
input.UseA5Lanscape = true
templateData := clData.generateTemplateData()
// generate file
urlPub, err := generateFile(input, templateData)
if err != nil {
return nil, err
}
cl.FileUrl = &urlPub
if err := tx.Save(&cl).Error; err != nil {
return nil, err
}
response := ResponseDto{
FileUrl: urlPub,
}
return &response, nil
}
func generateResume(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) {
// get value from resume by ref_id
includes := "Doctor.Employee.Person"
r, err := ur.ReadDetailData(er.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id)), Includes: includes}, &event)
if err != nil {
return nil, err
}
if r.FileUrl != nil {
if err := removeFile(string(input.EntityType_Code), *r.FileUrl); err != nil {
return nil, err
}
}
response := ResponseDto{
FileUrl: "",
}
return &response, nil
}
func generateResumeTemplateData(resume er.Resume, event pl.Event, tx *gorm.DB) (*ResumePDF, error) {
// get encounter
includes := "Patient,Patient.Person,Patient.Person.BirthRegency,Patient.Person.Contacts"
encounter, err := ue.ReadDetailData(ee.ReadDetailDto{Id: uint16(*resume.Encounter_Id), Includes: includes}, &event)
if err != nil {
return nil, err
}
fmt.Println(encounter)
// map template data
rData := er.ValueDto{}
if resume.Value != nil {
err := json.Unmarshal([]byte(*resume.Value), &rData)
if err != nil {
event.ErrInfo = pl.ErrorInfo{
Code: "data-unmarshal-fail",
Detail: err.Error(),
Raw: err,
}
return nil, err
}
} else {
return nil, errors.New("there is no data to be used")
}
templateData := ResumePDF{}
templateData.MedicalRecord = *encounter.Patient.Number
templateData.NIK = *encounter.Patient.Person.ResidentIdentityNumber
templateData.Name = encounter.Patient.Person.Name
templateData.BirthPlaceDate = pu.FormatPlaceAndDate(encounter.Patient.Person.BirthRegency.Name, *encounter.Patient.Person.BirthDate)
templateData.Gender = encounter.Patient.Person.GenderString()
templateData.Phone = encounter.Patient.Person.GetPhoneNumber()
// templateData.Class = get from vclaim sep eg. III
// templateData.Unit = get from vclaim sep eg. R.KERINCI - KLS 3
templateData.Doctor_Name = resume.Doctor.Employee.Person.FullName()
templateData.StartedAt = *pc.TimeToStringWithFormat(encounter.StartedAt, "")
templateData.FinishedAt = *pc.TimeToStringWithFormat(encounter.FinishedAt, "")
return &templateData, nil
}
@@ -44,8 +44,8 @@ type ResumePDF struct {
Class string Class string
Unit string Unit string
Doctor_Name string Doctor_Name string
StartedDate string StartedAt string
FinishedDate string FinishedAt string
DiagnosisIn string DiagnosisIn string
DiagnosisOut string DiagnosisOut string
Diagnosis []er.DiagnosisEntry Diagnosis []er.DiagnosisEntry
@@ -81,7 +81,7 @@ func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e
tx = dg.I tx = dg.I
} }
if err := tx.First(&data, input.Id).Error; err != nil { if err := tx.Scopes(gh.Preload(input.Includes)).First(&data, input.Id).Error; err != nil {
if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil { if processedErr := pu.HandleReadError(err, event, source, input.Id, data); processedErr != nil {
return nil, processedErr return nil, processedErr
} }
+17 -9
View File
@@ -8,6 +8,8 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
const StandartDateTimeFormat = "2006-01-02 15:04:05"
// check string pointer, if nil return default value // check string pointer, if nil return default value
func StrConvDefault(f *string, def string) string { func StrConvDefault(f *string, def string) string {
if f == nil { if f == nil {
@@ -96,20 +98,12 @@ func BoolToString(f *bool) *string {
return &t 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 // Handling gorm.DeletedAt
func DeletedAtToString(deletedAt *gorm.DeletedAt) *string { func DeletedAtToString(deletedAt *gorm.DeletedAt) *string {
if deletedAt == nil || !deletedAt.Valid { if deletedAt == nil || !deletedAt.Valid {
return nil return nil
} }
return TimeToString(&deletedAt.Time) return TimeToStringWithFormat(&deletedAt.Time, "")
} }
func BoolToFloat64(b bool) float64 { func BoolToFloat64(b bool) float64 {
@@ -151,3 +145,17 @@ func StringToUint64(s string) *uint64 {
} }
return &u return &u
} }
func TimeToStringWithFormat(t *time.Time, format string) *string {
result := ""
if t == nil || t.IsZero() {
return &result
}
if format == "" {
format = StandartDateTimeFormat
}
result = t.Format(format)
return &result
}
+10
View File
@@ -208,3 +208,13 @@ func GetLastTwoPathSegments(s string) string {
// fallback: return entire string if less than 2 segments // fallback: return entire string if less than 2 segments
return strings.Trim(path, "/") return strings.Trim(path, "/")
} }
func FormatPlaceAndDate(place string, t time.Time) string {
// Ensure place is uppercase
place = strings.ToUpper(strings.TrimSpace(place))
// Format date: DD-MM-YYYY
dateStr := t.Format("02-01-2006")
return place + ", " + dateStr
}