2020-10-29 22:44:21 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class ProjectFolder < ApplicationRecord
|
2023-04-21 21:25:52 +08:00
|
|
|
ID_PREFIX = 'PF'
|
|
|
|
include PrefixedIdModel
|
2024-05-21 00:22:32 +08:00
|
|
|
SEARCHABLE_ATTRIBUTES = ['project_folders.name', PREFIXED_ID_SQL].freeze
|
2023-04-21 21:25:52 +08:00
|
|
|
|
2020-12-10 19:10:32 +08:00
|
|
|
include ArchivableModel
|
2020-10-29 22:44:21 +08:00
|
|
|
include SearchableModel
|
|
|
|
include SearchableByNameModel
|
|
|
|
|
|
|
|
validates :name,
|
|
|
|
length: { minimum: Constants::NAME_MIN_LENGTH,
|
2021-02-12 20:35:44 +08:00
|
|
|
maximum: Constants::NAME_MAX_LENGTH },
|
|
|
|
uniqueness: { scope: %i(team_id parent_folder_id archived), case_sensitive: false }
|
2020-12-02 20:13:58 +08:00
|
|
|
validate :parent_folder_team, if: -> { parent_folder.present? }
|
2021-01-06 22:01:39 +08:00
|
|
|
validate :parent_folder_validation, if: -> { parent_folder.present? }
|
2020-10-29 22:44:21 +08:00
|
|
|
|
2020-12-02 20:13:58 +08:00
|
|
|
before_validation :inherit_team_from_parent_folder, on: :create, if: -> { parent_folder.present? }
|
2021-01-07 23:45:40 +08:00
|
|
|
before_validation :ensure_uniqueness_name_on_moving, on: :update, if: -> { parent_folder_id_changed? }
|
2020-10-29 22:44:21 +08:00
|
|
|
|
|
|
|
belongs_to :team, inverse_of: :project_folders, touch: true
|
2021-01-12 17:41:42 +08:00
|
|
|
belongs_to :parent_folder, class_name: 'ProjectFolder', optional: true, touch: true
|
2020-12-10 19:10:32 +08:00
|
|
|
belongs_to :archived_by, foreign_key: 'archived_by_id',
|
|
|
|
class_name: 'User',
|
|
|
|
inverse_of: :archived_project_folders,
|
|
|
|
optional: true
|
|
|
|
belongs_to :restored_by, foreign_key: 'restored_by_id',
|
|
|
|
class_name: 'User',
|
|
|
|
inverse_of: :restored_project_folders,
|
|
|
|
optional: true
|
2020-10-29 22:44:21 +08:00
|
|
|
has_many :projects, inverse_of: :project_folder, dependent: :nullify
|
|
|
|
has_many :project_folders, foreign_key: 'parent_folder_id', inverse_of: :parent_folder, dependent: :destroy
|
|
|
|
|
|
|
|
scope :top_level, -> { where(parent_folder: nil) }
|
|
|
|
|
2024-02-27 19:10:27 +08:00
|
|
|
def self.viewable_by_user(user, teams)
|
|
|
|
joins(team: :users)
|
|
|
|
.where(teams: { user_assignments: { user: user } })
|
|
|
|
.where(team: teams)
|
|
|
|
end
|
|
|
|
|
2024-04-18 21:45:34 +08:00
|
|
|
def self.search(user, include_archived, query = nil, current_team = nil, options = {})
|
|
|
|
teams = options[:teams] || current_team || user.teams.select(:id)
|
2020-11-10 22:04:35 +08:00
|
|
|
|
2024-04-18 21:45:34 +08:00
|
|
|
new_query = distinct.viewable_by_user(user, teams)
|
2024-05-21 00:22:32 +08:00
|
|
|
.where_attributes_like_boolean(SEARCHABLE_ATTRIBUTES, query, options)
|
2024-04-18 21:45:34 +08:00
|
|
|
new_query = new_query.active unless include_archived
|
|
|
|
|
|
|
|
new_query
|
2020-11-10 22:04:35 +08:00
|
|
|
end
|
|
|
|
|
2020-11-17 03:47:58 +08:00
|
|
|
def self.inner_folders(team, project_folder = nil)
|
|
|
|
entry_point_condition = project_folder ? 'parent_folder_id = ?' : 'parent_folder_id IS NULL'
|
|
|
|
inner_folders_sql =
|
|
|
|
"WITH RECURSIVE inner_project_folders(id, selected_folders_ids) AS (
|
|
|
|
SELECT id, ARRAY[id]
|
|
|
|
FROM project_folders
|
|
|
|
WHERE team_id = ? AND #{entry_point_condition}
|
|
|
|
UNION ALL
|
|
|
|
SELECT project_folders.id, selected_folders_ids || project_folders.id
|
|
|
|
FROM inner_project_folders
|
|
|
|
JOIN project_folders ON project_folders.parent_folder_id = inner_project_folders.id
|
|
|
|
WHERE NOT project_folders.id = ANY(selected_folders_ids)
|
|
|
|
)
|
|
|
|
SELECT id FROM inner_project_folders ORDER BY selected_folders_ids".gsub(/\n|\t/, ' ').gsub(/ +/, ' ')
|
|
|
|
|
2020-12-09 21:46:18 +08:00
|
|
|
if project_folder.present?
|
|
|
|
where("project_folders.id IN (#{inner_folders_sql})", team.id, project_folder.id)
|
|
|
|
else
|
|
|
|
where("project_folders.id IN (#{inner_folders_sql})", team.id)
|
|
|
|
end
|
2020-11-17 03:47:58 +08:00
|
|
|
end
|
|
|
|
|
2022-07-07 18:00:35 +08:00
|
|
|
def self.filter_by_teams(teams = [])
|
|
|
|
teams.blank? ? self : where(team: teams)
|
|
|
|
end
|
|
|
|
|
2020-11-24 22:42:06 +08:00
|
|
|
def parent_folders
|
|
|
|
outer_folders_sql =
|
|
|
|
'WITH RECURSIVE outer_project_folders(id, parent_folder_id, selected_folders_ids) AS (
|
|
|
|
SELECT id, parent_folder_id, ARRAY[id]
|
|
|
|
FROM project_folders
|
|
|
|
WHERE team_id = ? AND id = ?
|
|
|
|
UNION ALL
|
|
|
|
SELECT project_folders.id, project_folders.parent_folder_id, selected_folders_ids || project_folders.id
|
|
|
|
FROM outer_project_folders
|
|
|
|
JOIN project_folders ON project_folders.id = outer_project_folders.parent_folder_id
|
|
|
|
WHERE NOT project_folders.id = ANY(selected_folders_ids)
|
|
|
|
)
|
|
|
|
SELECT id FROM outer_project_folders ORDER BY selected_folders_ids'.gsub(/\n|\t/, ' ').gsub(/ +/, ' ')
|
|
|
|
|
|
|
|
ProjectFolder.where("project_folders.id IN (#{outer_folders_sql})", team.id, id)
|
|
|
|
end
|
|
|
|
|
2020-11-13 21:39:10 +08:00
|
|
|
def inner_projects
|
2021-01-19 16:47:08 +08:00
|
|
|
Project.where(project_folder: ProjectFolder.inner_folders(team, self) + [self])
|
2020-11-13 21:39:10 +08:00
|
|
|
end
|
|
|
|
|
2023-06-22 21:41:37 +08:00
|
|
|
def archived_branch?
|
|
|
|
return false if active? && parent_folder.blank?
|
|
|
|
return true if archived?
|
|
|
|
|
|
|
|
parent_folder.present? && parent_folder.archived_branch?
|
|
|
|
end
|
|
|
|
|
2020-10-29 22:44:21 +08:00
|
|
|
private
|
|
|
|
|
|
|
|
def inherit_team_from_parent_folder
|
|
|
|
self.team = parent_folder.team
|
|
|
|
end
|
2020-12-02 20:13:58 +08:00
|
|
|
|
|
|
|
def parent_folder_team
|
|
|
|
return if parent_folder.team_id == team_id
|
|
|
|
|
2021-01-06 22:01:39 +08:00
|
|
|
errors.add(:parent_folder, I18n.t('activerecord.errors.models.project_folder.attributes.parent_folder_team'))
|
|
|
|
end
|
|
|
|
|
|
|
|
def parent_folder_validation
|
2021-01-18 23:24:02 +08:00
|
|
|
if parent_folder.id == id
|
|
|
|
errors.add(:parent_folder, I18n.t('activerecord.errors.models.project_folder.attributes.parent_folder'))
|
|
|
|
elsif ProjectFolder.inner_folders(team, self).where(id: parent_folder_id).exists?
|
|
|
|
errors.add(:parent_folder, I18n.t('activerecord.errors.models.project_folder.attributes.parent_folder_child'))
|
|
|
|
end
|
2020-12-02 20:13:58 +08:00
|
|
|
end
|
2021-01-07 23:45:40 +08:00
|
|
|
|
|
|
|
def ensure_uniqueness_name_on_moving
|
|
|
|
return unless self.class.where(parent_folder: parent_folder).where('name ILIKE ?', name).any?
|
|
|
|
|
|
|
|
regex = /\((\d+)\)/
|
|
|
|
max_number = self.class
|
|
|
|
.where(parent_folder: parent_folder)
|
|
|
|
.where('name ILIKE ?', "#{name} (%)")
|
|
|
|
.order(name: :desc)
|
|
|
|
.pluck(:name)
|
|
|
|
.select { |s| s.match(regex) }
|
|
|
|
.map { |s| s.match(regex)[1].to_i }
|
|
|
|
.max || 0
|
|
|
|
|
|
|
|
self.name = name + " (#{max_number + 1})"
|
|
|
|
end
|
2020-10-29 22:44:21 +08:00
|
|
|
end
|