diff --git a/app/controllers/cost_objects_controller.rb b/app/controllers/cost_objects_controller.rb index 31fa1434be..defc8073d2 100644 --- a/app/controllers/cost_objects_controller.rb +++ b/app/controllers/cost_objects_controller.rb @@ -173,9 +173,9 @@ class CostObjectsController < ApplicationController end def update_material_budget_item - @element_id = params[:element_id] if params.has_key? :element_id + @element_id = params[:element_id] if params[:element_id].present? - @cost_type = CostType.find(params[:cost_type_id]) if params.has_key? :cost_type_id + @cost_type = CostType.find(params[:cost_type_id]) if params[:cost_type_id].present? @units = BigDecimal.new(Rate.clean_currency(params[:units])) @costs = (@units * @cost_type.rate_at(params[:fixed_date]).rate rescue 0.0) @@ -186,9 +186,8 @@ class CostObjectsController < ApplicationController end def update_labor_budget_item - @element_id = params[:element_id] if params.has_key? :element_id - - @user = User.find(params[:user_id]) + @element_id = params[:element_id] if params[:element_id].present? + @user = User.find(params[:user_id]) if params[:user_id].present? @hours = params[:hours].to_hours @costs = @hours * @user.rate_at(params[:fixed_date], @project).rate rescue 0.0 diff --git a/app/views/cost_objects/items/_material_budget_item.html.erb b/app/views/cost_objects/items/_material_budget_item.html.erb index d8fe830c80..66db83eee7 100644 --- a/app/views/cost_objects/items/_material_budget_item.html.erb +++ b/app/views/cost_objects/items/_material_budget_item.html.erb @@ -72,7 +72,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - + <%= t(:button_delete) %> diff --git a/app/views/cost_objects/subform/_labor_budget_subform.html.erb b/app/views/cost_objects/subform/_labor_budget_subform.html.erb index 714eea142c..1901b3ab76 100644 --- a/app/views/cost_objects/subform/_labor_budget_subform.html.erb +++ b/app/views/cost_objects/subform/_labor_budget_subform.html.erb @@ -93,7 +93,7 @@ end -%>
- + <%= t(:button_add_budget_item) %>
diff --git a/app/views/cost_objects/subform/_material_budget_subform.html.erb b/app/views/cost_objects/subform/_material_budget_subform.html.erb index ac97b5f822..4e33083989 100644 --- a/app/views/cost_objects/subform/_material_budget_subform.html.erb +++ b/app/views/cost_objects/subform/_material_budget_subform.html.erb @@ -100,7 +100,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- + <%= t(:button_add_budget_item) %>
diff --git a/app/views/costlog/edit.html.erb b/app/views/costlog/edit.html.erb index 1a9120a884..3f8058648d 100644 --- a/app/views/costlog/edit.html.erb +++ b/app/views/costlog/edit.html.erb @@ -30,13 +30,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. :post : :put %> + <%= labelled_tabular_form_for @cost_entry, url: url, html: { method: method } do |f| %> <%= error_messages_for 'cost_entry' %> <%= back_url_hidden_field_tag %> + <%= f.hidden_field :element_id, value: 'cost_entry', class: 'remote-field--input', data: { :'remote-field-key' =>'element_id' } %>
- <%= f.text_field :work_package_id, size: 6, required: true %> + <%= f.text_field :work_package_id, size: 6, required: true, autofocus: true %> <% if @cost_entry.work_package %>
@@ -45,7 +48,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. <% end %>
- <%= f.text_field :spent_on, size: 10, required: true %> + <%= f.text_field :spent_on, + size: 10, + required: true, + class: 'remote-field--input', + data: { :'remote-field-key' =>'fixed_date' } %> <%= calendar_for('cost_entry_spent_on') %>
@@ -59,7 +66,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. <% end %>
<%# see above %> - <%= f.select :cost_type_id, cost_types_collection_for_select_options, required: true, prompt: true %>

+ <%= f.select :cost_type_id, + cost_types_collection_for_select_options, + { prompt: true }, + { + required: true, + class: 'remote-field--input', + data: { :'remote-field-key' => 'cost_type_id' } + } %>

