petri_flow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/config/wf_manifest.js +2 -0
  6. data/app/assets/javascripts/wf/application.js +4 -0
  7. data/app/assets/stylesheets/wf/application.scss +2 -0
  8. data/app/assets/stylesheets/wf/arcs.css +4 -0
  9. data/app/assets/stylesheets/wf/cases.css +4 -0
  10. data/app/assets/stylesheets/wf/comments.css +4 -0
  11. data/app/assets/stylesheets/wf/fields.css +4 -0
  12. data/app/assets/stylesheets/wf/forms.css +4 -0
  13. data/app/assets/stylesheets/wf/guards.css +4 -0
  14. data/app/assets/stylesheets/wf/places.css +4 -0
  15. data/app/assets/stylesheets/wf/transitions.css +4 -0
  16. data/app/assets/stylesheets/wf/workflows.css +4 -0
  17. data/app/assets/stylesheets/wf/workitem_assignments.css +4 -0
  18. data/app/assets/stylesheets/wf/workitems.css +4 -0
  19. data/app/controllers/wf/application_controller.rb +14 -0
  20. data/app/controllers/wf/arcs_controller.rb +61 -0
  21. data/app/controllers/wf/cases_controller.rb +48 -0
  22. data/app/controllers/wf/comments_controller.rb +30 -0
  23. data/app/controllers/wf/fields_controller.rb +54 -0
  24. data/app/controllers/wf/forms_controller.rb +59 -0
  25. data/app/controllers/wf/guards_controller.rb +58 -0
  26. data/app/controllers/wf/places_controller.rb +53 -0
  27. data/app/controllers/wf/transitions_controller.rb +69 -0
  28. data/app/controllers/wf/workflows_controller.rb +59 -0
  29. data/app/controllers/wf/workitem_assignments_controller.rb +30 -0
  30. data/app/controllers/wf/workitems_controller.rb +79 -0
  31. data/app/helpers/wf/application_helper.rb +6 -0
  32. data/app/helpers/wf/arcs_helper.rb +6 -0
  33. data/app/helpers/wf/cases_helper.rb +6 -0
  34. data/app/helpers/wf/comments_helper.rb +6 -0
  35. data/app/helpers/wf/fields_helper.rb +6 -0
  36. data/app/helpers/wf/forms_helper.rb +6 -0
  37. data/app/helpers/wf/guards_helper.rb +6 -0
  38. data/app/helpers/wf/places_helper.rb +6 -0
  39. data/app/helpers/wf/transitions_helper.rb +6 -0
  40. data/app/helpers/wf/workflows_helper.rb +6 -0
  41. data/app/helpers/wf/workitem_assignments_helper.rb +6 -0
  42. data/app/helpers/wf/workitems_helper.rb +6 -0
  43. data/app/jobs/wf/application_job.rb +6 -0
  44. data/app/jobs/wf/fire_timed_workitem_job.rb +15 -0
  45. data/app/mailers/wf/application_mailer.rb +8 -0
  46. data/app/models/wf/application_record.rb +7 -0
  47. data/app/models/wf/arc.rb +42 -0
  48. data/app/models/wf/callbacks/assignment_default.rb +12 -0
  49. data/app/models/wf/callbacks/deadline_default.rb +11 -0
  50. data/app/models/wf/callbacks/enable_default.rb +11 -0
  51. data/app/models/wf/callbacks/fire_default.rb +11 -0
  52. data/app/models/wf/callbacks/hold_timeout_default.rb +11 -0
  53. data/app/models/wf/callbacks/notification_default.rb +11 -0
  54. data/app/models/wf/callbacks/time_default.rb +11 -0
  55. data/app/models/wf/callbacks/unassignment_default.rb +11 -0
  56. data/app/models/wf/case.rb +44 -0
  57. data/app/models/wf/case_assignment.rb +22 -0
  58. data/app/models/wf/case_command/add_comment.rb +17 -0
  59. data/app/models/wf/case_command/add_manual_assignment.rb +17 -0
  60. data/app/models/wf/case_command/add_token.rb +20 -0
  61. data/app/models/wf/case_command/add_workitem_assignment.rb +35 -0
  62. data/app/models/wf/case_command/begin_workitem_action.rb +33 -0
  63. data/app/models/wf/case_command/cancel.rb +17 -0
  64. data/app/models/wf/case_command/cancel_workitem.rb +20 -0
  65. data/app/models/wf/case_command/clear_manual_assignments.rb +16 -0
  66. data/app/models/wf/case_command/clear_workitem_assignments.rb +20 -0
  67. data/app/models/wf/case_command/consume_token.rb +23 -0
  68. data/app/models/wf/case_command/enable_transitions.rb +33 -0
  69. data/app/models/wf/case_command/end_workitem_action.rb +25 -0
  70. data/app/models/wf/case_command/finish_workitem.rb +18 -0
  71. data/app/models/wf/case_command/finished_p.rb +28 -0
  72. data/app/models/wf/case_command/fire_message_transition.rb +20 -0
  73. data/app/models/wf/case_command/fire_transition_internal.rb +41 -0
  74. data/app/models/wf/case_command/lock_token.rb +21 -0
  75. data/app/models/wf/case_command/new.rb +17 -0
  76. data/app/models/wf/case_command/release_token.rb +20 -0
  77. data/app/models/wf/case_command/remove_manual_assignment.rb +17 -0
  78. data/app/models/wf/case_command/remove_workitem_assignment.rb +24 -0
  79. data/app/models/wf/case_command/resume.rb +17 -0
  80. data/app/models/wf/case_command/set_workitem_assignments.rb +32 -0
  81. data/app/models/wf/case_command/start_case.rb +19 -0
  82. data/app/models/wf/case_command/start_workitem.rb +26 -0
  83. data/app/models/wf/case_command/suspend.rb +17 -0
  84. data/app/models/wf/case_command/sweep_automatic_transitions.rb +31 -0
  85. data/app/models/wf/case_command/sweep_timed_transitions.rb +16 -0
  86. data/app/models/wf/case_command/workitem_action.rb +20 -0
  87. data/app/models/wf/comment.rb +21 -0
  88. data/app/models/wf/demo_target.rb +18 -0
  89. data/app/models/wf/entry.rb +33 -0
  90. data/app/models/wf/field.rb +53 -0
  91. data/app/models/wf/field_value.rb +30 -0
  92. data/app/models/wf/form.rb +18 -0
  93. data/app/models/wf/group.rb +21 -0
  94. data/app/models/wf/guard.rb +73 -0
  95. data/app/models/wf/party.rb +22 -0
  96. data/app/models/wf/place.rb +28 -0
  97. data/app/models/wf/token.rb +37 -0
  98. data/app/models/wf/transition.rb +49 -0
  99. data/app/models/wf/transition_static_assignment.rb +21 -0
  100. data/app/models/wf/user.rb +26 -0
  101. data/app/models/wf/workflow.rb +182 -0
  102. data/app/models/wf/workitem.rb +78 -0
  103. data/app/models/wf/workitem_assignment.rb +19 -0
  104. data/app/views/layouts/wf/_alert.html.erb +4 -0
  105. data/app/views/layouts/wf/_footer.html.erb +13 -0
  106. data/app/views/layouts/wf/_nav.html.erb +13 -0
  107. data/app/views/layouts/wf/_notice.html.erb +4 -0
  108. data/app/views/layouts/wf/application.html.erb +34 -0
  109. data/app/views/wf/arcs/_form.html.erb +40 -0
  110. data/app/views/wf/arcs/edit.html.erb +1 -0
  111. data/app/views/wf/arcs/new.html.erb +1 -0
  112. data/app/views/wf/arcs/show.html.erb +67 -0
  113. data/app/views/wf/cases/_form.html.erb +27 -0
  114. data/app/views/wf/cases/index.html.erb +35 -0
  115. data/app/views/wf/cases/new.html.erb +1 -0
  116. data/app/views/wf/cases/show.html.erb +113 -0
  117. data/app/views/wf/comments/new.html.erb +27 -0
  118. data/app/views/wf/fields/_form.html.erb +42 -0
  119. data/app/views/wf/fields/edit.html.erb +1 -0
  120. data/app/views/wf/fields/new.html.erb +1 -0
  121. data/app/views/wf/forms/_form.html.erb +32 -0
  122. data/app/views/wf/forms/edit.html.erb +1 -0
  123. data/app/views/wf/forms/index.html.erb +32 -0
  124. data/app/views/wf/forms/new.html.erb +1 -0
  125. data/app/views/wf/forms/show.html.erb +53 -0
  126. data/app/views/wf/guards/_form.html.erb +43 -0
  127. data/app/views/wf/guards/edit.html.erb +1 -0
  128. data/app/views/wf/guards/new.html.erb +1 -0
  129. data/app/views/wf/places/_form.html.erb +43 -0
  130. data/app/views/wf/places/edit.html.erb +1 -0
  131. data/app/views/wf/places/new.html.erb +1 -0
  132. data/app/views/wf/transitions/_form.html.erb +92 -0
  133. data/app/views/wf/transitions/edit.html.erb +1 -0
  134. data/app/views/wf/transitions/new.html.erb +1 -0
  135. data/app/views/wf/workflows/_form.html.erb +32 -0
  136. data/app/views/wf/workflows/edit.html.erb +1 -0
  137. data/app/views/wf/workflows/index.html.erb +38 -0
  138. data/app/views/wf/workflows/new.html.erb +1 -0
  139. data/app/views/wf/workflows/show.html.erb +177 -0
  140. data/app/views/wf/workitem_assignments/new.html.erb +27 -0
  141. data/app/views/wf/workitems/index.html.erb +68 -0
  142. data/app/views/wf/workitems/pre_finish.html.erb +33 -0
  143. data/app/views/wf/workitems/show.html.erb +211 -0
  144. data/config/routes.rb +30 -0
  145. data/db/migrate/20200130201043_init.rb +226 -0
  146. data/db/migrate/20200130201641_init_some_data.rb +15 -0
  147. data/db/migrate/20200131200455_create_wf_entries.rb +19 -0
  148. data/db/migrate/20200201001543_add_target_field_name_for_guard.rb +7 -0
  149. data/lib/tasks/wf_tasks.rake +5 -0
  150. data/lib/wf/engine.rb +10 -0
  151. data/lib/wf/version.rb +5 -0
  152. data/lib/wf.rb +7 -0
  153. metadata +312 -0
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_field_values
6
+ #
7
+ # id :integer not null, primary key
8
+ # workflow_id :integer
9
+ # transition_id :integer
10
+ # form_id :integer
11
+ # field_id :integer
12
+ # value :text
13
+ # created_at :datetime not null
14
+ # updated_at :datetime not null
15
+ # entry_id :integer
16
+ #
17
+
18
+ module Wf
19
+ class FieldValue < ApplicationRecord
20
+ belongs_to :workflow, optional: true
21
+ belongs_to :transition, optional: true
22
+ belongs_to :form
23
+ belongs_to :field
24
+ belongs_to :entry
25
+
26
+ def value_after_cast
27
+ field.type_for_cast.cast(value)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_forms
6
+ #
7
+ # id :integer not null, primary key
8
+ # name :string
9
+ # description :text
10
+ # created_at :datetime not null
11
+ # updated_at :datetime not null
12
+ #
13
+
14
+ module Wf
15
+ class Form < ApplicationRecord
16
+ has_many :fields, dependent: :destroy
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_groups
6
+ #
7
+ # id :integer not null, primary key
8
+ # name :string
9
+ # created_at :datetime not null
10
+ # updated_at :datetime not null
11
+ #
12
+
13
+ module Wf
14
+ class Group < ApplicationRecord
15
+ has_many :users
16
+ has_one :party, as: :partable
17
+ after_create do
18
+ create_party(party_name: name)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_guards
6
+ #
7
+ # id :integer not null, primary key
8
+ # arc_id :integer
9
+ # workflow_id :integer
10
+ # fieldable_type :string
11
+ # fieldable_id :string
12
+ # op :string
13
+ # value :string
14
+ # exp :string
15
+ # created_at :datetime not null
16
+ # updated_at :datetime not null
17
+ # target_attr_name :string
18
+ #
19
+
20
+ module Wf
21
+ class Guard < ApplicationRecord
22
+ belongs_to :workflow
23
+ belongs_to :arc, touch: true, counter_cache: true
24
+ belongs_to :fieldable, polymorphic: true, optional: true
25
+
26
+ before_validation do
27
+ self.workflow = arc.workflow
28
+ end
29
+
30
+ OP = %w[
31
+ =
32
+ >
33
+ <
34
+ >=
35
+ <=
36
+ is_empty
37
+ ].freeze
38
+
39
+ def value_after_cast
40
+ field = fieldable
41
+ if fieldable
42
+ fieldable.type_for_cast.cast(value)
43
+ else
44
+ # TODO
45
+ end
46
+ end
47
+
48
+ def pass?(entry)
49
+ fv = entry.field_values.where(field_id: fieldable_id).first
50
+ return unless fv
51
+
52
+ if op == "="
53
+ fv.value_after_cast == value_after_cast
54
+ elsif op == ">"
55
+ fv.value_after_cast > value_after_cast
56
+ elsif op == "<"
57
+ fv.value_after_cast < value_after_cast
58
+ elsif op == ">="
59
+ fv.value_after_cast >= value_after_cast
60
+ elsif op == "<="
61
+ fv.value_after_cast <= value_after_cast
62
+ elsif op == "is_empty"
63
+ fv.value_after_cast.blank?
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ def inspect
70
+ %(#{fieldable&.form&.name}.#{fieldable&.name} #{op} #{value})
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_parties
6
+ #
7
+ # id :integer not null, primary key
8
+ # partable_type :string
9
+ # partable_id :string
10
+ # party_name :string
11
+ # created_at :datetime not null
12
+ # updated_at :datetime not null
13
+ #
14
+
15
+ module Wf
16
+ class Party < ApplicationRecord
17
+ # TODO: use acts_as_partable for sync group or role or user to party
18
+ belongs_to :partable, polymorphic: true
19
+ has_many :transition_static_assignments
20
+ has_many :workitem_assignments
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_places
6
+ #
7
+ # id :integer not null, primary key
8
+ # workflow_id :integer
9
+ # name :string
10
+ # description :text
11
+ # sort_order :integer default("0")
12
+ # place_type :integer default("0")
13
+ # created_at :datetime not null
14
+ # updated_at :datetime not null
15
+ #
16
+
17
+ module Wf
18
+ class Place < ApplicationRecord
19
+ belongs_to :workflow, touch: true
20
+ has_many :arcs
21
+ has_many :tokens
22
+ enum place_type: {
23
+ start: 0,
24
+ normal: 1,
25
+ end: 2
26
+ }
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_tokens
6
+ #
7
+ # id :integer not null, primary key
8
+ # workflow_id :integer
9
+ # case_id :integer
10
+ # targetable_type :string
11
+ # targetable_id :string
12
+ # place_id :integer
13
+ # state :integer default("0")
14
+ # locked_workitem_id :integer
15
+ # produced_at :datetime
16
+ # locked_at :datetime
17
+ # canceled_at :datetime
18
+ # consumed_at :datetime
19
+ # created_at :datetime not null
20
+ # updated_at :datetime not null
21
+ #
22
+
23
+ module Wf
24
+ class Token < ApplicationRecord
25
+ belongs_to :workflow
26
+ belongs_to :case
27
+ belongs_to :place
28
+ belongs_to :locked_workitem, class_name: "Wf::Workitem", optional: true
29
+
30
+ enum state: {
31
+ free: 0,
32
+ locked: 1,
33
+ canceled: 2,
34
+ consumed: 3
35
+ }
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_transitions
6
+ #
7
+ # id :integer not null, primary key
8
+ # name :string
9
+ # description :text
10
+ # workflow_id :integer
11
+ # sort_order :integer default("0")
12
+ # trigger_limit :integer
13
+ # trigger_type :integer default("0")
14
+ # created_at :datetime not null
15
+ # updated_at :datetime not null
16
+ # form_id :integer
17
+ # enable_callback :string default("Wf::Callbacks::EnableDefault")
18
+ # fire_callback :string default("Wf::Callbacks::FireDefault")
19
+ # notification_callback :string default("Wf::Callbacks::NotificationDefault")
20
+ # time_callback :string default("Wf::Callbacks::TimeDefault")
21
+ # deadline_callback :string default("Wf::Callbacks::DeadlineDefault")
22
+ # hold_timeout_callback :string default("Wf::Callbacks::HoldTimeoutDefault")
23
+ # assignment_callback :string default("Wf::Callbacks::AssignmentDefault")
24
+ # unassignment_callback :string default("Wf::Callbacks::UnassignmentDefault")
25
+ #
26
+
27
+ module Wf
28
+ class Transition < ApplicationRecord
29
+ belongs_to :workflow, touch: true
30
+ has_many :arcs
31
+ has_many :transition_static_assignments
32
+ has_many :static_parties, through: :transition_static_assignments, source: "party"
33
+ has_many :workitems
34
+ belongs_to :form, optional: true
35
+
36
+ enum trigger_type: {
37
+ user: 0,
38
+ automatic: 1,
39
+ message: 2,
40
+ time: 3
41
+ }
42
+
43
+ def explicit_or_split?
44
+ arcs.out.sum(:guards_count) >= 1
45
+ end
46
+
47
+ validates :name, presence: true
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_transition_static_assignments
6
+ #
7
+ # id :integer not null, primary key
8
+ # party_id :integer
9
+ # transition_id :integer
10
+ # workflow_id :integer
11
+ # created_at :datetime not null
12
+ # updated_at :datetime not null
13
+ #
14
+
15
+ module Wf
16
+ class TransitionStaticAssignment < ApplicationRecord
17
+ belongs_to :workflow
18
+ belongs_to :transition
19
+ belongs_to :party
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_users
6
+ #
7
+ # id :integer not null, primary key
8
+ # name :string
9
+ # created_at :datetime not null
10
+ # updated_at :datetime not null
11
+ # group_id :integer
12
+ #
13
+
14
+ module Wf
15
+ class User < ApplicationRecord
16
+ belongs_to :group, optional: true
17
+ has_one :party, as: :partable
18
+
19
+ # NOTICE: group or user or role all has_many users
20
+ has_many :users, foreign_key: :id
21
+
22
+ after_create do
23
+ create_party(party_name: name)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_workflows
6
+ #
7
+ # id :integer not null, primary key
8
+ # name :string
9
+ # description :text
10
+ # is_valid :boolean default("false")
11
+ # creator_id :string
12
+ # error_msg :text
13
+ # created_at :datetime not null
14
+ # updated_at :datetime not null
15
+ #
16
+
17
+ module Wf
18
+ class Workflow < ApplicationRecord
19
+ has_many :places, dependent: :destroy
20
+ has_many :transitions, dependent: :destroy
21
+ has_many :arcs, dependent: :destroy
22
+ has_many :transition_static_assignments
23
+ has_many :cases
24
+ has_many :workitems
25
+ has_many :tokens
26
+
27
+ validates :name, presence: true
28
+
29
+ # TODO: move to config
30
+ def self.enable_callbacks
31
+ [Wf::Callbacks::EnableDefault]
32
+ end
33
+
34
+ def self.fire_callbacks
35
+ [Wf::Callbacks::FireDefault]
36
+ end
37
+
38
+ def self.assignment_callbacks
39
+ [Wf::Callbacks::AssignmentDefault]
40
+ end
41
+
42
+ def self.unassignment_callbacks
43
+ [Wf::Callbacks::UnassignmentDefault]
44
+ end
45
+
46
+ def self.notification_callbacks
47
+ [Wf::Callbacks::NotificationDefault]
48
+ end
49
+
50
+ def self.deadline_callbacks
51
+ [Wf::Callbacks::DeadlineDefault]
52
+ end
53
+
54
+ def self.time_callbacks
55
+ [Wf::Callbacks::TimeDefault]
56
+ end
57
+
58
+ def self.hold_timeout_callbacks
59
+ [Wf::Callbacks::HoldTimeoutDefault]
60
+ end
61
+
62
+ def self.user_class
63
+ ::Wf::User
64
+ end
65
+
66
+ def self.org_classes
67
+ {
68
+ group: ::Wf::Group
69
+ }
70
+ end
71
+
72
+ after_save do
73
+ do_validate!
74
+ end
75
+
76
+ after_touch do
77
+ do_validate!
78
+ end
79
+
80
+ # TODO: can from start place to end place
81
+ def do_validate!
82
+ msgs = []
83
+ msgs << "must have start place" if places.start.blank?
84
+ msgs << "must have only one start place" if places.start.count > 1
85
+ msgs << "must have end place" if places.end.blank?
86
+ msgs << "must have only one end place" if places.end.count > 1
87
+ if msgs.present?
88
+ update_columns(is_valid: false, error_msg: msgs.join("\n"))
89
+ else
90
+ update_columns(is_valid: true, error_msg: "")
91
+ end
92
+ end
93
+
94
+ def to_graph(wf_case = nil)
95
+ graph = GraphViz.new(name, type: :digraph, rankdir: "LR", splines: true, ratio: :auto)
96
+ free_token_places = if wf_case
97
+ wf_case.tokens.free.map(&:place_id)
98
+ else
99
+ []
100
+ end
101
+ pg_mapping = {}
102
+ places.order("place_type ASC").each do |p|
103
+ if p.start?
104
+ fillcolor = :yellow
105
+ shape = :doublecircle
106
+ elsif p.end?
107
+ fillcolor = :green
108
+ shape = :doublecircle
109
+ else
110
+ fillcolor = :lightpink
111
+ shape = :circle
112
+ end
113
+
114
+ token_count = free_token_places.count(p.id)
115
+ if token_count >= 1
116
+ label = "&bull;"
117
+ xlabel = nil
118
+ fontsize = 25
119
+ else
120
+ label = p.name
121
+ xlabel = nil
122
+ end
123
+
124
+ pg = graph.add_nodes(p.name,
125
+ label: label,
126
+ xlabel: xlabel,
127
+ shape: shape,
128
+ fixedsize: true,
129
+ style: :filled,
130
+ fontsize: fontsize,
131
+ fillcolor: fillcolor,
132
+ href: Wf::Engine.routes.url_helpers.edit_workflow_place_path(self, p))
133
+ pg_mapping[p] = pg
134
+ end
135
+
136
+ tg_mapping = {}
137
+ transitions.each do |t|
138
+ tg = graph.add_nodes(t.name, label: t.name, shape: :box, style: :filled, fillcolor: :lightblue, href: Wf::Engine.routes.url_helpers.edit_workflow_transition_path(self, t))
139
+ tg_mapping[t] = tg
140
+ end
141
+
142
+ arcs.order("direction desc").each do |arc|
143
+ label = if arc.guards_count > 0
144
+ arc.guards.map(&:inspect).join(" & ")
145
+ else
146
+ ""
147
+ end
148
+ if arc.in?
149
+ graph.add_edges(
150
+ pg_mapping[arc.place],
151
+ tg_mapping[arc.transition],
152
+ label: label,
153
+ weight: 1,
154
+ labelfloat: false,
155
+ labelfontcolor: :red,
156
+ arrowhead: :vee,
157
+ href: Wf::Engine.routes.url_helpers.edit_workflow_arc_path(self, arc)
158
+ )
159
+ else
160
+ graph.add_edges(
161
+ tg_mapping[arc.transition],
162
+ pg_mapping[arc.place],
163
+ label: label,
164
+ weight: 1,
165
+ labelfloat: false,
166
+ labelfontcolor: :red,
167
+ arrowhead: :vee,
168
+ href: Wf::Engine.routes.url_helpers.edit_workflow_arc_path(self, arc)
169
+ )
170
+ end
171
+ end
172
+ graph
173
+ end
174
+
175
+ def render_graph(wf_case = nil)
176
+ graph = to_graph(wf_case)
177
+ path = Rails.root.join("tmp", "#{id}.svg")
178
+ graph.output(svg: path)
179
+ File.read(path)
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_workitems
6
+ #
7
+ # id :integer not null, primary key
8
+ # case_id :integer
9
+ # workflow_id :integer
10
+ # transition_id :integer
11
+ # targetable_type :string
12
+ # targetable_id :string
13
+ # state :integer default("0")
14
+ # enabled_at :datetime
15
+ # started_at :datetime
16
+ # canceled_at :datetime
17
+ # finished_at :datetime
18
+ # overridden_at :datetime
19
+ # deadline :datetime
20
+ # created_at :datetime not null
21
+ # updated_at :datetime not null
22
+ # trigger_time :datetime
23
+ # holding_user_id :string
24
+ #
25
+
26
+ module Wf
27
+ class Workitem < ApplicationRecord
28
+ belongs_to :workflow
29
+ belongs_to :transition
30
+ belongs_to :case
31
+ belongs_to :targetable, optional: true
32
+ belongs_to :holding_user, foreign_key: :holding_user_id, class_name: Workflow.user_class.to_s, optional: true
33
+ has_many :workitem_assignments
34
+ has_many :parties, through: :workitem_assignments, source: "party"
35
+ has_many :comments
36
+ has_many :entries
37
+
38
+ enum state: {
39
+ enabled: 0,
40
+ started: 1,
41
+ canceled: 2,
42
+ finished: 3,
43
+ overridden: 4
44
+ }
45
+
46
+ def name
47
+ "Workitem -> #{id}"
48
+ end
49
+
50
+ # TODO: guards exp && target guards
51
+ def pass_guard?(arc, has_passed = false)
52
+ if arc.guards_count == 0
53
+ !has_passed
54
+ else
55
+ entry = entries.where(user: holding_user).first
56
+ arc.guards.all? { |guard| guard.pass?(entry) }
57
+ end
58
+ end
59
+
60
+ def started_by?(user)
61
+ enabled? && owned_by?(user)
62
+ end
63
+
64
+ def finished_by?(user)
65
+ started? && owned_by?(user) && holding_user == user
66
+ end
67
+
68
+ def owned_by?(user)
69
+ Wf::Party.joins(workitem_assignments: { workitem: %i[transition case] })
70
+ .where(Wf::Transition.table_name => { trigger_type: Wf::Transition.trigger_types[:user] })
71
+ .where(Wf::Case.table_name => { state: Wf::Case.states[:active] })
72
+ .where(Wf::Workitem.table_name => { state: Wf::Workitem.states.values_at(:started, :enabled) })
73
+ .where(Wf::Workitem.table_name => { id: id }).map do |party|
74
+ party.partable.users.to_a
75
+ end.flatten.include?(user)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: wf_workitem_assignments
6
+ #
7
+ # id :integer not null, primary key
8
+ # party_id :integer
9
+ # workitem_id :integer
10
+ # created_at :datetime not null
11
+ # updated_at :datetime not null
12
+ #
13
+
14
+ module Wf
15
+ class WorkitemAssignment < ApplicationRecord
16
+ belongs_to :party
17
+ belongs_to :workitem
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ <% return if alert.blank? %>
2
+ <div id="alert" class="alert alert-warning">
3
+ <p><%= alert %></p>
4
+ </div>
@@ -0,0 +1,13 @@
1
+ <footer class="footer">
2
+ <div class="container">
3
+ <div class="has-text-centered">
4
+ <p>
5
+ <strong>
6
+ <a href="https://github.com/hooopo/petri_flow" target="_blank" rel="nofollow">Petri Flow</a>
7
+ </strong>
8
+ crafted by <a href="https://github.com/hooopo" target="_blank" rel="nofollow">Hooopo</a>.
9
+ The source code is licensed under <a href="http://opensource.org/licenses/mit-license.php">MIT license</a>.
10
+ </p>
11
+ </div>
12
+ </div>
13
+ </footer>
@@ -0,0 +1,13 @@
1
+ <nav class="navbar has-shadow" role="navigation" aria-label="main navigation">
2
+ <div class="container">
3
+ <div class="navbar-brand">
4
+ <%= link_to "Petri Flow", root_path, class: "navbar-item" %>
5
+ </div>
6
+
7
+ <div class="navbar-end">
8
+ <%= link_to current_user&.name, workitems_path %>
9
+ <%= link_to 'Forms', forms_path, class: "navbar-item #{"is-active" if controller_name == "forms"}" %>
10
+ <%= link_to 'Workflows', workflows_path, class: "navbar-item #{"is-active" if controller_name.start_with?("workflows")}" %>
11
+ </div>
12
+ </div>
13
+ </nav>
@@ -0,0 +1,4 @@
1
+ <% return if notice.blank? %>
2
+ <div id="notice" class="alert alert-success">
3
+ <p><%= notice %></p>
4
+ </div>
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Petri Flow</title>
5
+
6
+ <%= csrf_meta_tags %>
7
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
8
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0"/>
10
+
11
+ <%= stylesheet_link_tag "wf/application", media: "all", "data-turbolinks-track": "reload" %>
12
+ <%= javascript_include_tag "wf/application", "data-turbolinks-track": "reload" %>
13
+ </head>
14
+ <body>
15
+ <%= render "layouts/wf/notice" %>
16
+ <%= render "layouts/wf/alert" %>
17
+ <%= render "layouts/wf/nav" %>
18
+
19
+ <main class="container">
20
+ <nav aria-label="breadcrumb">
21
+ <ol class='breadcrumb'>
22
+ <% breadcrumb_trail do |crumb| %>
23
+ <li class="breadcrumb-item <%= crumb.current? ? 'active' : '' %>">
24
+ <%= link_to crumb.name, crumb.url, (crumb.current? ? {'aria-current' => 'page'} : {}) %>
25
+ </li>
26
+ <% end %>
27
+ </ol>
28
+ </nav>
29
+ <%= content_for?(:content) ? yield(:content) : yield %>
30
+ </main>
31
+
32
+ <%= render "layouts/wf/footer" %>
33
+ </body>
34
+ </html>