2019-06-13 21:47:29 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-06-23 21:19:08 +08:00
|
|
|
class Protocol < ApplicationRecord
|
2022-12-09 19:44:41 +08:00
|
|
|
ID_PREFIX = 'PT'
|
|
|
|
|
2022-12-01 02:52:36 +08:00
|
|
|
include ArchivableModel
|
2022-12-09 19:44:41 +08:00
|
|
|
include PrefixedIdModel
|
2022-10-28 20:47:43 +08:00
|
|
|
SEARCHABLE_ATTRIBUTES = ['protocols.name', 'protocols.description',
|
|
|
|
'protocols.authors', 'protocol_keywords.name', PREFIXED_ID_SQL].freeze
|
2023-03-02 21:34:14 +08:00
|
|
|
REPOSITORY_TYPES = %i(in_repository_published_original in_repository_draft in_repository_published_version).freeze
|
2022-11-22 17:55:41 +08:00
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
include SearchableModel
|
|
|
|
include RenamingUtil
|
2019-02-26 18:01:15 +08:00
|
|
|
include SearchableByNameModel
|
2022-05-13 21:45:24 +08:00
|
|
|
include Assignable
|
|
|
|
include PermissionCheckableModel
|
2019-03-22 17:52:26 +08:00
|
|
|
include TinyMceImages
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2023-03-21 17:59:31 +08:00
|
|
|
after_create :update_automatic_user_assignments, if: -> { visible? && in_repository? && parent.blank? }
|
2023-03-14 21:49:11 +08:00
|
|
|
before_update :change_visibility, if: :default_public_user_role_id_changed?
|
2023-03-27 19:11:00 +08:00
|
|
|
after_update :update_automatic_user_assignments,
|
|
|
|
if: -> { saved_change_to_default_public_user_role_id? && in_repository? }
|
2023-03-21 17:59:31 +08:00
|
|
|
skip_callback :create, :after, :create_users_assignments, if: -> { in_module? }
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2023-02-13 23:18:26 +08:00
|
|
|
enum visibility: { hidden: 0, visible: 1 }
|
2016-07-21 19:11:15 +08:00
|
|
|
enum protocol_type: {
|
|
|
|
unlinked: 0,
|
|
|
|
linked: 1,
|
2023-03-13 16:55:53 +08:00
|
|
|
in_repository_private: 2, # Deprecated
|
|
|
|
in_repository_public: 3, # Deprecated
|
|
|
|
in_repository_archived: 4, # Deprecated
|
|
|
|
in_repository_published_original: 5,
|
|
|
|
in_repository_draft: 6,
|
|
|
|
in_repository_published_version: 7
|
2016-07-21 19:11:15 +08:00
|
|
|
}
|
|
|
|
|
2023-04-07 19:25:25 +08:00
|
|
|
auto_strip_attributes :name, :description, nullify: false, if: lambda {
|
|
|
|
name_changed? || description_changed?
|
|
|
|
}
|
2016-09-16 17:39:37 +08:00
|
|
|
# Name is required when its actually specified (i.e. :in_repository? is true)
|
2016-10-05 23:45:20 +08:00
|
|
|
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
|
2019-04-24 19:37:22 +08:00
|
|
|
validates :description, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
|
2017-01-24 23:34:21 +08:00
|
|
|
validates :team, presence: true
|
2016-07-21 19:11:15 +08:00
|
|
|
validates :protocol_type, presence: true
|
2022-12-09 19:44:41 +08:00
|
|
|
validate :prevent_update,
|
|
|
|
on: :update,
|
2023-02-17 01:03:11 +08:00
|
|
|
if: lambda {
|
2023-02-25 00:28:36 +08:00
|
|
|
# skip check if only public role of visibility changed
|
|
|
|
(changes.keys | %w(default_public_user_role_id visibility)).length != 2 &&
|
2023-02-23 21:57:38 +08:00
|
|
|
in_repository_published? && !protocol_type_changed?(from: 'in_repository_draft') && !archived_changed?
|
2023-02-17 01:03:11 +08:00
|
|
|
}
|
2022-12-01 02:52:36 +08:00
|
|
|
|
|
|
|
with_options if: :in_module? do
|
|
|
|
validates :my_module, presence: true
|
|
|
|
validates :archived_by, absence: true
|
|
|
|
validates :archived_on, absence: true
|
|
|
|
end
|
|
|
|
with_options if: :linked? do
|
2022-12-09 19:44:41 +08:00
|
|
|
validate :linked_parent_type_constrain
|
2022-12-01 02:52:36 +08:00
|
|
|
validates :added_by, presence: true
|
|
|
|
validates :parent, presence: true
|
|
|
|
end
|
|
|
|
with_options if: :in_repository? do
|
|
|
|
validates :name, presence: true
|
|
|
|
validates :added_by, presence: true
|
|
|
|
validates :my_module, absence: true
|
2023-03-02 21:34:14 +08:00
|
|
|
validate :version_number_constraint
|
2022-12-01 02:52:36 +08:00
|
|
|
end
|
2022-12-09 19:44:41 +08:00
|
|
|
with_options if: :in_repository_published_version? do
|
|
|
|
validates :parent, presence: true
|
2023-03-07 20:03:01 +08:00
|
|
|
validate :parent_type_constraint
|
|
|
|
validate :versions_same_name_constraint
|
2022-12-09 19:44:41 +08:00
|
|
|
end
|
|
|
|
with_options if: :in_repository_draft? do
|
|
|
|
# Only one draft can exist for each protocol
|
2023-02-25 00:56:51 +08:00
|
|
|
validate :ensure_single_draft
|
2023-03-07 20:03:01 +08:00
|
|
|
validate :versions_same_name_constraint
|
2022-12-09 19:44:41 +08:00
|
|
|
end
|
2023-04-13 21:34:36 +08:00
|
|
|
with_options if: -> { in_repository? && !parent && !archived_changed?(from: false) } do |protocol|
|
2022-12-01 02:52:36 +08:00
|
|
|
# Active protocol must have unique name inside its team
|
2017-01-31 20:33:55 +08:00
|
|
|
protocol
|
2018-07-03 14:20:56 +08:00
|
|
|
.validates_uniqueness_of :name, case_sensitive: false,
|
2017-01-31 20:33:55 +08:00
|
|
|
scope: :team,
|
2019-06-13 21:47:29 +08:00
|
|
|
conditions: lambda {
|
2023-02-24 17:15:23 +08:00
|
|
|
where(
|
2022-12-01 02:52:36 +08:00
|
|
|
protocol_type: [
|
2022-12-09 19:44:41 +08:00
|
|
|
Protocol.protocol_types[:in_repository_published_original],
|
2022-12-01 02:52:36 +08:00
|
|
|
Protocol.protocol_types[:in_repository_draft]
|
2023-02-24 17:15:23 +08:00
|
|
|
],
|
|
|
|
parent_id: nil
|
2017-01-31 20:33:55 +08:00
|
|
|
)
|
|
|
|
}
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
2022-12-01 02:52:36 +08:00
|
|
|
with_options if: -> { in_repository? && archived? && !previous_version } do |protocol|
|
2016-07-21 19:11:15 +08:00
|
|
|
protocol.validates :archived_by, presence: true
|
|
|
|
protocol.validates :archived_on, presence: true
|
|
|
|
end
|
|
|
|
|
2017-01-31 20:33:55 +08:00
|
|
|
belongs_to :added_by,
|
|
|
|
class_name: 'User',
|
2017-06-28 21:21:32 +08:00
|
|
|
inverse_of: :added_protocols,
|
|
|
|
optional: true
|
2023-01-20 21:29:25 +08:00
|
|
|
belongs_to :last_modified_by,
|
|
|
|
class_name: 'User',
|
|
|
|
optional: true
|
2017-06-28 21:21:32 +08:00
|
|
|
belongs_to :my_module,
|
|
|
|
inverse_of: :protocols,
|
|
|
|
optional: true
|
2019-07-26 18:40:36 +08:00
|
|
|
belongs_to :team, inverse_of: :protocols
|
2023-02-13 23:18:26 +08:00
|
|
|
belongs_to :default_public_user_role,
|
|
|
|
class_name: 'UserRole',
|
|
|
|
optional: true
|
2022-12-01 02:52:36 +08:00
|
|
|
belongs_to :previous_version,
|
|
|
|
class_name: 'Protocol',
|
|
|
|
inverse_of: :next_version,
|
|
|
|
optional: true
|
2017-06-28 21:21:32 +08:00
|
|
|
belongs_to :parent,
|
|
|
|
class_name: 'Protocol',
|
|
|
|
optional: true
|
2017-01-31 20:33:55 +08:00
|
|
|
belongs_to :archived_by,
|
|
|
|
class_name: 'User',
|
2017-06-28 21:21:32 +08:00
|
|
|
inverse_of: :archived_protocols, optional: true
|
2017-01-31 20:33:55 +08:00
|
|
|
belongs_to :restored_by,
|
|
|
|
class_name: 'User',
|
2017-06-28 21:21:32 +08:00
|
|
|
inverse_of: :restored_protocols, optional: true
|
2023-01-20 21:29:25 +08:00
|
|
|
belongs_to :published_by,
|
|
|
|
class_name: 'User',
|
|
|
|
inverse_of: :published_protocols, optional: true
|
2017-01-31 20:33:55 +08:00
|
|
|
has_many :linked_children,
|
2023-03-07 16:58:36 +08:00
|
|
|
-> { linked },
|
2017-01-31 20:33:55 +08:00
|
|
|
class_name: 'Protocol',
|
|
|
|
foreign_key: 'parent_id'
|
2022-12-01 02:52:36 +08:00
|
|
|
has_one :next_version,
|
|
|
|
class_name: 'Protocol',
|
|
|
|
foreign_key: 'previous_version_id',
|
|
|
|
inverse_of: :previous_version,
|
|
|
|
dependent: :destroy
|
2023-03-01 22:04:53 +08:00
|
|
|
has_one :latest_published_version,
|
|
|
|
lambda {
|
|
|
|
in_repository_published_version.select('DISTINCT ON (parent_id) *')
|
|
|
|
.order(:parent_id, version_number: :desc)
|
|
|
|
},
|
|
|
|
class_name: 'Protocol',
|
|
|
|
foreign_key: 'parent_id'
|
|
|
|
has_one :draft,
|
|
|
|
-> { in_repository_draft.select('DISTINCT ON (parent_id) *').order(:parent_id) },
|
|
|
|
class_name: 'Protocol',
|
|
|
|
foreign_key: 'parent_id'
|
|
|
|
has_many :published_versions,
|
|
|
|
-> { in_repository_published_version },
|
|
|
|
class_name: 'Protocol',
|
2023-05-19 17:34:31 +08:00
|
|
|
foreign_key: 'parent_id',
|
|
|
|
inverse_of: :parent,
|
|
|
|
dependent: :destroy
|
|
|
|
has_many :linked_my_modules,
|
|
|
|
through: :linked_children,
|
|
|
|
source: :my_module
|
2017-01-31 20:33:55 +08:00
|
|
|
has_many :protocol_protocol_keywords,
|
|
|
|
inverse_of: :protocol,
|
|
|
|
dependent: :destroy
|
2016-07-21 19:11:15 +08:00
|
|
|
has_many :protocol_keywords, through: :protocol_protocol_keywords
|
|
|
|
has_many :steps, inverse_of: :protocol, dependent: :destroy
|
|
|
|
|
2017-05-05 22:41:23 +08:00
|
|
|
def self.search(user,
|
|
|
|
include_archived,
|
|
|
|
query = nil,
|
|
|
|
page = 1,
|
|
|
|
_current_team = nil,
|
|
|
|
options = {})
|
2023-03-17 17:43:50 +08:00
|
|
|
repository_protocols = latest_available_versions(user.teams)
|
|
|
|
.with_granted_permissions(user, ProtocolPermissions::READ)
|
|
|
|
.select(:id)
|
|
|
|
repository_protocols = repository_protocols.active unless include_archived
|
2017-01-31 20:33:55 +08:00
|
|
|
|
2023-03-17 17:43:50 +08:00
|
|
|
module_ids = MyModule.search(user, include_archived, nil, Constants::SEARCH_NO_LIMIT).pluck(:id)
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2023-03-17 17:43:50 +08:00
|
|
|
new_query = Protocol
|
|
|
|
.where(
|
|
|
|
'(protocol_type IN (?) AND my_module_id IN (?)) OR (protocols.id IN (?))',
|
|
|
|
[Protocol.protocol_types[:unlinked], Protocol.protocol_types[:linked]],
|
|
|
|
module_ids,
|
|
|
|
repository_protocols
|
|
|
|
)
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2023-03-17 17:43:50 +08:00
|
|
|
new_query = new_query.left_outer_joins(:protocol_keywords)
|
|
|
|
.where_attributes_like(SEARCHABLE_ATTRIBUTES, query, options)
|
|
|
|
.distinct
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
# Show all results if needed
|
2016-10-05 23:45:20 +08:00
|
|
|
if page == Constants::SEARCH_NO_LIMIT
|
2016-07-21 19:11:15 +08:00
|
|
|
new_query
|
|
|
|
else
|
2021-10-22 17:43:20 +08:00
|
|
|
new_query.limit(Constants::SEARCH_LIMIT).offset((page - 1) * Constants::SEARCH_LIMIT)
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-17 17:43:50 +08:00
|
|
|
def self.latest_available_versions(teams)
|
|
|
|
team_protocols = where(team: teams)
|
|
|
|
|
|
|
|
original_without_versions = team_protocols
|
|
|
|
.left_outer_joins(:published_versions)
|
|
|
|
.where(protocol_type: Protocol.protocol_types[:in_repository_published_original])
|
|
|
|
.where(published_versions: { id: nil })
|
|
|
|
.select(:id)
|
|
|
|
published_versions = team_protocols
|
|
|
|
.where(protocol_type: Protocol.protocol_types[:in_repository_published_version])
|
|
|
|
.order(:parent_id, version_number: :desc)
|
|
|
|
.select('DISTINCT ON (parent_id) id')
|
|
|
|
new_drafts = team_protocols
|
|
|
|
.where(protocol_type: Protocol.protocol_types[:in_repository_draft], parent_id: nil)
|
|
|
|
.select(:id)
|
|
|
|
|
|
|
|
where('protocols.id IN ((?) UNION (?) UNION (?))', original_without_versions, published_versions, new_drafts)
|
|
|
|
end
|
|
|
|
|
2019-02-26 18:01:15 +08:00
|
|
|
def self.viewable_by_user(user, teams)
|
2023-05-09 22:23:29 +08:00
|
|
|
# Team owners see all protocol templates in the team
|
|
|
|
owner_role = UserRole.find_predefined_owner_role
|
2023-05-30 18:41:53 +08:00
|
|
|
protocols = Protocol.where(team: teams)
|
|
|
|
.where(protocol_type: REPOSITORY_TYPES)
|
|
|
|
viewable_as_team_owner = protocols.joins("INNER JOIN user_assignments team_user_assignments " \
|
|
|
|
"ON team_user_assignments.assignable_type = 'Team' " \
|
|
|
|
"AND team_user_assignments.assignable_id = protocols.team_id")
|
|
|
|
.where(team_user_assignments: { user_id: user, user_role_id: owner_role })
|
|
|
|
.select(:id)
|
|
|
|
viewable_as_assigned = protocols.with_granted_permissions(user, ProtocolPermissions::READ).select(:id)
|
|
|
|
|
|
|
|
where('protocols.id IN ((?) UNION (?))', viewable_as_team_owner, viewable_as_assigned)
|
2019-02-26 18:01:15 +08:00
|
|
|
end
|
|
|
|
|
2022-07-07 18:00:35 +08:00
|
|
|
def self.filter_by_teams(teams = [])
|
|
|
|
teams.blank? ? self : where(team: teams)
|
|
|
|
end
|
|
|
|
|
2023-03-08 22:48:03 +08:00
|
|
|
def original_code
|
|
|
|
# returns linked protocol code, or code of the original version of the linked protocol
|
|
|
|
parent&.parent&.code || parent&.code || code
|
|
|
|
end
|
|
|
|
|
2022-04-22 19:07:51 +08:00
|
|
|
def insert_step(step, position)
|
|
|
|
ActiveRecord::Base.transaction do
|
|
|
|
steps.where('position >= ?', position).desc_order.each do |s|
|
|
|
|
s.update!(position: s.position + 1)
|
|
|
|
end
|
|
|
|
step.position = position
|
|
|
|
step.protocol = self
|
|
|
|
step.save!
|
2023-04-13 21:34:36 +08:00
|
|
|
rescue ActiveRecord::RecordInvalid => e
|
|
|
|
Rails.logger.error e.message
|
|
|
|
raise ActiveRecord::Rollback
|
2022-04-22 19:07:51 +08:00
|
|
|
end
|
|
|
|
step
|
|
|
|
end
|
|
|
|
|
2022-06-07 00:21:57 +08:00
|
|
|
def created_by
|
|
|
|
in_module? ? my_module.created_by : added_by
|
|
|
|
end
|
|
|
|
|
2023-03-21 17:59:31 +08:00
|
|
|
# Only for original published protocol
|
2023-03-01 22:04:53 +08:00
|
|
|
def published_versions_with_original
|
2023-03-21 17:59:31 +08:00
|
|
|
return Protocol.none unless in_repository_published_original?
|
|
|
|
|
2023-01-20 21:29:25 +08:00
|
|
|
team.protocols
|
|
|
|
.in_repository_published_version
|
|
|
|
.where(parent: self)
|
|
|
|
.or(team.protocols.in_repository_published_original.where(id: id))
|
|
|
|
end
|
|
|
|
|
2023-03-21 17:59:31 +08:00
|
|
|
# Only for original published protocol
|
|
|
|
def all_linked_children
|
|
|
|
return Protocol.none unless in_repository_published_original?
|
|
|
|
|
|
|
|
Protocol.linked.where(parent: published_versions_with_original)
|
|
|
|
end
|
|
|
|
|
2023-03-02 20:50:47 +08:00
|
|
|
def initial_draft?
|
|
|
|
in_repository_draft? && parent.blank?
|
2023-02-18 16:09:33 +08:00
|
|
|
end
|
|
|
|
|
2023-03-07 20:03:01 +08:00
|
|
|
def newer_published_version_present?
|
|
|
|
if in_repository_published_original?
|
|
|
|
published_versions.any?
|
|
|
|
elsif in_repository_published_version?
|
|
|
|
parent.published_versions.where('version_number > ?', version_number).any?
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-06 17:53:01 +08:00
|
|
|
def latest_published_version_or_self
|
|
|
|
latest_published_version || self
|
|
|
|
end
|
|
|
|
|
2022-06-07 00:21:57 +08:00
|
|
|
def permission_parent
|
|
|
|
in_module? ? my_module : team
|
|
|
|
end
|
|
|
|
|
2016-09-02 17:45:54 +08:00
|
|
|
def linked_modules
|
2023-03-17 17:43:50 +08:00
|
|
|
MyModule.joins(:protocols).where(protocols: { parent_id: id })
|
2016-09-02 17:45:54 +08:00
|
|
|
end
|
|
|
|
|
2016-09-05 16:58:37 +08:00
|
|
|
def linked_experiments(linked_mod)
|
2023-03-17 17:43:50 +08:00
|
|
|
Experiment.where(id: linked_mod.distinct.select(:experiment_id))
|
2016-09-02 17:45:54 +08:00
|
|
|
end
|
|
|
|
|
2016-09-05 16:58:37 +08:00
|
|
|
def linked_projects(linked_exp)
|
2023-03-17 17:43:50 +08:00
|
|
|
Project.where(id: linked_exp.distinct.select(:project_id))
|
2016-09-02 17:45:54 +08:00
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
def self.new_blank_for_module(my_module)
|
|
|
|
Protocol.new(
|
2017-01-24 23:34:21 +08:00
|
|
|
team: my_module.experiment.project.team,
|
2016-07-21 19:11:15 +08:00
|
|
|
protocol_type: :unlinked,
|
|
|
|
my_module: my_module
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Deep-clone given array of assets
|
2019-07-09 16:28:15 +08:00
|
|
|
def self.deep_clone_assets(assets_to_clone)
|
2020-10-06 19:53:53 +08:00
|
|
|
ActiveRecord::Base.no_touching do
|
|
|
|
assets_to_clone.each do |src_id, dest_id|
|
|
|
|
src = Asset.find_by(id: src_id)
|
|
|
|
dest = Asset.find_by(id: dest_id)
|
|
|
|
dest.destroy! if src.blank? && dest.present?
|
|
|
|
next unless src.present? && dest.present?
|
2018-02-01 22:44:08 +08:00
|
|
|
|
2020-10-06 19:53:53 +08:00
|
|
|
# Clone file
|
|
|
|
src.duplicate_file(dest)
|
|
|
|
end
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-25 18:53:27 +08:00
|
|
|
def self.clone_contents(src, dest, current_user, clone_keywords, only_contents = false)
|
|
|
|
dest.update(description: src.description, name: src.name) unless only_contents
|
|
|
|
|
2019-11-06 01:13:44 +08:00
|
|
|
src.clone_tinymce_assets(dest, dest.team)
|
2019-04-24 19:34:56 +08:00
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
# Update keywords
|
2019-06-13 21:47:29 +08:00
|
|
|
if clone_keywords
|
2016-07-21 19:11:15 +08:00
|
|
|
src.protocol_keywords.each do |keyword|
|
|
|
|
ProtocolProtocolKeyword.create(
|
|
|
|
protocol: dest,
|
|
|
|
protocol_keyword: keyword
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Copy steps
|
|
|
|
src.steps.each do |step|
|
2022-08-24 19:45:39 +08:00
|
|
|
step.duplicate(dest, current_user, step_position: step.position)
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def in_repository_active?
|
2022-12-09 19:44:41 +08:00
|
|
|
in_repository? && active?
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def in_repository?
|
2022-12-01 02:52:36 +08:00
|
|
|
in_repository_published? || in_repository_draft?
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2022-12-09 19:44:41 +08:00
|
|
|
def in_repository_published?
|
|
|
|
in_repository_published_original? || in_repository_published_version?
|
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
def in_module?
|
2017-01-31 20:33:55 +08:00
|
|
|
unlinked? || linked?
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def newer_than_parent?
|
2023-04-04 17:25:02 +08:00
|
|
|
return linked? if linked_at.nil?
|
|
|
|
|
2023-03-31 23:39:17 +08:00
|
|
|
linked? && updated_at > linked_at
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def parent_newer?
|
2023-03-27 17:39:09 +08:00
|
|
|
linked? && (
|
|
|
|
parent.newer_published_version_present? ||
|
|
|
|
# backward compatibility with original implementation
|
|
|
|
parent.published_on > updated_at
|
|
|
|
)
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def number_of_steps
|
|
|
|
steps.count
|
|
|
|
end
|
|
|
|
|
2023-06-08 15:54:59 +08:00
|
|
|
def archived_branch?
|
|
|
|
archived? || parent&.archived?
|
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
def completed_steps
|
2017-05-11 21:56:33 +08:00
|
|
|
steps.where(completed: true)
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2020-10-22 19:21:05 +08:00
|
|
|
def first_step_id
|
|
|
|
steps.find_by(position: 0)&.id
|
2020-10-19 18:17:03 +08:00
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
def space_taken
|
|
|
|
st = 0
|
2019-06-13 21:47:29 +08:00
|
|
|
steps.find_each do |step|
|
2016-07-21 19:11:15 +08:00
|
|
|
st += step.space_taken
|
|
|
|
end
|
|
|
|
st
|
|
|
|
end
|
|
|
|
|
|
|
|
def archive(user)
|
2023-03-23 22:32:22 +08:00
|
|
|
return false unless can_destroy?
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
self.archived_by = user
|
|
|
|
self.archived_on = Time.now
|
|
|
|
self.restored_by = nil
|
|
|
|
self.restored_on = nil
|
2023-02-09 21:10:07 +08:00
|
|
|
self.archived = true
|
2016-07-21 19:11:15 +08:00
|
|
|
result = save
|
|
|
|
|
|
|
|
# Update all module protocols that had
|
|
|
|
# parent set to this protocol
|
2019-06-13 21:47:29 +08:00
|
|
|
if result
|
2023-02-27 18:12:40 +08:00
|
|
|
reload
|
|
|
|
Protocol.where(
|
|
|
|
parent: self,
|
|
|
|
protocol_type: %i(in_repository_draft in_repository_published_version)
|
|
|
|
).update(
|
|
|
|
archived_by: user,
|
|
|
|
archived_on: archived_on,
|
|
|
|
restored_by: nil,
|
|
|
|
restored_on: nil,
|
|
|
|
archived: true
|
|
|
|
)
|
|
|
|
|
2019-03-08 17:57:09 +08:00
|
|
|
Activities::CreateActivityService
|
|
|
|
.call(activity_type: :archive_protocol_in_repository,
|
|
|
|
owner: user,
|
|
|
|
subject: self,
|
|
|
|
team: team,
|
|
|
|
message_items: {
|
|
|
|
protocol: id
|
|
|
|
})
|
|
|
|
end
|
2016-07-21 19:11:15 +08:00
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
def restore(user)
|
|
|
|
self.archived_by = nil
|
|
|
|
self.archived_on = nil
|
|
|
|
self.restored_by = user
|
|
|
|
self.restored_on = Time.now
|
2023-02-17 01:03:11 +08:00
|
|
|
self.archived = false
|
|
|
|
|
2019-07-03 21:35:37 +08:00
|
|
|
result = save
|
|
|
|
|
|
|
|
if result
|
2023-02-27 18:12:40 +08:00
|
|
|
reload
|
|
|
|
Protocol.where(
|
|
|
|
parent: self,
|
|
|
|
protocol_type: %i(in_repository_draft in_repository_published_version)
|
|
|
|
).update(
|
|
|
|
archived_by: nil,
|
|
|
|
archived_on: nil,
|
|
|
|
restored_by: user,
|
|
|
|
restored_on: restored_on,
|
|
|
|
archived: false
|
|
|
|
)
|
|
|
|
|
2019-07-03 21:35:37 +08:00
|
|
|
Activities::CreateActivityService
|
|
|
|
.call(activity_type: :restore_protocol_in_repository,
|
|
|
|
owner: user,
|
|
|
|
subject: self,
|
|
|
|
team: team,
|
|
|
|
message_items: {
|
|
|
|
protocol: id
|
|
|
|
})
|
|
|
|
end
|
|
|
|
result
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2023-03-01 22:19:34 +08:00
|
|
|
def update_keywords(keywords, user)
|
2016-07-21 19:11:15 +08:00
|
|
|
result = true
|
|
|
|
begin
|
|
|
|
Protocol.transaction do
|
|
|
|
# First, destroy all keywords
|
2019-06-13 21:47:29 +08:00
|
|
|
protocol_protocol_keywords.destroy_all
|
2016-07-21 19:11:15 +08:00
|
|
|
if keywords.present?
|
|
|
|
keywords.each do |kw_name|
|
2018-04-06 21:41:50 +08:00
|
|
|
kw = ProtocolKeyword.find_or_create_by(name: kw_name, team: team)
|
2019-06-13 21:47:29 +08:00
|
|
|
protocol_keywords << kw
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
2023-03-01 22:19:34 +08:00
|
|
|
update(last_modified_by: user)
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
end
|
2019-06-13 21:47:29 +08:00
|
|
|
rescue StandardError
|
2016-07-21 19:11:15 +08:00
|
|
|
result = false
|
|
|
|
end
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
def unlink
|
|
|
|
self.parent = nil
|
2023-03-07 20:03:01 +08:00
|
|
|
self.linked_at = nil
|
2016-07-21 19:11:15 +08:00
|
|
|
self.protocol_type = Protocol.protocol_types[:unlinked]
|
2019-06-13 21:47:29 +08:00
|
|
|
save!
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2023-02-28 21:17:34 +08:00
|
|
|
def update_from_parent(current_user, source)
|
2020-09-29 18:59:02 +08:00
|
|
|
ActiveRecord::Base.no_touching do
|
|
|
|
# First, destroy step contents
|
|
|
|
destroy_contents
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2020-09-29 18:59:02 +08:00
|
|
|
# Now, clone parent's step contents
|
2023-02-28 21:17:34 +08:00
|
|
|
Protocol.clone_contents(source, self, current_user, false)
|
2020-09-29 18:59:02 +08:00
|
|
|
end
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
# Lastly, update the metadata
|
2019-06-13 21:47:29 +08:00
|
|
|
reload
|
2016-07-21 19:11:15 +08:00
|
|
|
self.record_timestamps = false
|
|
|
|
self.added_by = current_user
|
2023-03-01 22:19:34 +08:00
|
|
|
self.last_modified_by = current_user
|
2023-02-28 21:17:34 +08:00
|
|
|
self.parent = source
|
2023-03-31 23:39:17 +08:00
|
|
|
self.updated_at = Time.zone.now
|
|
|
|
self.linked_at = updated_at
|
2019-06-13 21:47:29 +08:00
|
|
|
save!
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def load_from_repository(source, current_user)
|
2020-09-29 18:59:02 +08:00
|
|
|
ActiveRecord::Base.no_touching do
|
|
|
|
# First, destroy step contents
|
|
|
|
destroy_contents
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2020-09-29 18:59:02 +08:00
|
|
|
# Now, clone source's step contents
|
|
|
|
Protocol.clone_contents(source, self, current_user, false)
|
|
|
|
end
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
# Lastly, update the metadata
|
2019-06-13 21:47:29 +08:00
|
|
|
reload
|
2022-06-27 19:14:45 +08:00
|
|
|
self.name = source.name
|
2016-07-21 19:11:15 +08:00
|
|
|
self.record_timestamps = false
|
|
|
|
self.parent = source
|
|
|
|
self.added_by = current_user
|
2023-03-01 22:19:34 +08:00
|
|
|
self.last_modified_by = current_user
|
2016-07-21 19:11:15 +08:00
|
|
|
self.protocol_type = Protocol.protocol_types[:linked]
|
2023-03-31 23:39:17 +08:00
|
|
|
self.updated_at = Time.zone.now
|
|
|
|
self.linked_at = updated_at
|
2019-06-13 21:47:29 +08:00
|
|
|
save!
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2023-02-17 21:27:20 +08:00
|
|
|
def copy_to_repository(clone, current_user)
|
|
|
|
clone.team = team
|
|
|
|
clone.protocol_type = :in_repository_draft
|
|
|
|
clone.added_by = current_user
|
2023-03-01 22:19:34 +08:00
|
|
|
clone.last_modified_by = current_user
|
2023-03-02 22:35:18 +08:00
|
|
|
clone.description = description
|
2016-07-21 19:11:15 +08:00
|
|
|
# Don't proceed further if clone is invalid
|
2019-06-13 21:47:29 +08:00
|
|
|
return clone if clone.invalid?
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2020-09-29 18:59:02 +08:00
|
|
|
ActiveRecord::Base.no_touching do
|
|
|
|
# Okay, clone seems to be valid: let's clone it
|
|
|
|
clone = deep_clone(clone, current_user)
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2019-06-13 21:47:29 +08:00
|
|
|
clone
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2023-02-22 21:59:48 +08:00
|
|
|
def save_as_draft(current_user)
|
2023-03-03 22:36:43 +08:00
|
|
|
parent_protocol = parent || self
|
|
|
|
version = (parent_protocol.latest_published_version || self).version_number + 1
|
2023-02-22 21:59:48 +08:00
|
|
|
|
|
|
|
draft = dup
|
|
|
|
draft.version_number = version
|
|
|
|
draft.protocol_type = :in_repository_draft
|
2023-03-03 22:36:43 +08:00
|
|
|
draft.parent = parent_protocol
|
2023-02-22 21:59:48 +08:00
|
|
|
draft.published_by = nil
|
|
|
|
draft.published_on = nil
|
|
|
|
draft.version_comment = nil
|
2023-02-24 22:32:10 +08:00
|
|
|
draft.previous_version = self
|
2023-03-01 22:19:34 +08:00
|
|
|
draft.last_modified_by = current_user
|
2023-03-21 17:59:31 +08:00
|
|
|
draft.skip_user_assignments = true
|
2023-02-22 21:59:48 +08:00
|
|
|
|
|
|
|
return draft if draft.invalid?
|
|
|
|
|
|
|
|
ActiveRecord::Base.no_touching do
|
|
|
|
draft = deep_clone(draft, current_user)
|
|
|
|
end
|
|
|
|
|
2023-03-21 17:59:31 +08:00
|
|
|
parent_protocol.user_assignments.each do |parent_user_assignment|
|
|
|
|
parent_protocol.sync_child_protocol_user_assignment(parent_user_assignment, draft.id)
|
|
|
|
end
|
|
|
|
|
2023-02-22 21:59:48 +08:00
|
|
|
draft
|
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
def deep_clone_my_module(my_module, current_user)
|
|
|
|
clone = Protocol.new_blank_for_module(my_module)
|
2019-06-13 21:47:29 +08:00
|
|
|
clone.name = name
|
|
|
|
clone.authors = authors
|
|
|
|
clone.description = description
|
|
|
|
clone.protocol_type = protocol_type
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2019-06-13 21:47:29 +08:00
|
|
|
if linked?
|
2016-07-21 19:11:15 +08:00
|
|
|
clone.added_by = current_user
|
2019-06-13 21:47:29 +08:00
|
|
|
clone.parent = parent
|
2023-04-04 16:41:53 +08:00
|
|
|
clone.linked_at = linked_at
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2023-03-31 23:39:17 +08:00
|
|
|
ActiveRecord::Base.no_touching do
|
|
|
|
clone = deep_clone(clone, current_user)
|
|
|
|
end
|
|
|
|
clone
|
2023-03-31 22:49:40 +08:00
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
def deep_clone_repository(current_user)
|
|
|
|
clone = Protocol.new(
|
2019-06-13 21:47:29 +08:00
|
|
|
name: name,
|
|
|
|
authors: authors,
|
|
|
|
description: description,
|
2016-07-21 19:11:15 +08:00
|
|
|
added_by: current_user,
|
2023-03-01 22:19:34 +08:00
|
|
|
last_modified_by: current_user,
|
2019-06-13 21:47:29 +08:00
|
|
|
team: team,
|
2023-04-04 20:09:52 +08:00
|
|
|
protocol_type: :in_repository_draft
|
2016-07-21 19:11:15 +08:00
|
|
|
)
|
|
|
|
|
2019-04-02 17:50:37 +08:00
|
|
|
cloned = deep_clone(clone, current_user)
|
|
|
|
|
|
|
|
if cloned
|
|
|
|
Activities::CreateActivityService
|
|
|
|
.call(activity_type: :copy_protocol_in_repository,
|
2023-02-18 16:09:33 +08:00
|
|
|
owner: current_user,
|
|
|
|
subject: self,
|
|
|
|
team: team,
|
|
|
|
project: nil,
|
|
|
|
message_items: {
|
|
|
|
protocol_new: clone.id,
|
|
|
|
protocol_original: id
|
|
|
|
})
|
2019-04-02 17:50:37 +08:00
|
|
|
end
|
|
|
|
cloned
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2020-07-02 23:03:29 +08:00
|
|
|
def destroy_contents
|
2016-07-21 19:11:15 +08:00
|
|
|
# Calculate total space taken by the protocol
|
2019-06-13 21:47:29 +08:00
|
|
|
st = space_taken
|
2022-07-20 16:14:48 +08:00
|
|
|
steps.order(position: :desc).each do |step|
|
|
|
|
step.step_orderable_elements.delete_all
|
|
|
|
step.destroy!
|
|
|
|
end
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
# Release space taken by the step
|
2019-06-13 21:47:29 +08:00
|
|
|
team.release_space(st)
|
|
|
|
team.save
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
# Reload protocol
|
2019-06-13 21:47:29 +08:00
|
|
|
reload
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2016-10-06 22:13:35 +08:00
|
|
|
def can_destroy?
|
|
|
|
steps.map(&:can_destroy?).all?
|
|
|
|
end
|
|
|
|
|
2023-03-27 19:11:00 +08:00
|
|
|
def create_or_update_public_user_assignments!(assigned_by)
|
2023-02-13 23:18:26 +08:00
|
|
|
public_role = default_public_user_role || UserRole.find_predefined_viewer_role
|
|
|
|
team.user_assignments.where.not(user: assigned_by).find_each do |team_user_assignment|
|
|
|
|
new_user_assignment = user_assignments.find_or_initialize_by(user: team_user_assignment.user)
|
|
|
|
next if new_user_assignment.manually_assigned?
|
|
|
|
|
|
|
|
new_user_assignment.user_role = public_role
|
2022-10-19 23:26:13 +08:00
|
|
|
new_user_assignment.assigned_by = assigned_by
|
|
|
|
new_user_assignment.assigned = :automatically
|
|
|
|
new_user_assignment.save!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-06 23:06:38 +08:00
|
|
|
def child_version_protocols
|
|
|
|
published_versions.or(Protocol.where(id: draft&.id))
|
2023-03-03 20:20:15 +08:00
|
|
|
end
|
|
|
|
|
2023-03-06 23:06:38 +08:00
|
|
|
def sync_child_protocol_user_assignment(user_assignment, child_protocol_id = nil)
|
|
|
|
# Copy user assignments to child protocol(s)
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2023-03-06 23:06:38 +08:00
|
|
|
Protocol.transaction(requires_new: true) do
|
|
|
|
# Reload to ensure a potential new draft is also included in child versions
|
|
|
|
reload
|
|
|
|
|
|
|
|
(
|
|
|
|
# all or single child version protocol
|
|
|
|
child_protocol_id ? child_version_protocols.where(id: child_protocol_id) : child_version_protocols
|
|
|
|
).find_each do |child_protocol|
|
|
|
|
child_assignment = child_protocol.user_assignments.find_or_initialize_by(
|
|
|
|
user: user_assignment.user
|
|
|
|
)
|
|
|
|
|
|
|
|
if user_assignment.destroyed?
|
|
|
|
child_assignment.destroy! if child_assignment.persisted?
|
|
|
|
next
|
2023-03-03 20:20:15 +08:00
|
|
|
end
|
2023-03-06 23:06:38 +08:00
|
|
|
|
|
|
|
child_assignment.update!(
|
|
|
|
user_assignment.attributes.slice(
|
|
|
|
'user_role_id',
|
|
|
|
'assigned',
|
|
|
|
'assigned_by_id',
|
|
|
|
'team_id'
|
|
|
|
)
|
|
|
|
)
|
2023-03-03 20:20:15 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
private
|
|
|
|
|
2023-03-06 23:06:38 +08:00
|
|
|
def after_user_assignment_changed(user_assignment)
|
|
|
|
return unless in_repository_published_original?
|
|
|
|
|
|
|
|
sync_child_protocol_user_assignment(user_assignment)
|
|
|
|
end
|
|
|
|
|
2023-03-21 17:59:31 +08:00
|
|
|
def update_automatic_user_assignments
|
2023-04-04 17:20:38 +08:00
|
|
|
return if skip_user_assignments
|
|
|
|
|
2023-02-13 23:18:26 +08:00
|
|
|
case visibility
|
|
|
|
when 'visible'
|
2023-03-27 19:11:00 +08:00
|
|
|
create_or_update_public_user_assignments!(added_by)
|
2023-02-13 23:18:26 +08:00
|
|
|
when 'hidden'
|
2022-10-19 23:26:13 +08:00
|
|
|
automatic_user_assignments.where.not(user: added_by).destroy_all
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-21 19:11:15 +08:00
|
|
|
def deep_clone(clone, current_user)
|
|
|
|
# Save cloned protocol first
|
|
|
|
success = clone.save
|
|
|
|
|
|
|
|
# Rename protocol if needed
|
|
|
|
unless success
|
|
|
|
rename_record(clone, :name)
|
|
|
|
success = clone.save
|
|
|
|
end
|
|
|
|
|
2017-01-31 20:33:55 +08:00
|
|
|
raise ActiveRecord::RecordNotSaved unless success
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2022-07-25 18:53:27 +08:00
|
|
|
Protocol.clone_contents(self, clone, current_user, true, true)
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
clone.reload
|
2017-01-31 20:33:55 +08:00
|
|
|
clone
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
|
|
|
|
2022-12-01 02:52:36 +08:00
|
|
|
def prevent_update
|
|
|
|
errors.add(:base, I18n.t('activerecord.errors.models.protocol.unchangable'))
|
|
|
|
end
|
|
|
|
|
2022-12-09 19:44:41 +08:00
|
|
|
def linked_parent_type_constrain
|
2022-12-01 02:52:36 +08:00
|
|
|
unless parent.in_repository_published?
|
|
|
|
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_type'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-07 20:03:01 +08:00
|
|
|
def parent_type_constraint
|
2022-12-09 19:44:41 +08:00
|
|
|
unless parent.in_repository_published_original?
|
|
|
|
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_type'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-07 20:03:01 +08:00
|
|
|
def versions_same_name_constraint
|
2022-12-09 19:44:41 +08:00
|
|
|
if parent.present? && !parent.name.eql?(name)
|
2022-12-01 02:52:36 +08:00
|
|
|
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_version_name'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-02 21:34:14 +08:00
|
|
|
def version_number_constraint
|
|
|
|
if Protocol.where(protocol_type: Protocol::REPOSITORY_TYPES)
|
|
|
|
.where.not(id: id)
|
|
|
|
.where(version_number: version_number)
|
|
|
|
.where('(parent_id = :parent_id OR id = :parent_id)', parent_id: (parent_id || id)).any?
|
2022-12-01 02:52:36 +08:00
|
|
|
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_version_number'))
|
|
|
|
end
|
|
|
|
end
|
2023-02-25 00:56:51 +08:00
|
|
|
|
|
|
|
def ensure_single_draft
|
|
|
|
if parent&.draft && parent.draft.id != id
|
|
|
|
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_draft_number'))
|
|
|
|
end
|
|
|
|
end
|
2023-03-09 21:13:01 +08:00
|
|
|
|
2023-03-14 21:49:11 +08:00
|
|
|
def change_visibility
|
|
|
|
self.visibility = default_public_user_role_id.present? ? :visible : :hidden
|
|
|
|
end
|
2016-07-26 15:17:24 +08:00
|
|
|
end
|