Merge pull request #215 from dikstub-rssa/feat/chemo-plan

Feat/chemo plan
This commit is contained in:
Dwi Atmoko Purbo Sakti
2025-12-15 15:22:41 +07:00
committed by GitHub
28 changed files with 1477 additions and 354 deletions

No files matched your search

@@ -7,7 +7,6 @@ package encounter
import (
"errors"
"fmt"
un "simrs-vx/internal/use-case/main-use-case/nurse"
"strings"
"time"
@@ -24,9 +23,10 @@ import (
ere "simrs-vx/internal/domain/references/encounter"
erg "simrs-vx/internal/domain/references/organization"
eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist"
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"
@@ -42,12 +42,9 @@ import (
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"
erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist"
es "simrs-vx/internal/domain/main-entities/soapi"
esp "simrs-vx/internal/domain/main-entities/specialist"
// udo "simrs-vx/internal/use-case/main-use-case/device-order"
uaeh "simrs-vx/internal/use-case/main-use-case/adm-employee-hist"
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"
@@ -58,10 +55,10 @@ import (
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"
urdh "simrs-vx/internal/use-case/main-use-case/responsible-doctor-hist"
us "simrs-vx/internal/use-case/main-use-case/soapi"
)
@@ -396,192 +393,6 @@ func getMcuOrders(encounter_id uint, event *pl.Event, tx *gorm.DB) error {
return nil
}
func upsertResponsibleDoctorHist(input erdh.CreateDto, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, nil, "started", "DBCreate")
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
var latest erdh.ResponsibleDoctorHist
err := tx.
Where("\"Encounter_Id\" = ?", input.Encounter_Id).
Order("\"CreatedAt\" DESC").
Limit(1).
First(&latest).Error
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
// Insert
if _, err = urdh.CreateData(input, event, tx); err != nil {
return err
}
case err != nil:
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "read-fail",
Detail: "Failed to read responsible doctor history",
Raw: err,
}
return pl.SetLogError(event, input)
default:
// Update
if err := tx.Model(&latest).Updates(map[string]interface{}{
"Doctor_Code": input.Doctor_Code,
"StartedAt": input.StartedAt,
"UpdatedAt": time.Now(),
}).Error; err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "update-fail",
Detail: "Failed to update responsible doctor history",
Raw: err,
}
return pl.SetLogError(event, input)
}
}
pl.SetLogInfo(event, input, "complete")
return nil
}
func upsertAdmEmployeeHist(input eaeh.CreateDto, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, nil, "started", "DBCreate")
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
var latest eaeh.AdmEmployeeHist
err := tx.
Where("\"Encounter_Id\" = ?", input.Encounter_Id).
Order("\"CreatedAt\" DESC").
Limit(1).
First(&latest).Error
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
// Insert
if _, err = uaeh.CreateData(input, event, tx); err != nil {
return err
}
case err != nil:
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "read-fail",
Detail: "Failed to read responsible doctor history",
Raw: err,
}
return pl.SetLogError(event, input)
default:
// Update
if err := tx.Model(&latest).Updates(map[string]interface{}{
"Employee_Id": input.Employee_Id,
"StartedAt": input.StartedAt,
"UpdatedAt": time.Now(),
}).Error; err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "update-fail",
Detail: "Failed to update responsible doctor history",
Raw: err,
}
return pl.SetLogError(event, input)
}
}
pl.SetLogInfo(event, input, "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.FinishedAt)
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.FinishedAt)
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 getSoapiByResponsibleDoctor(enc e.Encounter, event *pl.Event) (data []es.Soapi, err error) {
pl.SetLogInfo(event, enc, "started", "DBReadList")
@@ -849,13 +660,31 @@ func insertdataClassCode(input e.CreateDto, soapiData []es.CreateDto, event *pl.
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.DVCNew,
Status_Code: erc.DVCVerified,
Specialist_Code: input.Specialist_Code,
Class_Code: ere.CCCAct,
}
// create data chemo
@@ -864,6 +693,13 @@ func insertDataSubClassAmbulatory(input e.CreateDto, soapiData []es.CreateDto, e
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,
@@ -1009,18 +845,18 @@ func getDoctors(doctorIds []string, event *pl.Event) ([]ed.Doctor, error) {
return doctors, nil
}
func validateSpecialistCodes(unitCodes map[string]struct{}, event *pl.Event) error {
if len(unitCodes) > 0 {
func validateSpecialistCodes(speCodes map[string]struct{}, event *pl.Event) error {
if len(speCodes) > 0 {
var codes []string
for code := range unitCodes {
for code := range speCodes {
codes = append(codes, code)
}
units, err := getSpecialists(codes, event)
specialists, err := getSpecialists(codes, event)
if err != nil {
return fmt.Errorf("failed to fetch units: %w", err)
}
if len(units) != len(codes) {
if len(specialists) != len(codes) {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "data-validation-fail",
@@ -1056,3 +892,127 @@ func validateDoctorCodes(doctorCodes map[string]struct{}, event *pl.Event) error
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
}
@@ -59,10 +59,11 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.En
} else {
tx = dg.I
}
tx = tx.Model(&e.Encounter{})
if input.AuthInfo.Doctor_Code != nil {
tx.Where("\"Responsible_Doctor_Code\" = ?", *input.AuthInfo.Doctor_Code) //
tx = tx.Where("\"Responsible_Doctor_Code\" = ?", *input.AuthInfo.Doctor_Code) //
}
if input.StartDate != nil && input.EndDate != nil {
@@ -75,7 +76,8 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.En
if input.Patient_Identifier != nil {
tx = tx.Joins("JOIN \"Patient\" ON \"Patient\".\"Id\" = \"Encounter\".\"Patient_Id\"").
Joins("JOIN \"Person\" ON \"Person\".\"Id\" = \"Patient\".\"Person_Id\"").Where("\"Person\".\"Name\" ILIKE ? OR \"Patient\".\"Number\" = ?", "%"+*input.Patient_Identifier+"%", *input.Patient_Identifier)
Joins("JOIN \"Person\" ON \"Person\".\"Id\" = \"Patient\".\"Person_Id\"").
Where("\"Person\".\"Name\" ILIKE ? OR \"Patient\".\"Number\" = ?", "%"+*input.Patient_Identifier+"%", *input.Patient_Identifier)
}
// TODO: getuk lib need to be updated to support this
@@ -83,20 +85,23 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.En
tx = tx.Where("\"Encounter\".\"Status_Code\" = ?", *input.Status_Code)
}
// if input.Specialist_Code != nil {
// tx = tx.Where("\"Encounter\".\"Specialist_Code\" = ?", *input.Specialist_Code)
// }
if input.Specialist_Code != nil {
tx = tx.Where("\"Encounter\".\"Specialist_Code\" = ?", *input.Specialist_Code)
}
if input.PaymentMethod_Code != nil {
tx = tx.Where("\"Encounter\".\"PaymentMethod_Code\" = ?", *input.PaymentMethod_Code)
}
tx = tx.Debug().
Scopes(gh.Filter(input.FilterDto)).
Scopes(gh.Preload(input.Includes)).
Count(&count).
Scopes(gh.Paginate(input, &pagination)).
Order("\"CreatedAt\" DESC")
if input.ChemoClass != nil {
tx = tx.Joins(`RIGHT JOIN "Chemo" "c" ON "c"."Encounter_Id" = "Encounter"."Id"`).
Where(`"c"."Class_Code" = ?`, *input.ChemoClass)
}
// count
if err := tx.Count(&count).Error; err != nil {
return nil, nil, plh.HandleListError(input, event, err)
}
// tx.Debug().Scopes(gh.Preload(input.Includes)).
// Scopes(gh.Filter(input.FilterDto)).
@@ -104,7 +109,11 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.En
// Scopes(gh.Paginate(input, &pagination)).
// Order("\"CreatedAt\" DESC")
if err := tx.Find(&data).Error; err != nil {
if err := tx.
Scopes(gh.Preload(input.Includes)).
Scopes(gh.Paginate(input, &pagination)).
Order("\"CreatedAt\" DESC").
Find(&data).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, &meta, nil
}