<% if @cost_entry.cost_type.nil? %> @@ -70,11 +84,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. size: 6, required: true, suffix: h(suffix), - suffix_id: 'cost_entry_unit_name' %> + suffix_id: 'cost_entry_unit_name', + class: 'remote-field--input', + data: { :'remote-field-key' => 'units' } %> <% end %> - <%= observe_field( "cost_entry_cost_type_id", url: {controller: :cost_objects, action: :update_material_budget_item, project_id: @cost_entry.project}, with: "'cost_type_id=' + encodeURIComponent(value) + '&units=' + encodeURIComponent(document.getElementById('cost_entry_units').value) + '&fixed_date=' + encodeURIComponent(document.getElementById('cost_entry_spent_on').value) + '&element_id=cost_entry'") %> - <%= observe_field( "cost_entry_units", frequency: 1, url: {controller: :cost_objects, action: :update_material_budget_item, project_id: @cost_entry.project}, with: "'cost_type_id=' + encodeURIComponent(document.getElementById('cost_entry_cost_type_id').value) + '&units=' + encodeURIComponent(value) + '&fixed_date=' + encodeURIComponent(document.getElementById('cost_entry_spent_on').value) + '&element_id=cost_entry'") %> - <%= observe_field( "cost_entry_spent_on", frequency: 1, url: {controller: :cost_objects, action: :update_material_budget_item, project_id: @cost_entry.project}, with: "'cost_type_id=' + encodeURIComponent(document.getElementById('cost_entry_cost_type_id').value) + '&units=' + encodeURIComponent(document.getElementById('cost_entry_units').value) + '&fixed_date=' + encodeURIComponent(value) + '&element_id=cost_entry'") %>
@@ -84,10 +97,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. <%= number_to_currency(@cost_entry.calculated_costs) %> - <%= update_page_tag do |page| - page << "makeEditable('cost_entry_costs', 'cost_entry[overridden_costs]');" - page << "edit($('cost_entry_costs'), 'cost_entry[overridden_costs]', '#{number_to_currency(@cost_entry.overridden_costs)}');" if @cost_entry.overridden_costs - end %> <% else %> " size="7" name="cost_entry[overridden_costs]" id="cost_entry_costs_edit"/> <%= Setting.plugin_openproject_costs['costs_currency'] %> @@ -104,3 +113,4 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. <%= styled_button_tag t(:button_save), class: '-with-icon icon-checkmark' %> <% end %> + diff --git a/app/views/hourly_rates/_rate.html.erb b/app/views/hourly_rates/_rate.html.erb index 088eab10a9..63b828edf5 100644 --- a/app/views/hourly_rates/_rate.html.erb +++ b/app/views/hourly_rates/_rate.html.erb @@ -19,6 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++#%> <%- + templated ||= false index ||= "INDEX" new_or_existing = rate.new_record? ? 'new' : 'existing' id_or_index = rate.new_record? ? index : rate.id @@ -26,10 +27,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. id_prefix = "user_#{new_or_existing}_rate_attributes_#{id_or_index}" name_prefix = "user[#{new_or_existing}_rate_attributes][#{id_or_index}]" classes ||= "" + classes += " subform-row-template" if templated -%> <%= fields_for prefix, rate do |rate_form| %> - + <%= rate_form.text_field :valid_from, :size => 10, :class => 'date', :index => id_or_index %><%= calendar_for("#{id_prefix}_valid_from") %> @@ -44,7 +46,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - <%= icon_wrapper('icon-context icon-delete', I18n.t(:button_delete)) %> + <%= icon_wrapper('icon-context icon-delete', I18n.t(:button_delete)) %> <% end %> diff --git a/app/views/hourly_rates/edit.html.erb b/app/views/hourly_rates/edit.html.erb index 8f4b829d06..19f156a609 100644 --- a/app/views/hourly_rates/edit.html.erb +++ b/app/views/hourly_rates/edit.html.erb @@ -18,22 +18,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++#%> -<%= render :partial => 'shared/costs_header' %> +<%= render partial: 'shared/costs_header' %> <% html_title t(:label_administration), "#{t(:label_edit)} #{l((@project.nil? ? :caption_default_rate_history_for : :caption_rate_history_for), user: ' ')} #{@user.name}" %> -<%= toolbar title: (@project.nil? ? t(:caption_default_rate_history_for, :user => @user.name) : t(:caption_rate_history_for_project, :user => @user.name, :project => @project.name) ) %> +<%= toolbar title: (@project.nil? ? t(:caption_default_rate_history_for, user: @user.name) : t(:caption_rate_history_for_project, user: @user.name, project: @project.name) ) %> <%- default_rate = @user.current_default_rate -%> <% if default_rate%>

<%= t(:label_current_default_rate) %>: <%= number_to_currency(default_rate.rate)%>

