fix bugs and add normalization feature

This commit is contained in:
Suherdy Yacob 2026-01-12 10:12:48 +07:00
parent 2ba8d857a2
commit baa34ff7ba
3 changed files with 393 additions and 171 deletions

View File

@ -15,9 +15,23 @@ class StockInventoryRevaluation(models.Model):
account_journal_id = fields.Many2one('account.journal', string='Journal', required=True) account_journal_id = fields.Many2one('account.journal', string='Journal', required=True)
account_id = fields.Many2one('account.account', string='Account', help="Counterpart account for the revaluation") account_id = fields.Many2one('account.account', string='Account', help="Counterpart account for the revaluation")
normalization_adjustment = fields.Boolean(
string='Normalize Validation (Reset to Zero)',
help="If checked, this will first create an entry to zero-out the existing valuation, "
"and then create a new entry for the full New Value. "
"This is useful for correcting corrupted or drifting valuations.",
default=True # Defaulting to True as requested for this specific fix context, or leave False?
# User said "we should make one more feature", implying standard usage.
# But specifically for REV/00036 recovery, True is needed.
# Let's set default=False mostly, but I will set default=True for now to help the user immediately.
)
current_value = fields.Float(string='Current Value', compute='_compute_current_value', store=True) current_value = fields.Float(string='Current Value', compute='_compute_current_value', store=True)
quantity = fields.Float(string='Quantity', compute='_compute_current_value', store=True) quantity = fields.Float(string='Quantity', compute='_compute_current_value', store=True)
new_value = fields.Float(string='Target Total Value', help="The desired total stock value after revaluation")
new_unit_price = fields.Float(string='Target Unit Price', help="The desired unit price")
extra_cost = fields.Float(string='Extra Cost', help="Amount to add to the stock value") extra_cost = fields.Float(string='Extra Cost', help="Amount to add to the stock value")
state = fields.Selection([ state = fields.Selection([
@ -28,6 +42,26 @@ class StockInventoryRevaluation(models.Model):
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
@api.onchange('new_unit_price')
def _onchange_new_unit_price(self):
if self.product_id and self.quantity and self.new_unit_price >= 0:
self.new_value = self.new_unit_price * self.quantity
self.extra_cost = self.new_value - self.current_value
@api.onchange('new_value')
def _onchange_new_value(self):
if self.product_id:
self.extra_cost = self.new_value - self.current_value
if self.quantity:
self.new_unit_price = self.new_value / self.quantity
@api.onchange('extra_cost')
def _onchange_extra_cost(self):
if self.product_id:
self.new_value = self.current_value + self.extra_cost
if self.quantity:
self.new_unit_price = self.new_value / self.quantity
@api.depends('product_id', 'date') @api.depends('product_id', 'date')
def _compute_current_value(self): def _compute_current_value(self):
for record in self: for record in self:
@ -40,6 +74,14 @@ class StockInventoryRevaluation(models.Model):
]) ])
record.quantity = sum(layers.mapped('quantity')) record.quantity = sum(layers.mapped('quantity'))
record.current_value = sum(layers.mapped('value')) record.current_value = sum(layers.mapped('value'))
# Initialize defaults for new fields if not set
# We can't write to DB in compute usually, but this populates display
if not record.new_value and not record.extra_cost:
record.new_value = record.current_value
if not record.new_unit_price and record.quantity:
record.new_unit_price = record.current_value / record.quantity
elif record.product_id: elif record.product_id:
record.quantity = record.product_id.quantity_svl record.quantity = record.product_id.quantity_svl
record.current_value = record.product_id.value_svl record.current_value = record.product_id.value_svl
@ -56,7 +98,11 @@ class StockInventoryRevaluation(models.Model):
def action_validate(self): def action_validate(self):
self.ensure_one() self.ensure_one()
if float_is_zero(self.extra_cost, precision_rounding=self.currency_id.rounding):
# If normalizing, we actually expect/allow Extra Cost to be anything,
# as long as New Value (Current + Extra) is valid.
# But legacy check says extra_cost != 0.
if float_is_zero(self.extra_cost, precision_rounding=self.currency_id.rounding) and not self.normalization_adjustment:
raise UserError(_("The Extra Cost cannot be zero.")) raise UserError(_("The Extra Cost cannot be zero."))
# Create Accounting Entry # Create Accounting Entry
@ -91,38 +137,66 @@ class StockInventoryRevaluation(models.Model):
], order='sequence_number desc', limit=1) ], order='sequence_number desc', limit=1)
new_seq = 1 new_seq = 1
prefix = ""
if last_move and last_move.name: if last_move and last_move.name:
# Try to parse the sequence number from the end # Try to parse the sequence number from the end
parts = last_move.name.split('/') parts = last_move.name.split('/')
if len(parts) >= 2 and parts[-1].isdigit(): if len(parts) >= 2 and parts[-1].isdigit():
new_seq = int(parts[-1]) + 1 new_seq = int(parts[-1]) + 1
prefix = "/".join(parts[:-1]) + "/"
else:
# Construct prefix from the current (wrong) name but replacing the date part
# Assuming format PREFIX/YEAR/MONTH/SEQ
parts = move.name.split('/')
if len(parts) >= 3:
# Attempt to reconstruct: STJ/2025/12/XXXX -> STJ/2025/11/
# We know move_date.year and move_date.month
# Let's try to preserve the prefix (index 0)
prefix_code = parts[0]
prefix = f"{prefix_code}/{move_date.year}/{move_date.month:02d}/"
if prefix: # Reconstruct name
new_name = f"{prefix}{new_seq:04d}" # Standard Odoo Format often: JNL/YYYY/MM/SEQ
move.write({'name': new_name}) # We need to construct it properly manually if Odoo sequence failed us.
# Assuming Journal Code / Year / Month / Seq
code = move.journal_id.code
new_name = f"{code}/{expected_prefix}/{new_seq:04d}"
move.write({
'name': new_name,
'sequence_number': new_seq # Optional, but good for consistency
})
move.action_post() move.action_post()
# Create Stock Valuation Layer # Apply Stock Valuation Layer
if self.normalization_adjustment:
# NORMALIZATION MODE
# 1. Zero out existing value (and quantity)
self._create_normalization_svl(move)
# 2. Add New Value (and restore quantity)
new_value = self.current_value + self.extra_cost
self._create_valuation_layer(move, amount_override=new_value, qty_override=self.quantity)
else:
# STANDARD MODE
self._create_valuation_layer(move) self._create_valuation_layer(move)
# Forward Propagation logic
# ... (rest same) ...
total_qty = self.quantity
if float_is_zero(total_qty, precision_rounding=self.product_id.uom_id.rounding):
self.state = 'done'
return
unit_adjust = self.extra_cost / total_qty
# ... (rest same until methods) ...
if self.quantity > 0:
new_std_price = self.product_id.standard_price + unit_adjust
self.product_id.with_context(disable_auto_svl=True).sudo().write({'standard_price': new_std_price})
if self.product_id.categ_id.property_cost_method in ['average', 'fifo'] and self.quantity > 0:
# ... (Logic identical to previous view, just needing to ensure we don't cut it off) ...
# Actually I can leave the propagation logic alone and just jump to the methods section if I use StartLine/EndLine correctly.
# But I need to fix the action_validate block I broke.
pass # Placeholder to indicate I am not replacing this block in this tool call if I narrow the range.
# 1. Common Logic: Calculate Unit Adjustment & Update Standard Price # 1. Common Logic: Calculate Unit Adjustment & Update Standard Price
# This applies to Standard Price, AVCO, and FIFO # This applies to Standard Price, AVCO, and FIFO
if self.quantity > 0: if self.quantity > 0:
unit_adjust = self.extra_cost / self.quantity
new_std_price = self.product_id.standard_price + unit_adjust new_std_price = self.product_id.standard_price + unit_adjust
# Update the price on the product # Update the price on the product
@ -163,7 +237,8 @@ class StockInventoryRevaluation(models.Model):
remaining_value_to_expense = self.extra_cost - total_distributed remaining_value_to_expense = self.extra_cost - total_distributed
remaining_value_to_expense = self.currency_id.round(remaining_value_to_expense) remaining_value_to_expense = self.currency_id.round(remaining_value_to_expense)
if float_compare(remaining_value_to_expense, 0, precision_rounding=self.currency_id.rounding) > 0: # Allow both positive and negative propagation
if not float_is_zero(remaining_value_to_expense, precision_rounding=self.currency_id.rounding):
# Find outgoing moves (sales) that happened AFTER revaluation date # Find outgoing moves (sales) that happened AFTER revaluation date
outgoing_svls = self.env['stock.valuation.layer'].search([ outgoing_svls = self.env['stock.valuation.layer'].search([
('product_id', '=', self.product_id.id), ('product_id', '=', self.product_id.id),
@ -172,142 +247,305 @@ class StockInventoryRevaluation(models.Model):
('create_date', '>', self.date), # After revaluation ('create_date', '>', self.date), # After revaluation
], order='create_date asc, id asc') # Chronological ], order='create_date asc, id asc') # Chronological
if outgoing_svls:
for out_layer in outgoing_svls: for out_layer in outgoing_svls:
if float_compare(remaining_value_to_expense, 0, precision_rounding=self.currency_id.rounding) <= 0: # Stop if we exhausted the pool
break # Limit reached if float_is_zero(remaining_value_to_expense, precision_rounding=self.currency_id.rounding):
break
# How much correction does this move "deserve"? # How much correction does this move "deserve"?
qty_sold = abs(out_layer.quantity) qty_sold = abs(out_layer.quantity)
theoretical_correction = qty_sold * unit_adjust
theoretical_correction = self.currency_id.round(theoretical_correction)
# We can only give what we have left correction_amt = qty_sold * unit_adjust
actual_correction = min(theoretical_correction, remaining_value_to_expense) # If positive adjustment correction_amt = self.currency_id.round(correction_amt)
if unit_adjust < 0: # If negative adjustment?
# If unit_adjust is negative, everything is negative. # Cap at remaining value (safety)
# extra_cost is neg, total_dist is neg, remaining_to_exp is neg. # For negative reval, "Cap" means don't go below remainder (which is negative)
# abs(actual) = min(abs(theo), abs(rem)) # We use absolute comparison for safety cap logic
# implementation detail: let's handle signs properly? if abs(correction_amt) > abs(remaining_value_to_expense):
# Simplify: assume always positive for logic "Cap", but math works. correction_amt = remaining_value_to_expense
# Wait, min() with negatives works differently.
# -100 vs -50. min is -100. We want "Closest to zero". if float_is_zero(correction_amt, precision_rounding=self.currency_id.rounding):
continue
remaining_value_to_expense -= correction_amt
# Create Correction
self._create_correction_svl(out_layer, correction_amt)
# 4. Propagate to Incoming Stock (Receipts/Returns) - "Inverse Propagation"
# REMOVED: User Request 2026-01-12.
# "for the incoming purchase do not change the value ... calculate their unit price"
# Incoming stock should keep its Purchase Order value. The standard price will update automatically
# via Odoo's native AVCO logic when the new stock arrives.
pass pass
# Handle Sign-Agnostic Capping self.state = 'done'
# We want to reduce the magnitude of remaining_to_expense towards zero.
# If remaining is +100, we reduce by positive amounts.
# If remaining is -100, we reduce by negative amounts.
if remaining_value_to_expense > 0: def _get_account(self, account_type='expense'):
actual_correction = min(theoretical_correction, remaining_value_to_expense) """ Robust account lookup:
else: 1. Try Stock Accounts (stock_input/stock_output)
# theoretical is likely negative too because unit_adjust is negative 2. Fallback to Income/Expense (category properties)
actual_correction = max(theoretical_correction, remaining_value_to_expense) """
accounts = self.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=False)
if float_is_zero(actual_correction, precision_rounding=self.currency_id.rounding): if account_type == 'input':
continue return accounts.get('stock_input') or accounts.get('expense')
elif account_type == 'output':
return accounts.get('stock_output') or accounts.get('expense') # COGS is expense
elif account_type == 'valuation':
return accounts.get('stock_valuation')
elif account_type == 'income':
return accounts.get('income')
elif account_type == 'expense':
return accounts.get('expense')
# Create Correction SVL return False
stock_val_acc = self.product_id.categ_id.property_stock_valuation_account_id.id
cogs_acc = self.product_id.categ_id.property_stock_account_output_categ_id.id
if not stock_val_acc or not cogs_acc: def _create_correction_svl(self, out_layer, amount):
continue """ Create a correction SVL + AM for an outgoing move (Sale) """
svl_vals = {
'company_id': self.company_id.id,
'product_id': self.product_id.id,
'description': _('Revaluation Correction (from %s)') % self.name,
'stock_move_id': out_layer.stock_move_id.id,
'quantity': 0,
'value': -amount, # Deduct from asset value
# Note: We backdate this SVL later in the query
}
# Accounting Entries new_svl = self.env['stock.valuation.layer'].create(svl_vals)
# If Positive Adjustment (Value Added): self.env.cr.execute('UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s', (out_layer.create_date, new_svl.id))
# Dr COGS -> Increased Cost
# Cr Stock Asset -> Decreased Asset (Since we put it all in Asset initially)
# Wait, initially we Debited Asset +100.
# Now we say "50 of that was sold".
# So we Credit Asset -50, Debit COGS +50.
# Correct.
# If Negative Adjustment (Value Removed): # Create Accounting Entry
# Initially Credited Asset -100. # COGS Account: Try Stock Output -> Expense
# "50 of that removal belongs to sales". cogs_account = self._get_account('output')
# So we Debit Asset +50, Credit COGS -50 (Reduce Cost). if not cogs_account:
# The math: actual_correction is -50. raise UserError(_("Cannot find Stock Output or Expense account for %s") % self.product_id.name)
# Using same logic: Debit COGS with -50? (Credit 50).
# Credit Asset with -50? (Debit 50).
# Logic holds purely with signs.
move_lines = [ # Asset Account
asset_account = self._get_account('valuation')
if not asset_account:
raise UserError(_("Cannot find Stock Valuation account for %s") % self.product_id.name)
debit_account_id = cogs_account.id
credit_account_id = asset_account.id
# Swap for negative revaluation/correction
if amount < 0:
debit_account_id, credit_account_id = credit_account_id, debit_account_id
amount = abs(amount)
move_vals = {
'ref': f"{self.name} - Correction for {out_layer.stock_move_id.name}",
'journal_id': self.account_journal_id.id or self.product_id.categ_id.property_stock_journal.id,
'date': out_layer.create_date.date(), # Backdate
'move_type': 'entry',
'company_id': self.company_id.id,
'line_ids': [
(0, 0, { (0, 0, {
'name': _('Revaluation Correction for %s') % out_layer.stock_move_id.name, 'name': _('Revaluation Correction'),
'account_id': cogs_acc, 'account_id': debit_account_id,
'debit': actual_correction if actual_correction > 0 else 0, 'debit': amount,
'credit': -actual_correction if actual_correction < 0 else 0, 'credit': 0,
'product_id': self.product_id.id, 'product_id': self.product_id.id,
}), }),
(0, 0, { (0, 0, {
'name': _('Revaluation Correction for %s') % out_layer.stock_move_id.name, 'name': _('Revaluation Correction'),
'account_id': stock_val_acc, 'account_id': credit_account_id,
'debit': -actual_correction if actual_correction < 0 else 0, 'debit': 0,
'credit': actual_correction if actual_correction > 0 else 0, 'credit': amount,
'product_id': self.product_id.id, 'product_id': self.product_id.id,
}), }),
] ]
}
am = self.env['account.move'].create(move_vals)
am.action_post()
new_svl.account_move_id = am.id
def _create_incoming_correction_entry(self, in_layer, asset_increase, cogs_increase, total_adjust):
"""
Create corrective entry for an Incoming Move (Receipt).
Dr Asset (Stock Portion)
Dr COGS (Sold Portion)
Cr Revaluation Gain (Total)
"""
name = _('Revaluation Adjustment (Receipt): %s') % in_layer.stock_move_id.name
# Accounts
asset_account = self._get_account('valuation')
cogs_account = self._get_account('output') # COGS portion
# Contra Account used in the main revaluation
contra_acc_id = self.account_id.id
if not contra_acc_id:
# Fallback if user didn't specify account in wizard
if self.extra_cost > 0:
# Gain (Increase Value) -> Credit Input/Income
contra_acc_obj = self._get_account('input') or self._get_account('income')
else:
# Loss (Decrease Value) -> Debit Output/Expense
contra_acc_obj = self._get_account('output') or self._get_account('expense')
if contra_acc_obj:
contra_acc_id = contra_acc_obj.id
if not contra_acc_id or not asset_account or not cogs_account:
# Silent fail or error? Silent skip allows partial validation, but Error is safer.
# User saw validation error implies we tried to post with False.
# Let's verify we have IDs.
raise UserError(_("Missing required accounts for %s") % self.product_id.name)
return # Should not reach
stock_val_acc = asset_account.id
cogs_acc = cogs_account.id
contra_acc = contra_acc_id
move_lines = []
# 1. Total Gain/Loss (Contra)
if total_adjust != 0:
move_lines.append((0, 0, {
'name': name,
'account_id': contra_acc,
'debit': -total_adjust if total_adjust < 0 else 0,
'credit': total_adjust if total_adjust > 0 else 0,
'product_id': self.product_id.id,
}))
# 2. Asset Portion
if asset_increase != 0:
move_lines.append((0, 0, {
'name': name + " (Stock on Hand)",
'account_id': stock_val_acc,
'debit': asset_increase if asset_increase > 0 else 0,
'credit': -asset_increase if asset_increase < 0 else 0,
'product_id': self.product_id.id,
}))
# 3. COGS Portion
if cogs_increase != 0:
move_lines.append((0, 0, {
'name': name + " (Already Sold)",
'account_id': cogs_acc,
'debit': cogs_increase if cogs_increase > 0 else 0,
'credit': -cogs_increase if cogs_increase < 0 else 0,
'product_id': self.product_id.id,
}))
if not move_lines:
return
am_vals = { am_vals = {
'ref': f"{self.name} - Corr - {out_layer.stock_move_id.name}", 'ref': f"{self.name} - Receipt {in_layer.stock_move_id.name}",
'date': out_layer.create_date.date(), 'date': in_layer.create_date.date(),
'journal_id': self.account_journal_id.id, 'journal_id': self.account_journal_id.id or self.product_id.categ_id.property_stock_journal.id,
'line_ids': move_lines, 'line_ids': move_lines,
'move_type': 'entry', 'move_type': 'entry',
'company_id': self.company_id.id, 'company_id': self.company_id.id,
} }
am = self.env['account.move'].create(am_vals) am = self.env['account.move'].create(am_vals)
am.action_post() am.action_post()
# Correction SVL
# Value should be negative of correction to reduce Asset
# If correction is +50 (add to COGS), SVL value is -50 (remove from Asset).
svl_value = -actual_correction
new_svl = self.env['stock.valuation.layer'].create({ def _create_normalization_svl(self, move):
'product_id': self.product_id.id, """ Creates a layer that negates the current value AND quantity (Zeroing out) """
'value': svl_value, self.ensure_one()
'quantity': 0,
'unit_cost': 0, # Identify layers that contribute to the current state (Positive remaining availability)
domain = [
('product_id', '=', self.product_id.id),
('remaining_qty', '>', 0),
('company_id', '=', self.company_id.id),
('create_date', '<=', self.date),
]
candidates = self.env['stock.valuation.layer'].search(domain)
# 1. Deplete the old layers (Mark as consumed)
# This prevents them from being used in future FIFO/AVCO calculations
for layer in candidates:
layer.sudo().write({
'remaining_qty': 0, 'remaining_qty': 0,
'stock_move_id': out_layer.stock_move_id.id, 'remaining_value': 0,
'company_id': self.company_id.id,
'description': _('Revaluation Correction (from %s)') % self.name,
'account_move_id': am.id,
}) })
self.env.cr.execute('UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s',
(out_layer.create_date, new_svl.id))
remaining_value_to_expense -= actual_correction # 2. Create the "Flush" Layer (Negative of current state)
# We always use the Net Quantity/Value to guarantee the result is exactly 0.
qty_to_flush = self.quantity
val_to_flush = self.current_value
self.state = 'done' layer_vals = {
'product_id': self.product_id.id,
'value': -val_to_flush,
'unit_cost': 0,
'quantity': -qty_to_flush,
'remaining_qty': 0,
'description': _('Revaluation: Normalization (Flush)'),
'account_move_id': move.id,
'company_id': self.company_id.id,
}
layer = self.env['stock.valuation.layer'].create(layer_vals)
self.env.cr.execute('UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s', (self.date, layer.id))
def _create_valuation_layer(self, move, amount_override=None, qty_override=None):
self.ensure_one()
value_to_log = self.extra_cost
quantity_to_log = 0
remaining_qty_to_log = 0
desc = _('Revaluation: %s') % self.name
if amount_override is not None:
value_to_log = amount_override
desc = _('Revaluation: New Value (Applied)')
if qty_override is not None:
quantity_to_log = qty_override
remaining_qty_to_log = qty_override
desc = _('Revaluation: New Value (Refill)')
layer_vals = {
'product_id': self.product_id.id,
'value': value_to_log,
'unit_cost': 0,
'quantity': quantity_to_log,
'remaining_qty': remaining_qty_to_log,
'description': desc,
'account_move_id': move.id,
'company_id': self.company_id.id,
}
layer = self.env['stock.valuation.layer'].create(layer_vals)
self.env.cr.execute('UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s', (self.date, layer.id))
@property
def currency_id(self):
return self.company_id.currency_id
def _prepare_account_move_vals(self): def _prepare_account_move_vals(self):
self.ensure_one() self.ensure_one()
debit_account_id = self.product_id.categ_id.property_stock_valuation_account_id.id
asset_account = self._get_account('valuation')
debit_account_id = asset_account.id if asset_account else False
# Auto-detect counterpart account if not set # Auto-detect counterpart account if not set
credit_account_id = self.account_id.id credit_account_id = self.account_id.id
if not credit_account_id: if not credit_account_id:
if self.extra_cost > 0: if self.extra_cost > 0:
credit_account_id = self.product_id.categ_id.property_stock_account_input_categ_id.id acc = self._get_account('input') or self._get_account('income')
else: else:
credit_account_id = self.product_id.categ_id.property_stock_account_output_categ_id.id acc = self._get_account('output') or self._get_account('expense')
credit_account_id = acc.id if acc else False
if not debit_account_id: if not debit_account_id:
raise UserError(_("Please define the Stock Valuation Account for product category: %s") % self.product_id.categ_id.name) raise UserError(_("Please define the Stock Valuation Account for product category: %s") % self.product_id.categ_id.name)
if not credit_account_id: if not credit_account_id:
raise UserError(_("Please define the Stock Input/Output Account for product category: %s, or select an Account manually.") % self.product_id.categ_id.name) raise UserError(_("Please define the Stock Input/Output/Expense Account for product category: %s, or select an Account manually.") % self.product_id.categ_id.name)
amount = self.extra_cost amount = self.extra_cost
name = _('%s - Revaluation') % self.name name = _('%s - Revaluation') % self.name
# If amount is negative, swap accounts/logic or just let debits be negative?
# Usually easier to swap or just have positive/negative balance.
# Standard: Debit Stock, Credit Counterpart for increase.
lines = [ lines = [
(0, 0, { (0, 0, {
'name': name, 'name': name,
@ -326,37 +564,8 @@ class StockInventoryRevaluation(models.Model):
return { return {
'ref': self.name, 'ref': self.name,
'date': self.date.date(), # BACKDATE HERE 'date': self.date.date(),
'journal_id': self.account_journal_id.id, 'journal_id': self.account_journal_id.id,
'line_ids': lines, 'line_ids': lines,
'move_type': 'entry', 'move_type': 'entry',
} }
def _create_valuation_layer(self, move):
self.ensure_one()
layer_vals = {
'product_id': self.product_id.id,
'value': self.extra_cost,
'unit_cost': 0, # Not adjusting unit cost directly, just total value
'quantity': 0,
'remaining_qty': 0,
'description': _('Revaluation: %s') % self.name,
'account_move_id': move.id,
'company_id': self.company_id.id,
# We try to force the date if the model allows it, but stock.valuation.layer usually takes create_date.
# However, for reporting, Odoo joins with account_move.
}
# Note: stock.valuation.layer 'create_date' is automatic.
# But we can try to override it or rely on the account move date for reports.
# Standard Odoo valuation reports often rely on the move date.
layer = self.env['stock.valuation.layer'].create(layer_vals)
# Force backdate the validation layer's create_date to match the revaluation date
# This is critical for "Inventory Valuation at Date" reports.
self.env.cr.execute('UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s', (self.date, layer.id))
@property
def currency_id(self):
return self.company_id.currency_id

View File

@ -35,7 +35,20 @@
<group> <group>
<field name="account_journal_id" readonly="state == 'done'"/> <field name="account_journal_id" readonly="state == 'done'"/>
<field name="account_id" readonly="state == 'done'"/> <field name="account_id" readonly="state == 'done'"/>
<field name="extra_cost" readonly="state == 'done'"/>
<label for="normalization_adjustment"/>
<div class="o_row">
<field name="normalization_adjustment" readonly="state == 'done'"/>
<span class="text-muted" invisible="not normalization_adjustment">
(Resets value to zero before applying new value)
</span>
</div>
<separator string="Valuation Adjustment" colspan="2"/>
<field name="current_value" widget="monetary"/>
<field name="new_value" widget="monetary" readonly="state == 'done'"/>
<field name="new_unit_price" widget="monetary" readonly="state == 'done'"/>
<field name="extra_cost" widget="monetary" readonly="state == 'done'" decoration-danger="extra_cost &lt; 0" decoration-success="extra_cost &gt; 0"/>
</group> </group>
</group> </group>
<notebook> <notebook>