Open dnnta opened 4 years ago
你的需求其实也是典型场景,类似 Jira Redmine 之类的工作流都是这样用的。
是这样,其实早年的 Dummy 是演示了一个请假流程,使用一个写死的请假记录的模型,然后工作流关联,后来感觉这个用例不够直观,就给换掉了,换成现在的审批流程。
改版前的最后一个提交是 https://github.com/rails-engine/flow_core/tree/b6404c8bdb18a41de260c07001c1e44bbd9103e6 (注:那个版本还没引入 Pipeline API)
当时那个版本的代码放到现在应该依然可用(Workflow
的 on_
系列钩子函数去掉了,因为 Instance
现在支持 STI),简单讲一下:
首先有一个请假记录模型 app/models/leave.rb
然后接入 Workflow,有两种做法:
创建 LeaveInstance
也就是请假的工作流实例类型,设置跟请假记录的关联(记得添加 migration add_references :flow_core_instances, :leaves
)
class LeaveInstance < FlowCore::Instance
belongs_to :leave
end
class LeaveWorkflow < FlowCore::Workflow
belongs_to :leave
private
def on_build_instance(instance)
# 这里是创建流程实例的 hook 方法,你可以在这里根据业务需要任意定制
instance.leave = leave
end
def instance_class
LeaveInstance
end
end
即可,如果你需要 Pipeline 方便编辑流程,那么多加一个
class LeavePipeline < FlowCore::Pipeline
belongs_to :leave
private
def on_build_workflow(workflow)
workflow.leave = leave
end
def workflow_class
LeaveWorkflow
end
end
就可以了,这样好处是扩展代码容易管理,流程有类型容易过滤,但是繁琐一点
另一种偷懒,你不为请假去定义流程的子类,你可以把流程要关联的请假记录的 id 塞进 Instance
的 payload
中去,初始化流程的代码类似这样去写
FlowCore::Workflow.create_instance payload: { leave_id: @leave.id }
然后可以在 Leave
模型中增加外键 belongs_to :instance
(类似 app/models/leave.rb#L6 这样)
如果你还是需要审批意见的话,不需要动态表单这样复杂的话,你可以修改我 Dummy 里的 HumanTask
,就让他关联一个你定义死的 AttachedForm
(指代审批意见表单或者答复表单),就可以了
这样渲染流程步骤的页面的时候,审批数据(就是请假记录)直接从关联中读取即可
我觉得你也可以这样理解,每一个 HumanTask
都是一个独立的 CRUD 型的任务记录(类似一条需要处理的消息),工作流的作用是根据流程所处的步骤去生成 HumanTask
记录,不知道这样类比你能否理解
这时候 HumanTask
的代码就比较简单了,类似
class HumanTask < FlowCore::ApplicationRecord
include FlowCore::TaskExecutable
belongs_to :workflow, class_name: "FlowCore::Workflow"
belongs_to :instance, class_name: "FlowCore::Instance", autosave: true
# 请假记录
belongs_to :leave, validate: true, autosave: true
# 对请假的审批意见
has_one :leave_approval, validate: true, autosave: true
# 这里配合定义关联时的 `validate: true, autosave: true` 做界面的时候可以用嵌套表单去编辑原始请假记录(如果有需要的话)和审批意见,
# 并且会做验证,如果关联的记录数据有错,依然不让保存
accpets_nested_attributes :leave
accpets_nested_attributes :leave_approval
belongs_to :assignable, polymorphic: true
enum status: {
unassigned: "unassigned",
assigned: "assigned",
form_filled: "form_filled", # 可选状态了
finished: "finished"
}
before_validation on: :create do
if task
self.workflow = task.workflow
self.instance = task.instance
end
end
after_create do
create_leave_approval! # 创建审批意见记录
end
def can_finish?
form_filled? && leave.valid? && (!leave_approval || leave_approval.valid?)
end
def can_assign?
unassigned?
end
def can_fill_form?
assigned? || form_filled?
end
def assign!(assignee)
return unless can_assign?
update! assignee: assignee, status: :assigned, assigned_at: Time.zone.now
end
# 提交完表单保存后,手动在控制器调用下,或者去掉这个状态也可以的
def fill_form!
return unless can_fill_form?
self.status = :form_filled
self.form_filled_at = Time.zone.now
save
end
def finish!
return unless can_finish?
self.status = :finished
self.finished_at = Time.zone.now
save!
end
end
这里因为你流程和审批意见表单都是物理模型了,就没必要存到 Instance
和 Task
的 payload
去了,不过要注意一个是,Dummy 里演示的基于 mruby 的 ArcGuard
设计是从 Task
的 payload
里读数据的然后做判断的,你如果觉得对你有用,那么还是要在 finish!
方法里把用于分支判断的字段回写到 Task
的 payload
里去
我接下来翻新 Dummy 的时候 用 任务管理 这个场景做一个类似你需求的例子吧,不知道我回复的你能否理解?
回答的很 nice,非常感谢。
周末更新了一下,去掉了 TransitionCallback
想了一些场景,发现没啥价值,这样还能少理解一个概念。
做通知任务处理人,直接在 HumanTask#assign!
里加一个 assignee.notifications.create
就好了,这需求简单的要死。
没推到rubygems上,master 已经做了,应该不会影响到现在版本的使用。
为你的勤奋点赞!
已经用你之前的指点在尝试了,现在有个问题是如果有多种工作流的话,HumanTask可能也要做个多态,不然不同类型的工作流的状态变化逻辑都在一个类处理好像行不通。或者应该是在 TransitionCallback 里处理把。
为你的勤奋点赞!
已经用你之前的指点在尝试了,现在有个问题是如果有多种工作流的话,HumanTask可能也要做个多态,不然不同类型的工作流的状态变化逻辑都在一个类处理好像行不通。或者应该是在 TransitionCallback 里处理把。
我觉得你可以在 HumanTask
上做 STI,然后不同工作流用 HumanTask
的子类,偷懒可以这样修改 HumanTrigger
,Dummy 的例子 app/models/flow_kit/transition_triggers/human_task.rb#L35 这里,简单修改成:
def on_task_enable(task)
transaction do
assignee =
case configuration.assign_to
when Configuration::ASSIGN_TO_ENUM[:candidate]
assignee_candidates.order("random()").first&.assignable
when Configuration::ASSIGN_TO_ENUM[:instance_creator]
task.instance&.creator
else
raise "Invalid `assign_to` value - #{configuration.assign_to}"
end
# 这里根据工作流的类型来决定创建哪种任务
human_task_type =
case task.workflow
when ApprovalWorkflow
"ApprovalHumanTask"
when BusinessWorkflow
"BusinessHumanTask"
else
raise "Unhandled workflow type - #{task.workflow.class}"
end
# `type: human_task_type` 这里利用 STI
human_task = task_class.create! type: human_task_type, task: task, attached_form: attached_form, form_override: form_override, status: :unassigned
human_task.assign! assignee
end
end
如果任务区别特别大的话,也可以做不同的模型,为区别很大的任务模型写对应的 Trigger
,或者偷懒上边的这个写法应该也可以适用。
正是这样做的👍
您好,咨询下 flow_core 集成已有业务系统的问题。 现有业务系统是通过 FSM 来进行简单状态管理的,现在想集成这个Gem 满足知会等功能。
但是文档里提到
翻看了源码,work_flow, instance, task 基本都关联了 form,且必须 form_filled 后才能 finish。现需要集成的效果是,现有的业务系统只需要在表单 save 时启动这个工作流实例,接收审核结果就行。因此 work_flow 只需要关联已存在的业务 model 就行。比如我们现有的采购流程,希望订单创建保存后,启动 work_flow,关联这个订单ID就行。
demo 里的工作流感觉有点怪,感觉是为了 work_flow 而创建了一个业务 form,而不是先有业务,再去跑的工作流。(之前基本没搞过工作流,这个感觉错了勿怪==~)。
该怎么集成呢,是把业务 model 转 FormKit:Form,适配 flow_kit, 还是对照 flow_kit 按照自己的业务重新撸一份呢