Workflow default status and computing positions

This commit is contained in:
2026-04-25 16:17:14 +03:00
parent 2a0a70c290
commit debff6dc22
10 changed files with 71 additions and 15 deletions
@@ -17,7 +17,7 @@ module ProjectAdmin
end end
@form = ProjectAdmin::Workflows::Statuses::BatchUpdate.new(form_params) @form = ProjectAdmin::Workflows::Statuses::BatchUpdate.new(form_params)
if @form.call(@workflow) if @form.perform(@workflow)
redirect_to(action: :edit_transitions) redirect_to(action: :edit_transitions)
else else
render :edit render :edit
@@ -29,13 +29,14 @@ module ProjectAdmin
end end
def batch_update_transitions def batch_update_transitions
form_params = params.expect(workflow: { task_statuses_attributes: [[:id, { next_status_ids: [] }]] }) form_params = params.expect(workflow: [:default_status_id,
{ task_statuses_attributes: [[:id, { next_status_ids: [] }]] }])
if form_params[:task_statuses_attributes].respond_to?(:keys) if form_params[:task_statuses_attributes].respond_to?(:keys)
form_params[:task_statuses_attributes] = form_params[:task_statuses_attributes].values form_params[:task_statuses_attributes] = form_params[:task_statuses_attributes].values
end end
@form = ProjectAdmin::Workflows::Statuses::UpdateTransitions.new(form_params) @form = ProjectAdmin::Workflows::Statuses::UpdateTransitions.new(form_params)
if @form.call(@workflow) if @form.perform(@workflow)
redirect_to project_admin_workflow_path(@project, @workflow) redirect_to project_admin_workflow_path(@project, @workflow)
else else
render :edit_transitions render :edit_transitions
+3 -2
View File
@@ -13,7 +13,7 @@ class TasksController < ApplicationController
Task.all Task.all
end end
@tasks = @tasks.includes(:status, :project, workflow: :task_statuses) @tasks = @tasks.includes(:project, workflow: %i[task_statuses default_status], status: :next_statuses)
end end
def show; end def show; end
@@ -21,7 +21,8 @@ class TasksController < ApplicationController
def new def new
@project = self.current_project = fetch_project || Project.order(:name).first @project = self.current_project = fetch_project || Project.order(:name).first
@workflow = fetch_workflow || @project.workflows.first @workflow = fetch_workflow || @project.workflows.first
@form = Tasks::Create.new(project_id: @project.id, workflow_id: @workflow.id) @form = Tasks::Create.new(project_id: @project.id, workflow_id: @workflow.id,
status_id: @workflow.default_status&.id)
end end
def create def create
+11
View File
@@ -6,7 +6,18 @@ class Workflow < ApplicationRecord
has_many :tasks, dependent: :restrict_with_exception has_many :tasks, dependent: :restrict_with_exception
has_many :task_statuses, dependent: :restrict_with_error has_many :task_statuses, dependent: :restrict_with_error
accepts_nested_attributes_for :task_statuses, allow_destroy: true accepts_nested_attributes_for :task_statuses, allow_destroy: true
belongs_to :default_status, class_name: 'TaskStatus', optional: true
enum :icon, { task: 'task', warning: 'warning' }, default: 'task', scopes: false enum :icon, { task: 'task', warning: 'warning' }, default: 'task', scopes: false
enum :color, { blue: 'blue', gray: 'gray', lime: 'lime', red: 'red', teal: 'teal' }, default: 'gray', scopes: false enum :color, { blue: 'blue', gray: 'gray', lime: 'lime', red: 'red', teal: 'teal' }, default: 'gray', scopes: false
validate :should_own_default_status
private
def should_own_default_status
return if default_status.nil?
errors.add(:default_status, 'Should own default status') unless default_status.id.in?(task_status_ids)
end
end end
@@ -40,7 +40,7 @@ module ProjectAdmin
@task_statuses = Array(attributes).map { |e| TaskStatus.new(e) } @task_statuses = Array(attributes).map { |e| TaskStatus.new(e) }
end end
def call(workflow) def perform(workflow)
@workflow = workflow @workflow = workflow
@workflow.assign_attributes(task_statuses_attributes: task_statuses.map(&:to_model_attributes)) @workflow.assign_attributes(task_statuses_attributes: task_statuses.map(&:to_model_attributes))
@@ -12,20 +12,50 @@ module ProjectAdmin
attribute :next_status_ids attribute :next_status_ids
end end
attr_accessor :task_statuses attr_accessor :workflow, :task_statuses
attribute :default_status_id, :integer
def self.from_model(workflow) def self.from_model(workflow)
new(task_statuses_attributes: workflow.task_statuses new(
.includes(:next_statuses) workflow:,
.map { |ts| { id: ts.id, next_status_ids: ts.next_status_ids } }) task_statuses_attributes: workflow.task_statuses
.includes(:next_statuses)
.map { |ts| { id: ts.id, next_status_ids: ts.next_status_ids } },
default_status_id: workflow.default_status_id
)
end end
def task_statuses_attributes=(attributes) def task_statuses_attributes=(attributes)
@task_statuses = Array(attributes).map { |e| TaskStatus.new(e) } @task_statuses = Array(attributes).map { |e| TaskStatus.new(e) }
end end
def call(workflow) def perform(workflow)
workflow.update(task_statuses_attributes: task_statuses.map(&:attributes)) @workflow = workflow
workflow.assign_attributes(default_status_id:, task_statuses_attributes: task_statuses.map(&:attributes))
save workflow
end
after_success do
if @workflow.default_status.nil?
@workflow.task_statuses.update!(position: 0)
return
end
@workflow.transaction do
@workflow.default_status.update!(position: 0)
seen_status_ids = Set[@workflow.default_status.id]
statuses_to_process = [@workflow.default_status]
until statuses_to_process.empty?
status = statuses_to_process.pop
next_statuses = status.next_statuses.where.not(id: seen_status_ids)
next_statuses.update(position: status.position + 1)
statuses_to_process.concat(next_statuses.to_a)
seen_status_ids.merge(next_statuses.map(&:id))
end
end
end end
end end
end end
@@ -40,7 +40,11 @@ module Tasks
private private
def workflow_task_statuses def workflow_task_statuses
@task.workflow.task_statuses.sort_by { |e| [e.position, e.name] } return @task.workflow.task_statuses.sort_by { |e| [e.position, e.name] } if @task.status.next_statuses.empty?
@task.status.next_statuses.sort_by { |e| [e.position, e.name] }.tap do |statuses|
statuses.prepend(@task.status)
end
end end
end end
end end
@@ -9,5 +9,5 @@ h1
h2 Statuses h2 Statuses
ul ul
- @workflow.task_statuses.each do |status| - @workflow.task_statuses.default_order.each do |status|
li= task_status_badge(status) li= task_status_badge(status)
@@ -3,6 +3,7 @@
= f.fields_for :task_statuses do |tsf| = f.fields_for :task_statuses do |tsf|
section section
div= task_status_badge tsf.object div= task_status_badge tsf.object
div= f.radio_button :default_status_id, tsf.object.id
div= tsf.collection_checkboxes :next_status_ids, @workflow.task_statuses - [tsf.object], :id, :name div= tsf.collection_checkboxes :next_status_ids, @workflow.task_statuses - [tsf.object], :id, :name
.submit .submit
@@ -0,0 +1,5 @@
class AddDefaultStatusToWorkflows < ActiveRecord::Migration[8.1]
def change
add_reference :workflows, :default_status, foreign_key: { to_table: :task_statuses }
end
end
Generated
+4 -1
View File
@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_04_25_112121) do ActiveRecord::Schema[8.1].define(version: 2026_04_25_123859) do
create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
t.text "body", size: :long t.text "body", size: :long
t.datetime "created_at", null: false t.datetime "created_at", null: false
@@ -114,10 +114,12 @@ ActiveRecord::Schema[8.1].define(version: 2026_04_25_112121) do
create_table "workflows", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| create_table "workflows", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
t.string "color", null: false t.string "color", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.bigint "default_status_id"
t.string "icon", null: false t.string "icon", null: false
t.string "name", null: false t.string "name", null: false
t.bigint "project_id", null: false t.bigint "project_id", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["default_status_id"], name: "index_workflows_on_default_status_id"
t.index ["project_id", "name"], name: "index_workflows_on_project_id_and_name", unique: true t.index ["project_id", "name"], name: "index_workflows_on_project_id_and_name", unique: true
t.index ["project_id"], name: "index_workflows_on_project_id" t.index ["project_id"], name: "index_workflows_on_project_id"
end end
@@ -130,4 +132,5 @@ ActiveRecord::Schema[8.1].define(version: 2026_04_25_112121) do
add_foreign_key "tasks", "projects" add_foreign_key "tasks", "projects"
add_foreign_key "tasks", "task_statuses", column: "status_id" add_foreign_key "tasks", "task_statuses", column: "status_id"
add_foreign_key "workflows", "projects" add_foreign_key "workflows", "projects"
add_foreign_key "workflows", "task_statuses", column: "default_status_id"
end end