package generatefile import ( "encoding/json" "errors" "fmt" "mime" "path/filepath" "time" evs "simrs-vx/internal/domain/bpjs-entities/vclaim-sep" evscl "simrs-vx/internal/domain/bpjs-entities/vclaim-sep-control-letter" ee "simrs-vx/internal/domain/main-entities/encounter" egc "simrs-vx/internal/domain/main-entities/general-consent" er "simrs-vx/internal/domain/main-entities/resume" es "simrs-vx/internal/domain/main-entities/screening" 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" us "simrs-vx/internal/use-case/main-use-case/screening" ercl "simrs-vx/internal/domain/references/clinical" erc "simrs-vx/internal/domain/references/common" docscfg "simrs-vx/internal/infra/docs-cfg" pc "simrs-vx/pkg/conv-helper" pf "simrs-vx/pkg/file-helper" pl "simrs-vx/pkg/logger" pm "simrs-vx/pkg/minio-helper" pp "simrs-vx/pkg/pdf-helper" pu "simrs-vx/pkg/use-case-helper" "gorm.io/gorm" ) // generate temporary file, upload to minio, generate public url, delete temporary file 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, UseA5Lanscape: input.UseA5Lanscape, }); 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, input.UseA5Lanscape) 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 } // 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 := evscl.ResponseForPDF{} if cl.Value != nil { err := json.Unmarshal([]byte(*cl.Value), &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 value to be used") } // get encounter id by vclaim sep number vs, err := uvs.ReadDetailData(evs.ReadDetailDto{Number: cl.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 := generateTemplateData(clData) // 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.IsValidated() { return nil, errors.New("resume is not validated") } if r.FileUrl != nil { if err := removeFile(string(input.EntityType_Code), *r.FileUrl); err != nil { return nil, err } } templateData, err := generateResumeTemplateData(*r, event, tx) if err != nil { return nil, err } input.FormatType = erc.DFTCPDF input.TemplateName = TDNR input.Encounter_Id = r.Encounter_Id // generate file urlPub, err := generateFile(input, templateData) if err != nil { return nil, err } r.FileUrl = &urlPub if err := tx.Save(&r).Error; err != nil { return nil, err } response := ResponseDto{ FileUrl: urlPub, } 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: *resume.Encounter_Id, Includes: includes}, &event) if err != nil { return nil, err } // 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, "") templateData.DiagnosisIn = rData.Assessment.DiagnosisIn templateData.Diagnosis = rData.Diagnosis.SecondaryDiagnosis templateData.MainComplaint = rData.Assessment.MainComplaint templateData.MedicalHistory = rData.Assessment.MedicalHistory templateData.PhysicalExamination = rData.Assessment.PhysicalExamination templateData.SupportingExamination = rData.Supporting.Notes templateData.MedicalActions = rData.Action.AdditionalActions templateData.MedicalAction = rData.Action.MedicalActions templateData.Consultations = rData.Consultation.Consultations templateData.Allergy = rData.Pharmacy.AllergySpecialConditions templateData.ConsciousnessLevel = rData.Discharge.ConsciousnessLevel templateData.BloodPressure = fmt.Sprintf("%.0f/%.0f", rData.Discharge.BloodPressureSystolic, rData.Discharge.BloodPressureDiastolic) templateData.BodyTemperature = rData.Discharge.BodyTemperature templateData.RespirationRate = rData.Discharge.RespirationRate templateData.HeartRate = rData.Discharge.HeartRate templateData.PainScale = rData.Discharge.PainScale templateData.ConditionOnDischarge = rData.Management.ConditionOnDischarge templateData.DischargeMethod = rData.Management.DischargeMethod templateData.Medications = rData.Medication.Medications templateData.Date = pu.GetTimeNow().Format("02-01-2006") // this below is just my guess // templateData.ControlHealthcare = get from vclaim sep eg. III // templateData.ControlUnit = get from vclaim sep eg. R.KERINCI - KLS 3 // templateData.ControlDate = get from vclaim sep eg. 2022-01-01 return &templateData, nil } func generateScreeningTemplateData(screenings []es.Screening, event pl.Event, tx *gorm.DB) (*ScreeningPDF, error) { // get encounter includes := "Patient,Patient.Person" encounter, err := ue.ReadDetailData(ee.ReadDetailDto{Id: *screenings[0].Encounter_Id, Includes: includes}, &event) if err != nil { return nil, err } switch screenings[0].Type { case ercl.SFTCA: // map template data sData := es.FormA{} if screenings[0].Value != nil { err := json.Unmarshal([]byte(*screenings[0].Value), &sData) 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 := ScreeningPDF{} templateData.IssuedDate = pu.GetTimeNow().Format("2006-01-02 15:04:05") templateData.MedicalRecord = *encounter.Patient.Number templateData.Name = encounter.Patient.Person.FullName() templateData.BirthDate = encounter.Patient.Person.BirthDate.Format("2006-01-02 15:04:05") templateData.Employee_Name = screenings[0].Employee.Person.FullName() templateData.EarlyMedic = sData.Screening.SelectedScreeningLabels() templateData.Assessment = sData.AssessmentDetail templateData.ProblemIdentification = sData.ProblemIdentification.SelectedProblemLabels() templateData.Planning = sData.PlanningDetail templateData.Date = screenings[0].CreatedAt.Format("2006-01-02 15:04:05") return &templateData, nil case ercl.SFTCB: // map template data templateData := ScreeningPDF{} templateData.IssuedDate = pu.GetTimeNow().Format("2006-01-02 15:04:05") templateData.MedicalRecord = *encounter.Patient.Number templateData.Name = encounter.Patient.Person.FullName() templateData.BirthDate = encounter.Patient.Person.BirthDate.Format("2006-01-02 15:04:05") for k, v := range screenings { templateData.FormB = append(templateData.FormB, ScreeningFormBPDF{ Number: k + 1, Date: v.CreatedAt.Format("2006-01-02 15:04:05"), Employee_Name: v.Employee.Person.FullName(), Value: *v.Value, }) } return &templateData, nil } return nil, errors.New("invalid screening type") } func generateScreening(input GenerateDto, event pl.Event, tx *gorm.DB) (*ResponseDto, error) { // get value from resume by ref_id includes := "Employee.Person" s, err := us.ReadDetailData(es.ReadDetailDto{Id: uint(*pc.StringToUint64(*input.Ref_Id)), Includes: includes}, &event) if err != nil { return nil, err } var screenings []es.Screening var templateData *ScreeningPDF if s.IsFormA() { if s.FileUrl != nil { if err := removeFile(string(input.EntityType_Code), *s.FileUrl); err != nil { return nil, err } } screenings = append(screenings, *s) input.FormatType = erc.DFTCPDF input.TemplateName = TDNSA input.Encounter_Id = s.Encounter_Id templateData, err = generateScreeningTemplateData(screenings, event, tx) if err != nil { return nil, err } } else { includes := "Employee.Person" sort := "CreatedAt:ASC" ss, _, err := us.ReadListData(es.ReadListDto{FilterDto: es.FilterDto{Encounter_Id: s.Encounter_Id, Type: ercl.SFTCB}, Includes: includes, Sort: sort}, &event, tx) if err != nil { return nil, err } screenings = append(screenings, ss...) input.FormatType = erc.DFTCPDF input.TemplateName = TDNSB input.Encounter_Id = s.Encounter_Id templateData, err = generateScreeningTemplateData(screenings, event, tx) if err != nil { return nil, err } } // generate file urlPub, err := generateFile(input, templateData) if err != nil { return nil, err } if len(screenings) > 1 { for i := range screenings { screenings[i].FileUrl = &urlPub } if err := tx.Save(&screenings).Error; err != nil { return nil, err } } else { s.FileUrl = &urlPub if err := tx.Save(&s).Error; err != nil { return nil, err } } response := ResponseDto{ FileUrl: urlPub, } return &response, nil }