diff --git a/oca_dependencies.txt b/oca_dependencies.txt index b3deeb47..5cdf7f9c 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -1,4 +1,6 @@ # list the OCA project dependencies, one per line +account-budgeting + # add a github url if you need a forked version odoo-addons https://github.com/avanzosc/odoo-addons.git diff --git a/project_budget/README.rst b/project_budget/README.rst new file mode 100644 index 00000000..11953910 --- /dev/null +++ b/project_budget/README.rst @@ -0,0 +1,50 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +============== +Project Budget +============== + +This module extends the functionality of budgets for projects. When a +project is created it will create a budget for current year with: + +* 12 lines of outgoings (one per month) +* 12 lines of incomings (one per month) +* And after checking it on project settings: + + * 1 line on december 30th for outgoings + * 1 line on december 30th for incomings + +It also allows creating those 26 budgetary lines for a budget with project +using a button. + +When installing the module it creates two budgetary positions that must be +configured. + +Configuration +============= + +To configure this module, you need to: + +#. Go to *Invoicing* > *Configuration* > *Management* > *Budgetary Positions*. +#. Configure **Income** and **Outcome** accounts. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, +please check there if your issue has already been reported. If you spotted +it first, help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Oihane Crucelaegui +* Ana Juaristi + +Do not contact contributors directly about support or help with technical issues. diff --git a/project_budget/__init__.py b/project_budget/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/project_budget/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/project_budget/__manifest__.py b/project_budget/__manifest__.py new file mode 100644 index 00000000..f9e852a0 --- /dev/null +++ b/project_budget/__manifest__.py @@ -0,0 +1,29 @@ +# Copyright 2018 Oihane Crucelaegui - AvanzOSC +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +{ + "name": "Project Bugdet", + "version": "12.0.1.0.0", + "category": "Project", + "license": "AGPL-3", + "author": "AvanzOSC", + "website": "http://www.avanzosc.es", + "depends": [ + "account_budget_oca", + "account_budget_template", + "project", + ], + "data": [ + "security/ir.model.access.csv", + "security/project_budget_groups.xml", + "data/project_budget_data.xml", + "views/crossovered_budget_view.xml", + "views/crossovered_budget_line_view.xml", + "views/project_project_view.xml", + "views/account_analytic_account_view.xml", + "views/res_config_settings_view.xml", + "wizards/project_budget_search_view.xml", + "wizards/project_initial_budget_view.xml", + ], + "installable": True, +} diff --git a/project_budget/data/project_budget_data.xml b/project_budget/data/project_budget_data.xml new file mode 100644 index 00000000..fb382fc7 --- /dev/null +++ b/project_budget/data/project_budget_data.xml @@ -0,0 +1,35 @@ + + + + 000 + Income + + True + + + + 001 + Expenses + + True + + + + Income + + + + + Outcome + + + + + Income / Outcome monthly + + monthly + + diff --git a/project_budget/i18n/es.po b/project_budget/i18n/es.po new file mode 100644 index 00000000..fa586cca --- /dev/null +++ b/project_budget/i18n/es.po @@ -0,0 +1,387 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_budget +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-12-02 11:36+0000\n" +"PO-Revision-Date: 2019-12-02 11:36+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__budget_count +msgid "# Budgets" +msgstr "Presupuestos" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__current_budget_count +msgid "# Current Budgets" +msgstr "Presupuesto actual" + +#. module: project_budget +#: model_terms:ir.actions.act_window,help:project_budget.project_budget_action +msgid "A budget is a forecast of your company's income and/or expenses expected for a period in the future. A budget is defined on some financial accounts and/or analytic accounts (that may represent projects, departments, categories of products, etc.)" +msgstr "Un presupuesto es una proyección de los ingresos y/o gastos esperados en su compañía en un período en el futuro. Se define en varias cuentas financieras y/o analíticas (que pueden representar proyectos, departamentos, categorías de productos, etc)" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__active +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Active" +msgstr "Activo" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__sum_amount +msgid "Amount Sum" +msgstr "Previsión final" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_account_analytic_account +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Analytic Account" +msgstr "Cuenta analítica" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Archived" +msgstr "Archivado" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_crossovered_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Budget" +msgstr "Presupuesto" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__budget_active +msgid "Budget Active" +msgstr "Presupuesto activado" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__budget_date +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__budget_date +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__date +msgid "Budget Date" +msgstr "Fecha del presupuesto" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_crossovered_budget_lines +msgid "Budget Line" +msgstr "Línea de Presupuesto" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_account_analytic_account__crossovered_budget_line_ids +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_pivot_view +msgid "Budget Lines" +msgstr "Líneas de Presupuesto" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Budget Lines Pivot Report" +msgstr "Informe pivot" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Budgetary Position" +msgstr "Posición Presupuestaria" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_search_view +msgid "Budgeted" +msgstr "Presupuestado" + +#. module: project_budget +#: model:ir.actions.act_window,name:project_budget.project_budget_action +#: model:ir.model.fields,field_description:project_budget.field_project_project__budget_ids +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_form_view +msgid "Budgets" +msgstr "Presupuestos" + +#. module: project_budget +#: model_terms:ir.actions.act_window,help:project_budget.project_budget_action +msgid "By keeping track of where your money goes, you may be less likely to overspend, and more likely to meet your financial goals. Forecast a budget by detailing the expected revenue per analytic account and monitor its evolution based on the actuals realised during that period." +msgstr "Haciendo un seguimiento de dónde va su dinero, puede ser menos probable gastar demasiado, y más probable alcanzar sus metas financieras. Prevea un presupuesto detallando los ingresos esperados por cuenta analítica y monitorice su evolución basada en lo real obtenido durante ese periodo." + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +#: model_terms:ir.ui.view,arch_db:project_budget.project_initial_budget_view_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: project_budget +#: model:ir.model.fields,help:project_budget.field_project_budget_search__initial_budget +msgid "Checking this will search only those with initial budgets" +msgstr "Marcando este check solo buscará por presupuestos iniciales" + +#. module: project_budget +#: model_terms:ir.actions.act_window,help:project_budget.project_budget_action +msgid "Click to create a new budget." +msgstr "Pulse para crear un nuevo presupuesto." + +#. module: project_budget +#: model:ir.model,name:project_budget.model_res_config_settings +msgid "Config Settings" +msgstr "Opciones de Configuración" + +#. module: project_budget +#: model:ir.actions.act_window,name:project_budget.project_initial_budget_wizard_action +#: model:ir.ui.view,arch_db:project_budget.project_initial_budget_view_form +msgid "Create Initial Budget" +msgstr "Crear presupuesto inicial" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Create Lines" +msgstr "Crear elementos" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_res_config_settings__summary_line +msgid "Create a line on december 30th as summary" +msgstr "Crear una línea el día 30 de diciembre como resumen" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__create_uid +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__create_date +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__current_budget_id +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_form_view2 +msgid "Current Budget" +msgstr "Presupuesto actual" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__display_name +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Group By" +msgstr "Agrupar por" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__has_current_budget +msgid "Has Current Budget" +msgstr "Tiene presupuesto actual" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__id +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__id +msgid "ID" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__initial +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Initial" +msgstr "Inicial" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__initial_budget +msgid "Initial Budget" +msgstr "Presupuesto inicial" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__initial_budget_line_id +msgid "Initial Budget Line" +msgstr "Elemento de presupuesto inicial" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Initial Budgets" +msgstr "Presupuestos iniciales" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__initial_planned_amount +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_tree_view +msgid "Initial Planned Amount" +msgstr "Presupuesto inicial" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.account_analytic_account_last_budget_lines_form_view +msgid "Initial planned amount" +msgstr "Importe planificado inicial" + +#. module: project_budget +#: code:addons/project_budget/models/project_project.py:70 +#, python-format +msgid "Initial {} budget: {}" +msgstr "Presupuesto inicial {}: {}" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "It will create a line on december 30th of the last year." +msgstr "Creará una línea con fecha 30 de diciembre del último año." + +#. module: project_budget +#: model:ir.model.fields,help:project_budget.field_res_config_settings__summary_line +msgid "It will create a line on december 30th, if more than one year is selected only for the last year." +msgstr "Creará una línea con fecha de 30 de diciembre, si se han seleccionado más de un año se pondrá en el último año." + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search____last_update +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__write_uid +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__write_date +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__max_date +msgid "Max. Date" +msgstr "Fecha máx." + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__min_date +msgid "Min. Date" +msgstr "Fecha min." + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__notes +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_form_view +msgid "Notes" +msgstr "Notas" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__practical_amount +msgid "Practical Amount" +msgstr "Importe Real" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_project_project +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__project_id +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__project_id +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__project_ids +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Project" +msgstr "Proyecto" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "Project Budget" +msgstr "Presupuesto de proyecto" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__project_user_id +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__project_user_id +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Project Manager" +msgstr "Responsable de proyecto" + +#. module: project_budget +#: model:ir.actions.server,name:project_budget.action_budget_line_compute_amount +msgid "Recompute Amounts" +msgstr "Recalcular importes" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Recompute Line Amount" +msgstr "Recalcular importe de línea" + +#. module: project_budget +#: model:ir.actions.server,name:project_budget.action_budget_compute_amount +msgid "Recompute Line Amounts" +msgstr "Recalcular importes de línea" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +msgid "Search Projects With Budgets" +msgstr "Buscar proyectos que contengan presupuesto" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +msgid "Search Projects Without Budgets" +msgstr "Buscar proyectos que no contengan presupuesto" + +#. module: project_budget +#: model:ir.actions.act_window,name:project_budget.action_project_budget_search +#: model:ir.ui.menu,name:project_budget.project_budget_search_menuitem +msgid "Search Projects by Budget Dates" +msgstr "Busqueda de proyectos por fechas de presupuesto" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "Show a smartbutton to access directly to current month budget." +msgstr "Mostrar un smartbutton para acceder directamente al presupuesto del mes en curso." + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_res_config_settings__group_smartbutton_actual_budget +#: model:res.groups,name:project_budget.smartbutton_actual_budget +msgid "Smartbutton To Current Month Budget" +msgstr "Smartbutton a presupuesto del mes en curso" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "Specific Settings For Project Budget" +msgstr "Ajustes de presupuesto de proyecto" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Sum Amount Total" +msgstr "Total previsión final" + +#. module: project_budget +#: code:addons/project_budget/models/crossovered_budget.py:62 +#, python-format +msgid "There can only be one initial budget per project and year." +msgstr "Sólo puede haber un presupuesto inicial por proyecto y año." + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "This will recompute all 'Practical Amount' and 'Amount Sum', are you sure?" +msgstr "Esto recalculará las columnas 'Contabilizado' y 'Previsión final', está seguro?" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_search_view +msgid "Unbudgeted" +msgstr "Sin presupuestar" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_project_budget_search +msgid "Wizard to Search Project by Budget Dates" +msgstr "Asistente para busqueda de proyecto por fecha de presupuesto" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_project_initial_budget +msgid "Wizard to create initial budget" +msgstr "Asistente para crear presupuesto inicial" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__year +msgid "Year" +msgstr "Año" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +#: model_terms:ir.ui.view,arch_db:project_budget.project_initial_budget_view_form +msgid "or" +msgstr "o" + diff --git a/project_budget/i18n/project_budget.pot b/project_budget/i18n/project_budget.pot new file mode 100644 index 00000000..c8492e4f --- /dev/null +++ b/project_budget/i18n/project_budget.pot @@ -0,0 +1,387 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_budget +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-12-02 11:36+0000\n" +"PO-Revision-Date: 2019-12-02 11:36+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__budget_count +msgid "# Budgets" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__current_budget_count +msgid "# Current Budgets" +msgstr "" + +#. module: project_budget +#: model_terms:ir.actions.act_window,help:project_budget.project_budget_action +msgid "A budget is a forecast of your company's income and/or expenses expected for a period in the future. A budget is defined on some financial accounts and/or analytic accounts (that may represent projects, departments, categories of products, etc.)" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__active +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Active" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__sum_amount +msgid "Amount Sum" +msgstr "" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_account_analytic_account +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Analytic Account" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Archived" +msgstr "" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_crossovered_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Budget" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__budget_active +msgid "Budget Active" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__budget_date +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__budget_date +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__date +msgid "Budget Date" +msgstr "" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_crossovered_budget_lines +msgid "Budget Line" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_account_analytic_account__crossovered_budget_line_ids +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_pivot_view +msgid "Budget Lines" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Budget Lines Pivot Report" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Budgetary Position" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_search_view +msgid "Budgeted" +msgstr "" + +#. module: project_budget +#: model:ir.actions.act_window,name:project_budget.project_budget_action +#: model:ir.model.fields,field_description:project_budget.field_project_project__budget_ids +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_form_view +msgid "Budgets" +msgstr "" + +#. module: project_budget +#: model_terms:ir.actions.act_window,help:project_budget.project_budget_action +msgid "By keeping track of where your money goes, you may be less likely to overspend, and more likely to meet your financial goals. Forecast a budget by detailing the expected revenue per analytic account and monitor its evolution based on the actuals realised during that period." +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +#: model_terms:ir.ui.view,arch_db:project_budget.project_initial_budget_view_form +msgid "Cancel" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,help:project_budget.field_project_budget_search__initial_budget +msgid "Checking this will search only those with initial budgets" +msgstr "" + +#. module: project_budget +#: model_terms:ir.actions.act_window,help:project_budget.project_budget_action +msgid "Click to create a new budget." +msgstr "" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: project_budget +#: model:ir.actions.act_window,name:project_budget.project_initial_budget_wizard_action +#: model_terms:ir.ui.view,arch_db:project_budget.project_initial_budget_view_form +msgid "Create Initial Budget" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Create Lines" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_res_config_settings__summary_line +msgid "Create a line on december 30th as summary" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__create_uid +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__create_uid +msgid "Created by" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__create_date +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__create_date +msgid "Created on" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__current_budget_id +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_form_view2 +msgid "Current Budget" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__display_name +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__display_name +msgid "Display Name" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Group By" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_project__has_current_budget +msgid "Has Current Budget" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__id +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__id +msgid "ID" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__initial +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Initial" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__initial_budget +msgid "Initial Budget" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__initial_budget_line_id +msgid "Initial Budget Line" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_search_view +msgid "Initial Budgets" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__initial_planned_amount +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_tree_view +msgid "Initial Planned Amount" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.account_analytic_account_last_budget_lines_form_view +msgid "Initial planned amount" +msgstr "" + +#. module: project_budget +#: code:addons/project_budget/models/project_project.py:70 +#, python-format +msgid "Initial {} budget: {}" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "It will create a line on december 30th of the last year." +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,help:project_budget.field_res_config_settings__summary_line +msgid "It will create a line on december 30th, if more than one year is selected only for the last year." +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search____last_update +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget____last_update +msgid "Last Modified on" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__write_uid +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__write_date +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__write_date +msgid "Last Updated on" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__max_date +msgid "Max. Date" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_project_budget_search__min_date +msgid "Min. Date" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__notes +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_form_view +msgid "Notes" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__practical_amount +msgid "Practical Amount" +msgstr "" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_project_project +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__project_id +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__project_id +#: model:ir.model.fields,field_description:project_budget.field_project_initial_budget__project_ids +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Project" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "Project Budget" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__project_user_id +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget_lines__project_user_id +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_line_search_view +msgid "Project Manager" +msgstr "" + +#. module: project_budget +#: model:ir.actions.server,name:project_budget.action_budget_line_compute_amount +msgid "Recompute Amounts" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Recompute Line Amount" +msgstr "" + +#. module: project_budget +#: model:ir.actions.server,name:project_budget.action_budget_compute_amount +msgid "Recompute Line Amounts" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +msgid "Search Projects With Budgets" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +msgid "Search Projects Without Budgets" +msgstr "" + +#. module: project_budget +#: model:ir.actions.act_window,name:project_budget.action_project_budget_search +#: model:ir.ui.menu,name:project_budget.project_budget_search_menuitem +msgid "Search Projects by Budget Dates" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "Show a smartbutton to access directly to current month budget." +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_res_config_settings__group_smartbutton_actual_budget +#: model:res.groups,name:project_budget.smartbutton_actual_budget +msgid "Smartbutton To Current Month Budget" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_config_settings_form_view +msgid "Specific Settings For Project Budget" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "Sum Amount Total" +msgstr "" + +#. module: project_budget +#: code:addons/project_budget/models/crossovered_budget.py:62 +#, python-format +msgid "There can only be one initial budget per project and year." +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.crossovered_budget_form_view +msgid "This will recompute all 'Practical Amount' and 'Amount Sum', are you sure?" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_project_search_view +msgid "Unbudgeted" +msgstr "" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_project_budget_search +msgid "Wizard to Search Project by Budget Dates" +msgstr "" + +#. module: project_budget +#: model:ir.model,name:project_budget.model_project_initial_budget +msgid "Wizard to create initial budget" +msgstr "" + +#. module: project_budget +#: model:ir.model.fields,field_description:project_budget.field_crossovered_budget__year +msgid "Year" +msgstr "" + +#. module: project_budget +#: model_terms:ir.ui.view,arch_db:project_budget.project_budget_search_view_form +#: model_terms:ir.ui.view,arch_db:project_budget.project_initial_budget_view_form +msgid "or" +msgstr "" + diff --git a/project_budget/i18n_extra/es.po b/project_budget/i18n_extra/es.po new file mode 100644 index 00000000..643156fc --- /dev/null +++ b/project_budget/i18n_extra/es.po @@ -0,0 +1,30 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_budget_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-10-01 10:21+0000\n" +"PO-Revision-Date: 2018-10-01 10:21+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_budget +#: model:ir.model.fields,field_description:account_budget_oca.field_crossovered_budget_lines__planned_amount +#: model_terms:ir.ui.view,arch_db:account_budget_oca.crossovered_budget_view_form +msgid "Planned Amount" +msgstr "Previsión" + +#. module: account_budget +#: model:ir.model.fields,field_description:account_budget_oca.field_crossovered_budget_lines__practical_amount +#: model_terms:ir.ui.view,arch_db:account_budget_oca.crossovered_budget_view_form +#: model_terms:ir.ui.view,arch_db:account_budget_oca.view_account_analytic_account_form_inherit_budget +msgid "Practical Amount" +msgstr "Contabilizado" + diff --git a/project_budget/models/__init__.py b/project_budget/models/__init__.py new file mode 100644 index 00000000..ddabcabe --- /dev/null +++ b/project_budget/models/__init__.py @@ -0,0 +1,4 @@ +from . import crossovered_budget +from . import project_project +from . import account_analytic_account +from . import res_config_settings diff --git a/project_budget/models/account_analytic_account.py b/project_budget/models/account_analytic_account.py new file mode 100644 index 00000000..5a8282a2 --- /dev/null +++ b/project_budget/models/account_analytic_account.py @@ -0,0 +1,22 @@ +# Copyright 2018 Alfredo de la Fuente - AvanzOSC +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +from odoo import api, fields, models + + +class AccountAnalyticAccount(models.Model): + _inherit = 'account.analytic.account' + + @api.multi + def _domain_budget_line(self): + if not self: + return [] + all_lines = self.env['crossovered.budget.lines'].search( + [('analytic_account_id', '=', self.id)]) + last_budget = all_lines.mapped('crossovered_budget_id')[-1:] + domain = ( + [('crossovered_budget_id', '=', last_budget.id)] if last_budget + else []) + return domain + + crossovered_budget_line_ids = fields.One2many( + domain=lambda self: self._domain_budget_line()) diff --git a/project_budget/models/crossovered_budget.py b/project_budget/models/crossovered_budget.py new file mode 100644 index 00000000..5a382e4f --- /dev/null +++ b/project_budget/models/crossovered_budget.py @@ -0,0 +1,149 @@ +# Copyright 2018 Oihane Crucelaegui - AvanzOSC +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import _, api, exceptions, fields, models + + +class CrossoveredBudget(models.Model): + _inherit = 'crossovered.budget' + + project_id = fields.Many2one( + comodel_name='project.project', string='Project', + states={'done': [('readonly', True)]}, ondelete='cascade') + project_user_id = fields.Many2one( + comodel_name='res.users', string='Project Manager', + related='project_id.user_id', store=True) + initial = fields.Boolean( + string='Initial', copy=False, states={'done': [('readonly', True)]}) + active = fields.Boolean(string='Active', default=True) + year = fields.Integer(string='Year', compute='_compute_year', store=True) + budget_date = fields.Date( + string='Budget Date', default=lambda s: fields.Date.context_today(s)) + + @api.depends('date_from') + def _compute_year(self): + for record in self: + record.year = record.date_from.year + + @api.multi + def action_create_period(self): + super(CrossoveredBudget, self).action_create_period() + budget_line_obj = self.env['crossovered.budget.lines'] + for budget in self.filtered(lambda b: b.project_id and + b.state == 'draft'): + get_param = self.env['ir.config_parameter'].sudo().get_param + if get_param('project_budget.summary_line', + 'False').lower() == 'true': + budget_posts = budget.budget_tmpl_id.budget_post_ids + vals = { + 'crossovered_budget_id': budget.id, + 'planned_amount': 0.0, + } + ds = budget.date_from + final_date = ds.replace(day=30, month=12) + if final_date < budget.date_to: + for budget_post in budget_posts: + vals.update({ + 'date_from': final_date, + 'date_to': final_date, + 'general_budget_id': budget_post.id, + }) + budget_line_obj.create(vals) + return True + + @api.multi + @api.constrains('project_id', 'initial', 'date_from') + def _check_initial_by_project(self): + for record in self.filtered('project_id'): + if len(self.search([('project_id', '=', record.project_id.id), + ('initial', '=', True), + ('year', '=', record.year)])) > 1: + raise exceptions.ValidationError( + _("There can only be one initial budget per project and " + "year.")) + + @api.multi + @api.returns('self', lambda value: value.id) + def copy(self, default=None): + new = super(CrossoveredBudget, self).copy(default=default) + self.filtered(lambda b: not b.initial).write({ + 'active': False, + }) + return new + + @api.multi + def button_recompute_line_amount(self): + self.mapped('crossovered_budget_line_ids').button_recompute_amount() + + @api.multi + def open_pivot_view(self): + self.ensure_one() + self.button_recompute_line_amount() + action = self.env.ref( + 'account_budget_oca.act_crossovered_budget_lines_view') + action_dict = action.read()[0] + action_dict.update({ + 'view_mode': 'pivot', + 'view_id': False, + 'views': [], + 'domain': [('crossovered_budget_id', '=', self.id)], + }) + return action_dict + + +class CrossoveredBudgetLines(models.Model): + _inherit = "crossovered.budget.lines" + + initial_budget_line_id = fields.Many2one( + comodel_name='crossovered.budget.lines', string='Initial Budget Line') + initial_planned_amount = fields.Float( + string='Initial Planned Amount', digits=0, store=True, + compute='_compute_initial_planned_amount') + notes = fields.Text(string='Notes') + project_id = fields.Many2one( + comodel_name='project.project', string='Project', + related='crossovered_budget_id.project_id', store=True) + project_user_id = fields.Many2one( + comodel_name='res.users', string='Project Manager', + related='crossovered_budget_id.project_id.user_id', store=True) + sum_amount = fields.Float( + string='Amount Sum', compute='_compute_sum_amount', store=True) + budget_active = fields.Boolean( + string='Budget Active', + related='crossovered_budget_id.active', store=True) + budget_date = fields.Date( + string='Budget Date', + related='crossovered_budget_id.budget_date', store=True) + practical_amount = fields.Float(store=True) + + @api.multi + def button_recompute_amount(self): + fields_list = ['practical_amount', 'sum_amount'] + for field in fields_list: + self.env.add_todo(self._fields[field], self) + self.recompute() + + @api.depends('initial_budget_line_id', 'planned_amount', + 'initial_budget_line_id.planned_amount') + def _compute_initial_planned_amount(self): + for line in self: + line.initial_planned_amount = ( + line.initial_budget_line_id.planned_amount + if line.initial_budget_line_id else line.planned_amount) + + @api.multi + @api.returns(None, lambda value: value[0]) + def copy_data(self, default=None): + self.ensure_one() + default = default or {} + default.update({ + 'initial_budget_line_id': + self.initial_budget_line_id.id or self.id, + }) + return super(CrossoveredBudgetLines, self).copy_data(default=default) + + @api.depends('planned_amount', 'practical_amount') + def _compute_sum_amount(self): + for record in self: + record.sum_amount = ( + record.planned_amount + record.practical_amount) diff --git a/project_budget/models/project_project.py b/project_budget/models/project_project.py new file mode 100644 index 00000000..c37845e5 --- /dev/null +++ b/project_budget/models/project_project.py @@ -0,0 +1,82 @@ +# Copyright 2018 Oihane Crucelaegui - AvanzOSC +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import _, api, fields, models +import calendar + + +class ProjectProject(models.Model): + _inherit = 'project.project' + + budget_ids = fields.One2many( + comodel_name='crossovered.budget', inverse_name='project_id', + string='Budgets') + budget_count = fields.Integer( + string='# Budgets', compute='_compute_budget_count') + current_budget_id = fields.Many2one( + string='Current Budget', comodel_name='crossovered.budget', + compute='_compute_budget_count') + current_budget_count = fields.Integer( + string='# Current Budgets', compute='_compute_budget_count') + has_current_budget = fields.Boolean( + string='Has Current Budget', compute='_compute_budget_count', + search='_search_current_budget') + + @api.multi + def _compute_budget_count(self): + today = fields.Date.context_today(self) + month_start = today.replace(day=1) + month_end = ( + today.replace( + day=calendar.monthrange(today.year, today.month)[1])) + for record in self: + record.budget_count = len( + record.with_context(active_test=False).budget_ids) + record.current_budget_id = record.budget_ids.filtered( + lambda b: b.budget_date >= month_start and + b.budget_date <= month_end)[:1] + record.current_budget_count = len(record.current_budget_id) + record.has_current_budget = bool(record.current_budget_id) + + @api.multi + def _search_current_budget(self, operator, value): + today = fields.Date.context_today(self) + month_start = today.replace(day=1) + month_end = ( + today.replace( + day=calendar.monthrange(today.year, today.month)[1])) + current_budgets = self.env['crossovered.budget'].search([ + ('project_id', '<>', False), + ('budget_date', '>=', month_start), + ('budget_date', '<=', month_end)]) + operator = 'in' if ((operator == '=' and value) or + (operator == '!=' and not value)) else 'not in' + return [('id', operator, current_budgets.mapped('project_id').ids)] + + @api.multi + def create_initial_project_budget(self, budget_date=False): + if not budget_date: + budget_date = fields.Date.context_today(self) + budget_obj = self.env['crossovered.budget'] + date_from = budget_date.replace(month=1, day=1) + date_to = budget_date.replace(month=12, day=31) + for record in self.filtered(lambda l: not any(l.budget_ids.filtered( + lambda b: b.initial and b.year == budget_date.year))): + budget = budget_obj.create({ + 'name': _( + u'Initial {} budget: {}').format(budget_date.year, + record.name), + 'initial': True, + 'creating_user_id': record.user_id.id, + 'date_from': date_from, + 'date_to': date_to, + 'budget_date': budget_date, + 'project_id': record.id, + }) + budget.button_compute_lines() + + @api.model + def create(self, values): + created = super(ProjectProject, self).create(values) + created.create_initial_project_budget() + return created diff --git a/project_budget/models/res_config_settings.py b/project_budget/models/res_config_settings.py new file mode 100644 index 00000000..b0e37f58 --- /dev/null +++ b/project_budget/models/res_config_settings.py @@ -0,0 +1,36 @@ +# Copyright 2018 Oihane Crucelaegui - AvanzOSC +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class ProjectConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + group_smartbutton_actual_budget = fields.Boolean( + string='Smartbutton To Current Month Budget', + implied_group='project_budget.smartbutton_actual_budget') + summary_line = fields.Boolean( + string='Create a line on december 30th as summary', + help='It will create a line on december 30th, if more than one year is' + ' selected only for the last year.') + + @api.model + def get_values(self): + res = super(ProjectConfigSettings, self).get_values() + get_param = self.env['ir.config_parameter'].sudo().get_param + # the value of the parameter is a nonempty string + res.update( + summary_line=get_param('project_budget.summary_line', + 'False').lower() == 'true', + ) + return res + + @api.multi + def set_values(self): + super(ProjectConfigSettings, self).set_values() + set_param = self.env['ir.config_parameter'].sudo().set_param + # we store the repr of the values, since the value of the parameter is + # a required string + set_param('project_budget.summary_line', + repr(self.summary_line)) diff --git a/project_budget/security/ir.model.access.csv b/project_budget/security/ir.model.access.csv new file mode 100644 index 00000000..6e15ab60 --- /dev/null +++ b/project_budget/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_project_budget_manager,access_project_budget manager,account_budget_oca.model_crossovered_budget,project.group_project_manager,1,1,1,0 diff --git a/project_budget/security/project_budget_groups.xml b/project_budget/security/project_budget_groups.xml new file mode 100644 index 00000000..a44fccbc --- /dev/null +++ b/project_budget/security/project_budget_groups.xml @@ -0,0 +1,7 @@ + + + + Smartbutton To Current Month Budget + + + diff --git a/project_budget/tests/__init__.py b/project_budget/tests/__init__.py new file mode 100644 index 00000000..726ec7fd --- /dev/null +++ b/project_budget/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_budget diff --git a/project_budget/tests/test_project_budget.py b/project_budget/tests/test_project_budget.py new file mode 100644 index 00000000..0ef3b342 --- /dev/null +++ b/project_budget/tests/test_project_budget.py @@ -0,0 +1,125 @@ +# Copyright 2018 Oihane Crucelaegui - AvanzOSC +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo.tests import common +from odoo import exceptions, fields + + +@common.at_install(False) +@common.post_install(True) +class TestProjectBudget(common.SavepointCase): + + @classmethod + def setUpClass(cls): + super(TestProjectBudget, cls).setUpClass() + cls.project_model = cls.env['project.project'] + cls.wizard = cls.env['project.initial.budget'] + cls.search_model = cls.env['project.budget.search'] + set_param = cls.env['ir.config_parameter'].sudo().set_param + set_param( + 'account_budget_template.budget_template_id', + cls.env.ref('project_budget.project_budget_template').id) + set_param('project_budget.summary_line', True) + cls.project = cls.project_model.create({ + 'name': 'New Project', + }) + + def test_res_config(self): + """Test the config file""" + settings = self.env['res.config.settings'].create({}) + self.assertTrue(settings.summary_line) + settings.summary_line = False + settings.set_values() + self.assertFalse(settings.summary_line) + + def test_project_creation(self): + self.assertTrue(self.project.budget_ids) + self.assertTrue(self.project.budget_ids[:1].initial) + self.assertEquals( + len(self.project.budget_ids), self.project.budget_count) + self.assertTrue(self.project.has_current_budget) + self.assertIn( + self.project, + self.project.search([('has_current_budget', '=', True)])) + self.assertEquals(len( + self.project.mapped('budget_ids.crossovered_budget_line_ids')), 26) + self.project.create_initial_project_budget() + self.assertEquals(len(self.project.budget_ids), 1) + action_dict = self.project.budget_ids[:1].open_pivot_view() + self.assertEquals(action_dict.get('view_mode'), 'pivot') + + def test_initial_budget_per_project_year(self): + new_budget = self.project.budget_ids.copy() + with self.assertRaises(exceptions.ValidationError): + new_budget.initial = True + + def test_budget_line_creation(self): + new_budget = self.project.budget_ids.copy(default={ + 'crossovered_budget_line_ids': [], + }) + new_budget.date_to = fields.Date.today().replace(month=12, day=29) + new_budget.button_compute_lines() + self.assertEquals(len( + new_budget.crossovered_budget_line_ids), 24) + + def test_initial_budget_duplication(self): + old_budget = self.project.budget_ids[:1] + self.assertFalse( + old_budget.crossovered_budget_line_ids[:1].initial_budget_line_id) + old_budget.crossovered_budget_line_ids[:1].write({ + 'planned_amount': 50.0, + }) + self.assertEquals( + old_budget.crossovered_budget_line_ids[:1].sum_amount, + old_budget.crossovered_budget_line_ids[:1].planned_amount + + old_budget.crossovered_budget_line_ids[:1].practical_amount) + new_budget = old_budget.copy() + self.assertEquals( + len(old_budget.crossovered_budget_line_ids), + len(new_budget.crossovered_budget_line_ids)) + new_budget.crossovered_budget_line_ids[:1].write({ + 'planned_amount': 150.0, + }) + self.assertNotEquals( + new_budget.crossovered_budget_line_ids[:1], + new_budget.crossovered_budget_line_ids[:1].initial_budget_line_id) + self.assertEquals( + old_budget.crossovered_budget_line_ids[:1], + new_budget.crossovered_budget_line_ids[:1].initial_budget_line_id) + self.assertEquals( + old_budget.crossovered_budget_line_ids[:1].planned_amount, + new_budget.crossovered_budget_line_ids[:1].initial_planned_amount) + self.assertNotEquals( + new_budget.crossovered_budget_line_ids[:1].planned_amount, + new_budget.crossovered_budget_line_ids[:1].initial_planned_amount) + self.assertEquals( + new_budget.crossovered_budget_line_ids[:1].sum_amount, + new_budget.crossovered_budget_line_ids[:1].planned_amount + + new_budget.crossovered_budget_line_ids[:1].practical_amount) + self.assertNotEquals( + old_budget.crossovered_budget_line_ids[:1].sum_amount, + new_budget.crossovered_budget_line_ids[:1].sum_amount) + + def test_initial_budget_wizard(self): + today = fields.Date.today() + self.assertEquals(len(self.project.budget_ids), 1) + wizard = self.wizard.with_context( + active_ids=[self.project.id]).create({ + 'date': today.replace(year=today.year + 1), + }) + self.assertIn(self.project, wizard.project_ids) + wizard.create_initial_project_budget() + self.assertEquals(len(self.project.budget_ids), 2) + + def test_search_project_budget(self): + wizard = self.search_model.create({ + 'min_date': fields.Date.today(), + 'max_date': fields.Date.today(), + 'initial_budget': True, + }) + action_dict = wizard.search_project_not_in_budget() + self.assertIn( + ('id', 'not in', self.project.ids), action_dict.get('domain')) + action_dict = wizard.search_project_in_budget() + self.assertIn( + ('id', 'in', self.project.ids), action_dict.get('domain')) diff --git a/project_budget/views/account_analytic_account_view.xml b/project_budget/views/account_analytic_account_view.xml new file mode 100644 index 00000000..a50625d5 --- /dev/null +++ b/project_budget/views/account_analytic_account_view.xml @@ -0,0 +1,25 @@ + + + + account.analytic.account.last_budget_lines.form + account.analytic.account + + + + + + + + + + True + + + True + + + True + + + + diff --git a/project_budget/views/crossovered_budget_line_view.xml b/project_budget/views/crossovered_budget_line_view.xml new file mode 100644 index 00000000..b956487e --- /dev/null +++ b/project_budget/views/crossovered_budget_line_view.xml @@ -0,0 +1,130 @@ + + + + tree,form,pivot + [('budget_active','=',True)] + + + + crossovered.budget.line.form + crossovered.budget.lines + + + + + + + + + + + + + True + + + True + + + True + + + + + + + + + + + crossovered.budget.line.tree + crossovered.budget.lines + + + + + + + + + + + + + top + + + + + + True + + + True + + + True + + + + + + + + + crossovered.budget.line.search + crossovered.budget.lines + + + + + + + + + + + + + + + + + + + + + + + crossovered.budget.lines + + + + + + + + + + + + + + Recompute Amounts + ir.actions.server + code + + + +if records: + action = records.button_recompute_amount() + + + diff --git a/project_budget/views/crossovered_budget_view.xml b/project_budget/views/crossovered_budget_view.xml new file mode 100644 index 00000000..8518a26c --- /dev/null +++ b/project_budget/views/crossovered_budget_view.xml @@ -0,0 +1,125 @@ + + + + {'active_test': False} + + + + crossovered.budget.form + crossovered.budget + + + + + + + + + + + + project.project + + + + + + + + + + + + + project.project + + + + + + + + + + + diff --git a/project_budget/views/res_config_settings_view.xml b/project_budget/views/res_config_settings_view.xml new file mode 100644 index 00000000..2b4df8f8 --- /dev/null +++ b/project_budget/views/res_config_settings_view.xml @@ -0,0 +1,36 @@ + + + + res.config.settings + + + +

