Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
Odoo Proprietary License v1.0
This software and associated files (the “Software”) may only be used (executed,
modified, executed after modifications) if you have purchased a valid license
from the authors, typically via Odoo Apps, or if you have received a written
agreement from the authors of the Software (see the COPYRIGHT file).
You may develop Odoo modules that use the Software as a library (typically by
depending on it, importing it and using its resources), but without copying any
source code or material from the Software. You may distribute those modules
under the license of your choice, provided that this license is compatible with
the terms of the Odoo Proprietary License (For example: LGPL, MIT, or
proprietary licenses similar to this one).
It is forbidden to publish, distribute, sublicense, or sell copies of the
Software or modified copies of the Software.
The above copyright notice and this permission notice must be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,55 @@
=====================
Product Label Builder
=====================
.. |badge1| image:: https://img.shields.io/badge/maturity-Production-green.png
:alt: Production
.. |badge2| image:: https://img.shields.io/badge/licence-OPL--1-purple.png
:target: https://www.odoo.com/documentation/18.0/legal/licenses.html
:alt: License: OPL-1
.. |badge3| image:: https://img.shields.io/badge/demo-Try%20me-FEA621.png
:target: https://garazd.biz/r/W6v
:alt: Try me on a Demo Instance
.. |badge4| image:: https://img.shields.io/badge/link-Garazd%20Apps-154577.png
:target: https://garazd.biz/shop/odoo-product-label-builder-112
:alt: Get the app on Garazd Apps store
|badge1| |badge2| |badge3| |badge4|
Product Barcode Label Building and Printing | Professional Tool to Print Labels | Barcode Product Label Builder | Product Label Designer | Sticker Label Maker | Dymo Label Maker | Barcode Label Generator | Direct Print
**Table of contents**
.. contents::
:local:
Credits
=======
Authors
~~~~~~~
* Garazd Creation
Maintainers
~~~~~~~~~~~
This module is maintained by the Garazd Creation.
.. image:: https://garazd.biz/logo.png
:alt: Garazd Creation
:target: https://garazd.biz
Our mission is to create convenient and effective business solutions
based on the Odoo ERP system in the areas in which we have the maximum
expertise, such as: eCommerce, marketing, SEO, integration with
marketplaces and analytic systems, product label printing and designing.
To solve these tasks, we create modules that complement each other,
extend the functionality of Odoo and improve the usability of the system.
Our solutions come with detailed documentation and additional materials
for easy use.

View File

@@ -0,0 +1,3 @@
from . import models
from . import wizard
from . import report

View File

