Features:
- Custom workflow creation per project/tracker
- Step-by-step workflow definition
- Assignees per step (user, role group, department)
- Next/Previous step navigation
- Reject to first step
- Skip step (admin only)
- Step deadline settings
- Workflow dashboard
- Group member selection when proceeding
🤖 Generated with Claude Code
112 lines
3.4 KiB
Ruby
112 lines
3.4 KiB
Ruby
class IssueWorkflowState < ActiveRecord::Base
|
|
belongs_to :issue
|
|
belongs_to :custom_workflow
|
|
belongs_to :current_step, class_name: 'WorkflowStep', optional: true
|
|
belongs_to :completed_by, class_name: 'User', optional: true
|
|
|
|
validates :issue_id, presence: true, uniqueness: true
|
|
validates :custom_workflow_id, presence: true
|
|
|
|
def current_step_name
|
|
current_step&.name || '(시작 전)'
|
|
end
|
|
|
|
def progress_percent
|
|
return 0 unless current_step
|
|
total = custom_workflow.step_count
|
|
return 100 if current_step.last_step?
|
|
|
|
current_position = current_step.position + 1
|
|
(current_position.to_f / total * 100).round
|
|
end
|
|
|
|
def can_proceed?(user)
|
|
return false unless current_step
|
|
return false if completed?
|
|
return true if user.admin? # 관리자는 항상 가능
|
|
return true if current_step.first_step? && issue.author == user # 첫 단계는 작성자가 진행 가능
|
|
return true if current_step.workflow_step_assignees.empty? # 담당자 없으면 누구나 가능
|
|
current_step.assignee?(user)
|
|
end
|
|
|
|
def can_go_back?(user)
|
|
return false unless current_step
|
|
return false if current_step.first_step?
|
|
# 관리자이거나 이전 단계 담당자면 가능
|
|
user.admin? || current_step.prev_step&.assignee?(user)
|
|
end
|
|
|
|
def proceed!(user, selected_assignee_id = nil)
|
|
return false unless can_proceed?(user)
|
|
|
|
next_step = current_step.next_step
|
|
if next_step
|
|
update(current_step: next_step)
|
|
update_issue_for_step(next_step, selected_assignee_id)
|
|
else
|
|
# 마지막 단계 완료
|
|
update(completed_at: Time.current, completed_by: user)
|
|
# 마지막 단계의 상태로 변경
|
|
update_issue_for_step(current_step)
|
|
end
|
|
true
|
|
end
|
|
|
|
def go_back!(user)
|
|
return false unless can_go_back?(user)
|
|
|
|
prev_step = current_step.prev_step
|
|
if prev_step
|
|
update(current_step: prev_step, completed_at: nil, completed_by: nil)
|
|
update_issue_for_step(prev_step)
|
|
end
|
|
true
|
|
end
|
|
|
|
def completed?
|
|
completed_at.present?
|
|
end
|
|
|
|
def visible_to?(user)
|
|
return true if user.admin?
|
|
return true if issue.author == user
|
|
return true if current_step&.assignee?(user)
|
|
|
|
# 모든 단계의 담당자인지 확인
|
|
custom_workflow.workflow_steps.any? { |step| step.assignee?(user) }
|
|
end
|
|
|
|
private
|
|
|
|
def update_issue_for_step(step, selected_assignee_id = nil)
|
|
return unless step
|
|
|
|
# 이력 남기기 위해 journal 초기화
|
|
issue.init_journal(User.current, "워크플로우: #{step.name} 단계로 이동")
|
|
|
|
# 상태 변경
|
|
if step.issue_status_id.present?
|
|
issue.status_id = step.issue_status_id
|
|
end
|
|
|
|
# 담당자 변경
|
|
if selected_assignee_id.present?
|
|
# 선택된 담당자가 있으면 그 사람으로 할당
|
|
issue.assigned_to_id = selected_assignee_id
|
|
else
|
|
# 개인 사용자가 지정된 경우 첫번째 사용자로 할당
|
|
user_assignees = step.workflow_step_assignees.where(assignee_type: 'user')
|
|
if user_assignees.any?
|
|
issue.assigned_to_id = user_assignees.first.assignee_id
|
|
elsif step.workflow_step_assignees.any?
|
|
# 그룹/부서만 있고 선택 안된 경우 첫번째 멤버로 할당
|
|
assignees = step.all_assignee_users
|
|
issue.assigned_to_id = assignees.first&.id
|
|
end
|
|
end
|
|
|
|
# validate: false로 저장 (프로젝트 멤버 체크 우회, 이력은 남김)
|
|
issue.save(validate: false)
|
|
end
|
|
end
|