Project Budget

+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/project_budget/wizards/__init__.py b/project_budget/wizards/__init__.py new file mode 100644 index 00000000..fcec70d9 --- /dev/null +++ b/project_budget/wizards/__init__.py @@ -0,0 +1,2 @@ +from . import project_budget_search +from . import project_initial_budget diff --git a/project_budget/wizards/project_budget_search.py b/project_budget/wizards/project_budget_search.py new file mode 100644 index 00000000..8050a523 --- /dev/null +++ b/project_budget/wizards/project_budget_search.py @@ -0,0 +1,53 @@ +# Copyright 2019 Oihane Crucelaegui - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models +from odoo.models import expression +from odoo.tools.safe_eval import safe_eval + +str2date = fields.Date.from_string + + +class ProjectBudgetSearch(models.TransientModel): + _name = 'project.budget.search' + _description = 'Wizard to Search Project by Budget Dates' + + min_date = fields.Date( + string='Min. Date', required=True, + default=lambda self: fields.Date.context_today(self)) + max_date = fields.Date(string='Max. Date') + initial_budget = fields.Boolean( + string='Initial Budget', + help='Checking this will search only those with initial budgets') + + @api.multi + def search_project_budget(self, operator='in'): + budget_obj = self.env['crossovered.budget'] + projects = self.env['project.project'] + action = self.env.ref('project.open_view_project_all') + action_dict = action.read()[0] if action else {} + for record in self: + min_date = str2date(record.min_date) + max_date = str2date(record.max_date or record.min_date) + budget_domain = [ + ('project_id', '<>', False), + ('budget_date', '>=', min_date), + ('budget_date', '<=', max_date), + ] + if record.initial_budget: + budget_domain = expression.AND([ + [('initial', '=', True)], + budget_domain]) + budgets = budget_obj.search(budget_domain) + projects |= budgets.mapped('project_id') + domain = expression.AND([ + [('id', operator, projects.ids)], + safe_eval(action.domain or '[]')]) + action_dict.update({'domain': domain}) + return action_dict + + def search_project_not_in_budget(self): + return self.search_project_budget(operator='not in') + + def search_project_in_budget(self): + return self.search_project_budget(operator='in') diff --git a/project_budget/wizards/project_budget_search_view.xml b/project_budget/wizards/project_budget_search_view.xml new file mode 100644 index 00000000..574186ca --- /dev/null +++ b/project_budget/wizards/project_budget_search_view.xml @@ -0,0 +1,38 @@ + + + + project.budget.search + +
+ + + + + +
+
+
+
+
+ + + Search Projects by Budget Dates + project.budget.search + form + form + + new + + + +
diff --git a/project_budget/wizards/project_initial_budget.py b/project_budget/wizards/project_initial_budget.py new file mode 100644 index 00000000..9c69a0e2 --- /dev/null +++ b/project_budget/wizards/project_initial_budget.py @@ -0,0 +1,27 @@ +# Copyright 2019 Oihane Crucelaegui - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProjectInitialBudget(models.TransientModel): + _name = 'project.initial.budget' + _description = 'Wizard to create initial budget' + + project_ids = fields.Many2many( + comodel_name='project.project', string='Project') + date = fields.Date( + string='Budget Date', default=lambda s: fields.Date.context_today(s)) + + @api.model + def default_get(self, fields): + rec = super(ProjectInitialBudget, self).default_get(fields) + rec.update({ + 'project_ids': [(6, 0, self.env.context.get('active_ids'))], + }) + return rec + + @api.multi + def create_initial_project_budget(self): + self.ensure_one() + self.project_ids.create_initial_project_budget(budget_date=self.date) diff --git a/project_budget/wizards/project_initial_budget_view.xml b/project_budget/wizards/project_initial_budget_view.xml new file mode 100644 index 00000000..0eba7f51 --- /dev/null +++ b/project_budget/wizards/project_initial_budget_view.xml @@ -0,0 +1,28 @@ + + + + project.initial.budget + +
+ + + + +
+
+
+
+
+ + +