Browse Source

commit inicial

Rodney Enciso Arias 7 years ago
commit
f499f0b3a3

+ 0 - 0
.scannerwork/.sonar_lock


+ 5 - 0
.scannerwork/report-task.txt

@@ -0,0 +1,5 @@
+projectKey=account:invoice:line:pricelist
+serverUrl=http://181.40.66.126:9000
+dashboardUrl=http://181.40.66.126:9000/dashboard/index/account:invoice:line:pricelist
+ceTaskId=AV9VGl1CLmS3QZZeLmuE
+ceTaskUrl=http://181.40.66.126:9000/api/ce/task?id=AV9VGl1CLmS3QZZeLmuE

+ 52 - 0
README.rst

@@ -0,0 +1,52 @@
+
+.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
+   :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+   :alt: License: AGPL-3
+
+==============================
+Account Invoice Line Pricelist
+==============================
+
+By installing this module, on customer/sale invoice lines the unit price will
+be calculated based on the Price List set for the Invoice. If no Price List is
+set on the invoice, then it will be calculated based on the Price List set for
+the Customer.
+
+By default, in the standard Odoo, the method 'product_id_change' of model
+'account.invoice.line' doesn't take in consideration the pricelist assigned
+to a customer when calculating the unit price of customer/sale invoice.
+Moreover, in the standard Odoo, the model 'account.invoice' hasn't a
+pricelist related field.
+
+For invoices created from sale orders, product prices are already based on
+price lists. This is the standard behavior on Odoo. Only for the invoices
+created manually, the pricelist is not take in consideration.
+This module aims to fix this behavior.
+
+The same functionality, as described above, is provided for sale refunds.
+
+Configuration
+=============
+
+No special configuration is needed.
+
+
+Usage
+=====
+
+To use this module, you need to:
+
+#. Set a price list for a Customer
+#. Create a sale invoice for that Customer
+#. On invoice lines, add some products (of which price is defined in price list)
+#. Check whether the unit prices of the products are set accordingly to the price list
+
+
+Credits
+=======
+
+Contributors
+------------
+
+* Andrea Stirpe <a.stirpe@onestein.nl>
+* Antonio Esposito <a.esposito@onestein.nl>

+ 5 - 0
__init__.py

@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 Onestein (<http://www.onestein.eu>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from . import models

BIN
__init__.pyc


+ 21 - 0
__openerp__.py

@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 Onestein (<http://www.onestein.eu>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+{
+    'name': 'Account Invoice Line Pricelist',
+    'images': [],
+    'summary': """Prices on invoice products based on partner pricelists""",
+    'version': '1.0',
+    'category': 'Accounting & Finance',
+    'author': 'Onestein - Eiru',
+    'website': 'http://www.onestein.eu',
+    'license': 'AGPL-3',
+    'depends': [
+        'account',
+        'product',
+    ],
+    'data': [
+        'views/account_invoice.xml',
+    ],
+}

+ 6 - 0
models/__init__.py

@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 Onestein (<http://www.onestein.eu>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from . import account_invoice
+from . import account_invoice_line

BIN
models/__init__.pyc


+ 49 - 0
models/account_invoice.py

@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 Onestein (<http://www.onestein.eu>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from openerp import models, fields, api
+
+
+class AccountInvoice(models.Model):
+    _inherit = 'account.invoice'
+
+    pricelist_id = fields.Many2one(
+        'product.pricelist',
+        'Pricelist',
+        help="Pricelist for current invoice.")
+
+    @api.multi
+    def onchange_partner_id(self, type, partner_id, date_invoice=False,
+                            payment_term=False, partner_bank_id=False,
+                            company_id=False):
+
+        res = super(AccountInvoice, self).onchange_partner_id(
+            type, partner_id, date_invoice=False,
+            payment_term=False, partner_bank_id=False,
+            company_id=False)
+
+        if type in ['out_invoice', 'out_refund']:
+            res['value'].update({
+                'pricelist_id': None,
+            })
+            if partner_id:
+                partner = self.env['res.partner'].browse(partner_id)
+                if partner.property_product_pricelist:
+                    res['value'].update({
+                        'pricelist_id': partner.property_product_pricelist.id,
+                        'currency_id': partner.property_product_pricelist.currency_id.id
+                    })
+
+        if type in ['in_invoice', 'in_refund']:
+            res['value'].update({
+                'pricelist_id': None,
+            })
+            if partner_id:
+                partner = self.env['res.partner'].browse(partner_id)
+                if partner.property_product_pricelist_purchase:
+                    res['value'].update({
+                        'pricelist_id': partner.property_product_pricelist_purchase.id,
+                        'currency_id': partner.property_product_pricelist.currency_id.id
+                    })
+        return res

BIN
models/account_invoice.pyc


+ 57 - 0
models/account_invoice_line.py

@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 Onestein (<http://www.onestein.eu>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from openerp import models, api
+
+
+class AccountInvoiceLine(models.Model):
+    _inherit = 'account.invoice.line'
+
+    @api.multi
+    def product_id_change(
+            self, product, uom_id, qty=0, name='', type='out_invoice',
+            partner_id=False, fposition_id=False, price_unit=False,
+            currency_id=False, company_id=None):
+        res = super(AccountInvoiceLine, self).product_id_change(
+            product, uom_id, qty=qty, name=name, type=type,
+            partner_id=partner_id, fposition_id=fposition_id,
+            price_unit=price_unit, currency_id=currency_id,
+            company_id=company_id)
+        if not res or not res['value']:
+            return res
+        if type not in ['out_invoice', 'out_refund']:
+            return res
+        if not partner_id or not product:
+            return res
+
+        if currency_id:
+            currency = self.env['res.currency'].browse(currency_id)
+            rounded_price_unit = currency.round(res['value']['price_unit'])
+            if 'price_unit' not in res['value']\
+                    or not res['value']['price_unit']\
+                    or 'uos_id' in res['value']\
+                    or 'quantity' in res['value']\
+                    or rounded_price_unit == price_unit:
+                product_data = self.env['product.product'].browse(product)
+                price_unit = product_data.lst_price
+
+                partner = self.env['res.partner'].browse(partner_id)
+                pricelist = self._context.get('pricelist_id', False) or \
+                    (partner.property_product_pricelist and
+                     partner.property_product_pricelist.id) \
+                    or None
+                if pricelist:
+                    pricelist = self.env['product.pricelist'].browse(pricelist)
+                    if 'uos_id' in res['value'] and res['value']['uos_id'] and res['value']['uos_id'] != product_data.uom_id.id:
+                        pricedict = pricelist.with_context(uom=res['value']['uos_id']).price_get(product, qty, partner_id)
+                    else:
+                        pricedict = pricelist.price_get(product, qty, partner_id)
+                    price_unit = pricedict[pricelist.id]
+
+                price_unit = pricelist.currency_id.compute(
+                    price_unit, currency, round=True)
+
+                price_unit = currency.round(price_unit)
+                res['value']['price_unit'] = price_unit
+        return res

BIN
models/account_invoice_line.pyc


+ 4 - 0
sonar-project.properties

@@ -0,0 +1,4 @@
+sonar.projectKey=account:invoice:line:pricelist
+sonar.projectName=account_invoice_line_pricelist
+sonar.projectVersion=1.0 
+sonar.sources=.

+ 5 - 0
tests/__init__.py

@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 Onestein (<http://www.onestein.eu>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from . import test_onchange_product_id

+ 106 - 0
tests/test_onchange_product_id.py

@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 Onestein (<http://www.onestein.eu>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from openerp.tests.common import TransactionCase
+
+
+class TestOnchangeProductId(TransactionCase):
+    """Test that when an included tax is mapped by a fiscal position,
+    the included tax must be subtracted to the price of the product.
+    """
+
+    def setUp(self):
+        super(TestOnchangeProductId, self).setUp()
+        curr_obj = self.env['res.currency']
+        self.eur = curr_obj.search([('name', '=', 'EUR')], limit=1).id
+        self.usd = curr_obj.search([('name', '=', 'USD')], limit=1).id
+        self.product = self.env['product.product'].create({
+            'name': 'Test Product',
+            'lst_price': 10.00,
+        })
+        version_dict1 = {
+            'name': 'Test version 1',
+            'items_id': [
+                (0, 0, {
+                    'name': 'Test item default',
+                    'base': 1,
+                    'price_discount': 0.1, })]
+        }
+        version_dict2 = {
+            'name': 'Test version 2',
+            'items_id': [
+                (0, 0, {
+                    'name': 'Test item default',
+                    'base': 1,
+                    'price_discount': 0.2, })]
+        }
+        self.plist1 = self.env['product.pricelist'].create(
+            {'name': 'Test pricelist 1',
+             'type': 'sale',
+             'currency_id': self.usd,
+             'version_id': [(0, 0, version_dict1)]})
+        self.plist2 = self.env['product.pricelist'].create(
+            {'name': 'Test pricelist 2',
+             'type': 'sale',
+             'currency_id': self.usd,
+             'version_id': [(0, 0, version_dict2)]})
+        self.partner = self.env['res.partner'].create(
+            {'name': 'Test partner',
+             'property_product_pricelist': self.plist1.id})
+        self.invoice_line = self.env['account.invoice.line'].create(
+            {'name': 'Test line',
+             'quantity': 1.0,
+             'price_unit': 1.0})
+
+    def test_onchange_product_id_pricelist(self):
+
+        currency = self.env['res.currency'].browse(self.eur)
+        rate = currency.rate_silent
+        price_unit = currency.round(10.00 * rate)
+        exp_value = currency.round((11.00 * rate))
+        res = self.invoice_line.product_id_change(
+            self.product.id, self.product.uom_id.id, qty=1.0,
+            partner_id=self.partner.id, currency_id=self.eur,
+            price_unit=price_unit,
+            company_id=self.env.user.company_id.id)
+
+        self.assertLessEqual(
+            abs(exp_value - res['value']['price_unit']), 0.0001,
+            "ERROR in getting price from pricelist")
+
+    def test_onchange_product_id_invoice_pricelist(self):
+
+        currency = self.env['res.currency'].browse(self.usd)
+        rate = currency.rate_silent
+        price_unit = currency.round(10.00 * rate)
+        exp_value = currency.round((12.00 * rate))
+        res = (
+            self.invoice_line
+            .with_context({'pricelist_id': self.plist2.id})
+            .product_id_change(
+                self.product.id, self.product.uom_id.id, qty=1.0,
+                partner_id=self.partner.id, currency_id=self.usd,
+                price_unit=price_unit,
+                company_id=self.env.user.company_id.id)
+        )
+
+        self.assertLessEqual(
+            abs(exp_value - res['value']['price_unit']), 0.0001,
+            "ERROR in getting price from pricelist")
+
+    def test_onchange_product_id_different_currency(self):
+
+        currency = self.env['res.currency'].browse(self.usd)
+        rate = currency.rate_silent
+        price_unit = currency.round(10.00 * rate)
+        exp_value = currency.round((11.00 * rate))
+        res = self.invoice_line.product_id_change(
+            self.product.id, self.product.uom_id.id, qty=1.0,
+            partner_id=self.partner.id, currency_id=self.usd,
+            price_unit=price_unit,
+            company_id=self.env.user.company_id.id)
+
+        self.assertLessEqual(
+            abs(exp_value - res['value']['price_unit']), 0.0001,
+            "ERROR in getting price from pricelist")

+ 42 - 0
views/account_invoice.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+
+        <record id="invoice_pricelist_form" model="ir.ui.view">
+            <field name="name">account.invoice.pricelist.form</field>
+            <field name="model">account.invoice</field>
+            <field name="inherit_id" ref="account.invoice_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='fiscal_position']" position="after">
+                    <field name="pricelist_id" domain="[('type','=','sale')]"/>
+                </xpath>
+                <xpath expr="//field[@name='invoice_line']/tree/field[@name='product_id']" position="attributes">
+                    <attribute name="context">{'type': type, 'pricelist_id': parent.pricelist_id}</attribute>
+                </xpath>
+                <xpath expr="//field[@name='invoice_line']/tree/field[@name='quantity']" position="attributes">
+                    <attribute name="context">{'type': type, 'pricelist_id': parent.pricelist_id}</attribute>
+                    <attribute name="on_change">product_id_change(product_id, uos_id, quantity, name, parent.type, parent.partner_id, parent.fiscal_position, price_unit, parent.currency_id, parent.company_id, context)</attribute>
+                </xpath>
+            </field>
+        </record>
+
+        <record id="invoice_pricelist_supplier_form" model="ir.ui.view">
+            <field name="name">account.invoice.pricelist.form</field>
+            <field name="model">account.invoice</field>
+            <field name="inherit_id" ref="account.invoice_supplier_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='fiscal_position']" position="after">
+                    <field name="pricelist_id" domain="[('type','=','sale')]"/>
+                </xpath>
+                <xpath expr="//field[@name='invoice_line']/tree/field[@name='product_id']" position="attributes">
+                    <attribute name="context">{'type': type, 'pricelist_id': parent.pricelist_id}</attribute>
+                </xpath>
+                <xpath expr="//field[@name='invoice_line']/tree/field[@name='quantity']" position="attributes">
+                    <attribute name="context">{'type': type, 'pricelist_id': parent.pricelist_id}</attribute>
+                    <attribute name="on_change">product_id_change(product_id, uos_id, quantity, name, parent.type, parent.partner_id, parent.fiscal_position, price_unit, parent.currency_id, parent.company_id, context)</attribute>
+                </xpath>
+            </field>
+        </record>
+
+    </data>
+</openerp>