changes
16
fusion_labels/fusion_labels/README.rst
Normal 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
|
||||
2
fusion_labels/fusion_labels/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizard
|
||||
34
fusion_labels/fusion_labels/__manifest__.py
Normal 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,
|
||||
}
|
||||
12
fusion_labels/fusion_labels/data/ir_filters_data.xml
Normal 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>
|
||||
@@ -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>
|
||||
14
fusion_labels/fusion_labels/data/product_data.xml
Normal 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>
|
||||
8
fusion_labels/fusion_labels/demo/ir_filters_demo.xml
Normal 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>
|
||||
21
fusion_labels/fusion_labels/demo/product_demo.xml
Normal 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>
|
||||
4
fusion_labels/fusion_labels/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import res_config_settings
|
||||
from . import product_template
|
||||
from . import product_product
|
||||
from . import print_label_type
|
||||
9
fusion_labels/fusion_labels/models/print_label_type.py
Normal 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.')]
|
||||
11
fusion_labels/fusion_labels/models/product_product.py
Normal 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
|
||||
11
fusion_labels/fusion_labels/models/product_template.py
Normal 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
|
||||
@@ -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')
|
||||
68
fusion_labels/fusion_labels/report/product_label_reports.xml
Normal 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>
|
||||
101
fusion_labels/fusion_labels/report/product_label_templates.xml
Normal 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>
|
||||
4
fusion_labels/fusion_labels/security/ir.model.access.csv
Normal 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
|
||||
|
BIN
fusion_labels/fusion_labels/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
118
fusion_labels/fusion_labels/static/description/index.html
Normal 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 © <a href="https://nexasystems.ca" target="_blank">Nexa Systems Inc</a></small></div>
|
||||
</div>
|
||||
</section>
|
||||
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 86 KiB |
1
fusion_labels/fusion_labels/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_print_product_label
|
||||
@@ -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'
|
||||
)
|
||||
@@ -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>
|
||||
2
fusion_labels/fusion_labels/wizard/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import print_product_label_line
|
||||
from . import print_product_label
|
||||
182
fusion_labels/fusion_labels/wizard/print_product_label.py
Normal 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])
|
||||
@@ -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})
|
||||
108
fusion_labels/fusion_labels/wizard/print_product_label_views.xml
Normal 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>
|
||||