Skip to content

Commit

Permalink
[IMP] printer_zpl2: Add GS1-128 barcode support and code improvements
Browse files Browse the repository at this point in the history
Add support for GS1-128 barcodes with configurable Application Identifiers:
- Support for common AIs (SSCC, GTIN, dates, weights, etc.)
- Automatic data formatting per GS1 specifications
- Unit of measure conversion for weight-based AIs (kg/lbs)
- Configurable field paths and sequence ordering
- Proper AI value formatting and validation
  • Loading branch information
Graeme Gellatly committed Feb 14, 2025
1 parent 8df7dfd commit 2bdde6a
Show file tree
Hide file tree
Showing 16 changed files with 758 additions and 136 deletions.
45 changes: 45 additions & 0 deletions printer_zpl2/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ Printer ZPL II
This module extends the **Report to printer** (``base_report_to_printer``)
module to add a ZPL II label printing feature.

Key features include:
* Design labels with components like text, barcodes, boxes, lines and images
* Support for GS1-128 barcodes with configurable Application Identifiers (AIs)
* Automatic data formatting according to GS1 specifications
* Unit of measure conversion for weight-based AIs

This module is meant to be used as a base for module development, and does not provide a GUI on its own.
See below for more details.

Expand All @@ -54,6 +60,27 @@ To configure this module, you need to:
#. Import ZPL2 code
#. Use the Test Mode tab during the creation

For GS1-128 barcodes, you can configure:

* Supported Application Identifiers (AIs):
* (00) SSCC
* (01) GTIN
* (10) Batch/Lot Number
* (11) Production Date (YYMMDD)
* (13) Packaging Date (YYMMDD)
* (15) Best Before Date (YYMMDD)
* (17) Expiration Date (YYMMDD)
* (21) Serial Number
* (30) Count
* (310n) Net Weight (kg)
* (320n) Net Weight (lbs)

* For each AI:
* Field path to get data from (e.g., "product_id.weight")
* For weight fields, UoM field path for automatic conversion
* Sequence order in the final barcode
* For weight AIs, number of decimal places (0-5)

It's also possible to add a label printing wizard on any model by creating a new *ir.actions.act_window* record.
For example, to add the printing wizard on the *product.product* model ::

Expand All @@ -76,6 +103,23 @@ Example : Print the label of a product ::
self.env['printing.printer'].browse(printer_id),
self.env['product.product'].browse(product_id))

For GS1-128 barcodes:

1. Create a new label component
2. Set the component type to "GS1-128"
3. Add Application Identifiers with their configurations:
* Select the AI type (e.g., GTIN, Weight, Date)
* Set the field path to get the data from
* For weight fields, optionally set the UoM field path
* Set the sequence to control AI order
* For weight fields, set the decimal places

The module will automatically:
* Format the data according to GS1 specifications
* Convert units of measure for weights
* Combine multiple AIs with proper separators
* Generate the final GS1-128 barcode

You can also use the generic label printing wizard, if added on some models.

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
Expand Down Expand Up @@ -124,6 +168,7 @@ Contributors
* Miquel Raïch <[email protected]>
* Lois Rilo <[email protected]>
* Tran Quoc Duong <[email protected]>
* Graeme Gellatly <[email protected]>

Maintainers
~~~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions printer_zpl2/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

from . import printing_label_zpl2
from . import printing_label_zpl2_component
from . import printing_label_zpl2_gs1_ai
143 changes: 7 additions & 136 deletions printer_zpl2/models/printing_label_zpl2.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from collections import defaultdict

import requests
from PIL import Image, ImageOps
from PIL import Image

from odoo import _, api, exceptions, fields, models
from odoo.exceptions import ValidationError
Expand Down Expand Up @@ -121,11 +121,6 @@ def check_recursion(self):
if id2 not in done:
todo.add(id2)

def _get_component_data(self, record, component, eval_args):
if component.data_autofill:
return component.autofill_data(record, eval_args)
return safe_eval(str(component.data), eval_args) or ""

def _get_to_data_to_print(
self,
record,
Expand Down Expand Up @@ -161,7 +156,7 @@ def _get_to_data_to_print(
),
}
)
data = self._get_component_data(record, component, eval_args)
data = component._get_data(record, eval_args)
if isinstance(data, str) and data == "component_not_show":
continue

Expand Down Expand Up @@ -207,135 +202,11 @@ def _generate_zpl2_components_data(
)

