diff --git a/models/__pycache__/stock_inventory_revaluation.cpython-312.pyc b/models/__pycache__/stock_inventory_revaluation.cpython-312.pyc index 5f85518..f539a0f 100644 Binary files a/models/__pycache__/stock_inventory_revaluation.cpython-312.pyc and b/models/__pycache__/stock_inventory_revaluation.cpython-312.pyc differ diff --git a/models/stock_inventory_revaluation.py b/models/stock_inventory_revaluation.py index 011a9c0..cfe8ba4 100755 --- a/models/stock_inventory_revaluation.py +++ b/models/stock_inventory_revaluation.py @@ -119,40 +119,168 @@ class StockInventoryRevaluation(models.Model): # Create Stock Valuation Layer self._create_valuation_layer(move) - # AVCO/FIFO Logic: Update Standard Price and Distribute Value to Layers - # This fixes the issue where revaluation creates a layer but doesn't update the moving average cost - if self.product_id.categ_id.property_cost_method in ['average', 'fifo'] and self.quantity > 0: - # 1. Update Standard Price - # We use disable_auto_svl to prevent Odoo from creating an extra SVL for this price change - new_std_price = self.product_id.standard_price + (self.extra_cost / self.quantity) - self.product_id.with_context(disable_auto_svl=True).sudo().write({'standard_price': new_std_price}) + # 1. Common Logic: Calculate Unit Adjustment & Update Standard Price + # This applies to Standard Price, AVCO, and FIFO + if self.quantity > 0: + unit_adjust = self.extra_cost / self.quantity + new_std_price = self.product_id.standard_price + unit_adjust + + # Update the price on the product + # For Standard Price: This sets the new fixed cost. + # For AVCO/FIFO: This updates the current running average. + self.product_id.with_context(disable_auto_svl=True).sudo().write({'standard_price': new_std_price}) + + # 2. AVCO/FIFO Specific Logic: Distribute Value to Layers & Propagate to Sales + # Standard Price does not use layers for costing, so we skip this part for it. + if self.product_id.categ_id.property_cost_method in ['average', 'fifo'] and self.quantity > 0: + + # Distribute to Remaining Stock (The "Survivor" Layers) + # Track how much we distributed + total_distributed = 0.0 - # 2. Distribute Value to Remaining Layers (Crucial for correct COGS later) remaining_svls = self.env['stock.valuation.layer'].search([ ('product_id', '=', self.product_id.id), ('remaining_qty', '>', 0), ('company_id', '=', self.company_id.id), + ('create_date', '<=', self.date), ]) + remaining_svls = remaining_svls.filtered(lambda l: l.create_date <= self.date) if remaining_svls: - remaining_qty_total = sum(remaining_svls.mapped('remaining_qty')) - if remaining_qty_total > 0: - remaining_value_to_distribute = self.extra_cost - remaining_value_unit_cost = remaining_value_to_distribute / remaining_qty_total + for layer in remaining_svls: + adjustment_amount = layer.remaining_qty * unit_adjust + adjustment_amount = self.currency_id.round(adjustment_amount) - for layer in remaining_svls: - if float_compare(layer.remaining_qty, remaining_qty_total, precision_rounding=self.product_id.uom_id.rounding) >= 0: - taken_remaining_value = remaining_value_to_distribute - else: - taken_remaining_value = remaining_value_unit_cost * layer.remaining_qty + if not float_is_zero(adjustment_amount, precision_rounding=self.currency_id.rounding): + layer.sudo().write({ + 'remaining_value': layer.remaining_value + adjustment_amount, + # 'value': layer.value + adjustment_amount # Leaving value untouched to preserve historic record + }) + total_distributed += adjustment_amount + + # 3. Distribute to Sold Stock (The "Forward Propagation") + # Only distribute what's left! This ensures we don't "correct" sales of NEW stock. + remaining_value_to_expense = self.extra_cost - total_distributed + 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: + # Find outgoing moves (sales) that happened AFTER revaluation date + outgoing_svls = self.env['stock.valuation.layer'].search([ + ('product_id', '=', self.product_id.id), + ('company_id', '=', self.company_id.id), + ('quantity', '<', 0), # Outgoing + ('create_date', '>', self.date), # After revaluation + ], order='create_date asc, id asc') # Chronological + + if outgoing_svls: + for out_layer in outgoing_svls: + if float_compare(remaining_value_to_expense, 0, precision_rounding=self.currency_id.rounding) <= 0: + break # Limit reached - # Rounding - taken_remaining_value = self.currency_id.round(taken_remaining_value) + # How much correction does this move "deserve"? + qty_sold = abs(out_layer.quantity) + theoretical_correction = qty_sold * unit_adjust + theoretical_correction = self.currency_id.round(theoretical_correction) - layer.sudo().write({'remaining_value': layer.remaining_value + taken_remaining_value}) + # We can only give what we have left + actual_correction = min(theoretical_correction, remaining_value_to_expense) # If positive adjustment + if unit_adjust < 0: # If negative adjustment? + # If unit_adjust is negative, everything is negative. + # extra_cost is neg, total_dist is neg, remaining_to_exp is neg. + # abs(actual) = min(abs(theo), abs(rem)) + # implementation detail: let's handle signs properly? + # Simplify: assume always positive for logic "Cap", but math works. + # Wait, min() with negatives works differently. + # -100 vs -50. min is -100. We want "Closest to zero". + pass - remaining_value_to_distribute -= taken_remaining_value - remaining_qty_total -= layer.remaining_qty + # Handle Sign-Agnostic Capping + # 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: + actual_correction = min(theoretical_correction, remaining_value_to_expense) + else: + # theoretical is likely negative too because unit_adjust is negative + actual_correction = max(theoretical_correction, remaining_value_to_expense) + + if float_is_zero(actual_correction, precision_rounding=self.currency_id.rounding): + continue + + # Create Correction SVL + 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: + continue + + # Accounting Entries + # If Positive Adjustment (Value Added): + # 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): + # Initially Credited Asset -100. + # "50 of that removal belongs to sales". + # So we Debit Asset +50, Credit COGS -50 (Reduce Cost). + # The math: actual_correction is -50. + # Using same logic: Debit COGS with -50? (Credit 50). + # Credit Asset with -50? (Debit 50). + # Logic holds purely with signs. + + move_lines = [ + (0, 0, { + 'name': _('Revaluation Correction for %s') % out_layer.stock_move_id.name, + 'account_id': cogs_acc, + 'debit': actual_correction if actual_correction > 0 else 0, + 'credit': -actual_correction if actual_correction < 0 else 0, + 'product_id': self.product_id.id, + }), + (0, 0, { + 'name': _('Revaluation Correction for %s') % out_layer.stock_move_id.name, + 'account_id': stock_val_acc, + 'debit': -actual_correction if actual_correction < 0 else 0, + 'credit': actual_correction if actual_correction > 0 else 0, + 'product_id': self.product_id.id, + }), + ] + + am_vals = { + 'ref': f"{self.name} - Corr - {out_layer.stock_move_id.name}", + 'date': out_layer.create_date.date(), + 'journal_id': self.account_journal_id.id, + 'line_ids': move_lines, + 'move_type': 'entry', + 'company_id': self.company_id.id, + } + am = self.env['account.move'].create(am_vals) + 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({ + 'product_id': self.product_id.id, + 'value': svl_value, + 'quantity': 0, + 'unit_cost': 0, + 'remaining_qty': 0, + 'stock_move_id': out_layer.stock_move_id.id, + '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 self.state = 'done'