1019 lines
28 KiB
Go
1019 lines
28 KiB
Go
/*
|
|
DESCRIPTION:
|
|
Any functions that are used internally by the use-case
|
|
*/
|
|
package encounter
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
authhelper "simrs-vx/internal/lib/auth"
|
|
|
|
dg "github.com/karincake/apem/db-gorm-pg"
|
|
"gorm.io/gorm"
|
|
|
|
pl "simrs-vx/pkg/logger"
|
|
pu "simrs-vx/pkg/use-case-helper"
|
|
|
|
ercl "simrs-vx/internal/domain/references/clinical"
|
|
erc "simrs-vx/internal/domain/references/common"
|
|
ere "simrs-vx/internal/domain/references/encounter"
|
|
erg "simrs-vx/internal/domain/references/organization"
|
|
|
|
ea "simrs-vx/internal/domain/main-entities/ambulatory"
|
|
ec "simrs-vx/internal/domain/main-entities/chemo"
|
|
ecpl "simrs-vx/internal/domain/main-entities/chemo-plan"
|
|
ecp "simrs-vx/internal/domain/main-entities/chemo-protocol"
|
|
edo "simrs-vx/internal/domain/main-entities/device-order"
|
|
ed "simrs-vx/internal/domain/main-entities/doctor"
|
|
ee "simrs-vx/internal/domain/main-entities/emergency"
|
|
e "simrs-vx/internal/domain/main-entities/encounter"
|
|
ei "simrs-vx/internal/domain/main-entities/inpatient"
|
|
emo "simrs-vx/internal/domain/main-entities/material-order"
|
|
emco "simrs-vx/internal/domain/main-entities/mcu-order"
|
|
em "simrs-vx/internal/domain/main-entities/medication"
|
|
emei "simrs-vx/internal/domain/main-entities/medication-item"
|
|
emi "simrs-vx/internal/domain/main-entities/medicine-mix"
|
|
emmi "simrs-vx/internal/domain/main-entities/medicine-mix-item"
|
|
en "simrs-vx/internal/domain/main-entities/nurse"
|
|
ep "simrs-vx/internal/domain/main-entities/prescription"
|
|
epi "simrs-vx/internal/domain/main-entities/prescription-item"
|
|
er "simrs-vx/internal/domain/main-entities/rehab"
|
|
es "simrs-vx/internal/domain/main-entities/soapi"
|
|
esp "simrs-vx/internal/domain/main-entities/specialist"
|
|
|
|
ua "simrs-vx/internal/use-case/main-use-case/ambulatory"
|
|
uc "simrs-vx/internal/use-case/main-use-case/chemo"
|
|
ud "simrs-vx/internal/use-case/main-use-case/doctor"
|
|
ue "simrs-vx/internal/use-case/main-use-case/emergency"
|
|
ui "simrs-vx/internal/use-case/main-use-case/inpatient"
|
|
um "simrs-vx/internal/use-case/main-use-case/medication"
|
|
umei "simrs-vx/internal/use-case/main-use-case/medication-item"
|
|
umi "simrs-vx/internal/use-case/main-use-case/medicine-mix"
|
|
ummi "simrs-vx/internal/use-case/main-use-case/medicine-mix-item"
|
|
_ "simrs-vx/internal/use-case/main-use-case/nurse"
|
|
un "simrs-vx/internal/use-case/main-use-case/nurse"
|
|
up "simrs-vx/internal/use-case/main-use-case/prescription"
|
|
upi "simrs-vx/internal/use-case/main-use-case/prescription-item"
|
|
ur "simrs-vx/internal/use-case/main-use-case/rehab"
|
|
us "simrs-vx/internal/use-case/main-use-case/soapi"
|
|
)
|
|
|
|
func setDataCreate(input *e.CreateDto, data *e.Encounter) {
|
|
data.Patient_Id = input.Patient_Id
|
|
data.RegisteredAt = input.RegisteredAt
|
|
data.Class_Code = input.Class_Code
|
|
data.Specialist_Code = input.Specialist_Code
|
|
data.Specialist_Code = input.Specialist_Code
|
|
data.Subspecialist_Code = input.Subspecialist_Code
|
|
data.VisitDate = input.VisitDate
|
|
data.PaymentMethod_Code = input.PaymentMethod_Code
|
|
data.InsuranceCompany_Code = input.InsuranceCompany_Code
|
|
data.Member_Number = input.Member_Number
|
|
data.RefType_Code = &input.RefTypeCode
|
|
data.Ref_Number = input.Ref_Number
|
|
data.Trx_Number = input.Trx_Number
|
|
data.Appointment_Doctor_Code = input.Appointment_Doctor_Code
|
|
data.Adm_Employee_Id = input.Adm_Employee_Id
|
|
data.RefSource_Name = input.RefSource_Name
|
|
data.Appointment_Id = input.Appointment_Id
|
|
data.Status_Code = erc.DSCNew
|
|
data.NewStatus = input.NewStatus
|
|
}
|
|
|
|
func setDataUpdate(src e.UpdateDto, dst *e.Encounter) {
|
|
dst.Specialist_Code = src.Specialist_Code
|
|
dst.Specialist_Code = src.Specialist_Code
|
|
dst.Subspecialist_Code = src.Subspecialist_Code
|
|
dst.VisitDate = src.VisitDate
|
|
dst.Appointment_Doctor_Code = src.Appointment_Doctor_Code
|
|
dst.Adm_Employee_Id = src.Adm_Employee_Id
|
|
dst.Appointment_Id = src.Appointment_Id
|
|
dst.RefSource_Name = src.RefSource_Name
|
|
dst.Trx_Number = src.Trx_Number
|
|
dst.Ref_Number = src.Ref_Number
|
|
dst.Member_Number = src.Member_Number
|
|
}
|
|
|
|
func setDataUpdateFromSource(input *e.UpdateDto, data *e.Encounter) {
|
|
data.Patient_Id = input.Patient_Id
|
|
data.RegisteredAt = input.RegisteredAt
|
|
data.Specialist_Code = input.Specialist_Code
|
|
data.Specialist_Code = input.Specialist_Code
|
|
data.Subspecialist_Code = input.Subspecialist_Code
|
|
data.VisitDate = input.VisitDate
|
|
data.StartedAt = input.StartedAt
|
|
data.FinishedAt = input.FinishedAt
|
|
data.PaymentMethod_Code = input.PaymentMethod_Code
|
|
data.InsuranceCompany_Code = input.InsuranceCompany_Code
|
|
data.Member_Number = input.Member_Number
|
|
data.RefType_Code = &input.RefTypeCode
|
|
data.Ref_Number = input.Ref_Number
|
|
data.Trx_Number = input.Trx_Number
|
|
data.Appointment_Doctor_Code = input.Appointment_Doctor_Code
|
|
data.Adm_Employee_Id = input.Adm_Employee_Id
|
|
data.Responsible_Doctor_Code = input.Responsible_Doctor_Code
|
|
data.Discharge_Method_Code = input.Discharge_Method_Code
|
|
data.RefSource_Name = input.RefSource_Name
|
|
data.Appointment_Id = input.Appointment_Id
|
|
data.EarlyEducation = input.EarlyEducation
|
|
data.MedicalDischargeEducation = input.MedicalDischargeEducation
|
|
data.AdmDischargeEducation = input.AdmDischargeEducation
|
|
data.DischargeReason = input.DischargeReason
|
|
data.Status_Code = input.Status_Code
|
|
data.Discharge_Date = input.Discharge_Date
|
|
data.NewStatus = input.NewStatus
|
|
data.Responsible_Nurse_Code = input.Responsible_Nurse_Code
|
|
}
|
|
|
|
func setDataDischarge(src e.DischargeDto, dst *e.Encounter) {
|
|
dst.Discharge_Method_Code = src.Discharge_Method_Code
|
|
dst.EarlyEducation = src.EarlyEducation
|
|
dst.MedicalDischargeEducation = src.MedicalDischargeEducation
|
|
dst.AdmDischargeEducation = src.AdmDischargeEducation
|
|
dst.DischargeReason = src.DischargeReason
|
|
dst.Status_Code = erc.DSCDone
|
|
|
|
now := time.Now()
|
|
dst.Discharge_Date = &now
|
|
dst.FinishedAt = &now
|
|
}
|
|
|
|
func setDataCheckIn(src e.CheckinDto, dst *e.Encounter) {
|
|
dst.Responsible_Nurse_Code = src.Responsible_Nurse_Code
|
|
dst.Responsible_Doctor_Code = src.Responsible_Doctor_Code
|
|
dst.StartedAt = src.StartedAt
|
|
dst.Status_Code = erc.DSCProcess
|
|
}
|
|
|
|
func setDischargeMethodCode(code ere.PolySwitchCode) *ere.DischargeMethodCode {
|
|
var dcm ere.DischargeMethodCode
|
|
|
|
switch code {
|
|
case ere.PSCConsulPoly:
|
|
dcm = ere.DMCConsulPoly
|
|
case ere.PSCConsulExecutive:
|
|
dcm = ere.DMCConsulExecutive
|
|
}
|
|
|
|
return &dcm
|
|
}
|
|
|
|
func checkSoapiByDocExists(encounter_id uint, event *pl.Event, tx *gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "checkSoapiByDocExists")
|
|
var soapies []es.Soapi
|
|
err := tx.
|
|
Preload("Employee").
|
|
Preload("Employee.User").
|
|
Where("\"Encounter_Id\" = ?", encounter_id).Find(&soapies).Error
|
|
if err != nil {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-get-fail",
|
|
Detail: "get soapi failed",
|
|
Raw: err,
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
|
|
if len(soapies) == 0 {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-notFound",
|
|
Detail: "no soapi found for encounter",
|
|
Raw: errors.New("soapi not found"),
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
|
|
for _, s := range soapies {
|
|
if s.Employee != nil && *s.Employee.Position_Code == erg.EPCDoc {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-update-fail",
|
|
Detail: "no soapi written by a doctor found",
|
|
Raw: errors.New("all soapi employees are not doctors"),
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
|
|
func createMedication(encounter_id uint, event *pl.Event, tx *gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "createMedication")
|
|
|
|
prescription, err := up.ReadDetailData(ep.ReadDetailDto{Encounter_Id: &encounter_id}, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
prescriptionItem, _, err := upi.ReadListData(epi.ReadListDto{
|
|
FilterDto: epi.FilterDto{Prescription_Id: &prescription.Id},
|
|
Includes: "medicineMix,medicineMix-MixItems"}, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(prescriptionItem) == 0 {
|
|
return nil
|
|
}
|
|
|
|
medicationCreate := em.CreateDto{
|
|
Encounter_Id: &encounter_id,
|
|
IssuedAt: pu.GetTimeNow(),
|
|
Status_Code: erc.DSCNew,
|
|
}
|
|
medication, err := um.CreateData(medicationCreate, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, prescriptionItem := range prescriptionItem {
|
|
if prescriptionItem.IsMix {
|
|
medMix_id, err := createMedicineMixAndItem(*prescriptionItem.MedicineMix, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
prescriptionItem.MedicineMix_Id = medMix_id
|
|
}
|
|
err := createMedicationItem(medication.Id, prescriptionItem, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createMedicineMixAndItem(input emi.MedicineMix, event *pl.Event, tx *gorm.DB) (*uint, error) {
|
|
pl.SetLogInfo(event, nil, "started", "createMedicineMix")
|
|
|
|
medicineMixCreate := emi.CreateDto{
|
|
Name: input.Name,
|
|
Uom_Code: input.Uom_Code,
|
|
}
|
|
medicineMix, err := umi.CreateData(medicineMixCreate, event, tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// recreate medicineMixItem with new medicineMix_id to keep medMixItem remain the same for prescriptionItem that is created
|
|
for _, medicineMixItem := range input.MixItems {
|
|
medicineMixItemCreate := emmi.CreateDto{
|
|
MedicineMix_Id: &medicineMix.Id,
|
|
Medicine_Code: medicineMixItem.Medicine_Code,
|
|
Dose: medicineMixItem.Dose,
|
|
}
|
|
_, err := ummi.CreateData(medicineMixItemCreate, event, tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
}
|
|
return &medicineMix.Id, nil
|
|
}
|
|
|
|
func createMedicationItem(medication_id uint, input epi.PrescriptionItem, event *pl.Event, tx *gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "createMedicationItem")
|
|
|
|
medicationItemCreate := emei.CreateDto{
|
|
Medication_Id: &medication_id,
|
|
IsMix: input.IsMix,
|
|
Medicine_Code: input.Medicine_Code,
|
|
MedicineMix_Id: input.MedicineMix_Id,
|
|
Usage: input.Usage,
|
|
Interval: input.Interval,
|
|
IntervalUnit_Code: input.IntervalUnit_Code,
|
|
Quantity: input.Quantity,
|
|
}
|
|
|
|
_, err := umei.CreateData(medicationItemCreate, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkNewOrdersExist(encounter_id uint, event *pl.Event, tx *gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "CheckNewOrdersExist")
|
|
var errs []string
|
|
if err := getDeviceOrders(encounter_id, event, tx); err != nil {
|
|
errs = append(errs, err.Error())
|
|
}
|
|
|
|
if err := getMaterialOrders(encounter_id, event, tx); err != nil {
|
|
errs = append(errs, err.Error())
|
|
}
|
|
|
|
if err := getMcuOrders(encounter_id, event, tx); err != nil {
|
|
errs = append(errs, err.Error())
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return fmt.Errorf("encounter has open orders: %s", strings.Join(errs, "; "))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getDeviceOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "getDeviceOrders")
|
|
var orders []edo.DeviceOrder
|
|
err := tx.Where("\"Encounter_Id\" = ? AND \"Status_Code\" = ?", encounter_id, erc.DSCNew).Find(&orders).Error
|
|
if err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil
|
|
}
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-get-fail",
|
|
Detail: "get device order failed",
|
|
Raw: err,
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
|
|
if len(orders) > 0 {
|
|
return fmt.Errorf("encounter has %d device orders", len(orders))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getMaterialOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "getMaterialOrders")
|
|
var orders []emo.MaterialOrder
|
|
err := tx.Where("\"Encounter_Id\" = ? AND \"Status_Code\" = ?", encounter_id, erc.DSCNew).Find(&orders).Error
|
|
if err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil
|
|
}
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-get-fail",
|
|
Detail: "get material order failed",
|
|
Raw: err,
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
|
|
if len(orders) > 0 {
|
|
return fmt.Errorf("encounter has %d material orders", len(orders))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getMcuOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "getMcuOrders")
|
|
var orders []emco.McuOrder
|
|
err := tx.Where("\"Encounter_Id\" = ? AND \"Status_Code\" = ?", encounter_id, erc.DSCNew).Find(&orders).Error
|
|
if err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil
|
|
}
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-get-fail",
|
|
Detail: "get mcu order failed",
|
|
Raw: err,
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
|
|
if len(orders) > 0 {
|
|
return fmt.Errorf("encounter has %d mcu orders", len(orders))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getSoapiByResponsibleDoctor(enc e.Encounter, event *pl.Event) (data []es.Soapi, err error) {
|
|
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",
|
|
}
|
|
return nil, pl.SetLogError(event, enc)
|
|
}
|
|
|
|
err = dg.I.
|
|
Model(&es.Soapi{}).
|
|
Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\"").
|
|
Where("\"Encounter_Id\" = ?", enc.Id).
|
|
Where("\"Employee\".\"Position_Code\" = ?", erg.EPCDoc).
|
|
Where("\"Soapi\".\"TypeCode\" IN ?", []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.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,
|
|
Code: "read-fail",
|
|
Detail: "Database read failed",
|
|
}
|
|
return nil, pl.SetLogError(event, enc)
|
|
}
|
|
|
|
pl.SetLogInfo(event, nil, "complete")
|
|
return
|
|
}
|
|
|
|
func getSoapiEncounterAdm(enc e.Encounter, event *pl.Event) (dataSoapi []es.CreateDto, err error) {
|
|
data, err := getSoapiByResponsibleDoctor(enc, event)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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: "Missing required Soapi types",
|
|
}
|
|
return nil, pl.SetLogError(event, enc)
|
|
}
|
|
return
|
|
}
|
|
|
|
func getSoapiByTypeCode(encounter *e.Encounter, event *pl.Event, mode string) (err error) {
|
|
pl.SetLogInfo(event, encounter, "started", "DBReadList")
|
|
|
|
var (
|
|
dataSoapi []es.Soapi
|
|
amb = encounter.Ambulatory
|
|
rehab = encounter.Rehab
|
|
)
|
|
|
|
// Set Query for get data Soapi by doc
|
|
tx := dg.I.
|
|
Model(&es.Soapi{}).
|
|
Joins("JOIN \"Employee\" ON \"Employee\".\"Id\" = \"Soapi\".\"Employee_Id\"").
|
|
Where("\"Encounter_Id\" = ?", encounter.Id).
|
|
Where("\"Employee\".\"Position_Code\" = ?", erg.EPCDoc)
|
|
|
|
// Set Case
|
|
switch {
|
|
case amb.Class_Code == ere.ACCReg:
|
|
tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", ercl.STCEEarlyMedic)
|
|
case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCAdm:
|
|
tx = tx.Where("\"Soapi\".\"TypeCode\" IN ?", []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.STCFunc, ercl.STCEarlyRehab})
|
|
case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCSeries:
|
|
tx = tx.Where("\"Soapi\".\"TypeCode\" = ?", ercl.STCEarlyRehab)
|
|
}
|
|
|
|
if err = tx.Find(&dataSoapi).Error; err != nil {
|
|
return setDBError(event, err, encounter)
|
|
}
|
|
|
|
pl.SetLogInfo(event, nil, "complete")
|
|
return validateExistedSoapi(dataSoapi, encounter, event, mode)
|
|
}
|
|
|
|
func validateExistedSoapi(dataSoapi []es.Soapi, dataEncounter *e.Encounter, event *pl.Event, mode string) error {
|
|
var (
|
|
amb = dataEncounter.Ambulatory
|
|
rehab = dataEncounter.Rehab
|
|
)
|
|
|
|
typeExist := make(map[ercl.SoapiTypeCode]bool)
|
|
for _, s := range dataSoapi {
|
|
typeExist[s.TypeCode] = true
|
|
}
|
|
|
|
required := []ercl.SoapiTypeCode{}
|
|
|
|
switch {
|
|
case amb.Class_Code == ere.ACCReg:
|
|
required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic}
|
|
case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCAdm:
|
|
required = []ercl.SoapiTypeCode{ercl.STCEEarlyMedic, ercl.STCFunc, ercl.STCEarlyRehab}
|
|
case amb.Class_Code == ere.ACCRehab && *rehab.VisitMode_Code == ere.VMCSeries:
|
|
required = []ercl.SoapiTypeCode{ercl.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 identifyPatientStatus(input e.CreateDto) (isNewPatient bool, err error) {
|
|
dataPatient, err := ReadList(e.ReadListDto{
|
|
FilterDto: e.FilterDto{Patient_Id: input.Patient_Id},
|
|
AuthInfo: authhelper.AuthInfo{User_Id: input.User_Id}})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if list, ok := dataPatient.Data.([]e.ResponseDto); ok {
|
|
if len(list) < 1 {
|
|
isNewPatient = true
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func determineVisitMode(recentRehabData *er.Rehab, input e.CreateDto, event *pl.Event) (ere.VisitModeCode, *e.Encounter, error) {
|
|
var (
|
|
visitModeCode ere.VisitModeCode
|
|
recentAdmEncounterData e.Encounter
|
|
isQuotaValid bool
|
|
err error
|
|
)
|
|
|
|
switch *recentRehabData.Status_Code {
|
|
case erc.DSCProcess:
|
|
visitModeCode = ere.VMCSeries
|
|
|
|
// verify whether the allocated visit count has not exceeded the limit
|
|
recentAdmEncounterData, isQuotaValid, err = verifyAllocatedVisitCount(input, event)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
if !isQuotaValid {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "visit-limit-exceeded",
|
|
Detail: "Encounter has exceeded the allowed number of visits",
|
|
Raw: errors.New("visit count exceeds allowed limit"),
|
|
}
|
|
return "", nil, pl.SetLogError(event, input)
|
|
}
|
|
|
|
if recentRehabData.ExpiredAt != nil && recentRehabData.ExpiredAt.Before(*pu.GetTimeNow()) {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "visit-limit-exceeded",
|
|
Detail: "Encounter period has expired",
|
|
Raw: errors.New("encounter expired"),
|
|
}
|
|
return "", nil, pl.SetLogError(event, input)
|
|
}
|
|
|
|
case erc.DSCDone:
|
|
visitModeCode = ere.VMCAdm
|
|
|
|
default:
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "invalid-status",
|
|
Detail: fmt.Sprintf("Unknown rehab status: %v", *recentRehabData.Status_Code),
|
|
Raw: errors.New("unsupported rehab status"),
|
|
}
|
|
return "", nil, pl.SetLogError(event, input)
|
|
}
|
|
|
|
return visitModeCode, &recentAdmEncounterData, nil
|
|
}
|
|
|
|
func insertdataClassCode(input e.CreateDto, soapiData []es.CreateDto, event *pl.Event, tx *gorm.DB) (err error) {
|
|
switch input.Class_Code {
|
|
case ere.ECAmbulatory:
|
|
subCode := ere.AmbulatoryClassCode(*input.SubClass_Code)
|
|
ambCreate := ea.CreateDto{
|
|
Encounter_Id: &input.Id,
|
|
Class_Code: subCode,
|
|
}
|
|
|
|
// create data Ambulatory
|
|
_, err = ua.CreateData(ambCreate, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// insert chemo/rehab
|
|
err = insertDataSubClassAmbulatory(input, soapiData, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case ere.ECEmergency:
|
|
subCode := ere.EmergencyClassCode(*input.SubClass_Code)
|
|
emerCreate := ee.CreateDto{
|
|
Encounter_Id: &input.Id,
|
|
Class_Code: subCode,
|
|
}
|
|
|
|
// create data emergency
|
|
_, err = ue.CreateData(emerCreate, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case ere.ECInpatient:
|
|
subCode := ere.InpatientClassCode(*input.SubClass_Code)
|
|
inpCreate := ei.CreateDto{
|
|
Encounter_Id: &input.Id,
|
|
Class_Code: subCode,
|
|
Infra_Code: input.Infra_Code,
|
|
}
|
|
|
|
// create data inpatient
|
|
_, err = ui.CreateData(inpCreate, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return errors.New("invalid encounter class code")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func insertDataSubClassAmbulatory(input e.CreateDto, soapiData []es.CreateDto, event *pl.Event, tx *gorm.DB) (err error) {
|
|
subCode := ere.AmbulatoryClassCode(*input.SubClass_Code)
|
|
var chemoPlan *ecpl.ChemoPlan
|
|
|
|
switch {
|
|
case subCode == ere.ACCChemo:
|
|
// validate if encounter Chemo valid
|
|
chemoPlan, err = validateChemo(*input.Patient_Id, event)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if chemoPlan == nil {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-not-found",
|
|
Detail: "chemo plan not found",
|
|
}
|
|
|
|
return pl.SetLogError(event, input)
|
|
}
|
|
|
|
chemoCreate := ec.CreateDto{
|
|
Encounter_Id: &input.Id,
|
|
Status_Code: erc.DVCVerified,
|
|
Specialist_Code: input.Specialist_Code,
|
|
Class_Code: ere.CCCAct,
|
|
}
|
|
|
|
// create data chemo
|
|
_, err = uc.CreateData(chemoCreate, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// set chemo-plan to planned
|
|
chemoPlan.Encounter_Id = &input.Id
|
|
err = updateChemoPlan(chemoPlan, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case subCode == ere.ACCRehab:
|
|
rehabData := er.CreateDto{
|
|
Encounter_Id: &input.Id,
|
|
VisitMode_Code: input.VisitMode_Code,
|
|
Status_Code: erc.DSCProcess,
|
|
}
|
|
|
|
// if visitMode_code is series, then bulk insert soapi
|
|
if input.VisitMode_Code == ere.VMCSeries {
|
|
rehabData.Parent_Encounter_Id = &input.RecentEncounterAdm.Id
|
|
|
|
// Insert Soapi
|
|
if err = us.CreateBulkData(soapiData, input.Id, event, tx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create rehab
|
|
if _, err = ur.CreateData(rehabData, event, tx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func verifyRehabLimit(data *e.Encounter, event *pl.Event, tx *gorm.DB) error {
|
|
// get data encounter adm
|
|
encounterAdmData, _, err := verifyAllocatedVisitCount(e.CreateDto{Patient_Id: data.Patient_Id}, event)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if the visit count has reached the allowed limit
|
|
// Mark latest rehab status as 'done' if exceeded.
|
|
if len(*encounterAdmData.RehabChildren) >= *encounterAdmData.Rehab.AllocatedVisitCount {
|
|
err = updateRehabStatus(er.UpdateDto{
|
|
Id: uint16(data.Rehab.Id),
|
|
CreateDto: er.CreateDto{Status_Code: erc.DSCDone}}, event, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func updateRehabStatus(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("\"Id\" = (?)", input.Id).
|
|
Update("\"Status_Code\"", input.Status_Code)
|
|
|
|
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
|
|
}
|
|
|
|
func validateForeignKey(input e.CheckinDto, event *pl.Event) error {
|
|
// validate employee_Id
|
|
if input.Responsible_Nurse_Code != nil {
|
|
if _, err := un.ReadDetailData(en.ReadDetailDto{Code: input.Responsible_Nurse_Code}, event); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// validate doctor_Code
|
|
if input.Responsible_Doctor_Code != nil {
|
|
if _, err := ud.ReadDetailData(ed.ReadDetailDto{Code: input.Responsible_Doctor_Code}, event); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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 getSpecialists(unitIds []string, event *pl.Event) ([]esp.Specialist, error) {
|
|
pl.SetLogInfo(event, nil, "started", "getSpecialists")
|
|
var units []esp.Specialist
|
|
err := dg.I.Where("\"Code\" IN ?", unitIds).Find(&units).Error
|
|
if err != nil {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-get-fail",
|
|
Detail: "get units",
|
|
Raw: err,
|
|
}
|
|
return nil, pl.SetLogError(event, nil)
|
|
}
|
|
return units, nil
|
|
}
|
|
|
|
func getDoctors(doctorIds []string, event *pl.Event) ([]ed.Doctor, error) {
|
|
pl.SetLogInfo(event, nil, "started", "getDoctors")
|
|
var doctors []ed.Doctor
|
|
err := dg.I.Where("\"Code\" IN ?", doctorIds).Find(&doctors).Error
|
|
if err != nil {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-get-fail",
|
|
Detail: "get doctors",
|
|
Raw: err,
|
|
}
|
|
return nil, pl.SetLogError(event, nil)
|
|
}
|
|
return doctors, nil
|
|
}
|
|
|
|
func validateSpecialistCodes(speCodes map[string]struct{}, event *pl.Event) error {
|
|
if len(speCodes) > 0 {
|
|
var codes []string
|
|
for code := range speCodes {
|
|
codes = append(codes, code)
|
|
}
|
|
|
|
specialists, err := getSpecialists(codes, event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch units: %w", err)
|
|
}
|
|
if len(specialists) != len(codes) {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-validation-fail",
|
|
Detail: "unit_code not found",
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateDoctorCodes(doctorCodes map[string]struct{}, event *pl.Event) error {
|
|
if len(doctorCodes) > 0 {
|
|
var codes []string
|
|
for code := range doctorCodes {
|
|
codes = append(codes, code)
|
|
}
|
|
|
|
doctors, err := getDoctors(codes, event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch doctors: %w", err)
|
|
}
|
|
if len(doctors) != len(codes) {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-validation-fail",
|
|
Detail: "doctor_code not found",
|
|
}
|
|
return pl.SetLogError(event, nil)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func updateChemoPlan(data *ecpl.ChemoPlan, event *pl.Event, dbx ...*gorm.DB) error {
|
|
pl.SetLogInfo(event, nil, "started", "getChemoFromEncounter")
|
|
var tx *gorm.DB
|
|
if len(dbx) > 0 {
|
|
tx = dbx[0]
|
|
} else {
|
|
tx = dg.I
|
|
}
|
|
|
|
err := tx.Model(&data).Updates(map[string]interface{}{
|
|
`"Status"`: ere.SPCPlanned,
|
|
`"Encounter_Id"`: data.Encounter_Id}).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pl.SetLogInfo(event, nil, "complete")
|
|
return nil
|
|
}
|
|
|
|
func getChemoProtocol(patientId uint, event *pl.Event) (*ecp.ChemoProtocol, error) {
|
|
pl.SetLogInfo(event, nil, "started", "getChemoProtocol")
|
|
data := ecp.ChemoProtocol{}
|
|
|
|
var tx = dg.I
|
|
|
|
tx = tx.Model(&ecp.ChemoProtocol{}).
|
|
Joins(`LEFT JOIN "ChemoPlan" cp ON cp."Protocol_Id" = "ChemoProtocol"."Id"`).
|
|
Where(`"ChemoProtocol"."Patient_Id" = ?`, patientId).
|
|
Preload("ChemoPlans", func(db *gorm.DB) *gorm.DB {
|
|
return db.
|
|
Where(`"Status" IS NULL OR "Status" = ?`, ere.SPCSchedule).
|
|
Order(`"Id" ASC`).
|
|
Limit(1)
|
|
}).
|
|
Order(`"CreatedAt" DESC`).
|
|
First(&data)
|
|
|
|
if err := tx.Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pl.SetLogInfo(event, nil, "complete")
|
|
return &data, nil
|
|
}
|
|
|
|
func validateChemo(patientId uint, event *pl.Event) (*ecpl.ChemoPlan, error) {
|
|
// get chemo adm
|
|
chemoAdm, err := getChemoAdm(patientId, event)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-not-found",
|
|
Detail: "patient doesn't have active chemo",
|
|
}
|
|
|
|
return nil, pl.SetLogError(event, patientId)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// validate is chemo verified
|
|
if chemoAdm.Status_Code != erc.DVCVerified {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-not-match",
|
|
Detail: fmt.Sprintf("chemo not yet verified"),
|
|
}
|
|
return nil, pl.SetLogError(event, chemoAdm)
|
|
}
|
|
|
|
// get chemo protocol
|
|
chemoProtocol, err := getChemoProtocol(patientId, event)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if chemoProtocol.Status_Code != erc.DVCVerified {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "data-not-match",
|
|
Detail: fmt.Sprintf("protocol chemo not yet verified"),
|
|
}
|
|
return nil, pl.SetLogError(event, chemoProtocol)
|
|
}
|
|
|
|
if now.Before(*chemoProtocol.StartDate) || now.After(*chemoProtocol.EndDate) {
|
|
event.Status = "failed"
|
|
event.ErrInfo = pl.ErrorInfo{
|
|
Code: "invalid-date-range",
|
|
Detail: "chemo cannot be performed because the current date is outside the allowed treatment window.",
|
|
}
|
|
return nil, pl.SetLogError(event, chemoProtocol)
|
|
}
|
|
|
|
if chemoProtocol.ChemoPlans == nil || len(*chemoProtocol.ChemoPlans) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Return the first chemo plan
|
|
return &(*chemoProtocol.ChemoPlans)[0], nil
|
|
}
|
|
|
|
func getChemoAdm(patientId uint, event *pl.Event) (*ec.Chemo, error) {
|
|
pl.SetLogInfo(event, nil, "started", "getChemoProtocol")
|
|
data := ec.Chemo{}
|
|
|
|
var tx = dg.I
|
|
|
|
tx = tx.Model(&ec.Chemo{}).
|
|
Joins(`LEFT JOIN "Encounter" e ON e."Id" = "Chemo"."Encounter_Id"`).
|
|
Where(`e."Patient_Id" = ? AND "Chemo"."Class_Code" = ?`, patientId, ere.CCCAdm).
|
|
Order(`"CreatedAt" DESC`).
|
|
First(&data)
|
|
|
|
if err := tx.Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pl.SetLogInfo(event, nil, "complete")
|
|
return &data, nil
|
|
}
|