Skip to content

Commit

Permalink
[FIX] contract: Template lines handling
Browse files Browse the repository at this point in the history
* Update contract template lines handling to fix #80
  • Loading branch information
lasley committed Aug 22, 2017
1 parent 7fdeb2c commit 6328eae
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 24 deletions.
1 change: 1 addition & 0 deletions contract/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
from . import account_analytic_contract
from . import account_analytic_account
from . import account_analytic_invoice_line
from . import account_analytic_contract_line
from . import account_invoice
32 changes: 31 additions & 1 deletion contract/models/account_analytic_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class AccountAnalyticAccount(models.Model):
string='Contract Template',
comodel_name='account.analytic.contract',
)
recurring_invoice_line_ids = fields.One2many(
string='Invoice Lines',
comodel_name='account.analytic.invoice.line',
inverse_name='analytic_account_id',
copy=True,
)
date_start = fields.Date(default=fields.Date.context_today)
recurring_invoices = fields.Boolean(
string='Generate recurring invoices automatically',
Expand All @@ -41,15 +47,30 @@ class AccountAnalyticAccount(models.Model):

@api.onchange('contract_template_id')
def _onchange_contract_template_id(self):
""" It updates contract fields with that of the template """
"""Update the contract fields with that of the template.
Take special consideration with the `recurring_invoice_line_ids`,
which must be created using the data from the contract lines. Cascade
deletion ensures that any errant lines that are created are also
deleted.
"""

contract = self.contract_template_id

for field_name, field in contract._fields.iteritems():

if field.name == 'recurring_invoice_line_ids':
lines = self._convert_contract_lines(contract)
self.recurring_invoice_line_ids = lines
continue

if any((
field.compute, field.related, field.automatic,
field.readonly, field.company_dependent,
field.name in self.NO_SYNC,
)):
continue

self[field_name] = self.contract_template_id[field_name]

@api.onchange('recurring_invoices')
Expand All @@ -61,6 +82,15 @@ def _onchange_recurring_invoices(self):
def _onchange_partner_id(self):
self.pricelist_id = self.partner_id.property_product_pricelist.id

@api.multi
def _convert_contract_lines(self, contract):
self.ensure_one()
new_lines = []
for contract_line in contract.recurring_invoice_line_ids:
vals = contract_line._convert_to_write(contract_line.read()[0])
new_lines.append((0, 0, vals))
return new_lines

@api.model
def get_relative_delta(self, recurring_rule_type, interval):
if recurring_rule_type == 'daily':
Expand Down
2 changes: 1 addition & 1 deletion contract/models/account_analytic_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class AccountAnalyticContract(models.Model):
string='Pricelist',
)
recurring_invoice_line_ids = fields.One2many(
comodel_name='account.analytic.invoice.line',
comodel_name='account.analytic.contract.line',
inverse_name='analytic_account_id',
copy=True,
string='Invoice Lines',
Expand Down
50 changes: 50 additions & 0 deletions contract/models/account_analytic_contract_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class AccountAnalyticContractLine(models.Model):

_name = 'account.analytic.contract.line'
_description = 'Contract Lines'
_inherit = 'account.analytic.invoice.line'

analytic_account_id = fields.Many2one(
string='Contract',
comodel_name='account.analytic.contract',
required=True,
ondelete='cascade',
)

@api.multi
@api.onchange('product_id')
def _onchange_product_id(self):
if not self.product_id:
return {'domain': {'uom_id': []}}

vals = {}
domain = {'uom_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):
vals['uom_id'] = self.product_id.uom_id

product = self.product_id.with_context(
lang=self.env.user.partner_id.lang,
partner=self.env.user.partner_id.id,
quantity=self.quantity,
date=fields.Datetime.now(),
pricelist=self.analytic_account_id.pricelist_id.id,
uom=self.uom_id.id,
)

name = product.name_get()[0][1]
if product.description_sale:
name += '\n' + product.description_sale
vals['name'] = name

vals['price_unit'] = product.price
self.update(vals)
return {'domain': domain}
39 changes: 30 additions & 9 deletions contract/models/account_analytic_invoice_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# © 2014 Angel Moya <angel.moya@domatix.com>
# © 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# © 2016 Carlos Dauden <carlos.dauden@tecnativa.com>
# Copyright 2016 LasLabs Inc.
# Copyright 2016-2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models
Expand All @@ -16,23 +16,44 @@ class AccountAnalyticInvoiceLine(models.Model):
_name = 'account.analytic.invoice.line'

product_id = fields.Many2one(
'product.product', string='Product', required=True)
'product.product',
string='Product',
required=True,
)
analytic_account_id = fields.Many2one(
'account.analytic.account', string='Analytic Account')
name = fields.Text(string='Description', required=True)
quantity = fields.Float(default=1.0, required=True)
'account.analytic.account',
string='Analytic Account',
required=True,
ondelete='cascade',
)
name = fields.Text(
string='Description',
required=True,
)
quantity = fields.Float(
default=1.0,
required=True,
)
uom_id = fields.Many2one(
'product.uom', string='Unit of Measure', required=True)
price_unit = fields.Float('Unit Price', required=True)
'product.uom',
string='Unit of Measure',
required=True,
)
price_unit = fields.Float(
'Unit Price',
required=True,
)
price_subtotal = fields.Float(
compute='_compute_price_subtotal',
digits=dp.get_precision('Account'),
string='Sub Total')
string='Sub Total',
)
discount = fields.Float(
string='Discount (%)',
digits=dp.get_precision('Discount'),
help='Discount that is applied in generated invoices.'
' It should be less or equal to 100')
' It should be less or equal to 100',
)

@api.multi
@api.depends('quantity', 'price_unit', 'discount')
Expand Down
2 changes: 2 additions & 0 deletions contract/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
"account_analytic_contract_user","Recurring user","model_account_analytic_contract","account.group_account_user",1,0,0,0
"account_analytic_invoice_line_manager","Recurring manager","model_account_analytic_invoice_line","account.group_account_manager",1,1,1,1
"account_analytic_invoice_line_user","Recurring user","model_account_analytic_invoice_line","account.group_account_user",1,0,0,0
"account_analytic_contract_line_manager","Recurring manager","model_account_analytic_contract_line","account.group_account_manager",1,1,1,1
"account_analytic_contract_line_user","Recurring user","model_account_analytic_contract_line","account.group_account_user",1,0,0,0
59 changes: 46 additions & 13 deletions contract/tests/test_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,28 @@ def setUp(self):
'date_start': '2016-02-15',
'recurring_next_date': '2016-02-29',
})
self.contract_line = self.env['account.analytic.invoice.line'].create({
self.line_vals = {
'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,
})
}
self.acct_line = self.env['account.analytic.invoice.line'].create(
self.line_vals,
)

def test_check_discount(self):
with self.assertRaises(ValidationError):
self.contract_line.write({'discount': 120})
self.acct_line.write({'discount': 120})

def test_contract(self):
self.assertAlmostEqual(self.contract_line.price_subtotal, 50.0)
res = self.contract_line._onchange_product_id()
self.assertAlmostEqual(self.acct_line.price_subtotal, 50.0)
res = self.acct_line._onchange_product_id()
self.assertIn('uom_id', res['domain'])
self.contract_line.price_unit = 100.0
self.acct_line.price_unit = 100.0

self.contract.partner_id = False
with self.assertRaises(ValidationError):
Expand Down Expand Up @@ -122,10 +125,10 @@ def test_onchange_recurring_invoices(self):

def test_uom(self):
uom_litre = self.env.ref('product.product_uom_litre')
self.contract_line.uom_id = uom_litre.id
self.contract_line._onchange_product_id()
self.assertEqual(self.contract_line.uom_id,
self.contract_line.product_id.uom_id)
self.acct_line.uom_id = uom_litre.id
self.acct_line._onchange_product_id()
self.assertEqual(self.acct_line.uom_id,
self.acct_line.product_id.uom_id)

def test_onchange_product_id(self):
line = self.env['account.analytic.invoice.line'].new()
Expand All @@ -134,8 +137,8 @@ def test_onchange_product_id(self):

def test_no_pricelist(self):
self.contract.pricelist_id = False
self.contract_line.quantity = 2
self.assertAlmostEqual(self.contract_line.price_subtotal, 100.0)
self.acct_line.quantity = 2
self.assertAlmostEqual(self.acct_line.price_subtotal, 100.0)

def test_check_journal(self):
contract_no_journal = self.contract.copy()
Expand All @@ -146,7 +149,7 @@ def test_check_journal(self):
contract_no_journal.recurring_create_invoice()

def test_onchange_contract_template_id(self):
""" It should change the contract values to match the template. """
"""It should change the contract values to match the template."""
self.contract.contract_template_id = self.template
self.contract._onchange_contract_template_id()
res = {
Expand All @@ -156,6 +159,36 @@ def test_onchange_contract_template_id(self):
del self.template_vals['name']
self.assertDictEqual(res, self.template_vals)

def test_onchange_contract_template_id_lines(self):
"""It should create invoice lines for the contract lines."""

self.acct_line.unlink()
self.line_vals['analytic_account_id'] = self.template.id
self.env['account.analytic.contract.line'].create(self.line_vals)
self.contract.contract_template_id = self.template

self.assertFalse(self.contract.recurring_invoice_line_ids,
'Recurring lines were not removed.')

self.contract._onchange_contract_template_id()
del self.line_vals['analytic_account_id']

self.assertEqual(len(self.contract.recurring_invoice_line_ids), 1)

for key, value in self.line_vals.items():
test_value = self.contract.recurring_invoice_line_ids[0][key]
try:
test_value = test_value.id
except AttributeError:
pass
self.assertEqual(test_value, value)

def test_send_mail_contract(self):
result = self.contract.action_contract_send()
self.assertEqual(result['res_model'], 'mail.compose.message')

def test_contract_onchange_product_id(self):
"""It should not error and return a blank UoM domain."""
line = self.env['account.analytic.contract.line'].new()
res = line._onchange_product_id()
self.assertFalse(res['domain']['uom_id'])

0 comments on commit 6328eae

Please sign in to comment.