[IMP] product: split out label templates

In previous versions (v14 and earlier) there was only 1 PDF product
label that was easily customizable by users. Then we became super
fancy and created 5 PDF product label formats that are called via 2
different report actions.

Unfortunately we were too fancy and the design of the new reports was
incompatible with user customization. To remedy this, this commit:

- makes it so you can actually open the report label in studio (i.e.
  `_prepare_data` in `product_label_report.py` adjusted to handle studio
  case + hardcode some values that used to be required from the
  `product.label.layout` wizard)
- splits out the non-dymo labels into separate report actions so they
  can be individually customized more easily (i.e. individually loaded
  from studio)
- splits out the show 4x12 price/no price templates so they can be
  separately modified without unintentionally affecting the other

Note that we expect users to be OK with:
-  the barcode auto-magic (i.e. clicking on the barcode within the label
   will NOT be product.barcode as some of them may expect) since we still
   want to cover the use case of SN/lots printing instead of the product
   barcode for pickings
- the user will take responsibility if they overlap/delete the
  `extra_html` layout wizard value from the label (since it's not
  visible in studio without lots of clicking)

Task: 3046178 - split product label templates
Upgrade PR: odoo/upgrade#5298

Part-of: odoo/odoo#126791
This commit is contained in:
Tiffany Chang (tic)
2023-10-24 12:38:05 +00:00
parent cee8608e69
commit 1f438ca0e1
5 changed files with 174 additions and 44 deletions
+47 -12
View File
@@ -6,16 +6,31 @@ from collections import defaultdict
from odoo import _, models
from odoo.exceptions import UserError
def _prepare_data(env, data):
def _prepare_data(env, docids, 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':
Product = env['product.template'].with_context(display_default_code=False)
elif data.get('active_model') == 'product.product':
Product = env['product.product'].with_context(display_default_code=False)
elif data.get("studio") and docids:
# special case: users trying to customize labels
products = env['product.template'].with_context(display_default_code=False).browse(docids)
quantity_by_product = defaultdict(list)
for product in products:
quantity_by_product[product].append((product.barcode, 1))
return {
'quantity': quantity_by_product,
'page_numbers': 1,
}
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 {}
total = 0
qty_by_product_in = data.get('quantity_by_product')
# search for products all at once, ordered by name desc since popitem() used in xml to print the labels
@@ -32,30 +47,50 @@ def _prepare_data(env, data):
quantity_by_product[Product.browse(int(product))] += (barcodes_qtys)
total += sum(qty for _, qty in barcodes_qtys)
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': (total - 1) // (layout_wizard.rows * layout_wizard.columns) + 1,
'price_included': data.get('price_included'),
'extra_html': layout_wizard.extra_html,
'pricelist': layout_wizard.pricelist_id,
}
class ReportProductTemplateLabel(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel'
_description = 'Product Label Report'
class ReportProductTemplateLabel2x7(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel2x7'
_description = 'Product Label Report 2x7'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, data)
return _prepare_data(self.env, docids, data)
class ReportProductTemplateLabel4x7(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel4x7'
_description = 'Product Label Report 4x7'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, docids, data)
class ReportProductTemplateLabel4x12(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel4x12'
_description = 'Product Label Report 4x12'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, docids, data)
class ReportProductTemplateLabel4x12NoPrice(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel4x12noprice'
_description = 'Product Label Report 4x12 No Price'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, docids, 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)
return _prepare_data(self.env, docids, data)
@@ -61,20 +61,34 @@
<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-end">
<strong t-out="pricelist._get_product_price(product, 1, pricelist.currency_id or product.currency_id)"
t-options="{'widget': 'monetary', 'display_currency': pricelist.currency_id or product.currency_id, '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="o_label_left_column">
<span class="text-nowrap" t-field="product.default_code"/>
</div>
<div class="o_label_price_medium text-end">
<strong t-out="pricelist._get_product_price(product, 1, pricelist.currency_id or product.currency_id)"
t-options="{'widget': 'monetary', 'display_currency': pricelist.currency_id or product.currency_id, 'label_price': True}"/>
</div>
<div class= "text-center o_label_small_barcode">
<t t-if="barcode">
<div t-out="barcode" style="padding:0" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
<span class="text-center" t-out="barcode"/>
</t>
</div>
</div>
</td>
</template>
<template id="report_simple_label4x12_no_price">
<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>
<div class="o_label_left_column o_label_full_with">
<span class="text-nowrap" t-field="product.default_code"/>
</div>
<div class= "text-center o_label_small_barcode">
<t t-if="barcode">
<div t-out="barcode" style="padding:0" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
@@ -120,15 +134,15 @@
<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 t-set="report_to_call" t-value="'2x7'"/>
</t>
<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-set="report_to_call" t-value="'4x7'"/>
</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-set="report_to_call" t-value="'4x12'"/>
</t>
<t t-foreach="range(page_numbers)" t-as="page">
<div class="o_label_sheet" t-att-style="padding_page">
@@ -156,7 +170,21 @@
<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}}"/>
<!-- IMPORTANT: explictly call templates in place of {{template}} otherwise studio won't load report correctly -->
<t t-if="report_to_call == '2x7'">
<t t-call="product.report_simple_label2x7"/>
</t>
<t t-elif="report_to_call == '4x7'">
<t t-call="product.report_simple_label4x7"/>
</t>
<t t-elif="report_to_call == '4x12'">
<t t-if="price_included">
<t t-call="product.report_simple_label4x12"/>
</t>
<t t-else="">
<t t-call="product.report_simple_label4x12_no_price"/>
</t>
</t>
</t>
</tr>
</t>
+37 -4
View File
@@ -15,12 +15,45 @@
<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>
<record id="report_product_template_label_2x7" model="ir.actions.report">
<field name="name">Product Label 2x7 (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="report_name">product.report_producttemplatelabel2x7</field>
<field name="report_file">product.report_producttemplatelabel2x7</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" eval="False"/>
<field name="binding_type">report</field>
</record>
<record id="report_product_template_label_4x7" model="ir.actions.report">
<field name="name">Product Label 4x7 (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatelabel4x7</field>
<field name="report_file">product.report_producttemplatelabel4x7</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" eval="False"/>
<field name="binding_type">report</field>
</record>
<record id="report_product_template_label_4x12" model="ir.actions.report">
<field name="name">Product Label 4x12 (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatelabel4x12</field>
<field name="report_file">product.report_producttemplatelabel4x12</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" eval="False"/>
<field name="binding_type">report</field>
</record>
<record id="report_product_template_label_4x12_noprice" model="ir.actions.report">
<field name="name">Product Label 4x12 No Price (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatelabel4x12noprice</field>
<field name="report_file">product.report_producttemplatelabel4x12noprice</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" eval="False"/>
@@ -1,11 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_producttemplatelabel">
<!-- IMPORTANT: explictly include col/row otherwise studio won't load report correctly -->
<template id="report_producttemplatelabel2x7">
<t t-call="web.basic_layout">
<div class="page">
<t t-call="product.report_productlabel">
<t t-set="products" t-value="products"/>
</t>
<t t-set="columns" t-value="2"/>
<t t-set="rows" t-value="7"/>
<t t-call="product.report_productlabel"/>
</div>
</t>
</template>
<template id="report_producttemplatelabel4x7">
<t t-call="web.basic_layout">
<div class="page">
<t t-set="columns" t-value="4"/>
<t t-set="rows" t-value="7"/>
<t t-call="product.report_productlabel"/>
</div>
</t>
</template>
<template id="report_producttemplatelabel4x12">
<t t-call="web.basic_layout">
<div class="page">
<t t-set="columns" t-value="4"/>
<t t-set="rows" t-value="12"/>
<t t-set="price_included" t-value="True"/>
<t t-call="product.report_productlabel"/>
</div>
</t>
</template>
<template id="report_producttemplatelabel4x12noprice">
<t t-call="web.basic_layout">
<div class="page">
<t t-set="columns" t-value="4"/>
<t t-set="rows" t-value="12"/>
<t t-set="price_included" t-value="False"/>
<t t-call="product.report_productlabel"/>
</div>
</t>
</template>
@@ -13,9 +47,7 @@
<template id="report_producttemplatelabel_dymo">
<t t-call="web.basic_layout">
<div class="page">
<t t-call="product.report_productlabel_dymo">
<t t-set="products" t-value="products"/>
</t>
<t t-call="product.report_productlabel_dymo"/>
</div>
</t>
</template>
@@ -29,8 +29,8 @@ class ProductLabelLayout(models.TransientModel):
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)
wizard.columns = columns.isdigit() and int(columns) or 1
wizard.rows = rows.isdigit() and int(rows) or 1
else:
wizard.columns, wizard.rows = 1, 1
@@ -42,7 +42,9 @@ class ProductLabelLayout(models.TransientModel):
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'
xml_id = 'product.report_product_template_label_%sx%s' % (self.columns, self.rows)
if 'xprice' not in self.print_format:
xml_id += '_noprice'
else:
xml_id = ''