mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Remove budget relations and state
This commit is contained in:
@@ -1,61 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Budgets
|
||||
class ChildBudgetsRowComponent < ::OpPrimer::BorderBoxRowComponent
|
||||
def budget_relation
|
||||
model
|
||||
end
|
||||
|
||||
def budget
|
||||
budget_relation.child_budget
|
||||
end
|
||||
|
||||
def id
|
||||
link_to "##{budget.id}", budget_path(budget)
|
||||
end
|
||||
|
||||
def subject
|
||||
link_to budget.subject, budget_path(budget)
|
||||
end
|
||||
|
||||
def project
|
||||
link_to budget.project.name, project_path(budget.project)
|
||||
end
|
||||
|
||||
def relation_type
|
||||
I18n.t(budget_relation.relation_type, scope: %i[activerecord attributes budget_relation relation_types])
|
||||
end
|
||||
|
||||
def budget_amount
|
||||
number_to_currency(budget.budget)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,70 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Budgets
|
||||
class ChildBudgetsTableComponent < ::OpPrimer::BorderBoxTableComponent
|
||||
columns :id, :subject, :project, :relation_type, :budget_amount
|
||||
main_column :subject, :proejct, :relation_type, :budget_amount
|
||||
|
||||
def sortable?
|
||||
false
|
||||
end
|
||||
|
||||
def paginated?
|
||||
false
|
||||
end
|
||||
|
||||
def has_actions?
|
||||
false
|
||||
end
|
||||
|
||||
def empty_row_message
|
||||
I18n.t :no_results_title_text
|
||||
end
|
||||
|
||||
def row_class
|
||||
Budgets::ChildBudgetsRowComponent
|
||||
end
|
||||
|
||||
def mobile_title
|
||||
I18n.t(:label_budget_child_budgets)
|
||||
end
|
||||
|
||||
def headers
|
||||
[
|
||||
[:id, { caption: Budget.human_attribute_name(:id) }],
|
||||
[:subject, { caption: Budget.human_attribute_name(:subject) }],
|
||||
[:project, { caption: Budget.human_attribute_name(:project) }],
|
||||
[:relation_type, { caption: BudgetRelation.human_attribute_name(:relation_type) }],
|
||||
[:budget_amount, { caption: Budget.human_attribute_name(:budget) }]
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,59 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
module Budgets
|
||||
class ParentPageHeaderComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include ApplicationHelper
|
||||
|
||||
def initialize(budget:, project:)
|
||||
super
|
||||
|
||||
@budget = budget
|
||||
@project = project
|
||||
end
|
||||
|
||||
def breadcrumb_items
|
||||
[
|
||||
{ href: project_overview_path(@project.id), text: @project.name },
|
||||
{ href: projects_budgets_path(@project.id), text: t(:label_budget_plural) },
|
||||
{ href: budget_path(@budget.id), text: t(:label_budget_id, id: @budget.id) },
|
||||
t(:button_manage_parent)
|
||||
]
|
||||
end
|
||||
|
||||
def call
|
||||
render(Primer::OpenProject::PageHeader.new) do |header|
|
||||
header.with_title { t(:button_manage_parent) }
|
||||
header.with_breadcrumbs(breadcrumb_items)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,21 +18,6 @@
|
||||
t(:button_update)
|
||||
end
|
||||
end
|
||||
if authorize_for(:budgets, :parent) && @budget.project.parent_id.present?
|
||||
header.with_action_button(
|
||||
tag: :a,
|
||||
mobile_icon: "git-pull-request",
|
||||
mobile_label: t(:button_manage_parent),
|
||||
size: :medium,
|
||||
href: url_for({ controller: "budgets", action: "parent", id: @budget }),
|
||||
aria: { label: t(:button_manage_parent) },
|
||||
data: { test_selector: "budget-parent-button" },
|
||||
title: t(:button_manage_parent)
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: "git-pull-request")
|
||||
t(:button_manage_parent)
|
||||
end
|
||||
end
|
||||
if authorize_for(:budgets, :copy)
|
||||
header.with_action_button(
|
||||
tag: :a,
|
||||
|
||||
@@ -38,7 +38,6 @@ module Budgets
|
||||
attribute :description
|
||||
attribute :fixed_date
|
||||
attribute :project
|
||||
attribute :state
|
||||
attribute :base_amount
|
||||
attribute :new_material_budget_item_attributes,
|
||||
readable: false
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
class BudgetsController < ApplicationController
|
||||
include AttachableServiceCall
|
||||
|
||||
before_action :find_budget, only: %i[show edit update copy destroy_info parent update_parent destroy_parent]
|
||||
before_action :find_budget, only: %i[show edit update copy destroy_info]
|
||||
before_action :find_budgets, only: :destroy
|
||||
before_action :check_and_update_belonging_work_packages, only: :destroy
|
||||
before_action :find_project_by_project_id, only: %i[new create update_material_budget_item update_labor_budget_item]
|
||||
@@ -47,12 +47,16 @@ class BudgetsController < ApplicationController
|
||||
|
||||
helper :sort
|
||||
include SortHelper
|
||||
|
||||
helper :projects
|
||||
include ProjectsHelper
|
||||
|
||||
helper :attachments
|
||||
include AttachmentsHelper
|
||||
|
||||
helper :costlog
|
||||
include CostlogHelper
|
||||
|
||||
helper :budgets
|
||||
include BudgetsHelper
|
||||
include PaginationHelper
|
||||
@@ -74,7 +78,6 @@ class BudgetsController < ApplicationController
|
||||
|
||||
def show
|
||||
@edit_allowed = User.current.allowed_in_project?(:edit_budgets, @project)
|
||||
@child_budget_relations = @budget.child_budget_relations.includes(child_budget: :project)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render action: "show", layout: !request.xhr? }
|
||||
@@ -195,35 +198,6 @@ class BudgetsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def parent
|
||||
@parent_projects = @project.ancestors
|
||||
@budget_candidates = Budget.visible(User.current).where(project_id: @parent_projects)
|
||||
|
||||
@parent_budget_relation = @budget.parent_budget_relation || BudgetRelation.new
|
||||
end
|
||||
|
||||
def update_parent
|
||||
parent_relation = @budget.parent_budget_relation || @budget.build_parent_budget_relation
|
||||
|
||||
if parent_relation.update(budget_relation_params)
|
||||
flash[:notice] = t(:notice_successful_update)
|
||||
redirect_to budget_path(@budget)
|
||||
else
|
||||
flash[:error] = t(:notice_failed_update)
|
||||
render action: :parent, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_parent
|
||||
if @budget.parent_budget_relation.destroy
|
||||
flash[:notice] = t(:notice_relation_destroyed)
|
||||
redirect_to budget_path(@budget), method: :get
|
||||
else
|
||||
flash[:error] = t(:notice_failed_delete)
|
||||
render action: :parent, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_budget
|
||||
@@ -265,10 +239,6 @@ class BudgetsController < ApplicationController
|
||||
response
|
||||
end
|
||||
|
||||
def budget_relation_params
|
||||
params.expect(budget_relation: %i[parent_budget_id relation_type])
|
||||
end
|
||||
|
||||
def default_budget_sort
|
||||
{
|
||||
"id" => "#{Budget.table_name}.id",
|
||||
|
||||
@@ -46,18 +46,6 @@ class Budget < ApplicationRecord
|
||||
has_many :cost_entries, through: :work_packages
|
||||
has_many :time_entries, through: :work_packages
|
||||
|
||||
has_one :parent_budget_relation, class_name: "BudgetRelation",
|
||||
foreign_key: "child_budget_id",
|
||||
dependent: :destroy,
|
||||
inverse_of: :child_budget
|
||||
|
||||
has_one :parent_budget, through: :parent_budget_relation, source: :parent_budget
|
||||
|
||||
has_many :child_budget_relations, class_name: "BudgetRelation",
|
||||
foreign_key: "parent_budget_id",
|
||||
dependent: :destroy,
|
||||
inverse_of: :parent_budget
|
||||
|
||||
include ActiveModel::ForbiddenAttributesProtection
|
||||
|
||||
acts_as_attachable
|
||||
@@ -68,16 +56,7 @@ class Budget < ApplicationRecord
|
||||
url: Proc.new { |o| { controller: "budgets", action: "show", id: o.id } }
|
||||
|
||||
validates :subject, :project, :author, :fixed_date, presence: true
|
||||
validates :subject, length: { maximum: 255 }
|
||||
validates :subject, length: { minimum: 1 }
|
||||
|
||||
enum :state, {
|
||||
planned: "planned",
|
||||
draft: "draft",
|
||||
submitted: "submitted",
|
||||
approved: "approved",
|
||||
rejected: "rejected"
|
||||
}, validate: { allow_nil: true }
|
||||
validates :subject, length: { minimum: 1, maximum: 255 }
|
||||
|
||||
class << self
|
||||
def visible(user)
|
||||
@@ -99,8 +78,10 @@ class Budget < ApplicationRecord
|
||||
protected
|
||||
|
||||
def copy_attributes(source)
|
||||
source.attributes.slice("project_id", "subject", "description", "fixed_date", "state",
|
||||
"base_amount").merge("author" => User.current)
|
||||
source
|
||||
.attributes
|
||||
.slice("project_id", "subject", "description", "fixed_date", "base_amount")
|
||||
.merge("author" => User.current)
|
||||
end
|
||||
|
||||
def copy_budget_items(source, sink, items:)
|
||||
@@ -119,34 +100,7 @@ class Budget < ApplicationRecord
|
||||
end
|
||||
|
||||
def budget
|
||||
base_amount + material_budget + labor_budget + budget_added_by_children
|
||||
end
|
||||
|
||||
def budget_added_by_children
|
||||
# TODO: Efficient with query
|
||||
@budget_added_by_children ||= child_budget_relations.add.includes(:child_budget).sum do |rel|
|
||||
rel.child_budget.budget
|
||||
end
|
||||
end
|
||||
|
||||
def allocated_to_children
|
||||
# TODO: Efficient with query
|
||||
@allocated_to_children ||= child_budget_relations.includes(:child_budget).sum { |rel| rel.child_budget.budget }
|
||||
end
|
||||
|
||||
def allocated_unused
|
||||
allocated_to_children - spent_on_children
|
||||
end
|
||||
|
||||
def spent_with_children
|
||||
spent + spent_on_children
|
||||
end
|
||||
|
||||
def spent_on_children
|
||||
# TODO: Efficient with query
|
||||
@spent_on_children ||= child_budget_relations.includes(:child_budget).sum do |rel|
|
||||
rel.child_budget.spent_with_children
|
||||
end
|
||||
base_amount + material_budget + labor_budget
|
||||
end
|
||||
|
||||
def type_label
|
||||
@@ -161,9 +115,7 @@ class Budget < ApplicationRecord
|
||||
def budget_ratio
|
||||
return 0.0 if budget.nil? || budget == 0.0
|
||||
|
||||
gone = spent + allocated_to_children
|
||||
|
||||
((gone / budget) * 100).round
|
||||
((spent / budget) * 100).round
|
||||
end
|
||||
|
||||
def css_classes
|
||||
@@ -215,7 +167,7 @@ class Budget < ApplicationRecord
|
||||
end
|
||||
|
||||
def available
|
||||
budget - spent - allocated_to_children
|
||||
budget - spent
|
||||
end
|
||||
|
||||
def new_material_budget_item_attributes=(material_budget_item_attributes)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class BudgetRelation < ApplicationRecord
|
||||
belongs_to :parent_budget, class_name: "Budget"
|
||||
belongs_to :child_budget, class_name: "Budget"
|
||||
|
||||
enum :relation_type, { add: "add", subtract: "subtract" }, default: :subtract
|
||||
|
||||
validates :child_budget, uniqueness: true
|
||||
validates :child_budget, :parent_budget, :relation_type, presence: true
|
||||
end
|
||||
@@ -1,39 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
class Queries::Projects::Selects::BudgetAllocated < Queries::Selects::Base
|
||||
def self.key
|
||||
:budget_allocated
|
||||
end
|
||||
|
||||
def caption
|
||||
I18n.t(:label_budget_allocated)
|
||||
end
|
||||
end
|
||||
@@ -50,12 +50,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
value: unitless_currency_number(@budget.base_amount) %>
|
||||
</div>
|
||||
|
||||
<div class="form--field">
|
||||
<%= f.select :state,
|
||||
Budget.states.keys.map { |state| [I18n.t(state, scope: %i[activerecord attributes budget states]), state] },
|
||||
{ include_blank: true } %>
|
||||
</div>
|
||||
|
||||
<%= render partial: "budgets/subform/material_budget_subform" %>
|
||||
<%= render partial: "budgets/subform/labor_budget_subform" %>
|
||||
|
||||
|
||||
@@ -39,13 +39,11 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<%= sort_header_tag("id", caption: "#", default_order: "desc") %>
|
||||
<%= sort_header_tag("subject", caption: Budget.human_attribute_name(:subject)) %>
|
||||
<%= sort_header_tag("state", caption: Budget.human_attribute_name(:state)) %>
|
||||
<th class="currency">
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
@@ -93,11 +91,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<tr id="budget-<%= budget.id %>" class="<%= budget.css_classes %>">
|
||||
<td><%= link_to budget.id, budget_path(budget.id) %></td>
|
||||
<%= content_tag(:td, link_to(h(budget.subject), budget_path(budget.id)), class: "subject") %>
|
||||
<td>
|
||||
<% if budget.state.present? %>
|
||||
<%= I18n.t(budget.state, scope: %i[activerecord attributes budget states]) %>
|
||||
<% else %> - <% end %>
|
||||
</td>
|
||||
<%= content_tag(:td, number_to_currency(budget.budget, precision: 0), class: "currency") %>
|
||||
<%= content_tag(:td, number_to_currency(budget.spent, precision: 0), class: "currency") %>
|
||||
<%= content_tag(:td, number_to_currency(budget.available, precision: 0), class: "currency") %>
|
||||
@@ -112,7 +105,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<% end %>
|
||||
<% if budgets.length > 0 %>
|
||||
<tr>
|
||||
<td />
|
||||
<td />
|
||||
<td />
|
||||
<td class="currency"><strong><%= number_to_currency(total_budget, precision: 0) %></strong></td>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<% html_title "#{t(:button_manage_parent)} #{t(:label_budget_id, id: @budget.id)}: #{@budget.subject}" %>
|
||||
|
||||
<%= render Budgets::ParentPageHeaderComponent.new(budget: @budget, project: @project) %>
|
||||
<%-
|
||||
parent_budget_relation = @parent_budget_relation
|
||||
parent_candidates = @budget_candidates
|
||||
%>
|
||||
<%=
|
||||
primer_form_with(scope: :budget_relation, action: :update_parent, method: :post) do |budget_parent_form|
|
||||
render_inline_form(budget_parent_form) do |form|
|
||||
form.html_content do
|
||||
render(Primer::Beta::Subhead.new(hide_border: true)) do |subhead|
|
||||
subhead.with_heading(tag: :h3, size: :medium) { I18n.t("budgets.parent_relation.heading") }
|
||||
subhead.with_description { I18n.t("budgets.parent_relation.description") }
|
||||
end
|
||||
end
|
||||
|
||||
form.select_list(
|
||||
name: :parent_budget_id,
|
||||
label: BudgetRelation.human_attribute_name(:parent_budget),
|
||||
required: true,
|
||||
input_width: :large
|
||||
) do |select|
|
||||
parent_candidates.each do |budget|
|
||||
select.option(
|
||||
label: "#{budget.project.name}: ##{budget.id} - #{budget.subject}",
|
||||
value: budget.id,
|
||||
selected: budget.id == parent_budget_relation.parent_budget_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
form.radio_button_group(name: :relation_type) do |group|
|
||||
group.radio_button(
|
||||
value: :add,
|
||||
checked: parent_budget_relation.add?,
|
||||
label: I18n.t("budgets.relation_types.add.label"),
|
||||
caption: I18n.t("budgets.relation_types.add.helptext")
|
||||
)
|
||||
|
||||
group.radio_button(
|
||||
value: :subtract,
|
||||
checked: parent_budget_relation.subtract?,
|
||||
label: I18n.t("budgets.relation_types.subtract.label"),
|
||||
caption: I18n.t("budgets.relation_types.subtract.helptext")
|
||||
)
|
||||
end
|
||||
|
||||
form.submit(name: :submit, label: I18n.t(:button_set_parent))
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<% if @budget.parent_budget_relation.present? %>
|
||||
<%= render(Primer::Beta::Subhead.new(mt: 5, hide_border: true)) do |subhead|
|
||||
subhead.with_heading(tag: :h3, size: :medium) { I18n.t("budgets.delete_parent_relation.heading") }
|
||||
subhead.with_description { I18n.t("budgets.delete_parent_relation.description") }
|
||||
end %>
|
||||
|
||||
<%= render Primer::Beta::Button.new(
|
||||
tag: :a,
|
||||
scheme: :danger,
|
||||
title: I18n.t(:button_delete_link_to_parent),
|
||||
href: parent_budget_path(@budget),
|
||||
data: { "method" => :delete }
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: :trash)
|
||||
I18n.t(:button_delete_link_to_parent)
|
||||
end %>
|
||||
<% end %>
|
||||
@@ -47,21 +47,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
key: Budget.human_attribute_name(:budget_ratio),
|
||||
value: extended_progress_bar(@budget.budget_ratio, width: "80px", legend: @budget.budget_ratio)
|
||||
)
|
||||
component.with_attribute(
|
||||
key: Budget.human_attribute_name(:state),
|
||||
value: @budget.state.present? ? I18n.t(@budget.state, scope: %i[activerecord attributes budget states]) : "-"
|
||||
)
|
||||
component.with_attribute(
|
||||
key: Budget.human_attribute_name(:parent_budget),
|
||||
value: if @budget.parent_budget_relation.present?
|
||||
link_to(
|
||||
"#{@budget.parent_budget.project.name}: #{@budget.parent_budget.subject} (#{I18n.t(@budget.parent_budget_relation.relation_type, scope: %i[activerecord attributes budget_relation relation_types])})",
|
||||
budget_path(@budget.parent_budget)
|
||||
)
|
||||
else
|
||||
"-"
|
||||
end
|
||||
)
|
||||
component.with_attribute(
|
||||
key: Budget.human_attribute_name(:description),
|
||||
value: content_tag(:div, class: "op-uc-container") do
|
||||
@@ -86,12 +71,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
key: Budget.human_attribute_name(:labor_budget),
|
||||
value: number_to_currency(@budget.labor_budget)
|
||||
)
|
||||
if @budget.child_budget_relations.add.any?
|
||||
component.with_attribute(
|
||||
key: Budget.human_attribute_name(:budget_added_by_children),
|
||||
value: number_to_currency(@budget.budget_added_by_children)
|
||||
)
|
||||
end
|
||||
component.with_attribute(
|
||||
key: Budget.human_attribute_name(:budget),
|
||||
value: content_tag(:strong, number_to_currency(@budget.budget))
|
||||
@@ -100,12 +79,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
key: Budget.human_attribute_name(:spent),
|
||||
value: number_to_currency(@budget.spent * -1)
|
||||
)
|
||||
if @budget.child_budget_relations.subtract.any?
|
||||
component.with_attribute(
|
||||
key: Budget.human_attribute_name(:allocated_to_children),
|
||||
value: number_to_currency(@budget.allocated_to_children * -1)
|
||||
)
|
||||
end
|
||||
component.with_attribute(
|
||||
key: Budget.human_attribute_name(:available),
|
||||
value: content_tag(:strong, number_to_currency(@budget.available))
|
||||
@@ -118,14 +91,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<%= list_attachments(resource) %>
|
||||
|
||||
<%= render partial: "budget_items" %>
|
||||
|
||||
<fieldset class="form--fieldset">
|
||||
<legend class="form--fieldset-legend">
|
||||
<%= I18n.t(:label_budget_child_budgets) %>
|
||||
</legend>
|
||||
<%= render(Budgets::ChildBudgetsTableComponent.new(rows: @child_budget_relations)) %>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="clear: both;"></div>
|
||||
|
||||
@@ -36,8 +36,6 @@ en:
|
||||
budget:
|
||||
author: "Author"
|
||||
available: "Available"
|
||||
allocated_to_children: "Allocated"
|
||||
budget_added_by_children: "Added by children"
|
||||
budget: "Planned"
|
||||
budget_ratio: "Spent (ratio)"
|
||||
description: "Description"
|
||||
@@ -48,23 +46,8 @@ en:
|
||||
labor_budget: "Planned labor costs"
|
||||
material_budget: "Planned unit costs"
|
||||
base_amount: "Base amount"
|
||||
parent_budget: "Parent budget"
|
||||
state: "State"
|
||||
states:
|
||||
planned: "Planned"
|
||||
draft: "Draft"
|
||||
submitted: "Submitted"
|
||||
approved: "Approved"
|
||||
rejected: "Rejected"
|
||||
work_package:
|
||||
budget_subject: "Budget title"
|
||||
budget_relation:
|
||||
parent_budget: "Parent budget"
|
||||
child_budget: "Child budget"
|
||||
relation_type: "Relation to this"
|
||||
relation_types:
|
||||
add: "add budget to parent"
|
||||
subtract: "allocated budget from parent"
|
||||
models:
|
||||
budget: "Budget"
|
||||
material_budget_item: "Unit"
|
||||
@@ -81,9 +64,6 @@ en:
|
||||
button_add_cost_type: "Add cost type"
|
||||
button_cancel_edit_budget: "Cancel editing budget"
|
||||
button_cancel_edit_costs: "Cancel editing costs"
|
||||
button_manage_parent: "Manage parent budget"
|
||||
button_set_parent: "Set parent"
|
||||
button_delete_link_to_parent: "Delete link to parent budget"
|
||||
|
||||
caption_labor: "Labor"
|
||||
caption_labor_costs: "Actual labor costs"
|
||||
@@ -105,18 +85,15 @@ en:
|
||||
label_budget_plural: "Budgets"
|
||||
label_budget_spent: "Budget spent"
|
||||
label_budget_spent_ratio: "Budget spent ratio"
|
||||
label_budget_allocated: "Budget allocated to children"
|
||||
label_deliverable: "Budget"
|
||||
label_example_placeholder: "e.g., %{decimal}"
|
||||
label_view_all_budgets: "View all budgets"
|
||||
label_yes: "Yes"
|
||||
label_budget_child_budgets: "Child budgets"
|
||||
label_budget_totals: "Totals"
|
||||
label_budget_details: "Budget details"
|
||||
|
||||
notice_budget_conflict: "Work packages must be of the same project."
|
||||
notice_no_budgets_available: "No budgets available."
|
||||
notice_relation_destroyed: "The budget relation has been removed."
|
||||
|
||||
permission_edit_budgets: "Edit budgets"
|
||||
permission_view_budgets: "View budgets"
|
||||
@@ -125,18 +102,3 @@ en:
|
||||
text_budget_reassign_to: "Reassign them to this budget:"
|
||||
text_budget_delete: "Delete the budget from all work packages"
|
||||
text_budget_destroy_assigned_wp: "There are %{count} work packages assigned to this budget. What do you want to do?"
|
||||
|
||||
budgets:
|
||||
parent_relation:
|
||||
heading: "Mark this project as a child budget to another budget"
|
||||
description: "This will set up a dependency from this budget to its parent. Depending on the type of relation, the budgeted amount will be added to or subtracted from the parent's budget."
|
||||
delete_parent_relation:
|
||||
heading: "Delete parent budget relation"
|
||||
description: "This will remove the dependency from this budget to its parent. The budgeted amount of the parent budget and all its ancestors will also change!"
|
||||
relation_types:
|
||||
add:
|
||||
label: "Add budget to parent"
|
||||
helptext: "The budgeted amount for this budget will be added to the parent's budget and also mark the amount as allocated to this budget."
|
||||
subtract:
|
||||
label: "Subtract budget from parent"
|
||||
helptext: "The budgeted amount for this budget will be subtracted from the parent's budget and marked as allocated to this budget."
|
||||
|
||||
@@ -36,9 +36,6 @@ Rails.application.routes.draw do
|
||||
|
||||
resources :budgets, only: %i[show update destroy edit] do
|
||||
member do
|
||||
get :parent
|
||||
post :parent, to: "budgets#update_parent"
|
||||
delete :parent, to: "budgets#destroy_parent"
|
||||
get :copy
|
||||
get :destroy_info
|
||||
end
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class CreateBudgetRelation < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :budget_relations do |t|
|
||||
t.references :parent_budget, null: false, foreign_key: { to_table: :budgets }
|
||||
t.references :child_budget, null: false, foreign_key: { to_table: :budgets }
|
||||
t.references :cost_type, null: true, foreign_key: { to_table: :cost_types }
|
||||
|
||||
t.string :relation_type, null: false, default: "add"
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,36 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class AddBudgetState < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :budgets, :state, :string, null: true
|
||||
add_column :budget_journals, :state, :string, null: true
|
||||
end
|
||||
end
|
||||
@@ -1,37 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class AddBudgetStateIndex < ActiveRecord::Migration[8.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_index :budgets, :state, algorithm: :concurrently
|
||||
end
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class UniqueBudgetRelationChild < ActiveRecord::Migration[8.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
remove_index :budget_relations, :child_budget_id, if_exists: true, algorithm: :concurrently
|
||||
add_index :budget_relations, :child_budget_id, unique: true, algorithm: :concurrently
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoveCostTypeFromBudgetRelation < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
remove_reference(:budget_relations, :cost_type, foreign_key: true)
|
||||
end
|
||||
end
|
||||
@@ -13,7 +13,7 @@ module Budgets
|
||||
permissible_on: :project
|
||||
permission :edit_budgets,
|
||||
{
|
||||
budgets: %i[index show edit update destroy destroy_info new create copy parent destroy_parent update_parent]
|
||||
budgets: %i[index show edit update destroy destroy_info new create copy]
|
||||
},
|
||||
permissible_on: :project
|
||||
end
|
||||
@@ -72,7 +72,6 @@ module Budgets
|
||||
select Queries::Projects::Selects::BudgetSpent
|
||||
select Queries::Projects::Selects::BudgetSpentRatio
|
||||
select Queries::Projects::Selects::BudgetAvailable
|
||||
select Queries::Projects::Selects::BudgetAllocated
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,12 +55,7 @@ module Budgets::Patches::Projects::RowComponentPatch
|
||||
end
|
||||
|
||||
def total_ratio
|
||||
gone = total_spent + total_allocated
|
||||
@total_ratio ||= total_planned.zero? ? 0 : ((gone / total_planned) * 100).round
|
||||
end
|
||||
|
||||
def total_allocated
|
||||
@total_allocated ||= budgets.sum(&:allocated_to_children)
|
||||
@total_ratio ||= total_planned.zero? ? 0 : ((total_spent / total_planned) * 100).round
|
||||
end
|
||||
|
||||
def budgets
|
||||
@@ -88,12 +83,6 @@ module Budgets::Patches::Projects::RowComponentPatch
|
||||
end
|
||||
end
|
||||
|
||||
def budget_allocated
|
||||
with_project_budgets do |project_budgets|
|
||||
number_to_currency(project_budgets.total_allocated, precision: 0)
|
||||
end
|
||||
end
|
||||
|
||||
def budget_available
|
||||
with_project_budgets do |project_budgets|
|
||||
number_to_currency(project_budgets.total_available, precision: 0)
|
||||
|
||||
@@ -38,10 +38,8 @@ FactoryBot.define do
|
||||
created_at { 3.days.ago }
|
||||
updated_at { 3.days.ago }
|
||||
|
||||
traits_for_enum(:state)
|
||||
|
||||
trait :with_base_amount do
|
||||
base_amount { BigDecimal(250000000) }
|
||||
base_amount { BigDecimal(250_000) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
FactoryBot.define do
|
||||
factory :budget_relation do
|
||||
parent_budget factory: :budget
|
||||
child_budget factory: :budget
|
||||
|
||||
traits_for_enum(:relation_type)
|
||||
end
|
||||
end
|
||||
@@ -1,407 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require_relative "../spec_helper"
|
||||
|
||||
RSpec.describe BudgetRelation do
|
||||
shared_let(:admin) { create(:admin) }
|
||||
before do
|
||||
allow(User).to receive(:current).and_return(admin)
|
||||
end
|
||||
|
||||
describe "calculation logic" do
|
||||
describe "bottom->up" do
|
||||
let(:portfolio) { create(:project, project_type: :portfolio, name: "Portfolio") }
|
||||
let(:portfolio_budget) do
|
||||
create(:budget, project: portfolio, base_amount: 0, subject: "Portfolio Budget")
|
||||
end
|
||||
|
||||
let(:program1) { create(:project, project_type: :program, parent: portfolio, name: "Program 1") }
|
||||
let(:program1_budget) { create(:budget, project: program1, base_amount: 0, subject: "Program 1 Budget") }
|
||||
|
||||
let(:project1) { create(:project, project_type: :project, parent: program1, name: "Project 1") }
|
||||
let(:project1_budget) { create(:budget, project: project1, base_amount: 5_000, subject: "Project 1 Budget") }
|
||||
|
||||
let(:project2) { create(:project, project_type: :project, parent: program1, name: "Project 2") }
|
||||
let(:project2_budget) { create(:budget, project: project2, base_amount: 2_500, subject: "Project 2 Budget") }
|
||||
|
||||
context "without any relations" do
|
||||
it "calculates the correct values" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 0,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 0,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(project1_budget).to have_attributes(budget: 5_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 5_000)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_500,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 2_500)
|
||||
end
|
||||
end
|
||||
|
||||
context "when setting up project1 to add itself to the program1 budget" do
|
||||
before do
|
||||
described_class.create!(parent_budget: program1_budget,
|
||||
child_budget: project1_budget,
|
||||
relation_type: :add)
|
||||
end
|
||||
|
||||
it "allocates the project's budget to the program's budget" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 0,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 5_000,
|
||||
allocated_to_children: 5_000,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(project1_budget).to have_attributes(budget: 5_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 5_000)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_500,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 2_500)
|
||||
end
|
||||
|
||||
context "when also setting up project2 to add itself to the program1 budget" do
|
||||
before do
|
||||
described_class.create!(parent_budget: program1_budget,
|
||||
child_budget: project2_budget,
|
||||
relation_type: :add)
|
||||
end
|
||||
|
||||
it "allocates both project's budget to the program's budget" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 0,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 7_500,
|
||||
allocated_to_children: 7_500,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(project1_budget).to have_attributes(budget: 5_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 5_000)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_500,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 2_500)
|
||||
end
|
||||
|
||||
context "when setting program1 to add itself to the portfolio budget" do
|
||||
before do
|
||||
described_class.create!(parent_budget: portfolio_budget,
|
||||
child_budget: program1_budget,
|
||||
relation_type: :add)
|
||||
end
|
||||
|
||||
it "allocates the program's budget to the portfolio's budget" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 7_500,
|
||||
allocated_to_children: 7_500,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 7_500,
|
||||
allocated_to_children: 7_500,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(project1_budget).to have_attributes(budget: 5_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 5_000)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_500,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 2_500)
|
||||
end
|
||||
|
||||
context "when adding some costs to the projects" do
|
||||
let(:work_package1) { create(:work_package, project: project1, budget: project1_budget) }
|
||||
let!(:cost_entry1) do
|
||||
create(:cost_entry, project: project1, entity: work_package1, overridden_costs: 500)
|
||||
end
|
||||
|
||||
let(:work_package2) { create(:work_package, project: project2, budget: project2_budget) }
|
||||
let!(:cost_entry2) do
|
||||
create(:cost_entry, project: project2, entity: work_package2, overridden_costs: 750)
|
||||
end
|
||||
|
||||
it "subtracts the costs from the budgets" do
|
||||
expect(portfolio_budget).to have_attributes(spent: 0,
|
||||
spent_with_children: 1_250)
|
||||
|
||||
expect(program1_budget).to have_attributes(spent: 0,
|
||||
spent_with_children: 1_250)
|
||||
|
||||
expect(project1_budget).to have_attributes(budget: 5_000,
|
||||
spent: 500,
|
||||
available: 4_500)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_500,
|
||||
spent: 750,
|
||||
available: 1_750)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "top->down" do
|
||||
let(:portfolio) { create(:project, project_type: :portfolio, name: "Portfolio") }
|
||||
let(:portfolio_budget) { create(:budget, project: portfolio, base_amount: 10_000, subject: "Portfolio Budget") }
|
||||
|
||||
let(:program1) { create(:project, project_type: :program, parent: portfolio, name: "Program 1") }
|
||||
let(:program1_budget) { create(:budget, project: program1, base_amount: 7_000, subject: "Program 1 Budget") }
|
||||
|
||||
let(:project1) { create(:project, project_type: :project, parent: program1, name: "Project 1") }
|
||||
let(:project1_budget) { create(:budget, project: project1, base_amount: 5_000, subject: "Project 1 Budget") }
|
||||
|
||||
let(:program2) { create(:project, project_type: :program, parent: portfolio, name: "Program 2") }
|
||||
let(:program2_budget) { create(:budget, project: program2, base_amount: 3_000, subject: "Program 2 Budget") }
|
||||
|
||||
let(:project2) { create(:project, project_type: :project, parent: program2, name: "Project 2") }
|
||||
let(:project2_budget) { create(:budget, project: project2, base_amount: 2_000, subject: "Project 2 Budget") }
|
||||
|
||||
context "without any relations" do
|
||||
it "calculates the correct values" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 10_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 10_000)
|
||||
end
|
||||
end
|
||||
|
||||
context "with the portfolio -> program1 relation" do
|
||||
before do
|
||||
described_class.create!(parent_budget: portfolio_budget,
|
||||
child_budget: program1_budget,
|
||||
relation_type: :subtract)
|
||||
end
|
||||
|
||||
it "allocates the program's budget out of the portfolio budget" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 10_000,
|
||||
allocated_to_children: 7_000,
|
||||
spent: 0,
|
||||
available: 3_000)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 7_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 7_000)
|
||||
end
|
||||
|
||||
context "with the program1 -> project1 relation" do
|
||||
before do
|
||||
described_class.create!(parent_budget: program1_budget,
|
||||
child_budget: project1_budget,
|
||||
relation_type: :subtract)
|
||||
end
|
||||
|
||||
it "allocates the project's budget out of the program's budget" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 10_000,
|
||||
allocated_to_children: 7_000,
|
||||
spent: 0,
|
||||
available: 3_000)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 7_000,
|
||||
allocated_to_children: 5_000,
|
||||
spent: 0,
|
||||
available: 2_000)
|
||||
|
||||
expect(project1_budget).to have_attributes(budget: 5_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 5_000)
|
||||
end
|
||||
end
|
||||
|
||||
context "with the portfolio -> program1, program2 relation" do
|
||||
before do
|
||||
described_class.create!(parent_budget: portfolio_budget,
|
||||
child_budget: program2_budget,
|
||||
relation_type: :subtract)
|
||||
end
|
||||
|
||||
it "allocates the program's budget out of the portfolio's budget" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 10_000,
|
||||
allocated_to_children: 10_000,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 7_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 7_000)
|
||||
|
||||
expect(program2_budget).to have_attributes(budget: 3_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 3_000)
|
||||
end
|
||||
|
||||
context "with the program2 -> project2 relation" do
|
||||
before do
|
||||
described_class.create!(parent_budget: program2_budget,
|
||||
child_budget: project2_budget,
|
||||
relation_type: :subtract)
|
||||
end
|
||||
|
||||
it "allocates the project's budget out of the program's budget" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 10_000,
|
||||
allocated_to_children: 10_000,
|
||||
spent: 0,
|
||||
available: 0)
|
||||
|
||||
expect(program2_budget).to have_attributes(budget: 3_000,
|
||||
allocated_to_children: 2_000,
|
||||
spent: 0,
|
||||
available: 1_000)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 0,
|
||||
available: 2_000)
|
||||
end
|
||||
|
||||
context "with logged costs on project1" do
|
||||
let(:work_package) { create(:work_package, project: project2, budget: project2_budget) }
|
||||
let!(:cost_entry) do
|
||||
create(:cost_entry, project: project2, entity: work_package, overridden_costs: 500)
|
||||
end
|
||||
|
||||
it "subtracts the cost from project2's budget" do
|
||||
expect(portfolio_budget.spent).to eq(0)
|
||||
expect(portfolio_budget.spent_with_children).to eq(500)
|
||||
|
||||
expect(program2_budget.spent).to eq(0)
|
||||
expect(program2_budget.spent_with_children).to eq(500)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 500,
|
||||
available: 1_500)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "bottom->up and top->down mixed" do
|
||||
# - The portfolio has a budget of 10.000€.
|
||||
# - The program does not have an assigned budget but is the sum of the project's budgets.
|
||||
# - The program budget is taken out of the portfolio budget.
|
||||
# - The projects have budgets of 5.000€ and 2.500€.
|
||||
# - The projects have half of their budgets already spent.
|
||||
|
||||
let!(:portfolio) { create(:project, project_type: :portfolio, name: "Portfolio") }
|
||||
let!(:portfolio_budget) { create(:budget, project: portfolio, base_amount: 10_000, subject: "Portfolio Budget") }
|
||||
|
||||
let!(:program1) { create(:project, project_type: :program, parent: portfolio, name: "Program 1") }
|
||||
let!(:program1_budget) { create(:budget, project: program1, base_amount: 0, subject: "Program 1 Budget") }
|
||||
|
||||
let!(:portfolio_program_relation) do
|
||||
create(:budget_relation, parent_budget: portfolio_budget, child_budget: program1_budget, relation_type: :subtract)
|
||||
end
|
||||
|
||||
let!(:project1) { create(:project, project_type: :project, parent: program1, name: "Project 1") }
|
||||
let!(:project1_budget) { create(:budget, project: project1, base_amount: 5_000, subject: "Project 1 Budget") }
|
||||
|
||||
let!(:project1_program_relation) do
|
||||
create(:budget_relation, parent_budget: program1_budget, child_budget: project1_budget, relation_type: :add)
|
||||
end
|
||||
|
||||
let!(:work_package1) { create(:work_package, project: project1, budget: project1_budget) }
|
||||
let!(:cost_entry1) do
|
||||
create(:cost_entry, project: project1, entity: work_package1, overridden_costs: 2_500)
|
||||
end
|
||||
|
||||
let!(:project2) { create(:project, project_type: :project, parent: program1, name: "Project 2") }
|
||||
let!(:project2_budget) { create(:budget, project: project2, base_amount: 2_500, subject: "Project 2 Budget") }
|
||||
|
||||
let!(:project2_program_relation) do
|
||||
create(:budget_relation, parent_budget: program1_budget, child_budget: project2_budget, relation_type: :add)
|
||||
end
|
||||
|
||||
let!(:work_package2) { create(:work_package, project: project2, budget: project2_budget) }
|
||||
let!(:cost_entry2) do
|
||||
create(:cost_entry, project: project2, entity: work_package2, overridden_costs: 1_250)
|
||||
end
|
||||
|
||||
it "calculates the correct values" do
|
||||
expect(portfolio_budget).to have_attributes(budget: 10_000,
|
||||
allocated_to_children: 7_500,
|
||||
spent: 0,
|
||||
spent_with_children: 3_750,
|
||||
available: 2_500)
|
||||
|
||||
expect(program1_budget).to have_attributes(budget: 7_500,
|
||||
allocated_to_children: 7_500,
|
||||
spent: 0,
|
||||
spent_with_children: 3_750,
|
||||
available: 0)
|
||||
|
||||
expect(project1_budget).to have_attributes(budget: 5_000,
|
||||
allocated_to_children: 0,
|
||||
spent: 2_500,
|
||||
spent_with_children: 2_500,
|
||||
available: 2_500)
|
||||
|
||||
expect(project2_budget).to have_attributes(budget: 2_500,
|
||||
allocated_to_children: 0,
|
||||
spent: 1_250,
|
||||
spent_with_children: 1_250,
|
||||
available: 1_250)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -57,40 +57,34 @@ RSpec.describe PermittedParams do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#budget" do
|
||||
describe "budget" do
|
||||
let(:attribute) { :budget }
|
||||
|
||||
context "subject" do
|
||||
describe "#subject" do
|
||||
let(:hash) { { "subject" => "subject_test" } }
|
||||
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "description" do
|
||||
describe "#description" do
|
||||
let(:hash) { { "description" => "description_test" } }
|
||||
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "fixed_date" do
|
||||
describe "#fixed_date" do
|
||||
let(:hash) { { "fixed_date" => "2017-03-01" } }
|
||||
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "base_amount" do
|
||||
describe "#base_amount" do
|
||||
let(:hash) { { "base_amount" => "250000.00" } }
|
||||
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "state" do
|
||||
let(:hash) { { "state" => "approved" } }
|
||||
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "project_id" do
|
||||
describe "#project_id" do
|
||||
let(:hash) { { "project_id" => 42 } }
|
||||
|
||||
it_behaves_like "allows params" do
|
||||
@@ -98,7 +92,7 @@ RSpec.describe PermittedParams do
|
||||
end
|
||||
end
|
||||
|
||||
context "existing material budget item" do
|
||||
describe "#existing_material_budget_item" do
|
||||
let(:hash) do
|
||||
{ "existing_material_budget_item_attributes" => { "1" => {
|
||||
"units" => "100.0",
|
||||
@@ -111,7 +105,7 @@ RSpec.describe PermittedParams do
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "new material budget item" do
|
||||
describe "#new_material_budget_item" do
|
||||
let(:hash) do
|
||||
{ "new_material_budget_item_attributes" => { "1" => {
|
||||
"units" => "20",
|
||||
@@ -124,7 +118,7 @@ RSpec.describe PermittedParams do
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "existing labor budget item" do
|
||||
describe "#existing_labor_budget_item" do
|
||||
let(:hash) do
|
||||
{ "existing_labor_budget_item_attributes" => { "1" => {
|
||||
"hours" => "20.0",
|
||||
@@ -137,7 +131,7 @@ RSpec.describe PermittedParams do
|
||||
it_behaves_like "allows params"
|
||||
end
|
||||
|
||||
context "new labor budget item" do
|
||||
describe "#new_labor_budget_item" do
|
||||
let(:hash) do
|
||||
{ "new_labor_budget_item_attributes" => { "1" => {
|
||||
"hours" => "5.0",
|
||||
|
||||
@@ -44,11 +44,11 @@ module Costs::Patches::PermittedParamsPatch
|
||||
def budget
|
||||
params.require(:budget).permit(:subject,
|
||||
:description,
|
||||
:fixed_date, :base_amount, :state,
|
||||
:fixed_date, :base_amount,
|
||||
{ new_material_budget_item_attributes: %i[units cost_type_id comments amount] },
|
||||
{ new_labor_budget_item_attributes: %i[hours user_id comments amount] },
|
||||
{ existing_material_budget_item_attributes: %i[units cost_type_id comments amount] },
|
||||
existing_labor_budget_item_attributes: %i[hours user_id comments amount])
|
||||
{ existing_labor_budget_item_attributes: %i[hours user_id comments amount] })
|
||||
end
|
||||
|
||||
def cost_type
|
||||
|
||||
@@ -149,7 +149,6 @@ RSpec.describe ProjectQuery do
|
||||
budget_planned
|
||||
budget_spent
|
||||
budget_spent_ratio
|
||||
budget_allocated
|
||||
])
|
||||
end
|
||||
end
|
||||
@@ -177,7 +176,6 @@ RSpec.describe ProjectQuery do
|
||||
budget_available
|
||||
budget_planned
|
||||
budget_spent
|
||||
budget_allocated
|
||||
budget_spent_ratio
|
||||
])
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user