Sort lot quantities after pick instead modify each move.

This commit refs #27635
This commit is contained in:
José Antonio Díaz Miralles 2023-08-16 17:03:58 +02:00 committed by Sergio Morillo
parent efd3d62fb4
commit a7a4cb0199
3 changed files with 70 additions and 75 deletions

View File

@ -1,13 +1,16 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.pool import Pool
from . import ir
from . import stock
from . import product
def register():
Pool.register(
stock.Lot,
stock.Move,
product.Template,
product.Product,
ir.Date,
module='stock_lot_fifo', type_='model')

17
ir.py Normal file
View File

@ -0,0 +1,17 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from datetime import date
from trytond.pool import PoolMeta
from trytond.transaction import Transaction
class Date(metaclass=PoolMeta):
__name__ = 'ir.date'
@classmethod
def today(cls, timezone=None):
fifo_assign_date = Transaction().context.get('fifo_assign_date')
if isinstance(fifo_assign_date, date):
return fifo_assign_date
return super().today(timezone=timezone)

125
stock.py
View File

@ -2,26 +2,10 @@
# copyright notices and license terms.
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
from trytond.pyson import Id
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
@property
def fifo_search_context(self):
return {
'stock_date_end': self.effective_date or self.planned_date,
'locations': [self.from_location.id],
'stock_assign': True,
'forecast': False,
}
@property
def fifo_search_domain(self):
return [
('product', '=', self.product.id),
]
class Lot(metaclass=PoolMeta):
__name__ = 'stock.lot'
@staticmethod
def _get_fifo_search_order_by():
@ -37,6 +21,10 @@ class Move(metaclass=PoolMeta):
order.append(('create_date', 'ASC'))
return order
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
def check_lot(self):
if (not self.product.lot_force_assign
or self.from_location.type == 'supplier'):
@ -44,65 +32,52 @@ class Move(metaclass=PoolMeta):
@classmethod
def assign_try(cls, moves, with_childs=True, grouping=('product',)):
'''
If lots required assign lots in FIFO before assigning move.
'''
not_lot_moves = []
date2lot_moves = {}
for move in moves:
if move.lot or move.product.lot_is_required(move.from_location,
move.to_location):
date2lot_moves.setdefault(
move.effective_date or move.planned_date, []).append(move)
else:
not_lot_moves.append(move)
not_lot_success, lot_success = True, True
if not_lot_moves:
not_lot_success = super().assign_try(
not_lot_moves, with_childs=with_childs, grouping=grouping)
grouping = grouping + ('lot',)
# TODO 6.2: Grouping not necessary in context.
for date_, lot_moves in date2lot_moves.items():
with Transaction().set_context(
assign_grouping=grouping, fifo_assign_date=date_):
lot_success &= super().assign_try(lot_moves,
with_childs=with_childs, grouping=grouping)
return not_lot_success & lot_success
def pick_product(self, quantities):
# TODO 6.2: Move this sorting to the method created for it
# def sort_quantities(self, quantities, locations, grouping):
pool = Pool()
Uom = pool.get('product.uom')
Lot = pool.get('stock.lot')
new_moves = []
lots_by_product = {}
consumed_quantities = {}
order = cls._get_fifo_search_order_by()
transaction = Transaction()
grouping = transaction.context.get('assign_grouping')
if not quantities or not grouping:
return super().pick_product(quantities)
for move in moves:
if (not move.lot and move.product.lot_is_required(
move.from_location, move.to_location)):
if move.product.id not in lots_by_product:
with Transaction().set_context(move.fifo_search_context):
lots_by_product[move.product.id] = [x for x in
Lot.search(move.fifo_search_domain, order=order)
if x.quantity > 0]
lot_ids = set([key[2] for key, _ in quantities if key[2]])
lot_id2order = {None: len(lot_ids)}
if lot_ids:
cursor = transaction.connection.cursor()
cursor.execute(*Lot.search([
('id', 'in', lot_ids)
], order=Lot._get_fifo_search_order_by(), query=True))
lot_id2order.update({row[0]: order
for order, row in enumerate(list(cursor.fetchall()))})
quantities = sorted(
quantities, key=lambda key_qty: lot_id2order.get(key_qty[0][2]))
lots = lots_by_product[move.product.id]
remainder = move.internal_quantity
while lots and remainder > 0.0:
lot = lots.pop(0)
production_quantity = 0.0
if getattr(move, 'production_input', False):
for production_input in move.production_input.inputs:
if (production_input.product == move.product
and production_input.state == 'draft'
and lot == production_input.lot):
production_quantity += production_input.quantity
consumed_quantities.setdefault(lot.id, production_quantity)
lot_quantity = lot.quantity - consumed_quantities[lot.id]
if not lot_quantity > 0.0:
continue
assigned_quantity = min(lot_quantity, remainder)
if assigned_quantity == remainder:
move.quantity = Uom.compute_qty(
move.product.default_uom, assigned_quantity,
move.uom)
move.lot = lot
move.save()
lots.insert(0, lot)
else:
quantity = Uom.compute_qty(
move.product.default_uom, assigned_quantity,
move.uom)
new_moves.extend(cls.copy([move], {
'lot': lot.id,
'quantity': quantity,
}))
consumed_quantities[lot.id] += assigned_quantity
remainder -= assigned_quantity
if not lots:
move.quantity = Uom.compute_qty(move.product.default_uom,
remainder, move.uom)
move.save()
lots_by_product[move.product.id] = lots
return super(Move, cls).assign_try(new_moves + moves,
with_childs=with_childs, grouping=grouping)
return super().pick_product(quantities)