// DDD 聚合根 ProcessInstance + 子实体 ProcessTask —— 移植自 rust model.rs L168-L542

// ⚠️ MoonBit 记录为引用语义:mut 字段原地共享可变;显式 clone 点用 clone_* 函数。

// content 用 String(JSON 文本)承载(rust 为 Vec,DB BLOB 边界在仓储层处理)。



///|

pub(all) struct ProcessDefine {

  mut id : Int64

  name : String

  display_name : String

  define_type : String

  mut state : Int

  mut content : String

  mut version : Int

  mut create_time : String?

  mut create_user : String?

  mut update_time : String?

  mut update_user : String?

} derive(Eq)



///|

pub fn ProcessDefine::clone(self : ProcessDefine) -> ProcessDefine {

  { ..self }

}



///|

/// ProcessInstance —— DDD 聚合根

pub(all) struct ProcessInstance {

  mut instance_id : Int64

  mut parent_id : Int64?

  mut define_id : Int64

  mut state : Int

  mut parent_node_name : String?

  mut business_no : String?

  mut operator : String

  mut expire_time : String?

  mut variables : @json.FlowData

  mut tasks : Array[ProcessTask]

  mut create_time : String?

  mut create_user : String?

  mut update_time : String?

  mut update_user : String?

  // 瞬态:define 信息(不落实例表)

  mut define_info : ProcessDefine?

}



///|

pub fn ProcessInstance::clone(self : ProcessInstance) -> ProcessInstance {

  {

    instance_id: self.instance_id,

    parent_id: self.parent_id,

    define_id: self.define_id,

    state: self.state,

    parent_node_name: self.parent_node_name,

    business_no: self.business_no,

    operator: self.operator,

    expire_time: self.expire_time,

    variables: @json.FlowData::from_map(clone_json_map(self.variables.inner())),

    tasks: self.tasks.map(fn(t) { t.clone() }),

    create_time: self.create_time,

    create_user: self.create_user,

    update_time: self.update_time,

    update_user: self.update_user,

    define_info: match self.define_info {

      Some(d) => Some(d.clone())

      None => None

    },

  }

}



///|

fn clone_json_map(src : Map[String, Json]) -> Map[String, Json] {

  let m : Map[String, Json] = Map([])

  for k, v in src {

    m[k] = v

  }

  m

}



///|

/// 工厂:新建实例(state=DOING=10)

pub fn ProcessInstance::create(

  define_info : ProcessDefine,

  operator : String,

  args : @json.FlowData

) -> ProcessInstance {

  let variables = @json.FlowData::new()

  variables.merge(args)

  {

    instance_id: 0L,

    parent_id: None,

    define_id: define_info.id,

    state: InstanceState::Doing.code(),

    parent_node_name: None,

    business_no: args.get_str("BUSINESS_NO"),

    operator,

    expire_time: None,

    variables,

    tasks: [],

    create_time: Some(current_time_str()),

    create_user: Some(operator),

    update_time: None,

    update_user: None,

    define_info: Some(define_info.clone()),

  }

}



///|

/// 工厂(子流程):带父实例

pub fn ProcessInstance::create_with_parent(

  define_info : ProcessDefine,

  operator : String,

  args : @json.FlowData,

  parent_id : Int64,

  parent_node_name : String

) -> ProcessInstance {

  let inst = ProcessInstance::create(define_info, operator, args)

  inst.parent_id = Some(parent_id)

  inst.parent_node_name = Some(parent_node_name)

  inst

}



///|

/// 完成任务:finish 任务 + 合并 f_ 变量(rust complete_task)

/// issues/115 契约第 5 条(spec/06 §transfer-5):`args` 是「实例变量 ← 任务既有变量 ←

/// 本次提交参数」的合并结果(由引擎 load_exec_for_task 组装),这里**逐键写入**任务变量

/// (合并语义、不整体替换),故转办账本 `tf_transferHistory` 能活到办结后,而本次提交的

/// submitType/表单值又能覆盖转办留下的槽位(反噬方向已被合并序挡住)。

pub fn ProcessInstance::complete_task(

  self : ProcessInstance,

  task_id : Int64,

  operator : String,

  args : @json.FlowData

) -> Unit raise @error.JeeflowError {

  let mut found : ProcessTask? = None

  for t in self.tasks {

    if t.task_id == task_id {

      found = Some(t)

    }

  }

  let task = match found {

    Some(t) => t

    None =>

      raise @error.InstanceNotFound(

        task_id,

      ) // rust 语义:任务不在聚合内报错;消息对齐 "任务不存在"

  }

  for k, v in args.inner() {

    task.variables.insert(k, v)

    if k.has_prefix("f_") {

      self.variables.insert(k, v)

    }

  }

  task.finish(operator)

}



///|

/// 仅任务级(不连带实例置 99)——spec 04 与 go/py/node 对齐,issues/180

pub fn ProcessInstance::abandon_task(

  self : ProcessInstance,

  task_id : Int64

) -> Unit raise @error.JeeflowError {

  let mut found : ProcessTask? = None

  for t in self.tasks {

    if t.task_id == task_id {

      found = Some(t)

    }

  }

  match found {

    Some(t) => {

      t.abandon()

    }

    None => raise @error.TaskNotFound(task_id)

  }

}



///|

pub fn ProcessInstance::abandon_all_doing(self : ProcessInstance) -> Unit {

  for t in self.tasks {

    if t.task_state == TaskState::Doing.code() {

      try {

        t.abandon()

      } catch {

        _ => ()

      }

    }

  }

}



///|

pub fn ProcessInstance::finish(self : ProcessInstance) -> Unit {

  self.state = InstanceState::Finished.code()

}



///|

pub fn ProcessInstance::reject(self : ProcessInstance) -> Unit {

  self.state = InstanceState::Reject.code()

}



///|

pub fn ProcessInstance::interrupt(self : ProcessInstance) -> Unit {

  for t in self.tasks {

    if t.task_state == TaskState::Doing.code() {

      try {

        t.interrupt()

      } catch {

        _ => ()

      }

    }

  }

  self.state = InstanceState::Interrupt.code()

}



///|

pub fn ProcessInstance::resume_from_interrupt(self : ProcessInstance) -> Unit {

  for t in self.tasks {

    if t.task_state == TaskState::Interrupt.code() {

      t.task_state = TaskState::Doing.code()

    }

  }

  self.state = InstanceState::Doing.code()

}



///|

pub fn ProcessInstance::pending(self : ProcessInstance) -> Unit {

  for t in self.tasks {

    if t.task_state == TaskState::Doing.code() {

      try {

        t.pending()

      } catch {

        _ => ()

      }

    }

  }

  self.state = InstanceState::Pending.code()

}



///|

/// 撤回:实例 30 WITHDRAW + 全部 DOING 任务 30(C28:任务 30 非 45)

/// issues/114(spec/06 §withdraw):`operator` = 真实撤回人,聚合根负责把实例与**被撤的**

/// 进行中任务的 `update_user` 回写为它;已完成(20)/已终止(40) 任务行不经本方法,天然不被改写。

///

/// issues/134 案 A(owner 2026-09-28 拍板,八栈逐字同形):撤回只允许**进行中(10)** 的实例。

/// 实例不是 10(已完成 20 / 已撤回 30 / 强行终止 40 / 已拒绝 45 / 挂起 50 / 已废弃 99)⇒ 抛内部码

/// **20010009**,本栈形状 = `@error.Business(固定文案)`(与 20010007/20010008 同款——引擎只发明

/// 99999999 一个失败码,细粒度原因由 msg 承载,spec/06 §2.2)。守卫排在下面的任务行循环**之前**

/// ⇒ 被拒时一行都不改、不落库;否则已办结实例会被静默改写成 30(已办列表与按状态聚合的统计凭空

/// 改历史,而调用方看不到任何报错)。文案逐字 `流程实例非进行中,无法撤回`,门面沿用 issues/121

/// 口径内部码不进 msg ⇒ 出口 `code=99999999` + 这句原文,不拼码、不加前缀。任务行层面的既有保护

/// (spec/06「已完成(20)/已终止(40) 的任务行不得被撤回改写」)保持原样。

pub fn ProcessInstance::withdraw(

  self : ProcessInstance,

  operator : String

) -> Unit raise @error.JeeflowError {

  // issues/134 案 A 实例状态守卫:非进行中(10) 一律拒,且不改写任何行(八栈同形)

  if self.state != InstanceState::Doing.code() {

    raise @error.Business("流程实例非进行中,无法撤回")

  }

  for t in self.tasks {

    if t.task_state == TaskState::Doing.code() {

      t.withdraw(operator)

    }

  }

  self.state = InstanceState::Withdraw.code()

  self.update_user = Some(operator)

  self.update_time = Some(current_time_str())

}



