[FIX] purchase_stock,stock_account: add analytic distribution to valuation lines
Steps to reproduce: - Using a company whose currency is USD, make sure to have a rate change with EUR a week before Today. - Create an analytic plan with Default Applicability set to Mandatory. - Create an analytic account for the plan. - Create a Storable Product with a Product Category that has FIFO Costing Method and Automated Inventory Valuation. - Create a Request for Quotation in EUR using the new product and the new analytic account. - Confirm it, click on Receive Products, then Validate. - Go back to the RFQ and click on Create Bill. - Change the date to two weeks ago (in order to trigger an exchange difference). - Confirm it. Current Behavior: A `ValidationError` is raised in `_validate_distribution()`: "One or more lines require a 100% analytic distribution." Cause: This happens because the exchange difference journal items are created without `analytic_distribution` even though they should, since it was set as mandatory. Solution: Make sure the `analytic_distribution` is properly set when creating stock valuation lines. A test is also added to cover this use case. task-3582164 closes odoo/odoo#147387 X-original-commit: fafcf452c68aad27754a8c9bd7bde1742be61cb8 Signed-off-by: Habib Ayob (ayh) <ayh@odoo.com> Signed-off-by: John Laterre (jol) <jol@odoo.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
from odoo import Command, fields
|
||||
from odoo.tests import tagged
|
||||
from .common import PurchaseTestCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPurchaseOrderProcess(PurchaseTestCommon):
|
||||
|
||||
def test_00_cancel_purchase_order_flow(self):
|
||||
@@ -54,3 +57,91 @@ class TestPurchaseOrderProcess(PurchaseTestCommon):
|
||||
})
|
||||
po.button_confirm()
|
||||
self.assertEqual(po.order_line.move_ids.product_packaging_id, packaging)
|
||||
|
||||
def test_analytic_distribution_propagation_with_exchange_difference(self):
|
||||
# Create 2 rates in order to generate an exchange difference later.
|
||||
eur = self.env.ref('base.EUR')
|
||||
eur.write({
|
||||
'rate_ids': [
|
||||
Command.clear(),
|
||||
Command.create({
|
||||
'name': fields.Date.from_string('2023-01-01'),
|
||||
'company_rate': 2.0,
|
||||
}),
|
||||
Command.create({
|
||||
'name': fields.Date.from_string('2023-12-01'),
|
||||
'company_rate': 3.0,
|
||||
}),
|
||||
],
|
||||
'active': True,
|
||||
})
|
||||
|
||||
# Create a mandatory analytic account.
|
||||
analytic_plan = self.env['account.analytic.plan'].create({
|
||||
'name': 'Analytic Plan',
|
||||
'default_applicability': 'mandatory',
|
||||
})
|
||||
analytic_account = self.env['account.analytic.account'].create({
|
||||
'name': 'Analytic Account',
|
||||
'plan_id': analytic_plan.id},
|
||||
)
|
||||
|
||||
# Create a storable product with FIFO costing method and automated inventory valuation.
|
||||
analytic_product_category = self.env['product.category'].create({
|
||||
'name': 'Analytic Product Category',
|
||||
'property_cost_method': 'fifo',
|
||||
'property_valuation': 'real_time',
|
||||
})
|
||||
analytic_product = self.env['product.product'].create({
|
||||
'name': 'Analytic Product',
|
||||
'detailed_type': 'product',
|
||||
'categ_id': analytic_product_category.id,
|
||||
'lst_price': 100.0,
|
||||
'standard_price': 25.0,
|
||||
})
|
||||
|
||||
# Create and confirm a Purchase Order using aforementioned product and currency.
|
||||
purchase_order = self.env['purchase.order'].create({
|
||||
'date_order': fields.Date.from_string('2023-12-04'),
|
||||
'currency_id': eur.id,
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': analytic_product.id,
|
||||
'product_qty': 10.0,
|
||||
'analytic_distribution': {analytic_account.id: 100},
|
||||
}),
|
||||
],
|
||||
})
|
||||
purchase_order.button_confirm()
|
||||
|
||||
# Make sure a stock move has been created to replenish the product.
|
||||
self.assertEqual(len(purchase_order.picking_ids.move_ids), 1)
|
||||
|
||||
stock_move = purchase_order.picking_ids.move_ids
|
||||
stock_move.quantity = stock_move.product_uom_qty
|
||||
|
||||
purchase_order.picking_ids.button_validate()
|
||||
purchase_order.action_create_invoice()
|
||||
|
||||
# Make sure a first Journal Entry has been created (to account for the stock move).
|
||||
self.assertEqual(len(stock_move.account_move_ids), 1)
|
||||
stock_account_move = stock_move.account_move_ids
|
||||
|
||||
# Make sure the Vendor Bill has been created,
|
||||
# and confirm it at an earlier date (to generate the exchange difference).
|
||||
self.assertEqual(len(purchase_order.invoice_ids), 1)
|
||||
|
||||
vendor_bill = purchase_order.invoice_ids
|
||||
vendor_bill.invoice_date = fields.Date.from_string('2023-11-01')
|
||||
vendor_bill.action_post()
|
||||
|
||||
# Make sure a second Journal Entry has been created (to account for the exchange difference).
|
||||
self.assertEqual(len(stock_move.account_move_ids), 2)
|
||||
exchange_account_move = stock_move.account_move_ids - stock_account_move
|
||||
|
||||
# Make sure both exchange Journal Items have the correct analytic distribution.
|
||||
self.assertEqual(len(exchange_account_move.line_ids), 2)
|
||||
for line in exchange_account_move.line_ids:
|
||||
self.assertTrue(line.analytic_distribution)
|
||||
self.assertEqual(line.analytic_distribution[str(analytic_account.id)], 100)
|
||||
|
||||
@@ -463,29 +463,33 @@ class StockMove(models.Model):
|
||||
def _generate_valuation_lines_data(self, partner_id, qty, debit_value, credit_value, debit_account_id, credit_account_id, svl_id, description):
|
||||
# This method returns a dictionary to provide an easy extension hook to modify the valuation lines (see purchase for an example)
|
||||
self.ensure_one()
|
||||
debit_line_vals = {
|
||||
|
||||
line_vals = {
|
||||
'name': description,
|
||||
'product_id': self.product_id.id,
|
||||
'quantity': qty,
|
||||
'product_uom_id': self.product_id.uom_id.id,
|
||||
'ref': description,
|
||||
'partner_id': partner_id,
|
||||
'balance': debit_value,
|
||||
'account_id': debit_account_id,
|
||||
}
|
||||
|
||||
credit_line_vals = {
|
||||
'name': description,
|
||||
'product_id': self.product_id.id,
|
||||
'quantity': qty,
|
||||
'product_uom_id': self.product_id.uom_id.id,
|
||||
'ref': description,
|
||||
'partner_id': partner_id,
|
||||
'balance': -credit_value,
|
||||
'account_id': credit_account_id,
|
||||
svl = self.env['stock.valuation.layer'].browse(svl_id)
|
||||
if svl.account_move_line_id.analytic_distribution:
|
||||
line_vals['analytic_distribution'] = svl.account_move_line_id.analytic_distribution
|
||||
|
||||
rslt = {
|
||||
'credit_line_vals': {
|
||||
**line_vals,
|
||||
'balance': -credit_value,
|
||||
'account_id': credit_account_id,
|
||||
},
|
||||
'debit_line_vals': {
|
||||
**line_vals,
|
||||
'balance': debit_value,
|
||||
'account_id': debit_account_id,
|
||||
},
|
||||
}
|
||||
|
||||
rslt = {'credit_line_vals': credit_line_vals, 'debit_line_vals': debit_line_vals}
|
||||
if credit_value != debit_value:
|
||||
# for supplier returns of product in average costing method, in anglo saxon mode
|
||||
diff_amount = debit_value - credit_value
|
||||
|
||||
Reference in New Issue
Block a user