[IMP] stock{_picking_batch}: print sn/lots from picking
Before this commit we could print the product labels of a picking from a picking, but we couldn't print only the lots/sns. This commit adds in this ability. Because we want to be flexible with this label printing (i.e. have the option to choose between printing zpl or pdf + 1 per unit or per lot/sn), we include it in the general "Print Labels" button with additional wizards. Note that we assume: 1. If there are no lot_ids in the picking => default to only product labels wizard (i.e. this also means if a picking that only creates New Lots/SN, lots/sns will not be printable until picking is validated.) 2. Lots/SN PDF labels will only be in the 4x12 label format 3. Batch pickings can also print lot/sn labels Part of task: 2494740 Related ENT PR: odoo/enterprise#23323 closes odoo/odoo#82389 Related: odoo/upgrade#3202 Signed-off-by: Arnold Moyaux <arm@odoo.com>
This commit is contained in:
@@ -65,6 +65,8 @@
|
||||
'wizard/stock_package_destination_views.xml',
|
||||
'wizard/stock_inventory_adjustment_name.xml',
|
||||
'wizard/stock_inventory_warning.xml',
|
||||
'wizard/stock_label_type.xml',
|
||||
'wizard/stock_lot_label_layout.xml',
|
||||
|
||||
'views/res_partner_views.xml',
|
||||
'views/product_strategy_views.xml',
|
||||
|
||||
@@ -1496,6 +1496,19 @@ class Picking(models.Model):
|
||||
'default_picking_quantity': 'picking'},
|
||||
}
|
||||
|
||||
def action_open_label_type(self):
|
||||
if self.user_has_groups('stock.group_production_lot') and self.move_line_ids.lot_id:
|
||||
view = self.env.ref('stock.picking_label_type_form')
|
||||
return {
|
||||
'name': _('Choose Type of Labels To Print'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'picking.label.type',
|
||||
'views': [(view.id, 'form')],
|
||||
'target': 'new',
|
||||
'context': {'default_picking_ids': self.ids},
|
||||
}
|
||||
return self.action_open_label_layout()
|
||||
|
||||
def _attach_sign(self):
|
||||
""" Render the delivery report in pdf and attach it to the picking in `self`. """
|
||||
self.ensure_one()
|
||||
|
||||
@@ -88,3 +88,5 @@ access_stock_inventory_warning,stock.inventory.warning,model_stock_inventory_war
|
||||
access_stock_inventory_adjustment_name,stock.inventory.adjustment.name,model_stock_inventory_adjustment_name,stock.group_stock_manager,1,1,1,0
|
||||
access_stock_request_count,stock.request.count,model_stock_request_count,stock.group_stock_manager,1,1,1,0
|
||||
access_stock_replenishment_info,stock.replenishment.info,model_stock_replenishment_info,stock.group_stock_manager,1,1,1,0
|
||||
access_stock_picking_label_type_user,picking.label.type.user,model_picking_label_type,stock.group_stock_user,1,1,1,0
|
||||
access_stock_lot_label_layout_user,lot.label.layout.user,model_lot_label_layout,stock.group_stock_user,1,1,1,0
|
||||
|
||||
|
@@ -253,7 +253,7 @@
|
||||
attrs="{'invisible': ['|', '|', ('id', '=', False), ('picking_type_code', '!=', 'outgoing'), ('state', '=', 'done')]}"
|
||||
full_name="partner_id" groups="stock.group_stock_sign_delivery"/>
|
||||
<button name="do_print_picking" string="Print" groups="stock.group_stock_user" type="object" attrs="{'invisible': [('state', '!=', 'assigned')]}" data-hotkey="o"/>
|
||||
<button string="Print Labels" type="object" name="action_open_label_layout"/>
|
||||
<button string="Print Labels" type="object" name="action_open_label_type"/>
|
||||
<button name="%(action_report_delivery)d" string="Print" attrs="{'invisible': [('state', '!=', 'done')]}" type="action" groups="base.group_user" data-hotkey="o"/>
|
||||
<button name="%(act_stock_return_picking)d" string="Return" attrs="{'invisible': [('state', '!=', 'done')]}" type="action" groups="base.group_user" data-hotkey="k"/>
|
||||
<button name="do_unreserve" string="Unreserve" groups="base.group_user" type="object" attrs="{'invisible': ['|', '|', '|', ('picking_type_code', '=', 'incoming'), ('immediate_transfer', '=', True), '&', ('state', '!=', 'assigned'), ('move_type', '!=', 'one'), '&', ('state', 'not in', ('assigned', 'confirmed')), ('move_type', '=', 'one')]}" data-hotkey="w"/>
|
||||
|
||||
@@ -8,6 +8,8 @@ from . import stock_change_product_qty
|
||||
from . import stock_inventory_conflict
|
||||
from . import stock_inventory_warning
|
||||
from . import stock_inventory_adjustment_name
|
||||
from . import stock_label_type
|
||||
from . import stock_lot_label_layout
|
||||
from . import stock_scheduler_compute
|
||||
from . import stock_immediate_transfer
|
||||
from . import stock_backorder_confirmation
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, fields, models
|
||||
|
||||
|
||||
class ProductLabelLayout(models.TransientModel):
|
||||
_name = 'picking.label.type'
|
||||
_description = 'Choose whether to print product or lot/sn labels'
|
||||
|
||||
picking_ids = fields.Many2many('stock.picking')
|
||||
label_type = fields.Selection([
|
||||
('products', 'Product Labels'),
|
||||
('lots', 'Lot/SN Labels')], string="Labels to print", required=True, default='products')
|
||||
|
||||
def process(self):
|
||||
if self.label_type == 'products':
|
||||
return self.picking_ids.action_open_label_layout()
|
||||
view = self.env.ref('stock.lot_label_layout_form_picking')
|
||||
return {
|
||||
'name': _('Choose Labels Layout'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'lot.label.layout',
|
||||
'views': [(view.id, 'form')],
|
||||
'target': 'new',
|
||||
'context': {'default_picking_ids': self.picking_ids.ids},
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="picking_label_type_form" model="ir.ui.view">
|
||||
<field name="name">picking.label.type.form</field>
|
||||
<field name="model">picking.label.type</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">25</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="label_type" widget="radio"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="process" string="Confirm" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ProductLabelLayout(models.TransientModel):
|
||||
_name = 'lot.label.layout'
|
||||
_description = 'Choose the sheet layout to print lot labels'
|
||||
|
||||
picking_ids = fields.Many2many('stock.picking')
|
||||
label_quantity = fields.Selection([
|
||||
('lots', 'One per lot/SN'),
|
||||
('units', 'One per unit')], string="Quantity to print", required=True, default='lots')
|
||||
print_format = fields.Selection([
|
||||
('4x12', '4 x 12'),
|
||||
('zpl', 'ZPL Labels')], string="Format", default='4x12', required=True)
|
||||
|
||||
def process(self):
|
||||
self.ensure_one()
|
||||
xml_id = 'stock.action_report_lot_label'
|
||||
if self.print_format == 'zpl':
|
||||
xml_id = 'stock.label_lot_template'
|
||||
if self.label_quantity == 'lots':
|
||||
docids = self.picking_ids.move_line_ids.lot_id.ids
|
||||
else:
|
||||
uom_categ_unit = self.env.ref('uom.product_uom_categ_unit')
|
||||
quantity_by_lot = defaultdict(int)
|
||||
for move_line in self.picking_ids.move_line_ids:
|
||||
if not move_line.lot_id:
|
||||
continue
|
||||
if move_line.product_uom_id.category_id == uom_categ_unit:
|
||||
quantity_by_lot[move_line.lot_id.id] += int(move_line.qty_done)
|
||||
else:
|
||||
quantity_by_lot[move_line.lot_id.id] += 1
|
||||
docids = []
|
||||
for lot_id, qty in quantity_by_lot.items():
|
||||
docids.append([lot_id] * qty)
|
||||
report_action = self.env.ref(xml_id).report_action(docids)
|
||||
report_action.update({'close_on_report_download': True})
|
||||
return report_action
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="lot_label_layout_form_picking" model="ir.ui.view">
|
||||
<field name="name">lot.label.layout.form</field>
|
||||
<field name="model">lot.label.layout</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">25</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="label_quantity" widget="radio"/>
|
||||
<field name="print_format" widget="radio"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="process" string="Confirm" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -280,6 +280,16 @@ class StockPickingBatch(models.Model):
|
||||
return action
|
||||
|
||||
def action_open_label_layout(self):
|
||||
if self.user_has_groups('stock.group_production_lot') and self.move_line_ids.lot_id:
|
||||
view = self.env.ref('stock.picking_label_type_form')
|
||||
return {
|
||||
'name': _('Choose Type of Labels To Print'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'picking.label.type',
|
||||
'views': [(view.id, 'form')],
|
||||
'target': 'new',
|
||||
'context': {'default_picking_ids': self.picking_ids.ids},
|
||||
}
|
||||
view = self.env.ref('stock.product_label_layout_form_picking')
|
||||
return {
|
||||
'name': _('Choose Labels Layout'),
|
||||
|
||||
Reference in New Issue
Block a user