This commit is contained in:
gsinghpal
2026-03-11 12:15:53 -04:00
parent f81e0cd918
commit db4b9aa278
1210 changed files with 173089 additions and 4044 deletions

View File

@@ -0,0 +1,16 @@
============
Fusion Labels
============
Print custom product labels with barcode support.
Features
--------
* Customizable product label layouts
* Barcode support on labels
* Multiple label types
* Configurable label printing
Author
------
* Nexa Systems Inc - https://nexasystems.ca

View File

@@ -0,0 +1,2 @@
from . import models
from . import wizard

View File

@@ -0,0 +1,34 @@
{
# App information
"name": "Fusion Labels",
"version": "19.0.1.0.0",
"category": "Extra Tools",
"summary": "Print custom product labels with barcode.",
"license": "OPL-1",
"depends": [
"product",
],
# Views
"data": [
"security/ir.model.access.csv",
"data/product_data.xml",
"data/print_label_type_data.xml",
"report/product_label_reports.xml",
"report/product_label_templates.xml",
"wizard/print_product_label_views.xml",
"views/res_config_settings_views.xml",
],
"demo": [
"demo/product_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,
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="filter_label_apps" model="ir.filters">
<field name="name">Fusion Labelss</field>
<field name="model_id">ir.module.module</field>
<field name="domain">[("name", "ilike", "fusion_labels")]</field>
<field name="user_id" eval="False"/>
<field name="is_default" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="type_product" model="print.label.type" forcecreate="True">
<field name="name">Products</field>
<field name="code">product.product</field>
</record>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo noupdate="1">
<record id="product_blank" model="product.product">
<field name="name">Blank Product</field>
<field name="type">consu</field>
<field name="description">Please do not delete this product! This product has been created by Fusion Labels solution for technical purposes, and it's used for label printing.</field>
<field name="active" eval="False"/>
</record>
<record id="fusion_labels.product_blank_product_template" model="product.template">
<field name="active" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="fusion_labels.filter_data_feed_apps" model="ir.filters">
<field name="is_default" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo noupdate="1">
<!-- Corner Desk Right Sit -->
<record id="product.product_product_5" model="product.product">
<field name="barcode">3333000022222</field>
</record>
<!-- Storage Box -->
<record id="product.product_product_7" model="product.product">
<field name="barcode">0123456789017</field>
</record>
<!-- Monitor Stand -->
<record id="product.monitor_stand" model="product.product">
<field name="barcode">0700020543219</field>
</record>
<!-- Flipover -->
<record id="product.product_product_20" model="product.product">
<field name="barcode">01234090543216</field>
</record>
</odoo>

View File

@@ -0,0 +1,4 @@
from . import res_config_settings
from . import product_template
from . import product_product
from . import print_label_type

View File

@@ -0,0 +1,9 @@
from odoo import fields, models
class PrintLabelTypePy(models.Model):
_name = "print.label.type"
_description = 'Label Types'
name = fields.Char(required=True, translate=True)
code = fields.Char(required=True)
_sql_constraints = [('print_label_type_code_uniq', 'UNIQUE (code)', 'Code of a print label type must be unique.')]

View File

@@ -0,0 +1,11 @@
from odoo import models
class ProductProduct(models.Model):
_inherit = "product.product"
def action_open_label_layout(self):
# flake8: noqa: E501
if not self.env['ir.config_parameter'].sudo().get_param('fusion_labels.replace_standard_wizard'):
return super(ProductProduct, self).action_open_label_layout()
action = self.env['ir.actions.act_window']._for_xml_id('fusion_labels.action_print_label_from_product')
action['context'] = {'default_product_product_ids': self.ids}
return action

View File

@@ -0,0 +1,11 @@
from odoo import models
class ProductTemplate(models.Model):
_inherit = "product.template"
def action_open_label_layout(self):
# flake8: noqa: E501
if not self.env['ir.config_parameter'].sudo().get_param('fusion_labels.replace_standard_wizard'):
return super(ProductTemplate, self).action_open_label_layout()
action = self.env['ir.actions.act_window']._for_xml_id('fusion_labels.action_print_label_from_template')
action['context'] = {'default_product_template_ids': self.ids}
return action

View File

@@ -0,0 +1,5 @@
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
replace_standard_wizard = fields.Boolean(config_parameter='fusion_labels.replace_standard_wizard')

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="paperformat_label_a4_blank" model="report.paperformat">
<field name="name">Label A4</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">10</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_50x38" model="report.paperformat">
<field name="name">Label 50x38 mm</field>
<field name="format">custom</field>
<field name="page_height">38</field>
<field name="page_width">50</field>
<field name="orientation">Portrait</field>
<field name="margin_top">1</field>
<field name="margin_bottom">0</field>
<field name="margin_left">0</field>
<field name="margin_right">0</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="action_report_product_label_A4_57x35" model="ir.actions.report">
<field name="name">Product Labels 57x35mm (A4, 21 pcs)</field>
<field name="model">print.product.label.line</field>
<field name="report_type">qweb-pdf</field>
<field name="paperformat_id" ref="paperformat_label_a4_blank"/>
<field name="report_name">fusion_labels.report_product_label_57x35_template</field>
<field name="report_file">fusion_labels.report_product_label_57x35_template</field>
<field name="print_report_name">'Product Labels 57x35mm'</field>
</record>
<record id="action_report_product_label_50x38" model="ir.actions.report">
<field name="name">Product Labels 50x38mm</field>
<field name="model">print.product.label.line</field>
<field name="report_type">qweb-pdf</field>
<field name="paperformat_id" ref="paperformat_label_50x38"/>
<field name="report_name">fusion_labels.report_product_label_50x38_template</field>
<field name="report_file">fusion_labels.report_product_label_50x38_template</field>
<field name="print_report_name">'Product Labels 50x38mm'</field>
</record>
<record id="action_report_product_label_from_template" model="ir.actions.report">
<field name="name">Product Label from your own template</field>
<field name="model">print.product.label.line</field>
<field name="report_type">qweb-pdf</field>
<field name="paperformat_id" ref="fusion_labels.paperformat_label_a4_blank"/>
<field name="report_name">fusion_labels.report_product_label_from_template</field>
<field name="report_file">fusion_labels.report_product_label_from_template</field>
<field name="print_report_name">'Product Labels Custom Design'</field>
</record>
</odoo>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="label_57x35">
<table class="table" style="margin: 0; padding: 0;">
<tr height="144px" style="border:0;margin:0;padding:0;">
<td class="col-xs-4 text-center" style="border:0;margin:0;padding:0;">
<div style="overflow:hidden; height:65px !important; padding: 5px 5px 0; line-height: 1.2;">
<span t-field="label.product_id.name" style="line-height: 1.2;"/>
<span t-if="label.product_id.product_template_attribute_value_ids"
t-esc="u', '.join(map(lambda x: x.attribute_line_id.attribute_id.name + u': ' + x.name, label.product_id.product_template_attribute_value_ids))"
class="badge"/>
</div>
<div style="width: 100%; padding: 0; margin: 0; font-size: 21px; font-weight: bold;">
<span t-if="label.product_id.currency_id.position == 'before'" t-field="label.product_id.currency_id.symbol"/>
<span t-field="label.product_id.lst_price"/>
<span t-if="label.product_id.currency_id.position == 'after'" t-field="label.product_id.currency_id.symbol"/>
</div>
<div t-if="label.product_id.default_code" style="width: 100%; padding: 0; margin: 0; font-size: 12px;">
<span t-field="label.product_id.default_code"/>
</div>
<t t-if="label.product_id.barcode">
<div t-out="label.product_id.barcode" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': 'overflow: hidden; width: 100%; height: 1.4rem;', 'humanreadable': label.wizard_id.humanreadable}" class="px-1"/>
</t>
</td>
</tr>
</table>
</template>
<template id="report_product_label_57x35_template">
<t t-call="web.html_container">
<t t-call="web.basic_layout">
<t t-set="count" t-value="0" />
<div class="page">
<div class="oe_structure"/>
<t t-foreach="docs" t-as="label">
<t t-set="qty" t-value="1" />
<t t-if="label.qty">
<t t-set="qty" t-value="label.qty" />
</t>
<t t-foreach="list(range(qty))" t-as="index_qty">
<div t-if="count % 21 == 0" style="page-break-after:always;"/>
<div t-if="count % 3 == 0" style="clear:both;"/>
<div t-att-style="'width: 233px; float: left; height: 145px; margin: 0 4px 4px; border: {};'.format('%dpx solid #777;' % label.wizard_id.border_width if label.wizard_id.border_width else '0')">
<t t-call="fusion_labels.label_57x35"/>
</div>
<t t-set="count" t-value="count+1" />
</t>
</t>
</div>
</t>
</t>
</template>
<template id="report_product_label_50x38_template">
<t t-call="web.html_container">
<t t-call="web.basic_layout">
<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-foreach="list(range(qty))" t-as="index_qty">
<div class="page" style="page-break-after: always;">
<div style="height: 136px !important; width: 165px !important;">
<table class="table" t-att-style="'height: 100%; border: {};'.format('%dpx solid #777;' % label.wizard_id.border_width if label.wizard_id.border_width else '0')">
<tr style="border: 0;">
<td class="text-center" style="padding: 0; margin: 0; border: 0;">
<div style="overflow: hidden; height: 64px !important; font-size: 13px; line-height: 1.2; margin-bottom: 2px;">
<span t-field="label.product_id.name" style="line-height: 1.2;"/>
<span t-if="label.product_id.product_template_attribute_value_ids"
t-esc="u', '.join(map(lambda x: x.attribute_line_id.attribute_id.name + u': ' + x.name, label.product_id.product_template_attribute_value_ids))"
class="badge"/>
</div>
<div style="width: 100%; overflow: hidden; height: 26px !important;">
<div style="float: right; width: 60%; font-size: 20px; font-weight: bold; line-height: 1.0; text-align: right; margin-bottom: 0px;">
<span t-if="label.product_id.currency_id.position == 'before'" t-field="label.product_id.currency_id.symbol"/>
<span t-field="label.product_id.lst_price"/>
<span t-if="label.product_id.currency_id.position == 'after'" t-field="label.product_id.currency_id.symbol"/>
</div>
<div style="float: left; width: 40%; font-size: 10px; font-weight: 900; line-height: 1.0; text-align: left; margin-bottom: 0px; padding-top: 6px;">
<span t-if="label.product_id.default_code"><span t-field="label.product_id.default_code"/> </span>
</div>
</div>
<t t-if="label.product_id.barcode">
<div t-out="label.product_id.barcode" style="margin-top: 5px;" t-options="{'widget': 'barcode', 'quiet': 0, 'symbology': 'auto', 'img_style': 'overflow: hidden; width: 100%; height: 2.0rem;', 'humanreadable': label.wizard_id.humanreadable}"/>
</t>
</td>
</tr>
</table>
</div>
</div>
</t>
</t>
</t>
</t>
</template>
<template id="report_product_label_from_template">
<t t-call="web.basic_layout"/>
</template>
</odoo>

View File

@@ -0,0 +1,4 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_print_product_label_line_user,access_print_product_label_line_user,model_print_product_label_line,base.group_user,1,1,1,1
access_print_product_label_user,access_print_product_label_user,model_print_product_label,base.group_user,1,1,1,0
access_print_label_type_user_read,access_print_label_type_user_read,model_print_label_type,base.group_user,1,0,0,0
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
2 access_print_product_label_line_user access_print_product_label_line_user model_print_product_label_line base.group_user 1 1 1 1
3 access_print_product_label_user access_print_product_label_user model_print_product_label base.group_user 1 1 1 0
4 access_print_label_type_user_read access_print_label_type_user_read model_print_label_type base.group_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,118 @@
<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 - Product Label Printing</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>Print custom product labels with barcode support on various paper formats. Includes configurable label templates for different sizes and designs.</p>
<p class="pt-3">This module includes two label templates:</p>
<ul>
<li><b>57x35mm</b> (21 pcs on A4 paper, 3 pcs x 7 rows)</li>
<li><b>50x38mm</b> (Dymo label, 2" x 1.5")</li>
</ul>
</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-barcode rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center;"></i>
<div>
<div class="h4 pt-2">Barcode Labels</div>
<p>Generate product labels with barcodes</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">Multiple Formats</div>
<p>Support for various paper sizes and label types</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-sliders rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center;"></i>
<div>
<div class="h4 pt-2">Customizable</div>
<p>Configure label content and appearance</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-check-square-o rounded-circle" style="line-height:5rem; height:5rem; width:5rem; text-align:center;"></i>
<div>
<div class="h4 pt-2">Tested</div>
<p>Includes unit tests for reliability</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:</div>
<div class="text-center my-5 mx-md-5">
<img src="product_barcode_label_50x38mm.png" class="w-100 w-lg-25 img-fluid rounded shadow-lg border" alt="Product Labels 50x38mm">
</div>
<div class="text-center my-5 mx-md-5">
<img src="product_barcode_label_A4_57x35mm.png" class="w-100 w-lg-50 img-fluid rounded shadow-lg border" alt="Product Labels A4 57x35mm">
</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 &copy; <a href="https://nexasystems.ca" target="_blank">Nexa Systems Inc</a></small></div>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

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

View File

@@ -0,0 +1,29 @@
from odoo.tests.common import TransactionCase
from odoo.tools import test_reports
from odoo.tests import tagged
@tagged('post_install', '-at_install', 'fusion_labels')
class TestPrintProductLabel(TransactionCase):
def setUp(self):
super(TestPrintProductLabel, self).setUp()
self.product_chair = self.env.ref('product.product_product_12')
self.product_drawer = self.env.ref('product.product_product_27')
def test_print_wizard(self):
ctx = {
'active_model': 'product.product',
'default_product_product_ids': [
self.product_chair.id,
self.product_drawer.id,
],
}
wizard = self.env['print.product.label'].with_context(**ctx).create({})
self.assertEqual(len(wizard.label_ids), 2)
test_reports.try_report(
self.env.cr,
self.env.uid,
'fusion_labels.report_product_label_57x35_template',
ids=wizard.label_ids.ids,
our_module='fusion_labels'
)

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Inherit base settings form and add Fusion Labels app block -->
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.fusion_labels</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="priority">60</field>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app string="Fusion Labels" name="fusion_labels" logo="/fusion_labels/static/description/icon.png">
<block title="General" id="fusion_labels_general">
<setting id="fusion_labels_replace_wizard"
string="Alternative Print Wizard"
help="Use the custom print wizard by clicking on the 'Print Labels' button instead of the standard Odoo label printing wizard.">
<field name="replace_standard_wizard"/>
</setting>
</block>
</app>
</xpath>
</field>
</record>
<!-- Settings action -->
<record id="res_config_settings_action_fusion_labels" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">main</field>
<field name="context">{'module': 'fusion_labels'}</field>
</record>
<!-- Settings menu under Settings -->
<menuitem id="menu_fusion_labels_config"
name="Fusion Labels"
parent="base.menu_administration"
action="res_config_settings_action_fusion_labels"
sequence="55"
groups="base.group_system"/>
</odoo>

View File

@@ -0,0 +1,2 @@
from . import print_product_label_line
from . import print_product_label

View File

@@ -0,0 +1,182 @@
# 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).
import base64
from typing import List
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.addons.base.models.res_partner import _lang_get
class PrintProductLabel(models.TransientModel):
_name = "print.product.label"
_description = 'Wizard to print Product Labels'
@api.model
def _complete_label_fields(self, label_ids: List[int]) -> List[int]:
"""Set additional fields for product labels. Method to override."""
# Increase a label sequence
labels = self.env['print.product.label.line'].browse(label_ids)
for seq, label in enumerate(labels):
label.sequence = 1000 + seq
return label_ids
@api.model
def _get_product_label_ids(self):
res = []
# flake8: noqa: E501
if self._context.get('active_model') == 'product.template':
products = self.env[self._context.get('active_model')].browse(
self._context.get('default_product_template_ids')
)
for product in products:
label = self.env['print.product.label.line'].create({
'product_id': product.product_variant_id.id,
})
res.append(label.id)
elif self._context.get('active_model') == 'product.product':
products = self.env[self._context.get('active_model')].browse(
self._context.get('default_product_product_ids')
)
for product in products:
label = self.env['print.product.label.line'].create({'product_id': product.id})
res.append(label.id)
res = self._complete_label_fields(res)
return res
@api.model
def default_get(self, fields_list):
default_vals = super(PrintProductLabel, self).default_get(fields_list)
if 'label_type_id' in fields_list and not default_vals.get('label_type_id'):
default_vals['label_type_id'] = self.env.ref('fusion_labels.type_product').id
return default_vals
name = fields.Char(default='Print Product Labels')
message = fields.Char(readonly=True)
output = fields.Selection(selection=[('pdf', 'PDF')], string='Print to', default='pdf')
mode = fields.Selection(
selection=[('product.product', 'Products')],
help='Technical field to specify the mode of the label printing wizard.',
default='product.product',
)
label_type_id = fields.Many2one(comodel_name='print.label.type', string='Label Type')
label_ids = fields.One2many(
comodel_name='print.product.label.line',
inverse_name='wizard_id',
string='Labels for Products',
default=_get_product_label_ids,
)
report_id = fields.Many2one(
comodel_name='ir.actions.report',
string='Label',
domain=[('model', '=', 'print.product.label.line')],
)
is_template_report = fields.Boolean(compute='_compute_is_template_report')
qty_per_product = fields.Integer(
string='Label quantity per product',
default=1,
)
# Options
humanreadable = fields.Boolean(
string='Human readable barcode',
help='Print digital code of barcode.',
default=False,
)
border_width = fields.Integer(
string='Border',
help='Border width for labels (in pixels). Set "0" for no border.'
)
lang = fields.Selection(
selection=_lang_get,
string='Language',
help="The language that will be used to translate label names.",
)
company_id = fields.Many2one(
comodel_name='res.company',
help='Specify a company for product labels.'
)
@api.depends('report_id')
def _compute_is_template_report(self):
for wizard in self:
# flake8: noqa: E501
wizard.is_template_report = self.report_id == self.env.ref('fusion_labels.action_report_product_label_from_template')
def get_labels_to_print(self):
self.ensure_one()
labels = self.label_ids.filtered(lambda l: l.selected and l.qty)
if not labels:
raise UserError(_('Nothing to print, set the quantity of labels in the table.'))
return labels
def _get_report_action_params(self):
"""Return two params for a report action: record "ids" and "data"."""
self.ensure_one()
return self.get_labels_to_print().ids, None
def _prepare_report(self):
self.ensure_one()
output_mode = self._context.get('print_mode', 'pdf')
if not self.report_id:
raise UserError(_('Please select a label type.'))
report = self.report_id.with_context(discard_logo_check=True, lang=self.lang)
report.sudo().write({'report_type': f'qweb-{output_mode}'})
return report
def action_print(self):
"""Print labels."""
self.ensure_one()
report = self._prepare_report()
return report.report_action(*self._get_report_action_params())
def action_set_qty(self):
"""Set a specific number of labels for all lines."""
self.ensure_one()
self.label_ids.write({'qty': self.qty_per_product})
def action_restore_initial_qty(self):
"""Restore the initial number of labels for all lines."""
self.ensure_one()
for label in self.label_ids:
if label.qty_initial:
label.update({'qty': label.qty_initial})
@api.model
def get_quick_report_action(
self, model_name: str, ids: List[int], qty: int = None, template=None, force_direct: bool = False,
):
""" Allow to get a report action for custom labels. Method to override. """
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_50x38').id})
return wizard.action_print()
@api.model
def _set_sequence(self, lbl, seq, processed):
if lbl in processed:
return seq, processed
lbl.sequence = seq
seq += 1
processed += lbl
return seq, processed
def action_sort_by_product(self):
self.ensure_one()
sequence = 1000
processed_labels = self.env['print.product.label.line'].browse()
# flake8: noqa: E501
for label in self.label_ids:
sequence, processed_labels = self._set_sequence(label, sequence, processed_labels)
tmpl_labels = self.label_ids.filtered(lambda l: l.product_id.product_tmpl_id == label.product_id.product_tmpl_id).sorted(lambda l: l.product_id.id, reverse=True) - label
for tmpl_label in tmpl_labels:
sequence, processed_labels = self._set_sequence(tmpl_label, sequence, processed_labels)
product_labels = tmpl_labels.filtered(lambda l: l.product_id == label.product_id) - tmpl_label
for product_label in product_labels:
sequence, processed_labels = self._set_sequence(product_label, sequence, processed_labels)
def get_pdf(self):
self.ensure_one()
report = self.with_context(print_mode='pdf')._prepare_report()
pdf_data = report._render_qweb_pdf(report, *self._get_report_action_params())
return base64.b64encode(pdf_data[0])

View File

@@ -0,0 +1,46 @@
# 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):
_name = "print.product.label.line"
_description = 'Line with a Product Label Data'
_order = 'sequence'
sequence = fields.Integer(default=900)
selected = fields.Boolean(string='Print', default=True)
wizard_id = fields.Many2one(comodel_name='print.product.label') # Do not make required
product_id = fields.Many2one(comodel_name='product.product', required=True)
barcode = fields.Char(compute='_compute_barcode')
qty_initial = fields.Integer(string='Initial Qty', default=1)
qty = fields.Integer(string='Label Qty', default=1)
custom_value = fields.Char(help="This field can be filled manually to use in label templates.")
company_id = fields.Many2one(comodel_name='res.company', compute='_compute_company_id')
# Allow users to specify a partner to use it on label templates
partner_id = fields.Many2one(comodel_name='res.partner', readonly=False)
@api.depends('wizard_id.company_id')
def _compute_company_id(self):
for label in self:
label.company_id = label.wizard_id.company_id.id \
if label.wizard_id.company_id else self.env.user.company_id.id
@api.depends('product_id')
def _compute_barcode(self):
for label in self:
label.barcode = label.product_id.barcode
def action_plus_qty(self):
self.ensure_one()
if not self.qty:
self.update({'selected': True})
self.update({'qty': self.qty + 1})
def action_minus_qty(self):
self.ensure_one()
if self.qty > 0:
self.update({'qty': self.qty - 1})
if not self.qty:
self.update({'selected': False})

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="print_product_label_view_form" model="ir.ui.view">
<field name="name">print.product.label.view.form</field>
<field name="model">print.product.label</field>
<field name="arch" type="xml">
<form create="false" string="Print Product Labels">
<header>
<button name="action_print"
string="Print"
help="Print product labels"
type="object"
icon="fa-print"
class="btn-primary mr8"/>
<button name="action_print"
string="Preview"
context="{'print_mode': 'html'}"
help="Preview product labels"
type="object"
icon="fa-search"
class="border btn-light mr8"/>
<field name="label_type_id" widget="selection_badge" invisible="1"/> <!-- Technical field to specify the label type -->
</header>
<div class="oe_button_box" name="button_box"/>
<field name="mode" invisible="1"/> <!-- Technical field to specify the mode of the label printing wizard -->
<field name="is_template_report" invisible="1"/> <!-- Technical field to specify if the custom label template is selected -->
<div class="alert alert-info text-center mb-3" invisible="not message" role="alert">
<field name="message"/>
</div>
<group col="6" colspan="2" name="top_group">
<group name="output_format" col="2" colspan="1">
<field name="output" widget="badge"/>
</group>
<group name="label_report" col="2" colspan="2">
<field name="report_id" string="Label" widget="radio" />
</group>
<group name="label_template" col="2" colspan="2" invisible="not is_template_report">
<label for="report_id" string="Template"/>
<div class="o_row">
<a name="label_builder_link" invisible="not is_template_report" href="https://nexasystems.ca" title="How to get Product Label Builder" target="_blank" rel="noopener noreferrer">
<span>Get the Label Builder to create your own labels</span>
</a>
</div>
</group>
</group>
<div name="extra_action" class="oe_row">
<button name="action_sort_by_product" title="Sort labels by a product" icon="fa-sort-amount-desc" type="object" class="btn-xs btn-light"/>
<span class="text-muted px-3">|</span>
<field name="qty_per_product" class="mr-2 mt-1 text-right" help="Label quantity per product" style="width: 40px !important;"/>
<button name="action_set_qty" string="Set quantity" help="Set a certain quantity for each line." type="object" class="btn-xs btn-light mr-2"/>
<button name="action_restore_initial_qty" title="Restore initial quantity" help="Restore initial quantity" icon="fa-undo" type="object" class="btn-xs btn-light"/>
</div>
<notebook>
<page string="Labels" name="labels">
<field name="label_ids" mode="list">
<list editable="top" decoration-muted="qty==0">
<field name="sequence" widget="handle" width="0.5"/>
<field name="selected" widget="boolean_toggle"/>
<field name="partner_id" optional="hide"/>
<field name="product_id"/>
<field name="barcode" optional="show"/>
<field name="custom_value" optional="hide"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
<button name="action_minus_qty" type="object" title="Decrease Qty" icon="fa-minus" class="btn-light"/>
<field name="qty" sum="Total" width="0.6" class="text-center font-weight-bold"/>
<button name="action_plus_qty" type="object" title="Increase Qty" icon="fa-plus" class="btn-light"/>
</list>
</field>
</page>
<page string="Options" name="options">
<group name="general_options" class="o_label_nowrap">
<group>
<field name="lang" widget="radio" options="{'horizontal': true}"/>
<field name="humanreadable" widget="boolean_toggle" />
<field name="border_width" />
</group>
<group>
<field name="company_id" widget="radio" groups="base.group_multi_company"/>
</group>
</group>
</page>
</notebook>
</form>
</field>
</record>
<record id="action_print_label_from_template" model="ir.actions.act_window">
<field name="name">Custom Product Labels</field>
<field name="res_model">print.product.label</field>
<field name="view_mode">form</field>
<field name="target">current</field>
<field name="context">{'default_product_template_ids': context.get('active_ids', [])}</field>
<field name="binding_model_id" ref="product.model_product_template"/>
<field name="binding_type">report</field>
</record>
<record id="action_print_label_from_product" model="ir.actions.act_window">
<field name="name">Custom Product Labels</field>
<field name="res_model">print.product.label</field>
<field name="view_mode">form</field>
<field name="target">current</field>
<field name="context">{'default_product_product_ids': context.get('active_ids', [])}</field>
<field name="binding_model_id" ref="product.model_product_product"/>
<field name="binding_type">report</field>
</record>
</odoo>