diff --git a/contract/__openerp__.py b/contract/__openerp__.py index bc9a51cb..c64a0d98 100644 --- a/contract/__openerp__.py +++ b/contract/__openerp__.py @@ -5,7 +5,7 @@ { 'name': 'Contracts Management recurring', - 'version': '9.0.1.0.0', + 'version': '9.0.1.1.0', 'category': 'Contract Management', 'license': 'AGPL-3', 'author': "OpenERP SA," diff --git a/contract/data/contract_cron.xml b/contract/data/contract_cron.xml index 95ae54de..26442c0d 100644 --- a/contract/data/contract_cron.xml +++ b/contract/data/contract_cron.xml @@ -8,7 +8,7 @@ days -1 - + diff --git a/contract/i18n/es.po b/contract/i18n/es.po index 878d56ea..af2995c7 100644 --- a/contract/i18n/es.po +++ b/contract/i18n/es.po @@ -1,21 +1,23 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * contract -# +# # Translators: msgid "" msgstr "" "Project-Id-Version: contract (9.0)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-11 02:47+0000\n" -"PO-Revision-Date: 2016-09-16 21:45+0000\n" -"Last-Translator: OCA Transbot \n" -"Language-Team: Spanish (http://www.transifex.com/oca/OCA-contract-9-0/language/es/)\n" +"POT-Creation-Date: 2016-09-25 22:56+0000\n" +"PO-Revision-Date: 2016-09-26 00:56+0100\n" +"Last-Translator: Carlos Incaser \n" +"Language-Team: Spanish (http://www.transifex.com/oca/OCA-contract-9-0/" +"language/es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: es\n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.5.4\n" #. module: contract #: model:ir.ui.view,arch_db:contract.account_analytic_account_recurring_form_form @@ -51,7 +53,6 @@ msgstr "Contrato" #. module: contract #: model:ir.actions.act_window,name:contract.action_account_analytic_overdue_all #: model:ir.ui.menu,name:contract.menu_action_account_analytic_overdue_all -#: model:ir.ui.menu,name:contract.menu_config_contract msgid "Contracts" msgstr "Contratos" @@ -96,7 +97,7 @@ msgid "Discount (%)" msgstr "Descuento (%)" #. module: contract -#: code:addons/contract/models/contract.py:59 +#: code:addons/contract/models/contract.py:60 #, python-format msgid "Discount should be less or equal to 100" msgstr "El descuento debería ser menor o igual a 100" @@ -106,7 +107,9 @@ msgstr "El descuento 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" +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_invoice_line_display_name @@ -166,24 +169,40 @@ msgstr "Última actualización en" #. module: contract #: model:ir.ui.view,arch_db:contract.account_analytic_account_recurring_form_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 msgid "Month(s)" msgstr "Mes(es)" +#. module: contract +#: selection:account.analytic.account,recurring_rule_type:0 +msgid "Month(s) last day" +msgstr "Mes(es) último día" + #. module: contract #: model:ir.ui.view,arch_db:contract.view_account_analytic_account_contract_search msgid "Next Invoice" msgstr "Próxima factura" #. module: contract -#: code:addons/contract/models/contract.py:196 +#: code:addons/contract/models/contract.py:230 #, 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'." +#. module: contract +#: selection:account.analytic.account,recurring_invoicing_type:0 +msgid "Post-paid" +msgstr "Pospago" + +#. module: contract +#: selection:account.analytic.account,recurring_invoicing_type:0 +msgid "Pre-paid" +msgstr "Prepago" + #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_pricelist_id msgid "Pricelist" @@ -199,6 +218,11 @@ msgstr "Producto" msgid "Quantity" msgstr "Cantidad" +#. module: contract +#: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_invoicing_type +msgid "Invoicing type" +msgstr "Tipo de facturación" + #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_account_recurring_rule_type msgid "Recurrency" @@ -225,6 +249,12 @@ msgstr "Repetir cada (días/semana/mes/año)" 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 +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" + #. module: contract #: model:ir.model.fields,field_description:contract.field_account_analytic_invoice_line_price_subtotal msgid "Sub Total" @@ -251,7 +281,7 @@ msgid "Year(s)" msgstr "Año(s)" #. module: contract -#: code:addons/contract/models/contract.py:188 +#: code:addons/contract/models/contract.py:222 #, python-format msgid "You must first select a Customer for Contract %s!" msgstr "¡Seleccione un cliente para este contrato %s!" diff --git a/contract/models/contract.py b/contract/models/contract.py index 33b72d37..ebd61df6 100644 --- a/contract/models/contract.py +++ b/contract/models/contract.py @@ -7,7 +7,6 @@ from dateutil.relativedelta import relativedelta import logging -import time from openerp import api, fields, models from openerp.addons.decimal_precision import decimal_precision as dp @@ -52,11 +51,13 @@ class AccountAnalyticInvoiceLine(models.Model): else: line.price_subtotal = subtotal - @api.one + @api.multi @api.constrains('discount') def _check_discount(self): - if self.discount > 100: - raise ValidationError(_("Discount should be less or equal to 100")) + for line in self: + if line.discount > 100: + raise ValidationError( + _("Discount should be less or equal to 100")) @api.multi @api.onchange('product_id') @@ -109,6 +110,7 @@ class AccountAnalyticAccount(models.Model): recurring_invoice_line_ids = fields.One2many( comodel_name='account.analytic.invoice.line', inverse_name='analytic_account_id', + copy=True, string='Invoice Lines') recurring_invoices = fields.Boolean( string='Generate recurring invoices automatically') @@ -116,11 +118,19 @@ class AccountAnalyticAccount(models.Model): [('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'), + ('monthlylastday', 'Month(s) last day'), ('yearly', 'Year(s)'), ], default='monthly', string='Recurrency', help="Specify Interval for automatic invoice generation.") + recurring_invoicing_type = fields.Selection( + [('pre-paid', 'Pre-paid'), + ('post-paid', 'Post-paid'), + ], + default='pre-paid', + string='Invoicing type', + help="Specify if process date is 'from' or 'to' invoicing date") recurring_interval = fields.Integer( default=1, string='Repeat Every', @@ -144,12 +154,35 @@ class AccountAnalyticAccount(models.Model): if self.date_start and self.recurring_invoices: self.recurring_next_date = self.date_start + @api.model + def get_relalive_delta(self, recurring_rule_type, interval): + if recurring_rule_type == 'daily': + return relativedelta(days=interval) + elif recurring_rule_type == 'weekly': + return relativedelta(weeks=interval) + elif recurring_rule_type == 'monthly': + return relativedelta(months=interval) + elif recurring_rule_type == 'monthlylastday': + return relativedelta(months=interval, day=31) + else: + return relativedelta(years=interval) + @api.model def _insert_markers(self, line, date_start, next_date, date_format): - line = line.replace('#START#', date_start.strftime(date_format)) - date_end = next_date - relativedelta(days=1) - line = line.replace('#END#', date_end.strftime(date_format)) - return line + contract = line.analytic_account_id + if contract.recurring_invoicing_type == 'pre-paid': + date_from = date_start + date_to = next_date - relativedelta(days=1) + else: + date_from = (date_start - + self.get_relalive_delta(contract.recurring_rule_type, + contract.recurring_interval) + + relativedelta(days=1)) + date_to = date_start + name = line.name + name = name.replace('#START#', date_from.strftime(date_format)) + name = name.replace('#END#', date_to.strftime(date_format)) + return name @api.model def _prepare_invoice_line(self, line, invoice_id): @@ -172,7 +205,7 @@ class AccountAnalyticAccount(models.Model): [('code', '=', contract.partner_id.lang)]) date_format = lang.date_format or '%m/%d/%Y' name = self._insert_markers( - name, self.env.context['old_date'], + line, self.env.context['old_date'], self.env.context['next_date'], date_format) invoice_line_vals.update({ 'name': name, @@ -181,68 +214,61 @@ class AccountAnalyticAccount(models.Model): }) return invoice_line_vals - @api.model - def _prepare_invoice(self, contract): - if not contract.partner_id: + @api.multi + def _prepare_invoice(self): + self.ensure_one() + if not self.partner_id: raise ValidationError( _("You must first select a Customer for Contract %s!") % - contract.name) - journal = contract.journal_id or self.env['account.journal'].search( + self.name) + journal = self.journal_id or self.env['account.journal'].search( [('type', '=', 'sale'), - ('company_id', '=', contract.company_id.id)], + ('company_id', '=', self.company_id.id)], limit=1) if not journal: raise ValidationError( _("Please define a sale journal for the company '%s'.") % - (contract.company_id.name or '',)) + (self.company_id.name or '',)) currency = ( - contract.pricelist_id.currency_id or - contract.partner_id.property_product_pricelist.currency_id or - contract.company_id.currency_id + self.pricelist_id.currency_id or + self.partner_id.property_product_pricelist.currency_id or + self.company_id.currency_id ) invoice = self.env['account.invoice'].new({ - 'reference': contract.code, + 'reference': self.code, 'type': 'out_invoice', - 'partner_id': contract.partner_id.address_get( + 'partner_id': self.partner_id.address_get( ['invoice'])['invoice'], 'currency_id': currency.id, 'journal_id': journal.id, - 'date_invoice': contract.recurring_next_date, - 'origin': contract.name, - 'company_id': contract.company_id.id, - 'contract_id': contract.id, + 'date_invoice': self.recurring_next_date, + 'origin': self.name, + 'company_id': self.company_id.id, + 'contract_id': self.id, + 'user_id': self.partner_id.user_id.id, }) # Get other invoice values from partner onchange invoice._onchange_partner_id() return invoice._convert_to_write(invoice._cache) - @api.model - def _create_invoice(self, contract): - invoice_vals = self._prepare_invoice(contract) + @api.multi + def _create_invoice(self): + self.ensure_one() + invoice_vals = self._prepare_invoice() invoice = self.env['account.invoice'].create(invoice_vals) - for line in contract.recurring_invoice_line_ids: + for line in self.recurring_invoice_line_ids: invoice_line_vals = self._prepare_invoice_line(line, invoice.id) self.env['account.invoice.line'].create(invoice_line_vals) invoice.compute_taxes() return invoice - @api.model - def recurring_create_invoice(self, automatic=False): - current_date = time.strftime('%Y-%m-%d') - contracts = self.search( - [('recurring_next_date', '<=', current_date), - ('account_type', '=', 'normal'), - ('recurring_invoices', '=', True)]) - for contract in contracts: + @api.multi + def recurring_create_invoice(self): + for contract in self: old_date = fields.Date.from_string( contract.recurring_next_date or fields.Date.today()) - interval = contract.recurring_interval - if contract.recurring_rule_type == 'daily': - new_date = old_date + relativedelta(days=interval) - elif contract.recurring_rule_type == 'weekly': - new_date = old_date + relativedelta(weeks=interval) - else: - new_date = old_date + relativedelta(months=interval) + new_date = old_date + self.get_relalive_delta( + contract.recurring_rule_type, contract.recurring_interval) ctx = self.env.context.copy() ctx.update({ 'old_date': old_date, @@ -251,9 +277,16 @@ class AccountAnalyticAccount(models.Model): 'force_company': contract.company_id.id, }) # Re-read contract with correct company - contract = contract.with_context(ctx) - self.with_context(ctx)._create_invoice(contract) + contract.with_context(ctx)._create_invoice() contract.write({ 'recurring_next_date': new_date.strftime('%Y-%m-%d') }) return True + + @api.model + def cron_recurring_create_invoice(self): + contracts = self.search( + [('recurring_next_date', '<=', fields.date.today()), + ('account_type', '=', 'normal'), + ('recurring_invoices', '=', True)]) + return contracts.recurring_create_invoice() diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 7adb7335..a04b8511 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -2,9 +2,6 @@ # © 2016 Carlos Dauden # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from dateutil.relativedelta import relativedelta -import datetime - from openerp.exceptions import ValidationError from openerp.tests.common import TransactionCase @@ -15,14 +12,14 @@ class TestContract(TransactionCase): super(TestContract, self).setUp() self.partner = self.env.ref('base.res_partner_2') self.product = self.env.ref('product.product_product_2') - self.tax = self.env.ref('l10n_generic_coa.sale_tax_template') - self.product.taxes_id = self.tax.ids self.product.description_sale = 'Test description sale' self.contract = self.env['account.analytic.account'].create({ 'name': 'Test Contract', 'partner_id': self.partner.id, 'pricelist_id': self.partner.property_product_pricelist.id, 'recurring_invoices': True, + 'date_start': '2016-02-15', + 'recurring_next_date': '2016-02-29', }) self.contract_line = self.env['account.analytic.invoice.line'].create({ 'analytic_account_id': self.contract.id, @@ -33,11 +30,6 @@ class TestContract(TransactionCase): 'price_unit': 100, 'discount': 50, }) - self.current_date = datetime.date.today() - self.contract_daily = self.contract.copy() - self.contract_daily.recurring_rule_type = 'daily' - self.contract_weekly = self.contract.copy() - self.contract_weekly.recurring_rule_type = 'weekly' def test_check_discount(self): with self.assertRaises(ValidationError): @@ -58,35 +50,53 @@ class TestContract(TransactionCase): self.invoice_monthly = self.env['account.invoice'].search( [('contract_id', '=', self.contract.id)]) self.assertTrue(self.invoice_monthly) - new_date = self.current_date + relativedelta( - months=self.contract.recurring_interval) - self.assertEqual(self.contract.recurring_next_date, - new_date.strftime('%Y-%m-%d')) + self.assertEqual(self.contract.recurring_next_date, '2016-03-29') self.inv_line = self.invoice_monthly.invoice_line_ids[0] self.assertAlmostEqual(self.inv_line.price_subtotal, 50.0) - self.assertTrue(self.inv_line.invoice_line_tax_ids) + self.assertEqual(self.contract.partner_id.user_id, + self.invoice_monthly.user_id) def test_contract_daily(self): - self.contract_daily.pricelist_id = False - self.contract_daily.recurring_create_invoice() + self.contract.recurring_next_date = '2016-02-29' + self.contract.recurring_rule_type = 'daily' + self.contract.pricelist_id = False + self.contract.cron_recurring_create_invoice() invoice_daily = self.env['account.invoice'].search( - [('contract_id', '=', self.contract_daily.id)]) + [('contract_id', '=', self.contract.id)]) self.assertTrue(invoice_daily) - new_date = self.current_date + relativedelta( - days=self.contract_daily.recurring_interval) - self.assertEqual(self.contract_daily.recurring_next_date, - new_date.strftime('%Y-%m-%d')) + self.assertEqual(self.contract.recurring_next_date, '2016-03-01') def test_contract_weekly(self): - self.contract_weekly.recurring_create_invoice() + self.contract.recurring_next_date = '2016-02-29' + self.contract.recurring_rule_type = 'weekly' + self.contract.recurring_invoicing_type = 'post-paid' + self.contract.recurring_create_invoice() + invoices_weekly = self.env['account.invoice'].search( + [('contract_id', '=', self.contract.id)]) + self.assertTrue(invoices_weekly) + self.assertEqual( + self.contract.recurring_next_date, '2016-03-07') + + def test_contract_yearly(self): + self.contract.recurring_next_date = '2016-02-29' + self.contract.recurring_rule_type = 'yearly' + self.contract.recurring_create_invoice() invoices_weekly = self.env['account.invoice'].search( - [('contract_id', '=', self.contract_weekly.id)]) + [('contract_id', '=', self.contract.id)]) self.assertTrue(invoices_weekly) - new_date = self.current_date + relativedelta( - weeks=self.contract_weekly.recurring_interval) - self.assertEqual(self.contract_weekly.recurring_next_date, - new_date.strftime('%Y-%m-%d')) + self.assertEqual( + self.contract.recurring_next_date, '2017-02-28') + + def test_contract_monthly_lastday(self): + self.contract.recurring_next_date = '2016-02-29' + self.contract.recurring_invoicing_type = 'post-paid' + self.contract.recurring_rule_type = 'monthlylastday' + self.contract.recurring_create_invoice() + invoices_monthly_lastday = self.env['account.invoice'].search( + [('contract_id', '=', self.contract.id)]) + self.assertTrue(invoices_monthly_lastday) + self.assertEqual(self.contract.recurring_next_date, '2016-03-31') def test_onchange_partner_id(self): self.contract._onchange_partner_id() diff --git a/contract/views/contract.xml b/contract/views/contract.xml index ca5d34ff..93b2f6b6 100644 --- a/contract/views/contract.xml +++ b/contract/views/contract.xml @@ -32,7 +32,7 @@ attrs="{'invisible': [('recurring_invoices','!=',True)]}" string="⇒ Show recurring invoices" class="oe_link"/> - +