Sort lot quantities after pick instead modify each move.
This commit refs #27635
This commit is contained in:
parent
efd3d62fb4
commit
a7a4cb0199
|
@ -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')
|
||||
|
|
|
@ -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
125
stock.py
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue