Carlos Dauden
5 years ago
18 changed files with 234 additions and 373 deletions
-
34contract_price_revision/README.rst
-
13contract_price_revision/__manifest__.py
-
88contract_price_revision/i18n/contract_price_revision.pot
-
111contract_price_revision/i18n/es.po
-
3contract_price_revision/models/__init__.py
-
19contract_price_revision/models/account_analytic_account.py
-
39contract_price_revision/models/account_analytic_invoice_line.py
-
31contract_price_revision/models/contract_line.py
-
1contract_price_revision/readme/CONTRIBUTORS.rst
-
2contract_price_revision/readme/USAGE.rst
-
11contract_price_revision/static/description/index.html
-
74contract_price_revision/tests/test_contract_price_revision.py
-
43contract_price_revision/views/account_analytic_account_views.xml
-
26contract_price_revision/views/contract_line.xml
-
2contract_price_revision/wizards/__init__.py
-
54contract_price_revision/wizards/contract_price_revision.py
-
6contract_price_revision/wizards/contract_price_revision_views.xml
-
50contract_price_revision/wizards/create_revision_line.py
@ -1,2 +1 @@ |
|||||
from . import account_analytic_account |
|
||||
from . import account_analytic_invoice_line |
|
||||
|
from . import contract_line |
@ -1,19 +0,0 @@ |
|||||
# Copyright 2019 Tecnativa <vicent.cubells@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
from odoo import api, models |
|
||||
|
|
||||
|
|
||||
class AccountAnalyticAccount(models.Model): |
|
||||
_inherit = "account.analytic.account" |
|
||||
|
|
||||
@api.model |
|
||||
def _prepare_invoice_line(self, line, invoice_id): |
|
||||
line_obj = self.env['account.invoice.line'] |
|
||||
invoice = self.env['account.invoice'].browse( |
|
||||
invoice_id, prefetch=self._prefetch, |
|
||||
) |
|
||||
# Line with automatic price are not taken into account |
|
||||
if (line.date_start and invoice.date_invoice < line.date_start) or \ |
|
||||
(line.date_end and invoice.date_invoice > line.date_end): |
|
||||
return line_obj |
|
||||
return super()._prepare_invoice_line(line, invoice_id) |
|
@ -1,39 +0,0 @@ |
|||||
# Copyright 2019 Tecnativa <vicent.cubells@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
from odoo import api, fields, models |
|
||||
import odoo.addons.decimal_precision as dp |
|
||||
|
|
||||
|
|
||||
class AccountAnalyticINvoiceLine(models.Model): |
|
||||
_inherit = "account.analytic.invoice.line" |
|
||||
|
|
||||
date_start = fields.Date( |
|
||||
string='Start Date', |
|
||||
) |
|
||||
date_end = fields.Date( |
|
||||
string='End Date', |
|
||||
) |
|
||||
previous_revision_id = fields.Many2one( |
|
||||
comodel_name='account.analytic.invoice.line', |
|
||||
string='Previous revision', |
|
||||
help='Relation with previous revision', |
|
||||
) |
|
||||
previous_price = fields.Float( |
|
||||
related='previous_revision_id.price_unit', |
|
||||
readonly=True, |
|
||||
) |
|
||||
variation_percent = fields.Float( |
|
||||
compute='_compute_variation_percent', |
|
||||
store=True, |
|
||||
digits=dp.get_precision('Product Price'), |
|
||||
string='Variation %', |
|
||||
) |
|
||||
|
|
||||
@api.multi |
|
||||
@api.depends('price_unit', 'previous_revision_id.price_unit') |
|
||||
def _compute_variation_percent(self): |
|
||||
for line in self: |
|
||||
if not (line.price_unit and line.previous_price): |
|
||||
continue |
|
||||
line.variation_percent = ( |
|
||||
(line.price_unit / line.previous_price - 1) * 100) |
|
@ -0,0 +1,31 @@ |
|||||
|
# Copyright 2019 Tecnativa - Vicent Cubells |
||||
|
# Copyright 2019 Tecnativa - Carlos Dauden |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from odoo import api, fields, models |
||||
|
import odoo.addons.decimal_precision as dp |
||||
|
|
||||
|
|
||||
|
class ContractLine(models.Model): |
||||
|
_inherit = 'contract.line' |
||||
|
|
||||
|
previous_price = fields.Float( |
||||
|
string='Previous price', |
||||
|
related='predecessor_contract_line_id.price_unit', |
||||
|
readonly=True, |
||||
|
) |
||||
|
variation_percent = fields.Float( |
||||
|
compute='_compute_variation_percent', |
||||
|
store=True, |
||||
|
digits=dp.get_precision('Product Price'), |
||||
|
string='Variation %', |
||||
|
) |
||||
|
|
||||
|
@api.depends('price_unit', 'predecessor_contract_line_id.price_unit') |
||||
|
def _compute_variation_percent(self): |
||||
|
for line in self: |
||||
|
if line.price_unit and line.previous_price: |
||||
|
line.variation_percent = ( |
||||
|
(line.price_unit / line.previous_price - 1) * 100) |
||||
|
else: |
||||
|
line.variation_percent = 0.0 |
@ -1,3 +1,4 @@ |
|||||
* `Tecnativa <https://www.tecnativa.com>`_: |
* `Tecnativa <https://www.tecnativa.com>`_: |
||||
|
|
||||
* Vicent Cubells |
* Vicent Cubells |
||||
|
* Carlos Dauden |
@ -1,71 +1,43 @@ |
|||||
# Copyright 2019 Tecnativa - Vicent Cubells <vicent.cubells@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|
||||
|
# Copyright 2019 Tecnativa - Vicent Cubells |
||||
|
# Copyright 2019 Tecnativa - Carlos Dauden |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
from dateutil.relativedelta import relativedelta |
|
||||
|
from odoo.addons.contract.tests.test_contract import TestContractBase |
||||
|
|
||||
from odoo.tests import common |
|
||||
from odoo import fields |
|
||||
|
|
||||
|
|
||||
class TestContractPriceRevision(common.SavepointCase): |
|
||||
@classmethod |
|
||||
def setUpClass(cls): |
|
||||
super(TestContractPriceRevision, cls).setUpClass() |
|
||||
partner = cls.env['res.partner'].create({ |
|
||||
'name': 'Partner test', |
|
||||
}) |
|
||||
product = cls.env['product.product'].create({ |
|
||||
'name': 'Test Product', |
|
||||
}) |
|
||||
cls.contract = cls.env['account.analytic.account'].create({ |
|
||||
'name': 'Contract test', |
|
||||
'partner_id': partner.id, |
|
||||
'date_start': fields.Date.today(), |
|
||||
'recurring_next_date': fields.Date.to_string( |
|
||||
fields.date.today() + relativedelta(days=7)), |
|
||||
'recurring_rule_type': 'monthly', |
|
||||
'recurring_invoice_line_ids': [(0, 0, { |
|
||||
'product_id': product.id, |
|
||||
'quantity': 1.0, |
|
||||
'uom_id': product.uom_id.id, |
|
||||
'name': product.name, |
|
||||
'price_unit': 33.0, |
|
||||
'automatic_price': True, |
|
||||
}), (0, 0, { |
|
||||
'product_id': product.id, |
|
||||
'quantity': 1.0, |
|
||||
'uom_id': product.uom_id.id, |
|
||||
'name': product.name, |
|
||||
'price_unit': 25.0, |
|
||||
'automatic_price': False, |
|
||||
})] |
|
||||
}) |
|
||||
|
class TestContractPriceRevision(TestContractBase): |
||||
|
|
||||
def execute_wizard(self): |
def execute_wizard(self): |
||||
wizard = self.env['create.revision.line.wizard'].create({ |
|
||||
'date_start': fields.Date.today(), |
|
||||
'date_end': fields.Date.to_string( |
|
||||
fields.date.today() + relativedelta(years=1)), |
|
||||
|
wizard = self.env['contract.price.revision.wizard'].create({ |
||||
|
'date_start': '2018-02-15', |
||||
'variation_percent': 100.0, |
'variation_percent': 100.0, |
||||
}) |
}) |
||||
wizard.with_context( |
wizard.with_context( |
||||
{'active_ids': [self.contract.id]}).action_apply() |
{'active_ids': [self.contract.id]}).action_apply() |
||||
|
|
||||
def test_contract_price_revision_wizard(self): |
def test_contract_price_revision_wizard(self): |
||||
self.assertEqual(len(self.contract.recurring_invoice_line_ids.ids), 2) |
|
||||
|
self.acct_line.copy({'automatic_price': True}) |
||||
|
self.assertEqual(len(self.contract.contract_line_ids.ids), 2) |
||||
self.execute_wizard() |
self.execute_wizard() |
||||
self.assertEqual(len(self.contract.recurring_invoice_line_ids.ids), 3) |
|
||||
lines = self.contract.mapped('recurring_invoice_line_ids').filtered( |
|
||||
lambda x: x.price_unit == 50.0) |
|
||||
|
self.assertEqual(len(self.contract.contract_line_ids.ids), 3) |
||||
|
lines = self.contract.contract_line_ids.filtered( |
||||
|
lambda x: x.price_unit == 200.0) |
||||
self.assertEqual(len(lines), 1) |
self.assertEqual(len(lines), 1) |
||||
|
|
||||
def test_contract_price_revision_invoicing(self): |
def test_contract_price_revision_invoicing(self): |
||||
|
self.acct_line.copy({'automatic_price': True}) |
||||
self.execute_wizard() |
self.execute_wizard() |
||||
self.contract.recurring_create_invoice() |
|
||||
|
invoice = self.contract.recurring_create_invoice() |
||||
invoices = self.env['account.invoice'].search([ |
invoices = self.env['account.invoice'].search([ |
||||
('contract_id', '=', self.contract.id)]) |
|
||||
|
('invoice_line_ids.contract_line_id', 'in', |
||||
|
self.contract.contract_line_ids.ids)]) |
||||
self.assertEqual(len(invoices), 1) |
self.assertEqual(len(invoices), 1) |
||||
lines = invoices.mapped('invoice_line_ids') |
|
||||
|
lines = invoice.invoice_line_ids |
||||
|
self.assertEqual(len(lines), 2) |
||||
|
lines = lines.filtered(lambda x: x.price_unit == 100.0) |
||||
|
self.assertEqual(len(lines), 1) |
||||
|
invoice = self.contract.recurring_create_invoice() |
||||
|
lines = invoice.invoice_line_ids |
||||
self.assertEqual(len(lines), 2) |
self.assertEqual(len(lines), 2) |
||||
lines = lines.filtered(lambda x: x.price_unit == 50.0) |
|
||||
|
lines = lines.filtered(lambda x: x.price_unit == 200.0) |
||||
self.assertEqual(len(lines), 1) |
self.assertEqual(len(lines), 1) |
@ -1,43 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<odoo> |
|
||||
|
|
||||
<record id="account_analytic_invoice_line_form_view" model="ir.ui.view"> |
|
||||
<field name="name">account.analytic.invoice.line.view</field> |
|
||||
<field name="model">account.analytic.invoice.line</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
<form string="Line Information"> |
|
||||
<group> |
|
||||
<group> |
|
||||
<field name="analytic_account_id"/> |
|
||||
<field name="automatic_price"/> |
|
||||
</group> |
|
||||
<group> |
|
||||
<field name="product_id"/> |
|
||||
<field name="name"/> |
|
||||
<field name="quantity"/> |
|
||||
<field name="uom_id"/> |
|
||||
<field name="price_unit"/> |
|
||||
<field name="variation_percent"/> |
|
||||
<label for="date_start" string="Validity"/> |
|
||||
<div><field name="date_start" class="oe_inline"/> to <field name="date_end" class="oe_inline"/></div> |
|
||||
<field name="discount"/> |
|
||||
<field name="price_subtotal"/> |
|
||||
</group> |
|
||||
</group> |
|
||||
</form> |
|
||||
</field> |
|
||||
</record> |
|
||||
<record id="account_analytic_account_recurring_form_form" model="ir.ui.view"> |
|
||||
<field name="name">Contract form price revision</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="contract.account_analytic_account_recurring_form_form"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="price_unit" position="after"> |
|
||||
<field name="variation_percent" attrs="{'readonly': [('automatic_price', '=', True)]}"/> |
|
||||
<field name="date_start" attrs="{'readonly': [('automatic_price', '=', True)]}"/> |
|
||||
<field name="date_end" attrs="{'readonly': [('automatic_price', '=', True)]}"/> |
|
||||
</field> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</odoo> |
|
@ -0,0 +1,26 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
|
||||
|
<record id="contract_line_tree_view" model="ir.ui.view"> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="inherit_id" ref="contract.contract_line_tree_view"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="last_date_invoiced" position="after"> |
||||
|
<field name="variation_percent" groups="base.group_no_one"/> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record id="contract_line_form_view" model="ir.ui.view"> |
||||
|
<field name="model">contract.line</field> |
||||
|
<field name="inherit_id" ref="contract.contract_line_form_view"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="discount" position="after"> |
||||
|
<field name="variation_percent" |
||||
|
groups="base.group_no_one" |
||||
|
attrs="{'invisible': [('predecessor_contract_line_id', '=', False)]}"/> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
</odoo> |
@ -1 +1 @@ |
|||||
from . import create_revision_line |
|
||||
|
from . import contract_price_revision |
@ -0,0 +1,54 @@ |
|||||
|
# Copyright 2019 Tecnativa - Vicent Cubells |
||||
|
# Copyright 2019 Tecnativa - Carlos Dauden |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from dateutil.relativedelta import relativedelta |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
import odoo.addons.decimal_precision as dp |
||||
|
|
||||
|
|
||||
|
class ContractPriceRevisionWizard(models.TransientModel): |
||||
|
""" Update contract price based on percentage variation """ |
||||
|
_name = 'contract.price.revision.wizard' |
||||
|
_description = "Wizard to update price based on percentage variation" |
||||
|
|
||||
|
date_start = fields.Date( |
||||
|
required=True, |
||||
|
) |
||||
|
date_end = fields.Date() |
||||
|
variation_percent = fields.Float( |
||||
|
digits=dp.get_precision('Product Price'), |
||||
|
required=True, |
||||
|
string='Variation %', |
||||
|
) |
||||
|
|
||||
|
def action_apply(self): |
||||
|
ContractLine = self.env['contract.line'] |
||||
|
active_ids = self.env.context['active_ids'] |
||||
|
for line in self.env['contract.contract'].browse(active_ids).mapped( |
||||
|
'contract_line_ids'): |
||||
|
if (line.automatic_price or line.successor_contract_line_id or |
||||
|
not line.recurring_next_date): |
||||
|
continue |
||||
|
line.update({ |
||||
|
'date_end': self.date_start - relativedelta(days=1), |
||||
|
}) |
||||
|
new_vals = line.copy_data({ |
||||
|
'date_start': self.date_start, |
||||
|
'date_end': self.date_end, |
||||
|
'predecessor_contract_line_id': line.id, |
||||
|
'price_unit': line.price_unit * ( |
||||
|
1.0 + self.variation_percent / 100.0), |
||||
|
})[0] |
||||
|
tmp_line = ContractLine.new(new_vals) |
||||
|
tmp_line._onchange_date_start() |
||||
|
new_line = ContractLine.create( |
||||
|
tmp_line._convert_to_write(tmp_line._cache)) |
||||
|
line.update({ |
||||
|
'successor_contract_line_id': new_line.id, |
||||
|
}) |
||||
|
action = self.env['ir.actions.act_window'].for_xml_id( |
||||
|
'contract', 'action_customer_contract') |
||||
|
action['domain'] = [('id', 'in', active_ids)] |
||||
|
return action |
@ -1,50 +0,0 @@ |
|||||
# Copyright 2019 Tecnativa <vicent.cubells@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from dateutil.relativedelta import relativedelta |
|
||||
|
|
||||
from odoo import api, fields, models |
|
||||
import odoo.addons.decimal_precision as dp |
|
||||
|
|
||||
|
|
||||
class CreateRevisionLineWizard(models.TransientModel): |
|
||||
_name = 'create.revision.line.wizard' |
|
||||
|
|
||||
date_start = fields.Date( |
|
||||
required=True, |
|
||||
) |
|
||||
date_end = fields.Date() |
|
||||
variation_percent = fields.Float( |
|
||||
digits=dp.get_precision('Product Price'), |
|
||||
required=True, |
|
||||
string='Variation %', |
|
||||
) |
|
||||
|
|
||||
@api.multi |
|
||||
def action_apply(self): |
|
||||
contract_obj = self.env['account.analytic.account'] |
|
||||
line_obj = self.env['account.analytic.invoice.line'] |
|
||||
active_ids = self.env.context['active_ids'] |
|
||||
line_news = line_obj |
|
||||
for item in contract_obj.browse(active_ids).mapped( |
|
||||
'recurring_invoice_line_ids').filtered( |
|
||||
lambda x: not x.automatic_price): |
|
||||
line_news |= item.copy({ |
|
||||
'date_start': self.date_start, |
|
||||
'date_end': self.date_end, |
|
||||
'previous_revision_id': item.id, |
|
||||
'price_unit': item.price_unit * ( |
|
||||
1.0 + self.variation_percent / 100.0), |
|
||||
}) |
|
||||
item.date_end = (fields.Date.from_string(self.date_start) - |
|
||||
relativedelta(days=1)) |
|
||||
action = self.env.ref( |
|
||||
'contract.action_account_analytic_sale_overdue_all').read()[0] |
|
||||
if len(active_ids) > 1: # pragma: no cover |
|
||||
action['domain'] = [('id', 'in', active_ids)] |
|
||||
elif active_ids: |
|
||||
action['views'] = [( |
|
||||
self.env.ref('contract.account_analytic_account_sale_form').id, |
|
||||
'form')] |
|
||||
action['res_id'] = active_ids[0] |
|
||||
return action |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue