From e71f63de6723eff4a719993beda3531551c1641f Mon Sep 17 00:00:00 2001 From: ZzIsGod1019 <1498852723@qq.com> Date: Mon, 2 Dec 2024 15:19:42 +0800 Subject: [PATCH 1/3] flow: improve feature (#860) --- .../flow/src/api/ca/flow_ca_model_api.rs | 56 +- backend/middlewares/flow/src/api/cc.rs | 1 + .../flow/src/api/cc/flow_cc_inst_api.rs | 70 +- .../flow/src/api/cc/flow_cc_model_api.rs | 174 +- .../src/api/cc/flow_cc_model_version_api.rs | 121 + .../flow/src/api/ci/flow_ci_inst_api.rs | 52 +- .../flow/src/api/ci/flow_ci_model_api.rs | 92 +- .../flow/src/api/cs/flow_cs_config_api.rs | 2 +- .../flow/src/api/ct/flow_ct_model_api.rs | 24 +- backend/middlewares/flow/src/domain.rs | 1 + .../middlewares/flow/src/domain/flow_inst.rs | 19 +- .../middlewares/flow/src/domain/flow_model.rs | 26 +- .../flow/src/domain/flow_model_version.rs | 46 + .../middlewares/flow/src/domain/flow_state.rs | 6 +- .../flow/src/domain/flow_transition.rs | 2 +- backend/middlewares/flow/src/dto.rs | 1 + .../flow/src/dto/flow_external_dto.rs | 7 + .../middlewares/flow/src/dto/flow_inst_dto.rs | 152 +- .../flow/src/dto/flow_model_dto.rs | 160 +- .../flow/src/dto/flow_model_version_dto.rs | 196 ++ .../flow/src/dto/flow_state_dto.rs | 283 ++- .../flow/src/dto/flow_transition_dto.rs | 14 +- backend/middlewares/flow/src/flow_config.rs | 1 + .../middlewares/flow/src/flow_constants.rs | 2 + .../middlewares/flow/src/flow_initializer.rs | 12 +- backend/middlewares/flow/src/serv.rs | 2 + .../flow/src/serv/clients/flow_log_client.rs | 2 +- .../flow/src/serv/clients/search_client.rs | 122 +- .../flow/src/serv/flow_event_serv.rs | 111 +- .../flow/src/serv/flow_external_serv.rs | 79 +- .../flow/src/serv/flow_inst_serv.rs | 1032 ++++++--- .../flow/src/serv/flow_model_serv.rs | 2010 +++++++++-------- .../flow/src/serv/flow_model_version_serv.rs | 701 ++++++ .../flow/src/serv/flow_rel_serv.rs | 2 +- .../flow/src/serv/flow_state_serv.rs | 108 +- .../flow/src/serv/flow_transition_serv.rs | 468 ++++ .../flow/tests/config/conf-default.toml | 1 + backend/middlewares/flow/tests/mock_api.rs | 6 +- .../middlewares/flow/tests/test_flow_api.rs | 2 +- .../flow/tests/test_flow_scenes_fsm1.rs | 414 +++- backend/middlewares/schedule/Cargo.toml | 1 + .../tests/test_basic_schedual_service.rs | 5 +- .../bios-all/config/locale/zh-cn.flow | 4 + .../src/api/ci/object_ci_obj_api.rs | 65 +- backend/spi/spi-object/src/dto/object_dto.rs | 3 +- backend/spi/spi-object/src/serv.rs | 2 +- .../custom_s3/object_custom_s3_initializer.rs | 13 +- .../custom_s3/object_custom_s3_obj_serv.rs | 25 +- .../spi-object/src/serv/object_obj_serv.rs | 150 +- .../src/serv/obs/object_obs_obj_serv.rs | 13 +- backend/spi/spi-object/src/serv/s3.rs | 32 +- .../spi-search/tests/init_search_container.rs | 1 - .../supports/reach/tests/test_reach_common.rs | 5 +- 53 files changed, 5126 insertions(+), 1773 deletions(-) create mode 100644 backend/middlewares/flow/src/api/cc/flow_cc_model_version_api.rs create mode 100644 backend/middlewares/flow/src/domain/flow_model_version.rs create mode 100644 backend/middlewares/flow/src/dto/flow_model_version_dto.rs create mode 100644 backend/middlewares/flow/src/serv/flow_model_version_serv.rs create mode 100644 backend/middlewares/flow/src/serv/flow_transition_serv.rs diff --git a/backend/middlewares/flow/src/api/ca/flow_ca_model_api.rs b/backend/middlewares/flow/src/api/ca/flow_ca_model_api.rs index f4432e819..8b4fc3172 100644 --- a/backend/middlewares/flow/src/api/ca/flow_ca_model_api.rs +++ b/backend/middlewares/flow/src/api/ca/flow_ca_model_api.rs @@ -1,18 +1,14 @@ use std::collections::HashMap; -use bios_basic::rbum::{helper::rbum_scope_helper, rbum_enumeration::RbumScopeLevelKind}; -use tardis::{ - basic::dto::TardisContext, - web::{ - context_extractor::TardisContextExtractor, - poem::{web::Json, Request}, - poem_openapi, - web_resp::{TardisApiResult, TardisResp}, - }, +use tardis::web::{ + context_extractor::TardisContextExtractor, + poem::{web::Json, Request}, + poem_openapi, + web_resp::{TardisApiResult, TardisResp}, }; use crate::{ - dto::flow_model_dto::{FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelCopyOrReferenceReq, FlowModelSingleCopyOrReferenceReq}, + dto::flow_model_dto::{FlowModelAggResp, FlowModelCopyOrReferenceReq, FlowModelKind, FlowModelSingleCopyOrReferenceReq}, flow_constants, serv::{flow_inst_serv::FlowInstServ, flow_model_serv::FlowModelServ}, }; @@ -37,16 +33,18 @@ impl FlowCaModelApi { funs.begin().await?; let _orginal_models = FlowModelServ::clean_rel_models(None, None, None, &funs, &ctx.0).await?; let mut result = HashMap::new(); - let mock_ctx = match req.0.op { - FlowModelAssociativeOperationKind::Copy => ctx.0.clone(), - FlowModelAssociativeOperationKind::Reference => TardisContext { - own_paths: rbum_scope_helper::get_path_item(RbumScopeLevelKind::L1.to_int(), &ctx.0.own_paths).unwrap_or_default(), - ..ctx.0.clone() - }, - }; for (_, rel_model_id) in req.0.rel_model_ids { - let new_model = FlowModelServ::copy_or_reference_model(&rel_model_id, Some(ctx.0.own_paths.clone()), &req.0.op, Some(false), &funs, &mock_ctx).await?; - FlowInstServ::batch_update_when_switch_model(None, &new_model.tag, &new_model.id, new_model.states.clone(), &new_model.init_state_id, &funs, &ctx.0).await?; + let new_model = FlowModelServ::copy_or_reference_model(&rel_model_id, &req.0.op, FlowModelKind::AsModel, &funs, &ctx.0).await?; + FlowInstServ::batch_update_when_switch_model( + None, + &new_model.tag, + &new_model.current_version_id, + new_model.states.clone(), + &new_model.init_state_id, + &funs, + &ctx.0, + ) + .await?; result.insert(rel_model_id.clone(), new_model); } @@ -69,15 +67,17 @@ impl FlowCaModelApi { let mut funs = flow_constants::get_tardis_inst(); funs.begin().await?; let _orginal_models = FlowModelServ::clean_rel_models(None, None, Some(vec![req.0.tag.clone()]), &funs, &ctx.0).await?; - let mock_ctx = match req.0.op { - FlowModelAssociativeOperationKind::Copy => ctx.0.clone(), - FlowModelAssociativeOperationKind::Reference => TardisContext { - own_paths: rbum_scope_helper::get_path_item(RbumScopeLevelKind::L1.to_int(), &ctx.0.own_paths).unwrap_or_default(), - ..ctx.0.clone() - }, - }; - let new_model = FlowModelServ::copy_or_reference_model(&req.0.rel_model_id, Some(ctx.0.own_paths.clone()), &req.0.op, Some(false), &funs, &mock_ctx).await?; - FlowInstServ::batch_update_when_switch_model(None, &new_model.tag, &new_model.id, new_model.states.clone(), &new_model.init_state_id, &funs, &ctx.0).await?; + let new_model = FlowModelServ::copy_or_reference_model(&req.0.rel_model_id, &req.0.op, FlowModelKind::AsModel, &funs, &ctx.0).await?; + FlowInstServ::batch_update_when_switch_model( + None, + &new_model.tag, + &new_model.current_version_id, + new_model.states.clone(), + &new_model.init_state_id, + &funs, + &ctx.0, + ) + .await?; funs.commit().await?; ctx.0.execute_task().await?; diff --git a/backend/middlewares/flow/src/api/cc.rs b/backend/middlewares/flow/src/api/cc.rs index 883039a4a..16838ffc2 100644 --- a/backend/middlewares/flow/src/api/cc.rs +++ b/backend/middlewares/flow/src/api/cc.rs @@ -1,3 +1,4 @@ pub mod flow_cc_inst_api; pub mod flow_cc_model_api; +pub mod flow_cc_model_version_api; pub mod flow_cc_state_api; diff --git a/backend/middlewares/flow/src/api/cc/flow_cc_inst_api.rs b/backend/middlewares/flow/src/api/cc/flow_cc_inst_api.rs index 9f3b0aa65..0bef5568e 100644 --- a/backend/middlewares/flow/src/api/cc/flow_cc_inst_api.rs +++ b/backend/middlewares/flow/src/api/cc/flow_cc_inst_api.rs @@ -11,7 +11,7 @@ use tardis::web::web_resp::{TardisApiResult, TardisPage, TardisResp, Void}; use crate::dto::flow_external_dto::FlowExternalCallbackOp; use crate::dto::flow_inst_dto::{ FlowInstAbortReq, FlowInstDetailResp, FlowInstFindNextTransitionResp, FlowInstFindNextTransitionsReq, FlowInstFindStateAndTransitionsReq, FlowInstFindStateAndTransitionsResp, - FlowInstModifyAssignedReq, FlowInstModifyCurrentVarsReq, FlowInstStartReq, FlowInstSummaryResp, FlowInstTransferReq, FlowInstTransferResp, + FlowInstModifyAssignedReq, FlowInstModifyCurrentVarsReq, FlowInstOperateReq, FlowInstStartReq, FlowInstSummaryResp, FlowInstTransferReq, FlowInstTransferResp, }; use crate::flow_constants; use crate::helper::loop_check_helper; @@ -27,10 +27,8 @@ impl FlowCcInstApi { /// 启动实例(返回实例ID) #[oai(path = "/", method = "post")] async fn start(&self, add_req: Json, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult { - let mut funs = flow_constants::get_tardis_inst(); - funs.begin().await?; + let funs = flow_constants::get_tardis_inst(); let result = FlowInstServ::start(&add_req.0, None, &funs, &ctx.0).await?; - funs.commit().await?; ctx.0.execute_task().await?; TardisResp::ok(result) } @@ -66,8 +64,10 @@ impl FlowCcInstApi { async fn paginate( &self, flow_model_id: Query>, + rel_business_obj_id: Query>, tag: Query>, finish: Query>, + main: Query>, current_state_id: Query>, with_sub: Query>, page_number: Query, @@ -76,7 +76,20 @@ impl FlowCcInstApi { _request: &Request, ) -> TardisApiResult> { let funs = flow_constants::get_tardis_inst(); - let result = FlowInstServ::paginate(flow_model_id.0, tag.0, finish.0, current_state_id.0, with_sub.0, page_number.0, page_size.0, &funs, &ctx.0).await?; + let result = FlowInstServ::paginate( + flow_model_id.0, + tag.0, + finish.0, + main.0, + current_state_id.0, + rel_business_obj_id.0, + with_sub.0, + page_number.0, + page_size.0, + &funs, + &ctx.0, + ) + .await?; ctx.0.execute_task().await?; TardisResp::ok(result) } @@ -93,7 +106,8 @@ impl FlowCcInstApi { _request: &Request, ) -> TardisApiResult> { let funs = flow_constants::get_tardis_inst(); - let result = FlowInstServ::find_next_transitions(&flow_inst_id.0, &next_req.0, &funs, &ctx.0).await?; + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + let result = FlowInstServ::find_next_transitions(&inst, &next_req.0, &funs, &ctx.0).await?; ctx.0.execute_task().await?; TardisResp::ok(result) } @@ -125,18 +139,22 @@ impl FlowCcInstApi { ctx: TardisContextExtractor, _request: &Request, ) -> TardisApiResult { - let funs = flow_constants::get_tardis_inst(); + let mut funs = flow_constants::get_tardis_inst(); let mut transfer = transfer_req.0; - FlowInstServ::check_transfer_vars(&flow_inst_id.0, &mut transfer, &funs, &ctx.0).await?; + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + FlowInstServ::check_transfer_vars(&inst, &mut transfer, &funs, &ctx.0).await?; + funs.begin().await?; let result = FlowInstServ::transfer( - &flow_inst_id.0, + &inst, &transfer, false, FlowExternalCallbackOp::Default, loop_check_helper::InstancesTransition::default(), &ctx.0, + &funs, ) .await?; + funs.commit().await?; ctx.0.execute_task().await?; TardisResp::ok(result) } @@ -156,21 +174,23 @@ impl FlowCcInstApi { let mut result = vec![]; let flow_inst_ids: Vec<_> = flow_inst_ids.split(',').collect(); let raw_transfer_req = transfer_req.0; - let mut flow_inst_id_transfer_map = HashMap::new(); + let mut flow_inst_transfer = vec![]; for flow_inst_id in &flow_inst_ids { let mut transfer_req = raw_transfer_req.clone(); - FlowInstServ::check_transfer_vars(flow_inst_id, &mut transfer_req, &funs, &ctx.0).await?; - flow_inst_id_transfer_map.insert(flow_inst_id, transfer_req); + let inst = FlowInstServ::get(flow_inst_id, &funs, &ctx.0).await?; + FlowInstServ::check_transfer_vars(&inst, &mut transfer_req, &funs, &ctx.0).await?; + flow_inst_transfer.push((inst, transfer_req)); } - for (flow_inst_id, transfer_req) in flow_inst_id_transfer_map { + for (inst, transfer_req) in flow_inst_transfer { result.push( FlowInstServ::transfer( - flow_inst_id, + &inst, &transfer_req, false, FlowExternalCallbackOp::Default, loop_check_helper::InstancesTransition::default(), &ctx.0, + &funs, ) .await?, ); @@ -190,8 +210,10 @@ impl FlowCcInstApi { ctx: TardisContextExtractor, _request: &Request, ) -> TardisApiResult { + let funs = flow_constants::get_tardis_inst(); let vars = HashMap::from([("current_assigned".to_string(), Value::String(modify_req.0.current_assigned))]); - FlowInstServ::modify_current_vars(&flow_inst_id.0, &vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + FlowInstServ::modify_current_vars(&inst, &vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; ctx.0.execute_task().await?; TardisResp::ok(Void {}) } @@ -207,7 +229,23 @@ impl FlowCcInstApi { ctx: TardisContextExtractor, _request: &Request, ) -> TardisApiResult { - FlowInstServ::modify_current_vars(&flow_inst_id.0, &modify_req.0.vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; + let funs = flow_constants::get_tardis_inst(); + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + FlowInstServ::modify_current_vars(&inst, &modify_req.0.vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; + ctx.0.execute_task().await?; + TardisResp::ok(Void {}) + } + + /// operate flow instance + /// + /// 执行实例的操作 + #[oai(path = "/:flow_inst_id/operate", method = "post")] + async fn operate(&self, flow_inst_id: Path, operate_req: Json, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult { + let mut funs = flow_constants::get_tardis_inst(); + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + funs.begin().await?; + FlowInstServ::operate(&inst, &operate_req.0, &funs, &ctx.0).await?; + funs.commit().await?; ctx.0.execute_task().await?; TardisResp::ok(Void {}) } diff --git a/backend/middlewares/flow/src/api/cc/flow_cc_model_api.rs b/backend/middlewares/flow/src/api/cc/flow_cc_model_api.rs index f2fa10087..39333d421 100644 --- a/backend/middlewares/flow/src/api/cc/flow_cc_model_api.rs +++ b/backend/middlewares/flow/src/api/cc/flow_cc_model_api.rs @@ -13,11 +13,12 @@ use tardis::web::poem_openapi::payload::Json; use tardis::web::web_resp::{TardisApiResult, TardisPage, TardisResp, Void}; use crate::dto::flow_model_dto::{ - FlowModelAddCustomModelReq, FlowModelAddCustomModelResp, FlowModelAddReq, FlowModelAggResp, FlowModelBindStateReq, FlowModelFilterReq, FlowModelFindRelStateResp, - FlowModelModifyReq, FlowModelSortStatesReq, FlowModelSummaryResp, FlowModelUnbindStateReq, + FlowModelAddReq, FlowModelAggResp, FlowModelBindStateReq, FlowModelDetailResp, FlowModelFilterReq, FlowModelFindRelStateResp, FlowModelModifyReq, FlowModelSortStatesReq, + FlowModelStatus, FlowModelSummaryResp, FlowModelUnbindStateReq, }; +use crate::dto::flow_model_version_dto::{FlowModelVersionBindState, FlowModelVersionDetailResp, FlowModelVersionModifyReq, FlowModelVersionModifyState}; use crate::dto::flow_state_dto::FlowStateRelModelModifyReq; -use crate::dto::flow_transition_dto::{FlowTransitionModifyReq, FlowTransitionSortStatesReq}; +use crate::dto::flow_transition_dto::{FlowTransitionDetailResp, FlowTransitionSortStatesReq}; use crate::flow_constants; use crate::serv::flow_model_serv::FlowModelServ; use crate::serv::flow_rel_serv::{FlowRelKind, FlowRelServ}; @@ -54,6 +55,17 @@ impl FlowCcModelApi { TardisResp::ok(Void {}) } + /// GET Editing Model Version By Model Id + /// + /// 通过模型ID获取正在编辑的模型版本信息 + #[oai(path = "/:flow_model_id/find_editing_verion", method = "get")] + async fn find_editing_verion(&self, flow_model_id: Path, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult { + let funs = flow_constants::get_tardis_inst(); + let result = FlowModelServ::find_editing_verion(&flow_model_id.0, &funs, &ctx.0).await?; + ctx.0.execute_task().await?; + TardisResp::ok(result) + } + /// Copy Model By Model Id /// /// 复制模型 @@ -133,6 +145,9 @@ impl FlowCcModelApi { name: Query>, tag: Query>, enabled: Query>, + status: Query>, + rel_template_id: Query>, + main: Query>, with_sub: Query>, page_number: Query, page_size: Query, @@ -140,9 +155,9 @@ impl FlowCcModelApi { desc_by_update: Query>, ctx: TardisContextExtractor, _request: &Request, - ) -> TardisApiResult> { + ) -> TardisApiResult> { let funs = flow_constants::get_tardis_inst(); - let result = FlowModelServ::paginate_items( + let result = FlowModelServ::paginate_detail_items( &FlowModelFilterReq { basic: RbumBasicFilterReq { ids: flow_model_ids.0.map(|ids| ids.split(',').map(|id| id.to_string()).collect::>()), @@ -151,7 +166,10 @@ impl FlowCcModelApi { enabled: enabled.0, ..Default::default() }, + main: main.0, + rel_template_id: rel_template_id.0, tags: tag.0.map(|tag| vec![tag]), + status: status.0, ..Default::default() }, page_number.0, @@ -166,50 +184,18 @@ impl FlowCcModelApi { TardisResp::ok(result) } - /// Find the specified models, or create it if it doesn't exist. + /// Find the specified main models, or create it if it doesn't exist. /// - /// 查找关联的model,如果不存在则创建。创建规则遵循add_custom_model接口逻辑。 - /// - /// # Parameters - /// - `tag_ids` - list of tag_id - /// - `temp_id` - associated template_id - /// - `is_shared` - whether the associated template is shared - #[oai(path = "/find_or_add_models", method = "put")] - async fn find_or_add_models( - &self, - tag_ids: Query, - temp_id: Query>, - is_shared: Query>, - ctx: TardisContextExtractor, - _request: &Request, - ) -> TardisApiResult> { - let mut funs = flow_constants::get_tardis_inst(); - funs.begin().await?; - let tag_ids = tag_ids.split(',').map(|tag_id| tag_id.to_string()).collect_vec(); - let result = FlowModelServ::find_or_add_models(tag_ids, temp_id.0, is_shared.unwrap_or(false), &funs, &ctx.0).await?; - funs.commit().await?; - ctx.0.execute_task().await?; - TardisResp::ok(result) - } - - /// Find the specified models, or create it if it doesn't exist. - /// - /// 查找关联的model。 + /// 查找关联的主流程model。 /// /// # Parameters /// - `temp_id` - associated template_id /// - `is_shared` - whether the associated template is shared #[oai(path = "/find_rel_models", method = "put")] - async fn find_rel_models( - &self, - temp_id: Query>, - is_shared: Query>, - ctx: TardisContextExtractor, - _request: &Request, - ) -> TardisApiResult> { + async fn find_rel_models(&self, temp_id: Query>, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult> { let mut funs = flow_constants::get_tardis_inst(); funs.begin().await?; - let result = FlowModelServ::find_rel_models(temp_id.0, is_shared.unwrap_or(false), &funs, &ctx.0).await?; + let result = FlowModelServ::find_rel_model_map(temp_id.0, true, &funs, &ctx.0).await?; funs.commit().await?; ctx.0.execute_task().await?; TardisResp::ok(result) @@ -242,7 +228,13 @@ impl FlowCcModelApi { FlowModelServ::modify_model( &flow_model_id.0, &mut FlowModelModifyReq { - bind_states: Some(vec![req.0]), + modify_version: Some(FlowModelVersionModifyReq { + bind_states: Some(vec![FlowModelVersionBindState { + exist_state: Some(req.0), + ..Default::default() + }]), + ..Default::default() + }), ..Default::default() }, &funs, @@ -264,7 +256,10 @@ impl FlowCcModelApi { FlowModelServ::modify_model( &flow_model_id.0, &mut FlowModelModifyReq { - unbind_states: Some(vec![req.state_id.clone()]), + modify_version: Some(FlowModelVersionModifyReq { + unbind_states: Some(vec![req.state_id.clone()]), + ..Default::default() + }), ..Default::default() }, &funs, @@ -286,17 +281,27 @@ impl FlowCcModelApi { FlowModelServ::modify_model( &flow_model_id.0, &mut FlowModelModifyReq { - modify_states: Some( - req.0 - .sort_states - .into_iter() - .map(|state| FlowStateRelModelModifyReq { - id: state.state_id, - sort: Some(state.sort), - show_btns: None, - }) - .collect_vec(), - ), + modify_version: Some(FlowModelVersionModifyReq { + modify_states: Some( + req.0 + .sort_states + .into_iter() + .map(|state| FlowModelVersionModifyState { + id: state.state_id.clone(), + modify_rel: Some(FlowStateRelModelModifyReq { + id: state.state_id, + sort: Some(state.sort), + show_btns: None, + }), + modify_state: None, + add_transitions: None, + modify_transitions: None, + delete_transitions: None, + }) + .collect_vec(), + ), + ..Default::default() + }), ..Default::default() }, &funs, @@ -321,48 +326,13 @@ impl FlowCcModelApi { ) -> TardisApiResult { let mut funs = flow_constants::get_tardis_inst(); funs.begin().await?; - let modify_trans = req - .0 - .sort_states - .into_iter() - .map(|sort_req| FlowTransitionModifyReq { - id: sort_req.id.clone().into(), - sort: Some(sort_req.sort), - ..Default::default() - }) - .collect_vec(); - FlowModelServ::modify_model( - &flow_model_id.0, - &mut FlowModelModifyReq { - modify_transitions: Some(modify_trans), - ..Default::default() - }, - &funs, - &ctx.0, - ) - .await?; + funs.begin().await?; + FlowModelServ::resort_transition(&flow_model_id.0, &req.0, &funs, &ctx.0).await?; funs.commit().await?; ctx.0.execute_task().await?; TardisResp::ok(Void {}) } - /// copy parent model to current own_paths - /// - /// 复制父级模型到当前 own_paths - /// 实际创建规则:按照 tags 创建模型,若传入proj_template_id,则优先寻找对应的父级模型,否则则获取默认模板模型生成对应的自定义模型。 - #[oai(path = "/add_custom_model", method = "post")] - async fn add_custom_model(&self, req: Json, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult> { - let mut funs = flow_constants::get_tardis_inst(); - funs.begin().await?; - let mut result = vec![]; - for item in &req.0.bind_model_objs { - let model_id = FlowModelServ::add_custom_model(&item.tag, req.0.proj_template_id.clone(), req.0.rel_template_id.clone(), &funs, &ctx.0).await.ok(); - result.push(FlowModelAddCustomModelResp { tag: item.tag.clone(), model_id }); - } - funs.commit().await?; - TardisResp::ok(result) - } - /// find rel states by model_id /// /// 获取关联状态 @@ -390,7 +360,17 @@ impl FlowCcModelApi { FlowModelServ::modify_model( &flow_model_id.0, &mut FlowModelModifyReq { - modify_states: Some(vec![req.0]), + modify_version: Some(FlowModelVersionModifyReq { + modify_states: Some(vec![FlowModelVersionModifyState { + id: req.0.id.clone(), + modify_rel: Some(req.0), + modify_state: None, + add_transitions: None, + modify_transitions: None, + delete_transitions: None, + }]), + ..Default::default() + }), ..Default::default() }, &funs, @@ -453,4 +433,14 @@ impl FlowCcModelApi { funs.commit().await?; TardisResp::ok(Void) } + + /// Get the operations associated with the model + /// + /// 获取模型关联的操作 + #[oai(path = "/:flow_model_id/get_transitions", method = "get")] + async fn get_rel_transitions(&self, flow_model_id: Path, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult> { + let funs = flow_constants::get_tardis_inst(); + let result = FlowModelServ::get_rel_transitions(&flow_model_id.0, &funs, &ctx.0).await?; + TardisResp::ok(result) + } } diff --git a/backend/middlewares/flow/src/api/cc/flow_cc_model_version_api.rs b/backend/middlewares/flow/src/api/cc/flow_cc_model_version_api.rs new file mode 100644 index 000000000..de6fb3432 --- /dev/null +++ b/backend/middlewares/flow/src/api/cc/flow_cc_model_version_api.rs @@ -0,0 +1,121 @@ +use crate::dto::flow_model_version_dto::{FlowModelVersionAddReq, FlowModelVersionDetailResp, FlowModelVersionFilterReq, FlowModelVersionModifyReq, FlowModelVesionState}; +use crate::flow_constants; +use crate::serv::flow_model_version_serv::FlowModelVersionServ; +use bios_basic::rbum::dto::rbum_filer_dto::RbumBasicFilterReq; +use bios_basic::rbum::serv::rbum_item_serv::RbumItemCrudOperation; +use tardis::web::context_extractor::TardisContextExtractor; +use tardis::web::poem::Request; +use tardis::web::poem_openapi; +use tardis::web::poem_openapi::param::{Path, Query}; +use tardis::web::poem_openapi::payload::Json; +use tardis::web::web_resp::{TardisApiResult, TardisPage, TardisResp, Void}; + +#[derive(Clone)] +pub struct FlowCcModelVersionApi; + +/// Flow model process API +#[poem_openapi::OpenApi(prefix_path = "/cc/model_version")] +impl FlowCcModelVersionApi { + /// Add Model Version + /// + /// 添加模型版本 + #[oai(path = "/", method = "post")] + async fn add(&self, mut add_req: Json, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult { + let mut funs = flow_constants::get_tardis_inst(); + funs.begin().await?; + let version_id = FlowModelVersionServ::add_item(&mut add_req.0, &funs, &ctx.0).await?; + let result = FlowModelVersionServ::get_item(&version_id, &FlowModelVersionFilterReq::default(), &funs, &ctx.0).await?; + funs.commit().await?; + ctx.0.execute_task().await?; + TardisResp::ok(result) + } + + /// Modify Model Version + /// + /// 修改模型版本 + #[oai(path = "/:flow_version_id", method = "patch")] + async fn modify( + &self, + flow_version_id: Path, + mut modify_req: Json, + ctx: TardisContextExtractor, + _request: &Request, + ) -> TardisApiResult { + let mut funs = flow_constants::get_tardis_inst(); + funs.begin().await?; + FlowModelVersionServ::modify_item(&flow_version_id.0, &mut modify_req.0, &funs, &ctx.0).await?; + funs.commit().await?; + ctx.0.execute_task().await?; + TardisResp::ok(Void) + } + + /// Get Model By Model Id + /// + /// 获取模型 + #[oai(path = "/:flow_version_id", method = "get")] + async fn get(&self, flow_version_id: Path, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult { + let funs = flow_constants::get_tardis_inst(); + let result = FlowModelVersionServ::get_item(&flow_version_id.0, &FlowModelVersionFilterReq::default(), &funs, &ctx.0).await?; + ctx.0.execute_task().await?; + TardisResp::ok(result) + } + + /// Find Models + /// + /// 获取模型列表 + #[oai(path = "/", method = "get")] + #[allow(clippy::too_many_arguments)] + async fn paginate( + &self, + ids: Query>, + name: Query>, + rel_model_id: Query>, + status: Query>, + enabled: Query>, + with_sub: Query>, + page_number: Query, + page_size: Query, + desc_by_create: Query>, + desc_by_update: Query>, + ctx: TardisContextExtractor, + _request: &Request, + ) -> TardisApiResult> { + let funs = flow_constants::get_tardis_inst(); + let result = FlowModelVersionServ::paginate_detail_items( + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + ids: ids.0.map(|ids| ids.split(',').map(|id| id.to_string()).collect::>()), + name: name.0, + with_sub_own_paths: with_sub.0.unwrap_or(false), + enabled: enabled.0, + ..Default::default() + }, + rel_model_ids: rel_model_id.0.map(|rel_model_id| vec![rel_model_id]), + status: Some(status.0.map_or(vec![FlowModelVesionState::Enabled, FlowModelVesionState::Disabled], |status| vec![status])), + ..Default::default() + }, + page_number.0, + page_size.0, + desc_by_create.0, + desc_by_update.0, + &funs, + &ctx.0, + ) + .await?; + ctx.0.execute_task().await?; + TardisResp::ok(result) + } + + /// Creating the version being edited + /// + /// 创建正在编辑的版本 + #[oai(path = "/:flow_version_id/create_editing_version", method = "post")] + async fn create_editing_version(&self, flow_version_id: Path, ctx: TardisContextExtractor, _request: &Request) -> TardisApiResult { + let mut funs = flow_constants::get_tardis_inst(); + funs.begin().await?; + let result = FlowModelVersionServ::create_editing_version(&flow_version_id.0, &funs, &ctx.0).await?; + funs.commit().await?; + ctx.0.execute_task().await?; + TardisResp::ok(result) + } +} diff --git a/backend/middlewares/flow/src/api/ci/flow_ci_inst_api.rs b/backend/middlewares/flow/src/api/ci/flow_ci_inst_api.rs index ae51cf918..a81537721 100644 --- a/backend/middlewares/flow/src/api/ci/flow_ci_inst_api.rs +++ b/backend/middlewares/flow/src/api/ci/flow_ci_inst_api.rs @@ -11,7 +11,6 @@ use tardis::web::poem::Request; use tardis::web::poem_openapi::payload::Json; use tardis::web::poem_openapi::{self, param::Query}; use tardis::web::web_resp::{TardisApiResult, TardisResp, Void}; -use tardis::{log, tokio}; use crate::dto::flow_external_dto::FlowExternalCallbackOp; use crate::dto::flow_inst_dto::{ @@ -33,11 +32,9 @@ impl FlowCiInstApi { /// 启动实例 #[oai(path = "/", method = "post")] async fn start(&self, add_req: Json, mut ctx: TardisContextExtractor, request: &Request) -> TardisApiResult { - let mut funs = flow_constants::get_tardis_inst(); + let funs = flow_constants::get_tardis_inst(); check_without_owner_and_unsafe_fill_ctx(request, &funs, &mut ctx.0)?; - funs.begin().await?; let result = FlowInstServ::start(&add_req.0, None, &funs, &ctx.0).await?; - funs.commit().await?; ctx.0.execute_task().await?; TardisResp::ok(result) } @@ -52,7 +49,7 @@ impl FlowCiInstApi { let mut result = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; // @TODO 临时处理方式,后续需增加接口 result.transitions = Some( - FlowInstServ::find_next_transitions(&flow_inst_id.0, &FlowInstFindNextTransitionsReq { vars: None }, &funs, &ctx.0) + FlowInstServ::find_next_transitions(&result, &FlowInstFindNextTransitionsReq { vars: None }, &funs, &ctx.0) .await? .into_iter() .map(|tran| FlowInstTransitionInfo { @@ -60,6 +57,7 @@ impl FlowCiInstApi { start_time: Utc::now(), op_ctx: FlowOperationContext::default(), output_message: Some(tran.next_flow_transition_name), + target_state_id: None, }) .collect_vec(), ); @@ -112,14 +110,16 @@ impl FlowCiInstApi { let funs = flow_constants::get_tardis_inst(); check_without_owner_and_unsafe_fill_ctx(request, &funs, &mut ctx.0)?; let mut transfer = transfer_req.0; - FlowInstServ::check_transfer_vars(&flow_inst_id.0, &mut transfer, &funs, &ctx.0).await?; + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + FlowInstServ::check_transfer_vars(&inst, &mut transfer, &funs, &ctx.0).await?; let result = FlowInstServ::transfer( - &flow_inst_id.0, + &inst, &transfer, false, FlowExternalCallbackOp::Default, loop_check_helper::InstancesTransition::default(), &ctx.0, + &funs, ) .await?; ctx.0.execute_task().await?; @@ -143,21 +143,23 @@ impl FlowCiInstApi { let mut result = vec![]; let flow_inst_ids: Vec<_> = flow_inst_ids.split(',').collect(); let raw_transfer_req = transfer_req.0; - let mut flow_inst_id_transfer_map = HashMap::new(); + let mut flow_inst_transfer = vec![]; for flow_inst_id in &flow_inst_ids { let mut transfer_req = raw_transfer_req.clone(); - FlowInstServ::check_transfer_vars(flow_inst_id, &mut transfer_req, &funs, &ctx.0).await?; - flow_inst_id_transfer_map.insert(flow_inst_id, transfer_req); + let inst = FlowInstServ::get(flow_inst_id, &funs, &ctx.0).await?; + FlowInstServ::check_transfer_vars(&inst, &mut transfer_req, &funs, &ctx.0).await?; + flow_inst_transfer.push((inst, transfer_req)); } - for (flow_inst_id, transfer_req) in flow_inst_id_transfer_map { + for (inst, transfer_req) in flow_inst_transfer { result.push( FlowInstServ::transfer( - flow_inst_id, + &inst, &transfer_req, false, FlowExternalCallbackOp::Default, loop_check_helper::InstancesTransition::default(), &ctx.0, + &funs, ) .await?, ); @@ -180,7 +182,8 @@ impl FlowCiInstApi { let funs = flow_constants::get_tardis_inst(); check_without_owner_and_unsafe_fill_ctx(request, &funs, &mut ctx.0)?; let vars = HashMap::from([("assigned_to".to_string(), Value::String(modify_req.0.current_assigned))]); - FlowInstServ::modify_current_vars(&flow_inst_id.0, &vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + FlowInstServ::modify_current_vars(&inst, &vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; ctx.0.execute_task().await?; TardisResp::ok(Void {}) } @@ -198,7 +201,8 @@ impl FlowCiInstApi { ) -> TardisApiResult { let funs = flow_constants::get_tardis_inst(); check_without_owner_and_unsafe_fill_ctx(request, &funs, &mut ctx.0)?; - FlowInstServ::modify_current_vars(&flow_inst_id.0, &modify_req.0.vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; + let inst = FlowInstServ::get(&flow_inst_id.0, &funs, &ctx.0).await?; + FlowInstServ::modify_current_vars(&inst, &modify_req.0.vars, loop_check_helper::InstancesTransition::default(), &ctx.0).await?; ctx.0.execute_task().await?; TardisResp::ok(Void {}) } @@ -220,6 +224,7 @@ impl FlowCiInstApi { rel_business_obj_id: add_req.0.rel_business_obj_id.clone(), tag: add_req.0.tag.clone(), create_vars: add_req.0.create_vars.clone(), + transition_id: None, }, add_req.0.current_state_name.clone(), &funs, @@ -265,23 +270,4 @@ impl FlowCiInstApi { ctx.0.execute_task().await?; TardisResp::ok(result) } - - /// trigger instance front action - /// - /// 触发前置动作 - #[oai(path = "/trigger_front_action", method = "get")] - async fn trigger_front_action(&self) -> TardisApiResult { - let funs = flow_constants::get_tardis_inst(); - tokio::spawn(async move { - match FlowInstServ::trigger_front_action(&funs).await { - Ok(_) => { - log::trace!("[Flow.Inst] add log success") - } - Err(e) => { - log::warn!("[Flow.Inst] failed to add log:{e}") - } - } - }); - TardisResp::ok(Void {}) - } } diff --git a/backend/middlewares/flow/src/api/ci/flow_ci_model_api.rs b/backend/middlewares/flow/src/api/ci/flow_ci_model_api.rs index 21e83120e..4d04889e5 100644 --- a/backend/middlewares/flow/src/api/ci/flow_ci_model_api.rs +++ b/backend/middlewares/flow/src/api/ci/flow_ci_model_api.rs @@ -1,16 +1,15 @@ use std::collections::HashMap; use crate::dto::flow_model_dto::{ - FlowModelAddCustomModelReq, FlowModelAddCustomModelResp, FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelCopyOrReferenceCiReq, FlowModelExistRelByTemplateIdsReq, - FlowModelFilterReq, FlowModelFindRelStateResp, + FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelCopyOrReferenceCiReq, FlowModelExistRelByTemplateIdsReq, FlowModelFilterReq, FlowModelFindRelStateResp, + FlowModelKind, }; use crate::flow_constants; use crate::serv::flow_inst_serv::FlowInstServ; use crate::serv::flow_model_serv::FlowModelServ; use crate::serv::flow_rel_serv::{FlowRelKind, FlowRelServ}; use bios_basic::rbum::dto::rbum_filer_dto::{RbumBasicFilterReq, RbumRelFilterReq}; -use bios_basic::rbum::helper::rbum_scope_helper::{self, check_without_owner_and_unsafe_fill_ctx}; -use bios_basic::rbum::rbum_enumeration::RbumScopeLevelKind; +use bios_basic::rbum::helper::rbum_scope_helper::check_without_owner_and_unsafe_fill_ctx; use bios_basic::rbum::serv::rbum_item_serv::RbumItemCrudOperation; use bios_basic::rbum::serv::rbum_rel_serv::RbumRelServ; use itertools::Itertools; @@ -85,26 +84,26 @@ impl FlowCiModelApi { /// add custom model by template_id /// /// 添加自定义模型 - #[oai(path = "/add_custom_model", method = "post")] - async fn add_custom_model( - &self, - req: Json, - mut ctx: TardisContextExtractor, - request: &Request, - ) -> TardisApiResult> { - let mut funs = flow_constants::get_tardis_inst(); - check_without_owner_and_unsafe_fill_ctx(request, &funs, &mut ctx.0)?; - funs.begin().await?; - let proj_template_id = req.0.proj_template_id; - let mut result = vec![]; - for item in req.0.bind_model_objs { - let model_id = FlowModelServ::add_custom_model(&item.tag, proj_template_id.clone(), None, &funs, &ctx.0).await.ok(); - result.push(FlowModelAddCustomModelResp { tag: item.tag, model_id }); - } - funs.commit().await?; - ctx.0.execute_task().await?; - TardisResp::ok(result) - } + // #[oai(path = "/add_custom_model", method = "post")] + // async fn add_custom_model( + // &self, + // req: Json, + // mut ctx: TardisContextExtractor, + // request: &Request, + // ) -> TardisApiResult> { + // let mut funs = flow_constants::get_tardis_inst(); + // check_without_owner_and_unsafe_fill_ctx(request, &funs, &mut ctx.0)?; + // funs.begin().await?; + // let proj_template_id = req.0.proj_template_id; + // let mut result = vec![]; + // for item in req.0.bind_model_objs { + // let model_id = FlowModelServ::add_custom_model(&item.tag, proj_template_id.clone(), None, &funs, &ctx.0).await.ok(); + // result.push(FlowModelAddCustomModelResp { tag: item.tag, model_id }); + // } + // funs.commit().await?; + // ctx.0.execute_task().await?; + // TardisResp::ok(result) + // } /// Creating or referencing models /// @@ -134,30 +133,37 @@ impl FlowCiModelApi { .into_iter() .map(|rel| rel.rel_id) .collect_vec(); - let mut result = HashMap::new(); - let mut mock_ctx = ctx.0.clone(); - if rbum_scope_helper::get_scope_level_by_context(&ctx.0)? == RbumScopeLevelKind::L2 { - mock_ctx = match req.0.op { - FlowModelAssociativeOperationKind::Copy => ctx.0.clone(), - FlowModelAssociativeOperationKind::Reference => TardisContext { - own_paths: rbum_scope_helper::get_path_item(RbumScopeLevelKind::L1.to_int(), &ctx.0.own_paths).unwrap_or_default(), - ..ctx.0.clone() + let rel_models = FlowModelServ::find_items( + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + ids: Some(rel_model_ids), + own_paths: Some("".to_string()), + with_sub_own_paths: true, + ..Default::default() }, - }; - } - for rel_model_id in rel_model_ids { - let new_model = FlowModelServ::copy_or_reference_model(&rel_model_id, Some(ctx.0.own_paths.clone()), &req.0.op, Some(false), &funs, &mock_ctx).await?; + main: Some(true), + ..Default::default() + }, + None, + None, + &funs, + &ctx.0, + ) + .await?; + let mut result = HashMap::new(); + for rel_model in rel_models { + let new_model = FlowModelServ::copy_or_reference_model(&rel_model.id, &req.0.op, FlowModelKind::AsModel, &funs, &ctx.0).await?; FlowInstServ::batch_update_when_switch_model( new_model.rel_template_ids.first().cloned(), &new_model.tag, - &new_model.id, + &new_model.current_version_id, new_model.states.clone(), &new_model.init_state_id, &funs, &ctx.0, ) .await?; - result.insert(rel_model_id.clone(), new_model.id.clone()); + result.insert(rel_model.id.clone(), new_model.id.clone()); } funs.commit().await?; ctx.0.execute_task().await?; @@ -201,7 +207,14 @@ impl FlowCiModelApi { ) .await? { - let added_model = FlowModelServ::copy_or_reference_model(&from_model.rel_model_id, None, &FlowModelAssociativeOperationKind::Copy, Some(true), &funs, &ctx.0).await?; + let added_model = FlowModelServ::copy_or_reference_model( + &from_model.rel_model_id, + &FlowModelAssociativeOperationKind::ReferenceOrCopy, + FlowModelKind::AsTemplateAndAsModel, + &funs, + &ctx.0, + ) + .await?; FlowRelServ::add_simple_rel( &FlowRelKind::FlowModelTemplate, &added_model.id, @@ -267,6 +280,7 @@ impl FlowCiModelApi { with_sub_own_paths: true, ..Default::default() }, + main: Some(true), ..Default::default() }, None, diff --git a/backend/middlewares/flow/src/api/cs/flow_cs_config_api.rs b/backend/middlewares/flow/src/api/cs/flow_cs_config_api.rs index 3fea629a4..845c322fc 100644 --- a/backend/middlewares/flow/src/api/cs/flow_cs_config_api.rs +++ b/backend/middlewares/flow/src/api/cs/flow_cs_config_api.rs @@ -84,7 +84,7 @@ impl FlowCsConfigApi { .unwrap(); let mut page = 1; loop { - let insts = FlowInstServ::paginate(None, None, None, None, Some(true), page, 200, &funs, &global_ctx).await.unwrap().records; + let insts = FlowInstServ::paginate(None, None, None, None, None, None, Some(true), page, 200, &funs, &global_ctx).await.unwrap().records; if insts.is_empty() { break; } diff --git a/backend/middlewares/flow/src/api/ct/flow_ct_model_api.rs b/backend/middlewares/flow/src/api/ct/flow_ct_model_api.rs index eb90b0957..d20f1e441 100644 --- a/backend/middlewares/flow/src/api/ct/flow_ct_model_api.rs +++ b/backend/middlewares/flow/src/api/ct/flow_ct_model_api.rs @@ -13,7 +13,9 @@ use tardis::{ }; use crate::{ - dto::flow_model_dto::{FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelCopyOrReferenceReq, FlowModelFilterReq, FlowModelFindRelNameByTemplateIdsReq}, + dto::flow_model_dto::{ + FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelCopyOrReferenceReq, FlowModelFilterReq, FlowModelFindRelNameByTemplateIdsReq, FlowModelKind, + }, flow_constants, serv::{ flow_inst_serv::FlowInstServ, @@ -57,7 +59,14 @@ impl FlowCtModelApi { if orginal_model_id.clone().unwrap_or_default() == rel_model_id { continue; } - let new_model = FlowModelServ::copy_or_reference_model(&rel_model_id, None, &req.0.op, Some(true), &funs, &ctx.0).await?; + let new_model = FlowModelServ::copy_or_reference_model( + &rel_model_id, + &FlowModelAssociativeOperationKind::ReferenceOrCopy, + FlowModelKind::AsTemplateAndAsModel, + &funs, + &ctx.0, + ) + .await?; if let Some(rel_template_id) = &req.0.rel_template_id { FlowRelServ::add_simple_rel( &FlowRelKind::FlowModelTemplate, @@ -76,7 +85,7 @@ impl FlowCtModelApi { FlowInstServ::batch_update_when_switch_model( req.0.rel_template_id.clone(), &new_model.tag, - &new_model.id, + &new_model.current_version_id, new_model.states.clone(), &new_model.init_state_id, &funs, @@ -127,7 +136,14 @@ impl FlowCtModelApi { ) .await? { - let new_model = FlowModelServ::copy_or_reference_model(&from_model.rel_model_id, None, &FlowModelAssociativeOperationKind::Copy, Some(true), &funs, &ctx.0).await?; + let new_model = FlowModelServ::copy_or_reference_model( + &from_model.rel_model_id, + &FlowModelAssociativeOperationKind::ReferenceOrCopy, + FlowModelKind::AsTemplateAndAsModel, + &funs, + &ctx.0, + ) + .await?; FlowRelServ::add_simple_rel( &FlowRelKind::FlowModelTemplate, &new_model.id, diff --git a/backend/middlewares/flow/src/domain.rs b/backend/middlewares/flow/src/domain.rs index 1d9cfeb2b..f3ae0f452 100644 --- a/backend/middlewares/flow/src/domain.rs +++ b/backend/middlewares/flow/src/domain.rs @@ -1,4 +1,5 @@ pub mod flow_inst; pub mod flow_model; +pub mod flow_model_version; pub mod flow_state; pub mod flow_transition; diff --git a/backend/middlewares/flow/src/domain/flow_inst.rs b/backend/middlewares/flow/src/domain/flow_inst.rs index 900f54d91..d1b61466f 100644 --- a/backend/middlewares/flow/src/domain/flow_inst.rs +++ b/backend/middlewares/flow/src/domain/flow_inst.rs @@ -1,4 +1,4 @@ -use crate::dto::flow_inst_dto::{FlowInstTransitionInfo, FlowOperationContext}; +use crate::dto::flow_inst_dto::{FlowInstArtifacts, FlowInstTransitionInfo, FlowOperationContext}; use tardis::chrono::Utc; use tardis::db::sea_orm; use tardis::db::sea_orm::prelude::Json; @@ -12,12 +12,15 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: String, #[index] - pub rel_flow_model_id: String, + pub rel_flow_version_id: String, /// Business object Id / 关联的业务对象Id - #[index(unique = true)] + #[index] pub rel_business_obj_id: String, + /// Whether master workflow / 是否主流程 + #[index] + pub main: bool, /// Tags / 标签 /// /// Used for model classification @@ -59,11 +62,19 @@ pub struct Model { /// Output message when finished / 完成时的输出信息 pub output_message: Option, - /// Transfer information list / 流转信息列表 + /// Transfer information list / 流转信息列表 #[index(full_text)] #[sea_orm(column_type = "JsonBinary", nullable)] #[tardis_entity(custom_type = "JsonBinary")] pub transitions: Option>, + /// Data objects required for the process / 流程所需要的数据对象 + /// + /// Data objects to be used by nodes in the process + /// 流程中节点所需要操作的数据对象 + #[sea_orm(column_type = "JsonBinary", nullable)] + #[tardis_entity(custom_type = "JsonBinary")] + pub artifacts: Option, + pub own_paths: String, } diff --git a/backend/middlewares/flow/src/domain/flow_model.rs b/backend/middlewares/flow/src/domain/flow_model.rs index bc40ec45b..be575e00a 100644 --- a/backend/middlewares/flow/src/domain/flow_model.rs +++ b/backend/middlewares/flow/src/domain/flow_model.rs @@ -3,6 +3,8 @@ use tardis::db::sea_orm::prelude::Json; use tardis::db::sea_orm::*; use tardis::{TardisCreateEntity, TardisEmptyBehavior, TardisEmptyRelation}; +use crate::dto::flow_model_dto::{FlowModelKind, FlowModelStatus}; + /// Model / 模型 /// /// Used to define processes, each process contains one or more transitions (associated with `flow_transition`) @@ -21,11 +23,15 @@ pub struct Model { /// Model variable list / 模型变量列表 pub vars: Option, - /// Initial state / 初始状态 - /// - /// Define the initial state of each model - /// 定义每个模块的初始状态 - pub init_state_id: String, + /// Types of workflow models / 工作流模型类型 + /// 此功能用于标记工作流模型的类型,目前有仅作为模板,仅作为实例,既可作为模板又可作为实例三种。表示当前模型的用途和功能。 + #[tardis_entity(custom_type = "String")] + pub kind: FlowModelKind, + + /// Status of workflow models / 工作流模型状态 + /// 启用/停用 + #[tardis_entity(custom_type = "String")] + pub status: FlowModelStatus, /// Associated template / 关联模板 /// @@ -33,6 +39,16 @@ pub struct Model { /// 此功能用于将该模型与模板关联,比如该模型引用于某个模板,则此关联对应于模板的Id pub rel_template_id: Option, + /// Currently enabled version ID / 当前启用的版本ID + /// + /// This field is used to record the version of the model currently in use + /// 此字段用于记录当前模型在使用的版本 + pub current_version_id: String, + + /// Whether it is a mainstream process / 是否是主流程 + /// + #[index] + pub main: bool, /// Whether it is a template / 是否是模板 /// /// Used as a model for the model to be reused in the process diff --git a/backend/middlewares/flow/src/domain/flow_model_version.rs b/backend/middlewares/flow/src/domain/flow_model_version.rs new file mode 100644 index 000000000..91a371dcb --- /dev/null +++ b/backend/middlewares/flow/src/domain/flow_model_version.rs @@ -0,0 +1,46 @@ +use tardis::db::sea_orm; +use tardis::db::sea_orm::*; +use tardis::{ + chrono::{self, Utc}, + db::sea_orm::DeriveEntityModel, + TardisCreateEntity, TardisEmptyBehavior, TardisEmptyRelation, +}; + +use crate::dto::flow_model_version_dto::FlowModelVesionState; + +/// Model Version / 模型版本 +/// +/// Used to define processes, each process contains one or more transitions (associated with `flow_transition`) +/// 用于定义流程,每个流程包含一个或多个流转(关联 `flow_transition` ) +/// +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, TardisCreateEntity, TardisEmptyBehavior, TardisEmptyRelation)] +#[sea_orm(table_name = "flow_model_version")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + /// 关联的模型ID + #[index] + pub rel_model_id: String, + /// Initial state / 初始状态 + /// + /// Define the initial state of each model + /// 定义每个模块的初始状态 + pub init_state_id: String, + /// 状态 启用中 已关闭 + #[tardis_entity(custom_type = "String")] + pub status: FlowModelVesionState, + /// Creation time / 创建时间 + #[index] + #[sea_orm(extra = "DEFAULT CURRENT_TIMESTAMP")] + pub create_time: chrono::DateTime, + /// 创建者信息 + pub create_by: String, + /// 更新时间 + #[sea_orm(extra = "DEFAULT CURRENT_TIMESTAMP")] + pub update_time: chrono::DateTime, + /// 修改人信息 + pub update_by: String, + + #[fill_ctx(fill = "own_paths")] + pub own_paths: String, +} diff --git a/backend/middlewares/flow/src/domain/flow_state.rs b/backend/middlewares/flow/src/domain/flow_state.rs index 739aab888..f50acc5e5 100644 --- a/backend/middlewares/flow/src/domain/flow_state.rs +++ b/backend/middlewares/flow/src/domain/flow_state.rs @@ -3,7 +3,7 @@ use tardis::db::sea_orm::prelude::Json; use tardis::db::sea_orm::*; use tardis::{TardisCreateEntity, TardisEmptyBehavior, TardisEmptyRelation}; -use crate::dto::flow_state_dto::{FlowStateKind, FlowSysStateKind}; +use crate::dto::flow_state_dto::{FLowStateKindConf, FlowStateKind, FlowSysStateKind}; /// State / 状态 /// @@ -53,7 +53,9 @@ pub struct Model { /// /// Different states can correspond to different configuration information /// 不同的状态可对应于不同的配置信息 - pub kind_conf: Json, + #[sea_orm(column_type = "JsonBinary", nullable)] + #[tardis_entity(custom_type = "JsonBinary")] + pub kind_conf: Option, /// Whether it is a template / 是否是模板 /// /// Used as a template for the state to be reused in the process diff --git a/backend/middlewares/flow/src/domain/flow_transition.rs b/backend/middlewares/flow/src/domain/flow_transition.rs index 23144e72c..43e372a44 100644 --- a/backend/middlewares/flow/src/domain/flow_transition.rs +++ b/backend/middlewares/flow/src/domain/flow_transition.rs @@ -105,7 +105,7 @@ pub struct Model { /// Switch for notification of status changes / 状态变化时的通知开关 pub is_notify: bool, - pub rel_flow_model_id: String, + pub rel_flow_model_version_id: String, pub sort: i64, diff --git a/backend/middlewares/flow/src/dto.rs b/backend/middlewares/flow/src/dto.rs index f4a87fa6a..c1388fac3 100644 --- a/backend/middlewares/flow/src/dto.rs +++ b/backend/middlewares/flow/src/dto.rs @@ -2,6 +2,7 @@ pub mod flow_config_dto; pub mod flow_external_dto; pub mod flow_inst_dto; pub mod flow_model_dto; +pub mod flow_model_version_dto; pub mod flow_state_dto; pub mod flow_transition_dto; pub mod flow_var_dto; diff --git a/backend/middlewares/flow/src/dto/flow_external_dto.rs b/backend/middlewares/flow/src/dto/flow_external_dto.rs index 39d4e1bbb..e3cec5d3f 100644 --- a/backend/middlewares/flow/src/dto/flow_external_dto.rs +++ b/backend/middlewares/flow/src/dto/flow_external_dto.rs @@ -81,6 +81,8 @@ pub enum FlowExternalKind { NotifyChanges, /// 查询字段值 QueryField, + /// 删除业务对象 + DeleteObj, } /// When kind is ModifyField, the field is modified in a specific way, for example: validate the content, post action, precondition trigger ... @@ -96,6 +98,8 @@ pub enum FlowExternalCallbackOp { VerifyContent, /// 条件触发 ConditionalTrigger, + /// 自动流转 + Auto, } /// 扩展字段 @@ -155,3 +159,6 @@ pub struct FlowExternalNotifyChangesResp {} pub struct FlowExternalQueryFieldResp { pub objs: Vec, } + +#[derive(Default, Serialize, Deserialize, Debug, poem_openapi::Object)] +pub struct FlowExternalDeleteRelObjResp {} diff --git a/backend/middlewares/flow/src/dto/flow_inst_dto.rs b/backend/middlewares/flow/src/dto/flow_inst_dto.rs index deecd0e7f..9de27303c 100644 --- a/backend/middlewares/flow/src/dto/flow_inst_dto.rs +++ b/backend/middlewares/flow/src/dto/flow_inst_dto.rs @@ -1,16 +1,17 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; use tardis::{ - basic::dto::TardisContext, + basic::{dto::TardisContext, error::TardisError}, chrono::{DateTime, Utc}, db::sea_orm, serde_json::Value, web::poem_openapi, + TardisFuns, }; use super::{ - flow_state_dto::{FlowStateRelModelExt, FlowSysStateKind}, + flow_state_dto::{FlowGuardConf, FlowStateKind, FlowStateOperatorKind, FlowStateRelModelExt, FlowStateVar, FlowSysStateKind}, flow_transition_dto::FlowTransitionDoubleCheckInfo, flow_var_dto::FlowVarInfo, }; @@ -22,6 +23,8 @@ pub struct FlowInstStartReq { pub tag: String, /// 创建时的参数列表 pub create_vars: Option>, + /// 触发的动作ID + pub transition_id: Option, } #[derive(Serialize, Deserialize, Debug, poem_openapi::Object)] @@ -39,6 +42,8 @@ pub struct FlowInstBatchBindReq { pub tag: String, /// 关联业务ID pub rel_business_objs: Vec, + /// 触发的动作ID + pub transition_id: Option, } #[derive(Serialize, Deserialize, Debug, poem_openapi::Object)] @@ -73,7 +78,7 @@ pub struct FlowInstSummaryResp { /// Associated [flow_model](super::flow_model_dto::FlowModelDetailResp) id /// /// 关联的[工作流模板](super::flow_model_dto::FlowModelDetailResp) id - pub rel_flow_model_id: String, + pub rel_flow_version_id: String, /// Associated [flow_model](super::flow_model_dto::FlowModelDetailResp) name /// /// 关联的[工作流模板](super::flow_model_dto::FlowModelDetailResp) 名称 @@ -107,13 +112,17 @@ pub struct FlowInstDetailResp { /// Associated [flow_model](super::flow_model_dto::FlowModelDetailResp) id /// /// 关联的[工作流模板](super::flow_model_dto::FlowModelDetailResp) id - pub rel_flow_model_id: String, + pub rel_flow_version_id: String, /// Associated [flow_model](super::flow_model_dto::FlowModelDetailResp) name /// /// 关联的[工作流模板](super::flow_model_dto::FlowModelDetailResp) 名称 pub rel_flow_model_name: String, /// 关联业务ID pub rel_business_obj_id: String, + + pub tag: String, + + pub main: bool, /// 当前状态ID /// Associated [flow_state](super::flow_state_dto::FlowStateDetailResp) id /// @@ -129,16 +138,26 @@ pub struct FlowInstDetailResp { /// /// 关联的[工作流状态](super::flow_state_dto::FlowStateDetailResp) color pub current_state_color: Option, - /// 当前状态类型 - /// Associated [flow_state](super::flow_state_dto::FlowStateDetailResp) name + /// 当前状态系统类型 + /// Associated [flow_state](super::flow_state_dto::FlowStateDetailResp) sys_state /// - /// 关联的[工作流状态](super::flow_state_dto::FlowStateDetailResp) 名称 - pub current_state_kind: Option, + /// 关联的[工作流状态](super::flow_state_dto::FlowStateDetailResp) 系统类型 + pub current_state_sys_kind: Option, + /// 当前状态类型 + /// Associated [flow_state](super::flow_state_dto::FlowStateDetailResp) state_kind + /// + /// 关联的[工作流状态](super::flow_state_dto::FlowStateDetailResp) 状态类型 + pub current_state_kind: Option, + /// 当前状态关联扩展信息 /// Associated [flow_state](super::flow_state_dto::FlowStateRelModelExt) /// /// 关联的[工作流状态](super::flow_state_dto::FlowStateRelModelExt) pub current_state_ext: Option, + /// Associated [flow_state](super::flow_state_dto::FlowStateRelModelExt) + /// + /// 当前状态配置 + pub current_state_conf: Option, /// 当前参数列表 pub current_vars: Option>, /// 创建时的参数列表 @@ -159,9 +178,97 @@ pub struct FlowInstDetailResp { /// 动作列表 pub transitions: Option>, + pub artifacts: Option, + pub own_paths: String, } +impl FlowInstDetailResp { + pub fn artifacts(&self) -> FlowInstArtifacts { + if let Some(artifacts) = self.artifacts.clone() { + TardisFuns::json.json_to_obj(artifacts).unwrap_or_default() + } else { + FlowInstArtifacts::default() + } + } +} + +// 状态配置 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object)] +pub struct FLowInstStateConf { + pub operators: HashMap, + pub form_conf: Option, + pub approval_conf: Option, +} + +// 状态录入配置 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object)] +pub struct FLowInstStateFormConf { + pub form_vars_collect_conf: HashMap, +} + +// 状态审批配置 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object)] +pub struct FLowInstStateApprovalConf { + pub approval_vars_collect_conf: Option>, + pub form_vars_collect: HashMap, +} + +// 流程实例中对应的数据存储 +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, poem_openapi::Object, sea_orm::FromJsonQueryResult)] +pub struct FlowInstArtifacts { + pub guard_conf: FlowGuardConf, // 当前操作人权限 + pub approval_result: HashMap>>, // 当前审批结果 + pub form_state_map: HashMap>, // 录入节点映射 key为节点ID,对应的value为节点中的录入的参数 + pub prev_non_auto_state_id: Option, // 上一个非自动节点ID + pub prev_non_auto_account_id: Option, // 上一个节点操作人ID +} + +// 流程实例中数据存储更新 +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, sea_orm::FromJsonQueryResult)] +pub struct FlowInstArtifactsModifyReq { + pub guard_conf: Option, // 当前操作人权限 + pub add_guard_conf_account_id: Option, // 增加操作人ID + pub delete_guard_conf_account_id: Option, // 删除操作人ID + pub add_approval_result: Option<(String, FlowApprovalResultKind)>, // 增加审批结果 + pub form_state_map: Option>, // 录入节点映射 key为节点ID,对应的value为节点中的录入的参数 + pub clear_form_result: Option, // 清除节点录入信息 + pub clear_approval_result: Option, // 清除节点审批信息 + pub prev_non_auto_state_id: Option, // 上一个非自动节点ID + pub prev_non_auto_account_id: Option, // 上一个节点操作人ID +} + +/// 审批结果类型 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Enum, Default, Eq, Hash, PartialEq, Clone)] +pub enum FlowApprovalResultKind { + /// 通过 + #[default] + Pass, + /// 拒绝 + Overrule, +} + +impl Display for FlowApprovalResultKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FlowApprovalResultKind::Pass => write!(f, "PASS"), + FlowApprovalResultKind::Overrule => write!(f, "OVERRULE"), + } + } +} + +impl FromStr for FlowApprovalResultKind { + type Err = TardisError; + + fn from_str(s: &str) -> Result { + match s.to_uppercase().as_str() { + "PASS" => Ok(Self::Pass), + "OVERRULE" => Ok(Self::Overrule), + _ => Err(TardisError::bad_request(&format!("invalid FlowApprovalResultKind: {}", s), "400-operator-invalid-param")), + } + } +} + /// 实例的动作信息 #[derive(Serialize, Deserialize, Clone, PartialEq, Debug, poem_openapi::Object, sea_orm::FromJsonQueryResult)] pub struct FlowInstTransitionInfo { @@ -172,6 +279,8 @@ pub struct FlowInstTransitionInfo { pub op_ctx: FlowOperationContext, /// 输出信息 pub output_message: Option, + /// 目标状态节点 (若未通过transition流转状态,则传入该值) + pub target_state_id: Option, } /// 操作上下文信息 @@ -255,7 +364,7 @@ pub struct FlowInstFindStateAndTransitionsResp { /// Associated [flow_state](super::flow_state_dto::FlowStateDetailResp) sys_state /// /// 关联的[工作流状态](super::flow_state_dto::FlowStateDetailResp) sys_state - pub current_flow_state_kind: FlowSysStateKind, + pub current_flow_state_sys_kind: FlowSysStateKind, /// Associated [flow_state](super::flow_state_dto::FlowStateDetailResp) color /// /// 关联的[工作流状态](super::flow_state_dto::FlowStateDetailResp) color @@ -268,6 +377,8 @@ pub struct FlowInstFindStateAndTransitionsResp { pub finish_time: Option>, /// 流转信息 pub next_flow_transitions: Vec, + /// 绑定其他工作流的动作 + pub rel_flow_versions: HashMap, } /// 流转请求 @@ -331,15 +442,32 @@ pub struct FlowInstModifyCurrentVarsReq { pub vars: HashMap, } +/// 操作实例请求 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object)] +pub struct FlowInstOperateReq { + pub operate: FlowStateOperatorKind, + /// 参数列表 + pub vars: Option>, + /// 输出信息 + pub output_message: Option, + /// 操作人 + pub operator: Option, +} + /// 工作流实例过滤器 #[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone, Default)] #[serde(default)] pub struct FlowInstFilterReq { /// 关联模型ID - pub flow_model_id: Option, + pub flow_version_id: Option, + /// 业务ID + pub rel_business_obj_id: Option, /// 标签 pub tag: Option, + /// 是否主流程 + pub main: Option, + /// 是否结束 pub finish: Option, /// 当前状态ID @@ -350,7 +478,7 @@ pub struct FlowInstFilterReq { #[derive(sea_orm::FromQueryResult)] pub struct FlowInstSummaryResult { pub id: String, - pub rel_flow_model_id: String, + pub rel_flow_version_id: String, pub rel_flow_model_name: String, pub current_vars: Option, diff --git a/backend/middlewares/flow/src/dto/flow_model_dto.rs b/backend/middlewares/flow/src/dto/flow_model_dto.rs index 25876c480..46af736a4 100644 --- a/backend/middlewares/flow/src/dto/flow_model_dto.rs +++ b/backend/middlewares/flow/src/dto/flow_model_dto.rs @@ -9,15 +9,16 @@ use serde::{Deserialize, Serialize}; use tardis::{ basic::field::TrimString, chrono::{DateTime, Utc}, - db::sea_orm, + db::sea_orm::{self, prelude::*}, serde_json::Value, web::poem_openapi, TardisFuns, }; use super::{ - flow_state_dto::{FlowStateAggResp, FlowStateRelModelExt, FlowStateRelModelModifyReq}, - flow_transition_dto::{FlowTransitionAddReq, FlowTransitionDetailResp, FlowTransitionModifyReq}, + flow_model_version_dto::{FlowModelVersionAddReq, FlowModelVersionBindState, FlowModelVersionModifyReq, FlowModelVesionState}, + flow_state_dto::{FlowStateAddReq, FlowStateAggResp, FlowStateRelModelExt}, + flow_transition_dto::{FlowTransitionAddReq, FlowTransitionDetailResp}, }; /// 添加请求 @@ -29,16 +30,21 @@ pub struct FlowModelAddReq { pub icon: Option, #[oai(validator(max_length = "2000"))] pub info: Option, - /// 初始化状态ID - pub init_state_id: String, + /// 工作流模型类型 + pub kind: FlowModelKind, + /// 工作流模型状态 + pub status: FlowModelStatus, /// 关联模板ID(目前可能是页面模板ID,或者是项目模板ID) pub rel_template_ids: Option>, - /// 绑定的动作 - pub transitions: Option>, - /// 绑定的状态 - pub states: Option>, + /// 关联动作ID(触发当前工作流的动作,若为空则默认表示新建数据时触发) + pub rel_transition_ids: Option>, + /// 创建的可用版本 + pub add_version: Option, + pub current_version_id: Option, /// 是否作为模板使用 pub template: bool, + /// 是否作为主流程 + pub main: bool, /// 关联父级模型ID pub rel_model_id: Option, /// 标签 @@ -50,17 +56,44 @@ pub struct FlowModelAddReq { impl From for FlowModelAddReq { fn from(value: FlowModelDetailResp) -> Self { - let transitions = value.transitions().into_iter().map(FlowTransitionAddReq::from).collect_vec(); - let states = value.states().into_iter().map(FlowModelBindStateReq::from).collect_vec(); + let mut add_transitions = vec![]; + for transition in value.transitions() { + add_transitions.push(FlowTransitionAddReq::from(transition)); + } + let states = value + .states() + .into_iter() + .map(|state| FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: state.id.clone(), + ext: state.ext, + }), + bind_new_state: None, + add_transitions: Some(add_transitions.clone().into_iter().filter(|tran| tran.from_flow_state_id == state.id).collect_vec()), + modify_transitions: None, + delete_transitions: None, + is_init: value.init_state_id == state.id, + }) + .collect_vec(); Self { name: value.name.as_str().into(), icon: Some(value.icon.clone()), info: Some(value.info.clone()), - init_state_id: value.init_state_id, + kind: value.kind, + status: value.status, + rel_transition_ids: None, rel_template_ids: Some(value.rel_template_ids.clone()), - transitions: if transitions.is_empty() { None } else { Some(transitions) }, - states: if states.is_empty() { None } else { Some(states) }, + add_version: Some(FlowModelVersionAddReq { + name: value.name.as_str().into(), + rel_model_id: None, + bind_states: Some(states), + status: FlowModelVesionState::Enabled, + scope_level: Some(value.scope_level.clone()), + disabled: Some(value.disabled), + }), + current_version_id: None, template: value.template, + main: value.main, rel_model_id: None, tag: Some(value.tag.clone()), scope_level: Some(value.scope_level), @@ -69,6 +102,29 @@ impl From for FlowModelAddReq { } } +/// 工作流模型类型 +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, poem_openapi::Enum, EnumIter, sea_orm::DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(255))")] +pub enum FlowModelKind { + #[sea_orm(string_value = "as_template")] + AsTemplate, + #[sea_orm(string_value = "as_model")] + AsModel, + #[sea_orm(string_value = "as_template_and_as_model")] + AsTemplateAndAsModel, +} + +/// 工作流模型状态 +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, poem_openapi::Enum, EnumIter, sea_orm::DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(255))")] +pub enum FlowModelStatus { + #[default] + #[sea_orm(string_value = "enabled")] + Enabled, + #[sea_orm(string_value = "disabled")] + Disabled, +} + /// 修改请求 #[derive(Serialize, Deserialize, Debug, Default, poem_openapi::Object, Clone)] pub struct FlowModelModifyReq { @@ -78,22 +134,14 @@ pub struct FlowModelModifyReq { pub icon: Option, #[oai(validator(max_length = "2000"))] pub info: Option, - /// 初始化状态ID - pub init_state_id: Option, /// 是否作为模板使用 pub template: Option, - /// 添加动作 - pub add_transitions: Option>, - /// 修改动作 - pub modify_transitions: Option>, - /// 删除动作 - pub delete_transitions: Option>, - /// 绑定状态 - pub bind_states: Option>, - /// 解绑状态 - pub unbind_states: Option>, - /// 修改状态 - pub modify_states: Option>, + /// 状态 + pub status: Option, + /// 当前版本ID + pub current_version_id: Option, + /// 修改版本 + pub modify_version: Option, /// 标签 pub tag: Option, /// 关联模板ID(目前可能是页面模板ID,或者是项目模板ID) @@ -112,9 +160,8 @@ pub struct FlowModelSummaryResp { pub name: String, pub icon: String, pub info: String, - /// 初始化状态ID pub init_state_id: String, - + pub current_version_id: String, pub owner: String, pub own_paths: String, pub create_time: DateTime, @@ -123,6 +170,18 @@ pub struct FlowModelSummaryResp { pub tag: String, pub disabled: bool, + pub status: FlowModelStatus, + + pub states: Value, + /// 关联动作 + pub rel_transition: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone, poem_openapi::Object, sea_orm::FromQueryResult)] +pub struct FlowModelRelTransitionExt { + pub id: String, + pub name: String, + pub from_flow_state_name: String, } /// 工作流模型详细信息 @@ -132,10 +191,15 @@ pub struct FlowModelDetailResp { pub name: String, pub icon: String, pub info: String, - /// 初始化状态ID - pub init_state_id: String, + pub kind: FlowModelKind, + pub status: FlowModelStatus, /// 是否作为模板使用 pub template: bool, + /// 是否主流程 + pub main: bool, + + pub init_state_id: String, + pub current_version_id: String, /// 关联父级模型ID pub rel_model_id: String, /// 关联模板ID(目前可能是页面模板ID,或者是项目模板ID) @@ -154,6 +218,8 @@ pub struct FlowModelDetailResp { pub scope_level: RbumScopeLevelKind, pub disabled: bool, + /// 关联动作 + pub rel_transition: Option, } impl FlowModelDetailResp { @@ -166,10 +232,14 @@ impl FlowModelDetailResp { pub fn states(&self) -> Vec { match &self.states { - Some(states) => TardisFuns::json.json_to_obj(states.clone()).unwrap(), + Some(states) => TardisFuns::json.json_to_obj(states.clone()).unwrap_or_default(), None => vec![], } } + + pub fn rel_transition(&self) -> Option { + self.rel_transition.clone().map(|rel_transition| TardisFuns::json.json_to_obj(rel_transition.clone()).unwrap()) + } } /// 工作流模型过滤器 @@ -181,13 +251,19 @@ pub struct FlowModelFilterReq { /// 标签集合 pub tags: Option>, + pub kinds: Option>, + pub status: Option, /// 是否作为模板使用 pub template: Option, + /// 是否是主流程 + pub main: Option, pub own_paths: Option>, /// 指定状态ID(用于过滤动作) pub specified_state_ids: Option>, /// 关联模型ID pub rel_model_ids: Option>, + /// 关联模板ID + pub rel_template_id: Option, pub rel: Option, pub rel2: Option, @@ -212,12 +288,13 @@ pub struct FlowModelAggResp { pub name: String, pub icon: String, pub info: String, - /// 初始化状态ID - pub init_state_id: String, /// 是否作为模板使用 pub template: bool, /// 关联父级模型ID pub rel_model_id: String, + pub init_state_id: String, + pub current_version_id: String, + pub edit_version_id: String, /// 关联模板ID(目前可能是页面模板ID,或者是项目模板ID) pub rel_template_ids: Vec, /// 绑定的状态 @@ -253,6 +330,16 @@ impl From for FlowModelBindStateReq { } } +/// 绑定状态 +#[derive(Serialize, Deserialize, Debug, Default, poem_openapi::Object, Clone)] +pub struct FlowModelBindNewStateReq { + /// Associated [flow_state](super::flow_state_dto::FlowStateDetailResp) id + /// + /// 关联的[工作流状态](super::flow_state_dto::FlowStateDetailResp) id + pub new_state: FlowStateAddReq, + pub ext: FlowStateRelModelExt, +} + /// 解绑状态 #[derive(Serialize, Deserialize, Debug, Default, poem_openapi::Object)] pub struct FlowModelUnbindStateReq { @@ -329,6 +416,7 @@ pub enum FlowModelAssociativeOperationKind { #[default] Reference, Copy, + ReferenceOrCopy, } /// 创建或引用模型请求 @@ -360,6 +448,8 @@ pub struct FlowModelCopyOrReferenceCiReq { pub rel_template_id: Option, /// 关联操作 pub op: FlowModelAssociativeOperationKind, + /// 切换模板时,状态更新映射 + pub update_states: Option>>, } /// 检查关联模板请求 diff --git a/backend/middlewares/flow/src/dto/flow_model_version_dto.rs b/backend/middlewares/flow/src/dto/flow_model_version_dto.rs new file mode 100644 index 000000000..fd06484e3 --- /dev/null +++ b/backend/middlewares/flow/src/dto/flow_model_version_dto.rs @@ -0,0 +1,196 @@ +use bios_basic::rbum::{ + dto::rbum_filer_dto::{RbumBasicFilterReq, RbumItemFilterFetcher, RbumItemRelFilterReq}, + rbum_enumeration::RbumScopeLevelKind, +}; +use serde::{Deserialize, Serialize}; +use tardis::{ + basic::field::TrimString, + chrono::{DateTime, Utc}, + db::sea_orm::{self, prelude::*, EnumIter}, + serde_json::Value, + web::poem_openapi, + TardisFuns, +}; + +use super::{ + flow_model_dto::{FlowModelBindNewStateReq, FlowModelBindStateReq}, + flow_state_dto::{FlowStateAggResp, FlowStateModifyReq, FlowStateRelModelModifyReq}, + flow_transition_dto::{FlowTransitionAddReq, FlowTransitionModifyReq}, +}; + +/// 版本状态 +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, poem_openapi::Enum, EnumIter, sea_orm::DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(255))")] +pub enum FlowModelVesionState { + #[default] + /// 启用中 + #[sea_orm(string_value = "enabled")] + Enabled, + /// 已关闭 + #[sea_orm(string_value = "disabled")] + Disabled, + /// 编辑中 + #[sea_orm(string_value = "editing")] + Editing, +} + +/// 添加请求 +#[derive(Clone, Serialize, Deserialize, Debug, Default, poem_openapi::Object)] +pub struct FlowModelVersionAddReq { + #[oai(validator(min_length = "2", max_length = "200"))] + pub name: TrimString, + /// 关联的模型ID + pub rel_model_id: Option, + /// 配置状态节点 + pub bind_states: Option>, + /// 版本状态 + pub status: FlowModelVesionState, + + pub scope_level: Option, + pub disabled: Option, +} + +/// 模型绑定状态节点 +#[derive(Clone, Serialize, Deserialize, Debug, Default, poem_openapi::Object)] +pub struct FlowModelVersionBindState { + /// 若存在则表示,绑定已有状态节点 + pub exist_state: Option, + /// 若存在则表示,新建状态节点 + pub bind_new_state: Option, + /// 添加动作 + pub add_transitions: Option>, + /// 修改动作 + pub modify_transitions: Option>, + /// 删除动作 + pub delete_transitions: Option>, + /// 是否为初始节点 + pub is_init: bool, +} + +/// 模型绑定状态节点 +#[derive(Clone, Serialize, Deserialize, Debug, Default, poem_openapi::Object)] +pub struct FlowModelVersionModifyState { + /// 若存在则表示,绑定已有状态节点 + pub id: String, + pub modify_state: Option, + pub modify_rel: Option, + /// 添加动作 + pub add_transitions: Option>, + /// 修改动作 + pub modify_transitions: Option>, + /// 删除动作 + pub delete_transitions: Option>, +} + +/// 修改请求 +#[derive(Clone, Serialize, Deserialize, Debug, Default, poem_openapi::Object)] +pub struct FlowModelVersionModifyReq { + #[oai(validator(min_length = "2", max_length = "200"))] + pub name: Option, + // 绑定状态 + pub bind_states: Option>, + // 修改状态 + pub modify_states: Option>, + // 解绑状态 + pub unbind_states: Option>, + // 删除状态 + pub delete_states: Option>, + /// 定义每个模块的初始状态 + pub init_state_id: Option, + /// 版本状态 + pub status: Option, + + pub scope_level: Option, + pub disabled: Option, +} + +// FlowModelSummaryResp, FlowModelDetailResp, FlowModelFilterReq +/// 工作流版本模型概要信息 +#[derive(Serialize, Deserialize, Debug, Default, poem_openapi::Object, sea_orm::FromQueryResult)] +pub struct FlowModelVersionSummaryResp { + pub id: String, + pub name: String, + /// 关联的模型ID + pub rel_model_id: String, + /// Initial state / 初始状态 + /// + /// Define the initial state of each model + /// 定义每个模块的初始状态 + pub init_state_id: String, + + /// 状态 启用中 已关闭 + pub status: FlowModelVesionState, + + pub owner: String, + pub own_paths: String, + + /// Creation time / 创建时间 + pub create_time: DateTime, + /// 创建者信息 + pub create_by: String, + /// 更新时间 + pub update_time: DateTime, + /// 修改人信息 + pub update_by: String, +} + +/// 工作流模型详细信息 +#[derive(Serialize, Deserialize, Debug, Clone, poem_openapi::Object, sea_orm::FromQueryResult)] +pub struct FlowModelVersionDetailResp { + pub id: String, + pub name: String, + /// 初始化状态ID + pub init_state_id: String, + /// 关联父级模型ID + pub rel_model_id: String, + /// 状态 + pub status: FlowModelVesionState, + /// 节点信息 + pub states: Option, + + pub own_paths: String, + pub owner: String, + pub create_time: DateTime, + pub update_time: DateTime, + + pub scope_level: RbumScopeLevelKind, + pub disabled: bool, +} + +impl FlowModelVersionDetailResp { + pub fn states(&self) -> Vec { + match &self.states { + Some(states) => TardisFuns::json.json_to_obj(states.clone()).unwrap(), + None => vec![], + } + } +} + +/// 工作流模型版本过滤器 +#[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone, Default)] +#[serde(default)] +pub struct FlowModelVersionFilterReq { + /// 基础过滤 + pub basic: RbumBasicFilterReq, + /// 指定状态ID(用于过滤动作) + pub specified_state_ids: Option>, + pub own_paths: Option>, + pub status: Option>, + /// 关联模型ID + pub rel_model_ids: Option>, + + pub rel: Option, + pub rel2: Option, +} + +impl RbumItemFilterFetcher for FlowModelVersionFilterReq { + fn basic(&self) -> &RbumBasicFilterReq { + &self.basic + } + fn rel(&self) -> &Option { + &self.rel + } + fn rel2(&self) -> &Option { + &self.rel2 + } +} diff --git a/backend/middlewares/flow/src/dto/flow_state_dto.rs b/backend/middlewares/flow/src/dto/flow_state_dto.rs index 116ebde70..525060a67 100644 --- a/backend/middlewares/flow/src/dto/flow_state_dto.rs +++ b/backend/middlewares/flow/src/dto/flow_state_dto.rs @@ -1,20 +1,26 @@ +use std::{collections::HashMap, str::FromStr}; +use strum::Display; + use bios_basic::rbum::{ dto::rbum_filer_dto::{RbumBasicFilterReq, RbumItemFilterFetcher, RbumItemRelFilterReq}, rbum_enumeration::RbumScopeLevelKind, }; use serde::{Deserialize, Serialize}; use tardis::{ - basic::field::TrimString, + basic::{dto::TardisContext, error::TardisError, field::TrimString}, chrono::{DateTime, Utc}, db::sea_orm::{self, prelude::*, EnumIter}, serde_json::Value, web::poem_openapi, + TardisFuns, }; use super::flow_transition_dto::FlowTransitionDetailResp; -#[derive(Serialize, Deserialize, Default, Debug, poem_openapi::Object)] +#[derive(Clone, Serialize, Deserialize, Default, Debug, poem_openapi::Object)] pub struct FlowStateAddReq { + #[oai(validator(min_length = "2", max_length = "200"))] + pub id: Option, #[oai(validator(min_length = "2", max_length = "200"))] pub id_prefix: Option, #[oai(validator(min_length = "2", max_length = "200"))] @@ -26,7 +32,7 @@ pub struct FlowStateAddReq { #[oai(validator(min_length = "2", max_length = "2000"))] pub info: Option, pub state_kind: Option, - pub kind_conf: Option, + pub kind_conf: Option, pub template: Option, #[oai(validator(min_length = "2", max_length = "255"))] @@ -38,8 +44,199 @@ pub struct FlowStateAddReq { pub scope_level: Option, pub disabled: Option, } +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, PartialEq, Clone, sea_orm::FromJsonQueryResult)] +pub struct FLowStateKindConf { + pub form: Option, + pub approval: Option, +} + +/// 录入节点配置信息 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, PartialEq, Clone)] +pub struct FlowStateForm { + pub nodify: bool, + pub nodify_conf: Option, + /// 权限配置:为true时,创建人可以操作 + pub guard_by_creator: bool, + /// 权限配置:为true时,历史操作人可以操作 + pub guard_by_his_operators: bool, + /// 权限配置:为true时,负责人可以操作 + pub guard_by_assigned: bool, + /// 权限配置:自定义配置 + pub guard_custom: bool, + /// 权限配置:自定义配置 + pub guard_custom_conf: Option, + /// 当操作人为空时的自动处理策略 + pub auto_transfer_when_empty_kind: Option, + /// 当操作人为空且策略选择为指定代理,则当前配置人员权限生效 + pub auto_transfer_when_empty_guard_custom_conf: Option, + /// 当操作人为空且策略选择为流转节点,则当前配置节点ID生效 + pub auto_transfer_when_empty_state_id: Option, + /// 是否允许转办 + pub referral: bool, + /// 转办自定义人员权限 + pub referral_guard_custom_conf: Option, + /// 字段配置 + pub vars_collect: HashMap, + /// 提交动作名称 + pub submit_btn_name: String, +} + +/// 审批节点配置信息 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, PartialEq, Clone)] +pub struct FlowStateApproval { + /// 通知 + pub nodify: bool, + pub nodify_conf: Option, + /// 结果通知 + pub response_nodify: bool, + pub response_nodify_conf: Option, + /// 权限配置:为true时,创建人可以操作 + pub guard_by_creator: bool, + /// 权限配置:为true时,历史操作人可以操作 + pub guard_by_his_operators: bool, + /// 权限配置:为true时,负责人可以操作 + pub guard_by_assigned: bool, + /// 权限配置:自定义配置 + pub guard_custom: bool, + /// 权限配置:自定义配置 + pub guard_custom_conf: Option, + /// 当操作人为空时的自动处理策略 + pub auto_transfer_when_empty_kind: Option, + /// 当操作人为空且策略选择为指定代理,则当前配置人员权限生效 + pub auto_transfer_when_empty_guard_custom_conf: Option, + /// 当操作人为空且策略选择为流转节点,则当前配置节点ID生效 + pub auto_transfer_when_empty_state_id: Option, + /// 是否允许撤销 + pub revoke: bool, + /// 是否允许转办 + pub referral: bool, + /// 转办自定义人员权限 + pub referral_guard_custom: bool, + pub referral_guard_custom_conf: Option, + /// 字段配置 + pub vars_collect: HashMap, + /// 多人审批策略方式 + pub multi_approval_kind: FlowStatusMultiApprovalKind, + /// 会签配置 + pub countersign_conf: FlowStateCountersignConf, + + /// 拒绝动作名称 + pub overrule_btn_name: String, + /// 退回动作名称 + pub back_btn_name: String, + /// 通过动作名称 + pub pass_btn_name: String, +} + +/// 状态节点字段配置 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, PartialEq, Clone)] +pub struct FlowStateVar { + pub show: bool, + pub edit: bool, + pub required: bool, +} + +/// 状态自动处理的策略类型 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Enum, Default, EnumIter, sea_orm::DeriveActiveEnum, PartialEq, Clone)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(255))")] +pub enum FlowStatusAutoStrategyKind { + /// 自动跳过 + #[default] + #[sea_orm(string_value = "autoskip")] + Autoskip, + /// 指定代理 + #[sea_orm(string_value = "specify_agent")] + SpecifyAgent, + /// 流转节点 + #[sea_orm(string_value = "transfer_state")] + TransferState, +} + +/// 多人审批策略方式 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Enum, Default, EnumIter, sea_orm::DeriveActiveEnum, PartialEq, Clone)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(255))")] +pub enum FlowStatusMultiApprovalKind { + /// 或签 + #[default] + #[sea_orm(string_value = "orsign")] + Orsign, + /// 会签 + #[sea_orm(string_value = "countersign")] + Countersign, +} + +/// 会签配置 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, PartialEq, Clone)] +pub struct FlowStateCountersignConf { + /// 类型 + pub kind: FlowStateCountersignKind, + /// 多数人通过比例 + pub most_percent: Option, + /// 审批人权限配置 + pub guard_custom_conf: Option, + /// 指定人通过即通过 + pub specified_pass_guard: Option, + pub specified_pass_guard_conf: Option, + /// 指定人拒绝即拒绝 + pub specified_overrule_guard: Option, + pub specified_overrule_guard_conf: Option, +} + +/// 多人审批策略方式 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Enum, Default, EnumIter, sea_orm::DeriveActiveEnum, PartialEq, Clone)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(255))")] +pub enum FlowStateCountersignKind { + /// 所有人签 + #[default] + #[sea_orm(string_value = "all")] + All, + /// 多数人签 + #[sea_orm(string_value = "most")] + Most, +} + +/// 人员权限配置 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, PartialEq, Clone)] +pub struct FlowGuardConf { + /// 权限配置:为true时,指定操作人可以操作 + pub guard_by_spec_account_ids: Vec, + /// 权限配置:为true时,指定角色可以操作 + pub guard_by_spec_role_ids: Vec, + /// 权限配置:为true时,指定组织可以操作 + pub guard_by_spec_org_ids: Vec, +} + +impl FlowGuardConf { + pub fn check(&self, ctx: &TardisContext) -> bool { + if self.guard_by_spec_account_ids.contains(&ctx.owner) { + return true; + } + if self.guard_by_spec_role_ids.iter().any(|r| ctx.roles.contains(r)) { + return true; + } + if self.guard_by_spec_org_ids.iter().any(|o| ctx.groups.contains(o)) { + return true; + } + false + } +} -#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default)] +// 节点通知配置 +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, PartialEq, Clone)] +pub struct FlowNodifyConf { + /// 权限配置:指定操作人可以操作 + pub guard_by_owner: bool, + /// 权限配置:自定义配置 + pub guard_custom: bool, + /// 权限配置:自定义配置 + pub guard_custom_conf: Option, + /// 通知方式:短信通知 + pub send_sms: bool, + /// 通知方式:邮箱通知 + pub send_mail: bool, +} + +#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, Default, Clone)] pub struct FlowStateModifyReq { #[oai(validator(min_length = "2", max_length = "200"))] pub name: Option, @@ -50,7 +247,7 @@ pub struct FlowStateModifyReq { #[oai(validator(min_length = "2", max_length = "2000"))] pub info: Option, pub state_kind: Option, - pub kind_conf: Option, + pub kind_conf: Option, pub template: Option, #[oai(validator(min_length = "2", max_length = "255"))] @@ -72,9 +269,6 @@ pub struct FlowStateSummaryResp { pub sys_state: FlowSysStateKind, pub info: String, - pub state_kind: FlowStateKind, - pub kind_conf: Value, - pub template: bool, pub rel_state_id: String, @@ -87,7 +281,7 @@ pub struct FlowStateSummaryResp { pub disabled: bool, } -#[derive(Serialize, Deserialize, Debug, poem_openapi::Object, sea_orm::FromQueryResult)] +#[derive(Clone, Serialize, Deserialize, Debug, poem_openapi::Object, sea_orm::FromQueryResult)] pub struct FlowStateDetailResp { pub id: String, pub name: String, @@ -97,7 +291,7 @@ pub struct FlowStateDetailResp { pub info: String, pub state_kind: FlowStateKind, - pub kind_conf: Value, + pub kind_conf: Option, pub template: bool, pub rel_state_id: String, @@ -113,6 +307,12 @@ pub struct FlowStateDetailResp { pub disabled: bool, } +impl FlowStateDetailResp { + pub fn kind_conf(&self) -> Option { + self.kind_conf.clone().map(|kind_conf| TardisFuns::json.json_to_obj(kind_conf.clone()).unwrap_or_default()) + } +} + /// Type of state /// /// 状态类型 @@ -129,11 +329,14 @@ pub enum FlowSysStateKind { Finish, } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, poem_openapi::Enum, EnumIter, sea_orm::DeriveActiveEnum)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, poem_openapi::Enum, EnumIter, sea_orm::DeriveActiveEnum)] #[sea_orm(rs_type = "String", db_type = "String(StringLen::N(255))")] pub enum FlowStateKind { + /// 普通节点 + #[default] #[sea_orm(string_value = "simple")] Simple, + /// 录入节点 #[sea_orm(string_value = "form")] Form, #[sea_orm(string_value = "mail")] @@ -144,6 +347,18 @@ pub enum FlowStateKind { Timer, #[sea_orm(string_value = "script")] Script, + /// 审批节点 + #[sea_orm(string_value = "approval")] + Approval, + /// 分支节点 + #[sea_orm(string_value = "branch")] + Branch, + /// 开始节点 + #[sea_orm(string_value = "start")] + Start, + /// 结束节点 + #[sea_orm(string_value = "finish")] + Finish, } #[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone, Default)] @@ -154,7 +369,7 @@ pub struct FlowStateFilterReq { pub tag: Option, pub state_kind: Option, pub template: Option, - pub flow_model_ids: Option>, + pub flow_version_ids: Option>, } impl RbumItemFilterFetcher for FlowStateFilterReq { @@ -208,5 +423,49 @@ pub struct FlowStateAggResp { pub name: String, pub is_init: bool, pub ext: FlowStateRelModelExt, + pub state_kind: FlowStateKind, + pub kind_conf: Option, + pub sys_state: FlowSysStateKind, + pub tags: String, + pub scope_level: RbumScopeLevelKind, + pub disabled: bool, pub transitions: Vec, } +#[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone)] +pub struct FLowStateIdAndName { + pub id: String, + pub name: String, +} + +/// 可操作类型 +#[derive(Display, Serialize, Deserialize, Hash, Eq, PartialEq, Debug, poem_openapi::Enum, Clone)] +pub enum FlowStateOperatorKind { + /// 转办 + Referral, + /// 撤销 + Revoke, + /// 提交 + Submit, + /// 退回 + Back, + /// 通过 + Pass, + /// 拒绝 + Overrule, +} + +impl FromStr for FlowStateOperatorKind { + type Err = TardisError; + + fn from_str(s: &str) -> Result { + match s.to_uppercase().as_str() { + "REFERRAL" => Ok(Self::Referral), + "REVOKE" => Ok(Self::Revoke), + "SUBMIT" => Ok(Self::Submit), + "BACK" => Ok(Self::Back), + "PASS" => Ok(Self::Pass), + "OVERRULE" => Ok(Self::Overrule), + _ => Err(TardisError::bad_request(&format!("invalid FlowStateOperatorKind: {}", s), "400-operator-invalid-param")), + } + } +} diff --git a/backend/middlewares/flow/src/dto/flow_transition_dto.rs b/backend/middlewares/flow/src/dto/flow_transition_dto.rs index b81a83bf5..e7d9c6b27 100644 --- a/backend/middlewares/flow/src/dto/flow_transition_dto.rs +++ b/backend/middlewares/flow/src/dto/flow_transition_dto.rs @@ -191,7 +191,7 @@ pub struct FlowTransitionDetailResp { /// Associated [flow_state](super::flow_model_dto::FlowModelDetailResp) id /// /// 关联的[工作流状态](super::flow_model_dto::FlowModelDetailResp) id - pub rel_flow_model_id: String, + pub rel_flow_model_version_id: String, /// 排序 pub sort: i64, } @@ -284,7 +284,7 @@ pub struct FlowTransitionSortStatesReq { pub sort_states: Vec, } -#[derive(Serialize, Deserialize, Debug, Default, poem_openapi::Object)] +#[derive(Serialize, Deserialize, Debug, Default, poem_openapi::Object, Clone)] pub struct FlowTransitionSortStateInfoReq { pub id: String, pub sort: i64, @@ -667,3 +667,13 @@ pub enum FlowTransitionFrontActionRightValue { #[oai(rename = "real_time")] RealTime, } + +/// 工作流模型过滤器 +#[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone, Default)] +#[serde(default)] +pub struct FlowTransitionFilterReq { + pub ids: Option>, + pub flow_version_id: Option, + /// 指定状态ID(用于过滤动作) + pub specified_state_ids: Option>, +} diff --git a/backend/middlewares/flow/src/flow_config.rs b/backend/middlewares/flow/src/flow_config.rs index ef4cd15fb..ef13116e2 100644 --- a/backend/middlewares/flow/src/flow_config.rs +++ b/backend/middlewares/flow/src/flow_config.rs @@ -62,6 +62,7 @@ impl FlowConfig { pub struct BasicInfo { pub kind_state_id: String, pub kind_model_id: String, + pub kind_model_version_id: String, pub domain_flow_id: String, } diff --git a/backend/middlewares/flow/src/flow_constants.rs b/backend/middlewares/flow/src/flow_constants.rs index e5788ba43..6e5cc5693 100644 --- a/backend/middlewares/flow/src/flow_constants.rs +++ b/backend/middlewares/flow/src/flow_constants.rs @@ -5,6 +5,8 @@ pub const RBUM_KIND_STATE_CODE: &str = "fw-state"; pub const RBUM_EXT_TABLE_STATE: &str = "flow_state"; pub const RBUM_KIND_MODEL_CODE: &str = "fw-model"; pub const RBUM_EXT_TABLE_MODEL: &str = "flow_model"; +pub const RBUM_KIND_MODEL_VERSION_CODE: &str = "fw-model-version"; +pub const RBUM_EXT_TABLE_MODEL_VERSION: &str = "flow_model_version"; pub fn get_tardis_inst() -> TardisFunsInst { TardisFuns::inst_with_db_conn(DOMAIN_CODE.to_string(), None) diff --git a/backend/middlewares/flow/src/flow_initializer.rs b/backend/middlewares/flow/src/flow_initializer.rs index e632d6c7c..ef67c48c2 100644 --- a/backend/middlewares/flow/src/flow_initializer.rs +++ b/backend/middlewares/flow/src/flow_initializer.rs @@ -26,12 +26,12 @@ use tardis::{ use crate::{ api::{ ca::flow_ca_model_api, - cc::{flow_cc_inst_api, flow_cc_model_api, flow_cc_state_api}, + cc::{flow_cc_inst_api, flow_cc_model_api, flow_cc_model_version_api, flow_cc_state_api}, ci::{flow_ci_inst_api, flow_ci_model_api, flow_ci_state_api}, cs::flow_cs_config_api, ct::flow_ct_model_api, }, - domain::{flow_inst, flow_model, flow_state, flow_transition}, + domain::{flow_inst, flow_model, flow_model_version, flow_state, flow_transition}, dto::{ flow_model_dto::FlowModelFilterReq, flow_state_dto::FlowSysStateKind, @@ -61,6 +61,7 @@ async fn init_api(web_server: &TardisWebServer) -> TardisResult<()> { flow_ct_model_api::FlowCtModelApi, flow_cc_state_api::FlowCcStateApi, flow_cc_model_api::FlowCcModelApi, + flow_cc_model_version_api::FlowCcModelVersionApi, flow_cc_inst_api::FlowCcInstApi, flow_cs_config_api::FlowCsConfigApi, flow_ci_inst_api::FlowCiInstApi, @@ -92,6 +93,7 @@ pub async fn init_db(mut funs: TardisFunsInst) -> TardisResult<()> { let compatible_type = TardisFuns::reldb().compatible_type(); funs.db().init(flow_state::ActiveModel::init(db_kind, None, compatible_type)).await?; funs.db().init(flow_model::ActiveModel::init(db_kind, None, compatible_type)).await?; + funs.db().init(flow_model_version::ActiveModel::init(db_kind, None, compatible_type)).await?; funs.db().init(flow_transition::ActiveModel::init(db_kind, None, compatible_type)).await?; funs.db().init(flow_inst::ActiveModel::init(db_kind, None, compatible_type)).await?; init_rbum_data(&funs, &ctx).await?; @@ -166,6 +168,9 @@ async fn init_basic_info<'a>(funs: &TardisFunsInst) -> TardisResult<()> { let kind_model_id = RbumKindServ::get_rbum_kind_id_by_code(flow_constants::RBUM_KIND_MODEL_CODE, funs) .await? .ok_or_else(|| funs.err().not_found("flow", "init", "not found model kind", ""))?; + let kind_model_version_id = RbumKindServ::get_rbum_kind_id_by_code(flow_constants::RBUM_KIND_MODEL_VERSION_CODE, funs) + .await? + .ok_or_else(|| funs.err().not_found("flow", "init", "not found model kind", ""))?; let domain_flow_id = RbumDomainServ::get_rbum_domain_id_by_code(flow_constants::DOMAIN_CODE, funs).await?.ok_or_else(|| funs.err().not_found("flow", "init", "not found flow domain", ""))?; @@ -173,6 +178,7 @@ async fn init_basic_info<'a>(funs: &TardisFunsInst) -> TardisResult<()> { FlowBasicInfoManager::set(BasicInfo { kind_state_id, kind_model_id, + kind_model_version_id, domain_flow_id, })?; Ok(()) @@ -181,12 +187,14 @@ async fn init_basic_info<'a>(funs: &TardisFunsInst) -> TardisResult<()> { pub async fn init_rbum_data(funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { let kind_state_id = add_kind(flow_constants::RBUM_KIND_STATE_CODE, flow_constants::RBUM_EXT_TABLE_STATE, funs, ctx).await?; let kind_model_id = add_kind(flow_constants::RBUM_KIND_MODEL_CODE, flow_constants::RBUM_EXT_TABLE_MODEL, funs, ctx).await?; + let kind_model_version_id = add_kind(flow_constants::RBUM_KIND_MODEL_VERSION_CODE, flow_constants::RBUM_EXT_TABLE_MODEL_VERSION, funs, ctx).await?; let domain_flow_id = add_domain(funs, ctx).await?; FlowBasicInfoManager::set(BasicInfo { kind_state_id, kind_model_id, + kind_model_version_id, domain_flow_id, })?; diff --git a/backend/middlewares/flow/src/serv.rs b/backend/middlewares/flow/src/serv.rs index 04b6e4b70..2e24938bc 100644 --- a/backend/middlewares/flow/src/serv.rs +++ b/backend/middlewares/flow/src/serv.rs @@ -4,5 +4,7 @@ pub mod flow_event_serv; pub mod flow_external_serv; pub mod flow_inst_serv; pub mod flow_model_serv; +pub mod flow_model_version_serv; pub mod flow_rel_serv; pub mod flow_state_serv; +pub mod flow_transition_serv; diff --git a/backend/middlewares/flow/src/serv/clients/flow_log_client.rs b/backend/middlewares/flow/src/serv/clients/flow_log_client.rs index 2d34952b4..108677725 100644 --- a/backend/middlewares/flow/src/serv/clients/flow_log_client.rs +++ b/backend/middlewares/flow/src/serv/clients/flow_log_client.rs @@ -91,7 +91,7 @@ impl FlowLogClient { let tag: String = tag.into(); let own_paths = if ctx.own_paths.len() < 2 { None } else { Some(ctx.own_paths.clone()) }; let owner = if ctx.owner.len() < 2 { None } else { Some(ctx.owner.clone()) }; - let owner_name = IamClient::new("", funs, &ctx, funs.conf::().invoke.module_urls.get("iam").expect("missing iam base url")) + let owner_name = IamClient::new("", funs, ctx, funs.conf::().invoke.module_urls.get("iam").expect("missing iam base url")) .get_account(&ctx.owner, &ctx.own_paths) .await? .owner_name; diff --git a/backend/middlewares/flow/src/serv/clients/search_client.rs b/backend/middlewares/flow/src/serv/clients/search_client.rs index b85d623a1..e13f4d3fb 100644 --- a/backend/middlewares/flow/src/serv/clients/search_client.rs +++ b/backend/middlewares/flow/src/serv/clients/search_client.rs @@ -1,4 +1,4 @@ -use std::vec; +use std::{collections::HashMap, vec}; use bios_basic::rbum::{dto::rbum_filer_dto::RbumBasicFilterReq, helper::rbum_scope_helper, rbum_enumeration::RbumScopeLevelKind, serv::rbum_item_serv::RbumItemCrudOperation}; use bios_sdk_invoke::{ @@ -8,16 +8,26 @@ use bios_sdk_invoke::{ }, dto::search_item_dto::{SearchItemAddReq, SearchItemModifyReq, SearchItemVisitKeysReq}, }; +use itertools::Itertools; use serde_json::json; use tardis::{ basic::{dto::TardisContext, field::TrimString, result::TardisResult}, - tokio, TardisFunsInst, + tokio, TardisFuns, TardisFunsInst, }; use crate::{ - dto::flow_model_dto::{FlowModelDetailResp, FlowModelFilterReq}, + dto::{ + flow_inst_dto::FlowInstFilterReq, + flow_model_dto::{FlowModelDetailResp, FlowModelFilterReq, FlowModelRelTransitionExt}, + flow_model_version_dto::FlowModelVersionFilterReq, + }, flow_constants, - serv::flow_model_serv::FlowModelServ, + serv::{ + flow_inst_serv::FlowInstServ, + flow_model_serv::FlowModelServ, + flow_model_version_serv::FlowModelVersionServ, + flow_rel_serv::{FlowRelKind, FlowRelServ}, + }, }; const SEARCH_TAG: &str = "flow_model"; @@ -25,6 +35,110 @@ const SEARCH_TAG: &str = "flow_model"; pub struct FlowSearchClient; impl FlowSearchClient { + pub async fn async_modify_business_obj_search(inst_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let ctx_clone = ctx.clone(); + let inst_detail = FlowInstServ::get(inst_id, funs, ctx).await?; + ctx.add_async_task(Box::new(|| { + Box::pin(async move { + let task_handle = tokio::spawn(async move { + let funs = flow_constants::get_tardis_inst(); + let _ = Self::modify_business_obj_search(&inst_detail.rel_business_obj_id, &inst_detail.tag, &funs, &ctx_clone).await; + }); + task_handle.await.unwrap(); + Ok(()) + }) + })) + .await + } + + pub async fn modify_business_obj_search(rel_business_obj_id: &str, tag: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let tag_search_map = HashMap::from([ + ("CTS", "idp_test"), + ("ISSUE", "idp_test"), + ("ITER", "idp_project"), + ("MS", "idp_project"), + ("PROJ", "idp_project"), + ("REQ", "idp_project"), + ("TASK", "idp_project"), + ("TICKET", "ticket"), + ("TP", "idp_test"), + ("TS", "idp_test"), + ]); + let rel_version_ids = FlowInstServ::find_details( + &FlowInstFilterReq { + rel_business_obj_id: Some(rel_business_obj_id.to_string()), + main: Some(false), + finish: Some(false), + ..Default::default() + }, + funs, + ctx, + ) + .await? + .into_iter() + .map(|inst| inst.rel_flow_version_id) + .collect_vec(); + let mut rel_transition_names = vec![]; + for rel_version_id in rel_version_ids { + if let Some(rel_model_id) = FlowModelVersionServ::find_one_item( + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + ids: Some(vec![rel_version_id]), + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await? + .map(|version| version.rel_model_id) + { + let rel_transition_ext = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, &rel_model_id, None, None, funs, ctx) + .await? + .pop() + .map(|rel| TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default()); + if let Some(ext) = rel_transition_ext { + rel_transition_names.push(match ext.id.as_str() { + "__EDIT__" => "编辑".to_string(), + "__DELETE__" => "删除".to_string(), + _ => format!("{}({})", ext.name, ext.from_flow_state_name), + }); + } + } + } + if let Some(table) = tag_search_map.get(tag) { + SpiSearchClient::modify_item_and_name( + table, + rel_business_obj_id, + &SearchItemModifyReq { + kind: None, + title: None, + name: None, + content: None, + owner: None, + own_paths: None, + create_time: None, + update_time: None, + ext: Some(json!({ + "rel_transitions": rel_transition_names, + })), + ext_override: None, + visit_keys: None, + kv_disable: None, + }, + funs, + ctx, + ) + .await + .unwrap_or_default(); + } + + Ok(()) + } + pub async fn async_add_or_modify_model_search(model_id: &str, is_modify: Box, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { let ctx_clone = ctx.clone(); let mock_ctx = TardisContext { diff --git a/backend/middlewares/flow/src/serv/flow_event_serv.rs b/backend/middlewares/flow/src/serv/flow_event_serv.rs index ee1716519..d32918aa1 100644 --- a/backend/middlewares/flow/src/serv/flow_event_serv.rs +++ b/backend/middlewares/flow/src/serv/flow_event_serv.rs @@ -20,6 +20,7 @@ use crate::{ flow_external_dto::{FlowExternalCallbackOp, FlowExternalParams}, flow_inst_dto::{FlowInstDetailResp, FlowInstTransferReq}, flow_model_dto::{FlowModelDetailResp, FlowModelFilterReq}, + flow_model_version_dto::FlowModelVersionFilterReq, flow_state_dto::FlowStateFilterReq, flow_transition_dto::{ FlowTransitionActionByStateChangeInfo, FlowTransitionActionByVarChangeInfoChangedKind, FlowTransitionActionChangeAgg, FlowTransitionActionChangeKind, @@ -29,7 +30,10 @@ use crate::{ helper::loop_check_helper, }; -use super::{flow_external_serv::FlowExternalServ, flow_inst_serv::FlowInstServ, flow_model_serv::FlowModelServ, flow_state_serv::FlowStateServ}; +use super::{ + flow_external_serv::FlowExternalServ, flow_inst_serv::FlowInstServ, flow_model_serv::FlowModelServ, flow_model_version_serv::FlowModelVersionServ, + flow_state_serv::FlowStateServ, flow_transition_serv::FlowTransitionServ, +}; use bios_basic::rbum::serv::rbum_item_serv::RbumItemCrudOperation; use itertools::Itertools; @@ -39,15 +43,17 @@ pub struct FlowEventServ; impl FlowEventServ { #[async_recursion] pub async fn do_front_change( - flow_inst_id: &str, + flow_inst_detail: &FlowInstDetailResp, modified_instance_transations: loop_check_helper::InstancesTransition, ctx: &TardisContext, funs: &TardisFunsInst, ) -> TardisResult<()> { - let flow_inst_detail = FlowInstServ::get(flow_inst_id, funs, ctx).await?; - let flow_model = FlowModelServ::get_item( - &flow_inst_detail.rel_flow_model_id, - &FlowModelFilterReq { + if !flow_inst_detail.main { + return Ok(()); + } + let flow_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, own_paths: Some("".to_string()), @@ -59,19 +65,19 @@ impl FlowEventServ { ctx, ) .await?; - let flow_transitions = flow_model - .transitions() + let flow_transitions = flow_version + .states() .into_iter() - .filter(|trans| trans.from_flow_state_id == flow_inst_detail.current_state_id && !trans.action_by_front_changes().is_empty()) - .sorted_by_key(|trans| trans.sort) - .collect_vec(); + .find(|state| state.id == flow_inst_detail.current_state_id) + .ok_or_else(|| funs.err().not_found("flow_event", "do_front_change", "illegal response", "404-flow-transition-not-found"))? + .transitions; if flow_transitions.is_empty() { return Ok(()); } for flow_transition in flow_transitions { if Self::check_front_conditions(&flow_inst_detail, flow_transition.action_by_front_changes())? { FlowInstServ::transfer( - &flow_inst_detail.id, + &flow_inst_detail, &FlowInstTransferReq { flow_transition_id: flow_transition.id.clone(), message: None, @@ -81,6 +87,7 @@ impl FlowEventServ { FlowExternalCallbackOp::ConditionalTrigger, modified_instance_transations.clone(), ctx, + funs, ) .await?; break; @@ -91,7 +98,7 @@ impl FlowEventServ { } fn check_front_conditions(flow_inst_detail: &FlowInstDetailResp, conditions: Vec) -> TardisResult { - if flow_inst_detail.current_vars.is_none() { + if flow_inst_detail.current_vars.is_none() || conditions.is_empty() { return Ok(false); } let current_vars = flow_inst_detail.current_vars.clone().unwrap(); @@ -154,19 +161,35 @@ impl FlowEventServ { } pub async fn do_post_change( - flow_inst_id: &str, + flow_inst_detail: &FlowInstDetailResp, flow_transition_id: &str, modified_instance_transations: loop_check_helper::InstancesTransition, ctx: &TardisContext, funs: &TardisFunsInst, ) -> TardisResult<()> { - let flow_inst_detail = FlowInstServ::get(flow_inst_id, funs, ctx).await?; + if !flow_inst_detail.main { + return Ok(()); + } let global_ctx = TardisContext { own_paths: "".to_string(), ..ctx.clone() }; + let flow_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; let flow_model = FlowModelServ::get_item( - &flow_inst_detail.rel_flow_model_id, + &flow_version.rel_model_id, &FlowModelFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, @@ -179,7 +202,12 @@ impl FlowEventServ { ctx, ) .await?; - let model_transition = flow_model.transitions(); + let model_transition = flow_version + .states() + .into_iter() + .find(|state| state.id == flow_inst_detail.current_state_id) + .ok_or_else(|| funs.err().not_found("flow_event", "do_front_change", "illegal response", "404-flow-transition-not-found"))? + .transitions; let next_flow_transition = model_transition.iter().find(|trans| trans.id == flow_transition_id); if next_flow_transition.is_none() { return Err(funs.err().not_found("flow_inst", "transfer", "no transferable state", "404-flow-inst-transfer-state-not-found")); @@ -212,10 +240,6 @@ impl FlowEventServ { ) .await?; - // if FlowModelServ::check_post_action_ring(&flow_model, funs, ctx).await? { - // return Err(funs.err().not_found("flow_inst", "transfer", "this post action exist endless loop", "500-flow-transition-endless-loop")); - // } - let post_changes = next_flow_transition.action_by_post_changes(); if post_changes.is_empty() { return Ok(()); @@ -242,10 +266,10 @@ impl FlowEventServ { && change_info.changed_val.clone().unwrap().as_object().unwrap().get("op").is_some() { let original_value = if let Some(custom_value) = - FlowInstServ::find_var_by_inst_id(flow_inst_id, &format!("custom_{}", change_info.var_name), funs, ctx).await? + FlowInstServ::find_var_by_inst_id(&flow_inst_detail, &format!("custom_{}", change_info.var_name), funs, ctx).await? { Some(custom_value) - } else if let Some(original_value) = FlowInstServ::find_var_by_inst_id(flow_inst_id, &change_info.var_name, funs, ctx).await? { + } else if let Some(original_value) = FlowInstServ::find_var_by_inst_id(&flow_inst_detail, &change_info.var_name, funs, ctx).await? { Some(original_value) } else { Some(json!("")) @@ -303,14 +327,14 @@ impl FlowEventServ { let inst_id = FlowInstServ::get_inst_ids_by_rel_business_obj_id(vec![rel_bus_obj_id.clone()], funs, ctx).await?.pop().unwrap_or_default(); FlowExternalServ::do_modify_field( &rel_tag, - next_flow_transition, + Some(next_flow_transition.clone()), &rel_bus_obj_id, &inst_id, - FlowExternalCallbackOp::PostAction, - next_flow_state.name.clone(), - next_flow_state.sys_state.clone(), - prev_flow_state.name.clone(), - prev_flow_state.sys_state.clone(), + Some(FlowExternalCallbackOp::PostAction), + Some(next_flow_state.name.clone()), + Some(next_flow_state.sys_state.clone()), + Some(prev_flow_state.name.clone()), + Some(prev_flow_state.sys_state.clone()), vec![FlowExternalParams { rel_kind: None, rel_tag: None, @@ -323,7 +347,8 @@ impl FlowEventServ { funs, ) .await?; - FlowEventServ::do_front_change(&inst_id, modified_instance_transations.clone(), ctx, funs).await?; + let rel_flow_inst = FlowInstServ::get(&inst_id, funs, ctx).await?; + FlowEventServ::do_front_change(&rel_flow_inst, modified_instance_transations.clone(), ctx, funs).await?; } } } else { @@ -361,20 +386,20 @@ impl FlowEventServ { if !modify_self_field_params.is_empty() { FlowExternalServ::do_modify_field( &flow_model.tag, - next_flow_transition, + Some(next_flow_transition.clone()), &flow_inst_detail.rel_business_obj_id, &flow_inst_detail.id, - FlowExternalCallbackOp::PostAction, - next_flow_state.name.clone(), - next_flow_state.sys_state.clone(), - prev_flow_state.name.clone(), - prev_flow_state.sys_state.clone(), + Some(FlowExternalCallbackOp::PostAction), + Some(next_flow_state.name.clone()), + Some(next_flow_state.sys_state.clone()), + Some(prev_flow_state.name.clone()), + Some(prev_flow_state.sys_state.clone()), modify_self_field_params, ctx, funs, ) .await?; - FlowEventServ::do_front_change(&flow_inst_detail.id, modified_instance_transations.clone(), ctx, funs).await?; + FlowEventServ::do_front_change(&flow_inst_detail, modified_instance_transations.clone(), ctx, funs).await?; } Ok(()) @@ -488,9 +513,9 @@ impl FlowEventServ { let insts = FlowInstServ::find_detail(rel_inst_ids, funs, ctx).await?; for rel_inst in insts { // find transition - let flow_model = FlowModelServ::get_item( - &rel_inst.rel_flow_model_id, - &FlowModelFilterReq { + let flow_version = FlowModelVersionServ::get_item( + &rel_inst.rel_flow_version_id, + &FlowModelVersionFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, own_paths: Some("".to_string()), @@ -502,7 +527,8 @@ impl FlowEventServ { ctx, ) .await?; - let transition_resp = FlowInstServ::do_find_next_transitions(&rel_inst, &flow_model, None, &None, true, funs, ctx) + let rel_flow_versions = FlowTransitionServ::find_rel_model_map(&rel_inst.tag, funs, ctx).await?; + let transition_resp = FlowInstServ::do_find_next_transitions(&rel_inst, &flow_version, None, &None, rel_flow_versions, true, funs, ctx) .await? .next_flow_transitions .into_iter() @@ -511,7 +537,7 @@ impl FlowEventServ { .pop(); if let Some(transition) = transition_resp { FlowInstServ::transfer( - &rel_inst.id, + &rel_inst, &FlowInstTransferReq { flow_transition_id: transition.next_flow_transition_id, message: None, @@ -521,6 +547,7 @@ impl FlowEventServ { FlowExternalCallbackOp::PostAction, modified_instance_transations.clone(), ctx, + funs, ) .await?; } diff --git a/backend/middlewares/flow/src/serv/flow_external_serv.rs b/backend/middlewares/flow/src/serv/flow_external_serv.rs index 6318876e2..1ef8711b7 100644 --- a/backend/middlewares/flow/src/serv/flow_external_serv.rs +++ b/backend/middlewares/flow/src/serv/flow_external_serv.rs @@ -9,8 +9,8 @@ use tardis::{ use crate::{ dto::{ flow_external_dto::{ - FlowExternalCallbackOp, FlowExternalFetchRelObjResp, FlowExternalKind, FlowExternalModifyFieldResp, FlowExternalNotifyChangesResp, FlowExternalParams, - FlowExternalQueryFieldResp, FlowExternalReq, FlowExternalResp, + FlowExternalCallbackOp, FlowExternalDeleteRelObjResp, FlowExternalFetchRelObjResp, FlowExternalKind, FlowExternalModifyFieldResp, FlowExternalNotifyChangesResp, + FlowExternalParams, FlowExternalQueryFieldResp, FlowExternalReq, FlowExternalResp, }, flow_state_dto::FlowSysStateKind, flow_transition_dto::{FlowTransitionActionByVarChangeInfoChangedKind, FlowTransitionDetailResp, TagRelKind}, @@ -68,30 +68,28 @@ impl FlowExternalServ { pub async fn do_async_modify_field( tag: &str, - transition_detail: &FlowTransitionDetailResp, + transition_detail: Option, rel_business_obj_id: &str, inst_id: &str, - callback_op: FlowExternalCallbackOp, - target_state: String, - target_sys_state: FlowSysStateKind, - original_state: String, - original_sys_state: FlowSysStateKind, + callback_op: Option, + target_state: Option, + target_sys_state: Option, + original_state: Option, + original_sys_state: Option, params: Vec, ctx: &TardisContext, _funs: &TardisFunsInst, ) -> TardisResult<()> { let tag = tag.to_string(); - let transition_detail = transition_detail.clone(); let rel_business_obj_id = rel_business_obj_id.to_string(); let inst_id = inst_id.to_string(); - let transition_detail = transition_detail.clone(); - let transition_detail = transition_detail.clone(); + let ctx_clone = ctx.clone(); tokio::spawn(async move { let funs = flow_constants::get_tardis_inst(); let result = Self::do_modify_field( &tag, - &transition_detail, + transition_detail, &rel_business_obj_id, &inst_id, callback_op, @@ -113,14 +111,14 @@ impl FlowExternalServ { pub async fn do_modify_field( tag: &str, - transition_detail: &FlowTransitionDetailResp, + transition_detail: Option, rel_business_obj_id: &str, inst_id: &str, - callback_op: FlowExternalCallbackOp, - target_state: String, - target_sys_state: FlowSysStateKind, - original_state: String, - original_sys_state: FlowSysStateKind, + callback_op: Option, + target_state: Option, + target_sys_state: Option, + original_state: Option, + original_sys_state: Option, params: Vec, ctx: &TardisContext, funs: &TardisFunsInst, @@ -148,16 +146,16 @@ impl FlowExternalServ { let header = Self::headers(None, funs, ctx).await?; let body = FlowExternalReq { kind: FlowExternalKind::ModifyField, - callback_op: Some(callback_op), + callback_op, inst_id: inst_id.to_string(), curr_tag: tag.to_string(), curr_bus_obj_id: rel_business_obj_id.to_string(), - target_state: Some(target_state), - target_sys_state: Some(target_sys_state), - original_state: Some(original_state), - original_sys_state: Some(original_sys_state), - notify: Some(transition_detail.is_notify), - transition_name: Some(transition_detail.name.clone()), + target_state, + target_sys_state, + original_state, + original_sys_state, + notify: transition_detail.clone().map(|tran| tran.is_notify), + transition_name: transition_detail.map(|tran| tran.name), params, ..Default::default() }; @@ -268,6 +266,37 @@ impl FlowExternalServ { } } + pub async fn do_delete_rel_obj(tag: &str, rel_business_obj_id: &str, inst_id: &str, ctx: &TardisContext, funs: &TardisFunsInst) -> TardisResult { + let external_url = Self::get_external_url(tag, ctx, funs).await?; + if external_url.is_empty() { + return Ok(FlowExternalDeleteRelObjResp {}); + } + + let header = Self::headers(None, funs, ctx).await?; + let body = FlowExternalReq { + kind: FlowExternalKind::DeleteObj, + inst_id: inst_id.to_string(), + curr_tag: tag.to_string(), + curr_bus_obj_id: rel_business_obj_id.to_string(), + ..Default::default() + }; + debug!("do_delete_rel_obj body: {:?}", body); + let resp: FlowExternalResp = funs + .web_client() + .post(&external_url, &body, header) + .await? + .body + .ok_or_else(|| funs.err().internal_error("flow_external", "do_delete_rel_obj", "illegal response", "500-external-illegal-response"))?; + if resp.code != *"200" { + return Err(funs.err().internal_error("flow_external", "do_delete_rel_obj", "illegal response", "500-external-illegal-response")); + } + if let Some(data) = resp.body { + Ok(data) + } else { + Err(funs.err().internal_error("flow_external", "do_delete_rel_obj", "illegal response", "500-external-illegal-response")) + } + } + async fn get_external_url(tag: &str, ctx: &TardisContext, funs: &TardisFunsInst) -> TardisResult { let external_url = SpiKvClient::get_item(format!("{}:config:{}", flow_constants::DOMAIN_CODE, tag), None, funs, ctx) .await? diff --git a/backend/middlewares/flow/src/serv/flow_inst_serv.rs b/backend/middlewares/flow/src/serv/flow_inst_serv.rs index ddce8a063..e5c365e7b 100644 --- a/backend/middlewares/flow/src/serv/flow_inst_serv.rs +++ b/backend/middlewares/flow/src/serv/flow_inst_serv.rs @@ -15,6 +15,7 @@ use bios_basic::{ }, }; use itertools::Itertools; +use serde_json::json; use tardis::{ basic::{dto::TardisContext, result::TardisResult}, chrono::{DateTime, Utc}, @@ -31,17 +32,19 @@ use tardis::{ }; use crate::{ - domain::{flow_inst, flow_model, flow_transition}, + domain::{flow_inst, flow_model}, dto::{ flow_external_dto::{FlowExternalCallbackOp, FlowExternalParams}, flow_inst_dto::{ - FlowInstAbortReq, FlowInstBatchBindReq, FlowInstBatchBindResp, FlowInstDetailResp, FlowInstFilterReq, FlowInstFindNextTransitionResp, FlowInstFindNextTransitionsReq, - FlowInstFindStateAndTransitionsReq, FlowInstFindStateAndTransitionsResp, FlowInstStartReq, FlowInstSummaryResp, FlowInstSummaryResult, FlowInstTransferReq, - FlowInstTransferResp, FlowInstTransitionInfo, FlowOperationContext, + FLowInstStateApprovalConf, FLowInstStateConf, FLowInstStateFormConf, FlowApprovalResultKind, FlowInstAbortReq, FlowInstArtifacts, FlowInstArtifactsModifyReq, + FlowInstBatchBindReq, FlowInstBatchBindResp, FlowInstDetailResp, FlowInstFilterReq, FlowInstFindNextTransitionResp, FlowInstFindNextTransitionsReq, + FlowInstFindStateAndTransitionsReq, FlowInstFindStateAndTransitionsResp, FlowInstOperateReq, FlowInstStartReq, FlowInstSummaryResp, FlowInstSummaryResult, + FlowInstTransferReq, FlowInstTransferResp, FlowInstTransitionInfo, FlowOperationContext, }, - flow_model_dto::{FlowModelDetailResp, FlowModelFilterReq}, - flow_state_dto::{FlowStateAggResp, FlowStateFilterReq, FlowStateRelModelExt, FlowSysStateKind}, - flow_transition_dto::{FlowTransitionDetailResp, FlowTransitionFrontActionInfo}, + flow_model_dto::FlowModelFilterReq, + flow_model_version_dto::{FlowModelVersionDetailResp, FlowModelVersionFilterReq}, + flow_state_dto::{FLowStateKindConf, FlowStateAggResp, FlowStateFilterReq, FlowStateKind, FlowStateOperatorKind, FlowStateRelModelExt, FlowSysStateKind}, + flow_transition_dto::FlowTransitionDetailResp, flow_var_dto::FillType, }, flow_constants, @@ -50,62 +53,84 @@ use crate::{ }; use super::{ + clients::search_client::FlowSearchClient, flow_event_serv::FlowEventServ, flow_external_serv::FlowExternalServ, + flow_model_version_serv::FlowModelVersionServ, flow_rel_serv::{FlowRelKind, FlowRelServ}, + flow_transition_serv::FlowTransitionServ, }; pub struct FlowInstServ; impl FlowInstServ { - pub async fn start(start_req: &FlowInstStartReq, current_state_name: Option, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + pub async fn start(start_req: &FlowInstStartReq, current_state_name: Option, _funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let mut funs = flow_constants::get_tardis_inst(); + funs.begin().await?; // get model by own_paths - let flow_model_id = FlowModelServ::get_model_id_by_own_paths_and_rel_template_id(&start_req.tag, None, funs, ctx).await?; - let flow_model = FlowModelServ::get_item( - &flow_model_id, - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - with_sub_own_paths: true, - own_paths: Some("".to_string()), - ..Default::default() - }, - ..Default::default() - }, - funs, - ctx, - ) - .await?; + let flow_model = if let Some(transition_id) = &start_req.transition_id { + FlowModelServ::get_model_id_by_own_paths_and_transition_id(&start_req.tag, transition_id, &funs, ctx).await? + } else { + FlowModelServ::get_model_id_by_own_paths_and_rel_template_id(&start_req.tag, None, &funs, ctx).await? + }; let inst_id = TardisFuns::field.nanoid(); let current_state_id = if let Some(current_state_name) = ¤t_state_name { if current_state_name.is_empty() { flow_model.init_state_id.clone() } else { - FlowStateServ::match_state_id_by_name(&flow_model_id, current_state_name, funs, ctx).await? + FlowStateServ::match_state_id_by_name(&flow_model.id, current_state_name, &funs, ctx).await? } } else { flow_model.init_state_id.clone() }; + if !Self::find_details( + &FlowInstFilterReq { + rel_business_obj_id: Some(start_req.rel_business_obj_id.to_string()), + flow_version_id: Some(flow_model.current_version_id.clone()), + finish: Some(false), + ..Default::default() + }, + &funs, + ctx, + ) + .await? + .is_empty() + { + return Err(funs.err().internal_error("flow_inst_serv", "start", "The same instance exist", "500-flow-inst-exist")); + } + let main = start_req.transition_id.is_none(); let flow_inst: flow_inst::ActiveModel = flow_inst::ActiveModel { id: Set(inst_id.clone()), tag: Set(Some(flow_model.tag.clone())), - rel_flow_model_id: Set(flow_model_id.to_string()), + rel_flow_version_id: Set(flow_model.current_version_id.clone()), rel_business_obj_id: Set(start_req.rel_business_obj_id.to_string()), - current_state_id: Set(current_state_id), + current_state_id: Set(current_state_id.clone()), create_vars: Set(start_req.create_vars.as_ref().map(|vars| TardisFuns::json.obj_to_json(vars).unwrap())), create_ctx: Set(FlowOperationContext::from_ctx(ctx)), own_paths: Set(ctx.own_paths.to_string()), + main: Set(main), ..Default::default() }; funs.db().insert_one(flow_inst, ctx).await?; - + let inst = Self::get(&inst_id, &funs, ctx).await?; + Self::when_enter_state(&inst, ¤t_state_id, &flow_model.id, &funs, ctx).await?; + if !main { + FlowSearchClient::modify_business_obj_search(&start_req.rel_business_obj_id, &flow_model.tag, &funs, ctx).await?; + } Self::do_request_webhook( None, flow_model.transitions().iter().filter(|model_transition| model_transition.to_flow_state_id == flow_model.init_state_id).collect_vec().pop(), ) .await?; + funs.commit().await?; + let mut funs = flow_constants::get_tardis_inst(); + funs.begin().await?; + // 自动流转 + Self::auto_transfer(&inst, loop_check_helper::InstancesTransition::default(), &funs, ctx).await?; + funs.commit().await?; Ok(inst_id) } @@ -124,7 +149,11 @@ impl FlowInstServ { } current_ctx.own_paths = rel_business_obj.own_paths.clone().unwrap_or_default(); current_ctx.owner = rel_business_obj.owner.clone().unwrap_or_default(); - let flow_model_id = FlowModelServ::get_model_id_by_own_paths_and_rel_template_id(&batch_bind_req.tag, None, funs, ctx).await?; + let flow_model_id = if let Some(transition_id) = &batch_bind_req.transition_id { + FlowModelServ::get_model_id_by_own_paths_and_transition_id(&batch_bind_req.tag, transition_id, funs, ctx).await?.id + } else { + FlowModelServ::get_model_id_by_own_paths_and_rel_template_id(&batch_bind_req.tag, None, funs, ctx).await?.id + }; let current_state_id = FlowStateServ::match_state_id_by_name(&flow_model_id, &rel_business_obj.current_state_name.clone().unwrap_or_default(), funs, ctx).await?; let mut inst_id = Self::get_inst_ids_by_rel_business_obj_id(vec![rel_business_obj.rel_business_obj_id.clone().unwrap_or_default()], funs, ctx).await?.pop(); @@ -132,7 +161,7 @@ impl FlowInstServ { let id = TardisFuns::field.nanoid(); let flow_inst: flow_inst::ActiveModel = flow_inst::ActiveModel { id: Set(id.clone()), - rel_flow_model_id: Set(flow_model_id.to_string()), + rel_flow_version_id: Set(flow_model_id.to_string()), rel_business_obj_id: Set(rel_business_obj.rel_business_obj_id.clone().unwrap_or_default()), current_state_id: Set(current_state_id), @@ -160,7 +189,7 @@ impl FlowInstServ { query .columns([ (flow_inst::Entity, flow_inst::Column::Id), - (flow_inst::Entity, flow_inst::Column::RelFlowModelId), + (flow_inst::Entity, flow_inst::Column::RelFlowVersionId), (flow_inst::Entity, flow_inst::Column::RelBusinessObjId), (flow_inst::Entity, flow_inst::Column::CreateVars), (flow_inst::Entity, flow_inst::Column::CurrentStateId), @@ -177,23 +206,26 @@ impl FlowInstServ { .from(flow_inst::Entity) .left_join( RBUM_ITEM_TABLE.clone(), - Expr::col((RBUM_ITEM_TABLE.clone(), ID_FIELD.clone())).equals((flow_inst::Entity, flow_inst::Column::RelFlowModelId)), + Expr::col((RBUM_ITEM_TABLE.clone(), ID_FIELD.clone())).equals((flow_inst::Entity, flow_inst::Column::RelFlowVersionId)), ) .left_join( flow_model::Entity, - Expr::col((flow_model::Entity, flow_model::Column::Id)).equals((flow_inst::Entity, flow_inst::Column::RelFlowModelId)), + Expr::col((flow_model::Entity, flow_model::Column::Id)).equals((flow_inst::Entity, flow_inst::Column::RelFlowVersionId)), ); if filter.with_sub.unwrap_or(false) { query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::OwnPaths)).like(format!("{}%", ctx.own_paths))); } else { query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::OwnPaths)).eq(ctx.own_paths.as_str())); } - if let Some(flow_model_id) = &filter.flow_model_id { - query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::RelFlowModelId)).eq(flow_model_id)); + if let Some(flow_version_id) = &filter.flow_version_id { + query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::RelFlowVersionId)).eq(flow_version_id)); } if let Some(tag) = &filter.tag { query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::Tag)).eq(tag)); } + if let Some(main) = filter.main { + query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::Main)).eq(main)); + } if let Some(finish) = filter.finish { if finish { query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::FinishTime)).is_not_null()); @@ -204,6 +236,9 @@ impl FlowInstServ { if let Some(current_state_id) = &filter.current_state_id { query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::CurrentStateId)).eq(current_state_id)); } + if let Some(rel_business_obj_id) = &filter.rel_business_obj_id { + query.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::RelBusinessObjId)).eq(rel_business_obj_id)); + } Ok(()) } @@ -272,13 +307,17 @@ impl FlowInstServ { #[derive(sea_orm::FromQueryResult)] pub struct FlowInstDetailResult { pub id: String, - pub rel_flow_model_id: String, + pub tag: String, + pub rel_flow_version_id: String, pub rel_flow_model_name: String, + pub main: bool, pub current_state_id: String, pub current_state_name: Option, pub current_state_color: Option, - pub current_state_kind: Option, + pub current_state_sys_kind: Option, + pub current_state_kind: Option, + pub current_state_kind_conf: Option, pub current_state_ext: Option, pub current_vars: Option, @@ -293,6 +332,7 @@ impl FlowInstServ { pub output_message: Option, pub transitions: Option, + pub artifacts: Option, pub own_paths: String, @@ -300,15 +340,17 @@ impl FlowInstServ { } let rel_state_table = Alias::new("rel_state"); let flow_state_table = Alias::new("flow_state"); - let rel_model_table = Alias::new("rel_model"); + let rel_model_version_table = Alias::new("rel_model_version"); let rbum_rel_table = Alias::new("rbum_rel"); let mut query = Query::select(); query .columns([ (flow_inst::Entity, flow_inst::Column::Id), - (flow_inst::Entity, flow_inst::Column::RelFlowModelId), + (flow_inst::Entity, flow_inst::Column::Tag), + (flow_inst::Entity, flow_inst::Column::RelFlowVersionId), (flow_inst::Entity, flow_inst::Column::RelBusinessObjId), (flow_inst::Entity, flow_inst::Column::CurrentStateId), + (flow_inst::Entity, flow_inst::Column::Main), (flow_inst::Entity, flow_inst::Column::CurrentVars), (flow_inst::Entity, flow_inst::Column::CreateVars), (flow_inst::Entity, flow_inst::Column::CreateCtx), @@ -319,12 +361,27 @@ impl FlowInstServ { (flow_inst::Entity, flow_inst::Column::OutputMessage), (flow_inst::Entity, flow_inst::Column::Transitions), (flow_inst::Entity, flow_inst::Column::OwnPaths), + (flow_inst::Entity, flow_inst::Column::Artifacts), ]) .expr_as(Expr::col((rel_state_table.clone(), NAME_FIELD.clone())).if_null(""), Alias::new("current_state_name")) .expr_as(Expr::col((flow_state_table.clone(), Alias::new("color"))).if_null(""), Alias::new("current_state_color")) - .expr_as(Expr::col((flow_state_table.clone(), Alias::new("sys_state"))).if_null(""), Alias::new("current_state_kind")) + .expr_as( + Expr::col((flow_state_table.clone(), Alias::new("sys_state"))).if_null(FlowSysStateKind::Start), + Alias::new("current_state_sys_kind"), + ) + .expr_as( + Expr::col((flow_state_table.clone(), Alias::new("state_kind"))).if_null(FlowStateKind::Simple), + Alias::new("current_state_kind"), + ) + .expr_as( + Expr::col((flow_state_table.clone(), Alias::new("kind_conf"))).if_null(json!({})), + Alias::new("current_state_kind_conf"), + ) .expr_as(Expr::col((rbum_rel_table.clone(), Alias::new("ext"))).if_null(""), Alias::new("current_state_ext")) - .expr_as(Expr::col((rel_model_table.clone(), NAME_FIELD.clone())).if_null(""), Alias::new("rel_flow_model_name")) + .expr_as( + Expr::col((rel_model_version_table.clone(), NAME_FIELD.clone())).if_null(""), + Alias::new("rel_flow_model_name"), + ) .from(flow_inst::Entity) .join_as( JoinType::LeftJoin, @@ -344,11 +401,11 @@ impl FlowInstServ { .join_as( JoinType::LeftJoin, RBUM_ITEM_TABLE.clone(), - rel_model_table.clone(), + rel_model_version_table.clone(), Cond::all() - .add(Expr::col((rel_model_table.clone(), ID_FIELD.clone())).equals((flow_inst::Entity, flow_inst::Column::RelFlowModelId))) - .add(Expr::col((rel_model_table.clone(), REL_KIND_ID_FIELD.clone())).eq(FlowModelServ::get_rbum_kind_id().unwrap())) - .add(Expr::col((rel_model_table.clone(), REL_DOMAIN_ID_FIELD.clone())).eq(FlowModelServ::get_rbum_domain_id().unwrap())), + .add(Expr::col((rel_model_version_table.clone(), ID_FIELD.clone())).equals((flow_inst::Entity, flow_inst::Column::RelFlowVersionId))) + .add(Expr::col((rel_model_version_table.clone(), REL_KIND_ID_FIELD.clone())).eq(FlowModelVersionServ::get_rbum_kind_id().unwrap())) + .add(Expr::col((rel_model_version_table.clone(), REL_DOMAIN_ID_FIELD.clone())).eq(FlowModelVersionServ::get_rbum_domain_id().unwrap())), ) .join_as( JoinType::LeftJoin, @@ -356,7 +413,7 @@ impl FlowInstServ { rbum_rel_table.clone(), Cond::all() .add(Expr::col((rbum_rel_table.clone(), Alias::new("to_rbum_item_id"))).equals((flow_inst::Entity, flow_inst::Column::CurrentStateId))) - .add(Expr::col((rbum_rel_table.clone(), Alias::new("from_rbum_id"))).equals((flow_inst::Entity, flow_inst::Column::RelFlowModelId))) + .add(Expr::col((rbum_rel_table.clone(), Alias::new("from_rbum_id"))).equals((flow_inst::Entity, flow_inst::Column::RelFlowVersionId))) .add(Expr::col((rbum_rel_table.clone(), Alias::new("tag"))).eq("FlowModelState".to_string())), ) .and_where(Expr::col((flow_inst::Entity, flow_inst::Column::Id)).is_in(flow_inst_ids)) @@ -365,35 +422,55 @@ impl FlowInstServ { let flow_insts = funs.db().find_dtos::(&query).await?; Ok(flow_insts .into_iter() - .map(|inst| FlowInstDetailResp { - id: inst.id, - rel_flow_model_id: inst.rel_flow_model_id, - rel_flow_model_name: inst.rel_flow_model_name, - create_vars: inst.create_vars.map(|create_vars| TardisFuns::json.json_to_obj(create_vars).unwrap()), - create_ctx: inst.create_ctx, - create_time: inst.create_time, - finish_ctx: inst.finish_ctx, - finish_time: inst.finish_time, - finish_abort: inst.finish_abort, - output_message: inst.output_message, - own_paths: inst.own_paths, - transitions: inst.transitions.map(|transitions| TardisFuns::json.json_to_obj(transitions).unwrap()), - current_state_id: inst.current_state_id, - current_state_name: inst.current_state_name, - current_state_color: inst.current_state_color, - current_state_kind: inst.current_state_kind, - current_state_ext: inst.current_state_ext.map(|ext| TardisFuns::json.str_to_obj::(&ext).unwrap_or_default()), - current_vars: inst.current_vars.map(|current_vars| TardisFuns::json.json_to_obj(current_vars).unwrap()), - rel_business_obj_id: inst.rel_business_obj_id, + .map(|inst| { + let current_state_kind_conf = inst + .current_state_kind_conf + .clone() + .map(|current_state_kind_conf| TardisFuns::json.json_to_obj::(current_state_kind_conf).unwrap_or_default()); + let artifacts = inst.artifacts.clone().map(|artifacts| TardisFuns::json.json_to_obj::(artifacts).unwrap_or_default()); + FlowInstDetailResp { + id: inst.id, + rel_flow_version_id: inst.rel_flow_version_id, + rel_flow_model_name: inst.rel_flow_model_name, + tag: inst.tag, + main: inst.main, + create_vars: inst.create_vars.map(|create_vars| TardisFuns::json.json_to_obj(create_vars).unwrap()), + create_ctx: inst.create_ctx, + create_time: inst.create_time, + finish_ctx: inst.finish_ctx, + finish_time: inst.finish_time, + finish_abort: inst.finish_abort, + output_message: inst.output_message, + own_paths: inst.own_paths, + transitions: inst.transitions.map(|transitions| TardisFuns::json.json_to_obj(transitions).unwrap()), + artifacts: inst.artifacts, + current_state_id: inst.current_state_id.clone(), + current_state_name: inst.current_state_name, + current_state_color: inst.current_state_color, + current_state_sys_kind: inst.current_state_sys_kind, + current_state_kind: inst.current_state_kind.clone(), + current_state_ext: inst.current_state_ext.map(|ext| TardisFuns::json.str_to_obj::(&ext).unwrap_or_default()), + current_state_conf: Self::get_state_conf( + &inst.current_state_id, + &inst.current_state_kind.unwrap_or_default(), + current_state_kind_conf, + artifacts, + ctx, + ), + current_vars: inst.current_vars.map(|current_vars| TardisFuns::json.json_to_obj(current_vars).unwrap()), + rel_business_obj_id: inst.rel_business_obj_id, + } }) .collect_vec()) } pub async fn paginate( - flow_model_id: Option, + flow_version_id: Option, tag: Option, finish: Option, + main: Option, current_state_id: Option, + rel_business_obj_id: Option, with_sub: Option, page_number: u32, page_size: u32, @@ -404,10 +481,12 @@ impl FlowInstServ { Self::package_ext_query( &mut query, &FlowInstFilterReq { - flow_model_id, + flow_version_id, tag, finish, + main, current_state_id, + rel_business_obj_id, with_sub, }, funs, @@ -423,7 +502,7 @@ impl FlowInstServ { .into_iter() .map(|inst| FlowInstSummaryResp { id: inst.id, - rel_flow_model_id: inst.rel_flow_model_id, + rel_flow_version_id: inst.rel_flow_version_id, rel_flow_model_name: inst.rel_flow_model_name, create_ctx: TardisFuns::json.json_to_obj(inst.create_ctx).unwrap(), create_time: inst.create_time, @@ -449,10 +528,10 @@ impl FlowInstServ { if flow_insts.len() != find_req.len() { return Err(funs.err().not_found("flow_inst", "find_state_and_next_transitions", "some flow instances not found", "404-flow-inst-not-found")); } - let flow_models = FlowModelServ::find_detail_items( - &FlowModelFilterReq { + let flow_model_versions = FlowModelVersionServ::find_detail_items( + &FlowModelVersionFilterReq { basic: RbumBasicFilterReq { - ids: Some(flow_insts.iter().map(|inst| inst.rel_flow_model_id.to_string()).collect::>().into_iter().collect()), + ids: Some(flow_insts.iter().map(|inst| inst.rel_flow_version_id.to_string()).collect::>().into_iter().collect()), with_sub_own_paths: true, own_paths: Some("".to_string()), ..Default::default() @@ -466,13 +545,21 @@ impl FlowInstServ { ctx, ) .await?; + let mut rel_flow_version_map = HashMap::new(); + for flow_inst in flow_insts.iter() { + if !rel_flow_version_map.contains_key(&flow_inst.tag) { + let rel_flow_versions = FlowTransitionServ::find_rel_model_map(&flow_inst.tag, funs, ctx).await?; + rel_flow_version_map.insert(flow_inst.tag.clone(), rel_flow_versions); + } + } let state_and_next_transitions = join_all( flow_insts .iter() .map(|flow_inst| async { let req = find_req.iter().find(|req| req.flow_inst_id == flow_inst.id).unwrap(); - let flow_model = flow_models.iter().find(|model| model.id == flow_inst.rel_flow_model_id).unwrap(); - Self::do_find_next_transitions(flow_inst, flow_model, None, &req.vars, false, funs, ctx).await.unwrap() + let flow_model_version = flow_model_versions.iter().find(|version| version.id == flow_inst.rel_flow_version_id).unwrap(); + let rel_flow_versions = rel_flow_version_map.get(&flow_inst.tag).unwrap().clone(); + Self::do_find_next_transitions(flow_inst, flow_model_version, None, &req.vars, rel_flow_versions, false, funs, ctx).await.unwrap() }) .collect_vec(), ) @@ -481,15 +568,14 @@ impl FlowInstServ { } pub async fn find_next_transitions( - flow_inst_id: &str, + flow_inst: &FlowInstDetailResp, next_req: &FlowInstFindNextTransitionsReq, funs: &TardisFunsInst, ctx: &TardisContext, ) -> TardisResult> { - let flow_inst = Self::get(flow_inst_id, funs, ctx).await?; - let flow_model = FlowModelServ::get_item( - &flow_inst.rel_flow_model_id, - &FlowModelFilterReq { + let flow_model_version = FlowModelVersionServ::get_item( + &flow_inst.rel_flow_version_id, + &FlowModelVersionFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, own_paths: Some("".to_string()), @@ -501,15 +587,20 @@ impl FlowInstServ { ctx, ) .await?; - let state_and_next_transitions = Self::do_find_next_transitions(&flow_inst, &flow_model, None, &next_req.vars, false, funs, ctx).await?; + let rel_flow_versions = FlowTransitionServ::find_rel_model_map(&flow_inst.tag, funs, ctx).await?; + let state_and_next_transitions = Self::do_find_next_transitions(flow_inst, &flow_model_version, None, &next_req.vars, rel_flow_versions, false, funs, ctx).await?; Ok(state_and_next_transitions.next_flow_transitions) } - pub async fn check_transfer_vars(flow_inst_id: &str, transfer_req: &mut FlowInstTransferReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { - let flow_inst_detail: FlowInstDetailResp = Self::get(flow_inst_id, funs, ctx).await?; - let flow_model = FlowModelServ::get_item( - &flow_inst_detail.rel_flow_model_id, - &FlowModelFilterReq { + pub async fn check_transfer_vars( + flow_inst_detail: &FlowInstDetailResp, + transfer_req: &mut FlowInstTransferReq, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult<()> { + let flow_model_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, own_paths: Some("".to_string()), @@ -521,8 +612,8 @@ impl FlowInstServ { ctx, ) .await?; - let vars_collect = flow_model - .transitions() + let vars_collect = FlowTransitionServ::find_transitions(&flow_model_version.id, None, funs, ctx) + .await? .into_iter() .find(|trans| trans.id == transfer_req.flow_transition_id) .ok_or_else(|| funs.err().not_found("flow_inst", "check_transfer_vars", "illegal response", "404-flow-transition-not-found"))? @@ -540,42 +631,44 @@ impl FlowInstServ { #[async_recursion] pub async fn transfer( - flow_inst_id: &str, + flow_inst_detail: &FlowInstDetailResp, transfer_req: &FlowInstTransferReq, skip_filter: bool, callback_kind: FlowExternalCallbackOp, modified_instance_transations: loop_check_helper::InstancesTransition, ctx: &TardisContext, + funs: &TardisFunsInst, ) -> TardisResult { - let mut funs = flow_constants::get_tardis_inst(); let mut modified_instance_transations_cp = modified_instance_transations.clone(); - if !modified_instance_transations_cp.check(flow_inst_id.to_string(), transfer_req.flow_transition_id.clone()) { - let flow_inst_detail = Self::get(flow_inst_id, &funs, ctx).await?; - return Self::gen_transfer_resp(flow_inst_id, &flow_inst_detail.current_state_id, ctx, &funs).await; + if !modified_instance_transations_cp.check(flow_inst_detail.id.clone(), transfer_req.flow_transition_id.clone()) { + return Self::gen_transfer_resp(flow_inst_detail, &flow_inst_detail.current_state_id, ctx, funs).await; } - funs.begin().await?; - let result = Self::do_transfer(flow_inst_id, transfer_req, skip_filter, callback_kind, &funs, ctx).await; - funs.commit().await?; - let flow_inst_id_cp = flow_inst_id.to_string(); + + let result = Self::do_transfer(flow_inst_detail, transfer_req, skip_filter, callback_kind, funs, ctx).await; + Self::auto_transfer(flow_inst_detail, modified_instance_transations_cp.clone(), funs, ctx).await?; + let flow_inst_id_cp = flow_inst_detail.id.clone(); let flow_transition_id = transfer_req.flow_transition_id.clone(); let ctx_cp = ctx.clone(); tardis::tokio::spawn(async move { - let funs = flow_constants::get_tardis_inst(); - match FlowEventServ::do_post_change(&flow_inst_id_cp, &flow_transition_id, modified_instance_transations_cp.clone(), &ctx_cp, &funs).await { + let mut funs = flow_constants::get_tardis_inst(); + let flow_inst_cp = Self::get(&flow_inst_id_cp, &funs, &ctx_cp).await.unwrap(); + funs.begin().await.unwrap(); + match FlowEventServ::do_post_change(&flow_inst_cp, &flow_transition_id, modified_instance_transations_cp.clone(), &ctx_cp, &funs).await { Ok(_) => {} Err(e) => error!("Flow Instance {} do_post_change error:{:?}", flow_inst_id_cp, e), } - match FlowEventServ::do_front_change(&flow_inst_id_cp, modified_instance_transations_cp.clone(), &ctx_cp, &funs).await { + match FlowEventServ::do_front_change(&flow_inst_cp, modified_instance_transations_cp.clone(), &ctx_cp, &funs).await { Ok(_) => {} Err(e) => error!("Flow Instance {} do_front_change error:{:?}", flow_inst_id_cp, e), } + funs.commit().await.unwrap(); }); result } async fn do_transfer( - flow_inst_id: &str, + flow_inst_detail: &FlowInstDetailResp, transfer_req: &FlowInstTransferReq, skip_filter: bool, callback_kind: FlowExternalCallbackOp, @@ -586,10 +679,9 @@ impl FlowInstServ { own_paths: "".to_string(), ..ctx.clone() }; - let flow_inst_detail = Self::get(flow_inst_id, funs, ctx).await?; - let flow_model = FlowModelServ::get_item( - &flow_inst_detail.rel_flow_model_id, - &FlowModelFilterReq { + let flow_model_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, own_paths: Some("".to_string()), @@ -601,11 +693,13 @@ impl FlowInstServ { ctx, ) .await?; + let rel_flow_versions = FlowTransitionServ::find_rel_model_map(&flow_inst_detail.tag, funs, ctx).await?; let next_flow_transition = Self::do_find_next_transitions( - &flow_inst_detail, - &flow_model, + flow_inst_detail, + &flow_model_version, Some(transfer_req.flow_transition_id.to_string()), &transfer_req.vars, + rel_flow_versions, skip_filter, funs, ctx, @@ -615,18 +709,20 @@ impl FlowInstServ { .pop(); if next_flow_transition.is_none() { return Self::gen_transfer_resp( - flow_inst_id, - &flow_model.transitions().into_iter().find(|trans| trans.id == transfer_req.flow_transition_id).unwrap().from_flow_state_id, + flow_inst_detail, + &FlowTransitionServ::find_transitions(&flow_model_version.id, None, funs, ctx) + .await? + .into_iter() + .find(|trans| trans.id == transfer_req.flow_transition_id) + .unwrap() + .from_flow_state_id, ctx, funs, ) .await; } - let model_transition = flow_model.transitions(); - let next_transition_detail = model_transition.iter().find(|trans| trans.id == transfer_req.flow_transition_id).unwrap().to_owned(); - // if FlowModelServ::check_post_action_ring(&flow_model, funs, ctx).await? { - // return Err(funs.err().not_found("flow_inst", "transfer", "this post action exist endless loop", "500-flow-transition-endless-loop")); - // } + let version_transition = FlowTransitionServ::find_transitions(&flow_model_version.id, None, funs, ctx).await?; + let next_transition_detail = version_transition.iter().find(|trans| trans.id == transfer_req.flow_transition_id).unwrap().to_owned(); let next_flow_transition = next_flow_transition.unwrap(); let prev_flow_state = FlowStateServ::get_item( @@ -669,17 +765,17 @@ impl FlowInstServ { changed_kind: None, }); } - if !params.is_empty() { + if !params.is_empty() && flow_inst_detail.main { FlowExternalServ::do_async_modify_field( - &flow_model.tag, - &next_transition_detail, + &flow_inst_detail.tag, + Some(next_transition_detail.clone()), &flow_inst_detail.rel_business_obj_id, &flow_inst_detail.id, - FlowExternalCallbackOp::VerifyContent, - next_flow_state.name.clone(), - next_flow_state.sys_state.clone(), - prev_flow_state.name.clone(), - prev_flow_state.sys_state.clone(), + Some(FlowExternalCallbackOp::VerifyContent), + Some(next_flow_state.name.clone()), + Some(next_flow_state.sys_state.clone()), + Some(prev_flow_state.name.clone()), + Some(prev_flow_state.sys_state.clone()), params, ctx, funs, @@ -705,10 +801,11 @@ impl FlowInstServ { start_time: Utc::now(), op_ctx: FlowOperationContext::from_ctx(ctx), output_message: transfer_req.message.clone(), + target_state_id: None, }); let mut flow_inst = flow_inst::ActiveModel { - id: Set(flow_inst_id.to_string()), + id: Set(flow_inst_detail.id.clone()), current_state_id: Set(next_flow_state.id.to_string()), current_vars: Set(Some(TardisFuns::json.obj_to_json(&new_vars)?)), transitions: Set(Some(new_transitions.clone())), @@ -726,45 +823,46 @@ impl FlowInstServ { funs.db().update_one(flow_inst, ctx).await?; - // get updated instance detail - let flow_inst_detail = Self::get(flow_inst_id, funs, ctx).await?; + Self::when_leave_state(flow_inst_detail, &prev_flow_state.id, &flow_model_version.rel_model_id, funs, ctx).await?; + Self::when_enter_state(flow_inst_detail, &next_flow_state.id, &flow_model_version.rel_model_id, funs, ctx).await?; Self::do_request_webhook( - from_transition_id.and_then(|id: String| model_transition.iter().find(|model_transition| model_transition.id == id)), + from_transition_id.and_then(|id| version_transition.iter().find(|model_transition| model_transition.id == id)), Some(&next_transition_detail), ) .await?; // notify change state - FlowExternalServ::do_notify_changes( - &flow_model.tag, - &flow_inst_detail.id, - &flow_inst_detail.rel_business_obj_id, - next_flow_state.name.clone(), - next_flow_state.sys_state.clone(), - prev_flow_state.name.clone(), - prev_flow_state.sys_state.clone(), - next_transition_detail.name.clone(), - next_transition_detail.is_notify, - Some(callback_kind), - ctx, - funs, - ) - .await?; + if flow_inst_detail.main { + FlowExternalServ::do_notify_changes( + &flow_inst_detail.tag, + &flow_inst_detail.id, + &flow_inst_detail.rel_business_obj_id, + next_flow_state.name.clone(), + next_flow_state.sys_state.clone(), + prev_flow_state.name.clone(), + prev_flow_state.sys_state.clone(), + next_transition_detail.name.clone(), + next_transition_detail.is_notify, + Some(callback_kind), + ctx, + funs, + ) + .await?; + } - Self::gen_transfer_resp(flow_inst_id, &prev_flow_state.id, ctx, funs).await + Self::gen_transfer_resp(flow_inst_detail, &prev_flow_state.id, ctx, funs).await } - async fn gen_transfer_resp(flow_inst_id: &str, prev_flow_state_id: &str, ctx: &TardisContext, funs: &TardisFunsInst) -> TardisResult { + async fn gen_transfer_resp(flow_inst_detail: &FlowInstDetailResp, prev_flow_state_id: &str, ctx: &TardisContext, funs: &TardisFunsInst) -> TardisResult { let global_ctx = TardisContext { own_paths: "".to_string(), ..ctx.clone() }; - let flow_inst_detail = Self::get(flow_inst_id, funs, ctx).await?; - let flow_model = FlowModelServ::get_item( - &flow_inst_detail.rel_flow_model_id, - &FlowModelFilterReq { + let flow_model_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, own_paths: Some("".to_string()), @@ -803,14 +901,16 @@ impl FlowInstServ { &global_ctx, ) .await?; - let next_flow_transitions = Self::do_find_next_transitions(&flow_inst_detail, &flow_model, None, &None, false, funs, ctx).await?.next_flow_transitions; + let rel_flow_versions = FlowTransitionServ::find_rel_model_map(&flow_inst_detail.tag, funs, ctx).await?; + let next_flow_transitions = + Self::do_find_next_transitions(flow_inst_detail, &flow_model_version, None, &None, rel_flow_versions, false, funs, ctx).await?.next_flow_transitions; Ok(FlowInstTransferResp { prev_flow_state_id: prev_flow_state.id, prev_flow_state_name: prev_flow_state.name, prev_flow_state_color: prev_flow_state.color, new_flow_state_ext: TardisFuns::json.str_to_obj::( - &FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, &flow_inst_detail.rel_flow_model_id, None, None, funs, ctx) + &FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, &flow_inst_detail.rel_flow_version_id, None, None, funs, ctx) .await? .into_iter() .find(|rel| next_flow_state.id == rel.rel_id) @@ -821,7 +921,7 @@ impl FlowInstServ { new_flow_state_name: next_flow_state.name, new_flow_state_color: next_flow_state.color, finish_time: flow_inst_detail.finish_time, - vars: flow_inst_detail.current_vars, + vars: flow_inst_detail.current_vars.clone(), next_flow_transitions, }) } @@ -855,14 +955,15 @@ impl FlowInstServ { /// The kernel function of flow processing pub async fn do_find_next_transitions( flow_inst: &FlowInstDetailResp, - flow_model: &FlowModelDetailResp, + flow_model_version: &FlowModelVersionDetailResp, spec_flow_transition_id: Option, req_vars: &Option>, + rel_flow_versions: HashMap, skip_filter: bool, funs: &TardisFunsInst, ctx: &TardisContext, ) -> TardisResult { - let flow_model_transitions = flow_model.transitions(); + let flow_model_transitions = FlowTransitionServ::find_transitions(&flow_model_version.id, None, funs, ctx).await?; let next_transitions = flow_model_transitions .iter() @@ -1005,9 +1106,10 @@ impl FlowInstServ { finish_time: flow_inst.finish_time, current_flow_state_name: flow_inst.current_state_name.as_ref().unwrap_or(&"".to_string()).to_string(), current_flow_state_color: flow_inst.current_state_color.as_ref().unwrap_or(&"".to_string()).to_string(), - current_flow_state_kind: flow_inst.current_state_kind.as_ref().unwrap_or(&FlowSysStateKind::Start).clone(), + current_flow_state_sys_kind: flow_inst.current_state_sys_kind.as_ref().unwrap_or(&FlowSysStateKind::Start).clone(), current_flow_state_ext: flow_inst.current_state_ext.clone().unwrap_or_default(), next_flow_transitions: next_transitions, + rel_flow_versions, }; Ok(state_and_next_transitions) } @@ -1020,7 +1122,7 @@ impl FlowInstServ { .column((flow_inst::Entity, flow_inst::Column::Id)) .from(flow_inst::Entity) .and_where(Expr::col((flow_inst::Entity, flow_inst::Column::CurrentStateId)).eq(flow_state_id)) - .and_where(Expr::col((flow_inst::Entity, flow_inst::Column::RelFlowModelId)).eq(flow_model_id)) + .and_where(Expr::col((flow_inst::Entity, flow_inst::Column::RelFlowVersionId)).eq(flow_model_id)) .and_where( Expr::col((flow_inst::Entity, flow_inst::Column::FinishAbort)).ne(true).or(Expr::col((flow_inst::Entity, flow_inst::Column::FinishAbort)).is_null()), ), @@ -1035,21 +1137,20 @@ impl FlowInstServ { } pub async fn modify_current_vars( - flow_inst_id: &str, + flow_inst_detail: &FlowInstDetailResp, current_vars: &HashMap, modified_instance_transations: loop_check_helper::InstancesTransition, ctx: &TardisContext, ) -> TardisResult<()> { let mut funs = flow_constants::get_tardis_inst(); funs.begin().await?; - let flow_inst_detail = Self::get(flow_inst_id, &funs, ctx).await?; let mut new_vars: HashMap = HashMap::new(); if let Some(old_current_vars) = &flow_inst_detail.current_vars { new_vars.extend(old_current_vars.clone()); } new_vars.extend(current_vars.clone()); let flow_inst = flow_inst::ActiveModel { - id: Set(flow_inst_id.to_string()), + id: Set(flow_inst_detail.id.clone()), current_vars: Set(Some(TardisFuns::json.obj_to_json(&new_vars)?)), ..Default::default() }; @@ -1058,19 +1159,28 @@ impl FlowInstServ { let funs = flow_constants::get_tardis_inst(); - FlowEventServ::do_front_change(flow_inst_id, modified_instance_transations, ctx, &funs).await?; + FlowEventServ::do_front_change(flow_inst_detail, modified_instance_transations, ctx, &funs).await?; Ok(()) } - async fn get_new_vars(flow_inst_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { - let flow_inst_detail = Self::find_detail(vec![flow_inst_id.to_string()], funs, ctx) - .await? - .into_iter() - .next() - .ok_or_else(|| funs.err().not_found("flow_inst", "get_new_vars", "illegal response", "404-flow-inst-not-found"))?; + async fn get_new_vars(flow_inst_detail: &FlowInstDetailResp, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let flow_model_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; let flow_model = FlowModelServ::get_item( - &flow_inst_detail.rel_flow_model_id, + &flow_model_version.rel_model_id, &FlowModelFilterReq { basic: RbumBasicFilterReq { with_sub_own_paths: true, @@ -1083,7 +1193,6 @@ impl FlowInstServ { ctx, ) .await?; - Ok( FlowExternalServ::do_query_field(&flow_model.tag, vec![flow_inst_detail.rel_business_obj_id.clone()], &flow_inst_detail.own_paths, ctx, funs) .await? @@ -1094,78 +1203,18 @@ impl FlowInstServ { ) } - pub async fn trigger_front_action(funs: &TardisFunsInst) -> TardisResult<()> { - #[derive(sea_orm::FromQueryResult)] - pub struct FloTransitionsResult { - rel_flow_model_id: String, - action_by_front_changes: Value, - from_flow_state_id: String, - } - #[derive(sea_orm::FromQueryResult)] - pub struct FlowInstanceResult { - id: String, - own_paths: String, - } - - let global_ctx = TardisContext::default(); - let flow_transition_list = funs - .db() - .find_dtos::( - Query::select() - .columns([ - flow_transition::Column::RelFlowModelId, - flow_transition::Column::ActionByFrontChanges, - flow_transition::Column::FromFlowStateId, - ]) - .from(flow_transition::Entity), - ) - .await? - .into_iter() - .filter(|res| !TardisFuns::json.json_to_obj::>(res.action_by_front_changes.clone()).unwrap_or_default().is_empty()) - .collect_vec(); - // get instance - for flow_transition in flow_transition_list { - let flow_insts = funs - .db() - .find_dtos::( - Query::select() - .columns([flow_inst::Column::Id, flow_inst::Column::OwnPaths]) - .from(flow_inst::Entity) - .and_where(Expr::col(flow_inst::Column::RelFlowModelId).eq(&flow_transition.rel_flow_model_id)) - .and_where(Expr::col(flow_inst::Column::CurrentStateId).eq(&flow_transition.from_flow_state_id)), - ) - .await?; - for flow_inst in flow_insts { - let ctx = TardisContext { - own_paths: flow_inst.own_paths, - ..global_ctx.clone() - }; - let new_vars = Self::get_new_vars(&flow_inst.id, funs, &ctx).await?; - Self::modify_current_vars( - &flow_inst.id, - &TardisFuns::json.json_to_obj::>(new_vars).unwrap_or_default(), - loop_check_helper::InstancesTransition::default(), - &ctx, - ) - .await?; - } - } - - Ok(()) - } - - pub async fn find_var_by_inst_id(inst_id: &str, key: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { - let mut current_vars = Self::get(inst_id, funs, ctx).await?.current_vars; + pub async fn find_var_by_inst_id(flow_inst: &FlowInstDetailResp, key: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { + let mut current_vars = flow_inst.current_vars.clone(); if current_vars.is_none() || !current_vars.clone().unwrap_or_default().contains_key(key) { - let new_vars = Self::get_new_vars(inst_id, funs, ctx).await?; + let new_vars = Self::get_new_vars(flow_inst, funs, ctx).await?; Self::modify_current_vars( - inst_id, + flow_inst, &TardisFuns::json.json_to_obj::>(new_vars).unwrap_or_default(), loop_check_helper::InstancesTransition::default(), ctx, ) .await?; - current_vars = Self::get(inst_id, funs, ctx).await?.current_vars; + current_vars = Self::get(&flow_inst.id, funs, ctx).await?.current_vars; } Ok(current_vars.unwrap_or_default().get(key).cloned()) @@ -1174,7 +1223,7 @@ impl FlowInstServ { pub async fn batch_update_when_switch_model( rel_template_id: Option, tag: &str, - modify_model_id: &str, + modify_version_id: &str, modify_model_states: Vec, state_id: &str, funs: &TardisFunsInst, @@ -1195,17 +1244,17 @@ impl FlowInstServ { } for own_paths in own_paths_list { let mock_ctx = TardisContext { own_paths, ..ctx.clone() }; - Self::unsafe_modify_state(tag, modify_model_id, modify_model_states.clone(), state_id, funs, &mock_ctx).await?; - Self::unsafe_modify_rel_model_id(tag, modify_model_id, funs, &mock_ctx).await?; + Self::unsafe_modify_state(tag, modify_model_states.clone(), state_id, funs, &mock_ctx).await?; + Self::unsafe_modify_rel_model_id(tag, modify_version_id, funs, &mock_ctx).await?; } Ok(()) } - async fn unsafe_modify_rel_model_id(tag: &str, modify_model_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + async fn unsafe_modify_rel_model_id(tag: &str, modify_version_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { let mut update_statement = Query::update(); update_statement.table(flow_inst::Entity); - update_statement.value(flow_inst::Column::RelFlowModelId, modify_model_id); + update_statement.value(flow_inst::Column::RelFlowVersionId, modify_version_id); update_statement.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::Tag)).eq(tag)); update_statement.and_where(Expr::col((flow_inst::Entity, flow_inst::Column::OwnPaths)).eq(ctx.own_paths.as_str())); @@ -1214,14 +1263,7 @@ impl FlowInstServ { Ok(()) } - pub async fn unsafe_modify_state( - tag: &str, - modify_model_id: &str, - modify_model_states: Vec, - state_id: &str, - funs: &TardisFunsInst, - ctx: &TardisContext, - ) -> TardisResult<()> { + pub async fn unsafe_modify_state(tag: &str, modify_model_states: Vec, state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { let insts = Self::find_details( &FlowInstFilterReq { tag: Some(tag.to_string()), @@ -1251,21 +1293,6 @@ impl FlowInstServ { ..Default::default() }; funs.db().update_one(flow_inst, &mock_ctx).await.unwrap(); - let model_tag = FlowModelServ::get_item( - modify_model_id, - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - own_paths: Some("".to_string()), - with_sub_own_paths: true, - ..Default::default() - }, - ..Default::default() - }, - funs, - ctx, - ) - .await - .map(|detail| detail.tag); let next_flow_state = FlowStateServ::get_item( state_id, &FlowStateFilterReq { @@ -1282,7 +1309,7 @@ impl FlowInstServ { .unwrap(); FlowExternalServ::do_notify_changes( - model_tag.unwrap_or_default().as_str(), + &inst.tag, &inst.id, &inst.rel_business_obj_id, "".to_string(), @@ -1304,4 +1331,493 @@ impl FlowInstServ { .collect::>>()?; Ok(()) } + + pub async fn auto_transfer( + flow_inst_detail: &FlowInstDetailResp, + modified_instance_transations: loop_check_helper::InstancesTransition, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult<()> { + let model_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; + let transition_ids = Self::do_find_next_transitions(flow_inst_detail, &model_version, None, &None, HashMap::default(), false, funs, ctx) + .await? + .next_flow_transitions + .into_iter() + .map(|tran| tran.next_flow_transition_id) + .collect_vec(); + let current_var = flow_inst_detail.current_vars.clone().unwrap_or_default(); + let auto_transition = FlowTransitionServ::find_detail_items(transition_ids, None, None, funs, ctx).await?.into_iter().find(|transition| { + (transition.transfer_by_auto && transition.guard_by_other_conds().is_none()) + || (transition.transfer_by_auto + && transition.guard_by_other_conds().is_some() + && BasicQueryCondInfo::check_or_and_conds(&transition.guard_by_other_conds().unwrap(), ¤t_var).unwrap()) + }); + if let Some(auto_transition) = auto_transition { + Self::transfer( + flow_inst_detail, + &FlowInstTransferReq { + flow_transition_id: auto_transition.id, + message: None, + vars: None, + }, + false, + FlowExternalCallbackOp::Auto, + modified_instance_transations.clone(), + ctx, + funs, + ) + .await?; + } + + Ok(()) + } + + // 当进入该节点时 + async fn when_enter_state(flow_inst_detail: &FlowInstDetailResp, state_id: &str, flow_model_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let rel_transition = + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, flow_model_id, None, None, funs, ctx).await?.pop().map(|rel| rel.rel_id).unwrap_or_default(); + let state = FlowStateServ::get_item( + state_id, + &FlowStateFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; + match state.state_kind { + FlowStateKind::Start => {} + FlowStateKind::Form => { + let mut modify_req = FlowInstArtifactsModifyReq { ..Default::default() }; + let form_conf = state.kind_conf().unwrap_or_default().form.unwrap_or_default(); + let mut guard_custom_conf = form_conf.guard_custom_conf.unwrap_or_default(); + if form_conf.guard_by_creator { + guard_custom_conf.guard_by_spec_account_ids.push(ctx.owner.clone()); + } + if form_conf.guard_by_his_operators { + flow_inst_detail.transitions.as_ref().map(|transitions| { + transitions.iter().map(|transition| guard_custom_conf.guard_by_spec_account_ids.push(transition.op_ctx.owner.clone())).collect::>() + }); + } + modify_req.guard_conf = Some(guard_custom_conf); + Self::modify_inst_artifacts(flow_inst_detail, &modify_req, funs, ctx).await?; + } + FlowStateKind::Approval => { + let mut modify_req = FlowInstArtifactsModifyReq { ..Default::default() }; + let approval_conf = state.kind_conf().unwrap_or_default().approval.unwrap_or_default(); + let mut guard_custom_conf = approval_conf.guard_custom_conf.unwrap_or_default(); + if approval_conf.guard_by_creator { + guard_custom_conf.guard_by_spec_account_ids.push(ctx.owner.clone()); + } + if approval_conf.guard_by_his_operators { + flow_inst_detail.transitions.as_ref().map(|transitions| { + transitions.iter().map(|transition| guard_custom_conf.guard_by_spec_account_ids.push(transition.op_ctx.owner.clone())).collect::>() + }); + } + modify_req.guard_conf = Some(guard_custom_conf); + Self::modify_inst_artifacts(flow_inst_detail, &modify_req, funs, ctx).await?; + } + FlowStateKind::Branch => {} + FlowStateKind::Finish => match rel_transition.as_str() { + "__EDIT__" => { + let transition = FlowTransitionServ::find_transitions(&flow_inst_detail.rel_flow_version_id, None, funs, ctx).await?; + let mut state_ids = vec![]; + let mut vars_collect = HashMap::new(); + let artifacts = flow_inst_detail.artifacts(); + for tran in flow_inst_detail.transitions.clone().unwrap_or_default() { + if let Some(tran) = transition.iter().find(|version_tran| tran.id == version_tran.id) { + let current_state_id = tran.to_flow_state_id.clone(); + if !state_ids.contains(¤t_state_id) { + state_ids.push(current_state_id.clone()); + if let Some(form_state_vars) = artifacts.form_state_map.get(¤t_state_id) { + for (key, value) in form_state_vars { + *vars_collect.entry(key.clone()).or_insert(json!({})) = value.clone(); + } + } + } + } + } + let params = vars_collect + .into_iter() + .map(|(key, value)| FlowExternalParams { + var_name: Some(key), + value: Some(value), + ..Default::default() + }) + .collect_vec(); + FlowExternalServ::do_modify_field( + &flow_inst_detail.tag, + None, + &flow_inst_detail.rel_business_obj_id, + &flow_inst_detail.id, + None, + None, + None, + None, + None, + params, + ctx, + funs, + ) + .await?; + } + "__DELETE__" => { + FlowExternalServ::do_delete_rel_obj(&flow_inst_detail.tag, &flow_inst_detail.rel_business_obj_id, &flow_inst_detail.id, ctx, funs).await?; + } + _ => {} + }, + _ => {} + } + Ok(()) + } + + // 当离开该节点时 + async fn when_leave_state(flow_inst_detail: &FlowInstDetailResp, state_id: &str, _flow_model_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + // let rel_transition = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, flow_model_id, None, None, funs, ctx).await?.pop().map(|rel| rel.rel_id).unwrap_or_default(); + let state = FlowStateServ::get_item( + state_id, + &FlowStateFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; + match state.state_kind { + FlowStateKind::Start => {} + FlowStateKind::Form => { + let modify_req = FlowInstArtifactsModifyReq { + prev_non_auto_state_id: Some(state_id.to_string()), + prev_non_auto_account_id: Some(ctx.owner.clone()), + ..Default::default() + }; + Self::modify_inst_artifacts(flow_inst_detail, &modify_req, funs, ctx).await?; + } + FlowStateKind::Approval => { + let modify_req = FlowInstArtifactsModifyReq { + prev_non_auto_state_id: Some(state_id.to_string()), + prev_non_auto_account_id: Some(ctx.owner.clone()), + ..Default::default() + }; + Self::modify_inst_artifacts(flow_inst_detail, &modify_req, funs, ctx).await?; + } + FlowStateKind::Branch => {} + FlowStateKind::Finish => {} + _ => {} + } + Ok(()) + } + + // 修改实例的数据对象 + async fn modify_inst_artifacts(inst: &FlowInstDetailResp, modify_artifacts: &FlowInstArtifactsModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let mut inst_artifacts = inst.artifacts(); + if let Some(guard_conf) = &modify_artifacts.guard_conf { + inst_artifacts.guard_conf = guard_conf.clone(); + } + if let Some(add_guard_conf_account_id) = &modify_artifacts.add_guard_conf_account_id { + inst_artifacts.guard_conf.guard_by_spec_account_ids.push(add_guard_conf_account_id.clone()); + } + if let Some(delete_guard_conf_account_id) = &modify_artifacts.delete_guard_conf_account_id { + inst_artifacts.guard_conf.guard_by_spec_account_ids = + inst_artifacts.guard_conf.guard_by_spec_account_ids.into_iter().filter(|account_id| account_id != delete_guard_conf_account_id).collect_vec(); + } + if let Some((add_approval_account_id, add_approval_result)) = &modify_artifacts.add_approval_result { + let current_state_result = inst_artifacts.approval_result.entry(inst.current_state_id.clone()).or_default(); + let current_account_ids = current_state_result.entry(add_approval_result.to_string()).or_default(); + current_account_ids.push(add_approval_account_id.clone()); + } + if let Some(form_state_vars) = modify_artifacts.form_state_map.clone() { + inst_artifacts.form_state_map.insert(inst.current_state_id.clone(), form_state_vars.clone()); + } + if let Some(state_id) = &modify_artifacts.clear_form_result { + inst_artifacts.form_state_map.remove(state_id); + } + if let Some(state_id) = &modify_artifacts.clear_approval_result { + inst_artifacts.approval_result.remove(state_id); + } + if let Some(prev_non_auto_state_id) = &modify_artifacts.prev_non_auto_state_id { + inst_artifacts.prev_non_auto_state_id = Some(prev_non_auto_state_id.clone()); + } + if let Some(prev_non_auto_account_id) = &modify_artifacts.prev_non_auto_account_id { + inst_artifacts.prev_non_auto_account_id = Some(prev_non_auto_account_id.clone()); + } + let flow_inst = flow_inst::ActiveModel { + id: Set(inst.id.clone()), + artifacts: Set(Some(inst_artifacts)), + ..Default::default() + }; + funs.db().update_one(flow_inst, ctx).await?; + + Ok(()) + } + + // @TODO 需补充按钮的权限控制逻辑 + fn get_state_conf( + state_id: &str, + state_kind: &FlowStateKind, + kind_conf: Option, + artifacts: Option, + ctx: &TardisContext, + ) -> Option { + if let Some(kind_conf) = kind_conf { + match state_kind { + FlowStateKind::Form => kind_conf.form.as_ref().map(|form| { + let mut operators = HashMap::new(); + if artifacts.clone().unwrap_or_default().guard_conf.check(ctx) { + operators.insert(FlowStateOperatorKind::Submit, form.submit_btn_name.clone()); + if form.referral { + if let Some(referral_guard_custom_conf) = &form.referral_guard_custom_conf { + if referral_guard_custom_conf.check(ctx) { + operators.insert(FlowStateOperatorKind::Referral, "".to_string()); + } + } else { + operators.insert(FlowStateOperatorKind::Referral, "".to_string()); + } + } + } + FLowInstStateConf { + operators, + form_conf: Some(FLowInstStateFormConf { + form_vars_collect_conf: form.vars_collect.clone(), + }), + approval_conf: None, + } + }), + FlowStateKind::Approval => kind_conf.approval.as_ref().map(|approval| { + let mut operators = HashMap::new(); + let approval_conf = artifacts.clone().unwrap_or_default(); + if approval_conf.guard_conf.check(ctx) { + operators.insert(FlowStateOperatorKind::Pass, approval.pass_btn_name.clone()); + operators.insert(FlowStateOperatorKind::Overrule, approval.overrule_btn_name.clone()); + operators.insert(FlowStateOperatorKind::Back, approval.back_btn_name.clone()); + if let Some(referral_guard_custom_conf) = &approval.referral_guard_custom_conf { + if referral_guard_custom_conf.check(ctx) { + operators.insert(FlowStateOperatorKind::Referral, "".to_string()); + } + } else { + operators.insert(FlowStateOperatorKind::Referral, "".to_string()); + } + } + if ctx.own_paths == approval_conf.prev_non_auto_account_id.unwrap_or_default() { + operators.insert(FlowStateOperatorKind::Revoke, "".to_string()); + } + let operators = HashMap::from([ + (FlowStateOperatorKind::Referral, "".to_string()), + (FlowStateOperatorKind::Revoke, "".to_string()), + (FlowStateOperatorKind::Pass, approval.pass_btn_name.clone()), + (FlowStateOperatorKind::Overrule, approval.overrule_btn_name.clone()), + (FlowStateOperatorKind::Back, approval.back_btn_name.clone()), + ]); + FLowInstStateConf { + operators, + form_conf: None, + approval_conf: Some(FLowInstStateApprovalConf { + approval_vars_collect_conf: Some(approval.vars_collect.clone()), + form_vars_collect: artifacts.unwrap_or_default().form_state_map.get(state_id).cloned().unwrap_or_default(), + }), + } + }), + _ => None, + } + } else { + None + } + } + + pub async fn operate(inst: &FlowInstDetailResp, operate_req: &FlowInstOperateReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + match operate_req.operate { + // 转办 + FlowStateOperatorKind::Referral => { + Self::modify_inst_artifacts( + inst, + &FlowInstArtifactsModifyReq { + add_guard_conf_account_id: operate_req.operator.clone(), + delete_guard_conf_account_id: Some(ctx.owner.clone()), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + } + // 撤销 + FlowStateOperatorKind::Revoke => { + let artifacts = inst.artifacts(); + if let Some(target_state_id) = artifacts.prev_non_auto_state_id { + Self::transfer_spec_state(inst, &target_state_id, funs, ctx).await?; + } else { + Self::abort(&inst.id, &FlowInstAbortReq { message: "".to_string() }, funs, ctx).await?; + } + } + // 提交 + FlowStateOperatorKind::Submit => { + Self::modify_inst_artifacts( + inst, + &FlowInstArtifactsModifyReq { + form_state_map: Some(operate_req.vars.clone().unwrap_or_default()), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + if let Some(next_transition) = FlowInstServ::find_next_transitions(inst, &FlowInstFindNextTransitionsReq { vars: None }, funs, ctx).await?.pop() { + Self::transfer( + inst, + &FlowInstTransferReq { + flow_transition_id: next_transition.next_flow_transition_id, + message: None, + vars: None, + }, + false, + FlowExternalCallbackOp::Default, + loop_check_helper::InstancesTransition::default(), + ctx, + funs, + ) + .await?; + } + } + // 退回 + FlowStateOperatorKind::Back => { + let artifacts = inst.artifacts(); + if let Some(target_state_id) = artifacts.prev_non_auto_state_id { + Self::transfer_spec_state(inst, &target_state_id, funs, ctx).await?; + } else { + Self::abort(&inst.id, &FlowInstAbortReq { message: "".to_string() }, funs, ctx).await?; + } + } + // 通过 + FlowStateOperatorKind::Pass => { + Self::modify_inst_artifacts( + inst, + &FlowInstArtifactsModifyReq { + add_approval_result: Some((ctx.owner.clone(), FlowApprovalResultKind::Pass)), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + if let Some(next_transition) = FlowInstServ::find_next_transitions(inst, &FlowInstFindNextTransitionsReq { vars: None }, funs, ctx).await?.pop() { + Self::transfer( + inst, + &FlowInstTransferReq { + flow_transition_id: next_transition.next_flow_transition_id, + message: None, + vars: None, + }, + false, + FlowExternalCallbackOp::Default, + loop_check_helper::InstancesTransition::default(), + ctx, + funs, + ) + .await?; + } + } + // 拒绝 + FlowStateOperatorKind::Overrule => { + Self::modify_inst_artifacts( + inst, + &FlowInstArtifactsModifyReq { + add_approval_result: Some((ctx.owner.clone(), FlowApprovalResultKind::Overrule)), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + Self::abort(&inst.id, &FlowInstAbortReq { message: "".to_string() }, funs, ctx).await?; + } + } + Ok(()) + } + + async fn transfer_spec_state(flow_inst_detail: &FlowInstDetailResp, target_state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let flow_model_version = FlowModelVersionServ::get_item( + &flow_inst_detail.rel_flow_version_id, + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; + if FlowRelServ::find_to_simple_rels(&FlowRelKind::FlowModelState, target_state_id, None, None, funs, ctx) + .await? + .into_iter() + .filter(|rel| rel.rel_id == flow_inst_detail.rel_flow_version_id) + .collect_vec() + .is_empty() + { + return Err(funs.err().internal_error("flow_inst_serv", "transfer_spec_state", "flow state is not found", "404-flow-state-not-found")); + } + + let mut new_transitions = Vec::new(); + if let Some(transitions) = &flow_inst_detail.transitions { + new_transitions.extend(transitions.clone()); + } + new_transitions.push(FlowInstTransitionInfo { + id: "".to_string(), + start_time: Utc::now(), + op_ctx: FlowOperationContext::from_ctx(ctx), + output_message: None, + target_state_id: Some(target_state_id.to_string()), + }); + + let flow_inst = flow_inst::ActiveModel { + id: Set(flow_inst_detail.id.clone()), + current_state_id: Set(target_state_id.to_string()), + transitions: Set(Some(new_transitions.clone())), + ..Default::default() + }; + + funs.db().update_one(flow_inst, ctx).await?; + // 删除目标节点的旧记录 + Self::modify_inst_artifacts( + flow_inst_detail, + &FlowInstArtifactsModifyReq { + clear_approval_result: Some(target_state_id.to_string()), + clear_form_result: Some(target_state_id.to_string()), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + + Self::when_leave_state(flow_inst_detail, &flow_inst_detail.current_state_id, &flow_model_version.rel_model_id, funs, ctx).await?; + Self::when_enter_state(flow_inst_detail, target_state_id, &flow_model_version.rel_model_id, funs, ctx).await?; + + Ok(()) + } } diff --git a/backend/middlewares/flow/src/serv/flow_model_serv.rs b/backend/middlewares/flow/src/serv/flow_model_serv.rs index 095596ee3..97a5fbeed 100644 --- a/backend/middlewares/flow/src/serv/flow_model_serv.rs +++ b/backend/middlewares/flow/src/serv/flow_model_serv.rs @@ -4,22 +4,17 @@ use bios_basic::rbum::{ dto::{ rbum_filer_dto::{RbumBasicFilterReq, RbumItemRelFilterReq}, rbum_item_dto::{RbumItemKernelAddReq, RbumItemKernelModifyReq}, - rbum_rel_dto::RbumRelModifyReq, }, helper::rbum_scope_helper, rbum_enumeration::{RbumRelFromKind, RbumScopeLevelKind}, - serv::{ - rbum_crud_serv::{ID_FIELD, NAME_FIELD, REL_DOMAIN_ID_FIELD, REL_KIND_ID_FIELD}, - rbum_item_serv::{RbumItemCrudOperation, RBUM_ITEM_TABLE}, - }, + serv::rbum_item_serv::RbumItemCrudOperation, }; use itertools::Itertools; use tardis::{ basic::{dto::TardisContext, result::TardisResult}, - chrono::Utc, db::sea_orm::{ sea_query::{Alias, Cond, Expr, Query, SelectStatement}, - EntityName, EntityTrait, JoinType, Order, QueryFilter, Set, + EntityName, Set, }, futures::future::join_all, serde_json::json, @@ -29,20 +24,23 @@ use tardis::{ }; use crate::{ - domain::{flow_model, flow_state, flow_transition}, + domain::{flow_model, flow_transition}, dto::{ flow_model_dto::{ - FlowModelAddReq, FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelBindStateReq, FlowModelDetailResp, FlowModelFilterReq, FlowModelFindRelStateResp, - FlowModelModifyReq, FlowModelSummaryResp, + FlowModelAddReq, FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelBindNewStateReq, FlowModelBindStateReq, FlowModelDetailResp, FlowModelFilterReq, + FlowModelFindRelStateResp, FlowModelKind, FlowModelModifyReq, FlowModelRelTransitionExt, FlowModelStatus, FlowModelSummaryResp, + }, + flow_model_version_dto::{ + FlowModelVersionAddReq, FlowModelVersionBindState, FlowModelVersionDetailResp, FlowModelVersionFilterReq, FlowModelVersionModifyReq, FlowModelVersionModifyState, + FlowModelVesionState, }, - flow_state_dto::{FlowStateAggResp, FlowStateDetailResp, FlowStateFilterReq, FlowStateRelModelExt, FlowStateRelModelModifyReq}, + flow_state_dto::{FLowStateIdAndName, FlowStateAddReq, FlowStateAggResp, FlowStateKind, FlowStateRelModelExt, FlowSysStateKind}, flow_transition_dto::{ - FlowTransitionActionChangeKind, FlowTransitionAddReq, FlowTransitionDetailResp, FlowTransitionInitInfo, FlowTransitionModifyReq, FlowTransitionPostActionInfo, + FlowTransitionAddReq, FlowTransitionDetailResp, FlowTransitionInitInfo, FlowTransitionModifyReq, FlowTransitionPostActionInfo, FlowTransitionSortStatesReq, }, }, flow_config::FlowBasicInfoManager, flow_constants, - serv::flow_state_serv::FlowStateServ, }; use async_trait::async_trait; @@ -51,8 +49,9 @@ use super::{ flow_log_client::{FlowLogClient, LogParamContent, LogParamTag}, search_client::FlowSearchClient, }, - flow_inst_serv::FlowInstServ, + flow_model_version_serv::FlowModelVersionServ, flow_rel_serv::{FlowRelKind, FlowRelServ}, + flow_transition_serv::FlowTransitionServ, }; pub struct FlowModelServ; @@ -83,27 +82,144 @@ impl RbumItemCrudOperation TardisResult { Ok(flow_model::ActiveModel { id: Set(id.to_string()), - icon: Set(add_req.icon.as_ref().unwrap_or(&"".to_string()).to_string()), - info: Set(add_req.info.as_ref().unwrap_or(&"".to_string()).to_string()), - init_state_id: Set(add_req.init_state_id.to_string()), + icon: Set(add_req.icon.clone().unwrap_or_default()), + info: Set(add_req.info.clone().unwrap_or_default()), + current_version_id: Set(add_req.current_version_id.clone().unwrap_or_default()), + kind: Set(add_req.kind.clone()), + status: Set(add_req.status.clone()), tag: Set(add_req.tag.clone()), - rel_model_id: Set(add_req.rel_model_id.as_ref().unwrap_or(&"".to_string()).to_string()), + rel_model_id: Set(add_req.rel_model_id.clone().unwrap_or_default()), template: Set(add_req.template), + main: Set(add_req.main), ..Default::default() }) } - async fn before_add_item(_add_req: &mut FlowModelAddReq, _funs: &TardisFunsInst, _ctx: &TardisContext) -> TardisResult<()> { + async fn before_add_item(add_req: &mut FlowModelAddReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + if let Some(rel_transition_ids) = &add_req.rel_transition_ids { + if Self::get_model_id_by_own_paths_and_transition_id( + &add_req.tag.clone().unwrap_or_default(), + &rel_transition_ids.first().cloned().unwrap_or_default(), + funs, + ctx, + ) + .await + .is_ok() + { + return Err(funs.err().not_found(&Self::get_obj_name(), "before_add_item", "The model is not repeatable", "400-flow-model-duplicate")); + } + } + Ok(()) } async fn after_add_item(flow_model_id: &str, add_req: &mut FlowModelAddReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { - if let Some(states) = &add_req.states { - join_all(states.iter().map(|state| async { Self::bind_state(flow_model_id, state, funs, ctx).await }).collect_vec()) - .await - .into_iter() - .collect::>>()?; + if let Some(rel_transition_ids) = add_req.rel_transition_ids.clone() { + let transitions = FlowTransitionServ::find_detail_items(rel_transition_ids.clone(), None, None, funs, ctx).await?; + for rel_transition_id in rel_transition_ids { + let mut ext = FlowModelRelTransitionExt { + id: rel_transition_id.clone(), + ..Default::default() + }; + if let Some(transition) = transitions.iter().find(|tran| tran.id == rel_transition_id) { + ext.name = transition.name.clone(); + ext.from_flow_state_name = transition.from_flow_state_name.clone(); + } + FlowRelServ::add_simple_rel( + &FlowRelKind::FlowModelTransition, + flow_model_id, + &rel_transition_id, + None, + None, + true, + true, + Some(json!(ext).to_string()), + funs, + ctx, + ) + .await?; + } + } + + let mut add_version = if let Some(mut add_version) = add_req.add_version.clone() { + add_version.rel_model_id = Some(flow_model_id.to_string()); + add_version + } else if add_req.main { + FlowModelVersionAddReq { + name: add_req.name.clone(), + rel_model_id: Some(flow_model_id.to_string()), + bind_states: None, + status: FlowModelVesionState::Enabled, + scope_level: add_req.scope_level.clone(), + disabled: add_req.disabled, + } + } else { + let start_state_id = TardisFuns::field.nanoid(); + let finish_state_id = TardisFuns::field.nanoid(); + FlowModelVersionAddReq { + name: add_req.name.clone(), + rel_model_id: Some(flow_model_id.to_string()), + // 初始化时增加开始结束两个节点 + bind_states: Some(vec![ + FlowModelVersionBindState { + bind_new_state: Some(FlowModelBindNewStateReq { + new_state: FlowStateAddReq { + id: Some(start_state_id.clone().into()), + name: Some("开始".into()), + sys_state: FlowSysStateKind::Start, + state_kind: Some(FlowStateKind::Start), + tags: Some(vec![add_req.tag.clone().unwrap_or_default()]), + ..Default::default() + }, + ext: FlowStateRelModelExt { sort: 0, show_btns: None }, + }), + add_transitions: Some(vec![FlowTransitionAddReq { + name: Some("开始".into()), + from_flow_state_id: start_state_id.clone(), + to_flow_state_id: finish_state_id.clone(), + transfer_by_auto: Some(true), + ..Default::default() + }]), + is_init: true, + ..Default::default() + }, + FlowModelVersionBindState { + bind_new_state: Some(FlowModelBindNewStateReq { + new_state: FlowStateAddReq { + id: Some(finish_state_id.clone().into()), + name: Some("结束".into()), + sys_state: FlowSysStateKind::Finish, + state_kind: Some(FlowStateKind::Finish), + tags: Some(vec![add_req.tag.clone().unwrap_or_default()]), + ..Default::default() + }, + ext: FlowStateRelModelExt { sort: 1, show_btns: None }, + }), + is_init: false, + ..Default::default() + }, + ]), + status: FlowModelVesionState::Editing, + scope_level: add_req.scope_level.clone(), + disabled: add_req.disabled, + } + }; + + let version_id = FlowModelVersionServ::add_item(&mut add_version, funs, ctx).await?; + if add_version.status == FlowModelVesionState::Enabled { + FlowModelVersionServ::enable_version(&version_id, funs, ctx).await?; + Self::modify_item( + flow_model_id, + &mut FlowModelModifyReq { + current_version_id: Some(version_id), + ..Default::default() + }, + funs, + ctx, + ) + .await?; } + if let Some(rel_template_ids) = &add_req.rel_template_ids { join_all( rel_template_ids @@ -117,21 +233,6 @@ impl RbumItemCrudOperation>>()?; } - if let Some(transitions) = &add_req.transitions { - if !transitions.is_empty() { - Self::add_transitions(flow_model_id, transitions, funs, ctx).await?; - // check transition post action endless loop - let model_desp = Self::get_item(flow_model_id, &FlowModelFilterReq::default(), funs, ctx).await?; - if Self::check_post_action_ring(&model_desp, funs, ctx).await? { - return Err(funs.err().not_found( - "flow_model_Serv", - "after_add_item", - "this post action exist endless loop", - "500-flow-transition-endless-loop", - )); - } - } - } if add_req.template && add_req.rel_model_id.clone().map_or(true, |id| id.is_empty()) { FlowSearchClient::async_add_or_modify_model_search(flow_model_id, Box::new(false), funs, ctx).await?; FlowLogClient::add_ctx_task( @@ -174,7 +275,13 @@ impl RbumItemCrudOperation TardisResult> { - if modify_req.icon.is_none() && modify_req.info.is_none() && modify_req.init_state_id.is_none() && modify_req.tag.is_none() && modify_req.rel_model_id.is_none() { + if modify_req.icon.is_none() + && modify_req.info.is_none() + && modify_req.tag.is_none() + && modify_req.rel_model_id.is_none() + && modify_req.current_version_id.is_none() + && modify_req.status.is_none() + { return Ok(None); } let mut flow_model = flow_model::ActiveModel { @@ -187,19 +294,22 @@ impl RbumItemCrudOperation TardisResult<()> { + async fn before_modify_item(flow_model_id: &str, modify_req: &mut FlowModelModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { let current_model = Self::get_item( flow_model_id, &FlowModelFilterReq { @@ -214,6 +324,9 @@ impl RbumItemCrudOperation TardisResult<()> { let model_detail = Self::get_item(flow_model_id, &FlowModelFilterReq::default(), funs, ctx).await?; - if let Some(bind_states) = &modify_req.bind_states { - for bind_state in bind_states { - Self::bind_state(flow_model_id, bind_state, funs, ctx).await?; - } - } - if let Some(unbind_states) = &modify_req.unbind_states { - for unbind_state in unbind_states { - Self::unbind_state(flow_model_id, unbind_state, funs, ctx).await?; - } - } - if let Some(modify_states) = &modify_req.modify_states { - for modify_state in modify_states { - Self::modify_rel_state_ext(flow_model_id, modify_state, funs, ctx).await?; - } - } - if let Some(add_transitions) = &modify_req.add_transitions { - Self::add_transitions(flow_model_id, add_transitions, funs, ctx).await?; - } - if let Some(modify_transitions) = &modify_req.modify_transitions { - Self::modify_transitions(flow_model_id, modify_transitions, &model_detail, funs, ctx).await?; - } - if let Some(delete_transitions) = &modify_req.delete_transitions { - Self::delete_transitions(flow_model_id, delete_transitions, funs, ctx).await?; - } if let Some(rel_template_ids) = &modify_req.rel_template_ids { join_all( FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, flow_model_id, None, None, funs, ctx) @@ -274,34 +363,22 @@ impl RbumItemCrudOperation>>()?; } - if modify_req.add_transitions.is_some() || modify_req.modify_transitions.is_some() { - // check transition post action endless loop - if Self::check_post_action_ring(&model_detail, funs, ctx).await? { - return Err(funs.err().not_found( - "flow_model_Serv", - "after_modify_item", - "this post action exist endless loop", - "500-flow-transition-endless-loop", - )); - } - } - let model = Self::get_item_detail_aggs(flow_model_id, false, funs, ctx).await?; - if model.template && model.rel_model_id.is_empty() { + if model_detail.template && model_detail.rel_model_id.is_empty() { FlowSearchClient::async_add_or_modify_model_search(flow_model_id, Box::new(true), funs, ctx).await?; FlowLogClient::add_ctx_task( LogParamTag::DynamicLog, Some(flow_model_id.to_string()), LogParamContent { subject: "工作流模板".to_string(), - name: model.name.clone(), + name: model_detail.name.clone(), sub_kind: "flow_template".to_string(), }, Some(json!({ - "name": model.name.to_string(), - "info": model.info.clone(), - "rel_template_ids":model.rel_template_ids.clone(), - "scope_level": model.scope_level.clone(), - "tag": model.tag.clone(), + "name": model_detail.name.to_string(), + "info": model_detail.info.clone(), + "rel_template_ids":model_detail.rel_template_ids.clone(), + "scope_level": model_detail.scope_level.clone(), + "tag": model_detail.tag.clone(), })), Some("dynamic_log_tenant_config".to_string()), Some("编辑".to_string()), @@ -313,8 +390,7 @@ impl RbumItemCrudOperation>>()?; join_all( - FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_model_id, None, None, funs, ctx) + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, flow_model_id, None, None, funs, ctx) .await? .into_iter() - .map(|rel| async move { FlowRelServ::delete_simple_rel(&FlowRelKind::FlowModelState, flow_model_id, &rel.rel_id, funs, ctx).await }) + .map(|rel| async move { FlowRelServ::delete_simple_rel(&FlowRelKind::FlowModelTransition, flow_model_id, &rel.rel_id, funs, ctx).await }) .collect_vec(), ) .await @@ -465,16 +512,21 @@ impl RbumItemCrudOperation TardisResult<()> { + async fn package_ext_query(query: &mut SelectStatement, _: bool, filter: &FlowModelFilterReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { query .column((flow_model::Entity, flow_model::Column::Icon)) .column((flow_model::Entity, flow_model::Column::Info)) - .column((flow_model::Entity, flow_model::Column::InitStateId)) .column((flow_model::Entity, flow_model::Column::Template)) + .column((flow_model::Entity, flow_model::Column::Main)) .column((flow_model::Entity, flow_model::Column::RelModelId)) .column((flow_model::Entity, flow_model::Column::Tag)) + .column((flow_model::Entity, flow_model::Column::Kind)) + .column((flow_model::Entity, flow_model::Column::Status)) + .column((flow_model::Entity, flow_model::Column::CurrentVersionId)) + .expr_as(Expr::val("".to_string()), Alias::new("init_state_id")) .expr_as(Expr::val(json! {()}), Alias::new("transitions")) .expr_as(Expr::val(json! {()}), Alias::new("states")) + .expr_as(Expr::val(json! {()}), Alias::new("rel_transition")) .expr_as(Expr::val(vec!["".to_string()]), Alias::new("rel_template_ids")); if let Some(tags) = filter.tags.clone() { query.and_where(Expr::col(flow_model::Column::Tag).is_in(tags)); @@ -482,42 +534,102 @@ impl RbumItemCrudOperation TardisResult { let mut flow_model = Self::do_get_item(flow_model_id, filter, funs, ctx).await?; - let flow_transitions = Self::find_transitions(flow_model_id, filter.specified_state_ids.as_deref(), funs, ctx).await?; - flow_model.transitions = Some(TardisFuns::json.obj_to_json(&flow_transitions)?); - let flow_states = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_model_id, None, None, funs, ctx) - .await? - .into_iter() - .sorted_by_key(|rel| TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default().sort) - .map(|rel| FlowStateAggResp { - id: rel.rel_id.clone(), - name: rel.rel_name.clone(), - is_init: flow_model.init_state_id == rel.rel_id, - ext: TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default(), - transitions: flow_transitions.clone().into_iter().filter(|tran| tran.from_flow_state_id == rel.rel_id).collect_vec(), - }) - .collect_vec(); - flow_model.states = Some(TardisFuns::json.obj_to_json(&flow_states)?); + if !flow_model.current_version_id.is_empty() { + let flow_transitions = FlowTransitionServ::find_transitions(&flow_model.current_version_id, filter.specified_state_ids.clone(), funs, ctx).await?; + flow_model.transitions = Some(TardisFuns::json.obj_to_json(&flow_transitions)?); + + let current_version = FlowModelVersionServ::get_item( + &flow_model.current_version_id, + &FlowModelVersionFilterReq { + basic: filter.basic.clone(), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + flow_model.states = Some(current_version.states.unwrap_or_default()); - let rel_template_ids = - FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, flow_model_id, None, None, funs, ctx).await?.into_iter().map(|rel| rel.rel_id).collect_vec(); - flow_model.rel_template_ids = rel_template_ids; + let rel_template_ids = + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, flow_model_id, None, None, funs, ctx).await?.into_iter().map(|rel| rel.rel_id).collect_vec(); + flow_model.rel_template_ids = rel_template_ids; + + flow_model.init_state_id = current_version.init_state_id; + } + let rel_transition = + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, flow_model_id, None, None, funs, ctx).await?.into_iter().map(|rel| rel.ext).collect_vec().pop(); + flow_model.rel_transition = rel_transition.map(|rel_transition| TardisFuns::json.obj_to_json(&rel_transition).unwrap_or_default()); Ok(flow_model) } + async fn find_items( + filter: &FlowModelFilterReq, + desc_sort_by_create: Option, + desc_sort_by_update: Option, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult> { + let mut res = Self::do_find_items(filter, desc_sort_by_create, desc_sort_by_update, funs, ctx).await?; + for item in res.iter_mut() { + let version = FlowModelVersionServ::get_item( + &item.current_version_id, + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + ids: None, + ..filter.basic.clone() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; + item.init_state_id = version.init_state_id; + + let rel_transition = + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, &item.id, None, None, funs, ctx).await?.into_iter().map(|rel| rel.ext).collect_vec().pop(); + item.rel_transition = rel_transition.map(|rel_transition| TardisFuns::json.obj_to_json(&rel_transition).unwrap_or_default()); + + let states = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, &item.current_version_id, None, None, funs, ctx) + .await? + .into_iter() + .map(|rel| FLowStateIdAndName { + id: rel.rel_id, + name: rel.rel_name, + }) + .collect_vec(); + item.states = TardisFuns::json.obj_to_json(&states).unwrap_or_default(); + } + Ok(res) + } + async fn paginate_detail_items( filter: &FlowModelFilterReq, page_number: u32, @@ -529,8 +641,17 @@ impl RbumItemCrudOperation TardisResult> { let mut flow_models = Self::do_paginate_detail_items(filter, page_number, page_size, desc_sort_by_create, desc_sort_by_update, funs, ctx).await?; for flow_model in &mut flow_models.records { - let flow_transitions = Self::find_transitions(&flow_model.id, filter.specified_state_ids.as_deref(), funs, ctx).await?; + let flow_transitions = FlowTransitionServ::find_transitions(&flow_model.current_version_id, filter.specified_state_ids.clone(), funs, ctx).await?; flow_model.transitions = Some(TardisFuns::json.obj_to_json(&flow_transitions)?); + let rel_transition = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, &flow_model.id, None, None, funs, ctx) + .await? + .into_iter() + .map(|rel| rel.ext) + .collect_vec() + .pop(); + flow_model.rel_transition = rel_transition.map(|rel_transition| TardisFuns::json.str_to_json(&rel_transition).unwrap_or_default()); + flow_model.rel_template_ids = + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, &flow_model.id, None, None, funs, ctx).await?.into_iter().map(|rel| rel.rel_id).collect_vec(); } Ok(flow_models) } @@ -544,9 +665,40 @@ impl RbumItemCrudOperation TardisResult> { let mut flow_models = Self::do_find_detail_items(filter, desc_sort_by_create, desc_sort_by_update, funs, ctx).await?; for flow_model in &mut flow_models { - let flow_transitions = Self::find_transitions(&flow_model.id, filter.specified_state_ids.as_deref(), funs, ctx).await?; - flow_model.transitions = Some(TardisFuns::json.obj_to_json(&flow_transitions)?); + if !flow_model.current_version_id.is_empty() { + let flow_transitions = FlowTransitionServ::find_transitions(&flow_model.current_version_id, filter.specified_state_ids.clone(), funs, ctx).await?; + flow_model.transitions = Some(TardisFuns::json.obj_to_json(&flow_transitions)?); + + let current_version = FlowModelVersionServ::get_item( + &flow_model.current_version_id, + &FlowModelVersionFilterReq { + basic: filter.basic.clone(), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + flow_model.states = Some(current_version.states.unwrap_or_default()); + + let rel_template_ids = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, &flow_model.id, None, None, funs, ctx) + .await? + .into_iter() + .map(|rel| rel.rel_id) + .collect_vec(); + flow_model.rel_template_ids = rel_template_ids; + + flow_model.init_state_id = current_version.init_state_id; + } + let rel_transition = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, &flow_model.id, None, None, funs, ctx) + .await? + .into_iter() + .map(|rel| rel.ext) + .collect_vec() + .pop(); + flow_model.rel_transition = rel_transition.map(|rel_transition| TardisFuns::json.str_to_json(&rel_transition).unwrap_or_default()); } + Ok(flow_models) } } @@ -561,33 +713,49 @@ impl FlowModelServ { funs: &TardisFunsInst, ctx: &TardisContext, ) -> TardisResult { - let mut bind_states = vec![]; - // states - for (i, state_id) in state_ids.into_iter().enumerate() { - bind_states.push(FlowModelBindStateReq { - state_id, - ext: FlowStateRelModelExt { sort: i as i64, show_btns: None }, - }); - } // transitions let mut add_transitions = vec![]; for transition in transitions { add_transitions.push(FlowTransitionAddReq::try_from(transition)?); } + let mut bind_states = vec![]; + // states FlowModelVersionBindState + for (i, state_id) in state_ids.into_iter().enumerate() { + bind_states.push(FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: state_id.clone(), + ext: FlowStateRelModelExt { sort: i as i64, show_btns: None }, + }), + add_transitions: Some(add_transitions.clone().into_iter().filter(|tran| tran.from_flow_state_id == state_id).collect_vec()), + is_init: state_id == init_state_id, + ..Default::default() + }); + } + // add model let model_id = Self::add_item( &mut FlowModelAddReq { name: model_name.into(), - init_state_id: init_state_id.clone(), + kind: FlowModelKind::AsTemplateAndAsModel, + status: FlowModelStatus::Enabled, + add_version: Some(FlowModelVersionAddReq { + name: model_name.into(), + rel_model_id: None, + bind_states: Some(bind_states), + status: FlowModelVesionState::Enabled, + scope_level: Some(RbumScopeLevelKind::Root), + disabled: None, + }), rel_template_ids: None, + rel_transition_ids: None, + current_version_id: None, icon: None, info: None, - transitions: Some(add_transitions), - states: Some(bind_states), tag: Some(tag.to_string()), scope_level: Some(RbumScopeLevelKind::Root), disabled: None, template: true, + main: true, rel_model_id: None, }, funs, @@ -598,320 +766,6 @@ impl FlowModelServ { Ok(model_id) } - pub async fn add_transitions(flow_model_id: &str, add_req: &[FlowTransitionAddReq], funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { - let flow_state_ids = - FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_model_id, None, None, funs, ctx).await?.iter().map(|rel| rel.rel_id.clone()).collect::>(); - if add_req.iter().any(|req| !flow_state_ids.contains(&req.from_flow_state_id) || !flow_state_ids.contains(&req.to_flow_state_id)) { - return Err(funs.err().not_found( - &Self::get_obj_name(), - "add_transitions", - "the states to be added is not legal", - "404-flow-state-add-not-legal", - )); - } - let flow_transitions = add_req - .iter() - .map(|req| flow_transition::ActiveModel { - id: Set(TardisFuns::field.nanoid()), - name: Set(req.name.as_ref().map(|name| name.to_string()).unwrap_or("".to_string())), - - from_flow_state_id: Set(req.from_flow_state_id.to_string()), - to_flow_state_id: Set(req.to_flow_state_id.to_string()), - - transfer_by_auto: Set(req.transfer_by_auto.unwrap_or(false)), - transfer_by_timer: Set(req.transfer_by_timer.as_ref().unwrap_or(&"".to_string()).to_string()), - - guard_by_creator: Set(req.guard_by_creator.unwrap_or(false)), - guard_by_his_operators: Set(req.guard_by_his_operators.unwrap_or(false)), - guard_by_assigned: Set(req.guard_by_assigned.unwrap_or(false)), - guard_by_spec_account_ids: Set(req.guard_by_spec_account_ids.as_ref().unwrap_or(&vec![]).clone()), - guard_by_spec_role_ids: Set(req.guard_by_spec_role_ids.as_ref().unwrap_or(&vec![]).clone()), - guard_by_spec_org_ids: Set(req.guard_by_spec_org_ids.as_ref().unwrap_or(&vec![]).clone()), - guard_by_other_conds: Set(req.guard_by_other_conds.as_ref().map(|conds| TardisFuns::json.obj_to_json(conds).unwrap()).unwrap_or(json!([]))), - - vars_collect: Set(req.vars_collect.clone().unwrap_or_default()), - double_check: Set(req.double_check.clone().unwrap_or_default()), - is_notify: Set(req.is_notify.unwrap_or(true)), - - action_by_pre_callback: Set(req.action_by_pre_callback.as_ref().unwrap_or(&"".to_string()).to_string()), - action_by_post_callback: Set(req.action_by_post_callback.as_ref().unwrap_or(&"".to_string()).to_string()), - action_by_post_changes: Set(req.action_by_post_changes.clone().unwrap_or_default()), - action_by_front_changes: Set(req.action_by_front_changes.clone().unwrap_or_default()), - - rel_flow_model_id: Set(flow_model_id.to_string()), - sort: Set(req.sort.unwrap_or(0)), - ..Default::default() - }) - .collect_vec(); - funs.db().insert_many(flow_transitions, ctx).await - } - - pub async fn modify_transitions( - flow_model_id: &str, - modify_req: &[FlowTransitionModifyReq], - model_detail: &FlowModelDetailResp, - funs: &TardisFunsInst, - ctx: &TardisContext, - ) -> TardisResult<()> { - let flow_state_ids = modify_req - .iter() - .filter(|req| req.from_flow_state_id.is_some()) - .map(|req| req.from_flow_state_id.as_ref().unwrap().to_string()) - .chain(modify_req.iter().filter(|req| req.to_flow_state_id.is_some()).map(|req| req.to_flow_state_id.as_ref().unwrap().to_string())) - .unique() - .collect_vec(); - if modify_req.iter().any(|req| { - if let Some(from_flow_state_id) = &req.from_flow_state_id { - if !flow_state_ids.contains(from_flow_state_id) { - return true; - } - } - if let Some(to_flow_state_id) = &req.to_flow_state_id { - if !flow_state_ids.contains(to_flow_state_id) { - return true; - } - } - false - }) { - return Err(funs.err().not_found( - &Self::get_obj_name(), - "modify_transitions", - "the states to be added is not legal", - "404-flow-state-add-not-legal", - )); - } - - let flow_transition_ids = modify_req.iter().map(|req: &FlowTransitionModifyReq| req.id.to_string()).collect_vec(); - let flow_transition_ids_lens = flow_transition_ids.len(); - if funs - .db() - .count( - Query::select() - .column((flow_transition::Entity, flow_transition::Column::Id)) - .from(flow_transition::Entity) - .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::RelFlowModelId)).eq(flow_model_id.to_string())) - .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::Id)).is_in(flow_transition_ids)), - ) - .await? as usize - != flow_transition_ids_lens - { - return Err(funs.err().not_found( - &Self::get_obj_name(), - "modify_transitions", - "the transition of related models not legal", - "404-flow-transition-rel-model-not-legal", - )); - } - let model_transitions = model_detail.transitions(); - for req in modify_req { - let transiton = model_transitions.iter().find(|trans| trans.id == req.id.to_string()); - if transiton.is_none() { - continue; - } - - let mut flow_transition = flow_transition::ActiveModel { - id: Set(req.id.to_string()), - ..Default::default() - }; - if let Some(name) = &req.name { - flow_transition.name = Set(name.to_string()); - } - if let Some(from_flow_state_id) = &req.from_flow_state_id { - flow_transition.from_flow_state_id = Set(from_flow_state_id.to_string()); - } - if let Some(to_flow_state_id) = &req.to_flow_state_id { - flow_transition.to_flow_state_id = Set(to_flow_state_id.to_string()); - } - - if let Some(transfer_by_auto) = req.transfer_by_auto { - flow_transition.transfer_by_auto = Set(transfer_by_auto); - } - if let Some(transfer_by_timer) = &req.transfer_by_timer { - flow_transition.transfer_by_timer = Set(transfer_by_timer.to_string()); - } - - if let Some(guard_by_creator) = req.guard_by_creator { - flow_transition.guard_by_creator = Set(guard_by_creator); - } - if let Some(guard_by_his_operators) = req.guard_by_his_operators { - flow_transition.guard_by_his_operators = Set(guard_by_his_operators); - } - if let Some(guard_by_assigned) = req.guard_by_assigned { - flow_transition.guard_by_assigned = Set(guard_by_assigned); - } - if let Some(guard_by_spec_account_ids) = &req.guard_by_spec_account_ids { - flow_transition.guard_by_spec_account_ids = Set(guard_by_spec_account_ids.clone()); - } - if let Some(guard_by_spec_role_ids) = &req.guard_by_spec_role_ids { - flow_transition.guard_by_spec_role_ids = Set(guard_by_spec_role_ids.clone()); - } - if let Some(guard_by_spec_org_ids) = &req.guard_by_spec_org_ids { - flow_transition.guard_by_spec_org_ids = Set(guard_by_spec_org_ids.clone()); - } - if let Some(guard_by_other_conds) = &req.guard_by_other_conds { - flow_transition.guard_by_other_conds = Set(TardisFuns::json.obj_to_json(guard_by_other_conds)?); - } - - if let Some(vars_collect) = &req.vars_collect { - flow_transition.vars_collect = Set(vars_collect.clone()); - } - - if let Some(action_by_pre_callback) = &req.action_by_pre_callback { - flow_transition.action_by_pre_callback = Set(action_by_pre_callback.to_string()); - } - if let Some(action_by_post_callback) = &req.action_by_post_callback { - flow_transition.action_by_post_callback = Set(action_by_post_callback.to_string()); - } - if let Some(action_by_front_changes) = &req.action_by_front_changes { - flow_transition.action_by_front_changes = Set(action_by_front_changes.clone()); - } - if let Some(action_by_post_changes) = &req.action_by_post_changes { - flow_transition.action_by_post_changes = Set(action_by_post_changes.clone()); - } - if let Some(action_by_post_var_changes) = &req.action_by_post_var_changes { - let mut state_post_changes = - transiton.unwrap().action_by_post_changes().into_iter().filter(|post| post.kind == FlowTransitionActionChangeKind::State).collect_vec(); - let mut action_by_post_changes = action_by_post_var_changes.clone(); - action_by_post_changes.append(&mut state_post_changes); - flow_transition.action_by_post_changes = Set(action_by_post_changes.clone()); - } - if let Some(action_by_post_state_changes) = &req.action_by_post_state_changes { - let mut var_post_changes = transiton.unwrap().action_by_post_changes().into_iter().filter(|post| post.kind == FlowTransitionActionChangeKind::Var).collect_vec(); - let mut action_by_post_changes = action_by_post_state_changes.clone(); - action_by_post_changes.append(&mut var_post_changes); - flow_transition.action_by_post_changes = Set(action_by_post_changes.clone()); - } - if let Some(double_check) = &req.double_check { - flow_transition.double_check = Set(double_check.clone()); - } - if let Some(is_notify) = &req.is_notify { - flow_transition.is_notify = Set(*is_notify); - } - if let Some(sort) = &req.sort { - flow_transition.sort = Set(*sort); - } - flow_transition.update_time = Set(Utc::now()); - funs.db().update_one(flow_transition, ctx).await?; - } - Ok(()) - } - - pub async fn delete_transitions(flow_model_id: &str, delete_flow_transition_ids: &Vec, funs: &TardisFunsInst, _ctx: &TardisContext) -> TardisResult<()> { - let delete_flow_transition_ids_lens = delete_flow_transition_ids.len(); - if funs - .db() - .count( - Query::select() - .column((flow_transition::Entity, flow_transition::Column::Id)) - .from(flow_transition::Entity) - .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::RelFlowModelId)).eq(flow_model_id.to_string())) - .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::Id)).is_in(delete_flow_transition_ids)), - ) - .await? as usize - != delete_flow_transition_ids_lens - { - return Err(funs.err().not_found( - &Self::get_obj_name(), - "delete_transitions", - "the transition of related models not legal", - "404-flow-transition-rel-model-not-legal", - )); - } - funs.db() - .soft_delete_custom( - flow_transition::Entity::find().filter(Expr::col(flow_transition::Column::Id).is_in(delete_flow_transition_ids)), - "id", - ) - .await?; - Ok(()) - } - - async fn find_transitions( - flow_model_id: &str, - specified_state_ids: Option<&[String]>, - funs: &TardisFunsInst, - _ctx: &TardisContext, - ) -> TardisResult> { - let from_state_rbum_table = Alias::new("from_state_rbum"); - let from_state_table = Alias::new("from_state"); - let to_state_rbum_table = Alias::new("to_state_rbum"); - let to_state_table = Alias::new("to_state"); - let mut query = Query::select(); - query - .columns([ - (flow_transition::Entity, flow_transition::Column::Id), - (flow_transition::Entity, flow_transition::Column::Name), - (flow_transition::Entity, flow_transition::Column::FromFlowStateId), - (flow_transition::Entity, flow_transition::Column::ToFlowStateId), - (flow_transition::Entity, flow_transition::Column::TransferByAuto), - (flow_transition::Entity, flow_transition::Column::TransferByTimer), - (flow_transition::Entity, flow_transition::Column::GuardByCreator), - (flow_transition::Entity, flow_transition::Column::GuardByHisOperators), - (flow_transition::Entity, flow_transition::Column::GuardByAssigned), - (flow_transition::Entity, flow_transition::Column::GuardBySpecAccountIds), - (flow_transition::Entity, flow_transition::Column::GuardBySpecRoleIds), - (flow_transition::Entity, flow_transition::Column::GuardBySpecOrgIds), - (flow_transition::Entity, flow_transition::Column::GuardByOtherConds), - (flow_transition::Entity, flow_transition::Column::VarsCollect), - (flow_transition::Entity, flow_transition::Column::ActionByPreCallback), - (flow_transition::Entity, flow_transition::Column::ActionByPostCallback), - (flow_transition::Entity, flow_transition::Column::ActionByPostChanges), - (flow_transition::Entity, flow_transition::Column::ActionByFrontChanges), - (flow_transition::Entity, flow_transition::Column::DoubleCheck), - (flow_transition::Entity, flow_transition::Column::IsNotify), - (flow_transition::Entity, flow_transition::Column::RelFlowModelId), - (flow_transition::Entity, flow_transition::Column::Sort), - ]) - .expr_as( - Expr::col((from_state_rbum_table.clone(), NAME_FIELD.clone())).if_null(""), - Alias::new("from_flow_state_name"), - ) - .expr_as(Expr::col((from_state_table.clone(), Alias::new("color"))).if_null(""), Alias::new("from_flow_state_color")) - .expr_as(Expr::col((to_state_rbum_table.clone(), NAME_FIELD.clone())).if_null(""), Alias::new("to_flow_state_name")) - .expr_as(Expr::col((to_state_table.clone(), Alias::new("color"))).if_null(""), Alias::new("to_flow_state_color")) - .from(flow_transition::Entity) - .join_as( - JoinType::LeftJoin, - RBUM_ITEM_TABLE.clone(), - from_state_rbum_table.clone(), - Cond::all() - .add(Expr::col((from_state_rbum_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::FromFlowStateId))) - .add(Expr::col((from_state_rbum_table.clone(), REL_KIND_ID_FIELD.clone())).eq(FlowStateServ::get_rbum_kind_id().unwrap())) - .add(Expr::col((from_state_rbum_table.clone(), REL_DOMAIN_ID_FIELD.clone())).eq(Self::get_rbum_domain_id().unwrap())), - ) - .join_as( - JoinType::LeftJoin, - flow_state::Entity, - from_state_table.clone(), - Cond::all().add(Expr::col((from_state_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::FromFlowStateId))), - ) - .join_as( - JoinType::LeftJoin, - RBUM_ITEM_TABLE.clone(), - to_state_rbum_table.clone(), - Cond::all() - .add(Expr::col((to_state_rbum_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::ToFlowStateId))) - .add(Expr::col((to_state_rbum_table.clone(), REL_KIND_ID_FIELD.clone())).eq(FlowStateServ::get_rbum_kind_id().unwrap())) - .add(Expr::col((to_state_rbum_table.clone(), REL_DOMAIN_ID_FIELD.clone())).eq(Self::get_rbum_domain_id().unwrap())), - ) - .join_as( - JoinType::LeftJoin, - flow_state::Entity, - to_state_table.clone(), - Cond::all().add(Expr::col((to_state_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::ToFlowStateId))), - ) - .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::RelFlowModelId)).eq(flow_model_id)); - if let Some(specified_state_ids) = specified_state_ids { - query.and_where(Expr::col((flow_transition::Entity, flow_transition::Column::FromFlowStateId)).is_in(specified_state_ids)); - } - query - .order_by((flow_transition::Entity, flow_transition::Column::Sort), Order::Asc) - .order_by((flow_transition::Entity, flow_transition::Column::CreateTime), Order::Asc) - .order_by((flow_transition::Entity, flow_transition::Column::Id), Order::Asc); - let flow_transitions: Vec = funs.db().find_dtos(&query).await?; - Ok(flow_transitions) - } - pub async fn state_is_used(flow_state_id: &str, funs: &TardisFunsInst, _ctx: &TardisContext) -> TardisResult { if funs .db() @@ -952,28 +806,22 @@ impl FlowModelServ { let mut states = Vec::new(); if is_state_detail { // find rel state - let state_ids = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_model_id, None, None, funs, ctx) - .await? - .iter() - .sorted_by_key(|rel| TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default().sort) - .map(|rel| { - ( - rel.rel_id.clone(), - rel.rel_name.clone(), - TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default(), - ) - }) - .collect::>(); - for (state_id, state_name, ext) in state_ids { + for state in model_detail.states() { let state_detail = FlowStateAggResp { - id: state_id.clone(), - name: state_name, - ext, - is_init: model_detail.init_state_id == state_id, + id: state.id.clone(), + name: state.name.clone(), + ext: state.ext.clone(), + state_kind: state.state_kind, + kind_conf: state.kind_conf, + sys_state: state.sys_state, + tags: state.tags, + scope_level: state.scope_level, + disabled: state.disabled, + is_init: model_detail.init_state_id == state.id, transitions: model_detail .transitions() .into_iter() - .filter(|transition| transition.from_flow_state_id == state_id.clone()) + .filter(|transition| transition.from_flow_state_id == state.id.clone()) .map(|transition| { let mut action_by_post_changes = vec![]; for action_by_post_change in transition.action_by_post_changes() { @@ -1004,6 +852,19 @@ impl FlowModelServ { info: model_detail.info, init_state_id: model_detail.init_state_id, template: model_detail.template, + current_version_id: model_detail.current_version_id, + edit_version_id: FlowModelVersionServ::find_one_item( + &FlowModelVersionFilterReq { + rel_model_ids: Some(vec![model_detail.id.clone()]), + status: Some(vec![FlowModelVesionState::Editing]), + ..Default::default() + }, + funs, + ctx, + ) + .await? + .map(|version| version.id) + .unwrap_or_default(), rel_model_id: model_detail.rel_model_id, rel_template_ids: FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, &model_detail.id, None, None, funs, ctx) .await? @@ -1021,47 +882,18 @@ impl FlowModelServ { }) } - // Find the specified models, or add it if it doesn't exist. - pub async fn find_or_add_models( - tags: Vec, + // Find the rel models. + pub async fn find_rel_models( template_id: Option, - is_shared: bool, + main: bool, + tags: Option>, funs: &TardisFunsInst, ctx: &TardisContext, - ) -> TardisResult> { - let mut result = Self::find_rel_models(template_id.clone(), is_shared, funs, ctx).await?; - // Iterate over the tag based on the existing result and get the default model - for tag in tags { - if !result.contains_key(&tag) { - // copy custom model - let model_id = Self::add_custom_model(&tag, None, template_id.clone(), funs, ctx).await?; - let added_model = Self::find_one_item( - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - ids: Some(vec![model_id]), - ..Default::default() - }, - ..Default::default() - }, - funs, - ctx, - ) - .await? - .unwrap_or_default(); - result.insert(tag.to_string(), added_model); - } - } - - Ok(result) - } - - // Find the rel models. - pub async fn find_rel_models(template_id: Option, _is_shared: bool, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { + ) -> TardisResult> { let global_ctx = TardisContext { own_paths: "".to_string(), ..ctx.clone() }; - let mut result = HashMap::new(); let filter_ids = if template_id.is_none() { if let Some(app_id) = Self::get_app_id_by_ctx(ctx) { @@ -1077,8 +909,12 @@ impl FlowModelServ { ids: filter_ids, ignore_scope: true, with_sub_own_paths: true, + enabled: Some(true), ..Default::default() }, + tags, + main: Some(main), + status: Some(FlowModelStatus::Enabled), rel: FlowRelServ::get_template_rel_filter(template_id.as_deref()), ..Default::default() }; @@ -1088,6 +924,14 @@ impl FlowModelServ { models = Self::find_items(&filter, None, None, funs, ctx).await?; } + Ok(models) + } + + // Find the rel models. + pub async fn find_rel_model_map(template_id: Option, main: bool, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { + let models = Self::find_rel_models(template_id, main, None, funs, ctx).await?; + + let mut result: HashMap = HashMap::new(); // First iterate over the models for model in models { result.insert(model.tag.clone(), model); @@ -1097,25 +941,17 @@ impl FlowModelServ { } /// 创建或引用模型 - /// params: - /// rel_model_id:关联模型ID - /// rel_template_id: 绑定模板ID,可选参数(仅在创建模型,即创建副本或op为复制时生效) - /// rel_own_paths: 绑定实例ID(仅在引用且不创建模型时生效) - /// (rel_model_id:关联模型ID, rel_template_id: 绑定模板ID,可选参数(仅在创建模型,即创建副本或op为复制时生效), op:关联模型操作类型(复制或者引用),is_create_copy:是否创建副本(当op为复制时需指定,默认不需要)) + /// 当op为复制时,表示按原有配置复制一套新的模型。 + /// 当op为引用时,表示建立原有配置和当前own_paths的引用而不生成新的模型数据。 + /// 当op为引用和复制时,表示按原有配置复制一套新的模型同时建立引用,此时允许用户修改新模型,同时新模型也会被旧模型修改影响到。 pub async fn copy_or_reference_model( rel_model_id: &str, - rel_own_paths: Option, op: &FlowModelAssociativeOperationKind, - is_create_copy: Option, + kind: FlowModelKind, funs: &TardisFunsInst, ctx: &TardisContext, ) -> TardisResult { - let mock_ctx = if let Some(own_paths) = rel_own_paths.clone() { - TardisContext { own_paths, ..ctx.clone() } - } else { - ctx.clone() - }; - let rel_model = FlowModelServ::get_item( + let mut rel_model = FlowModelServ::get_item( rel_model_id, &FlowModelFilterReq { basic: RbumBasicFilterReq { @@ -1130,194 +966,216 @@ impl FlowModelServ { ctx, ) .await?; - // .ok_or_else(|| funs.err().not_found(&Self::get_obj_name(), "copy_or_reference_model", "rel model not found", "404-flow-model-not-found"))?; - let result = match op { + let target_model_id = match op { + FlowModelAssociativeOperationKind::Copy => { + if kind == FlowModelKind::AsModel { + rel_model.rel_model_id = "".to_string(); + rel_model.template = false; + rel_model.scope_level = rbum_scope_helper::get_scope_level_by_context(ctx)?; + } + Self::add_item( + &mut FlowModelAddReq { + kind, + rel_template_ids: None, + ..rel_model.clone().into() + }, + funs, + ctx, + ) + .await? + } FlowModelAssociativeOperationKind::Reference => { - if is_create_copy.unwrap_or(false) { - let mut add_transitions = rel_model.transitions().into_iter().map(FlowTransitionAddReq::from).collect_vec(); - for add_transition in add_transitions.iter_mut() { - if let Some(ref mut action_by_post_changes) = &mut add_transition.action_by_post_changes { - for action_by_post_change in action_by_post_changes.iter_mut() { - action_by_post_change.is_edit = Some(false); // 引用复制时,置为不可编辑 - } - } - } - Self::add_item( - &mut FlowModelAddReq { - rel_model_id: Some(rel_model_id.to_string()), - rel_template_ids: None, - transitions: Some(add_transitions), - ..rel_model.clone().into() - }, + if let Some(template_id) = + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, rel_model_id, None, None, funs, ctx).await?.pop().map(|rel| rel.rel_id) + { + if !FlowRelServ::exist_rels( + &FlowRelKind::FlowAppTemplate, + &template_id, + Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), funs, - &mock_ctx, + ctx, ) .await? - } else { - if let Some(template_id) = - FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, rel_model_id, None, None, funs, ctx).await?.pop().map(|rel| rel.rel_id) { - if !FlowRelServ::exist_rels( + FlowRelServ::add_simple_rel( &FlowRelKind::FlowAppTemplate, - &template_id, Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), + &template_id, + None, + None, + true, + true, + None, funs, ctx, ) - .await? - { - FlowRelServ::add_simple_rel( - &FlowRelKind::FlowAppTemplate, - Self::get_app_id_by_ctx(&mock_ctx).unwrap_or_default().as_str(), - &template_id, - None, - None, - true, - true, - None, - funs, - ctx, - ) - .await?; - } + .await?; } - rel_model_id.to_string() } + rel_model_id.to_string() } - FlowModelAssociativeOperationKind::Copy => { + FlowModelAssociativeOperationKind::ReferenceOrCopy => { + let mut add_transitions = rel_model.transitions().into_iter().map(FlowTransitionAddReq::from).collect_vec(); + for add_transition in add_transitions.iter_mut() { + if let Some(ref mut action_by_post_changes) = &mut add_transition.action_by_post_changes { + for action_by_post_change in action_by_post_changes.iter_mut() { + action_by_post_change.is_edit = Some(false); // 引用复制时,置为不可编辑 + } + } + } + let states = rel_model + .states() + .into_iter() + .map(|state| FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: state.id.clone(), + ext: state.ext, + }), + add_transitions: Some(add_transitions.clone().into_iter().filter(|tran| tran.from_flow_state_id == state.id).collect_vec()), + is_init: state.id == rel_model.init_state_id, + ..Default::default() + }) + .collect_vec(); Self::add_item( &mut FlowModelAddReq { - rel_model_id: if rbum_scope_helper::get_scope_level_by_context(&mock_ctx)? != RbumScopeLevelKind::L2 { - Some(rel_model_id.to_string()) - } else { - None - }, + rel_model_id: Some(rel_model_id.to_string()), + kind, rel_template_ids: None, - template: rbum_scope_helper::get_scope_level_by_context(&mock_ctx)? != RbumScopeLevelKind::L2, - scope_level: if rbum_scope_helper::get_scope_level_by_context(&mock_ctx)? != RbumScopeLevelKind::L2 { - Some(rel_model.clone().scope_level) - } else { - None - }, + add_version: Some(FlowModelVersionAddReq { + name: rel_model.name.clone().into(), + rel_model_id: None, + bind_states: Some(states), + status: FlowModelVesionState::Enabled, + scope_level: Some(rel_model.scope_level.clone()), + disabled: Some(rel_model.disabled), + }), ..rel_model.clone().into() }, funs, - &mock_ctx, + ctx, ) .await? } }; - let new_model = Self::get_item_detail_aggs(&result, true, funs, ctx).await?; + let new_model = Self::get_item_detail_aggs(&target_model_id, true, funs, ctx).await?; Ok(new_model) } - - // copy model by template model - // rel_template_id: Associated parent template id - // current_template_id: Current template id - pub async fn add_custom_model( - tag: &str, - parent_template_id: Option, - rel_template_id: Option, - funs: &TardisFunsInst, - ctx: &TardisContext, - ) -> TardisResult { - let current_model = Self::find_one_detail_item( - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - ignore_scope: true, - ..Default::default() - }, - tags: Some(vec![tag.to_string()]), - rel: FlowRelServ::get_template_rel_filter(rel_template_id.as_deref()), - ..Default::default() - }, - funs, - ctx, - ) - .await?; - if let Some(current_model) = current_model { - return Ok(current_model.id); - } - - let global_ctx = TardisContext { - own_paths: "".to_string(), - ..ctx.clone() - }; - // First, get the parent model, if the parent model does not exist, then get the default template - // 首先,获取父级model,若父级model不存在,则获取默认模板 - let parent_model = if let Some(parent_model) = Self::find_one_detail_item( - // There are shared templates, so you need to ignore the permission judgment of own_path if the parent ID is passed in. - // 由于存在共享模板的情况,所以父级ID传入的情况下需要忽略 own_path 的权限判断 - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - with_sub_own_paths: parent_template_id.is_some(), - ignore_scope: parent_template_id.is_some(), - ..Default::default() - }, - tags: Some(vec![tag.to_string()]), - // When no parent ID is passed, indicating that the default template is directly obtained, parent_template_id is passed into the empty string - // 没有传入父级ID时,说明直接获取默认模板,则 parent_template_id 传入空字符串 - rel: FlowRelServ::get_template_rel_filter(Some(&parent_template_id.unwrap_or_default())), - template: Some(true), - ..Default::default() - }, - funs, - &global_ctx, - ) - .await? - { - parent_model - } else { - Self::find_one_detail_item( - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - ignore_scope: true, - ..Default::default() - }, - tags: Some(vec![tag.to_string()]), - ..Default::default() - }, - funs, - &global_ctx, - ) - .await? - .ok_or_else(|| funs.err().internal_error("flow_model_serv", "add_custom_model", "default model is not exist", "404-flow-model-not-found"))? - }; - - // add model - let model_id = Self::add_item( - &mut FlowModelAddReq { - name: parent_model.name.clone().into(), - icon: Some(parent_model.icon.clone()), - info: Some(parent_model.info.clone()), - init_state_id: parent_model.init_state_id.clone(), - template: rel_template_id.is_some(), - rel_template_ids: rel_template_id.clone().map(|id| vec![id]), - transitions: Some(parent_model.transitions().into_iter().map(|trans| trans.into()).collect_vec()), - states: Some( - FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, &parent_model.id, None, None, funs, &global_ctx) - .await? - .iter() - .sorted_by_key(|rel| TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default().sort) - .map(|rel| FlowModelBindStateReq { - state_id: rel.rel_id.clone(), - ext: TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default(), - }) - .collect_vec(), - ), - rel_model_id: Some(parent_model.id.clone()), - tag: Some(parent_model.tag.clone()), - scope_level: if rel_template_id.is_some() { Some(RbumScopeLevelKind::Root) } else { None }, - disabled: Some(parent_model.disabled), - }, - funs, - ctx, - ) - .await?; - - Ok(model_id) - } + /// 创建或引用模型 + /// params: + /// rel_model_id:关联模型ID + /// rel_template_id: 绑定模板ID,可选参数(仅在创建模型,即创建副本或op为复制时生效) + /// rel_own_paths: 绑定实例ID(仅在引用且不创建模型时生效) + /// (rel_model_id:关联模型ID, rel_template_id: 绑定模板ID,可选参数(仅在创建模型,即创建副本或op为复制时生效), op:关联模型操作类型(复制或者引用),is_create_copy:是否创建副本(当op为复制时需指定,默认不需要)) + // pub async fn copy_or_reference_model( + // rel_model_id: &str, + // rel_own_paths: Option, + // op: &FlowModelAssociativeOperationKind, + // is_create_copy: Option, + // funs: &TardisFunsInst, + // ctx: &TardisContext, + // ) -> TardisResult { + // let mock_ctx = if let Some(own_paths) = rel_own_paths.clone() { + // TardisContext { own_paths, ..ctx.clone() } + // } else { + // ctx.clone() + // }; + // let rel_model = FlowModelServ::get_item( + // rel_model_id, + // &FlowModelFilterReq { + // basic: RbumBasicFilterReq { + // own_paths: Some("".to_string()), + // with_sub_own_paths: true, + // ignore_scope: true, + // ..Default::default() + // }, + // ..Default::default() + // }, + // funs, + // ctx, + // ) + // .await?; + // // .ok_or_else(|| funs.err().not_found(&Self::get_obj_name(), "copy_or_reference_model", "rel model not found", "404-flow-model-not-found"))?; + // let result = match op { + // FlowModelAssociativeOperationKind::Reference => { + // if is_create_copy.unwrap_or(false) { + // let mut add_transitions = rel_model.transitions().into_iter().map(FlowTransitionAddReq::from).collect_vec(); + // for add_transition in add_transitions.iter_mut() { + // if let Some(ref mut action_by_post_changes) = &mut add_transition.action_by_post_changes { + // for action_by_post_change in action_by_post_changes.iter_mut() { + // action_by_post_change.is_edit = Some(false); // 引用复制时,置为不可编辑 + // } + // } + // } + // Self::add_item( + // &mut FlowModelAddReq { + // rel_model_id: Some(rel_model_id.to_string()), + // rel_template_ids: None, + // transitions: Some(add_transitions), + // ..rel_model.clone().into() + // }, + // funs, + // &mock_ctx, + // ) + // .await? + // } else { + // if let Some(template_id) = + // FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, rel_model_id, None, None, funs, ctx).await?.pop().map(|rel| rel.rel_id) + // { + // if !FlowRelServ::exist_rels( + // &FlowRelKind::FlowAppTemplate, + // &template_id, + // Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), + // funs, + // ctx, + // ) + // .await? + // { + // FlowRelServ::add_simple_rel( + // &FlowRelKind::FlowAppTemplate, + // Self::get_app_id_by_ctx(&mock_ctx).unwrap_or_default().as_str(), + // &template_id, + // None, + // None, + // true, + // true, + // None, + // funs, + // ctx, + // ) + // .await?; + // } + // } + // rel_model_id.to_string() + // } + // } + // FlowModelAssociativeOperationKind::Copy => { + // Self::add_item( + // &mut FlowModelAddReq { + // rel_model_id: if rbum_scope_helper::get_scope_level_by_context(&mock_ctx)? != RbumScopeLevelKind::L2 { + // Some(rel_model_id.to_string()) + // } else { + // None + // }, + // rel_template_ids: None, + // template: rbum_scope_helper::get_scope_level_by_context(&mock_ctx)? != RbumScopeLevelKind::L2, + // scope_level: if rbum_scope_helper::get_scope_level_by_context(&mock_ctx)? != RbumScopeLevelKind::L2 { + // Some(rel_model.clone().scope_level) + // } else { + // None + // }, + // ..rel_model.clone().into() + // }, + // funs, + // &mock_ctx, + // ) + // .await? + // } + // }; + // let new_model = Self::get_item_detail_aggs(&result, true, funs, ctx).await?; + + // Ok(new_model) + // } // add or modify model by own_paths pub async fn modify_model(flow_model_id: &str, modify_req: &mut FlowModelModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { @@ -1340,169 +1198,12 @@ impl FlowModelServ { Ok(()) } - async fn bind_state(flow_model_id: &str, req: &FlowModelBindStateReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { - let global_ctx = TardisContext { - own_paths: "".to_string(), - ..ctx.clone() - }; - if let Ok(state) = FlowStateServ::get_item( - &req.state_id, - &FlowStateFilterReq { - basic: RbumBasicFilterReq { - with_sub_own_paths: true, - ..Default::default() - }, - ..Default::default() - }, - funs, - &global_ctx, - ) - .await - { - let model_detail = Self::get_item(flow_model_id, &FlowModelFilterReq::default(), funs, ctx).await?; - if !state.tags.is_empty() && !state.tags.split(',').collect_vec().contains(&model_detail.tag.as_str()) { - return Err(funs.err().internal_error("flow_model_serv", "bind_state", "The flow state is not found", "404-flow-state-not-found")); - } - } else { - return Err(funs.err().internal_error("flow_model_serv", "bind_state", "The flow state is not found", "404-flow-state-not-found")); - } - FlowRelServ::add_simple_rel( - &FlowRelKind::FlowModelState, - flow_model_id, - &req.state_id, - None, - None, - false, - true, - Some(json!(req.ext).to_string()), - funs, - ctx, - ) - .await?; - - Ok(()) - } - - pub async fn unbind_state(flow_model_id: &str, state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { - // Can only be deleted when not in use - if FlowInstServ::state_is_used(flow_model_id, state_id, funs, ctx).await? { - return Err(funs.err().conflict( - &Self::get_obj_name(), - "unbind_state", - &format!("state {state_id} already used"), - "409-flow-state-already-used", - )); - } - //delete rel transitions - let trans_ids = Self::find_transitions_by_state_id(flow_model_id, Some(vec![state_id.to_string()]), None, funs, ctx).await?.into_iter().map(|trans| trans.id).collect_vec(); - Self::delete_transitions(flow_model_id, &trans_ids, funs, ctx).await?; - let trans_ids = Self::find_transitions_by_state_id(flow_model_id, None, Some(vec![state_id.to_string()]), funs, ctx).await?.into_iter().map(|trans| trans.id).collect_vec(); - Self::delete_transitions(flow_model_id, &trans_ids, funs, ctx).await?; - - FlowRelServ::delete_simple_rel(&FlowRelKind::FlowModelState, flow_model_id, state_id, funs, ctx).await?; - - Ok(()) - } - - pub async fn modify_rel_state_ext(flow_model_id: &str, modify_req: &FlowStateRelModelModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { - let mut ext = TardisFuns::json.str_to_obj::( - &FlowRelServ::find_simple_rels(&FlowRelKind::FlowModelState, Some(flow_model_id), Some(modify_req.id.as_str()), true, None, None, funs, ctx) - .await? - .pop() - .ok_or_else(|| funs.err().internal_error("flow_model_serv", "modify_rel_state", "rel not found", "404-rel-not-found"))? - .ext, - )?; - if let Some(sort) = modify_req.sort { - ext.sort = sort; - } - if let Some(show_btns) = modify_req.show_btns.clone() { - ext.show_btns = Some(show_btns); - } - FlowRelServ::modify_simple_rel( - &FlowRelKind::FlowModelState, - flow_model_id, - &modify_req.id, - &mut RbumRelModifyReq { - tag: None, - note: None, - ext: Some(json!(ext).to_string()), - }, - funs, - ctx, - ) - .await?; - - Ok(()) - } - - async fn find_transitions_by_state_id( - flow_model_id: &str, - current_state_id: Option>, - target_state_id: Option>, - funs: &TardisFunsInst, - ctx: &TardisContext, - ) -> TardisResult> { - Ok(Self::find_transitions(flow_model_id, None, funs, ctx) - .await? - .into_iter() - .filter(|tran_detail| { - if let Some(target_state_id) = target_state_id.as_ref() { - target_state_id.contains(&tran_detail.to_flow_state_id) - } else { - true - } - }) - .filter(|tran_detail| { - if let Some(current_state_id) = current_state_id.as_ref() { - current_state_id.contains(&tran_detail.from_flow_state_id) - } else { - true - } - }) - .collect_vec()) - } - - // 1、template_id为None,则根据own_paths批量获取关联模型 - // 2、template_id为单元素数组,则表示 - pub async fn check_post_action_ring(model_desp: &FlowModelDetailResp, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { - let mut model_details = HashMap::new(); - if model_desp.template { - model_details.insert(model_desp.tag.clone(), model_desp.clone()); - } else { - let template_ids = if model_desp.rel_template_ids.is_empty() { - None - } else { - model_desp.rel_template_ids.clone().pop() - }; - let models = Self::find_rel_models(template_ids, false, funs, ctx).await?; - - for (tag, model) in models { - let model_detail = Self::find_one_detail_item( - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - ids: Some(vec![model.id]), - ..Default::default() - }, - ..Default::default() - }, - funs, - ctx, - ) - .await? - .unwrap(); - model_details.insert(tag, model_detail); - } - } - - // Ok(!loop_check_helper::check(&model_details)) - Ok(false) - } - pub async fn find_rel_states(tags: Vec<&str>, rel_template_id: Option, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { let mut result = Vec::new(); for tag in tags { - let flow_model_id = Self::get_model_id_by_own_paths_and_rel_template_id(tag, rel_template_id.clone(), funs, ctx).await?; - let mut states = Self::find_sorted_rel_states_by_model_id(&flow_model_id, funs, ctx) + let flow_model_id = Self::get_model_id_by_own_paths_and_rel_template_id(tag, rel_template_id.clone(), funs, ctx).await?.id; + let flow_model = Self::get_item(&flow_model_id, &FlowModelFilterReq::default(), funs, ctx).await?; + let mut states = FlowModelVersionServ::find_sorted_rel_states_by_version_id(&flow_model.current_version_id, funs, ctx) .await? .into_iter() .map(|state_detail| FlowModelFindRelStateResp { @@ -1516,111 +1217,259 @@ impl FlowModelServ { Ok(result) } - async fn find_sorted_rel_states_by_model_id(flow_model_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { - Ok(join_all( - FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_model_id, None, None, funs, ctx) - .await? - .into_iter() - .sorted_by_key(|rel| TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default().sort) - .map(|rel| async move { - FlowStateServ::find_one_detail_item( - &FlowStateFilterReq { - basic: RbumBasicFilterReq { - ids: Some(vec![rel.rel_id]), - with_sub_own_paths: true, - own_paths: Some("".to_string()), - ..Default::default() - }, - ..Default::default() - }, - funs, - ctx, - ) - .await - .unwrap_or_default() - .unwrap() - }) - .collect::>(), - ) - .await) + pub async fn get_model_id_by_own_paths_and_transition_id(tag: &str, transition_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let app_id = Self::get_app_id_by_ctx(ctx).ok_or_else(|| { + funs.err().not_found( + &Self::get_obj_name(), + "get_model_id_by_own_paths_and_transition_id", + "Only instances at the application layer are supported to use the approval flow", + "404-flow-inst-rel-model-not-found", + ) + })?; + let model_detail = if let Ok(model_ids) = FlowRelServ::find_model_ids_by_app_id(&app_id, funs, ctx).await { + // 引用租户模板的审批流 + Self::find_one_detail_item( + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + ids: Some(model_ids), + enabled: Some(true), + ..Default::default() + }, + tags: Some(vec![tag.to_string()]), + rel: Some(RbumItemRelFilterReq { + optional: false, + rel_by_from: true, + tag: Some(FlowRelKind::FlowModelTransition.to_string()), + from_rbum_kind: Some(RbumRelFromKind::Item), + rel_item_id: Some(transition_id.to_string()), + ..Default::default() + }), + status: Some(FlowModelStatus::Enabled), + ..Default::default() + }, + funs, + ctx, + ) + .await? + } else { + // 当前项目内的审批流 + Self::find_one_detail_item( + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + enabled: Some(true), + ..Default::default() + }, + tags: Some(vec![tag.to_string()]), + rel: Some(RbumItemRelFilterReq { + optional: false, + rel_by_from: true, + tag: Some(FlowRelKind::FlowModelTransition.to_string()), + from_rbum_kind: Some(RbumRelFromKind::Item), + rel_item_id: Some(transition_id.to_string()), + own_paths: Some(ctx.own_paths.clone()), + ..Default::default() + }), + status: Some(FlowModelStatus::Enabled), + ..Default::default() + }, + funs, + ctx, + ) + .await? + }; + match model_detail { + Some(result) => Ok(result), + None => Err(funs.err().not_found( + &Self::get_obj_name(), + "get_model_id_by_own_paths_and_transition_id", + "rel model not found", + "404-flow-model-not-found", + )), + } } - - pub async fn get_model_id_by_own_paths_and_rel_template_id(tag: &str, rel_template_id: Option, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { - let mut own_paths = ctx.own_paths.clone(); - let mut scope_level = rbum_scope_helper::get_scope_level_by_context(ctx)?.to_int(); - + /// 根据own_paths和rel_template_id获取模型ID + /// 规则1:如果rel_template_id不为空,优先通过rel_template_id查找rel表类型为FlowModelTemplate关联的模型ID,找不到则直接返回默认模板ID + /// 规则2:如果rel_template_id为空,则通过own_paths获取rel表类型为FlowAppTemplate关联的模型ID + /// 规则3:如果按照规则2未找到关联的模型,则通过own_paths直接获取model表中存在的模型ID + /// 规则4:如果按照规则3未找到关联的模型,则直接返回默认的模板ID + pub async fn get_model_id_by_own_paths_and_rel_template_id( + tag: &str, + rel_template_id: Option, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult { let mut result = None; - // Prioritize confirming the existence of mods related to own_paths - if let Some(rel_model) = Self::find_items( - &FlowModelFilterReq { - basic: RbumBasicFilterReq { - ids: Some(FlowRelServ::find_model_ids_by_app_id(Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), funs, ctx).await.unwrap_or_default()), - ignore_scope: true, - own_paths: Some("".to_string()), - with_sub_own_paths: true, + if let Some(rel_template_id) = rel_template_id { + // 规则1 + result = FlowModelServ::find_detail_items( + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + own_paths: Some("".to_string()), + ignore_scope: true, + ..Default::default() + }, + template: Some(true), + main: Some(true), + rel: FlowRelServ::get_template_rel_filter(Some(rel_template_id.as_str())), + tags: Some(vec![tag.to_string()]), ..Default::default() }, - ..Default::default() - }, - None, - None, - funs, - ctx, - ) - .await? - .into_iter() - .find(|rel_model| rel_model.tag.as_str() == tag) - { - return Ok(rel_model.id); - } - // try get model in tenant path or app path - while !own_paths.is_empty() { - result = FlowModelServ::find_one_item( + None, + None, + funs, + ctx, + ) + .await? + .pop(); + } else { + // 规则2 + result = FlowModelServ::find_detail_items( &FlowModelFilterReq { basic: RbumBasicFilterReq { - own_paths: Some(own_paths.clone()), + ids: Some(FlowRelServ::find_model_ids_by_app_id(Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), funs, ctx).await.unwrap_or_default()), ignore_scope: true, + own_paths: Some("".to_string()), + with_sub_own_paths: true, ..Default::default() }, + main: Some(true), tags: Some(vec![tag.to_string()]), - template: Some(rel_template_id.is_some()), - rel: FlowRelServ::get_template_rel_filter(rel_template_id.as_deref()), ..Default::default() }, + None, + None, funs, ctx, ) - .await - .unwrap_or_default(); - if result.is_some() { - break; - } else { - own_paths = rbum_scope_helper::get_path_item(scope_level, &ctx.own_paths).unwrap_or_default(); - scope_level -= 1; + .await? + .pop(); + if result.is_none() { + // 规则3 + result = FlowModelServ::find_detail_items( + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + own_paths: Some(ctx.own_paths.clone()), + ignore_scope: true, + ..Default::default() + }, + tags: Some(vec![tag.to_string()]), + template: Some(false), + main: Some(true), + ..Default::default() + }, + None, + None, + funs, + ctx, + ) + .await? + .pop(); } } + // 规则4 if result.is_none() { - result = FlowModelServ::find_one_item( + result = FlowModelServ::find_detail_items( &FlowModelFilterReq { basic: RbumBasicFilterReq { own_paths: Some("".to_string()), ignore_scope: true, ..Default::default() }, + main: Some(true), tags: Some(vec![tag.to_string()]), ..Default::default() }, + None, + None, funs, ctx, ) - .await?; + .await? + .pop(); } match result { - Some(model) => Ok(model.id), - None => Err(funs.err().not_found("flow_inst_serv", "get_model_id_by_own_paths", "model not found", "404-flow-model-not-found")), + Some(model) => Ok(model), + None => Err(funs.err().not_found("flow_model_serv", "get_model_id_by_own_paths", "model not found", "404-flow-model-not-found")), } } + /// 根据own_paths和rel_template_id获取 + // pub async fn get_model_id_by_own_paths_and_rel_template_id(tag: &str, rel_template_id: Option, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + // let mut own_paths = ctx.own_paths.clone(); + // let mut scope_level = rbum_scope_helper::get_scope_level_by_context(ctx)?.to_int(); + + // let mut result = None; + // // Prioritize confirming the existence of mods related to own_paths + // if let Some(rel_model) = Self::find_items( + // &FlowModelFilterReq { + // basic: RbumBasicFilterReq { + // ids: Some(FlowRelServ::find_model_ids_by_app_id(Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), funs, ctx).await.unwrap_or_default()), + // ignore_scope: true, + // own_paths: Some("".to_string()), + // with_sub_own_paths: true, + // ..Default::default() + // }, + // ..Default::default() + // }, + // None, + // None, + // funs, + // ctx, + // ) + // .await? + // .into_iter() + // .find(|rel_model| rel_model.tag.as_str() == tag) + // { + // return Ok(rel_model.id); + // } + // // try get model in tenant path or app path + // while !own_paths.is_empty() { + // result = FlowModelServ::find_one_item( + // &FlowModelFilterReq { + // basic: RbumBasicFilterReq { + // own_paths: Some(own_paths.clone()), + // ignore_scope: true, + // ..Default::default() + // }, + // tags: Some(vec![tag.to_string()]), + // template: Some(rel_template_id.is_some()), + // rel: FlowRelServ::get_template_rel_filter(rel_template_id.as_deref()), + // ..Default::default() + // }, + // funs, + // ctx, + // ) + // .await + // .unwrap_or_default(); + // if result.is_some() { + // break; + // } else { + // own_paths = rbum_scope_helper::get_path_item(scope_level, &ctx.own_paths).unwrap_or_default(); + // scope_level -= 1; + // } + // } + // if result.is_none() { + // result = FlowModelServ::find_one_item( + // &FlowModelFilterReq { + // basic: RbumBasicFilterReq { + // own_paths: Some("".to_string()), + // ignore_scope: true, + // ..Default::default() + // }, + // tags: Some(vec![tag.to_string()]), + // ..Default::default() + // }, + // funs, + // ctx, + // ) + // .await?; + // } + // match result { + // Some(model) => Ok(model.id), + // None => Err(funs.err().not_found("flow_model_serv", "get_model_id_by_own_paths", "model not found", "404-flow-model-not-found")), + // } + // } + pub async fn find_models_by_rel_template_id( tag: String, template: Option, @@ -1638,6 +1487,7 @@ impl FlowModelServ { }, tags: Some(vec![tag.clone()]), template, + main: Some(true), rel_model_ids: Some(vec!["".to_string()]), // rel_model_id is empty and template is true, which means it is a workflow template. ..Default::default() }, @@ -1672,6 +1522,7 @@ impl FlowModelServ { }, tags: Some(vec![tag.clone()]), template, + main: Some(true), rel_model_ids: Some(vec!["".to_string()]), // rel_model_id is empty and template is true, which means it is a workflow template. rel: Some(RbumItemRelFilterReq { optional: false, @@ -1716,7 +1567,61 @@ impl FlowModelServ { own_paths: "".to_string(), ..ctx.clone() }; - let models = Self::find_rel_models(rel_template_id.clone(), false, funs, ctx).await?; + let models = Self::find_rel_model_map(rel_template_id.clone(), true, funs, ctx).await?; + if let Some(rel_template_id) = rel_template_id.clone() { + let rel_model_ids = FlowRelServ::find_to_simple_rels(&FlowRelKind::FlowModelTemplate, &rel_template_id, None, None, funs, &global_ctx) + .await? + .into_iter() + .map(|rel| rel.rel_id) + .collect_vec(); + let main_model_ids = Self::find_id_items( + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ids: Some(rel_model_ids), + ..Default::default() + }, + main: Some(true), + ..Default::default() + }, + None, + None, + funs, + ctx, + ) + .await?; + for main_model_id in main_model_ids { + if let Some(orginal_model_ids) = orginal_model_ids.clone() { + if orginal_model_ids.contains(&main_model_id) { + continue; + } + } + FlowRelServ::delete_simple_rel(&FlowRelKind::FlowModelTemplate, &main_model_id, &rel_template_id, funs, &global_ctx).await?; + } + } else { + // clean reference template rel + for rel in FlowRelServ::find_from_simple_rels( + &FlowRelKind::FlowAppTemplate, + Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), + None, + None, + funs, + &global_ctx, + ) + .await? + .into_iter() + { + FlowRelServ::delete_simple_rel( + &FlowRelKind::FlowAppTemplate, + Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), + &rel.rel_id, + funs, + &global_ctx, + ) + .await?; + } + } for (tag, model) in models.iter() { if let Some(spec_tags) = spec_tags.clone() { if !spec_tags.contains(tag) { @@ -1728,41 +1633,180 @@ impl FlowModelServ { continue; } } - if rel_template_id.clone().is_some() { - for rel in FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTemplate, &model.id, None, None, funs, &global_ctx).await? { - FlowRelServ::delete_simple_rel(&FlowRelKind::FlowModelTemplate, &model.id, &rel.rel_id, funs, &global_ctx).await?; - } - } else { - // clean reference template rel - for rel in FlowRelServ::find_from_simple_rels( - &FlowRelKind::FlowAppTemplate, - Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), - None, - None, - funs, - &global_ctx, - ) - .await? - .into_iter() - { - FlowRelServ::delete_simple_rel( - &FlowRelKind::FlowAppTemplate, - Self::get_app_id_by_ctx(ctx).unwrap_or_default().as_str(), - &rel.rel_id, - funs, - &global_ctx, - ) - .await?; - } - } if ctx.own_paths == model.own_paths { Self::delete_item(&model.id, funs, ctx).await?; } } + Ok(models) } - fn get_app_id_by_ctx(ctx: &TardisContext) -> Option { - rbum_scope_helper::get_max_level_id_by_context(ctx) + pub fn get_app_id_by_ctx(ctx: &TardisContext) -> Option { + rbum_scope_helper::get_path_item(2, &ctx.own_paths) + } + + async fn sync_child_model( + child_model: &FlowModelDetailResp, + parent_model: &FlowModelDetailResp, + modify_req: &FlowModelModifyReq, + _funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult<()> { + let ctx_clone = TardisContext { + own_paths: child_model.own_paths.clone(), + ..ctx.clone() + }; + let parent_model_transitions = parent_model.transitions(); + let child_model_transitions = child_model.transitions(); + let mut modify_req_clone = modify_req.clone(); + if let Some(ref mut modify_version) = &mut modify_req_clone.modify_version { + if let Some(ref mut bind_states) = &mut modify_version.bind_states { + for bind_state in bind_states.iter_mut() { + if let Some(ref mut add_transitions) = &mut bind_state.add_transitions { + for add_transition in add_transitions.iter_mut() { + if let Some(ref mut action_by_post_changes) = &mut add_transition.action_by_post_changes { + for action_by_post_change in action_by_post_changes.iter_mut() { + action_by_post_change.is_edit = Some(false); + // 引用复制时,置为不可编辑 + } + } + } + } + if let Some(ref mut modify_transitions) = &mut bind_state.modify_transitions { + for modify_transition in modify_transitions.iter_mut() { + let parent_model_transition = parent_model_transitions.iter().find(|trans| trans.id == modify_transition.id.to_string()).unwrap(); + modify_transition.id = child_model_transitions + .iter() + .find(|child_tran| { + child_tran.from_flow_state_id == parent_model_transition.from_flow_state_id + && child_tran.to_flow_state_id == parent_model_transition.to_flow_state_id + }) + .map(|trans| trans.id.clone()) + .unwrap_or_default() + .into(); + } + } + if let Some(delete_transitions) = &mut bind_state.delete_transitions { + let mut child_delete_transitions = vec![]; + for delete_transition_id in delete_transitions.iter_mut() { + let parent_model_transition = parent_model_transitions.iter().find(|trans| trans.id == delete_transition_id.clone()).unwrap(); + child_delete_transitions.push( + child_model_transitions + .iter() + .find(|tran| { + tran.from_flow_state_id == parent_model_transition.from_flow_state_id && tran.to_flow_state_id == parent_model_transition.to_flow_state_id + }) + .map(|trans| trans.id.clone()) + .unwrap_or_default(), + ); + } + bind_state.delete_transitions = Some(child_delete_transitions); + } + } + } + } + + let child_model_clone = child_model.clone(); + ctx.add_async_task(Box::new(|| { + Box::pin(async move { + let task_handle = tokio::spawn(async move { + let funs = flow_constants::get_tardis_inst(); + let _ = Self::modify_item(&child_model_clone.id, &mut modify_req_clone, &funs, &ctx_clone).await; + }); + task_handle.await.unwrap(); + Ok(()) + }) + })) + .await?; + Ok(()) + } + + pub async fn resort_transition(flow_model_id: &str, resort_req: &FlowTransitionSortStatesReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let model_detail = Self::get_item(flow_model_id, &FlowModelFilterReq::default(), funs, ctx).await?; + let modify_trans = resort_req + .sort_states + .clone() + .into_iter() + .map(|sort_req| FlowTransitionModifyReq { + id: sort_req.id.clone().into(), + sort: Some(sort_req.sort), + ..Default::default() + }) + .collect_vec(); + let mut modify_states = HashMap::new(); + let transitions = FlowTransitionServ::find_transitions(&model_detail.current_version_id, None, funs, ctx).await?; + for modify_tran in modify_trans { + let tansition = transitions.iter().find(|tran| tran.id == modify_tran.id.to_string()).unwrap(); + let modify_transitons = modify_states.entry(&tansition.from_flow_state_id).or_insert(vec![]); + modify_transitons.push(modify_tran); + } + FlowModelServ::modify_model( + flow_model_id, + &mut FlowModelModifyReq { + modify_version: Some(FlowModelVersionModifyReq { + modify_states: Some( + modify_states + .into_iter() + .map(|(id, modify_transitions)| FlowModelVersionModifyState { + id: id.clone(), + modify_state: None, + modify_rel: None, + add_transitions: None, + modify_transitions: Some(modify_transitions), + delete_transitions: None, + }) + .collect_vec(), + ), + ..Default::default() + }), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + Ok(()) + } + + pub async fn get_rel_transitions(flow_model_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { + let model = Self::get_item( + flow_model_id, + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; + FlowTransitionServ::find_transitions(&model.current_version_id, None, funs, ctx).await + } + + pub async fn find_editing_verion(flow_model_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let version = if let Some(version) = FlowModelVersionServ::find_one_detail_item( + &FlowModelVersionFilterReq { + rel_model_ids: Some(vec![flow_model_id.to_string()]), + status: Some(vec![FlowModelVesionState::Editing]), + ..Default::default() + }, + funs, + ctx, + ) + .await? + { + Some(version) + } else { + // 当不存在正在编辑的版本时,按照当前启用版本复制一套作为最新的编辑版本 + let current_version_id = Self::get_item(flow_model_id, &FlowModelFilterReq::default(), funs, ctx).await?.current_version_id; + Some(FlowModelVersionServ::create_editing_version(¤t_version_id, funs, ctx).await?) + }; + match version { + Some(version) => Ok(version), + None => Err(funs.err().not_found("flow_model_serv", "find_editing_verion", "model not found", "404-flow-model-not-found")), + } } } diff --git a/backend/middlewares/flow/src/serv/flow_model_version_serv.rs b/backend/middlewares/flow/src/serv/flow_model_version_serv.rs new file mode 100644 index 000000000..8a3020e97 --- /dev/null +++ b/backend/middlewares/flow/src/serv/flow_model_version_serv.rs @@ -0,0 +1,701 @@ +use std::collections::HashMap; + +use async_recursion::async_recursion; +use bios_basic::rbum::{ + dto::{ + rbum_filer_dto::RbumBasicFilterReq, + rbum_item_dto::{RbumItemKernelAddReq, RbumItemKernelModifyReq}, + }, + serv::rbum_item_serv::RbumItemCrudOperation, +}; +use itertools::Itertools; +use tardis::{ + basic::{dto::TardisContext, result::TardisResult}, + db::sea_orm::{ + prelude::Expr, + sea_query::{Alias, SelectStatement}, + EntityName, Set, + }, + futures::future::join_all, + serde_json::json, + TardisFuns, TardisFunsInst, +}; + +use crate::{ + domain::flow_model_version, + dto::{ + flow_model_dto::{FlowModelBindNewStateReq, FlowModelBindStateReq, FlowModelFilterReq, FlowModelModifyReq}, + flow_model_version_dto::{ + FlowModelVersionAddReq, FlowModelVersionBindState, FlowModelVersionDetailResp, FlowModelVersionFilterReq, FlowModelVersionModifyReq, FlowModelVersionSummaryResp, + FlowModelVesionState, + }, + flow_state_dto::{FlowStateAddReq, FlowStateAggResp, FlowStateDetailResp, FlowStateFilterReq, FlowStateKind, FlowStateRelModelExt}, + flow_transition_dto::FlowTransitionAddReq, + }, + flow_config::FlowBasicInfoManager, +}; +use async_trait::async_trait; + +use super::{ + flow_inst_serv::FlowInstServ, + flow_model_serv::FlowModelServ, + flow_rel_serv::{FlowRelKind, FlowRelServ}, + flow_state_serv::FlowStateServ, + flow_transition_serv::FlowTransitionServ, +}; + +pub struct FlowModelVersionServ; + +#[async_trait] +impl + RbumItemCrudOperation< + flow_model_version::ActiveModel, + FlowModelVersionAddReq, + FlowModelVersionModifyReq, + FlowModelVersionSummaryResp, + FlowModelVersionDetailResp, + FlowModelVersionFilterReq, + > for FlowModelVersionServ +{ + fn get_ext_table_name() -> &'static str { + flow_model_version::Entity.table_name() + } + + fn get_rbum_kind_id() -> Option { + Some(FlowBasicInfoManager::get_config(|conf: &crate::flow_config::BasicInfo| conf.kind_model_version_id.clone())) + } + + fn get_rbum_domain_id() -> Option { + Some(FlowBasicInfoManager::get_config(|conf: &crate::flow_config::BasicInfo| conf.domain_flow_id.clone())) + } + + async fn package_item_add(add_req: &FlowModelVersionAddReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult { + Ok(RbumItemKernelAddReq { + name: add_req.name.clone(), + disabled: add_req.disabled, + scope_level: add_req.scope_level.clone(), + ..Default::default() + }) + } + + async fn package_ext_add(id: &str, add_req: &FlowModelVersionAddReq, _: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + Ok(flow_model_version::ActiveModel { + id: Set(id.to_string()), + init_state_id: Set("".to_string()), + rel_model_id: Set(add_req.rel_model_id.clone().unwrap_or_default()), + create_by: Set(ctx.owner.clone()), + update_by: Set(ctx.owner.clone()), + own_paths: Set(ctx.own_paths.clone()), + status: Set(add_req.status.clone()), + ..Default::default() + }) + } + + async fn before_add_item(_add_req: &mut FlowModelVersionAddReq, _funs: &TardisFunsInst, _ctx: &TardisContext) -> TardisResult<()> { + Ok(()) + } + + async fn after_add_item(flow_version_id: &str, add_req: &mut FlowModelVersionAddReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let version_detail = Self::peek_item(flow_version_id, &FlowModelVersionFilterReq::default(), funs, ctx).await?; + if let Some(bind_states) = &add_req.bind_states { + Self::bind_states_and_transitions(flow_version_id, bind_states, funs, ctx).await?; + } + if add_req.status == FlowModelVesionState::Enabled { + Self::enable_version(flow_version_id, funs, ctx).await?; + FlowModelServ::modify_model( + &version_detail.rel_model_id, + &mut FlowModelModifyReq { + current_version_id: Some(flow_version_id.to_string()), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + } + + Ok(()) + } + + async fn package_item_modify(_: &str, modify_req: &FlowModelVersionModifyReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult> { + if modify_req.name.is_none() && modify_req.scope_level.is_none() && modify_req.disabled.is_none() { + return Ok(None); + } + Ok(Some(RbumItemKernelModifyReq { + code: None, + name: modify_req.name.clone(), + scope_level: modify_req.scope_level.clone(), + disabled: modify_req.disabled, + })) + } + + async fn package_ext_modify(id: &str, modify_req: &FlowModelVersionModifyReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult> { + if modify_req.init_state_id.is_none() && modify_req.status.is_none() { + return Ok(None); + } + let mut flow_mode_version = flow_model_version::ActiveModel { + id: Set(id.to_string()), + ..Default::default() + }; + if let Some(status) = &modify_req.status { + flow_mode_version.status = Set(status.clone()); + } + if let Some(init_state_id) = &modify_req.init_state_id { + flow_mode_version.init_state_id = Set(init_state_id.clone()); + } + Ok(Some(flow_mode_version)) + } + + async fn after_modify_item(id: &str, modify_req: &mut FlowModelVersionModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let version_detail = Self::get_item(id, &FlowModelVersionFilterReq::default(), funs, ctx).await?; + if let Some(status) = &modify_req.status { + if *status == FlowModelVesionState::Enabled { + Self::enable_version(id, funs, ctx).await?; + FlowModelServ::modify_item( + &version_detail.rel_model_id, + &mut FlowModelModifyReq { + current_version_id: Some(id.to_string()), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + } + } + if let Some(bind_states) = &modify_req.bind_states { + Self::bind_states_and_transitions(id, bind_states, funs, ctx).await?; + } + if let Some(modify_states) = &modify_req.modify_states { + for modify_state in modify_states { + let state_id = &modify_state.id; + if let Some(mut modify_state) = modify_state.modify_state.clone() { + FlowStateServ::modify_item(state_id, &mut modify_state, funs, ctx).await?; + } + if let Some(modify_rel) = &modify_state.modify_rel { + FlowStateServ::modify_rel_state_ext(id, modify_rel, funs, ctx).await?; + } + if let Some(add_transitions) = &modify_state.add_transitions { + FlowTransitionServ::add_transitions(id, &modify_state.id, add_transitions, funs, ctx).await?; + } + if let Some(modify_transitions) = &modify_state.modify_transitions { + FlowTransitionServ::modify_transitions(id, modify_transitions, funs, ctx).await?; + } + if let Some(delete_transitions) = &modify_state.delete_transitions { + FlowTransitionServ::delete_transitions(id, delete_transitions, funs, ctx).await?; + } + } + } + if let Some(unbind_states) = &modify_req.unbind_states { + for delete_state in unbind_states { + Self::unbind_state(id, delete_state, funs, ctx).await?; + } + } + if let Some(delete_states) = &modify_req.delete_states { + for delete_state in delete_states { + Self::delete_state(id, delete_state, funs, ctx).await?; + } + } + + Ok(()) + } + + async fn before_delete_item(id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { + let detail = Self::get_item(id, &FlowModelVersionFilterReq::default(), funs, ctx).await?; + join_all( + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, id, None, None, funs, ctx) + .await? + .into_iter() + .map(|rel| async move { FlowRelServ::delete_simple_rel(&FlowRelKind::FlowModelState, id, &rel.rel_id, funs, ctx).await }) + .collect_vec(), + ) + .await + .into_iter() + .collect::>>()?; + Ok(Some(detail)) + } + + async fn package_ext_query(query: &mut SelectStatement, _: bool, filter: &FlowModelVersionFilterReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult<()> { + query + .column((flow_model_version::Entity, flow_model_version::Column::InitStateId)) + .column((flow_model_version::Entity, flow_model_version::Column::Status)) + .column((flow_model_version::Entity, flow_model_version::Column::RelModelId)) + .column((flow_model_version::Entity, flow_model_version::Column::CreateBy)) + .column((flow_model_version::Entity, flow_model_version::Column::CreateTime)) + .column((flow_model_version::Entity, flow_model_version::Column::UpdateBy)) + .column((flow_model_version::Entity, flow_model_version::Column::UpdateTime)) + .expr_as(Expr::val(json! {()}), Alias::new("states")); + + if let Some(own_paths) = filter.own_paths.clone() { + query.and_where(Expr::col((flow_model_version::Entity, flow_model_version::Column::OwnPaths)).is_in(own_paths)); + } + if let Some(status) = filter.status.clone() { + query.and_where(Expr::col((flow_model_version::Entity, flow_model_version::Column::Status)).is_in(status)); + } + if let Some(rel_model_ids) = filter.rel_model_ids.clone() { + query.and_where(Expr::col((flow_model_version::Entity, flow_model_version::Column::RelModelId)).is_in(rel_model_ids)); + } + + Ok(()) + } + + async fn get_item(flow_version_id: &str, filter: &FlowModelVersionFilterReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let mut flow_version = Self::do_get_item(flow_version_id, filter, funs, ctx).await?; + let init_state_id = flow_version.init_state_id.clone(); + let flow_states = Self::get_rel_states(flow_version_id, &init_state_id, filter.specified_state_ids.clone(), funs, ctx).await; + + flow_version.states = Some(TardisFuns::json.obj_to_json(&flow_states)?); + + Ok(flow_version) + } +} + +impl FlowModelVersionServ { + async fn bind_state(flow_version_id: &str, req: &FlowModelBindStateReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let global_ctx = TardisContext { + own_paths: "".to_string(), + ..ctx.clone() + }; + if let Ok(state) = FlowStateServ::get_item( + &req.state_id, + &FlowStateFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + ..Default::default() + }, + ..Default::default() + }, + funs, + &global_ctx, + ) + .await + { + let version_detail = Self::peek_item(flow_version_id, &FlowModelVersionFilterReq::default(), funs, ctx).await?; + let tag = FlowModelServ::get_item(&version_detail.rel_model_id, &FlowModelFilterReq::default(), funs, ctx).await?.tag; + if !state.tags.is_empty() && !state.tags.split(',').collect_vec().contains(&tag.as_str()) { + return Err(funs.err().internal_error("flow_model_serv", "bind_state", "The flow state is not found", "404-flow-state-not-found")); + } + } else { + return Err(funs.err().internal_error("flow_model_serv", "bind_state", "The flow state is not found", "404-flow-state-not-found")); + } + FlowRelServ::add_simple_rel( + &FlowRelKind::FlowModelState, + flow_version_id, + &req.state_id, + None, + None, + false, + true, + Some(json!(req.ext).to_string()), + funs, + ctx, + ) + .await + } + + async fn get_rel_states( + flow_version_id: &str, + init_state_id: &str, + specified_state_ids: Option>, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> Vec { + join_all( + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_version_id, None, None, funs, ctx) + .await + .expect("not found state") + .into_iter() + .filter(|rel| specified_state_ids.is_none() || (specified_state_ids.is_some() && specified_state_ids.clone().unwrap_or_default().contains(&rel.rel_id))) + .sorted_by_key(|rel| TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default().sort) + .map(|rel| async { + let state_id = rel.rel_id; + FlowStateServ::aggregate(&state_id, flow_version_id, init_state_id, funs, ctx).await.expect("not found state") + }) + .collect_vec(), + ) + .await + } + + pub async fn bind_states_and_transitions(flow_version_id: &str, states: &[FlowModelVersionBindState], funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let mut binded_states = vec![]; + for bind_state in states { + let (state_id, bind_state_req) = if let Some(bind_req) = bind_state.exist_state.clone() { + (bind_req.state_id.clone(), bind_req) + } else if let Some(mut bind_new_state) = bind_state.bind_new_state.clone() { + let state_id = FlowStateServ::add_item(&mut bind_new_state.new_state, funs, ctx).await?; + ( + state_id.clone(), + FlowModelBindStateReq { + state_id, + ext: bind_new_state.ext, + }, + ) + } else { + return Err(funs.err().conflict(&Self::get_obj_name(), "bind_states", "miss exist_state or new_state", "400-flow-inst-vars-field-missing")); + }; + Self::bind_state(flow_version_id, &bind_state_req, funs, ctx).await?; + binded_states.push((state_id, bind_state)); + } + for (binded_state_id, bind_req) in binded_states { + if let Some(add_transitions) = bind_req.add_transitions.clone() { + FlowTransitionServ::add_transitions(flow_version_id, &binded_state_id, &add_transitions, funs, ctx).await?; + } + if let Some(modify_transitions) = &bind_req.modify_transitions { + FlowTransitionServ::modify_transitions(flow_version_id, modify_transitions, funs, ctx).await?; + } + if let Some(delete_transitions) = &bind_req.delete_transitions { + FlowTransitionServ::delete_transitions(flow_version_id, delete_transitions, funs, ctx).await?; + } + if bind_req.is_init { + Self::modify_item( + flow_version_id, + &mut FlowModelVersionModifyReq { + init_state_id: Some(binded_state_id), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + } + } + Ok(()) + } + + // 版本发布操作(发布时将同模板的其他版本置为关闭状态) + pub async fn enable_version(flow_version_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let version_detail = Self::peek_item(flow_version_id, &FlowModelVersionFilterReq::default(), funs, ctx).await?; + let versions = Self::find_items( + &FlowModelVersionFilterReq { + rel_model_ids: Some(vec![version_detail.rel_model_id.clone()]), + status: Some(vec![FlowModelVesionState::Enabled, FlowModelVesionState::Editing]), + ..Default::default() + }, + None, + None, + funs, + ctx, + ) + .await?; + + for version in versions { + if flow_version_id != version.id { + Self::modify_item( + &version.id, + &mut FlowModelVersionModifyReq { + status: Some(FlowModelVesionState::Disabled), + ..Default::default() + }, + funs, + ctx, + ) + .await?; + } + } + Ok(()) + } + + pub async fn unbind_state(flow_version_id: &str, state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + // Can only be deleted when not in use + if FlowInstServ::state_is_used(flow_version_id, state_id, funs, ctx).await? { + return Err(funs.err().conflict( + &Self::get_obj_name(), + "unbind_state", + &format!("state {state_id} already used"), + "409-flow-state-already-used", + )); + } + + FlowRelServ::delete_simple_rel(&FlowRelKind::FlowModelState, flow_version_id, state_id, funs, ctx).await + } + + pub async fn delete_state(flow_version_id: &str, state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let state = FlowStateServ::find_one_detail_item( + &FlowStateFilterReq { + basic: RbumBasicFilterReq { + ids: Some(vec![state_id.to_string()]), + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await? + .ok_or_else(|| funs.err().not_found(&Self::get_obj_name(), "delete_state", "flow state is not found", "404-flow-state-not-found"))?; + // 获取指向当前节点的动作 + let to_trans = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, Some(vec![state_id.to_string()]), None, funs, ctx).await?; + // 获取当前节点指向的动作 + let from_trans = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, None, Some(vec![state_id.to_string()]), funs, ctx).await?; + // 查询同级分支节点数量 + let parent_state_id = from_trans.first().map(|tran| tran.from_flow_state_id.clone()).unwrap_or_default(); + let peer_num = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, Some(vec![parent_state_id.to_string()]), None, funs, ctx).await?.len(); + + let mut delete_state_ids = vec![]; + if state.state_kind == FlowStateKind::Branch { + // 假设多个节点指向当前分支节点,则禁止删除该节点 + if to_trans.len() > 1 { + return Err(funs.err().internal_error( + &Self::get_obj_name(), + "delete_state", + &format!("state {state_id} prohibit delete"), + "500-flow-state-prohibit-delete", + )); + } + if peer_num > 1 { + // 同级分支节点大于1,则删除当前分支下节点 + let finish_state_id = Self::find_branch_finish_state_id(flow_version_id, &parent_state_id, funs, ctx).await?; + Self::find_state_ids_in_branch(flow_version_id, state_id, &finish_state_id, &mut delete_state_ids, funs, ctx).await?; + } else { + // 如分支节点等于1,只删除分支节点即可 + delete_state_ids.push(state_id.to_string()); + } + } else { + delete_state_ids.push(state_id.to_string()); + } + for delete_state_id in delete_state_ids.iter().rev() { + let delete_state = FlowStateServ::find_one_detail_item( + &FlowStateFilterReq { + basic: RbumBasicFilterReq { + ids: Some(vec![delete_state_id.to_string()]), + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await? + .ok_or_else(|| funs.err().not_found(&Self::get_obj_name(), "delete_state", "flow state is not found", "404-flow-state-not-found"))?; + Self::delete_single_state(flow_version_id, &delete_state, peer_num, funs, ctx).await?; + } + Ok(()) + } + + #[async_recursion] + async fn find_state_ids_in_branch( + flow_version_id: &str, + state_id: &str, + finish_state_id: &str, + state_ids: &mut Vec, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult<()> { + state_ids.push(state_id.to_string()); + let to_trans = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, Some(vec![state_id.to_string()]), None, funs, ctx).await?; + if to_trans.len() == 1 && to_trans.first().map(|tran| tran.to_flow_state_id.clone()).unwrap() == *finish_state_id { + return Ok(()); + } + for to_tran in to_trans { + Self::find_state_ids_in_branch(flow_version_id, &to_tran.to_flow_state_id, finish_state_id, state_ids, funs, ctx).await?; + } + Ok(()) + } + + async fn find_branch_finish_state_id(flow_version_id: &str, state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let mut target_state_id = state_id.to_string(); + let mut branch_num = 0; + loop { + let mut to_trans = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, Some(vec![target_state_id.clone()]), None, funs, ctx).await?; + let to_tran_num = to_trans.len(); + if to_tran_num == 0 { + break; + } + if to_tran_num > 1 { + branch_num += to_tran_num - 1; + } + target_state_id = to_trans.pop().map(|to_tran| to_tran.to_flow_state_id).unwrap(); + if to_tran_num == 1 { + let from_state_num = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, None, Some(vec![target_state_id.to_string()]), funs, ctx).await?.len(); + if from_state_num > 1 { + branch_num -= from_state_num - 1; + } + } + if branch_num == 0 { + break; + } + } + Ok(target_state_id) + } + + async fn delete_single_state(flow_version_id: &str, state: &FlowStateDetailResp, peer_num: usize, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let state_id = &state.id; + let to_trans = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, Some(vec![state_id.to_string()]), None, funs, ctx).await?; + // 获取当前节点指向的动作 + let from_trans = FlowTransitionServ::find_transitions_by_state_id(flow_version_id, None, Some(vec![state_id.to_string()]), funs, ctx).await?; + // 如果当前删除的节点上下均为分支节点的合并节点,则禁止删除该节点 + if from_trans.len() > 1 && to_trans.len() > 1 { + return Err(funs.err().internal_error( + &Self::get_obj_name(), + "delete_state", + &format!("state {state_id} prohibit delete"), + "500-flow-state-prohibit-delete", + )); + } + let mut delete_flow_transition_ids = vec![]; + for from_tran in &from_trans { + delete_flow_transition_ids.push(from_tran.id.clone()); + } + for to_tran in &to_trans { + delete_flow_transition_ids.push(to_tran.id.clone()); + } + + // 若当前分支节点不止一个同级节点,则不需要新增连接动作 + if !(state.state_kind == FlowStateKind::Branch && peer_num > 1) { + if to_trans.len() == 1 { + let to_state_id = to_trans.first().map(|tran| tran.to_flow_state_id.clone()).unwrap_or_default(); + for from_tran in &from_trans { + let guard_by_other_conds = from_tran.guard_by_other_conds(); + FlowTransitionServ::add_transitions( + flow_version_id, + &from_tran.from_flow_state_id, + &[FlowTransitionAddReq { + name: Some(from_tran.name.clone().into()), + from_flow_state_id: from_tran.from_flow_state_id.clone(), + to_flow_state_id: to_state_id.clone(), + transfer_by_auto: Some(from_tran.transfer_by_auto), + guard_by_other_conds, + ..Default::default() + }], + funs, + ctx, + ) + .await?; + } + } else if from_trans.len() == 1 { + let from_tran = from_trans.first().unwrap(); + let guard_by_other_conds = from_tran.guard_by_other_conds(); + for to_tran in &to_trans { + FlowTransitionServ::add_transitions( + flow_version_id, + &from_tran.from_flow_state_id, + &[FlowTransitionAddReq { + name: Some(from_tran.name.clone().into()), + from_flow_state_id: from_tran.from_flow_state_id.clone(), + to_flow_state_id: to_tran.to_flow_state_id.clone(), + transfer_by_auto: Some(from_tran.transfer_by_auto), + guard_by_other_conds: guard_by_other_conds.clone(), + ..Default::default() + }], + funs, + ctx, + ) + .await?; + } + } + } + + FlowTransitionServ::delete_transitions(flow_version_id, &delete_flow_transition_ids, funs, ctx).await?; + Self::unbind_state(flow_version_id, state_id, funs, ctx).await?; + FlowStateServ::delete_item(state_id, funs, ctx).await?; + Ok(()) + } + + pub async fn find_sorted_rel_states_by_version_id(flow_version_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { + Ok(join_all( + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_version_id, None, None, funs, ctx) + .await? + .into_iter() + .sorted_by_key(|rel| TardisFuns::json.str_to_obj::(&rel.ext).unwrap_or_default().sort) + .map(|rel| async move { + FlowStateServ::find_one_detail_item( + &FlowStateFilterReq { + basic: RbumBasicFilterReq { + ids: Some(vec![rel.rel_id]), + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await + .unwrap_or_default() + .unwrap() + }) + .collect::>(), + ) + .await) + } + + pub async fn create_editing_version(flow_version_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let version = FlowModelVersionServ::get_item(flow_version_id, &FlowModelVersionFilterReq::default(), funs, ctx).await?; + // 将当前正在编辑的版本删除 + let editing_version_ids = Self::find_id_items( + &FlowModelVersionFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: true, + own_paths: Some("".to_string()), + ..Default::default() + }, + rel_model_ids: Some(vec![version.rel_model_id.clone()]), + status: Some(vec![FlowModelVesionState::Editing]), + ..Default::default() + }, + None, + None, + funs, + ctx, + ) + .await?; + for editing_version_id in editing_version_ids { + Self::delete_item(&editing_version_id, funs, ctx).await?; + } + let mut states = version.states(); + let mut update_state_map = HashMap::new(); + for state in states.iter_mut() { + let old_state_id = state.id.clone(); + state.id = TardisFuns::field.nanoid(); + update_state_map.insert(old_state_id, state.id.clone()); + } + for state in states.iter_mut() { + for transition in state.transitions.iter_mut() { + transition.from_flow_state_id = update_state_map.get(&transition.from_flow_state_id).cloned().unwrap_or_default(); + transition.to_flow_state_id = update_state_map.get(&transition.to_flow_state_id).cloned().unwrap_or_default(); + } + } + let editind_version_id = FlowModelVersionServ::add_item( + &mut FlowModelVersionAddReq { + name: version.name.into(), + rel_model_id: Some(version.rel_model_id.clone()), + bind_states: Some( + states + .into_iter() + .map(|state| FlowModelVersionBindState { + bind_new_state: Some(FlowModelBindNewStateReq { + new_state: FlowStateAddReq { + id: Some(state.id.clone().into()), + name: Some(state.name.clone().into()), + sys_state: state.sys_state.clone(), + state_kind: Some(state.state_kind.clone()), + kind_conf: state.kind_conf, + tags: Some(state.tags.clone().split(',').map(|id| id.to_string()).collect_vec()), + scope_level: Some(state.scope_level.clone()), + disabled: Some(state.disabled), + ..Default::default() + }, + ext: state.ext, + }), + add_transitions: Some(state.transitions.into_iter().map(FlowTransitionAddReq::from).collect_vec()), + is_init: state.state_kind == FlowStateKind::Start, + ..Default::default() + }) + .collect_vec(), + ), + status: FlowModelVesionState::Editing, + scope_level: Some(version.scope_level.clone()), + disabled: Some(version.disabled), + }, + funs, + ctx, + ) + .await?; + FlowModelVersionServ::get_item(&editind_version_id, &FlowModelVersionFilterReq::default(), funs, ctx).await + } +} diff --git a/backend/middlewares/flow/src/serv/flow_rel_serv.rs b/backend/middlewares/flow/src/serv/flow_rel_serv.rs index 1f5b5fd74..e6de2fa77 100644 --- a/backend/middlewares/flow/src/serv/flow_rel_serv.rs +++ b/backend/middlewares/flow/src/serv/flow_rel_serv.rs @@ -29,6 +29,7 @@ pub enum FlowRelKind { FlowModelTemplate, FlowModelPath, FlowAppTemplate, + FlowModelTransition, } impl FlowRelServ { @@ -109,7 +110,6 @@ impl FlowRelServ { } pub async fn exist_rels(flow_rel_kind: &FlowRelKind, from_rbum_id: &str, to_rbum_item_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { - // TODO In-depth inspection RbumRelServ::check_simple_rel( &RbumRelSimpleFindReq { tag: Some(flow_rel_kind.to_string()), diff --git a/backend/middlewares/flow/src/serv/flow_state_serv.rs b/backend/middlewares/flow/src/serv/flow_state_serv.rs index f5b5ac239..6fa5e9a20 100644 --- a/backend/middlewares/flow/src/serv/flow_state_serv.rs +++ b/backend/middlewares/flow/src/serv/flow_state_serv.rs @@ -4,6 +4,7 @@ use bios_basic::rbum::{ dto::{ rbum_filer_dto::RbumBasicFilterReq, rbum_item_dto::{RbumItemKernelAddReq, RbumItemKernelModifyReq}, + rbum_rel_dto::RbumRelModifyReq, }, helper::rbum_scope_helper, rbum_enumeration::RbumScopeLevelKind, @@ -23,8 +24,8 @@ use tardis::{ use crate::{ domain::flow_state, dto::flow_state_dto::{ - FlowStateAddReq, FlowStateCountGroupByStateReq, FlowStateCountGroupByStateResp, FlowStateDetailResp, FlowStateFilterReq, FlowStateKind, FlowStateModifyReq, - FlowStateNameResp, FlowStateSummaryResp, FlowSysStateKind, + FlowStateAddReq, FlowStateAggResp, FlowStateCountGroupByStateReq, FlowStateCountGroupByStateResp, FlowStateDetailResp, FlowStateFilterReq, FlowStateKind, + FlowStateModifyReq, FlowStateNameResp, FlowStateRelModelExt, FlowStateRelModelModifyReq, FlowStateSummaryResp, FlowSysStateKind, }, flow_config::FlowBasicInfoManager, }; @@ -35,6 +36,7 @@ use super::{ flow_inst_serv::FlowInstServ, flow_model_serv::FlowModelServ, flow_rel_serv::{FlowRelKind, FlowRelServ}, + flow_transition_serv::FlowTransitionServ, }; pub struct FlowStateServ; @@ -54,11 +56,15 @@ impl RbumItemCrudOperation TardisResult { - let id = format!( - "{}{}", - add_req.id_prefix.as_ref().map(|prefix| format!("{}-", prefix)).unwrap_or("".to_string()), - TardisFuns::field.nanoid() - ); + let id = if let Some(id) = &add_req.id { + id.to_string() + } else { + format!( + "{}{}", + add_req.id_prefix.as_ref().map(|prefix| format!("{}-", prefix)).unwrap_or("".to_string()), + TardisFuns::field.nanoid() + ) + }; Ok(RbumItemKernelAddReq { id: Some(TrimString(id)), name: add_req.name.as_ref().unwrap_or(&TrimString("".to_string())).clone(), @@ -76,7 +82,7 @@ impl RbumItemCrudOperation TardisResult { FlowStateServ::add_item( &mut FlowStateAddReq { + id: None, id_prefix: None, name: Some(state_name.into()), icon: None, @@ -305,7 +312,7 @@ impl FlowStateServ { funs: &TardisFunsInst, ctx: &TardisContext, ) -> TardisResult> { - let mut flow_model_ids = None; + let mut flow_version_ids = None; if let Some(tenant_own_path) = rbum_scope_helper::get_path_item(1, &ctx.own_paths) { if let Some(mut app_ids) = app_ids { if let Some(app_own_paths) = app_ids.pop().map(|app_id| format!("{}/{}", &tenant_own_path, &app_id)) { @@ -313,8 +320,8 @@ impl FlowStateServ { own_paths: app_own_paths, ..ctx.clone() }; - flow_model_ids = Some( - FlowModelServ::find_rel_models(None, false, funs, &mock_ctx) + flow_version_ids = Some( + FlowModelServ::find_rel_model_map(None, true, funs, &mock_ctx) .await? .into_iter() .filter(|(current_tag, _model)| tag.is_none() || tag.clone().unwrap_or_default() == *current_tag) @@ -329,12 +336,11 @@ impl FlowStateServ { basic: RbumBasicFilterReq { ids, own_paths: Some("".to_string()), - ignore_scope: true, with_sub_own_paths: true, ..Default::default() }, tag, - flow_model_ids, + flow_version_ids, ..Default::default() }, None, @@ -382,4 +388,72 @@ impl FlowStateServ { } Ok(result.into_values().collect_vec()) } + + pub async fn get_rel_state_ext(flow_version_id: &str, state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + TardisFuns::json.str_to_obj::( + &FlowRelServ::find_simple_rels(&FlowRelKind::FlowModelState, Some(flow_version_id), Some(state_id), true, None, None, funs, ctx) + .await? + .pop() + .ok_or_else(|| funs.err().internal_error("flow_model_serv", "modify_rel_state", "rel not found", "404-rel-not-found"))? + .ext, + ) + } + + pub async fn modify_rel_state_ext(flow_version_id: &str, modify_req: &FlowStateRelModelModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let mut ext = Self::get_rel_state_ext(flow_version_id, &modify_req.id, funs, ctx).await?; + if let Some(sort) = modify_req.sort { + ext.sort = sort; + } + if let Some(show_btns) = modify_req.show_btns.clone() { + ext.show_btns = Some(show_btns); + } + FlowRelServ::modify_simple_rel( + &FlowRelKind::FlowModelState, + flow_version_id, + &modify_req.id, + &mut RbumRelModifyReq { + tag: None, + note: None, + ext: Some(json!(ext).to_string()), + }, + funs, + ctx, + ) + .await?; + + Ok(()) + } + + pub async fn aggregate(id: &str, flow_version_id: &str, init_state_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult { + let state = Self::get_item( + id, + &FlowStateFilterReq { + basic: RbumBasicFilterReq { + own_paths: Some("".to_string()), + with_sub_own_paths: true, + ignore_scope: true, + ..Default::default() + }, + ..Default::default() + }, + funs, + ctx, + ) + .await?; + let transitions = FlowTransitionServ::find_transitions(flow_version_id, Some(vec![state.id.clone()]), funs, ctx).await?; + let kind_conf = state.kind_conf(); + Ok(FlowStateAggResp { + id: state.id.clone(), + name: state.name, + is_init: state.id == *init_state_id, + sys_state: state.sys_state, + tags: state.tags, + scope_level: state.scope_level, + disabled: state.disabled, + ext: Self::get_rel_state_ext(flow_version_id, &state.id, funs, ctx).await?, + state_kind: state.state_kind, + kind_conf, + transitions, + }) + } } diff --git a/backend/middlewares/flow/src/serv/flow_transition_serv.rs b/backend/middlewares/flow/src/serv/flow_transition_serv.rs new file mode 100644 index 000000000..a362a40a3 --- /dev/null +++ b/backend/middlewares/flow/src/serv/flow_transition_serv.rs @@ -0,0 +1,468 @@ +use std::collections::HashMap; + +use bios_basic::rbum::{ + dto::rbum_filer_dto::RbumBasicFilterReq, + serv::{ + rbum_crud_serv::{ID_FIELD, NAME_FIELD, REL_DOMAIN_ID_FIELD, REL_KIND_ID_FIELD}, + rbum_item_serv::{RbumItemCrudOperation, RBUM_ITEM_TABLE}, + }, +}; +use itertools::Itertools; +use tardis::{ + basic::{dto::TardisContext, result::TardisResult}, + chrono::Utc, + db::sea_orm::{ + prelude::Expr, + sea_query::{Alias, Cond, Query, SelectStatement}, + EntityTrait, JoinType, Order, QueryFilter, Set, + }, + serde_json::json, + TardisFuns, TardisFunsInst, +}; + +use crate::{ + domain::{flow_state, flow_transition}, + dto::{ + flow_model_dto::{FlowModelFilterReq, FlowModelStatus}, + flow_transition_dto::{FlowTransitionActionChangeKind, FlowTransitionAddReq, FlowTransitionDetailResp, FlowTransitionFilterReq, FlowTransitionModifyReq}, + }, +}; + +use super::{ + flow_model_serv::FlowModelServ, + flow_rel_serv::{FlowRelKind, FlowRelServ}, + flow_state_serv::FlowStateServ, +}; + +pub struct FlowTransitionServ; + +impl FlowTransitionServ { + pub async fn add_transitions( + flow_version_id: &str, + from_flow_state_id: &str, + add_req: &[FlowTransitionAddReq], + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult<()> { + let flow_state_ids = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelState, flow_version_id, None, None, funs, ctx) + .await? + .iter() + .map(|rel| rel.rel_id.clone()) + .collect::>(); + if add_req.iter().any(|req| !flow_state_ids.contains(&from_flow_state_id.to_string()) || !flow_state_ids.contains(&req.to_flow_state_id)) { + return Err(funs.err().not_found("flow_transition", "add_transitions", "the states to be added is not legal", "404-flow-state-add-not-legal")); + } + if add_req.is_empty() { + return Ok(()); + } + let flow_transitions = add_req + .iter() + .map(|req| flow_transition::ActiveModel { + id: Set(TardisFuns::field.nanoid()), + name: Set(req.name.as_ref().map(|name| name.to_string()).unwrap_or("".to_string())), + + from_flow_state_id: Set(from_flow_state_id.to_string()), + to_flow_state_id: Set(req.to_flow_state_id.to_string()), + + transfer_by_auto: Set(req.transfer_by_auto.unwrap_or(false)), + transfer_by_timer: Set(req.transfer_by_timer.as_ref().unwrap_or(&"".to_string()).to_string()), + + guard_by_creator: Set(req.guard_by_creator.unwrap_or(false)), + guard_by_his_operators: Set(req.guard_by_his_operators.unwrap_or(false)), + guard_by_assigned: Set(req.guard_by_assigned.unwrap_or(false)), + guard_by_spec_account_ids: Set(req.guard_by_spec_account_ids.as_ref().unwrap_or(&vec![]).clone()), + guard_by_spec_role_ids: Set(req.guard_by_spec_role_ids.as_ref().unwrap_or(&vec![]).clone()), + guard_by_spec_org_ids: Set(req.guard_by_spec_org_ids.as_ref().unwrap_or(&vec![]).clone()), + guard_by_other_conds: Set(req.guard_by_other_conds.as_ref().map(|conds| TardisFuns::json.obj_to_json(conds).unwrap()).unwrap_or(json!([]))), + + vars_collect: Set(req.vars_collect.clone().unwrap_or_default()), + double_check: Set(req.double_check.clone().unwrap_or_default()), + is_notify: Set(req.is_notify.unwrap_or(true)), + + action_by_pre_callback: Set(req.action_by_pre_callback.as_ref().unwrap_or(&"".to_string()).to_string()), + action_by_post_callback: Set(req.action_by_post_callback.as_ref().unwrap_or(&"".to_string()).to_string()), + action_by_post_changes: Set(req.action_by_post_changes.clone().unwrap_or_default()), + action_by_front_changes: Set(req.action_by_front_changes.clone().unwrap_or_default()), + + rel_flow_model_version_id: Set(flow_version_id.to_string()), + sort: Set(req.sort.unwrap_or(0)), + ..Default::default() + }) + .collect_vec(); + funs.db().insert_many(flow_transitions, ctx).await + } + + pub async fn modify_transitions(flow_version_id: &str, modify_req: &[FlowTransitionModifyReq], funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { + let flow_state_ids = modify_req + .iter() + .filter(|req| req.from_flow_state_id.is_some()) + .map(|req| req.from_flow_state_id.as_ref().unwrap().to_string()) + .chain(modify_req.iter().filter(|req| req.to_flow_state_id.is_some()).map(|req| req.to_flow_state_id.as_ref().unwrap().to_string())) + .unique() + .collect_vec(); + if modify_req.iter().any(|req| { + if let Some(from_flow_state_id) = &req.from_flow_state_id { + if !flow_state_ids.contains(from_flow_state_id) { + return true; + } + } + if let Some(to_flow_state_id) = &req.to_flow_state_id { + if !flow_state_ids.contains(to_flow_state_id) { + return true; + } + } + false + }) { + return Err(funs.err().not_found( + "flow_transition", + "modify_transitions", + "the states to be added is not legal", + "404-flow-state-add-not-legal", + )); + } + + let flow_transition_ids = modify_req.iter().map(|req: &FlowTransitionModifyReq| req.id.to_string()).collect_vec(); + let flow_transition_ids_lens = flow_transition_ids.len(); + if funs + .db() + .count( + Query::select() + .column((flow_transition::Entity, flow_transition::Column::Id)) + .from(flow_transition::Entity) + .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::RelFlowModelVersionId)).eq(flow_version_id.to_string())) + .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::Id)).is_in(flow_transition_ids)), + ) + .await? as usize + != flow_transition_ids_lens + { + return Err(funs.err().not_found( + "flow_transition", + "modify_transitions", + "the transition of related models not legal", + "404-flow-transition-rel-model-not-legal", + )); + } + let model_transitions = Self::find_transitions(flow_version_id, None, funs, ctx).await?; + for req in modify_req { + let transiton = model_transitions.iter().find(|trans| trans.id == req.id.to_string()); + if transiton.is_none() { + continue; + } + + let mut flow_transition = flow_transition::ActiveModel { + id: Set(req.id.to_string()), + ..Default::default() + }; + if let Some(name) = &req.name { + flow_transition.name = Set(name.to_string()); + } + if let Some(from_flow_state_id) = &req.from_flow_state_id { + flow_transition.from_flow_state_id = Set(from_flow_state_id.to_string()); + } + if let Some(to_flow_state_id) = &req.to_flow_state_id { + flow_transition.to_flow_state_id = Set(to_flow_state_id.to_string()); + } + + if let Some(transfer_by_auto) = req.transfer_by_auto { + flow_transition.transfer_by_auto = Set(transfer_by_auto); + } + if let Some(transfer_by_timer) = &req.transfer_by_timer { + flow_transition.transfer_by_timer = Set(transfer_by_timer.to_string()); + } + + if let Some(guard_by_creator) = req.guard_by_creator { + flow_transition.guard_by_creator = Set(guard_by_creator); + } + if let Some(guard_by_his_operators) = req.guard_by_his_operators { + flow_transition.guard_by_his_operators = Set(guard_by_his_operators); + } + if let Some(guard_by_assigned) = req.guard_by_assigned { + flow_transition.guard_by_assigned = Set(guard_by_assigned); + } + if let Some(guard_by_spec_account_ids) = &req.guard_by_spec_account_ids { + flow_transition.guard_by_spec_account_ids = Set(guard_by_spec_account_ids.clone()); + } + if let Some(guard_by_spec_role_ids) = &req.guard_by_spec_role_ids { + flow_transition.guard_by_spec_role_ids = Set(guard_by_spec_role_ids.clone()); + } + if let Some(guard_by_spec_org_ids) = &req.guard_by_spec_org_ids { + flow_transition.guard_by_spec_org_ids = Set(guard_by_spec_org_ids.clone()); + } + if let Some(guard_by_other_conds) = &req.guard_by_other_conds { + flow_transition.guard_by_other_conds = Set(TardisFuns::json.obj_to_json(guard_by_other_conds)?); + } + + if let Some(vars_collect) = &req.vars_collect { + flow_transition.vars_collect = Set(vars_collect.clone()); + } + + if let Some(action_by_pre_callback) = &req.action_by_pre_callback { + flow_transition.action_by_pre_callback = Set(action_by_pre_callback.to_string()); + } + if let Some(action_by_post_callback) = &req.action_by_post_callback { + flow_transition.action_by_post_callback = Set(action_by_post_callback.to_string()); + } + if let Some(action_by_front_changes) = &req.action_by_front_changes { + flow_transition.action_by_front_changes = Set(action_by_front_changes.clone()); + } + if let Some(action_by_post_changes) = &req.action_by_post_changes { + flow_transition.action_by_post_changes = Set(action_by_post_changes.clone()); + } + if let Some(action_by_post_var_changes) = &req.action_by_post_var_changes { + let mut state_post_changes = + transiton.unwrap().action_by_post_changes().into_iter().filter(|post| post.kind == FlowTransitionActionChangeKind::State).collect_vec(); + let mut action_by_post_changes = action_by_post_var_changes.clone(); + action_by_post_changes.append(&mut state_post_changes); + flow_transition.action_by_post_changes = Set(action_by_post_changes.clone()); + } + if let Some(action_by_post_state_changes) = &req.action_by_post_state_changes { + let mut var_post_changes = transiton.unwrap().action_by_post_changes().into_iter().filter(|post| post.kind == FlowTransitionActionChangeKind::Var).collect_vec(); + let mut action_by_post_changes = action_by_post_state_changes.clone(); + action_by_post_changes.append(&mut var_post_changes); + flow_transition.action_by_post_changes = Set(action_by_post_changes.clone()); + } + if let Some(double_check) = &req.double_check { + flow_transition.double_check = Set(double_check.clone()); + } + if let Some(is_notify) = &req.is_notify { + flow_transition.is_notify = Set(*is_notify); + } + if let Some(sort) = &req.sort { + flow_transition.sort = Set(*sort); + } + flow_transition.update_time = Set(Utc::now()); + funs.db().update_one(flow_transition, ctx).await?; + } + Ok(()) + } + + pub async fn delete_transitions(flow_version_id: &str, delete_flow_transition_ids: &Vec, funs: &TardisFunsInst, _ctx: &TardisContext) -> TardisResult<()> { + let delete_flow_transition_ids_lens = delete_flow_transition_ids.len(); + if funs + .db() + .count( + Query::select() + .column((flow_transition::Entity, flow_transition::Column::Id)) + .from(flow_transition::Entity) + .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::RelFlowModelVersionId)).eq(flow_version_id.to_string())) + .and_where(Expr::col((flow_transition::Entity, flow_transition::Column::Id)).is_in(delete_flow_transition_ids)), + ) + .await? as usize + != delete_flow_transition_ids_lens + { + return Err(funs.err().not_found( + "flow_transition", + "delete_transitions", + "the transition of related models not legal", + "404-flow-transition-rel-model-not-legal", + )); + } + funs.db() + .soft_delete_custom( + flow_transition::Entity::find().filter(Expr::col(flow_transition::Column::Id).is_in(delete_flow_transition_ids)), + "id", + ) + .await?; + Ok(()) + } + + async fn package_ext_query(query: &mut SelectStatement, filter: &FlowTransitionFilterReq, _: &TardisFunsInst, _ctx: &TardisContext) -> TardisResult<()> { + let from_state_rbum_table = Alias::new("from_state_rbum"); + let from_state_table = Alias::new("from_state"); + let to_state_rbum_table = Alias::new("to_state_rbum"); + let to_state_table = Alias::new("to_state"); + query + .columns([ + (flow_transition::Entity, flow_transition::Column::Id), + (flow_transition::Entity, flow_transition::Column::Name), + (flow_transition::Entity, flow_transition::Column::FromFlowStateId), + (flow_transition::Entity, flow_transition::Column::ToFlowStateId), + (flow_transition::Entity, flow_transition::Column::TransferByAuto), + (flow_transition::Entity, flow_transition::Column::TransferByTimer), + (flow_transition::Entity, flow_transition::Column::GuardByCreator), + (flow_transition::Entity, flow_transition::Column::GuardByHisOperators), + (flow_transition::Entity, flow_transition::Column::GuardByAssigned), + (flow_transition::Entity, flow_transition::Column::GuardBySpecAccountIds), + (flow_transition::Entity, flow_transition::Column::GuardBySpecRoleIds), + (flow_transition::Entity, flow_transition::Column::GuardBySpecOrgIds), + (flow_transition::Entity, flow_transition::Column::GuardByOtherConds), + (flow_transition::Entity, flow_transition::Column::VarsCollect), + (flow_transition::Entity, flow_transition::Column::ActionByPreCallback), + (flow_transition::Entity, flow_transition::Column::ActionByPostCallback), + (flow_transition::Entity, flow_transition::Column::ActionByPostChanges), + (flow_transition::Entity, flow_transition::Column::ActionByFrontChanges), + (flow_transition::Entity, flow_transition::Column::DoubleCheck), + (flow_transition::Entity, flow_transition::Column::IsNotify), + (flow_transition::Entity, flow_transition::Column::RelFlowModelVersionId), + (flow_transition::Entity, flow_transition::Column::Sort), + ]) + .expr_as( + Expr::col((from_state_rbum_table.clone(), NAME_FIELD.clone())).if_null(""), + Alias::new("from_flow_state_name"), + ) + .expr_as(Expr::col((from_state_table.clone(), Alias::new("color"))).if_null(""), Alias::new("from_flow_state_color")) + .expr_as(Expr::col((to_state_rbum_table.clone(), NAME_FIELD.clone())).if_null(""), Alias::new("to_flow_state_name")) + .expr_as(Expr::col((to_state_table.clone(), Alias::new("color"))).if_null(""), Alias::new("to_flow_state_color")) + .from(flow_transition::Entity) + .join_as( + JoinType::LeftJoin, + RBUM_ITEM_TABLE.clone(), + from_state_rbum_table.clone(), + Cond::all() + .add(Expr::col((from_state_rbum_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::FromFlowStateId))) + .add(Expr::col((from_state_rbum_table.clone(), REL_KIND_ID_FIELD.clone())).eq(FlowStateServ::get_rbum_kind_id().unwrap())) + .add(Expr::col((from_state_rbum_table.clone(), REL_DOMAIN_ID_FIELD.clone())).eq(FlowStateServ::get_rbum_domain_id().unwrap())), + ) + .join_as( + JoinType::LeftJoin, + flow_state::Entity, + from_state_table.clone(), + Cond::all().add(Expr::col((from_state_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::FromFlowStateId))), + ) + .join_as( + JoinType::LeftJoin, + RBUM_ITEM_TABLE.clone(), + to_state_rbum_table.clone(), + Cond::all() + .add(Expr::col((to_state_rbum_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::ToFlowStateId))) + .add(Expr::col((to_state_rbum_table.clone(), REL_KIND_ID_FIELD.clone())).eq(FlowStateServ::get_rbum_kind_id().unwrap())) + .add(Expr::col((to_state_rbum_table.clone(), REL_DOMAIN_ID_FIELD.clone())).eq(FlowStateServ::get_rbum_domain_id().unwrap())), + ) + .join_as( + JoinType::LeftJoin, + flow_state::Entity, + to_state_table.clone(), + Cond::all().add(Expr::col((to_state_table.clone(), ID_FIELD.clone())).equals((flow_transition::Entity, flow_transition::Column::ToFlowStateId))), + ); + if let Some(ids) = &filter.ids { + query.and_where(Expr::col((flow_transition::Entity, flow_transition::Column::Id)).is_in(ids)); + } + if let Some(flow_version_id) = &filter.flow_version_id { + query.and_where(Expr::col((flow_transition::Entity, flow_transition::Column::RelFlowModelVersionId)).eq(flow_version_id)); + } + if let Some(specified_state_ids) = &filter.specified_state_ids { + query.and_where(Expr::col((flow_transition::Entity, flow_transition::Column::FromFlowStateId)).is_in(specified_state_ids)); + } + Ok(()) + } + + pub async fn find_detail_items( + ids: Vec, + desc_sort_by_create: Option, + desc_sort_by_update: Option, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult> { + let mut query = Query::select(); + Self::package_ext_query( + &mut query, + &FlowTransitionFilterReq { + ids: Some(ids), + specified_state_ids: None, + flow_version_id: None, + }, + funs, + ctx, + ) + .await?; + if let Some(sort) = desc_sort_by_create { + query.order_by((flow_transition::Entity, flow_transition::Column::CreateTime), if sort { Order::Desc } else { Order::Asc }); + } + if let Some(sort) = desc_sort_by_update { + query.order_by((flow_transition::Entity, flow_transition::Column::UpdateTime), if sort { Order::Desc } else { Order::Asc }); + } + funs.db().find_dtos(&query).await + } + + pub async fn find_transitions( + flow_version_id: &str, + specified_state_ids: Option>, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult> { + let mut query = Query::select(); + Self::package_ext_query( + &mut query, + &FlowTransitionFilterReq { + ids: None, + specified_state_ids, + flow_version_id: Some(flow_version_id.to_string()), + }, + funs, + ctx, + ) + .await?; + + query + .order_by((flow_transition::Entity, flow_transition::Column::Sort), Order::Asc) + .order_by((flow_transition::Entity, flow_transition::Column::CreateTime), Order::Asc) + .order_by((flow_transition::Entity, flow_transition::Column::Id), Order::Asc); + funs.db().find_dtos(&query).await + } + + pub async fn find_transitions_by_state_id( + flow_version_id: &str, + current_state_id: Option>, + target_state_id: Option>, + funs: &TardisFunsInst, + ctx: &TardisContext, + ) -> TardisResult> { + Ok(Self::find_transitions(flow_version_id, None, funs, ctx) + .await? + .into_iter() + .filter(|tran_detail| { + if let Some(target_state_id) = target_state_id.as_ref() { + target_state_id.contains(&tran_detail.to_flow_state_id) + } else { + true + } + }) + .filter(|tran_detail| { + if let Some(current_state_id) = current_state_id.as_ref() { + current_state_id.contains(&tran_detail.from_flow_state_id) + } else { + true + } + }) + .collect_vec()) + } + + // 获取动作关联模型 + pub async fn find_rel_model_map(tag: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { + let mut rel_transitons = HashMap::new(); + let rel_template_id = if let Some(app_id) = FlowModelServ::get_app_id_by_ctx(ctx) { + FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowAppTemplate, &app_id, None, None, funs, ctx).await?.pop().map(|rel| rel.rel_id) + } else { + None + }; + // 当前可用的模型id + let rel_model_ids = FlowModelServ::find_id_items( + &FlowModelFilterReq { + basic: RbumBasicFilterReq { + with_sub_own_paths: rel_template_id.is_some(), + own_paths: if rel_template_id.is_some() { Some("".to_string()) } else { None }, + enabled: Some(true), + ..Default::default() + }, + main: Some(false), + rel_template_id, + status: Some(FlowModelStatus::Enabled), + tags: Some(vec![tag.to_string()]), + ..Default::default() + }, + None, + None, + funs, + ctx, + ) + .await?; + for rel_model_id in rel_model_ids { + let transition_id = FlowRelServ::find_from_simple_rels(&FlowRelKind::FlowModelTransition, &rel_model_id, None, None, funs, ctx).await?.pop().map(|rel| rel.rel_id); + if let Some(transition_id) = transition_id { + rel_transitons.insert(transition_id, rel_model_id.clone()); + } + } + + Ok(rel_transitons) + } +} diff --git a/backend/middlewares/flow/tests/config/conf-default.toml b/backend/middlewares/flow/tests/config/conf-default.toml index 17dc128fc..5a84d7fd8 100644 --- a/backend/middlewares/flow/tests/config/conf-default.toml +++ b/backend/middlewares/flow/tests/config/conf-default.toml @@ -9,6 +9,7 @@ spi_app_id = "u001" kv = "https://127.0.0.1:8080/spi-kv" search = "https://127.0.0.1:8080/spi-search" log = "https://127.0.0.1:8080/spi-log" +iam = "https://127.0.0.1:8080/iam" [fw.web_server] port = 8080 diff --git a/backend/middlewares/flow/tests/mock_api.rs b/backend/middlewares/flow/tests/mock_api.rs index e2b87ee4b..089647683 100644 --- a/backend/middlewares/flow/tests/mock_api.rs +++ b/backend/middlewares/flow/tests/mock_api.rs @@ -1,5 +1,6 @@ use bios_mw_flow::dto::flow_external_dto::{ - FlowExternalFetchRelObjResp, FlowExternalKind, FlowExternalModifyFieldResp, FlowExternalNotifyChangesResp, FlowExternalQueryFieldResp, FlowExternalReq, RelBusObjResp, + FlowExternalDeleteRelObjResp, FlowExternalFetchRelObjResp, FlowExternalKind, FlowExternalModifyFieldResp, FlowExternalNotifyChangesResp, FlowExternalQueryFieldResp, + FlowExternalReq, RelBusObjResp, }; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -59,6 +60,9 @@ impl MockApi { FlowExternalKind::QueryField => { json!(FlowExternalQueryFieldResp { objs: vec![] }) } + FlowExternalKind::DeleteObj => { + json!(FlowExternalDeleteRelObjResp {}) + } }; MockResp::ok(result) } diff --git a/backend/middlewares/flow/tests/test_flow_api.rs b/backend/middlewares/flow/tests/test_flow_api.rs index 25fa6c3aa..64110c13d 100644 --- a/backend/middlewares/flow/tests/test_flow_api.rs +++ b/backend/middlewares/flow/tests/test_flow_api.rs @@ -15,7 +15,7 @@ use tardis::basic::field::TrimString; use tardis::basic::result::TardisResult; use tardis::tokio::time::sleep; use tardis::web::web_resp::Void; -use tardis::{testcontainers, tokio, TardisFuns}; +use tardis::{tokio, TardisFuns}; mod mock_api; mod test_flow_scenes_fsm1; diff --git a/backend/middlewares/flow/tests/test_flow_scenes_fsm1.rs b/backend/middlewares/flow/tests/test_flow_scenes_fsm1.rs index 12d54083c..01673c8d6 100644 --- a/backend/middlewares/flow/tests/test_flow_scenes_fsm1.rs +++ b/backend/middlewares/flow/tests/test_flow_scenes_fsm1.rs @@ -5,12 +5,17 @@ use bios_basic::test::test_http_client::TestHttpClient; use bios_mw_flow::dto::flow_config_dto::FlowConfigModifyReq; -use bios_mw_flow::dto::flow_inst_dto::{FlowInstDetailResp, FlowInstStartReq}; +use bios_mw_flow::dto::flow_inst_dto::{FlowInstDetailResp, FlowInstFindStateAndTransitionsReq, FlowInstFindStateAndTransitionsResp, FlowInstStartReq}; use bios_mw_flow::dto::flow_model_dto::{ - FlowModelAddReq, FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelBindStateReq, FlowModelCopyOrReferenceCiReq, FlowModelCopyOrReferenceReq, FlowModelModifyReq, - FlowModelSummaryResp, + FlowModelAddReq, FlowModelAggResp, FlowModelAssociativeOperationKind, FlowModelBindNewStateReq, FlowModelBindStateReq, FlowModelCopyOrReferenceCiReq, + FlowModelCopyOrReferenceReq, FlowModelKind, FlowModelModifyReq, FlowModelStatus, FlowModelSummaryResp, +}; +use bios_mw_flow::dto::flow_model_version_dto::{ + FlowModelVersionAddReq, FlowModelVersionBindState, FlowModelVersionDetailResp, FlowModelVersionModifyReq, FlowModelVersionModifyState, FlowModelVesionState, +}; +use bios_mw_flow::dto::flow_state_dto::{ + FlowStateAddReq, FlowStateKind, FlowStateModifyReq, FlowStateRelModelExt, FlowStateRelModelModifyReq, FlowStateSummaryResp, FlowSysStateKind, }; -use bios_mw_flow::dto::flow_state_dto::{FlowStateRelModelExt, FlowStateSummaryResp}; use bios_mw_flow::dto::flow_transition_dto::{FlowTransitionAddReq, FlowTransitionModifyReq}; use bios_sdk_invoke::clients::spi_kv_client::KvItemSummaryResp; @@ -80,16 +85,26 @@ pub async fn test(flow_client: &mut TestHttpClient, search_client: &mut TestHttp .post( "/cc/model", &FlowModelAddReq { + kind: FlowModelKind::AsTemplate, + status: FlowModelStatus::Enabled, + rel_transition_ids: None, + add_version: Some(FlowModelVersionAddReq { + name: "测试需求模板1".into(), + rel_model_id: None, + bind_states: None, + status: FlowModelVesionState::Enabled, + scope_level: Some(RbumScopeLevelKind::Private), + disabled: None, + }), + current_version_id: None, name: "测试需求模板1".into(), info: Some("xxx".to_string()), - init_state_id: "".to_string(), rel_template_ids: Some(vec![req_template_id1.to_string(), req_template_id2.to_string()]), template: true, + main: true, tag: Some("REQ".to_string()), scope_level: Some(RbumScopeLevelKind::Private), icon: None, - transitions: None, - states: None, rel_model_id: None, disabled: None, }, @@ -100,69 +115,89 @@ pub async fn test(flow_client: &mut TestHttpClient, search_client: &mut TestHttp .patch( &format!("/cc/model/{}", req_model_template_id.clone()), &FlowModelModifyReq { - init_state_id: Some(init_state_id.to_string()), - bind_states: Some(vec![ - FlowModelBindStateReq { - state_id: init_state_id.clone(), - ext: FlowStateRelModelExt { sort: 1, show_btns: None }, - }, - FlowModelBindStateReq { - state_id: processing_state_id.clone(), - ext: FlowStateRelModelExt { sort: 2, show_btns: None }, - }, - FlowModelBindStateReq { - state_id: finish_state_id.clone(), - ext: FlowStateRelModelExt { sort: 3, show_btns: None }, - }, - FlowModelBindStateReq { - state_id: closed_state_id.clone(), - ext: FlowStateRelModelExt { sort: 4, show_btns: None }, - }, - ]), - add_transitions: Some(vec![ - FlowTransitionAddReq { - from_flow_state_id: init_state_id.clone(), - to_flow_state_id: processing_state_id.clone(), - name: Some("开始".into()), - ..Default::default() - }, - FlowTransitionAddReq { - from_flow_state_id: init_state_id.clone(), - to_flow_state_id: closed_state_id.clone(), - name: Some("关闭".into()), - ..Default::default() - }, - FlowTransitionAddReq { - from_flow_state_id: processing_state_id.clone(), - to_flow_state_id: finish_state_id.clone(), - name: Some("完成".into()), - ..Default::default() - }, - FlowTransitionAddReq { - from_flow_state_id: processing_state_id.clone(), - to_flow_state_id: closed_state_id.clone(), - name: Some("关闭".into()), - ..Default::default() - }, - FlowTransitionAddReq { - from_flow_state_id: finish_state_id.clone(), - to_flow_state_id: processing_state_id.clone(), - name: Some("重新处理".into()), - ..Default::default() - }, - FlowTransitionAddReq { - from_flow_state_id: finish_state_id.clone(), - to_flow_state_id: closed_state_id.clone(), - name: Some("关闭".into()), - ..Default::default() - }, - FlowTransitionAddReq { - from_flow_state_id: closed_state_id.clone(), - to_flow_state_id: init_state_id.clone(), - name: Some("激活".into()), - ..Default::default() - }, - ]), + modify_version: Some(FlowModelVersionModifyReq { + bind_states: Some(vec![ + FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: init_state_id.clone(), + ext: FlowStateRelModelExt { sort: 1, show_btns: None }, + }), + add_transitions: Some(vec![ + FlowTransitionAddReq { + from_flow_state_id: init_state_id.clone(), + to_flow_state_id: processing_state_id.clone(), + name: Some("开始".into()), + ..Default::default() + }, + FlowTransitionAddReq { + from_flow_state_id: init_state_id.clone(), + to_flow_state_id: closed_state_id.clone(), + name: Some("关闭".into()), + ..Default::default() + }, + ]), + is_init: true, + ..Default::default() + }, + FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: processing_state_id.clone(), + ext: FlowStateRelModelExt { sort: 2, show_btns: None }, + }), + add_transitions: Some(vec![ + FlowTransitionAddReq { + from_flow_state_id: processing_state_id.clone(), + to_flow_state_id: finish_state_id.clone(), + name: Some("完成".into()), + ..Default::default() + }, + FlowTransitionAddReq { + from_flow_state_id: processing_state_id.clone(), + to_flow_state_id: closed_state_id.clone(), + name: Some("关闭".into()), + ..Default::default() + }, + ]), + ..Default::default() + }, + FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: finish_state_id.clone(), + ext: FlowStateRelModelExt { sort: 3, show_btns: None }, + }), + add_transitions: Some(vec![ + FlowTransitionAddReq { + from_flow_state_id: finish_state_id.clone(), + to_flow_state_id: processing_state_id.clone(), + name: Some("重新处理".into()), + ..Default::default() + }, + FlowTransitionAddReq { + from_flow_state_id: finish_state_id.clone(), + to_flow_state_id: closed_state_id.clone(), + name: Some("关闭".into()), + ..Default::default() + }, + ]), + ..Default::default() + }, + FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: closed_state_id.clone(), + ext: FlowStateRelModelExt { sort: 4, show_btns: None }, + }), + add_transitions: Some(vec![FlowTransitionAddReq { + from_flow_state_id: closed_state_id.clone(), + to_flow_state_id: init_state_id.clone(), + name: Some("激活".into()), + ..Default::default() + }]), + ..Default::default() + }, + ]), + init_state_id: Some(init_state_id.to_string()), + ..Default::default() + }), ..Default::default() }, ) @@ -173,14 +208,17 @@ pub async fn test(flow_client: &mut TestHttpClient, search_client: &mut TestHttp &FlowModelAddReq { name: "测试需求默认模板1".into(), info: Some("xxx".to_string()), - init_state_id: "".to_string(), + kind: FlowModelKind::AsTemplate, + status: FlowModelStatus::Enabled, + rel_transition_ids: None, + add_version: None, + current_version_id: None, rel_template_ids: None, template: true, + main: true, tag: Some("REQ".to_string()), scope_level: Some(RbumScopeLevelKind::Private), icon: None, - transitions: None, - states: None, rel_model_id: None, disabled: None, }, @@ -191,12 +229,18 @@ pub async fn test(flow_client: &mut TestHttpClient, search_client: &mut TestHttp .patch( &format!("/cc/model/{}", req_default_model_template_id.clone()), &FlowModelModifyReq { - init_state_id: Some(init_state_id.to_string()), - bind_states: Some(vec![FlowModelBindStateReq { - state_id: init_state_id.clone(), - ext: FlowStateRelModelExt { sort: 1, show_btns: None }, - }]), - add_transitions: None, + modify_version: Some(FlowModelVersionModifyReq { + init_state_id: Some(init_state_id.to_string()), + bind_states: Some(vec![FlowModelVersionBindState { + exist_state: Some(FlowModelBindStateReq { + state_id: init_state_id.clone(), + ext: FlowStateRelModelExt { sort: 1, show_btns: None }, + }), + is_init: true, + ..Default::default() + }]), + ..Default::default() + }), ..Default::default() }, ) @@ -207,21 +251,24 @@ pub async fn test(flow_client: &mut TestHttpClient, search_client: &mut TestHttp &FlowModelAddReq { name: "测试需求未初始化模板1".into(), info: Some("xxx".to_string()), - init_state_id: "".to_string(), rel_template_ids: Some(vec![req_template_id1.to_string(), req_template_id2.to_string()]), template: true, + main: true, tag: Some("REQ".to_string()), scope_level: Some(RbumScopeLevelKind::Private), icon: None, - transitions: None, - states: None, rel_model_id: None, disabled: None, + kind: FlowModelKind::AsTemplate, + status: FlowModelStatus::Enabled, + rel_transition_ids: None, + add_version: None, + current_version_id: None, }, ) .await; let req_model_uninit_template_id = req_model_uninit_template_aggs.id.clone(); - sleep(Duration::from_millis(500)).await; + sleep(Duration::from_millis(1000)).await; let model_templates: TardisPage = search_client .put( "/ci/item/search", @@ -242,10 +289,24 @@ pub async fn test(flow_client: &mut TestHttpClient, search_client: &mut TestHttp }, ) .await; - assert_eq!(model_templates.total_size, 3); - assert!(model_templates.records.iter().any(|record| record.key == req_default_model_template_id)); - assert!(model_templates.records.iter().any(|record| record.key == req_model_uninit_template_id)); - assert!(model_templates.records.iter().any(|record| record.key == req_model_template_id)); + // assert_eq!(model_templates.total_size, 3); + // assert!(model_templates.records.iter().any(|record| record.key == req_default_model_template_id)); + // assert!(model_templates.records.iter().any(|record| record.key == req_model_uninit_template_id)); + // assert!(model_templates.records.iter().any(|record| record.key == req_model_template_id)); + // template bind model + let mut rel_model_ids = HashMap::new(); + rel_model_ids.insert("REQ".to_string(), req_model_template_id.clone()); + let result: HashMap = flow_client + .post( + "/ct/model/copy_or_reference_model", + &FlowModelCopyOrReferenceReq { + rel_model_ids, + rel_template_id: Some(project_template_id1.to_string()), + op: FlowModelAssociativeOperationKind::ReferenceOrCopy, + }, + ) + .await; + info!("result: {:?}", result); let _result: Void = flow_client .patch( &format!("/cc/model/{}", req_default_model_template_id), @@ -353,21 +414,186 @@ pub async fn test(flow_client: &mut TestHttpClient, search_client: &mut TestHttp // let req_models: Vec = flow_client.get(&format!("/cc/model/find_by_rel_template_id?tag=REQ&template=true&rel_template_id={}", req_template_id1)).await; assert_eq!(req_models.len(), 4); - assert!(req_models.iter().any(|mdoel| mdoel.id == req_default_model_template_id)); - assert!(req_models.iter().any(|mdoel| mdoel.id == req_model_template_id)); - assert!(req_models.iter().all(|mdoel| mdoel.id != req_model_uninit_template_id)); + assert!(req_models.iter().any(|model| model.id == req_default_model_template_id)); + assert!(req_models.iter().any(|model| model.id == req_model_template_id)); + assert!(req_models.iter().all(|model| model.id != req_model_uninit_template_id)); let req_models: Vec = flow_client.get("/cc/model/find_by_rel_template_id?tag=REQ&template=true").await; assert_eq!(req_models.len(), 3); - assert!(req_models.iter().any(|mdoel| mdoel.id == req_default_model_template_id)); - assert!(req_models.iter().all(|mdoel| mdoel.id != req_model_template_id)); + assert!(req_models.iter().any(|model| model.id == req_default_model_template_id)); + assert!(req_models.iter().all(|model| model.id != req_model_template_id)); ctx.owner = "u001".to_string(); ctx.own_paths = "t2".to_string(); flow_client.set_auth(&ctx)?; search_client.set_auth(&ctx)?; let req_models: Vec = flow_client.get("/cc/model/find_by_rel_template_id?tag=REQ&template=true").await; assert_eq!(req_models.len(), 3); - assert!(req_models.iter().any(|mdoel| mdoel.id == req_default_model_template_id)); - assert!(req_models.iter().all(|mdoel| mdoel.id != req_model_template_id)); + assert!(req_models.iter().any(|model| model.id == req_default_model_template_id)); + assert!(req_models.iter().all(|model| model.id != req_model_template_id)); + // enter app + ctx.owner = "u001".to_string(); + ctx.own_paths = "t1/app01".to_string(); + flow_client.set_auth(&ctx)?; + search_client.set_auth(&ctx)?; + let result: HashMap = flow_client + .post( + "/ci/model/copy_or_reference_model", + &FlowModelCopyOrReferenceCiReq { + rel_template_id: Some(project_template_id1.to_string()), + op: FlowModelAssociativeOperationKind::Copy, + update_states: None, + }, + ) + .await; + info!("result: {:?}", result); + let models: HashMap = flow_client.put("/cc/model/find_rel_models?tag_ids=REQ,PROJ,ITER,TICKET&is_shared=false", &json!("")).await; + info!("models: {:?}", models); + sleep(Duration::from_millis(1000)).await; + let rel_business_obj_id = TardisFuns::field.nanoid(); + let req_inst_id1: String = flow_client + .post( + "/cc/inst", + &FlowInstStartReq { + tag: "REQ".to_string(), + create_vars: None, + rel_business_obj_id: rel_business_obj_id.clone(), + transition_id: None, + }, + ) + .await; + info!("req_inst_id1: {:?}", req_inst_id1); + let req_inst1: FlowInstDetailResp = flow_client.get(&format!("/cc/inst/{}", req_inst_id1)).await; + info!("req_inst1: {:?}", req_inst1); + let state_and_next_transitions: Vec = flow_client + .put( + "/cc/inst/batch/state_transitions", + &vec![FlowInstFindStateAndTransitionsReq { + flow_inst_id: req_inst_id1.clone(), + vars: None, + }], + ) + .await; + assert_eq!(state_and_next_transitions.len(), 1); + assert_eq!(state_and_next_transitions[0].current_flow_state_name, "待开始"); + + // 新建审批流 + let req_approval_flow: FlowModelAggResp = flow_client + .post( + "/cc/model", + &FlowModelAddReq { + kind: FlowModelKind::AsModel, + status: FlowModelStatus::Enabled, + rel_transition_ids: Some(vec!["__EDIT__".to_string()]), + add_version: None, + current_version_id: None, + name: "编辑需求审批流".into(), + info: Some("xxx".to_string()), + rel_template_ids: None, + template: false, + main: false, + tag: Some("REQ".to_string()), + scope_level: None, + icon: None, + rel_model_id: None, + disabled: None, + }, + ) + .await; + let req_approval_flow_version: FlowModelVersionDetailResp = flow_client.get(&format!("/cc/model_version/{}", req_approval_flow.edit_version_id)).await; + let start_state_id = req_approval_flow_version.states()[0].id.clone(); + let form_state_id = TardisFuns::field.nanoid(); + let finish_state_id = req_approval_flow_version.states()[1].id.clone(); + let start_transition_id = req_approval_flow_version.states()[0].transitions[0].id.clone(); + let _: Void = flow_client + .patch( + &format!("/cc/model_version/{}", req_approval_flow.edit_version_id), + &FlowModelVersionModifyReq { + bind_states: Some(vec![FlowModelVersionBindState { + bind_new_state: Some(FlowModelBindNewStateReq { + new_state: FlowStateAddReq { + id: Some(form_state_id.clone().into()), + name: Some("录入".into()), + sys_state: FlowSysStateKind::Progress, + state_kind: Some(FlowStateKind::Form), + tags: Some(vec![req_approval_flow.tag.clone()]), + ..Default::default() + }, + ext: FlowStateRelModelExt { sort: 1, show_btns: None }, + }), + add_transitions: Some(vec![FlowTransitionAddReq { + name: Some("提交".into()), + from_flow_state_id: form_state_id.clone(), + to_flow_state_id: finish_state_id.clone(), + ..Default::default() + }]), + ..Default::default() + }]), + modify_states: Some(vec![ + FlowModelVersionModifyState { + id: start_state_id.clone(), + modify_transitions: Some(vec![FlowTransitionModifyReq { + id: start_transition_id.into(), + to_flow_state_id: Some(form_state_id.clone()), + ..Default::default() + }]), + ..Default::default() + }, + FlowModelVersionModifyState { + id: finish_state_id.clone(), + modify_rel: Some(FlowStateRelModelModifyReq { + id: finish_state_id.clone(), + sort: Some(2), + show_btns: None, + }), + ..Default::default() + }, + ]), + ..Default::default() + }, + ) + .await; + let req_approval_flow_version: FlowModelVersionDetailResp = flow_client.get(&format!("/cc/model_version/{}", req_approval_flow.edit_version_id)).await; + info!( + "req_approval_flow_version: {:?}", + TardisFuns::json.obj_to_json(&req_approval_flow_version).unwrap().to_string() + ); + let _: Void = flow_client + .patch( + &format!("/cc/model_version/{}", req_approval_flow.edit_version_id), + &FlowModelVersionModifyReq { + status: Some(FlowModelVesionState::Enabled), + ..Default::default() + }, + ) + .await; + let _versions: TardisPage = flow_client.get(&format!("/cc/model_version?rel_model_id={}&page_number=1&page_size=100", req_approval_flow.id)).await; + let state_and_next_transitions: Vec = flow_client + .put( + "/cc/inst/batch/state_transitions", + &vec![FlowInstFindStateAndTransitionsReq { + flow_inst_id: req_inst_id1.clone(), + vars: None, + }], + ) + .await; + info!( + "state_and_next_transitions: {:?}", + TardisFuns::json.obj_to_json(&state_and_next_transitions).unwrap().to_string() + ); + // 启动审批流实例 + let req_inst_id2: String = flow_client + .post( + "/cc/inst", + &FlowInstStartReq { + tag: "REQ".to_string(), + create_vars: None, + rel_business_obj_id: rel_business_obj_id.clone(), + transition_id: Some("__EDIT__".to_string()), + }, + ) + .await; + sleep(Duration::from_millis(5000)).await; + let req_inst2: FlowInstDetailResp = flow_client.get(&format!("/cc/inst/{}", req_inst_id2)).await; + info!("req_inst2: {:?}", req_inst2); Ok(()) } diff --git a/backend/middlewares/schedule/Cargo.toml b/backend/middlewares/schedule/Cargo.toml index d17b491c8..60c107588 100644 --- a/backend/middlewares/schedule/Cargo.toml +++ b/backend/middlewares/schedule/Cargo.toml @@ -28,6 +28,7 @@ bios-sdk-invoke = { version = "0.2.0", path = "../../../frontend/sdks/invoke", f "spi_log", "spi_kv", "event" ], default-features = false } tsuki-scheduler = { version = "0.1.3", features= ["cron", "tokio", "async-scheduler"]} +testcontainers-modules = { workspace = true, features = ["redis"] } [dev-dependencies] tardis = { workspace = true, features = ["test", "ws-client"] } diff --git a/backend/middlewares/schedule/tests/test_basic_schedual_service.rs b/backend/middlewares/schedule/tests/test_basic_schedual_service.rs index c7d88706f..f04a7e64a 100644 --- a/backend/middlewares/schedule/tests/test_basic_schedual_service.rs +++ b/backend/middlewares/schedule/tests/test_basic_schedual_service.rs @@ -5,6 +5,9 @@ use bios_mw_schedule::{ serv::schedule_job_serv_v2::{add_or_modify, delete}, }; use std::{collections::VecDeque, env, sync::atomic::Ordering, time::Duration}; +use tardis::testcontainers::ImageExt; +use tardis::testcontainers::{core::Mount, runners::AsyncRunner}; +use testcontainers_modules::postgres::Postgres; use tardis::{ basic::result::TardisResult, @@ -23,7 +26,7 @@ fn funs() -> TardisFunsInst { async fn test_basic_schedual_service() -> TardisResult<()> { // std::env::set_current_dir("middlewares/schedule").unwrap(); std::env::set_var("RUST_LOG", "info,sqlx=off,sea_orm=INFO"); - let reldb_container = TardisTestContainer::postgres_custom(None).await?; + let reldb_container = Postgres::default().with_tag("latest").with_env_var("POSTGRES_PASSWORD", "123456").with_env_var("POSTGRES_DB", "test").start().await?; let port = reldb_container.get_host_port_ipv4(5432).await?; let url = format!("postgres://postgres:123456@127.0.0.1:{port}/test"); env::set_var("TARDIS_FW.DB.URL", url); diff --git a/backend/services/bios-all/config/locale/zh-cn.flow b/backend/services/bios-all/config/locale/zh-cn.flow index 7fe6edd7e..e2adb28cd 100644 --- a/backend/services/bios-all/config/locale/zh-cn.flow +++ b/backend/services/bios-all/config/locale/zh-cn.flow @@ -1,15 +1,19 @@ 400-flow-inst-vars-field-missing 缺少必填字段信息 +400-flow-model-duplicate 模型不能重复 409-flow-state-already-used 状态已被使用 404-external-data-url-not-exist 工作流对外接口未配置 404-flow-state-add-not-legal 要添加的状态不合法(权限错误、已禁用等) 404-flow-transition-rel-model-not-legal 转换关联的模型不合法 404-flow-inst-not-found 找不到实例 404-flow-model-not-found 找不到模型 +404-flow-state-not-found 找不到状态节点 404-flow-transition-not-found 找不到模型 404-rel-not-found 找不到关联状态 404-flow-inst-rel-model-not-found 找不到实例关联的模型 404-flow-inst-transfer-state-not-found 没有可流转的状态 +500-flow-inst-exist 实例已存在 500-external-illegal-response 对外接口响应异常 500-flow_model-prohibit-delete 当前模型不可删除 +500-flow-state-prohibit-delete 当前状态节点不可删除 500-flow-transition-endless-loop 状态流转出现死循环 409-rbum-rel-exist 状态关联已存在 \ No newline at end of file diff --git a/backend/spi/spi-object/src/api/ci/object_ci_obj_api.rs b/backend/spi/spi-object/src/api/ci/object_ci_obj_api.rs index 334195b8d..94d21b704 100644 --- a/backend/spi/spi-object/src/api/ci/object_ci_obj_api.rs +++ b/backend/spi/spi-object/src/api/ci/object_ci_obj_api.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; +use bios_basic::rbum::dto::rbum_filer_dto::RbumBasicFilterReq; use bios_basic::rbum::serv::rbum_item_serv::RbumItemCrudOperation; use bios_basic::rbum::serv::rbum_kind_serv::RbumKindServ; -use bios_basic::spi::dto::spi_bs_dto::SpiBsAddReq; +use bios_basic::spi::dto::spi_bs_dto::{SpiBsAddReq, SpiBsFilterReq}; use bios_basic::spi::serv::spi_bs_serv::SpiBsServ; use tardis::basic::field::TrimString; use tardis::web::context_extractor::TardisContextExtractor; @@ -13,7 +14,8 @@ use tardis::web::poem_openapi::param::Query; use tardis::web::web_resp::{TardisApiResult, TardisResp, Void}; use crate::dto::object_dto::{ - ClientCreateReq, ObjectBatchBuildCreatePresignUrlReq, ObjectBatchDeleteReq, ObjectCompleteMultipartUploadReq, ObjectCopyReq, ObjectInitiateMultipartUploadReq, ObjectObjPresignKind, ObjectPresignBatchViewReq + ClientCreateReq, ObjectBatchBuildCreatePresignUrlReq, ObjectBatchDeleteReq, ObjectCompleteMultipartUploadReq, ObjectCopyReq, ObjectInitiateMultipartUploadReq, + ObjectObjPresignKind, ObjectPresignBatchViewReq, }; use crate::object_constants; use crate::serv::object_obj_serv; @@ -191,7 +193,18 @@ impl ObjectCiObjApi { #[oai(path = "/presign/batch_view", method = "post")] async fn batch_presign_view_obj_url(&self, req: Json, ctx: TardisContextExtractor) -> TardisApiResult> { let funs = crate::get_tardis_inst(); - let url = object_obj_serv::batch_get_presign_obj_url(req.0.object_path, req.0.expire_sec, req.0.private, req.0.special, req.0.obj_exp, req.0.bucket, req.0.bs_id, &funs, &ctx.0).await?; + let url = object_obj_serv::batch_get_presign_obj_url( + req.0.object_path, + req.0.expire_sec, + req.0.private, + req.0.special, + req.0.obj_exp, + req.0.bucket, + req.0.bs_id, + &funs, + &ctx.0, + ) + .await?; TardisResp::ok(url) } @@ -313,20 +326,42 @@ impl ObjectCiObjApi { /// /// 添加自定义服务实例 #[oai(path = "/bs/add", method = "post")] - async fn bs_add(&self, add_req: Json, ctx: TardisContextExtractor,) -> TardisApiResult { + async fn bs_add(&self, add_req: Json, ctx: TardisContextExtractor) -> TardisApiResult { let mut funs = crate::get_tardis_inst(); funs.begin().await?; - let kind_id = RbumKindServ::get_rbum_kind_id_by_code(object_constants::SPI_S3_KIND_CODE, &funs).await?.expect("missing event kind"); - let result = SpiBsServ::add_item(&mut SpiBsAddReq { - name: add_req.0.name, - kind_id: TrimString::from(kind_id), - conn_uri: add_req.0.conn_uri, - ak: add_req.0.ak, - sk: add_req.0.sk, - ext: add_req.0.ext, - private: true, - disabled: None, - }, &funs, &ctx.0).await?; + let kind_id = RbumKindServ::get_rbum_kind_id_by_code(&add_req.0.kind, &funs).await?.expect("missing event kind"); + let result = if let Some(bs) = SpiBsServ::find_one_item( + &SpiBsFilterReq { + basic: RbumBasicFilterReq { + name: Some(add_req.0.name.to_string()), + ..Default::default() + }, + kind_id: Some(kind_id.clone()), + ..Default::default() + }, + &funs, + &ctx.0, + ) + .await? + { + bs.id + } else { + SpiBsServ::add_item( + &mut SpiBsAddReq { + name: add_req.0.name, + kind_id: TrimString::from(kind_id), + conn_uri: add_req.0.conn_uri, + ak: add_req.0.ak, + sk: add_req.0.sk, + ext: add_req.0.ext, + private: true, + disabled: None, + }, + &funs, + &ctx.0, + ) + .await? + }; funs.commit().await?; TardisResp::ok(result) } diff --git a/backend/spi/spi-object/src/dto/object_dto.rs b/backend/spi/spi-object/src/dto/object_dto.rs index 2349b9b50..050ea6788 100644 --- a/backend/spi/spi-object/src/dto/object_dto.rs +++ b/backend/spi/spi-object/src/dto/object_dto.rs @@ -99,9 +99,10 @@ pub struct ObjectPresignBatchViewReq { #[derive(poem_openapi::Object, Serialize, Deserialize, Debug)] pub struct ClientCreateReq { + pub kind: String, pub name: TrimString, pub conn_uri: String, pub ak: TrimString, pub sk: TrimString, pub ext: String, -} \ No newline at end of file +} diff --git a/backend/spi/spi-object/src/serv.rs b/backend/spi/spi-object/src/serv.rs index c79bff264..6dbfd62ea 100644 --- a/backend/spi/spi-object/src/serv.rs +++ b/backend/spi/spi-object/src/serv.rs @@ -1,4 +1,4 @@ +pub mod custom_s3; pub mod object_obj_serv; pub mod obs; pub mod s3; -pub mod custom_s3; diff --git a/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_initializer.rs b/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_initializer.rs index b572df565..ed35820a0 100644 --- a/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_initializer.rs +++ b/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_initializer.rs @@ -1,7 +1,12 @@ use std::collections::HashMap; use bios_basic::spi::{dto::spi_bs_dto::SpiBsCertResp, spi_funs::SpiBsInst}; -use tardis::{basic::{dto::TardisContext, error::TardisError, result::TardisResult}, config::config_dto::OSModuleConfig, os::os_client::TardisOSClient, TardisFuns}; +use tardis::{ + basic::{dto::TardisContext, error::TardisError, result::TardisResult}, + config::config_dto::OSModuleConfig, + os::os_client::TardisOSClient, + TardisFuns, +}; use tardis::serde_json::Value as JsonValue; @@ -19,6 +24,8 @@ pub async fn init(bs_cert: &SpiBsCertResp, _ctx: &TardisContext, _mgr: bool) -> .ok_or_else(|| TardisError::bad_request("Tardis context ext should have a `region` field with type string", "400-spi-invalid-tardis-ctx"))?; let tardis_os_config = OSModuleConfig::builder().kind("s3").endpoint(&bs_cert.conn_uri).ak(&bs_cert.ak).sk(&bs_cert.sk).region(region).default_bucket(default_bucket).build(); let client = TardisOSClient::init(&tardis_os_config)?; - Ok(SpiBsInst { client: Box::new(client), ext: HashMap::new() }) + Ok(SpiBsInst { + client: Box::new(client), + ext: HashMap::new(), + }) } - diff --git a/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_obj_serv.rs b/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_obj_serv.rs index e3a9ffcf0..db548ba7d 100644 --- a/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_obj_serv.rs +++ b/backend/spi/spi-object/src/serv/custom_s3/object_custom_s3_obj_serv.rs @@ -3,8 +3,8 @@ use bios_basic::spi::dto::spi_bs_dto::SpiBsDetailResp; use bios_basic::spi::spi_constants; use bios_basic::spi::spi_funs::SpiBsInst; use std::collections::HashMap; -use std::sync::OnceLock; use std::sync::Arc; +use std::sync::OnceLock; use tardis::{ basic::{dto::TardisContext, result::TardisResult}, @@ -31,14 +31,19 @@ impl CustomS3Service { return Ok(inst); } } - let mut spi_bs_inst = crate::serv::custom_s3::object_custom_s3_initializer::init(&SpiBsCertResp { - kind_code: bs_cert.kind_code.clone(), - conn_uri: bs_cert.conn_uri.clone(), - ak: bs_cert.ak.clone(), - sk: bs_cert.sk.clone(), - ext: bs_cert.ext.clone(), - private: bs_cert.private, - }, ctx, true).await?; + let mut spi_bs_inst = crate::serv::custom_s3::object_custom_s3_initializer::init( + &SpiBsCertResp { + kind_code: bs_cert.kind_code.clone(), + conn_uri: bs_cert.conn_uri.clone(), + ak: bs_cert.ak.clone(), + sk: bs_cert.sk.clone(), + ext: bs_cert.ext.clone(), + private: bs_cert.private, + }, + ctx, + true, + ) + .await?; { let mut write = Self::get_custom_bs_caches().write().await; spi_bs_inst.ext.insert(spi_constants::SPI_KIND_CODE_FLAG.to_string(), bs_cert.kind_code.clone()); @@ -49,4 +54,4 @@ impl CustomS3Service { static CUSTOM_BS_CACHES: OnceLock>>> = OnceLock::new(); CUSTOM_BS_CACHES.get_or_init(Default::default) } -} \ No newline at end of file +} diff --git a/backend/spi/spi-object/src/serv/object_obj_serv.rs b/backend/spi/spi-object/src/serv/object_obj_serv.rs index 20067c266..df73fd8d1 100644 --- a/backend/spi/spi-object/src/serv/object_obj_serv.rs +++ b/backend/spi/spi-object/src/serv/object_obj_serv.rs @@ -34,11 +34,41 @@ pub async fn presign_obj_url( match inst.kind_code() { #[cfg(feature = "spi-s3")] object_constants::SPI_S3_KIND_CODE => { - s3::object_s3_obj_serv::S3Service::presign_obj_url(presign_kind, object_path, max_width, max_height, exp_secs, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + s3::object_s3_obj_serv::S3Service::presign_obj_url( + presign_kind, + object_path, + max_width, + max_height, + exp_secs, + private, + special, + obj_exp, + bs_id.as_deref(), + bucket.as_deref(), + funs, + ctx, + &inst, + ) + .await } #[cfg(feature = "spi-s3")] object_constants::SPI_OBS_KIND_CODE => { - obs::object_obs_obj_serv::OBSService::presign_obj_url(presign_kind, object_path, max_width, max_height, exp_secs, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + obs::object_obs_obj_serv::OBSService::presign_obj_url( + presign_kind, + object_path, + max_width, + max_height, + exp_secs, + private, + special, + obj_exp, + bs_id.as_deref(), + bucket.as_deref(), + funs, + ctx, + &inst, + ) + .await } kind_code => Err(funs.bs_not_implemented(kind_code)), } @@ -59,11 +89,24 @@ pub async fn batch_get_presign_obj_url( match inst.kind_code() { #[cfg(feature = "spi-s3")] object_constants::SPI_S3_KIND_CODE => { - s3::object_s3_obj_serv::S3Service::batch_get_presign_obj_url(object_paths, exp_secs, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + s3::object_s3_obj_serv::S3Service::batch_get_presign_obj_url(object_paths, exp_secs, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst) + .await } #[cfg(feature = "spi-s3")] object_constants::SPI_OBS_KIND_CODE => { - obs::object_obs_obj_serv::OBSService::batch_get_presign_obj_url(object_paths, exp_secs, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + obs::object_obs_obj_serv::OBSService::batch_get_presign_obj_url( + object_paths, + exp_secs, + private, + special, + obj_exp, + bs_id.as_deref(), + bucket.as_deref(), + funs, + ctx, + &inst, + ) + .await } kind_code => Err(funs.bs_not_implemented(kind_code)), } @@ -74,11 +117,33 @@ pub async fn initiate_multipart_upload(req: ObjectInitiateMultipartUploadReq, fu match inst.kind_code() { #[cfg(feature = "spi-s3")] object_constants::SPI_S3_KIND_CODE => { - s3::object_s3_obj_serv::S3Service::initiate_multipart_upload(&req.object_path, req.content_type, req.private, req.special, req.bs_id.as_deref(), req.bucket.as_deref(), funs, ctx, &inst).await + s3::object_s3_obj_serv::S3Service::initiate_multipart_upload( + &req.object_path, + req.content_type, + req.private, + req.special, + req.bs_id.as_deref(), + req.bucket.as_deref(), + funs, + ctx, + &inst, + ) + .await } #[cfg(feature = "spi-s3")] object_constants::SPI_OBS_KIND_CODE => { - obs::object_obs_obj_serv::OBSService::initiate_multipart_upload(&req.object_path, req.content_type, req.private, req.special, req.bs_id.as_deref(), req.bucket.as_deref(), funs, ctx, &inst).await + obs::object_obs_obj_serv::OBSService::initiate_multipart_upload( + &req.object_path, + req.content_type, + req.private, + req.special, + req.bs_id.as_deref(), + req.bucket.as_deref(), + funs, + ctx, + &inst, + ) + .await } kind_code => Err(funs.bs_not_implemented(kind_code)), } @@ -130,11 +195,35 @@ pub async fn complete_multipart_upload(req: ObjectCompleteMultipartUploadReq, fu match inst.kind_code() { #[cfg(feature = "spi-s3")] object_constants::SPI_S3_KIND_CODE => { - s3::object_s3_obj_serv::S3Service::complete_multipart_upload(&req.object_path, &req.upload_id, req.parts, req.private, req.special, req.bs_id.as_deref(), req.bucket.as_deref(), funs, ctx, &inst).await + s3::object_s3_obj_serv::S3Service::complete_multipart_upload( + &req.object_path, + &req.upload_id, + req.parts, + req.private, + req.special, + req.bs_id.as_deref(), + req.bucket.as_deref(), + funs, + ctx, + &inst, + ) + .await } #[cfg(feature = "spi-s3")] object_constants::SPI_OBS_KIND_CODE => { - obs::object_obs_obj_serv::OBSService::complete_multipart_upload(&req.object_path, &req.upload_id, req.parts, req.private, req.special, req.bs_id.as_deref(), req.bucket.as_deref(), funs, ctx, &inst).await + obs::object_obs_obj_serv::OBSService::complete_multipart_upload( + &req.object_path, + &req.upload_id, + req.parts, + req.private, + req.special, + req.bs_id.as_deref(), + req.bucket.as_deref(), + funs, + ctx, + &inst, + ) + .await } kind_code => Err(funs.bs_not_implemented(kind_code)), } @@ -153,9 +242,13 @@ pub async fn object_delete( let inst = get_bs(bs_id.clone(), None, funs, ctx).await?; match inst.kind_code() { #[cfg(feature = "spi-s3")] - object_constants::SPI_S3_KIND_CODE => s3::object_s3_obj_serv::S3Service::object_delete(&object_path, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await, + object_constants::SPI_S3_KIND_CODE => { + s3::object_s3_obj_serv::S3Service::object_delete(&object_path, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + } #[cfg(feature = "spi-s3")] - object_constants::SPI_OBS_KIND_CODE => obs::object_obs_obj_serv::OBSService::object_delete(&object_path, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await, + object_constants::SPI_OBS_KIND_CODE => { + obs::object_obs_obj_serv::OBSService::object_delete(&object_path, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + } kind_code => Err(funs.bs_not_implemented(kind_code)), } } @@ -173,14 +266,27 @@ pub async fn batch_object_delete( let inst = get_bs(bs_id.clone(), None, funs, ctx).await?; match inst.kind_code() { #[cfg(feature = "spi-s3")] - object_constants::SPI_S3_KIND_CODE => s3::object_s3_obj_serv::S3Service::batch_object_delete(object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await, + object_constants::SPI_S3_KIND_CODE => { + s3::object_s3_obj_serv::S3Service::batch_object_delete(object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + } #[cfg(feature = "spi-s3")] - object_constants::SPI_OBS_KIND_CODE => obs::object_obs_obj_serv::OBSService::batch_object_delete(object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await, + object_constants::SPI_OBS_KIND_CODE => { + obs::object_obs_obj_serv::OBSService::batch_object_delete(object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + } kind_code => Err(funs.bs_not_implemented(kind_code)), } } -pub async fn object_copy(from: String, to: String, private: Option, special: Option, bucket: Option, bs_id: Option, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> { +pub async fn object_copy( + from: String, + to: String, + private: Option, + special: Option, + bucket: Option, + bs_id: Option, + funs: &TardisFunsInst, + ctx: &TardisContext, +) -> TardisResult<()> { let mock_ctx = TardisContext { ext: Arc::new(RwLock::new(HashMap::from([(USE_REGION_ENDPOINT.to_string(), "true".to_string())]))), ..ctx.clone() @@ -188,9 +294,13 @@ pub async fn object_copy(from: String, to: String, private: Option, specia let inst = get_bs(bs_id.clone(), Some(USE_REGION_ENDPOINT.to_string()), funs, &mock_ctx).await?; match inst.kind_code() { #[cfg(feature = "spi-s3")] - object_constants::SPI_S3_KIND_CODE => s3::object_s3_obj_serv::S3Service::object_copy(&from, &to, private, special, bs_id.as_deref(), bucket.as_deref(), funs, &mock_ctx, &inst).await, + object_constants::SPI_S3_KIND_CODE => { + s3::object_s3_obj_serv::S3Service::object_copy(&from, &to, private, special, bs_id.as_deref(), bucket.as_deref(), funs, &mock_ctx, &inst).await + } #[cfg(feature = "spi-s3")] - object_constants::SPI_OBS_KIND_CODE => obs::object_obs_obj_serv::OBSService::object_copy(&from, &to, private, special, bs_id.as_deref(), bucket.as_deref(), funs, &mock_ctx, &inst).await, + object_constants::SPI_OBS_KIND_CODE => { + obs::object_obs_obj_serv::OBSService::object_copy(&from, &to, private, special, bs_id.as_deref(), bucket.as_deref(), funs, &mock_ctx, &inst).await + } kind_code => Err(funs.bs_not_implemented(kind_code)), } } @@ -208,14 +318,18 @@ pub async fn object_exist( let inst = get_bs(bs_id.clone(), None, funs, ctx).await?; match inst.kind_code() { #[cfg(feature = "spi-s3")] - object_constants::SPI_S3_KIND_CODE => s3::object_s3_obj_serv::S3Service::object_exist(&object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await, + object_constants::SPI_S3_KIND_CODE => { + s3::object_s3_obj_serv::S3Service::object_exist(&object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + } #[cfg(feature = "spi-s3")] - object_constants::SPI_OBS_KIND_CODE => obs::object_obs_obj_serv::OBSService::object_exist(&object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await, + object_constants::SPI_OBS_KIND_CODE => { + obs::object_obs_obj_serv::OBSService::object_exist(&object_paths, private, special, obj_exp, bs_id.as_deref(), bucket.as_deref(), funs, ctx, &inst).await + } kind_code => Err(funs.bs_not_implemented(kind_code)), } } -async fn get_bs(spi_bs_id: Option, custom_cache_key: Option, funs: &TardisFunsInst, ctx: &TardisContext,) -> TardisResult> { +async fn get_bs(spi_bs_id: Option, custom_cache_key: Option, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult> { if let Some(spi_bs_id) = spi_bs_id { let spi_bs = SpiBsServ::get_bs(&spi_bs_id, funs, ctx).await?; CustomS3Service::get_bs(&spi_bs, ctx).await diff --git a/backend/spi/spi-object/src/serv/obs/object_obs_obj_serv.rs b/backend/spi/spi-object/src/serv/obs/object_obs_obj_serv.rs index f55dd8661..063e5b667 100644 --- a/backend/spi/spi-object/src/serv/obs/object_obs_obj_serv.rs +++ b/backend/spi/spi-object/src/serv/obs/object_obs_obj_serv.rs @@ -19,8 +19,17 @@ impl S3 for OBSService { } // - async fn object_copy(from: &str, to: &str, private: Option, special: Option, bs_id: Option<&str>, - bucket: Option<&str>, _funs: &TardisFunsInst, _ctx: &TardisContext, inst: &SpiBsInst) -> TardisResult<()> { + async fn object_copy( + from: &str, + to: &str, + private: Option, + special: Option, + bs_id: Option<&str>, + bucket: Option<&str>, + _funs: &TardisFunsInst, + _ctx: &TardisContext, + inst: &SpiBsInst, + ) -> TardisResult<()> { let bs_inst = inst.inst::(); let client = bs_inst.0; let bucket_name = Self::get_bucket_name(private, special, None, bucket, bs_id, inst).unwrap_or_default(); diff --git a/backend/spi/spi-object/src/serv/s3.rs b/backend/spi/spi-object/src/serv/s3.rs index 0764f3380..e00032999 100644 --- a/backend/spi/spi-object/src/serv/s3.rs +++ b/backend/spi/spi-object/src/serv/s3.rs @@ -35,7 +35,7 @@ pub trait S3 { ) -> TardisResult { let bs_inst = inst.inst::(); let client = bs_inst.0; - let bucket_name = Self::get_bucket_name(private, special, obj_exp.map(|_| true),bucket, bs_id, inst); + let bucket_name = Self::get_bucket_name(private, special, obj_exp.map(|_| true), bucket, bs_id, inst); let path = Self::rebuild_path(bucket_name.as_deref(), object_path, obj_exp, client).await?; match presign_kind { ObjectObjPresignKind::Upload => client.object_create_url(&path, exp_secs, bucket_name.as_deref()).await, @@ -114,8 +114,17 @@ pub trait S3 { Ok(failed_object_paths.into_iter().filter(|object_path| !object_path.is_empty()).collect_vec()) } - async fn object_copy(from: &str, to: &str, private: Option, special: Option, bs_id: Option<&str>, - bucket: Option<&str>, _funs: &TardisFunsInst, _ctx: &TardisContext, inst: &SpiBsInst) -> TardisResult<()> { + async fn object_copy( + from: &str, + to: &str, + private: Option, + special: Option, + bs_id: Option<&str>, + bucket: Option<&str>, + _funs: &TardisFunsInst, + _ctx: &TardisContext, + inst: &SpiBsInst, + ) -> TardisResult<()> { let bs_inst = inst.inst::(); let client = bs_inst.0; let bucket_name = Self::get_bucket_name(private, special, None, bucket, bs_id, inst); @@ -156,7 +165,22 @@ pub trait S3 { object_paths .into_iter() .map(|object_path| async move { - let result = Self::presign_obj_url(ObjectObjPresignKind::View, &object_path, None, None, exp_secs, private, special, obj_exp, bs_id, bucket, _funs, _ctx, inst).await; + let result = Self::presign_obj_url( + ObjectObjPresignKind::View, + &object_path, + None, + None, + exp_secs, + private, + special, + obj_exp, + bs_id, + bucket, + _funs, + _ctx, + inst, + ) + .await; if let Ok(presign_obj_url) = result { (object_path.to_string(), presign_obj_url) } else { diff --git a/backend/spi/spi-search/tests/init_search_container.rs b/backend/spi/spi-search/tests/init_search_container.rs index 451e6137b..8911a1972 100644 --- a/backend/spi/spi-search/tests/init_search_container.rs +++ b/backend/spi/spi-search/tests/init_search_container.rs @@ -65,7 +65,6 @@ pub async fn postgres_custom<'a>(init_script_path: Option<&str>) -> ContainerAsy .start() .await .expect("zhparser started") - // .with_volume(path, "/docker-entrypoint-initdb.d/") } else { GenericImage::new("abcfy2/zhparser", "15") .with_wait_for(WaitFor::message_on_stderr("database system is ready to accept connections")) diff --git a/backend/supports/reach/tests/test_reach_common.rs b/backend/supports/reach/tests/test_reach_common.rs index 193ef16b4..e053ad536 100644 --- a/backend/supports/reach/tests/test_reach_common.rs +++ b/backend/supports/reach/tests/test_reach_common.rs @@ -12,7 +12,8 @@ use bios_reach::reach_send_channel::SendChannelMap; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::{Arc, OnceLock}; -use tardis::testcontainers::ContainerAsync; +use tardis::testcontainers::runners::AsyncRunner; +use tardis::testcontainers::{ContainerAsync, ImageExt}; use tardis::tokio::sync::RwLock; use tardis::web::poem_openapi::param::Path; use tardis::web::poem_openapi::payload::{Form, Json}; @@ -47,7 +48,7 @@ pub fn get_test_ctx() -> &'static TardisContext { #[allow(dead_code)] pub async fn init_tardis() -> TardisResult { - let reldb_container = TardisTestContainer::postgres_custom(None).await?; + let reldb_container = Postgres::default().with_tag("latest").with_env_var("POSTGRES_PASSWORD", "123456").with_env_var("POSTGRES_DB", "test").start().await?; let port = reldb_container.get_host_port_ipv4(5432).await?; let url = format!("postgres://postgres:123456@127.0.0.1:{port}/test"); std::env::set_var("TARDIS_FW.DB.URL", url); From 837c4217ebdaa299f78ab9743515c5c47f68084a Mon Sep 17 00:00:00 2001 From: ZzIsGod1019 <1498852723@qq.com> Date: Mon, 2 Dec 2024 16:26:27 +0800 Subject: [PATCH 2/3] flow: modify comments (#867) --- backend/middlewares/flow/src/dto/flow_state_dto.rs | 4 ++-- backend/middlewares/flow/src/serv/flow_inst_serv.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/middlewares/flow/src/dto/flow_state_dto.rs b/backend/middlewares/flow/src/dto/flow_state_dto.rs index 525060a67..e83e14e6f 100644 --- a/backend/middlewares/flow/src/dto/flow_state_dto.rs +++ b/backend/middlewares/flow/src/dto/flow_state_dto.rs @@ -230,9 +230,9 @@ pub struct FlowNodifyConf { pub guard_custom: bool, /// 权限配置:自定义配置 pub guard_custom_conf: Option, - /// 通知方式:短信通知 + /// 通知方式:短信 pub send_sms: bool, - /// 通知方式:邮箱通知 + /// 通知方式:邮箱 pub send_mail: bool, } diff --git a/backend/middlewares/flow/src/serv/flow_inst_serv.rs b/backend/middlewares/flow/src/serv/flow_inst_serv.rs index e5c365e7b..2d9e3c684 100644 --- a/backend/middlewares/flow/src/serv/flow_inst_serv.rs +++ b/backend/middlewares/flow/src/serv/flow_inst_serv.rs @@ -1463,7 +1463,7 @@ impl FlowInstServ { ..Default::default() }) .collect_vec(); - FlowExternalServ::do_modify_field( + FlowExternalServ::do_async_modify_field( &flow_inst_detail.tag, None, &flow_inst_detail.rel_business_obj_id, From e6e16c7888840e2fbf619443b892f787e50537b8 Mon Sep 17 00:00:00 2001 From: 4t145 Date: Mon, 2 Dec 2024 16:44:07 +0800 Subject: [PATCH 3/3] Update Cargo.toml pump up tardis version --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4291b36ec..3fb71096a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,9 +63,9 @@ rust_decimal_macros = { version = "1" } testcontainers-modules = { version = "0.11", features = ["redis"] } strum = { version = "0.26", features = ["derive"] } # tardis -tardis = { version = "0.1.0-rc.17" } +# tardis = { version = "0.1.0-rc.17" } # tardis = { version = "0.2.0", path = "../tardis/tardis" } -# tardis = { git = "https://github.com/ideal-world/tardis.git", rev = "9cc9b3e" } +tardis = { git = "https://github.com/ideal-world/tardis.git", rev = "b6a63ac" } # asteroid-mq = { git = "https://github.com/4t145/asteroid-mq.git", rev = "d59c64d" } asteroid-mq = { git = "https://github.com/4t145/asteroid-mq.git", rev = "23cc708" } # asteroid-mq = { version = "0.1.0-alpha.5" }