diff --git a/models/stock_inventory_backdate.py b/models/stock_inventory_backdate.py
index 297c78e..eccef5e 100755
--- a/models/stock_inventory_backdate.py
+++ b/models/stock_inventory_backdate.py
@@ -149,6 +149,53 @@ class StockInventoryBackdate(models.Model):
return qty_in - qty_out
+ def _get_historical_cost(self, product, date, company_id):
+ """
+ Calculate historical unit cost at a specific date.
+ - Standard Price: Returns current standard price.
+ - AVCO/FIFO: Calculates historical weighted average.
+ """
+ cost_method = product.categ_id.property_cost_method
+
+ if cost_method == 'standard':
+ return product.standard_price
+
+ # Optimized Key: Use SQL for aggregation instead of loading all objects
+ sql = """
+ SELECT SUM(quantity), SUM(value)
+ FROM stock_valuation_layer
+ WHERE product_id = %s
+ AND create_date <= %s
+ AND company_id = %s
+ """
+ self.env.cr.execute(sql, (product.id, date, company_id.id))
+ result = self.env.cr.fetchone()
+
+ quantity = result[0] or 0.0
+ value = result[1] or 0.0
+
+ if quantity != 0:
+ return value / quantity
+
+ # Fallback: Try to find the last valid unit_cost from an incoming layer
+ # This helps if current stock is 0 but we want the 'last known cost'
+ last_in_sql = """
+ SELECT value, quantity
+ FROM stock_valuation_layer
+ WHERE product_id = %s
+ AND create_date <= %s
+ AND company_id = %s
+ AND quantity > 0
+ ORDER BY create_date DESC, id DESC
+ LIMIT 1
+ """
+ self.env.cr.execute(last_in_sql, (product.id, date, company_id.id))
+ last_in = self.env.cr.fetchone()
+ if last_in and last_in[1] != 0:
+ return last_in[0] / last_in[1]
+
+ return product.standard_price
+
def action_validate(self):
"""Validate and create backdated stock moves"""
self.ensure_one()
@@ -220,6 +267,11 @@ class StockInventoryBackdateLine(models.Model):
default=0.0,
help="Positive value adds stock, negative value removes stock."
)
+ unit_cost = fields.Float(
+ string='Unit Cost',
+ digits='Product Price',
+ help="Custom unit cost for this backdated adjustment. If left 0, it may use standard price."
+ )
product_uom_id = fields.Many2one(
'uom.uom',
string='Unit of Measure',
@@ -264,6 +316,17 @@ class StockInventoryBackdateLine(models.Model):
if not self.counted_qty and not self.difference_qty:
self.counted_qty = self.theoretical_qty
self.difference_qty = 0.0
+
+ # Calculate historical unit cost
+ if self.inventory_id.backdate_datetime:
+ limit_date = self.inventory_id.backdate_datetime
+ self.unit_cost = self.inventory_id._get_historical_cost(
+ self.product_id,
+ limit_date,
+ self.inventory_id.company_id
+ )
+ else:
+ self.unit_cost = self.product_id.standard_price
def _create_stock_move(self):
"""Create backdated stock move for this line"""
@@ -384,19 +447,87 @@ class StockInventoryBackdateLine(models.Model):
# Update stock valuation layer
# Check if valuation layer exists
- svl_count = self.env['stock.valuation.layer'].search_count([('stock_move_id', '=', move.id)])
- _logger.info(f"Found {svl_count} stock valuation layers for move {move.id}")
+ svl = self.env['stock.valuation.layer'].search([('stock_move_id', '=', move.id)], limit=1)
+ # svl_count = self.env['stock.valuation.layer'].search_count([('stock_move_id', '=', move.id)])
+ _logger.info(f"Found stock valuation layer for move {move.id}: {svl}")
- if svl_count > 0:
- self.env.cr.execute(
- "UPDATE stock_valuation_layer SET create_date = %s WHERE stock_move_id = %s",
- (backdate, move.id)
- )
- _logger.info(f"Updated stock_valuation_layer for move {move.id}, rows affected: {self.env.cr.rowcount}")
+ if svl:
+ new_val_layer_vals = {}
+ # Update Date
+ new_val_layer_vals['create_date'] = backdate
+
+ # --- COST ADJUSTMENT LOGIC ---
+ # If unit_cost is provided, we override the value on the layer
+ # and potentially update the account move
+ if self.unit_cost and self.unit_cost > 0:
+ original_value = svl.value
+ new_value = self.unit_cost * qty
+ if self.difference_qty < 0:
+ new_value = -abs(new_value) # Ensure negative if qty is negative
+
+ if abs(new_value - original_value) > 0.01:
+ _logger.info(f"Overriding SVL Value from {original_value} to {new_value} (Unit Cost: {self.unit_cost})")
+
+ # Update SVL via SQL to avoid constraint issues or re-triggering logic
+ # Also need to update remaining_value if applicable?
+ # For incoming (positive diff), remaining_value should match value.
+ # For outgoing, usually remaining_value is 0 on the layer itself (it consumes others).
+
+ update_sql = "UPDATE stock_valuation_layer SET create_date = %s, value = %s, unit_cost = %s"
+ params = [backdate, new_value, self.unit_cost]
+
+ if self.difference_qty > 0:
+ # Incoming: update remaining_value too
+ update_sql += ", remaining_value = %s"
+ params.append(new_value)
+
+ update_sql += " WHERE id = %s"
+ params.append(svl.id)
+
+ self.env.cr.execute(update_sql, tuple(params))
+
+ # Check Account Move and update amount
+ if move.account_move_ids:
+ for am in move.account_move_ids:
+ # Update lines
+ # We need to find which line is Debit/Credit and scale them?
+ # Or just assuming 2 lines, simpler to match the total?
+ # Let's check amounts.
+
+ # Usually Inventory Adjustment creates:
+ # Dr Stock, Cr Inventory Adjustment (for Gain)
+ # Dr Inventory Adjustment, Cr Stock (for Loss)
+
+ # We just need to replace the absolute amount on all lines that matched the old absolute amount?
+ # Risky if multiple lines.
+
+ # Better: Iterate lines. If line amount matches old abs(value), update to new abs(value).
+ for line in am.line_ids:
+ if abs(line.debit - abs(original_value)) < 0.01:
+ self.env.cr.execute("UPDATE account_move_line SET debit = %s WHERE id = %s", (abs(new_value), line.id))
+ if abs(line.credit - abs(original_value)) < 0.01:
+ self.env.cr.execute("UPDATE account_move_line SET credit = %s WHERE id = %s", (abs(new_value), line.id))
+
+ _logger.info(f"Updated account_move {am.id} amounts to match new value {new_value}")
+
+ else:
+ # just update date
+ self.env.cr.execute(
+ "UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s",
+ (backdate, svl.id)
+ )
+ else:
+ # No custom cost, just update date
+ self.env.cr.execute(
+ "UPDATE stock_valuation_layer SET create_date = %s WHERE id = %s",
+ (backdate, svl.id)
+ )
+
+ _logger.info(f"Updated stock_valuation_layer for move {move.id}")
else:
_logger.warning(f"No stock valuation layer found for move {move.id}. Product may not use real-time valuation or cost is zero.")
- # Update account moves if they exist
+ # Update account moves dates if they exist (Run unconditionally as date fix is always needed)
# Refresh move to get account_move_ids
move = self.env['stock.move'].browse(move.id)
if move.account_move_ids:
diff --git a/views/stock_inventory_backdate_views.xml b/views/stock_inventory_backdate_views.xml
index 3708ae5..6559620 100755
--- a/views/stock_inventory_backdate_views.xml
+++ b/views/stock_inventory_backdate_views.xml
@@ -65,6 +65,7 @@
decoration-warning="theoretical_qty < 0"
force_save="1"/>
+