31 |
32 | <%-# If no default contract, then it indicates this is a new expense. Populate dropdown with last created contract -%>
33 | <% if @contracts_expense.contract_id == nil %>
34 | <% @contracts_expense.contract_id = @project.contracts.maximum(:id) %>
35 | <% end %>
36 | <% @contracts = @project.contracts_for_all_ancestor_projects %>
37 | <%= select("contracts_expense", "contract_id", @contracts.collect { |c| [ c.getDisplayTitle, c.id ] }, {:include_blank => l(:label_select_contract)}) %>
38 |
39 |
<%= f.text_field :issue_id, :size => 5 %>
40 |
<%= f.text_field :description, :size => 50 %>
41 |
42 | <%= f.submit l(:label_save_expense) %>
43 |
44 | <% end %>
45 |
--------------------------------------------------------------------------------
/test/unit/user_test.rb:
--------------------------------------------------------------------------------
1 | # Redmine - project management software
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang
3 | #
4 | # This program is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either version 2
7 | # of the License, or (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program; if not, write to the Free Software
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 |
18 | require File.expand_path('../../test_helper', __FILE__)
19 |
20 | class UserTest < ActiveSupport::TestCase
21 | fixtures :projects, :contracts, :time_entries, :user_project_rates, :users
22 |
23 | def setup
24 | Setting.plugin_contracts = {
25 | 'automatic_contract_creation' => false
26 | }
27 | @project = projects(:projects_001)
28 | @contract = contracts(:contract_one)
29 | @contract.project_id = @project.id
30 | @contract.save
31 | @user = @project.users.first
32 | end
33 |
34 | test "should have many user project rates" do
35 | assert_respond_to @user, :user_project_rates
36 | end
37 |
38 | test "should have many user contract rates" do
39 | assert_respond_to @user, :user_contract_rates
40 | end
41 |
42 | test "should add a new user project rate" do
43 | assert_not_nil @user
44 | if upr = @project.user_project_rate_by_user(@user)
45 | upr.destroy
46 | end
47 | assert_nil @project.user_project_rate_by_user(@user)
48 | upr = @project.user_project_rates.create!(:user_id => @user.id, :rate => 25.00)
49 | assert_equal [upr], @project.user_project_rates
50 | assert_equal @user, upr.user
51 | assert_equal 25.00, upr.rate
52 | end
53 |
54 | end
55 |
--------------------------------------------------------------------------------
/app/views/contracts/index.html.erb:
--------------------------------------------------------------------------------
1 |
41 | <%= submit_tag(l(:label_apply)) %>
42 | <% end %>
43 |
44 | <% content_for :header_tags do %>
45 | <%= stylesheet_link_tag 'contracts', :plugin => 'contracts' %>
46 | <% end %>
47 |
--------------------------------------------------------------------------------
/app/controllers/contracts_expenses_controller.rb:
--------------------------------------------------------------------------------
1 | class ContractsExpensesController < ApplicationController
2 | before_filter :set_project, :authorize, :only => [:new, :edit, :update, :create, :destroy]
3 | before_filter :set_expense, :only => [:edit, :update, :destroy]
4 |
5 | def new
6 | @contracts_expense = ContractsExpense.new
7 | load_contracts
8 | end
9 |
10 | def edit
11 | load_contracts
12 | end
13 |
14 | def create
15 | @contracts_expense = ContractsExpense.new(expense_params)
16 |
17 | respond_to do |format|
18 | if @contracts_expense.save
19 | format.html { redirect_to contract_urlpath(@contracts_expense), notice: l(:text_expense_created) }
20 | else
21 | load_contracts
22 | format.html { render action: 'new' }
23 | end
24 | end
25 | end
26 |
27 | def update
28 | respond_to do |format|
29 | if @contracts_expense.update_attributes(expense_params)
30 | format.html { redirect_to contract_urlpath(@contracts_expense), notice: l(:text_expense_updated) }
31 | else
32 | load_contracts
33 | format.html { render action: 'edit' }
34 | end
35 | end
36 | end
37 |
38 | def destroy
39 | back_to = contract_urlpath(@contracts_expense)
40 | @contracts_expense.destroy
41 | flash[:notice] = l(:text_expense_deleted)
42 | respond_to do |format|
43 | format.html { redirect_to back_to }
44 | end
45 | end
46 |
47 | private
48 |
49 | def contract_urlpath(expense)
50 | url_for({ :controller => 'contracts', :action => 'show', :project_id => expense.contract.project.identifier, :id => expense.contract.id, :contracts_expenses => 'true'})
51 | end
52 |
53 | def set_expense
54 | @contracts_expense = ContractsExpense.find(params[:id])
55 | if @contracts_expense.contract.is_locked
56 | flash[:error] = l(:text_expenses_uneditable)
57 | redirect_to contract_urlpath(@contracts_expense)
58 | end
59 | end
60 |
61 | def set_project
62 | @project = Project.find(params[:project_id])
63 | end
64 |
65 | def load_contracts
66 | @contracts = Contract.order("start_date ASC").where(:project_id => @project.id).where(:is_locked => false)
67 | end
68 |
69 | private
70 |
71 | def expense_params
72 | params.require(:contracts_expense).permit(:name, :expense_date, :amount, :contract_id, :issue_id, :description)
73 | end
74 |
75 | end
76 |
--------------------------------------------------------------------------------
/lib/contracts/patches/project_patch.rb:
--------------------------------------------------------------------------------
1 | require_dependency 'project'
2 |
3 | module Contracts
4 | module ProjectPatch
5 | def self.included(base)
6 | base.send(:include, InstanceMethods)
7 | base.class_eval do
8 | has_many :contracts
9 | has_many :user_project_rates
10 | end
11 | end
12 |
13 | module InstanceMethods
14 |
15 | def unlocked_contracts
16 | contracts.select { |contract| !contract.is_locked }
17 | end
18 |
19 | def user_project_rate_by_user(user)
20 | self.user_project_rates.select { |upr| upr.user_id == user.id}.first
21 | end
22 |
23 | def rate_for_user(user)
24 | upr = self.user_project_rate_by_user(user)
25 | upr.nil? ? 0.0 : upr.rate
26 | end
27 |
28 | def set_user_rate(user, rate)
29 | upr = user_project_rate_by_user(user)
30 | if upr.nil?
31 | self.user_project_rates.create!(:user_id => user.id, :rate => rate)
32 | else
33 | upr.update_attribute(:rate, rate)
34 | upr
35 | end
36 | end
37 |
38 | def total_amount_purchased
39 | self.contracts.map(&:purchase_amount).inject(0, &:+)
40 | end
41 |
42 | def total_hours_purchased
43 | self.contracts.map(&:hours_purchased).inject(0, &:+)
44 | end
45 |
46 | def total_amount_remaining
47 | self.contracts.map(&:amount_remaining).inject(0, &:+)
48 | end
49 |
50 | def total_hours_remaining
51 | self.contracts.map(&:hours_remaining).inject(0, &:+)
52 | end
53 |
54 | def contracts_for_all_ancestor_projects(contracts=self.contracts)
55 | if self.parent != nil
56 | parent = self.parent
57 | contracts += parent.contracts_for_all_ancestor_projects
58 | end
59 | return contracts
60 | end
61 |
62 | def unlocked_contracts_for_all_ancestor_projects(contracts = self.unlocked_contracts)
63 | if self.parent != nil
64 | parent = self.parent
65 | contracts += parent.unlocked_contracts_for_all_ancestor_projects
66 | end
67 | return contracts
68 | end
69 |
70 | def time_entries_for_all_descendant_projects(time_entries=self.time_entries)
71 | if self.children != nil
72 | self.children.each { |child| time_entries += child.time_entries_for_all_descendant_projects }
73 | end
74 | return time_entries
75 | end
76 | end
77 | end
78 | end
79 |
80 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | require_dependency 'contracts/hooks/hooks'
2 | require_dependency 'contracts/patches/time_entry_patch'
3 | require_dependency 'contracts/patches/timelog_controller_patch'
4 | require_dependency 'contracts/patches/user_patch'
5 | require_dependency 'contracts/patches/project_patch'
6 | require_dependency 'contracts/validators/is_after_agreement_date_validator'
7 | require_dependency 'contracts/validators/is_after_start_date_validator'
8 |
9 |
10 | Redmine::Plugin.register :contracts do
11 | name 'Redmine Contracts With Time Tracking'
12 | author 'Ben Syzek, Shanti Braford, Wesley Jones'
13 | description 'A Redmine plugin that allows you to manage contracts and associate time-entries with those contracts.'
14 | version '2.4'
15 | url 'https://github.com/upgradeya/redmine-contracts-with-time-tracking-plugin.git'
16 |
17 | requires_redmine :version_or_higher => '3.0'
18 |
19 | menu :application_menu, :contracts, { :controller => :contracts, :action => :all }, :caption => :label_contracts, :if => Proc.new { User.current.logged? && User.current.allowed_to?(:view_all_contracts_for_project, nil, :global => true) }
20 | menu :project_menu, :contracts, { :controller => :contracts, :action => :index }, :caption => :label_contracts, :param => :project_id
21 |
22 | settings :default => {'empty' => true}, :partial => 'settings/contract_settings'
23 |
24 | project_module :contracts do
25 | permission :view_all_contracts_for_project, :contracts => [:index, :series]
26 | permission :view_contract_details, :contracts => :show
27 | permission :edit_contracts, :contracts => [:edit, :update, :add_time_entries, :assoc_time_entries_with_contract, :lock]
28 | permission :create_contracts, :contracts => [:new, :create]
29 | permission :delete_contracts, :contracts => :destroy
30 | permission :view_hourly_rate, :contracts => :view_hourly_rate #view_hourly_rate is a fake action!
31 | permission :create_expenses, :contracts_expenses => [:new, :create]
32 | permission :edit_expenses, :contracts_expenses => [:edit, :update]
33 | permission :delete_expenses, :contracts_expenses => :destroy
34 | permission :view_expenses, :contracts_expenses => :show
35 | end
36 | end
37 |
38 | # Load your patches from contracts/lib/contracts/patches/
39 | ActionDispatch::Callbacks.to_prepare do
40 | Project.send(:include, Contracts::ProjectPatch)
41 | TimeEntry.send(:include, Contracts::TimeEntryPatch)
42 | TimelogController.send(:include, Contracts::TimelogControllerPatch)
43 | User.send(:include, Contracts::UserPatch)
44 | require_dependency 'contract_category'
45 | end
46 |
--------------------------------------------------------------------------------
/config/locales/ru.yml:
--------------------------------------------------------------------------------
1 | # Russian strings go here for Rails i18n
2 | ru:
3 | project_module_contracts: Контракты
4 | permission_view_all_contracts_for_project: Просмотр всех контрактов проекта
5 | permission_view_contract_details: Просмотр деталей контракта
6 | permission_edit_contracts: Редактирование контрактов
7 | permission_create_contracts: Создание контрактов
8 | permission_delete_contracts: Удаление контрактов
9 | permission_view_hourly_rate: Просмотр стоимости часа
10 |
11 | contracts: Контракты
12 | label_contracts: Контракты
13 | label_contract: Контракт
14 | label_new_contract: Новый контракт
15 | label_editing_contract: Редактирование контракта
16 | label_add_time_entries: Добавить работы
17 | label_edit: Редактировать
18 | label_delete: Удалить
19 | label_view_contract: Просмотр контракта
20 | label_view_invoice: Просмотр счета
21 | label_date_period: Период действия
22 | label_amount_purchased: Оплачено
23 | label_invoice: Счет
24 | label_members: Участники
25 | label_hours: Часы
26 | label_time_entries: Список работ
27 | label_add_to_contract: Добавить
28 | label_date: Дата
29 | label_current_contract: Текущий контракт
30 | label_member: Участник
31 | label_activity: Действие
32 | label_issue: Задача
33 | label_comments: Комментарий
34 | label_apply: Применить
35 | label_name: Название
36 | label_agreed_on: Заключен
37 | label_purchased: Оплачено
38 | label_remaining: Осталось
39 | label_hours_worked: Часов потрачено
40 | label_hours_left: "~Часов осталось"
41 | label_total_purchased: Всего оплачено по контрактам
42 | label_total_remaining: Всего осталось по контрактам
43 | label_or: "или"
44 | label_create_contract: Создать контракт
45 | label_update_contract: Обновить контракт
46 | label_contract_empty: Без контракта
47 |
48 | text_are_you_sure_delete_title: "Вы действительно хотите удалить %{value}?"
49 | text_are_you_sure_delete_time_entry: "Вы действительно хотите удалить эту запись?"
50 | text_time_exceeded_time_remaining: "Эта работа превышает оставшееся время по контракту на %{hours_over}.\nДля работы в рамках контракта укажите время не более %{hours_remaining}."
51 | text_must_come_after_agreement_date: должна быть не ранее даты соглашения
52 | text_must_come_after_start_date: должна быть позже даты начала
53 | text_contract_saved: Контракт успешно создан!
54 | text_contract_updated: Контракт успешно обновлен!
55 | text_contract_deleted: Контракт успешно удален
56 | text_hours_over_contract: "Лимит по контракту превышен на %{hours_over}."
57 |
58 | field_contract: Контракт
59 | field_title: Заголовок
60 | field_description: Описание
61 | field_agreement_date: Дата соглашения
62 | field_start_date: Дата начала работ
63 | field_end_date: Дата завершения работ
64 | field_purchase_amount: Сумма к оплате
65 | field_hourly_rate: Часовой рейт
66 | field_contract_url: Ссылка на контракт
67 | field_invoice_url: Ссылка на счет
68 |
--------------------------------------------------------------------------------
/config/locales/no.yml:
--------------------------------------------------------------------------------
1 | # Norwegian bokmål strings go here for Rails i18n
2 | "no":
3 | project_module_contracts: Kontrakter
4 | permission_view_all_contracts_for_project: Vis alle kontrakter for prosjektet
5 | permission_view_contract_details: Vis kontraktdetaljer
6 | permission_edit_contracts: Endre kontrakter
7 | permission_create_contracts: Lag kontrakter
8 | permission_delete_contracts: Slett kontrakter
9 | permission_view_hourly_rate: Vis timepris
10 | permission_create_expenses: Lag utgifter
11 | permission_edit_expenses: Endre utgifter
12 | permission_delete_expenses: Slett utgifter
13 | permission_view_expenses: Vis utgifter
14 |
15 | contracts: Kontrakter
16 | label_contracts: Kontrakter
17 | label_contract: Kontrakt
18 | label_new_contract: Ny kontrakt
19 | label_editing_contract: Endre kontrakt
20 | label_add_time_entries: Legg til tid brukt
21 | label_add_time_entries: Legg til flere tilfeller av tid brukt
22 | label_add_expense: Legg til utgift
23 | label_log_time: Logg tid
24 | label_edit: Endre
25 | label_delete: Slett
26 | label_view_contract: Vis kontrakt
27 | label_view_invoice: Vis faktura
28 | label_date_period: Dato-område
29 | label_amount_purchased: Beløp kjøpt
30 | label_invoice: Faktura
31 | label_members: Medlemmer
32 | label_hours: Timer
33 | label_contractor_rate: Kontraktørens timepris
34 | label_billable_amount: Fakturerbart beløp
35 | label_time_entries: Tid brukt
36 | label_add_to_contract: Legg til i kontrakt
37 | label_date: Dato
38 | label_current_contract: Gjeldende kontrakt
39 | label_member: Medlem
40 | label_activity: Aktivitet
41 | label_issue: Sak
42 | label_comments: Kommentarer
43 | label_apply: Bruk
44 | label_name: Navn
45 | label_agreed_on: Avtalt
46 | label_purchased: Kjøpt
47 | label_remaining: Gjenstår
48 | label_hours_worked: Timer arbeidet
49 | label_hours_left: "~Timer igjen"
50 | label_total_purchased: Totalt kjøpt for alle kontrakter
51 | label_total_remaining: Totalt gjenstående for alle kontrakter
52 | label_or: "-eller-"
53 | label_create_contract: Lag kontrakt
54 | label_update_contract: Oppdater kontrakt
55 | label_contract_empty: Ingen kontrakt
56 | label_select_contract: "[Velg kontrakt]"
57 | label_save_expense: Lagre utgift
58 | label_expenses: Utgifter
59 | label_description: Beskrivelse
60 | label_amount: Beløp
61 | label_edit_expense: Endre utgift
62 | label_new_expense: Ny utgift
63 |
64 | text_are_you_sure_delete_title: "Er du sikker på at du vil slette %{value}?"
65 | text_are_you_sure_delete_time_entry: "Er du sikker på at du vil slette denne tidsregistreringen?"
66 | text_are_you_sure_delete_expense: "Er du sikker på at du vil slette denne utgiften?"
67 | text_time_exceeded_time_remaining: "Denne tidsregistreringen overgår gjenstående tid for denne kontrakten med %{hours_over}.\nFor å være innenfor kontrakt, vennligst endre tidsregistreringen til ikke mer enn %{hours_remaining}."
68 | text_must_come_after_agreement_date: må komme på eller etter avtalt dato
69 | text_must_come_after_start_date: må komme etter startdatoen
70 | text_contract_saved: Kontrakten er lagret!
71 | text_contract_updated: Kontrakten er oppdatert!
72 | text_contract_deleted: Kontrakten er slettet
73 | text_hours_over_contract: "Du er nå %{hours_over} over kontraktens grense."
74 | text_invalid_rate: "Kontraktørens timepris må være null eller større."
75 | text_invalid_issue_id: "er ikke en gyldig saks-ID"
76 | text_expense_created: 'Utgiften er laget.'
77 | text_expense_updated: 'Utgiften er oppdatert.'
78 | text_na: 'Ikke tilgjengelig'
79 |
80 | field_contract: Kontrakt
81 | field_title: Tittel
82 | field_description: Beskrivelse
83 | field_agreement_date: Avtaledato
84 | field_start_date: Startdato
85 | field_end_date: Sluttdato
86 | field_purchase_amount: Kjøpsbeløp
87 | field_hourly_rate: Timepris
88 | field_contract_url: Kontrakt URL
89 | field_invoice_url: Faktura URL
90 |
91 | field_expense_name: Navn
92 | field_expense_date: Utgiftsdato
93 | field_amount: Beløp
94 | field_expense_contract: Kontrakt
95 | field_issue: Sak (ID)
--------------------------------------------------------------------------------
/lib/contracts/hooks/hooks.rb:
--------------------------------------------------------------------------------
1 | module Contracts
2 | class ContractsHookListener < Redmine::Hook::ViewListener
3 |
4 | def view_timelog_edit_form_bottom(context={})
5 | if context[:time_entry].project_id != nil
6 | @current_project = Project.find(context[:time_entry].project_id)
7 | @contracts = @current_project.contracts_for_all_ancestor_projects
8 |
9 | if !@contracts.empty?
10 | if context[:time_entry].contract_id != nil
11 | selected_contract = context[:time_entry].contract_id
12 | elsif !(@current_project.contracts.empty?)
13 | selected_contract = @current_project.contracts.maximum(:id)
14 | elsif !(@contracts.empty?)
15 | selected_contract = @contracts.max_by(&:id).id
16 | else
17 | selected_contract = ''
18 | end
19 | contract_unselectable = false
20 | if !selected_contract.blank?
21 | # There is a selected contract. Check to see if it has been locked
22 | selected_contract_obj = Contract.find(selected_contract)
23 | if selected_contract_obj.is_locked
24 | # Contract has been locked. Only list that contract in the drop-down
25 | @contracts = [selected_contract_obj]
26 | contract_unselectable = true
27 | else
28 | # Only show NON-locked contracts in the drop-down
29 | @contracts = @current_project.unlocked_contracts_for_all_ancestor_projects
30 | end
31 | else
32 | # There is NO selected contract. Only show NON-locked contracts in the drop-down
33 | @contracts = @current_project.unlocked_contracts_for_all_ancestor_projects
34 | end
35 | db_options = options_from_collection_for_select(@contracts, :id, :getDisplayTitle, selected_contract)
36 | no_contract_option = "\n".html_safe
37 | if !contract_unselectable
38 | all_options = no_contract_option << db_options
39 | else
40 | # Contract selected has already been locked. Do not show the [Select Contract] label.
41 | all_options = db_options
42 | end
43 | select = context[:form].select :contract_id, all_options
44 | return "
#{select}
"
45 | end
46 | else
47 | "
This page will not work due to the contracts plugin. You must log time entries from within a project."
48 | end
49 | end
50 |
51 | # Poor Man's Cron
52 | def controller_account_success_authentication_after(context={})
53 | # check to see if cron has ran today or if its null
54 | last_run = Setting.plugin_contracts[:last_cron_run]
55 | if last_run.nil? || last_run < Date.today
56 | # Get all monthly recurring contracts
57 | monthly_contracts = Contract.monthly
58 | # Loop thru the contracts and check if any have passed their recurring date
59 | monthly_contracts.each do |contract|
60 | if Date.today > (contract.start_date + 1.month)
61 | # Create new contract and expire the old one
62 | new_contract = Contract.new
63 | if new_contract.copy(contract)
64 | expire_contract(contract)
65 | end
66 | end
67 | end
68 |
69 | # Get all yearly recurring contracts
70 | yearly_contracts = Contract.yearly
71 | # Loop thru the contracts and check if any have passed their recurring date
72 | yearly_contracts.each do |contract|
73 | if Date.today > (contract.start_date + 1.year)
74 | # Create new contract and expire the old one
75 | new_contract = Contract.new
76 | if new_contract.copy(contract)
77 | expire_contract(contract)
78 | end
79 | end
80 | end
81 | end
82 |
83 | Setting.plugin_contracts.update({last_cron_run: Date.today})
84 | end
85 |
86 | def expire_contract(contract)
87 | contract.completed!
88 | contract.save
89 | end
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/config/locales/es.yml:
--------------------------------------------------------------------------------
1 | es:
2 | project_module_contracts: Contratos
3 | permission_view_all_contracts_for_project: Ver Todos los contratos por proyecto
4 | permission_view_contract_details: Ver detalles del contrato
5 | permission_edit_contracts: Editar contratos
6 | permission_create_contracts: Crear contratos
7 | permission_delete_contracts: Eliminar contratos
8 | permission_view_hourly_rate: Ver tarifa por hora
9 | permission_create_expenses: Crear gastos
10 | permission_edit_expenses: Editar gastos
11 | permission_delete_expenses: Eliminar gastos
12 | permission_view_expenses: Ver gastos
13 |
14 | contracts: Contratos
15 | label_contracts: Contratos
16 | label_contract: Contrato
17 | label_new_contract: Nuevo Contrato
18 | label_editing_contract: Editar Contrato
19 | label_add_time_entries: Añadir registro de tiempo
20 | label_add_time_entries: Asignación masiva de registro de tiempo
21 | label_add_expense: Añadir Gasto
22 | label_edit: Editar
23 | label_delete: Eliminar
24 | label_view_contract: Ver Contrato
25 | label_view_invoice: Ver Factura
26 | label_date_period: Rango de fechas
27 | label_amount_purchased: Importe contratado
28 | label_invoice: Factura
29 | label_members: Miembros
30 | label_hours: Horas
31 | label_contractor_rate: Tarifa Contratada
32 | label_billable_amount: Cantidad Facturable
33 | label_time_entries: Registros de tiepo
34 | label_add_to_contract: Añadir al Contrato
35 | label_date: Fecha
36 | label_current_contract: Contrato Actual
37 | label_member: Miembro
38 | label_activity: Actividad
39 | label_issue: Cuestión
40 | label_comments: Comentarios
41 | label_apply: Aplicar
42 | label_name: Nombre
43 | label_agreed_on: Convenido
44 | label_purchased: Contratado
45 | label_remaining: Pendiente
46 | label_hours_worked: Horas Trabajadas
47 | label_hours_left: Horas Restantes
48 | label_total_purchased: Total Contratado para Todos los Contratos
49 | label_total_remaining: Total Pendiente para Todos los Contratos
50 | label_or: "-o-"
51 | label_create_contract: Crear Contrato
52 | label_update_contract: Actualizar Contrato
53 | label_contract_empty: Contrato Vacio
54 | label_select_contract: "[Elige un Contrato]"
55 | label_save_expense: Guardar Gasto
56 | label_expenses: Gastos
57 | label_description: Descripción
58 | label_amount: Cantidad
59 | label_edit_expense: Editar Gasto
60 | label_new_expense: Nuevo Gasto
61 |
62 | text_are_you_sure_delete_title: "¿Estás seguro que quieres eliminar %{value}?"
63 | text_are_you_sure_delete_time_entry: "¿Estás seguro que quieres eliminar este registro?"
64 | text_are_you_sure_delete_expense: "¿Estás seguro que quieres eliminar este gasto?"
65 | text_time_exceeded_time_remaining: "Este registro de tiempo super el máximo de tiempo en %{hours_over}.\Para mantenerte en el contrato, edita este registro para que quede en %{hours_remaining}."
66 | text_must_come_after_agreement_date: debe ser tras la fecha aceptada
67 | text_must_come_after_start_date: debe venir después de la fecha de comienzo
68 | text_contract_saved: Contrato guarado correctamente!
69 | text_contract_updated: Contrato actualizado correctamente!
70 | text_contract_deleted: Contrato eliminado correctamente
71 | text_hours_over_contract: "Estás %{hours_over} horas por encioma del límite del contrato"
72 | text_invalid_rate: "La tarifa del contrato debe ser superior a cero."
73 | text_invalid_issue_id: "no es un ID de cuestión básico"
74 | text_expense_created: 'Gasto creado correctamente.'
75 | text_expense_updated: 'Gasto actualizado correctamente.'
76 | text_na: 'N/A'
77 |
78 | field_contract: Contrato
79 | field_title: Título
80 | field_description: Descripción
81 | field_agreement_date: Fecha del acuerdo
82 | field_start_date: Fecha de inicio
83 | field_end_date: Fecha de fin
84 | field_purchase_amount: Cantidad comprada
85 | field_hourly_rate: Tarifa horaria
86 | field_contract_url: URL del Contrato
87 | field_invoice_url: URL de la Factura
88 |
89 | field_expense_name: Nombre
90 | field_expense_date: Fecha de gastos
91 | field_amount: Cantidad
92 | field_expense_contract: Contrato
93 | field_issue: Cuestión (ID)
94 |
--------------------------------------------------------------------------------
/config/locales/bg.yml:
--------------------------------------------------------------------------------
1 | bg:
2 | project_module_contracts: Договори
3 | permission_view_all_contracts_for_project: Достъп до всички договори по проекта
4 | permission_view_contract_details: Достъп до детайли на договор
5 | permission_edit_contracts: Достъп до редактиране на договор
6 | permission_create_contracts: Право за създаване на договор
7 | permission_delete_contracts: Право за изтриване на договор
8 | permission_view_hourly_rate: Достъпо до тарифи
9 | permission_create_expenses: Право за създаване на разход
10 | permission_edit_expenses: Право за редакция на разход
11 | permission_delete_expenses: Право за изтриване на разход
12 | permission_view_expenses: Достъпо до разходи
13 |
14 | contracts: Договори
15 | label_contracts: Договори
16 | label_contract: Договор
17 | label_new_contract: Нов договор
18 | label_editing_contract: Редакция на договор
19 | label_add_time_entries: Въвеждане на време
20 | label_add_time_entries: Въвеждане на време (масово)
21 | label_add_expense: Добавяне на разход
22 | label_log_time: Добави време
23 | label_edit: Редакция
24 | label_delete: Изтриване
25 | label_view_contract: Виж договор
26 | label_view_invoice: Виж фактура
27 | label_date_period: Срок на валидност
28 | label_amount_purchased: Стойност на договора
29 | label_invoice: Фактури
30 | label_members: Членове
31 | label_hours: Часове
32 | label_contractor_rate: Тарифи
33 | label_billable_amount: Платима сума
34 | label_time_entries: Вписвания на време
35 | label_add_to_contract: Добави към договор
36 | label_date: Дата
37 | label_current_contract: Текущ договор
38 | label_member: Изпълнител
39 | label_activity: Дейност
40 | label_issue: Задача
41 | label_comments: Коментари
42 | label_apply: Приложи
43 | label_name: Име
44 | label_agreed_on: Дата на договора
45 | label_purchased: Обща сума
46 | label_remaining: Оставащ
47 | label_hours_worked: Изработени часове
48 | label_hours_left: "~ Оставащи часове"
49 | label_total_purchased: Обща стойност на всички договори
50 | label_total_remaining: Общо оставащо за всички договори
51 | label_or: "-или-"
52 | label_create_contract: Създай договор
53 | label_update_contract: Промени договор
54 | label_contract_empty: Няма договор
55 | label_select_contract: "[Избери договор]"
56 | label_save_expense: Запази разход
57 | label_expenses: Разходи
58 | label_description: Описание
59 | label_amount: Количество
60 | label_edit_expense: Редактирай разход
61 | label_new_expense: Нов разход
62 |
63 | text_are_you_sure_delete_title: "Сигурни ли сте, че искате да изтриете %{value}?"
64 | text_are_you_sure_delete_time_entry: "Сигурни ли сре, че искате да изтриете това време?"
65 | text_are_you_sure_delete_expense: "Сигурни ли сте, че искате да изтриете този разход?"
66 | text_time_exceeded_time_remaining: "Въведеното време надвишава оставащото време в настоящия договор с %{hours_over} часа.\nЗа да може да въведете време, моля въведете стойност по-малка от %{hours_remaining}."
67 | text_must_come_after_agreement_date: Тряба да е след датата на споразумението
68 | text_must_come_after_start_date: Трябва да е след началната дата
69 | text_contract_saved: Договорът е успешно запазен!
70 | text_contract_updated: Договорът беше променен успешно!
71 | text_contract_deleted: Договорът беше изтрит успешно
72 | text_hours_over_contract: "В момента надвишаване договореният лимит с %{hours_over} часа."
73 | text_invalid_rate: "Тарифата трябва да е 0 или по-висока."
74 | text_invalid_issue_id: "Невалиден ID"
75 | text_expense_created: 'Разходът беше създаден успешно.'
76 | text_expense_updated: 'Разходът беше променен успешно.'
77 | text_na: 'N/A'
78 |
79 | field_contract: Договор
80 | field_title: Заглавие
81 | field_description: Описание
82 | field_agreement_date: Дата на договора
83 | field_start_date: Начална дата
84 | field_end_date: Крайна дата
85 | field_purchase_amount: Закупено количество
86 | field_hourly_rate: Цена на час
87 | field_contract_url: URL на договора
88 | field_invoice_url: URL на фактурата
89 |
90 | field_expense_name: Име
91 | field_expense_date: Дата на разхода
92 | field_amount: Сума
93 | field_expense_contract: Договор
94 | field_issue: Задача (ID)
--------------------------------------------------------------------------------
/config/locales/de.yml:
--------------------------------------------------------------------------------
1 | # German strings go here for Rails i18n
2 | de:
3 | project_module_contracts: Verträge
4 | permission_view_all_contracts_for_project: Alle Verträge für Projekt anzeigen
5 | permission_view_contract_details: Vertragsdetails anzeigen
6 | permission_edit_contracts: Verträge bearbeitn
7 | permission_create_contracts: Verträge erstellen
8 | permission_delete_contracts: Verträge öschen
9 | permission_view_hourly_rate: Stundensatz
10 | permission_create_expenses: Ausgabe erstellen
11 | permission_edit_expenses: Ausgaben bearbeiten
12 | permission_delete_expenses: Ausgaben löschen
13 | permission_view_expenses: Ausgaben anzeigen
14 |
15 |
16 | contracts: Verträge
17 | label_contracts: Verträge
18 | label_contract: Vertrag
19 | label_new_contract: Neuer Vertrag
20 | label_editing_contract: Vertrag bearbeiten
21 | label_add_time_entries: Zeiten zuordnen
22 | label_add_expense: Ausgabe hinzufügen
23 | label_log_time: Zeit stoppen
24 | label_edit: bearbeiten
25 | label_delete: löschen
26 | label_view_contract: Vertrag anzeigen
27 | label_view_invoice: Rechnung anzeigen
28 | label_date_period: Vertragszeitraum
29 | label_amount_purchased: bezahlter Betrag
30 | label_invoice: Rechnung
31 | label_members: Mitglieder
32 | label_hours: Stunden
33 | label_contractor_rate: Preis pro Stunde
34 | label_billable_amount: Abrechenbarer Betrag
35 | label_time_entries: gebuchte Zeiten
36 | label_add_to_contract: dem Vertrag zuordnen
37 | label_date: Datum
38 | label_current_contract: aktueller Vertrag
39 | label_member: Mitglied
40 | label_activity: Aktivität
41 | label_issue: Ticket
42 | label_comments: Kommentare
43 | label_apply: Anwenden
44 | label_name: Name
45 | label_agreed_on: abgeschlossen am
46 | label_purchased: vereinbart
47 | label_remaining: verbleibend
48 | label_hours_worked: gearbeitete Stunden
49 | label_hours_left: "~verbleibende Stunden"
50 | label_total_purchased: vereinbart für alle Verträge
51 | label_total_remaining: verbleibend für alle Verträge
52 | label_or: "-oder-"
53 | label_create_contract: Vertrag anlegen
54 | label_update_contract: Vertrag berarbeiten
55 | label_contract_empty: kein Vertrag
56 | label_select_contract: "[Vertrag auswählen]"
57 | label_save_expense: Ausgabe speichern
58 | label_expenses: Ausgaben
59 | label_description: Beschreibung
60 | label_amount: Betrag
61 | label_edit_expense: Ausgabe bearbeiten
62 | label_new_expense: Neue Ausgabe
63 |
64 | text_are_you_sure_delete_title: "Sind sie sicher das Sie %{value} löschen möchten?"
65 | text_are_you_sure_delete_time_entry: "Sind Sie sicher, dass sie diesen Zeiteintrag löschen möchten?"
66 | text_are_you_sure_delete_expense: "Sind Sie sicher, dass sie diese Ausgabe löschen wollen?"
67 | text_time_exceeded_time_remaining: "Dieser Zeiteintrag überschreitet die verbeleibende Zeit im Vertrag um %{hours_over} Stunden.\nUm im Vertragsrahmen zu bleiben ändern Sie bitte den Eintrag auf nicht mehr als %{hours_remaining} Stunden."
68 | text_must_come_after_agreement_date: muss nach dem Vertragsdatum liegen
69 | text_must_come_after_start_date: muss nach dem Startdatum des Vertrages liegen
70 | text_contract_saved: Der Vertrag wurde erfolgreich gespeichert!
71 | text_contract_updated: Der Vertrag wurde erfolgreich aktualisiert!
72 | text_contract_deleted: Der Vertrag wurde erfolgreich gelöscht
73 | text_hours_over_contract: "Das vereinbarte Stundenkontingent wird um %{hours_over} Stunden überschritten"
74 | text_invalid_rate: "Preis pro Stunde muss größer oder gleich Null sein."
75 | text_invalid_issue_id: "ist keine gültige Ticket-ID"
76 | text_expense_created: "Ausgabe wurde erfolgreich erstellt."
77 | text_expense_updated: "Ausgabe wurde erfolgreich aktualisiert."
78 | text_na: "keine Angabe"
79 |
80 | field_contract: Vertrag
81 | field_title: Titel
82 | field_description: Beschreibung
83 | field_agreement_date: Vertragsdatum
84 | field_start_date: Beginn
85 | field_end_date: Ende
86 | field_purchase_amount: Summe
87 | field_hourly_rate: Stundensatz
88 | field_contract_url: Vertrags-URL
89 | field_invoice_url: Rechnungs-URL
90 |
91 | field_expense_name: Name
92 | field_expense_date: Datum für Ausgabe
93 | field_amount: Betrag
94 | field_expense_contract: Vertrag
95 | field_issue: Ticket (ID)
96 |
--------------------------------------------------------------------------------
/config/locales/pt-BR.yml:
--------------------------------------------------------------------------------
1 | pt-BR:
2 | project_module_contratos: Contratos
3 | permission_view_all_contratos_for_project: Visualizar todos contratos do projeto
4 | permission_view_contract_details: Exibir detalhes do contrato
5 | permission_edit_contratos: Editar contratos
6 | permission_create_contratos: Criar contratos
7 | permission_delete_contratos: Excluir contratos
8 | permission_view_hourly_rate: Visualizar taxa por hora
9 | permission_create_despesas: Inserir despesas
10 | permission_edit_despesas: Editar despesas
11 | permission_delete_despesas: Excluir despesas
12 | permission_view_despesas: Visualizar despesas
13 |
14 | contracts: Contratos
15 | label_contracts: Contratos
16 | label_contract: Contrato
17 | label_new_contract: Novo Contrato
18 | label_editing_contract: Editando Contrato
19 | label_add_time_entries: Inserir Registro de Tempo
20 | label_add_time_entries: Atribuir Registros de Tempo em Massa
21 | label_add_expense: Inserir Despesa
22 | label_log_time: Registro de Tempo
23 | label_edit: Editar
24 | label_delete: Excluir
25 | label_view_contract: Visualizar Contrato
26 | label_view_invoice: Visualizar Fatura
27 | label_date_period: Período
28 | label_amount_purchased: Valor Negociado
29 | label_invoice: Fatura
30 | label_members: Membros
31 | label_hours: Horas
32 | label_contractor_rate: Contratoor Taxa
33 | label_billable_amount: Valor Faturado
34 | label_time_entries: Registros de Tempo
35 | label_add_to_contract: Inserir to Contrato
36 | label_date: Data
37 | label_current_contract: Contrato Atual
38 | label_member: Membro
39 | label_activity: Atividade
40 | label_issue: Tarefa
41 | label_comments: Comentários
42 | label_apply: Aplicar
43 | label_name: Nome
44 | label_agreed_on: Aceite Em
45 | label_purchased: Vendido
46 | label_remaining: Disponível
47 | label_hours_worked: Horas Trabalhadas
48 | label_hours_left: "~Horas Disponíveis"
49 | label_total_purchased: Total Negociado para Todos Contratos
50 | label_total_remaining: Total Disponível para Todos Contratos
51 | label_or: "-ou-"
52 | label_create_contract: Inserir Contrato
53 | label_update_contract: Atualizar Contrato
54 | label_contract_empty: Sem Contrato
55 | label_select_contract: "[Selecione um contrato]"
56 | label_save_expense: Gravar Despesa
57 | label_expenses: Despesas
58 | label_description: Descrição
59 | label_amount: Valor
60 | label_edit_expense: Editar Despesa
61 | label_new_expense: Novo Despesa
62 |
63 | text_are_you_sure_delete_title: "Você tem certeza que deseja excluir %{value}?"
64 | text_are_you_sure_delete_time_entry: "Você tem certeza que deseja excluir este registro de tempo?"
65 | text_are_you_sure_delete_expense: "Você tem certeza que deseja excluir esta despesa?"
66 | text_time_exceeded_time_remaining: "Este registro de tempo excedeu o total de horas disponíveis para o contrato em %{hours_over} horas.\nPara existir uma coerência com o contrato, por favor edite o registro de tempo de forma que não seja superior a %{hours_remaining} horas."
67 | text_must_come_after_agreement_date: tem que ser igual ou posterior à Data de Aceite
68 | text_must_come_after_start_date: tem que ser posterior à Data de Início
69 | text_contract_saved: Contrato gravado com sucesso!
70 | text_contract_updated: Contrato atualizado com sucesso!
71 | text_contract_deleted: Contrato excluído com sucesso
72 | text_hours_over_contract: "Você está com um desvio de %{hours_over} horas acima do número de horas contradas."
73 | text_invalid_rate: "Taxa por Hora deve ser não pode ser negativa ou nula."
74 | text_invalid_issue_id: "não é um ID válido de Tarefa"
75 | text_expense_created: 'Despesa criada com sucesso.'
76 | text_expense_updated: 'Despesa atualizada com sucesso.'
77 | text_na: 'N/A'
78 |
79 | field_contract: Contrato
80 | field_title: Título
81 | field_description: Descrição
82 | field_agreement_date: Data Aceite Contrato
83 | field_start_date: Data Início
84 | field_end_date: Data Término
85 | field_purchase_amount: Valor Vendido
86 | field_hourly_rate: Taxa por Hora
87 | field_contract_url: Contrato URL
88 | field_invoice_url: Fatura URL
89 |
90 | field_expense_name: Nome
91 | field_expense_date: Data Despesa
92 | field_amount: Valor
93 | field_expense_contract: Contrato
94 | field_issue: Tarefa (ID)
--------------------------------------------------------------------------------
/test/functional/timelog_controller_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Redmine - project management software
3 | # Copyright (C) 2006-2012 Jean-Philippe Lang
4 | #
5 | # This program is free software; you can redistribute it and/or
6 | # modify it under the terms of the GNU General Public License
7 | # as published by the Free Software Foundation; either version 2
8 | # of the License, or (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program; if not, write to the Free Software
17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 |
19 | require File.expand_path('../../test_helper', __FILE__)
20 | require 'timelog_controller'
21 |
22 | # Re-raise errors caught by the controller.
23 | class TimelogController; def rescue_action(e) raise e end; end
24 |
25 | class TimelogControllerTest < ActionController::TestCase
26 | fixtures :projects, :enabled_modules, :roles, :members,
27 | :member_roles, :issues, :time_entries, :users,
28 | :trackers, :enumerations, :issue_statuses,
29 | :custom_fields, :custom_values, :contracts
30 |
31 | include Redmine::I18n
32 |
33 | def setup
34 | @controller = TimelogController.new
35 | @request = ActionController::TestRequest.new
36 | @response = ActionController::TestResponse.new
37 | @contract = contracts(:contract_three)
38 | @contract2 = contracts(:contract_two)
39 | end
40 |
41 | test "should create time entry if hours is under amount remaining" do
42 | Setting.plugin_contracts = {
43 | 'automatic_contract_creation' => false
44 | }
45 | @request.session[:user_id] = 3
46 | post :create, :project_id => 1,
47 | :time_entry => {:comments => 'Some work on TimelogControllerTest',
48 | # Not the default activity
49 | :activity_id => '11',
50 | :spent_on => '2015-03-14',
51 | :issue_id => '1',
52 | :hours => 1,
53 | :contract_id => @contract.id}
54 | assert_response 302
55 | assert_equal "Successful creation.", flash[:notice]
56 | end
57 |
58 | test "should not create time entry if hours is over amount remaining" do
59 | Setting.plugin_contracts = {
60 | 'automatic_contract_creation' => false
61 | }
62 | entry_hours = @contract.hours_remaining + 1
63 | @request.session[:user_id] = 3
64 | post :create, :project_id => 1,
65 | :time_entry => {:comments => 'Some work on TimelogControllerTest',
66 | # Not the default activity
67 | :activity_id => '11',
68 | :spent_on => '2015-03-14',
69 | :issue_id => '1',
70 | :hours => entry_hours,
71 | :contract_id => @contract.id}
72 | assert_response 200
73 | assert_select("div#errorExplanation", /Hours is invalid. The contract/)
74 | end
75 |
76 | test "a new contract is created automatically" do
77 | Setting.plugin_contracts = {
78 | 'automatic_contract_creation' => true
79 | }
80 | @contract.project_contract_id = 10;
81 | @contract.save
82 | @request.session[:user_id] = 3
83 | entry_hours = @contract.hours_remaining + 1
84 | post :create, :project_id => 1,
85 | :time_entry => {:comments => 'Some work on TimelogControllerTest',
86 | # Not the default activity
87 | :activity_id => '11',
88 | :spent_on => '2015-03-14',
89 | :issue_id => '1',
90 | :hours => entry_hours,
91 | :contract_id => @contract.id}
92 | assert_response 302
93 | assert_equal "Successful creation.", flash[:notice]
94 | assert_match /Your time entry has been split into two entries/, flash[:contract]
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A Redmine plugin for managing the time/money being spent on client contracts.
2 |
3 | This plugin allows you to:
4 |
5 | - Create and store client contracts
6 | - Visualize how much time/money has been spent on a particular contract
7 | - Associate time entries with specific contracts
8 |
9 | ### Special thanks to [UpgradeYa](http://www.upgradeya.com) for funding this project.
10 |
11 | Installation
12 | ------------
13 | Option 1 - Download zip
14 |
15 | 1. Download the zip (for Redmine 2 you will need to download the v1.3.1 zip from the github releases page)
16 | 1. Unzip the redmine-contracts-with-time-tracking-plugin-master folder, rename it to contracts, and place it in the redmine plugins folder.
17 | 1. run 'rake redmine:plugins:migrate RAILS_ENV=production' from your redmine root directory
18 |
19 | Option 2 - Git clone
20 |
21 | 1. Run 'git clone https://github.com/upgradeya/redmine-contracts-with-time-tracking-plugin.git plugins/contracts' from your redmine root directory
22 | * Note : use 'git submodule add' instead of 'git clone' if your install folder is part of a git project.
23 | 1. This step is only for Redmine 2 - After you run the git command above, cd into the contracts directory and run 'git checkout tags/v1.3.1'
24 | 1. run 'rake redmine:plugins:migrate RAILS_ENV=production' from your redmine root directory
25 |
26 | Screenshots
27 | -----------
28 |
29 | ### View all contracts for a project:
30 | 
31 |
32 | ### View contract details:
33 | 
34 |
35 | ### Create and edit contracts:
36 | 
37 |
38 | ### Set permisisons:
39 | 
40 |
41 | Changelog
42 | ---------
43 | Contracts v2.2 2017-3-6
44 | -----------------------
45 | - Added a recurring contract option so you that can have fixed contracts created automatically each month or year.
46 |
47 | Contracts v2.2 2017-2-7
48 | -----------------------
49 | - Added a 'Fixed Price' contract type that calculates your effective rate and hides hourly information from your client (in permissions uncheck 'View spent time ').
50 | - Added a summary tab that shows the cumulative time spent on each issue within that contract.
51 | - Fixed the no confirmation for deletion bug.
52 |
53 | Contracts v2.1 2016-3-5
54 | -----------------------
55 | - Renamed the expenses database table name to prevent conflicts with other redmine plugins
56 |
57 | Contracts v2.0 2016-1-9
58 | -----------------------
59 | - Contracts plugin is now Redmine 3 compatible
60 |
61 | Contracts v1.3.1, 2015-12-27
62 | ----------------------------
63 | - Implemented new feature to lock contracts. This can be used to prevent old contracts and their time entries from accidentally being edited.
64 | - Locked contracts are hidden from new time entry dropdowns
65 | - Implementing caching on locked contracts to decrease load time on the contract pages.
66 |
67 | Contracts v1.2.0, 2015-12-14
68 | ----------------------------
69 | - On contract form the fields are now inline and date fields use calendar widget. Required fields are now marked. Any validations will re-populate the screen with previous data.
70 | - Adding a time entry selects last created contract. Used to use start and end date. For sub-projects it selects the last created contract within the sub-project if a contract exists. Also fixed for expenses. Currently there is no way to add expense in sub-project to the parent contract if there are no sub-project contracts.
71 | - New Agreement Pending - (basically just not marking that field as required) Agreed on date shows agreement pending on contract list and detail page. Date range is not shown when agreement is pending.
72 | - If they have auto-contract creation enabled, a time entry that exceeds the remaining contract will auto-create a new contract and submit a time entry to the new contract with the remaining time.
73 | - Discussion on the title. Fixed title format. Auto-increments based on all the projects IDs. Need to add a per project identifier so the auto-increment is project based and not entire redmine based.
74 |
--------------------------------------------------------------------------------
/test/unit/project_test.rb:
--------------------------------------------------------------------------------
1 | # Redmine - project management software
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang
3 | #
4 | # This program is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either version 2
7 | # of the License, or (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program; if not, write to the Free Software
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 |
18 | require File.expand_path('../../test_helper', __FILE__)
19 |
20 | class ProjectTest < ActiveSupport::TestCase
21 | fixtures :projects, :contracts, :time_entries, :user_project_rates,
22 | :user_contract_rates, :users, :members, :enabled_modules
23 |
24 | def setup
25 | Setting.plugin_contracts = {
26 | 'automatic_contract_creation' => false
27 | }
28 | @project = projects(:projects_001)
29 | @parent_project = projects(:projects_003)
30 | @sub_subproject = projects(:projects_004)
31 | @contract = contracts(:contract_one)
32 | @contract2 = contracts(:contract_two)
33 | @time_entry1 = time_entries(:time_entries_001)
34 | @time_entry2 = time_entries(:time_entries_004)
35 | @time_entry3 = time_entries(:time_entries_005)
36 | @contract.project_id = @project.id
37 | @contract2.project_id = @project.id
38 | @contract.save
39 | @contract2.save
40 | @sub_subproject.parent_id = @parent_project.id
41 | @sub_subproject.save
42 | @project.time_entries.clear
43 | @project.time_entries.append(@time_entry1)
44 | @project.save
45 | @time_entry3.project_id = @sub_subproject.id
46 | @time_entry3.save
47 | @user = @project.users.first
48 | end
49 |
50 | test "should have many contracts" do
51 | assert_respond_to @project, "contracts"
52 | end
53 |
54 | test "should calculate amount purchased across all contracts" do
55 | assert_equal @project.total_amount_purchased, @project.contracts.map(&:purchase_amount).inject(0, &:+)
56 | end
57 |
58 | test "should calculate approximate hours purchased across all contracts" do
59 | assert_equal @project.total_hours_purchased, @project.contracts.map(&:hours_purchased).inject(0, &:+)
60 | end
61 |
62 | test "should calculate amount remaining across all contracts" do
63 | assert_equal @project.total_amount_remaining, @project.contracts.map(&:amount_remaining).inject(0, &:+)
64 | end
65 |
66 | test "should calculate hours remaining across all contracts" do
67 | assert_equal @project.total_hours_remaining, @project.contracts.map(&:hours_remaining).inject(0, &:+)
68 | end
69 |
70 | test "should get contracts for all ancestor projects" do
71 | @contract2.project_id = @parent_project.id
72 | @contract2.save
73 | assert_equal 3, @sub_subproject.contracts_for_all_ancestor_projects.count
74 | end
75 |
76 | test "should get all time entries for current project and all descendent projects" do
77 | time_entries = @project.time_entries_for_all_descendant_projects
78 | assert_equal 3, time_entries.count
79 | assert time_entries.include?(@time_entry1)
80 | assert time_entries.include?(@time_entry2)
81 | assert time_entries.include?(@time_entry3)
82 | end
83 |
84 | test "should have many user project rates" do
85 | assert_not_nil @user
86 | @project.set_user_rate(@user, 25.00)
87 | assert_operator @project.user_project_rates.size, :>=, 1
88 | end
89 |
90 | test "should get a user project rate by user" do
91 | assert_not_nil @user
92 | upr = @project.set_user_rate(@user, 25.00)
93 | assert_equal upr, @project.user_project_rate_by_user(@user)
94 | end
95 |
96 | test "should get a rate for a user" do
97 | assert_not_nil @user
98 | @project.set_user_rate(@user, 25.00)
99 | assert_equal 25.00, @project.rate_for_user(@user)
100 | end
101 |
102 | test "should set a user rate" do
103 | assert_not_nil @user
104 | # check the value is not already set
105 | assert_not_equal 37.25, @project.rate_for_user(@user)
106 | @project.set_user_rate(@user, 37.25)
107 | assert_equal 37.25, @project.rate_for_user(@user)
108 | end
109 |
110 | end
111 |
--------------------------------------------------------------------------------
/app/views/contracts/_form.html.erb:
--------------------------------------------------------------------------------
1 | <% controller.action_name == "new" || controller.action_name == "create" ? action = "create" : action = "update" %>
2 | <% controller.action_name == "new" || controller.action_name == "create" ? method = :post : method = :put %>
3 |
4 | <%= labelled_form_for @contract, :url => { :action => action }, :method => method do |f| %>
5 | <%= f.hidden_field :project_id, :value => @project.id %>
6 |
32 |
33 |
34 | <% @contracts.each do |contract| %>
35 | <% # Check if the contract is locked and if the user filtered out locked contracts. %>
36 | <% if !contract.is_locked || session[:show_locked_contracts] %>
37 | <% # Show all the contracts if you're on a project index page. If you're on the "All" contracts page %>
38 | <% # check if the user filtered for active recurring contracts. %>
39 | <% if @project != nil || (!contract.is_fixed_price || !session[:show_only_active_recurring] || (session[:show_only_active_recurring] && (contract.monthly? || contract.yearly?))) %>
40 |
104 | <% end %>
105 | <% end %>
106 | <% end %>
107 |
108 |
109 |
--------------------------------------------------------------------------------
/test/unit/contract_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../../test_helper', __FILE__)
2 |
3 | class ContractTest < ActiveSupport::TestCase
4 | fixtures :contracts, :time_entries, :projects, :issues,
5 | :user_contract_rates, :user_project_rates,
6 | :users, :members, :member_roles
7 |
8 | def setup
9 | Setting.plugin_contracts = {
10 | 'automatic_contract_creation' => false
11 | }
12 | @contract = contracts(:contract_one)
13 | @contract2 = contracts(:contract_two)
14 | @project = projects(:projects_001)
15 | @issue = issues(:issues_001)
16 | @time_entry = time_entries(:time_entries_001)
17 | @time_entry.contract_id = @contract.id
18 | @time_entry.project_id = @project.id
19 | @time_entry.issue_id = @issue.id
20 | assert @time_entry.save
21 | @user = @project.users.first
22 | end
23 |
24 | test "should not save without project contract id" do
25 | @contract.project_contract_id = nil
26 | assert !@contract.save
27 | end
28 |
29 | test "project contract id should be unique" do
30 | @contract2.project_id = @contract.project_id
31 | @contract2.project_contract_id = @contract.project_contract_id
32 | assert !@contract2.save
33 | end
34 |
35 | test "project contract id should be more than 0" do
36 | @contract2.project_contract_id = -1
37 | assert !@contract2.save
38 | @contract2.project_contract_id = 0
39 | assert !@contract2.save
40 | end
41 |
42 | test "project contract id should be less than 1000" do
43 | @contract2.project_contract_id = 1000
44 | assert !@contract2.save
45 | end
46 |
47 | test "project contract id should be between 0 and 1000" do
48 | @contract2.project_contract_id = 444
49 | assert @contract2.save
50 | end
51 |
52 | test "should not save without start date" do
53 | @contract.start_date = nil
54 | assert !@contract.save
55 | end
56 |
57 | test "should save without end date" do
58 | @contract.end_date = nil
59 | assert @contract.save
60 | end
61 |
62 | test "should save without agreement date" do
63 | @contract.agreement_date = nil
64 | assert @contract.save
65 | end
66 |
67 | test "should not save without hourly rate" do
68 | @contract.hourly_rate = nil
69 | assert !@contract.save
70 | end
71 |
72 | test "should not save without purchase amount" do
73 | @contract.purchase_amount = nil
74 | assert !@contract.save
75 | end
76 |
77 | test "should not save without project id" do
78 | @contract.project_id = nil
79 | assert !@contract.save
80 | end
81 |
82 | test "agreement date can come after start date" do
83 | @contract.agreement_date = @contract.start_date + 7
84 | assert @contract.save
85 | end
86 |
87 | test "start date should come before end date" do
88 | @contract.end_date = @contract.start_date - 7
89 | assert !@contract.save
90 | end
91 |
92 | test "should have time entries" do
93 | assert_respond_to @contract, "time_entries"
94 | assert_equal @contract.time_entries.count, 1
95 | end
96 |
97 | test "should have members with time entries" do
98 | assert_equal @contract, @time_entry.reload.contract
99 | assert_equal 2, @time_entry.user.id
100 | assert_equal 1, @contract.time_entries.reload.size
101 | assert_equal [@time_entry.user], @contract.members_with_entries
102 | end
103 |
104 | test "should have a user project rate or default rate" do
105 | assert_equal @contract.hourly_rate.to_f, @contract.user_project_rate_or_default(@contract.project.users.first).to_f
106 | end
107 |
108 | test "should calculate total hours spent" do
109 | assert_equal @contract.hours_spent, @time_entry.hours
110 | end
111 |
112 | test "should calculate the billable amount for a contract based upon contractor-specific rates" do
113 | billable = @time_entry.hours * @contract.user_project_rate_or_default(@time_entry.user)
114 | @contract.billable_amount_total = @contract.calculate_billable_amount_total
115 | assert_equal billable, @contract.billable_amount_total
116 | end
117 |
118 | test "should calculate dollar amount remaining for contract" do
119 | @contract.billable_amount_total = @contract.calculate_billable_amount_total
120 | amount_remaining = @contract.purchase_amount - (@contract.billable_amount_total)
121 | assert_equal @contract.amount_remaining, amount_remaining
122 | end
123 |
124 | test "should set rates accessor" do
125 | rates = {"3"=>"27.00", "1"=>"35.00"}
126 | @contract.rates = rates
127 | assert_equal rates, @contract.rates
128 | end
129 |
130 | test "should apply rates to project's user project rates after save" do
131 | assert_equal 2, @project.users.size
132 | rate_hash = {}
133 | @project.users.each do |user|
134 | rate_hash[user.id.to_s] = '25.00'
135 | end
136 | @contract.rates = rate_hash
137 | @contract.project_id = @project.id
138 | assert @contract.save
139 | @project.users.each do |user|
140 | assert_equal 25.00, @project.rate_for_user(user)
141 | end
142 | end
143 |
144 | test "should have many user contract rates" do
145 | assert_respond_to @contract, :user_contract_rates
146 | assert_not_nil @user
147 | assert_equal 0, @contract.user_contract_rates.size
148 | ucr = @contract.user_contract_rates.create!(:user_id => @user.id, :rate => 37.50)
149 | assert_equal @user, ucr.user
150 | assert_equal 37.50, ucr.rate
151 | end
152 |
153 | test "should get a user project rate by user" do
154 | assert_not_nil @user
155 | ucr = @contract.user_contract_rates.create!(:user_id => @user.id, :rate => 48.00)
156 | assert_equal ucr, @contract.user_contract_rate_by_user(@user)
157 | end
158 |
159 | test "should get a rate for a user" do
160 | assert_not_nil @user
161 | @contract.user_contract_rates.create!(:user_id => @user.id, :rate => 25.00)
162 | assert_equal 25.00, @contract.rate_for_user(@user)
163 | end
164 |
165 | test "should set a user rate" do
166 | assert_not_nil @user
167 | assert_equal 0, @contract.user_contract_rates.size
168 | assert_nil @contract.user_contract_rate_by_user(@user)
169 | @contract.set_user_contract_rate(@user, 37.25)
170 | assert_equal 37.25, @contract.rate_for_user(@user)
171 | end
172 |
173 | test "should get a sum of contract expenses" do
174 | assert_equal 0, @contract.contracts_expenses.size
175 | assert_equal 0.0, @contract.expenses_total
176 | 2.times do
177 | ContractsExpense.create!(:name => 'Foo', :expense_date => '2013-05-15', :amount => 1.11, :contract_id => @contract.id)
178 | end
179 | assert_equal 2, @contract.contracts_expenses.reload.size
180 | assert_equal 2.22, @contract.expenses_total
181 | end
182 |
183 | end
184 |
--------------------------------------------------------------------------------
/config/locales/nl.yml:
--------------------------------------------------------------------------------
1 | # Dutch strings go here for Rails i18n
2 | nl:
3 | project_module_contracts: Contracten
4 | permission_view_all_contracts_for_project: Alle contracten voor project bekijken
5 | permission_view_contract_details: Contractdetails bekijken
6 | permission_edit_contracts: Contracten bewerken
7 | permission_create_contracts: Contracten aanmaken
8 | permission_delete_contracts: Contracten verwijderen
9 | permission_view_hourly_rate: Bekijk uurtarief
10 | permission_create_expenses: Kostenposten aanmaken
11 | permission_edit_expenses: Kostenposten bewerken
12 | permission_delete_expenses: Kostenposten verwijderen
13 | permission_view_expenses: Kostenposten bekijken
14 |
15 | contracts: Contracten
16 | label_contracts: Contracten
17 | label_contract: Contracten
18 | label_contractors_rates: Uurtarieven
19 | label_new_contract: Nieuw Contract
20 | label_editing_contract: Contract bewerken
21 | label_add_time_entries: Tijd registreren
22 | label_add_time_entries: Tijdregistraties in bulk toewijzen
23 | label_add_expense: Kostenpost toevoegen
24 | label_log_time: Tijd registreren
25 | label_edit: Bewerken
26 | label_delete: Verwijderen
27 | label_view_contract: Contract bekijken
28 | label_view_invoice: Factuur bekijken
29 | label_date_period: Datumbereik
30 | label_amount_purchased: Aantal aangeschaft
31 | label_invoice: Factuur
32 | label_members: Leden
33 | label_hours: Uren
34 | label_contractor_rate: Uurtarief
35 | label_billable_amount: Factureerbaar aantal
36 | label_time_entries: Tijdregistraties
37 | label_hourly_priced_contracts: Uurcontracten
38 | label_effective_rate: Effectieve uurtarieven
39 | label_fixed_priced_contracts: Vaste prijs contracten
40 | label_add_to_contract: Aan contract toevoegen
41 | label_date: Datum
42 | label_current_contract: Huidig contract
43 | label_member: Lid
44 | label_activity: Activiteit
45 | label_issue: Incident
46 | label_cost: Kosten
47 | label_comments: Commentaar
48 | label_apply: Toepassen
49 | label_name: Naam
50 | label_agreed_on: Akkoord op
51 | label_purchased: Gekocht
52 | label_remaining: Resterend
53 | label_hours_worked: Uren gewerkt
54 | label_hours_left: "~Uren resterend"
55 | label_total_purchased: Totaal gekocht voor alle contracten
56 | label_total_remaining: Totaal resterend voor alle contracten
57 | label_total_fixed: Totaal gekocht voor vaste prijs contracten
58 | label_total_hourly: Totaal gekocht voor uurcontracten
59 | label_remaining_hourly: Totaal resterend voor uurcontracten
60 | label_or: "-of-"
61 | label_create_contract: Contract aanmaken
62 | label_update_contract: Contract bijwerken
63 | label_contract_empty: Geen contract
64 | label_select_contract: "[Selecteer een contract]"
65 | label_save_expense: Kostenpost opslaan
66 | label_expenses: Kostenposten
67 | label_summary: Samenvatting
68 | label_description: Omschrijving
69 | label_amount: Aantal
70 | label_edit_expense: Kostenpost bewerken
71 | label_new_expense: Nieuwe kostenpost
72 | label_lock: Sluiten
73 | label_unlock: Openen
74 | label_show_locked_contracts: Toon gesloten contracten
75 |
76 | text_contract_list: Contractenoverzicht
77 | text_are_you_sure_delete_title: "Weet je zeker dat je %{value} wilt verwijderen?"
78 | text_are_you_sure_delete_time_entry: "Weet je zeker dat je deze tijdregistratie wilt verwijderen?"
79 | text_are_you_sure_delete_expense: "Weet je zeker dat je deze kostenpost wilt verwijderen?"
80 | text_time_exceeded_time_remaining: "Deze tijdregistatie overschrijdt de resterende tijd op het contract met %{hours_over} uren.\nPas de tijdregistratie aan zodat deze niet meer dan %{hours_remaining} is om binnen het contract te blijven."
81 | text_must_come_after_agreement_date: moet op of na de datum van akkoord zijn
82 | text_must_come_after_start_date: moet na de startdatum zijn
83 | text_contract_saved: Contract succesvol opgeslagen!
84 | text_contract_updated: Contract succesvol bijgewerkt!
85 | text_contract_deleted: Contract succesvol verwijderd
86 | text_hours_over_contract: "Je bent nu %{hours_over} uren over de limiet van het contract heen."
87 | text_split_time_entry_saved: Je tijdregistratie is gesplitst naar twee tijdregistraties, omdat het de resterende tijd op het geselecteerde contract overschreed. De tweede tijdregistratie is toegevoegd aan een nieuw contract in afwachting van akkoord.
88 | text_one_time_entry_saved: Een nieuw contract in afwachting van akkoord is aangemaakt en je tijdregistratie is opgeslagen, omdat het contract dat je hebt geselecteerd geen resterende tijd meer bevat.
89 | text_hours_too_large: is invalid. The amount exceeds the time remaining plus the size of a new contract.
90 | text_invalid_rate: "Uurtarief moet 0 of hoger zijn."
91 | text_invalid_issue_id: "is een ongeldig incident ID"
92 | text_invalid_hours: "is ongeldig. Het contract %{title} heeft slechts %{hours} resterende uren. Vraag je beheerder om het automatisch aanmaken van contracten in te schakelen in de contract instellingen."
93 | text_second_time_entry_failure: "er is iets fout gegaan. De tweede tijdregistratie voor deze gesplitste tijdregistratie kon niet worden opgeslagen. %{error}"
94 | text_auto_contract_failure: "er is iets fout gegaan. Het nieuwe contract voor deze gesplitste tijdregistratie kon niet worden opgeslagen. %{error}"
95 | text_expense_created: 'Kostenpost is succesvol aangemaakt.'
96 | text_expense_updated: 'Kostenpost is succesvol bijgewerkt.'
97 | text_agreement_pending: 'wacht op akkoord'
98 | text_no_end_date: 'geen einddatum'
99 | text_na: 'n.v.t.'
100 | text_contract_locked: Contract is succesvolg gesloten.
101 | text_contract_unlocked: Contract is succesvol geopend.
102 | text_locked_contract_cache_updated: Deze tijdregistratie is gekoppeld met een gesloten contract. De gecachte waarden van het contract zijn bijgewerkt om je wijzigingen zichtbaar te maken.
103 | text_expenses_uneditable: Kostenposten voor dit contract zijn niet te bewerken (contract is gesloten).
104 | text_expense_deleted: Kostenpost verwijderd.
105 | text_apply: Toepassen
106 | text_previous_id: "Het laatste contract dat is aangemaakt voor dit project heeft ID %{previous_id}"
107 | text_contract_category_helper: "[Optioneel] Gebruikt om contracten makkelijker te identificeren door de titel te varieren. Deze worden beheerd als enumeraties."
108 | text_contract_title_helper: "[Optioneel] Gebruikt om de standaardtitel te overschrijven."
109 | text_hour: / u
110 |
111 | field_contract: Contract
112 | field_project_contract: Contract ID
113 | field_description: Omschrijving
114 | field_agreement_date: Datum akkoord
115 | field_start_date: Startdatum
116 | field_end_date: Einddatum
117 | field_purchase_amount: Gekocht aantal
118 | field_hourly_rate: Uurtarief
119 | field_contract_url: Contract URL
120 | field_invoice_url: Factuur URL
121 | field_is_fixed_price: Vaste prijs
122 |
123 | field_expense_name: Naam
124 | field_expense_date: Datum kostenpost
125 | field_amount: Aantal
126 | field_expense_contract: Contract
127 | field_issue: Incident (ID)
128 |
129 | enumeration_contract_categories: Contract categorieën
130 |
--------------------------------------------------------------------------------
/app/views/contracts/show.html.erb:
--------------------------------------------------------------------------------
1 |