Files
simrsx-be/internal/use-case/main-use-case/encounter/lib.go
T
2025-11-04 11:11:24 +07:00

582 lines
15 KiB
Go

package encounter
import (
// std
"errors"
"fmt"
erc "simrs-vx/internal/domain/references/clinical"
ere "simrs-vx/internal/domain/references/encounter"
ero "simrs-vx/internal/domain/references/organization"
"strings"
// external
dg "github.com/karincake/apem/db-gorm-pg"
gh "github.com/karincake/getuk"
"gorm.io/gorm"
// pkg
plh "simrs-vx/pkg/lib-helper"
pl "simrs-vx/pkg/logger"
pu "simrs-vx/pkg/use-case-helper"
// internal
eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist"
ea "simrs-vx/internal/domain/main-entities/ambulatory"
e "simrs-vx/internal/domain/main-entities/encounter"
er "simrs-vx/internal/domain/main-entities/rehab"
erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist"
es "simrs-vx/internal/domain/main-entities/soapi"
)
func CreateData(input e.CreateDto, event *pl.Event, dbx ...*gorm.DB) (*e.Encounter, error) {
pl.SetLogInfo(event, nil, "started", "DBCreate")
data := e.Encounter{}
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.Encounter, *e.MetaDto, error) {
if input.AuthInfo.User_Id == 0 {
return nil, nil, plh.HandleListError(input, event, errors.New("user_id is required"))
}
pl.SetLogInfo(event, input, "started", "DBReadList")
data := []e.Encounter{}
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.Encounter{})
if input.AuthInfo.Doctor_Id != nil {
tx.Where("\"Responsible_Doctor_Id\" = ?", *input.AuthInfo.Doctor_Id)
}
tx.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 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.Encounter, error) {
pl.SetLogInfo(event, input, "started", "DBReadDetail")
data := e.Encounter{}
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
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 {
return nil, processedErr
}
}
pl.SetLogInfo(event, nil, "complete")
return &data, nil
}
func UpdateData(input e.UpdateDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, data, "started", "DBUpdate")
setDataUpdate(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.Encounter, 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
}
func updateDischargeData(input e.DischargeDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, data, "started", "DBUpdateDischarge")
setDataDischarge(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 IsDone(encounter_id uint, event *pl.Event, dbx ...*gorm.DB) bool {
pl.SetLogInfo(event, nil, "started", "DBIsDone")
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
var data e.Encounter
if err := tx.Where("\"Id\" = ?", encounter_id).First(&data).Error; err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "data-get-fail",
Detail: "Database get failed",
Raw: err,
}
return false
}
pl.SetLogInfo(event, nil, "complete")
return data.IsDone()
}
func UpdateStatusData(input e.UpdateStatusDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, data, "started", "DBUpdate")
setDataUpdateStatus(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 updateCheckInData(input e.CheckinDto, data *e.Encounter, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, data, "started", "DBUpdate")
setDataCheckIn(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 updateLatestResponsibleDoctorHist(input e.CheckinDto, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, "started", "DBUpdate")
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
subQuery := tx.
Select("\"Id\"").
Model(&erdh.ResponsibleDoctorHist{}).
Where("\"Encounter_Id\" = ?", input.Id).
Order("\"CreatedAt\" DESC").
Limit(1)
result := tx.
Model(&erdh.ResponsibleDoctorHist{}).
Where("\"Id\" = (?)", subQuery).
Update("\"FinishedAt\"", input.StartedAt)
if result.Error != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "data-update-fail",
Detail: "Database update failed",
Raw: result.Error,
}
return pl.SetLogError(event, input)
}
if result.RowsAffected == 0 {
pl.SetLogInfo(event, input, "no previous data found to update")
return nil
}
pl.SetLogInfo(event, nil, "complete")
return nil
}
func updateLatestAdmEmployeeHist(input e.CheckinDto, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, "started", "DBUpdate")
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
subQuery := tx.
Select("\"Id\"").
Model(&eaeh.AdmEmployeeHist{}).
Where("\"Encounter_Id\" = ?", input.Id).
Order("\"CreatedAt\" DESC").
Limit(1)
result := tx.
Model(&eaeh.AdmEmployeeHist{}).
Where("\"Id\" = (?)", subQuery).
Update("\"FinishedAt\"", input.StartedAt)
if result.Error != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "data-update-fail",
Detail: "Database update failed",
Raw: result.Error,
}
return pl.SetLogError(event, input)
}
if result.RowsAffected == 0 {
pl.SetLogInfo(event, input, "no previous data found to update")
return nil
}
pl.SetLogInfo(event, nil, "complete")
return nil
}
func verifyAllocatedVisitCount(i e.CreateDto, event *pl.Event) (e.Encounter, bool, error) {
pl.SetLogInfo(event, nil, "started", "DBGetRecentEncounterAdm")
var (
tx = dg.I
recentEncounterAdm e.Encounter
countEncounterSeries int64
)
err := tx.
Scopes(gh.Preload("Rehab,Responsible_Doctor")).
Joins("JOIN \"Ambulatory\" ON \"Ambulatory\".\"Encounter_Id\" = \"Encounter\".\"Id\"").
Where("\"Patient_Id\" = ?", i.Patient_Id).
Where("\"Ambulatory\".\"Class_Code\" = ? AND \"Ambulatory\".\"VisitMode_Code\" = ?", ere.ACCRme, ere.VMCAdm).
Order("\"CreatedAt\" DESC").
First(&recentEncounterAdm).Error
if err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "read-recentEncounter-fail",
Detail: "Database read failed",
Raw: err,
}
return e.Encounter{}, false, pl.SetLogError(event, i)
}
err = tx.
Debug().
Model(&e.Encounter{}).
Joins("JOIN \"Ambulatory\" ON \"Ambulatory\".\"Encounter_Id\" = \"Encounter\".\"Id\"").
Where("\"Patient_Id\" = ?", i.Patient_Id).
Where("\"Ambulatory\".\"Class_Code\" = ? AND \"Ambulatory\".\"VisitMode_Code\" = ?", ere.ACCRme, ere.VMCSeries).
Where("\"Encounter\".\"CreatedAt\" > ?", recentEncounterAdm.CreatedAt).
Count(&countEncounterSeries).Error
if err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "read-countEncounter-fail",
Detail: "Database read failed",
Raw: err,
}
return e.Encounter{}, false, pl.SetLogError(event, i)
}
return recentEncounterAdm, countEncounterSeries < int64(*recentEncounterAdm.Rehab.AllocatedVisitCount), nil
}
func getSoapiEncounterAdm(enc e.Encounter, event *pl.Event) (dataSoapi []es.CreateDto, err error) {
var data []es.Soapi
pl.SetLogInfo(event, enc, "started", "DBReadList")
if enc.Responsible_Doctor == nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "no responsible-doctor found",
Detail: "Encounter does not have responsible-doctor",
}
}
err = dg.I.Debug().
Model(&es.Soapi{}).
Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\"").
Where("\"Encounter_Id\" = ?", enc.Id).
Where("\"Employee\".\"Position_Code\" = ?", ero.EPCDoc).
Where("\"Soapi\".\"TypeCode\" IN ?", []erc.SoapiTypeCode{erc.STCEEarlyMedic, erc.STCFunc}).
Where("\"Soapi\".\"Employee_Id\" = ?", *enc.Responsible_Doctor.Employee_Id).
Find(&data).Error
if err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Raw: err,
}
if errors.Is(err, gorm.ErrRecordNotFound) {
event.ErrInfo.Code = "data-not-found"
event.ErrInfo.Detail = "Data not found"
return nil, pl.SetLogError(event, enc)
}
event.ErrInfo.Code = "read-fail"
event.ErrInfo.Detail = "Database read failed"
return nil, pl.SetLogError(event, enc)
}
pl.SetLogInfo(event, nil, "complete")
for _, s := range data {
// set data soapi for copy
dataSoapi = append(dataSoapi, es.CreateDto{
Employee_Id: s.Employee_Id,
Time: s.Time,
TypeCode: s.TypeCode,
Value: s.Value,
})
}
if len(dataSoapi) < 2 {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "missing-soapi",
Detail: fmt.Sprintf("Missing required Soapi types"),
}
return nil, pl.SetLogError(event, enc)
}
return
}
func getSoapiByTypeCode(encounterId uint, event *pl.Event, mode string) (err error) {
pl.SetLogInfo(event, encounterId, "started", "DBReadList")
var (
dataAmbulatory ea.Ambulatory
dataSoapi []es.Soapi
)
// Get Data Ambulatory
if err = dg.I.
Where("\"Encounter_Id\" = ?", encounterId).
First(&dataAmbulatory).Error; err != nil {
return setDBError(event, err, encounterId)
}
// Set Query for get data Soapi
tx := dg.I.
Model(&es.Soapi{}).
Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\"").
Where("\"Encounter_Id\" = ?", encounterId).
Where("\"Employee\".\"Position_Code\" = ?", ero.EPCDoc)
// Set Case
switch {
case dataAmbulatory.Class_Code == ere.ACCReg:
tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", erc.STCEEarlyMedic)
case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCAdm:
tx = tx.Where("\"Soapi\".\"TypeCode\" IN ?", []erc.SoapiTypeCode{erc.STCEEarlyMedic, erc.STCFunc, erc.STCEarlyRehab})
case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCSeries:
tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", erc.STCEarlyRehab)
}
if err = tx.Find(&dataSoapi).Error; err != nil {
return setDBError(event, err, encounterId)
}
pl.SetLogInfo(event, nil, "complete")
return validateExistedSoapi(dataSoapi, &dataAmbulatory, event, mode)
}
func validateExistedSoapi(dataSoapi []es.Soapi, dataAmbulatory *ea.Ambulatory, event *pl.Event, mode string) error {
typeExist := make(map[erc.SoapiTypeCode]bool)
for _, s := range dataSoapi {
typeExist[s.TypeCode] = true
}
required := []erc.SoapiTypeCode{}
switch {
case dataAmbulatory.Class_Code == ere.ACCReg:
required = []erc.SoapiTypeCode{erc.STCEEarlyMedic}
case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCAdm:
required = []erc.SoapiTypeCode{erc.STCEEarlyMedic, erc.STCFunc, erc.STCEarlyRehab}
case dataAmbulatory.Class_Code == ere.ACCRme && dataAmbulatory.VisitMode_Code == ere.VMCSeries:
required = []erc.SoapiTypeCode{erc.STCEarlyRehab}
}
var missing, existing []string
for _, code := range required {
if typeExist[code] {
existing = append(existing, string(code))
} else {
missing = append(missing, string(code))
}
}
switch mode {
case "check-in":
if len(existing) > 0 {
return setSoapiError(event, fmt.Sprintf("Soapi type(s) %s exist, can't check-in", strings.Join(existing, ", ")))
}
case "check-out":
if len(missing) > 0 {
return setSoapiError(event, fmt.Sprintf("Soapi type(s) %s not found, can't check-out", strings.Join(missing, ", ")))
}
}
return nil
}
func setSoapiError(event *pl.Event, detail string) error {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "invalid-soapi-state",
Detail: detail,
}
return pl.SetLogError(event, detail)
}
func setDBError(event *pl.Event, err error, ctx any) error {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Raw: err,
Code: "read-fail",
Detail: "Database read failed",
}
return pl.SetLogError(event, ctx)
}
func updateRehabDoctor(input er.UpdateDto, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, "started", "DBUpdate")
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
result := tx.
Model(&er.Rehab{}).
Where("\"Encounter_Id\" = (?)", input.Encounter_Id).
Update("\"Doctor_Id\"", input.Doctor_Id)
if result.Error != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "data-update-fail",
Detail: "Database update failed",
Raw: result.Error,
}
return pl.SetLogError(event, input)
}
pl.SetLogInfo(event, nil, "complete")
return nil
}