mirror of
https://gitlab.com/datalifeit/trytond-stock_lot_fifo
synced 2023-12-14 06:02:54 +01:00
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
|
# The COPYRIGHT file at the top level of this repository contains the full
|
||||||
# copyright notices and license terms.
|
# copyright notices and license terms.
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
|
from . import ir
|
||||||
from . import stock
|
from . import stock
|
||||||
from . import product
|
from . import product
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
Pool.register(
|
Pool.register(
|
||||||
|
stock.Lot,
|
||||||
stock.Move,
|
stock.Move,
|
||||||
product.Template,
|
product.Template,
|
||||||
product.Product,
|
product.Product,
|
||||||
|
ir.Date,
|
||||||
module='stock_lot_fifo', type_='model')
|
module='stock_lot_fifo', type_='model')
|
||||||
|
|
17
ir.py
Normal file
17
ir.py
Normal 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
125
stock.py
|
@ -2,26 +2,10 @@
|
||||||
# copyright notices and license terms.
|
# copyright notices and license terms.
|
||||||
from trytond.pool import Pool, PoolMeta
|
from trytond.pool import Pool, PoolMeta
|
||||||
from trytond.transaction import Transaction
|
from trytond.transaction import Transaction
|
||||||
from trytond.pyson import Id
|
|
||||||
|
|
||||||
|
|
||||||
class Move(metaclass=PoolMeta):
|
class Lot(metaclass=PoolMeta):
|
||||||
__name__ = 'stock.move'
|
__name__ = 'stock.lot'
|
||||||
|
|
||||||
@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),
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_fifo_search_order_by():
|
def _get_fifo_search_order_by():
|
||||||
|
@ -37,6 +21,10 @@ class Move(metaclass=PoolMeta):
|
||||||
order.append(('create_date', 'ASC'))
|
order.append(('create_date', 'ASC'))
|
||||||
return order
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
class Move(metaclass=PoolMeta):
|
||||||
|
__name__ = 'stock.move'
|
||||||
|
|
||||||
def check_lot(self):
|
def check_lot(self):
|
||||||
if (not self.product.lot_force_assign
|
if (not self.product.lot_force_assign
|
||||||
or self.from_location.type == 'supplier'):
|
or self.from_location.type == 'supplier'):
|
||||||
|
@ -44,65 +32,52 @@ class Move(metaclass=PoolMeta):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def assign_try(cls, moves, with_childs=True, grouping=('product',)):
|
def assign_try(cls, moves, with_childs=True, grouping=('product',)):
|
||||||
'''
|
not_lot_moves = []
|
||||||
If lots required assign lots in FIFO before assigning move.
|
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()
|
pool = Pool()
|
||||||
Uom = pool.get('product.uom')
|
|
||||||
Lot = pool.get('stock.lot')
|
Lot = pool.get('stock.lot')
|
||||||
|
|
||||||
new_moves = []
|
transaction = Transaction()
|
||||||
lots_by_product = {}
|
grouping = transaction.context.get('assign_grouping')
|
||||||
consumed_quantities = {}
|
if not quantities or not grouping:
|
||||||
order = cls._get_fifo_search_order_by()
|
return super().pick_product(quantities)
|
||||||
|
|
||||||
for move in moves:
|
lot_ids = set([key[2] for key, _ in quantities if key[2]])
|
||||||
if (not move.lot and move.product.lot_is_required(
|
lot_id2order = {None: len(lot_ids)}
|
||||||
move.from_location, move.to_location)):
|
if lot_ids:
|
||||||
if move.product.id not in lots_by_product:
|
cursor = transaction.connection.cursor()
|
||||||
with Transaction().set_context(move.fifo_search_context):
|
cursor.execute(*Lot.search([
|
||||||
lots_by_product[move.product.id] = [x for x in
|
('id', 'in', lot_ids)
|
||||||
Lot.search(move.fifo_search_domain, order=order)
|
], order=Lot._get_fifo_search_order_by(), query=True))
|
||||||
if x.quantity > 0]
|
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]
|
return super().pick_product(quantities)
|
||||||
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)
|
|
||||||
|
|
Loading…
Reference in a new issue