[MIG] printer_zpl2: Migration to 13.0

This commit is contained in:
Rod Schouteden
2019-09-30 15:35:28 +02:00
committed by mreficent
parent fe3208cd64
commit 4291cca6ca
23 changed files with 2074 additions and 1345 deletions

View File

@@ -1,10 +1,29 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg ==============
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html Printer ZPL II
:alt: License: AGPL-3 ==============
===================== .. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ZPL II Label printing !! This file is generated by oca-gen-addon-readme !!
===================== !! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freport--print--send-lightgray.png?logo=github
:target: https://github.com/OCA/report-print-send/tree/13.0/printer_zpl2
:alt: OCA/report-print-send
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/report-print-send-13-0/report-print-send-13-0-printer_zpl2
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/144/13.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module extends the **Report to printer** (``base_report_to_printer``) This module extends the **Report to printer** (``base_report_to_printer``)
module to add a ZPL II label printing feature. module to add a ZPL II label printing feature.
@@ -12,6 +31,11 @@ module to add a ZPL II label printing feature.
This module is meant to be used as a base for module development, and does not provide a GUI on its own. 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. See below for more details.
**Table of contents**
.. contents::
:local:
Installation Installation
============ ============
@@ -55,45 +79,59 @@ You can also use the generic label printing wizard, if added on some models.
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/144/12.0 :target: https://runbot.odoo-community.org/runbot/144/12.0
Known issues / Roadmap Changelog
====================== =========
* Develop a "Designer" view in a separate module, to allow drawing labels with simple mouse clicks/drags 13.0.1.0.0 (2019-09-30)
~~~~~~~~~~~~~~~~~~~~~~~
* [RELEASE] Port from V12.
* Selection lists do not support integers any longer
* Binary field now returns False when empty instead of none,
change tests to reflect this
* work around an appels vs oranges warning
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues Bugs are tracked on `GitHub Issues <https://github.com/OCA/report-print-send/issues>`_.
<https://github.com/OCA/report-print-send/issues>`_. In case of trouble, please In case of trouble, please check there if your issue has already been reported.
check there if your issue has already been reported. If you spotted it first, If you spotted it first, help us smashing it by providing a detailed and welcomed
help us smashing it by providing a detailed and welcomed feedback. `feedback <https://github.com/OCA/report-print-send/issues/new?body=module:%20printer_zpl2%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits Credits
======= =======
Images Authors
------ ~~~~~~~
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. * SUBTENO-IT
* FLorent de Labarre
* Apertoso NV
Contributors Contributors
------------ ~~~~~~~~~~~~
* Sylvain Garancher <sylvain.garancher@syleam.fr> * Sylvain Garancher <sylvain.garancher@syleam.fr>
* Florent de Labarre * Florent de Labarre
* Jos De Graeve <Jos.DeGraeve@apertoso.be> * Jos De Graeve <Jos.DeGraeve@apertoso.be>
* Rod Schouteden <rod.schouteden@dynapps.be>
Maintainer Maintainers
---------- ~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png .. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association :alt: Odoo Community Association
:target: https://odoo-community.org :target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use. promote its widespread use.
To contribute to this module, please visit https://odoo-community.org. This module is part of the `OCA/report-print-send <https://github.com/OCA/report-print-send/tree/13.0/printer_zpl2>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -2,24 +2,20 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Printer ZPL II', "name": "Printer ZPL II",
'version': '12.0.1.0.0', "version": "13.0.1.0.0",
'category': 'Printer', "category": "Printer",
'author': 'SUBTENO-IT, FLorent de Labarre, ' "author": "SUBTENO-IT, FLorent de Labarre, "
'Apertoso NV, Odoo Community Association (OCA)', "Apertoso NV, Odoo Community Association (OCA)",
'website': 'http://www.syleam.fr/', "website": "http://www.syleam.fr/",
'license': 'AGPL-3', "license": "AGPL-3",
'external_dependencies': { "external_dependencies": {"python": ["zpl2"]},
'python': ['zpl2'], "depends": ["base_report_to_printer"],
}, "data": [
'depends': [ "security/ir.model.access.csv",
'base_report_to_printer', "views/printing_label_zpl2.xml",
"wizard/print_record_label.xml",
"wizard/wizard_import_zpl2.xml",
], ],
'data': [ "installable": True,
'security/ir.model.access.csv',
'views/printing_label_zpl2.xml',
'wizard/print_record_label.xml',
'wizard/wizard_import_zpl2.xml',
],
'installable': True,
} }

View File

