[IMP] stock: choose destination wizard
Purpose : during a transfer, if some move lines have different destination locations and the user click on 'put in pack' button, an warning is triggered. This message, saying : 'You cannot put in the same pack move lines having different destination location', is replaced by a wizard allowing the user to choose a unique destination location. This location will be set as location_dest_id on all transfer's stock move lines. After that, the wizard calls again the _put_in_pack() method where all the logic remains the same. Task : 1872345
This commit is contained in:
@@ -113,6 +113,9 @@ class StockPicking(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def put_in_pack(self):
|
def put_in_pack(self):
|
||||||
|
res = super(StockPicking, self).put_in_pack()
|
||||||
|
if isinstance(res, dict) and res.get('type'):
|
||||||
|
return res
|
||||||
if self.carrier_id and self.carrier_id.delivery_type not in ['base_on_rule', 'fixed']:
|
if self.carrier_id and self.carrier_id.delivery_type not in ['base_on_rule', 'fixed']:
|
||||||
view_id = self.env.ref('delivery.choose_delivery_package_view_form').id
|
view_id = self.env.ref('delivery.choose_delivery_package_view_form').id
|
||||||
return {
|
return {
|
||||||
@@ -123,10 +126,14 @@ class StockPicking(models.Model):
|
|||||||
'view_id': view_id,
|
'view_id': view_id,
|
||||||
'views': [(view_id, 'form')],
|
'views': [(view_id, 'form')],
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
'context': dict(self.env.context, current_package_carrier_type=self.carrier_id.delivery_type),
|
'context': dict(
|
||||||
|
self.env.context,
|
||||||
|
current_package_carrier_type=self.carrier_id.delivery_type,
|
||||||
|
default_stock_quant_package_id=res.id
|
||||||
|
),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return self._put_in_pack()
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def action_send_confirmation_email(self):
|
def action_send_confirmation_email(self):
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ class ChooseDeliveryPackage(models.TransientModel):
|
|||||||
return {'warning': warning_mess}
|
return {'warning': warning_mess}
|
||||||
|
|
||||||
def put_in_pack(self):
|
def put_in_pack(self):
|
||||||
picking_id = self.env['stock.picking'].browse(self.env.context['active_id'])
|
|
||||||
if not self.stock_quant_package_id:
|
|
||||||
stock_quant_package = picking_id._put_in_pack()
|
|
||||||
self.stock_quant_package_id = stock_quant_package
|
|
||||||
# write shipping weight and product_packaging on 'stock_quant_package' if needed
|
# write shipping weight and product_packaging on 'stock_quant_package' if needed
|
||||||
if self.delivery_packaging_id:
|
if self.delivery_packaging_id:
|
||||||
self.stock_quant_package_id.packaging_id = self.delivery_packaging_id
|
self.stock_quant_package_id.packaging_id = self.delivery_packaging_id
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
'wizard/stock_warn_insufficient_qty_views.xml',
|
'wizard/stock_warn_insufficient_qty_views.xml',
|
||||||
'wizard/product_replenish_views.xml',
|
'wizard/product_replenish_views.xml',
|
||||||
'wizard/stock_track_confirmation_views.xml',
|
'wizard/stock_track_confirmation_views.xml',
|
||||||
|
'wizard/stock_package_destination_views.xml',
|
||||||
|
|
||||||
'views/res_partner_views.xml',
|
'views/res_partner_views.xml',
|
||||||
'views/product_strategy_views.xml',
|
'views/product_strategy_views.xml',
|
||||||
|
|||||||
@@ -986,6 +986,26 @@ class Picking(models.Model):
|
|||||||
|
|
||||||
return _explore(self.env['stock.picking'], self.env['stock.move'], moves)
|
return _explore(self.env['stock.picking'], self.env['stock.move'], moves)
|
||||||
|
|
||||||
|
def check_destinations(self):
|
||||||
|
if len(self.move_line_ids.filtered(lambda l: l.qty_done > 0 and not l.result_package_id).mapped('location_dest_id')) > 1:
|
||||||
|
view_id = self.env.ref('stock.stock_package_destination_form_view').id
|
||||||
|
wiz = self.env['stock.package.destination'].create({
|
||||||
|
'picking_id': self.id,
|
||||||
|
'location_dest_id': self.move_line_ids[0].location_dest_id.id,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'name': _('Choose destination location'),
|
||||||
|
'view_type': 'form',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_model': 'stock.package.destination',
|
||||||
|
'view_id': view_id,
|
||||||
|
'views': [(view_id, 'form')],
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_id': wiz.id,
|
||||||
|
'target': 'new'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
def _put_in_pack(self):
|
def _put_in_pack(self):
|
||||||
package = False
|
package = False
|
||||||
@@ -994,8 +1014,6 @@ class Picking(models.Model):
|
|||||||
if move_line_ids:
|
if move_line_ids:
|
||||||
move_lines_to_pack = self.env['stock.move.line']
|
move_lines_to_pack = self.env['stock.move.line']
|
||||||
package = self.env['stock.quant.package'].create({})
|
package = self.env['stock.quant.package'].create({})
|
||||||
if len(move_line_ids.mapped('location_dest_id')) > 1:
|
|
||||||
raise UserError('You cannot put in the same pack move lines having different destination locations')
|
|
||||||
for ml in move_line_ids:
|
for ml in move_line_ids:
|
||||||
if float_compare(ml.qty_done, ml.product_uom_qty,
|
if float_compare(ml.qty_done, ml.product_uom_qty,
|
||||||
precision_rounding=ml.product_uom_id.rounding) >= 0:
|
precision_rounding=ml.product_uom_id.rounding) >= 0:
|
||||||
@@ -1027,6 +1045,9 @@ class Picking(models.Model):
|
|||||||
return package
|
return package
|
||||||
|
|
||||||
def put_in_pack(self):
|
def put_in_pack(self):
|
||||||
|
res = self.check_destinations()
|
||||||
|
if res.get('type'):
|
||||||
|
return res
|
||||||
return self._put_in_pack()
|
return self._put_in_pack()
|
||||||
|
|
||||||
def button_scrap(self):
|
def button_scrap(self):
|
||||||
|
|||||||
@@ -180,3 +180,76 @@ class TestPacking(TransactionCase):
|
|||||||
self.assertEqual(package_level_reserved.location_id.id, shelf1_location.id, 'The reserved package level must be reserved in shelf1')
|
self.assertEqual(package_level_reserved.location_id.id, shelf1_location.id, 'The reserved package level must be reserved in shelf1')
|
||||||
self.assertEqual(package_level_confirmed.location_id.id, self.stock_location.id, 'The not reserved package should keep its location')
|
self.assertEqual(package_level_confirmed.location_id.id, self.stock_location.id, 'The not reserved package should keep its location')
|
||||||
self.assertEqual(picking.package_level_ids.mapped('is_done'), [True, True], 'Both package should still done')
|
self.assertEqual(picking.package_level_ids.mapped('is_done'), [True, True], 'Both package should still done')
|
||||||
|
|
||||||
|
def test_put_in_pack_to_different_location(self):
|
||||||
|
""" Hitting 'Put in pack' button while some move lines go to different
|
||||||
|
location should trigger a wizard. This wizard applies the same destination
|
||||||
|
location to all the move lines
|
||||||
|
"""
|
||||||
|
shelf1_location = self.env['stock.location'].create({
|
||||||
|
'name': 'shelf1',
|
||||||
|
'usage': 'internal',
|
||||||
|
'location_id': self.stock_location.id,
|
||||||
|
})
|
||||||
|
shelf2_location = self.env['stock.location'].create({
|
||||||
|
'name': 'shelf2',
|
||||||
|
'usage': 'internal',
|
||||||
|
'location_id': self.stock_location.id,
|
||||||
|
})
|
||||||
|
picking = self.env['stock.picking'].create({
|
||||||
|
'picking_type_id': self.warehouse.in_type_id.id,
|
||||||
|
'location_id': self.stock_location.id,
|
||||||
|
'location_dest_id': self.stock_location.id,
|
||||||
|
'state': 'draft',
|
||||||
|
})
|
||||||
|
ship_move_a = self.env['stock.move'].create({
|
||||||
|
'name': 'move 1',
|
||||||
|
'product_id': self.productA.id,
|
||||||
|
'product_uom_qty': 5.0,
|
||||||
|
'product_uom': self.productA.uom_id.id,
|
||||||
|
'location_id': self.customer_location.id,
|
||||||
|
'location_dest_id': shelf1_location.id,
|
||||||
|
'picking_id': picking.id,
|
||||||
|
'state': 'draft',
|
||||||
|
})
|
||||||
|
picking.action_confirm()
|
||||||
|
picking.action_assign()
|
||||||
|
picking.move_line_ids.filtered(lambda ml: ml.product_id == self.productA).qty_done = 5.0
|
||||||
|
picking.put_in_pack()
|
||||||
|
pack1 = self.env['stock.quant.package'].search([])[-1]
|
||||||
|
picking.write({
|
||||||
|
'move_line_ids': [(0, 0, {
|
||||||
|
'product_id': self.productB.id,
|
||||||
|
'product_uom_qty': 7.0,
|
||||||
|
'qty_done': 7.0,
|
||||||
|
'product_uom_id': self.productB.uom_id.id,
|
||||||
|
'location_id': self.customer_location.id,
|
||||||
|
'location_dest_id': shelf2_location.id,
|
||||||
|
'picking_id': picking.id,
|
||||||
|
'state': 'confirmed',
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
picking.write({
|
||||||
|
'move_line_ids': [(0, 0, {
|
||||||
|
'product_id': self.productA.id,
|
||||||
|
'product_uom_qty': 5.0,
|
||||||
|
'qty_done': 5.0,
|
||||||
|
'product_uom_id': self.productA.uom_id.id,
|
||||||
|
'location_id': self.customer_location.id,
|
||||||
|
'location_dest_id': shelf1_location.id,
|
||||||
|
'picking_id': picking.id,
|
||||||
|
'state': 'confirmed',
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
wizard_values = picking.put_in_pack()
|
||||||
|
wizard = self.env[(wizard_values.get('res_model'))].browse(wizard_values.get('res_id'))
|
||||||
|
wizard.location_dest_id = shelf2_location.id
|
||||||
|
wizard.action_done()
|
||||||
|
picking.action_done()
|
||||||
|
pack2 = self.env['stock.quant.package'].search([])[-1]
|
||||||
|
self.assertEqual(pack2.location_id.id, shelf2_location.id, 'The package must be stored in shelf2')
|
||||||
|
self.assertEqual(pack1.location_id.id, shelf1_location.id, 'The package must be stored in shelf1')
|
||||||
|
qp1 = pack2.quant_ids[0]
|
||||||
|
qp2 = pack2.quant_ids[1]
|
||||||
|
self.assertEqual(qp1.quantity, 5, 'The quant has not the good quantity')
|
||||||
|
self.assertEqual(qp2.quantity, 7, 'The quant has not the good quantity')
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ from . import stock_rules_report
|
|||||||
from . import stock_warn_insufficient_qty
|
from . import stock_warn_insufficient_qty
|
||||||
from . import product_replenish
|
from . import product_replenish
|
||||||
from . import stock_track_confirmation
|
from . import stock_track_confirmation
|
||||||
|
from . import stock_package_destination
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseDestinationLocation(models.TransientModel):
|
||||||
|
_name = 'stock.package.destination'
|
||||||
|
|
||||||
|
picking_id = fields.Many2one('stock.picking', required=True)
|
||||||
|
move_line_ids = fields.Many2many('stock.move.line', 'Products', compute='_compute_move_line_ids', required=True)
|
||||||
|
location_dest_id = fields.Many2one('stock.location', 'Destination location', required=True)
|
||||||
|
filtered_location = fields.One2many(comodel_name='stock.location', compute='_filter_location')
|
||||||
|
|
||||||
|
@api.one
|
||||||
|
@api.depends('picking_id')
|
||||||
|
def _compute_move_line_ids(self):
|
||||||
|
self.move_line_ids = self.picking_id.move_line_ids.filtered(lambda l: l.qty_done > 0 and not l.result_package_id)
|
||||||
|
|
||||||
|
@api.one
|
||||||
|
@api.depends('move_line_ids')
|
||||||
|
def _filter_location(self):
|
||||||
|
self.filtered_location = self.move_line_ids.mapped('location_dest_id')
|
||||||
|
|
||||||
|
def action_done(self):
|
||||||
|
# set the same location on each move line and pass again in _put_in_pack
|
||||||
|
for line in self.move_line_ids:
|
||||||
|
line.location_dest_id = self.location_dest_id
|
||||||
|
return self.picking_id.put_in_pack()
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="stock_package_destination_form_view" model="ir.ui.view">
|
||||||
|
<field name="name">stock.package.destination.view</field>
|
||||||
|
<field name="model">stock.package.destination</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<div>
|
||||||
|
You are trying to put products going to different locations into the same package
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<field name="move_line_ids" style="margin-top:10px;">
|
||||||
|
<tree>
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="location_dest_id"/>
|
||||||
|
<field name="qty_done" String="quantity"/>
|
||||||
|
<field name="lot_id" groups="stock.group_production_lot"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Where do you want to send the products ?</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<field name="filtered_location" invisible="1"/>
|
||||||
|
<field name="location_dest_id" domain="[('id', 'in', filtered_location)]" options="{'no_create': True, 'no_open': True}"/>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<button string="Confirm" name="action_done" type="object" class="btn-primary"/>
|
||||||
|
<button string="Discard" name="cancel_button" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user