Initial commit

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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