From 16410b3e8b5c5aa19f22c858ce6ab78bded2b7fa Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 28 May 2018 15:41:28 -0700 Subject: [PATCH] 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 + + + + + + + + + + + +