mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 19:12:06 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -1,10 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields
|
||||
import datetime
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.addons.stock_account.tests.test_stockvaluationlayer import TestStockValuationCommon
|
||||
from odoo.addons.stock_account.tests.common import TestStockValuationCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
|
|
@ -12,9 +13,15 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSaleStockMargin, cls).setUpClass()
|
||||
cls.pricelist = cls.env['product.pricelist'].create({'name': 'Simple Pricelist'})
|
||||
super().setUpClass()
|
||||
cls.pricelist = cls.env['product.pricelist'].create({
|
||||
'name': 'Simple Pricelist',
|
||||
'company_id': False,
|
||||
})
|
||||
cls.env['res.currency.rate'].search([]).unlink()
|
||||
cls.customer = cls.env['res.partner'].create({
|
||||
'name': 'Customer',
|
||||
})
|
||||
|
||||
#########
|
||||
# UTILS #
|
||||
|
|
@ -23,8 +30,8 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
def _create_sale_order(self):
|
||||
return self.env['sale.order'].create({
|
||||
'name': 'Sale order',
|
||||
'partner_id': self.env.ref('base.partner_admin').id,
|
||||
'partner_invoice_id': self.env.ref('base.partner_admin').id,
|
||||
'partner_id': self.customer.id,
|
||||
'partner_invoice_id': self.customer.id,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
})
|
||||
|
||||
|
|
@ -35,17 +42,28 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
'price_unit': price_unit,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': quantity,
|
||||
'product_uom': self.env.ref('uom.product_uom_unit').id,
|
||||
})
|
||||
|
||||
def _create_product(self):
|
||||
product_template = self.env['product.template'].create({
|
||||
'name': 'Super product',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'categ_id': self.env.ref('product.product_category_goods').id,
|
||||
})
|
||||
product_template.categ_id.property_cost_method = 'fifo'
|
||||
return product_template.product_variant_ids
|
||||
|
||||
def _setup_multicurrency(self):
|
||||
usd = self.env.ref('base.USD')
|
||||
self.company_currency = self.env.company.currency_id
|
||||
self.other_currency = self.env.ref('base.EUR') if self.company_currency == usd else usd
|
||||
date = fields.Date.today()
|
||||
self.env['res.currency.rate'].create([
|
||||
{'currency_id': self.company_currency.id, 'rate': 1, 'name': date},
|
||||
{'currency_id': self.other_currency.id, 'rate': 2, 'name': date},
|
||||
])
|
||||
return self.company_currency, self.other_currency
|
||||
|
||||
#########
|
||||
# TESTS #
|
||||
#########
|
||||
|
|
@ -63,7 +81,7 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
self.assertEqual(order_line.purchase_price, 35)
|
||||
self.assertEqual(sale_order.margin, 15)
|
||||
|
||||
sale_order.picking_ids.move_ids.quantity_done = 1
|
||||
sale_order.picking_ids.move_ids.write({'quantity': 1, 'picked': True})
|
||||
sale_order.picking_ids.button_validate()
|
||||
|
||||
self.assertEqual(order_line.purchase_price, 35)
|
||||
|
|
@ -81,10 +99,10 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
order_line = self._create_sale_order_line(sale_order, product, 2, 50)
|
||||
sale_order.action_confirm()
|
||||
|
||||
self.assertEqual(order_line.purchase_price, 32)
|
||||
self.assertAlmostEqual(sale_order.margin, 36)
|
||||
self.assertEqual(order_line.purchase_price, 19.5)
|
||||
self.assertAlmostEqual(sale_order.margin, 61)
|
||||
|
||||
sale_order.picking_ids.move_ids.quantity_done = 2
|
||||
sale_order.picking_ids.move_ids.write({'quantity': 2, 'picked': True})
|
||||
sale_order.picking_ids.button_validate()
|
||||
|
||||
self.assertAlmostEqual(order_line.purchase_price, 24.5)
|
||||
|
|
@ -104,7 +122,7 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
self.assertEqual(order_line.purchase_price, 10)
|
||||
self.assertAlmostEqual(sale_order.margin, 20)
|
||||
|
||||
sale_order.picking_ids.move_ids.quantity_done = 1
|
||||
sale_order.picking_ids.move_ids.write({'quantity': 1, 'picked': True})
|
||||
sale_order.picking_ids.button_validate()
|
||||
|
||||
self.assertAlmostEqual(order_line.purchase_price, 10)
|
||||
|
|
@ -122,12 +140,11 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
order_line = self._create_sale_order_line(sale_order, product, 2, 20)
|
||||
sale_order.action_confirm()
|
||||
|
||||
self.assertEqual(order_line.purchase_price, 10)
|
||||
self.assertAlmostEqual(sale_order.margin, 20)
|
||||
self.assertEqual(order_line.purchase_price, 15)
|
||||
self.assertAlmostEqual(sale_order.margin, 10)
|
||||
|
||||
sale_order.picking_ids.move_ids.quantity_done = 1
|
||||
res = sale_order.picking_ids.button_validate()
|
||||
Form(self.env[res['res_model']].with_context(res['context'])).save().process()
|
||||
sale_order.picking_ids.move_ids.write({'quantity': 1, 'picked': True})
|
||||
Form.from_action(self.env, sale_order.picking_ids.button_validate()).save().process()
|
||||
|
||||
self.assertAlmostEqual(order_line.purchase_price, 15)
|
||||
self.assertAlmostEqual(order_line.margin, 10)
|
||||
|
|
@ -150,17 +167,16 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
order_line_2 = self._create_sale_order_line(sale_order, product_2, 4, 20)
|
||||
sale_order.action_confirm()
|
||||
|
||||
self.assertAlmostEqual(order_line_1.purchase_price, 35)
|
||||
self.assertAlmostEqual(order_line_2.purchase_price, 17)
|
||||
self.assertAlmostEqual(order_line_1.margin, 25 * 2)
|
||||
self.assertAlmostEqual(order_line_2.margin, 3 * 4)
|
||||
self.assertAlmostEqual(sale_order.margin, 62)
|
||||
self.assertAlmostEqual(order_line_1.purchase_price, 43)
|
||||
self.assertAlmostEqual(order_line_2.purchase_price, 14)
|
||||
self.assertAlmostEqual(order_line_1.margin, 17 * 2)
|
||||
self.assertAlmostEqual(order_line_2.margin, 6 * 4)
|
||||
self.assertAlmostEqual(sale_order.margin, 58)
|
||||
|
||||
sale_order.picking_ids.move_ids[0].quantity_done = 2
|
||||
sale_order.picking_ids.move_ids[1].quantity_done = 3
|
||||
sale_order.picking_ids.move_ids[0].write({'quantity': 2, 'picked': True})
|
||||
sale_order.picking_ids.move_ids[1].write({'quantity': 3, 'picked': True})
|
||||
|
||||
res = sale_order.picking_ids.button_validate()
|
||||
Form(self.env[res['res_model']].with_context(res['context'])).save().process()
|
||||
Form.from_action(self.env, sale_order.picking_ids.button_validate()).save().process()
|
||||
|
||||
self.assertAlmostEqual(order_line_1.purchase_price, 43) # (35 + 51) / 2
|
||||
self.assertAlmostEqual(order_line_2.purchase_price, 12.5) # (17 + 11 + 11 + 11) / 4
|
||||
|
|
@ -170,16 +186,17 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
|
||||
def test_sale_stock_margin_6(self):
|
||||
""" Test that the purchase price doesn't change when there is a service product in the SO"""
|
||||
product = self.product_standard
|
||||
service = self.env['product.product'].create({
|
||||
'name': 'Service',
|
||||
'type': 'service',
|
||||
'list_price': 100.0,
|
||||
'standard_price': 50.0})
|
||||
self.product1.list_price = 80.0
|
||||
self.product1.standard_price = 40.0
|
||||
product.list_price = 80.0
|
||||
product.standard_price = 40.0
|
||||
sale_order = self._create_sale_order()
|
||||
order_line_1 = self._create_sale_order_line(sale_order, service, 1, 100)
|
||||
order_line_2 = self._create_sale_order_line(sale_order, self.product1, 1, 80)
|
||||
order_line_2 = self._create_sale_order_line(sale_order, product, 1, 80)
|
||||
|
||||
self.assertEqual(order_line_1.purchase_price, 50, "Sales order line cost should be 50.00")
|
||||
self.assertEqual(order_line_2.purchase_price, 40, "Sales order line cost should be 40.00")
|
||||
|
|
@ -199,18 +216,7 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
self.assertEqual(order_line_2.purchase_price, 40, "Sales order line cost should be 40.00")
|
||||
|
||||
def test_so_and_multicurrency(self):
|
||||
ResCurrencyRate = self.env['res.currency.rate']
|
||||
company_currency = self.env.company.currency_id
|
||||
other_currency = self.env.ref('base.EUR') if company_currency == self.env.ref('base.USD') else self.env.ref('base.USD')
|
||||
|
||||
date = fields.Date.today()
|
||||
ResCurrencyRate.create({'currency_id': company_currency.id, 'rate': 1, 'name': date})
|
||||
other_currency_rate = ResCurrencyRate.search([('name', '=', date), ('currency_id', '=', other_currency.id)])
|
||||
if other_currency_rate:
|
||||
other_currency_rate.rate = 2
|
||||
else:
|
||||
ResCurrencyRate.create({'currency_id': other_currency.id, 'rate': 2, 'name': date})
|
||||
|
||||
_company_currency, other_currency = self._setup_multicurrency()
|
||||
so = self._create_sale_order()
|
||||
so.pricelist_id = self.env['product.pricelist'].create({
|
||||
'name': 'Super Pricelist',
|
||||
|
|
@ -252,10 +258,13 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
'currency_id': new_company_currency.id,
|
||||
})
|
||||
self.env.user.company_id = new_company.id
|
||||
self.env = self.env.user.with_company(new_company.id).env
|
||||
|
||||
self.pricelist.currency_id = new_company_currency.id
|
||||
|
||||
product = self._create_product()
|
||||
product.categ_id.property_cost_method = 'fifo'
|
||||
product.standard_price = 100
|
||||
|
||||
incoming_picking_type = self.env['stock.picking.type'].search([('company_id', '=', new_company.id), ('code', '=', 'incoming')], limit=1)
|
||||
production_location = self.env['stock.location'].search([('company_id', '=', new_company.id), ('usage', '=', 'production')])
|
||||
|
|
@ -266,21 +275,16 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
'location_dest_id': incoming_picking_type.default_location_dest_id.id,
|
||||
})
|
||||
self.env['stock.move'].create({
|
||||
'name': 'Incoming Product',
|
||||
'product_id': product.id,
|
||||
'location_id': production_location.id,
|
||||
'location_dest_id': incoming_picking_type.default_location_dest_id.id,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 100,
|
||||
'picking_type_id': incoming_picking_type.id,
|
||||
'picking_id': picking.id,
|
||||
})
|
||||
picking.action_confirm()
|
||||
res_dict = picking.button_validate()
|
||||
wizard = Form(self.env[(res_dict.get('res_model'))].with_context(res_dict['context'])).save()
|
||||
wizard.process()
|
||||
|
||||
picking.button_validate()
|
||||
self.pricelist.currency_id = new_company_currency.id
|
||||
partner = self.env['res.partner'].create({'name': 'Super Partner'})
|
||||
so = self.env['sale.order'].create({
|
||||
|
|
@ -298,6 +302,7 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
self.assertEqual(sol.margin, 100)
|
||||
|
||||
def test_purchase_price_changes(self):
|
||||
self._setup_multicurrency()
|
||||
so = self._create_sale_order()
|
||||
product = self._create_product()
|
||||
product.categ_id.property_cost_method = 'standard'
|
||||
|
|
@ -310,7 +315,318 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
|||
so = so_form.save()
|
||||
email_act = so.action_quotation_send()
|
||||
email_ctx = email_act.get('context', {})
|
||||
so.with_context(**email_ctx).message_post_with_template(email_ctx.get('default_template_id'))
|
||||
so.with_context(**email_ctx).message_post_with_source(
|
||||
self.env['mail.template'].browse(email_ctx.get('default_template_id')),
|
||||
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
|
||||
)
|
||||
|
||||
self.assertEqual(so.state, 'sent')
|
||||
self.assertEqual(so.order_line[0].purchase_price, 15)
|
||||
so.action_confirm()
|
||||
self.assertEqual(so.order_line[0].purchase_price, 15)
|
||||
|
||||
# Set SO back to draft, and trigger purchase price recompute via currency change
|
||||
so.with_context(disable_cancel_warning=True).action_cancel()
|
||||
so.action_draft()
|
||||
so.currency_id = self.other_currency
|
||||
self.assertEqual(so.order_line.move_ids.state, 'cancel')
|
||||
self.assertEqual(so.order_line.purchase_price, 40)
|
||||
|
||||
def test_add_product_on_delivery_price_unit_on_sale(self):
|
||||
""" Adding a product directly on a sale order's delivery should result in the new SOL
|
||||
having its `purchase_price` and `margin` + `margin_percent` fields correctly calculated.
|
||||
"""
|
||||
products = [self._create_product() for _ in range(2)]
|
||||
for product, cost, price in zip(products, [20, 10], [25, 20]):
|
||||
product.categ_id.property_cost_method = 'standard'
|
||||
product.write({
|
||||
'standard_price': cost,
|
||||
'list_price': price,
|
||||
'invoice_policy': 'delivery',
|
||||
})
|
||||
sale_order = self._create_sale_order()
|
||||
self._create_sale_order_line(sale_order, products[0], 10, products[0].list_price)
|
||||
sale_order.action_confirm()
|
||||
delivery = sale_order.picking_ids[0]
|
||||
with Form(delivery) as delivery_form:
|
||||
with delivery_form.move_ids.new() as move:
|
||||
move.product_id = products[1]
|
||||
move.product_uom_qty = 10
|
||||
delivery.move_ids.quantity = 10
|
||||
delivery.button_validate()
|
||||
self.assertRecordValues(
|
||||
sale_order.order_line.filtered(lambda sol: sol.product_id == products[1]),
|
||||
[{
|
||||
'price_unit': products[1].list_price,
|
||||
'purchase_price': products[1].standard_price,
|
||||
'margin': 100,
|
||||
'margin_percent': 0.5,
|
||||
}]
|
||||
)
|
||||
|
||||
def test_add_standard_product_on_delivery_cost_on_sale_order(self):
|
||||
""" test that if product with standard cost method is added in delivery, the cost is computed."""
|
||||
product = self.product_standard
|
||||
product.write({
|
||||
'standard_price': 20,
|
||||
'list_price': 25,
|
||||
'invoice_policy': 'order',
|
||||
})
|
||||
product2 = self.env['product.product'].create({
|
||||
'name': 'product2',
|
||||
'type': 'consu',
|
||||
'is_storable': True,
|
||||
'standard_price': 10,
|
||||
'list_price': 20,
|
||||
'invoice_policy': 'order',
|
||||
})
|
||||
sale_order = self._create_sale_order()
|
||||
self._create_sale_order_line(sale_order, product, 10, product.list_price)
|
||||
sale_order.action_confirm()
|
||||
delivery = sale_order.picking_ids[0]
|
||||
with Form(delivery) as delivery_form:
|
||||
with delivery_form.move_ids.new() as move:
|
||||
move.product_id = product2
|
||||
move.product_uom_qty = 10
|
||||
delivery.move_ids.quantity = 10
|
||||
delivery.button_validate()
|
||||
self.assertEqual(sale_order.order_line.filtered(lambda sol: sol.product_id == product2).purchase_price, 10)
|
||||
|
||||
def test_add_avco_product_on_delivery_cost_on_sale_order(self):
|
||||
""" test that if product with avco cost method and an order "invoice_policy" is added in delivery, the cost is computed."""
|
||||
categ_average = self.env['product.category'].create({
|
||||
'name': 'AVERAGE',
|
||||
'property_cost_method': 'average'
|
||||
})
|
||||
self.product = self.product_avco
|
||||
self.product.write({
|
||||
'standard_price': 20,
|
||||
'list_price': 25,
|
||||
'invoice_policy': 'order',
|
||||
})
|
||||
product2 = self.env['product.product'].create({
|
||||
'name': 'product2',
|
||||
'type': 'consu',
|
||||
'is_storable': True,
|
||||
'categ_id': categ_average.id,
|
||||
'standard_price': 10,
|
||||
'list_price': 20,
|
||||
'invoice_policy': 'order',
|
||||
})
|
||||
sale_order = self._create_sale_order()
|
||||
self._create_sale_order_line(sale_order, self.product, 10, self.product.list_price)
|
||||
sale_order.action_confirm()
|
||||
delivery = sale_order.picking_ids[0]
|
||||
with Form(delivery) as delivery_form:
|
||||
with delivery_form.move_ids.new() as move:
|
||||
move.product_id = product2
|
||||
move.product_uom_qty = 10
|
||||
delivery.move_ids.quantity = 10
|
||||
delivery.button_validate()
|
||||
self.assertEqual(sale_order.order_line.filtered(lambda sol: sol.product_id == product2).purchase_price, 10)
|
||||
|
||||
def test_avco_does_not_mix_products_on_compute_avg_price(self):
|
||||
"""
|
||||
Ensure that when stock moves are duplicated and their product changed,
|
||||
the sale line linkage is cleared correctly, preventing average price
|
||||
computation from mixing valuation layers of different products.
|
||||
This test verifies that:
|
||||
- The duplicated delivery's moves lose the original sale_line_id when the product changes.
|
||||
- A new sale order line is created for the new product, increasing the total order lines.
|
||||
- Validations of deliveries and return pickings proceed without errors.
|
||||
- The purchase price on the original sale line remains accurate (unchanged).
|
||||
"""
|
||||
self.product_avco_auto.uom_id = self.env.ref('uom.product_uom_dozen').id
|
||||
sale_order = self._create_sale_order()
|
||||
sale_order_line = self._create_sale_order_line(sale_order, self.product_avco, 1)
|
||||
sale_order.action_confirm()
|
||||
|
||||
first_delivery = sale_order.picking_ids
|
||||
second_delivery = first_delivery.copy()
|
||||
self.assertEqual(second_delivery.move_ids.sale_line_id, sale_order_line)
|
||||
second_delivery.move_ids.product_id = self.product_avco_auto
|
||||
self.assertFalse(second_delivery.move_ids.sale_line_id)
|
||||
self.assertTrue(len(sale_order.order_line), 2)
|
||||
second_delivery.action_confirm()
|
||||
second_delivery.move_ids.quantity = 1
|
||||
second_delivery.button_validate()
|
||||
self.assertEqual(second_delivery.move_ids.sale_line_id, sale_order.order_line - sale_order_line)
|
||||
stock_picking_return = self.env['stock.return.picking'].create({
|
||||
'picking_id': second_delivery.id,
|
||||
})
|
||||
stock_picking_return.product_return_moves.quantity = 1
|
||||
return_picking = stock_picking_return._create_return()
|
||||
return_picking.move_ids.quantity = 1
|
||||
return_picking.button_validate()
|
||||
self.assertEqual(return_picking.state, 'done')
|
||||
|
||||
first_delivery.move_ids.quantity = 1
|
||||
first_delivery.button_validate()
|
||||
self.assertEqual(first_delivery.state, 'done')
|
||||
self.assertEqual(sale_order_line.purchase_price, 10)
|
||||
|
||||
def test_avco_different_uom(self):
|
||||
pack_of_6 = self.ref('uom.product_uom_pack_6')
|
||||
self.product_avco.write({
|
||||
'standard_price': 1,
|
||||
'list_price': 3,
|
||||
'uom_ids': [pack_of_6],
|
||||
})
|
||||
sale_order = self._create_sale_order()
|
||||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'name': 'Sale order',
|
||||
'order_id': sale_order.id,
|
||||
'product_id': self.product_avco.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom_id': pack_of_6,
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
self.assertEqual(sale_order_line.margin, 12.0)
|
||||
|
||||
def test_avco_calc(self):
|
||||
""" test purchase_price and margin correct calculation for avco product"""
|
||||
# need to freezetime due to test being too fast resulting in inconsistent AVCO calculation for in/out moves having the same exact validation date
|
||||
with freeze_time() as freeze:
|
||||
so = self._create_sale_order()
|
||||
self.product_avco_auto.list_price = 100
|
||||
self._make_in_move(self.product_avco_auto, 2, 20)
|
||||
self._make_in_move(self.product_avco_auto, 2, 40)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 30, "standard_price for avco = (2 * 20 + 2 * 40) / (2 + 2) = 30: 4 in stock")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
|
||||
# SOL quantity=2, qty_delivered=0
|
||||
sol = self._create_sale_order_line(so, self.product_avco_auto, 2, 100)
|
||||
self.assertEqual(sol.product_uom_qty, 2)
|
||||
self.assertEqual(sol.qty_delivered, 0)
|
||||
self.assertEqual(sol.purchase_price, 30, "purchase_price should match product's standard_price")
|
||||
self.assertEqual(sol.margin, 140, "margin = (sale price - purchase_price) * SOL quantity = (100 - 30) * 2 = 140")
|
||||
|
||||
# SOL quantity=2, qty_delivered=1
|
||||
so.action_confirm()
|
||||
move = sol.move_ids
|
||||
move.quantity = 1
|
||||
delivery = move.picking_id
|
||||
backorder_wizard_values = delivery.button_validate()
|
||||
backorder_wizard = self.env[(backorder_wizard_values.get('res_model'))].browse(backorder_wizard_values.get('res_id')).with_context(backorder_wizard_values['context'])
|
||||
backorder_wizard.process()
|
||||
# purchase_unit_from_delivery = line.move_ids(done)._get_price_unit = (1 * 30) / (1) = 30
|
||||
# qty_from_std_price = max(SOL quantity - qty_from_delivery, 0) = 2 - 1 = 1
|
||||
self.assertEqual(sol.purchase_price, 30, "purchase_price = (qty_delivered * purchase_unit_from_delivery + qty_from_std_price * standard_price)/(qty_from_delivery + qty_from_std_price) = (1 * 30 + 1 * 30)/ (1 + 1) = 30")
|
||||
self.assertEqual(sol.margin, 140, "margin = (sale price - purchase_price) * SOL quantity = (100 - 30) * 2 = 140")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
|
||||
# SOL quantity=2, qty_delivered=3
|
||||
self._make_in_move(self.product_avco_auto, 2, 142.5)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 75, "standard_price for avco = (3 * 30 + 2 * 142.5) / (3 + 2) = 75: 3 remaining + 2 added to stock")
|
||||
self.assertEqual(sol.purchase_price, 30, "purchase_price shouldn't have changed")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
move = sol.move_ids.filtered(lambda m: m.state != 'done')
|
||||
move.quantity = 2
|
||||
delivery = move.picking_id
|
||||
delivery.button_validate()
|
||||
# purchase_unit_from_delivery = line.move_ids(done)._get_price_unit = (1 * 30 + 2 * 75) / (1 + 2) = 60
|
||||
# qty_from_std_price = max(SOL quantity - qty_from_delivery, 0) = max(2 - 3, 0) = 0
|
||||
self.assertEqual(sol.purchase_price, 60, "purchase_price = (qty_delivered * purchase_unit_from_delivery + qty_from_std_price * standard_price)/(qty_from_delivery + qty_from_std_price) = (3 * 60 + 0 * 75)/ (3 + 0) = 60")
|
||||
self.assertEqual(sol.margin, 80, "margin = (sale price - purchase_price) * SOL quantity = (100 - 60) * 2 = 80")
|
||||
|
||||
def test_avco_zero_quantity(self):
|
||||
""" test that the purchase_price and margin are still calculated correctly when 0 quantity SOL
|
||||
including when a return is done for avco valuated product"""
|
||||
# need to freezetime due to test being too fast resulting in inconsistent AVCO calculation for in/out moves having the same exact validation date
|
||||
with freeze_time() as freeze:
|
||||
so = self._create_sale_order()
|
||||
self.product_avco_auto.list_price = 100
|
||||
self._make_in_move(self.product_avco_auto, 2, 20)
|
||||
self._make_in_move(self.product_avco_auto, 2, 40)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 30, "standard_price for avco = (2 * 20 + 2 * 40) / (2 + 2) = 30: 4 in stock")
|
||||
sol = self._create_sale_order_line(so, self.product_avco_auto, 1, 100)
|
||||
# SOL quantity=1, qty_delivered=0
|
||||
self.assertEqual(sol.product_uom_qty, 1)
|
||||
self.assertEqual(sol.qty_delivered, 0)
|
||||
self.assertEqual(sol.purchase_price, self.product_avco_auto.standard_price, "purchase_price should match product's standard_price")
|
||||
self.assertEqual(sol.margin, 70, "margin = (sale price - purchase_price) * SOL quantity = (100 - 30) * 1 = 70")
|
||||
so.action_confirm()
|
||||
|
||||
# SOL quantity=0, qty_delivered=0
|
||||
sol2 = self._create_sale_order_line(so, self.product_avco_auto, 0, 90)
|
||||
self.assertEqual(sol2.product_uom_qty, 0)
|
||||
self.assertEqual(sol2.qty_delivered, 0)
|
||||
self.assertEqual(sol2.purchase_price, self.product_avco_auto.standard_price, "0 Quantity and 0 Delivered => default to standard price")
|
||||
self.assertEqual(sol2.margin, 0, "margin = 0 if no quantities sold/delivered")
|
||||
|
||||
# SOL quantity=1, qty_delivered=1
|
||||
self._make_in_move(self.product_avco_auto, 2, 60)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 40, "standard_price for avco = (4 * 30 + 2 * 60) / (4 + 2) = 40: 4 existing + 2 added to stock")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
move = sol.move_ids
|
||||
move.quantity = sol.product_uom_qty
|
||||
delivery = move.picking_id
|
||||
delivery.button_validate()
|
||||
self.assertEqual(sol.product_uom_qty, 1)
|
||||
self.assertEqual(sol.qty_delivered, 1)
|
||||
self.assertEqual(sol.purchase_price, self.product_avco_auto.standard_price, "purchase_price should match product's standard_price")
|
||||
self.assertEqual(sol.margin, 60, "margin = (sale price - purchase_price) * SOL quantity = (100 - 40) * 1 = 60")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
|
||||
# SOL quantity=1, qty_delivered=-2
|
||||
self._make_in_move(self.product_avco_auto, 2, 5)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 30, "standard_price for avco = (5 * 40 + 2 * 5) / (5 + 2) = 30: 1 delivered, 5 remaining + 2 added to stock")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
stock_return_picking_form = Form(self.env['stock.return.picking'].with_context(active_id=delivery.id, active_model='stock.picking'))
|
||||
stock_return_picking = stock_return_picking_form.save()
|
||||
stock_return_picking.product_return_moves.quantity = 3.0
|
||||
stock_return_picking_action = stock_return_picking.action_create_returns()
|
||||
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
|
||||
return_pick.button_validate()
|
||||
self.assertEqual(sol.product_uom_qty, 1)
|
||||
self.assertEqual(sol.qty_delivered, -2)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 33, "standard_price for avco = (7 * 30 + 3 * 40) / (7 + 3)) = 33: 7 remaining + 3 returned")
|
||||
self.assertEqual(sol.purchase_price, self.product_avco_auto.standard_price, "< 0 Delivered => default to standard price")
|
||||
self.assertEqual(sol.margin, 67, "margin = (sale price - purchase_price) * SOL quantity = (100 - 33) * 1 = 67")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
|
||||
# SOL quantity=0, qty_delivered=-2
|
||||
self._make_in_move(self.product_avco_auto, 2, 30)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 32.5, "standard_price for avco = (10 * 33 + 2 * 30) / 12 = 32.5: 10 remaining + 2 added to stock")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
sol.product_uom_qty = 0
|
||||
self.assertEqual(sol.purchase_price, self.product_avco_auto.standard_price, "< 0 Delivered => default to standard price")
|
||||
self.assertEqual(sol.margin, -135, "margin = (sale price - purchase_price) * qty_delivered = (100 - 32.5) * -2 = -135")
|
||||
|
||||
# SOL quantity=0, qty_delivered=2
|
||||
so2 = self._create_sale_order()
|
||||
# throwaway product so we can deliver extra product in delivery
|
||||
throwaway_sol = self._create_sale_order_line(so2, self.product_standard, 1, 100)
|
||||
so2.action_confirm()
|
||||
move = throwaway_sol.move_ids
|
||||
move.quantity = throwaway_sol.product_uom_qty
|
||||
delivery = move.picking_id
|
||||
with Form(delivery) as delivery_form:
|
||||
with delivery_form.move_ids.new() as extra_move:
|
||||
extra_move.product_id = self.product_avco_auto
|
||||
extra_move.quantity = 2
|
||||
delivery.button_validate()
|
||||
sol3 = so2.order_line - throwaway_sol
|
||||
self.assertEqual(sol3.product_uom_qty, 0)
|
||||
self.assertEqual(sol3.qty_delivered, 2)
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 32.5, 'no new incoming moves, std price should be unchanged')
|
||||
# purchase_unit_from_delivery = line.move_ids(done)._get_price_unit = (2 * 32.5) / 2 = 32.5
|
||||
self.assertEqual(sol3.purchase_price, self.product_avco_auto.standard_price, "purchase_price should match product's standard_price")
|
||||
self.assertEqual(sol3.margin, -65, "margin = SOL qty * sale price - purchase_price * qty_delivered = (0 - 32.5) * 2 = -65")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
|
||||
# SOL quantity=0, qty_delivered=2-1=1, returned = 1
|
||||
self._make_in_move(self.product_avco_auto, 2, 17.5) # force different standard_price
|
||||
self.assertEqual(self.product_avco_auto.standard_price, 30, "standard_price for avco = (10 * 32.5 + 2 * 17.5) / (10 + 2) = 30: 2 delivered, 10 remaining + 2 added to stock")
|
||||
freeze.tick(delta=datetime.timedelta(seconds=2))
|
||||
stock_return_picking_form = Form(self.env['stock.return.picking'].with_context(active_id=delivery.id, active_model='stock.picking'))
|
||||
stock_return_picking = stock_return_picking_form.save()
|
||||
stock_return_picking.product_return_moves.quantity = 1.0
|
||||
stock_return_picking_action = stock_return_picking.action_create_returns()
|
||||
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
|
||||
return_pick.button_validate()
|
||||
self.assertEqual(sol3.product_uom_qty, 0)
|
||||
self.assertEqual(sol3.qty_delivered, 1)
|
||||
# purchase_unit_from_delivery = line.move_ids(done)._get_price_unit = (2 * 32.5 + 1 * 32.5) / (2 + 1) = 32.5
|
||||
self.assertEqual(sol3.purchase_price, 32.5, "purchase_price = 2 * 32.5 + 1 * 32.5) / (2 + 1) = 32.5")
|
||||
self.assertEqual(sol3.margin, -32.5, "margin = SOL qty * sale price - purchase_price * qty_delivered = (0 - 32.5) * 1 = -32.5")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue