Feature/remove timelog (#8557)
* rename costs, introduce budgets * move files from costs to budgets * rename cost_object to budget * remove unused code * move hook - should be turned into standard code in the long run * move type attributes change over to budgets * move patch to work_package proper * move budget menu item up * combine reporting, time and cost module * remove rails based time_entries & reports code * rename cost object filter * adapt menu spec expectations * use cost project module name in administration * include timeline labels in migration * properly place budget linking method * fix permitted params * remove outdated routing spec * adapt budget request specs * ensure order of descendent updates * remove outdated specs * fix checking for reporting to be enabled * fix displaying spent units * fix time entries activity event url * reenable current rate tab * fix path on budget page * allow bulk editing of budgets only in one project scenario * fix sanitizing reference in controller * include module required for format_date * fix reference to correct units from work package spent units * linting * remove outdated spec * remove outdated views and permission references * remove acts_as_event from time_entries There is no atom link for time entries * remove acts_as_event from projects There are no atom links for projects * introduce budget filter for cost reports * remove actions added to removed controller * move time entries to the costs module * factor in view_own permission when calculating time entry visibility * linting * move mounting of time entries * include budgets into api v3 documentationpull/8572/head
parent
81bd0e34cd
commit
6826f90ee2
@ -1,207 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class TimeEntries::ReportsController < ApplicationController |
||||
helper_method :gon |
||||
|
||||
menu_item :issues |
||||
before_action :find_optional_project |
||||
before_action :load_available_criterias |
||||
|
||||
include SortHelper |
||||
include TimelogHelper |
||||
include CustomFieldsHelper |
||||
|
||||
menu_item :time_entries |
||||
|
||||
def show |
||||
# Set tab param to recognize correct selected tab |
||||
params[:tab] = params[:tab] || 'report' |
||||
|
||||
@criterias = params[:criterias] || [] |
||||
@criterias = @criterias.select { |criteria| @available_criterias.has_key? criteria } |
||||
@criterias.uniq! |
||||
@criterias = @criterias[0, 3] |
||||
|
||||
@columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month' |
||||
|
||||
retrieve_date_range |
||||
|
||||
unless @criterias.empty? |
||||
sql_select = @criterias.map { |criteria| @available_criterias[criteria][:sql] + ' AS ' + criteria }.join(', ') |
||||
sql_group_by = @criterias.map { |criteria| @available_criterias[criteria][:sql] }.join(', ') |
||||
|
||||
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours" |
||||
sql << " FROM #{TimeEntry.table_name}" |
||||
sql << time_report_joins |
||||
sql << ' WHERE' |
||||
sql << ' (%s) AND' % context_sql_condition |
||||
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)] |
||||
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on" |
||||
|
||||
@hours = ActiveRecord::Base.connection.select_all(sql) |
||||
|
||||
@hours.each do |row| |
||||
case @columns |
||||
when 'year' |
||||
row['year'] = row['tyear'] |
||||
when 'month' |
||||
row['month'] = "#{row['tyear']}-#{row['tmonth']}" |
||||
when 'week' |
||||
row['week'] = "#{row['tyear']}-#{row['tweek']}" |
||||
when 'day' |
||||
row['day'] = "#{row['spent_on']}" |
||||
end |
||||
end |
||||
|
||||
@total_hours = @hours.inject(0) { |s, k| s = s + k['hours'].to_f } |
||||
|
||||
@periods = [] |
||||
# Date#at_beginning_of_ not supported in Rails 1.2.x |
||||
date_from = @from.to_time |
||||
# 100 columns max |
||||
while date_from <= @to.to_time && @periods.length < 100 |
||||
case @columns |
||||
when 'year' |
||||
@periods << "#{date_from.year}" |
||||
date_from = (date_from + 1.year).at_beginning_of_year |
||||
when 'month' |
||||
@periods << "#{date_from.year}-#{date_from.month}" |
||||
date_from = (date_from + 1.month).at_beginning_of_month |
||||
when 'week' |
||||
@periods << "#{date_from.year}-#{date_from.to_date.cweek}" |
||||
date_from = (date_from + 7.day).at_beginning_of_week |
||||
when 'day' |
||||
@periods << "#{date_from.to_date}" |
||||
date_from = date_from + 1.day |
||||
end |
||||
end |
||||
end |
||||
|
||||
respond_to do |format| |
||||
format.html do render layout: !request.xhr? end |
||||
format.csv do |
||||
render csv: report_to_csv(@criterias, @periods, @hours), filename: 'timelog.csv' |
||||
end |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def load_available_criterias |
||||
@available_criterias = { 'project' => { sql: "#{TimeEntry.table_name}.project_id", |
||||
klass: Project, |
||||
label: Project.model_name.human }, |
||||
'version' => { sql: "#{WorkPackage.table_name}.version_id", |
||||
klass: Version, |
||||
label: Version.model_name.human }, |
||||
'category' => { sql: "#{WorkPackage.table_name}.category_id", |
||||
klass: Category, |
||||
label: Category.model_name.human }, |
||||
'member' => { sql: "#{TimeEntry.table_name}.user_id", |
||||
klass: User, |
||||
label: Member.model_name.human }, |
||||
'type' => { sql: "#{WorkPackage.table_name}.type_id", |
||||
klass: ::Type, |
||||
label: ::Type.model_name.human }, |
||||
'activity' => { sql: "#{TimeEntry.table_name}.activity_id", |
||||
klass: TimeEntryActivity, |
||||
label: :label_activity }, |
||||
'work_package' => { sql: "#{TimeEntry.table_name}.work_package_id", |
||||
klass: WorkPackage, |
||||
label: WorkPackage.model_name.human } |
||||
} |
||||
|
||||
# Add list and boolean custom fields as available criterias |
||||
custom_fields = (@project.nil? ? WorkPackageCustomField.for_all : @project.all_work_package_custom_fields) |
||||
|
||||
if @project |
||||
custom_fields.select { |cf| %w(list bool).include? cf.field_format }.each do |cf| |
||||
@available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c |
||||
WHERE c.custom_field_id = #{cf.id} |
||||
AND c.customized_type = 'WorkPackage' |
||||
AND c.customized_id = #{WorkPackage.table_name}.id)", |
||||
format: cf, |
||||
label: cf.name } |
||||
end |
||||
end |
||||
|
||||
# Add list and boolean time entry custom fields |
||||
TimeEntryCustomField.all.select { |cf| %w(list bool).include? cf.field_format }.each do |cf| |
||||
@available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c |
||||
WHERE c.custom_field_id = #{cf.id} |
||||
AND c.customized_type = 'TimeEntry' |
||||
AND c.customized_id = #{TimeEntry.table_name}.id)", |
||||
format: cf, |
||||
label: cf.name } |
||||
end |
||||
|
||||
# Add list and boolean time entry activity custom fields |
||||
TimeEntryActivityCustomField.all.select { |cf| %w(list bool).include? cf.field_format }.each do |cf| |
||||
@available_criterias["cf_#{cf.id}"] = { sql: "(SELECT c.value FROM #{CustomValue.table_name} c |
||||
WHERE c.custom_field_id = #{cf.id} |
||||
AND c.customized_type = 'Enumeration' |
||||
AND c.customized_id = #{TimeEntry.table_name}.activity_id)", |
||||
format: cf, |
||||
label: cf.name } |
||||
end |
||||
|
||||
call_hook(:controller_timelog_available_criterias, available_criterias: @available_criterias, project: @project) |
||||
@available_criterias |
||||
end |
||||
|
||||
def time_report_joins |
||||
sql = '' |
||||
sql << " LEFT JOIN #{WorkPackage.table_name} ON #{TimeEntry.table_name}.work_package_id = #{WorkPackage.table_name}.id" |
||||
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id" |
||||
# TODO: rename hook |
||||
call_hook(:controller_timelog_time_report_joins, sql: sql) |
||||
sql |
||||
end |
||||
|
||||
def default_breadcrumb |
||||
I18n.t(:label_spent_time) |
||||
end |
||||
|
||||
def context_sql_condition |
||||
if @project.nil? |
||||
project_context_sql_condition |
||||
elsif @issue.nil? |
||||
@project.project_condition(Setting.display_subprojects_work_packages?).to_sql |
||||
else |
||||
WorkPackage.self_and_descendants_of_condition(@issue) |
||||
end |
||||
end |
||||
|
||||
def project_context_sql_condition |
||||
time_entry_table = TimeEntry.arel_table |
||||
allowed_project_ids = Project.allowed_to(User.current, :view_time_entries).select(:id).arel |
||||
time_entry_table[:project_id].in(allowed_project_ids).to_sql |
||||
end |
||||
end |
@ -1,232 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class TimelogController < ApplicationController |
||||
helper_method :gon |
||||
|
||||
before_action :find_work_package, only: %i[new create] |
||||
before_action :find_project, only: %i[new create] |
||||
before_action :find_time_entry, only: %i[show edit update destroy] |
||||
before_action :authorize, except: [:index] |
||||
before_action :find_optional_project, only: [:index] |
||||
|
||||
include SortHelper |
||||
include TimelogHelper |
||||
include CustomFieldsHelper |
||||
include PaginationHelper |
||||
include Layout |
||||
|
||||
menu_item :time_entries |
||||
|
||||
def index |
||||
# Set tab param to recognize correct selected tab |
||||
params[:tab] = params[:tab] || 'details' |
||||
|
||||
sort_init 'spent_on', 'desc' |
||||
sort_update 'spent_on' => 'spent_on', |
||||
'user' => 'user_id', |
||||
'activity' => 'activity_id', |
||||
'project' => "#{Project.table_name}.name", |
||||
'work_package' => 'work_package_id', |
||||
'comments' => 'comments', |
||||
'hours' => 'hours' |
||||
|
||||
cond = ARCondition.new |
||||
if @issue |
||||
cond << WorkPackage.self_and_descendants_of_condition(@issue) |
||||
elsif @project |
||||
cond << @project.project_condition(Setting.display_subprojects_work_packages?).to_sql |
||||
end |
||||
|
||||
retrieve_date_range allow_nil: true |
||||
if @from && @to |
||||
cond << ['spent_on BETWEEN ? AND ?', @from, @to] |
||||
end |
||||
|
||||
respond_to do |format| |
||||
format.html do |
||||
render layout: layout_non_or_no_menu |
||||
end |
||||
format.csv do |
||||
# Export all entries |
||||
@entries = TimeEntry |
||||
.visible |
||||
.includes(:project, |
||||
:activity, |
||||
:user, |
||||
work_package: %i[type assigned_to priority]) |
||||
.references(:projects) |
||||
.where(cond.conditions) |
||||
.distinct(false) |
||||
.order(sort_clause) |
||||
|
||||
render csv: entries_to_csv(@entries), filename: 'timelog.csv' |
||||
end |
||||
end |
||||
end |
||||
|
||||
def new |
||||
@time_entry = new_time_entry(@project, @issue, permitted_params.time_entry.to_h) |
||||
|
||||
call_hook(:controller_timelog_edit_before_save, params: params, time_entry: @time_entry) |
||||
|
||||
render action: 'edit' |
||||
end |
||||
|
||||
def create |
||||
combined_params = permitted_params |
||||
.time_entry |
||||
.to_h |
||||
.reverse_merge(project: @project, |
||||
work_package_id: @issue) |
||||
|
||||
call = TimeEntries::CreateService |
||||
.new(user: current_user) |
||||
.call(combined_params) |
||||
|
||||
@time_entry = call.result |
||||
|
||||
respond_for_saving call |
||||
end |
||||
|
||||
def edit |
||||
@time_entry.attributes = permitted_params.time_entry |
||||
|
||||
call_hook(:controller_timelog_edit_before_save, params: params, time_entry: @time_entry) |
||||
end |
||||
|
||||
def update |
||||
service = TimeEntries::UpdateService |
||||
.new(user: current_user, |
||||
model: @time_entry) |
||||
call = service.call(attributes: permitted_params.time_entry) |
||||
respond_for_saving call |
||||
end |
||||
|
||||
def destroy |
||||
if @time_entry.destroy && @time_entry.destroyed? |
||||
respond_to do |format| |
||||
format.html do |
||||
flash[:notice] = l(:notice_successful_delete) |
||||
redirect_back fallback_location: { action: 'index', project_id: @time_entry.project } |
||||
end |
||||
format.json do |
||||
render json: { text: l(:notice_successful_delete) } |
||||
end |
||||
end |
||||
else |
||||
respond_to do |format| |
||||
format.html do |
||||
flash[:error] = l(:notice_unable_delete_time_entry) |
||||
redirect_back fallback_location: { action: 'index', project_id: @time_entry.project } |
||||
end |
||||
format.json do |
||||
render json: { isError: true, text: l(:notice_unable_delete_time_entry) } |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def find_time_entry |
||||
@time_entry = TimeEntry.find(params[:id]) |
||||
unless @time_entry.editable_by?(User.current) |
||||
render_403 |
||||
return false |
||||
end |
||||
@project = @time_entry.project |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
def find_project |
||||
@project = Project.find(project_id_from_params) if @project.nil? |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
def new_time_entry(project, work_package, attributes) |
||||
time_entry = TimeEntry.new(project: project, |
||||
work_package: work_package, |
||||
user: User.current, |
||||
spent_on: User.current.today) |
||||
|
||||
time_entry.attributes = attributes |
||||
|
||||
time_entry |
||||
end |
||||
|
||||
def respond_for_saving(call) |
||||
@errors = call.errors |
||||
|
||||
if call.success? |
||||
respond_to do |format| |
||||
format.html do |
||||
flash[:notice] = l(:notice_successful_update) |
||||
redirect_back_or_default action: 'index', project_id: @time_entry.project |
||||
end |
||||
end |
||||
else |
||||
respond_to do |format| |
||||
format.html do |
||||
render action: 'edit' |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
def project_id_from_params |
||||
if params.has_key?(:project_id) |
||||
params[:project_id] |
||||
elsif params.has_key?(:time_entry) && permitted_params.time_entry.has_key?(:project_id) |
||||
permitted_params.time_entry[:project_id] |
||||
end |
||||
end |
||||
|
||||
def find_work_package |
||||
@issue = work_package_from_params |
||||
@project = @issue.project unless @issue.nil? |
||||
end |
||||
|
||||
def work_package_from_params |
||||
if params.has_key?(:work_package_id) |
||||
work_package_id = params[:work_package_id] |
||||
elsif params.has_key?(:time_entry) && permitted_params.time_entry.has_key?(:work_package_id) |
||||
work_package_id = permitted_params.time_entry[:work_package_id] |
||||
end |
||||
|
||||
WorkPackage.find_by id: work_package_id |
||||
end |
||||
|
||||
def default_breadcrumb |
||||
I18n.t(:label_spent_time) |
||||
end |
||||
end |
@ -1,279 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module TimelogHelper |
||||
include ApplicationHelper |
||||
|
||||
def time_entry_tabs |
||||
[ |
||||
{ |
||||
name: 'details', |
||||
partial: 'timelog/time_entry_tab', |
||||
path: polymorphic_time_entries_path(@issue || @project), |
||||
label: :label_details |
||||
}, |
||||
{ |
||||
name: 'report', |
||||
partial: 'time_entries/reports/reports_tab', |
||||
path: polymorphic_time_entries_report_path(@issue || @project), |
||||
label: :label_report |
||||
} |
||||
] |
||||
end |
||||
|
||||
# Returns a collection of activities for a select field. time_entry |
||||
# is optional and will be used to check if the selected TimeEntryActivity |
||||
# is active. |
||||
def activity_collection_for_select_options(time_entry = nil, project = nil) |
||||
project ||= @project |
||||
activities = |
||||
if project.nil? |
||||
TimeEntryActivity.all.active |
||||
else |
||||
TimeEntryActivity::Scopes::ActiveInProject.fetch(project) |
||||
end |
||||
|
||||
activities.map do |a| |
||||
if a.is_default? && !time_entry&.activity |
||||
[a.name, a.id, selected: true] |
||||
else |
||||
[a.name, a.id] |
||||
end |
||||
end |
||||
end |
||||
|
||||
def select_hours(data, criteria, value) |
||||
if value.to_s.empty? |
||||
data.select { |row| row[criteria].blank? } |
||||
else |
||||
data.select { |row| row[criteria].to_s == value.to_s } |
||||
end |
||||
end |
||||
|
||||
def sum_hours(data) |
||||
sum = 0 |
||||
data.each do |row| |
||||
sum += row['hours'].to_f |
||||
end |
||||
sum |
||||
end |
||||
|
||||
def options_for_period_select(value) |
||||
options_for_select([[l(:label_all_time), 'all'], |
||||
[l(:label_today), 'today'], |
||||
[l(:label_yesterday), 'yesterday'], |
||||
[l(:label_this_week), 'current_week'], |
||||
[l(:label_last_week), 'last_week'], |
||||
[l(:label_last_n_days, 7), '7_days'], |
||||
[l(:label_this_month), 'current_month'], |
||||
[l(:label_last_month), 'last_month'], |
||||
[l(:label_last_n_days, 30), '30_days'], |
||||
[l(:label_this_year), 'current_year']], |
||||
value) |
||||
end |
||||
|
||||
def entries_to_csv(entries) |
||||
decimal_separator = l(:general_csv_decimal_separator) |
||||
custom_fields = TimeEntryCustomField.all |
||||
export = CSV.generate(col_sep: l(:general_csv_separator)) { |csv| |
||||
# csv header fields |
||||
headers = [TimeEntry.human_attribute_name(:spent_on), |
||||
TimeEntry.human_attribute_name(:user), |
||||
TimeEntry.human_attribute_name(:activity), |
||||
TimeEntry.human_attribute_name(:project), |
||||
TimeEntry.human_attribute_name(:issue), |
||||
TimeEntry.human_attribute_name(:type), |
||||
TimeEntry.human_attribute_name(:subject), |
||||
TimeEntry.human_attribute_name(:hours), |
||||
TimeEntry.human_attribute_name(:comments) |
||||
] |
||||
# Export custom fields |
||||
headers += custom_fields.map(&:name) |
||||
|
||||
csv << WorkPackage::Exporter::CSV.encode_csv_columns(headers) |
||||
# csv lines |
||||
entries.each do |entry| |
||||
fields = [format_date(entry.spent_on), |
||||
entry.user, |
||||
entry.activity, |
||||
entry.project, |
||||
(entry.work_package ? entry.work_package.id : nil), |
||||
(entry.work_package ? entry.work_package.type : nil), |
||||
(entry.work_package ? entry.work_package.subject : nil), |
||||
entry.hours.to_s.gsub('.', decimal_separator), |
||||
entry.comments |
||||
] |
||||
fields += custom_fields.map { |f| show_value(entry.custom_value_for(f)) } |
||||
|
||||
csv << WorkPackage::Exporter::CSV.encode_csv_columns(fields) |
||||
end |
||||
} |
||||
export |
||||
end |
||||
|
||||
def format_criteria_value(criteria, value) |
||||
if value.blank? |
||||
l(:label_none) |
||||
elsif k = @available_criterias[criteria][:klass] |
||||
obj = k.find_by(id: value.to_i) |
||||
if obj.is_a?(WorkPackage) |
||||
obj.visible? ? h("#{obj.type} ##{obj.id}: #{obj.subject}") : h("##{obj.id}") |
||||
else |
||||
obj |
||||
end |
||||
else |
||||
format_value(value, @available_criterias[criteria][:format]) |
||||
end |
||||
end |
||||
|
||||
def report_to_csv(criterias, periods, hours) |
||||
export = CSV.generate(col_sep: l(:general_csv_separator)) { |csv| |
||||
# Column headers |
||||
headers = criterias.map { |criteria| |
||||
label = @available_criterias[criteria][:label] |
||||
label.is_a?(Symbol) ? l(label) : label |
||||
} |
||||
headers += periods |
||||
headers << l(:label_total) |
||||
csv << headers.map { |c| to_utf8_for_timelogs(c) } |
||||
# Content |
||||
report_criteria_to_csv(csv, criterias, periods, hours) |
||||
# Total row |
||||
row = [l(:label_total)] + [''] * (criterias.size - 1) |
||||
total = 0 |
||||
periods.each do |period| |
||||
sum = sum_hours(select_hours(hours, @columns, period.to_s)) |
||||
total += sum |
||||
row << (sum > 0 ? '%.2f' % sum : '') |
||||
end |
||||
row << '%.2f' % total |
||||
csv << row |
||||
} |
||||
export |
||||
end |
||||
|
||||
def report_criteria_to_csv(csv, criterias, periods, hours, level = 0) |
||||
hours.map { |h| h[criterias[level]].to_s }.uniq.each do |value| |
||||
hours_for_value = select_hours(hours, criterias[level], value) |
||||
next if hours_for_value.empty? |
||||
row = [''] * level |
||||
row << to_utf8_for_timelogs(format_criteria_value(criterias[level], value)) |
||||
row += [''] * (criterias.length - level - 1) |
||||
total = 0 |
||||
periods.each do |period| |
||||
sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)) |
||||
total += sum |
||||
row << (sum > 0 ? '%.2f' % sum : '') |
||||
end |
||||
row << '%.2f' % total |
||||
csv << row |
||||
|
||||
if criterias.length > level + 1 |
||||
report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1) |
||||
end |
||||
end |
||||
end |
||||
|
||||
def to_utf8_for_timelogs(s) |
||||
s.to_s.encode(l(:general_csv_encoding), 'UTF-8'); rescue; s.to_s end |
||||
|
||||
def polymorphic_time_entries_path(object) |
||||
polymorphic_path([object, :time_entries]) |
||||
end |
||||
|
||||
def polymorphic_new_time_entry_path(object) |
||||
polymorphic_path([:new, object, :time_entry,]) |
||||
end |
||||
|
||||
def polymorphic_time_entries_report_path(object) |
||||
polymorphic_path([object, :time_entries, :report]) |
||||
end |
||||
|
||||
# Retrieves the date range based on predefined ranges or specific from/to param dates |
||||
def retrieve_date_range(allow_nil: false) |
||||
@free_period = false |
||||
@from = nil |
||||
@to = nil |
||||
|
||||
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?) |
||||
case params[:period].to_s |
||||
when 'today' |
||||
@from = @to = Date.today |
||||
when 'yesterday' |
||||
@from = @to = Date.today - 1 |
||||
when 'current_week' |
||||
@from = Date.today - (Date.today.cwday - 1) % 7 |
||||
@to = @from + 6 |
||||
when 'last_week' |
||||
@from = Date.today - 7 - (Date.today.cwday - 1) % 7 |
||||
@to = @from + 6 |
||||
when '7_days' |
||||
@from = Date.today - 7 |
||||
@to = Date.today |
||||
when 'current_month' |
||||
@from = Date.civil(Date.today.year, Date.today.month, 1) |
||||
@to = (@from >> 1) - 1 |
||||
when 'last_month' |
||||
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1 |
||||
@to = (@from >> 1) - 1 |
||||
when '30_days' |
||||
@from = Date.today - 30 |
||||
@to = Date.today |
||||
when 'current_year' |
||||
@from = Date.civil(Date.today.year, 1, 1) |
||||
@to = Date.civil(Date.today.year, 12, 31) |
||||
end |
||||
elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?)) |
||||
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end |
||||
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end |
||||
@free_period = true |
||||
# default |
||||
end |
||||
|
||||
@from, @to = @to, @from if @from && @to && @from > @to |
||||
|
||||
unless allow_nil |
||||
@from ||= (TimeEntry.earliest_date_for_project(@project) || Date.today) |
||||
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today) |
||||
end |
||||
end |
||||
|
||||
def find_optional_project |
||||
if !params[:issue_id].blank? |
||||
@issue = WorkPackage.find(params[:issue_id]) |
||||
@project = @issue.project |
||||
elsif !params[:work_package_id].blank? |
||||
@issue = WorkPackage.find(params[:work_package_id]) |
||||
@project = @issue.project |
||||
elsif !params[:project_id].blank? |
||||
@project = Project.find(params[:project_id]) |
||||
end |
||||
deny_access unless User.current.allowed_to?(:view_time_entries, @project, global: true) |
||||
end |
||||
end |
@ -0,0 +1,141 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module WorkPackages::Costs |
||||
extend ActiveSupport::Concern |
||||
|
||||
included do |
||||
belongs_to :budget, inverse_of: :work_packages |
||||
has_many :cost_entries, dependent: :delete_all |
||||
|
||||
# disabled for now, implements part of ticket blocking |
||||
validate :validate_budget |
||||
|
||||
after_update :move_cost_entries |
||||
|
||||
associated_to_ask_before_destruction CostEntry, |
||||
->(work_packages) { CostEntry.on_work_packages(work_packages).count.positive? }, |
||||
method(:cleanup_cost_entries_before_destruction_of) |
||||
|
||||
def costs_enabled? |
||||
project&.costs_enabled? |
||||
end |
||||
|
||||
def validate_budget |
||||
if budget_id_changed? |
||||
unless budget_id.blank? || project.budget_ids.include?(budget_id) |
||||
errors.add :budget, :inclusion |
||||
end |
||||
end |
||||
end |
||||
|
||||
def material_costs |
||||
if respond_to?(:cost_entries_sum) # column has been eager loaded into result set |
||||
cost_entries_sum.to_f |
||||
else |
||||
::WorkPackage::MaterialCosts.new(user: User.current).costs_of work_packages: self_and_descendants |
||||
end |
||||
end |
||||
|
||||
def labor_costs |
||||
if respond_to?(:time_entries_sum) # column has been eager loaded into result set |
||||
time_entries_sum.to_f |
||||
else |
||||
::WorkPackage::LaborCosts.new(user: User.current).costs_of work_packages: self_and_descendants |
||||
end |
||||
end |
||||
|
||||
def overall_costs |
||||
labor_costs + material_costs |
||||
end |
||||
|
||||
# Wraps the association to get the Cost Object subject. Needed for the |
||||
# Query and filtering |
||||
def budget_subject |
||||
budget&.subject |
||||
end |
||||
|
||||
def move_cost_entries |
||||
return unless saved_change_to_project_id? |
||||
|
||||
CostEntry |
||||
.where(work_package_id: id) |
||||
.update_all(project_id: project_id) |
||||
end |
||||
end |
||||
|
||||
class_methods do |
||||
protected |
||||
|
||||
def cleanup_cost_entries_before_destruction_of(work_packages, user, to_do = { action: 'destroy' }) |
||||
work_packages = Array(work_packages) |
||||
|
||||
return false unless to_do.present? |
||||
|
||||
case to_do[:action] |
||||
when 'destroy' |
||||
true |
||||
# nothing to do |
||||
when 'nullify' |
||||
work_packages.each do |wp| |
||||
wp.errors.add(:base, :nullify_is_not_valid_for_cost_entries) |
||||
end |
||||
|
||||
false |
||||
when 'reassign' |
||||
reassign_cost_entries_before_destruction(work_packages, user, to_do[:reassign_to_id]) |
||||
else |
||||
false |
||||
end |
||||
end |
||||
|
||||
def reassign_cost_entries_before_destruction(work_packages, user, ids) |
||||
reassign_to = ::WorkPackage |
||||
.joins(:project) |
||||
.merge(Project.allowed_to(user, :edit_cost_entries)) |
||||
.find_by_id(ids) |
||||
|
||||
if reassign_to.nil? |
||||
work_packages.each do |wp| |
||||
wp.errors.add(:base, :is_not_a_valid_target_for_cost_entries, id: ids) |
||||
end |
||||
|
||||
false |
||||
else |
||||
condition = "work_package_id = #{reassign_to.id}, project_id = #{reassign_to.project_id}" |
||||
::WorkPackage.update_cost_entries(work_packages.map(&:id), condition) |
||||
end |
||||
end |
||||
|
||||
def update_cost_entries(work_packages, action) |
||||
CostEntry.where(work_package_id: work_packages).update_all(action) |
||||
end |
||||
end |
||||
end |
@ -1,48 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is an open source project management software. |
||||
Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<% @hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value| %> |
||||
|
||||
<% hours_for_value = select_hours(hours, criterias[level], value) -%> |
||||
<% next if hours_for_value.empty? -%> |
||||
<tr class="<%= 'last-level' unless criterias.length > level+1 %>"> |
||||
<%= ('<td></td>' * level).html_safe %> |
||||
<td><%= h(format_criteria_value(criterias[level], value)) %></td> |
||||
<%= ('<td></td>' * (criterias.length - level - 1)).html_safe -%> |
||||
<% total = 0 -%> |
||||
<% @periods.each do |period| -%> |
||||
<% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)); total += sum -%> |
||||
<td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td> |
||||
<% end -%> |
||||
<td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td> |
||||
</tr> |
||||
<% if criterias.length > level+1 -%> |
||||
<%= render(partial: 'report_criteria', locals: { criterias: criterias, hours: hours_for_value, level: (level + 1) }) %> |
||||
<% end -%> |
||||
<% end %> |
@ -1,140 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is an open source project management software. |
||||
Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<%= styled_form_tag(polymorphic_time_entries_report_path(@issue || @project), method: :get, id: 'query_form', class: '-vertical') do %> |
||||
<% @criterias.each do |criteria| %> |
||||
<%= hidden_field_tag 'criterias[]', criteria, id: nil %> |
||||
<% end %> |
||||
|
||||
<div class="timelog-report-selection small-6"> |
||||
<div class="grid-block"> |
||||
|
||||
<div class="form--field"> |
||||
<%= styled_label_tag :columns, "#{t(:label_details)}:" %> |
||||
<%= styled_select_tag 'columns', options_for_select([[l(:label_year), 'year'], |
||||
[l(:label_month), 'month'], |
||||
[l(:label_week), 'week'], |
||||
[l(:label_day_plural).titleize, 'day']], @columns), |
||||
container_class: '-slim' %> |
||||
</div> |
||||
|
||||
<div class="form--field"> |
||||
<%= styled_label_tag :criterias, "#{t(:button_add)}:" %> |
||||
<% available_criterias = [[]] + (@available_criterias.keys - @criterias).collect{ |k| |
||||
[l_or_humanize(@available_criterias[k][:label]), k] |
||||
} |
||||
%> |
||||
<%= styled_select_tag('criterias[]', options_for_select(available_criterias), |
||||
id: "criterias", |
||||
disabled: (@criterias.length >= 3), |
||||
container_class: '-slim') %> |
||||
</div> |
||||
</div> |
||||
<div class="grid-block"> |
||||
<%= submit_tag t(:button_apply), class: 'button -highlight' %> |
||||
<%= link_to t(:button_clear), {project_id: @project, issue_id: @issue, period_type: params[:period_type], period: params[:period], from: @from, to: @to, columns: @columns}, class: 'button' %> |
||||
</div> |
||||
</div> |
||||
<% end %> |
||||
<% unless @criterias.empty? %> |
||||
<div class="total-hours"> |
||||
<p><%= t(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %></p> |
||||
</div> |
||||
|
||||
<% unless @hours.empty? %> |
||||
<div class="generic-table--container"> |
||||
<div class="generic-table--results-container"> |
||||
<table class="generic-table" id="time-report"> |
||||
<colgroup> |
||||
<% @criterias.each do |criteria| %> |
||||
<col highlight-col> |
||||
<% end %> |
||||
<% @periods.each do |period| %> |
||||
<col highlight-col> |
||||
<% end %> |
||||
<col highlight-col> |
||||
</colgroup> |
||||
<thead> |
||||
<tr> |
||||
<% @criterias.each do |criteria| %> |
||||
<th> |
||||
<div class="generic-table--sort-header-outer"> |
||||
<div class="generic-table--sort-header"> |
||||
<span> |
||||
<%= l_or_humanize(@available_criterias[criteria][:label]) %> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</th> |
||||
<% end %> |
||||
<% @periods.each do |period| %> |
||||
<th class="period"> |
||||
<div class="generic-table--sort-header-outer"> |
||||
<div class="generic-table--sort-header"> |
||||
<span> |
||||
<%= period %> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</th> |
||||
<% end %> |
||||
<th class="total"> |
||||
<div class="generic-table--sort-header-outer"> |
||||
<div class="generic-table--sort-header"> |
||||
<span> |
||||
<%= t(:label_total) %> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<%= render partial: 'report_criteria', locals: {criterias: @criterias, hours: @hours, level: 0} %> |
||||
<tr class="total"> |
||||
<td><%= t(:label_total) %></td> |
||||
<%= ('<td></td>' * (@criterias.size - 1)).html_safe %> |
||||
<% total = 0 -%> |
||||
<% @periods.each do |period| -%> |
||||
<% sum = sum_hours(select_hours(@hours, @columns, period.to_s)); total += sum -%> |
||||
<td class="hours"><%= html_hours("%.2f" % sum) if sum > 0 %></td> |
||||
<% end -%> |
||||
<td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
<%= other_formats_links do |f| %> |
||||
<%= f.link_to 'CSV', url: permitted_params.timelog.to_h %> |
||||
<% end %> |
||||
<% end %> |
||||
<% end %> |
@ -1,45 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is an open source project management software. |
||||
Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<%= toolbar title: t(:label_spent_time) do %> |
||||
<% if User.current.allowed_to?({controller: 'timelog', action: :new}, @project) %> |
||||
<li class="toolbar-item"> |
||||
<%= link_to polymorphic_new_time_entry_path(@issue || @project), class: 'button' do %> |
||||
<%= op_icon('button--icon icon-time') %> |
||||
<span class="button--text"><%= t(:button_log_time) %></span> |
||||
<% end %> |
||||
</li> |
||||
<% end %> |
||||
<% end %> |
||||
|
||||
<%= render partial: 'timelog/date_range' %> |
||||
|
||||
<%= render_tabs time_entry_tabs %> |
||||
|
||||
<% html_title t(:label_spent_time), t(:label_report) %> |
@ -1,65 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is an open source project management software. |
||||
Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<% content_for :header_tags do %> |
||||
<meta name="required_script" content="timelog_date_range" /> |
||||
<% end %> |
||||
|
||||
<fieldset id="date-range" class="simple-filters--container"> |
||||
<legend><%= l(:label_date_range) %></legend> |
||||
<ul class="simple-filters--filters"> |
||||
<li class="simple-filters--filter -with-radio-buttons"> |
||||
<%= styled_label_tag "period_type_list", l(:description_date_range_list), class: "hidden-for-sighted simple-filters--filter-name" %> |
||||
<%= styled_radio_button_tag 'period_type', '1', |
||||
!@free_period, |
||||
id: "period_type_list"%> |
||||
<div class="simple-filters--filter-value"> |
||||
<%= styled_select_tag 'period', |
||||
options_for_period_select(params[:period]), |
||||
class: "-narrow" %> |
||||
</div> |
||||
</li> |
||||
<li class="simple-filters--filter -with-radio-buttons"> |
||||
<%= styled_label_tag "period_type_interval", l(:description_date_range_interval), class: "hidden-for-sighted simple-filters--filter-name" %> |
||||
<%= styled_radio_button_tag 'period_type', '2', @free_period, id: "period_type_interval" %> |
||||
<%= styled_label_tag("from", l(:label_date_from), class: 'simple-filters--filter-name') %> |
||||
<div class="simple-filters--filter-value"> |
||||
<%= styled_text_field_tag('from', @from, class: '-augmented-datepicker', size: 10) %> |
||||
</div> |
||||
<%= styled_label_tag("to", l(:label_date_to), class: 'simple-filters--filter-name') %> |
||||
<div class="simple-filters--filter-value"> |
||||
<%= styled_text_field_tag('to', @to, class: '-augmented-datepicker', size: 10) %> |
||||
</div> |
||||
</li> |
||||
<li class="simple-filters--controls"> |
||||
<%= styled_button_tag l(:button_apply), class: 'button -highlight -small' %> |
||||
<%= link_to l(:button_clear), polymorphic_time_entries_path(@issue || @project), class: 'button -small' %> |
||||
</li> |
||||
</ul> |
||||
</fieldset> |
@ -1,40 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is an open source project management software. |
||||
Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<div class="notification-box -warning"> |
||||
<a title="close" class="notification-box--close icon-context icon-close"></a> |
||||
<div class="notification-box--content"> |
||||
<%= t('deprecations.time_entries') %> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<%= other_formats_links do |f| %> |
||||
<%= f.link_to 'CSV', url: permitted_params.timelog.to_h %> |
||||
<% end %> |
||||
</div> |
@ -1,67 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is an open source project management software. |
||||
Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<%= toolbar title: t(:label_spent_time) %> |
||||
<%= labelled_tabular_form_for [@time_entry.project, @time_entry], as: :time_entry do |f| %> |
||||
<%= error_messages_for_contract @time_entry, @errors %> |
||||
<%= back_url_hidden_field_tag %> |
||||
|
||||
<div class="form--field"> |
||||
<%= f.text_field :work_package_id, size: 6, container_class: '-xslim' %> |
||||
<div class="form--field-instructions"> |
||||
<%= h("#{(@time_entry.work_package.type.nil?) ? '' : @time_entry.work_package.type.name} ##{@time_entry.work_package.id}: #{@time_entry.work_package.subject}") if @time_entry.work_package%> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="form--field -required"> |
||||
<%= f.text_field :spent_on, size: 10, required: true, container_class: '-xslim', class: '-augmented-datepicker' %> |
||||
</div> |
||||
|
||||
<div class="form--field -required"> |
||||
<%= f.text_field :hours, size: 6, required: true, container_class: '-xslim', class: '-number' %> |
||||
</div> |
||||
|
||||
<div class="form--field"> |
||||
<%= f.text_field :comments, size: 100, container_class: '-wide' %> |
||||
</div> |
||||
|
||||
<div class="form--field -required"> |
||||
<%= f.select :activity_id, |
||||
activity_collection_for_select_options(@time_entry), |
||||
prompt: "--- #{t(:actionview_instancetag_blank_option)} ---", |
||||
required: true, |
||||
container_class: '-slim' %> |
||||
</div> |
||||
|
||||
<%= render partial: "customizable/form", |
||||
locals: { form: f, all_fields: true, only_required: false } %> |
||||
|
||||
<%= call_hook(:view_timelog_edit_form_bottom, { time_entry: @time_entry, form: f }) %> |
||||
<%= f.button t(:button_save), class: 'button -highlight -with-icon icon-checkmark' %> |
||||
<% end %> |
@ -1,47 +0,0 @@ |
||||
<%#-- copyright |
||||
OpenProject is an open source project management software. |
||||
Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<%= toolbar title: l(:label_spent_time) do %> |
||||
<% if User.current.allowed_to?({controller: :timelog, action: :new}, @project) %> |
||||
<li class="toolbar-item"> |
||||
<%= link_to polymorphic_new_time_entry_path(@issue || @project), class: 'button' do %> |
||||
<%= op_icon('button--icon icon-time') %> |
||||
<span class="button--text"><%= l(:button_log_time) %></span> |
||||
<% end %> |
||||
</li> |
||||
<% end %> |
||||
<% end %> |
||||
|
||||
<%= form_tag(polymorphic_path([@issue || @project, :time_entries]), method: :get, id: 'query_form') do %> |
||||
<%= render partial: 'date_range' %> |
||||
<% end %> |
||||
|
||||
<%= render_tabs time_entry_tabs %> |
||||
|
||||
<% html_title l(:label_spent_time) %> |
@ -0,0 +1,90 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Migration |
||||
module MigrationUtils |
||||
class ModuleRenamer |
||||
class << self |
||||
def add_to_enabled(new_module, old_modules) |
||||
execute <<~SQL |
||||
INSERT INTO |
||||
enabled_modules ( |
||||
project_id, |
||||
name |
||||
) |
||||
SELECT |
||||
DISTINCT(project_id), |
||||
'#{new_module}' |
||||
FROM |
||||
enabled_modules |
||||
WHERE |
||||
name IN (#{comma_separated_strings(old_modules)}) |
||||
SQL |
||||
end |
||||
|
||||
def remove_from_enabled(modules) |
||||
execute <<~SQL |
||||
DELETE FROM |
||||
enabled_modules |
||||
WHERE |
||||
name IN (#{comma_separated_strings(modules)}) |
||||
SQL |
||||
end |
||||
|
||||
def add_to_default(new_modules, old_modules) |
||||
# avoid creating the settings implicitly on new installations |
||||
setting = Setting.find_by(name: 'default_projects_modules') |
||||
|
||||
return unless setting |
||||
|
||||
cleaned_setting = setting.value - Array(old_modules) |
||||
|
||||
if setting.value != cleaned_setting |
||||
Setting.default_projects_modules = cleaned_setting + Array(new_modules) |
||||
end |
||||
end |
||||
|
||||
def remove_from_default(name) |
||||
add_to_default([], name) |
||||
end |
||||
|
||||
private |
||||
|
||||
def execute(string) |
||||
ActiveRecord::Base.connection.execute string |
||||
end |
||||
|
||||
def comma_separated_strings(array) |
||||
Array(array).map { |i| "'#{i}'" }.join(', ') |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,147 @@ |
||||
# Group Budgets |
||||
|
||||
*Note: Budgets are currently only implemented as a stub. Further properties of budgets might be added at a future date, however they will require the view budget permission to be displayed.* |
||||
|
||||
## Linked Properties: |
||||
| Link | Description | Type | Constraints | Supported operations | |
||||
|:---------:|-------------------------------------------- | ------------- | --------------------- | -------------------- | |
||||
| self | This budget | Budget | not null | READ | |
||||
|
||||
## Properties |
||||
| Property | Description | Type | Constraints | Supported operations | Condition | |
||||
| :---------: | ------------------------------------------- | ----------- | ----------- | -------------------- | --------------------------- | |
||||
| id | Budget id | Integer | x > 0 | READ | | |
||||
| subject | Budget name | String | not empty | READ | | |
||||
|
||||
## Budget [/api/v3/budgets/{id}] |
||||
|
||||
+ Model |
||||
+ Body |
||||
|
||||
{ |
||||
"_type" : "Budget", |
||||
"_links" : { |
||||
"self" : { |
||||
"href" : "/api/v3/budgets/1", |
||||
"title" : "Q3 2015" |
||||
} |
||||
}, |
||||
"id" : 1, |
||||
"subject" : "Q3 2015" |
||||
} |
||||
|
||||
|
||||
## view Budget [GET] |
||||
|
||||
+ Parameters |
||||
+ id (required, integer, `1`) ... Budget id |
||||
|
||||
+ Response 200 (application/hal+json) |
||||
|
||||
[Budget][] |
||||
|
||||
+ Response 403 (application/hal+json) |
||||
|
||||
Returned if the client does not have sufficient permissions. |
||||
|
||||
**Required permission:** view work packages **or** view budgets (on the budgets project) |
||||
|
||||
+ Body |
||||
|
||||
{ |
||||
"_type": "Error", |
||||
"errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission", |
||||
"message": "You are not allowed to see this budget." |
||||
} |
||||
|
||||
## Budgets by Project [/api/v3/projects/{id}/budgets] |
||||
|
||||
+ Model |
||||
+ Body |
||||
|
||||
{ |
||||
"_links" : { |
||||
"self" : { |
||||
"href" : "/api/v3/projects/1/budgets" |
||||
} |
||||
}, |
||||
"_type" : "Collection", |
||||
"total" : 2, |
||||
"count" : 2, |
||||
"_embedded" : { |
||||
"elements" : [ |
||||
{ |
||||
"_type" : "Budget", |
||||
"_links" : { |
||||
"self" : { |
||||
"href" : "/api/v3/budgets/1", |
||||
"title" : "Q3 2015" |
||||
} |
||||
}, |
||||
"id" : 1, |
||||
"subject" : "Q3 2015" |
||||
}, |
||||
{ |
||||
"_type" : "Budget", |
||||
"_links" : { |
||||
"self" : { |
||||
"href" : "/api/v3/budgets/2", |
||||
"title" : "Q4 2015" |
||||
} |
||||
}, |
||||
"id" : 2, |
||||
"subject" : "Q4 2015" |
||||
} |
||||
] |
||||
} |
||||
} |
||||
|
||||
|
||||
## view Budgets of a Project [GET] |
||||
|
||||
+ Parameters |
||||
+ id (required, integer, `1`) ... Project id |
||||
|
||||
+ Response 200 (application/hal+json) |
||||
|
||||
[Budgets by Project][] |
||||
|
||||
+ Response 403 (application/hal+json) |
||||
|
||||
Returned if the client does not have sufficient permissions to see the budgets of the given |
||||
project. |
||||
|
||||
**Required permission:** view work packages **or** view budgets |
||||
|
||||
*Note that you will only receive this error, if you are at least allowed to see the corresponding project.* |
||||
|
||||
+ Body |
||||
|
||||
{ |
||||
"_type": "Error", |
||||
"errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission", |
||||
"message": "You are not allowed to see the budgets of this project." |
||||
} |
||||
|
||||
+ Response 404 (application/hal+json) |
||||
|
||||
Returned if either: |
||||
|
||||
* the project does not exist |
||||
* the client does not have sufficient permissions to see the project |
||||
* the costs module is not enabled on the given project |
||||
|
||||
**Required permission:** view project |
||||
|
||||
*Note: A client without sufficient permissions shall not be able to test for the existence of a project. |
||||
That's why a 404 is returned here, even if a 403 might be more appropriate.* |
||||
|
||||
+ Body |
||||
|
||||
{ |
||||
"_type": "Error", |
||||
"errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", |
||||
"message": "The specified project does not exist." |
||||
} |
||||
|
||||
|
@ -1,72 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
(function($) { |
||||
$(function() { |
||||
/* |
||||
* See /app/views/timelog/_date_range.html.erb |
||||
*/ |
||||
if ($('#date-range').length < 1) { |
||||
return; |
||||
} |
||||
var intervalInputs = $('#to, #from'), |
||||
// select
|
||||
period = $('#period'), |
||||
// radio buttons
|
||||
periodOptionList = $('#period_type_list'), |
||||
periodOptionInterval = $('#period_type_interval'); |
||||
|
||||
var disableInputFields = function(radioButton) { |
||||
return function () { |
||||
if (radioButton == periodOptionList) { |
||||
jQuery('#period').attr("tabindex", -1); |
||||
jQuery('#from').removeAttr("tabindex"); |
||||
jQuery('#to').removeAttr("tabindex"); |
||||
} |
||||
else { |
||||
jQuery('#from').attr("tabindex", -1); |
||||
jQuery('#to').attr("tabindex", -1); |
||||
jQuery('#period').removeAttr("tabindex"); |
||||
} |
||||
}; |
||||
}; |
||||
|
||||
jQuery(document).ready(function() { |
||||
if (periodOptionInterval.is(':checked')) { |
||||
jQuery('#period').attr("tabindex", -1); |
||||
} |
||||
else { |
||||
jQuery('#from').attr("tabindex", -1); |
||||
jQuery('#to').attr("tabindex", -1); |
||||
} |
||||
}); |
||||
|
||||
periodOptionList.on('change', disableInputFields(periodOptionInterval)); |
||||
periodOptionInterval.on('change', disableInputFields(periodOptionList)); |
||||
|
||||
}); |
||||
}(jQuery)); |
@ -0,0 +1,3 @@ |
||||
source 'https://rubygems.org' |
||||
|
||||
gemspec |
@ -0,0 +1,264 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class BudgetsController < ApplicationController |
||||
before_action :find_budget, only: %i[show edit update copy] |
||||
before_action :find_budgets, only: :destroy |
||||
before_action :find_project, only: %i[new create update_material_budget_item update_labor_budget_item] |
||||
before_action :find_optional_project, only: :index |
||||
|
||||
before_action :authorize_global, only: :index |
||||
before_action :authorize, except: [ |
||||
# unrestricted actions |
||||
:index, |
||||
:update_material_budget_item, |
||||
:update_labor_budget_item |
||||
] |
||||
|
||||
helper :sort |
||||
include SortHelper |
||||
helper :projects |
||||
include ProjectsHelper |
||||
helper :attachments |
||||
include AttachmentsHelper |
||||
helper :costlog |
||||
include CostlogHelper |
||||
helper :budgets |
||||
include BudgetsHelper |
||||
include PaginationHelper |
||||
|
||||
def index |
||||
sort_init 'id', 'desc' |
||||
sort_update default_budget_sort |
||||
|
||||
@budgets = visible_sorted_budgets |
||||
|
||||
respond_to do |format| |
||||
format.html do |
||||
render action: 'index', layout: !request.xhr? |
||||
end |
||||
format.csv { send_data(budgets_to_csv(@budgets), type: 'text/csv; header=present', filename: 'export.csv') } |
||||
end |
||||
end |
||||
|
||||
def show |
||||
@edit_allowed = User.current.allowed_to?(:edit_budgets, @project) |
||||
respond_to do |format| |
||||
format.html { render action: 'show', layout: !request.xhr? } |
||||
end |
||||
end |
||||
|
||||
def new |
||||
@budget ||= Budget.new |
||||
@budget.project_id = @project.id |
||||
@budget.fixed_date ||= Date.today |
||||
|
||||
render layout: !request.xhr? |
||||
end |
||||
|
||||
def copy |
||||
source = Budget.find(params[:id].to_i) |
||||
@budget = Budget.new |
||||
|
||||
if source |
||||
@budget.copy_from(source) |
||||
end |
||||
|
||||
@budget.fixed_date ||= Date.today |
||||
|
||||
render action: :new, layout: !request.xhr? |
||||
end |
||||
|
||||
def create |
||||
@budget = Budget.new |
||||
@budget.project_id = @project.id |
||||
|
||||
# fixed_date must be set before material_budget_items and labor_budget_items |
||||
@budget.fixed_date = if params[:budget] && params[:budget][:fixed_date] |
||||
params[:budget].delete(:fixed_date) |
||||
else |
||||
Date.today |
||||
end |
||||
|
||||
@budget.attributes = permitted_params.budget |
||||
@budget.attach_files(permitted_params.attachments.to_h) |
||||
|
||||
if @budget.save |
||||
flash[:notice] = t(:notice_successful_create) |
||||
redirect_to(params[:continue] ? { action: 'new' } : { action: 'show', id: @budget }) |
||||
else |
||||
render action: 'new', layout: !request.xhr? |
||||
end |
||||
end |
||||
|
||||
def edit |
||||
@budget.attributes = permitted_params.budget if params[:budget] |
||||
end |
||||
|
||||
def update |
||||
# TODO: This was simply copied over from edit in order to have |
||||
# something as a starting point for separating the two |
||||
# Please go ahead and start removing code where necessary |
||||
|
||||
@budget.attributes = permitted_params.budget if params[:budget] |
||||
if params[:budget][:existing_material_budget_item_attributes].nil? |
||||
@budget.existing_material_budget_item_attributes = ({}) |
||||
end |
||||
if params[:budget][:existing_labor_budget_item_attributes].nil? |
||||
@budget.existing_labor_budget_item_attributes = ({}) |
||||
end |
||||
|
||||
@budget.attach_files(permitted_params.attachments.to_h) |
||||
|
||||
if @budget.save |
||||
flash[:notice] = t(:notice_successful_update) |
||||
redirect_to(params[:back_to] || { action: 'show', id: @budget }) |
||||
else |
||||
render action: 'edit' |
||||
end |
||||
rescue ActiveRecord::StaleObjectError |
||||
# Optimistic locking exception |
||||
flash.now[:error] = t(:notice_locking_conflict) |
||||
end |
||||
|
||||
def destroy |
||||
@budgets.each(&:destroy) |
||||
flash[:notice] = t(:notice_successful_delete) |
||||
redirect_to action: 'index', project_id: @project |
||||
end |
||||
|
||||
def update_material_budget_item |
||||
@element_id = params[:element_id] |
||||
|
||||
cost_type = CostType.where(id: params[:cost_type_id]).first |
||||
|
||||
if cost_type && params[:units].present? |
||||
volume = Rate.parse_number_string_to_number(params[:units]) |
||||
@costs = volume * cost_type.rate_at(params[:fixed_date]).rate rescue 0.0 |
||||
@unit = volume == 1.0 ? cost_type.unit : cost_type.unit_plural |
||||
else |
||||
@costs = 0.0 |
||||
@unit = cost_type.try(:unit_plural) || '' |
||||
end |
||||
|
||||
respond_to do |format| |
||||
format.json do |
||||
render json: render_item_as_json(@element_id, @costs, @unit, @project, :view_cost_rates) |
||||
end |
||||
end |
||||
end |
||||
|
||||
def update_labor_budget_item |
||||
@element_id = params[:element_id] |
||||
user = User.where(id: params[:user_id]).first |
||||
|
||||
if user && params[:hours] |
||||
hours = params[:hours].to_s.to_hours |
||||
@costs = hours * user.rate_at(params[:fixed_date], @project).rate rescue 0.0 |
||||
else |
||||
@costs = 0.0 |
||||
end |
||||
|
||||
respond_to do |format| |
||||
format.json do |
||||
render json: render_item_as_json(@element_id, @costs, @unit, @project, :view_hourly_rates) |
||||
end |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def find_budget |
||||
# This function comes directly from issues_controller.rb (Redmine 0.8.4) |
||||
@budget = Budget.includes(:project, :author).find_by(id: params[:id]) |
||||
@project = @budget.project if @budget |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
def find_budgets |
||||
# This function comes directly from issues_controller.rb (Redmine 0.8.4) |
||||
|
||||
@budgets = Budget.where(id: params[:id] || params[:ids]) |
||||
raise ActiveRecord::RecordNotFound if @budgets.empty? |
||||
|
||||
projects = @budgets.map(&:project).compact.uniq |
||||
if projects.size == 1 |
||||
@project = projects.first |
||||
else |
||||
# TODO: let users bulk edit/move/destroy budgets from different projects |
||||
render_error 'Can not bulk edit/move/destroy cost objects from different projects' and return false |
||||
end |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
def find_project |
||||
@project = Project.find(params[:project_id]) |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
def find_optional_project |
||||
@project = Project.find(params[:project_id]) unless params[:project_id].blank? |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
def render_item_as_json(element_id, costs, unit, project, permission) |
||||
response = { |
||||
"#{element_id}_unit_name" => ActionController::Base.helpers.sanitize(unit), |
||||
"#{element_id}_currency" => Setting.plugin_costs['costs_currency'] |
||||
} |
||||
|
||||
if current_user.allowed_to?(permission, project) |
||||
response["#{element_id}_costs"] = number_to_currency(costs) |
||||
response["#{element_id}_cost_value"] = costs |
||||
end |
||||
|
||||
response |
||||
end |
||||
|
||||
def default_budget_sort |
||||
{ |
||||
'id' => "#{Budget.table_name}.id", |
||||
'subject' => "#{Budget.table_name}.subject", |
||||
'fixed_date' => "#{Budget.table_name}.fixed_date" |
||||
} |
||||
end |
||||
|
||||
def visible_sorted_budgets |
||||
Budget |
||||
.visible(current_user) |
||||
.order(sort_clause) |
||||
.includes(:author) |
||||
.where(project_id: @project.id) |
||||
.page(page_param) |
||||
.per_page(per_page_param) |
||||
end |
||||
end |
@ -0,0 +1,10 @@ |
||||
# encoding: UTF-8 |
||||
|
||||
Gem::Specification.new do |s| |
||||
s.name = "budgets" |
||||
s.version = '1.0.0' |
||||
s.authors = ["OpenProject"] |
||||
s.summary = "OpenProject Budgets." |
||||
|
||||
s.files = Dir["{app,config,db,lib}/**/*"] |
||||
end |
@ -0,0 +1,89 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
--- |
||||
en: |
||||
activerecord: |
||||
attributes: |
||||
budget: |
||||
author: "Author" |
||||
available: "Available" |
||||
budget: "Planned" |
||||
budget_ratio: "Spent (ratio)" |
||||
created_on: "Created on" |
||||
description: "Description" |
||||
fixed_date: "Fixed date" |
||||
spent: "Spent" |
||||
status: "Status" |
||||
subject: "Subject" |
||||
type: "Cost type" |
||||
updated_on: "Updated on" |
||||
work_package: |
||||
budget_subject: "Budget title" |
||||
budget: |
||||
labor_budget: "Planned labor costs" |
||||
material_budget: "Planned unit costs" |
||||
models: |
||||
budget: "Budget" |
||||
material_budget_item: "Unit" |
||||
attributes: |
||||
budget: "Planned costs" |
||||
budget: "Budget" |
||||
|
||||
button_add_budget_item: "Add planned costs" |
||||
button_add_budget: "Add budget" |
||||
button_add_cost_type: "Add cost type" |
||||
button_cancel_edit_budget: "Cancel editing budget" |
||||
button_cancel_edit_costs: "Cancel editing costs" |
||||
|
||||
caption_labor: "Labor" |
||||
caption_labor_costs: "Actual labor costs" |
||||
caption_material_costs: "Actual unit costs" |
||||
budgets_title: "Budgets" |
||||
|
||||
events: |
||||
budget: "Budget edited" |
||||
|
||||
help_click_to_edit: "Click here to edit." |
||||
help_currency_format: "Format of displayed currency values. %n is replaced with the currency value, %u ist replaced with the currency unit." |
||||
help_override_rate: "Enter a value here to override the default rate." |
||||
|
||||
label_budget: "Budget" |
||||
label_budget_new: "New budget" |
||||
label_budget_plural: "Budgets" |
||||
label_cost_type_specific: "Budget #%{id}: %{name}" |
||||
label_deliverable: "Budget" |
||||
label_view_all_budgets: "View all budgets" |
||||
label_yes: "Yes" |
||||
|
||||
notice_budget_conflict: "WorkPackages must be of the same project." |
||||
notice_no_budgets_available: "No budgets available." |
||||
|
||||
permission_edit_budgets: "Edit budgets" |
||||
permission_view_budgets: "View budgets" |
||||
project_module_budgets: "Budgets" |
@ -0,0 +1,150 @@ |
||||
class RenameCostObjectToBudget < ActiveRecord::Migration[6.0] |
||||
def up |
||||
remove_column :cost_objects, :type |
||||
rename_table :cost_objects, :budgets |
||||
|
||||
execute <<~SQL |
||||
UPDATE types |
||||
SET attribute_groups = REGEXP_REPLACE(attribute_groups, ' cost_object', ' budget') |
||||
WHERE attribute_groups LIKE '%cost_object%' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE role_permissions |
||||
SET permission = 'view_budgets' |
||||
WHERE permission = 'view_cost_objects' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE role_permissions |
||||
SET permission = 'edit_budgets' |
||||
WHERE permission = 'edit_cost_objects' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE journals |
||||
SET activity_type = 'budgets' |
||||
WHERE activity_type = 'cost_objects' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE attachments |
||||
SET container_type = 'Budget' |
||||
WHERE container_type = 'CostObject' |
||||
SQL |
||||
|
||||
rename_in_queries('cost_object', 'budget') |
||||
rename_in_cost_queries('CostObject', 'Budget') |
||||
|
||||
rename_column :budgets, :created_on, :created_at |
||||
rename_column :budgets, :updated_on, :updated_at |
||||
|
||||
rename_table :cost_object_journals, :budget_journals |
||||
remove_column :budget_journals, :created_on |
||||
|
||||
rename_column :work_packages, :cost_object_id, :budget_id |
||||
rename_column :work_package_journals, :cost_object_id, :budget_id |
||||
rename_column :labor_budget_items, :cost_object_id, :budget_id |
||||
rename_column :labor_budget_items, :budget, :amount |
||||
rename_column :material_budget_items, :cost_object_id, :budget_id |
||||
rename_column :material_budget_items, :budget, :amount |
||||
end |
||||
|
||||
def down |
||||
rename_column :material_budget_items, :amount, :budget |
||||
rename_column :material_budget_items, :budget_id, :cost_object_id |
||||
rename_column :labor_budget_items, :amount, :budget |
||||
rename_column :labor_budget_items, :budget_id, :cost_object_id |
||||
rename_column :work_packages, :budget_id, :cost_object_id |
||||
rename_column :work_package_journals, :budget_id, :cost_object_id |
||||
|
||||
add_column :budget_journals, :created_on, :timestamp |
||||
rename_table :budget_journals, :cost_object_journals |
||||
|
||||
rename_column :budgets, :created_at, :created_on |
||||
rename_column :budgets, :updated_at, :updated_on |
||||
|
||||
rename_in_queries('budget', 'cost_object') |
||||
rename_in_cost_queries('Budget', 'CostObject') |
||||
|
||||
execute <<~SQL |
||||
UPDATE attachments |
||||
SET container_type = 'CostObject' |
||||
WHERE container_type = 'Budget' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE journals |
||||
SET activity_type = 'cost_objects' |
||||
WHERE activity_type = 'budgets' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE role_permissions |
||||
SET permission = 'view_cost_objects' |
||||
WHERE permission = 'view_budgets' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE role_permissions |
||||
SET permission = 'edit_cost_objects' |
||||
WHERE permission = 'edit_budgets' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE types |
||||
SET attribute_groups = REGEXP_REPLACE(attribute_groups, ' budget', ' cost_object') |
||||
WHERE attribute_groups LIKE '%budget%' |
||||
SQL |
||||
|
||||
add_column :budgets, :type, :string |
||||
|
||||
execute <<~SQL |
||||
UPDATE budgets SET type = 'VariableCostObject' |
||||
SQL |
||||
|
||||
change_column :budgets, :type, :string, null: false |
||||
|
||||
rename_table :budgets, :cost_objects |
||||
end |
||||
|
||||
def rename_in_queries(old, new) |
||||
execute <<~SQL |
||||
UPDATE queries |
||||
SET filters = REGEXP_REPLACE(filters, '#{old}_id:', '#{new}_id:') |
||||
WHERE filters LIKE '%#{old}_id%' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE queries |
||||
SET sort_criteria = REGEXP_REPLACE(sort_criteria, '#{old}', '#{new}') |
||||
WHERE sort_criteria LIKE '%#{old}%' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE queries |
||||
SET column_names = REGEXP_REPLACE(column_names, '#{old}', '#{new}') |
||||
WHERE column_names LIKE '%#{old}%' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE queries |
||||
SET group_by = REGEXP_REPLACE(group_by, '#{old}', '#{new}') |
||||
WHERE group_by LIKE '%#{old}%' |
||||
SQL |
||||
|
||||
execute <<~SQL |
||||
UPDATE queries |
||||
SET timeline_labels = REGEXP_REPLACE(timeline_labels, '#{old.camelize(:lower)}', '#{new.camelize(:lower)}') |
||||
WHERE timeline_labels LIKE '%#{old.camelize(:lower)}%' |
||||
SQL |
||||
end |
||||
|
||||
def rename_in_cost_queries(old, new) |
||||
execute <<~SQL |
||||
UPDATE cost_queries |
||||
SET serialized = REGEXP_REPLACE(serialized, '#{old}', '#{new}') |
||||
WHERE serialized LIKE '%#{old}%' |
||||
SQL |
||||
end |
||||
end |
@ -0,0 +1,122 @@ |
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {Injectable} from "@angular/core"; |
||||
import {HttpClient} from '@angular/common/http'; |
||||
import {HalResourceNotificationService} from "core-app/modules/hal/services/hal-resource-notification.service"; |
||||
|
||||
@Injectable() |
||||
export class CostBudgetSubformAugmentService { |
||||
|
||||
constructor(private halNotification:HalResourceNotificationService, |
||||
private http:HttpClient) { |
||||
} |
||||
|
||||
listen() { |
||||
jQuery('costs-budget-subform').each((i, match) => { |
||||
let el = jQuery(match); |
||||
|
||||
const container = el.find('.budget-item-container'); |
||||
const templateEl = el.find('.budget-row-template'); |
||||
templateEl.detach(); |
||||
const template = templateEl[0].outerHTML; |
||||
let rowIndex = parseInt(el.attr('item-count') as string); |
||||
|
||||
// Refresh row on changes
|
||||
el.on('change', '.budget-item-value', (evt) => { |
||||
let row = jQuery(evt.target).closest('.cost_entry'); |
||||
this.refreshRow(el, row.attr('id') as string); |
||||
}); |
||||
|
||||
el.on('click', '.delete-budget-item', (evt) => { |
||||
evt.preventDefault(); |
||||
jQuery(evt.target).closest('.cost_entry').remove(); |
||||
return false; |
||||
}); |
||||
|
||||
// Add new row handler
|
||||
el.find('.budget-add-row').click((evt) => { |
||||
evt.preventDefault(); |
||||
let row = jQuery(template.replace(/INDEX/g, rowIndex.toString())); |
||||
row.show(); |
||||
row.removeClass('budget-row-template'); |
||||
container.append(row); |
||||
rowIndex += 1; |
||||
return false; |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Refreshes the given row after updating values |
||||
*/ |
||||
public refreshRow(el:JQuery, row_identifier:string) { |
||||
let row = el.find('#' + row_identifier); |
||||
let request = this.buildRefreshRequest(row, row_identifier); |
||||
|
||||
this.http |
||||
.post( |
||||
el.attr('update-url')!, |
||||
request, |
||||
{ |
||||
headers: { 'Accept': 'application/json' }, |
||||
withCredentials: true |
||||
}) |
||||
.subscribe( |
||||
(data:any) => { |
||||
_.each(data, (val:string, selector:string) => { |
||||
let element = document.getElementById(selector) as HTMLElement|HTMLInputElement|undefined; |
||||
if (element instanceof HTMLInputElement) { |
||||
element.value = val; |
||||
} else if (element) { |
||||
element.textContent = val; |
||||
} |
||||
}); |
||||
}, |
||||
(error:any) => this.halNotification.handleRawError(error) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Returns the params for the update request |
||||
*/ |
||||
private buildRefreshRequest(row:JQuery, row_identifier:string) { |
||||
let request:any = { |
||||
element_id: row_identifier, |
||||
fixed_date: jQuery('#budget_fixed_date').val() |
||||
}; |
||||
|
||||
// Augment common values with specific values for this type
|
||||
row.find('.budget-item-value').each((_i:number, el:any) => { |
||||
let field = jQuery(el); |
||||
request[field.data('requestKey')] = field.val() || '0'; |
||||
}); |
||||
|
||||
return request; |
||||
} |
||||
} |
@ -0,0 +1,67 @@ |
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {Injectable} from "@angular/core"; |
||||
|
||||
@Injectable() |
||||
export class CostSubformAugmentService { |
||||
|
||||
constructor() { |
||||
jQuery('costs-subform').each((i, match) => { |
||||
let el = jQuery(match); |
||||
|
||||
const container = el.find('.subform-container'); |
||||
|
||||
const templateEl = el.find('.subform-row-template'); |
||||
templateEl.detach(); |
||||
const template = templateEl[0].outerHTML; |
||||
let rowIndex = parseInt(el.attr('item-count')!); |
||||
|
||||
el.on('click', '.delete-row-button,.delete-budget-item', (evt:any) => { |
||||
jQuery(evt.target).closest('.subform-row').remove(); |
||||
return false; |
||||
}); |
||||
|
||||
// Add new row handler
|
||||
el.find('.add-row-button,.wp-inline-create--add-link').click((evt:any) => { |
||||
evt.preventDefault(); |
||||
let row = jQuery(template.replace(/INDEX/g, rowIndex.toString())); |
||||
row.show(); |
||||
row.removeClass('subform-row-template'); |
||||
container.append(row); |
||||
rowIndex += 1; |
||||
|
||||
container.find('.subform-row:last-child input:first').focus(); |
||||
|
||||
return false; |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,101 @@ |
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
export class PlannedCostsFormAugment { |
||||
|
||||
public obj:JQuery; |
||||
public objId:string; |
||||
public objName:string; |
||||
|
||||
static listen() { |
||||
jQuery(document).on('click', '.costs--edit-planned-costs-btn', (evt) => { |
||||
const form = jQuery(evt.target as any).closest('cost-unit-subform') as JQuery; |
||||
new PlannedCostsFormAugment(form); |
||||
}); |
||||
} |
||||
|
||||
constructor(public $element:JQuery) { |
||||
this.objId = this.$element.attr('obj-id')!; |
||||
this.objName = this.$element.attr('obj-name')!; |
||||
this.obj = jQuery(`#${this.objId}_costs`) as any; |
||||
|
||||
this.makeEditable(); |
||||
} |
||||
|
||||
public makeEditable() { |
||||
this.edit_and_focus(); |
||||
} |
||||
|
||||
private edit_and_focus() { |
||||
this.edit(); |
||||
|
||||
jQuery('#' + this.objId + '_costs_edit').trigger('focus'); |
||||
jQuery('#' + this.objId + '_costs_edit').trigger('select'); |
||||
} |
||||
|
||||
private getCurrency() { |
||||
return jQuery('#' + this.objId + '_currency').val(); |
||||
} |
||||
|
||||
private getValue() { |
||||
return jQuery('#' + this.objId + '_cost_value').val(); |
||||
} |
||||
|
||||
private edit() { |
||||
this.obj.hide(); |
||||
|
||||
let id = this.obj[0].id; |
||||
let currency = this.getCurrency(); |
||||
let value = this.getValue(); |
||||
let name = this.objName; |
||||
|
||||
let template = ` |
||||
<section class="form--section" id="${id}_section"> |
||||
<div class="form--field"> |
||||
<div class="form--field-container"> |
||||
<div id="${id}_cancel" class="form--field-affix -transparent icon icon-close"></div> |
||||
<div id="${id}_editor" class="form--text-field-container"> |
||||
<input id="${id}_edit" class="form--text-field" name="${name}" value="${value}" class="currency" type="text" /> |
||||
</div> |
||||
<div class="form--field-affix" id="${id}_affix">${currency}</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
`;
|
||||
|
||||
jQuery(template).insertAfter(this.obj); |
||||
|
||||
let that = this; |
||||
jQuery('#' + id + '_cancel').on('click', function () { |
||||
jQuery('#' + id + '_section').remove(); |
||||
that.obj.show(); |
||||
return false; |
||||
}); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,70 @@ |
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2020 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,
|
||||
// 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 docs/COPYRIGHT.rdoc for more details.
|
||||
|
||||
import {Injector, NgModule} from '@angular/core'; |
||||
import {OpenProjectPluginContext} from 'core-app/modules/plugins/plugin-context'; |
||||
import {BudgetResource} from './hal/resources/budget-resource'; |
||||
import {multiInput} from 'reactivestates'; |
||||
import {CostSubformAugmentService} from "./augment/cost-subform.augment.service"; |
||||
import {PlannedCostsFormAugment} from "core-app/modules/plugins/linked/budgets/augment/planned-costs-form"; |
||||
import {CostBudgetSubformAugmentService} from "core-app/modules/plugins/linked/budgets/augment/cost-budget-subform.augment.service"; |
||||
|
||||
export function initializeCostsPlugin(injector:Injector) { |
||||
window.OpenProject.getPluginContext().then((pluginContext:OpenProjectPluginContext) => { |
||||
pluginContext.services.editField.extendFieldType('select', ['Budget']); |
||||
|
||||
let displayFieldService = pluginContext.services.displayField; |
||||
displayFieldService.extendFieldType('resource', ['Budget']); |
||||
|
||||
let halResourceService = pluginContext.services.halResource; |
||||
halResourceService.registerResource('Budget', {cls: BudgetResource}); |
||||
|
||||
let states = pluginContext.services.states; |
||||
states.add('budgets', multiInput<BudgetResource>()); |
||||
|
||||
// Augment previous cost-subforms
|
||||
new CostSubformAugmentService(); |
||||
PlannedCostsFormAugment.listen(); |
||||
|
||||
const budgetSubform = injector.get(CostBudgetSubformAugmentService); |
||||
budgetSubform.listen(); |
||||
}); |
||||
} |
||||
|
||||
|
||||
@NgModule({ |
||||
providers: [ |
||||
CostBudgetSubformAugmentService, |
||||
], |
||||
}) |
||||
export class PluginModule { |
||||
constructor(injector:Injector) { |
||||
initializeCostsPlugin(injector); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
@ -0,0 +1,4 @@ |
||||
require "budgets/engine" |
||||
|
||||
module Budgets |
||||
end |
@ -0,0 +1,67 @@ |
||||
module Budgets |
||||
class Engine < ::Rails::Engine |
||||
include OpenProject::Plugins::ActsAsOpEngine |
||||
|
||||
register 'budgets', |
||||
author_url: 'https://www.openproject.com', |
||||
bundled: true, |
||||
name: 'Budgets' do |
||||
project_module :budgets do |
||||
permission :view_budgets, { budgets: %i[index show] } |
||||
permission :edit_budgets, { budgets: %i[index show edit update destroy new create copy] } |
||||
end |
||||
|
||||
menu :project_menu, |
||||
:budgets, |
||||
{ controller: '/budgets', action: 'index' }, |
||||
param: :project_id, |
||||
if: ->(project) { project.module_enabled?(:budgets) }, |
||||
after: :costs, |
||||
caption: :budgets_title, |
||||
icon: 'icon2 icon-budget' |
||||
end |
||||
|
||||
activity_provider :budgets, class_name: 'Activities::BudgetActivityProvider', default: false |
||||
|
||||
add_api_path :budget do |id| |
||||
"#{root}/budgets/#{id}" |
||||
end |
||||
|
||||
add_api_path :budgets_by_project do |project_id| |
||||
"#{project(project_id)}/budgets" |
||||
end |
||||
|
||||
add_api_path :attachments_by_budget do |id| |
||||
"#{budget(id)}/attachments" |
||||
end |
||||
|
||||
add_api_endpoint 'API::V3::Root' do |
||||
mount ::API::V3::Budgets::BudgetsAPI |
||||
end |
||||
|
||||
add_api_endpoint 'API::V3::Projects::ProjectsAPI', :id do |
||||
mount ::API::V3::Budgets::BudgetsByProjectAPI |
||||
end |
||||
|
||||
initializer 'budgets.register_latest_project_activity' do |
||||
Project.register_latest_project_activity on: 'Budget', |
||||
attribute: :updated_at |
||||
end |
||||
|
||||
initializer 'budgets.register_hooks' do |
||||
# TODO: avoid hooks as this is part of the core now |
||||
require 'budgets/hooks/work_package_hook' |
||||
end |
||||
|
||||
config.to_prepare do |
||||
# Add to the budget to the costs group |
||||
::Type.add_default_mapping(:costs, :budget) |
||||
|
||||
::Type.add_constraint :budget, ->(_type, project: nil) { |
||||
project.nil? || project.module_enabled?(:budgets) |
||||
} |
||||
|
||||
Queries::Register.filter Query, Queries::WorkPackages::Filter::BudgetFilter |
||||
end |
||||
end |
||||
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue