Move POC picking to stock_scanner module. (#1)

Fix show last product.
Now we order the pending moves by locations.
If there are diferents locations, we show the name before the move.
Fix picking more than one unit at the same time.

046607
This commit is contained in:
juanjo-nan 2021-08-06 10:50:35 +02:00 committed by Juanjo Garcia
parent ec21c45a65
commit 94f2fca489
8 changed files with 251 additions and 6 deletions

View file

@ -2,6 +2,7 @@
# copyright notices and license terms.
from trytond.pool import Pool
from . import stock
from . import picking
def register():
@ -11,4 +12,10 @@ def register():
stock.ShipmentIn,
stock.ShipmentOut,
stock.ShipmentOutReturn,
picking.StockPickingShipmentOutAsk,
picking.StockPickingShipmentOutScan,
picking.StockPickingShipmentOutResult,
module='stock_scanner', type_='model')
Pool.register(
picking.StockPickingShipmentOut,
module='stock_scanner', type_='wizard')

161
picking.py Normal file
View file

@ -0,0 +1,161 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateTransition, StateView, Button
from trytond.pool import Pool
class StockPickingShipmentOutAsk(ModelView):
'Stock Picking Shipment Out Ask'
__name__ = 'stock.picking.shipment.out.ask'
shipment = fields.Char('Shipment')
to_pick = fields.Selection('get_to_pick', 'To Pick', sort=True)
@classmethod
def get_to_pick(cls):
pool = Pool()
Shipment = pool.get('stock.shipment.out')
return [(s.id, s.rec_name) for s in Shipment.search([
('state', '=', 'assigned'),
])]
class StockPickingShipmentOutScan(ModelView):
'Stock Picking Shipment Out Scan'
__name__ = 'stock.picking.shipment.out.scan'
shipment = fields.Many2One('stock.shipment.out', 'Shipment', readonly=True)
product = fields.Many2One('product.product', 'Product', readonly=True)
to_pick = fields.Char('To pick')
pending_moves = fields.Text('APP Pending Moves', readonly=True)
class StockPickingShipmentOutResult(ModelView):
"Stock Picking Shipment Out Result"
__name__ = 'stock.picking.shipment.out.result'
shipment = fields.Many2One('stock.shipment.out', 'Shipment', readonly=True)
note = fields.Text('Note', readonly=True)
class StockPickingShipmentOut(Wizard):
"Stock Picking Shipment Out Ask"
__name__ = 'stock.picking.shipment.out'
start_state = 'ask'
ask = StateView('stock.picking.shipment.out.ask',
'stock_scanner.stock_picking_shipment_out_start', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Scan', 'scan', 'tryton-ok', True),
])
scan = StateView('stock.picking.shipment.out.scan',
'stock_scanner.stock_picking_shipment_out_scan', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Back', 'ask', 'tryton-back'),
Button('Pick', 'pick', 'tryton-launch', True),
Button('Packed', 'packed', 'tryton-ok'),
])
pick = StateTransition()
packed = StateTransition()
result = StateView('stock.picking.shipment.out.result',
'stock_scanner.stock_picking_shipment_out_result', [
Button('Start', 'ask', 'tryton-back', True),
Button('Done', 'end', 'tryton-ok'),
])
def transition_pick(self):
pool = Pool()
Shipment = pool.get('stock.shipment.out')
shipment = Shipment(self.scan.shipment)
def qty(value):
try:
return float(value)
except ValueError:
return False
shipment = Shipment(self.scan.shipment)
to_pick = self.scan.to_pick
quantity = qty(to_pick)
if shipment.scanned_product and quantity and len(to_pick) < 5:
shipment.scanned_quantity = shipment.scanned_uom.round(quantity)
shipment.save()
Shipment.scan([shipment])
shipment = Shipment(shipment.id)
else:
for move in shipment.pending_moves:
if move.matches_scan(to_pick):
self.scan.product = move.product
shipment.scanned_product = move.product
shipment.scanned_quantity = 1
shipment.on_change_scanned_product()
shipment.save()
Shipment.scan([shipment])
shipment.scanned_product = move.product
shipment.save()
break
else:
self.scan.product = None
return 'scan'
def transition_packed(self):
pool = Pool()
Shipment = pool.get('stock.shipment.out')
shipment = Shipment(self.scan.shipment)
Shipment.assign([shipment])
Shipment.pack([shipment])
return 'result'
def default_scan(self, fields):
pool = Pool()
Shipment = pool.get('stock.shipment.out')
Location = pool.get('stock.location')
# Get storage_location locations
storages = Location.search([('type', '=', 'warehouse')])
storage_locations = []
for storage in storages:
storage_locations.append(storage.storage_location)
if self.ask.to_pick is not None:
shipment = Shipment(self.ask.to_pick)
else:
shipments = Shipment.search([
('state', '=', 'assigned'),
('number', '=', self.ask.shipment),
], limit=1)
if not shipments:
return {}
shipment, = shipments
defaults = {}
defaults['shipment'] = shipment.id
if hasattr(self.scan, 'product'):
defaults['product'] = self.scan.product and self.scan.product.id
pending_moves = []
locations_move = {}
for move in shipment.pending_moves:
locations_move.setdefault(move.from_location, [])
locations_move[move.from_location].append(move)
for location in sorted(locations_move, key=lambda x: x.name):
if location not in storage_locations:
pending_moves.append(
u'<div align="left">'
'<font size="4"><u><b>{}</b></u></font>'
'</div>'.format(location.name))
for move in locations_move[location]:
pending_moves.append(
u'<div align="left">'
'<font size="4">{} <b>{}</b></font>'
'</div>'.format(move.pending_quantity,
move.product.rec_name))
defaults['pending_moves'] = '\n'.join(pending_moves)
return defaults
def default_result(self, fields):
defaults = {}
defaults['shipment'] = self.scan.shipment and self.scan.shipment.id
return defaults

