You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

107 lines
6.8 KiB

# -*- coding: utf-8 -*-
from collections import defaultdict
from odoo import api, models, _
from odoo.exceptions import UserError
from odoo.tools import float_is_zero
class StockMove(models.Model):
_inherit = "stock.move"
def product_price_update_before_done(self, forced_qty=None):
tmpl_dict = defaultdict(lambda: 0.0)
# adapt standard price on incomming moves if the product cost_method is 'average'
std_price_update = {}
for move in self.filtered(lambda move: move.location_id.usage in ('supplier', 'production') and move.product_id.cost_method in ('average', 'last')):
product_tot_qty_available = move.product_id.qty_available + tmpl_dict[move.product_id.id]
rounding = move.product_id.uom_id.rounding
qty_done = 0.0
if float_is_zero(product_tot_qty_available, precision_rounding=rounding):
new_std_price = move._get_price_unit()
elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding) or \
float_is_zero(product_tot_qty_available + qty_done, precision_rounding=rounding):
new_std_price = move._get_price_unit()
else:
# Get the standard price
if move.product_id.cost_method == 'average':
amount_unit = std_price_update.get(
(move.company_id.id, move.product_id.id)) or move.product_id.standard_price
qty_done = move.product_uom._compute_quantity(move.quantity_done, move.product_id.uom_id)
qty = forced_qty or qty_done
new_std_price = ((amount_unit * product_tot_qty_available) + (move._get_price_unit() * qty)) / (product_tot_qty_available + qty_done)
if move.product_id.cost_method == 'last' and move.product_id.valuation == 'real_time' or move.product_id.valuation == 'manual_periodic':
new_std_price = move._get_price_unit()
products = self.env['product.product'].browse(move.product_id.id)
account_id = products.property_account_creditor_price_difference.id or products.categ_id.property_account_creditor_price_difference_categ.id
if not account_id:
raise UserError(_('Configuration error. Please configure the price difference account on the product or its category to process this operation.'))
products.create_price_change_account_move(new_std_price, account_id, move.company_id.id, move.origin)
tmpl_dict[move.product_id.id] += qty_done
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
move.product_id.with_context(force_company=move.company_id.id).sudo().write({'standard_price': new_std_price})
std_price_update[move.company_id.id, move.product_id.id] = new_std_price
class StockMoveLine(models.Model):
_inherit = 'stock.move.line'
def write(self, vals):
""" When editing a done stock.move.line, we impact the valuation. Users may increase or
decrease the `qty_done` field. There are three cost method available: standard, average
and fifo. We implement the logic in a similar way for standard and average: increase
or decrease the original value with the standard or average price of today. In fifo, we
have a different logic wheter the move is incoming or outgoing. If the move is incoming, we
update the value and remaining_value/qty with the unit price of the move. If the move is
outgoing and the user increases qty_done, we call _run_fifo and it'll consume layer(s) in
the stack the same way a new outgoing move would have done. If the move is outoing and the
user decreases qty_done, we either increase the last receipt candidate if one is found or
we decrease the value with the last fifo price.
"""
if 'qty_done' in vals:
moves_to_update = {}
for move_line in self.filtered(
lambda ml: ml.state == 'done' and (ml.move_id._is_in() or ml.move_id._is_out())):
moves_to_update[move_line.move_id] = vals['qty_done'] - move_line.qty_done
for move_id, qty_difference in moves_to_update.items():
move_vals = {}
if move_id.product_id.cost_method in ['standard', 'average', 'last']:
correction_value = qty_difference * move_id.product_id.standard_price
if move_id._is_in():
move_vals['value'] = move_id.value + correction_value
elif move_id._is_out():
move_vals['value'] = move_id.value - correction_value
else:
if move_id._is_in():
correction_value = qty_difference * move_id.price_unit
new_remaining_value = move_id.remaining_value + correction_value
move_vals['value'] = move_id.value + correction_value
move_vals['remaining_qty'] = move_id.remaining_qty + qty_difference
move_vals['remaining_value'] = move_id.remaining_value + correction_value
elif move_id._is_out() and qty_difference > 0:
correction_value = self.env['stock.move']._run_fifo(move_id, quantity=qty_difference)
# no need to adapt `remaining_qty` and `remaining_value` as `_run_fifo` took care of it
move_vals['value'] = move_id.value - correction_value
elif move_id._is_out() and qty_difference < 0:
candidates_receipt = self.env['stock.move'].search(move_id._get_in_domain(), order='date, id desc', limit=1)
if candidates_receipt:
candidates_receipt.write({
'remaining_qty': candidates_receipt.remaining_qty + -qty_difference,
'remaining_value': candidates_receipt.remaining_value + (
-qty_difference * candidates_receipt.price_unit),
})
correction_value = qty_difference * candidates_receipt.price_unit
else:
correction_value = qty_difference * move_id.product_id.standard_price
move_vals['value'] = move_id.value - correction_value
move_id.write(move_vals)
if move_id.product_id.valuation == 'real_time':
move_id.with_context(force_valuation_amount=correction_value, forced_quantity=qty_difference)._account_entry_move()
if qty_difference > 0:
move_id.product_price_update_before_done(forced_qty=qty_difference)
return super(StockMoveLine, self).write(vals)