///|

pub fn ProcessInstance::add_variable(self : ProcessInstance, args : @json.FlowData) -> Unit {

  self.variables.merge(args)

}



///|

pub fn ProcessInstance::remove_variables(self : ProcessInstance, keys : Array[String]) -> Unit {

  for k in keys {

    self.variables.remove(k) |> ignore

  }

}



///|

/// 建任务(子实体工厂;task_id=0 由 ID 生成器赋值——rust 91b:原片赋 id 再克隆返回)

pub fn ProcessInstance::create_task(

  self : ProcessInstance,

  task_name : String,

  display_name : String,

  actor_ids : Array[String],

  operator : String,

  task_type : TaskType,

  perform_type : PerformType,

  form_key : String?,

  parent_task_id : Int64?

) -> ProcessTask {

  let task : ProcessTask = {

    task_id: 0L,

    process_instance_id: self.instance_id,

    task_name,

    display_name,

    task_type: task_type.code(),

    perform_type: perform_type.code(),

    task_state: TaskState::Doing.code(),

    actor_id: None,

    actor_ids: actor_ids.copy(),

    finish_time: None,

    expire_time: None,

    form_key,

    parent_task_id,

    variables: @json.FlowData::new(),

    create_time: Some(current_time_str()),

    create_user: Some(operator),

    update_time: None,

    update_user: None,

  }

  self.tasks.push(task)

  task

}



///|

/// 会签任务:每人一个独立任务(C9:串行一次只建一个由引擎控制;此处为并行建全入口)

pub fn ProcessInstance::create_countersign_tasks(

  self : ProcessInstance,

  task_name : String,

  display_name : String,

  actor_ids : Array[String],

  operator : String,

  task_type : TaskType,

  form_key : String?,

  parent_task_id : Int64?

) -> Array[ProcessTask] {

  let out : Array[ProcessTask] = []

  for actor in actor_ids {

    out.push(

      self.create_task(

        task_name,

        display_name,

        [actor],

        operator,

        task_type,

        Countersign,

        form_key,

        parent_task_id,

      ),

    )

  }

  out

}



///|

/// 历史任务(custom 节点用,直接 FINISHED)

/// ⚠️ issues/137 B 拍板"不删"时它**引擎侧零调用者**(grep 只命中本定义与 core/model 单测);

/// **issues/142 A 批(2026-09-30)把它接线了** —— `Engine::execute_custom_node` 记录类腿调它,

/// 并经由 `Engine::persist_history_task` 走 save_task/add_task_actor **真落库**

/// (spec/02 §6.2 第 1 条:只在聚合 `instance.tasks` 里 append 一条不算做到)。

/// 行形状:state=FINISHED(20) + actor_id=处理人 + actor_ids=[处理人],前两条与主路径

/// ProcessTask::finish 写的列同判据。

/// ⚠️ **2026-09-30 issues/142 A 批收口改了一列**(spec/02 §6.2 第 1bis 条,owner 拍):

/// `finish_time` 从此**必写**。原先这里钉的是「办理审计三列都不写」,理由「自动节点没有办理

/// 这一步」;但这条 DONE 行在用户面上要经 `processTask/doneList`(`task_state<>10 AND

/// operator=?`)与 `processInstance/approvalRecord` 查得到,那两支都按 operator+finish_time

/// 取数 ⇒ 缺完成时间的留痕等于没落过(与第 1 条「查不到的留痕=没留痕」同一把尺子)。

/// `expire_time` **必须留 None**(spec/02 §6 的 custom 属性字典无 expireTime,

/// 写它就得臆造属性,也与 issues/126 owner 口径「节点没配就保持 NULL」同向)——

/// 条文里「两列写、一列不写」说的就是 expire_time 这一列,别顺手把时间列全补上。

/// 另:`update_time`/`update_user` 本栈建单(create_task)本就不写,与 java

/// `ProcessTask.create` 顺手写这两列不同形,属本栈既有形状、不在本条判据内。

/// 直调行为由

/// i137b_zero_caller_create_test.mbt 逐字段钉住(该用例跟着本裁定改判)。

pub fn ProcessInstance::create_history_task(

  self : ProcessInstance,

  task_name : String,

  display_name : String,

  operator : String,

  task_type : TaskType

) -> ProcessTask {

  let task = self.create_task(

    task_name,

    display_name,

    [operator],

    operator,

    task_type,

    Normal,

    None,

    None,

  )

  task.task_state = TaskState::Finished.code()

  task.actor_id = Some(operator)

  // spec/02 §6.2 第 1bis 条(issues/142 A 批收口,owner 09-30):留痕行必须带完成时间。

  // MoonBit 的 struct 是引用语义 ⇒ 这里赋值即聚合里那一格,不存在 rust 那边

  // 「返回行与 tasks 里那格分叉」的问题;但 MySQL 仓的 INSERT 语句原先没有 finish_time 列,

  // 值到不了库(repository-mysql/repo/repository.mbt::save_task 本轮补列)。

  task.finish_time = Some(current_time_str())

  task

}



///|

/// 退回上一步:为新节点建待办任务

/// ⚠️ issues/137 B:**真·零调用者**(连单测都没有;grep reject_task 全仓只命中本定义)。

/// 引擎的"退回上一步"支在 core/engine/engine_ops.mbt 里用结构体字面量手搓复活行

/// (随行拷贝 history 各列 + parent_task_id=Some(carry) + 按当前节点表达式重算

/// expire_time),没走这里 ⇒ 本函数无人调。

/// 仍承担**契约形状义务**:它=主路径建单 create_task(task_name, display_name,

/// actor_ids, operator, Major, Normal, None, Some(parent_task_id)) 的具名别名,

/// 逐字段同形(i137b_zero_caller_create_test.mbt 用 == 钉住)。

/// owner 2026-09-29 拍板**不删**。

pub fn ProcessInstance::reject_task(

  self : ProcessInstance,

  task_name : String,

  display_name : String,

  actor_ids : Array[String],

  operator : String,

  parent_task_id : Int64

) -> ProcessTask {

  self.create_task(

    task_name,

    display_name,

    actor_ids,

    operator,

    Major,

    Normal,

    None,

    Some(parent_task_id),

  )

}



///|

pub fn ProcessInstance::get_doing_tasks(self : ProcessInstance) -> Array[ProcessTask] {

  let out : Array[ProcessTask] = []

  for t in self.tasks {

    if t.task_state == TaskState::Doing.code() {

      out.push(t)

    }

  }

  out

}



///|

pub fn ProcessInstance::get_doing_tasks_by_names(

  self : ProcessInstance,

  names : Array[String]

) -> Array[ProcessTask] {

  let out : Array[ProcessTask] = []

  for t in self.tasks {

    if t.task_state == TaskState::Doing.code() && names.contains(t.task_name) {

      out.push(t)

    }

  }

  out

}



///|

pub fn ProcessInstance::get_finished_tasks(self : ProcessInstance) -> Array[ProcessTask] {

  let out : Array[ProcessTask] = []

  for t in self.tasks {

    if t.task_state == TaskState::Finished.code() {

      out.push(t)

    }

  }

  out

}



///|

pub fn ProcessInstance::get_done_tasks_by_names(

  self : ProcessInstance,

  names : Array[String]

) -> Array[ProcessTask] {

  let out : Array[ProcessTask] = []

  for t in self.tasks {

    if t.task_state == TaskState::Finished.code() && names.contains(t.task_name) {

      out.push(t)

    }

  }

  out

}



///|

pub fn ProcessInstance::get_history_tasks(self : ProcessInstance) -> Array[ProcessTask] {

  self.tasks.copy()

}



///|

pub fn ProcessInstance::is_all_tasks_finished(self : ProcessInstance) -> Bool {

  let mut any_doing = false

  for t in self.tasks {

    if t.task_state == TaskState::Doing.code() {

      any_doing = true

    }

  }

  !(any_doing)

}



///|

pub fn ProcessInstance::is_doing(self : ProcessInstance) -> Bool {

  self.state == InstanceState::Doing.code()

}



///|

pub fn ProcessInstance::is_finished(self : ProcessInstance) -> Bool {

  self.state == InstanceState::Finished.code()

}



///|

/// ProcessTask —— 子实体

pub(all) struct ProcessTask {

  mut task_id : Int64

  mut process_instance_id : Int64

  mut task_name : String

  mut display_name : String

  mut task_type : Int

  mut perform_type : Int

  mut task_state : Int

  mut actor_id : String?

  mut actor_ids : Array[String]

  mut finish_time : String?

  mut expire_time : String?

  mut form_key : String?

  mut parent_task_id : Int64?

  mut variables : @json.FlowData

  mut create_time : String?

  mut create_user : String?

  mut update_time : String?

  mut update_user : String?

}



///|

pub fn ProcessTask::clone(self : ProcessTask) -> ProcessTask {

  {

    task_id: self.task_id,

    process_instance_id: self.process_instance_id,

    task_name: self.task_name,

    display_name: self.display_name,

    task_type: self.task_type,

    perform_type: self.perform_type,

    task_state: self.task_state,

    actor_id: self.actor_id,

    actor_ids: self.actor_ids.copy(),

    finish_time: self.finish_time,

    expire_time: self.expire_time,

    form_key: self.form_key,

    parent_task_id: self.parent_task_id,

    variables: @json.FlowData::from_map(clone_json_map(self.variables.inner())),

    create_time: self.create_time,

    create_user: self.create_user,

    update_time: self.update_time,

    update_user: self.update_user,

  }

}



///|

/// 完成(state→FINISHED=20);守卫:仅 DOING + 处理人可办(并发语义 §6.2)

/// 任务变量的合并写入在调用方(complete_task / complete_task_in_aggregate),

/// 本方法只置状态与办理人列 + `update_user`/`update_time` 审计列(对齐 java finish)。

pub fn ProcessTask::finish(self : ProcessTask, operator : String) -> Unit raise @error.JeeflowError {

  if self.task_state != TaskState::Doing.code() {

    raise @error.InvalidState("Task \{self.task_id} is not in DOING state (current=\{self.task_state})")

  }

  if !self.is_allowed(operator) {

    raise @error.PermissionDenied("Operator \{operator} is not allowed on task \{self.task_id}")

  }

  self.task_state = TaskState::Finished.code()

  self.actor_id = Some(operator)

  self.finish_time = Some(current_time_str())

  self.update_user = Some(operator)

  self.update_time = Some(current_time_str())

}



///|

pub fn ProcessTask::abandon(self : ProcessTask) -> Unit raise @error.JeeflowError {

  if self.task_state != TaskState::Doing.code() {

    raise @error.InvalidState("Task \{self.task_id} is not in DOING state")

  }

  self.task_state = TaskState::Abandon.code()

}



///|

/// 随实例撤回:任务态 30 + `update_user` 回写真实撤回人(issues/114,spec/06 §withdraw)

pub fn ProcessTask::withdraw(self : ProcessTask, operator : String) -> Unit {

  self.task_state = TaskState::Withdraw.code()

  self.update_user = Some(operator)

  self.update_time = Some(current_time_str())

}



///|

pub fn ProcessTask::interrupt(self : ProcessTask) -> Unit raise @error.JeeflowError {

  if self.task_state != TaskState::Doing.code() {

    raise @error.InvalidState("Task \{self.task_id} is not in DOING state")

  }

  self.task_state = TaskState::Interrupt.code()

}



///|

pub fn ProcessTask::pending(self : ProcessTask) -> Unit raise @error.JeeflowError {

  if self.task_state != TaskState::Doing.code() {

    raise @error.InvalidState("Task \{self.task_id} is not in DOING state")

  }

  self.task_state = TaskState::Pending.code()

}



///|

pub fn ProcessTask::resume_from_interrupt(self : ProcessTask) -> Unit {

  if self.task_state == TaskState::Interrupt.code() {

    self.task_state = TaskState::Doing.code()

  }

}



///|

/// 处理人校验:"flow.auto"/"flow.admin" 直通

pub fn ProcessTask::is_allowed(self : ProcessTask, operator : String) -> Bool {

  // issues/194:spec 04:55-56「忽略大小写」——与门面 is_privileged_operator 同口径

  let lower = operator.to_lower()

  if lower == "flow.auto" || lower == "flow.admin" {

    return true

  }

  self.is_doing() && self.actor_ids.contains(operator)

}



///|

pub fn ProcessTask::is_doing(self : ProcessTask) -> Bool {

  self.task_state == TaskState::Doing.code()

}



///|

pub fn ProcessTask::is_finished(self : ProcessTask) -> Bool {

  self.task_state == TaskState::Finished.code()

}



// ── 显式 extend(implicit_impl_as_method 弃办:impl 的方法不再被隐式提升为常规方法)──
// 由 scripts/moon_warn_w2.py 从编译器消息里生成;一行一个 (类型, trait),可 diff 可复审。
pub extend ProcessDefine with Eq::{equal, not_equal}