oca-ocb-accounting/odoo-bringout-oca-ocb-stock_account/stock_account/models/product.py
Ernad Husremovic 768b70e05e 19.0 vanilla
2026-03-09 09:30:07 +01:00

676 lines
33 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from bisect import bisect
from collections import defaultdict
from datetime import datetime
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.fields import Domain
from odoo.tools import SQL
class ProductTemplate(models.Model):
_inherit = 'product.template'
cost_method = fields.Selection(
string="Cost Method",
selection=[
('standard', "Standard Price"),
('fifo', "First In First Out (FIFO)"),
('average', "Average Cost (AVCO)"),
],
compute='_compute_cost_method',
)
valuation = fields.Selection(
string="Valuation",
selection=[
('periodic', 'Periodic (at closing)'),
('real_time', 'Perpetual (at invoicing)'),
],
compute='_compute_valuation', search='_search_valuation',
)
lot_valuated = fields.Boolean(
string="Valuation by Lot/Serial",
compute='_compute_lot_valuated', store=True, readonly=False,
help="If checked, the valuation will be specific by Lot/Serial number.",
)
# TODO remove in master
property_price_difference_account_id = fields.Many2one(
'account.account', 'Price Difference Account', company_dependent=True, ondelete='restrict',
check_company=True,
help="""With perpetual valuation, this account will hold the price difference between the standard price and the bill price.""")
def _search_valuation(self, operator, value):
if operator != '=':
raise UserError(self.env._("You can only use the '=' operator to search on valuation field."))
if value not in ['periodic', 'real_time']:
raise UserError(self.env._("Only the value 'periodic' and 'real_time' are accepted to search on valuation field."))
domain_categ = Domain([('categ_id.property_valuation', operator, value)])
domain_company = Domain(['|', ('categ_id.property_valuation', '=', False), ('categ_id', '=', False), ('company_id.inventory_valuation', operator, value)])
return domain_company | domain_categ
@api.depends('tracking')
def _compute_lot_valuated(self):
for product in self:
if product.tracking == 'none':
product.lot_valuated = False
@api.depends_context('company')
@api.depends('categ_id.property_cost_method')
def _compute_cost_method(self):
for product_template in self:
product_template.cost_method = (
product_template.categ_id.with_company(
product_template.company_id
).property_cost_method
or (product_template.company_id or self.env.company).cost_method
)
@api.depends_context('company')
@api.depends('categ_id.property_valuation')
def _compute_valuation(self):
for product_template in self:
product_template.valuation = product_template.categ_id.with_company(
product_template.company_id).property_valuation or self.env.company.inventory_valuation
def write(self, vals):
product_ids_to_update = set()
lot_ids_to_update = set()
if 'categ_id' in vals:
category = self.env['product.category'].browse(vals['categ_id'])
cost_method = category.property_cost_method if category else self.env.company.cost_method
for product in self:
if product.cost_method != cost_method:
product_ids_to_update.update(product.product_variant_ids.ids)
if 'lot_valuated' in vals:
if vals.get('lot_valuated'):
products_to_enable = self.filtered(lambda p: not p.lot_valuated)
if products_to_enable:
problematic_quants = self.env['stock.quant'].search([
('product_id', 'in', products_to_enable.product_variant_ids.ids),
('lot_id', '=', False),
('quantity', '!=', 0),
('location_id.is_valued_internal', '=', True),
])
if problematic_quants:
raise UserError(self.env._(
"You cannot enable lot valuation because the following products have"
" on-hand quantities without a lot/serial number:\n%s",
problematic_quants.product_id.mapped('display_name'),
))
for product in self:
if product.lot_valuated != vals.get('lot_valuated', product.lot_valuated):
product_ids_to_update.update(product.product_variant_ids.ids)
products_to_update = self.env['product.product'].browse(product_ids_to_update)
lot_ids_to_update.update(self.env['stock.lot'].sudo().search([
('product_id', 'in', products_to_update.filtered(lambda p: p.lot_valuated).ids),
]).ids)
res = super().write(vals)
if 'lot_valuated' in vals:
lot_ids_to_update.update(self.env['stock.lot'].sudo().search([
('product_id', 'in', self.product_variant_ids.ids),
]).ids)
if product_ids_to_update:
self.env['product.product'].browse(product_ids_to_update)._update_standard_price()
if lot_ids_to_update:
self.env['stock.lot'].browse(lot_ids_to_update).sudo()._update_standard_price()
return res
# -------------------------------------------------------------------------
# Misc.
# -------------------------------------------------------------------------
def _get_product_accounts(self):
""" Add the stock accounts related to product to the result of super()
@return: dictionary which contains information regarding stock accounts and super (income+expense accounts)
"""
accounts = super()._get_product_accounts()
accounts['stock_valuation'] = (
self.categ_id.property_stock_valuation_account_id
or self.categ_id._fields['property_stock_valuation_account_id'].get_company_dependent_fallback(self.categ_id)
or self.env.company.account_stock_valuation_id
)
accounts['stock_variation'] = accounts['stock_valuation'].account_stock_variation_id
return accounts
def get_product_accounts(self, fiscal_pos=None):
""" Add the stock journal related to product to the result of super()
@return: dictionary which contains all needed information regarding stock accounts and journal and super (income+expense accounts)
"""
accounts = super().get_product_accounts(fiscal_pos=fiscal_pos)
accounts.update({
'stock_journal': (
self.categ_id.property_stock_journal
or self.categ_id._fields['property_stock_journal'].get_company_dependent_fallback(self.categ_id)
or self.env.company.account_stock_journal_id
)
})
return accounts
class ProductProduct(models.Model):
_inherit = 'product.product'
avg_cost = fields.Monetary(
string="Average Cost", compute='_compute_value',
compute_sudo=True, currency_field='company_currency_id')
total_value = fields.Monetary(
string="Total Value", compute='_compute_value',
compute_sudo=True, currency_field='company_currency_id')
company_currency_id = fields.Many2one(
'res.currency', 'Valuation Currency', compute='_compute_value', compute_sudo=True,
help="Technical field to correctly show the currently selected company's currency that corresponds "
"to the totaled value of the product's valuation layers")
@api.depends_context('to_date', 'company', 'warehouse_id')
@api.depends('cost_method', 'stock_move_ids.value', 'standard_price')
def _compute_value(self):
company_id = self.env.company
self.company_currency_id = company_id.currency_id
# PERF: Pre-compute:the sum of 'total_value' of lots per product in go
std_price_by_company_id = {}
total_value_by_company_id = {}
lot_valuated_products_ids = {p.id for p in self if p.lot_valuated}
for company in self.env.companies:
std_price_by_product_id = defaultdict(float)
total_value_by_product_id = defaultdict(float)
products = self.with_company(company.id).with_context(allowed_company_ids=company.ids)
products = products._with_valuation_context()
at_date = fields.Datetime.to_datetime(self.env.context.get('to_date'))
if at_date:
at_date = at_date.replace(hour=23, minute=59, second=59)
products = products.with_context(at_date=at_date)
env = products.env
if lot_valuated_products_ids:
domain = Domain([('product_id', 'in', lot_valuated_products_ids)])
if not self.env.context.get('warehouse_id'):
domain &= Domain([('product_qty', '!=', 0)])
lots_by_product = env['stock.lot']._read_group(
domain,
groupby=['product_id'],
aggregates=['id:recordset']
)
# Collect all lots and trigger batch computation of total_value
env['stock.lot'].browse(
lot.id
for _, lots in lots_by_product
for lot in lots
).mapped('total_value')
for product, lots in lots_by_product:
value = sum(lots.mapped('total_value'))
std_price_by_product_id[product.id] = value / product.qty_available if product.qty_available else product.standard_price
total_value_by_product_id[product.id] = value
product_ids_grouped_by_cost_method = defaultdict(set)
ratio_by_product_id = {}
for product in products:
if product.lot_valuated:
continue
product_whole_company_context = product
if 'warehouse_id' in self.env.context:
product_whole_company_context = product.with_context(warehouse_id=False)
if product.uom_id.is_zero(product.qty_available):
total_value_by_product_id[product.id] = 0
std_price_by_product_id[product.id] = product.standard_price
continue
if product.uom_id.is_zero(product_whole_company_context.qty_available):
total_value_by_product_id[product.id] = product.standard_price * product.qty_available
std_price_by_product_id[product.id] = product.standard_price
continue
if product.uom_id.compare(product.qty_available, product_whole_company_context.qty_available) != 0:
ratio = product.qty_available / product_whole_company_context.qty_available
ratio_by_product_id[product.id] = ratio
product_ids_grouped_by_cost_method[product.cost_method].add(product.id)
for cost_method, product_ids in product_ids_grouped_by_cost_method.items():
products = products.env['product.product'].browse(product_ids).with_context(warehouse_id=False)
# To remove once price_unit isn't truncate in sql anymore (no need of force_recompute)
if cost_method == 'standard':
std_prices, total_values = products._run_standard_batch(at_date=at_date)
elif cost_method == 'average':
std_prices, total_values = products._run_average_batch(at_date=at_date)
else:
std_prices, total_values = products._run_fifo_batch(at_date=at_date)
std_price_by_product_id.update(std_prices)
total_value_by_product_id.update(total_values)
for product in products:
total_value = total_value_by_product_id.get(product.id, 0)
total_value_by_product_id[product.id] = total_value * ratio_by_product_id.get(product.id, 1)
std_price_by_company_id[company.id] = std_price_by_product_id
total_value_by_company_id[company.id] = total_value_by_product_id
for product in self:
product.total_value = sum(total_value_by_company_id[c.id].get(product.id, 0) for c in self.env.companies)
product.avg_cost = product.total_value / product.qty_available if product.qty_available else std_price_by_company_id[self.env.company.id].get(product.id, product.standard_price)
@api.model_create_multi
def create(self, vals_list):
products = super().create(vals_list)
products.with_context(valuation_date=datetime.min)._change_standard_price({product: 0 for product in products if product.standard_price})
return products
def write(self, vals):
old_price = False
if 'standard_price' in vals and not self.env.context.get('disable_auto_revaluation'):
old_price = {product: product.standard_price for product in self}
if 'lot_valuated' in vals:
# lot_valuated must be updated from the ProductTemplate
self.product_tmpl_id.write({'lot_valuated': vals.pop('lot_valuated')})
res = super().write(vals)
if old_price:
self._change_standard_price(old_price)
return res
# -------------------------------------------------------------------------
# Private
# -------------------------------------------------------------------------
def _change_standard_price(self, old_price):
product_values = []
product_ids_lot_valuated = set()
date = self.env.context.get('valuation_date') or fields.Datetime.now()
for product in self:
if product.cost_method == 'fifo' or product.standard_price == old_price.get(product):
continue
if product.lot_valuated:
product_ids_lot_valuated.add(product.id)
product_values.append({
'product_id': product.id,
'value': product.standard_price,
'company_id': product.company_id.id or self.env.company.id,
'date': date,
'description': _('Price update from %(old_price)s to %(new_price)s by %(user)s',
old_price=old_price.get(product), new_price=product.standard_price, user=self.env.user.name)
})
self.env['product.value'].sudo().create(product_values)
if product_ids_lot_valuated:
for (product, lots) in self.env['stock.lot']._read_group(
[('product_id', 'in', product_ids_lot_valuated)], ['product_id'], ['id:recordset']):
lots.with_context(disable_auto_revaluation=True).standard_price = product.standard_price
return
def _get_standard_price_at_date(self, date=None):
""" Get Last Price History """
self.ensure_one()
if not date or date == fields.Date.today():
return self.standard_price
if self.cost_method != 'standard':
raise ValidationError(_("You can only get the standard price at a given date for products with 'Standard Price' as cost method."))
product_value = self._get_last_product_value(date).get(self)
return product_value.value if product_value else self.standard_price
def _get_last_product_value(self, date=None, lot=False):
domain = Domain([
('product_id', 'in', self.ids),
('move_id', '=', False),
('company_id', '=', self.env.company.id),
])
if lot:
domain &= Domain(['|', ('lot_id', '=', lot.id), ('lot_id', '=', False)])
else:
domain &= Domain([('lot_id', '=', False)])
if date:
domain &= Domain([('date', '<=', date)])
query = self.env['product.value'].sudo()._search(domain)
query_select = SQL('distinct ON (product_value.product_id) product_value.id')
query.order = SQL('product_value.product_id, product_value.date DESC, product_value.id DESC')
query._ids = tuple(id_ for id_, in self.env.execute_query(query.select(query_select)))
product_values = self.env['product.value'].browse(query._ids)
product_values.sudo().fetch(['product_id', 'value', 'date'])
return {pv.product_id: pv for pv in product_values}
def _get_last_in(self, date=None):
last_in_domain = Domain([('is_in', '=', True), ('product_id', '=', self.id)])
if date:
last_in_domain &= Domain([('date', '<=', date)])
last_in = self.env['stock.move'].search(last_in_domain, order='date desc, id desc', limit=1)
return last_in
def _with_valuation_context(self):
valued_locations = self.env['stock.location'].search([('is_valued_internal', '=', True)])
return self.with_context(location=valued_locations.ids, owners=[False, self.env.company.partner_id.id])
def _get_remaining_moves(self):
moves_qty_by_product = {}
for product in self:
moves, remaining_qty = product._run_fifo_get_stack()
moves = self.env['stock.move'].concat(*moves)
if not moves:
continue
qty_by_move = {m: m.quantity for m in moves[1:]}
qty_by_move[moves[0]] = remaining_qty
moves_qty_by_product[product] = qty_by_move
return moves_qty_by_product
def _run_standard_batch(self, at_date=None, lot=None):
std_price_by_product_id = {product.id: product.standard_price for product in self}
if at_date:
product_value_by_product = self._get_last_product_value(at_date, lot=lot)
std_price_by_product_id = {
product.id: product_value_by_product[product].value if product in product_value_by_product else product.standard_price
for product in self
}
value_by_product_id = {p.id: p.qty_available * std_price_by_product_id.get(p.id, 0) for p in self}
return std_price_by_product_id, value_by_product_id
def _run_average_batch(self, at_date=None, lot=None, force_recompute=False):
std_price_by_product_id = {}
value_by_product_id = {}
quantity_by_product_id = {}
if not at_date and not force_recompute:
std_price_by_product_id = {p.id: p.standard_price for p in self}
value_by_product_id = {p.id: p.qty_available * std_price_by_product_id.get(p.id, 0) for p in self}
return std_price_by_product_id, value_by_product_id
moves_domain = Domain([
('product_id', 'in', self._as_query()),
('company_id', '=', self.env.company.id),
'|', '|', ('is_in', '=', True), ('is_dropship', '=', True), ('is_out', '=', True)
])
if lot:
moves_domain &= Domain([
('move_line_ids.lot_id', 'in', lot.id),
])
if at_date:
moves_domain &= Domain([
('date', '<=', at_date),
])
# PERF avoid memoryerror
move_fields = ['date', 'is_dropship', 'is_in', 'is_out', 'location_dest_id', 'location_id', 'move_line_ids',
'picked', 'value', 'product_id']
last_manual_value_by_product = self._get_last_product_value(at_date, lot=lot)
oldest_manual_value = min(pv.date for pv in last_manual_value_by_product.values()) if last_manual_value_by_product else False
if oldest_manual_value:
moves_domain &= Domain([('date', '>=', oldest_manual_value)])
moves = self.env['stock.move'].search_fetch(
moves_domain,
field_names=move_fields,
order='date, id'
)
moves.move_line_ids.fetch(['company_id', 'location_id', 'location_dest_id', 'lot_id', 'owner_id', 'picked', 'quantity_product_uom'])
moves_by_product = moves.grouped(key=lambda m: m.product_id)
# Start from last user input values.
for manual_value in last_manual_value_by_product.values():
product = manual_value.product_id
if lot:
quantity = lot.with_context(to_date=manual_value.date, skip_in_progress=True).product_qty
else:
quantity = product.with_context(to_date=manual_value.date).qty_available
std_price_by_product_id[product.id] = manual_value.value
quantity_by_product_id[product.id] = quantity
value_by_product_id[product.id] = manual_value.value * quantity
moves = moves_by_product.get(product, self.env['stock.move'])
index = bisect(moves, manual_value.date, key=lambda m: m.date)
moves = moves[index:]
moves_by_product[product] = moves
# Replay the valuation history
for product, moves in moves_by_product.items():
# Start from last manual input
quantity = quantity_by_product_id.get(product.id, 0)
average_cost = std_price_by_product_id.get(product.id, 0)
value = value_by_product_id.get(product.id, 0)
for move in moves:
if move.is_in or move.is_dropship:
in_qty = move._get_valued_qty()
in_value = move.value
if at_date or move.is_dropship:
in_value = move._get_value(at_date=at_date)
if lot:
lot_qty = move._get_valued_qty(lot)
in_value = (in_value * lot_qty / in_qty) if in_qty else 0
in_qty = lot_qty
previous_qty = quantity
quantity += in_qty
# Regular case, value from accumulation
if previous_qty > 0:
value += in_value
average_cost = value / quantity
# From negative quantity case, value from last_in
elif previous_qty <= 0:
average_cost = in_value / in_qty if in_qty else average_cost
value = average_cost * quantity
if move.is_out or move.is_dropship:
out_qty = move._get_valued_qty()
out_value = out_qty * average_cost
if lot:
lot_qty = move._get_valued_qty(lot)
out_value = (out_value * lot_qty / out_qty) if out_qty else 0
out_qty = lot_qty
value -= out_value
quantity -= out_qty
std_price_by_product_id[product.id] = average_cost
value_by_product_id[product.id] = value
return std_price_by_product_id, value_by_product_id
def _run_fifo_batch(self, at_date=None, lot=None, location=None):
std_price_by_product_id = {}
value_by_product_id = {}
for product in self:
quantity = product.qty_available
if lot:
quantity = lot.product_qty
value = product._run_fifo(quantity, lot, at_date, location)
std_price = value / quantity if quantity else 0
std_price_by_product_id[product.id] = std_price
value_by_product_id[product.id] = value
return std_price_by_product_id, value_by_product_id
def _run_fifo(self, quantity, lot=None, at_date=None, location=None):
""" Returns the value for the next outgoing product base on the qty give as argument."""
self.ensure_one()
if self.uom_id.compare(quantity, 0) <= 0:
if at_date:
last_in = self._get_last_in(at_date)
return quantity * (last_in._get_price_unit() if last_in else self.standard_price)
return quantity * self.standard_price
external_location = location and location.is_valued_external
fifo_cost = 0
fifo_stack, qty_on_first_move = self._run_fifo_get_stack(lot=lot, at_date=at_date, location=location)
last_move = False
# Going up to get the quantity in the argument
while quantity > 0 and fifo_stack:
move = fifo_stack.pop(0)
last_move = move
move_value = move.value
if at_date:
move_value = move._get_value(at_date=at_date)
if qty_on_first_move:
valued_qty = move._get_valued_qty()
in_qty = qty_on_first_move
in_value = move_value * in_qty / valued_qty
qty_on_first_move = 0
else:
in_qty = move._get_valued_qty()
in_value = move_value
if in_qty > quantity:
in_value = in_value * quantity / in_qty
in_qty = quantity
fifo_cost += in_value
quantity -= in_qty
# When we required more quantity than available we extrapolate with the last known price
if quantity > 0:
if last_move and last_move.quantity:
fifo_cost += quantity * (last_move.value / last_move.quantity)
else:
fifo_cost += quantity * self.standard_price
return fifo_cost
def _run_fifo_get_stack(self, lot=None, at_date=None, location=None):
# TODO: return a list of tuple (move, valued_qty) instead
external_location = location and location.is_valued_external
fifo_stack = []
fifo_stack_size = 0
if location:
self = self.with_context(location=location.ids) # noqa: PLW0642
if lot:
fifo_stack_size = lot.product_qty
else:
fifo_stack_size = self._with_valuation_context().with_context(to_date=at_date).qty_available
if self.env.context.get('fifo_qty_already_processed'):
# When validating multiple moves at the same time, the qty_available won't be up to date yet
fifo_stack_size -= self.env.context['fifo_qty_already_processed']
if self.uom_id.compare(fifo_stack_size, 0) <= 0:
return fifo_stack, 0
moves_domain = Domain([
('product_id', '=', self.id),
('company_id', '=', self.env.company.id)
])
if lot:
moves_domain &= Domain([('move_line_ids.lot_id', 'in', lot.id)])
if at_date:
moves_domain &= Domain([('date', '<=', at_date)])
if location:
moves_domain &= Domain([('location_dest_id', '=', location.id)])
if external_location:
moves_domain &= Domain([('is_out', '=', True)])
else:
moves_domain &= Domain([('is_in', '=', True)])
# Arbitrary limit as we can't guess how many moves correspond to the qty_available, but avoid fetching all moves at the same time.
initial_limit = 100
moves_in = self.env['stock.move'].search(moves_domain, order='date desc, id desc', limit=initial_limit)
remaining_qty_on_first_stack_move = 0
current_offset = 0
# Go to the bottom of the stack
while self.uom_id.compare(fifo_stack_size, 0) > 0 and moves_in:
move = moves_in[0]
moves_in = moves_in[1:]
in_qty = move._get_valued_qty()
fifo_stack.append(move)
remaining_qty_on_first_stack_move = min(in_qty, fifo_stack_size)
fifo_stack_size -= in_qty
if self.uom_id.compare(fifo_stack_size, 0) > 0 and not moves_in:
# We need to fetch more moves
current_offset += 1
moves_in = self.env['stock.move'].search(moves_domain, order='date desc, id desc', offset=current_offset * initial_limit, limit=initial_limit)
fifo_stack.reverse()
return fifo_stack, remaining_qty_on_first_stack_move
def _update_standard_price(self, extra_value=None, extra_quantity=None):
# TODO: Add extra value and extra quantity kwargs to avoid total recomputation
products_by_cost_method = defaultdict(set)
for product in self:
if product.lot_valuated:
product.sudo().with_context(disable_auto_revaluation=True).standard_price = product.avg_cost
continue
products_by_cost_method[product.cost_method].add(product.id)
for cost_method, product_ids in products_by_cost_method.items():
products = self.env['product.product'].browse(product_ids)
if cost_method == 'standard':
continue
if cost_method == 'fifo':
for product in products:
qty_available = product._with_valuation_context().qty_available
if product.uom_id.compare(qty_available, 0) > 0:
product.sudo().with_context(disable_auto_revaluation=True).standard_price = product.total_value / qty_available
elif last_in := product._get_last_in():
if last_in_price_unit := last_in._get_price_unit():
product.sudo().with_context(disable_auto_revaluation=True).standard_price = last_in_price_unit
continue
if cost_method == 'average':
new_standard_price_by_product = self._run_average_batch(force_recompute=True)[0]
for product in products:
if product.id in new_standard_price_by_product:
product.with_context(disable_auto_revaluation=True).sudo().standard_price = new_standard_price_by_product[product.id]
# -------------------------------------------------------------------------
# Old to remove
# -------------------------------------------------------------------------
def _run_avco(self, at_date=None, lot=None, method="realtime"):
self.ensure_one()
price_unit, value = self._run_average_batch(at_date=at_date, lot=lot, force_recompute=True)
return price_unit.get(self.id, 0), value.get(self.id, 0)
def _get_value_from_lots(self):
return 0
class ProductCategory(models.Model):
_inherit = 'product.category'
anglo_saxon_accounting = fields.Boolean(
string="Use Anglo-Saxon Accounting", compute="_compute_anglo_saxon_accounting",
help="If checked, the product will be valued using the Anglo-Saxon accounting method.")
property_valuation = fields.Selection(
string="Inventory Valuation",
selection=[
('periodic', 'Periodic (at closing)'),
('real_time', 'Perpetual (at invoicing)'),
],
company_dependent=True, copy=True, tracking=True,
help="""Periodic: The accounting entries are suggested manually in the inventory valuation report.
Perpetual: An accounting entry is automatically created to value the inventory when a product is billed or invoiced.
""")
property_cost_method = fields.Selection(
string="Costing Method",
selection=[
('standard', "Standard Price"),
('fifo', "First In First Out (FIFO)"),
('average', "Average Cost (AVCO)"),
],
company_dependent=True, copy=True,
default=lambda self: self.env.company.cost_method,
help="""Standard Price: The products are valued at their standard cost defined on the product.
Average Cost (AVCO): The products are valued at weighted average cost.
First In First Out (FIFO): The products are valued supposing those that enter the company first will also leave it first.
""",
tracking=True,
)
property_stock_journal = fields.Many2one(
'account.journal', 'Stock Journal', company_dependent=True,
help="When doing automated inventory valuation, this is the Accounting Journal in which entries will be automatically posted when stock moves are processed.")
property_stock_valuation_account_id = fields.Many2one(
'account.account', 'Stock Valuation Account', company_dependent=True, ondelete='restrict',
check_company=True,
help="""When automated inventory valuation is enabled on a product, this account will hold the current value of the products.""")
property_price_difference_account_id = fields.Many2one(
'account.account', 'Price Difference Account', company_dependent=True, ondelete='restrict',
check_company=True,
help="""With perpetual valuation, this account will hold the price difference between the standard price and the bill price.""")
account_stock_variation_id = fields.Many2one(
'account.account', string="Stock Variation Account", readonly=False,
related="property_stock_valuation_account_id.account_stock_variation_id")
@api.depends_context('company')
def _compute_anglo_saxon_accounting(self):
self.anglo_saxon_accounting = self.env.company.anglo_saxon_accounting
def write(self, vals):
products_to_update = self.env['product.product']
if 'property_cost_method' in vals:
updated_categories = self.filtered(lambda c: c.property_cost_method != vals['property_cost_method'])
if updated_categories:
products_to_update = self.env['product.product'].search([('categ_id', 'in', updated_categories.ids)])
res = super().write(vals)
if products_to_update:
products_to_update._update_standard_price()
products_lot_valuated = products_to_update.filtered(lambda p: p.lot_valuated)
if products_lot_valuated:
self.env['stock.lot'].sudo().search([('product_id', 'in', products_lot_valuated.ids)])._update_standard_price()
return res