// facade actions:design(9) + surrogate(5) + stats(3) + highLight/bizData(视图)
// ═══ 流程设计(需扩展仓储)═══
///|
fn[R : @spi.ProcessRepository, E] Facade::ext(self : Facade[R, E]) -> E {
self.ext_repo
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_page(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let page_num = arg_i64_or(args, "pageNum", 1L)
let page_size = arg_i64_or(args, "pageSize", 20L)
let filters = facade_parse_m_filters(args)
let page = (self.ext()).page_designs(@model.PageQuery::new(page_num, page_size))
finalize_page(page, design_to_json, filters, page_num, page_size)
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_detail(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let id = match arg_i64(args, "id") {
Some(v) => v
None => raise @error.Business("缺少id参数")
}
let design = match (self.ext()).find_design_by_id(id) {
Some(d) => d
None => raise @error.Business("流程设计不存在")
}
let his = (self.ext()).list_design_his(id)
let json_object : Json? = if his.is_empty() {
None
} else {
parse_graph(his[0].content)
}
// issues/07:jsonObject 缺基本信息时从设计表补齐
let o = @json.empty_object()
@json.obj_set(o, "id", i64_json(design.id))
@json.obj_set(o, "name", @json.string_of(design.name))
@json.obj_set(o, "displayName", @json.string_of(design.display_name))
@json.obj_set(o, "type", @json.string_of(design.design_type))
@json.obj_set(o, "icon", opt_json(design.icon))
@json.obj_set(o, "isDeployed", num_json(design.is_deployed))
@json.obj_set(o, "remark", opt_json(design.remark))
let job = match json_object {
Some(j) => j
None => @json.empty_object()
}
if @json.get_str(job, "name") is None {
@json.obj_set(job, "name", @json.string_of(design.name))
}
if @json.get_str(job, "displayName") is None {
@json.obj_set(job, "displayName", @json.string_of(design.display_name))
}
if @json.get_str(job, "type") is None {
@json.obj_set(job, "type", @json.string_of(design.design_type))
}
if @json.get_i64(job, "processDesignId") is None {
@json.obj_set(job, "processDesignId", i64_json(design.id))
}
@json.obj_set(o, "jsonObject", job)
let his_arr = @json.empty_array()
for h in his {
@json.arr_push(his_arr, design_his_to_json(h))
}
@json.obj_set(o, "his", his_arr)
o
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_save(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let operator = operator_arg(args)
let id = arg_i64(args, "id")
let design : @model.ProcessDesign = match id {
None => {
let d : @model.ProcessDesign = {
id: 0L,
name: arg_str(args, "name").unwrap_or(""),
display_name: arg_str(args, "displayName").unwrap_or(""),
design_type: arg_str_or(args, "type", "approval"),
icon: arg_str(args, "icon"),
is_deployed: 0,
remark: arg_str(args, "remark"),
create_time: Some(@model.current_time_str()),
create_user: Some(operator),
update_time: None,
update_user: Some(operator),
}
d
}
Some(i) => {
let d = match ext.find_design_by_id(i) {
Some(x) => x
None => raise @error.Business("流程设计不存在")
}
match arg_str(args, "displayName") {
Some(v) => d.display_name = v
None => ()
}
match arg_str(args, "type") {
Some(v) => d.design_type = v
None => ()
}
match arg_str(args, "icon") {
Some(v) => d.icon = Some(v)
None => ()
}
match arg_str(args, "remark") {
Some(v) => d.remark = Some(v)
None => ()
}
d.update_user = Some(operator)
// 内容快照变更 → 置为未部署(issues/08)
if content_bytes(args) is Some(_) {
d.is_deployed = 0
}
d
}
}
if id is None {
ext.save_design(design)
} else {
ext.update_design(design)
}
// 内容快照存历史表
match content_bytes(args) {
Some(content) => {
let his : @model.ProcessDesignHis = {
id: 0L,
process_design_id: design.id,
content,
create_time: Some(@model.current_time_str()),
create_user: Some(operator),
}
ext.save_design_his(his)
}
None => ()
}
let o = @json.empty_object()
@json.obj_set(o, "id", i64_json(design.id))
o
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_update(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let id = match arg_i64(args, "id") {
Some(v) => v
None => raise @error.Business("缺少id参数")
}
let design = match ext.find_design_by_id(id) {
Some(d) => d
None => raise @error.Business("流程设计不存在")
}
match arg_str(args, "name") {
Some(v) => design.name = v
None => ()
}
match arg_str(args, "displayName") {
Some(v) => design.display_name = v
None => ()
}
match arg_str(args, "type") {
Some(v) => design.design_type = v
None => ()
}
match arg_str(args, "icon") {
Some(v) => design.icon = Some(v)
None => ()
}
match arg_str(args, "remark") {
Some(v) => design.remark = Some(v)
None => ()
}
design.update_user = Some(arg_str_or(args, "operator", "system"))
ext.update_design(design)
@json.null_json()
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_update_define(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let design_id = match arg_i64(args, "processDesignId") {
Some(v) => v
None => raise @error.Business("processDesignId 缺失")
}
let design = match ext.find_design_by_id(design_id) {
Some(d) => d
None => raise @error.Business("流程设计不存在")
}
let content = match content_bytes(args) {
Some(c) => c
None => raise @error.Business("content 缺失")
}
// 与最新快照相同则不重复入库(java 同款)
let his = ext.list_design_his(design_id)
let same = if his.is_empty() {
false
} else {
his[0].content == content
}
if !same {
let h : @model.ProcessDesignHis = {
id: 0L,
process_design_id: design_id,
content,
create_time: Some(@model.current_time_str()),
create_user: Some(arg_str_or(args, "operator", "system")),
}
ext.save_design_his(h)
}
// 同步基本信息 + 置未部署
// parse_model 是 `-> ProcessModel raise JeeflowError`:解析失败 ⇒ 三个字段保持原值
//(旧 Err ⇒ () 同一条落空路径)
try {
let model = @parser.parse_model(content)
design.name = model.name
design.display_name = model.display_name
design.design_type = model.model_type
} catch {
_ => ()
}
design.is_deployed = 0
design.update_user = Some(arg_str_or(args, "operator", "system"))
ext.update_design(design)
@json.null_json()
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_remove(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
for id in arg_ids(args) {
(self.ext()).remove_design(id)
}
@json.null_json()
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_deploy(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let design_id = match arg_i64(args, "id") {
Some(v) => v
None => raise @error.Business("缺少id参数")
}
let design = match ext.find_design_by_id(design_id) {
Some(d) => d
None => raise @error.Business("流程设计不存在")
}
let his = ext.list_design_his(design_id)
if his.is_empty() {
raise @error.Business("流程设计没有内容,无法发布")
}
let content = his[0].content
let model = @parser.parse_model(content)
let define_id = self.save_deployed_define(model, content)
design.is_deployed = 1
design.update_user = Some(arg_str_or(args, "operator", "system"))
ext.update_design(design)
let o = @json.empty_object()
@json.obj_set(o, "processDefineId", i64_json(define_id))
o
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_redeploy(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let design_id = match arg_i64(args, "id") {
Some(v) => v
None => raise @error.Business("缺少id参数")
}
let design = match ext.find_design_by_id(design_id) {
Some(d) => d
None => raise @error.Business("流程设计不存在")
}
let his = ext.list_design_his(design_id)
if his.is_empty() {
raise @error.Business("流程设计没有内容,无法发布")
}
let content = his[0].content
let model = @parser.parse_model(content)
// 按 name 最新 define:有则替换(继承原 version,C29/issues/59)无则新建
let query = @model.PageQuery::new(1L, 1L)
query.add_filter(@model.QueryFilter::make("t", @model.Eq, "name", model.name))
let page = (self.repo).page_defines(query)
let define_id : Int64 = if page.rows().is_empty() {
self.save_deployed_define(model, content)
} else {
let last = page.rows()[0]
let define_info = (self.repo).find_define_by_id(last.id).unwrap()
let updated : @model.ProcessDefine = { ..define_info,
name: model.name,
display_name: model.display_name,
define_type: model.model_type,
content,
// 保留原 version(禁 null→1 漂移,C29/issues/59)
version: last.version,
update_user: Some(arg_str_or(args, "operator", "system")),
}
(self.repo).update_define(updated)
last.id
}
design.is_deployed = 1
design.update_user = Some(arg_str_or(args, "operator", "system"))
ext.update_design(design)
let o = @json.empty_object()
@json.obj_set(o, "processDefineId", i64_json(define_id))
o
}
///|
/// deploy 版本管理:按 name 最新 version+1 插入 state=1(java saveDeployedDefine)
async fn[R : @spi.ProcessRepository, E] Facade::save_deployed_define(self : Facade[R, E], model : @parser.ProcessModel, content : String) -> Int64 raise @error.JeeflowError {
let query = @model.PageQuery::new(1L, 1L)
query.add_filter(@model.QueryFilter::make("t", @model.Eq, "name", model.name))
let page = (self.repo).page_defines(query)
let version = if page.rows().is_empty() {
0
} else {
page.rows()[0].version + 1
}
let define_info : @model.ProcessDefine = {
id: 0L,
name: model.name,
display_name: model.display_name,
define_type: model.model_type,
state: 1,
content,
version,
create_time: Some(@model.current_time_str()),
create_user: Some("system"),
update_time: None,
update_user: None,
}
(self.repo).save_define(define_info)
define_info.id
}
///|
/// listByType:发起页数据契约(issues/28;分组契约 C14)
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::design_list_by_type(self : Facade[R, E], _args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let query = @model.PageQuery::new(1L, 2147483646L)
let design_page = ext.page_designs(query)
let def_query = @model.PageQuery::new(1L, 2147483646L)
let def_page = (self.repo).page_defines(def_query)
// 每 name 最新 define(version 最大)
let latest : Map[String, @model.DefineRow] = Map([])
for row in def_page.rows() {
match latest.get(row.name) {
Some(prev) => if row.version > prev.version { latest[row.name] = row }
None => latest[row.name] = row
}
}
let groups : Map[String, Json] = Map([])
for d in design_page.rows() {
let dtype = d.design_type
let items = match groups.get(dtype) {
Some(arr) => arr
None => {
let arr = @json.empty_array()
groups[dtype] = arr
arr
}
}
let item = @json.empty_object()
@json.obj_set(item, "processDesignId", i64_json(d.id))
@json.obj_set(item, "name", @json.string_of(d.name))
@json.obj_set(item, "displayName", @json.string_of(d.display_name))
@json.obj_set(item, "icon", opt_json(d.icon))
@json.obj_set(item, "remark", opt_json(d.remark))
match latest.get(d.name) {
Some(l) => {
@json.obj_set(item, "processDefineId", i64_json(l.id))
@json.obj_set(item, "processDefineState", num_json(l.state))
}
None => {
@json.obj_set(item, "processDefineId", @json.null_json())
@json.obj_set(item, "processDefineState", @json.null_json())
}
}
let his = ext.list_design_his(d.id)
if !his.is_empty() {
@json.obj_set(item, "jsonObject", opt_json_wrap(parse_graph(his[0].content)))
}
@json.arr_push(items, item)
}
obj_json(groups)
}
// ═══ 委托代理 ═══
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::surrogate_page(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let page_num = arg_i64_or(args, "pageNum", 1L)
let page_size = arg_i64_or(args, "pageSize", 20L)
let filters = facade_parse_m_filters(args)
// issues/152 ②(spec 06 §4.5「processSurrogate/page 归属不变式」):t.operator 是归属列,
// 与 instance_page 逐字同形注入 §2.5 归一后的 operator(缺键/空串/全空白 ⇒ 缺省 user1)——
// 「我的委托只看自己授出的行」由引擎自己保证,不再靠集成壳注入 operator 才成立;换宿主/
// 直调 SPI 也不再退化成全库台账。第二层兜底(绕过门面直调仓储)见两仓储 page_surrogates。
let query = @model.PageQuery::new(page_num, page_size)
query.set_operator(Some(operator_arg(args)))
let page = (self.ext()).page_surrogates(query)
finalize_page(page, surrogate_to_json, filters, page_num, page_size)
}
///|
/// `enabled` 的**写侧**归一(spec 06 §4.5 条款 4「读写两侧分别定」,issues/148)。
/// 参考实现=Java `JeeflowFacade.java:1557-1560`(`enabledArg == null ? 1 : toInt(enabledArg, 0)`)——
/// 归一在**引擎门面**里,13 个集成壳对 `enabled` 零命中,所以这条不能挪给宿主做。
/// - 键缺失 ⇒ **1**(契约默认启用,沿用 C26 既有语义);
/// - 键在且解析得出整数 ⇒ 原值落库(`1`/`"1"`/`2`/`0` 都照实存,读侧判据另行裁决);
/// - 键在但解析不出整数(`"abc"` / `""` / `"1x"` / JSON null / 布尔 / 对象)⇒ **0**,且**不得抛错**——
/// 抛错会把"停用一条委托"这个正常用户动作变成 99999999,且旧文案 `非法id: abc` 连字段都说错。
/// 读侧(只有 1 生效)在 `@model.ProcessSurrogate::is_effective`,两侧分家、别混。
fn surrogate_enabled_arg(args : Map[String, Json]) -> Int {
match args.get("enabled") {
None => 1
Some(v) =>
match @json.as_i64(v) {
Some(n) => n.to_int()
None =>
match v {
Json::String(s) =>
try {
@string.parse_int64(s).to_int()
} catch {
_ => 0
}
_ => 0
}
}
}
}
///|
/// 时间窗双格式解析(C26:空格 / ISO T)
fn parse_surrogate_time(s : String?) -> String? {
match s {
Some(raw) => {
let t = raw.trim(chars=" \t\r\n").to_owned()
if t.is_empty() {
return None
}
if t.length() >= 19 {
let bytes = t.to_array()
if bytes[10] == 'T' {
return Some(t[0:10].to_owned() + " " + t[11:19].to_owned())
}
return Some(t[0:19].to_owned())
}
Some(t)
}
None => None
}
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::surrogate_save(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let operator = operator_arg(args)
let id = arg_i64(args, "id")
let surrogate : @model.ProcessSurrogate = match id {
None => {
let s : @model.ProcessSurrogate = {
id: 0L,
process_name: arg_str(args, "processName").unwrap_or(""),
operator,
surrogate: arg_str(args, "surrogate").unwrap_or(""),
start_time: parse_surrogate_time(arg_str(args, "startTime")),
end_time: parse_surrogate_time(arg_str(args, "endTime")),
enabled: surrogate_enabled_arg(args),
create_time: Some(@model.current_time_str()),
create_user: Some(operator),
update_time: None,
update_user: Some(operator),
}
s
}
Some(i) => {
let s = match ext.find_surrogate_by_id(i) {
Some(x) => x
None => raise @error.Business("委托记录不存在")
}
self.apply_surrogate_fields(s, args, operator)
s
}
}
if id is None {
ext.save_surrogate(surrogate)
} else {
ext.update_surrogate(surrogate)
}
let o = @json.empty_object()
@json.obj_set(o, "id", i64_json(surrogate.id))
o
}
///|
fn[R, E] Facade::apply_surrogate_fields(self : Facade[R, E], s : @model.ProcessSurrogate, args : Map[String, Json], operator : String) -> Unit {
let _ = self
match arg_str(args, "processName") {
Some(v) => s.process_name = v
None => ()
}
// 授权人仅显式传入且**非空白**时覆盖(issues/77 不清空原授权人 + issues/152 ③:空串/全空白
// 不得写进 operator——那种行是「死行」,get_surrogate 的 WHERE operator = ? 永不命中,
// 台账看得见、待办永不并人;spec 06 §2.5 空串=缺键同档)。空白档 ⇒ save 新建路径由
// operator_arg 归一缺省兜底(不落空串),update 路径保留原授权人。与 java
// JeeflowFacade.applySurrogateFields 的「只认非空显式值」收紧同形。
match arg_str(args, "operator") {
Some(v) => if v.trim(chars=" \t\r\n").is_empty() { () } else { s.operator = v }
None => ()
}
match arg_str(args, "surrogate") {
Some(v) => s.surrogate = v
None => ()
}
s.start_time = parse_surrogate_time(arg_str(args, "startTime"))
s.end_time = parse_surrogate_time(arg_str(args, "endTime"))
// C26:enabled=0 不得折叠成 1;issues/148:脏值不得抛(与 save 同一份写侧口径)
s.enabled = surrogate_enabled_arg(args)
s.update_user = Some(operator)
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::surrogate_update(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let ext = self.ext()
let operator = operator_arg(args)
let id = match arg_i64(args, "id") {
Some(v) => v
None => raise @error.Business("id 缺失")
}
let surrogate = match ext.find_surrogate_by_id(id) {
Some(s) => s
None => raise @error.Business("委托记录不存在")
}
self.apply_surrogate_fields(surrogate, args, operator)
ext.update_surrogate(surrogate)
let o = @json.empty_object()
@json.obj_set(o, "id", i64_json(surrogate.id))
o
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::surrogate_detail(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let id = match arg_i64(args, "id") {
Some(v) => v
None => raise @error.Business("缺少id参数")
}
match (self.ext()).find_surrogate_by_id(id) {
Some(s) => surrogate_to_json(s)
None => raise @error.Business("委托记录不存在")
}
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::surrogate_remove(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
for id in arg_ids(args) {
(self.ext()).remove_surrogate(id)
}
@json.null_json()
}
// ═══ 视图:bizData / highLight ═══
///|
pub async fn[R : @spi.ProcessRepository, E] Facade::biz_data(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let instance_id = match arg_id(args, ["processInstanceId", "id"]) {
Some(v) => v
None => raise @error.Business("processInstanceId 缺失")
}
let inst = match (self.repo).find_instance_by_id(instance_id) {
Some(i) => i
None => raise @error.InstanceNotFound(instance_id)
}
let define_info = match (self.repo).find_define_by_id(inst.define_id) {
Some(d) => d
None => raise @error.DefineNotFound(inst.define_id)
}
// relTableName 从 content 顶层解析(回落 name)
let table = match rel_table_name_of(define_info.content) {
Some(t) => t
None => raise @error.Business("流程定义未配置 relTableName")
}
// metaTableReader 未注册显式报错(issues/28)
match self.engine.context().biz_data_reader {
Some(reader) => {
// reader: `(String, Int64) -> Map[String,Json]? raise JeeflowError`
let rows = try {
(reader)(table, instance_id)
} catch {
// issues/137 §3-1(spec 06 §2.12 覆盖面② bizData 族;java 基准 JeeflowFacade#bizData
// 只给固定文案「业务数据读取失败」,原文进 SEVERE 日志):reader 是第三方 provider/
// persist 腿,它抛的原文不是引擎写的契约文案,不得拼进 Business 档对外 msg——Business
// 是契约档、出口逐字透出,判别式在出口挡不住它(形状上就是引擎自己写的文案),必须在
// 构造点收口。旧形状把 e.message() 拼进 msg,正是批三 §1 普查点名的泄漏(php :891/
// csharp 批二修掉的同形)。原文只进日志。
e => {
let detail = e.detail().unwrap_or("")
println("[jeeflow] bizData 读取失败: table=\{table} cause=\{e.message()} detail=\{detail}")
raise @error.Business("业务数据读取失败")
}
}
match rows {
Some(row) => obj_json(row)
None => @json.null_json()
}
}
None => raise @error.Business("业务数据读取器未注册(ServiceContext.put(\"metaTableReader\", ...),需引入 jeeflow-persist)")
}
}
///|
fn rel_table_name_of(content : String) -> String? {
match parse_graph(content) {
Some(meta) => {
let t = @json.get_str(meta, "relTableName")
match t {
Some(s) => if !s.trim(chars=" \t\r\n").is_empty() { Some(s.trim(chars=" \t\r\n").to_owned()) } else { rel_fallback_name(meta) }
None => rel_fallback_name(meta)
}
}
None => None
}
}
///|
fn rel_fallback_name(meta : Json) -> String? {
match @json.get_str(meta, "name") {
Some(s) => if !s.trim(chars=" \t\r\n").is_empty() { Some(s.trim(chars=" \t\r\n").to_owned()) } else { None }
None => None
}
}
///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::high_light(self : Facade[R, E], args : Map[String, Json]) -> Json raise @error.JeeflowError {
let id = match arg_i64(args, "id") {
Some(v) => v
None => raise @error.Business("缺少id参数")
}
let inst = match (self.repo).find_instance_by_id(id) {
Some(i) => i
None => raise @error.InstanceNotFound(id)
}
let active : Array[String] = []
let doing = (self.repo).find_doing_tasks(id, [])
for t in doing {
if !active.contains(t.task_name) {
active.push(t.task_name)
}
}
let history = (self.repo).find_history_tasks(id)
let history_nodes : Array[String] = []
for t in history {
if !active.contains(t.task_name) && !history_nodes.contains(t.task_name) {
history_nodes.push(t.task_name)
}
}
let define_info = (self.repo).find_define_by_id(inst.define_id)
let node_progress = @json.empty_object()
let edges : Array[String] = []
match define_info {
Some(d) => {
// 同上:坏定义 ⇒ 进度/路径两档都留空(旧 Err ⇒ ())
try {
let model = @parser.parse_model(d.content)
self.build_node_progress(model, history, node_progress)
// issues/153②:路径补全多带 `inst`/`d` 两参——决策边求值要 Execution(实例变量之外的
// 前置任务变量取自历史任务,实例/定义是 `Execution::make` 的必需件),求值口=引擎那一个。
self.collect_path(
model, model.get_start(), active, history_nodes, edges, inst.variables, history, inst, d
)
} catch {
_ => ()
}
}
None => ()
}
let o = @json.empty_object()
let active_arr = @json.empty_array()
for n in active {
@json.arr_push(active_arr, @json.string_of(n))
}
@json.obj_set(o, "activeNodeNames", active_arr)
let hist_arr = @json.empty_array()
for n in history_nodes {
@json.arr_push(hist_arr, @json.string_of(n))
}
@json.obj_set(o, "historyNodeNames", hist_arr)
let edge_arr = @json.empty_array()
for n in edges {
@json.arr_push(edge_arr, @json.string_of(n))
}
@json.obj_set(o, "historyEdgeNames", edge_arr)
@json.obj_set(o, "nodeProgress", node_progress)
o
}
///|
/// 节点成员进度(issues/153③ · spec 06 §4.6 义务 3):由包内自由函数抬成 `Facade` 方法
/// 并改 `async`——成员 `name` 要经 `IUserProvider`(本栈形态=`ctx.user_provider` 那一支
/// async 闭包 SPI)解析,自由函数拿不到 `self.engine`,同步函数调不了 async SPI。
/// 抬成方法/改 async 都是**为接通既有 SPI 口**,不是新增能力:判据、名册来源
/// (`operatorList_{node}` 优先 ⇒ actor_ids 并集)、会签 `type`、done/active 判定逐字未动。
async fn[R, E] Facade::build_node_progress(
self : Facade[R, E],
model : @parser.ProcessModel,
history : Array[@model.ProcessTask],
out : Json
) -> Unit {
// 节点顺序(history 首现序)
let names : Array[String] = []
let seen : Map[String, Unit] = Map([])
for t in history {
if seen.get(t.task_name) is None {
seen[t.task_name] = ()
names.push(t.task_name)
}
}
for name in names {
let ts : Array[@model.ProcessTask] = []
for t in history {
if t.task_name == name {
ts.push(t)
}
}
if ts.is_empty() {
continue
}
// 会签全量办理人:任务变量 operatorList_{node} 优先(C9/issues/93),否则 actorIds 并集
let mut members : Array[String] = []
// issues/131:名册是任务变量 `operatorList_{node}` 里的**数组**(java ProcessInstance.java:257
// 存的就是 List)。逗号串那一支有意不兜——存量兼容不做(owner 2026-09-28)。
let op_key = "operatorList_" + name
for t in ts {
let got = t.variables.get_string_array(op_key)
if got.length() > 0 {
members = got
break
}
}
if members.is_empty() {
for t in ts {
for a in t.actor_ids {
if !members.contains(a) {
members.push(a)
}
}
}
}
if members.is_empty() {
continue
}
// 会签判定 + 类型
let mut is_cs = false
let mut cs_type = ""
match model.get_node(name) {
Some(node) => {
is_cs = node.is_countersign()
cs_type = node.countersign_type()
}
None => ()
}
let member_list = @json.empty_array()
for id in members {
let m = @json.empty_object()
@json.obj_set(m, "id", @json.string_of(id))
// issues/153③(spec 06 §4.6 义务 3):name **必须**经 IUserProvider 解析,只有查不到才空串。
// 本轮之前这里是硬编码空串,上一行注释写着「由调用方注入 user provider 前先空串」——
// 但**不存在那个调用方**(`high_light` 直接把 model/history 递进来,从未注入过 provider),
// 于是 SPI 注册了也永远出空串,违反义务 3(spec 明写"注册了但这一路没接"就是违约)。
// 接线姿势照本包既有唯一一处 user_provider 用法 `actions_main.mbt:685-696`(candidatePage),
// 判据对齐 java `JeeflowFacade.java:689-697` `resolveUserName`:
// provider 未注册 ⇒ 空串(这才是 spec 许可的降级档);注册了但查不到 / realName 为空 /
// 解析报错 ⇒ 空串(前端降级显示 id)。
let member_name = match self.engine.context().user_provider {
Some(get_user) =>
try {
match (get_user)(id) {
Some(u) => u.real_name()
None => ""
}
} catch {
_ => ""
}
None => ""
}
@json.obj_set(m, "name", @json.string_of(member_name))
let mut done = false
for t in ts {
if t.task_state == 20 && t.actor_ids.contains(id) {
done = true
}
}
if done {
@json.obj_set(m, "done", @json.bool_of(true))
} else {
for t in ts {
if t.task_state == 10 && !t.actor_ids.is_empty() && t.actor_ids[0] == id {
@json.obj_set(m, "active", @json.bool_of(true))
}
}
}
@json.arr_push(member_list, m)
}
let item = @json.empty_object()
@json.obj_set(item, "members", member_list)
if is_cs && !cs_type.is_empty() {
@json.obj_set(item, "type", @json.string_of(cs_type))
}
@json.obj_set(out, name, item)
}
}
///|
/// 决策边求值入参(issues/153② · spec 06 §4.6 义务 2):args = 实例变量 ∪ 决策节点
/// **前置任务**变量。逐字对齐 go `facade/facade.go:1269-1297`(`evalDecisionExpr`)与
/// java `JeeflowFacade.java:735-746`:取决策节点**第一条入边**的源节点,在历史任务里找
/// 第一个 `task_name` 命中的行,把它的任务变量并进实例变量(前置任务变量覆盖同名实例变量)。
fn decision_args(
model : @parser.ProcessModel,
decision : @parser.NodeModel,
instance_vars : @json.FlowData,
history_tasks : Array[@model.ProcessTask]
) -> @json.FlowData {
let args = @json.FlowData::new()
args.merge(instance_vars)
match model.get_input_edges(decision.id).get(0) {
Some(in_edge) => {
let src = in_edge.source_node_id
for t in history_tasks {
if t.task_name == src {
args.merge(t.variables)
break
}
}
}
None => ()
}
args
}
///|
/// 路径补全:start 沿 outputs 递归,遇活跃节点停止;决策节点表达式求值只收 true 边(C14)
///
/// issues/153②(spec 06 §4.6 义务 2):**本轮之前这里完全不滤**——决策节点的每条出边一律收,
/// 于是未走的分支也被点亮(`scripts/action-manifest.json:148` 早就承诺了"只收 true 边",
/// 是代码没做到,不是契约没写)。现按 go `facade.go:1233-1270` `collectPath` 的形状求值后过滤:
/// · **带表达式**的出边先求值,判 false 的边「边名与目标节点都不收」⇒ 整条分支不亮;
/// · **不带表达式**的出边照收(java `JeeflowFacade.java:707-710` 的
/// `node instanceof DecisionModel && isNotEmpty(tm.getExpr()) && !eval(...)` 同判据)。
/// 求值口=`Engine::evaluate_expression`(引擎路由决策边那一支,同一个口不分两套);
/// 求值报错按 go `facade.go:1293-1296`「err ⇒ false」判,不让一次求值失败把整份高亮清空
/// (故本函数不 raise,错误在这一格就地收敛)。
/// 由包内自由函数抬成 `Facade` 方法:求值口在 `self.engine` 上,`collect_path` 需要它。
fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Facade::collect_path(
self : Facade[R, E],
model : @parser.ProcessModel,
node : @parser.NodeModel?,
active : Array[String],
history : Array[String],
edges : Array[String],
instance_vars : @json.FlowData,
history_tasks : Array[@model.ProcessTask],
inst : @model.ProcessInstance,
flow_define : @model.ProcessDefine
) -> Unit {
match node {
Some(n) => {
let is_decision = n.node_type is @parser.Decision
for tm in model.get_output_edges(n.id) {
// 决策节点的带表达式出边:求值 false ⇒ 这一支没实际执行,边名与目标节点都不收
let take = match tm.expr() {
Some(expr) =>
if !is_decision || expr.is_empty() {
true
} else {
let merged = decision_args(model, n, instance_vars, history_tasks)
let exec = @exec.Execution::make(inst, model, flow_define, inst.operator, merged)
try {
(self.engine).evaluate_expression(expr, exec)
} catch {
_ => false
}
}
None => true
}
if take {
let edge_name = match tm.label {
Some(l) => Some(l)
None => Some(tm.id)
}
match edge_name {
Some(en) => if !edges.contains(en) { edges.push(en) }
None => ()
}
let next = model.get_target_node(tm)
match next {
Some(nx) => {
if !active.contains(nx.id) && !history.contains(nx.id) {
history.push(nx.id)
}
if !active.contains(nx.id) {
self.collect_path(
model, Some(nx), active, history, edges, instance_vars, history_tasks, inst, flow_define
)
}
}
None => ()
}
}
}
}
None => ()
}
}