@@ -1,14 +1,16 @@
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>) # Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import time
import base64 import base64
import datetime import datetime
import io import io
import logging import logging
import time
import requests import requests
from PIL import Image, ImageOps from PIL import Image, ImageOps
from odoo import api, exceptions, fields, models, _
from odoo import _, api, exceptions, fields, models
from odoo.tools.safe_eval import safe_eval from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -16,59 +18,248 @@ _logger = logging.getLogger(__name__)
try: try:
import zpl2 import zpl2
except ImportError: except ImportError:
_logger.debug('Cannot `import zpl2`.') _logger.debug("Cannot `import zpl2`.")
class PrintingLabelZpl2(models.Model): class PrintingLabelZpl2(models.Model):
_name = 'printing.label.zpl2' _name = "printing.label.zpl2"
_description = 'ZPL II Label' _description = "ZPL II Label"
_order = 'model_id, name, id' _order = "model_id, name, id"
name = fields.Char(required=True, help='Label Name.') name = fields.Char(required=True, help="Label Name.")
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
description = fields.Char(help='Long description for this label.') description = fields.Char(help="Long description for this label.")
model_id = fields.Many2one( model_id = fields.Many2one(
comodel_name='ir.model', string='Model', required=True, comodel_name="ir.model",
help='Model used to print this label.') string="Model",
required=True,
help="Model used to print this label.",
)
origin_x = fields.Integer( origin_x = fields.Integer(
required=True, default=10, required=True,
help='Origin point of the contents in the label, X coordinate.') default=10,
help="Origin point of the contents in the label, X coordinate.",
)
origin_y = fields.Integer( origin_y = fields.Integer(
required=True, default=10, required=True,
help='Origin point of the contents in the label, Y coordinate.') default=10,
help="Origin point of the contents in the label, Y coordinate.",
)
width = fields.Integer( width = fields.Integer(
required=True, default=480, required=True,
help='Width of the label, will be set on the printer before printing.') default=480,
help="Width of the label, will be set on the printer before printing.",
)
component_ids = fields.One2many( component_ids = fields.One2many(
comodel_name='printing.label.zpl2.component', inverse_name='label_id', comodel_name="printing.label.zpl2.component",
string='Label Components', inverse_name="label_id",
help='Components which will be printed on the label.', copy=True) string="Label Components",
help="Components which will be printed on the label.",
copy=True,
)
restore_saved_config = fields.Boolean( restore_saved_config = fields.Boolean(
string="Restore printer's configuration", string="Restore printer's configuration",
help="Restore printer's saved configuration and end of each label ", help="Restore printer's saved configuration and end of each label ",
default=True) default=True,
)
action_window_id = fields.Many2one( action_window_id = fields.Many2one(
comodel_name='ir.actions.act_window', string='Action', readonly=True) comodel_name="ir.actions.act_window", string="Action", readonly=True
test_print_mode = fields.Boolean(string='Mode Print') )
test_labelary_mode = fields.Boolean(string='Mode Labelary') test_print_mode = fields.Boolean(string="Mode Print")
record_id = fields.Integer(string='Record ID', default=1) test_labelary_mode = fields.Boolean(string="Mode Labelary")
extra = fields.Text(string="Extra", default='{}') record_id = fields.Integer(string="Record ID", default=1)
printer_id = fields.Many2one( extra = fields.Text(default="{}")
comodel_name='printing.printer', string='Printer') printer_id = fields.Many2one(comodel_name="printing.printer", string="Printer")
labelary_image = fields.Binary(string='Image from Labelary', readonly=True) labelary_image = fields.Binary(string="Image from Labelary", readonly=True)
labelary_dpmm = fields.Selection( labelary_dpmm = fields.Selection(
selection=[ selection=[
('6dpmm', '6dpmm (152 pdi)'), ("6dpmm", "6dpmm (152 pdi)"),
('8dpmm', '8dpmm (203 dpi)'), ("8dpmm", "8dpmm (203 dpi)"),
('12dpmm', '12dpmm (300 pdi)'), ("12dpmm", "12dpmm (300 pdi)"),
('24dpmm', '24dpmm (600 dpi)'), ("24dpmm", "24dpmm (600 dpi)"),
], string='Print density', required=True, default='8dpmm') ],
labelary_width = fields.Float(string='Width in mm', default=140) string="Print density",
labelary_height = fields.Float(string='Height in mm', default=70) required=True,
default="8dpmm",
)
labelary_width = fields.Float(string="Width in mm", default=140)
labelary_height = fields.Float(string="Height in mm", default=70)
def _generate_zpl2_components_data_repeatable(
self, component, data, label_offset_x, label_offset_y
):
to_print = []
# Generate a list of elements if the component is repeatable
for idx in range(
component.repeat_offset, component.repeat_offset + component.repeat_count
):
printed_data = data
# Pick the right value if data is a collection
if isinstance(data, (list, tuple, set, models.BaseModel)):
# If we reached the end of data, quit the loop
if idx >= len(data):
break
# Set the real data to display
printed_data = data[idx]
position = idx - component.repeat_offset
to_print.append(
(
component,
printed_data,
label_offset_x + component.repeat_offset_x * position,
label_offset_y + component.repeat_offset_y * position,
)
)
return to_print
def _component_type_selector(
self,
component,
label_data,
component_offset_x,
component_offset_y,
offset_x,
offset_y,
data,
):
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:
return
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,
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,
)
def _generate_zpl2_components_data( def _generate_zpl2_components_data(
self, label_data, record, page_number=1, page_count=1, self,
label_offset_x=0, label_offset_y=0, **extra): label_data,
record,
page_number=1,
page_count=1,
label_offset_x=0,
label_offset_y=0,
**extra
):
self.ensure_one() self.ensure_one()
# Add all elements to print in a list of tuples : # Add all elements to print in a list of tuples :
@@ -76,161 +267,41 @@ class PrintingLabelZpl2(models.Model):
to_print = [] to_print = []
for component in self.component_ids: for component in self.component_ids:
eval_args = extra eval_args = extra
eval_args.update({ eval_args.update(
'object': record, {
'page_number': str(page_number + 1), "object": record,
'page_count': str(page_count), "page_number": str(page_number + 1),
'time': time, "page_count": str(page_count),
'datetime': datetime, "time": time,
}) "datetime": datetime,
data = safe_eval(component.data, eval_args) or '' }
)
data = safe_eval(component.data, eval_args) or ""
if data == 'component_not_show': if isinstance(data, str) and data == "component_not_show":
continue continue
to_print = self._generate_zpl2_components_data_repeatable(
# Generate a list of elements if the component is repeatable component, data, label_offset_x, label_offset_y
for idx in range( )
component.repeat_offset,
component.repeat_offset + component.repeat_count):
printed_data = data
# Pick the right value if data is a collection
if isinstance(data, (list, tuple, set, models.BaseModel)):
# If we reached the end of data, quit the loop
if idx >= len(data):
break
# Set the real data to display
printed_data = data[idx]
position = idx - component.repeat_offset
to_print.append((
component, printed_data,
label_offset_x + component.repeat_offset_x * position,
label_offset_y + component.repeat_offset_y * position,
))
for (component, data, offset_x, offset_y) in to_print: for (component, data, offset_x, offset_y) in to_print:
component_offset_x = component.origin_x + offset_x component_offset_x = component.origin_x + offset_x
component_offset_y = component.origin_y + offset_y component_offset_y = component.origin_y + offset_y
if component.component_type == 'text': self._component_type_selector(
barcode_arguments = dict([ component,
(field_name, component[field_name]) label_data,
for field_name in [ component_offset_x,
zpl2.ARG_FONT, component_offset_y,
zpl2.ARG_ORIENTATION, offset_x,
zpl2.ARG_HEIGHT, offset_y,
zpl2.ARG_WIDTH, data,
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,
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 = dict([
(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)
def _generate_zpl2_data(self, record, page_count=1, **extra): def _generate_zpl2_data(self, record, page_count=1, **extra):
self.ensure_one() self.ensure_one()
label_data = zpl2.Zpl2() label_data = zpl2.Zpl2()
labelary_emul = extra.get('labelary_emul', False) labelary_emul = extra.get("labelary_emul", False)
for page_number in range(page_count): for page_number in range(page_count):
# Initialize printer's configuration # Initialize printer's configuration
label_data.label_start() label_data.label_start()
@@ -241,8 +312,12 @@ class PrintingLabelZpl2(models.Model):
label_data.label_home(self.origin_x, self.origin_y) label_data.label_home(self.origin_x, self.origin_y)
self._generate_zpl2_components_data( self._generate_zpl2_components_data(
label_data, record, page_number=page_number, label_data,
page_count=page_count, **extra) record,
page_number=page_number,
page_count=page_count,
**extra
)
# Restore printer's configuration and end the label # Restore printer's configuration and end the label
if self.restore_saved_config: if self.restore_saved_config:
@@ -255,51 +330,53 @@ class PrintingLabelZpl2(models.Model):
for label in self: for label in self:
if record._name != label.model_id.model: if record._name != label.model_id.model:
raise exceptions.UserError( raise exceptions.UserError(
_('This label cannot be used on {model}').format( _("This label cannot be used on {model}").format(model=record._name)
model=record._name)) )
# Send the label to printer # Send the label to printer
label_contents = label._generate_zpl2_data( label_contents = label._generate_zpl2_data(
record, page_count=page_count, **extra) record, page_count=page_count, **extra
)
printer.print_document( printer.print_document(
report=None, content=label_contents, doc_format='raw') report=None, content=label_contents, doc_format="raw"
)
return True return True
def create_action(self): def create_action(self):
for label in self.filtered(lambda record: not record.action_window_id): for label in self.filtered(lambda record: not record.action_window_id):
label.action_window_id = self.env['ir.actions.act_window'].create({ label.action_window_id = self.env["ir.actions.act_window"].create(
'name': _('Print Label'), {
'src_model': label.model_id.model, "name": _("Print Label"),
'binding_model_id': label.model_id.id, "binding_model_id": label.model_id.id,
'res_model': 'wizard.print.record.label', "res_model": "wizard.print.record.label",
'view_mode': 'form', "view_mode": "form",
'target': 'new', "target": "new",
'binding_type': 'action', "binding_type": "action",
}) }
)
return True return True
def unlink_action(self): def unlink_action(self):
self.mapped('action_window_id').unlink() self.mapped("action_window_id").unlink()
def import_zpl2(self): def import_zpl2(self):
self.ensure_one() self.ensure_one()
return { return {
'view_type': 'form', "view_mode": "form",
'view_mode': 'form', "res_model": "wizard.import.zpl2",
'res_model': 'wizard.import.zpl2', "type": "ir.actions.act_window",
'type': 'ir.actions.act_window', "target": "new",
'target': 'new', "context": {"default_label_id": self.id},
'context': {'default_label_id': self.id},
} }
def _get_record(self): def _get_record(self):
self.ensure_one() self.ensure_one()
Obj = self.env[self.model_id.model] Obj = self.env[self.model_id.model]
record = Obj.search([('id', '=', self.record_id)], limit=1) record = Obj.search([("id", "=", self.record_id)], limit=1)
if not record: if not record:
record = Obj.search([], limit=1, order='id desc') record = Obj.search([], limit=1, order="id desc")
self.record_id = record.id self.record_id = record.id
return record return record
@@ -308,58 +385,66 @@ class PrintingLabelZpl2(models.Model):
for label in self: for label in self:
if label.test_print_mode and label.record_id and label.printer_id: if label.test_print_mode and label.record_id and label.printer_id:
record = label._get_record() record = label._get_record()
extra = safe_eval(label.extra, {'env': self.env}) extra = safe_eval(label.extra, {"env": self.env})
if record: if record:
label.print_label(label.printer_id, record, **extra) label.print_label(label.printer_id, record, **extra)
@api.onchange( @api.onchange(
'record_id', 'labelary_dpmm', 'labelary_width', 'labelary_height', "record_id",
'component_ids', 'origin_x', 'origin_y') "labelary_dpmm",
"labelary_width",
"labelary_height",
"component_ids",
"origin_x",
"origin_y",
)
def _on_change_labelary(self): def _on_change_labelary(self):
self.ensure_one() self.ensure_one()
if not(self.test_labelary_mode and self.record_id and if not (
self.labelary_width and self.labelary_height and self.test_labelary_mode
self.labelary_dpmm and self.component_ids): and self.record_id
and self.labelary_width
and self.labelary_height
and self.labelary_dpmm
and self.component_ids
):
return return
record = self._get_record() record = self._get_record()
if record: if record:
# If case there an error (in the data field with the safe_eval # If case there an error (in the data field with the safe_eval
# for exemple) the new component or the update is not lost. # for exemple) the new component or the update is not lost.
try: try:
url = 'http://api.labelary.com/v1/printers/' \ url = (
'{dpmm}/labels/{width}x{height}/0/' "http://api.labelary.com/v1/printers/"
"{dpmm}/labels/{width}x{height}/0/"
)
width = round(self.labelary_width / 25.4, 2) width = round(self.labelary_width / 25.4, 2)
height = round(self.labelary_height / 25.4, 2) height = round(self.labelary_height / 25.4, 2)
url = url.format( url = url.format(dpmm=self.labelary_dpmm, width=width, height=height)
dpmm=self.labelary_dpmm, width=width, height=height) extra = safe_eval(self.extra, {"env": self.env})
extra = safe_eval(self.extra, {'env': self.env}) zpl_file = self._generate_zpl2_data(record, labelary_emul=True, **extra)
zpl_file = self._generate_zpl2_data( files = {"file": zpl_file}
record, labelary_emul=True, **extra) headers = {"Accept": "image/png"}
files = {'file': zpl_file} response = requests.post(url, headers=headers, files=files, stream=True)
headers = {'Accept': 'image/png'}
response = requests.post(
url, headers=headers, files=files, stream=True)
if response.status_code == 200: if response.status_code == 200:
# Add a padd # Add a padd
im = Image.open(io.BytesIO(response.content)) im = Image.open(io.BytesIO(response.content))
im_size = im.size im_size = im.size
new_im = Image.new( new_im = Image.new(
'RGB', (im_size[0] + 2, im_size[1] + 2), "RGB", (im_size[0] + 2, im_size[1] + 2), (164, 164, 164)
(164, 164, 164)) )
new_im.paste(im, (1, 1)) new_im.paste(im, (1, 1))
imgByteArr = io.BytesIO() imgByteArr = io.BytesIO()
new_im.save(imgByteArr, format='PNG') new_im.save(imgByteArr, format="PNG")
self.labelary_image = base64.b64encode( self.labelary_image = base64.b64encode(imgByteArr.getvalue())
imgByteArr.getvalue())
else: else:
return {'warning': { return {
'title': _('Error with Labelary API.'), "warning": {
'message': response.status_code, "title": _("Error with Labelary API."),
}} "message": response.status_code,
}
}
except Exception as e: except Exception as e:
self.labelary_image = False self.labelary_image = False
return {'warning': { return {"warning": {"title": _("Some thing is wrong."), "message": e}}
'title': _('Some thing is wrong.'),
'message': e,
}}

View File

@@ -2,6 +2,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging import logging
from odoo import fields, models from odoo import fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -9,7 +10,7 @@ _logger = logging.getLogger(__name__)
try: try:
import zpl2 import zpl2
except ImportError: except ImportError:
_logger.debug('Cannot `import zpl2`.') _logger.debug("Cannot `import zpl2`.")
DEFAULT_PYTHON_CODE = """# Python One-Liners DEFAULT_PYTHON_CODE = """# Python One-Liners
# - object: record on which the action is triggered; may be be void # - object: record on which the action is triggered; may be be void
@@ -25,162 +26,208 @@ DEFAULT_PYTHON_CODE = """# Python One-Liners
class PrintingLabelZpl2Component(models.Model): class PrintingLabelZpl2Component(models.Model):
_name = 'printing.label.zpl2.component' _name = "printing.label.zpl2.component"
_description = 'ZPL II Label Component' _description = "ZPL II Label Component"
_order = 'sequence, id' _order = "sequence, id"
label_id = fields.Many2one( label_id = fields.Many2one(
comodel_name='printing.label.zpl2', string='Label', comodel_name="printing.label.zpl2",
required=True, ondelete='cascade', help='Label using this component.') string="Label",
sequence = fields.Integer(help='Order used to print the elements.') required=True,
name = fields.Char(required=True, help='Name of the component.') ondelete="cascade",
help="Label using this component.",
)
sequence = fields.Integer(help="Order used to print the elements.")
name = fields.Char(required=True, help="Name of the component.")
origin_x = fields.Integer( origin_x = fields.Integer(
required=True, default=10, required=True,
help='Origin point of the component in the label, X coordinate.') default=10,
help="Origin point of the component in the label, X coordinate.",
)
origin_y = fields.Integer( origin_y = fields.Integer(
required=True, default=10, required=True,
help='Origin point of the component in the label, Y coordinate.') default=10,
help="Origin point of the component in the label, Y coordinate.",
)
component_type = fields.Selection( component_type = fields.Selection(
selection=[ selection=[
('text', 'Text'), ("text", "Text"),
('rectangle', 'Rectangle / Line'), ("rectangle", "Rectangle / Line"),
('diagonal', 'Diagonal Line'), ("diagonal", "Diagonal Line"),
('circle', 'Circle'), ("circle", "Circle"),
('graphic', 'Graphic'), ("graphic", "Graphic"),
(zpl2.BARCODE_CODE_11, 'Code 11'), (zpl2.BARCODE_CODE_11, "Code 11"),
(zpl2.BARCODE_INTERLEAVED_2_OF_5, 'Interleaved 2 of 5'), (zpl2.BARCODE_INTERLEAVED_2_OF_5, "Interleaved 2 of 5"),
(zpl2.BARCODE_CODE_39, 'Code 39'), (zpl2.BARCODE_CODE_39, "Code 39"),
(zpl2.BARCODE_CODE_49, 'Code 49'), (zpl2.BARCODE_CODE_49, "Code 49"),
(zpl2.BARCODE_PDF417, 'PDF417'), (zpl2.BARCODE_PDF417, "PDF417"),
(zpl2.BARCODE_EAN_8, 'EAN-8'), (zpl2.BARCODE_EAN_8, "EAN-8"),
(zpl2.BARCODE_UPC_E, 'UPC-E'), (zpl2.BARCODE_UPC_E, "UPC-E"),
(zpl2.BARCODE_CODE_128, 'Code 128'), (zpl2.BARCODE_CODE_128, "Code 128"),
(zpl2.BARCODE_EAN_13, 'EAN-13'), (zpl2.BARCODE_EAN_13, "EAN-13"),
(zpl2.BARCODE_QR_CODE, 'QR Code'), (zpl2.BARCODE_QR_CODE, "QR Code"),
('sublabel', 'Sublabel'), ("sublabel", "Sublabel"),
('zpl2_raw', 'ZPL2'), ("zpl2_raw", "ZPL2"),
], string='Type', required=True, default='text', oldname='type', ],
help='Type of content, simple text or barcode.') string="Type",
required=True,
default="text",
help="Type of content, simple text or barcode.",
)
font = fields.Selection( font = fields.Selection(
selection=[ selection=[
(zpl2.FONT_DEFAULT, 'Default'), (zpl2.FONT_DEFAULT, "Default"),
(zpl2.FONT_9X5, '9x5'), (zpl2.FONT_9X5, "9x5"),
(zpl2.FONT_11X7, '11x7'), (zpl2.FONT_11X7, "11x7"),
(zpl2.FONT_18X10, '18x10'), (zpl2.FONT_18X10, "18x10"),
(zpl2.FONT_28X15, '28x15'), (zpl2.FONT_28X15, "28x15"),
(zpl2.FONT_26X13, '26x13'), (zpl2.FONT_26X13, "26x13"),
(zpl2.FONT_60X40, '60x40'), (zpl2.FONT_60X40, "60x40"),
(zpl2.FONT_21X13, '21x13'), (zpl2.FONT_21X13, "21x13"),
], required=True, default=zpl2.FONT_DEFAULT, ],
help='Font to use, for text only.') required=True,
thickness = fields.Integer(help='Thickness of the line to draw.') default=zpl2.FONT_DEFAULT,
help="Font to use, for text only.",
)
thickness = fields.Integer(help="Thickness of the line to draw.")
color = fields.Selection( color = fields.Selection(
selection=[ selection=[(zpl2.COLOR_BLACK, "Black"), (zpl2.COLOR_WHITE, "White")],
(zpl2.COLOR_BLACK, 'Black'), default=zpl2.COLOR_BLACK,
(zpl2.COLOR_WHITE, 'White'), help="Color of the line to draw.",
], default=zpl2.COLOR_BLACK, )
help='Color of the line to draw.')
orientation = fields.Selection( orientation = fields.Selection(
selection=[ selection=[
(zpl2.ORIENTATION_NORMAL, 'Normal'), (zpl2.ORIENTATION_NORMAL, "Normal"),
(zpl2.ORIENTATION_ROTATED, 'Rotated'), (zpl2.ORIENTATION_ROTATED, "Rotated"),
(zpl2.ORIENTATION_INVERTED, 'Inverted'), (zpl2.ORIENTATION_INVERTED, "Inverted"),
(zpl2.ORIENTATION_BOTTOM_UP, 'Read from Bottom up'), (zpl2.ORIENTATION_BOTTOM_UP, "Read from Bottom up"),
], required=True, default=zpl2.ORIENTATION_NORMAL, ],
help='Orientation of the barcode.') required=True,
default=zpl2.ORIENTATION_NORMAL,
help="Orientation of the barcode.",
)
diagonal_orientation = fields.Selection( diagonal_orientation = fields.Selection(
selection=[ selection=[
(zpl2.DIAGONAL_ORIENTATION_LEFT, 'Left (\\)'), (zpl2.DIAGONAL_ORIENTATION_LEFT, "Left (\\)"),
(zpl2.DIAGONAL_ORIENTATION_RIGHT, 'Right (/)'), (zpl2.DIAGONAL_ORIENTATION_RIGHT, "Right (/)"),
], default=zpl2.DIAGONAL_ORIENTATION_LEFT, ],
help='Orientation of the diagonal line.') default=zpl2.DIAGONAL_ORIENTATION_LEFT,
help="Orientation of the diagonal line.",
)
check_digits = fields.Boolean( check_digits = fields.Boolean(
help='Check if you want to compute and print the check digit.') help="Check if you want to compute and print the check digit."
)
height = fields.Integer( height = fields.Integer(
help='Height of the printed component. For a text component, height ' help="Height of the printed component. For a text component, height "
'of a single character.') "of a single character."
)
width = fields.Integer( width = fields.Integer(
help='Width of the printed component. For a text component, width of ' help="Width of the printed component. For a text component, width of "
'a single character.') "a single character."
rounding = fields.Integer( )
help='Rounding of the printed rectangle corners.') rounding = fields.Integer(help="Rounding of the printed rectangle corners.")
interpretation_line = fields.Boolean( interpretation_line = fields.Boolean(
help='Check if you want the interpretation line to be printed.') help="Check if you want the interpretation line to be printed."
)
interpretation_line_above = fields.Boolean( interpretation_line_above = fields.Boolean(
help='Check if you want the interpretation line to be printed above ' help="Check if you want the interpretation line to be printed above "
'the barcode.') "the barcode."
module_width = fields.Integer( )
default=2, help='Module width for the barcode.') module_width = fields.Integer(default=2, help="Module width for the barcode.")
bar_width_ratio = fields.Float( bar_width_ratio = fields.Float(
default=3.0, help='Ratio between wide bar and narrow bar.') default=3.0, help="Ratio between wide bar and narrow bar."
security_level = fields.Integer(help='Security level for error detection.') )
columns_count = fields.Integer(help='Number of data columns to encode.') security_level = fields.Integer(help="Security level for error detection.")
rows_count = fields.Integer(help='Number of rows to encode.') columns_count = fields.Integer(help="Number of data columns to encode.")
truncate = fields.Boolean( rows_count = fields.Integer(help="Number of rows to encode.")
help='Check if you want to truncate the barcode.') truncate = fields.Boolean(help="Check if you want to truncate the barcode.")
model = fields.Selection( model = fields.Selection(
selection=[ selection=[
(zpl2.MODEL_ORIGINAL, 'Original'), (str(zpl2.MODEL_ORIGINAL), "Original"),
(zpl2.MODEL_ENHANCED, 'Enhanced'), (str(zpl2.MODEL_ENHANCED), "Enhanced"),
], default=zpl2.MODEL_ENHANCED, ],
help='Barcode model, used by some barcode types like QR Code.') default=str(zpl2.MODEL_ENHANCED),
help="Barcode model, used by some barcode types like QR Code.",
)
magnification_factor = fields.Integer( magnification_factor = fields.Integer(
default=1, help='Magnification Factor, from 1 to 10.') default=1, help="Magnification Factor, from 1 to 10."
)
error_correction = fields.Selection( error_correction = fields.Selection(
selection=[ selection=[
(zpl2.ERROR_CORRECTION_ULTRA_HIGH, 'Ultra-high Reliability Level'), (zpl2.ERROR_CORRECTION_ULTRA_HIGH, "Ultra-high Reliability Level"),
(zpl2.ERROR_CORRECTION_HIGH, 'High Reliability Level'), (zpl2.ERROR_CORRECTION_HIGH, "High Reliability Level"),
(zpl2.ERROR_CORRECTION_STANDARD, 'Standard Level'), (zpl2.ERROR_CORRECTION_STANDARD, "Standard Level"),
(zpl2.ERROR_CORRECTION_HIGH_DENSITY, 'High Density Level'), (zpl2.ERROR_CORRECTION_HIGH_DENSITY, "High Density Level"),
], required=True, default=zpl2.ERROR_CORRECTION_HIGH, ],
help='Error correction for some barcode types like QR Code.') required=True,
mask_value = fields.Integer(default=7, help='Mask Value, from 0 to 7.') default=zpl2.ERROR_CORRECTION_HIGH,
help="Error correction for some barcode types like QR Code.",
)
mask_value = fields.Integer(default=7, help="Mask Value, from 0 to 7.")
data = fields.Text( data = fields.Text(
default=DEFAULT_PYTHON_CODE, required=True, default=DEFAULT_PYTHON_CODE,
help='Data to print on this component. Resource values can be ' required=True,
'inserted with %(object.field_name)s.') help="Data to print on this component. Resource values can be "
"inserted with %(object.field_name)s.",
)
sublabel_id = fields.Many2one( sublabel_id = fields.Many2one(
comodel_name='printing.label.zpl2', string='Sublabel', comodel_name="printing.label.zpl2",
help='Another label to include into this one as a component. ' string="Sublabel",
'This allows to define reusable labels parts.') help="Another label to include into this one as a component. "
"This allows to define reusable labels parts.",
)
repeat = fields.Boolean( repeat = fields.Boolean(
string='Repeatable', string="Repeatable",
help='Check this box to repeat this component on the label.') help="Check this box to repeat this component on the label.",
)
repeat_offset = fields.Integer( repeat_offset = fields.Integer(
default=0, default=0, help="Number of elements to skip when reading a list of elements."
help='Number of elements to skip when reading a list of elements.') )
repeat_count = fields.Integer( repeat_count = fields.Integer(
default=1, default=1, help="Maximum count of repeats of the component."
help='Maximum count of repeats of the component.') )
repeat_offset_x = fields.Integer( repeat_offset_x = fields.Integer(
help='X coordinate offset between each occurence of this component on ' help="X coordinate offset between each occurence of this component on "
'the label.') "the label."
)
repeat_offset_y = fields.Integer( repeat_offset_y = fields.Integer(
help='Y coordinate offset between each occurence of this component on ' help="Y coordinate offset between each occurence of this component on "
'the label.') "the label."
)
reverse_print = fields.Boolean( reverse_print = fields.Boolean(
help='If checked, the data will be printed in the inverse color of ' help="If checked, the data will be printed in the inverse color of "
'the background.') "the background."
)
in_block = fields.Boolean( in_block = fields.Boolean(
help='If checked, the data will be restrected in a ' help="If checked, the data will be restrected in a "
'defined block on the label.') "defined block on the label."
block_width = fields.Integer(help='Width of the block.') )
block_width = fields.Integer(help="Width of the block.")
block_lines = fields.Integer( block_lines = fields.Integer(
default=1, help='Maximum number of lines to print in the block.') default=1, help="Maximum number of lines to print in the block."
)
block_spaces = fields.Integer( block_spaces = fields.Integer(
help='Number of spaces added between lines in the block.') help="Number of spaces added between lines in the block."
)
block_justify = fields.Selection( block_justify = fields.Selection(
selection=[ selection=[
(zpl2.JUSTIFY_LEFT, 'Left'), (zpl2.JUSTIFY_LEFT, "Left"),
(zpl2.JUSTIFY_CENTER, 'Center'), (zpl2.JUSTIFY_CENTER, "Center"),
(zpl2.JUSTIFY_JUSTIFIED, 'Justified'), (zpl2.JUSTIFY_JUSTIFIED, "Justified"),
(zpl2.JUSTIFY_RIGHT, 'Right'), (zpl2.JUSTIFY_RIGHT, "Right"),
], string='Justify', required=True, default='L', ],
help='Choose how the text will be justified in the block.') string="Justify",
required=True,
default="L",
help="Choose how the text will be justified in the block.",
)
block_left_margin = fields.Integer( block_left_margin = fields.Integer(
string='Left Margin', string="Left Margin",
help='Left margin for the second and other lines in the block.') help="Left margin for the second and other lines in the block.",
)
graphic_image = fields.Binary( graphic_image = fields.Binary(
string='Image', attachment=True, string="Image",
help='This field holds a static image to print. ' attachment=True,
'If not set, the data field is evaluated.') help="This field holds a static image to print. "
"If not set, the data field is evaluated.",
)

View File

@@ -0,0 +1,17 @@
To configure this module, you need to:
#. Go to *Settings > Printing > Labels > ZPL II*
#. Create new labels
#. Import ZPL2 code
#. Use the Test Mode tab during the creation
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 ::
<act_window id="action_wizard_purchase"
name="Print Label"
src_model="product.product"
res_model="wizard.print.record.label"
view_mode="form"
target="new"
key2="client_action_multi"/>

View File

@@ -0,0 +1,4 @@
* Sylvain Garancher <sylvain.garancher@syleam.fr>
* Florent de Labarre
* Jos De Graeve <Jos.DeGraeve@apertoso.be>
* Rod Schouteden <rod.schouteden@dynapps.be>

View File

@@ -0,0 +1,5 @@
This module extends the **Report to printer** (``base_report_to_printer``)
module to add a ZPL II label printing feature.
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.

View File

@@ -0,0 +1,8 @@
13.0.1.0.0 (2019-09-30)
~~~~~~~~~~~~~~~~~~~~~~~
* [RELEASE] Port from V12.
* Selection lists do not support integers any longer
* Binary field now returns False when empty instead of none,
change tests to reflect this
* work around an appels vs oranges warning

View File

@@ -0,0 +1 @@
Nothing special, just install the module.

View File

@@ -0,0 +1,13 @@
To print a label, you need to call use the label printing method from anywhere (other modules, server actions, etc.).
Example : Print the label of a product ::
self.env['printing.label.zpl2'].browse(label_id).print_label(
self.env['printing.printer'].browse(printer_id),
self.env['product.product'].browse(product_id))
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
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/144/12.0

View File

@@ -0,0 +1,484 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Printer ZPL II</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="printer-zpl-ii">
<h1 class="title">Printer ZPL II</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/report-print-send/tree/13.0/printer_zpl2"><img alt="OCA/report-print-send" src="https://img.shields.io/badge/github-OCA%2Freport--print--send-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/report-print-send-13-0/report-print-send-13-0-printer_zpl2"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/144/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module extends the <strong>Report to printer</strong> (<tt class="docutils literal">base_report_to_printer</tt>)
module to add a ZPL II label printing feature.</p>
<p>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.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id2">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="id3">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id4">Usage</a></li>
<li><a class="reference internal" href="#changelog" id="id5">Changelog</a><ul>
<li><a class="reference internal" href="#id1" id="id6">13.0.1.0.0 (2019-09-30)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="id7">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id8">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id9">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id10">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id11">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id2">Installation</a></h1>
<p>Nothing special, just install the module.</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id3">Configuration</a></h1>
<p>To configure this module, you need to:</p>
<ol class="arabic simple">
<li>Go to <em>Settings &gt; Printing &gt; Labels &gt; ZPL II</em></li>
<li>Create new labels</li>
<li>Import ZPL2 code</li>
<li>Use the Test Mode tab during the creation</li>
</ol>
<p>Its also possible to add a label printing wizard on any model by creating a new <em>ir.actions.act_window</em> record.
For example, to add the printing wizard on the <em>product.product</em> model</p>
<pre class="literal-block">
&lt;act_window id=&quot;action_wizard_purchase&quot;
name=&quot;Print Label&quot;
src_model=&quot;product.product&quot;
res_model=&quot;wizard.print.record.label&quot;
view_mode=&quot;form&quot;
target=&quot;new&quot;
key2=&quot;client_action_multi&quot;/&gt;
</pre>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id4">Usage</a></h1>
<p>To print a label, you need to call use the label printing method from anywhere (other modules, server actions, etc.).</p>
<p>Example : Print the label of a product</p>
<pre class="literal-block">
self.env['printing.label.zpl2'].browse(label_id).print_label(
self.env['printing.printer'].browse(printer_id),
self.env['product.product'].browse(product_id))
</pre>
<p>You can also use the generic label printing wizard, if added on some models.</p>
<a class="reference external image-reference" href="https://runbot.odoo-community.org/runbot/144/12.0"><img alt="Try me on Runbot" src="https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas" /></a>
</div>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#id5">Changelog</a></h1>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id6">13.0.1.0.0 (2019-09-30)</a></h2>
<ul class="simple">
<li>[RELEASE] Port from V12.</li>
<li>Selection lists do not support integers any longer</li>
<li>Binary field now returns False when empty instead of none,
change tests to reflect this</li>
<li>work around an appels vs oranges warning</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id7">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/report-print-send/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/report-print-send/issues/new?body=module:%20printer_zpl2%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id8">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id9">Authors</a></h2>
<ul class="simple">
<li>SUBTENO-IT</li>
<li>FLorent de Labarre</li>
<li>Apertoso NV</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id10">Contributors</a></h2>
<ul class="simple">
<li>Sylvain Garancher &lt;<a class="reference external" href="mailto:sylvain.garancher&#64;syleam.fr">sylvain.garancher&#64;syleam.fr</a>&gt;</li>
<li>Florent de Labarre</li>
<li>Jos De Graeve &lt;<a class="reference external" href="mailto:Jos.DeGraeve&#64;apertoso.be">Jos.DeGraeve&#64;apertoso.be</a>&gt;</li>
<li>Rod Schouteden &lt;<a class="reference external" href="mailto:rod.schouteden&#64;dynapps.be">rod.schouteden&#64;dynapps.be</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id11">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/report-print-send/tree/13.0/printer_zpl2">OCA/report-print-send</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -3,30 +3,35 @@
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
model = 'odoo.addons.base_report_to_printer.models.printing_server' model = "odoo.addons.base_report_to_printer.models.printing_server"
class TestWizardPrintRecordLabel(TransactionCase): class TestWizardPrintRecordLabel(TransactionCase):
def setUp(self): def setUp(self):
super(TestWizardPrintRecordLabel, self).setUp() super(TestWizardPrintRecordLabel, self).setUp()
self.Model = self.env['wizard.print.record.label'] self.Model = self.env["wizard.print.record.label"]
self.server = self.env['printing.server'].create({}) self.server = self.env["printing.server"].create({})
self.printer = self.env['printing.printer'].create({ self.printer = self.env["printing.printer"].create(
'name': 'Printer', {
'server_id': self.server.id, "name": "Printer",
'system_name': 'Sys Name', "server_id": self.server.id,
'default': True, "system_name": "Sys Name",
'status': 'unknown', "default": True,
'status_message': 'Msg', "status": "unknown",
'model': 'res.users', "status_message": "Msg",
'location': 'Location', "model": "res.users",
'uri': 'URI', "location": "Location",
}) "uri": "URI",
self.label = self.env['printing.label.zpl2'].create({ }
'name': 'ZPL II Label', )
'model_id': self.env.ref( self.label = self.env["printing.label.zpl2"].create(
'base_report_to_printer.model_printing_printer').id, {
}) "name": "ZPL II Label",
"model_id": self.env.ref(
"base_report_to_printer.model_printing_printer"
).id,
}
)
def test_create_action(self): def test_create_action(self):
""" Check the creation of action """ """ Check the creation of action """

File diff suppressed because it is too large Load Diff

View File

@@ -4,30 +4,35 @@ import mock
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
model = 'odoo.addons.base_report_to_printer.models.printing_server' model = "odoo.addons.base_report_to_printer.models.printing_server"
class TestWizardPrintRecordLabel(TransactionCase): class TestWizardPrintRecordLabel(TransactionCase):
def setUp(self): def setUp(self):
super(TestWizardPrintRecordLabel, self).setUp() super(TestWizardPrintRecordLabel, self).setUp()
self.Model = self.env['wizard.print.record.label'] self.Model = self.env["wizard.print.record.label"]
self.server = self.env['printing.server'].create({}) self.server = self.env["printing.server"].create({})
self.printer = self.env['printing.printer'].create({ self.printer = self.env["printing.printer"].create(
'name': 'Printer', {
'server_id': self.server.id, "name": "Printer",
'system_name': 'Sys Name', "server_id": self.server.id,
'default': True, "system_name": "Sys Name",
'status': 'unknown', "default": True,
'status_message': 'Msg', "status": "unknown",
'model': 'res.users', "status_message": "Msg",
'location': 'Location', "model": "res.users",
'uri': 'URI', "location": "Location",
}) "uri": "URI",
self.label = self.env['printing.label.zpl2'].create({ }
'name': 'ZPL II Label', )
'model_id': self.env.ref( self.label = self.env["printing.label.zpl2"].create(
'base_report_to_printer.model_printing_printer').id, {
}) "name": "ZPL II Label",
"model_id": self.env.ref(
"base_report_to_printer.model_printing_printer"
).id,
}
)
def test_get_record(self): def test_get_record(self):
""" Check if return a record """ """ Check if return a record """
@@ -35,12 +40,12 @@ class TestWizardPrintRecordLabel(TransactionCase):
res = self.label._get_record() res = self.label._get_record()
Obj = self.env[self.label.model_id.model] Obj = self.env[self.label.model_id.model]
record = Obj.search([('id', '=', self.label.record_id)], limit=1) record = Obj.search([("id", "=", self.label.record_id)], limit=1)
if not record: if not record:
record = Obj.search([], limit=1, order='id desc') record = Obj.search([], limit=1, order="id desc")
self.assertEqual(res, record) self.assertEqual(res, record)
@mock.patch('%s.cups' % model) @mock.patch("%s.cups" % model)
def test_print_label_test(self, cups): def test_print_label_test(self, cups):
""" Check if print test """ """ Check if print test """
self.label.test_print_mode = True self.label.test_print_mode = True
@@ -53,18 +58,17 @@ class TestWizardPrintRecordLabel(TransactionCase):
""" Check if not execute next if not in this mode """ """ Check if not execute next if not in this mode """
self.label.test_labelary_mode = False self.label.test_labelary_mode = False
self.label._on_change_labelary() self.label._on_change_labelary()
self.assertIs(self.label.labelary_image, None) self.assertIs(self.label.labelary_image, False)
def test_emulation_with_bad_header(self): def test_emulation_with_bad_header(self):
""" Check if bad header """ """ Check if bad header """
self.label.test_labelary_mode = True self.label.test_labelary_mode = True
self.label.labelary_width = 80 self.label.labelary_width = 80
self.label.labelary_dpmm = '8dpmm' self.label.labelary_dpmm = "8dpmm"
self.label.labelary_height = 10000000 self.label.labelary_height = 10000000
self.env['printing.label.zpl2.component'].create({ self.env["printing.label.zpl2.component"].create(
'name': 'ZPL II Label', {"name": "ZPL II Label", "label_id": self.label.id, "data": '"Test"'}
'label_id': self.label.id, )
'data': '"Test"'})
self.label._on_change_labelary() self.label._on_change_labelary()
self.assertFalse(self.label.labelary_image) self.assertFalse(self.label.labelary_image)
@@ -73,24 +77,22 @@ class TestWizardPrintRecordLabel(TransactionCase):
self.label.test_labelary_mode = True self.label.test_labelary_mode = True
self.label.labelary_width = 80 self.label.labelary_width = 80
self.label.labelary_height = 30 self.label.labelary_height = 30
self.label.labelary_dpmm = '8dpmm' self.label.labelary_dpmm = "8dpmm"
component = self.env['printing.label.zpl2.component'].create({ component = self.env["printing.label.zpl2.component"].create(
'name': 'ZPL II Label', {"name": "ZPL II Label", "label_id": self.label.id, "data": "wrong_data"}
'label_id': self.label.id, )
'data': 'wrong_data'})
self.label._on_change_labelary() self.label._on_change_labelary()
component.unlink() component.unlink()
self.assertIs(self.label.labelary_image, None) self.assertIs(self.label.labelary_image, False)
def test_emulation_with_good_data(self): def test_emulation_with_good_data(self):
""" Check if ok """ """ Check if ok """
self.label.test_labelary_mode = True self.label.test_labelary_mode = True
self.label.labelary_width = 80 self.label.labelary_width = 80
self.label.labelary_height = 30 self.label.labelary_height = 30
self.label.labelary_dpmm = '8dpmm' self.label.labelary_dpmm = "8dpmm"
self.env['printing.label.zpl2.component'].create({ self.env["printing.label.zpl2.component"].create(
'name': 'ZPL II Label', {"name": "ZPL II Label", "label_id": self.label.id, "data": '"good_data"'}
'label_id': self.label.id, )
'data': '"good_data"', })
self.label._on_change_labelary() self.label._on_change_labelary()
self.assertTrue(self.label.labelary_image) self.assertTrue(self.label.labelary_image)

View File

@@ -7,92 +7,89 @@ from odoo.tests.common import TransactionCase
class TestWizardImportZpl2(TransactionCase): class TestWizardImportZpl2(TransactionCase):
def setUp(self): def setUp(self):
super(TestWizardImportZpl2, self).setUp() super(TestWizardImportZpl2, self).setUp()
self.Model = self.env['wizard.print.record.label'] self.Model = self.env["wizard.print.record.label"]
self.server = self.env['printing.server'].create({}) self.server = self.env["printing.server"].create({})
self.printer = self.env['printing.printer'].create({ self.printer = self.env["printing.printer"].create(
'name': 'Printer', {
'server_id': self.server.id, "name": "Printer",
'system_name': 'Sys Name', "server_id": self.server.id,
'default': True, "system_name": "Sys Name",
'status': 'unknown', "default": True,
'status_message': 'Msg', "status": "unknown",
'model': 'res.users', "status_message": "Msg",
'location': 'Location', "model": "res.users",
'uri': 'URI', "location": "Location",
}) "uri": "URI",
self.label = self.env['printing.label.zpl2'].create({ }
'name': 'ZPL II Label', )
'model_id': self.env.ref( self.label = self.env["printing.label.zpl2"].create(
'base_report_to_printer.model_printing_printer').id, {
}) "name": "ZPL II Label",
"model_id": self.env.ref(
"base_report_to_printer.model_printing_printer"
).id,
}
)
def test_open_wizard(self): def test_open_wizard(self):
""" open wizard from label""" """ open wizard from label"""
res = self.label.import_zpl2() res = self.label.import_zpl2()
self.assertEqual( self.assertEqual(res.get("context").get("default_label_id"), self.label.id)
res.get('context').get('default_label_id'),
self.label.id)
def test_wizard_import_zpl2(self): def test_wizard_import_zpl2(self):
""" Import ZPL2 from wizard """ """ Import ZPL2 from wizard """
zpl_data = ("^XA\n" zpl_data = (
"^CI28\n" "^XA\n"
"^LH0,0\n" "^CI28\n"
"^CF0\n" "^LH0,0\n"
"^CFA,10\n" "^CF0\n"
"^CFB,10,10\n" "^CFA,10\n"
"^FO10,10^A0N,30,30^FDTEXT^FS\n" "^CFB,10,10\n"
"^BY2,3.0^FO600,60^BCN,30,N,N,N" "^FO10,10^A0N,30,30^FDTEXT^FS\n"
"^FDAJFGJAGJVJVHK^FS\n" "^BY2,3.0^FO600,60^BCN,30,N,N,N"
"^FO10,40^A0N,20,40^FB150,2,1,J,0^FDTEXT BLOCK^FS\n" "^FDAJFGJAGJVJVHK^FS\n"
"^FO300,10^GC100,3,B^FS\n" "^FO10,40^A0N,20,40^FB150,2,1,J,0^FDTEXT BLOCK^FS\n"
"^FO10,200^GB200,200,100,B,0^FS\n" "^FO300,10^GC100,3,B^FS\n"
"^FO10,60^GFA,16.0,16.0,2.0," "^FO10,200^GB200,200,100,B,0^FS\n"
"b'FFC0FFC0FFC0FFC0FFC0FFC0FFC0FFC0'^FS\n" "^FO10,60^GFA,16.0,16.0,2.0,"
"^FO10,200^GB300,100,6,W,0^FS\n" "b'FFC0FFC0FFC0FFC0FFC0FFC0FFC0FFC0'^FS\n"
"^BY2,3.0^FO300,10^B1N,N,30,N,N^FD678987656789^FS\n" "^FO10,200^GB300,100,6,W,0^FS\n"
"^BY2,3.0^FO300,70^B2N,30,Y,Y,N^FD567890987768^FS\n" "^BY2,3.0^FO300,10^B1N,N,30,N,N^FD678987656789^FS\n"
"^BY2,3.0^FO300,120^B3N,N,30,N,N^FD98765456787656^FS\n" "^BY2,3.0^FO300,70^B2N,30,Y,Y,N^FD567890987768^FS\n"
"^BY2,3.0^FO300,200^BQN,2,5,Q,7" "^BY2,3.0^FO300,120^B3N,N,30,N,N^FD98765456787656^FS\n"
"^FDMM,A876567897656787658654645678^FS\n" "^BY2,3.0^FO300,200^BQN,2,5,Q,7"
"^BY2,3.0^FO400,250^BER,40,Y,Y^FD9876789987654567^FS\n" "^FDMM,A876567897656787658654645678^FS\n"
"^BY2,3.0^FO350,250^B7N,20,0,0,0,N^FD8765678987656789^FS\n" "^BY2,3.0^FO400,250^BER,40,Y,Y^FD9876789987654567^FS\n"
"^BY2,3.0^FO700,10^B9N,20,N,N,N^FD87657890987654^FS\n" "^BY2,3.0^FO350,250^B7N,20,0,0,0,N^FD8765678987656789^FS\n"
"^BY2,3.0^FO600,200^B4N,50,N^FD7654567898765678^FS\n" "^BY2,3.0^FO700,10^B9N,20,N,N,N^FD87657890987654^FS\n"
"^BY2,3.0^FO600,300^BEN,50,Y,Y^FD987654567890876567^FS\n" "^BY2,3.0^FO600,200^B4N,50,N^FD7654567898765678^FS\n"
"^FO300,300^AGI,50,50^FR^FDINVERTED^FS\n" "^BY2,3.0^FO600,300^BEN,50,Y,Y^FD987654567890876567^FS\n"
"^BY2,3.0^FO700,200^B8,50,N,N^FD987609876567^FS\n" "^FO300,300^AGI,50,50^FR^FDINVERTED^FS\n"
"^JUR\n" "^BY2,3.0^FO700,200^B8,50,N,N^FD987609876567^FS\n"
"^XZ") "^JUR\n"
"^XZ"
)
vals = {'label_id': self.label.id, vals = {"label_id": self.label.id, "delete_component": True, "data": zpl_data}
'delete_component': True, wizard = self.env["wizard.import.zpl2"].create(vals)
'data': zpl_data}
wizard = self.env['wizard.import.zpl2'].create(vals)
wizard.import_zpl2() wizard.import_zpl2()
self.assertEqual( self.assertEqual(18, len(self.label.component_ids))
18,
len(self.label.component_ids))
def test_wizard_import_zpl2_add(self): def test_wizard_import_zpl2_add(self):
""" Import ZPL2 from wizard ADD""" """ Import ZPL2 from wizard ADD"""
self.env['printing.label.zpl2.component'].create({ self.env["printing.label.zpl2.component"].create(
'name': 'ZPL II Label', {
'label_id': self.label.id, "name": "ZPL II Label",
'data': '"data"', "label_id": self.label.id,
'sequence': 10}) "data": '"data"',
zpl_data = ("^XA\n" "sequence": 10,
"^CI28\n" }
"^LH0,0\n" )
"^FO10,10^A0N,30,30^FDTEXT^FS\n" zpl_data = (
"^JUR\n" "^XA\n" "^CI28\n" "^LH0,0\n" "^FO10,10^A0N,30,30^FDTEXT^FS\n" "^JUR\n" "^XZ"
"^XZ") )
vals = {'label_id': self.label.id, vals = {"label_id": self.label.id, "delete_component": False, "data": zpl_data}
'delete_component': False, wizard = self.env["wizard.import.zpl2"].create(vals)
'data': zpl_data}
wizard = self.env['wizard.import.zpl2'].create(vals)
wizard.import_zpl2() wizard.import_zpl2()
self.assertEqual( self.assertEqual(2, len(self.label.component_ids))
2,
len(self.label.component_ids))

View File

@@ -5,37 +5,41 @@ import mock
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
model = "odoo.addons.base_report_to_printer.models.printing_server"
model = 'odoo.addons.base_report_to_printer.models.printing_server'
class TestWizardPrintRecordLabel(TransactionCase): class TestWizardPrintRecordLabel(TransactionCase):
def setUp(self): def setUp(self):
super(TestWizardPrintRecordLabel, self).setUp() super(TestWizardPrintRecordLabel, self).setUp()
self.Model = self.env['wizard.print.record.label'] self.Model = self.env["wizard.print.record.label"]
self.server = self.env['printing.server'].create({}) self.server = self.env["printing.server"].create({})
self.printer = self.env['printing.printer'].create({ self.printer = self.env["printing.printer"].create(
'name': 'Printer', {
'server_id': self.server.id, "name": "Printer",
'system_name': 'Sys Name', "server_id": self.server.id,
'default': True, "system_name": "Sys Name",
'status': 'unknown', "default": True,
'status_message': 'Msg', "status": "unknown",
'model': 'res.users', "status_message": "Msg",
'location': 'Location', "model": "res.users",
'uri': 'URI', "location": "Location",
}) "uri": "URI",
self.label = self.env['printing.label.zpl2'].create({ }
'name': 'ZPL II Label', )
'model_id': self.env.ref( self.label = self.env["printing.label.zpl2"].create(
'base_report_to_printer.model_printing_printer').id, {
}) "name": "ZPL II Label",
"model_id": self.env.ref(
"base_report_to_printer.model_printing_printer"
).id,
}
)
@mock.patch('%s.cups' % model) @mock.patch("%s.cups" % model)
def test_print_record_label(self, cups): def test_print_record_label(self, cups):
""" Check that printing a label using the generic wizard works """ """ Check that printing a label using the generic wizard works """
wizard_obj = self.Model.with_context( wizard_obj = self.Model.with_context(
active_model='printing.printer', active_model="printing.printer",
active_id=self.printer.id, active_id=self.printer.id,
active_ids=[self.printer.id], active_ids=[self.printer.id],
printer_zpl2_id=self.printer.id, printer_zpl2_id=self.printer.id,
@@ -50,41 +54,48 @@ class TestWizardPrintRecordLabel(TransactionCase):
""" Check that printer_id and label_id are not automatically filled """ Check that printer_id and label_id are not automatically filled
when there are multiple possible values when there are multiple possible values
""" """
self.env['printing.printer'].create({ self.env["printing.printer"].create(
'name': 'Other_Printer', {
'server_id': self.server.id, "name": "Other_Printer",
'system_name': 'Sys Name', "server_id": self.server.id,
'default': True, "system_name": "Sys Name",
'status': 'unknown', "default": True,
'status_message': 'Msg', "status": "unknown",
'model': 'res.users', "status_message": "Msg",
'location': 'Location', "model": "res.users",
'uri': 'URI', "location": "Location",
}) "uri": "URI",
self.env['printing.label.zpl2'].create({ }
'name': 'Other ZPL II Label', )
'model_id': self.env.ref( self.env["printing.label.zpl2"].create(
'base_report_to_printer.model_printing_printer').id, {
}) "name": "Other ZPL II Label",
"model_id": self.env.ref(
"base_report_to_printer.model_printing_printer"
).id,
}
)
wizard_obj = self.Model.with_context( wizard_obj = self.Model.with_context(
active_model='printing.printer', active_model="printing.printer",
active_id=self.printer.id, active_id=self.printer.id,
active_ids=[self.printer.id], active_ids=[self.printer.id],
) )
values = wizard_obj.default_get(['printer_id', 'label_id']) values = wizard_obj.default_get(["printer_id", "label_id"])
self.assertEqual(values.get('printer_id', False), False) self.assertEqual(values.get("printer_id", False), False)
self.assertEqual(values.get('label_id', False), False) self.assertEqual(values.get("label_id", False), False)
def test_wizard_multiple_labels_but_on_different_models(self): def test_wizard_multiple_labels_but_on_different_models(self):
""" Check that label_id is automatically filled when there are multiple """ Check that label_id is automatically filled when there are multiple
labels, but only one on the right model labels, but only one on the right model
""" """
self.env['printing.label.zpl2'].create({ self.env["printing.label.zpl2"].create(
'name': 'Other ZPL II Label', {
'model_id': self.env.ref('base.model_res_users').id, "name": "Other ZPL II Label",
}) "model_id": self.env.ref("base.model_res_users").id,
}
)
wizard_obj = self.Model.with_context( wizard_obj = self.Model.with_context(
active_model='printing.printer', active_model="printing.printer",
active_id=self.printer.id, active_id=self.printer.id,
active_ids=[self.printer.id], active_ids=[self.printer.id],
printer_zpl2_id=self.printer.id, printer_zpl2_id=self.printer.id,

View File

@@ -7,7 +7,7 @@
<record id="view_printing_label_zpl2_tree" model="ir.ui.view"> <record id="view_printing_label_zpl2_tree" model="ir.ui.view">
<field name="model">printing.label.zpl2</field> <field name="model">printing.label.zpl2</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="ZPL II Label"> <tree>
<field name="name"/> <field name="name"/>
<field name="model_id"/> <field name="model_id"/>
</tree> </tree>
@@ -44,7 +44,7 @@
<notebook> <notebook>
<page string="Components"> <page string="Components">
<field name="component_ids" nolabel="1" colspan="4"> <field name="component_ids" nolabel="1" colspan="4">
<tree string="Label Component"> <tree>
<field name="sequence"/> <field name="sequence"/>
<field name="name"/> <field name="name"/>
<field name="component_type"/> <field name="component_type"/>
@@ -178,7 +178,6 @@
<field name="name">ZPL II Labels</field> <field name="name">ZPL II Labels</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">printing.label.zpl2</field> <field name="res_model">printing.label.zpl2</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_printing_label_zpl2_search"/> <field name="search_view_id" ref="view_printing_label_zpl2_search"/>
<field name="domain">[]</field> <field name="domain">[]</field>

View File

@@ -1,45 +1,53 @@
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>) # Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, api, fields from odoo import api, fields, models
class PrintRecordLabel(models.TransientModel): class PrintRecordLabel(models.TransientModel):
_name = 'wizard.print.record.label' _name = "wizard.print.record.label"
_description = 'Print Record Label' _description = "Print Record Label"
printer_id = fields.Many2one( printer_id = fields.Many2one(
comodel_name='printing.printer', string='Printer', required=True, comodel_name="printing.printer",
help='Printer used to print the labels.') string="Printer",
required=True,
help="Printer used to print the labels.",
)
label_id = fields.Many2one( label_id = fields.Many2one(
comodel_name='printing.label.zpl2', string='Label', required=True, comodel_name="printing.label.zpl2",
string="Label",
required=True,
domain=lambda self: [ domain=lambda self: [
('model_id.model', '=', self.env.context.get('active_model'))], ("model_id.model", "=", self.env.context.get("active_model"))
help='Label to print.') ],
help="Label to print.",
)
@api.model @api.model
def default_get(self, fields_list): def default_get(self, fields_list):
values = super(PrintRecordLabel, self).default_get(fields_list) values = super(PrintRecordLabel, self).default_get(fields_list)
# Automatically select the printer and label, if only one is available # Automatically select the printer and label, if only one is available
printers = self.env['printing.printer'].search( printers = self.env["printing.printer"].search(
[('id', '=', self.env.context.get('printer_zpl2_id'))]) [("id", "=", self.env.context.get("printer_zpl2_id"))]
)
if not printers: if not printers:
printers = self.env['printing.printer'].search([]) printers = self.env["printing.printer"].search([])
if len(printers) == 1: if len(printers) == 1:
values['printer_id'] = printers.id values["printer_id"] = printers.id
labels = self.env['printing.label.zpl2'].search([ labels = self.env["printing.label.zpl2"].search(
('model_id.model', '=', self.env.context.get('active_model')), [("model_id.model", "=", self.env.context.get("active_model"))]
]) )
if len(labels) == 1: if len(labels) == 1:
values['label_id'] = labels.id values["label_id"] = labels.id
return values return values
def print_label(self): def print_label(self):
""" Prints a label per selected record """ """ Prints a label per selected record """
record_model = self.env.context['active_model'] record_model = self.env.context["active_model"]
for record_id in self.env.context['active_ids']: for record_id in self.env.context["active_ids"]:
record = self.env[record_model].browse(record_id) record = self.env[record_model].browse(record_id)
self.label_id.print_label(self.printer_id, record) self.label_id.print_label(self.printer_id, record)

View File

@@ -24,7 +24,6 @@
<field name="name">Print Label</field> <field name="name">Print Label</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">wizard.print.record.label</field> <field name="res_model">wizard.print.record.label</field>
<field name="view_type">form</field>
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="domain">[]</field> <field name="domain">[]</field>
<field name="context">{}</field> <field name="context">{}</field>

View File

@@ -1,42 +1,42 @@
# Copyright (C) 2018 Florent de Labarre (<https://github.com/fmdl>) # Copyright (C) 2018 Florent de Labarre (<https://github.com/fmdl>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import re
import base64 import base64
import binascii import binascii
import io import io
import logging
import re
from PIL import Image, ImageOps from PIL import Image, ImageOps
from odoo import fields, models, _
from odoo import _ as translate, fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
try: try:
import zpl2 import zpl2
except ImportError: except ImportError:
_logger.debug('Cannot `import zpl2`.') _logger.debug("Cannot `import zpl2`.")
def _compute_arg(data, arg): def _compute_arg(data, arg):
vals = {} vals = {}
for i, d in enumerate(data.split(',')): for i, d in enumerate(data.split(",")):
vals[arg[i]] = d vals[arg[i]] = d
return vals return vals
def _field_origin(data): def _field_origin(data):
if data[:2] == 'FO': if data[:2] == "FO":
position = data[2:] position = data[2:]
vals = _compute_arg(position, ['origin_x', 'origin_y']) vals = _compute_arg(position, ["origin_x", "origin_y"])
return vals return vals
return {} return {}
def _font_format(data): def _font_format(data):
if data[:1] == 'A': if data[:1] == "A":
data = data.split(',') data = data.split(",")
vals = {} vals = {}
if len(data[0]) > 1: if len(data[0]) > 1:
vals[zpl2.ARG_FONT] = data[0][1] vals[zpl2.ARG_FONT] = data[0][1]
@@ -52,15 +52,10 @@ def _font_format(data):
def _default_font_format(data): def _default_font_format(data):
if data[:2] == 'CF': if data[:2] == "CF":
args = [ args = [zpl2.ARG_FONT, zpl2.ARG_HEIGHT, zpl2.ARG_WIDTH]
zpl2.ARG_FONT,
zpl2.ARG_HEIGHT,
zpl2.ARG_WIDTH,
]
vals = _compute_arg(data[2:], args) vals = _compute_arg(data[2:], args)
if vals.get(zpl2.ARG_HEIGHT, False) \ if vals.get(zpl2.ARG_HEIGHT, False) and not vals.get(zpl2.ARG_WIDTH, False):
and not vals.get(zpl2.ARG_WIDTH, False):
vals.update({zpl2.ARG_WIDTH: vals.get(zpl2.ARG_HEIGHT)}) vals.update({zpl2.ARG_WIDTH: vals.get(zpl2.ARG_HEIGHT)})
else: else:
vals.update({zpl2.ARG_HEIGHT: 10, zpl2.ARG_HEIGHT: 10}) vals.update({zpl2.ARG_HEIGHT: 10, zpl2.ARG_HEIGHT: 10})
@@ -69,7 +64,7 @@ def _default_font_format(data):
def _field_block(data): def _field_block(data):
if data[:2] == 'FB': if data[:2] == "FB":
vals = {zpl2.ARG_IN_BLOCK: True} vals = {zpl2.ARG_IN_BLOCK: True}
args = [ args = [
zpl2.ARG_BLOCK_WIDTH, zpl2.ARG_BLOCK_WIDTH,
@@ -84,8 +79,8 @@ def _field_block(data):
def _code11(data): def _code11(data):
if data[:2] == 'B1': if data[:2] == "B1":
vals = {'component_type': zpl2.BARCODE_CODE_11} vals = {"component_type": zpl2.BARCODE_CODE_11}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_CHECK_DIGITS, zpl2.ARG_CHECK_DIGITS,
@@ -99,8 +94,8 @@ def _code11(data):
def _interleaved2of5(data): def _interleaved2of5(data):
if data[:2] == 'B2': if data[:2] == "B2":
vals = {'component_type': zpl2.BARCODE_INTERLEAVED_2_OF_5} vals = {"component_type": zpl2.BARCODE_INTERLEAVED_2_OF_5}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -114,8 +109,8 @@ def _interleaved2of5(data):
def _code39(data): def _code39(data):
if data[:2] == 'B3': if data[:2] == "B3":
vals = {'component_type': zpl2.BARCODE_CODE_39} vals = {"component_type": zpl2.BARCODE_CODE_39}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_CHECK_DIGITS, zpl2.ARG_CHECK_DIGITS,
@@ -129,8 +124,8 @@ def _code39(data):
def _code49(data): def _code49(data):
if data[:2] == 'B4': if data[:2] == "B4":
vals = {'component_type': zpl2.BARCODE_CODE_49} vals = {"component_type": zpl2.BARCODE_CODE_49}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -143,8 +138,8 @@ def _code49(data):
def _pdf417(data): def _pdf417(data):
if data[:2] == 'B7': if data[:2] == "B7":
vals = {'component_type': zpl2.BARCODE_PDF417} vals = {"component_type": zpl2.BARCODE_PDF417}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -159,8 +154,8 @@ def _pdf417(data):
def _ean8(data): def _ean8(data):
if data[:2] == 'B8': if data[:2] == "B8":
vals = {'component_type': zpl2.BARCODE_EAN_8} vals = {"component_type": zpl2.BARCODE_EAN_8}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -173,8 +168,8 @@ def _ean8(data):
def _upce(data): def _upce(data):
if data[:2] == 'B9': if data[:2] == "B9":
vals = {'component_type': zpl2.BARCODE_UPC_E} vals = {"component_type": zpl2.BARCODE_UPC_E}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -188,8 +183,8 @@ def _upce(data):
def _code128(data): def _code128(data):
if data[:2] == 'BC': if data[:2] == "BC":
vals = {'component_type': zpl2.BARCODE_CODE_128} vals = {"component_type": zpl2.BARCODE_CODE_128}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -204,8 +199,8 @@ def _code128(data):
def _ean13(data): def _ean13(data):
if data[:2] == 'BE': if data[:2] == "BE":
vals = {'component_type': zpl2.BARCODE_EAN_13} vals = {"component_type": zpl2.BARCODE_EAN_13}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -218,8 +213,8 @@ def _ean13(data):
def _qrcode(data): def _qrcode(data):
if data[:2] == 'BQ': if data[:2] == "BQ":
vals = {'component_type': zpl2.BARCODE_QR_CODE} vals = {"component_type": zpl2.BARCODE_QR_CODE}
args = [ args = [
zpl2.ARG_ORIENTATION, zpl2.ARG_ORIENTATION,
zpl2.ARG_MODEL, zpl2.ARG_MODEL,
@@ -233,25 +228,21 @@ def _qrcode(data):
def _default_barcode_field(data): def _default_barcode_field(data):
if data[:2] == 'BY': if data[:2] == "BY":
args = [ args = [zpl2.ARG_MODULE_WIDTH, zpl2.ARG_BAR_WIDTH_RATIO, zpl2.ARG_HEIGHT]
zpl2.ARG_MODULE_WIDTH,
zpl2.ARG_BAR_WIDTH_RATIO,
zpl2.ARG_HEIGHT,
]
return _compute_arg(data[2:], args) return _compute_arg(data[2:], args)
return {} return {}
def _field_reverse_print(data): def _field_reverse_print(data):
if data[:2] == 'FR': if data[:2] == "FR":
return {zpl2.ARG_REVERSE_PRINT: True} return {zpl2.ARG_REVERSE_PRINT: True}
return {} return {}
def _graphic_box(data): def _graphic_box(data):
if data[:2] == 'GB': if data[:2] == "GB":
vals = {'component_type': 'rectangle'} vals = {"component_type": "rectangle"}
args = [ args = [
zpl2.ARG_WIDTH, zpl2.ARG_WIDTH,
zpl2.ARG_HEIGHT, zpl2.ARG_HEIGHT,
@@ -265,48 +256,43 @@ def _graphic_box(data):
def _graphic_circle(data): def _graphic_circle(data):
if data[:2] == 'GC': if data[:2] == "GC":
vals = {'component_type': 'circle'} vals = {"component_type": "circle"}
args = [ args = [zpl2.ARG_WIDTH, zpl2.ARG_THICKNESS, zpl2.ARG_COLOR]
zpl2.ARG_WIDTH,
zpl2.ARG_THICKNESS,
zpl2.ARG_COLOR,
]
vals.update(_compute_arg(data[2:], args)) vals.update(_compute_arg(data[2:], args))
return vals return vals
return {} return {}
def _graphic_field(data): def _graphic_field(data):
if data[:3] == 'GFA': if data[:3] == "GFA":
vals = {} vals = {}
args = [ args = [
'compression', "compression",
'total_bytes', "total_bytes",
'total_bytes', "total_bytes",
'bytes_per_row', "bytes_per_row",
'ascii_data', "ascii_data",
] ]
vals.update(_compute_arg(data[3:], args)) vals.update(_compute_arg(data[3:], args))
# Image # Image
rawData = re.sub('[^A-F0-9]+', '', vals['ascii_data']) rawData = re.sub("[^A-F0-9]+", "", vals["ascii_data"])
rawData = binascii.unhexlify(rawData) rawData = binascii.unhexlify(rawData)
width = int(float(vals['bytes_per_row']) * 8) width = int(float(vals["bytes_per_row"]) * 8)
height = int(float(vals['total_bytes']) / width) * 8 height = int(float(vals["total_bytes"]) / width) * 8
img = Image.frombytes( img = Image.frombytes("1", (width, height), rawData, "raw").convert("L")
'1', (width, height), rawData, 'raw').convert('L')
img = ImageOps.invert(img) img = ImageOps.invert(img)
imgByteArr = io.BytesIO() imgByteArr = io.BytesIO()
img.save(imgByteArr, format='PNG') img.save(imgByteArr, format="PNG")
image = base64.b64encode(imgByteArr.getvalue()) image = base64.b64encode(imgByteArr.getvalue())
return { return {
'component_type': 'graphic', "component_type": "graphic",
'graphic_image': image, "graphic_image": image,
zpl2.ARG_WIDTH: width, zpl2.ARG_WIDTH: width,
zpl2.ARG_HEIGHT: height, zpl2.ARG_HEIGHT: height,
} }
@@ -314,29 +300,29 @@ def _graphic_field(data):
def _get_data(data): def _get_data(data):
if data[:2] == 'FD': if data[:2] == "FD":
return {'data': '"%s"' % data[2:]} return {"data": '"%s"' % data[2:]}
return {} return {}
SUPPORTED_CODE = { SUPPORTED_CODE = {
'FO': {'method': _field_origin}, "FO": {"method": _field_origin},
'FD': {'method': _get_data}, "FD": {"method": _get_data},
'A': {'method': _font_format}, "A": {"method": _font_format},
'FB': {'method': _field_block}, "FB": {"method": _field_block},
'B1': {'method': _code11}, "B1": {"method": _code11},
'B2': {'method': _interleaved2of5}, "B2": {"method": _interleaved2of5},
'B3': {'method': _code39}, "B3": {"method": _code39},
'B4': {'method': _code49}, "B4": {"method": _code49},
'B7': {'method': _pdf417}, "B7": {"method": _pdf417},
'B8': {'method': _ean8}, "B8": {"method": _ean8},
'B9': {'method': _upce}, "B9": {"method": _upce},
'BC': {'method': _code128}, "BC": {"method": _code128},
'BE': {'method': _ean13}, "BE": {"method": _ean13},
'BQ': {'method': _qrcode}, "BQ": {"method": _qrcode},
'BY': { "BY": {
'method': _default_barcode_field, "method": _default_barcode_field,
'default': [ "default": [
zpl2.BARCODE_CODE_11, zpl2.BARCODE_CODE_11,
zpl2.BARCODE_INTERLEAVED_2_OF_5, zpl2.BARCODE_INTERLEAVED_2_OF_5,
zpl2.BARCODE_CODE_39, zpl2.BARCODE_CODE_39,
@@ -349,93 +335,93 @@ SUPPORTED_CODE = {
zpl2.BARCODE_QR_CODE, zpl2.BARCODE_QR_CODE,
], ],
}, },
'CF': {'method': _default_font_format, 'default': ['text']}, "CF": {"method": _default_font_format, "default": ["text"]},
'FR': {'method': _field_reverse_print}, "FR": {"method": _field_reverse_print},
'GB': {'method': _graphic_box}, "GB": {"method": _graphic_box},
'GC': {'method': _graphic_circle}, "GC": {"method": _graphic_circle},
'GFA': {'method': _graphic_field}, "GFA": {"method": _graphic_field},
} }
class WizardImportZPl2(models.TransientModel): class WizardImportZPl2(models.TransientModel):
_name = 'wizard.import.zpl2' _name = "wizard.import.zpl2"
_description = 'Import ZPL2' _description = "Import ZPL2"
label_id = fields.Many2one( label_id = fields.Many2one(
comodel_name='printing.label.zpl2', string='Label', comodel_name="printing.label.zpl2", string="Label", required=True, readonly=True
required=True, readonly=True,) )
data = fields.Text( data = fields.Text(required=True, help="Printer used to print the labels.")
required=True, help='Printer used to print the labels.')
delete_component = fields.Boolean( delete_component = fields.Boolean(
string='Delete existing components', default=False) string="Delete existing components", default=False
)
def _start_sequence(self): def _start_sequence(self):
sequences = self.mapped('label_id.component_ids.sequence') sequences = self.mapped("label_id.component_ids.sequence")
if sequences: if sequences:
return max(sequences) + 1 return max(sequences) + 1
return 0 return 0
def import_zpl2(self): def import_zpl2(self):
self.ensure_one() self.ensure_one()
Zpl2Component = self.env['printing.label.zpl2.component'] Zpl2Component = self.env["printing.label.zpl2.component"]
if self.delete_component: if self.delete_component:
self.mapped('label_id.component_ids').unlink() self.mapped("label_id.component_ids").unlink()
sequence = self._start_sequence() sequence = self._start_sequence()
default = {} default = {}
for i, line in enumerate(self.data.split('\n')): for i, line in enumerate(self.data.split("\n")):
vals = {} vals = {}
args = line.split('^') args = line.split("^")
for arg in args: for arg in args:
for key, code in SUPPORTED_CODE.items(): for _, code in SUPPORTED_CODE.items():
component_arg = code['method'](arg) component_arg = code["method"](arg)
if component_arg: if component_arg:
if code.get('default', False): if code.get("default", False):
for deft in code.get('default'): for deft in code.get("default"):
default.update({deft: component_arg}) default.update({deft: component_arg})
else: else:
vals.update(component_arg) vals.update(component_arg)
break break
if vals: if vals:
if 'component_type' not in vals.keys(): if "component_type" not in vals.keys():
vals.update({'component_type': 'text'}) vals.update({"component_type": "text"})
if vals['component_type'] in default.keys(): if vals["component_type"] in default.keys():
vals.update(default[vals['component_type']]) vals.update(default[vals["component_type"]])
vals = self._update_vals(vals) vals = self._update_vals(vals)
seq = sequence + i * 10 seq = sequence + i * 10
vals.update({ vals.update(
'name': _('Import %s') % seq, {
'sequence': seq, "name": translate("Import %s") % seq,
'label_id': self.label_id.id, "sequence": seq,
}) "label_id": self.label_id.id,
}
)
Zpl2Component.create(vals) Zpl2Component.create(vals)
def _update_vals(self, vals): def _update_vals(self, vals):
if 'orientation' in vals.keys() and vals['orientation'] == '': if "orientation" in vals.keys() and vals["orientation"] == "":
vals['orientation'] = 'N' vals["orientation"] = "N"
# Field # Field
Zpl2Component = self.env['printing.label.zpl2.component'] Zpl2Component = self.env["printing.label.zpl2.component"]
model_fields = Zpl2Component.fields_get() model_fields = Zpl2Component.fields_get()
component = {} component = {}
for field, value in vals.items(): for field, value in vals.items():
if field in model_fields.keys(): if field in model_fields.keys():
field_type = model_fields[field].get('type', False) field_type = model_fields[field].get("type", False)
if field_type == 'boolean': if field_type == "boolean":
if not value or value == zpl2.BOOL_NO: if not value or value == zpl2.BOOL_NO:
value = False value = False
else: else:
value = True value = True
if field_type in ('integer', 'float'): if field_type in ("integer", "float"):
value = float(value) value = float(value)
if field == 'model':
value = int(float(value))
component.update({field: value}) component.update({field: value})
return component return component

View File

@@ -1 +1,2 @@
pycups==1.9.74 pycups==1.9.74
zpl2==1.2

View File

@@ -1,18 +1,12 @@
import setuptools import setuptools
with open('VERSION.txt', 'r') as f: with open("VERSION.txt", "r") as f:
version = f.read().strip() version = f.read().strip()
setuptools.setup( setuptools.setup(
name="odoo13-addons-oca-report-print-send", name="odoo13-addons-oca-report-print-send",
description="Meta package for oca-report-print-send Odoo addons", description="Meta package for oca-report-print-send Odoo addons",
version=version, version=version,
install_requires=[ install_requires=["odoo13-addon-base_report_to_printer"],
'odoo13-addon-base_report_to_printer', classifiers=["Programming Language :: Python", "Framework :: Odoo"],
'odoo13-addon-stock_picking_auto_print',
],
classifiers=[
'Programming Language :: Python',
'Framework :: Odoo',
]
) )

View File

@@ -1,6 +1,3 @@
import setuptools import setuptools
setuptools.setup( setuptools.setup(setup_requires=["setuptools-odoo"], odoo_addon=True)
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)