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
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)
|
|
|