protocol chemo finish

This commit is contained in:
vanilia
2025-12-12 13:48:34 +07:00
parent f5568dfc61
commit cfdfac223f
14 changed files with 261 additions and 120 deletions
@@ -17,7 +17,7 @@ type CreateDto struct {
PlanDate *time.Time `json:"planDate"`
RealizationDate *time.Time `json:"realizationDate"`
Notes *string `json:"notes"`
Status ere.StatusProtocolChemo `json:"status"`
Status *ere.StatusProtocolChemo `json:"status"`
Encounter_Id *uint `json:"encounter_id"`
}
@@ -48,11 +48,12 @@ type DeleteDto struct {
type FailDto struct {
Id uint `json:"id"`
Reasons *string `json:"reason"`
Reasons string `json:"reasons" validate:"required"`
}
type FailReason struct {
Date *time.Time `json:"date"`
FailReason *string `json:"failReason"`
FailReason string `json:"failReason"`
Encounter_Id *uint `json:"encounter_id"`
}
type MetaDto struct {
@@ -70,7 +71,7 @@ type ResponseDto struct {
PlanDate *time.Time `json:"planDate"`
RealizationDate *time.Time `json:"realizationDate"`
Notes *string `json:"notes"`
Status ere.StatusProtocolChemo `json:"status"`
Status *ere.StatusProtocolChemo `json:"status"`
Reasons *string `json:"reasons"`
}
@@ -18,7 +18,7 @@ type ChemoPlan struct {
PlanDate *time.Time `json:"planDate"`
RealizationDate *time.Time `json:"realizationDate"`
Notes *string `json:"notes"`
Status ere.StatusProtocolChemo `json:"status"`
Status *ere.StatusProtocolChemo `json:"status"`
Encounter_Id *uint `json:"encounter_id"`
Encounter *ee.Encounter `json:"encounter,omitempty" gorm:"foreignKey:Encounter_Id;references:Id"`
Reasons *string `json:"reasons"` // json
@@ -39,6 +39,7 @@ type FilterDto struct {
VerifiedBy_User_Id *uint `json:"verifiedBy-user-id"`
Specialist_Code *string `json:"specialist-code"`
Patient_Id *uint `json:"patient-id"`
Class_Code ere.ChemoClassCode `json:"class-code"`
}
type ReadDetailDto struct {
@@ -0,0 +1,88 @@
package chemo_protocol
import (
"net/http"
rw "github.com/karincake/risoles"
sf "github.com/karincake/semprit"
e "simrs-vx/internal/domain/main-entities/chemo-plan"
ep "simrs-vx/internal/domain/main-entities/chemo-protocol"
u "simrs-vx/internal/use-case/main-use-case/chemo-plan"
)
type myBase struct{}
var O myBase
func (obj myBase) Create(w http.ResponseWriter, r *http.Request) {
dto := ep.CreateDto{}
if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res {
return
}
res, err := u.Create(dto)
rw.DataResponse(w, res, err)
}
func (obj myBase) GetList(w http.ResponseWriter, r *http.Request) {
dto := e.ReadListDto{}
sf.UrlQueryParam(&dto, *r.URL)
res, err := u.ReadList(dto)
rw.DataResponse(w, res, err)
}
func (obj myBase) GetDetail(w http.ResponseWriter, r *http.Request) {
id := rw.ValidateInt(w, "id", r.PathValue("id"))
if id <= 0 {
return
}
dto := e.ReadDetailDto{}
sf.UrlQueryParam(&dto, *r.URL)
dto.Id = uint(id)
res, err := u.ReadDetail(dto)
rw.DataResponse(w, res, err)
}
func (obj myBase) Update(w http.ResponseWriter, r *http.Request) {
id := rw.ValidateInt(w, "id", r.PathValue("id"))
if id <= 0 {
return
}
dto := e.UpdateDto{}
if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res {
return
}
dto.Id = uint(id)
res, err := u.Update(dto)
rw.DataResponse(w, res, err)
}
func (obj myBase) Delete(w http.ResponseWriter, r *http.Request) {
id := rw.ValidateInt(w, "id", r.PathValue("id"))
if id <= 0 {
return
}
dto := e.DeleteDto{}
dto.Id = uint(id)
res, err := u.Delete(dto)
rw.DataResponse(w, res, err)
}
func (obj myBase) Fail(w http.ResponseWriter, r *http.Request) {
id := rw.ValidateInt(w, "id", r.PathValue("id"))
if id <= 0 {
return
}
dto := e.FailDto{}
if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res {
return
}
dto.Id = uint(id)
res, err := u.Fail(dto)
rw.DataResponse(w, res, err)
}
@@ -81,11 +81,8 @@ func (obj myBase) Verify(w http.ResponseWriter, r *http.Request) {
}
dto := e.VerifyDto{}
if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res {
return
}
dto.Id = uint(id)
authInfo, err := pa.GetAuthInfo(r)
if err != nil {
rw.WriteJSON(w, http.StatusUnauthorized, d.IS{"message": err.Error()}, nil)
@@ -81,6 +81,7 @@ import (
/******************** sources ********************/
antibioticsrc "simrs-vx/internal/interface/main-handler/antibiotic-src"
antibioticsrccat "simrs-vx/internal/interface/main-handler/antibiotic-src-category"
chemoplan "simrs-vx/internal/interface/main-handler/chemo-plan"
chemoprotocol "simrs-vx/internal/interface/main-handler/chemo-protocol"
device "simrs-vx/internal/interface/main-handler/device"
diagnosesrc "simrs-vx/internal/interface/main-handler/diagnose-src"
@@ -323,6 +324,9 @@ func SetRoutes() http.Handler {
"PATCH /{id}/verify": chemoprotocol.O.Verify,
"PATCH /{id}/reject": chemoprotocol.O.Reject,
})
hk.GroupRoutes("/v1/chemo-plan", r, auth.GuardMW, hk.MapHandlerFunc{
"PATCH /{id}/fail": chemoplan.O.Fail,
})
hc.RegCrud(r, "/v1/upload-file", uploadfile.O)
hc.RegCrud(r, "/v1/encounter-document", encounterdocument.O)
hc.RegCrud(r, "/v1/general-consent", generalconsent.O)
@@ -176,7 +176,7 @@ func Update(input e.UpdateDto) (*d.Data, error) {
return err
}
if err := UpdateData(data, &event, tx); err != nil {
if err := UpdateData(data, "", &event, tx); err != nil {
return err
}
@@ -284,7 +284,7 @@ func Fail(input e.FailDto) (*d.Data, error) {
return nil, pl.SetLogError(&event, data)
}
if data.Status != ere.SPCPlanned {
if *data.Status != ere.SPCPlanned {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "invalid-chemo-status",
@@ -293,7 +293,7 @@ func Fail(input e.FailDto) (*d.Data, error) {
}
err = dg.I.Transaction(func(tx *gorm.DB) error {
if err := UpdateData(data, &event, tx); err != nil {
if err := UpdateFailData(input, data, &event, tx); err != nil {
return err
}
@@ -34,16 +34,23 @@ func setDataCreate(input *ep.CreateDto) (data []e.ChemoPlan) {
return
}
func setDataUpdate(data *e.ChemoPlan) {
func setDataCreateSoapi(data *e.ChemoPlan) {
data.RealizationDate = &now
status := ere.SPCComplete
data.Status = status
data.Status = &status
}
func setDataDeleteSoapi(data *e.ChemoPlan) {
status := ere.SPCPlanned
data.Status = &status
}
func setDatafail(input e.FailDto, data *e.ChemoPlan) {
data.RealizationDate = nil
data.Status = ere.SPCSchedule
status := ere.SPCSchedule
data.Status = &status
var reasons []e.FailReason
@@ -54,6 +61,7 @@ func setDatafail(input e.FailDto, data *e.ChemoPlan) {
reasons = append(reasons, e.FailReason{
FailReason: input.Reasons,
Date: &now,
Encounter_Id: data.Encounter_Id,
})
if b, err := json.Marshal(reasons); err == nil {
@@ -98,9 +98,15 @@ func ReadDetailData(input e.ReadDetailDto, event *pl.Event, dbx ...*gorm.DB) (*e
return &data, nil
}
func UpdateData(data *e.ChemoPlan, event *pl.Event, dbx ...*gorm.DB) error {
func UpdateData(data *e.ChemoPlan, method string, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, data, "started", "DBUpdate")
setDataUpdate(data)
switch method {
case "c":
setDataCreateSoapi(data)
case "d":
setDataDeleteSoapi(data)
}
var tx *gorm.DB
if len(dbx) > 0 {
@@ -148,7 +154,7 @@ func DeleteData(data *e.ChemoPlan, event *pl.Event, dbx ...*gorm.DB) error {
func UpdateFailData(input e.FailDto, data *e.ChemoPlan, event *pl.Event, dbx ...*gorm.DB) error {
pl.SetLogInfo(event, data, "started", "DBUpdate")
setDataUpdate(data)
setDatafail(input, data)
var tx *gorm.DB
if len(dbx) > 0 {
@@ -20,4 +20,5 @@ func setData[T *e.CreateDto | *e.UpdateDto](input T, data *e.Chemo) {
data.Encounter_Id = inputSrc.Encounter_Id
data.Status_Code = inputSrc.Status_Code
data.Specialist_Code = inputSrc.Specialist_Code
data.Class_Code = inputSrc.Class_Code
}
@@ -47,6 +47,11 @@ func ReadListData(input e.ReadListDto, event *pl.Event, dbx ...*gorm.DB) ([]e.Ch
tx = dg.I
}
if input.Class_Code != "" {
tx = tx.Where(`"Chemo"."Class_Code" = ?`, input.Class_Code)
input.Class_Code = ""
}
tx = tx.
Model(&e.Chemo{}).
Joins(`LEFT JOIN "Encounter" "e" ON "e"."Id" = "Chemo"."Encounter_Id"`).
@@ -660,21 +660,24 @@ 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
chemoProtoc, err := validateChemoAdm(*input.Patient_Id, event)
chemoPlan, err = validateChemo(*input.Patient_Id, event)
if err != nil {
return err
}
if chemoProtoc == nil {
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{
@@ -691,7 +694,8 @@ func insertDataSubClassAmbulatory(input e.CreateDto, soapiData []es.CreateDto, e
}
// set chemo-plan to planned
err = updateChemoPlan(chemoProtoc, event, tx)
chemoPlan.Encounter_Id = &input.Id
err = updateChemoPlan(chemoPlan, event, tx)
if err != nil {
return err
}
@@ -898,7 +902,10 @@ func updateChemoPlan(data *ecpl.ChemoPlan, event *pl.Event, dbx ...*gorm.DB) err
tx = dg.I
}
if err := tx.Model(&data).Update(`"Status"`, ere.SPCPlanned).Error; err != nil {
err := tx.Model(&data).Updates(map[string]interface{}{
`"Status"`: ere.SPCPlanned,
`"Encounter_Id"`: data.Encounter_Id}).Error
if err != nil {
return err
}
@@ -913,11 +920,15 @@ func getChemoProtocol(patientId uint, event *pl.Event) (*ecp.ChemoProtocol, erro
var tx = dg.I
tx = tx.Model(&ecp.ChemoProtocol{}).
Joins(`"ChemoPlan" cp ON cp."Protocol_Id" = "ChemoProtocol"."Id"`).
Where(`"Patient.Id" = ? AND cp."Status" IS NULL`, patientId).
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 tx.Order(`"Id" ASC`).Limit(1)
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 {
@@ -928,35 +939,80 @@ func getChemoProtocol(patientId uint, event *pl.Event) (*ecp.ChemoProtocol, erro
return &data, nil
}
func validateChemoAdm(patientId uint, event *pl.Event) (*ecpl.ChemoPlan, error) {
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
chemo, err := getChemoProtocol(patientId, event)
chemoProtocol, err := getChemoProtocol(patientId, event)
if err != nil {
return nil, err
}
if chemo.Status_Code != erc.DVCVerified {
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, chemo)
return nil, pl.SetLogError(event, chemoProtocol)
}
if now.Before(*chemo.StartDate) || now.After(*chemo.EndDate) {
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, chemo)
return nil, pl.SetLogError(event, chemoProtocol)
}
if chemo.ChemoPlans == nil || len(*chemo.ChemoPlans) == 0 {
if chemoProtocol.ChemoPlans == nil || len(*chemoProtocol.ChemoPlans) == 0 {
return nil, nil
}
// Return the first chemo plan
return &(*chemo.ChemoPlans)[0], nil
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
}
+14 -2
View File
@@ -42,7 +42,7 @@ func Create(input e.CreateDto) (*d.Data, error) {
return nil, pl.SetLogError(&event, input)
}
chemoPlan, err := validateIfEncounterIsChemo(*input.Encounter_Id, &event)
chemoPlan, err := validateIfEncounterIsChemo(*input.Encounter_Id, "c", &event)
if err != nil {
return nil, err
}
@@ -57,7 +57,7 @@ func Create(input e.CreateDto) (*d.Data, error) {
// update chemoPlan
if chemoPlan != nil {
chemoPlan.Encounter_Id = input.Encounter_Id
if err = ucp.UpdateData(chemoPlan, &event, tx); err != nil {
if err = ucp.UpdateData(chemoPlan, "c", &event, tx); err != nil {
return err
}
}
@@ -252,6 +252,18 @@ func Delete(input e.DeleteDto) (*d.Data, error) {
return err
}
// update chemoPlan
chemoPlan, err := validateIfEncounterIsChemo(*data.Encounter_Id, "d", &event)
if err != nil {
return err
}
if chemoPlan != nil {
if err = ucp.UpdateData(chemoPlan, "d", &event, tx); err != nil {
return err
}
}
mwRunner.setMwType(pu.MWTPre)
// Run pre-middleware
if err = mwRunner.ExecuteIfSyncOn(func() error {
+18 -56
View File
@@ -6,11 +6,7 @@ package soapi
import (
"errors"
"fmt"
erc "simrs-vx/internal/domain/references/common"
pl "simrs-vx/pkg/logger"
pu "simrs-vx/pkg/use-case-helper"
"time"
dg "github.com/karincake/apem/db-gorm-pg"
"gorm.io/gorm"
@@ -55,7 +51,7 @@ func setBulkData(input []e.CreateDto, encounterId uint) []e.Soapi {
return data
}
func validateIfEncounterIsChemo(encounterId uint, event *pl.Event) (*ecpl.ChemoPlan, error) {
func validateIfEncounterIsChemo(encounterId uint, method string, event *pl.Event) (*ecpl.ChemoPlan, error) {
// get encounter
enc, err := getEncounter(encounterId, event)
if err != nil {
@@ -64,12 +60,12 @@ func validateIfEncounterIsChemo(encounterId uint, event *pl.Event) (*ecpl.ChemoP
// Encounter must be Ambulatory and NOT Rehab
a := enc.Ambulatory
if enc.Class_Code != ere.ECAmbulatory || a == nil || a.Class_Code == ere.ACCRehab {
if a == nil || a.Class_Code == ere.ACCRehab {
return nil, nil
}
// get chemo protocol
chemo, err := getChemo(*enc.Patient_Id, event)
chemo, err := getChemo(*enc.Patient_Id, enc.Id, event)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
@@ -77,10 +73,6 @@ func validateIfEncounterIsChemo(encounterId uint, event *pl.Event) (*ecpl.ChemoP
return nil, pl.SetLogError(event, nil)
}
if err = chemoProtocoValidation(chemo, event); err != nil {
}
if chemo.ChemoPlans == nil || len(*chemo.ChemoPlans) == 0 {
return nil, nil
}
@@ -89,69 +81,39 @@ func validateIfEncounterIsChemo(encounterId uint, event *pl.Event) (*ecpl.ChemoP
return &(*chemo.ChemoPlans)[0], nil
}
func chemoProtocoValidation(chemo *ecp.ChemoProtocol, event *pl.Event) error {
if chemo.Status_Code != erc.DVCVerified {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "data-not-match",
Detail: fmt.Sprintf("protocol chemo not yet verified"),
}
return pl.SetLogError(event, chemo)
}
now := time.Now()
if now.Before(*chemo.StartDate) || now.After(*chemo.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 pl.SetLogError(event, chemo)
}
return nil
}
func getEncounter(encounterId uint, event *pl.Event) (*ee.Encounter, error) {
pl.SetLogInfo(event, nil, "started", "getEncounter")
data := ee.Encounter{}
var tx = dg.I
if err := tx.Model(&ee.Encounter{}).
Where(`"Encounter_Id" = ?`, encounterId).
Scopes(withConditionalAmbulatory()).
First(&data).Error; err != nil {
if processedErr := pu.HandleReadError(err, event, source, nil, encounterId); processedErr != nil {
return nil, processedErr
}
err := tx.Model(&ee.Encounter{}).
Joins(`LEFT JOIN "Ambulatory" a ON a."Encounter_Id" = "Encounter"."Id"`).
Where(`"Encounter"."Id" = ?`, encounterId).
Where(`"Encounter"."Class_Code" = ?`, ere.ECAmbulatory).
Preload("Ambulatory").
First(&data).Error
if err != nil {
return nil, err
}
pl.SetLogInfo(event, nil, "complete")
return &data, nil
}
func withConditionalAmbulatory() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Preload(`"Ambulatory"`, func(db *gorm.DB) *gorm.DB {
return db.Joins(`JOIN "Encounter" e ON "e"."Id" = "Ambulatory"."Encounter_Id"`)
})
}
}
func getChemo(patientId uint, event *pl.Event) (*ecp.ChemoProtocol, error) {
pl.SetLogInfo(event, nil, "started", "getChemoFromEncounter")
func getChemo(patientId, encounterId uint, event *pl.Event) (*ecp.ChemoProtocol, error) {
pl.SetLogInfo(event, nil, "started", "getChemo")
data := ecp.ChemoProtocol{}
var tx = dg.I
tx = tx.Model(&ecp.ChemoProtocol{}).
Joins(`"ChemoPlan" cp ON cp."Protocol_Id" = "ChemoProtocol"."Id"`).
Where(`"Patient.Id" = ? AND cp."Status" = ?`, patientId, ere.SPCPlanned).
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 tx.Order(`"Id" ASC`).Limit(1)
return db.
Where(`"Encounter_Id" = ?`, encounterId)
}).
First(&data)