33
picking.xml Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0"?>
<!-- This file is part uudgore module for Tryton.
The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Stock Picking Shipment Out-->
<record model="ir.ui.view" id="stock_picking_shipment_out_start">
<field name="model">stock.picking.shipment.out.ask</field>
<field name="type">form</field>
<field name="name">stock_picking_shipment_out_start</field>
</record>
<record model="ir.ui.view" id="stock_picking_shipment_out_scan">
<field name="model">stock.picking.shipment.out.scan</field>
<field name="type">form</field>
<field name="name">stock_picking_shipment_out_scan</field>
</record>
<record model="ir.ui.view" id="stock_picking_shipment_out_result">
<field name="model">stock.picking.shipment.out.result</field>
<field name="type">form</field>
<field name="name">stock_picking_shipment_out_result</field>
</record>
<record model="ir.action.wizard" id="act_stock_picking_shipment_out">
<field name="name">Shipment Out Picking</field>
<field name="wiz_name">stock.picking.shipment.out</field>
<field name="window" eval="False"/>
</record>
<menuitem name="Shipment Out Picking" parent="stock.menu_shipment_out_form"
action="act_stock_picking_shipment_out"
id="menu_stock_shipment_out_picking"
sequence="50"/>
</data>
</tryton>

View file

@ -15,7 +15,7 @@ __all__ = ['Configuration', 'Move', 'ShipmentIn',
MIXIN_STATES = {
'readonly': ~Eval('state').in_(['waiting', 'draft']),
'readonly': ~Eval('state').in_(['waiting', 'draft', 'assigned']),
}
@ -102,6 +102,14 @@ class Move(metaclass=PoolMeta):
'scanned_quantity': cls.default_scanned_quantity(),
})
def matches_scan(self, input_):
if self.product.code == input_:
return True
for identifier in self.product.identifiers:
if identifier.code == input_:
return True
return False
class StockScanMixin(object):
scanner_enabled = fields.Function(fields.Boolean('Scanner Enabled'),
@ -109,7 +117,8 @@ class StockScanMixin(object):
pending_moves = fields.Function(fields.One2Many('stock.move', None,
'Pending Moves', states={
'invisible': (~Eval('scanner_enabled', False)
| ~Eval('state', 'draft').in_(['waiting', 'draft'])),
| ~Eval('state', 'draft').in_(['waiting', 'draft',
'assigned'])),
}, depends=['scanner_enabled', 'state'],
help='List of pending products to be scan.'),
'get_pending_moves')
@ -147,11 +156,13 @@ class StockScanMixin(object):
},
'reset_scanned_quantities': {
'icon': 'tryton-refresh',
'invisible': ~Eval('state').in_(['waiting', 'draft'])
'invisible': ~Eval('state').in_(['waiting', 'draft',
'assigned'])
},
'scan_all': {
'icon': 'tryton-warning',
'invisible': ~Eval('state').in_(['waiting', 'draft'])
'invisible': ~Eval('state').in_(['waiting', 'draft',
'assigned'])
},
})
@ -382,9 +393,11 @@ class ShipmentOut(StockScanMixin, metaclass=PoolMeta):
return move
@classmethod
def assign_try(cls, shipments):
def pack(cls, shipments):
cls.wait(shipments)
cls.set_scanned_quantity_as_quantity(shipments, 'inventory_moves')
return super(ShipmentOut, cls).assign_try(shipments)
cls.assign_try(shipments)
return super(ShipmentOut, cls).pack(shipments)
class ShipmentOutReturn(ShipmentOut, metaclass=PoolMeta):

View file

@ -8,4 +8,5 @@ extras_depend:
stock_valued
xml:
message.xml
picking.xml
stock.xml

View file

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part stock_picking module for Tryton.
The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
<form>
<image name="tryton-info" xexpand="0" xfill="0"/>
<label string="Done picking shipment" id="packed" yalign="0.0" xalign="0.0" xexpand="1"/>
<newline/>
<label name="shipment"/>
<field name="shipment"/>
<newline/>
<field name="note" colspan="6"/>
</form>

View file

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part stock_picking module for Tryton.
The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
<form col="4" cursor="to_pick">
<field name="shipment" colspan="4"/>
<field name="product" colspan="4"/>
<field name="to_pick" colspan="4"/>
<field name="pending_moves" colspan="4" widget="richtext" toolbar="0" yexpand="1" yfill="1"/>
</form>

View file

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part stock_picking module for Tryton.
The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
<form col="6">
<label name="shipment"/>
<field name="shipment"/>
<label name="to_pick"/>
<field name="to_pick"/>
</form>