diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml
index 7cdd489d9c5..f84db02876e 100644
--- a/addons/mrp/mrp_view.xml
+++ b/addons/mrp/mrp_view.xml
@@ -560,7 +560,7 @@
-
+
diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml
index aaa860fa793..86029a085d6 100644
--- a/addons/mrp_repair/mrp_repair_view.xml
+++ b/addons/mrp_repair/mrp_repair_view.xml
@@ -49,7 +49,7 @@
-
+
@@ -96,7 +96,7 @@
-
+
diff --git a/addons/stock/res_config.py b/addons/stock/res_config.py
index b03bbedff68..1a8b86fd54f 100644
--- a/addons/stock/res_config.py
+++ b/addons/stock/res_config.py
@@ -67,7 +67,10 @@ This installs the module product_expiry."""),
'group_stock_packaging': fields.boolean("Allow to define several packaging methods on products",
implied_group='product.group_stock_packaging',
help="""Allows you to create and manage your packaging dimensions and types you want to be maintained in your system."""),
- 'group_stock_tracking_lot': fields.boolean("Track serial number on products",
+ 'group_stock_production_lot': fields.boolean("Track lots or serial numbers",
+ implied_group='stock.group_production_lot',
+ help="""This allows you to assign a lot (or serial number) to the pickings and moves. This can make it possible to know which production lot was sent to a certain client, ..."""),
+ 'group_stock_tracking_lot': fields.boolean("Use packages: pallets, boxes, ...",
implied_group='stock.group_tracking_lot',
help="""This allows you to manage products by using serial numbers. When you select a serial number on product moves, you can get the traceability of that product."""),
'group_stock_tracking_owner': fields.boolean("Manage owner on stock",
diff --git a/addons/stock/res_config_view.xml b/addons/stock/res_config_view.xml
index b83100e115b..4a2f8b2686e 100644
--- a/addons/stock/res_config_view.xml
+++ b/addons/stock/res_config_view.xml
@@ -27,13 +27,17 @@
-
-
+
+
-
+
+
+
+
+
diff --git a/addons/stock/security/stock_security.xml b/addons/stock/security/stock_security.xml
index 343235e8265..976e83545bd 100644
--- a/addons/stock/security/stock_security.xml
+++ b/addons/stock/security/stock_security.xml
@@ -19,8 +19,13 @@
+
+ Manage Lots / Serial Numbers
+
+
+
- Manage Serial Numbers
+ Manage Packages
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 72cc87d5862..4d7ca421dd4 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -683,6 +683,7 @@ class stock_picking(osv.osv):
for move in move_lines:
if move[1]:
res[pick.id] = 'partially_available'
+ break
return res
def _get_pickings(self, cr, uid, ids, context=None):
@@ -3444,11 +3445,11 @@ class stock_pack_operation(osv.osv):
res = {}
for ops in self.browse(cr, uid, ids, context=context):
res[ops.id] = 0
- if ops.package_id:
+ if ops.package_id and not ops.product_id:
#dont try to compute the remaining quantity for packages because it's not relevant (a package could include different products).
#should use _get_remaining_prod_quantities instead
continue
- elif ops.product_id:
+ else:
qty = ops.product_qty
if ops.product_uom_id:
qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id)
@@ -3533,24 +3534,74 @@ class stock_pack_operation(osv.osv):
if qty_to_assign <= 0:
break
+ def _check_quants_reserved(ops):
+ if ops.package_id and not ops.product_id:
+ for quant in quant_obj.browse(cr, uid, package_obj.get_content(cr, uid, [ops.package_id.id]), context=context):
+ if quant.reservation_id and quant.reservation_id.id in [x.id for x in ops.picking_id.move_lines] and (not quants_done.get(quant.id)):
+ #Entire packages means entire quants from those packages
+ if not quants_done.get(quant.id):
+ quants_done[quant.id] = 0
+ link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': quant.qty}, context=context)
+ else:
+ qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id)
+ #Check moves with same product
+ for move in [x for x in ops.picking_id.move_lines if ops.product_id.id == x.product_id.id]:
+ for quant in move.reserved_quant_ids:
+ if not qty > 0:
+ break
+ if ops.package_id:
+ flag = quant.package_id and bool(package_obj.search(cr, uid, [('id', 'child_of', [ops.package_id.id]), ('id', '=', quant.package_id.id)], context=context)) or False
+ else:
+ flag = not quant.package_id.id
+ flag = flag and ((ops.lot_id and ops.lot_id.id == quant.lot_id.id) or not ops.lot_id)
+ flag = flag and (ops.owner_id.id == quant.owner_id.id)
+ if flag:
+ quant_qty = quant.qty
+ if quants_done.get(quant.id):
+ if quants_done[quant.id] == 0:
+ continue
+ quant_qty = quants_done[quant.id]
+ if quant_qty > qty:
+ qty_todo = qty
+ quants_done[quant.id] = quant_qty - qty
+ else:
+ qty_todo = quant_qty
+ quants_done[quant.id] = 0
+ qty -= qty_todo
+ link_obj.create(cr, uid, {'move_id': quant.reservation_id.id, 'operation_id': ops.id, 'qty': qty_todo}, context=context)
+
link_obj = self.pool.get('stock.move.operation.link')
uom_obj = self.pool.get('product.uom')
package_obj = self.pool.get('stock.quant.package')
- if op_ids:
- #sort moves in order to process first the ones that have already reserved quants
- ops = self.browse(cr, uid, op_ids[0], context=context)
- sorted_moves = ops.picking_id.move_lines
- sorted_moves.sort(key = lambda x: x.product_qty - x.reserved_availability)
- for op in self.browse(cr, uid, op_ids, context=context):
+ quant_obj = self.pool.get('stock.quant')
+ quants_done = {}
+
+ operations = self.browse(cr, uid, op_ids, context=context)
+ operations.sort(key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
+ sorted_moves = []
+ for op in operations:
+ if not sorted_moves:
+ #sort moves in order to process first the ones that have already reserved quants
+ sorted_moves = op.picking_id.move_lines
+ sorted_moves.sort(key=lambda x: x.product_qty - x.reserved_availability)
+
to_unlink_ids = [x.id for x in op.linked_move_operation_ids]
if to_unlink_ids:
link_obj.unlink(cr, uid, to_unlink_ids, context=context)
+ _check_quants_reserved(op)
+
+ for op in operations:
+ op.refresh()
if op.product_id:
- normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.product_qty, op.product_id.uom_id.id)
- _create_link_for_product(op.product_id.id, normalized_qty)
+ #TODO: Remaining qty: UoM conversions are done twice
+ normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.remaining_qty, op.product_id.uom_id.id)
+ if normalized_qty > 0:
+ _create_link_for_product(op.product_id.id, normalized_qty)
elif op.package_id:
- for product_id, qty in package_obj._get_all_products_quantities(cr, uid, op.package_id.id, context=context).items():
- _create_link_for_product(product_id, qty)
+ prod_quants = self._get_remaining_prod_quantities(cr, uid, op, context=context)
+ for product_id, qty in prod_quants.items():
+ if qty > 0:
+ _create_link_for_product(product_id, qty)
def process_packaging(self, cr, uid, operation, quants, context=None):
''' Process the packaging of a given operation, after the quants have been moved. If there was not enough quants found
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml
index 91f7b69d157..596988da556 100644
--- a/addons/stock/stock_view.xml
+++ b/addons/stock/stock_view.xml
@@ -117,7 +117,7 @@
-
+
@@ -130,7 +130,7 @@
-
+
@@ -276,7 +276,7 @@
#
@@ -773,7 +773,7 @@
-
+
@@ -1739,7 +1739,7 @@
-
+
@@ -1747,7 +1747,7 @@
-
+
@@ -1777,7 +1777,7 @@
-
+
@@ -1801,7 +1801,7 @@
-
+
@@ -1941,10 +1941,10 @@
-
-
-
-
+
+
+
+
diff --git a/addons/stock/test/packing.yml b/addons/stock/test/packing.yml
index 4978d44750e..c85e2b0d769 100644
--- a/addons/stock/test/packing.yml
+++ b/addons/stock/test/packing.yml
@@ -139,7 +139,11 @@
backorder_ids = self.search(cr, uid, [('backorder_id', '=', ref('delivery_order1'))], context=context)
assert backorder_ids, "Backorder should have been created"
self.do_prepare_partial(cr, uid, backorder_ids, context=context)
- stock_pack = self.pool.get('stock.pack.operation').create(cr, uid, {'picking_id': backorder_ids[0], 'product_qty': 20})
+ picking = self.browse(cr, uid, backorder_ids[0])
+ assert len(picking.pack_operation_ids) == 2, "Wrong number of pack operation"
+ for pack_op in picking.pack_operation_ids:
+ assert pack_op.product_qty == 1, "Wrong quantity in pack operation (%s found instead of 1)" % (pack_op.product_qty)
+ assert pack_op.package_id.name in ('Pallet 2', 'Pallet 3'), "Wrong pallet info in pack operation (%s found)" % (pack_op.package_id.name)
self.action_assign(cr, uid, backorder_ids, context=context)
self.do_transfer(cr, uid, backorder_ids, context=context)
-
diff --git a/addons/stock/test/packingneg.yml b/addons/stock/test/packingneg.yml
index 5f26c2f59ac..d42cbfa52e2 100644
--- a/addons/stock/test/packingneg.yml
+++ b/addons/stock/test/packingneg.yml
@@ -93,10 +93,11 @@
!python {model: stock.picking}: |
self.do_transfer(cr, uid, [ref('delivery_order_neg')], context=context)
-
- Check the quants that you have 120 pieces pallet 1 in customers, -20 pieces pallet 2 in stock and 120 and 20 with customers and 20/30 in stock, 10 in customers from pallet 3
+ Check the quants that you have 120 pieces pallet 1 in customers, -20 pieces pallet 2 in stock, 120 + 20 pieces 2 in customer with lot, and a total quantity of 50 in stock from pallet 3 (should be 20+30, as it has been split by reservation), finally 10 in customers from pallet 3
-
!python {model: stock.quant}: |
reco_id = self.search(cr ,uid , [('product_id','=',ref('product_neg'))], context=context)
+ pallet_3_stock_qty = 0
for rec in self.browse(cr, uid, reco_id, context=context):
if rec.package_id.name == 'Palneg 1' and rec.location_id.id == ref('stock_location_customers'):
assert rec.qty == 120, "Should have 120 pieces on pallet 1"
@@ -106,11 +107,12 @@
elif rec.lot_id.name == 'Lot neg' and rec.location_id.id == ref('stock_location_customers'):
assert ((rec.qty == 20 or rec.qty == 120) and not rec.package_id), "Should have 140 pieces (120+20) in customer location from pallet 2 and lot A"
elif rec.package_id.name == 'Palneg 3' and rec.location_id.id == ref('stock_location_stock'):
- assert rec.qty == 30 or rec.qty == 20, "Should have 30 and 20 pieces in stock on pallet 3"
+ pallet_3_stock_qty += rec.qty
elif not rec.package_id and not rec.lot_id and rec.location_id.id == ref('stock_location_customers'):
assert rec.qty == 10, "Should have 10 pieces in customer location from pallet 3"
else:
assert False, "Unrecognized quant"
+ assert pallet_3_stock_qty == 50, "Should have 50 pieces in stock on pallet 3"
-
Create a picking for reconciling the negative quant
-
diff --git a/addons/stock/wizard/stock_change_product_qty_view.xml b/addons/stock/wizard/stock_change_product_qty_view.xml
index 81016e467e9..935374fae4b 100644
--- a/addons/stock/wizard/stock_change_product_qty_view.xml
+++ b/addons/stock/wizard/stock_change_product_qty_view.xml
@@ -10,7 +10,7 @@
-
+