petri_flow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>