package encounter import ( "errors" "fmt" us "simrs-vx/internal/use-case/main-use-case/soapi" "strconv" "time" dg "github.com/karincake/apem/db-gorm-pg" d "github.com/karincake/dodol" "gorm.io/gorm" pl "simrs-vx/pkg/logger" pu "simrs-vx/pkg/use-case-helper" erc "simrs-vx/internal/domain/references/common" ere "simrs-vx/internal/domain/references/encounter" erg "simrs-vx/internal/domain/references/organization" eaeh "simrs-vx/internal/domain/main-entities/adm-employee-hist" edc "simrs-vx/internal/domain/main-entities/death-cause" e "simrs-vx/internal/domain/main-entities/encounter" eir "simrs-vx/internal/domain/main-entities/internal-reference" erdh "simrs-vx/internal/domain/main-entities/responsible-doctor-hist" es "simrs-vx/internal/domain/main-entities/soapi" uaeh "simrs-vx/internal/use-case/main-use-case/adm-employee-hist" udc "simrs-vx/internal/use-case/main-use-case/death-cause" uir "simrs-vx/internal/use-case/main-use-case/internal-reference" urdh "simrs-vx/internal/use-case/main-use-case/responsible-doctor-hist" ) const source = "encounter" var now = time.Now() func Create(input e.CreateDto) (*d.Data, error) { var ( data e.Encounter recentSoapiDataforCopy []es.CreateDto err error ) event := pl.Event{ Feature: "Create", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "create") // validate rehab bpjs if input.RefTypeCode == ere.RTCBpjs && input.Class_Code == ere.ECAmbulatory && ere.AmbulatoryClassCode(*input.SubClass_Code) == ere.ACCRehab { // get latest rehab data recentRehabData, err := getLatestRehabData(input, &event) if err != nil { return nil, err } if recentRehabData != nil { // determine VisitModeCode input.VisitMode_Code, input.RecentEncounterAdm, err = determineVisitMode(recentRehabData, input, &event) if err != nil { return nil, err } } else { input.VisitMode_Code = ere.VMCAdm } // if visitMode_Code is series, then get data soapi for copy if input.VisitMode_Code == ere.VMCSeries { // get data soapi recentSoapiDataforCopy, err = getSoapiEncounterAdm(*input.RecentEncounterAdm, &event) if err != nil { return nil, err } } } // check if user has employee position if !input.AuthInfo.HasEmployeePosition() { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "auth-forbidden", Detail: "user has no employee position", Raw: errors.New("authentication failed"), } return nil, pl.SetLogError(&event, input) } // check only user with registration position is allowed to create encounter if input.AuthInfo.Employee_Position_Code != nil && *input.AuthInfo.Employee_Position_Code != string(erg.EPCReg) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "auth-forbidden", Detail: "user position is not allowed, only user with registration position is allowed to create encounter", Raw: errors.New("authentication failed"), } return nil, pl.SetLogError(&event, input) } else { input.Adm_Employee_Id = input.AuthInfo.Employee_Id } // check if patient is new in the hospital input.NewStatus, err = identifyPatientStatus(input) err = dg.I.Transaction(func(tx *gorm.DB) error { mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunCreateMiddleware(createPreMw, &input, &data); err != nil { return err } // create encounter if resData, err := CreateData(input, &event, tx); err != nil { return err } else { data = *resData input.Id = data.Id } // insert ambulatory/emergency/inpatient err = insertdataClassCode(input, recentSoapiDataforCopy, &event, tx) if err != nil { return err } // insert adm_employee_hist if _, err := uaeh.CreateData(eaeh.CreateDto{ Encounter_Id: &data.Id, Employee_Id: data.Adm_Employee_Id, StartedAt: &now}, &event, tx); err != nil { return err } mwRunner.setMwType(pu.MWTPost) // Run post-middleware if err := mwRunner.RunCreateMiddleware(createPostMw, &input, &data); err != nil { return err } pl.SetLogInfo(&event, nil, "complete") return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.II{ "source": source, "structure": "single-data", "status": "created", }, Data: data.ToResponse(), }, nil } func ReadList(input e.ReadListDto) (*d.Data, error) { var data *e.Encounter var dataList []e.Encounter var metaList *e.MetaDto var err error event := pl.Event{ Feature: "ReadList", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "readList") err = dg.I.Transaction(func(tx *gorm.DB) error { mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunReadListMiddleware(readListPreMw, &input, data); err != nil { return err } if dataList, metaList, err = ReadListData(input, &event, tx); err != nil { return err } mwRunner.setMwType(pu.MWTPost) // Run post-middleware if err := mwRunner.RunReadListMiddleware(readListPostMw, &input, data); err != nil { return err } return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "list-data", "status": "fetched", "page_number": strconv.Itoa(metaList.PageNumber), "page_size": strconv.Itoa(metaList.PageSize), "record_totalCount": strconv.Itoa(metaList.Count), "record_currentCount": strconv.Itoa(len(dataList)), }, Data: e.ToResponseList(dataList), }, nil } func ReadDetail(input e.ReadDetailDto) (*d.Data, error) { var data *e.Encounter var err error event := pl.Event{ Feature: "ReadDetail", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "readDetail") err = dg.I.Transaction(func(tx *gorm.DB) error { mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunReadDetailMiddleware(readDetailPreMw, &input, data); err != nil { return err } if data, err = ReadDetailData(input, &event, tx); err != nil { return err } mwRunner.setMwType(pu.MWTPost) // Run post-middleware if err := mwRunner.RunReadDetailMiddleware(readDetailPostMw, &input, data); err != nil { return err } return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "fetched", }, Data: data.ToResponse(), }, nil } func Update(input e.UpdateDto) (*d.Data, error) { rdDto := e.ReadDetailDto{Id: uint16(input.Id)} var data *e.Encounter var err error event := pl.Event{ Feature: "Update", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "update") err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } if data.IsDone() { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-state-mismatch", Detail: "encounter is done", Raw: errors.New("encounter is done"), } return pl.SetLogError(&event, input) } mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { return err } if err := UpdateData(input, data, &event, tx); err != nil { return err } pl.SetLogInfo(&event, nil, "complete") mwRunner.setMwType(pu.MWTPost) // Run post-middleware if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { return err } return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "updated", }, Data: data.ToResponse(), }, nil } func Delete(input e.DeleteDto) (*d.Data, error) { rdDto := e.ReadDetailDto{Id: uint16(input.Id)} var data *e.Encounter var err error event := pl.Event{ Feature: "Delete", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "delete") // check if user has employee position if !input.AuthInfo.HasEmployeePosition() { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "auth-forbidden", Detail: "user has no employee position", Raw: errors.New("authentication failed"), } return nil, pl.SetLogError(&event, input) } // check only user with registration position is allowed to create encounter if input.AuthInfo.Employee_Position_Code != nil && *input.AuthInfo.Employee_Position_Code != string(erg.EPCReg) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "auth-forbidden", Detail: "user position is not allowed, only user with registration position is allowed to create encounter", Raw: errors.New("authentication failed"), } return nil, pl.SetLogError(&event, input) } err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunDeleteMiddleware(readDetailPreMw, &rdDto, data); err != nil { return err } if err := DeleteData(data, &event, tx); err != nil { return err } mwRunner.setMwType(pu.MWTPost) // Run post-middleware if err := mwRunner.RunDeleteMiddleware(readDetailPostMw, &rdDto, data); err != nil { return err } return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "deleted", }, Data: data.ToResponse(), }, nil } func CheckOut(input e.DischargeDto) (*d.Data, error) { rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Ambulatory,Rehab"} var data *e.Encounter var err error event := pl.Event{ Feature: "CheckOut", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "checkOut") err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } if data.IsDone() { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-state-mismatch", Detail: "encounter is done", Raw: errors.New("encounter is done"), } return pl.SetLogError(&event, input) } if data.Ambulatory != nil && (data.Ambulatory.Class_Code == ere.ACCReg || data.Ambulatory.Class_Code == ere.ACCRehab) { // validate if soapi exist if err = getSoapiByTypeCode(data, &event, "check-out"); err != nil { return err } if data.Ambulatory.Class_Code == ere.ACCRehab { // verify and update rehabData if visit count has reached the allowed limit if err = verifyRehabLimit(data, &event, tx); err != nil { return err } } } else { // chemo TBC if err := checkSoapiByDocExists(data.Id, &event, tx); err != nil { return err } } if err := updateDischargeData(input, data, &event, tx); err != nil { return err } if err := checkNewOrdersExist(data.Id, &event, tx); err != nil { return err } if err := createMedication(data.Id, &event, tx); err != nil { if !pu.IsDataNotFoundError(err) { return err } } // update finishedAt in latest responsible_doctor_hist if err = updateLatestResponsibleDoctorHist(e.CheckinDto{Id: input.Id, FinishedAt: &now}, &event, tx); err != nil { return err } // update finishedAt in latest adm_employee_hist if err = updateLatestAdmEmployeeHist(e.CheckinDto{Id: input.Id, FinishedAt: &now}, &event, tx); err != nil { return err } // insert data death-cause if *input.Discharge_Method_Code == ere.DMCDeath { if _, err = udc.CreateData(edc.CreateDto{Encounter_Id: &input.Id, Value: input.DeathCause}, &event, tx); err != nil { return err } } pl.SetLogInfo(&event, nil, "complete") return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "checkOut", }, Data: data.ToResponse(), }, nil } func UpdateStatusCode(input e.UpdateStatusDto) (*d.Data, error) { rdDto := e.ReadDetailDto{Id: input.Id} var data *e.Encounter var err error event := pl.Event{ Feature: "Update Status Code", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "update") err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } if data.IsDone() { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-state-mismatch", Detail: "encounter is done", Raw: errors.New("encounter is done"), } return pl.SetLogError(&event, input) } if input.StatusCode == erc.DSCCancel { // TODO: Prevent cancellation if the billing has been verified // TODO: Only "supervisi pendaftaran" could cancel encounter roleAllowedToCancel := []string{ string(erg.EPCReg), string(erg.EPCNur), string(erg.EPCDoc), } // check if user has employee position if !input.AuthInfo.HasEmployeePosition() { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "auth-forbidden", Detail: "user has no employee position", Raw: errors.New("authentication failed"), } return pl.SetLogError(&event, input) } if input.AuthInfo.Employee_Position_Code != nil && !pu.Contains(roleAllowedToCancel, *input.AuthInfo.Employee_Position_Code) { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "auth-forbidden", Detail: "user position is not allowed, only user with registration, nurse, or doctor position is allowed to cancel encounter", Raw: errors.New("authentication failed"), } return pl.SetLogError(&event, input) } // Prevent cancellation if soapi exist encounterId := uint(input.Id) dataSoapi, _, err := us.ReadListData(es.ReadListDto{ FilterDto: es.FilterDto{ Encounter_Id: &encounterId, }}, &event, tx) if err != nil { return err } if len(dataSoapi) > 0 { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "cancel-not-allowed", Detail: "encounter can't be cancelled because SOAPI already exists", Raw: errors.New("encounter has SOAPI records"), } return pl.SetLogError(&event, input) } } mwRunner := newMiddlewareRunner(&event, tx) mwRunner.setMwType(pu.MWTPre) // Run pre-middleware if err := mwRunner.RunUpdateMiddleware(readDetailPreMw, &rdDto, data); err != nil { return err } if err := UpdateStatusData(input, data, &event, tx); err != nil { return err } pl.SetLogInfo(&event, nil, "complete") mwRunner.setMwType(pu.MWTPost) // Run post-middleware if err := mwRunner.RunUpdateMiddleware(readDetailPostMw, &rdDto, data); err != nil { return err } return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "updated", }, Data: data.ToResponse(), }, nil } func CheckIn(input e.CheckinDto) (*d.Data, error) { rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Rehab,Ambulatory"} var data *e.Encounter var err error event := pl.Event{ Feature: "CheckIn", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "checkIn") // validate foreign key if err := validateForeignKey(input); err != nil { return nil, err } // set startedAt if input.StartedAt == nil { input.StartedAt = &now } err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } if data.IsDone() { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-state-mismatch", Detail: "encounter is done", Raw: errors.New("encounter is done"), } return pl.SetLogError(&event, input) } // validate if soapi exist if data.Ambulatory != nil && (data.Ambulatory.Class_Code == ere.ACCReg || data.Ambulatory.Class_Code == ere.ACCRehab) { err = getSoapiByTypeCode(data, &event, "check-in") if err != nil { return err } } // Upsert responsible_doctor_hist if responsible_doctor_code has changed if data.Responsible_Doctor_Code == nil || *input.Responsible_Doctor_Code != *data.Responsible_Doctor_Code { // upsert responsibleDoctorHist if err = upsertResponsibleDoctorHist(erdh.CreateDto{ Encounter_Id: &data.Id, Doctor_Code: input.Responsible_Doctor_Code, StartedAt: input.StartedAt, }, &event, tx); err != nil { return err } } // Upsert adm_employee_hist if adm_employee_id has changed if input.Adm_Employee_Id != nil && *input.Adm_Employee_Id != *data.Adm_Employee_Id { // upsert admEmployeeHist if err = upsertAdmEmployeeHist(eaeh.CreateDto{ Encounter_Id: &data.Id, Employee_Id: input.Adm_Employee_Id, StartedAt: input.StartedAt, }, &event, tx); err != nil { return err } } // update encounter data if err := updateCheckInData(input, data, &event, tx); err != nil { return err } pl.SetLogInfo(&event, nil, "complete") return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "checkIn", }, Data: data.ToResponse(), }, nil } func RequestSwitchUnit(input e.SwitchUnitDto) (*d.Data, error) { rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Responsible_Doctor"} var data *e.Encounter var err error event := pl.Event{ Feature: "RequestSwitchUnit", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "checkOut") unitCodes := make(map[string]struct{}) doctorCodes := make(map[string]struct{}) for _, ref := range *input.InternalReferences { if ref.Unit_Code != nil { unitCodes[*ref.Unit_Code] = struct{}{} } if ref.Doctor_Code != nil { doctorCodes[*ref.Doctor_Code] = struct{}{} } } // validate unit if err = validateUnitCodes(unitCodes, &event); err != nil { return nil, err } // validate doctor if err = validateDoctorCodes(doctorCodes, &event); err != nil { return nil, err } err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } //if data.IsDone() { // event.Status = "failed" // event.ErrInfo = pl.ErrorInfo{ // Code: "data-state-mismatch", // Detail: "encounter is done", // Raw: errors.New("encounter is done"), // } // return pl.SetLogError(&event, input) //} // verify Soapi exist for current responsible doctor dataSoapi, err := getSoapiByResponsibleDoctor(*data, &event) if err != nil { return err } if len(dataSoapi) < 1 { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "missing-soapi", Detail: fmt.Sprintf("Missing soapi from latest responsible doctor"), } return pl.SetLogError(&event, input) } // bulk internal references if err := uir.CreateBulkData(*input.InternalReferences, input.Id, &event, tx); err != nil { return err } pl.SetLogInfo(&event, nil, "complete") return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "requestSwitchUnit", }, Data: data.ToResponse(), }, nil } func ApproveSwitchUnit(input e.ApproveUnitDto) (*d.Data, error) { rdDto := e.ReadDetailDto{Id: uint16(input.Id), Includes: "Responsible_Doctor"} var data *e.Encounter var err error event := pl.Event{ Feature: "ApproveSwitchUnit", Source: source, } // Start log pl.SetLogInfo(&event, input, "started", "approveSwitchUnit") err = dg.I.Transaction(func(tx *gorm.DB) error { pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") if data, err = ReadDetailData(rdDto, &event, tx); err != nil { return err } // get internal reference irData, err := uir.ReadDetailData(eir.ReadDetailDto{ Id: input.InternalReferences_Id, Includes: "Doctor"}, &event, tx) if err != nil { return err } if *irData.Status_Code != erc.DACNew { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "data-state-mismatch", Detail: "internal references is approve", Raw: errors.New("internal references is approve"), } return pl.SetLogError(&event, input) } //if data.IsDone() { // event.Status = "failed" // event.ErrInfo = pl.ErrorInfo{ // Code: "data-state-mismatch", // Detail: "encounter is done", // Raw: errors.New("encounter is done"), // } // return pl.SetLogError(&event, input) //} // verify Soapi exist for current responsible doctor dataSoapi, err := getSoapiByResponsibleDoctor(*data, &event) if err != nil { return err } if len(dataSoapi) < 1 { event.Status = "failed" event.ErrInfo = pl.ErrorInfo{ Code: "missing-soapi", Detail: fmt.Sprintf("Missing soapi from latest responsible doctor"), } return pl.SetLogError(&event, input) } // update internal reference if err = uir.UpdateData(eir.UpdateDto{ Id: input.InternalReferences_Id, CreateDto: eir.CreateDto{ Encounter_Id: &input.Id, Doctor_Code: irData.Doctor_Code, Unit_Code: irData.Unit_Code, Status_Code: erc.DACApproved, }}, irData, &event, tx); err != nil { return err } // update encounter if err = updateEncounterApproveSwitchUnit(*irData, &event, tx); err != nil { return err } // update latest responsible doctor hist if err = updateLatestResponsibleDoctorHist(e.CheckinDto{Id: input.Id, FinishedAt: &now}, &event, tx); err != nil { return err } // create responsible doctor based on internal reference data if _, err = urdh.CreateData(erdh.CreateDto{ Encounter_Id: &input.Id, Doctor_Code: irData.Doctor_Code, StartedAt: &now, }, &event, tx); err != nil { return err } // update data response data.Responsible_Doctor_Code = irData.Doctor_Code data.Unit_Code = irData.Unit_Code // data.Specialist_Code = irData.Doctor.Specialist_Code data.Subspecialist_Code = irData.Doctor.Subspecialist_Code pl.SetLogInfo(&event, nil, "complete") return nil }) if err != nil { return nil, err } return &d.Data{ Meta: d.IS{ "source": source, "structure": "single-data", "status": "updated", }, Data: data.ToResponse(), }, nil }