changes
17
fusion_labels/fusion_labels_pro/README.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
================
|
||||
Fusion Labels Pro
|
||||
================
|
||||
|
||||
Professional product barcode label builder and designer.
|
||||
|
||||
Features
|
||||
--------
|
||||
* Visual label template builder
|
||||
* Configurable label sections and elements
|
||||
* Professional label design tools
|
||||
* Advanced barcode label generation
|
||||
* Direct print support
|
||||
|
||||
Author
|
||||
------
|
||||
* Nexa Systems Inc - https://nexasystems.ca
|
||||
3
fusion_labels/fusion_labels_pro/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import models
|
||||
from . import wizard
|
||||
from . import report
|
||||
45
fusion_labels/fusion_labels_pro/__manifest__.py
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
# App information
|
||||
"name": "Fusion Labels Pro",
|
||||
"version": "19.0.1.0.0",
|
||||
"category": "Extra Tools",
|
||||
"summary": "Professional product barcode label builder and designer.",
|
||||
"license": "OPL-1",
|
||||
"depends": [
|
||||
"fusion_labels_print",
|
||||
"link_tracker",
|
||||
],
|
||||
# Views
|
||||
"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",
|
||||
],
|
||||
"images": ["static/description/icon.png"],
|
||||
# Author
|
||||
"author": "Nexa Systems Inc",
|
||||
"website": "https://nexasystems.ca",
|
||||
"maintainer": "Nexa Systems Inc",
|
||||
"support": "support@nexasystems.ca",
|
||||
# Technical
|
||||
"installable": True,
|
||||
"auto_install": False,
|
||||
"application": True,
|
||||
"price": "245.00",
|
||||
"currency": "USD",
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="fusion_show_label_template_limit" model="ir.config_parameter" forcecreate="True">
|
||||
<field name="key">fusion_labels.show_label_template_limit</field>
|
||||
<field name="value">7</field>
|
||||
</record>
|
||||
<record id="fusion_barcode_multiplier" model="ir.config_parameter" forcecreate="True">
|
||||
<field name="key">fusion_labels_pro.barcode_multiplier</field>
|
||||
<field name="value">1</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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="fusion_labels.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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
7
fusion_labels/fusion_labels_pro/models/__init__.py
Normal 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
|
||||
@@ -0,0 +1,717 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# License OPL-1 (https://www.odoo.com/documentation/19.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(
|
||||
'fusion_labels_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('fusion_labels_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()
|
||||
@@ -0,0 +1,267 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# License OPL-1 (https://www.odoo.com/documentation/19.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('fusion_labels.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(
|
||||
'fusion_labels.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('fusion_labels_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())
|
||||
15
fusion_labels/fusion_labels_pro/models/product_product.py
Normal file
@@ -0,0 +1,15 @@
|
||||
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('fusion_labels.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
|
||||
15
fusion_labels/fusion_labels_pro/models/product_template.py
Normal file
@@ -0,0 +1,15 @@
|
||||
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('fusion_labels.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
|
||||
27
fusion_labels/fusion_labels_pro/models/res_company.py
Normal file
@@ -0,0 +1,27 @@
|
||||
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],
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
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',
|
||||
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,
|
||||
)
|
||||
show_label_template_limit = fields.Integer(
|
||||
string='Template Display Limit',
|
||||
config_parameter='fusion_labels.show_label_template_limit',
|
||||
default=7,
|
||||
)
|
||||
barcode_multiplier = fields.Integer(
|
||||
string='Barcode Size Multiplier',
|
||||
config_parameter='fusion_labels_pro.barcode_multiplier',
|
||||
default=1,
|
||||
)
|
||||
20
fusion_labels/fusion_labels_pro/models/res_users.py
Normal file
@@ -0,0 +1,20 @@
|
||||
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).',
|
||||
)
|
||||
1
fusion_labels/fusion_labels_pro/report/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import product_label_report
|
||||
@@ -0,0 +1,13 @@
|
||||
from odoo import models
|
||||
class ReportFusionProductLabelFromTemplate(models.AbstractModel):
|
||||
_name = 'report.fusion_labels.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'),
|
||||
}
|
||||
103
fusion_labels/fusion_labels_pro/report/product_label_reports.xml
Normal 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>
|
||||
@@ -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&value=%s&width=%d&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="fusion_labels.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 >= skip" t-call="fusion_labels_pro.product_label_from_template"/>
|
||||
</div>
|
||||
<t t-set="index" t-value="index + 1"/>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -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
|
||||
|
BIN
fusion_labels/fusion_labels_pro/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
113
fusion_labels/fusion_labels_pro/static/description/index.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<section class="oe_container" style="font-family: 'Montserrat', 'Odoo Unicode Support Noto', sans-serif;">
|
||||
<div class="row mt-3">
|
||||
<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;"><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;"><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="col-12 d-flex d-column">
|
||||
<h2 class="h1 align-self-center text-center" style="color: #154577; font-weight: 900;">Fusion Labels Pro - Product Label Builder</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>Professional product barcode label builder and designer. Create custom label templates with configurable sections, advanced barcode generation, and direct printing support.</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>Features</div>
|
||||
<div class="s_features_grid p-2 pt-4 o_colored_level" 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">
|
||||
<i class="fa fa-2x float-left mr-3 bg-warning fa-paint-brush rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center;"></i>
|
||||
<div>
|
||||
<div class="h4 pt-2">Label Builder</div>
|
||||
<p>Visual template designer with drag-and-drop sections</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 s_col_no_bgcolor pb24">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<i class="fa fa-2x float-left mr-3 bg-warning fa-barcode rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center;"></i>
|
||||
<div>
|
||||
<div class="h4 pt-2">Advanced Barcodes</div>
|
||||
<p>Multiple barcode formats and customization</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 s_col_no_bgcolor pb24">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<i class="fa fa-2x float-left mr-3 bg-warning fa-print rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center;"></i>
|
||||
<div>
|
||||
<div class="h4 pt-2">Direct Print</div>
|
||||
<p>Print labels directly without downloading PDF</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 s_col_no_bgcolor pb24">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<i class="fa fa-2x float-left mr-3 bg-warning fa-search rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center;"></i>
|
||||
<div>
|
||||
<div class="h4 pt-2">Preview</div>
|
||||
<p>Preview labels before printing</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">
|
||||
<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 border-top border-right" style="border-color: #FEA621 !important; border-radius: 0 15px 0 0;">
|
||||
<div class="media-body">
|
||||
<div class="py-2 px-md-5" style="line-height: 2rem;">Labels are generated in PDF format with various sizes:</div>
|
||||
<div class="text-center my-5 mx-md-5">
|
||||
<img src="product_barcode_label_100x100mm.png" class="w-100 w-lg-25 img-fluid rounded shadow-lg border" alt="Product Labels 100x100mm">
|
||||
</div>
|
||||
<div class="text-center my-5 mx-md-5">
|
||||
<img src="product_barcode_label_A4_63x38mm.png" class="w-100 w-lg-50 img-fluid rounded shadow-lg border" alt="Product Labels A4 63x38mm">
|
||||
</div>
|
||||
</div>
|
||||
</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: 19.0.1.0.0</div>
|
||||
<div class="col-6 text-right"><small>Copyright © <a href="https://nexasystems.ca" target="_blank">Nexa Systems Inc</a></small></div>
|
||||
</div>
|
||||
</section>
|
||||
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 156 KiB |
BIN
fusion_labels/fusion_labels_pro/static/img/no-barcode.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
1
fusion_labels/fusion_labels_pro/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_access_rights
|
||||
43
fusion_labels/fusion_labels_pro/tests/common.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# 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('fusion_labels_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({})
|
||||
25
fusion_labels/fusion_labels_pro/tests/test_access_rights.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# 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()
|
||||
@@ -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>
|
||||
@@ -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 <= 1"/>
|
||||
<field name="row_gap" invisible="rows <= 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>
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Action to open all label templates -->
|
||||
<record id="action_print_product_label_template" 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>
|
||||
|
||||
<!-- Action to open all label sections -->
|
||||
<record id="action_print_product_label_section" model="ir.actions.act_window">
|
||||
<field name="name">Label Sections</field>
|
||||
<field name="res_model">print.product.label.section</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Inherit the base Fusion Labels settings form and add Pro settings -->
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.fusion_labels_pro</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="fusion_labels.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//block[@id='fusion_labels_general']" position="after">
|
||||
|
||||
<block title="Label Preview" id="fusion_labels_preview">
|
||||
<setting id="fusion_labels_preview_type"
|
||||
string="Template Preview"
|
||||
help="Configure how label templates are previewed in the designer.">
|
||||
<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>
|
||||
</block>
|
||||
|
||||
<block title="Label Templates" id="fusion_labels_templates">
|
||||
<div class="o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<span class="o_form_label">Manage Templates</span>
|
||||
<div class="text-muted">Create and configure label templates and their sections.</div>
|
||||
<div class="mt16">
|
||||
<button name="%(action_print_product_label_template)d"
|
||||
type="action"
|
||||
string="Label Templates"
|
||||
class="btn-link"
|
||||
icon="fa-arrow-right"/>
|
||||
</div>
|
||||
<div class="mt8">
|
||||
<button name="%(action_print_product_label_section)d"
|
||||
type="action"
|
||||
string="Label Sections"
|
||||
class="btn-link"
|
||||
icon="fa-arrow-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</block>
|
||||
|
||||
<block title="Advanced" id="fusion_labels_advanced">
|
||||
<setting id="fusion_labels_barcode_multiplier"
|
||||
string="Barcode Size Multiplier"
|
||||
help="Multiplier applied to barcode dimensions on labels. Increase for larger barcodes.">
|
||||
<field name="barcode_multiplier"/>
|
||||
</setting>
|
||||
<setting id="fusion_labels_template_limit"
|
||||
string="Template Display Limit"
|
||||
help="Maximum number of label templates shown in the print wizard selection.">
|
||||
<field name="show_label_template_limit"/>
|
||||
</setting>
|
||||
</block>
|
||||
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
19
fusion_labels/fusion_labels_pro/views/res_users_views.xml
Normal 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.fusion_labels_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>
|
||||
69
fusion_labels/fusion_labels_pro/views/templates.xml
Normal 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&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(/fusion_labels_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"/>&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&value=%s&width=%d&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>
|
||||
5
fusion_labels/fusion_labels_pro/wizard/__init__.py
Normal 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
|
||||
214
fusion_labels/fusion_labels_pro/wizard/print_product_label.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# License OPL-1 (https://www.odoo.com/documentation/19.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('fusion_labels.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('fusion_labels.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('fusion_labels.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('fusion_labels.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=fusion_labels_',
|
||||
'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())
|
||||
@@ -0,0 +1,32 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# License OPL-1 (https://www.odoo.com/documentation/19.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
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# 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)
|
||||
@@ -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>
|
||||
@@ -0,0 +1,113 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# 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('fusion_labels.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',
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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.fusion_labels_pro</field>
|
||||
<field name="model">print.product.label</field>
|
||||
<field name="inherit_id" ref="fusion_labels.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>
|
||||
@@ -0,0 +1,60 @@
|
||||
# Copyright © 2025 Nexa Systems Inc (https://nexasystems.ca)
|
||||
# @author: Nexa Systems Inc (support@nexasystems.ca)
|
||||
|
||||
# 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 'fusion_label_template_id' not in default_vals:
|
||||
default_vals['fusion_label_template_id'] = self.env.user.print_label_template_id.id
|
||||
default_vals['fusion_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')
|
||||
fusion_label_template_id = fields.Many2one(
|
||||
comodel_name='print.product.label.template',
|
||||
string='Label Template',
|
||||
)
|
||||
fusion_allowed_template_ids = fields.Many2many(
|
||||
comodel_name='print.product.label.template',
|
||||
compute='_compute_fusion_allowed_template_ids',
|
||||
string='Allowed Templates',
|
||||
help='Technical field to restrict allowed for the current user templates.',
|
||||
)
|
||||
fusion_allowed_template_count = fields.Integer(compute='_compute_fusion_allowed_template_ids')
|
||||
fusion_show_template_limit = fields.Integer(compute='_compute_fusion_allowed_template_ids')
|
||||
|
||||
@api.depends_context('uid')
|
||||
@api.depends('use_alternative_template')
|
||||
def _compute_fusion_allowed_template_ids(self):
|
||||
allowed_templates = self.env['print.product.label.template']._get_user_allowed_templates()
|
||||
self.fusion_allowed_template_ids = [Command.set(allowed_templates.ids)]
|
||||
self.fusion_allowed_template_count = len(allowed_templates)
|
||||
# flake8: noqa: E501
|
||||
self.fusion_show_template_limit = self.env['ir.config_parameter'].sudo().get_param('fusion_labels.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.fusion_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.fusion_label_template_id,
|
||||
close_window=True,
|
||||
)
|
||||
@@ -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.fusion_labels_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="fusion_allowed_template_ids" invisible="1"/> <!-- Technical field -->
|
||||
<field name="fusion_allowed_template_count" invisible="1"/> <!-- Technical field -->
|
||||
<field name="fusion_show_template_limit" invisible="1"/> <!-- Technical field -->
|
||||
<field name="fusion_label_template_id"
|
||||
domain="[('id', 'in', fusion_allowed_template_ids)]"
|
||||
options="{'no_create': True}"
|
||||
invisible="not use_alternative_template or use_alternative_template and fusion_allowed_template_count <= fusion_show_template_limit"
|
||||
required="use_alternative_template and fusion_allowed_template_count > fusion_show_template_limit"/>
|
||||
<field name="fusion_label_template_id"
|
||||
domain="[('id', 'in', fusion_allowed_template_ids)]"
|
||||
options="{'no_create': True}"
|
||||
invisible="not use_alternative_template or use_alternative_template and fusion_allowed_template_count > fusion_show_template_limit"
|
||||
required="use_alternative_template and fusion_allowed_template_count <= fusion_show_template_limit"
|
||||
widget="radio"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||