diff --git a/app/controllers/project_admin/workflows/statuses_controller.rb b/app/controllers/project_admin/workflows/statuses_controller.rb index 3157233..6ba9922 100644 --- a/app/controllers/project_admin/workflows/statuses_controller.rb +++ b/app/controllers/project_admin/workflows/statuses_controller.rb @@ -17,7 +17,7 @@ module ProjectAdmin end @form = ProjectAdmin::Workflows::Statuses::BatchUpdate.new(form_params) - if @form.call(@workflow) + if @form.perform(@workflow) redirect_to(action: :edit_transitions) else render :edit @@ -29,13 +29,14 @@ module ProjectAdmin end 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) form_params[:task_statuses_attributes] = form_params[:task_statuses_attributes].values end @form = ProjectAdmin::Workflows::Statuses::UpdateTransitions.new(form_params) - if @form.call(@workflow) + if @form.perform(@workflow) redirect_to project_admin_workflow_path(@project, @workflow) else render :edit_transitions diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index 44313d2..964947e 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -13,7 +13,7 @@ class TasksController < ApplicationController Task.all end - @tasks = @tasks.includes(:status, :project, workflow: :task_statuses) + @tasks = @tasks.includes(:project, workflow: %i[task_statuses default_status], status: :next_statuses) end def show; end @@ -21,7 +21,8 @@ class TasksController < ApplicationController def new @project = self.current_project = fetch_project || Project.order(:name).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 def create diff --git a/app/models/workflow.rb b/app/models/workflow.rb index dc39ade..3bc135a 100644 --- a/app/models/workflow.rb +++ b/app/models/workflow.rb @@ -6,7 +6,18 @@ class Workflow < ApplicationRecord has_many :tasks, dependent: :restrict_with_exception has_many :task_statuses, dependent: :restrict_with_error 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 :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 diff --git a/app/services/project_admin/workflows/statuses/batch_update.rb b/app/services/project_admin/workflows/statuses/batch_update.rb index 40bb1b4..d45fb25 100644 --- a/app/services/project_admin/workflows/statuses/batch_update.rb +++ b/app/services/project_admin/workflows/statuses/batch_update.rb @@ -40,7 +40,7 @@ module ProjectAdmin @task_statuses = Array(attributes).map { |e| TaskStatus.new(e) } end - def call(workflow) + def perform(workflow) @workflow = workflow @workflow.assign_attributes(task_statuses_attributes: task_statuses.map(&:to_model_attributes)) diff --git a/app/services/project_admin/workflows/statuses/update_transitions.rb b/app/services/project_admin/workflows/statuses/update_transitions.rb index 05eb6ad..3fad643 100644 --- a/app/services/project_admin/workflows/statuses/update_transitions.rb +++ b/app/services/project_admin/workflows/statuses/update_transitions.rb @@ -12,20 +12,50 @@ module ProjectAdmin attribute :next_status_ids end - attr_accessor :task_statuses + attr_accessor :workflow, :task_statuses + + attribute :default_status_id, :integer def self.from_model(workflow) - new(task_statuses_attributes: workflow.task_statuses - .includes(:next_statuses) - .map { |ts| { id: ts.id, next_status_ids: ts.next_status_ids } }) + new( + workflow:, + 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 def task_statuses_attributes=(attributes) @task_statuses = Array(attributes).map { |e| TaskStatus.new(e) } end - def call(workflow) - workflow.update(task_statuses_attributes: task_statuses.map(&:attributes)) + def perform(workflow) + @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 diff --git a/app/view_models/tasks/statuses/selector_view_model.rb b/app/view_models/tasks/statuses/selector_view_model.rb index 5a3c11c..37093e2 100644 --- a/app/view_models/tasks/statuses/selector_view_model.rb +++ b/app/view_models/tasks/statuses/selector_view_model.rb @@ -40,7 +40,11 @@ module Tasks private 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 diff --git a/app/views/project_admin/workflows/show.html.slim b/app/views/project_admin/workflows/show.html.slim index 39831e5..4a5d9aa 100644 --- a/app/views/project_admin/workflows/show.html.slim +++ b/app/views/project_admin/workflows/show.html.slim @@ -9,5 +9,5 @@ h1 h2 Statuses ul - - @workflow.task_statuses.each do |status| + - @workflow.task_statuses.default_order.each do |status| li= task_status_badge(status) diff --git a/app/views/project_admin/workflows/statuses/edit_transitions.html.slim b/app/views/project_admin/workflows/statuses/edit_transitions.html.slim index 4ede69f..d0d6605 100644 --- a/app/views/project_admin/workflows/statuses/edit_transitions.html.slim +++ b/app/views/project_admin/workflows/statuses/edit_transitions.html.slim @@ -3,6 +3,7 @@ = f.fields_for :task_statuses do |tsf| section 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 .submit diff --git a/db/migrate/20260425123859_add_default_status_to_workflows.rb b/db/migrate/20260425123859_add_default_status_to_workflows.rb new file mode 100644 index 0000000..9782d23 --- /dev/null +++ b/db/migrate/20260425123859_add_default_status_to_workflows.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 56ae9ac..e87c5e9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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| t.text "body", size: :long 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| t.string "color", null: false t.datetime "created_at", null: false + t.bigint "default_status_id" t.string "icon", null: false t.string "name", null: false t.bigint "project_id", 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: "index_workflows_on_project_id" 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", "task_statuses", column: "status_id" add_foreign_key "workflows", "projects" + add_foreign_key "workflows", "task_statuses", column: "default_status_id" end