[IMP] Tests and filters added. Add _id/_ids in old property fields

This commit is contained in:
Carlos Incaser
2016-03-23 13:01:15 +01:00
parent 84874d3d76
commit d994fb6411
7 changed files with 147 additions and 37 deletions

View File

@@ -6,29 +6,42 @@
Contract Contract
======== ========
This module helps you to manage contracts with recurring invoices. * This module recover contracts management with recurring invoicing functions.
Usage Usage
===== =====
To use this module, you need to: To use this module, you need to:
#. Go to Sales -> Contracts and select or create a new contract.
#. Check *Generate recurring invoices automatically*.
#. Fill fields and add new lines.
#. To view discount field set *Discount on lines* in user access rights.
#. A cron is created with daily interval, but if you are in debug mode can
click on *Create invoices* to force this action.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/167/8.0 :target: https://runbot.odoo-community.org/runbot/110/9.0
For further information, please visit: For further information, please visit:
* https://www.odoo.com/forum/help-1 * https://www.odoo.com/forum/help-1
Known issues / Roadmap
======================
* Recovery states and others functional fields in Contracts.
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/contract/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/contract/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/contract/issues/new?body=module:%20contract%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. `here <https://github.com/OCA/contract/issues/new?body=module:%20contract%0Aversion:%209.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits Credits
======= =======

View File

@@ -6,6 +6,7 @@
'name': 'Contracts Management recurring', 'name': 'Contracts Management recurring',
'version': '9.0.1.0.0', 'version': '9.0.1.0.0',
'category': 'Other', 'category': 'Other',
'license': 'AGPL-3',
'author': "OpenERP SA,Odoo Community Association (OCA)", 'author': "OpenERP SA,Odoo Community Association (OCA)",
'website': 'http://openerp.com', 'website': 'http://openerp.com',
'depends': ['base', 'account', 'analytic'], 'depends': ['base', 'account', 'analytic'],
@@ -13,6 +14,7 @@
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/contract_cron.xml', 'data/contract_cron.xml',
'views/contract.xml', 'views/contract.xml',
'views/account_invoice_view.xml',
], ],
'installable': True, 'installable': True,
'images': [], 'images': [],

View File

@@ -6,7 +6,7 @@ from dateutil.relativedelta import relativedelta
import logging import logging
import time import time
from openerp import api, exceptions, fields, models from openerp import api, fields, models
from openerp.addons.decimal_precision import decimal_precision as dp from openerp.addons.decimal_precision import decimal_precision as dp
from openerp.exceptions import ValidationError from openerp.exceptions import ValidationError
from openerp.tools.translate import _ from openerp.tools.translate import _
@@ -15,7 +15,7 @@ _logger = logging.getLogger(__name__)
class AccountAnalyticInvoiceLine(models.Model): class AccountAnalyticInvoiceLine(models.Model):
_name = "account.analytic.invoice.line" _name = 'account.analytic.invoice.line'
product_id = fields.Many2one( product_id = fields.Many2one(
'product.product', string='Product', required=True) 'product.product', string='Product', required=True)
@@ -65,7 +65,8 @@ class AccountAnalyticInvoiceLine(models.Model):
vals = {} vals = {}
domain = {'uom_id': [ domain = {'uom_id': [
('category_id', '=', self.product_id.uom_id.category_id.id)]} ('category_id', '=', self.product_id.uom_id.category_id.id)]}
if not self.uom_id or (self.product_id.uom_id.category_id.id != self.uom_id.category_id.id): if not self.uom_id or (self.product_id.uom_id.category_id.id !=
self.uom_id.category_id.id):
vals['uom_id'] = self.product_id.uom_id vals['uom_id'] = self.product_id.uom_id
product = self.product_id.with_context( product = self.product_id.with_context(
@@ -131,32 +132,38 @@ class AccountAnalyticAccount(models.Model):
default=_default_journal, default=_default_journal,
domain="[('type', '=', 'sale'),('company_id', '=', company_id)]") domain="[('type', '=', 'sale'),('company_id', '=', company_id)]")
@api.multi
def copy(self, default=None): def copy(self, default=None):
# Reset next invoice date # Reset next invoice date
default['recurring_next_date'] = \ default['recurring_next_date'] = \
self._defaults['recurring_next_date']() self._defaults['recurring_next_date']()
return super(AccountAnalyticAccount, self).copy(default=default) return super(AccountAnalyticAccount, self).copy(default=default)
@api.onchange('partner_id')
def _onchange_partner_id(self):
self.pricelist_id = self.partner_id.property_product_pricelist.id
@api.onchange('recurring_invoices') @api.onchange('recurring_invoices')
def _onchange_recurring_invoices(self): def _onchange_recurring_invoices(self):
if self.date_start and self.recurring_invoices: if self.date_start and self.recurring_invoices:
self.recurring_next_date = self.date_start self.recurring_next_date = self.date_start
@api.model @api.model
def _insert_markers(self, line, date_start, date_end, date_format): def _insert_markers(self, line, date_start, next_date, date_format):
line = line.replace('#START#', date_start.strftime(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)) line = line.replace('#END#', date_end.strftime(date_format))
return line return line
@api.model @api.model
def _prepare_invoice_line(self, line, invoice_id): def _prepare_invoice_line(self, line, invoice_id):
product = line.product_id product = line.product_id
account_id = product.property_account_income_id.id or \ account_id = (product.property_account_income_id.id or
product.categ_id.property_account_income_categ_id.id product.categ_id.property_account_income_categ_id.id)
contract = line.analytic_account_id contract = line.analytic_account_id
fpos = contract.partner_id.property_account_position_id fpos = contract.partner_id.property_account_position_id
account_id = fpos.map_account(account_id) account_id = fpos.map_account(account_id)
tax_id = fpos.map_tax(product.taxes_id) taxes = fpos.map_tax(product.taxes_id)
name = line.name name = line.name
if 'old_date' in self.env.context and 'next_date' in self.env.context: if 'old_date' in self.env.context and 'next_date' in self.env.context:
lang_obj = self.env['res.lang'] lang_obj = self.env['res.lang']
@@ -173,10 +180,10 @@ class AccountAnalyticAccount(models.Model):
'account_analytic_id': contract.id, 'account_analytic_id': contract.id,
'price_unit': line.price_unit, 'price_unit': line.price_unit,
'quantity': line.quantity, 'quantity': line.quantity,
'uos_id': line.uom_id.id, 'uom_id': line.uom_id.id,
'product_id': line.product_id.id, 'product_id': line.product_id.id,
'invoice_id': invoice_id, 'invoice_id': invoice_id,
'invoice_line_tax_id': [(6, 0, tax_id)], 'invoice_line_tax_ids': [(6, 0, taxes.ids)],
'discount': line.discount, 'discount': line.discount,
} }
@@ -184,19 +191,17 @@ class AccountAnalyticAccount(models.Model):
def _prepare_invoice(self, contract): def _prepare_invoice(self, contract):
if not contract.partner_id: if not contract.partner_id:
raise ValidationError( raise ValidationError(
_('No Customer Defined!'),
_("You must first select a Customer for Contract %s!") % _("You must first select a Customer for Contract %s!") %
contract.name) contract.name)
partner = contract.partner_id partner = contract.partner_id
fpos = partner.property_account_position_id fpos = partner.property_account_position_id
journal_ids = self.env['account.journal'].search( journal = contract.journal_id or self.env['account.journal'].search(
[('type', '=', 'sale'), [('type', '=', 'sale'),
('company_id', '=', contract.company_id.id)], ('company_id', '=', contract.company_id.id)],
limit=1) limit=1)
if not journal_ids: if not journal:
raise ValidationError( raise ValidationError(
_('Error!'), _("Please define a sale journal for the company '%s'.") %
_('Please define a sale journal for the company "%s".') %
(contract.company_id.name or '',)) (contract.company_id.name or '',))
inv_data = { inv_data = {
'reference': contract.code, 'reference': contract.code,
@@ -204,22 +209,19 @@ class AccountAnalyticAccount(models.Model):
'type': 'out_invoice', 'type': 'out_invoice',
'partner_id': partner.id, 'partner_id': partner.id,
'currency_id': partner.property_product_pricelist.currency_id.id, 'currency_id': partner.property_product_pricelist.currency_id.id,
'journal_id': journal_ids.id, 'journal_id': journal.id,
'date_invoice': contract.recurring_next_date, 'date_invoice': contract.recurring_next_date,
'origin': contract.name, 'origin': contract.name,
'fiscal_position': fpos and fpos.id, 'fiscal_position_id': fpos.id,
'payment_term': partner.property_payment_term_id.id, 'payment_term_id': partner.property_payment_term_id.id,
'company_id': contract.company_id.id, 'company_id': contract.company_id.id,
'journal_id': contract.journal_id.id,
'contract_id': contract.id, 'contract_id': contract.id,
} }
# if contract.journal_id:
# inv_data['journal_id'] = contract.journal_id.id
invoice = self.env['account.invoice'].create(inv_data) invoice = self.env['account.invoice'].create(inv_data)
for line in contract.recurring_invoice_line_ids: for line in contract.recurring_invoice_line_ids:
invoice_line_vals = self._prepare_invoice_line(line, invoice.id) invoice_line_vals = self._prepare_invoice_line(line, invoice.id)
self.env['account.invoice.line'].create(invoice_line_vals) self.env['account.invoice.line'].create(invoice_line_vals)
# invoice.button_compute() invoice.compute_taxes()
return invoice return invoice
@api.model @api.model
@@ -235,11 +237,11 @@ class AccountAnalyticAccount(models.Model):
interval = contract.recurring_interval interval = contract.recurring_interval
old_date = next_date old_date = next_date
if contract.recurring_rule_type == 'daily': if contract.recurring_rule_type == 'daily':
new_date = next_date + relativedelta(days=interval - 1) new_date = next_date + relativedelta(days=interval)
elif contract.recurring_rule_type == 'weekly': elif contract.recurring_rule_type == 'weekly':
new_date = next_date + relativedelta(weeks=interval, days=-1) new_date = next_date + relativedelta(weeks=interval)
else: else:
new_date = next_date + relativedelta(months=interval, days=-1) new_date = next_date + relativedelta(months=interval)
ctx = self.env.context.copy() ctx = self.env.context.copy()
ctx.update({ ctx.update({
'old_date': old_date, 'old_date': old_date,

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# (c) 2015 Antiun Ingeniería S.L. - Sergio Teruel
# (c) 2015 Antiun Ingeniería S.L. - Carlos Dauden
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
from . import test_contract

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# © 2016 Incaser Informatica S.L. - Carlos Dauden
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
from openerp.exceptions import ValidationError
from openerp.tests.common import TransactionCase
class TestContract(TransactionCase):
# Use case : Prepare some data for current test case
def setUp(self):
super(TestContract, self).setUp()
self.partner = self.env.ref('base.res_partner_2')
self.product = self.env.ref('product.product_product_2')
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,
})
self.contract_line = self.env['account.analytic.invoice.line'].create({
'analytic_account_id': self.contract.id,
'product_id': self.product.id,
'name': 'Services from #START# to #END#',
'quantity': 1,
'uom_id': self.product.uom_id.id,
'price_unit': 100,
'discount': 50,
})
def test_check_discount(self):
with self.assertRaises(ValidationError):
self.contract_line.write({'discount': 120})
def test_create_invoice(self):
self.contract.recurring_create_invoice()
self.invoice = self.env['account.invoice'].search(
[('contract_id', '=', self.contract.id)])
self.assertTrue(self.invoice)
self.inv_line = self.invoice.invoice_line_ids[0]
self.assertAlmostEqual(self.inv_line.price_subtotal, 50.0)

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Invoice search view with contract -->
<record id="view_account_invoice_filter_contract" model="ir.ui.view">
<field name="name">account.invoice.select.contract</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
<field name="arch" type="xml">
<field name="date" position="after">
<separator/>
<field name="contract_id"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -40,13 +40,16 @@
<div> <div>
<field name="recurring_invoices" class="oe_inline"/> <field name="recurring_invoices" class="oe_inline"/>
<label for="recurring_invoices" /> <label for="recurring_invoices" />
<button class="oe_link" name="recurring_create_invoice" attrs="{'invisible': [('recurring_invoices','!=',True)]}" string="Create invoices" type="object" groups="base.group_no_one"/> <button name="recurring_create_invoice" type="object"
<button attrs="{'invisible': [('recurring_invoices','!=',True)]}" attrs="{'invisible': [('recurring_invoices','!=',True)]}"
name="%(contract.act_recurring_invoices)d" string="Create invoices" class="oe_link"
string="⇒ show invoices" class="oe_link" type="action"/> groups="base.group_no_one"/>
<button name="%(contract.act_recurring_invoices)d" type="action"
attrs="{'invisible': [('recurring_invoices','!=',True)]}"
string="⇒ show invoices" class="oe_link"/>
</div> </div>
<group attrs="{'invisible': [('recurring_invoices','!=',True)]}"> <group attrs="{'invisible': [('recurring_invoices','!=',True)]}">
<field name="journal_id" /> <field name="journal_id"/>
<field name="pricelist_id"/> <field name="pricelist_id"/>
<label for="recurring_interval"/> <label for="recurring_interval"/>
<div> <div>
@@ -87,17 +90,41 @@
</record> </record>
<!-- Analytic Account search view for contract --> <!-- Analytic Account search view for contract -->
<record id="view_account_analytic_account_journal_search" model="ir.ui.view"> <record id="view_account_analytic_account_contract_search" model="ir.ui.view">
<field name="name">account.analytic.account.journal.search</field> <field name="name">account.analytic.account.contract.search</field>
<field name="model">account.analytic.account</field> <field name="model">account.analytic.account</field>
<field name="inherit_id" <field name="inherit_id" ref="analytic.view_account_analytic_account_search"/>
ref="analytic.view_account_analytic_account_search" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="name" position="before"> <field name="name" position="after">
<field name="journal_id"/> <field name="journal_id"/>
<field name="pricelist_id"/>
<filter name="recurring_invoice" string="Recurring Invoice" domain="[('recurring_invoices','=',True)]"/>
<group expand="0" string="Group By...">
<filter string="Next Invoice" domain="[]" context="{'group_by':'recurring_next_date'}"/>
</group>
</field> </field>
</field> </field>
</record> </record>
<!-- Action Sales/Sales/Contracts -->
<record id="action_account_analytic_overdue_all" model="ir.actions.act_window">
<field name="name">Contracts</field>
<field name="res_model">account.analytic.account</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_active':1, 'search_default_recurring_invoice':1}</field>
<field name="search_view_id" ref="analytic.view_account_analytic_account_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new contract.
</p><p>
Use contracts to follow tasks, issues, timesheets or invoicing based on
work done, expenses and/or sales orders. Odoo will automatically manage
the alerts for the renewal of the contracts to the right salesperson.
</p>
</field>
</record>
<menuitem action="action_account_analytic_overdue_all" id="menu_action_account_analytic_overdue_all" sequence="8" parent="base.menu_sales"/>
</data> </data>
</openerp> </openerp>