[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
|
||||
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']:
|
||||
view_id = self.env.ref('delivery.choose_delivery_package_view_form').id
|
||||
return {
|
||||
@@ -123,10 +126,14 @@ class StockPicking(models.Model):
|
||||
'view_id': view_id,
|
||||
'views': [(view_id, 'form')],
|
||||
'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:
|
||||
return self._put_in_pack()
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def action_send_confirmation_email(self):
|
||||
|
||||
@@ -55,10 +55,6 @@ class ChooseDeliveryPackage(models.TransientModel):
|
||||
return {'warning': warning_mess}
|
||||
|
||||
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
|
||||
if 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/product_replenish_views.xml',
|
||||
'wizard/stock_track_confirmation_views.xml',
|
||||
'wizard/stock_package_destination_views.xml',
|
||||
|
||||
'views/res_partner_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)
|
||||
|
||||
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):
|
||||
package = False
|
||||
@@ -994,8 +1014,6 @@ class Picking(models.Model):
|
||||
if move_line_ids:
|
||||
move_lines_to_pack = self.env['stock.move.line']
|
||||
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:
|
||||
if float_compare(ml.qty_done, ml.product_uom_qty,
|
||||
precision_rounding=ml.product_uom_id.rounding) >= 0:
|
||||
@@ -1027,6 +1045,9 @@ class Picking(models.Model):
|
||||
return package
|
||||
|
||||
def put_in_pack(self):
|
||||
res = self.check_destinations()
|
||||
if res.get('type'):
|
||||
return res
|
||||
return self._put_in_pack()
|
||||
|
||||
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_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')
|
||||
|
||||
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 product_replenish
|
||||
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