from odoo import models, fields, api, _ from odoo.exceptions import UserError from odoo.tools import float_compare, float_is_zero class StockInventoryRevaluation(models.Model): _name = 'stock.inventory.revaluation' _description = 'Stock Inventory Revaluation' _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char(string='Reference', required=True, copy=False, readonly=True, default=lambda self: _('New')) date = fields.Datetime(string='Date', required=True, default=fields.Datetime.now) product_id = fields.Many2one('product.product', string='Product', required=True, domain=[('type', '=', 'product')]) 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") current_value = fields.Float(string='Current Value', compute='_compute_current_value', store=True) quantity = fields.Float(string='Quantity', compute='_compute_current_value', store=True) extra_cost = fields.Float(string='Extra Cost', help="Amount to add to the stock value") state = fields.Selection([ ('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Cancelled') ], string='Status', default='draft', tracking=True) company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) @api.depends('product_id', 'date') def _compute_current_value(self): for record in self: if record.product_id and record.date: # Calculate quantity and value at the specific date layers = self.env['stock.valuation.layer'].search([ ('product_id', '=', record.product_id.id), ('create_date', '<=', record.date), ('company_id', '=', record.company_id.id) ]) record.quantity = sum(layers.mapped('quantity')) record.current_value = sum(layers.mapped('value')) elif record.product_id: record.quantity = record.product_id.quantity_svl record.current_value = record.product_id.value_svl else: record.quantity = 0.0 record.current_value = 0.0 @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code('stock.inventory.revaluation') or _('New') return super().create(vals_list) def action_validate(self): self.ensure_one() if float_is_zero(self.extra_cost, precision_rounding=self.currency_id.rounding): raise UserError(_("The Extra Cost cannot be zero.")) # Create Accounting Entry move_vals = self._prepare_account_move_vals() move = self.env['account.move'].create(move_vals) move.action_post() # Create Stock Valuation Layer self._create_valuation_layer(move) self.state = 'done' def _prepare_account_move_vals(self): self.ensure_one() debit_account_id = self.product_id.categ_id.property_stock_valuation_account_id.id # Auto-detect counterpart account if not set credit_account_id = self.account_id.id if not credit_account_id: if self.extra_cost > 0: credit_account_id = self.product_id.categ_id.property_stock_account_input_categ_id.id else: credit_account_id = self.product_id.categ_id.property_stock_account_output_categ_id.id if not debit_account_id: raise UserError(_("Please define the Stock Valuation Account for product category: %s") % self.product_id.categ_id.name) 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) amount = self.extra_cost 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 = [ (0, 0, { 'name': name, 'account_id': debit_account_id, 'debit': amount if amount > 0 else 0, 'credit': -amount if amount < 0 else 0, 'product_id': self.product_id.id, }), (0, 0, { 'name': name, 'account_id': credit_account_id, 'debit': -amount if amount < 0 else 0, 'credit': amount if amount > 0 else 0, }), ] return { 'ref': self.name, 'date': self.date.date(), # BACKDATE HERE 'journal_id': self.account_journal_id.id, 'line_ids': lines, '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