From 9c3bf1cb031ec24fd41981f1b38584fc215caf60 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 7 Dec 2017 17:30:04 +0100 Subject: [PATCH] [10.0][FIX+IMP] contract: Improve usability and don't fail on wrong data (#130) * [FIX+IMP] contract: Improve usability and don't fail on wrong data * Cron create invoices masked for avoiding silent errors * New constraints for assuring data consistency * UI helps for entering consistent data * Spanish translation * Remove double company_id field on form --- contract/__manifest__.py | 2 +- contract/i18n/es.po | 117 +++++++++++++----- contract/models/account_analytic_account.py | 77 ++++++++++-- contract/tests/test_contract.py | 73 +++++++---- .../views/account_analytic_account_view.xml | 22 +++- .../views/account_analytic_contract_view.xml | 2 +- contract/views/res_partner_view.xml | 2 +- 7 files changed, 221 insertions(+), 74 deletions(-) diff --git a/contract/__manifest__.py b/contract/__manifest__.py index 4fad58ad..60831f92 100644 --- a/contract/__manifest__.py +++ b/contract/__manifest__.py @@ -8,7 +8,7 @@ { 'name': 'Contracts Management - Recurring', - 'version': '11.0.1.1.0', + 'version': '11.0.1.2.0', 'category': 'Contract Management', 'license': 'AGPL-3', 'author': "OpenERP SA, " diff --git a/contract/i18n/es.po b/contract/i18n/es.po index 9c15557c..a3d6a64a 100644 --- a/contract/i18n/es.po +++ b/contract/i18n/es.po @@ -5,6 +5,8 @@ # Translators: # OCA Transbot , 2017 # Pedro M. Baeza , 2017 +# * contract +# msgid "" msgstr "" "Project-Id-Version: Odoo Server 11.0\n" @@ -16,13 +18,11 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: \n" #. module: contract #: model:mail.template,body_html:contract.email_contract_template -msgid "" -"\n" +msgid "\n" "
\n" "

Hello ${object.partner_id.name or ''},

\n" "

A new contract has been created:

\n" @@ -70,8 +70,7 @@ msgid "" "
\n" "\n" " " -msgstr "" -"\n" +msgstr "\n" "
\n" "

Hola ${object.partner_id.name or ''},

\n" "

Se ha creado un nuevo contrato:

\n" @@ -143,7 +142,7 @@ msgstr "Contrato: " #. module: contract #: model:ir.ui.view,arch_db:contract.report_contract_document msgid "Date Start: " -msgstr "Fecha inicio: " +msgstr "Fecha de inicio: " #. module: contract #: model:ir.ui.view,arch_db:contract.report_contract_document @@ -200,15 +199,21 @@ msgstr "Cuenta analítica" #. module: contract #: model:ir.actions.act_window,help:contract.account_analytic_contract_action msgid "Click to create a new contract template." -msgstr "Pinche para crear una nueva plantilla de contrato" +msgstr "Pulse para crear una nueva plantilla de contrato." #. module: contract #: model:ir.actions.act_window,help:contract.action_account_analytic_overdue_all msgid "Click to create a new contract." -msgstr "Pinche para crear un contrato nuevo. " +msgstr "Pulse para crear un contrato nuevo. " #. module: contract #: code:addons/contract/models/account_analytic_account.py:264 +#: model:ir.model.fields,field_description:contract.field_account_analytic_contract_company_id +msgid "Company" +msgstr "Compañía" + +#. module: contract +#: code:addons/contract/models/account_analytic_account.py:321 #, python-format msgid "Compose Email" msgstr "Componer correo electrónico" @@ -226,6 +231,12 @@ msgstr "Contacto" msgid "Contract" msgstr "Contrato" +#. module: contract +#: code:addons/contract/models/account_analytic_account.py:138 +#, python-format +msgid "Contract '%s' start date can't be later than end date" +msgstr "La fecha de inicio del contrato '%s' no puede ser superior a la fecha de fin" + #. module: contract #: model:ir.model,name:contract.model_account_analytic_contract_line msgid "Contract Lines" @@ -233,6 +244,7 @@ msgstr "Líneas de contrato" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_contract_template_id +#: model:ir.model.fields,field_description:contract.field_project_project_contract_template_id #: model:ir.ui.view,arch_db:contract.account_analytic_contract_view_form msgid "Contract Template" msgstr "Plantilla de contrato" @@ -255,6 +267,12 @@ msgstr "Plantillas de contrato" msgid "Contracts" msgstr "Contratos" +#. module: contract +#: model:ir.model.fields,field_description:contract.field_account_analytic_account_create_invoice_visibility +#: model:ir.model.fields,field_description:contract.field_project_project_create_invoice_visibility +msgid "Create invoice visibility" +msgstr "Visibilidad de crear factura" + #. module: contract #: model:ir.ui.view,arch_db:contract.account_analytic_account_recurring_form_form msgid "Create invoices" @@ -276,6 +294,7 @@ msgstr "Creado en" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_date_end +#: model:ir.model.fields,field_description:contract.field_project_project_date_end #: model:ir.ui.view,arch_db:contract.view_account_analytic_account_contract_search msgid "Date End" msgstr "Fecha fin" @@ -283,10 +302,11 @@ msgstr "Fecha fin" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_date_start msgid "Date Start" -msgstr "Fecha inicio" +msgstr "Fecha de inicio" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_next_date +#: model:ir.model.fields,field_description:contract.field_project_project_recurring_next_date msgid "Date of Next Invoice" msgstr "Próxima fecha de factura" @@ -317,12 +337,8 @@ msgstr "El descuento debería ser menor o igual a 100" #. module: contract #: model:ir.model.fields,help:contract.field_account_analytic_contract_line_discount #: model:ir.model.fields,help:contract.field_account_analytic_invoice_line_discount -msgid "" -"Discount that is applied in generated invoices. It should be less or equal " -"to 100" -msgstr "" -"Descuento que es aplicado en las facturas generadas. Debería ser menor o " -"igual a 100" +msgid "Discount that is applied in generated invoices. It should be less or equal to 100" +msgstr "Descuento que es aplicado en las facturas generadas. Debería ser menor o igual a 100" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_display_name @@ -345,6 +361,7 @@ msgstr "Generar facturas recurrentes desde los contratos" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_invoices +#: model:ir.model.fields,field_description:contract.field_project_project_recurring_invoices msgid "Generate recurring invoices automatically" msgstr "Generar facturas recurrentes automáticamente." @@ -368,6 +385,7 @@ msgstr "Factura" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_invoice_line_ids #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_recurring_invoice_line_ids +#: model:ir.model.fields,field_description:contract.field_project_project_recurring_invoice_line_ids #: model:ir.ui.view,arch_db:contract.account_analytic_contract_view_form msgid "Invoice Lines" msgstr "Líneas de factura" @@ -385,12 +403,15 @@ msgstr "Tipo Facturación" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_invoicing_type #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_recurring_invoicing_type +#: model:ir.model.fields,field_description:contract.field_project_project_recurring_invoicing_type +#: model:ir.ui.view,arch_db:contract.account_analytic_contract_view_search msgid "Invoicing type" msgstr "Tipo de facturación" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_journal_id #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_journal_id +#: model:ir.model.fields,field_description:contract.field_project_project_journal_id #: model:ir.ui.view,arch_db:contract.account_analytic_contract_view_search msgid "Journal" msgstr "Diario" @@ -420,8 +441,7 @@ msgstr "Última actualización en" #: model:ir.ui.view,arch_db:contract.account_analytic_account_recurring_form_form #: model:ir.ui.view,arch_db:contract.account_analytic_contract_view_form msgid "Legend (for the markers inside invoice lines description)" -msgstr "" -"Leyenda (para los marcadores dentro de descripción en lineas de factura)" +msgstr "Leyenda (para los marcadores dentro de descripción en lineas de factura)" #. module: contract #: selection:account.analytic.account,recurring_rule_type:0 @@ -445,16 +465,21 @@ msgstr "Nombre" msgid "Next Invoice" msgstr "Próxima factura" +#. module: contract +#: model:ir.model,name:contract.model_res_partner +msgid "Partner" +msgstr "Empresa" + #. module: contract #: model:ir.ui.view,arch_db:contract.view_account_analytic_account_contract_search msgid "Partner and dependents" msgstr "Empresa y contactos" #. module: contract -#: code:addons/contract/models/account_analytic_account.py:170 +#: code:addons/contract/models/account_analytic_account.py:224 #, python-format msgid "Please define a sale journal for the company '%s'." -msgstr "Por favor define un diario de ventas para la compañía '%s'." +msgstr "Por favor defina un diario de ventas para la compañía '%s'." #. module: contract #: selection:account.analytic.account,recurring_invoicing_type:0 @@ -471,6 +496,7 @@ msgstr "Prepago" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_pricelist_id #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_pricelist_id +#: model:ir.model.fields,field_description:contract.field_project_project_pricelist_id #: model:ir.ui.view,arch_db:contract.account_analytic_contract_view_search msgid "Pricelist" msgstr "Lista de precios" @@ -490,6 +516,7 @@ msgstr "Cantidad" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_rule_type #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_recurring_rule_type +#: model:ir.model.fields,field_description:contract.field_project_project_recurring_rule_type #: model:ir.ui.view,arch_db:contract.account_analytic_contract_view_search msgid "Recurrence" msgstr "Recurrencia" @@ -503,12 +530,14 @@ msgstr "Facturas recurrentes" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_interval #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_recurring_interval +#: model:ir.model.fields,field_description:contract.field_project_project_recurring_interval msgid "Repeat Every" msgstr "Repetir cada" #. module: contract #: model:ir.model.fields,help:contract.field_account_analytic_account_recurring_interval #: model:ir.model.fields,help:contract.field_account_analytic_contract_recurring_interval +#: model:ir.model.fields,help:contract.field_project_project_recurring_interval msgid "Repeat every (Days/Week/Month/Year)" msgstr "Repetir cada (días/semana/mes/año)" @@ -532,20 +561,21 @@ msgstr "Secuencia" #: model:ir.model.fields,help:contract.field_account_analytic_contract_line_sequence #: model:ir.model.fields,help:contract.field_account_analytic_invoice_line_sequence msgid "Sequence of the contract line when displaying contracts" -msgstr "Secuencia de la línea de contrato cuando se visualizan los contratos" +msgstr "Secuencia de la linea de contrato cuando se muestra en los contratos" #. module: contract #: model:ir.model.fields,help:contract.field_account_analytic_account_recurring_rule_type #: model:ir.model.fields,help:contract.field_account_analytic_contract_recurring_rule_type +#: model:ir.model.fields,help:contract.field_project_project_recurring_rule_type msgid "Specify Interval for automatic invoice generation." msgstr "Especifica el intervalo para la generación de facturas automática." #. module: contract #: model:ir.model.fields,help:contract.field_account_analytic_account_recurring_invoicing_type #: model:ir.model.fields,help:contract.field_account_analytic_contract_recurring_invoicing_type +#: model:ir.model.fields,help:contract.field_project_project_recurring_invoicing_type msgid "Specify if process date is 'from' or 'to' invoicing date" -msgstr "" -"Especifica si la fecha de proceso es desde o hasta la fecha de facturación" +msgstr "Especifica si la fecha de proceso es desde o hasta la fecha de facturación" #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_contract_line_price_subtotal @@ -573,7 +603,7 @@ msgstr "NIF:" #. module: contract #: model:ir.ui.view,arch_db:contract.view_account_analytic_account_contract_search msgid "Valid" -msgstr "Vigente" +msgstr "Válido" #. module: contract #: selection:account.analytic.account,recurring_rule_type:0 @@ -588,21 +618,43 @@ msgid "Year(s)" msgstr "Año(s)" #. module: contract -#: code:addons/contract/models/account_analytic_account.py:162 +#: code:addons/contract/models/account_analytic_account.py:111 +#, python-format +msgid "You can't have a next invoicing date before the start of the contract '%s'" +msgstr "No puede tener una fecha de próxima factura anterior a la fecha de inicio del contrato '%s'" + +#. module: contract +#: code:addons/contract/models/account_analytic_account.py:216 #, python-format msgid "You must first select a Customer for Contract %s!" msgstr "¡Seleccione un cliente para este contrato %s!" #. module: contract -#: code:addons/contract/models/account_analytic_account.py:217 +#: code:addons/contract/models/account_analytic_account.py:273 #, python-format -msgid "" -"You must review start and end dates!\n" +msgid "You must review start and end dates!\n" "%s" -msgstr "" -"Debe revisar las fechas de inicio y de fin\n" +msgstr "Debe revisar las fechas de inicio y de fin\n" "%s" +#. module: contract +#: code:addons/contract/models/account_analytic_account.py:102 +#, python-format +msgid "You must supply a customer for the contract '%s'" +msgstr "Debe especificar un cliente para el contrato '%s'" + +#. module: contract +#: code:addons/contract/models/account_analytic_account.py:120 +#, python-format +msgid "You must supply a next invoicing date for contract '%s'" +msgstr "Debe suministrar una fecha de próxima factura para el contrato '%s'" + +#. module: contract +#: code:addons/contract/models/account_analytic_account.py:129 +#, python-format +msgid "You must supply a start date for contract '%s'" +msgstr "Debe suministrar una fecha de inicio para el contrato '%s'" + #. module: contract #: model:ir.model,name:contract.model_account_analytic_contract msgid "account.analytic.contract" @@ -615,10 +667,11 @@ msgstr "account.analytic.invoice.line" #. module: contract #: model:ir.ui.view,arch_db:contract.view_partner_form -msgid "show the contracts for this partner" -msgstr "Mostrar los contratos de este partner" +msgid "Show the contracts for this partner" +msgstr "Muestra los contratos para esta empresa/contacto" #. module: contract #: model:ir.ui.view,arch_db:contract.account_analytic_account_recurring_form_form msgid "⇒ Show recurring invoices" msgstr "⇒ Mostrar facturas recurrentes" + diff --git a/contract/models/account_analytic_account.py b/contract/models/account_analytic_account.py index de6464c6..1e1a1db3 100644 --- a/contract/models/account_analytic_account.py +++ b/contract/models/account_analytic_account.py @@ -50,6 +50,17 @@ class AccountAnalyticAccount(models.Model): index=True, default=lambda self: self.env.user, ) + create_invoice_visibility = fields.Boolean( + compute='_compute_create_invoice_visibility', + ) + + @api.depends('recurring_next_date', 'date_end') + def _compute_create_invoice_visibility(self): + for contract in self: + contract.create_invoice_visibility = ( + not contract.date_end or + contract.recurring_next_date <= contract.date_end + ) @api.onchange('contract_template_id') def _onchange_contract_template_id(self): @@ -76,15 +87,60 @@ class AccountAnalyticAccount(models.Model): )): self[field_name] = self.contract_template_id[field_name] - @api.onchange('recurring_invoices') - def _onchange_recurring_invoices(self): - if self.date_start and self.recurring_invoices: + @api.onchange('date_start') + def _onchange_date_start(self): + if self.date_start: self.recurring_next_date = self.date_start @api.onchange('partner_id') def _onchange_partner_id(self): self.pricelist_id = self.partner_id.property_product_pricelist.id + @api.constrains('partner_id', 'recurring_invoices') + def _check_partner_id_recurring_invoices(self): + for contract in self.filtered('recurring_invoices'): + if not contract.partner_id: + raise ValidationError( + _("You must supply a customer for the contract '%s'") % + contract.name + ) + + @api.constrains('recurring_next_date', 'date_start') + def _check_recurring_next_date_start_date(self): + for contract in self.filtered('recurring_next_date'): + if contract.date_start > contract.recurring_next_date: + raise ValidationError( + _("You can't have a next invoicing date before the start " + "of the contract '%s'") % contract.name + ) + + @api.constrains('recurring_next_date', 'recurring_invoices') + def _check_recurring_next_date_recurring_invoices(self): + for contract in self.filtered('recurring_invoices'): + if not contract.recurring_next_date: + raise ValidationError( + _("You must supply a next invoicing date for contract " + "'%s'") % contract.name + ) + + @api.constrains('date_start', 'recurring_invoices') + def _check_date_start_recurring_invoices(self): + for contract in self.filtered('recurring_invoices'): + if not contract.date_start: + raise ValidationError( + _("You must supply a start date for contract '%s'") % + contract.name + ) + + @api.constrains('date_start', 'date_end') + def _check_start_end_dates(self): + for contract in self.filtered('date_end'): + if contract.date_start > contract.date_end: + raise ValidationError( + _("Contract '%s' start date can't be later than end date") + % contract.name + ) + @api.multi def _convert_contract_lines(self, contract): self.ensure_one() @@ -204,8 +260,8 @@ class AccountAnalyticAccount(models.Model): @api.multi def recurring_create_invoice(self): - """ - Create invoices from contracts + """Create invoices from contracts + :return: invoices created """ invoices = self.env['account.invoice'] @@ -213,9 +269,12 @@ class AccountAnalyticAccount(models.Model): ref_date = contract.recurring_next_date or fields.Date.today() if (contract.date_start > ref_date or contract.date_end and contract.date_end < ref_date): + if self.env.context.get('cron'): + continue # Don't fail on cron jobs raise ValidationError( _("You must review start and end dates!\n%s") % - contract.name) + contract.name + ) old_date = fields.Date.from_string(ref_date) new_date = old_date + self.get_relative_delta( contract.recurring_rule_type, contract.recurring_interval) @@ -223,20 +282,20 @@ class AccountAnalyticAccount(models.Model): ctx.update({ 'old_date': old_date, 'next_date': new_date, - # Force company for correct evaluate domain access rules + # Force company for correct evaluation of domain access rules 'force_company': contract.company_id.id, }) # Re-read contract with correct company invoices |= contract.with_context(ctx)._create_invoice() contract.write({ - 'recurring_next_date': new_date.strftime('%Y-%m-%d') + 'recurring_next_date': fields.Date.to_string(new_date) }) return invoices @api.model def cron_recurring_create_invoice(self): today = fields.Date.today() - contracts = self.search([ + contracts = self.with_context(cron=True).search([ ('recurring_invoices', '=', True), ('recurring_next_date', '<=', today), '|', diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 0746c0e9..c82baf0b 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -64,18 +64,14 @@ class TestContract(TestContractBase): res = self.acct_line._onchange_product_id() self.assertIn('uom_id', res['domain']) self.acct_line.price_unit = 100.0 - - self.contract.partner_id = False with self.assertRaises(ValidationError): - self.contract.recurring_create_invoice() + self.contract.partner_id = False self.contract.partner_id = self.partner.id - self.contract.recurring_create_invoice() self.invoice_monthly = self.env['account.invoice'].search( [('contract_id', '=', self.contract.id)]) self.assertTrue(self.invoice_monthly) self.assertEqual(self.contract.recurring_next_date, '2016-03-29') - self.inv_line = self.invoice_monthly.invoice_line_ids[0] self.assertTrue(self.inv_line.invoice_line_tax_ids) self.assertAlmostEqual(self.inv_line.price_subtotal, 50.0) @@ -128,11 +124,11 @@ class TestContract(TestContractBase): self.assertEqual(self.contract.pricelist_id, self.contract.partner_id.property_product_pricelist) - def test_onchange_recurring_invoices(self): - self.contract.recurring_next_date = False - self.contract._onchange_recurring_invoices() - self.assertEqual(self.contract.recurring_next_date, - self.contract.date_start) + def test_onchange_date_start(self): + date = '2016-01-01' + self.contract.date_start = date + self.contract._onchange_date_start() + self.assertEqual(self.contract.recurring_next_date, date) def test_uom(self): uom_litre = self.env.ref('product.product_uom_litre') @@ -159,6 +155,31 @@ class TestContract(TestContractBase): with self.assertRaises(ValidationError): contract_no_journal.recurring_create_invoice() + def test_check_date_end(self): + with self.assertRaises(ValidationError): + self.contract.date_end = '2015-12-31' + + def test_check_recurring_next_date_start_date(self): + with self.assertRaises(ValidationError): + self.contract.write({ + 'date_start': '2017-01-01', + 'recurring_next_date': '2016-01-01', + }) + + def test_check_recurring_next_date_recurring_invoices(self): + with self.assertRaises(ValidationError): + self.contract.write({ + 'recurring_invoices': True, + 'recurring_next_date': False, + }) + + def test_check_date_start_recurring_invoices(self): + with self.assertRaises(ValidationError): + self.contract.write({ + 'recurring_invoices': True, + 'date_start': False, + }) + def test_onchange_contract_template_id(self): """It should change the contract values to match the template.""" self.contract.contract_template_id = self.template @@ -240,24 +261,14 @@ class TestContract(TestContractBase): self.contract.copy() self.assertEqual(self.partner.contract_count, count) - def test_date_end(self): - """It should don't create invoices from finished contract.""" - AccountInvoice = self.env['account.invoice'] - self.contract.date_end = '2015-12-31' - with self.assertRaises(ValidationError): - self.contract.recurring_create_invoice() - init_count = AccountInvoice.search_count( - [('contract_id', '=', self.contract.id)]) - self.contract.cron_recurring_create_invoice() - last_count = AccountInvoice.search_count( - [('contract_id', '=', self.contract.id)]) - self.assertEqual(last_count, init_count) - def test_same_date_start_and_date_end(self): """It should create one invoice with same start and end date.""" AccountInvoice = self.env['account.invoice'] - self.contract.date_start = self.contract.date_end = fields.Date.today() - self.contract.recurring_next_date = self.contract.date_start + self.contract.write({ + 'date_start': fields.Date.today(), + 'date_end': fields.Date.today(), + 'recurring_next_date': fields.Date.today(), + }) init_count = AccountInvoice.search_count( [('contract_id', '=', self.contract.id)]) self.contract.cron_recurring_create_invoice() @@ -266,3 +277,15 @@ class TestContract(TestContractBase): self.assertEqual(last_count, init_count + 1) with self.assertRaises(ValidationError): self.contract.recurring_create_invoice() + + def test_compute_create_invoice_visibility(self): + self.contract.write({ + 'recurring_next_date': '2017-01-01', + 'date_start': '2016-01-01', + 'date_end': False, + }) + self.assertTrue(self.contract.create_invoice_visibility) + self.contract.date_end = '2017-01-01' + self.assertTrue(self.contract.create_invoice_visibility) + self.contract.date_end = '2016-01-01' + self.assertFalse(self.contract.create_invoice_visibility) diff --git a/contract/views/account_analytic_account_view.xml b/contract/views/account_analytic_account_view.xml index 3058a09e..1d268041 100644 --- a/contract/views/account_analytic_account_view.xml +++ b/contract/views/account_analytic_account_view.xml @@ -8,6 +8,9 @@ primary + + {'required': [('recurring_invoices', '=', True)]} +