diff --git a/addons/stock_landed_costs/tests/__init__.py b/addons/stock_landed_costs/tests/__init__.py new file mode 100644 index 00000000000..6fba363c3bf --- /dev/null +++ b/addons/stock_landed_costs/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_stock_landed_costs_purchase diff --git a/addons/stock_landed_costs/tests/common.py b/addons/stock_landed_costs/tests/common.py new file mode 100644 index 00000000000..84f5b0d4817 --- /dev/null +++ b/addons/stock_landed_costs/tests/common.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +from odoo.tests import common + +class TestStockLandedCostsCommon(common.TransactionCase): + + def setUp(self): + super(TestStockLandedCostsCommon, self).setUp() + # Objects + self.Product = self.env['product.product'] + self.Picking = self.env['stock.picking'] + self.Move = self.env['stock.move'] + self.LandedCost = self.env['stock.landed.cost'] + self.CostLine = self.env['stock.landed.cost.lines'] + # References + self.supplier_id = self.ref('base.res_partner_2') + self.customer_id = self.ref('base.res_partner_4') + self.picking_type_in_id = self.ref('stock.picking_type_in') + self.picking_type_out_id = self.ref('stock.picking_type_out') + self.supplier_location_id = self.ref('stock.stock_location_suppliers') + self.stock_location_id = self.ref('stock.stock_location_stock') + self.customer_location_id = self.ref('stock.stock_location_customers') + self.categ_all = self.env.ref('product.product_category_all') + # Create account + self.default_account = self.env['account.account'].create({ + 'name': "Purchased Stocks", + 'code': "X1101", + 'user_type_id': self.env['account.account.type'].create({ + 'name': 'Expenses', + 'type': 'other'}).id, + 'reconcile': True}) + self.expenses_journal = self.env['account.journal'].create({ + 'name': 'Expenses - Test', + 'code': 'TEXJ', + 'type': 'purchase', + 'default_debit_account_id': self.default_account.id, + 'default_credit_account_id': self.default_account.id}) + # Create product refrigerator & oven + self.product_refrigerator = self.Product.create({ + 'name': 'Refrigerator', + 'type': 'product', + 'cost_method': 'real', + 'valuation': 'real_time', + 'standard_price': 1.0, + 'weight': 10, + 'volume': 1, + 'categ_id': self.categ_all.id}) + self.product_oven = self.Product.create({ + 'name': 'Microwave Oven', + 'type': 'product', + 'cost_method': 'real', + 'valuation': 'real_time', + 'standard_price': 1.0, + 'weight': 20, + 'volume': 1.5, + 'categ_id': self.categ_all.id}) + # Create service type product 1.Labour 2.Brokerage 3.Transportation 4.Packaging + self.landed_cost = self._create_services('Landed Cost') + self.brokerage_quantity = self._create_services('Brokerage Cost') + self.transportation_weight = self._create_services('Transportation Cost') + self.packaging_volume = self._create_services('Packaging Cost') + + def _create_services(self, name): + return self.Product.create({ + 'name': name, + 'landed_cost_ok': True, + 'type': 'service'}) diff --git a/addons/stock_landed_costs/tests/test_stock_landed_costs_purchase.py b/addons/stock_landed_costs/tests/test_stock_landed_costs_purchase.py new file mode 100644 index 00000000000..bf0290fe021 --- /dev/null +++ b/addons/stock_landed_costs/tests/test_stock_landed_costs_purchase.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo +from odoo.addons.stock_landed_costs.tests.common import TestStockLandedCostsCommon + +@odoo.tests.common.at_install(False) +@odoo.tests.common.post_install(True) +class TestLandedCosts(TestStockLandedCostsCommon): + + def setUp(self): + super(TestLandedCosts, self).setUp() + # Create picking incoming shipment + self.picking_in = self.Picking.create({ + 'partner_id': self.supplier_id, + 'picking_type_id': self.picking_type_in_id, + 'location_id': self.supplier_location_id, + 'location_dest_id': self.stock_location_id}) + self.Move.create({ + 'name': self.product_refrigerator.name, + 'product_id': self.product_refrigerator.id, + 'product_uom_qty': 5, + 'product_uom': self.product_refrigerator.uom_id.id, + 'picking_id': self.picking_in.id, + 'location_id': self.supplier_location_id, + 'location_dest_id': self.stock_location_id}) + self.Move.create({ + 'name': self.product_oven.name, + 'product_id': self.product_oven.id, + 'product_uom_qty': 10, + 'product_uom': self.product_oven.uom_id.id, + 'picking_id': self.picking_in.id, + 'location_id': self.supplier_location_id, + 'location_dest_id': self.stock_location_id}) + # Create picking outgoing shipment + self.picking_out = self.Picking.create({ + 'partner_id': self.customer_id, + 'picking_type_id': self.picking_type_out_id, + 'location_id': self.stock_location_id, + 'location_dest_id': self.customer_location_id}) + self.Move.create({ + 'name': self.product_refrigerator.name, + 'product_id': self.product_refrigerator.id, + 'product_uom_qty': 2, + 'product_uom': self.product_refrigerator.uom_id.id, + 'picking_id': self.picking_out.id, + 'location_id': self.stock_location_id, + 'location_dest_id': self.customer_location_id}) + + def test_00_landed_costs_on_incoming_shipment(self): + """ Test landed cost on incoming shipment """ + # + # (A) Purchase product + + # Services Quantity Weight Volume + # ----------------------------------------------------- + # 1. Refrigerator 5 10 1 + # 2. Oven 10 20 1.5 + + # (B) Add some costs on purchase + + # Services Amount Split Method + # ------------------------------------------- + # 1.labour 10 By Equal + # 2.brokerage 150 By Quantity + # 3.transportation 250 By Weight + # 4.packaging 20 By Volume + + # Process incoming shipment + income_ship = self._process_incoming_shipment() + # Create landed costs + stock_landed_cost = self._create_landed_costs({ + 'equal_price_unit': 10, + 'quantity_price_unit': 150, + 'weight_price_unit': 250, + 'volume_price_unit': 20}, income_ship) + # Compute landed costs + stock_landed_cost.compute_landed_cost() + + valid_vals = { + 'equal': 5.0, + 'by_quantity_refrigerator': 50.0, + 'by_quantity_oven': 100.0, + 'by_weight_refrigerator': 50.0, + 'by_weight_oven': 200, + 'by_volume_refrigerator': 5.0, + 'by_volume_oven': 15.0} + + # Check valuation adjustment line recognized or not + self._validate_additional_landed_cost_lines(stock_landed_cost, valid_vals) + # Validate the landed cost. + stock_landed_cost.button_validate() + self.assertTrue(stock_landed_cost.account_move_id, 'Landed costs should be available account move lines') + account_entry = self.env['account.move.line'].read_group( + [('move_id', '=', stock_landed_cost.account_move_id.id)], ['debit', 'credit', 'move_id'], ['move_id'])[0] + self.assertEqual(account_entry['debit'], account_entry['credit'], 'Debit and credit are not equal') + self.assertEqual(account_entry['debit'], 430.0, 'Wrong Account Entry') + + def test_01_negative_landed_costs_on_incoming_shipment(self): + """ Test negative landed cost on incoming shipment """ + # + # (A) Purchase Product + + # Services Quantity Weight Volume + # ----------------------------------------------------- + # 1. Refrigerator 5 10 1 + # 2. Oven 10 20 1.5 + + # (B) Sale refrigerator's part of the quantity + + # (C) Add some costs on purchase + + # Services Amount Split Method + # ------------------------------------------- + # 1.labour 10 By Equal + # 2.brokerage 150 By Quantity + # 3.transportation 250 By Weight + # 4.packaging 20 By Volume + + # (D) Decrease cost that already added on purchase + # (apply negative entry) + + # Services Amount Split Method + # ------------------------------------------- + # 1.labour -5 By Equal + # 2.brokerage -50 By Quantity + # 3.transportation -50 By Weight + # 4.packaging -5 By Volume + + # Process incoming shipment + income_ship = self._process_incoming_shipment() + # Refrigerator outgoing shipment. + self._process_outgoing_shipment() + # Apply landed cost for incoming shipment. + stock_landed_cost = self._create_landed_costs({ + 'equal_price_unit': 10, + 'quantity_price_unit': 150, + 'weight_price_unit': 250, + 'volume_price_unit': 20}, income_ship) + # Compute landed costs + stock_landed_cost.compute_landed_cost() + valid_vals = { + 'equal': 5.0, + 'by_quantity_refrigerator': 50.0, + 'by_quantity_oven': 100.0, + 'by_weight_refrigerator': 50.0, + 'by_weight_oven': 200.0, + 'by_volume_refrigerator': 5.0, + 'by_volume_oven': 15.0} + # Check valuation adjustment line recognized or not + self._validate_additional_landed_cost_lines(stock_landed_cost, valid_vals) + # Validate the landed cost. + stock_landed_cost.button_validate() + self.assertTrue(stock_landed_cost.account_move_id, 'Landed costs should be available account move lines') + # Create negative landed cost for previously incoming shipment. + stock_negative_landed_cost = self._create_landed_costs({ + 'equal_price_unit': -5, + 'quantity_price_unit': -50, + 'weight_price_unit': -50, + 'volume_price_unit': -5}, income_ship) + # Compute negative landed costs + stock_negative_landed_cost.compute_landed_cost() + valid_vals = { + 'equal': -2.5, + 'by_quantity_refrigerator': -16.67, + 'by_quantity_oven': -33.33, + 'by_weight_refrigerator': -10.00, + 'by_weight_oven': -40.00, + 'by_volume_refrigerator': -1.25, + 'by_volume_oven': -3.75} + # Check valuation adjustment line recognized or not + self._validate_additional_landed_cost_lines(stock_negative_landed_cost, valid_vals) + # Validate the landed cost. + stock_negative_landed_cost.button_validate() + self.assertEqual(stock_negative_landed_cost.state, 'done', 'Negative landed costs should be in done state') + self.assertTrue(stock_negative_landed_cost.account_move_id, 'Landed costs should be available account move lines') + account_entry = self.env['account.move.line'].read_group( + [('move_id', '=', stock_negative_landed_cost.account_move_id.id)], ['debit', 'credit', 'move_id'], ['move_id'])[0] + self.assertEqual(account_entry['debit'], account_entry['credit'], 'Debit and credit are not equal') + self.assertEqual(account_entry['debit'], 122.168, 'Wrong Account Entry') + + def _process_incoming_shipment(self): + """ Two product incoming shipment. """ + # Confirm incoming shipment. + self.picking_in.action_confirm() + # Transfer incoming shipment + self.picking_in.do_transfer() + return self.picking_in + + def _process_outgoing_shipment(self): + """ One product Outgoing shipment. """ + # Confirm outgoing shipment. + self.picking_out.action_confirm() + # Product assign to outgoing shipments + self.picking_out.action_assign() + # Transfer picking. + self.picking_out.do_transfer() + + def _create_landed_costs(self, value, picking_in): + return self.LandedCost.create(dict( + picking_ids=[(6, 0, [picking_in.id])], + account_journal_id=self.expenses_journal.id, + cost_lines=[ + (0, 0, { + 'name': 'equal split', + 'split_method': 'equal', + 'price_unit': value['equal_price_unit'], + 'product_id': self.landed_cost.id}), + (0, 0, { + 'name': 'split by quantity', + 'split_method': 'by_quantity', + 'price_unit': value['quantity_price_unit'], + 'product_id': self.brokerage_quantity.id}), + (0, 0, { + 'name': 'split by weight', + 'split_method': 'by_weight', + 'price_unit': value['weight_price_unit'], + 'product_id': self.transportation_weight.id}), + (0, 0, { + 'name': 'split by volume', + 'split_method': 'by_volume', + 'price_unit': value['volume_price_unit'], + 'product_id': self.packaging_volume.id}) + ], + )) + + def _validate_additional_landed_cost_lines(self, stock_landed_cost, valid_vals): + for valuation in stock_landed_cost.valuation_adjustment_lines: + add_cost = valuation.additional_landed_cost + split_method = valuation.cost_line_id.split_method + product = valuation.move_id.product_id + if split_method == 'equal': + self.assertEqual(add_cost, valid_vals['equal'], self._error_message(valid_vals['equal'], add_cost)) + elif split_method == 'by_quantity' and product == self.product_refrigerator: + self.assertEqual(add_cost, valid_vals['by_quantity_refrigerator'], self._error_message(valid_vals['by_quantity_refrigerator'], add_cost)) + elif split_method == 'by_quantity' and product == self.product_oven: + self.assertEqual(add_cost, valid_vals['by_quantity_oven'], self._error_message(valid_vals['by_quantity_oven'], add_cost)) + elif split_method == 'by_weight' and product == self.product_refrigerator: + self.assertEqual(add_cost, valid_vals['by_weight_refrigerator'], self._error_message(valid_vals['by_weight_refrigerator'], add_cost)) + elif split_method == 'by_weight' and product == self.product_oven: + self.assertEqual(add_cost, valid_vals['by_weight_oven'], self._error_message(valid_vals['by_weight_oven'], add_cost)) + elif split_method == 'by_volume' and product == self.product_refrigerator: + self.assertEqual(add_cost, valid_vals['by_volume_refrigerator'], self._error_message(valid_vals['by_volume_refrigerator'], add_cost)) + elif split_method == 'by_volume' and product == self.product_oven: + self.assertEqual(add_cost, valid_vals['by_volume_oven'], self._error_message(valid_vals['by_volume_oven'], add_cost)) + + def _error_message(self, actucal_cost, computed_cost): + return 'Additional Landed Cost should be %s instead of %s' % (actucal_cost, computed_cost)