[IMP] product,stock{_picking_batch}: product labels revamp

This commit removes the pdf and zpl reports for product templates and
variants. Instead, labels are printed via a new wizard allowing to
specify the label quantity, format and optional text.

There are a bunch of usual retail label sizes in pdf or zpl code

Task : 2501730

closes odoo/odoo#69690

Related: odoo/enterprise#20246
Related: odoo/upgrade#2542
Signed-off-by: William Henrotin <Whenrow@users.noreply.github.com>
This commit is contained in:
William Henrotin
2021-09-01 15:01:48 +00:00
parent 33476a4888
commit d9f45ba694
30 changed files with 579 additions and 229 deletions
+1
View File
@@ -4,3 +4,4 @@
from . import models
from . import report
from . import populate
from . import wizard
+5 -1
View File
@@ -30,6 +30,7 @@ Print product labels with barcode.
'data/product_data.xml',
'security/product_security.xml',
'security/ir.model.access.csv',
'wizard/product_label_layout_views.xml',
'views/res_config_settings_views.xml',
'views/product_attribute_views.xml',
'views/product_views.xml',
@@ -40,7 +41,7 @@ Print product labels with barcode.
'report/product_product_templates.xml',
'report/product_template_templates.xml',
'report/product_packaging.xml',
'report/product_pricelist_report_templates.xml'
'report/product_pricelist_report_templates.xml',
],
'demo': [
'data/product_demo.xml',
@@ -51,6 +52,9 @@ Print product labels with barcode.
'web.assets_backend': [
'product/static/src/js/**/*',
],
'web.report_assets_common': [
'product/static/src/scss/report_label_sheet.scss',
],
'web.qunit_suite_tests': [
'product/static/tests/**/*',
],
+5
View File
@@ -596,6 +596,11 @@ class ProductProduct(models.Model):
)
return super().view_header_get(view_id, view_type)
def action_open_label_layout(self):
action = self.env['ir.actions.act_window']._for_xml_id('product.action_open_label_layout')
action['context'] = {'default_product_ids': self.ids}
return action
def open_pricelist_rules(self):
self.ensure_one()
domain = ['|',
@@ -528,6 +528,11 @@ class ProductTemplate(models.Model):
'', args=[('id', 'in', list(searched_ids))],
operator='ilike', limit=limit, name_get_uid=name_get_uid)
def action_open_label_layout(self):
action = self.env['ir.actions.act_window']._for_xml_id('product.action_open_label_layout')
action['context'] = {'default_product_tmpl_ids': self.ids}
return action
def open_pricelist_rules(self):
self.ensure_one()
domain = ['|',
+1
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import product_label_report
from . import product_pricelist_report
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, models
from odoo.exceptions import UserError
def _prepare_data(env, data):
# change product ids by actual product object to get access to fields in xml template
# we needed to pass ids because reports only accepts native python types (int, float, strings, ...)
if data.get('active_model') == 'product.template':
quantity_by_product = {env['product.template'].with_context(display_default_code=False).browse(int(p)): q for p, q in data.get('quantity_by_product').items()}
elif data.get('active_model') == 'product.product':
quantity_by_product = {env['product.product'].with_context(display_default_code=False).browse(int(p)): q for p, q in data.get('quantity_by_product').items()}
else:
raise UserError(_('Product model not defined, Please contact your administrator.'))
layout_wizard = env['product.label.layout'].browse(data.get('layout_wizard'))
if not layout_wizard:
return {}
return {
'quantity': quantity_by_product,
'rows': layout_wizard.rows,
'columns': layout_wizard.columns,
'page_numbers': (sum(quantity_by_product.values()) - 1) // (layout_wizard.rows * layout_wizard.columns) + 1,
'price_included': data.get('price_included'),
'extra_html': layout_wizard.extra_html,
}
class ReportProductTemplateLabel(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel'
_description = 'Product Label Report'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, data)
class ReportProductTemplateLabelDymo(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel_dymo'
_description = 'Product Label Report'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, data)
@@ -1,75 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="report_simple_label">
<div style="width: 32%; display: inline-table; height:14rem;">
<table class="table table-bordered mb-0" style="border: 2px solid black;">
<tr>
<th class="table-active text-left" style="height: 4rem;">
<strong t-field="product.display_name"/>
</th>
</tr>
<tr>
<td style="height: 2rem">
<strong>Price:</strong>
<strong t-field="product.lst_price" t-options="{'widget': 'monetary', 'display_currency': product.company_id.currency_id}"/>
</td>
</tr>
<tr>
<td class="text-center align-middle" style="height: 6rem">
<template id="report_simple_label2x7">
<t t-set="barcode_size" t-value="'width:33mm;height:14mm'"/>
<t t-set="table_style" t-value="'width:97mm;height:37.1mm;' + table_style"/>
<td t-att-style="make_invisible and 'visibility:hidden;'" >
<div class="o_label_full" t-att-style="table_style">
<div class="o_label_name">
<strong t-field="product.display_name"/>
</div>
<div class="o_label_data">
<div class="text-center o_label_left_column">
<span class="text-nowrap" t-field="product.default_code"/>
<t t-if="product.barcode">
<div t-field="product.barcode" t-options="{'widget': 'barcode', 'symbology': 'auto', 'width': 600, 'height': 150, 'img_style': 'width:100%;height::4rem;'}"/>
<span t-field="product.barcode"/>
<div t-field="product.barcode" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
<span class="text-center" t-field="product.barcode"/>
</t>
<t t-else=""><span class="text-muted">No barcode available</span></t>
</td>
</tr>
</table>
</div>
<div class="text-right" style="line-height:normal">
<div class="o_label_extra_data">
<t t-out="extra_html"/>
</div>
<strong class="o_label_price" t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</div>
<div class="o_label_clear"></div>
</div>
</div>
</td>
</template>
<template id="report_simple_label4x7">
<t t-set="barcode_size" t-value="'width:33mm;height:8mm'"/>
<t t-set="table_style" t-value="'width:47mm;height:37.1mm;' + table_style"/>
<td t-att-style="make_invisible and 'visibility:hidden;'" >
<div class="o_label_full" t-att-style="table_style">
<div class="o_label_name">
<strong t-field="product.display_name"/>
</div>
<div class="text-right" style="padding-top:0;padding-bottom:0">
<strong class="o_label_price_medium" t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</div>
<div class= "text-center o_label_small_barcode">
<span class="text-nowrap" t-field="product.default_code"/>
<t t-if="product.barcode">
<div t-field="product.barcode" style="padding:0" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
<span class="text-center" t-field="product.barcode"/>
</t>
</div>
</div>
</td>
</template>
<template id="report_simple_label4x12">
<t t-set="barcode_size" t-value="'width:33mm;height:4mm'"/>
<t t-set="table_style" t-value="'width:43mm;height:19mm;' + table_style"/>
<td t-att-style="make_invisible and 'visibility:hidden;'" >
<div class="o_label_full o_label_small_text" t-att-style="table_style">
<div class="o_label_name">
<strong t-field="product.display_name"/>
</div>
<t t-if="price_included">
<div class="o_label_left_column">
<span class="text-nowrap" t-field="product.default_code"/>
</div>
<div class="o_label_price_medium text-right">
<strong t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</div>
</t>
<t t-else="">
<div class="o_label_left_column o_label_full_with">
<span class="text-nowrap" t-field="product.default_code"/>
</div>
</t>
<div class= "text-center o_label_small_barcode">
<t t-if="product.barcode">
<div t-field="product.barcode" style="padding:0" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
<span class="text-center" t-field="product.barcode"/>
</t>
</div>
</div>
</td>
</template>
<template id="report_simple_label_dymo">
<div class="o_label_sheet o_label_dymo" t-att-style="padding_page">
<div class="o_label_full" t-att-style="table_style">
<div class= "text-left o_label_small_barcode">
<t t-if="product.barcode">
<!-- `quiet=0` to remove the left and right margins on the barcode -->
<div t-field="product.barcode" style="padding:0" t-options="{'widget': 'barcode', 'quiet': 0, 'symbology': 'auto', 'img_style': barcode_size}"/>
</t>
</div>
<div class="o_label_name" style="line-height: 130%;height:2.7em;background-color: transparent;">
<span t-field="product.display_name"/>
</div>
<div class="o_label_left_column">
<small class="text-nowrap" t-field="product.default_code"/>
</div>
<div class="text-right" style="padding: 0 4px;">
<strong class="o_label_price_small" t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
<div class="o_label_extra_data">
<t t-out="extra_html"/>
</div>
</div>
</div>
</div>
</template>
<template id="report_productlabel">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="product">
<t t-call="product.report_simple_label">
<t t-set="product" t-value="product"/>
</t>
<t t-call="web.html_container">
<t t-if="columns and rows">
<t t-if="columns == 2 and rows == 7">
<t t-set="padding_page" t-value="'padding: 14mm 3mm'"/>
<t t-set="report_to_call" t-value="'product.report_simple_label2x7'"/>
</t>
</div>
<t t-if="columns == 4 and rows == 7">
<t t-set="padding_page" t-value="'padding: 14mm 3mm'"/>
<t t-set="report_to_call" t-value="'product.report_simple_label4x7'"/>
</t>
<t t-if="columns == 4 and rows == 12">
<t t-set="padding_page" t-value="'padding: 20mm 8mm'"/>
<t t-set="report_to_call" t-value="'product.report_simple_label4x12'"/>
</t>
<t t-foreach="range(page_numbers)" t-as="page">
<div class="o_label_sheet" t-att-style="padding_page">
<table class="my-0 table table-sm table-borderless">
<t t-foreach="range(rows)" t-as="row">
<tr>
<t t-foreach="range(columns)" t-as="column">
<t t-if="not current_quantity and quantity">
<t t-set="current_data" t-value="quantity.popitem()"/>
<t t-set="product" t-value="current_data[0]"/>
<t t-set="current_quantity" t-value="current_data[1]"/>
</t>
<t t-if="current_quantity">
<t t-set="make_invisible" t-value="False"/>
<t t-set="current_quantity" t-value="current_quantity - 1"/>
</t>
<t t-else="">
<t t-set="make_invisible" t-value="True"/>
</t>
<t t-set="table_style" t-value="'border: 1px solid %s;' % (product.env.user.company_id.primary_color or 'black')"/>
<t t-call="{{report_to_call}}"/>
</t>
</tr>
</t>
</table>
</div>
</t>
</t>
</t>
</template>
<template id="report_simple_barcode">
<div style="width: 32%; display: inline-table; height: 10rem;">
<table class="table table-bordered mb-0" style="border: 2px solid black;">
<tr>
<th class="table-active text-left" style="height: 4rem;">
<strong t-field="product.display_name"/>
</th>
</tr>
<tr>
<td class="text-center align-middle" style="height: 6rem;">
<t t-if="product.barcode">
<div t-field="product.barcode" t-options="{'widget': 'barcode', 'symbology': 'auto', 'width': 600, 'height': 150, 'img_style': 'width:100%;height:4rem'}"/>
<span t-field="product.barcode"/>
</t>
<t t-else=""><span class="text-muted">No barcode available</span></t>
</td>
</tr>
</table>
</div>
</template>
<template id="report_productbarcode">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="product">
<t t-call="product.report_simple_barcode">
<t t-set="product" t-value="product"/>
</t>
<template id="report_productlabel_dymo">
<t t-call="web.html_container">
<t t-set="barcode_size" t-value="'width:45.5mm;height:7.5mm'"/>
<t t-set="table_style" t-value="'width:100%;height:32mm;'"/>
<t t-set="padding_page" t-value="'padding: 2mm'"/>
<t t-foreach="quantity.items()" t-as="qty_by_product">
<t t-set="product" t-value="qty_by_product[0]"/>
<t t-foreach="range(qty_by_product[1])" t-as="qty">
<t t-call="product.report_simple_label_dymo"/>
</t>
</div>
</t>
</t>
</template>
</data>
+26 -33
View File
@@ -1,47 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="report_product_label" model="ir.actions.report">
<field name="name">Product Label (PDF)</field>
<field name="model">product.product</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_productlabel</field>
<field name="report_file">product.report_productlabel</field>
<field name="print_report_name">'Products Labels - %s' % (object.name)</field>
<field name="binding_model_id" ref="product.model_product_product"/>
<field name="binding_type">report</field>
<record id="paperformat_label_sheet" model="report.paperformat">
<field name="name">A4 Label Sheet</field>
<field name="default" eval="True" />
<field name="format">A4</field>
<field name="page_height">0</field>
<field name="page_width">0</field>
<field name="orientation">Portrait</field>
<field name="margin_top">0</field>
<field name="margin_bottom">0</field>
<field name="margin_left">0</field>
<field name="margin_right">0</field>
<field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field>
</record>
<record id="report_product_template_label" model="ir.actions.report">
<field name="name">Product Label (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatelabel</field>
<field name="report_file">product.report_producttemplatelabel</field>
<field name="paperformat_id" ref="product.paperformat_label_sheet"/>
<field name="print_report_name">'Products Labels - %s' % (object.name)</field>
<field name="binding_model_id" ref="product.model_product_template"/>
<field name="binding_type">report</field>
</record>
<record id="report_product_product_barcode" model="ir.actions.report">
<field name="name">Product Barcode (PDF)</field>
<field name="model">product.product</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_productbarcode</field>
<field name="report_file">product.report_productbarcode</field>
<field name="print_report_name">'Products barcode - %s' % (object.name)</field>
<field name="binding_model_id" ref="product.model_product_product"/>
<field name="binding_type">report</field>
</record>
<record id="report_product_template_barcode" model="ir.actions.report">
<field name="name">Product Barcode (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatebarcode</field>
<field name="report_file">product.report_producttemplatebarcode</field>
<field name="print_report_name">'Products barcode - %s' % (object.name)</field>
<field name="binding_model_id" ref="product.model_product_template"/>
<field name="binding_model_id" eval="False"/>
<field name="binding_type">report</field>
</record>
@@ -78,5 +60,16 @@
<field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field>
</record>
<record id="report_product_template_label_dymo" model="ir.actions.report">
<field name="name">Product Label (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatelabel_dymo</field>
<field name="report_file">product.report_producttemplatelabel_dymo</field>
<field name="paperformat_id" ref="product.paperformat_label_sheet_dymo"/>
<field name="print_report_name">'Products Labels - %s' % (object.name)</field>
<field name="binding_type">report</field>
</record>
</data>
</odoo>
@@ -3,27 +3,21 @@
<template id="report_producttemplatelabel">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="template">
<t t-foreach="template.product_variant_ids" t-as="product">
<t t-call="product.report_simple_label">
<t t-set="product" t-value="product"/>
</t>
</t>
<t t-call="product.report_productlabel">
<t t-set="products" t-value="products"/>
</t>
</div>
</t>
</template>
<template id="report_producttemplatebarcode">
<template id="report_producttemplatelabel_dymo">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="template">
<t t-foreach="template.product_variant_ids" t-as="product">
<t t-call="product.report_simple_barcode">
<t t-set="product" t-value="product"/>
</t>
</t>
<t t-call="product.report_productlabel_dymo">
<t t-set="products" t-value="products"/>
</t>
</div>
</t>
</template>
</odoo>
@@ -26,3 +26,4 @@ access_product_attribute_custom_value_manager,product.attribute.custom value man
access_product_product_attribute_manager,product.template.attribute value manager,model_product_template_attribute_value,base.group_system,1,1,1,1
access_product_template_attribute_exclusion_manager,product.template.attribute exclusion manager,model_product_template_attribute_exclusion,base.group_system,1,1,1,1
access_product_template_attribute_line_manager,product.template.attribute line manager,model_product_template_attribute_line,base.group_system,1,1,1,1
access_product_label_layout_user,product.label.layout.user,model_product_label_layout,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
26 access_product_product_attribute_manager product.template.attribute value manager model_product_template_attribute_value base.group_system 1 1 1 1
27 access_product_template_attribute_exclusion_manager product.template.attribute exclusion manager model_product_template_attribute_exclusion base.group_system 1 1 1 1
28 access_product_template_attribute_line_manager product.template.attribute line manager model_product_template_attribute_line base.group_system 1 1 1 1
29 access_product_label_layout_user product.label.layout.user model_product_label_layout base.group_system 1 1 1 1
@@ -0,0 +1,69 @@
.o_label_sheet {
margin-left: -4mm;
margin-right: -4mm;
overflow: hidden;
width: 210mm;
height: 297mm;
page-break-before: always;
&.o_label_dymo {
font-size:90%;
width: 57mm;
height: 32mm;
}
div {
padding: 2px 4px;
}
div.o_label_small_text {
font-size: 60%;
line-height: 130%;
}
div.o_label_name {
background-color: ghostwhite;
height: 3em;
overflow: hidden;
}
div.o_label_full {
overflow: hidden;
padding: 0;
margin: auto;
}
div.o_label_left_column {
float: left;
font-size: .6em;
overflow:hidden;
width: 40%;
&.o_label_full_with {
width: 100%
}
}
div.o_label_right_column {
float: right;
}
div.o_label_small_barcode {
font-size: .6em;
padding: 0 4px;
line-height: normal;
}
strong.o_label_price {
font-size: 2em;
}
strong.o_label_price_medium {
font-size: 1.3em;
line-height: normal;
padding: 0;
padding-right: 2mm;
}
strong.o_label_price_small {
font-size: 0.9em;
padding: 0 4px;
padding-right: 2mm;
}
div.o_label_extra_data {
overflow: hidden;
height: 2.5em;
padding: 0;
}
div.o_label_clear {
clear: both;
}
}
@@ -5,6 +5,9 @@
<field name="model">product.template</field>
<field name="arch" type="xml">
<tree string="Product" multi_edit="1" sample="1">
<header>
<button string="Print Labels" type="object" name="action_open_label_layout"/>
</header>
<field name="product_variant_count" invisible="1"/>
<field name="sale_ok" invisible="1"/>
<field name="currency_id" invisible="1"/>
+15
View File
@@ -13,6 +13,7 @@
name="%(product_attribute_value_action)d"
attrs="{'invisible': ['|', ('attribute_line_ids', '&lt;=', 0), ('is_product_variant', '=', True)]}"
groups="product.group_product_variant"/>
<button string="Print Labels" type="object" name="action_open_label_layout"/>
</header>
<sheet>
<field name='product_variant_count' invisible='1'/>
@@ -221,6 +222,9 @@
<field name="mode">primary</field>
<field name="arch" type="xml">
<form string="Variant Information" duplicate="false">
<header>
<button string="Print Labels" type="object" name="action_open_label_layout"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box"/>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
@@ -305,6 +309,9 @@
<field eval="7" name="priority"/>
<field name="arch" type="xml">
<tree string="Product Variants" multi_edit="1" duplicate="false" sample="1">
<header>
<button string="Print Labels" type="object" name="action_open_label_layout"/>
</header>
<field name="priority" widget="priority" nolabel="1" readonly="1"/>
<field name="default_code" optional="show" readonly="1"/>
<field name="barcode" optional="hide" readonly="1"/>
@@ -726,5 +733,13 @@ action = {
</field>
</record>
<record id="action_open_label_layout" model="ir.actions.act_window">
<field name="name">Choose Labels Layout</field>
<field name="res_model">product.label.layout</field>
<field name="view_ids"
eval="[(5, 0, 0),
(0, 0, {'view_mode': 'form', 'view_id': ref('product_label_layout_form')})]" />
<field name="target">new</field>
</record>
</data>
</odoo>
+3
View File
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import product_label_layout
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class ProductLabelLayout(models.TransientModel):
_name = 'product.label.layout'
_description = 'Choose the sheet layout to print the labels'
print_format = fields.Selection([
('dymo', 'Dymo'),
('2x7xprice', '2 x 7 with price'),
('4x7xprice', '4 x 7 with price'),
('4x12', '4 x 12'),
('4x12xprice', '4 x 12 with price')], string="Format", default='2x7xprice', required=True)
custom_quantity = fields.Integer('Quantity', default=1, required=True)
product_ids = fields.Many2many('product.product')
product_tmpl_ids = fields.Many2many('product.template')
extra_html = fields.Html('Extra Content', default='')
rows = fields.Integer(compute='_compute_dimensions')
columns = fields.Integer(compute='_compute_dimensions')
@api.depends('print_format')
def _compute_dimensions(self):
for wizard in self:
if 'x' in wizard.print_format:
columns, rows = wizard.print_format.split('x')[:2]
wizard.columns = int(columns)
wizard.rows = int(rows)
else:
wizard.columns, wizard.rows = 1, 1
def _prepare_report_data(self):
if self.custom_quantity <= 0:
raise UserError(_('You need to set a positive quantity.'))
# Get layout grid
if self.print_format == 'dymo':
xml_id = 'product.report_product_template_label_dymo'
elif 'x' in self.print_format:
xml_id = 'product.report_product_template_label'
else:
xml_id = ''
active_model = ''
if self.product_tmpl_ids:
products = self.product_tmpl_ids.ids
active_model = 'product.template'
elif self.product_ids:
products = self.product_ids.ids
active_model = 'product.product'
# Build data to pass to the report
data = {
'picking_quantity': self.picking_quantity,
'active_model': active_model,
'quantity_by_product': {p: self.custom_quantity for p in products},
'layout_wizard': self.id,
'price_included': 'xprice' in self.print_format,
}
return xml_id, data
def process(self):
self.ensure_one()
xml_id, data = self._prepare_report_data()
if not xml_id:
raise UserError(_('Unable to find report template for %s format', self.print_format))
return self.env.ref(xml_id).report_action(None, data=data)
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_label_layout_form" model="ir.ui.view">
<field name="name">product.label.layout.form</field>
<field name="model">product.label.layout</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form>
<group>
<group>
<field name="product_ids" invisible="1"/>
<field name="product_tmpl_ids" invisible="1"/>
<field name="custom_quantity"/>
<field name="print_format" widget="radio"/>
</group>
<group>
<field name="extra_html" widget="html" attrs="{'invisible': [('print_format', 'not in', ('dymo', '2x7xprice'))]}"/>
</group>
</group>
<footer>
<button name="process" string="Confirm" type="object" class="btn-primary"/>
<button string="Discard" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>
+1
View File
@@ -59,6 +59,7 @@
'wizard/stock_rules_report_views.xml',
'wizard/stock_warn_insufficient_qty_views.xml',
'wizard/product_replenish_views.xml',
'wizard/product_label_layout_views.xml',
'wizard/stock_track_confirmation_views.xml',
'wizard/stock_orderpoint_snooze_views.xml',
'wizard/stock_package_destination_views.xml',
+14
View File
@@ -1431,6 +1431,20 @@ class Picking(models.Model):
def action_view_reception_report(self):
return self.env["ir.actions.actions"]._for_xml_id("stock.stock_reception_action")
def action_open_label_layout(self):
view = self.env.ref('stock.product_label_layout_form_picking')
return {
'name': _('Choose Labels Layout'),
'type': 'ir.actions.act_window',
'res_model': 'product.label.layout',
'views': [(view.id, 'form')],
'target': 'new',
'context': {
'default_product_ids': self.move_lines.product_id.ids,
'default_move_line_ids': self.move_line_ids.ids,
'default_picking_quantity': 'picking'},
}
def _attach_sign(self):
""" Render the delivery report in pdf and attach it to the picking in `self`. """
self.ensure_one()
+1
View File
@@ -6,3 +6,4 @@ from . import report_stock_quantity
from . import report_stock_reception
from . import report_stock_rule
from . import stock_traceability
from . import product_label_report
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, models
from odoo.exceptions import UserError
class ReportProductLabel(models.AbstractModel):
_name = 'report.stock.label_product_product_view'
_description = 'Product Label Report'
def _get_report_values(self, docids, data):
if data.get('active_model') == 'product.template':
data['quantity'] = {self.env['product.template'].browse(int(p)): q for p, q in data.get('quantity_by_product').items()}
elif data.get('active_model') == 'product.product':
data['quantity'] = {self.env['product.product'].browse(int(p)): q for p, q in data.get('quantity_by_product').items()}
else:
raise UserError(_('Product model not defined, Please contact your administrator.'))
return data
+21 -65
View File
@@ -1,82 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="label_product_template_view">
<t t-foreach="docs" t-as="product">
<t t-translation="off">
^XA
^FO100,50
^A0N,44,33^FD<t t-esc="product.display_name"/>^FS
^FO100,100
<t t-if="product.currency_id.position == 'after'">
^CI28
^A0N,44,33^FH^FDPrice: <t t-esc="product.list_price" t-options='{"widget": "float", "precision": 2}'/><t t-esc="product.currency_id.symbol"/>^FS
</t>
<t t-if="product.currency_id.position == 'before'">
^CI28
^A0N,44,33^FH^FDPrice: <t t-esc="product.currency_id.symbol"/><t t-esc="product.list_price" t-options='{"widget": "float", "precision": 2}'/>^FS
</t>
<t t-if="product.barcode">
^FO100,150^BY3
^BCN,100,Y,N,N
^FD<t t-esc="product.barcode"/>^FS
</t>
^XZ
</t>
</t>
</template>
<template id="label_product_product_view">
<t t-foreach="docs" t-as="product">
<t t-translation="off">
<t t-foreach="quantity.items()" t-as="qty_by_product">
<t t-set="product" t-value="qty_by_product[0]"/>
<t t-foreach="range(qty_by_product[1])" t-as="qty">
<t t-translation="off">
^XA
^FO100,50
^A0N,44,33^FD<t t-esc="product.display_name"/>^FS
^FO100,100
<t t-if="product.currency_id.position == 'after'">
^FT100,80^A0N,40,30^FD<t t-esc="product.display_name"/>^FS
<t t-if="product.default_code and len(product.default_code) &gt; 15">
^FT100,115^A0N,30,24^FD<t t-esc="product.default_code[:15]"/>^FS
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code[15:30]"/>^FS
</t>
<t t-else="">
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code"/>^FS
</t>
<t t-if="price_included">
^FO600,100,1
^CI28
^A0N,44,33^FH^FDPrice: <t t-esc="product.lst_price" t-options='{"widget": "float", "precision": 2}'/><t t-esc="product.currency_id.symbol"/>^FS
<t t-if="product.currency_id.position == 'after'">
^A0N,66,48^FH^FD<t t-esc="product.list_price" t-options='{"widget": "float", "precision": 2}'/><t t-esc="product.currency_id.symbol"/>^FS
</t>
<t t-if="product.currency_id.position == 'before'">
^CI28
^A0N,44,33^FH^FDPrice: <t t-esc="product.currency_id.symbol"/><t t-esc="product.lst_price" t-options='{"widget": "float", "precision": 2}'/>^FS
^A0N,66,48^FH^FD<t t-esc="product.currency_id.symbol"/><t t-esc="product.list_price" t-options='{"widget": "float", "precision": 2}'/>^FS
</t>
</t>
<t t-if="product.barcode">
^FO100,150^BY3
^FO100,160^BY3
^BCN,100,Y,N,N
^FD<t t-esc="product.barcode"/>^FS
</t>
^XZ
</t>
</t>
</template>
<template id="label_barcode_product_template_view">
<t t-foreach="docs" t-as="product">
<t t-translation="off">
^XA
^FO100,50
^A0N,44,33^FD<t t-esc="product.display_name"/>^FS
<t t-if="product.barcode ">
^FO100,100^BY3
^BCN,100,Y,N,N
^FD<t t-esc="product.barcode"/>^FS
</t>
^XZ
</t>
</t>
</template>
<template id="label_barcode_product_product_view">
<t t-foreach="docs" t-as="product">
<t t-translation="off">
^XA
^FO100,50
^A0N,44,33^FD<t t-esc="product.display_name"/>^FS
<t t-if="product.barcode">
^FO100,100^BY3
^BCN,100,Y,N,N
^FD<t t-esc="product.barcode"/>^FS
</t>
^XZ
</t>
</t>
</t>
</t>
</template>
<template id="label_lot_template_view">
+1 -46
View File
@@ -86,40 +86,13 @@
<field name="report_name">stock.report_stock_rule</field>
<field name="report_file">stock.report_stock_rule</field>
</record>
<record id="label_product_template" model="ir.actions.report">
<field name="name">Product Label (ZPL)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-text</field>
<field name="report_name">stock.label_product_template_view</field>
<field name="report_file">stock.label_product_template_view</field>
<field name="binding_model_id" ref="product.model_product_template"/>
<field name="binding_type">report</field>
</record>
<record id="label_product_product" model="ir.actions.report">
<field name="name">Product Label (ZPL)</field>
<field name="model">product.product</field>
<field name="report_type">qweb-text</field>
<field name="report_name">stock.label_product_product_view</field>
<field name="report_file">stock.label_product_product_view</field>
<field name="binding_model_id" ref="product.model_product_product"/>
<field name="binding_type">report</field>
</record>
<record id="label_barcode_product_template" model="ir.actions.report">
<field name="name">Product Barcode (ZPL)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-text</field>
<field name="report_name">stock.label_barcode_product_template_view</field>
<field name="report_file">stock.label_barcode_product_template_view</field>
<field name="binding_model_id" ref="product.model_product_template"/>
<field name="binding_type">report</field>
</record>
<record id="label_barcode_product_product" model="ir.actions.report">
<field name="name">Product Barcode (ZPL)</field>
<field name="model">product.product</field>
<field name="report_type">qweb-text</field>
<field name="report_name">stock.label_barcode_product_product_view</field>
<field name="report_file">stock.label_barcode_product_product_view</field>
<field name="binding_model_id" ref="product.model_product_product"/>
<field name="binding_model_id" eval="False"/>
<field name="binding_type">report</field>
</record>
<record id="label_lot_template" model="ir.actions.report">
@@ -131,24 +104,6 @@
<field name="binding_model_id" ref="model_stock_production_lot"/>
<field name="binding_type">report</field>
</record>
<record id="action_label_transfer_template_zpl" model="ir.actions.report">
<field name="name">Barcodes (ZPL)</field>
<field name="model">stock.picking</field>
<field name="report_type">qweb-text</field>
<field name="report_name">stock.label_transfer_template_view_zpl</field>
<field name="report_file">stock.label_transfer_template_view_zpl</field>
<field name="binding_model_id" ref="model_stock_picking"/>
<field name="binding_type">report</field>
</record>
<record id="action_label_transfer_template_pdf" model="ir.actions.report">
<field name="name">Barcodes (PDF)</field>
<field name="model">stock.picking</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">stock.label_transfer_template_view_pdf</field>
<field name="report_file">stock.label_transfer_template_view_pdf</field>
<field name="binding_model_id" ref="model_stock_picking"/>
<field name="binding_type">report</field>
</record>
<record id="label_package_template" model="ir.actions.report">
<field name="name">Package Barcode (ZPL)</field>
<field name="model">stock.quant.package</field>
+11 -13
View File
@@ -209,19 +209,17 @@
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_variant_easy_edit_view"/>
<field name="arch" type="xml">
<sheet position="before">
<header>
<button string="Update Quantity" type="object"
groups="stock.group_stock_manager"
name="action_update_quantity_on_hand"
attrs="{'invisible': [('type', '!=', 'product')]}"/>
<button string="Replenish" type="action"
name="%(action_product_replenish)d"
context="{'default_product_id': id}"
groups="stock.group_stock_user"
attrs="{'invisible': [('type', '!=', 'product')]}"/>
</header>
</sheet>
<header position="inside">
<button string="Update Quantity" type="object"
groups="stock.group_stock_manager"
name="action_update_quantity_on_hand"
attrs="{'invisible': [('type', '!=', 'product')]}"/>
<button string="Replenish" type="action"
name="%(action_product_replenish)d"
context="{'default_product_id': id}"
groups="stock.group_stock_user"
attrs="{'invisible': [('type', '!=', 'product')]}"/>
</header>
<div name="button_box" position="inside">
<button string="Putaway Rules" type="object"
name="action_view_related_putaway_rules"
@@ -251,6 +251,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 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), '&amp;', ('state', '!=', 'assigned'), ('move_type', '!=', 'one'), '&amp;', ('state', 'not in', ('assigned', 'confirmed')), ('move_type', '=', 'one')]}" data-hotkey="w"/>
+1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import product_label_layout
from . import stock_assign_serial_numbers
from . import stock_picking_return
from . import stock_change_product_qty
@@ -0,0 +1,34 @@
# -*- 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):
_inherit = 'product.label.layout'
move_line_ids = fields.Many2many('stock.move.line')
picking_quantity = fields.Selection([
('picking', 'Transfer Quantities'),
('custom', 'Custom')], string="Quantity to print", default='custom')
print_format = fields.Selection(selection_add=[
('zpl', 'ZPL Labels'),
('zplxprice', 'ZPL Labels with price')
], ondelete={'zpl': 'set default', 'zplxprice': 'set default'})
def _prepare_report_data(self):
xml_id, data = super()._prepare_report_data()
if 'zpl' in self.print_format:
xml_id = 'stock.label_product_product'
if self.picking_quantity == 'picking' and self.move_line_ids:
qties = defaultdict(int)
uom_unit = self.env.ref('uom.product_uom_categ_unit', raise_if_not_found=False)
for line in self.move_line_ids:
if line.product_uom_id.category_id == uom_unit:
qties[line.product_id.id] += line.qty_done
# Pass only products with some quantity done to the report
data['quantity_by_product'] = {p: int(q) for p, q in qties.items() if q}
return xml_id, data
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_label_layout_form_picking" model="ir.ui.view">
<field name="name">product.label.layout.form</field>
<field name="model">product.label.layout</field>
<field name="mode">primary</field>
<field name="priority">25</field>
<field name="inherit_id" ref="product.product_label_layout_form"/>
<field name="arch" type="xml">
<xpath expr='//field[@name="custom_quantity"]' position="before">
<field name="picking_quantity"/>
</xpath>
<xpath expr='//field[@name="custom_quantity"]' position="attributes">
<attribute name="attrs">
{'invisible': [('picking_quantity', '=', 'picking')]}
</attribute>
</xpath>
</field>
</record>
</odoo>
@@ -233,6 +233,22 @@ class StockPickingBatch(models.Model):
action['context'] = {'default_picking_ids': self.picking_ids.ids}
return action
def action_open_label_layout(self):
view = self.env.ref('product.product_label_layout_form')
return {
'name': _('Choose Labels Layout'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'product.label.layout',
'views': [(view.id, 'form')],
'view_id': view.id,
'target': 'new',
'context': {
'default_product_ids': self.move_lines.product_id.ids,
'default_move_line_ids': self.move_line_ids.ids,
'default_picking_quantity': 'picking'},
}
# -------------------------------------------------------------------------
# Miscellaneous
# -------------------------------------------------------------------------
@@ -102,6 +102,7 @@
('state', '!=', 'draft'),
('show_check_availability', '=', False)]}"/>
<button name="action_print" states="in_progress,done" string="Print" type="object"/>
<button string="Print Labels" type="object" name="action_open_label_layout"/>
<button name="action_cancel" string="Cancel" type="object" states="in_progress"/>
<field name="state" widget="statusbar" statusbar_visible="draft,in_progress,done"/>
</header>
@@ -451,6 +451,12 @@ class MonetaryConverter(models.AbstractModel):
else:
post = '\N{NO-BREAK SPACE}{symbol}'.format(symbol=display_currency.symbol or '')
if options.get('label_price'):
sep = lang.decimal_point
integer_part, decimal_part = formatted_amount.split(sep)
integer_part += sep
return M('{pre}<span class="oe_currency_value">{0}</span><span class="oe_currency_value" style="font-size:0.5em">{1}</span>{post}').format(integer_part, decimal_part, pre=pre, post=post)
return M('{pre}<span class="oe_currency_value">{0}</span>{post}').format(formatted_amount, pre=pre, post=post)
@api.model