Files
tryton/modules/product_cost_fifo/move.py
T

172 lines
6.4 KiB
Python
Raw Normal View History

2014-12-02 12:55:33 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2010-08-19 22:16:13 +02:00
from decimal import Decimal
2018-12-29 14:20:29 +01:00
2021-12-11 17:58:43 +01:00
from sql import Literal, operators
2019-10-14 00:04:14 +02:00
2018-12-29 14:20:29 +01:00
from trytond.i18n import gettext
2021-12-11 17:58:43 +01:00
from trytond.model import Check, ModelView, Workflow, fields
2018-12-29 14:20:29 +01:00
from trytond.model.exceptions import AccessError
2021-12-11 17:58:43 +01:00
from trytond.modules.product import round_price
2012-09-24 22:09:23 +02:00
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
2012-09-24 22:09:23 +02:00
2008-11-03 18:51:00 +01:00
2018-06-26 19:11:49 +02:00
class Move(metaclass=PoolMeta):
2012-09-24 22:09:23 +02:00
__name__ = 'stock.move'
2019-10-14 00:04:14 +02:00
fifo_quantity = fields.Float(
'FIFO Quantity',
help="Quantity used by FIFO.")
fifo_quantity_available = fields.Function(fields.Float(
"FIFO Quantity Available",
help="Quantity available for FIFO"),
'get_fifo_quantity_available')
2008-11-03 18:51:00 +01:00
2012-09-24 22:09:23 +02:00
@classmethod
def __setup__(cls):
super(Move, cls).__setup__()
cls._allow_modify_closed_period.add('fifo_quantity')
t = cls.__table__()
2012-09-24 22:09:23 +02:00
cls._sql_constraints += [
('check_fifo_quantity',
Check(t, t.quantity >= t.fifo_quantity),
2018-12-29 14:20:29 +01:00
'product_cost_fifo.msg_move_fifo_quantity_greater'),
]
2008-11-03 18:51:00 +01:00
@classmethod
def __register__(cls, module):
table_h = cls.__table_handler__(module)
super().__register__(module)
# Migration from 6.6: rename check_fifo_quantity_out to
# check_fifo_quantity
table_h.drop_constraint('check_fifo_quantity_out')
2012-09-24 22:09:23 +02:00
@staticmethod
def default_fifo_quantity():
2010-12-12 16:50:44 +01:00
return 0.0
2008-11-03 18:51:00 +01:00
2019-10-14 00:04:14 +02:00
def get_fifo_quantity_available(self, name):
return self.quantity - (self.fifo_quantity or 0)
@classmethod
def domain_fifo_quantity_available(cls, domain, tables):
table, _ = tables[None]
name, operator, value = domain
field = cls.fifo_quantity_available._field
Operator = fields.SQL_OPERATORS[operator]
column = (
cls.quantity.sql_column(table)
- cls.fifo_quantity.sql_column(table))
expression = Operator(column, field._domain_value(operator, value))
if isinstance(expression, operators.In) and not expression.right:
expression = Literal(False)
elif isinstance(expression, operators.NotIn) and not expression.right:
expression = Literal(True)
expression = field._domain_add_null(
column, operator, value, expression)
return expression
def _update_fifo_out_product_cost_price(self):
2008-11-03 18:51:00 +01:00
'''
Update the product cost price of the given product on the move. Update
2023-04-26 17:03:35 +00:00
fifo_quantity on the concerned incoming moves. Return the
cost price for outputing the given product and quantity.
2008-11-03 18:51:00 +01:00
'''
pool = Pool()
Uom = pool.get('product.uom')
2008-11-03 18:51:00 +01:00
2023-08-22 13:18:33 +02:00
total_qty = Uom.compute_qty(
self.unit, self.quantity, self.product.default_uom, round=False)
2008-11-03 18:51:00 +01:00
2019-10-14 00:04:14 +02:00
with Transaction().set_context(company=self.company.id):
fifo_moves = self.product.get_fifo_move(total_qty)
2008-11-03 18:51:00 +01:00
cost_price = Decimal(0)
consumed_qty = 0.0
to_save = []
for move, move_qty in fifo_moves:
consumed_qty += move_qty
2020-08-10 23:38:04 +02:00
cost_price += move.get_cost_price() * Decimal(str(move_qty))
2023-08-22 13:18:33 +02:00
move_qty = Uom.compute_qty(
self.product.default_uom, move_qty, move.unit, round=False)
move.fifo_quantity = (move.fifo_quantity or 0.0) + move_qty
# Due to float, the fifo quantity result can exceed the quantity.
2023-08-22 13:18:33 +02:00
assert move.quantity >= move.fifo_quantity - move.unit.rounding
move.fifo_quantity = min(move.fifo_quantity, move.quantity)
to_save.append(move)
2019-10-24 18:57:55 +02:00
if consumed_qty:
cost_price = cost_price / Decimal(str(consumed_qty))
2019-10-24 18:57:55 +02:00
else:
cost_price = self.product.get_multivalue(
'cost_price', **self._cost_price_pattern)
# Compute average cost price
2020-08-10 23:38:04 +02:00
average_cost_price = self._compute_product_cost_price(
'out', product_cost_price=cost_price)
2019-10-24 18:57:55 +02:00
if cost_price:
2020-04-23 21:44:00 +02:00
cost_price = round_price(cost_price)
else:
cost_price = average_cost_price
2021-08-27 08:59:26 +02:00
return cost_price, average_cost_price, to_save
2008-11-03 18:51:00 +01:00
2014-08-29 16:24:28 +02:00
def _do(self):
2021-08-27 08:59:26 +02:00
cost_price, to_save = super(Move, self)._do()
cost_price_method = self.product.get_multivalue(
'cost_price_method', **self._cost_price_pattern)
2020-08-10 23:38:04 +02:00
if (self.from_location.type != 'storage'
2014-08-29 16:24:28 +02:00
and self.to_location.type == 'storage'
and cost_price_method == 'fifo'):
cost_price = self._compute_product_cost_price('in')
2014-08-29 16:24:28 +02:00
elif (self.to_location.type == 'supplier'
and self.from_location.type == 'storage'
and cost_price_method == 'fifo'):
cost_price = self._compute_product_cost_price('out')
2014-08-29 16:24:28 +02:00
elif (self.from_location.type == 'storage'
and self.to_location.type != 'storage'
and cost_price_method == 'fifo'):
2021-08-27 08:59:26 +02:00
fifo_cost_price, cost_price, moves = (
self._update_fifo_out_product_cost_price())
if self.cost_price_required:
if self.cost_price is None:
self.cost_price = fifo_cost_price
if self.product_cost_price is None:
self.product_cost_price = cost_price
2021-08-27 08:59:26 +02:00
to_save.extend(moves)
return cost_price, to_save
2008-11-03 18:51:00 +01:00
2012-09-24 22:09:23 +02:00
@classmethod
2012-12-27 21:54:05 +01:00
@ModelView.button
2020-06-06 10:30:11 +01:00
@Workflow.transition('cancelled')
2012-12-27 21:54:05 +01:00
def cancel(cls, moves):
2022-09-14 19:10:34 +02:00
for move in moves:
if move.fifo_quantity:
raise AccessError(
gettext('product_cost_fifo.msg_move_cancel_fifo',
move=move.rec_name))
2022-08-26 09:52:51 +02:00
super().cancel(moves)
2008-11-03 18:51:00 +01:00
2012-09-24 22:09:23 +02:00
@classmethod
def delete(cls, moves):
2013-07-02 15:59:48 +02:00
fifo_moves = cls.search([
2012-09-24 22:09:23 +02:00
('id', 'in', [m.id for m in moves]),
('fifo_quantity', '!=', 0.0),
])
2013-07-02 15:59:48 +02:00
if fifo_moves:
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('product_cost_fifo.msg_move_delete_fifo',
move=fifo_moves[0].rec_name))
2012-09-24 22:09:23 +02:00
super(Move, cls).delete(moves)
2020-08-25 18:06:27 +02:00
@classmethod
def copy(cls, moves, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.setdefault('fifo_quantity', cls.default_fifo_quantity())
return super().copy(moves, default=default)