<% end %> -<%= javascript_tag do -%> - RatesForm = new Subform('<%= escape_javascript(render(:partial => "rate", :object => HourlyRate.new )) %>',<%= @rates.length %>,'rates_body'); -<% end -%> - -<%= labelled_tabular_form_for @user, :url => {:action => 'update', :project_id => @project}, :method => :put do |f| %> +<% template_object = %> + +<%= labelled_tabular_form_for @user, url: {action: 'update', project_id: @project}, method: :put do |f| %> <%= back_url_hidden_field_tag %> <%= error_messages_for 'user' %> <%- @rates.each do |rate| -%> @@ -71,9 +69,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - + + <%= render partial: "hourly_rates/rate", object: HourlyRate.new, locals: { templated: true } %> <%- @rates.each_with_index do |rate, index| -%> - <%= render :partial => 'rate', :object => rate, :locals => {:index => index} %> + <%= render partial: 'rate', object: rate, locals: {index: index} %> <%- end -%> @@ -82,7 +81,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- <%= link_to_function t(:button_add_rate), "addRate($('add_rate_date'))", {class: "button -alt-highlight -with-icon icon-add"} %> + + <%= t(:button_add_rate) %> + <%= styled_button_tag t(:button_save), class: '-with-icon icon-checkmark' %>
<% end %> + diff --git a/app/views/shared/_costs_header.html.erb b/app/views/shared/_costs_header.html.erb index 383261e091..8028ebf618 100644 --- a/app/views/shared/_costs_header.html.erb +++ b/app/views/shared/_costs_header.html.erb @@ -20,5 +20,4 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. <% content_for :header_tags do %> <%= stylesheet_link_tag 'costs/costs.css' %> - <%= javascript_include_tag 'costs/costs.js' %> <% end %> diff --git a/frontend/app/components/budget/cost-budget-subform.directive.ts b/frontend/app/components/budget/cost-budget-subform.directive.ts index 2dda208744..d12f5789ff 100644 --- a/frontend/app/components/budget/cost-budget-subform.directive.ts +++ b/frontend/app/components/budget/cost-budget-subform.directive.ts @@ -43,7 +43,7 @@ export class CostBudgetSubformController { // Updater URL for the rows contained here public updateUrl: string; - constructor(public $element, public $http, public wpNotificationsService) { + constructor(public $element, public wpNotificationsService) { this.container = $element.find('.budget-item-container'); this.rowIndex = parseInt(this.itemCount); @@ -121,7 +121,7 @@ function costsBudgetSubform() { restrict: 'E', scope: { updateUrl: '@', - rowIndex: '@' + itemCount: '@' }, link: (scope, element, attr, ctrl) => { const template = element.find('.budget-row-template'); diff --git a/frontend/app/components/subform/cost-subform.directive.ts b/frontend/app/components/subform/cost-subform.directive.ts new file mode 100644 index 0000000000..1c3dc42642 --- /dev/null +++ b/frontend/app/components/subform/cost-subform.directive.ts @@ -0,0 +1,92 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +// +// 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 doc/COPYRIGHT.rdoc for more details. +// ++ + +export class CostSubformController { + + // Container for rows + private container: ng.IAugmentedJQuery; + + // Template for new rows to insert, is rendered with INDEX placeholder + private rowTemplate: string; + + // Current row index + public rowIndex: number; + + // subform item count as output by rails + public itemCount: string; + + constructor(public $element) { + this.container = $element.find('.subform-container'); + this.rowIndex = parseInt(this.itemCount); + + $element.on('click', '.delete-row-button', (evt) => { + var row = angular.element(evt.target).closest('.subform-row'); + row.remove(); + }); + + // Add new row handler + $element.find('.add-row-button').click(() => { + this.addRow(); + }); + } + + /** + * Adds a new empty budget item row with the correct index set + */ + public addRow() { + this.container.append(this.indexedTemplate); + this.rowIndex += 1; + + this.container.find('.subform-row:last-child input:first').focus(); + } + + /** + * Return the next possible new row from rowTemplate, + * with the index set to the current last value. + */ + private get indexedTemplate() { + return this.rowTemplate.replace(/INDEX/g, this.rowIndex.toString()); + } +} + +function costsSubform() { + return { + restrict: 'E', + scope: { itemCount: '@' }, + link: (scope, element, attr, ctrl) => { + const template = element.find('.subform-row-template'); + ctrl.rowTemplate = template[0].outerHTML; + template.remove(); + }, + bindToController: true, + controller: CostSubformController, + controllerAs: '$ctrl' + }; +} + +angular.module('openproject').directive('costsSubform', costsSubform); diff --git a/spec/features/members_hourly_rates_spec.rb b/spec/features/members_hourly_rates_spec.rb index f7556426b2..896a744192 100644 --- a/spec/features/members_hourly_rates_spec.rb +++ b/spec/features/members_hourly_rates_spec.rb @@ -48,6 +48,9 @@ describe 'hourly rates on a member', type: :feature, js: true do fill_in 'Valid from', with: date.strftime('%Y-%m-%d') if date fill_in 'Rate', with: rate end + + # Close the date picker if still open + find('.ui-datepicker-close').click rescue nil end def change_rate_date(from:, to:) diff --git a/spec/features/update_budget_spec.rb b/spec/features/update_budget_spec.rb index 72976ea04f..3120edf557 100644 --- a/spec/features/update_budget_spec.rb +++ b/spec/features/update_budget_spec.rb @@ -25,7 +25,7 @@ describe 'updating a budget', type: :feature, js: true do let(:budget) { FactoryGirl.create :cost_object, author: user, project: project } before do - allow(User).to receive(:current).and_return user + login_as(user) end describe 'with new cost items' do @@ -33,7 +33,7 @@ describe 'updating a budget', type: :feature, js: true do FactoryGirl.create :cost_type, name: 'Post-war', unit: 'cap', unit_plural: 'caps' end - let(:page) { Pages::EditBudget.new budget.id } + let(:budget_page) { Pages::EditBudget.new budget.id } before do project.add_member! user, FactoryGirl.create(:role) @@ -43,22 +43,22 @@ describe 'updating a budget', type: :feature, js: true do end it 'creates the cost items' do - page.visit! + budget_page.visit! click_on 'Update' - page.add_unit_costs! 3, comment: 'Stimpak' - page.add_labor_costs! 5, user_name: user.name, comment: 'treatment' + budget_page.add_unit_costs! 3, comment: 'Stimpak' + budget_page.add_labor_costs! 5, user_name: user.name, comment: 'treatment' click_on 'Submit' - expect(page).to have_content('Successful update') + expect(budget_page).to have_content('Successful update') - page.toggle_unit_costs! - expect(page.unit_costs_at(1)).to have_content '150.00 EUR' - expect(page.overall_unit_costs).to have_content '150.00 EUR' + budget_page.toggle_unit_costs! + expect(budget_page.unit_costs_at(1)).to have_content '150.00 EUR' + expect(budget_page.overall_unit_costs).to have_content '150.00 EUR' - page.toggle_labor_costs! - expect(page.labor_costs_at(1)).to have_content '125.00 EUR' - expect(page.overall_labor_costs).to have_content '125.00 EUR' + budget_page.toggle_labor_costs! + expect(budget_page.labor_costs_at(1)).to have_content '125.00 EUR' + expect(budget_page.overall_labor_costs).to have_content '125.00 EUR' end end @@ -79,7 +79,7 @@ describe 'updating a budget', type: :feature, js: true do cost_object: budget end - let(:page) { Pages::EditBudget.new budget.id } + let(:budget_page) { Pages::EditBudget.new budget.id } before do project.add_member! user, FactoryGirl.create(:role) @@ -93,25 +93,25 @@ describe 'updating a budget', type: :feature, js: true do end it 'updates the cost items' do - page.visit! + budget_page.visit! click_on 'Update' - page.edit_unit_costs! material_budget_item.id, units: 5, + budget_page.edit_unit_costs! material_budget_item.id, units: 5, comment: 'updated num stimpaks' - page.edit_labor_costs! labor_budget_item.id, hours: 3, + budget_page.edit_labor_costs! labor_budget_item.id, hours: 3, user_name: user.name, comment: 'updated treatment duration' click_on 'Submit' - expect(page).to have_content('Successful update') + expect(budget_page).to have_content('Successful update') - page.toggle_unit_costs! - expect(page.unit_costs_at(1)).to have_content '250.00 EUR' - expect(page.overall_unit_costs).to have_content '250.00 EUR' + budget_page.toggle_unit_costs! + expect(budget_page.unit_costs_at(1)).to have_content '250.00 EUR' + expect(budget_page.overall_unit_costs).to have_content '250.00 EUR' - page.toggle_labor_costs! - expect(page.labor_costs_at(1)).to have_content '75.00 EUR' - expect(page.overall_labor_costs).to have_content '75.00 EUR' + budget_page.toggle_labor_costs! + expect(budget_page.labor_costs_at(1)).to have_content '75.00 EUR' + expect(budget_page.overall_labor_costs).to have_content '75.00 EUR' end end end