From 60723d10473eff727b60958c3eb168956a668a1a Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli <nim@odoo.com> Date: Thu, 27 Aug 2015 12:27:33 +0200 Subject: [PATCH] [ADD] sale_expense: adaptation due to the new Sale module Links expenses and sale modules. Reason: complete rewrite of the Sale module. Responsible: fp, dbo, nim --- addons/sale_expense/__init__.py | 3 + addons/sale_expense/__openerp__.py | 27 ++++++ addons/sale_expense/models/__init__.py | 4 + addons/sale_expense/models/analytic.py | 19 ++++ addons/sale_expense/models/product.py | 15 ++++ addons/sale_expense/sale_expense_demo.xml | 21 +++++ addons/sale_expense/tests/__init__.py | 1 + .../sale_expense/tests/test_sale_expense.py | 86 +++++++++++++++++++ addons/sale_expense/views/product_view.xml | 15 ++++ 9 files changed, 191 insertions(+) create mode 100644 addons/sale_expense/__init__.py create mode 100644 addons/sale_expense/__openerp__.py create mode 100644 addons/sale_expense/models/__init__.py create mode 100644 addons/sale_expense/models/analytic.py create mode 100644 addons/sale_expense/models/product.py create mode 100644 addons/sale_expense/sale_expense_demo.xml create mode 100644 addons/sale_expense/tests/__init__.py create mode 100644 addons/sale_expense/tests/test_sale_expense.py create mode 100644 addons/sale_expense/views/product_view.xml diff --git a/addons/sale_expense/__init__.py b/addons/sale_expense/__init__.py new file mode 100644 index 000000000000..48f417bbd932 --- /dev/null +++ b/addons/sale_expense/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import models diff --git a/addons/sale_expense/__openerp__.py b/addons/sale_expense/__openerp__.py new file mode 100644 index 000000000000..6907791fab31 --- /dev/null +++ b/addons/sale_expense/__openerp__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Sales Expense', + 'version': '1.0', + 'category': 'Hidden', + 'summary': 'Quotation, Sale Orders, Delivery & Invoicing Control', + 'description': """ +Module used for demo data +========================= + +Create some products for which you can re-invoice the costs. +This module does not add any feature, despite a few demo data to +test the features easily. +""", + 'author': 'OpenERP SA', + 'website': 'https://www.odoo.com/page/warehouse', + 'depends': ['sale', 'hr_expense'], + 'data': [ + 'views/product_view.xml', + ], + 'demo': ['sale_expense_demo.xml'], + 'test': [], + 'installable': True, + 'auto_install': True, +} diff --git a/addons/sale_expense/models/__init__.py b/addons/sale_expense/models/__init__.py new file mode 100644 index 000000000000..2bf570fe2beb --- /dev/null +++ b/addons/sale_expense/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import analytic +import product diff --git a/addons/sale_expense/models/analytic.py b/addons/sale_expense/models/analytic.py new file mode 100644 index 000000000000..3baeeaaa4f54 --- /dev/null +++ b/addons/sale_expense/models/analytic.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import models + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + def _get_invoice_price(self, order): + if self.product_id.invoice_policy == 'cost' and self.product_id.expense_policy == 'sales_price': + return self.product_id.with_context( + partner=order.partner_id.id, + date_order=order.date_order, + pricelist=order.pricelist_id.id, + uom=self.product_uom_id.id + ).price + else: + return super(AccountAnalyticLine, self)._get_invoice_price(order) diff --git a/addons/sale_expense/models/product.py b/addons/sale_expense/models/product.py new file mode 100644 index 000000000000..0d3276b453f7 --- /dev/null +++ b/addons/sale_expense/models/product.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import fields, models + + +class ProductProduct(models.Model): + _inherit = "product.template" + + expense_policy = fields.Selection( + [('cost', 'At Cost'), ('sales_price', 'At Sales Price')], + string='Expense Invoice Policy', + help="If you invoice at cost, the expense will be invoiced on the sale order at the cost of the analytic line;" + "if you invoice at sales price, the price of the product will be used instead.", + default='cost') diff --git a/addons/sale_expense/sale_expense_demo.xml b/addons/sale_expense/sale_expense_demo.xml new file mode 100644 index 000000000000..ee398004c98a --- /dev/null +++ b/addons/sale_expense/sale_expense_demo.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<openerp> + <data noupdate="1"> + + <record id="hr_expense.car_travel" model="product.product"> + <field name="invoice_policy">cost</field> + <field name="expense_policy">sales_price</field> + </record> + + <record id="hr_expense.air_ticket" model="product.product"> + <field name="invoice_policy">cost</field> + <field name="expense_policy">cost</field> + </record> + + <record id="hr_expense.hotel_rent" model="product.product"> + <field name="invoice_policy">cost</field> + <field name="expense_policy">cost</field> + </record> + + </data> +</openerp> diff --git a/addons/sale_expense/tests/__init__.py b/addons/sale_expense/tests/__init__.py new file mode 100644 index 000000000000..2ceb1c00c655 --- /dev/null +++ b/addons/sale_expense/tests/__init__.py @@ -0,0 +1 @@ +import test_sale_expense diff --git a/addons/sale_expense/tests/test_sale_expense.py b/addons/sale_expense/tests/test_sale_expense.py new file mode 100644 index 000000000000..837d2612a537 --- /dev/null +++ b/addons/sale_expense/tests/test_sale_expense.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from openerp.addons.sale.tests.test_sale_common import TestSale +from openerp import workflow + + +class TestSaleExpense(TestSale): + def test_sale_expense(self): + """ Test the behaviour of sales orders when managing expenses """ + # create a so with a product invoiced on delivery + prod = self.env.ref('product.product_product_1') + so = self.env['sale.order'].create({ + 'partner_id': self.partner.id, + 'partner_invoice_id': self.partner.id, + 'partner_shipping_id': self.partner.id, + 'order_line': [(0, 0, {'name': prod.name, 'product_id': prod.id, 'product_uom_qty': 2, 'product_uom': prod.uom_id.id, 'price_unit': prod.list_price})], + 'pricelist_id': self.env.ref('product.list0').id, + }) + so.action_confirm() + so._create_analytic_account() # normally created at so confirmation when you use the right products + init_price = so.amount_total + + # create some expense and validate it (expense at cost) + prod_exp = self.env.ref('hr_expense.air_ticket') + company = self.env.ref('base.main_company') + journal = self.env['account.journal'].create({'name': 'Purchase Journal - Test', 'code': 'HRTPJ', 'type': 'purchase', 'company_id': company.id}) + account_payable = self.env['account.account'].create({'code': 'X1111', 'name': 'HR Expense - Test Payable Account', 'user_type_id': self.env.ref('account.data_account_type_payable').id, 'reconcile': True}) + exp = self.env['hr.expense.expense'].create({ + 'name': 'Test Expense', + 'user_id': self.user.id, + 'line_ids': [(0, 0, {'product_id': prod_exp.id, + 'name': 'Air Travel', + 'analytic_account': so.project_id.id, + 'unit_amount': 700.0, + })], + 'journal_id': journal.id, + 'employee_payable_account_id': account_payable.id, + }) + cr, uid = self.cr, self.uid + # Submit to Manager + workflow.trg_validate(uid, 'hr.expense.expense', exp.id, 'confirm', cr) + # Approve + workflow.trg_validate(uid, 'hr.expense.expense', exp.id, 'validate', cr) + # Create Expense Entries + workflow.trg_validate(uid, 'hr.expense.expense', exp.id, 'done', cr) + + # expense should now be in sales order + self.assertTrue(prod_exp in map(lambda so: so.product_id, so.order_line), 'Sale Expense: expense product should be in so') + sol = so.order_line.filtered(lambda sol: sol.product_id.id == prod_exp.id) + self.assertEqual((sol.price_unit, sol.qty_delivered), (700.0, 1.0), 'Sale Expense: error when invoicing an expense at cost') + self.assertEqual(so.amount_total, init_price, 'Sale Expense: price of so not updated after adding expense') + + # create some expense and validate it (expense at sales price) + init_price = so.amount_total + prod_exp = self.env.ref('hr_expense.car_travel') + exp = self.env['hr.expense.expense'].create({ + 'name': 'Test Expense', + 'user_id': self.user.id, + 'line_ids': [(0, 0, {'product_id': prod_exp.id, + 'name': 'Car Travel', + 'analytic_account': so.project_id.id, + 'uom_id': self.env.ref('product.product_uom_km').id, + 'unit_amount': 0.15, + 'unit_quantity': 100, + })], + 'journal_id': journal.id, + 'employee_payable_account_id': account_payable.id, + }) + # Submit to Manager + workflow.trg_validate(uid, 'hr.expense.expense', exp.id, 'confirm', cr) + # Approve + workflow.trg_validate(uid, 'hr.expense.expense', exp.id, 'validate', cr) + # Create Expense Entries + workflow.trg_validate(uid, 'hr.expense.expense', exp.id, 'done', cr) + + # expense should now be in sales order + self.assertTrue(prod_exp in map(lambda so: so.product_id, so.order_line), 'Sale Expense: expense product should be in so') + sol = so.order_line.filtered(lambda sol: sol.product_id.id == prod_exp.id) + self.assertEqual((sol.price_unit, sol.qty_delivered), (0.32, 100.0), 'Sale Expense: error when invoicing an expense at cost') + self.assertEqual(so.amount_total, init_price, 'Sale Expense: price of so not updated after adding expense') + # self.assertTrue(so.invoice_status, 'no', 'Sale Expense: expenses should not impact the invoice_status of the so') + + # both expenses should be invoiced + inv_id = so.action_invoice_create() + inv = self.env['account.invoice'].browse(inv_id) + self.assertEqual(inv.amount_total, 732.0, 'Sale Expense: invoicing of exepense is wrong') diff --git a/addons/sale_expense/views/product_view.xml b/addons/sale_expense/views/product_view.xml new file mode 100644 index 000000000000..5f2dbd81c0f5 --- /dev/null +++ b/addons/sale_expense/views/product_view.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record model="ir.ui.view" id="product_template_form_view_expense_policy"> + <field name="name">product.template.expense.policy</field> + <field name="model">product.template</field> + <field name="inherit_id" ref="sale.product_template_form_view_invoice_policy"/> + <field name="arch" type="xml"> + <field name="invoice_policy" position="after"> + <field name="expense_policy" widget="radio" + attrs="{'invisible': [('invoice_policy','!=','cost')]}"/> + </field> + </field> + </record> +</odoo> + \ No newline at end of file -- GitLab