forgeplus/app/models/issue.rb

362 lines
15 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# == Schema Information
#
# Table name: issues
#
# id :integer not null, primary key
# tracker_id :integer not null
# project_id :integer not null
# subject :string(255)
# description :text(4294967295)
# due_date :date
# category_id :integer
# status_id :integer not null
# assigned_to_id :integer
# priority_id :integer
# fixed_version_id :integer
# author_id :integer not null
# created_on :datetime
# updated_on :datetime
# start_date :date
# done_ratio :integer default("0"), not null
# estimated_hours :float(24)
# parent_id :integer
# root_id :integer
# lft :integer
# rgt :integer
# is_private :boolean default("0"), not null
# closed_on :datetime
# project_issues_index :integer
# issue_type :string(255)
# token :integer default("0")
# issue_tags_value :string(255)
# is_lock :boolean default("0")
# issue_classify :string(255)
# ref_name :string(255)
# branch_name :string(255)
# blockchain_token_num :integer
# pm_project_id :integer
# pm_sprint_id :integer
# pm_issue_type :integer
# time_scale :decimal(10, 2) default("0.00")
# child_count :integer default("0")
# changer_id :integer
#
# Indexes
#
# index_issues_on_assigned_to_id (assigned_to_id)
# index_issues_on_author_id (author_id)
# index_issues_on_category_id (category_id)
# index_issues_on_changer_id (changer_id)
# index_issues_on_created_on (created_on)
# index_issues_on_fixed_version_id (fixed_version_id)
# index_issues_on_priority_id (priority_id)
# index_issues_on_root_id_and_lft_and_rgt (root_id,lft,rgt)
# index_issues_on_status_id (status_id)
# index_issues_on_tracker_id (tracker_id)
# issues_project_id (project_id)
#
class Issue < ApplicationRecord
#issue_type 1为普通2为悬赏
belongs_to :project, counter_cache: true, touch: true, optional: true
belongs_to :tracker,optional: true
has_many :project_trends, as: :trend, dependent: :destroy
has_one :pull_request
# belongs_to :issue_tag,optional: true
belongs_to :priority, class_name: 'IssuePriority', foreign_key: :priority_id,optional: true
belongs_to :version, foreign_key: :fixed_version_id,optional: true, counter_cache: true
belongs_to :user,optional: true, foreign_key: :author_id
belongs_to :issue_status, foreign_key: :status_id,optional: true
belongs_to :parent_issue, class_name: 'Issue', optional: true, foreign_key: :root_id, counter_cache: :child_count
has_many :commit_issues
has_many :attachments, as: :container, dependent: :destroy
# has_many :memos
has_many :journals, as: :journalized, dependent: :destroy
has_many :journal_details, through: :journals
has_many :claims, dependent: :destroy
has_many :claim_users, through: :claims, source: :user
has_many :issue_tags_relates, dependent: :destroy
has_many :issue_tags, through: :issue_tags_relates
has_many :issue_times, dependent: :destroy
has_many :issue_depends, dependent: :destroy
has_many :issue_assigners, dependent: :destroy
has_many :assigners, through: :issue_assigners
has_many :issue_participants, dependent: :destroy
has_many :participants, through: :issue_participants
has_many :children_issues, class_name: 'Issue', foreign_key: :root_id, dependent: :destroy
has_many :show_participants, -> {joins(:issue_participants).where.not(issue_participants: {participant_type: 'atme'}).distinct}, through: :issue_participants, source: :participant
has_many :show_assigners, -> {joins(:issue_assigners).distinct}, through: :issue_assigners, source: :assigner
has_many :show_issue_tags, -> {joins(:issue_tags_relates).distinct}, through: :issue_tags_relates, source: :issue_tag
has_many :comment_journals, -> {where.not(notes: nil)}, class_name: 'Journal', as: :journalized
has_many :operate_journals, -> {where(notes: nil)}, class_name: 'Journal', as: :journalized
has_many :pull_attached_issues, dependent: :destroy
has_many :attach_pull_requests, through: :pull_attached_issues, source: :pull_request
# PM 关联工作项目
has_many :pm_links, as: :linkable, dependent: :destroy
belongs_to :changer, class_name: 'User', foreign_key: :changer_id, optional: true
scope :issue_includes, ->{includes(:user)}
scope :issue_many_includes, ->{includes(journals: :user)}
scope :issue_issue, ->{where(issue_classify: [nil, 'issue'])}
scope :issue_pull_request, ->{where(issue_classify: 'pull_request')}
scope :issue_index_includes, ->{includes(:tracker, :priority, :version, :issue_status, :journals,:issue_tags,user: :user_extension)}
scope :pm_includes, -> {includes(:project, :show_issue_tags, :issue_status, :priority, :version, :user, :show_assigners, :comment_journals, :operate_journals)}
scope :closed, ->{where(status_id: 5)}
scope :opened, ->{where.not(status_id: 5)}
after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic
before_save :check_pm_and_update_due_date
after_save :incre_or_decre_closed_issues_count, :change_versions_count, :send_update_message_to_notice_system, :associate_attachment_container, :generate_uuid
after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic, :destroy_be_pm_links
def pm_issue_type_string
case pm_issue_type
when 1
"requirement"
when 2
"task"
when 3
"bug"
else
"issue"
end
end
def destroy_be_pm_links
PmLink.where(be_linkable_type:"Issue",be_linkable_id:self.id).map(&:destroy)
end
def check_pm_and_update_due_date
if pm_project_id.present? && pm_issue_type.present? && status_id_changed?
status_ids = case pm_issue_type
when 1
[3,5]
when 2
[3,5]
when 3
[5]
else
[]
end
if status_ids.include? self.status_id
self.due_date = self.due_date || Time.current
end
end
end
def is_issuely_issue?
self.issue_classify.nil? || self.issue_classify == 'issue'
end
def incre_or_decre_closed_issues_count
if previous_changes[:status_id].present? && is_issuely_issue?
if previous_changes[:status_id][1] == 5
CacheAsyncSetJob.perform_later("project_common_service", {closed_issues: 1}, self.project_id)
end
if previous_changes[:status_id][0] == 5
CacheAsyncSetJob.perform_later("project_common_service", {closed_issues: -1}, self.project_id)
end
end
end
def incre_project_common
CacheAsyncSetJob.perform_later('project_common_service', {issues: 1}, self.project_id) if is_issuely_issue?
end
def decre_project_common
CacheAsyncSetJob.perform_later('project_common_service', {issues: -1}, self.project_id) if is_issuely_issue?
end
def incre_user_statistic
CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: 1}, self.author_id) if is_issuely_issue?
end
def decre_user_statistic
CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: -1}, self.author_id) if is_issuely_issue?
end
def refresh_root_issue_count
return if root_id.nil? || root_id.zero?
root_issue = Issue.find_by(id: root_id)
root_count = Issue.where(root_id: root_id).count
root_issue.update(child_count: root_count)
end
def incre_platform_statistic
CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: 1}) if is_issuely_issue?
end
def decre_platform_statistic
CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: -1}) if is_issuely_issue?
end
def get_assign_user
User&.find_by_id(self.assigned_to_id) if self.assigned_to_id.present?
end
def create_journal_detail(change_files, issue_files, issue_file_ids, user_id)
journal_params = {
journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
}
journal = Journal.new journal_params
if journal.save
if change_files
old_attachment_names = self.attachments.select(:filename,:id).where(id: issue_file_ids).pluck(:filename).join(',')
new_attachment_name = self.attachments.select(:filename,:id).where(id: issue_files).pluck(:filename).join(',')
journal.journal_details.create(property: 'attachment', prop_key: "#{issue_files.size}", old_value: old_attachment_names, value: new_attachment_name)
end
change_values = %w(subject description is_private assigned_to_id tracker_id status_id priority_id fixed_version_id start_date due_date estimated_hours done_ratio issue_tags_value issue_type token branch_name)
change_values.each do |at|
if self.send("saved_change_to_#{at}?")
journal.journal_details.create(property: 'attr', prop_key: "#{at}", old_value: self.send("#{at}_before_last_save"), value: self.send(at))
end
end
end
end
def custom_journal_detail(prop_key, old_value, value, user_id)
journal_params = {
journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
}
journal = Journal.new journal_params
if journal.save
journal.journal_details.create(property: 'attr', prop_key: prop_key, old_value: old_value, value: value)
end
end
def get_journals_size
journals.size
end
def self.issues_count(tracker_id)
includes(:trakcer).where(tracker_id: tracker_id).size
end
def get_issue_tags
if issue_tags.present?
issue_tags.select(:id,:name,:color).uniq.as_json
else
nil
end
end
def generate_uuid
# return if pm_project_id.nil?
# attachments.map(&:generate_uuid)
end
def is_collaborators?
if self.assigned_to_id.present? && self.project.present?
self.project.member?(self.assigned_to_id)
else
false
end
end
def get_issue_tags_name
if issue_tags.present?
issue_tags.select(:name).uniq.pluck(:name).join(',')
else
nil
end
end
def only_reply_journals
journals.where.not(notes: [nil, '']).journal_includes.limit(2)
end
def change_versions_count
if self.saved_change_to_fixed_version_id?
before_version = Version.find_by_id(self.fixed_version_id_before_last_save)
Rails.logger.info self.fixed_version_id_before_last_save
if before_version.present?
Rails.logger.info self.status_id
Rails.logger.info self.status_id_before_last_save
# 更改前状态为完成 或者 更改前后都为完成状态
if self.status_id_before_last_save == 5
percent = before_version.issues_count == 0 ? 0.0 : ((before_version.closed_issues_count - 1).to_f / before_version.issues_count)
before_version.update_attributes(closed_issues_count: (before_version.closed_issues_count - 1), percent: percent)
end
end
end
if self.version.present? && (self.saved_change_to_status_id? || self.saved_change_to_fixed_version_id?)
if self.status_id == 5
percent = self.version.issues_count == 0 ? 0.0 : ((self.version.closed_issues_count + 1).to_f / self.version.issues_count)
self.version.update_attributes(closed_issues_count: (self.version.closed_issues_count + 1), percent: percent)
elsif self.status_id_before_last_save == 5 && !self.saved_change_to_fixed_version_id?
percent = self.version.issues_count == 0 ? 0.0 : ((self.version.closed_issues_count - 1).to_f / self.version.issues_count)
self.version.update_attributes(closed_issues_count: (self.version.closed_issues_count - 1), percent: percent)
end
end
end
def update_closed_issues_count_in_project!
self.project.decrement!(:closed_issues_count) if self.status_id == 5 && self.project.present?
end
def send_update_message_to_notice_system
SendTemplateMessageJob.perform_later('IssueExpire', self.id) if Site.has_notice_menu? && self.due_date == Date.today + 1.days
end
# 关附件到功能
def associate_attachment_container
return if self.project_id == 0
att_ids = []
# 附件的格式为(/api/attachments/ + 附件id的形式提取出id进行附件属性关联做附件访问权限控制
att_ids += self.description.to_s.scan(/\(\/api\/attachments\/.+\)/).map{|s|s.match(/\d+/)[0]}
att_ids += self.description.to_s.scan(/\/api\/attachments\/.+\"/).map{|s|s.match(/\d+/)[0]}
att_ids += self.description.to_s.scan(/\/api\/attachments\/\d+/).map{|s|s.match(/\d+/)[0]}
if att_ids.present?
Attachment.where(id: att_ids).where("container_type IS NULL OR container_type = 'Issue'").update_all(container_id: self.project_id, container_type: 'Project')
end
att_ids2 = []
# uuid_regex= /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
# 附件的格式为(/api/attachments/ + uuid的形式提取出id进行附件属性关联做附件访问权限控制
att_ids2 += self.description.to_s.scan(/\(\/api\/attachments\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\)/).map{|s|s.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/)[0]}
att_ids2 += self.description.to_s.scan(/\/api\/attachments\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/).map{|s|s.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/)[0]}
if att_ids2.present?
Attachment.where(uuid: att_ids2).where("container_type IS NULL OR container_type = 'Issue'").update_all(container_id: self.project_id, container_type: 'Project')
end
end
def to_builder
Jbuilder.new do |issue|
issue.(self, :id, :project_issues_index, :subject, :description, :branch_name, :start_date, :due_date)
issue.created_at self.created_on.strftime('%Y-%m-%d %H:%M')
issue.updated_at self.updated_on.strftime('%Y-%m-%d %H:%M')
issue.tags self.show_issue_tags.map{|t| JSON.parse(t.to_builder.target!)}
issue.status self.issue_status.to_builder
if self.priority.present?
issue.priority self.priority.to_builder
else
issue.priority nil
end
if self.version.present?
issue.milestone self.version.to_builder
else
issue.milestone nil
end
issue.author self.user.to_builder
issue.assigners self.show_assigners.map{|t| JSON.parse(t.to_builder.target!)}
issue.participants self.participants.distinct.map{|t| JSON.parse(t.to_builder.target!)}
issue.comment_journals_count self.comment_journals.size
issue.operate_journals_count self.operate_journals.size
issue.attachments self.attachments.map{|t| JSON.parse(t.to_builder.target!)}
end
end
def self.full_children_issues(issue, issues = [])
issue.children_issues.each do |i|
issues << i
full_children_issues(i, issues)
end
issues
end
end