From 7496be73a3500b6bb071fdbee0b3d770c6f1d272 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 28 May 2018 15:41:28 -0700 Subject: [PATCH 1/6] Initial commit of `account_invoice_margin` for 11.0 --- account_invoice_margin/__init__.py | 1 + account_invoice_margin/__manifest__.py | 23 +++++++ account_invoice_margin/models/__init__.py | 1 + .../models/account_invoice.py | 62 ++++++++++++++++++ account_invoice_margin/tests/__init__.py | 1 + .../tests/test_invoice_margin.py | 64 +++++++++++++++++++ .../views/account_invoice_views.xml | 18 ++++++ 7 files changed, 170 insertions(+) create mode 100644 account_invoice_margin/__init__.py create mode 100755 account_invoice_margin/__manifest__.py create mode 100644 account_invoice_margin/models/__init__.py create mode 100644 account_invoice_margin/models/account_invoice.py create mode 100644 account_invoice_margin/tests/__init__.py create mode 100644 account_invoice_margin/tests/test_invoice_margin.py create mode 100644 account_invoice_margin/views/account_invoice_views.xml diff --git a/account_invoice_margin/__init__.py b/account_invoice_margin/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/account_invoice_margin/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_invoice_margin/__manifest__.py b/account_invoice_margin/__manifest__.py new file mode 100755 index 00000000..3fe289bf --- /dev/null +++ b/account_invoice_margin/__manifest__.py @@ -0,0 +1,23 @@ +{ + 'name': 'Invoice Margin', + 'author': 'Hibou Corp. ', + 'version': '11.0.1.0.0', + 'category': 'Accounting', + 'sequence': 95, + 'summary': 'Invoices include margin calculation.', + 'description': """ +Invoices include margin calculation. +If the invoice line comes from a sale order line, the cost will come +from the sale order line. + """, + 'website': 'https://hibou.io/', + 'depends': [ + 'account', + 'sale_margin', + ], + 'data': [ + 'views/account_invoice_views.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/account_invoice_margin/models/__init__.py b/account_invoice_margin/models/__init__.py new file mode 100644 index 00000000..8e072db8 --- /dev/null +++ b/account_invoice_margin/models/__init__.py @@ -0,0 +1 @@ +from . import account_invoice diff --git a/account_invoice_margin/models/account_invoice.py b/account_invoice_margin/models/account_invoice.py new file mode 100644 index 00000000..b0ff750d --- /dev/null +++ b/account_invoice_margin/models/account_invoice.py @@ -0,0 +1,62 @@ +from odoo import api, fields, models +from odoo.addons import decimal_precision as dp + + +class AccountInvoiceLine(models.Model): + _inherit = "account.invoice.line" + + margin = fields.Float(compute='_product_margin', digits=dp.get_precision('Product Price'), store=True) + purchase_price = fields.Float(string='Cost', digits=dp.get_precision('Product Price')) + + def _compute_margin(self, invoice_id, product_id, product_uom_id, sale_line_ids): + # if sale_line_ids and don't re-browse + for line in sale_line_ids: + return line.purchase_price + frm_cur = invoice_id.company_currency_id + to_cur = invoice_id.currency_id + purchase_price = product_id.standard_price + if product_uom_id != product_id.uom_id: + purchase_price = product_id.uom_id._compute_price(purchase_price, product_uom_id) + ctx = self.env.context.copy() + ctx['date'] = invoice_id.date if invoice_id.date else fields.Date.context_today(invoice_id) + price = frm_cur.with_context(ctx).compute(purchase_price, to_cur, round=False) + return price + + @api.onchange('product_id', 'uom_id') + def product_id_change_margin(self): + if not self.product_id or not self.uom_id: + return + self.purchase_price = self._compute_margin(self.invoice_id, self.product_id, self.uom_id, self.sale_line_ids) + + @api.model + def create(self, vals): + line = super(AccountInvoiceLine, self).create(vals) + line.product_id_change_margin() + return line + + @api.depends('product_id', 'purchase_price', 'quantity', 'price_unit', 'price_subtotal') + def _product_margin(self): + for line in self: + currency = line.invoice_id.currency_id + price = line.purchase_price + if line.product_id and not price: + date = line.invoice_id.date if line.invoice_id.date else fields.Date.context_today(line.invoice_id) + from_cur = line.invoice_id.company_currency_id.with_context(date=date) + price = from_cur.compute(line.product_id.standard_price, currency, round=False) + + line.margin = currency.round(line.price_subtotal - (price * line.quantity)) + + +class AccountInvoice(models.Model): + _inherit = "account.invoice" + + margin = fields.Monetary(compute='_product_margin', + help="It gives profitability by calculating the difference between the Unit Price and the cost.", + currency_field='currency_id', + digits=dp.get_precision('Product Price'), + store=True) + + @api.depends('invoice_line_ids.margin') + def _product_margin(self): + for invoice in self: + invoice.margin = sum(invoice.invoice_line_ids.mapped('margin')) diff --git a/account_invoice_margin/tests/__init__.py b/account_invoice_margin/tests/__init__.py new file mode 100644 index 00000000..982ec0aa --- /dev/null +++ b/account_invoice_margin/tests/__init__.py @@ -0,0 +1 @@ +from . import test_invoice_margin diff --git a/account_invoice_margin/tests/test_invoice_margin.py b/account_invoice_margin/tests/test_invoice_margin.py new file mode 100644 index 00000000..0fc6fb79 --- /dev/null +++ b/account_invoice_margin/tests/test_invoice_margin.py @@ -0,0 +1,64 @@ +from odoo.addons.sale_margin.tests.test_sale_margin import TestSaleMargin + +class TestInvoiceMargin(TestSaleMargin): + + def setUp(self): + super(TestInvoiceMargin, self).setUp() + self.AccountInvoice = self.env['account.invoice'] + + def test_invoice_margin(self): + """ Test the sale_margin module in Odoo. """ + # Create a sales order for product Graphics Card. + sale_order_so11 = self.SaleOrder.create({ + 'name': 'Test_SO011', + 'order_line': [ + (0, 0, { + 'name': '[CARD] Graphics Card', + 'purchase_price': 700.0, + 'price_unit': 1000.0, + 'product_uom': self.product_uom_id, + 'product_uom_qty': 10.0, + 'state': 'draft', + 'product_id': self.product_id}), + (0, 0, { + 'name': 'Line without product_uom', + 'price_unit': 1000.0, + 'purchase_price': 700.0, + 'product_uom_qty': 10.0, + 'state': 'draft', + 'product_id': self.product_id}) + ], + 'partner_id': self.partner_id, + 'partner_invoice_id': self.partner_invoice_address_id, + 'partner_shipping_id': self.partner_invoice_address_id, + 'pricelist_id': self.pricelist_id}) + # Confirm the sales order. + sale_order_so11.action_confirm() + # Verify that margin field gets bind with the value. + self.assertEqual(sale_order_so11.margin, 6000.00, "Sales order margin should be 6000.00") + + # Invoice the sales order. + inv_id = sale_order_so11.action_invoice_create() + inv = self.AccountInvoice.browse(inv_id) + self.assertEqual(inv.margin, sale_order_so11.margin) + + account = self.env['account.account'].search([('internal_type', '=', 'other')], limit=1) + inv = self.AccountInvoice.create({ + 'partner_id': self.partner_id, + 'invoice_line_ids': [ + (0, 0, { + 'account_id': account.id, + 'name': '[CARD] Graphics Card', + 'purchase_price': 600.0, + 'price_unit': 1000.0, + 'quantity': 10.0, + 'product_id': self.product_id}), + (0, 0, { + 'account_id': account.id, + 'name': 'Line without product_uom', + 'price_unit': 1000.0, + 'purchase_price': 800.0, + 'quantity': 10.0,}) + ], + }) + self.assertEqual(inv.margin, 6000.0) diff --git a/account_invoice_margin/views/account_invoice_views.xml b/account_invoice_margin/views/account_invoice_views.xml new file mode 100644 index 00000000..cce0cdc3 --- /dev/null +++ b/account_invoice_margin/views/account_invoice_views.xml @@ -0,0 +1,18 @@ + + + + + account.invoice.margin.view.form + account.invoice + + + + + + + + + + + + From 164dfebfdd2e993246a3dfe83230817233ab85f0 Mon Sep 17 00:00:00 2001 From: Kristen Marie Kulha Date: Fri, 14 Sep 2018 16:09:42 -0700 Subject: [PATCH 2/6] Added README to `account_invoice_margin`. --- account_invoice_margin/README.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 account_invoice_margin/README.rst diff --git a/account_invoice_margin/README.rst b/account_invoice_margin/README.rst new file mode 100644 index 00000000..a148ac23 --- /dev/null +++ b/account_invoice_margin/README.rst @@ -0,0 +1,28 @@ +****************************** +Hibou - Account Invoice Margin +****************************** + +Include a margin calculation on invoices. + +For more information and add-ons, visit `Hibou.io `_. + + +============= +Main Features +============= + +* Adds computed field `margin` to invoices to give the profitability by calculating the difference between the Unit Price and the Cost. + +.. image:: https://user-images.githubusercontent.com/15882954/45578631-880c0000-b837-11e8-9c4d-d2f15c3c0592.png + :alt: 'Customer Invoice' + :width: 988 + :align: left + + +======= +License +======= + +Please see `LICENSE `_. + +Copyright Hibou Corp. 2018 From 41c3f8f5e42d6eb79b4aa1cfd033802810f83caa Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 8 Apr 2019 14:39:12 -0700 Subject: [PATCH 3/6] MIG `account_invoice_margin` to 12.0 --- account_invoice_margin/__manifest__.py | 2 +- account_invoice_margin/models/account_invoice.py | 4 ++-- account_invoice_margin/tests/test_invoice_margin.py | 10 +++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/account_invoice_margin/__manifest__.py b/account_invoice_margin/__manifest__.py index 3fe289bf..562b81e0 100755 --- a/account_invoice_margin/__manifest__.py +++ b/account_invoice_margin/__manifest__.py @@ -1,7 +1,7 @@ { 'name': 'Invoice Margin', 'author': 'Hibou Corp. ', - 'version': '11.0.1.0.0', + 'version': '12.0.1.0.0', 'category': 'Accounting', 'sequence': 95, 'summary': 'Invoices include margin calculation.', diff --git a/account_invoice_margin/models/account_invoice.py b/account_invoice_margin/models/account_invoice.py index b0ff750d..295d68f7 100644 --- a/account_invoice_margin/models/account_invoice.py +++ b/account_invoice_margin/models/account_invoice.py @@ -19,7 +19,7 @@ class AccountInvoiceLine(models.Model): purchase_price = product_id.uom_id._compute_price(purchase_price, product_uom_id) ctx = self.env.context.copy() ctx['date'] = invoice_id.date if invoice_id.date else fields.Date.context_today(invoice_id) - price = frm_cur.with_context(ctx).compute(purchase_price, to_cur, round=False) + price = frm_cur.with_context(ctx)._convert(purchase_price, to_cur, invoice_id.company_id, ctx['date'], round=False) return price @api.onchange('product_id', 'uom_id') @@ -42,7 +42,7 @@ class AccountInvoiceLine(models.Model): if line.product_id and not price: date = line.invoice_id.date if line.invoice_id.date else fields.Date.context_today(line.invoice_id) from_cur = line.invoice_id.company_currency_id.with_context(date=date) - price = from_cur.compute(line.product_id.standard_price, currency, round=False) + price = from_cur._convert(line.product_id.standard_price, currency, line.company_id, date, round=False) line.margin = currency.round(line.price_subtotal - (price * line.quantity)) diff --git a/account_invoice_margin/tests/test_invoice_margin.py b/account_invoice_margin/tests/test_invoice_margin.py index 0fc6fb79..27945325 100644 --- a/account_invoice_margin/tests/test_invoice_margin.py +++ b/account_invoice_margin/tests/test_invoice_margin.py @@ -1,4 +1,6 @@ from odoo.addons.sale_margin.tests.test_sale_margin import TestSaleMargin +from datetime import datetime + class TestInvoiceMargin(TestSaleMargin): @@ -10,10 +12,11 @@ class TestInvoiceMargin(TestSaleMargin): """ Test the sale_margin module in Odoo. """ # Create a sales order for product Graphics Card. sale_order_so11 = self.SaleOrder.create({ + 'date_order': datetime.today(), 'name': 'Test_SO011', 'order_line': [ (0, 0, { - 'name': '[CARD] Graphics Card', + 'name': '[CARD] Individual Workplace', 'purchase_price': 700.0, 'price_unit': 1000.0, 'product_uom': self.product_uom_id, @@ -26,8 +29,7 @@ class TestInvoiceMargin(TestSaleMargin): 'purchase_price': 700.0, 'product_uom_qty': 10.0, 'state': 'draft', - 'product_id': self.product_id}) - ], + 'product_id': self.product_id})], 'partner_id': self.partner_id, 'partner_invoice_id': self.partner_invoice_address_id, 'partner_shipping_id': self.partner_invoice_address_id, @@ -37,6 +39,8 @@ class TestInvoiceMargin(TestSaleMargin): # Verify that margin field gets bind with the value. self.assertEqual(sale_order_so11.margin, 6000.00, "Sales order margin should be 6000.00") + sale_order_so11.order_line.write({'qty_delivered': 10.0}) + # Invoice the sales order. inv_id = sale_order_so11.action_invoice_create() inv = self.AccountInvoice.browse(inv_id) From 22c96bb9d33d38219f6c3d3cad5664ae414486c9 Mon Sep 17 00:00:00 2001 From: Bhoomi Date: Thu, 26 Sep 2019 12:27:32 -0400 Subject: [PATCH 4/6] MIG `account_invoice_margin` For Odoo 13.0 ( Test Case have error. Module works perfect). --- account_invoice_margin/__manifest__.py | 2 +- .../models/account_invoice.py | 34 +++++++++---------- .../tests/test_invoice_margin.py | 8 ++--- .../views/account_invoice_views.xml | 6 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/account_invoice_margin/__manifest__.py b/account_invoice_margin/__manifest__.py index 562b81e0..a8580707 100755 --- a/account_invoice_margin/__manifest__.py +++ b/account_invoice_margin/__manifest__.py @@ -1,7 +1,7 @@ { 'name': 'Invoice Margin', 'author': 'Hibou Corp. ', - 'version': '12.0.1.0.0', + 'version': '13.0.1.0.0', 'category': 'Accounting', 'sequence': 95, 'summary': 'Invoices include margin calculation.', diff --git a/account_invoice_margin/models/account_invoice.py b/account_invoice_margin/models/account_invoice.py index 295d68f7..6659ac70 100644 --- a/account_invoice_margin/models/account_invoice.py +++ b/account_invoice_margin/models/account_invoice.py @@ -2,53 +2,53 @@ from odoo import api, fields, models from odoo.addons import decimal_precision as dp -class AccountInvoiceLine(models.Model): - _inherit = "account.invoice.line" +class AccountMoveLine(models.Model): + _inherit = "account.move.line" margin = fields.Float(compute='_product_margin', digits=dp.get_precision('Product Price'), store=True) purchase_price = fields.Float(string='Cost', digits=dp.get_precision('Product Price')) - def _compute_margin(self, invoice_id, product_id, product_uom_id, sale_line_ids): + def _compute_margin(self, move_id, product_id, product_uom_id, sale_line_ids): # if sale_line_ids and don't re-browse for line in sale_line_ids: return line.purchase_price - frm_cur = invoice_id.company_currency_id - to_cur = invoice_id.currency_id + frm_cur = move_id.company_currency_id + to_cur = move_id.currency_id purchase_price = product_id.standard_price if product_uom_id != product_id.uom_id: purchase_price = product_id.uom_id._compute_price(purchase_price, product_uom_id) ctx = self.env.context.copy() - ctx['date'] = invoice_id.date if invoice_id.date else fields.Date.context_today(invoice_id) - price = frm_cur.with_context(ctx)._convert(purchase_price, to_cur, invoice_id.company_id, ctx['date'], round=False) + ctx['date'] = move_id.date if move_id.date else fields.Date.context_today(move_id) + price = frm_cur.with_context(ctx)._convert(purchase_price, to_cur, move_id.company_id, ctx['date'], round=False) return price - @api.onchange('product_id', 'uom_id') + @api.onchange('product_id', 'product_uom_id') def product_id_change_margin(self): - if not self.product_id or not self.uom_id: + if not self.product_id or not self.product_uom_id: return - self.purchase_price = self._compute_margin(self.invoice_id, self.product_id, self.uom_id, self.sale_line_ids) + self.purchase_price = self._compute_margin(self.move_id, self.product_id, self.product_uom_id, self.sale_line_ids) - @api.model + @api.model_create_multi def create(self, vals): - line = super(AccountInvoiceLine, self).create(vals) + line = super(AccountMoveLine, self).create(vals) line.product_id_change_margin() return line @api.depends('product_id', 'purchase_price', 'quantity', 'price_unit', 'price_subtotal') def _product_margin(self): for line in self: - currency = line.invoice_id.currency_id + currency = line.move_id.currency_id price = line.purchase_price if line.product_id and not price: - date = line.invoice_id.date if line.invoice_id.date else fields.Date.context_today(line.invoice_id) - from_cur = line.invoice_id.company_currency_id.with_context(date=date) + date = line.move_id.date if line.move_id.date else fields.Date.context_today(line.move_id) + from_cur = line.move_id.company_currency_id.with_context(date=date) price = from_cur._convert(line.product_id.standard_price, currency, line.company_id, date, round=False) line.margin = currency.round(line.price_subtotal - (price * line.quantity)) -class AccountInvoice(models.Model): - _inherit = "account.invoice" +class AccountMove(models.Model): + _inherit = "account.move" margin = fields.Monetary(compute='_product_margin', help="It gives profitability by calculating the difference between the Unit Price and the cost.", diff --git a/account_invoice_margin/tests/test_invoice_margin.py b/account_invoice_margin/tests/test_invoice_margin.py index 27945325..3bf5cad7 100644 --- a/account_invoice_margin/tests/test_invoice_margin.py +++ b/account_invoice_margin/tests/test_invoice_margin.py @@ -6,7 +6,7 @@ class TestInvoiceMargin(TestSaleMargin): def setUp(self): super(TestInvoiceMargin, self).setUp() - self.AccountInvoice = self.env['account.invoice'] + self.AccountMove = self.env['account.move'] def test_invoice_margin(self): """ Test the sale_margin module in Odoo. """ @@ -42,12 +42,12 @@ class TestInvoiceMargin(TestSaleMargin): sale_order_so11.order_line.write({'qty_delivered': 10.0}) # Invoice the sales order. - inv_id = sale_order_so11.action_invoice_create() - inv = self.AccountInvoice.browse(inv_id) + inv_id = sale_order_so11._create_invoices() + inv = self.AccountMove.browse(inv_id) self.assertEqual(inv.margin, sale_order_so11.margin) account = self.env['account.account'].search([('internal_type', '=', 'other')], limit=1) - inv = self.AccountInvoice.create({ + inv = self.AccountMove.create({ 'partner_id': self.partner_id, 'invoice_line_ids': [ (0, 0, { diff --git a/account_invoice_margin/views/account_invoice_views.xml b/account_invoice_margin/views/account_invoice_views.xml index cce0cdc3..e1495b0b 100644 --- a/account_invoice_margin/views/account_invoice_views.xml +++ b/account_invoice_margin/views/account_invoice_views.xml @@ -3,10 +3,10 @@ account.invoice.margin.view.form - account.invoice - + account.move + - + From 707bf1e01c027e14563c5c9d3fdd17b679fe91e1 Mon Sep 17 00:00:00 2001 From: Bhoomi Date: Mon, 30 Sep 2019 16:50:20 -0400 Subject: [PATCH 5/6] FIX `account_invoice_margin` Fix Precision warning on fields. --- account_invoice_margin/models/account_invoice.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/account_invoice_margin/models/account_invoice.py b/account_invoice_margin/models/account_invoice.py index 6659ac70..709e6dd7 100644 --- a/account_invoice_margin/models/account_invoice.py +++ b/account_invoice_margin/models/account_invoice.py @@ -5,8 +5,8 @@ from odoo.addons import decimal_precision as dp class AccountMoveLine(models.Model): _inherit = "account.move.line" - margin = fields.Float(compute='_product_margin', digits=dp.get_precision('Product Price'), store=True) - purchase_price = fields.Float(string='Cost', digits=dp.get_precision('Product Price')) + margin = fields.Float(compute='_product_margin', digits='Product Price', store=True) + purchase_price = fields.Float(string='Cost', digits='Product Price') def _compute_margin(self, move_id, product_id, product_uom_id, sale_line_ids): # if sale_line_ids and don't re-browse @@ -39,12 +39,13 @@ class AccountMoveLine(models.Model): for line in self: currency = line.move_id.currency_id price = line.purchase_price + margin = line.price_subtotal - (price * line.quantity) if line.product_id and not price: date = line.move_id.date if line.move_id.date else fields.Date.context_today(line.move_id) from_cur = line.move_id.company_currency_id.with_context(date=date) price = from_cur._convert(line.product_id.standard_price, currency, line.company_id, date, round=False) - line.margin = currency.round(line.price_subtotal - (price * line.quantity)) + line.margin = currency.round(margin) if currency else margin class AccountMove(models.Model): @@ -53,7 +54,7 @@ class AccountMove(models.Model): margin = fields.Monetary(compute='_product_margin', help="It gives profitability by calculating the difference between the Unit Price and the cost.", currency_field='currency_id', - digits=dp.get_precision('Product Price'), + digits='Product Price', store=True) @api.depends('invoice_line_ids.margin') From 44b86ee115f969acaf9be626d924418541ecc8aa Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 1 Oct 2019 17:13:33 +0200 Subject: [PATCH 6/6] FIX `account_invoice_margin` tests passing properly --- account_invoice_margin/tests/test_invoice_margin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/account_invoice_margin/tests/test_invoice_margin.py b/account_invoice_margin/tests/test_invoice_margin.py index 3bf5cad7..d62373d4 100644 --- a/account_invoice_margin/tests/test_invoice_margin.py +++ b/account_invoice_margin/tests/test_invoice_margin.py @@ -42,12 +42,12 @@ class TestInvoiceMargin(TestSaleMargin): sale_order_so11.order_line.write({'qty_delivered': 10.0}) # Invoice the sales order. - inv_id = sale_order_so11._create_invoices() - inv = self.AccountMove.browse(inv_id) + inv = sale_order_so11._create_invoices() self.assertEqual(inv.margin, sale_order_so11.margin) account = self.env['account.account'].search([('internal_type', '=', 'other')], limit=1) inv = self.AccountMove.create({ + 'type': 'in_invoice', 'partner_id': self.partner_id, 'invoice_line_ids': [ (0, 0, { @@ -65,4 +65,5 @@ class TestInvoiceMargin(TestSaleMargin): 'quantity': 10.0,}) ], }) + self.assertEqual(len(inv.invoice_line_ids), 2) self.assertEqual(inv.margin, 6000.0)