mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
249 lines
9.3 KiB
Ruby
249 lines
9.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#-- copyright
|
|
# OpenProject is an open source project management software.
|
|
# Copyright (C) the OpenProject GmbH
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
#
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
|
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
|
# Copyright (C) 2010-2013 the ChiliProject Team
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# See COPYRIGHT and LICENSE files for more details.
|
|
#++
|
|
|
|
class Journal < ApplicationRecord
|
|
self.table_name = "journals"
|
|
self.ignored_columns += ["activity_type"]
|
|
|
|
include ::JournalChanges
|
|
include ::JournalFormatter
|
|
include ::Acts::Journalized::FormatHooks
|
|
include Journal::Timestamps
|
|
include Reactable
|
|
|
|
# Inline attachments for Journal#notes aka comments
|
|
acts_as_attachable view_permission: :view_work_packages,
|
|
add_on_new_permission: :add_work_package_comments,
|
|
add_on_persisted_permission: :edit_own_work_package_comments,
|
|
delete_permission: :edit_own_work_package_comments
|
|
|
|
register_journal_formatter OpenProject::JournalFormatter::ActiveStatus
|
|
register_journal_formatter OpenProject::JournalFormatter::AgendaItemDiff
|
|
register_journal_formatter OpenProject::JournalFormatter::AgendaItemDuration
|
|
register_journal_formatter OpenProject::JournalFormatter::AgendaItemPosition
|
|
register_journal_formatter OpenProject::JournalFormatter::AgendaItemTitle
|
|
register_journal_formatter OpenProject::JournalFormatter::AllocatedTime
|
|
register_journal_formatter OpenProject::JournalFormatter::Attachment
|
|
register_journal_formatter OpenProject::JournalFormatter::Cause
|
|
register_journal_formatter OpenProject::JournalFormatter::CustomComment
|
|
register_journal_formatter OpenProject::JournalFormatter::CustomField
|
|
register_journal_formatter OpenProject::JournalFormatter::Diff
|
|
register_journal_formatter OpenProject::JournalFormatter::FileLink
|
|
register_journal_formatter OpenProject::JournalFormatter::IgnoreNonWorkingDays
|
|
register_journal_formatter OpenProject::JournalFormatter::MeetingStartTime
|
|
register_journal_formatter OpenProject::JournalFormatter::MeetingState
|
|
register_journal_formatter OpenProject::JournalFormatter::MeetingWorkPackageId
|
|
register_journal_formatter OpenProject::JournalFormatter::ParticipantChange
|
|
register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseActive
|
|
register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseDates
|
|
register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseDefinition
|
|
register_journal_formatter OpenProject::JournalFormatter::ProjectStatusCode
|
|
register_journal_formatter OpenProject::JournalFormatter::ScheduleManually
|
|
register_journal_formatter OpenProject::JournalFormatter::SubprojectNamedAssociation
|
|
register_journal_formatter OpenProject::JournalFormatter::Template
|
|
register_journal_formatter OpenProject::JournalFormatter::TimeEntryHours
|
|
register_journal_formatter OpenProject::JournalFormatter::TimeEntryNamedAssociation
|
|
register_journal_formatter OpenProject::JournalFormatter::Visibility
|
|
register_journal_formatter OpenProject::JournalFormatter::WikiDiff
|
|
|
|
# Attributes related to the cause are stored in a JSONB column so we can easily add new relations and related
|
|
# attributes without a heavy database migration. Fields will be prefixed with `cause_` but are stored in the JSONB
|
|
# hash without that prefix
|
|
store_accessor :cause,
|
|
%i[
|
|
type
|
|
feature
|
|
import_history
|
|
work_package_id
|
|
changed_days
|
|
status_name
|
|
status_id
|
|
status_changes
|
|
],
|
|
prefix: true
|
|
VALID_CAUSE_TYPES = %w[
|
|
default_attribute_written
|
|
import
|
|
progress_mode_changed_to_status_based
|
|
status_changed
|
|
system_update
|
|
total_percent_complete_mode_changed_to_work_weighted_average
|
|
work_package_children_changed_times
|
|
work_package_parent_changed_times
|
|
work_package_predecessor_changed_times
|
|
work_package_related_changed_times
|
|
work_package_duplicate_closed
|
|
working_days_changed
|
|
].freeze
|
|
|
|
# Make sure each journaled model instance only has unique version ids
|
|
validates :version, uniqueness: { scope: %i[journable_id journable_type] }
|
|
validates :cause_type, inclusion: { in: VALID_CAUSE_TYPES, allow_blank: true }
|
|
|
|
belongs_to :user
|
|
belongs_to :journable, polymorphic: true
|
|
belongs_to :data, polymorphic: true, dependent: :destroy
|
|
|
|
has_many :agenda_item_journals, class_name: "Journal::MeetingAgendaItemJournal", dependent: :delete_all
|
|
has_many :participant_journals, class_name: "Journal::MeetingParticipantJournal", dependent: :delete_all
|
|
has_many :attachable_journals, class_name: "Journal::AttachableJournal", dependent: :delete_all
|
|
has_many :customizable_journals, class_name: "Journal::CustomizableJournal", dependent: :delete_all
|
|
has_many :custom_comment_journals, class_name: "Journal::CustomCommentJournal", dependent: :delete_all
|
|
has_many :project_phase_journals, class_name: "Journal::ProjectPhaseJournal", dependent: :delete_all
|
|
has_many :storable_journals, class_name: "Journal::StorableJournal", dependent: :delete_all
|
|
|
|
has_many :notifications, dependent: :destroy
|
|
|
|
include ::Scopes::Scoped
|
|
|
|
scopes :with_sequence_version
|
|
|
|
# Scopes to all journals excluding the initial journal - useful for change
|
|
# logs like the history on issue#show
|
|
scope :changing, -> { where(["version > 1"]) }
|
|
|
|
scope :for_wiki_page, -> { where(journable_type: "WikiPage") }
|
|
scope :for_work_package, -> { where(journable_type: "WorkPackage") }
|
|
scope :for_meeting, -> { where(journable_type: "Meeting") }
|
|
|
|
alias_attribute :internal, :restricted
|
|
|
|
# In conjunction with the included Comparable module, allows comparison of journal records
|
|
# based on their corresponding version numbers, creation timestamps and IDs.
|
|
def <=>(other)
|
|
[version, created_at, id].map(&:to_i) <=> [other.version, other.created_at, other.id].map(&:to_i)
|
|
end
|
|
|
|
# Returns whether the version has a version number of 1. Useful when deciding whether to ignore
|
|
# the version during reversion, as initial versions have no serialized changes attached. Helps
|
|
# maintain backwards compatibility.
|
|
def initial?
|
|
version < 2
|
|
end
|
|
|
|
# The anchor number for html output
|
|
def anchor
|
|
version - 1
|
|
end
|
|
|
|
# Possible shortcut to the associated project
|
|
def project
|
|
if journable.respond_to?(:project)
|
|
journable.project
|
|
elsif journable.is_a? Project
|
|
journable
|
|
end
|
|
end
|
|
|
|
def attachments_visible?(user = User.current)
|
|
if internal?
|
|
journable.attachments_visible?(user) && user.allowed_in_project?(:view_internal_comments, project)
|
|
else
|
|
journable.attachments_visible?(user)
|
|
end
|
|
end
|
|
|
|
def visible?(user = User.current)
|
|
if internal?
|
|
journable.visible?(user) && user.allowed_in_project?(:view_internal_comments, project)
|
|
else
|
|
journable.visible?(user)
|
|
end
|
|
end
|
|
|
|
def editable_by?(user)
|
|
journable.journal_editable_by?(self, user)
|
|
end
|
|
|
|
def details
|
|
get_changes
|
|
end
|
|
|
|
def new_value_for(prop)
|
|
details[prop].last if details.key? prop
|
|
end
|
|
|
|
def old_value_for(prop)
|
|
details[prop].first if details.key? prop
|
|
end
|
|
|
|
def previous
|
|
predecessor
|
|
end
|
|
|
|
def successor
|
|
return @successor if defined?(@successor)
|
|
|
|
@successor = self.class
|
|
.where(journable_type:, journable_id:)
|
|
.where("#{self.class.table_name}.version > ?", version)
|
|
.order(version: :asc)
|
|
.first
|
|
end
|
|
|
|
def noop?
|
|
(!notes || notes&.empty?) && get_changes.empty?
|
|
end
|
|
|
|
def has_cause?
|
|
cause_type.present?
|
|
end
|
|
|
|
def has_unread_notifications_for_user?(user)
|
|
# we optionally set the instance variable @unread_notifications in the ActivityEagerLoadingWrapper
|
|
# in order to avoid N+1 queries
|
|
if instance_variable_defined?(:@unread_notifications)
|
|
@unread_notifications&.any? { |notification| notification.recipient_id == user.id }
|
|
else
|
|
notifications.where(read_ian: false, recipient_id: user.id).any?
|
|
end
|
|
end
|
|
|
|
def predecessor
|
|
return @predecessor if defined?(@predecessor)
|
|
|
|
@predecessor = if initial?
|
|
nil
|
|
else
|
|
self.class
|
|
.where(journable_type:, journable_id:)
|
|
.where("#{self.class.table_name}.version < ?", version)
|
|
.order(version: :desc)
|
|
.first
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def has_file_links?
|
|
journable.respond_to?(:file_links)
|
|
end
|
|
end
|