@@ -0,0 +1,48 @@
# Copyright © 2022 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/18.0/legal/licenses.html).
# flake8: noqa: E501
{
'name': 'Odoo Product Label Builder',
'version': '19.0.1.0.0',
'category': 'Extra Tools',
'author': 'Garazd Creation',
'website': 'https://garazd.biz/en/shop/category/odoo-product-labels-15',
'license': 'OPL-1',
'summary': 'Product Barcode Label Building and Printing | Professional Tool to Print Labels | Barcode Product Label Builder | Product Label Designer | Sticker Label Maker | Dymo Label Maker | Barcode Label Generator | Direct Print',
'images': ['static/description/banner.png', 'static/description/icon.png'],
'live_test_url': 'https://garazd.biz/r/W6v',
'depends': [
'garazd_product_label_print',
'link_tracker',
],
'data': [
'security/ir.model.access.csv',
'report/product_label_reports.xml',
'report/product_label_templates.xml',
'views/print_product_label_template_views.xml',
'views/print_product_label_section_views.xml',
'views/res_config_settings_views.xml',
'views/templates.xml',
'views/res_users_views.xml',
'data/print_product_label_template_data.xml',
'data/print_product_label_section_data.xml',
'wizard/print_product_label_preview_views.xml',
'wizard/print_product_label_views.xml',
'wizard/print_product_label_template_add_views.xml',
'wizard/product_label_layout_views.xml',
],
'demo': [
'data/product_pricelist_demo.xml',
'data/res_company_demo.xml',
],
'price': 245.00,
'currency': 'EUR',
'support': 'support@garazd.biz',
'application': True,
'installable': True,
'auto_install': False,
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="garazd_show_label_template_limit" model="ir.config_parameter" forcecreate="True">
<field name="key">garazd_product_label.show_label_template_limit</field>
<field name="value">7</field>
</record>
<record id="garazd_barcode_multiplier" model="ir.config_parameter" forcecreate="True">
<field name="key">garazd_product_label_pro.barcode_multiplier</field>
<field name="value">1</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,567 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- Sections for the Label 50x25 mm -->
<record id="label_section_50x25_product_name" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_50x25"/>
<field name="sequence">1</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__name"/>
<field name="height">11</field>
<field name="align">left</field>
<field name="font_size">11</field>
<field name="line_height">1.2</field>
<field name="padding_top">1</field>
<field name="padding_left">2</field>
<field name="padding_right">2</field>
<field name="active" eval="True"/>
</record>
<record id="label_section_50x25_product_price" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_50x25"/>
<field name="sequence">2</field>
<field name="type">field</field>
<field name="field_id" ref="field_print_product_label_line__price"/>
<field name="value_format">%.2f</field>
<field name="height">7</field>
<field name="align">right</field>
<field name="font_size">22</field>
<field name="font_weight">900</field>
<field name="line_height">1.0</field>
<field name="padding_right">3</field>
<field name="widget">price</field>
<field name="active" eval="True"/>
</record>
<record id="label_section_50x25_product_barcode" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_50x25"/>
<field name="sequence">3</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__barcode"/>
<field name="height">5</field>
<field name="align">center</field>
<field name="widget">barcode</field>
</record>
<record id="label_section_50x25_product_barcode_text" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_50x25"/>
<field name="sequence">4</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__barcode"/>
<field name="height">3</field>
<field name="align">center</field>
<field name="font_size">8</field>
<field name="letter_spacing">1.4</field>
</record>
<!-- Sections for the Label 100x100 mm -->
<record id="label_section_100x100_product_image" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">1</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__image_1024"/>
<field name="height">23</field>
<field name="width">50</field>
<field name="position">left</field>
<field name="padding_bottom">3</field>
<field name="widget">image</field>
</record>
<record id="label_section_100x100_blank" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">2</field>
<field name="type">text</field>
<field name="height">23</field>
<field name="width">25</field>
<field name="position">left</field>
</record>
<record id="label_section_100x100_company_website_qrcode" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">3</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__company_id"/>
<field name="relation_field_id" ref="base.field_res_company__website"/>
<field name="height">23</field>
<field name="width">25</field>
<field name="position">right</field>
<field name="padding_bottom">3</field>
<field name="widget">qr_code</field>
</record>
<record id="label_section_100x100_product_code_label" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">4</field>
<field name="type">text</field>
<field name="value">Code:</field>
<field name="height">5</field>
<field name="position">left</field>
<field name="width">55</field>
<field name="align">left</field>
<field name="font_size">13</field>
<field name="padding_top">1</field>
<field name="with_border_top" eval="True"/>
<field name="with_border_right" eval="True"/>
</record>
<record id="label_section_100x100_product_price_label" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">5</field>
<field name="type">text</field>
<field name="value">Price:</field>
<field name="height">5</field>
<field name="position">right</field>
<field name="width">45</field>
<field name="align">left</field>
<field name="font_size">13</field>
<field name="padding_top">1</field>
<field name="padding_left">3</field>
<field name="with_border_top" eval="True"/>
</record>
<record id="label_section_100x100_product_code" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">6</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__default_code"/>
<field name="height">12</field>
<field name="position">left</field>
<field name="width">55</field>
<field name="align">left</field>
<field name="font_size">26</field>
<field name="font_size_measure">px</field>
<field name="font_weight">bold</field>
<field name="padding_top">3</field>
<field name="padding_right">2</field>
<field name="with_border_right" eval="True"/>
</record>
<record id="label_section_100x100_product_price" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">7</field>
<field name="type">field</field>
<field name="field_id" ref="field_print_product_label_line__price"/>
<field name="value_format">%.2f</field>
<field name="height">12</field>
<field name="position">right</field>
<field name="width">44</field>
<field name="align">left</field>
<field name="font_size">26</field>
<field name="font_weight">900</field>
<field name="padding_top">2</field>
<field name="padding_left">2</field>
<field name="padding_bottom">2</field>
<field name="widget">price</field>
</record>
<record id="label_section_100x100_product_name_label" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">8</field>
<field name="type">text</field>
<field name="value">Description:</field>
<field name="height">7</field>
<field name="align">left</field>
<field name="font_size">13</field>
<field name="padding_top">3</field>
<field name="padding_left">2</field>
<field name="with_border_top" eval="True"/>
</record>
<record id="label_section_100x100_product_name" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">9</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__name"/>
<field name="height">30</field>
<field name="position">left</field>
<field name="width">55</field>
<field name="align">left</field>
<field name="font_size">18</field>
<field name="line_height">1.5</field>
<field name="padding_left">2</field>
<field name="margin_bottom">3</field>
<field name="with_border_right" eval="True"/>
</record>
<record id="label_section_100x100_product_multi_price" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">10</field>
<field name="type">multi_price</field>
<field name="multi_price_order">desc</field>
<field name="value_format">%.2f</field>
<field name="height">30</field>
<field name="position">right</field>
<field name="width">44</field>
<field name="align">left</field>
<field name="font_size">12</field>
<field name="line_height">1.1</field>
<field name="padding_left">3</field>
<field name="with_border_left" eval="False"/>
</record>
<record id="label_section_100x100_product_attributes" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">11</field>
<field name="type">product_attributes</field>
<field name="height">7</field>
<field name="font_size">18</field>
<field name="line_height">1.2</field>
<field name="font_weight">bold</field>
<field name="padding_top">1</field>
<field name="with_background" eval="True"/>
<field name="background_color">#EBEBEB</field>
</record>
<record id="label_section_100x100_company_footer" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">12</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__create_date"/>
<field name="value_format">%d-%m-%Y</field>
<field name="height">8</field>
<field name="position">left</field>
<field name="width">25</field>
<field name="align">left</field>
<field name="font_size">10</field>
<field name="padding_top">4</field>
</record>
<record id="label_section_100x100_company_name" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">13</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__company_id"/>
<field name="relation_field_id" ref="base.field_res_company__name"/>
<field name="height">8</field>
<field name="position">left</field>
<field name="width">50</field>
<field name="align">center</field>
<field name="font_size">10</field>
<field name="font_weight">bold</field>
<field name="padding_top">4</field>
</record>
<record id="label_section_100x100_company_phone" model="print.product.label.section">
<field name="template_id" ref="label_template_custom_100x100"/>
<field name="sequence">14</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__company_id"/>
<field name="relation_field_id" ref="base.field_res_company__phone"/>
<field name="height">8</field>
<field name="position">right</field>
<field name="width">25</field>
<field name="align">right</field>
<field name="font_size">10</field>
<field name="padding_top">4</field>
</record>
<!-- Sections for the Label A4 63x38 mm -->
<record id="label_section_a4_63x38_product_barcode" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_63x38"/>
<field name="sequence">1</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__barcode"/>
<field name="height">11</field>
<field name="align">center</field>
<field name="padding_top">2</field>
<field name="widget">barcode</field>
<field name="barcode_is_humanreadable" eval="True"/>
</record>
<record id="label_section_a4_63x38_product_price" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_63x38"/>
<field name="sequence">2</field>
<field name="type">field</field>
<field name="field_id" ref="field_print_product_label_line__price"/>
<field name="value_format">%.2f</field>
<field name="height">8</field>
<field name="position">left</field>
<field name="width">50</field>
<field name="align">left</field>
<field name="font_size">24</field>
<field name="font_size_measure">px</field>
<field name="font_weight">900</field>
<field name="line_height">1.0</field>
<field name="widget">price</field>
<field name="padding_top">0</field>
<field name="padding_left">5</field>
<field name="active" eval="True"/>
</record>
<record id="label_section_a4_63x38_product_code" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_63x38"/>
<field name="sequence">3</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__default_code"/>
<field name="height">8</field>
<field name="position">right</field>
<field name="width">50</field>
<field name="align">center</field>
<field name="font_size">15</field>
<field name="font_size_measure">px</field>
<field name="font_weight">bold</field>
<field name="letter_spacing">-0.2</field>
<field name="padding_top">4</field>
<field name="padding_right">2</field>
<field name="active" eval="True"/>
</record>
<record id="label_section_a4_63x38_product_type_text" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_63x38"/>
<field name="sequence">4</field>
<field name="type">text</field>
<field name="value">Product Type:</field>
<field name="height">5</field>
<field name="position">left</field>
<field name="width">50</field>
<field name="align">right</field>
<field name="font_size">10</field>
<field name="font_size_measure">px</field>
<field name="padding_top">2</field>
<field name="padding_right">2</field>
</record>
<record id="label_section_a4_63x38_product_type" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_63x38"/>
<field name="sequence">5</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__type"/>
<field name="height">5</field>
<field name="position">right</field>
<field name="width">50</field>
<field name="align">left</field>
<field name="font_size">10</field>
<field name="font_weight">bold</field>
<field name="padding_top">2</field>
</record>
<record id="label_section_a4_63x38_product_name" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_63x38"/>
<field name="sequence">6</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__name"/>
<field name="height">14</field>
<field name="align">center</field>
<field name="font_size">14</field>
<field name="font_size_measure">px</field>
<field name="line_height">1.1</field>
<field name="padding_top">1</field>
<field name="padding_left">2</field>
<field name="padding_right">2</field>
</record>
<!-- Sections for the Label A4 99x38 mm -->
<record id="label_section_a4_99x38_image" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_99x38"/>
<field name="sequence">1</field>
<field name="type">image</field>
<field name="image" type="base64" file="base/static/img/res_partner_3-image.png"/>
<field name="height">27</field>
<field name="width">30</field>
<field name="position">left</field>
<field name="padding_left">2</field>
<field name="padding_right">2</field>
<field name="with_border_right" eval="True"/>
</record>
<record id="label_section_a4_99x38_product_price_label" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_99x38"/>
<field name="sequence">2</field>
<field name="type">text</field>
<field name="value">PRICE:</field>
<field name="height">8</field>
<field name="position">left</field>
<field name="width">30</field>
<field name="align">left</field>
<field name="font_size">15</field>
<field name="font_size_measure">px</field>
<field name="font_weight">100</field>
<field name="padding_top">2</field>
<field name="padding_left">2</field>
</record>
<record id="label_section_a4_93x38_product_price" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_99x38"/>
<field name="sequence">3</field>
<field name="type">field</field>
<field name="field_id" ref="field_print_product_label_line__price"/>
<field name="value_format">%.2f</field>
<field name="height">8</field>
<field name="position">right</field>
<field name="width">40</field>
<field name="align">right</field>
<field name="font_size">23</field>
<field name="font_size_measure">px</field>
<field name="font_weight">900</field>
<field name="padding_right">2</field>
<field name="widget">price</field>
</record>
<record id="label_section_a4_99x38_product_name" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_99x38"/>
<field name="sequence">4</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__name"/>
<field name="height">19</field>
<field name="width">70</field>
<field name="position">right</field>
<field name="align">left</field>
<field name="font_size">18</field>
<field name="font_size_measure">px</field>
<field name="line_height">1.2</field>
<field name="padding_top">1</field>
<field name="padding_left">2</field>
<field name="padding_right">2</field>
<field name="with_border_top" eval="True"/>
</record>
<record id="label_section_a4_99x38_product_barcode" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_99x38"/>
<field name="sequence">5</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__barcode"/>
<field name="height">8</field>
<field name="width">70</field>
<field name="position">right</field>
<field name="align">center</field>
<field name="padding_top">1</field>
<field name="with_border_top" eval="True"/>
<field name="widget">barcode</field>
</record>
<record id="label_section_a4_99x38_company_website" model="print.product.label.section">
<field name="template_id" ref="label_template_a4_99x38"/>
<field name="sequence">6</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__company_id"/>
<field name="relation_field_id" ref="base.field_res_company__website"/>
<field name="height">8</field>
<field name="width">30</field>
<field name="position">left</field>
<field name="align">center</field>
<field name="font_size">9</field>
<field name="padding_top">3</field>
<field name="with_border_right" eval="True"/>
</record>
<!-- Sections for the Label Letter 2⅝" x 1" -->
<record id="label_section_letter_66x25_product_name" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_66x25"/>
<field name="sequence">1</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__name"/>
<field name="height">13</field>
<field name="position">left</field>
<field name="width">60</field>
<field name="align">center</field>
<field name="font_size">14</field>
<field name="line_height">1.2</field>
<field name="padding_right">2</field>
<field name="with_border_right" eval="True"/>
</record>
<record id="label_section_letter_66x25_product_code" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_66x25"/>
<field name="sequence">2</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__default_code"/>
<field name="height">13</field>
<field name="position">right</field>
<field name="width">40</field>
<field name="align">center</field>
<field name="font_size">13</field>
<field name="font_weight">bold</field>
<field name="padding_top">5</field>
<field name="padding_left">1</field>
<field name="with_border_bottom" eval="True"/>
</record>
<record id="label_section_letter_66x25_product_barcode" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_66x25"/>
<field name="sequence">3</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__barcode"/>
<field name="height">11</field>
<field name="position">left</field>
<field name="width">60</field>
<field name="align">center</field>
<field name="padding_top">1</field>
<field name="padding_bottom">1</field>
<field name="with_border_right" eval="True"/>
<field name="widget">barcode</field>
</record>
<record id="label_section_letter_66x25_product_price" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_66x25"/>
<field name="sequence">4</field>
<field name="type">field</field>
<field name="field_id" ref="field_print_product_label_line__price"/>
<field name="value_format">%.2f</field>
<field name="height">11</field>
<field name="position">right</field>
<field name="width">40</field>
<field name="align">center</field>
<field name="font_size">23</field>
<field name="font_size_measure">px</field>
<field name="font_weight">900</field>
<field name="padding_top">3</field>
<field name="widget">price</field>
</record>
<!-- Sections for the Label Letter 4" x 2" -->
<record id="label_section_letter_101x50_promo" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_101x50"/>
<field name="sequence">1</field>
<field name="type">text</field>
<field name="value">SPECIAL OFFER!</field>
<field name="text_color">#FFFFFF</field>
<field name="height">12</field>
<field name="align">center</field>
<field name="font_size">30</field>
<field name="font_weight">bold</field>
<field name="line_height">1.2</field>
<field name="letter_spacing">2</field>
<field name="padding_top">1</field>
<field name="padding_bottom">1</field>
<field name="with_background" eval="True"/>
</record>
<record id="label_section_letter_101x50_product_name" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_101x50"/>
<field name="sequence">2</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__product_id"/>
<field name="relation_field_id" ref="product.field_product_product__name"/>
<field name="height">18</field>
<field name="align">left</field>
<field name="font_size">22</field>
<field name="font_size_measure">px</field>
<field name="line_height">1.2</field>
<field name="padding_left">1</field>
<field name="padding_top">3</field>
<field name="with_border_bottom" eval="True"/>
</record>
<record id="label_section_letter_101x50_product_barcode" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_101x50"/>
<field name="sequence">3</field>
<field name="type">field</field>
<field name="field_id" ref="garazd_product_label.field_print_product_label_line__barcode"/>
<field name="height">15</field>
<field name="position">left</field>
<field name="width">50</field>
<field name="align">center</field>
<field name="padding_top">2</field>
<field name="widget">barcode</field>
</record>
<record id="label_section_letter_101x50_product_price" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_101x50"/>
<field name="sequence">4</field>
<field name="type">price</field>
<field name="value_format">%.2f</field>
<field name="height">6</field>
<field name="position">right</field>
<field name="width">50</field>
<field name="align">left</field>
<field name="font_size">20</field>
<field name="font_weight">normal</field>
<field name="padding_top">1</field>
<field name="padding_left">1</field>
<field name="widget" eval="False"/>
<field name="text_decoration">line-through</field>
</record>
<record id="label_section_letter_101x50_product_promo_price" model="print.product.label.section">
<field name="template_id" ref="label_template_letter_101x50"/>
<field name="sequence">5</field>
<field name="type">promo_price</field>
<field name="value_format">%.2f</field>
<field name="height">9</field>
<field name="position">right</field>
<field name="width">50</field>
<field name="align">left</field>
<field name="font_size">32</field>
<field name="font_weight">bold</field>
<field name="padding_top">1</field>
</record>
</odoo>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="label_template_custom_50x25" model="print.product.label.template">
<field name="sequence">1</field>
<field name="name">Label 50x25 mm</field>
<field name="paperformat_id" ref="paperformat_label_custom_50x25"/>
<field name="orientation">Portrait</field>
<field name="cols">1</field>
<field name="rows">1</field>
<field name="width">50</field>
<field name="height">25</field>
</record>
<record id="label_template_custom_100x100" model="print.product.label.template">
<field name="sequence">2</field>
<field name="name">Label 100x100 mm</field>
<field name="paperformat_id" ref="paperformat_label_custom_100x100"/>
<field name="orientation">Portrait</field>
<field name="cols">1</field>
<field name="rows">1</field>
<field name="width">100</field>
<field name="height">99.5</field>
<field name="padding_top">3</field>
<field name="padding_left">3</field>
<field name="padding_right">3</field>
<field name="padding_bottom">3</field>
</record>
<record id="label_template_a4_63x38" model="print.product.label.template">
<field name="sequence">3</field>
<field name="name">Label 63x38 mm (A4: 21 pcs, 3x7)</field>
<field name="paperformat_id" ref="paperformat_label_a4_63x38"/>
<field name="orientation">Portrait</field>
<field name="cols">3</field>
<field name="rows">7</field>
<field name="col_gap">2</field>
<field name="row_gap">2</field>
<field name="width">63.5</field>
<field name="height">38.1</field>
</record>
<record id="label_template_a4_99x38" model="print.product.label.template">
<field name="sequence">4</field>
<field name="name">Label 99x38 mm (A4: 14 pcs, 2x7)</field>
<field name="paperformat_id" ref="paperformat_label_a4_99x38"/>
<field name="orientation">Portrait</field>
<field name="cols">2</field>
<field name="rows">7</field>
<field name="col_gap">1</field>
<field name="row_gap">1</field>
<field name="width">99.1</field>
<field name="height">38.1</field>
<field name="padding_top">2</field>
<field name="padding_left">2</field>
<field name="padding_right">2</field>
<field name="padding_bottom">2</field>
</record>
<record id="label_template_letter_66x25" model="print.product.label.template">
<field name="sequence">5</field>
<field name="name">Label 66x25 mm / 2⅝" x 1" (Letter: 30 pcs, 3x10)</field>
<field name="paperformat_id" ref="paperformat_label_letter_66x25"/>
<field name="orientation">Portrait</field>
<field name="cols">3</field>
<field name="rows">10</field>
<field name="col_gap">3</field>
<field name="row_gap">0</field>
<field name="width">66.54</field>
<field name="height">25.4</field>
<field name="padding_top">1</field>
<field name="padding_left">2</field>
<field name="padding_right">2</field>
<field name="description">1" x 2⅝", Avery 6460. Avery Template Presta 94200. Compatibility: 15660, 15700, 15960, 16460, 16790, 18160, 18260, 18660, 22837, 28660, 32660, 38260, 45160, 48160, 48260, 48360, 48460, 48860, 48960, 5136, 5160, 5260, 55160, 5520, 55360, 5620, 5630, 5660, 58160, 58660, 5960, 6240, 6521, 6525, 6526, 6585, 80509, 8160, 8215, 8250, 8460, 85560, 8620, 8660, 88560, 8860, 8920, 95520, 95915.</field>
</record>
<record id="label_template_letter_101x50" model="print.product.label.template">
<field name="sequence">6</field>
<field name="name">Label 101x50 mm / 4" x 2" (Letter: 10 pcs, 2x5)</field>
<field name="paperformat_id" ref="paperformat_label_letter_101x50"/>
<field name="orientation">Portrait</field>
<field name="cols">2</field>
<field name="rows">5</field>
<field name="col_gap">2</field>
<field name="width">101.6</field>
<field name="height">50.8</field>
<field name="padding_top">3</field>
<field name="padding_left">3</field>
<field name="padding_right">3</field>
<field name="description">4" x 2", Avery 5163. Compatibility: 15513, 15702, 16791, 18163, 18863, 38863, 48163, 48263, 48363, 48463, 48863, 5137, 5263, 55163, 5523, 55463, 58163, 5963, 6427, 6527, 6528, 8163, 8253, 8363, 8463, 85563, 8923, 95523, 95910, 95945.</field>
</record>
</odoo>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- Since we are adding pricelists, we activate the feature -->
<record id="base.group_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('product.group_product_pricelist'))]"/>
</record>
<record id="main_pricelist" model="product.pricelist" forcecreate="True">
<field name="name">Regular Pricelist</field>
<field name="currency_id" ref="base.USD"/>
<field name="company_id" ref="base.main_company"/>
<field name="active" eval="True"/>
</record>
<record id="promo_pricelist" model="product.pricelist" forcecreate="True">
<field name="name">Promo Pricelist</field>
<field name="currency_id" ref="base.USD"/>
<field name="company_id" ref="base.main_company"/>
<field name="active" eval="True"/>
</record>
<record id="pricelist_item_promo_product" model="product.pricelist.item">
<field name="pricelist_id" ref="promo_pricelist"/>
<field name="applied_on">0_product_variant</field>
<field name="product_id" ref="product.product_product_4c"/>
<field name="compute_price">percentage</field>
<field name="percent_price">33</field>
</record>
<record id="pricelist_item_promo_global" model="product.pricelist.item">
<field name="pricelist_id" ref="promo_pricelist"/>
<field name="applied_on">3_global</field>
<field name="compute_price">percentage</field>
<field name="percent_price">5</field>
</record>
</odoo>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="base.main_company" model="res.company">
<field name="print_label_preview_product_id" ref="product.product_product_4c"/>
<field name="print_label_preview_sale_pricelist_id" ref="main_pricelist"/>
<field name="print_label_preview_sale_pricelist_id" ref="promo_pricelist"/>
</record>
</odoo>

View File

@@ -0,0 +1,33 @@
.. _changelog:
Changelog
=========
`18.0.1.1.0`
------------
- Improve views with the "invisible" fields.
- Improve processing of the binary fields for nested relations.
- Fix adding of the horizontal gap between labels for multi-label sheets.
- Improve previewing of label sections - skip inactive sections.
- Add the option to specify allowed product attributes for the "Product Attributes" type of label sections.
- Add the parameter "garazd_product_label_pro.barcode_multiplier" to enhance the barcode quality.
- Add checking that a label section has a value before the post processing.
`18.0.1.0.1`
------------
- Fix tests.
`18.0.1.0.0`
------------
- Migration from 17.0.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
from . import print_product_label_template
from . import print_product_label_section
from . import res_company
from . import res_config_settings
from . import res_users
from . import product_product
from . import product_template

View File

@@ -0,0 +1,719 @@
# Copyright © 2023 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/15.0/legal/licenses.html).
from typing import Dict, List
from odoo import _, _lt, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_compare
class PrintProductLabelSection(models.Model):
_name = "print.product.label.section"
_description = 'Template Sections of Product Labels'
_order = 'sequence'
def _default_sequence(self):
return (self.search([], order="sequence desc", limit=1).sequence or 0) + 1
sequence = fields.Integer(default=_default_sequence)
template_id = fields.Many2one(
comodel_name='print.product.label.template',
string='Template',
ondelete='cascade',
required=True,
)
template_preview_html = fields.Html(compute='_compute_template_preview_html')
preview = fields.Boolean(default=True, help='Show the sample label.')
type = fields.Selection(
selection=[
('text', 'Text'),
('field', 'Model Field'),
('price', 'Price'),
('promo_price', 'Promo Price'),
('multi_price', 'Multiple prices (by quantity)'),
('product_attributes', 'Product Attributes'),
('image', 'Image'),
],
default='text',
required=True,
)
value = fields.Char()
value_format = fields.Char(string='Format', help='Format for date and digit fields.')
value_prefix = fields.Char()
value_suffix = fields.Char()
image = fields.Binary(attachment=True)
field_id = fields.Many2one(
comodel_name='ir.model.fields',
string='Field',
ondelete='cascade',
domain="[('id', 'in', field_ids)]",
)
field_name = fields.Char(related='field_id.name')
field_ids = fields.Many2many(
comodel_name='ir.model.fields',
string='Available Fields',
help='Technical field for a domain',
compute='_compute_field_ids',
)
field_ttype = fields.Selection(related='field_id.ttype')
relation_model_id = fields.Many2one(
comodel_name='ir.model',
compute='_compute_relation_model_id',
)
relation_field_id = fields.Many2one(
comodel_name='ir.model.fields',
help='The first level of the relation field. Allow you to select related fields'
' in case when you have chosen the "Field" with the type "many2one".',
)
relation_field_ttype = fields.Selection(
related='relation_field_id.ttype',
string="Relation Field Type",
)
nested_relation_model_id = fields.Many2one(
comodel_name='ir.model',
compute='_compute_nested_relation_model_id',
)
nested_relation_field_id = fields.Many2one(
comodel_name='ir.model.fields',
help='The second level of relation field. Allow you to select related fields in '
'case when you have chosen the "Relation Field" with the type "many2one".',
)
height = fields.Float(
string='Height, mm', digits=(10, 2), help='Section Height in mm.',
)
width = fields.Float(digits=(10, 2), help='Section Width.')
width_measure = fields.Selection(
selection=[
('%', 'Percents'),
('mm', 'mm'),
],
default='%',
required=True,
)
position = fields.Selection(
selection=[
('none', 'Full Width'),
('left', 'Left Side'),
('right', 'Right Side'),
],
string='Float',
default='none',
required=True,
)
line_height = fields.Float(digits=(10, 2), default=1.0, help='Section Line Height Ratio.')
align = fields.Selection(
selection=[
('left', 'Left'),
('center', 'Center'),
('right', 'Right'),
('justify', 'Justify'),
],
default='center',
required=True,
)
font_name = fields.Selection(
selection=[
('Lato', 'Lato'),
('Roboto', 'Roboto'),
('Open_Sans', 'Open Sans'),
('Montserrat', 'Montserrat'),
('Oswald', 'Oswald'),
('Raleway', 'Raleway'),
('Tajawal', 'Tajawal'),
('Fira_Mono', 'Fira Mono'),
],
string='Font',
help='Specify the name of a custom font for this label section.',
)
font_size = fields.Float(digits=(10, 2), default=12, required=True)
font_size_measure = fields.Selection(
selection=[('px', 'Pixels'), ('mm', 'mm')],
string='Measure',
default='px',
required=True,
)
font_weight = fields.Selection(
selection=[
('100', '100'),
('normal', 'normal'),
('bold', 'bold'),
('900', '900'),
],
default='normal',
required=True,
)
letter_spacing = fields.Float(digits=(10, 2), help='Space between letters, in mm.')
text_decoration = fields.Selection(
selection=[
('line-through', 'Line Through'),
('none', 'None'),
],
default='none',
)
text_color = fields.Char(default='#000000')
widget = fields.Selection(
selection=[
('price', 'Price'),
('barcode', 'Barcode'),
('qr_code', 'QR code'),
('image', 'Image'),
('attribute_list', 'Attribute Values'),
],
)
with_product_attribute_name = fields.Boolean(
string='With Attribute Name',
help='Place a product attribute name before the attribute value on labels.',
)
attribute_ids = fields.Many2many(
comodel_name='product.attribute',
string='Allowed Attributes',
help='Specify product attributes to show. '
'If no one is selected, all attribute that are related to a product will be shown.'
)
barcode_is_humanreadable = fields.Boolean(string='Human-readable')
barcode_symbology = fields.Selection(
selection=[
('auto', 'Auto'),
('EAN8', 'EAN8'),
('EAN13', 'EAN13'),
('Code128', 'Code128'),
],
string='Symbology',
default='auto',
required=True,
)
shorten_url = fields.Boolean(
string='Shorten URL',
help='If the section value is a URL, you can short it with the Odoo internal '
'link tracker to get a link like this one: https://your-domain/r/aBc.',
)
make_url_absolute = fields.Boolean(
string='Absolute URL',
help="If the section value is a URL, and it's a relative, you can make it "
"absolute and add your base domain by activating this option.",
)
currency_position = fields.Selection(
selection=[
('default', 'By default'),
('before', 'Before price'),
('after', 'After price'),
('none', 'Without currency code'),
],
default='default',
required=True,
)
multi_price_limit = fields.Integer(
help='Specify the limit to restrict a number of prices.',
default=10,
)
multi_price_order = fields.Selection(
selection=[
('desc', 'In descending order'),
('asc', 'In ascending order'),
],
string='Price Order',
default='desc',
required=True,
)
padding_top = fields.Float(digits=(10, 2), help='Page Right Padding, in mm.')
padding_bottom = fields.Float(digits=(10, 2), help='Page Bottom Padding, in mm.')
padding_left = fields.Float(digits=(10, 2), help='Page Left Padding, in mm.')
padding_right = fields.Float(digits=(10, 2), help='Page Right Padding, in mm.')
margin_top = fields.Float(digits=(10, 2), help='Page Right Margin, in mm.')
margin_bottom = fields.Float(digits=(10, 2), help='Page Bottom Margin, in mm.')
margin_left = fields.Float(digits=(10, 2), help='Page Left Margin, in mm.')
margin_right = fields.Float(digits=(10, 2), help='Page Right Margin, in mm.')
with_border_top = fields.Boolean(string="Border Top")
with_border_bottom = fields.Boolean(string="Border Bottom")
with_border_left = fields.Boolean(string="Border Left")
with_border_right = fields.Boolean(string="Border Right")
border_width = fields.Integer(default=1, help='Border Width, in px')
with_background = fields.Boolean(string="Background")
background_color = fields.Char(default='#BBBBBB')
active = fields.Boolean(default=True)
@api.constrains('height')
def _check_height(self):
for section in self:
if not section.height:
raise ValidationError(_('The section height must be set.'))
@api.constrains('type', 'widget')
def _check_widget_image(self):
for section in self:
if section.type != 'field' and section.widget == 'image':
raise ValidationError(_('You can use the widget "Image" only for the "Model Fields" section types.'))
@api.depends('type')
def _compute_field_ids(self):
for section in self:
if section.type == 'field':
domain = [('model', '=', 'print.product.label.line')] + self._get_field_domain()
available_fields = self.env['ir.model.fields'].search(domain)
section.field_ids = [(6, 0, available_fields.ids)]
else:
section.field_ids = None
@api.depends('type', 'field_id')
def _compute_relation_model_id(self):
for section in self:
if section.type == 'field' and section.field_id.ttype in self.relation_field_types():
section.relation_model_id = self.env['ir.model'].search([
('model', '=', section.field_id.relation)
])[:1].id
else:
section.relation_model_id = None
@api.depends('type', 'field_id', 'relation_field_id')
def _compute_nested_relation_model_id(self):
for section in self:
if (
section.type == 'field'
and section.field_id
and section.field_id.ttype in self.relation_field_types()
and section.relation_field_id
and section.relation_field_id.ttype in self.relation_field_types()
):
section.nested_relation_model_id = self.env['ir.model'].search([
('model', '=', section.relation_field_id.relation),
])[:1].id
else:
section.nested_relation_model_id = None
@api.depends('template_id')
def _compute_template_preview_html(self):
for section in self:
# flake8: noqa: E501
section.template_preview_html = section.template_id.with_context(editable_section_id=section.id).preview_html
@api.onchange('type')
def _onchange_type(self):
for section in self:
if section.type != 'field':
section.field_id = section.relation_field_id = section.nested_relation_field_id = False
@api.onchange('field_id')
def _onchange_field_id(self):
for section in self:
if section.relation_field_id.model_id != section.relation_model_id:
section.relation_field_id = section.nested_relation_field_id = False
@api.onchange('relation_field_id')
def _onchange_relation_field_id(self):
for section in self:
if section.nested_relation_field_id.model_id != section.nested_relation_model_id:
section.nested_relation_field_id = False
@api.onchange('type', 'field_id', 'widget')
def _onchange_widget(self):
for section in self:
# Reset the "Price" widget
if section.widget == 'price' and section.field_id != self.env.ref(
'garazd_product_label_pro.field_print_product_label_line__price'):
section.widget = False
# Reset the value format
section.value_format = False
@api.model
def binary_field_types(self):
return ['binary']
@api.model
def text_field_types(self):
return ['char', 'text', 'html', 'selection']
@api.model
def digit_field_types(self):
return ['float', 'monetary', 'integer']
@api.model
def date_field_types(self):
return ['date', 'datetime']
@api.model
def non_relation_field_types(self):
return self.binary_field_types() + self.text_field_types() \
+ self.digit_field_types() + self.date_field_types()
@api.model
def relation_field_types(self):
return ['many2one']
@api.model
def multi_relation_field_types(self):
return ['many2many', 'one2many']
@api.model
def _get_field_domain(self):
return [('ttype', 'in', self.non_relation_field_types() + self.relation_field_types())] # flake8: noqa: E501
def get_float_position(self):
self.ensure_one()
width = 100 if self.width_measure == '%' and self.width > 100 else self.width
return "width: %(width).2f%(measure)s; float: %(float)s;" % {
'width': width,
'measure': self.width_measure,
'float': self.position,
}
def get_barcode_size(self) -> Dict[str, int]:
self.ensure_one()
# flake8: noqa: E501
multiplier = int(self.env['ir.config_parameter'].sudo().get_param('garazd_product_label_pro.barcode_multiplier', 1))
return {'width': 600 * multiplier, 'height': 100 * multiplier}
def get_border_style(self):
self.ensure_one()
border_style = ''
for side in ['top', 'bottom', 'left', 'right']:
if self['with_border_%s' % side]:
border_style += 'border-%(side)s: %(width)dpx solid #000; ' % {
'side': side,
'width': self.border_width,
}
return border_style
def get_background_style(self):
self.ensure_one()
bg_style = ''
if self.with_background and self.background_color:
bg_style += f'background-color: {self.background_color}; '
return bg_style
def get_font_name(self) -> str:
self.ensure_one()
return self.font_name.replace('_', ' ') if self.font_name else ''
def get_attribute_data(self, label=None) -> List[Dict]:
self.ensure_one()
section = self
attribute_data = []
if label:
product = label.product_id
for attribute in product.product_tmpl_id.attribute_line_ids.mapped('attribute_id'):
# Process only allowed attributes
if section.attribute_ids and attribute not in section.attribute_ids:
continue
attr_vals = {'name': attribute.name, 'values': []}
attribute_values = product.product_tmpl_id.attribute_line_ids.value_ids.filtered(
lambda av: av.attribute_id == attribute
)
for val in attribute_values:
value_data = {'name': val.name, 'active': False}
if val in product.product_template_attribute_value_ids.mapped('product_attribute_value_id'):
value_data['active'] = True
attr_vals['values'].append(value_data)
attribute_data.append(attr_vals)
return attribute_data
def get_html_style(self, label=None) -> str:
self.ensure_one()
style = "overflow: hidden; " \
"height: %(height).2fmm; " \
"padding: %(padding_top).2fmm %(padding_right).2fmm" \
" %(padding_bottom).2fmm %(padding_left).2fmm; " \
"margin: %(margin_top).2fmm %(margin_right).2fmm" \
" %(margin_bottom).2fmm %(margin_left).2fmm; " \
"text-align: %(align)s; " \
"font-size: %(font_size)s; " \
"color: %(text_color)s; " \
"line-height: %(line_height).2f; " \
"letter-spacing: %(letter_spacing).2fmm; " \
"font-weight: %(font_weight)s; "\
"text-decoration: %(text_decoration)s;" % {
'height': self.height,
'align': self.align,
'font_size': '%.2f%s' % (self.font_size, self.font_size_measure),
'text_color': self.text_color,
'line_height': self.line_height,
'letter_spacing': self.letter_spacing,
'font_weight': self.font_weight,
'padding_top': self.padding_top,
'padding_right': self.padding_right,
'padding_bottom': self.padding_bottom,
'padding_left': self.padding_left,
'margin_top': self.margin_top,
'margin_right': self.margin_right,
'margin_bottom': self.margin_bottom,
'margin_left': self.margin_left,
'text_decoration': self.text_decoration or 'none',
}
# Section width settings
style += "clear: both;" if self.position == 'none' else self.get_float_position()
# Section borders
style += self.get_border_style()
# Section background
style += self.get_background_style()
# Hide the crossed regular price section if a price and the promo price are equal
# pylint: disable-msg=too-many-boolean-expressions
if ((self.type == 'price' or self.type == 'field' and self.field_name == 'price')
and 'promo_price' in self.template_id.section_ids.mapped('type')
and label and float_compare(label.price, label.promo_price, precision_digits=2) == 0
):
style += "opacity: 0;"
# Section font family
if self.font_name:
style += f'font-family: "{self.get_font_name()}";'
return style
def _format_digit_value(self, value) -> str:
self.ensure_one()
try:
digit_value = ('%s' % (self.value_format or '%s')) % value
except (ValueError, TypeError):
digit_value = 'ERROR! Check the format'
return digit_value
def _get_field_value(self, record, field) -> str:
"""Return a value of the "field" for the "record"."""
self.ensure_one()
section = self
# Retrieving a value
if field.ttype == 'selection':
selection = record.fields_get([field.name])[field.name].get('selection', [])
vals = dict(selection)
value = vals.get(record[field.name])
else:
value = record[field.name]
# Format empty values
value = value if value is not False else ''
# Format values for the digit and date fields
if value and not section.widget:
# Format values for the digit fields
if field.ttype in self.digit_field_types():
value = section._format_digit_value(value)
# Format values for the date fields
elif field.ttype in ['date', 'datetime']:
lang = self.env['res.lang']._lang_get(self.env.lang) or self.env.ref('base.lang_en')
value = value.strftime(section.value_format or lang.date_format)
return value
@api.model
def process_price_value(self, label, price: float, currency=None) -> float:
"""
Post-processing of the price value before converting to the string.
Method to override.
"""
return price
def _get_price_value(self, label, pricelist=False, min_quantity=1.0) -> str:
self.ensure_one()
section = self
product = label.product_id
if pricelist:
price = pricelist._get_product_price(product, min_quantity)
else:
price = label.price
currency = pricelist.currency_id if pricelist else label.currency_id
# Post-processing before converting to the string
value = self.process_price_value(label, price, currency=currency)
# Converting to the string
value = section._format_digit_value(value)
if section.currency_position != 'none':
# flake8: noqa: E501
before = section.currency_position == 'before' or section.currency_position == 'default' and currency.position == 'before'
if before:
value = '%s %s' % (currency.symbol, value)
else:
value = '%s %s' % (value, currency.symbol)
return value
@api.model
def get_pricelist_items(self, product, pricelist, sort_reverse=False):
"""Collect all pricelist rules that affect the current product."""
price_rules = pricelist.item_ids.filtered(
lambda l: l.product_id == product and l.min_quantity
)
price_rules |= pricelist.item_ids.filtered(
lambda l: l.product_tmpl_id == product.product_tmpl_id
and not l.product_id and l.min_quantity
)
price_rules |= self.env['product.pricelist.item'].search([
('pricelist_id', '=', pricelist.id),
('categ_id', 'parent_of', product.categ_id.id),
('min_quantity', '!=', 0),
])
# Remove rules with duplicated min quantity values
res = self.env['product.pricelist.item'].browse()
for rule in price_rules:
if rule.min_quantity not in res.mapped('min_quantity'):
res += rule
return res.sorted('min_quantity', reverse=sort_reverse)
@api.model
def get_short_url(self, url: str, title: str) -> str:
# Search a short link, if it exists
link = self.env['link.tracker'].search([('url', '=', url if url.startswith('http') else f'http://{url}')])
if not link:
# Create a new short link, if it does not exist
link = self.env['link.tracker'].sudo().create({
'url': url,
'title': title,
})
return link.short_url
def get_value(self, label):
"""Return value for a section depending on label.
:param label: record of "print.product.label.line" model
:return: str
"""
# flake8: noqa: E501
# pylint: disable=too-many-branches
self.ensure_one()
section = self.sudo() # Add sudo to allow users without "Administration/Access Rights" generate labels
value = ''
allowed_text_field_types = self.text_field_types() + self.digit_field_types() + self.date_field_types()
allowed_relation_field_types = self.relation_field_types()
# There are three relation levels:
# Level 0 - the product label level
# Level 1 - the level of relation field of the product label model
# Level 2 - the level of relation field of the relation field
if section.type == 'text':
value = section.value or ''
elif section.type == 'price':
value = section._get_price_value(label, label.wizard_id.pricelist_id)
elif section.type == 'promo_price' and label.wizard_id.sale_pricelist_id:
value = section._get_price_value(label, label.wizard_id.sale_pricelist_id)
elif section.type == 'multi_price' and label.wizard_id.pricelist_id:
pricelist = label.wizard_id.pricelist_id
qty_prices = []
for pl_rule in self.get_pricelist_items(
label.product_id, pricelist,
sort_reverse=section.multi_price_order == 'asc',
)[:section.multi_price_limit]:
qty_prices.append({
'qty': pl_rule.min_quantity,
'amount': section._get_price_value(label, label.wizard_id.pricelist_id, min_quantity=pl_rule.min_quantity),
'currency': pricelist.currency_id.symbol,
})
value = qty_prices
# pylint: disable=too-many-nested-blocks
elif section.type == 'field' and section.field_id:
# Level 0
# Text and digit fields
if section.field_ttype in allowed_text_field_types:
value = section._get_field_value(label, section.field_id)
# Relation fields
elif section.relation_field_id and section.field_id.ttype in allowed_relation_field_types:
# Level 1
record = label[section.field_id.name]
if record:
# Text and digit fields of the relation field
if section.relation_field_ttype in allowed_text_field_types:
value = section._get_field_value(record, section.relation_field_id)
# Nested relation fields of the relation field
elif section.relation_field_ttype in allowed_relation_field_types:
# Level 2
nested_record = record[section.relation_field_id.name]
if nested_record:
# Text and digit fields of the relation field
if section.nested_relation_field_id.ttype in allowed_text_field_types:
value = section._get_field_value(
nested_record, section.nested_relation_field_id
)
# Post-processing
# Prefix and Suffix
if value and section.value_prefix:
value = f"{section.value_prefix}{value}"
if value and section.value_suffix:
value = f"{value}{section.value_suffix}"
# Make a URL an absolute, if it's relative
if value and section.make_url_absolute and not value.startswith('http'):
value = f"{section.get_base_url().rstrip('/')}{value}"
if value and section.shorten_url and not self._context.get('preview_mode'):
# Generate a shorten URL
value = self.get_short_url(value, "%s - %s" % (section.template_id.name, section.display_name))
return value
def get_image_url(self, label) -> str:
"""
Return URL for a section binary field depending on label.
:param label: record of "print.product.label.line" model
:return: str
"""
self.ensure_one()
section = self.sudo() # Add sudo to allow users without "Administration/Access Rights" generate labels
res = ''
if (
section.type != 'field'
or section.relation_field_ttype != 'many2one' and (
not section.relation_model_id
or not section.relation_field_id or section.relation_field_id.ttype != 'binary'
)
# Nested fields
or section.relation_field_ttype == 'many2one' and (
not section.nested_relation_model_id
or not section.nested_relation_field_id or section.nested_relation_field_id.ttype != 'binary'
)
):
return res
model_name = section.relation_model_id.model
field_name = section.field_id.name
record = label[field_name]
relation_field_name = section.relation_field_id.name
# Nested fields
if section.relation_field_ttype == 'many2one' and section.nested_relation_field_id.ttype == 'binary':
model_name = section.nested_relation_field_id.model
record = label[field_name][section.relation_field_id.name]
relation_field_name = section.nested_relation_field_id.name
if record:
res = '/web/image/%s/%d/%s' % (model_name, record.id, relation_field_name)
return res
@api.depends('type', 'value', 'field_id', 'field_id.field_description',
'relation_field_id', 'relation_field_id.field_description', 'widget')
def _compute_display_name(self):
for section in self:
section.display_name = "%s%s" % (
section.type == 'text'
and (section.value and f"{_lt('Text:')} {section.value}" or _lt('Blank'))
or section.type == 'price' and _lt('Price')
or section.type == 'promo_price' and _lt('Promo Price')
or section.type == 'multi_price' and _lt('Multiple prices (by qty)')
or section.type == 'product_attributes' and (_('Product Attributes%s') % (
f" [{', '.join(attr.name for attr in section.attribute_ids)}]" if section.attribute_ids else ''
))
or section.type == 'image' and _lt('Image')
or section.type == 'field'
and "%s %s" % (
section.field_id.field_description,
section.relation_field_id
and section.relation_field_id.field_description or '',
),
section.widget and (" (widget: %s)" % section.widget) or '',
)
def action_pdf_preview(self):
self.ensure_one()
return self.template_id.action_pdf_preview()

View File

@@ -0,0 +1,269 @@
# Copyright © 2023 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/15.0/legal/licenses.html).
from odoo import _, api, fields, models
from odoo.tools import float_round
from odoo.exceptions import UserError
class PrintProductLabelTemplate(models.Model):
_name = "print.product.label.template"
_description = 'Product Label Templates'
_order = 'sequence'
def _default_sequence(self):
return (self.search([], order="sequence desc", limit=1).sequence or 0) + 1
sequence = fields.Integer(default=_default_sequence)
name = fields.Char(required=True)
type_id = fields.Many2one(
comodel_name='print.label.type',
default=lambda self: self.env.ref('garazd_product_label.type_product'),
help='You can specify a type of this label. It makes sense if you use '
'additional extensions to print labels not for products only but for other objects as well. '
'Like as Stock Packages, Sales Orders, Manufacturing Orders, etc. '
'By this type, you can filter your label templates in the print wizard.',
ondelete='set null',
required=False,
)
section_ids = fields.One2many(
comodel_name='print.product.label.section',
inverse_name='template_id',
string='Sections',
)
section_count = fields.Integer(compute='_compute_section_count')
paperformat_id = fields.Many2one(
comodel_name='report.paperformat',
# All label paperformats should have prefix "Label: "
domain="[('name', 'like', 'Label: %')]",
readonly=True,
required=True,
)
format = fields.Selection(related='paperformat_id.format', store=True)
orientation = fields.Selection(
related='paperformat_id.orientation',
readonly=False,
required=True,
help='Page Orientation. Only system administrators can change this value.',
)
rows = fields.Integer(default=1, required=True)
cols = fields.Integer(default=1, required=True)
margin_top = fields.Float(
related='paperformat_id.margin_top',
help='Page Top Margin in mm. Only system administrators can change this value.',
readonly=False,
)
margin_bottom = fields.Float(
related='paperformat_id.margin_bottom',
help='Page Bottom Margin in mm. '
'Only system administrators can change this value.',
readonly=False,
)
margin_left = fields.Float(
related='paperformat_id.margin_left',
help='Page Left Margin in mm. Only system administrators can change this value.',
readonly=False,
)
margin_right = fields.Float(
related='paperformat_id.margin_right',
help='Page Right Margin in mm. '
'Only system administrators can change this value.',
readonly=False,
)
padding_top = fields.Float(
default=0, digits=(10, 2), help='Label Right Padding in mm.')
padding_bottom = fields.Float(
default=0, digits=(10, 2), help='Label Bottom Padding in mm.')
padding_left = fields.Float(
default=0, digits=(10, 2), help='Label Left Padding in mm.')
padding_right = fields.Float(
default=0, digits=(10, 2), help='Label Right Padding in mm.')
label_style = fields.Char(string='Custom Label Style')
width = fields.Float(digits=(10, 2), help='Label Width in mm.')
height = fields.Float(digits=(10, 2), help='Label Height in mm.')
row_gap = fields.Float(
string='Horizontal',
digits=(10, 2),
default=0,
help='Horizontal gap between labels, in mm.',
)
col_gap = fields.Float(
string='Vertical',
digits=(10, 2),
default=0,
help='Vertical gap between labels, in mm.',
)
is_oversized = fields.Boolean(compute='_compute_is_oversized')
description = fields.Char()
preview = fields.Boolean(default=True, help='Show the sample label.')
preview_html = fields.Html(compute='_compute_preview_html', compute_sudo=True)
ratio_px_in_mm = fields.Float(
string='Ratio (px in mm)',
digits=(10, 4),
compute='_compute_ratio_px_in_mm',
help="Technical field that indicates how many pixels in 1 mm.",
store=True,
)
active = fields.Boolean(default=True)
@api.depends('section_ids')
def _compute_section_count(self):
for template in self:
template.section_count = len(template.section_ids)
@api.depends('width', 'height', 'section_ids', 'section_ids.height')
def _compute_is_oversized(self):
for template in self:
total_height = sum(template.section_ids.mapped('height')) \
+ template.padding_top + template.padding_bottom
template.is_oversized = template.height < total_height
@api.depends('paperformat_id', 'paperformat_id.dpi')
def _compute_ratio_px_in_mm(self):
for template in self:
template.ratio_px_in_mm = template.paperformat_id.dpi / 25.4
def _set_paperformat(self):
self.ensure_one()
self.env.ref(
'garazd_product_label.action_report_product_label_from_template'
).sudo().paperformat_id = self.paperformat_id.id
def write(self, vals):
"""If the Dymo label width or height were changed,
we should change it to the related paperformat."""
res = super(PrintProductLabelTemplate, self).write(vals)
if 'width' in vals or 'height' in vals:
for template in self:
if template.paperformat_id.format == 'custom' \
and template.cols == 1 and template.rows == 1:
template.paperformat_id.sudo().write({
# flake8: noqa: E501
'page_width': float_round(template.width, precision_rounding=1, rounding_method='UP'),
'page_height': float_round(template.height, precision_rounding=1, rounding_method='UP'),
})
return res
def unlink(self):
paperformats = self.mapped('paperformat_id')
res = super(PrintProductLabelTemplate, self).unlink()
paperformats.sudo().unlink()
return res
def get_demo_product(self):
self.ensure_one()
product = self.env['product.product'].browse()
# If the label template is opened from the print wizard (the 'print_wizard_id' value in the context),
# use the first product from the list. Otherwise, use the demo product
# Case 1: Get a real product to display
if self.env.company.print_label_preview_type == 'real_product':
if self._context.get('print_product_id'):
product = self.env['product.product'].browse(self._context.get('print_product_id'))
if not product and self._context.get('print_wizard_id'):
wizard = self.env['print.product.label'].browse(self._context.get('print_wizard_id'))
if wizard.label_ids:
product = wizard.label_ids[0].product_id
# Case 2: Get a demo product that is specified in the general settings
if not product:
product = self.env.company.print_label_preview_product_id
return product
def get_demo_product_label(self, product: models.Model = None):
self.ensure_one()
PrintWizard = self.env['print.product.label']
ProductLabel = self.env['print.product.label.line']
wizard = PrintWizard.browse()
label = ProductLabel.browse()
# Get a real product to display
# If the label template is opened from the print wizard (the 'print_wizard_id' value in the context),
# use the first product from the list. Otherwise, use the demo product
if self.env.company.print_label_preview_type == 'real_product' and self._context.get('print_wizard_id'):
wizard = PrintWizard.browse(self._context.get('print_wizard_id'))
label = wizard.label_ids[:1]
demo_product = product or self.get_demo_product()
if not demo_product:
raise UserError(_("Please select a demo product in the General Settings."))
if not wizard or not label:
pricelist_id = self._context.get('pricelist_id')
pricelist = self.env['product.pricelist'].browse(pricelist_id) \
if pricelist_id else self.env.company.print_label_preview_pricelist_id
sale_pricelist_id = self._context.get('sale_pricelist_id')
sale_pricelist = self.env['product.pricelist'].browse(sale_pricelist_id) \
if sale_pricelist_id else self.env.company.print_label_preview_sale_pricelist_id
wizard = PrintWizard.create({
'template_id': self.id,
'company_id': self.env.company.id,
'pricelist_id': pricelist.id,
'sale_pricelist_id': sale_pricelist.id,
})
label = ProductLabel.create({
'wizard_id': wizard.id,
'product_id': demo_product.id,
'price': demo_product.lst_price,
'barcode': demo_product.barcode,
})
return label
@api.depends('preview')
def _compute_preview_html(self):
for template in self:
demo_product = template.get_demo_product()
template.preview_html = template.get_preview_html() if demo_product else ''
def get_preview_html(self):
self.ensure_one()
values = {
'back_style':
'background-color: #CCCCCC; '
'width: 100%; height: 100%; '
'padding: 15px; '
'overflow: hidden;',
'label_style':
'width: %(width)fmm; '
'height: %(height)fmm; '
'background-color: #FFFFFF; '
'margin: auto; '
'padding: %(padding_top)fmm '
'%(padding_right)fmm '
'%(padding_bottom)fmm '
'%(padding_left)fmm; '
'%(label_custom_style)s' % {
'width': self.width,
'height': self.height,
'padding_top': self.padding_top,
'padding_right': self.padding_right,
'padding_bottom': self.padding_bottom,
'padding_left': self.padding_left,
'label_custom_style': self.label_style or '',
},
'sections': self.section_ids.filtered('active'),
'label': self.get_demo_product_label(),
'editable_section_id': self._context.get('editable_section_id', False),
}
return self.env['ir.ui.view']._render_template('garazd_product_label_pro.label_preview', values)
@api.depends_context('uid')
def _get_user_allowed_templates(self):
"""
System administrators are not restricted anyway.
Other users are restricted if allowed templates are specified in their settings,
otherwise these users can use all templates.
"""
all_templates = self.search([])
return all_templates if self.env.user.has_group('base.group_system') \
else all_templates if not self.env.user.print_label_allowed_template_ids \
else self.env.user.print_label_allowed_template_ids
def action_pdf_preview(self):
self.ensure_one()
label = self.get_demo_product_label()
return self.env['print.product.label']._pdf_preview(label.wizard_id.get_pdf())

View File

@@ -0,0 +1,17 @@
from odoo import models
class ProductProduct(models.Model):
_inherit = "product.product"
def action_open_label_layout(self):
"""
If a user has direct print option and a label template, return the direct print action,
Otherwise, return the standard Odoo print wizard.
"""
print_wizard = super(ProductProduct, self).action_open_label_layout()
if not self.env['ir.config_parameter'].sudo().get_param('garazd_product_label.replace_standard_wizard'):
return print_wizard
return self.env['print.product.label'].get_quick_report_action(
model_name='product.product', ids=self.ids, close_window=True,
) if self.env.user.print_label_directly and self.env.user.print_label_template_id else print_wizard

View File

@@ -0,0 +1,17 @@
from odoo import models
class ProductTemplate(models.Model):
_inherit = "product.template"
def action_open_label_layout(self):
"""
If a user has direct print option and a label template, return the direct print action,
Otherwise, return the standard Odoo print wizard.
"""
print_wizard = super(ProductTemplate, self).action_open_label_layout()
if not self.env['ir.config_parameter'].sudo().get_param('garazd_product_label.replace_standard_wizard'):
return print_wizard
return self.env['print.product.label'].get_quick_report_action(
model_name='product.template', ids=self.ids, close_window=True,
) if self.env.user.print_label_directly and self.env.user.print_label_template_id else print_wizard

View File

@@ -0,0 +1,29 @@
from odoo import fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
print_label_preview_type = fields.Selection(
selection=[('demo_product', 'Demo Product'), ('real_product', 'Real Product')],
default='demo_product',
required=True,
help='Specify what product should be used during label designing.',
)
print_label_preview_product_id = fields.Many2one(
comodel_name='product.product',
string='Demo Product',
default=lambda self: self.env['product.product'].search([
('barcode', '!=', False)
], limit=1),
)
print_label_preview_pricelist_id = fields.Many2one(
comodel_name='product.pricelist',
string='Demo Pricelist',
default=lambda self: self.env['product.pricelist'].search([])[:1],
)
print_label_preview_sale_pricelist_id = fields.Many2one(
comodel_name='product.pricelist',
string='Demo Promo Pricelist',
default=lambda self: self.env['product.pricelist'].search([])[1:2],
)

View File

@@ -0,0 +1,26 @@
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
print_label_preview_type = fields.Selection(
related='company_id.print_label_preview_type',
# selection=[('real_product', 'Draft'), ('demo_product', 'Done')],
readonly=False,
)
print_label_preview_product_id = fields.Many2one(
string='Demo Product',
related='company_id.print_label_preview_product_id',
readonly=False,
)
print_label_preview_pricelist_id = fields.Many2one(
string='Demo Pricelist',
related='company_id.print_label_preview_pricelist_id',
readonly=False,
)
print_label_preview_sale_pricelist_id = fields.Many2one(
string='Demo Promo Pricelist',
related='company_id.print_label_preview_sale_pricelist_id',
readonly=False,
)

View File

@@ -0,0 +1,22 @@
from odoo import fields, models
class ResUsers(models.Model):
_inherit = 'res.users'
print_label_template_id = fields.Many2one(
comodel_name='print.product.label.template',
string='Default Template',
)
print_label_allowed_template_ids = fields.Many2many(
comodel_name='print.product.label.template',
string='Allowed Templates',
help='Restrict this user to the specified label templates. If no templates are specified, allow all templates.'
'Please take a note that the system administrators cannot be restricted, they always see all templates.',
)
print_label_directly = fields.Boolean(
string='Immediate Printing',
help='If this option is active and the default label template is specified, '
'after clicking on the Print Labels button the alternative print wizard will be skipped, '
'and labels will be send to print without downloading (the browser printing window will be shown).',
)

View File

@@ -0,0 +1 @@
from . import product_label_report

View File

@@ -0,0 +1,15 @@
from odoo import models
class ReportGarazdProductLabelFromTemplate(models.AbstractModel):
_name = 'report.garazd_product_label.report_product_label_from_template'
_description = 'Custom Product Label Report'
def _get_report_values(self, docids, data):
labels = self.env['print.product.label.line'].browse(data.get('ids', []))
return {
'doc_model': 'print.product.label.line',
'doc_ids': labels.ids,
'docs': labels,
'data': data.get('data'),
}

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="paperformat_label_custom_50x25" model="report.paperformat">
<field name="name">Label: 50x25 mm</field>
<field name="format">custom</field>
<field name="page_height">25</field>
<field name="page_width">50</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="header_spacing">0</field>
<field name="header_line" eval="False"/>
<field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field>
<field name="default" eval="False"/>
</record>
<record id="paperformat_label_custom_100x100" model="report.paperformat">
<field name="name">Label: 100x100mm</field>
<field name="format">custom</field>
<field name="page_height">100</field>
<field name="page_width">100</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="header_spacing">0</field>
<field name="dpi">96</field>
<field name="default" eval="False"/>
</record>
<record id="paperformat_label_a4_63x38" model="report.paperformat">
<field name="name">Label: A4 63x38mm</field>
<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">9</field>
<field name="margin_bottom">0</field>
<field name="margin_left">8</field>
<field name="margin_right">7</field>
<field name="header_line" eval="False"/>
<field name="header_spacing">0</field>
<field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field>
<field name="default" eval="False"/>
</record>
<record id="paperformat_label_a4_99x38" model="report.paperformat">
<field name="name">Label: A4 99x38mm</field>
<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">10</field>
<field name="margin_bottom">0</field>
<field name="margin_left">5</field>
<field name="margin_right">5</field>
<field name="header_line" eval="False"/>
<field name="header_spacing">0</field>
<field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field>
<field name="default" eval="False"/>
</record>
<record id="paperformat_label_letter_66x25" model="report.paperformat">
<field name="name">Label: Letter 1"x2⅝"</field>
<field name="format">Letter</field>
<field name="page_height">0</field>
<field name="page_width">0</field>
<field name="orientation">Portrait</field>
<field name="margin_top">12</field>
<field name="margin_bottom">8</field>
<field name="margin_left">4.76</field>
<field name="margin_right">4.76</field>
<field name="disable_shrinking" eval="True"/>
<field name="header_spacing">0</field>
<field name="dpi">96</field>
<field name="default" eval="False"/>
</record>
<record id="paperformat_label_letter_101x50" model="report.paperformat">
<field name="name">Label: Letter 4"x2"</field>
<field name="format">Letter</field>
<field name="page_height">0</field>
<field name="page_width">0</field>
<field name="orientation">Portrait</field>
<field name="margin_top">12.7</field>
<field name="margin_bottom">8</field>
<field name="margin_left">4.76</field>
<field name="margin_right">4.76</field>
<field name="disable_shrinking" eval="True"/>
<field name="header_spacing">0</field>
<field name="dpi">96</field>
<field name="default" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="product_label_from_template">
<div name="label_div">
<div name="transform_div">
<t t-foreach="label.wizard_id.template_id.section_ids" t-as="section">
<div t-if="section.active" t-attf-style="{{section.get_html_style(label)}}">
<t t-set="value" t-value="section.get_value(label)"/>
<t t-if="section.widget == 'barcode'">
<t t-set="barcode_div_size" t-value="'width: 85%; height: 100%;'"/>
<t t-set="barcode_size" t-value="section.get_barcode_size()"/>
<!-- Barcode parameters: addons/web/controllers/report.py(#59 - report_barcode()), odoo/addons/base/models/ir_actions_report.py(#675 - barcode()) -->
<div t-out="str(value)" style="padding: 0; height: 100%;" t-options="{'widget': 'barcode', 'width': barcode_size['width'], 'height': barcode_size['height'], 'quiet': 0, 'symbology': section.barcode_symbology, 'img_style': barcode_div_size, 'humanreadable': int(section.barcode_is_humanreadable)}"/>
</t>
<t t-elif="section.widget == 'price'">
<div t-field="label.price" t-options="{'widget': 'monetary', 'label_price': True, 'display_currency': label.currency_id}"/>
</t>
<t t-elif="section.widget == 'qr_code'">
<t t-set="qr_code_size_px" t-value="section.height * 40"/>
<img t-att-src="'/report/barcode/?barcode_type=QR&amp;value=%s&amp;width=%d&amp;height=%d'%(value, qr_code_size_px, qr_code_size_px)" t-attf-style="display: block; height: 100%; width: {{ section.height - section.padding_top - section.padding_bottom }}mm;"/>
</t>
<t t-elif="section.widget == 'image'">
<div t-attf-style="width:100%;height:100%;background-image:url({{ section.get_image_url(label) }});background-size:contain;background-repeat:no-repeat;background-position:center;"/>
</t>
<t t-elif="section.type == 'image'">
<div t-attf-style="width:100%;height:100%;background-image:url(/web/image/print.product.label.section/{{ section.id }}/image);background-size:contain;background-repeat:no-repeat;background-position:center;"/>
</t>
<t t-elif="section.type == 'product_attributes'">
<t t-if="section.widget == 'attribute_list'">
<div t-foreach="section.get_attribute_data(label)" t-as="attr">
<span t-if="section.with_product_attribute_name" t-out="attr['name']" class="mx-1"/>
<t t-foreach="attr['values']" t-as="attr_value">
<span t-att-class="'badge rounded bg-%s text-%s mr-1 px-2 py-1' % ('dark' if attr_value['active'] else 'light', 'light' if attr_value['active'] else 'dark')">
<t t-out="attr_value['name']"/>
</span>
</t>
</div>
</t>
<div t-else="" t-foreach="section.get_attribute_data(label)" t-as="attr" class="d-inline-flex flex-wrap">
<span class="mw-100 mx-1">
<t t-if="section.with_product_attribute_name" class="mr-1"><t t-out="attr['name']"/>: </t>
<t t-foreach="attr['values']" t-as="attr_value">
<span t-if="attr_value['active']" t-out="attr_value['name']" style="padding-left: 3px;"/>
</t>
</span>
</div>
</t>
<t t-elif="section.type == 'multi_price'">
<t t-foreach="section.get_value(label)" t-as="price">
<t t-out="price['amount']"/>
<small>(from <t t-out="price['qty']"/> <t t-out="label.product_id.uom_id.name"/>)</small>
<br/>
</t>
</t>
<div t-else="" t-out="value"/>
</div>
</t>
</div>
</div>
</template>
<template id="report_product_label_from_template" inherit_id="garazd_product_label.report_product_label_from_template">
<xpath expr="//t[@t-call='web.basic_layout']" position="inside">
<style>body {margin: 0 !important; padding: 0 !important;}</style>
<t t-set="index" t-value="0"/>
<t t-set="cols" t-value="data.get('cols', 1)"/>
<t t-set="rows" t-value="data.get('rows', 1)"/>
<t t-set="labels_per_sheet" t-value="cols * rows"/>
<t t-set="skip" t-value="data.get('skip_places', 0)"/>
<t t-foreach="docs" t-as="label">
<t t-set="qty" t-value="1"/>
<t t-if="label.qty" t-set="qty" t-value="label.qty"/>
<t t-if="labels_per_sheet" t-foreach="range(not index and (qty + skip) or qty)" t-as="label_index">
<div t-if="index % labels_per_sheet == 0" style="page-break-before: always;"/>
<!-- Horizontal gap between labels -->
<t t-if="index and index % cols == 0 and index % labels_per_sheet != 0" t-set="horizontal_gap" t-value="'height: 1px; margin-bottom: %.2fmm;' % data.get('row_gap', 0)"/>
<t t-else="" t-set="horizontal_gap" t-value="''"/>
<!-- New line -->
<div t-if="index % cols == 0" t-att-style="'clear:both;' + horizontal_gap"/>
<!-- Vertical gap between labels -->
<t t-if="index % cols != 0" t-set="vertical_gap" t-value="'margin-left: %.2fmm;' % data.get('col_gap', 0)"/>
<t t-else="" t-set="vertical_gap" t-value="''"/>
<!-- Label <div> block -->
<div t-att-style="data.get('label_style', '') + vertical_gap">
<t t-if="index &gt;= skip" t-call="garazd_product_label_pro.product_label_from_template"/>
</div>
<t t-set="index" t-value="index + 1"/>
</t>
</t>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,5 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_print_product_label_template_user,access_print_product_label_template_user,model_print_product_label_template,base.group_user,1,1,1,1
access_print_product_label_section_user,access_print_product_label_section_user,model_print_product_label_section,base.group_user,1,1,1,1
access_print_product_label_template_add_user,access_print_product_label_template_add_user,model_print_product_label_template_add,base.group_user,1,1,1,1
access_print_product_label_preview_user,access_print_product_label_preview_user,model_print_product_label_preview,base.group_user,1,1,0,1
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
2 access_print_product_label_template_user access_print_product_label_template_user model_print_product_label_template base.group_user 1 1 1 1
3 access_print_product_label_section_user access_print_product_label_section_user model_print_product_label_section base.group_user 1 1 1 1
4 access_print_product_label_template_add_user access_print_product_label_template_add_user model_print_product_label_template_add base.group_user 1 1 1 1
5 access_print_product_label_preview_user access_print_product_label_preview_user model_print_product_label_preview base.group_user 1 1 0 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,691 @@
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row mt-3">
<ul class="offset-3 col-6 text-left d-md-none list-unstyled pt-5">
<li class="border-top border-bottom"><div class="my-2 mx-sm-2" style="color: #6F649A;"><i class="fa fa-check-circle"></i> Community</div></li>
<li class="border-bottom"><div class="my-2 mx-sm-2" style="color: #694D65;"><i class="fa fa-check-circle"></i> Enterprise</div></li>
</ul>
<div class="col-12 text-right d-none d-md-block">
<span class="m-2" style="float: right; background-color: #694D65; border-color: #694D65; color: #FFFFFF; padding: 5px 18px; border-radius: 15px 0 15px 0; opacity: 1;"><i class="fa fa-check"></i> Enterprise</span>
<span class="m-2" style="float: right; background-color: #6F649A; border-color: #6F649A; color: #FFFFFF; padding: 5px 18px; border-radius: 15px 0 15px 0; opacity: 1;"><i class="fa fa-check"></i> Community</span>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="offset-2 col-8 offset-md-3 col-md-6 offset-lg-0 col-lg-2">
<div class="text-center shadow ml8">
<div class="s_panel_video" data-video-id="NqZMPPCRMOk?rel=0&start=0" style="cursor: pointer; width: 100%;" data-container="body" data-toggle="popover" data-placement="top" data-content="Click on the image to play the video.">
<img class="s_tooltip_tabs_tooltip_image s_figure_link img-fluid rounded-top pb-0" src="banner_youtube.png" alt="Product Label Builder youtube video tutorial">
</div>
<a href="https://youtu.be/NqZMPPCRMOk" target="_blank" class="btn btn-danger rounded-bottom p-1 w-100" role="button" style="width: 100%; font-size: 0.7rem;">Watch on Youtube <i class="fa fa-external-link"></i></a>
</div>
</div>
<div class="offset-xl-1 col-lg-9 d-flex d-column mt-5 mt-lg-0">
<h2 class="h1 align-self-center text-center" style="color: #154577; font-weight: 900; font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">Professional tool to build and print product labels</h2>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<div class="h2 text-left ml8" style="color: #FEA621;"><i class="fa fa-newspaper-o mr8"></i>Description</div>
<div class="media p-2 pt-4 overflow-hidden" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid;">
<div class="py-2 px-md-5 lead w-100" style="line-height: 2.5rem;"><p>
There are a lot of benefits of using the Odoo system, but sometimes the standard functionality is not enough for your business needs.
Printing product labels is one of the bottlenecks of the system, where there is a lack of a convenient tool with wide capabilities.
</p>
<p><b>Garazd Creation</b> has developed the tool that allows printing labels in the user-friendly interface with a variety of label options, create and modify own label templates for any paper format and design.</p>
<p>Odoo edit product label app "<b>Barcode Product Label Builder</b>" provides the following options to print product labels:</p>
<div style="line-height:2.5rem">
<div><i class="fa fa-fw fa-tags mr-2" style="color: #FEA621"></i> <b>Quantity management</b> per product label.</div>
<div><i class="fa fa-fw fa-money mr-2" style="color: #FEA621"></i> Choosing a <b>pricelist</b> to specify the product price and currency.</div>
<div><i class="fa fa-fw fa-print mr-2" style="color: #FEA621"></i> <a href="#odoo-label-printing-direct">Direct Odoo label printing</a>, without download.</div>
<div><i class="fa fa-fw fa-percent mr-2" style="color: #FEA621"></i> Special Price Sections: "<b>Promo Price</b>" - to show an additional sale price, "<b>Multi Price</b>" - to show prices from a pricelist based on minimal quantity.</div>
<div><i class="fa fa-fw fa-language mr-2" style="color: #FEA621"></i> <b>Language selection</b> to translate label text fields.</div>
<div><i class="fa fa-fw fa-file-o mr-2" style="color: #FEA621"></i> <b>Skipping free label places</b> when you print on the multi-label sheets.</div>
<div><i class="fa fa-fw fa-star mr-2" style="color: #FEA621"></i> And the main feature is the <a href="#odoo-product-label-builder">product label Builder</a>, which allows you to create and design label templates.</div>
<div><i class="fa fa-fw fa-plus mr-2" style="color: #FEA621"></i> <a href="#odoo-product-label-template">Six ready-made label templates</a> are included.</div>
</div>
<p class="mt-3">Labels that are created by this app, compatible with any thermal label printer which allows to print PDF files.</p>
<p class="mt-3">
Product Label Builder with a functional label print wizard changes the situation with product label printing in Odoo and provides a way to create and design labels on your own without developers.
In Odoo print product labels can be a convenient operation, so simple and fast.
</p>
</div>
</div>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<div class="h2 text-left ml8" style="color: #FEA621;"><i class="fa fa-check-square-o mr8"></i>Benefits</div>
<div class="s_features_grid p-2 pt-4 o_colored_level" data-snippet="s_features_grid" data-name="Features Grid" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid; background-color: #ffa81f14;">
<div class="container p-0">
<div class="row px-md-5">
<div class="col-lg-6 s_col_no_bgcolor pb24">
<div class="row">
<div class="col-lg-12 o_colored_level" data-name="Box">
<i class="s_features_grid_icon fa fa-2x float-left mr32 mr-3 bg-warning fa-smile-o rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center; float:left;"></i>
<div class="s_features_grid_content">
<div class="h4 pt-2">Easy &amp; Simple</div>
<p>Easy to activate and configure</p>
</div>
</div>
</div>
</div>
<div class="col-lg-6 s_col_no_bgcolor pb24">
<div class="row">
<div class="col-lg-12 o_colored_level" data-name="Box">
<i class="s_features_grid_icon fa fa-2x float-left mr32 mr-3 bg-warning fa-youtube-play rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center; float:left;"></i>
<div class="s_features_grid_content">
<div class="h4 pt-2">Watch Demo</div>
<p>Watch the video tutorial</p>
</div>
</div>
</div>
</div>
<div class="col-lg-6 s_col_no_bgcolor pb24">
<div class="row">
<div class="col-lg-12 o_colored_level" data-name="Box">
<i class="s_features_grid_icon fa fa-2x float-left mr32 mr-3 bg-warning fa-support rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center; float:left;"></i>
<div class="s_features_grid_content">
<div class="h4 pt-2">Support</div>
<p>Free 30 days support and 180 days bug-fixing</p>
</div>
</div>
</div>
</div>
<div class="col-lg-6 s_col_no_bgcolor pb24">
<div class="row">
<div class="col-lg-12 o_colored_level" data-name="Box">
<i class="s_features_grid_icon fa fa-2x float-left mr32 mr-3 bg-dark fa-search rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center; float:left;"></i>
<div class="s_features_grid_content">
<div class="h4 pt-2">Try me</div>
<p>Demo &amp; Test. Click on the "Live Preview" button</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<section class="oe_container" style="font-family: 'Montserrat','Odoo Unicode Support Noto',sans-serif; line-height: 2rem;">
<div class="h2 text-left ml8" style="color: #FEA621;"><i class="fa fa-graduation-cap mr8"></i>Advanced Settings</div>
<div class="media p-2 pt-4" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid;">
<div class="media-body">
<div class="py-2 px-md-5" style="line-height: 2rem;">
The professional version of the label printing wizard provides advanced label settings:
</div>
<ol class="py-2 mx-md-3">
<li>Select the label type "<b>Product Label from your own template</b>".</li>
<li>Then choose a label template.</li>
<blockquote>
There are 6 ready-made label templates after module installation: 2 <b>Dymo</b> labels, 2 on the <b>A4</b> paper format, 2 on the <b>Letter</b> paper format.
You can modify them for you needs, or clone.
</blockquote>
<li>To preview a label template, activate the switch "<b>Show Label</b>".</li>
</ol>
<div class="text-center my-5 mx-md-5">
<img src="print_product_label_pro_settings.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 product label settings">
</div>
<ol class="py-2 mx-md-3" start="4">
<li>Sometimes, if you are printing on A4 or Letter size sheets, you may want to skip the first few label spaces on the sheet, just enter the number of spaces to skip.</li>
<li>Select a pricelist to specify product prices on labels.</li>
<blockquote>
The currency will be changed according to the selected pricelist and the currency symbol will be placed before or after a product price, as it specified in settings.
</blockquote>
</ol>
<div class="h3 mt-4 p-2 pl-md-5">Label Options</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
You can also set some label options on the <b>Options</b> tab:
</div>
<ul class="py-2 mx-md-5">
<li><b>Language</b> - to specify the language to translate label fields.</li>
<li><b>Human readable barcode</b> - to print a barcode digit code on the labels.</li>
<li><b>Border</b> - to set the label border width.</li>
<blockquote>Set to <b>0</b> to print labels without border.</blockquote>
</ul>
<div class="text-center my-5 mx-md-5">
<img src="print_product_label_options.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 product label options">
</div>
<ul class="py-2 mx-md-5">
<li><b>Company</b> - you can specify the company if you use several companies in your Odoo database with this option, this company related data will be used on the labels.</li>
<li><b>Sales Pricelist</b> - product prices from this pricelist will be used on labels in "Promo Price" label sections.</li>
</ul>
<div class="h3 mt-4 p-2 pl-md-5">Label Printing</div>
<div class="py-2 px-md-5 mb-4" style="line-height: 2rem;">
Finally, you can preview the labels before printing, by clicking on the <span class="btn btn-sm btn-secondary"><i class="fa fa-search"></i> <b>PREVIEW</b></span> button,
or download labels in the PDF format with the <span class="btn btn-sm btn-secondary"><i class="fa fa-download"/></i> <b>DOWNLOAD</b></span> button, or print them with the <span class="btn btn-sm btn-primary"><i class="fa fa-print"/></i> <b>PRINT</b></span>
</div>
<div class="h3 mt-4 p-2 pl-md-5">Label Settings</div>
<div class="py-2 px-md-5 mb-4" style="line-height: 2rem;">
<p>If you want to use the custom print wizard, by clicking on the "Print Labels" button, instead of standard, go to the "General Settings" menu and activate the "Print with the alternative wizard" option.</p>
<p>Here, you can also configure the demo product and pricelists to preview label templates in the label designer.</p>
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_general_settings.png" class="w-100 w-lg-75 img-fluid rounded shadow-lg border" alt="Odoo product label settings for template preview in 18.0">
</div>
<div class="py-2 px-md-5 mb-4" style="line-height: 2rem;">
To open a label template list, go to the "Settings" - "Technical" - "Reporting" - "Label Templates" menu.
</div>
<div class="h3 mt-4 p-2 pl-md-5">User Defaults</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_user_default_template.png" class="w-100 w-lg-50 img-fluid rounded shadow-lg border" alt="User default label template setting in Odoo 18.0">
</div>
<div class="py-2 px-md-5 mb-4" style="line-height: 2rem;">
Specify the default label template for your system users in the user settings on the <b>Preferences</b> tab.
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<section class="oe_container" style="font-family: 'Montserrat','Odoo Unicode Support Noto',sans-serif; line-height: 2rem;" id="odoo-product-label-builder">
<div class="h2 text-left ml8" style="color: #FEA621;"><i class="fa fa-object-ungroup mr8"></i>Working with Label Templates</div>
<div class="media p-2 pt-4 bg-light" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid;">
<div class="media-body">
<div class="h3 mt-5 p-2 px-md-5">Creating a Label Template</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
Click on the <span class="btn-sm btn-primary"><i class="fa fa-plus"/> ADD</span> button to create a new label template.
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_add_template.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 create label template ">
</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
<p>To add a sticker label for a thermal printer, choose the paper format "Custom" and specify the label width and height (in mm).</p>
<p>In case when you need multi labels, several labels on a sheet, you must also specify the "Cols" and "Rows" parameters in the "Layout" section. Then enter the width and height of the custom page.</p>
<p>To add an A4 or US Letter label template, select the appropriate paper format and specify the "Cols" and "Rows" options for a sheet layout.</p>
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_add_dymo_template.png" class="w-100 w-lg-75 img-fluid rounded shadow-lg border" alt="Odoo 18.0 add label template ">
</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
Click on the <span class="btn-sm btn-primary"><i class="fa fa-plus"/> CREATE TEMPLATE</span> to continue with template settings.
</div>
<div class="h3 mt-5 p-2 px-md-5">Adding Label Sections</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
You can create label template sections and specify necessary parameters for them.
Sections can be of different types, we'll look at them next.
Let's create one by clicking on "<b>Sections</b>" button and creating a new record.
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_add_section.png" class="w-100 w-lg-75 img-fluid rounded shadow-lg border" alt="Odoo 18.0 add label template section">
</div>
<div class="h3 mt-5 p-2 px-md-5">Section Settings</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
The first section will be the "Product Name" with the following settings:
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_section_settings_data.png" class="w-100 w-lg-75 img-fluid rounded shadow-lg border" alt="Odoo 18.0 label data settings">
</div>
<ul class="py-2 mx-md-5">
<li><b>Type:</b> <code>Model Field</code></li>
<li><b>Field:</b> <code>Product</code></li>
<li><b>Relation Field:</b> <code>Name</code></li>
<div class="alert alert-info py-2 my-3" role="alert">
<p class="h5">Available section types:</p>
<p class="pt-3">Section type "<code>Text</code>" allows to print static text, like as "Price:", "Sale", "Special Offer", etc.</p>
<p>The "<code>Price</code>" section adds a product price with a currency symbol. You can specify the currency symbol position before or after the price value.</p>
<p>The "<code>Promo Price</code>" section allows you to put an additional promotional price on the label. In this case, you should specify the Sales Pricelist in the Label Print Wizard.</p>
<p>The "<code>Multi Price</code>" section is intended to display multiple prices for a product in case you use pricelist rules based on the minimum quantity.</p>
<div class="offset-2 col-8 offset-lg-3 col-lg-6 text-center my-5">
<img src="product_barcode_label_100x100mm_multi_price.png" class="w-100 w-lg-50 img-fluid rounded shadow-lg border" alt="Odoo barcode product sticker label 100 x 100 mm with several prices">
</div>
<p>Section type "<code>Model Field</code>" allows specifying any char or numeric field from the following label fields:</p>
<ul>
<li>Product (product.product)</li>
<li>Company (res.company)</li>
<li>Stock Transfer (stock.picking) - with <a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_picking/" target="_blank">this additional module</a></li>
<li>Purchase Order (purchaser.order) - with <a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_purchase/" target="_blank">this additional module</a></li>
<li>Product Packaging (product.packaging) - with <a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_packaging/" target="_blank">this additional module</a></li>
<li>Manufacturing Order (mrp.production) - with <a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_mrp/" target="_blank">this additional module</a></li>
</ul>
<p class="pt-3">The "<code>Product Attributes</code>" section type adds product attribute values to a label, like as: "Color: Black, Size: XL". Manage to include a product attribute name or not by the "<b>With Attribute Name</b>" option.</p>
<p>The "<code>Image</code>" section type gives you the option to place an image on your label.</p>
</div>
</ul>
<div class="py-2 px-md-5" style="line-height: 2rem;">
Let's continue with label design settings:
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_section_settings_design.png" class="w-100 w-lg-75 img-fluid rounded shadow-lg border" alt="Odoo 18.0 label design settings">
</div>
<ul class="py-2 mx-md-5">
<li><b>Height, mm:</b> <code>10</code></li>
<li><b>Float Position:</b> <code>Full Width</code></li>
<div class="alert alert-info py-2 my-3" role="alert">
<p>The other available values are <code>Left Side</code> and <code>Right Side</code>.</p>
<p>If you choose one of them, you can place multiple sections in one row and specify their width.</p>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_section_float.png" class="w-100 w-lg-50 img-fluid rounded shadow-lg border" alt="Odoo 18.0 label section float settings">
</div>
</div>
<li><b>Widget:</b>
<ul class="py-2 mx-md-5">
<li><code>Price</code> - show product price with a currency symbol and small cents, is used only for the "Product Price" field.</li>
<li><code>Barcode</code> - generate a section value as a barcode.</li>
<li><code>QR Code</code> - generate a QR code from the section value. Can be used with any text value.</li>
<li><code>Image</code> - show binary fields as an image. Can be used to show product images or other binary fields containing image data.</li>
</ul>
</li>
</ul>
<div class="py-2 px-md-5" style="line-height: 2rem;">
Next, let's take a look at what text settings are available:
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_section_settings_text.png" class="w-100 w-lg-75 img-fluid rounded shadow-lg border" alt="Odoo 18.0 label font and text settings">
</div>
<ul class="py-2 mx-md-5">
<li><b>Align:</b> <code>Center, Left, Right, Justify</code> values are available.</li>
<li><b>Font Weight:</b> can be <code>100, normal, bold, or 900</code>.</li>
<li><b>Font Size:</b> <code>20</code>, this value can be in pixels or mm.</li>
<li><b>Line Height:</b> <code>1.0</code>.</li>
<li><b>Letter Spacing:</b> <code>0</code>, can be positive (<code>2.5</code>) or negative (<code>-1</code>), in mm.</li>
<li><b>Color:</b> <code>#FFFFFF</code>. A handy color widget allows you to choose any color from the palette.</li>
<li>
<b>Format:</b> You have an ability to specify a format such as <code>%.2f</code>, <code>%d</code> for the <a href="https://docs.python.org/3/library/stdtypes.html#printf-style-bytes-formatting" target="_blank">digit values</a>,
and <code>%d/%m/%Y</code>, <code>%Y %B %d %H:%M:%S</code> for the <a href="https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes" target="_blank">date and datetime values</a>.
</li>
<li><b>Text Decoration:</b> <code>Line Through</code>. Use this option to cross out the regular price when you add a promotional price to a label, or in other cases.</li>
</ul>
<div class="py-2 px-md-5" style="line-height: 2rem;">
<p>In addition, on the "<b>Paddings</b>" tab, you can configure the section padding attributes (in mm).</p>
<p>On the "<b>Margins</b>" tab, you can specify the section margins (in mm).</p>
<p>On the "<b>Borders</b>" tab, add borders for any side of the section: <code>Top</code>, <code>Bottom</code>, <code>Left</code>, or <code>Right</code>, and specify the border width (in pixels).</p>
<p>On the "<b>Background</b>" tab, you can set up the section background.</p>
</div>
<div class="h3 mt-5 p-2 px-md-5">Shorten URL</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
In cases when you want to put a QR code with a URL on a label or just place a short URL, the URL shortening can provide additional benefits such as:
<ul class="py-2 mx-md-5">
<li>more quality QR code image (as the shorted URL text value is usually smaller);</li>
<li>tracking information about how often and when the URL was opened.</li>
</ul>
You can activate this option for label sections with the types "<code>Text</code>" or "<code>Model Field</code>":
<div class="text-center my-3">
<img src="product_label_shorten_url_option.png" class="w-75 w-lg-50 img-fluid rounded shadow-lg border" alt="Odoo product label section option shorten URL in 18.0">
</div>
This functionality is handled by the Odoo internal link tracker and allows getting a link like this one: <code>https://your-domain/r/aBc</code>.
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_shorten_url.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo Product Label Link Tracker shorten URL in 18.0">
</div>
<div class="h3 mt-5 p-2 px-md-5">Visibility and Order of Sections</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
By clicking the "<b>Sections</b>" button on the label template form, you can open a list of sections and perform actions with them:
</div>
<ol class="py-2 mx-md-5">
<li><b>Change order</b> - just drag and drop sections to set the order that you need.</li>
<li><b>Hide or Show</b> - switch the "Active" option to hide or show the section.</li>
</ol>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_section_list.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 template sections">
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<section class="oe_container" style="font-family: 'Montserrat','Odoo Unicode Support Noto',sans-serif; line-height: 2rem;">
<div class="h2 text-left ml8" style="color: #FEA621;"><i class="fa fa-search mr8"></i>Label Preview</div>
<div class="media p-2 pt-4" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid;">
<div class="media-body">
<div class="py-2 px-md-5" style="line-height: 2rem;">
<p>During label designing, activate the "Preview" option to show your product label without having to generate a PDF sheet.</p>
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_preview.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 barcode product label preview mode">
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<section class="oe_container" style="font-family: 'Montserrat','Odoo Unicode Support Noto',sans-serif; line-height: 2rem;" id="odoo-label-printing-direct">
<div class="h2 text-left ml8" style="color: #FEA621;"><i class="fa fa-print mr8"></i>Print Label Directly</div>
<div class="media p-2 pt-4" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid;">
<div class="media-body">
<div class="py-2 px-md-5" style="line-height: 2rem;">
<p>With this feature, you can print product labels directly to your local or network printer. You don't need to download the PDF before.
Just click on the "Print" button and the browser printing window will be opened, where you'll be able to select a printer.</p>
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_direct_print.png" class="w-100 img-fluid rounded shadow-lg border" alt="18.0 odoo direct print label">
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_label_builder_direct_print_window.jpg" class="w-lg-50 img-fluid rounded shadow-lg border" alt="18.0 odoo label printing directly">
</div>
<div class="py-2 px-md-5" style="line-height: 2rem;">
<p>For downloading the labels, use the corresponding "Download" button.</p>
<div class="alert alert-info py-2 my-3" role="alert">There are no external services or servers are required.</div>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<section class="oe_container" style="font-family: 'Montserrat','Odoo Unicode Support Noto',sans-serif; line-height: 2rem;" id="odoo-product-label-template">
<div class="h2 text-left ml8" style="color: #FEA621;"><i class="fa fa-barcode mr8"></i>Label Samples</div>
<div class="media p-2 pt-4 bg-light" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid;">
<div class="media-body">
<div class="py-2 px-md-5" style="line-height: 2rem;">
Labels will be generated in the PDF format:
</div>
<div class="h3 mt-4 p-2 pl-md-5">Sticker Labels for thermal printers (Dymo Labels)</div>
<div class="row px-md-3">
<div class="col-lg-6 mb-4">
<img src="product_barcode_label_50x25mm.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 dymo barcode product label 50 x 25 mm">
</div>
<div class="col-lg-6 mb-4">
<img src="product_barcode_label_100x100mm.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 dymo barcode product label 100 x 100 mm">
</div>
</div>
<div class="h3 mt-4 p-2 pl-md-5">Labels on the US Letter sheet</div>
<div class="row px-md-3">
<div class="col-lg-6 mb-4">
<img src="product_barcode_label_letter_101x50mm.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 us letter barcode product label 101 x 50 mm">
</div>
<div class="col-lg-6 mb-4">
<img src="product_barcode_label_letter_66x25mm.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 us letter barcode product label 66 x 25 mm">
</div>
</div>
<div class="h3 mt-4 p-2 pl-md-5">Labels on the A4 sheet</div>
<div class="row px-md-3">
<div class="col-lg-6 mb-4">
<img src="product_barcode_label_A4_99x38mm.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 A4 barcode product label 99 x 38 mm">
</div>
<div class="col-lg-6 mb-4">
<img src="product_barcode_label_A4_63x38mm.png" class="w-100 img-fluid rounded shadow-lg border" alt="Odoo 18.0 a4 barcode product label 63 x 38 mm">
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat','Odoo Unicode Support Noto',sans-serif;">
<div class="row oe_spaced">
<div class="col-12">
<div class="h2 text-left pl-2 pt-3" style="color: #FEA621; font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">Related Modules</div>
<div class="mb-3 bg-light" style="border-color:#FEA621 !important; border-radius: 0 15px 0 0; border-top-style: solid; border-right-style: solid;">
<div id="related_modules" class="carousel slide mt-2 mx-3" data-ride="carousel" data-interval="15000" data-pause="hover">
<div class="carousel-inner">
<div class="carousel-item row active" style="min-height: 0px;">
<div class="col-xs-12 col-sm-4 col-md-4 my-4 px-3" style="float:left">
<a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_picking/" target="_blank">
<div class="card shadow-sm border rounded deep-1 deep_hover">
<img class="img img-responsive center-block border-bottom w-100" src="banner_garazd_product_label_picking.png" alt="Odoo App Stock Product Labels" title="Stock Product Labels | Print custom product barcode labels for stock transfers | Product Barcode Labels Printing | Print Product Labels | Barcode Product Labels Printinf from Stock | Barcode labels for inventory | Label Inventory Tag">
<div class="card-body">
<div class="card-title h5 text-nowrap text-truncate" title="Stock Product Labels">Stock Product Labels</div>
<p class="card-text small text-nowrap text-truncate" title="Print custom product barcode labels for stock transfers | Product Barcode Labels Printing | Print Product Labels | Barcode Product Labels Printinf from Stock | Barcode labels for inventory | Label Inventory Tag">Print custom product barcode labels for stock transfers | Product Barcode Labels Printing | Print Product Labels | Barcode Product Labels Printinf from Stock | Barcode labels for inventory | Label Inventory Tag</p>
</div>
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 my-4 px-3" style="float:left">
<a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_repair/" target="_blank">
<div class="card shadow-sm border rounded deep-1 deep_hover">
<img class="img img-responsive center-block border-bottom w-100" src="banner_garazd_product_label_repair.png" alt="Odoo App Repair Product Labels" title="Repair Product Labels | Product Labels in Repair Management | Barcode Label Printing for Repair Operations">
<div class="card-body">
<div class="card-title h5 text-nowrap text-truncate" title="Repair Product Labels">Repair Product Labels</div>
<p class="card-text small text-nowrap text-truncate" title="Product Labels in Repair Management | Barcode Label Printing for Repair Operations">Product Labels in Repair Management | Barcode Label Printing for Repair Operations</p>
</div>
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 my-4 px-3" style="float:left">
<a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_purchase/" target="_blank">
<div class="card shadow-sm border rounded deep-1 deep_hover">
<img class="img img-responsive center-block border-bottom w-100" src="banner_garazd_product_label_purchase.png" alt="Odoo App Purchase Product Labels" title="Purchase Product Labels | Print custom product barcode labels for purchase orders | Barcode product label printing from purchase order">
<div class="card-body">
<div class="card-title h5 text-nowrap text-truncate" title="Purchase Product Labels">Purchase Product Labels</div>
<p class="card-text small text-nowrap text-truncate" title="Print custom product barcode labels for purchase orders | Barcode product label printing from purchase order">Print custom product barcode labels for purchase orders | Barcode product label printing from purchase order</p>
</div>
</div>
</a>
</div>
</div>
<div class="carousel-item row" style="min-height: 0px;">
<div class="col-xs-12 col-sm-4 col-md-4 my-4 px-3" style="float:left">
<a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_packaging/" target="_blank">
<div class="card shadow-sm border rounded deep-1 deep_hover">
<img class="img img-responsive center-block border-bottom w-100" src="banner_garazd_product_label_packaging.png" alt="Odoo App Product Packaging Labels" title="Product Packaging Labels | Custom Product Packagings Labels | Product Barcode Labels Printing | Print Product Labels">
<div class="card-body">
<div class="card-title h5 text-nowrap text-truncate" title="Product Packaging Labels">Product Packaging Labels</div>
<p class="card-text small text-nowrap text-truncate" title="Custom Product Packagings Labels | Product Barcode Labels Printing | Print Product Labels">Custom Product Packagings Labels | Product Barcode Labels Printing | Print Product Labels</p>
</div>
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 my-4 px-3" style="float:left">
<a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_mrp/" target="_blank">
<div class="card shadow-sm border rounded deep-1 deep_hover">
<img class="img img-responsive center-block border-bottom w-100" src="banner_garazd_product_label_mrp.png" alt="Odoo App MRP Product Labels" title="MRP Product Labels | Custom Product Labels in Manufacturing | Barcode Product Labels Printinf from Manufacturing">
<div class="card-body">
<div class="card-title h5 text-nowrap text-truncate" title="MRP Product Labels">MRP Product Labels</div>
<p class="card-text small text-nowrap text-truncate" title="Custom Product Labels in Manufacturing | Barcode Product Labels Printinf from Manufacturing">Custom Product Labels in Manufacturing | Barcode Product Labels Printinf from Manufacturing</p>
</div>
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 my-4 px-3" style="float:left">
<a href="https://apps.odoo.com/apps/modules/18.0/garazd_product_label_maintenance/" target="_blank">
<div class="card shadow-sm border rounded deep-1 deep_hover">
<img class="img img-responsive center-block border-bottom w-100" src="banner_garazd_product_label_maintenance.png" alt="Odoo App Maintenance Barcode Labels" title="Maintenance Barcode Labels | Maintenance Labels">
<div class="card-body">
<div class="card-title h5 text-nowrap text-truncate" title="Maintenance Barcode Labels">Maintenance Barcode Labels</div>
<p class="card-text small text-nowrap text-truncate" title="Maintenance Labels">Maintenance Labels</p>
</div>
</div>
</a>
</div>
</div>
</div>
<!-- Control arrows -->
<a class="carousel-control-prev w-0" href="#related_modules" data-slide="prev" style="color: #FEA621;">
<span class="carousel-control-prev-icon"><i class="fa fa-chevron-left fa-2x"></i></span>
</a>
<a class="carousel-control-next w-0" href="#related_modules" data-slide="next" style="color: #FEA621;">
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right fa-2x"></i></span>
</a>
</div>
</div>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif; background-color: #FEA621; border-color: #fea621; padding: 12px 28px; border-radius: 15px 0 15px 0;">
<div class="row my-2">
<div class="col-lg-4 text-center mb-5 mb-lg-0">
<div class="h3 p-2 mb-2 font-weight-bold">Contact Us</div>
<div class="lead text-white pb-2">Support, customizations, and development</div>
<div class="w-100 w-md-75 w-lg-100 my-1 pl-3 text-nowrap text-truncate btn btn-dark rounded">
<i class="fa fa-envelope fa-fw" style="font-size:1.2rem;"></i>
<a href="mailto:support@garazd.biz" target="_blank">support@garazd.biz</a>
</div>
<div class="w-100 w-md-75 w-lg-100 my-1 pl-3 text-nowrap text-truncate btn btn-dark rounded">
<i class="fa fa-skype fa-fw" style="font-size:1.2rem;"></i>
<span>Skype: </span>
<a href="skype:live:7f0648d653072c5f?chat" target="_blank">GarazdCreation</a>
</div>
<div class="w-100 w-md-75 w-lg-100 m-auto my-1 pl-3 text-nowrap text-truncate btn-light border border-dark rounded" style="line-height: 2.4rem;">
<a href="https://garazd.biz/contactus" target="_blank">https://garazd.biz/contactus</a>
</div>
</div>
<div class="col-12 col-lg-4 border-left border-right">
<div class="h3 p-2 mb-3 text-center font-weight-bold">Our expertise</div>
<div class="text-left">
<div class="my-3 pl-3 text-nowrap text-truncate lead text-white" data-container="body" data-toggle="popover" data-placement="top" data-content="Garazd Creation is official Odoo Partner with the status 'Learning Partner'.">
<i class="fa fa-handshake-o fa-fw"></i> Odoo Learning Partner
</div>
<div class="my-3 pl-3 text-nowrap text-truncate lead text-white" data-container="body" data-toggle="popover" data-placement="top" data-content="Odoo Community Association member.">
<i class="fa fa-group fa-fw"></i> OCA Member
</div>
<div class="my-3 pl-3 text-nowrap text-truncate lead text-white" data-container="body" data-toggle="popover" data-placement="top" data-content="Garazd Creation with Odoo since 2014.">
<i class="fa fa-calendar-check-o fa-fw"></i> With Odoo since 2014
</div>
<div class="my-3 pl-3 lead text-white" data-container="body" data-toggle="popover" data-placement="top" data-content="Over 20,000 app downloads and purchases in Odoo Apps Store.">
<i class="fa fa-download fa-fw"></i> Over 20,000 app downloads and purchases
</div>
<div class="my-3 pl-3 lead text-white" data-container="body" data-toggle="popover" data-placement="top" data-content="Our apps in the TOP 10 on Odoo Apps.">
<i class="fa fa-trophy fa-fw"></i> Our apps in the TOP 10 on Odoo Apps
</div>
</div>
</div>
<div class="col-12 col-lg-4 mt-5 mt-lg-0">
<div class="h3 p-2 text-center font-weight-bold">Explore our apps</div>
<div class="text-center mb-3">
<a href="https://apps.odoo.com/apps/browse?repo_maintainer_id=119796" target="_blank">
<div class="text-black bg-warning shadow-sm rounded">
<i class="fa fa-compass fa-4x py-0"></i>
</div>
</a>
</div>
<div class="h4 p-2 text-center font-weight-bold">Watch and subscribe</div>
<div class="text-center">
<a href="https://www.youtube.com/channel/UCjHB3t-qc6SQmbgnS8bSPOA?sub_confirmation=1" target="_blank">
<div class="text-black bg-warning shadow-sm rounded">
<i class="fa fa-youtube-play fa-4x py-0"></i>
</div>
</a>
</div>
</div>
</div>
</section>
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
<div class="row mt-5 mb-4 align-items-center">
<div class="col-6 text-left">Version: 18.0.1.1.0</div>
<div class="col-6 text-right" style="text-align: right;"><small>Module design is reserved</small> | Copyright &copy; <a href="https://garazd.biz/" target="_blank">Garazd Creation</a></div>
<section class="oe_container col-12 mt-5" style="font-family: 'Montserrat','Odoo Unicode Support Noto',sans-serif; line-height: 1.75rem;">
<div class="h2 text-left ml8 mr32" style="color: #FEA621;"><i class="fa fa-code-fork mr8"></i>Changelog</div>
<div class="media p-2 pt-4" style="border-color:#FEA621 !important; border-radius: 15px; border: solid;">
<small class="media-body">
<ul class="list-unstyled">
<li class="media">
<div class="media-body mb-4">
<div class="h5 ml32 my-1" style="font-size: 1.0rem;">
<span class="font-weight-bold bg-dark text-white rounded py-1 px-3">18.0.1.1.0</span>
<span class="text-muted ml16">2024-12-13</span>
</div>
<ul class="mt-2">
<li class="mx-3"><small>Improve views with the "invisible" fields.</small></li>
<li class="mx-3"><small>Improve processing of the binary fields for nested relations.</small></li>
<li class="mx-3"><small>Fix adding of the horizontal gap between labels for multi-label sheets.</small></li>
<li class="mx-3"><small>Improve previewing of label sections - skip inactive sections.</small></li>
<li class="mx-3"><small>Add the option to specify allowed product attributes for the "Product Attributes" type of label sections.</small></li>
<li class="mx-3"><small>Add the parameter "garazd_product_label_pro.barcode_multiplier" to enhance the barcode quality.</small></li>
<li class="mx-3"><small>Add checking that a label section has a value before the post processing.</small></li>
</ul>
</div>
</li>
<li class="media">
<div class="media-body mb-4">
<div class="h5 ml32 my-1" style="font-size: 1.0rem;">
<span class="font-weight-bold bg-dark text-white rounded py-1 px-3">18.0.1.0.1</span>
<span class="text-muted ml16">2024-12-11</span>
</div>
<ul class="mt-2">
<li class="mx-3"><small>Fix tests.</small></li>
</ul>
</div>
</li>
<li class="media">
<div class="media-body mb-4">
<div class="h5 ml32 my-1" style="font-size: 1.0rem;">
<span class="font-weight-bold bg-dark text-white rounded py-1 px-3">18.0.1.0.0</span>
<span class="text-muted ml16">2024-10-22</span>
</div>
<ul class="mt-2">
<li class="mx-3"><small>Migration from 17.0.</small></li>
</ul>
</div>
</li>
</ul>
</div>
</small>
</div>
</section>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1 @@
from . import test_access_rights

View File

@@ -0,0 +1,45 @@
# Copyright © 2023 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/16.0/legal/licenses.html).
from odoo.tests.common import TransactionCase
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestProductLabel(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.label_template_50x25 = cls.env['print.product.label.template'].create({
'name': 'Test Label',
'paperformat_id': cls.env.ref('garazd_product_label_pro.paperformat_label_custom_50x25').id,
'orientation': 'Portrait',
'cols': 1,
'rows': 1,
'width': 50,
'height': 25,
})
cls.product_a = cls.env['product.product'].create({
'name': 'Test Product A',
'type': 'consu',
'list_price': 20.0,
'barcode': '1234567890',
})
cls.product_b = cls.env['product.product'].create({
'name': 'Test Product B',
'type': 'consu',
'list_price': 199.99,
'barcode': '9999999999999',
})
def setUp(self):
super(TestProductLabel, self).setUp()
self.print_wizard = self.env['print.product.label'].with_context(**{
'active_model': 'product.product',
'default_product_product_ids': [self.product_a.id, self.product_b.id],
}).create({})

View File

@@ -0,0 +1,27 @@
# Copyright © 2023 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/16.0/legal/licenses.html).
from odoo.tests import tagged
from odoo.addons.base.tests.common import BaseUsersCommon
from .common import TestProductLabel
@tagged('post_install', '-at_install')
class TestAccessRights(BaseUsersCommon, TestProductLabel):
def test_access_internal_user(self):
""" Test internal user's access rights """
PrintWizard = self.env['print.product.label'].with_user(self.user_internal)
wizard_as_internal_user = PrintWizard.browse(self.print_wizard.id)
# Internal user can use label templates
wizard_as_internal_user.read()
# Internal user can change label templates
wizard_as_internal_user.write({'template_id': self.label_template_50x25.id})
# Internal user can preview label templates
wizard_as_internal_user.action_print()

View File

@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="print_product_label_section_view_form" model="ir.ui.view">
<field name="name">print.product.label.section.view.form</field>
<field name="model">print.product.label.section</field>
<field name="arch" type="xml">
<form>
<sheet>
<widget name="web_ribbon" text="Archived" bg_color="bg-danger" invisible="active"/>
<field name="template_preview_html" class="oe_avatar" invisible="not preview"/>
<div class="oe_title">
<h1><div class="d-flex">
<field name="display_name" class="text-break"/>
</div></h1>
</div>
<div name="options">
<div class="d-inline-block">
<label for="preview" class="mr8"/>
<field name="preview" widget="boolean_toggle"/>
<button name="action_pdf_preview" type="object" string="PDF Preview" icon="fa-file-pdf-o" class="border btn btn-sm btn-light ms-2"/>
</div>
</div>
<field name="active" invisible="1"/> <!-- Technical field for the web ribbon -->
<field name="field_ttype" invisible="1"/> <!-- Technical field for domain -->
<field name="field_ids" invisible="1"/> <!-- Technical field for domain " TODO: remove ? -->
<field name="relation_model_id" invisible="1"/> <!-- Technical field for domain -->
<field name="relation_field_ttype" invisible="1"/> <!-- Technical field for domain -->
<field name="nested_relation_model_id" invisible="1"/> <!-- Technical field for domain -->
<group>
<group string="Data">
<field name="type" />
<field name="value" invisible="type != 'text'"/>
<field name="image" invisible="type != 'image'" required="type == 'image'"/>
<field name="field_id"
invisible="type != 'field'"
options="{'no_open': True, 'no_create': True}"/>
<label for="relation_field_id" invisible="type != 'field' or type == 'field' and field_ttype not in ['many2one', 'many2many', 'one2many']" required="type == 'field' and field_ttype in ['many2one', 'many2many', 'one2many']"/>
<div class="o_row" invisible="type != 'field' or type == 'field' and field_ttype not in ['many2one', 'many2many', 'one2many']" required="type == 'field' and field_ttype in ['many2one', 'many2many', 'one2many']">
<field name="relation_field_id"
domain="[('model_id', '=', relation_model_id), ('ttype', 'in', ['binary', 'char', 'text', 'html', 'selection', 'float', 'monetary', 'integer', 'date', 'datetime', 'many2one'])]"
options="{'no_open': True, 'no_create': True}"/>
<field name="nested_relation_field_id"
invisible="type != 'field' or type == 'field' and relation_field_ttype not in ['many2one']"
required="type == 'field' and relation_field_ttype in ['many2one']"
domain="[('model_id', '=', nested_relation_model_id), ('ttype', 'in', ['binary', 'char', 'text', 'html', 'selection', 'float', 'monetary', 'integer', 'date', 'datetime'])]"
options="{'no_open': True, 'no_create': True}"/>
</div>
<!-- Options for section types -->
<field name="currency_position" invisible="type not in ['price', 'promo_price', 'multi_price']"/>
<field name="multi_price_order" string="Order" invisible="type != 'multi_price'"/>
<field name="multi_price_limit" string="Limit" invisible="type != 'multi_price'"/>
<field name="with_product_attribute_name" widget="boolean_toggle" invisible="type != 'product_attributes'"/>
<field name="attribute_ids" widget="many2many_tags" invisible="type != 'product_attributes'" options="{'no_open': True, 'no_create': True}"/>
</group>
<group string="Design">
<field name="height"/>
<field name="position"/>
<label for="width" invisible="position == 'none'"/>
<div class="o_row" invisible="position == 'none'">
<field name="width" required="position != 'none'"/>
<field name="width_measure" />
</div>
<label for="widget" invisible="type not in ['text', 'field', 'product_attributes']"/>
<div class="o_row" invisible="type not in ['text', 'field', 'product_attributes']">
<field name="widget"/>
<label for="barcode_is_humanreadable" string="human-readable" invisible="widget != 'barcode'" class="ms-2"/>
<field name="barcode_is_humanreadable" widget="boolean_toggle" invisible="widget != 'barcode'"/>
<label for="barcode_symbology" string="type" invisible="widget != 'barcode'" class="ms-1"/>
<field name="barcode_symbology" widget="selection" invisible="widget != 'barcode'"/>
</div>
</group>
</group>
<notebook>
<page string="Text" name="text_setting">
<group>
<group>
<field name="align" widget="radio" options="{'horizontal': true}"/>
<field name="font_weight" widget="radio" options="{'horizontal': true}"/>
<field name="font_name" string="Font"/>
<small class="o_row alert alert-info" colspan="2" role="alert" invisible="not font_name">Note: Fonts are not applied on the sample label. Use the PDF Preview to see the actual generated labels.</small>
<label for="font_size"/>
<div class="o_row">
<field name="font_size" />
<field name="font_size_measure" />
</div>
<field name="line_height"/>
<field name="letter_spacing"/>
<field name="text_color" string="Color" widget="color"/>
</group>
<group>
<field name="value_format" invisible="type not in ['field', 'price', 'promo_price', 'multi_price'] or widget != False"/>
<field name="text_decoration"/>
<separator string="URL values" colspan="2"/>
<field name="make_url_absolute" widget="boolean_toggle" invisible="type not in ['text', 'field']"/>
<field name="shorten_url" widget="boolean_toggle" invisible="type not in ['text', 'field']"/>
</group>
</group>
</page>
<page string="Paddings" name="padding">
<group col="4" colspan="2">
<group col="2" colspan="2">
<label for="padding_top" string="Top"/>
<div>
<field name="padding_top" class="oe_inline"/> mm
</div>
<label for="padding_bottom" string="Bottom"/>
<div>
<field name="padding_bottom" class="oe_inline"/> mm
</div>
</group>
<group col="2" colspan="2">
<label for="padding_left" string="Left"/>
<div>
<field name="padding_left" class="oe_inline"/> mm
</div>
<label for="padding_right" string="Right"/>
<div>
<field name="padding_right" class="oe_inline"/> mm
</div>
</group>
</group>
</page>
<page string="Margins" name="margin">
<group col="4" colspan="2">
<group col="2" colspan="2">
<label for="margin_top" string="Top"/>
<div>
<field name="margin_top" class="oe_inline"/> mm
</div>
<label for="margin_bottom" string="Bottom"/>
<div>
<field name="margin_bottom" class="oe_inline"/> mm
</div>
</group>
<group col="2" colspan="2">
<label for="margin_left" string="Left"/>
<div>
<field name="margin_left" class="oe_inline"/> mm
</div>
<label for="margin_right" string="Right"/>
<div>
<field name="margin_right" class="oe_inline"/> mm
</div>
</group>
</group>
</page>
<page string="Borders" name="border">
<group col="4" colspan="2">
<group col="2" colspan="2">
<field name="with_border_top" string="Top" widget="boolean_toggle"/>
<field name="with_border_bottom" string="Bottom" widget="boolean_toggle"/>
</group>
<group col="2" colspan="2">
<field name="with_border_left" string="Left" widget="boolean_toggle"/>
<field name="with_border_right" string="Right" widget="boolean_toggle"/>
</group>
<group col="2" colspan="2" invisible="not with_border_top and not with_border_left and not with_border_right and not with_border_bottom">
<field name="border_width" string="Width, px"/>
</group>
</group>
</page>
<page string="Background" name="background">
<group>
<group>
<field name="with_background" widget="boolean_toggle"/>
<field name="background_color" widget="color" invisible="not with_background"/>
</group>
</group>
</page>
<page string="Value Processing" name="post_processing">
<group>
<group>
<field name="value_prefix"/>
<field name="value_suffix"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="print_product_label_section_view_tree" model="ir.ui.view">
<field name="name">print.product.label.section.view.list</field>
<field name="model">print.product.label.section</field>
<field name="arch" type="xml">
<list decoration-info="type=='text' and not widget"
decoration-danger="widget=='price' or type=='promo_price'"
decoration-success="widget in ['barcode', 'qr_code']"
decoration-warning="type=='image' or widget=='image'"
decoration-muted="not active">
<field name="sequence" widget="handle"/>
<field name="display_name"/>
<field name="font_name" string="Font" optional="hide"/>
<field name="position" optional="show"/>
<field name="type" optional="hide"/>
<field name="field_id" optional="hide"/>
<field name="relation_field_id" optional="hide"/>
<field name="widget" optional="hide"/>
<field name="value_format" optional="hide"/>
<field name="align" optional="show"/>
<field name="font_size" optional="show"/>
<field name="font_size_measure" optional="hide"/>
<field name="line_height" optional="show"/>
<field name="font_weight" optional="show"/>
<field name="height" sum="Total Label Height" optional="show"/>
<field name="active" widget="boolean_toggle" optional="show"/>
</list>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_product_label_section_list" model="ir.actions.act_window">
<field name="type">ir.actions.act_window</field>
<field name="name">Sections</field>
<field name="res_model">print.product.label.section</field>
<field name="view_mode">list,form</field>
<field name="context">{'default_template_id': active_id, 'active_test': False}</field>
<field name="domain">[('template_id', '=', active_id)]</field>
</record>
<record id="print_product_label_template_view_form" model="ir.ui.view">
<field name="name">print.product.label.template.view.form</field>
<field name="model">print.product.label.template</field>
<field name="arch" type="xml">
<form create="false" duplicate="false">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="%(action_product_label_section_list)d"
type="action"
class="oe_stat_button"
icon="fa-bars"
title="Sections"
>
<field name="section_count" string="Sections" widget="statinfo"/>
</button>
</div>
<field name="active" invisible="1"/> <!-- Technical field for the web ribbon -->
<field name="format" invisible="1"/> <!-- Technical field for domain -->
<field name="is_oversized" invisible="1"/> <!-- Technical field for domain -->
<widget name="web_ribbon" text="Archived" bg_color="bg-danger" invisible="active"/>
<field name="preview_html" class="oe_avatar" invisible="not preview"/>
<div class="oe_title">
<h1>
<div class="d-flex">
<field class="text-break" name="name"/>
</div>
</h1>
<div>
<label for="type_id" class="mr16"/>
<field name="type_id" widget="selection"/>
</div>
</div>
<div name="options">
<h2 class="d-flex gap-3 g-0 align-items-end">
<div>
<label for="width" string="Width" class="mr16"/>
<field name="width"/>
</div>
<div>
<label for="height" string="Height" class="mr16"/>
<field name="height"/>
</div>
</h2>
<div class="d-inline-block">
<label for="preview" class="mr8"/>
<field name="preview" widget="boolean_toggle"/>
<button name="action_pdf_preview" type="object" string="PDF Preview" icon="fa-file-pdf-o" class="border btn btn-sm btn-light rounded ms-2"/>
</div>
</div>
<notebook>
<page string="Sections" name="sections">
<field name="section_ids" mode="list">
<list create="false"
decoration-info="type=='text' and not widget"
decoration-danger="widget=='price' or type=='promo_price'"
decoration-success="widget in ['barcode', 'qr_code']"
decoration-warning="type=='image' or widget=='image'">
<field name="sequence" widget="handle"/>
<field name="display_name" string="Name"/>
<field name="font_name" string="Font" optional="hide"/>
<field name="widget" optional="hide"/>
<field name="position" optional="show"/>
<field name="type" optional="show"/>
<field name="field_name" invisible="type != 'field'" optional="hide"/>
<field name="value" invisible="type != 'text'" optional="hide"/>
<field name="with_background" optional="hide" width="0.5"/>
<field name="height" sum="Total Label Height" width="0.5" optional="show"/>
</list>
</field>
</page>
<page string="Page" name="page_settings">
<group col="4" colspan="2">
<group string="General" name="general" col="2" colspan="2">
<field name="paperformat_id" options="{'no_create': True}"/>
<field name="orientation" groups="base.group_system"/>
</group>
<group string="Margins, mm" name="page_margin_settings" col="2" colspan="2" groups="base.group_system">
<group>
<field name="margin_top" string="Top"/>
<field name="margin_bottom" string="Bottom"/>
</group>
<group>
<field name="margin_left" string="Left"/>
<field name="margin_right" string="Right"/>
</group>
</group>
<group string="Layout" col="2" colspan="2">
<field name="cols"/>
<field name="rows"/>
</group>
<group string="Gap between labels, mm" col="2" colspan="2">
<field name="col_gap" invisible="cols &lt;= 1"/>
<field name="row_gap" invisible="rows &lt;= 1"/>
</group>
</group>
</page>
<page string="Paddings" name="padding">
<group col="4" colspan="2">
<group col="2" colspan="2">
<label for="padding_top" string="Top"/>
<div><field name="padding_top" class="oe_inline"/> mm</div>
<label for="padding_bottom" string="Bottom"/>
<div><field name="padding_bottom" class="oe_inline"/> mm</div>
</group>
<group col="2" colspan="2">
<label for="padding_left" string="Left"/>
<div><field name="padding_left" class="oe_inline"/> mm</div>
<label for="padding_right" string="Right"/>
<div><field name="padding_right" class="oe_inline"/> mm</div>
</group>
</group>
</page>
<page string="Technical" name="technical" groups="base.group_no_one">
<group class="o_label_nowrap">
<div class="oe_row text-warning" colspan="2">Be aware specifying custom HTML styles, it can break the label generation!</div>
<field name="label_style" placeholder="For example, background-color: #FFFF00; border: 2px solid #000;"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="print_product_label_template_view_tree" model="ir.ui.view">
<field name="name">print.product.label.template.view.list</field>
<field name="model">print.product.label.template</field>
<field name="arch" type="xml">
<list create="false" decoration-muted="not active">
<field name="sequence" widget="handle"/>
<field name="type_id" optional="show"/>
<field name="name"/>
<field name="preview_html" string="Preview" optional="hide"/>
<field name="orientation" optional="hide"/>
<field name="width" optional="show"/>
<field name="height" optional="show"/>
<field name="paperformat_id" optional="show"/>
<field name="cols" optional="show"/>
<field name="rows" optional="show"/>
<field name="section_count" sum="Total" optional="show"/>
<field name="active" widget="boolean_toggle" optional="show"/>
</list>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.garazd_product_label_pro</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="garazd_product_label.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//setting[@id='product_label_printing_wizard']" position="after">
<setting id="product_label_preview_product_setting" string="Label Template Preview" help="Define default values to preview label templates">
<div class="content-group">
<div class="mt16">
<label for="print_label_preview_type" string="Use in Preview" class="col-3 col-lg-3 o_light_label"/>
<field name="print_label_preview_type" class="o_light_label" required="True" widget="radio"/>
</div>
<div class="mt16" invisible="print_label_preview_type != 'demo_product'">
<label for="print_label_preview_product_id" string="Demo Product" class="col-3 col-lg-3 o_light_label"/>
<field name="print_label_preview_product_id" class="o_light_label" required="print_label_preview_type == 'demo_product'"/>
</div>
<div class="mt16">
<label for="print_label_preview_pricelist_id" string="Pricelist" class="col-3 col-lg-3 o_light_label"/>
<field name="print_label_preview_pricelist_id" class="o_light_label" options="{'no_create': True}"/>
</div>
<div class="mt16">
<label for="print_label_preview_sale_pricelist_id" string="Promo Pricelist" class="col-3 col-lg-3 o_light_label"/>
<field name="print_label_preview_sale_pricelist_id" class="o_light_label" options="{'no_create': True}"/>
</div>
</div>
</setting>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.form.inherit.garazd_product_label_pro</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<group string="Product Labels">
<field name="print_label_template_id" options="{'no_create': True}" required="print_label_directly"/>
<field name="print_label_allowed_template_ids" widget="many2many_tags"/>
<field name="print_label_directly" widget="boolean_toggle"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="label_preview" name="Product Label Preview">
<div t-att-style="back_style">
<div t-att-style="label_style">
<div name="transform_div">
<t t-foreach="sections" t-as="section">
<t t-set="value" t-value="section.get_value(label)"/>
<t t-set="section_height" t-value="section.height - section.padding_top - section.padding_bottom"/>
<div t-att-style="'%s%s' % (section.get_html_style(), 'opacity: 0.3;' if editable_section_id and section.id != editable_section_id else '')">
<t t-if="section.type == 'product_attributes'">
<t t-if="section.widget == 'attribute_list'">
<div t-foreach="section.get_attribute_data(label)" t-as="attr">
<span t-if="section.with_product_attribute_name" t-out="attr['name']" class="mx-1"/>
<t t-foreach="attr['values']" t-as="attr_value">
<span t-att-class="'badge rounded bg-%s text-%s mr-1' % ('dark' if attr_value['active'] else 'light', 'light' if attr_value['active'] else 'dark')">
<t t-out="attr_value['name']"/>
</span>
</t>
</div>
</t>
<div t-else="" t-foreach="section.get_attribute_data(label)" t-as="attr" class="d-inline-flex flex-wrap">
<span class="mw-100 mx-1">
<t t-if="section.with_product_attribute_name" class="mr-1"><t t-out="attr['name']"/>: </t>
<t t-foreach="attr['values']" t-as="attr_value">
<span t-if="attr_value['active']" t-out="attr_value['name']" style="padding-left: 3px;"/>
</t>
</span>
</div>
</t>
<div t-if="section.type == 'multi_price'">
<t t-foreach="value" t-as="price">
<t t-out="price['amount']"/>
<small>(from <t t-out="price['qty']"/> <t t-out="label.product_id.uom_id.name"/>)</small>
<br/>
</t>
</div>
<t t-elif="section.widget == 'barcode'">
<img t-if="value" t-att-src="'/report/barcode/%s/%s?quiet=0&amp;humanreadable=%d' % (section.barcode_symbology, value, int(section.barcode_is_humanreadable))" style="width:85%;height:100%;"/>
<div t-else="" t-attf-style="width:100%;height:{{ section_height }}mm;background-image:url(/garazd_product_label_pro/static/img/no-barcode.png);background-size:contain;background-repeat:no-repeat;background-position:center;"/>
</t>
<t t-elif="section.widget == 'price'">
<div>
<t t-esc="label.currency_id.symbol"/>&amp;nbsp;
<span class="oe_currency_value" t-esc="'%.0f.' % label.price"/><span class="oe_currency_value" style="font-size:0.5em">00</span>
</div>
</t>
<t t-elif="section.widget == 'qr_code'">
<t t-set="qr_code_size_px" t-value="section.height * section.template_id.ratio_px_in_mm"/>
<img t-att-src="'/report/barcode/?barcode_type=QR&amp;value=%s&amp;width=%d&amp;height=%d'%(value, qr_code_size_px, qr_code_size_px)" t-attf-style="display: block; height: 100%; width: {{ section_height }}mm;"/>
</t>
<t t-elif="section.widget == 'image'">
<div t-attf-style="width:100%;height:100%;background-image:url({{ section.get_image_url(label) }});background-size:contain;background-repeat:no-repeat;background-position:center;"/>
</t>
<t t-elif="section.type == 'image'">
<div t-attf-style="width:100%;height:100%;background-image:url(/web/image/print.product.label.section/{{ section.id }}/image);background-size:contain;background-repeat:no-repeat;background-position:center;"/>
</t>
<t t-else="">
<div t-esc="value" style="white-space:normal" t-att-title="value"/>
</t>
</div>
</t>
</div>
</div>
</div>
</template>
</odoo>

View File

@@ -0,0 +1,5 @@
from . import print_product_label
from . import print_product_label_line
from . import print_product_label_template_add
from . import product_label_layout
from . import print_product_label_preview

View File

@@ -0,0 +1,216 @@
# Copyright © 2023 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/15.0/legal/licenses.html).
from typing import List
from odoo import _, api, Command, fields, models
from odoo.exceptions import UserError
class PrintProductLabel(models.TransientModel):
_inherit = "print.product.label"
report_id = fields.Many2one(
default=lambda self: self.env.ref('garazd_product_label.action_report_product_label_from_template'),
)
template_id = fields.Many2one(
comodel_name='print.product.label.template',
# flake8: noqa: E501
default=lambda self: self.env.user.print_label_template_id or self.env['print.product.label.template'].search([], limit=1),
)
allowed_template_ids = fields.Many2many(
comodel_name='print.product.label.template',
compute='_compute_allowed_template_ids',
help='Technical field to restrict allowed label templates.',
)
allowed_template_count = fields.Integer(compute='_compute_allowed_template_ids', help='Technical field.')
template_preview_html = fields.Html(
compute='_compute_template_preview_html',
compute_sudo=True,
)
label_template_preview = fields.Boolean(help='Show Label Template Sample.')
pricelist_id = fields.Many2one(
comodel_name='product.pricelist',
)
sale_pricelist_id = fields.Many2one(
comodel_name='product.pricelist',
string='Sales Pricelist',
help='Specify this second pricelist to put one more product price to a label.',
)
skip_place_count = fields.Integer(
string='Skip Places',
default=0,
help='Specify how many places for labels should be skipped on printing. This can'
' be useful if you are printing on a sheet with labels already printed.',
)
label_type_id = fields.Many2one(
help='You can filter label templates by selecting their type. It makes sense if you use '
'additional extensions to print labels not for products only but for other objects as well. '
'Like as Stock Packages, Sales Orders, Manufacturing Orders, etc. '
'>>> To view available extensions go to the "Actions" menu and click to the "Get Label Extensions".',
# default=lambda self: self.env.ref('garazd_product_label.type_product'),
# required=True,
)
show_template_limit = fields.Integer(compute='_compute_allowed_template_ids')
@api.depends('label_type_id')
def _compute_allowed_template_ids(self):
for wizard in self:
user_allowed_templates = self.env['print.product.label.template']._get_user_allowed_templates()
allowed_templates = user_allowed_templates.filtered(lambda lt: lt.type_id == wizard.label_type_id)
## Add templates without the specified type
# if wizard.mode == 'product.product':
# allowed_templates += user_allowed_templates.filtered(lambda lt: not lt.type_id)
wizard.allowed_template_ids = [Command.set(allowed_templates.ids)]
wizard.allowed_template_count = len(allowed_templates)
# flake8: noqa: E501
wizard.show_template_limit = self.env['ir.config_parameter'].sudo().get_param('garazd_product_label.show_label_template_limit', 7)
@api.depends('template_id', 'pricelist_id', 'sale_pricelist_id', 'lang')
def _compute_template_preview_html(self):
for wizard in self:
products = wizard.label_ids.mapped('product_id')
wizard.template_id.with_context(print_product_id=products[:1].id)._compute_preview_html()
wizard.template_preview_html = wizard.with_context(**{
'print_wizard_id': wizard.id, # It allows previewing real products on label designing
'preview_mode': True, # It's used to avoid generating of a shorten URL
'pricelist_id': wizard.pricelist_id.id, # It's used for previewing on label designing
'sale_pricelist_id': wizard.sale_pricelist_id.id, # It's used for previewing on label designing
'lang': wizard.lang or self._context.get('lang'),
}).template_id.preview_html
@api.onchange('label_type_id')
def _onchange_label_type_id(self):
for wizard in self:
user_template = self.env.user.print_label_template_id
if user_template and user_template.id in wizard.allowed_template_ids.ids:
wizard.template_id = user_template.id
else:
wizard.template_id = wizard.allowed_template_ids[0].id if wizard.allowed_template_ids else False
wizard._compute_template_preview_html()
def _get_label_data(self):
self.ensure_one()
labels = self.get_labels_to_print()
if not self.is_template_report:
return {'ids': labels.ids, 'data': {}}
if not self.template_id:
raise UserError(_('Select the label template to print.'))
self.template_id._set_paperformat()
label_data = {
'ids': labels.ids,
'data': {
'rows': self.template_id.rows,
'cols': self.template_id.cols,
'row_gap': self.template_id.row_gap,
'col_gap': self.template_id.col_gap,
'label_style':
'overflow: hidden;'
'font-family: "Arial";'
'width: %(width).2fmm;'
'height: %(height).2fmm;'
'padding: %(padding_top).2fmm %(padding_right).2fmm'
' %(padding_bottom).2fmm %(padding_left).2fmm;'
'border: %(border)s;'
'%(custom_style)s' % {
'width': self.template_id.width,
'height': self.template_id.height,
'padding_top': self.template_id.padding_top,
'padding_right': self.template_id.padding_right,
'padding_bottom': self.template_id.padding_bottom,
'padding_left': self.template_id.padding_left,
'border': "%dpx solid #EEE" % self.border_width
if self.border_width else 0,
'custom_style': self.template_id.label_style or '',
},
'skip_places': self.skip_place_count,
},
}
# Add extra styles for multi labels
if self.template_id.cols != 1 or self.template_id.rows != 1:
label_data['data']['label_style'] += 'float: left;'
return label_data
def _get_report_action_params(self):
ids, data = super(PrintProductLabel, self)._get_report_action_params()
if self.is_template_report:
ids = None
data = self._get_label_data()
return ids, data
@api.model
def get_quick_report_action(
self, model_name: str, ids: List[int], qty: int = None, template=None,
force_direct: bool = False, close_window: bool = False,
):
""" Overwritten completely to use with custom label templates. """
template = template or self.env.user.print_label_template_id
if not template:
raise UserError(_('Specify a label template for the current user to print custom labels.'))
wizard = self.with_context(**{
'active_model': model_name,
f'default_{model_name.replace(".", "_")}_ids': ids,
}).create({
'report_id': self.env.ref('garazd_product_label.action_report_product_label_from_template').id,
'template_id': template.id,
})
if isinstance(qty, int):
wizard.label_ids.write({'qty': qty, 'qty_initial': qty})
report_action = wizard.action_print()
if close_window:
report_action.update({'close_on_report_download': True})
return wizard.action_print_direct() if self.env.user.print_label_directly or force_direct else report_action
def action_add_template(self):
self.ensure_one()
return {
'name': _('Add a New Label Template'),
'type': 'ir.actions.act_window',
'res_model': 'print.product.label.template.add',
'view_mode': 'form',
'target': 'new',
}
def action_edit_template(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': self.template_id._name,
'res_id': self.template_id.id,
'view_mode': 'form',
}
def action_reset_skip(self):
"""Reset the skip empty places count value. """
self.ensure_one()
self.write({'skip_place_count': 0})
@api.model
def open_extension_app_list(self):
return {
'type': 'ir.actions.act_url',
'url': 'https://apps.odoo.com/apps/browse?repo_maintainer_id=119796&search=garazd_product_label_',
'target': 'new',
'target_type': 'public',
}
@api.model
def _pdf_preview(self, label_data: bytes):
preview = self.env['print.product.label.preview'].sudo().create({'label_pdf': label_data})
return {
'name': _('Label Preview'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': preview._name,
'res_id': preview.id,
'target': 'new',
}
def action_pdf_preview(self):
self.ensure_one()
return self._pdf_preview(self.get_pdf())

View File

@@ -0,0 +1,34 @@
# Copyright © 2022 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/15.0/legal/licenses.html).
from odoo import api, fields, models
class PrintProductLabelLine(models.TransientModel):
_inherit = "print.product.label.line"
price = fields.Float(digits='Product Price', compute='_compute_product_price')
currency_id = fields.Many2one(comodel_name='res.currency', compute='_compute_product_price')
promo_price = fields.Float(digits='Product Price', compute='_compute_product_price')
promo_currency_id = fields.Many2one(comodel_name='res.currency', compute='_compute_product_price')
@api.depends('product_id', 'wizard_id.pricelist_id', 'wizard_id.sale_pricelist_id')
def _compute_product_price(self):
# When we add a new line by UI in the wizard form, the line doesn't
# have a product. So we calculate prices only for lines with products
with_product = self.filtered('product_id')
for line in with_product:
# flake8: noqa: E501
pricelist = line.wizard_id.pricelist_id
line.price = pricelist._get_product_price(line.product_id, 1.0) if pricelist else line.product_id.lst_price
line.currency_id = pricelist.currency_id.id if pricelist else line.product_id.currency_id.id
promo_pricelist = line.wizard_id.sale_pricelist_id
line.promo_price = promo_pricelist._get_product_price(line.product_id, 1.0) if promo_pricelist else line.price
line.promo_currency_id = promo_pricelist.currency_id.id if promo_pricelist else line.currency_id.id
(self - with_product).price = False
(self - with_product).currency_id = False
(self - with_product).promo_price = False
(self - with_product).promo_currency_id = False

View File

@@ -0,0 +1,13 @@
# Copyright © 2024 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/17.0/legal/licenses.html).
from odoo import fields, models
class PrintProductLabelPreview(models.TransientModel):
_name = "print.product.label.preview"
_description = "Preview Labels in PDF"
label_pdf = fields.Binary(string='PDF', readonly=True)

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="print_product_label_preview_view_form" model="ir.ui.view">
<field name="name">print.product.label.preview.form</field>
<field name="model">print.product.label.preview</field>
<field name="arch" type="xml">
<form>
<field name="label_pdf" widget="pdf_viewer"/>
<footer>
<button special="cancel" class="oe_link" string="Close"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,115 @@
# Copyright © 2022 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/16.0/legal/licenses.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PrintProductLabelTemplateAdd(models.TransientModel):
_name = "print.product.label.template.add"
_description = 'Wizard to add a new product label templates'
type_id = fields.Many2one(
comodel_name='print.label.type',
string='Label Type',
default=lambda self: self.env.ref('garazd_product_label.type_product'),
required=True,
)
width = fields.Integer(help='Label Width in mm.', required=True)
height = fields.Integer(help='Label Height in mm.', required=True)
rows = fields.Integer(default=1, required=True)
cols = fields.Integer(default=1, required=True)
paper_format = fields.Selection(
selection=[
('custom', 'Custom'),
('A4', 'A4'),
('Letter', 'US Letter'),
],
help="Select Proper Paper size",
default='custom',
required=True,
)
orientation = fields.Selection(
selection=[
('Portrait', 'Portrait'),
('Landscape', 'Landscape'),
],
default='Portrait',
required=True,
)
page_width = fields.Integer(help='Page Width in mm.')
page_height = fields.Integer(help='Page Height in mm.')
@api.constrains('rows', 'cols', 'width', 'height')
def _check_page_layout(self):
for wizard in self:
if not (wizard.width and wizard.height):
raise ValidationError(_('The label sizes must be set.'))
if not (wizard.cols and wizard.rows):
raise ValidationError(
_('The page layout values "Cols" and "Rows" must be set.'))
if wizard.paper_format == 'custom' and wizard._is_multi_layout():
if not (self.page_width or self.page_height):
raise ValidationError(
_('The page sizes "Page Width" and "Page Height" must be set.'))
if self.page_width < self.width:
raise ValidationError(
_('The page width must be not less than label width.'))
if self.page_height < self.height:
raise ValidationError(
_('The page height must be not less than label height.'))
def _is_multi_layout(self):
self.ensure_one()
return self.cols > 1 or self.rows > 1
def _get_label_name(self):
self.ensure_one()
# flake8: noqa: E501
paperformat_name = 'Custom' if self.paper_format == 'custom' else self.paper_format
page_sizes = f" {self.page_width}x{self.page_height} mm" if self.page_width and self.page_height else ""
layout_name = f" ({paperformat_name}{page_sizes}: {self.cols * self.rows} pcs, {self.cols}x{self.rows})" if self.paper_format != "custom" or self._is_multi_layout() else ""
return f'Label: {self.width}x{self.height} mm{layout_name}'
def _create_paperformat(self):
self.ensure_one()
return self.env['report.paperformat'].sudo().create({
'name': self._get_label_name(),
'format': self.paper_format,
'page_width': 0 if self.paper_format != 'custom'
else self.page_width if self._is_multi_layout()
else self.width,
'page_height': 0 if self.paper_format != 'custom'
else self.page_height if self._is_multi_layout()
else self.height,
'orientation': self.orientation,
'margin_top': 0,
'margin_bottom': 0,
'margin_left': 0,
'margin_right': 0,
'header_spacing': 0,
'header_line': False,
'disable_shrinking': True,
'dpi': 96,
'default': False,
})
def action_create(self):
self.ensure_one()
template = self.env['print.product.label.template'].create({
'type_id': self.type_id.id,
'name': self._get_label_name().replace(':', '', 1),
'paperformat_id': self._create_paperformat().id,
'width': self.width,
'height': self.height,
'rows': self.rows,
'cols': self.cols,
})
return {
'type': 'ir.actions.act_window',
'res_model': template._name,
'res_id': template.id,
'view_mode': 'form',
}

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="print_product_label_template_add_view_form" model="ir.ui.view">
<field name="name">print.product.label.template.add.view.form</field>
<field name="model">print.product.label.template.add</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="type_id" widget="selection_badge"/>
</group>
<group>
<separator string="Label" colspan="2"/>
<group>
<label for="width"/>
<div class="o_row"><field name="width" string="Width"/><span class="text-muted"> mm</span></div>
</group>
<group>
<label for="height"/>
<div class="o_row"><field name="height" string="Height"/><span class="text-muted"> mm</span></div>
</group>
<separator string="Layout" colspan="2"/>
<group>
<field name="cols" title="Cols"/>
</group>
<group>
<field name="rows" title="Rows"/>
</group>
<separator string="Page" colspan="2"/>
<group>
<field name="paper_format" widget="radio" options="{'horizontal': true}"/>
<field name="page_width"
string="Page Width"
invisible="paper_format != 'custom' or cols == 1 and rows == 1"/>
</group>
<group>
<field name="orientation" widget="radio" options="{'horizontal': true}"/>
<field name="page_height"
string="Page Height"
invisible="paper_format != 'custom' or cols == 1 and rows == 1"/>
</group>
</group>
</sheet>
<footer>
<button name="action_create"
string="Create Template"
type="object"
icon="fa-plus"
class="btn-primary"/>
<button special="cancel" class="oe_link" string="Close"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_product_label_template_list" model="ir.actions.act_window">
<field name="name">Label Templates</field>
<field name="res_model">print.product.label.template</field>
<field name="view_mode">list,form</field>
<field name="context">{'active_test': False}</field>
</record>
<menuitem
id="label_templates_menu"
name="Label Templates"
parent="base.reporting_menuitem"
action="action_product_label_template_list"
sequence="10"
groups="base.group_no_one"/>
<record id="print_product_label_view_form" model="ir.ui.view">
<field name="name">print.product.label.view.form.inherit.garazd_product_label_pro</field>
<field name="model">print.product.label</field>
<field name="inherit_id" ref="garazd_product_label.print_product_label_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='label_type_id']" position="before">
<button name="action_pdf_preview" type="object" string="PDF Preview" icon="fa-file-pdf-o" class="border btn-light mr8"/>
</xpath>
<xpath expr="//field[@name='label_type_id']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr="//field[@name='output']" position="after">
<field name="label_template_preview" string="Show Label" widget="boolean_toggle" invisible="not is_template_report"/>
</xpath>
<xpath expr="//group[@name='label_report']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//a[@name='label_builder_link']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//group[@name='label_template']" position="inside">
<div class="o_row" colspan="2">
<field name="show_template_limit" invisible="1"/>
<field name="allowed_template_ids" invisible="1"/>
<field name="allowed_template_count" invisible="1"/>
<field name="template_id"
domain="[('id', 'in', allowed_template_ids)]"
required="is_template_report"
options="{'no_open': True, 'no_create': True}"
/>
<button name="action_edit_template"
title="Edit Template"
type="object"
icon="fa-external-link"
context="{'print_wizard_id': id}"
invisible="not template_id"/>
<button name="action_add_template"
string="Add"
type="object"
icon="fa-plus"
class="btn-primary"
title="Create a new label template"/>
</div>
</xpath>
<xpath expr="//group[@name='label_template']" position="after">
<group col="2" colspan="2">
<div class="o_row" colspan="2" nolabel="1">
<field name="template_preview_html" invisible="not label_template_preview"/>
</div>
</group>
</xpath>
<xpath expr="//button[@name='action_restore_initial_qty']" position="after">
<span class="text-muted px-3">|</span>
<span class="oe_form_field pr-2">Skip:</span>
<field name="skip_place_count" class="mr-2" style="width: 40px !important; text-align: right;"/>
<button name="action_reset_skip"
title="Reset the value of skipping empty places."
type="object"
class="btn-xs btn-light"
icon="fa-remove"/>
<span class="text-muted px-3">|</span>
<field name="pricelist_id" class="mr8" options="{'no_open': True, 'no_create': True}" placeholder="Select a pricelist..."/>
</xpath>
<xpath expr="//list/field[@name='barcode']" position="after">
<field name="price" optional="show"/>
<field name="currency_id" optional="show"/>
<field name="promo_price" optional="hide"/>
<field name="promo_currency_id" string="Currency" optional="hide"/>
</xpath>
<xpath expr="//page[@name='options']//field[@name='company_id']" position="after">
<field name="sale_pricelist_id" options="{'no_create': True}"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,62 @@
# Copyright © 2024 Garazd Creation (https://garazd.biz)
# @author: Yurii Razumovskyi (support@garazd.biz)
# @author: Iryna Razumovska (support@garazd.biz)
# License OPL-1 (https://www.odoo.com/documentation/16.0/legal/licenses.html).
from odoo import _, api, Command, fields, models
from odoo.exceptions import UserError
class ProductLabelLayout(models.TransientModel):
_inherit = 'product.label.layout'
@api.model
def default_get(self, fields_list):
# flake8: noqa: E501
default_vals = super(ProductLabelLayout, self).default_get(fields_list)
if 'garazd_label_template_id' not in default_vals:
default_vals['garazd_label_template_id'] = self.env.user.print_label_template_id.id
default_vals['garazd_allowed_template_ids'] = [Command.set(self.env['print.product.label.template']._get_user_allowed_templates().ids)]
return default_vals
use_alternative_template = fields.Boolean(string='Custom Templates')
garazd_label_template_id = fields.Many2one(
comodel_name='print.product.label.template',
string='Label Template',
)
garazd_allowed_template_ids = fields.Many2many(
comodel_name='print.product.label.template',
compute='_compute_garazd_allowed_template_ids',
string='Allowed Templates',
help='Technical field to restrict allowed for the current user templates.',
)
garazd_allowed_template_count = fields.Integer(compute='_compute_garazd_allowed_template_ids')
garazd_show_template_limit = fields.Integer(compute='_compute_garazd_allowed_template_ids')
@api.depends_context('uid')
@api.depends('use_alternative_template')
def _compute_garazd_allowed_template_ids(self):
allowed_templates = self.env['print.product.label.template']._get_user_allowed_templates()
self.garazd_allowed_template_ids = [Command.set(allowed_templates.ids)]
self.garazd_allowed_template_count = len(allowed_templates)
# flake8: noqa: E501
self.garazd_show_template_limit = self.env['ir.config_parameter'].sudo().get_param('garazd_product_label.show_label_template_limit', 7)
def process(self):
self.ensure_one()
if not self.use_alternative_template:
return super(ProductLabelLayout, self).process()
if not self.garazd_label_template_id:
raise UserError(_("Please specify a custom label template."))
if not (self.product_tmpl_ids or self.product_ids):
# flake8: noqa: E501
raise UserError(_("No product to print, if the product is archived please unarchive it before printing its label."))
return self.env['print.product.label'].get_quick_report_action(
model_name='product.template' if self.product_tmpl_ids else 'product.product',
ids=self.product_tmpl_ids.ids if self.product_tmpl_ids else self.product_ids.ids,
qty=self.custom_quantity,
template=self.garazd_label_template_id,
close_window=True,
)

View File

@@ -0,0 +1,35 @@
<?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.inherit.garazd_product_label_pro</field>
<field name="model">product.label.layout</field>
<field name="inherit_id" ref="product.product_label_layout_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='print_format']" position="before">
<field name="use_alternative_template" widget="boolean_toggle"/>
</xpath>
<xpath expr="//field[@name='print_format']" position="attributes">
<attribute name="invisible">use_alternative_template</attribute>
</xpath>
<xpath expr="//field[@name='print_format']" position="after">
<field name="garazd_allowed_template_ids" invisible="1"/> <!-- Technical field -->
<field name="garazd_allowed_template_count" invisible="1"/> <!-- Technical field -->
<field name="garazd_show_template_limit" invisible="1"/> <!-- Technical field -->
<field name="garazd_label_template_id"
domain="[('id', 'in', garazd_allowed_template_ids)]"
options="{'no_create': True}"
invisible="not use_alternative_template or use_alternative_template and garazd_allowed_template_count &lt;= garazd_show_template_limit"
required="use_alternative_template and garazd_allowed_template_count &gt; garazd_show_template_limit"/>
<field name="garazd_label_template_id"
domain="[('id', 'in', garazd_allowed_template_ids)]"
options="{'no_create': True}"
invisible="not use_alternative_template or use_alternative_template and garazd_allowed_template_count &gt; garazd_show_template_limit"
required="use_alternative_template and garazd_allowed_template_count &lt;= garazd_show_template_limit"
widget="radio"
/>
</xpath>
</field>
</record>
</odoo>