for (component, data, offset_x, offset_y) in to_print:
component_offset_x = component.origin_x + offset_x
component_offset_y = component.origin_y + offset_y
if component.component_type == "text":
barcode_arguments = {
field_name: component[field_name]
for field_name in [
zpl2.ARG_FONT,
zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT,
zpl2.ARG_WIDTH,
zpl2.ARG_REVERSE_PRINT,
zpl2.ARG_IN_BLOCK,
zpl2.ARG_BLOCK_WIDTH,
zpl2.ARG_BLOCK_LINES,
zpl2.ARG_BLOCK_SPACES,
zpl2.ARG_BLOCK_JUSTIFY,
zpl2.ARG_BLOCK_LEFT_MARGIN,
]
}
label_data.font_data(
component_offset_x, component_offset_y, barcode_arguments, data
)
elif component.component_type == "zpl2_raw":
label_data._write_command(data)
elif component.component_type == "rectangle":
label_data.graphic_box(
component_offset_x,
component_offset_y,
{
zpl2.ARG_WIDTH: component.width,
zpl2.ARG_HEIGHT: component.height,
zpl2.ARG_THICKNESS: component.thickness,
zpl2.ARG_COLOR: component.color,
zpl2.ARG_ROUNDING: component.rounding,
},
)
elif component.component_type == "diagonal":
label_data.graphic_diagonal_line(
component_offset_x,
component_offset_y,
{
zpl2.ARG_WIDTH: component.width,
zpl2.ARG_HEIGHT: component.height,
zpl2.ARG_THICKNESS: component.thickness,
zpl2.ARG_COLOR: component.color,
zpl2.ARG_DIAGONAL_ORIENTATION: component.diagonal_orientation,
},
)
elif component.component_type == "graphic":
# During the on_change don't take the bin_size
image = (
component.with_context(bin_size_graphic_image=False).graphic_image
or data
)
try:
pil_image = Image.open(io.BytesIO(base64.b64decode(image))).convert(
"RGB"
)
except Exception:
continue
if component.width and component.height:
pil_image = pil_image.resize((component.width, component.height))

# Invert the colors
if component.reverse_print:
pil_image = ImageOps.invert(pil_image)

# Rotation (PIL rotates counter clockwise)
if component.orientation == zpl2.ORIENTATION_ROTATED:
pil_image = pil_image.transpose(Image.ROTATE_270)
elif component.orientation == zpl2.ORIENTATION_INVERTED:
pil_image = pil_image.transpose(Image.ROTATE_180)
elif component.orientation == zpl2.ORIENTATION_BOTTOM_UP:
pil_image = pil_image.transpose(Image.ROTATE_90)

label_data.graphic_field(
component_offset_x, component_offset_y, pil_image
)
elif component.component_type == "circle":
label_data.graphic_circle(
component_offset_x,
component_offset_y,
{
zpl2.ARG_DIAMETER: component.width,
zpl2.ARG_THICKNESS: component.thickness,
zpl2.ARG_COLOR: component.color,
},
)
elif component.component_type == "sublabel":
component_offset_x += component.sublabel_id.origin_x
component_offset_y += component.sublabel_id.origin_y
component.sublabel_id._generate_zpl2_components_data(
label_data,
data if isinstance(data, models.BaseModel) else record,
label_offset_x=component_offset_x,
label_offset_y=component_offset_y,
)
else:
if component.component_type == zpl2.BARCODE_QR_CODE:
# Adding Control Arguments to QRCode data Label
data = "{}A,{}".format(component.error_correction, data)

barcode_arguments = {
field_name: component[field_name]
for field_name in [
zpl2.ARG_ORIENTATION,
zpl2.ARG_CHECK_DIGITS,
zpl2.ARG_HEIGHT,
zpl2.ARG_INTERPRETATION_LINE,
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
zpl2.ARG_SECURITY_LEVEL,
zpl2.ARG_COLUMNS_COUNT,
zpl2.ARG_ROWS_COUNT,
zpl2.ARG_TRUNCATE,
zpl2.ARG_MODULE_WIDTH,
zpl2.ARG_BAR_WIDTH_RATIO,
zpl2.ARG_MODEL,
zpl2.ARG_MAGNIFICATION_FACTOR,
zpl2.ARG_ERROR_CORRECTION,
zpl2.ARG_MASK_VALUE,
]
}
label_data.barcode_data(
component.origin_x + offset_x,
component.origin_y + offset_y,
component.component_type,
barcode_arguments,
data,
)
getattr(
component,
"_process_type_%s" % component.component_type,
component._process_type_barcode,
)(label_data, data, offset_x, offset_y, record)

def _generate_zpl2_data(self, record, page_count=1, **extra):
self.ensure_one()
Expand Down
Loading

0 comments on commit 2bdde6a

Please sign in to comment.