[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:
@@ -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>
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
|
||||
Reference in New Issue
Block a user