mirror of
https://github.com/bringout/oca-report.git
synced 2026-04-18 04:02:01 +02:00
Initial commit: OCA Report packages (45 packages)
This commit is contained in:
commit
2f4db400df
2543 changed files with 469120 additions and 0 deletions
46
odoo-bringout-oca-report-print-send-printer_zpl2/README.md
Normal file
46
odoo-bringout-oca-report-print-send-printer_zpl2/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Printer ZPL II
|
||||
|
||||
Odoo addon: printer_zpl2
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-report-print-send-printer_zpl2
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- base_report_to_printer
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Printer ZPL II
|
||||
- **Version**: 16.0.1.1.1
|
||||
- **Category**: Printer
|
||||
- **License**: AGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/report-print-send](https://github.com/OCA/report-print-send) branch 16.0, addon `printer_zpl2`.
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original AGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||
V --> C[Controllers]
|
||||
V --> W[Wizards – Transient Models]
|
||||
C --> M[Models and ORM]
|
||||
W --> M
|
||||
M --> R[Reports]
|
||||
DX[Data XML] --> M
|
||||
S[Security – ACLs and Groups] -. enforces .-> M
|
||||
|
||||
subgraph Printer_zpl2 Module - printer_zpl2
|
||||
direction LR
|
||||
M:::layer
|
||||
W:::layer
|
||||
C:::layer
|
||||
V:::layer
|
||||
R:::layer
|
||||
S:::layer
|
||||
DX:::layer
|
||||
end
|
||||
|
||||
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||
```
|
||||
|
||||
Notes
|
||||
- Views include tree/form/kanban templates and report templates.
|
||||
- Controllers provide website/portal routes when present.
|
||||
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||
- Data XML loads data/demo records; Security defines groups and access.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for printer_zpl2. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [base_report_to_printer](../../odoo-bringout-oca-report-print-send-base_report_to_printer)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# FAQ
|
||||
|
||||
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||
- Q: How to enable? A: Start server with --addon printer_zpl2 or install in UI.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-report-print-send-printer_zpl2"
|
||||
# or
|
||||
uv pip install odoo-bringout-oca-report-print-send-printer_zpl2"
|
||||
```
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in printer_zpl2.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class printing_label_zpl2
|
||||
class printing_label_zpl2_component
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: printer_zpl2. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon printer_zpl2
|
||||
- License: LGPL-3
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Security
|
||||
|
||||
Access control and security definitions in printer_zpl2.
|
||||
|
||||
## Access Control Lists (ACLs)
|
||||
|
||||
Model access permissions defined in:
|
||||
- **[ir.model.access.csv](../printer_zpl2/security/ir.model.access.csv)**
|
||||
- 7 model access rules
|
||||
|
||||
## Record Rules
|
||||
|
||||
Row-level security rules defined in:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Security Layers"
|
||||
A[Users] --> B[Groups]
|
||||
B --> C[Access Control Lists]
|
||||
C --> D[Models]
|
||||
B --> E[Record Rules]
|
||||
E --> F[Individual Records]
|
||||
end
|
||||
```
|
||||
|
||||
Security files overview:
|
||||
- **[ir.model.access.csv](../printer_zpl2/security/ir.model.access.csv)**
|
||||
- Model access permissions (CRUD rights)
|
||||
|
||||
Notes
|
||||
- Access Control Lists define which groups can access which models
|
||||
- Record Rules provide row-level security (filter records by user/group)
|
||||
- Security groups organize users and define permission sets
|
||||
- All security is enforced at the ORM level by Odoo
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Troubleshooting
|
||||
|
||||
- Ensure Python and Odoo environment matches repo guidance.
|
||||
- Check database connectivity and logs if startup fails.
|
||||
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Usage
|
||||
|
||||
Start Odoo including this addon (from repo root):
|
||||
|
||||
```bash
|
||||
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon printer_zpl2
|
||||
```
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Wizards
|
||||
|
||||
Transient models exposed as UI wizards in printer_zpl2.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class PrintRecordLabel
|
||||
class PrintRecordLabelLines
|
||||
class WizardImportZPl2
|
||||
```
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
==============
|
||||
Printer ZPL II
|
||||
==============
|
||||
|
||||
..
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:959cd2e93d084b3fb9c30ec5d95b216eed7d1d372aa195ec5126d9c06031815e
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |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/16.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-16-0/report-print-send-16-0-printer_zpl2
|
||||
:alt: Translate me on Weblate
|
||||
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
||||
:target: https://runboat.odoo-community.org/builds?repo=OCA/report-print-send&target_branch=16.0
|
||||
:alt: Try me on Runboat
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
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.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Nothing special, just install the module.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
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"/>
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
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
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
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
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/report-print-send/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/OCA/report-print-send/issues/new?body=module:%20printer_zpl2%0Aversion:%2016.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
|
||||
=======
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
* SUBTENO-IT
|
||||
* FLorent de Labarre
|
||||
* Apertoso NV
|
||||
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Sylvain Garancher <sylvain.garancher@syleam.fr>
|
||||
* Florent de Labarre
|
||||
* Jos De Graeve <Jos.DeGraeve@apertoso.be>
|
||||
* Rod Schouteden <rod.schouteden@dynapps.be>
|
||||
* Miquel Raïch <miquel.raich@forgeflow.com>
|
||||
* Lois Rilo <lois.rilo@forgeflow.com>
|
||||
* Tran Quoc Duong <duontq@trobz.com>
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
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.
|
||||
|
||||
This module is part of the `OCA/report-print-send <https://github.com/OCA/report-print-send/tree/16.0/printer_zpl2>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Copyright (C) 2016 SUBTENO-IT (<https://subteno-it.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Copyright (C) 2016-2022 SUBTENO-IT (<https://subteno-it.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
"name": "Printer ZPL II",
|
||||
"version": "16.0.1.1.1",
|
||||
"category": "Printer",
|
||||
"summary": "Add a ZPL II label printing feature",
|
||||
"author": "SUBTENO-IT, FLorent de Labarre, "
|
||||
"Apertoso NV, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/report-print-send",
|
||||
"license": "AGPL-3",
|
||||
"depends": ["base_report_to_printer"],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"views/printing_label_zpl2.xml",
|
||||
"wizard/print_record_label.xml",
|
||||
"wizard/wizard_import_zpl2.xml",
|
||||
],
|
||||
"installable": True,
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,5 @@
|
|||
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import printing_label_zpl2
|
||||
from . import printing_label_zpl2_component
|
||||
|
|
@ -0,0 +1,528 @@
|
|||
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import base64
|
||||
import io
|
||||
import itertools
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
import requests
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
from odoo import _, api, exceptions, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools.safe_eval import safe_eval, wrap_module
|
||||
|
||||
from . import zpl2
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PrintingLabelZpl2(models.Model):
|
||||
_name = "printing.label.zpl2"
|
||||
_description = "ZPL II Label"
|
||||
_order = "model_id, name, id"
|
||||
|
||||
name = fields.Char(required=True, help="Label Name.")
|
||||
active = fields.Boolean(default=True)
|
||||
description = fields.Char(help="Long description for this label.")
|
||||
model_id = fields.Many2one(
|
||||
comodel_name="ir.model",
|
||||
string="Model",
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
help="Model used to print this label.",
|
||||
)
|
||||
origin_x = fields.Integer(
|
||||
required=True,
|
||||
default=10,
|
||||
help="Origin point of the contents in the label, X coordinate.",
|
||||
)
|
||||
origin_y = fields.Integer(
|
||||
required=True,
|
||||
default=10,
|
||||
help="Origin point of the contents in the label, Y coordinate.",
|
||||
)
|
||||
width = fields.Integer(
|
||||
required=True,
|
||||
default=480,
|
||||
help="Width of the label, will be set on the printer before printing.",
|
||||
)
|
||||
component_ids = fields.One2many(
|
||||
comodel_name="printing.label.zpl2.component",
|
||||
inverse_name="label_id",
|
||||
string="Label Components",
|
||||
help="Components which will be printed on the label.",
|
||||
copy=True,
|
||||
)
|
||||
restore_saved_config = fields.Boolean(
|
||||
string="Restore printer's configuration",
|
||||
help="Restore printer's saved configuration and end of each label ",
|
||||
default=True,
|
||||
)
|
||||
action_window_id = fields.Many2one(
|
||||
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")
|
||||
record_id = fields.Integer(string="Record ID", default=1)
|
||||
extra = fields.Text(default="{}")
|
||||
printer_id = fields.Many2one(comodel_name="printing.printer", string="Printer")
|
||||
labelary_image = fields.Binary(
|
||||
string="Image from Labelary", compute="_compute_labelary_image"
|
||||
)
|
||||
labelary_dpmm = fields.Selection(
|
||||
selection=[
|
||||
("6dpmm", "6dpmm (152 pdi)"),
|
||||
("8dpmm", "8dpmm (203 dpi)"),
|
||||
("12dpmm", "12dpmm (300 pdi)"),
|
||||
("24dpmm", "24dpmm (600 dpi)"),
|
||||
],
|
||||
string="Print density",
|
||||
required=True,
|
||||
default="8dpmm",
|
||||
)
|
||||
labelary_width = fields.Float(string="Width in mm", default=140)
|
||||
labelary_height = fields.Float(string="Height in mm", default=70)
|
||||
|
||||
@api.constrains("component_ids")
|
||||
def check_recursion(self):
|
||||
cr = self._cr
|
||||
self.flush_recordset(["component_ids"])
|
||||
query = (
|
||||
'SELECT "{}", "{}" FROM "{}" '
|
||||
'WHERE "{}" IN %s AND "{}" IS NOT NULL'.format(
|
||||
"label_id",
|
||||
"sublabel_id",
|
||||
"printing_label_zpl2_component",
|
||||
"label_id",
|
||||
"sublabel_id",
|
||||
)
|
||||
)
|
||||
|
||||
succs = defaultdict(set) # transitive closure of successors
|
||||
preds = defaultdict(set) # transitive closure of predecessors
|
||||
todo, done = set(self.ids), set()
|
||||
while todo:
|
||||
cr.execute(query, [tuple(todo)]) # pylint: disable=E8103
|
||||
done.update(todo)
|
||||
todo.clear()
|
||||
for id1, id2 in cr.fetchall():
|
||||
for x, y in itertools.product(
|
||||
[id1] + list(preds[id1]), [id2] + list(succs[id2])
|
||||
):
|
||||
if x == y:
|
||||
raise ValidationError(_("You can not create recursive labels."))
|
||||
succs[x].add(y)
|
||||
preds[y].add(x)
|
||||
if id2 not in done:
|
||||
todo.add(id2)
|
||||
|
||||
def _get_component_data(self, record, component, eval_args):
|
||||
if component.data_autofill:
|
||||
return component.autofill_data(record, eval_args)
|
||||
return safe_eval(str(component.data), eval_args) or ""
|
||||
|
||||
def _get_to_data_to_print(
|
||||
self,
|
||||
record,
|
||||
page_number=1,
|
||||
page_count=1,
|
||||
label_offset_x=0,
|
||||
label_offset_y=0,
|
||||
**extra
|
||||
):
|
||||
to_print = []
|
||||
for component in self.component_ids:
|
||||
eval_args = extra
|
||||
eval_args.update(
|
||||
{
|
||||
"object": record,
|
||||
"page_number": str(page_number + 1),
|
||||
"page_count": str(page_count),
|
||||
"time": wrap_module(
|
||||
__import__("time"), ["time", "strptime", "strftime"]
|
||||
),
|
||||
"datetime": wrap_module(
|
||||
__import__("datetime"),
|
||||
[
|
||||
"date",
|
||||
"datetime",
|
||||
"time",
|
||||
"timedelta",
|
||||
"timezone",
|
||||
"tzinfo",
|
||||
"MAXYEAR",
|
||||
"MINYEAR",
|
||||
],
|
||||
),
|
||||
}
|
||||
)
|
||||
data = self._get_component_data(record, component, eval_args)
|
||||
if isinstance(data, str) and data == "component_not_show":
|
||||
continue
|
||||
|
||||
# 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
|
||||
|
||||
# flake8: noqa: C901
|
||||
def _generate_zpl2_components_data(
|
||||
self,
|
||||
label_data,
|
||||
record,
|
||||
page_number=1,
|
||||
page_count=1,
|
||||
label_offset_x=0,
|
||||
label_offset_y=0,
|
||||
**extra
|
||||
):
|
||||
to_print = self._get_to_data_to_print(
|
||||
record, page_number, page_count, label_offset_x, label_offset_y, **extra
|
||||
)
|
||||
|
||||
for (component, data, offset_x, offset_y) in to_print:
|
||||
component_offset_x = component.origin_x + offset_x
|
||||
component_offset_y = component.origin_y + offset_y
|
||||
if component.component_type == "text":
|
||||
barcode_arguments = {
|
||||
field_name: component[field_name]
|
||||
for field_name in [
|
||||
zpl2.ARG_FONT,
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_WIDTH,
|
||||
zpl2.ARG_REVERSE_PRINT,
|
||||
zpl2.ARG_IN_BLOCK,
|
||||
zpl2.ARG_BLOCK_WIDTH,
|
||||
zpl2.ARG_BLOCK_LINES,
|
||||
zpl2.ARG_BLOCK_SPACES,
|
||||
zpl2.ARG_BLOCK_JUSTIFY,
|
||||
zpl2.ARG_BLOCK_LEFT_MARGIN,
|
||||
]
|
||||
}
|
||||
label_data.font_data(
|
||||
component_offset_x, component_offset_y, barcode_arguments, data
|
||||
)
|
||||
elif component.component_type == "zpl2_raw":
|
||||
label_data._write_command(data)
|
||||
elif component.component_type == "rectangle":
|
||||
label_data.graphic_box(
|
||||
component_offset_x,
|
||||
component_offset_y,
|
||||
{
|
||||
zpl2.ARG_WIDTH: component.width,
|
||||
zpl2.ARG_HEIGHT: component.height,
|
||||
zpl2.ARG_THICKNESS: component.thickness,
|
||||
zpl2.ARG_COLOR: component.color,
|
||||
zpl2.ARG_ROUNDING: component.rounding,
|
||||
},
|
||||
)
|
||||
elif component.component_type == "diagonal":
|
||||
label_data.graphic_diagonal_line(
|
||||
component_offset_x,
|
||||
component_offset_y,
|
||||
{
|
||||
zpl2.ARG_WIDTH: component.width,
|
||||
zpl2.ARG_HEIGHT: component.height,
|
||||
zpl2.ARG_THICKNESS: component.thickness,
|
||||
zpl2.ARG_COLOR: component.color,
|
||||
zpl2.ARG_DIAGONAL_ORIENTATION: component.diagonal_orientation,
|
||||
},
|
||||
)
|
||||
elif component.component_type == "graphic":
|
||||
# During the on_change don't take the bin_size
|
||||
image = (
|
||||
component.with_context(bin_size_graphic_image=False).graphic_image
|
||||
or data
|
||||
)
|
||||
try:
|
||||
pil_image = Image.open(io.BytesIO(base64.b64decode(image))).convert(
|
||||
"RGB"
|
||||
)
|
||||
except Exception:
|
||||
continue
|
||||
if component.width and component.height:
|
||||
pil_image = pil_image.resize((component.width, component.height))
|
||||
|
||||
# Invert the colors
|
||||
if component.reverse_print:
|
||||
pil_image = ImageOps.invert(pil_image)
|
||||
|
||||
# Rotation (PIL rotates counter clockwise)
|
||||
if component.orientation == zpl2.ORIENTATION_ROTATED:
|
||||
pil_image = pil_image.transpose(Image.ROTATE_270)
|
||||
elif component.orientation == zpl2.ORIENTATION_INVERTED:
|
||||
pil_image = pil_image.transpose(Image.ROTATE_180)
|
||||
elif component.orientation == zpl2.ORIENTATION_BOTTOM_UP:
|
||||
pil_image = pil_image.transpose(Image.ROTATE_90)
|
||||
|
||||
label_data.graphic_field(
|
||||
component_offset_x, component_offset_y, pil_image
|
||||
)
|
||||
elif component.component_type == "circle":
|
||||
label_data.graphic_circle(
|
||||
component_offset_x,
|
||||
component_offset_y,
|
||||
{
|
||||
zpl2.ARG_DIAMETER: component.width,
|
||||
zpl2.ARG_THICKNESS: component.thickness,
|
||||
zpl2.ARG_COLOR: component.color,
|
||||
},
|
||||
)
|
||||
elif component.component_type == "sublabel":
|
||||
component_offset_x += component.sublabel_id.origin_x
|
||||
component_offset_y += component.sublabel_id.origin_y
|
||||
component.sublabel_id._generate_zpl2_components_data(
|
||||
label_data,
|
||||
data if isinstance(data, models.BaseModel) else record,
|
||||
label_offset_x=component_offset_x,
|
||||
label_offset_y=component_offset_y,
|
||||
)
|
||||
else:
|
||||
if component.component_type == zpl2.BARCODE_QR_CODE:
|
||||
# Adding Control Arguments to QRCode data Label
|
||||
data = "{}A,{}".format(component.error_correction, data)
|
||||
|
||||
barcode_arguments = {
|
||||
field_name: component[field_name]
|
||||
for field_name in [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_CHECK_DIGITS,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
zpl2.ARG_SECURITY_LEVEL,
|
||||
zpl2.ARG_COLUMNS_COUNT,
|
||||
zpl2.ARG_ROWS_COUNT,
|
||||
zpl2.ARG_TRUNCATE,
|
||||
zpl2.ARG_MODULE_WIDTH,
|
||||
zpl2.ARG_BAR_WIDTH_RATIO,
|
||||
zpl2.ARG_MODEL,
|
||||
zpl2.ARG_MAGNIFICATION_FACTOR,
|
||||
zpl2.ARG_ERROR_CORRECTION,
|
||||
zpl2.ARG_MASK_VALUE,
|
||||
]
|
||||
}
|
||||
label_data.barcode_data(
|
||||
component.origin_x + offset_x,
|
||||
component.origin_y + offset_y,
|
||||
component.component_type,
|
||||
barcode_arguments,
|
||||
data,
|
||||
)
|
||||
|
||||
def _generate_zpl2_data(self, record, page_count=1, **extra):
|
||||
self.ensure_one()
|
||||
label_data = zpl2.Zpl2()
|
||||
|
||||
labelary_emul = extra.get("labelary_emul", False)
|
||||
for page_number in range(page_count):
|
||||
# Initialize printer's configuration
|
||||
label_data.label_start()
|
||||
if not labelary_emul:
|
||||
label_data.print_width(self.width)
|
||||
label_data.label_encoding()
|
||||
|
||||
label_data.label_home(self.origin_x, self.origin_y)
|
||||
|
||||
self._generate_zpl2_components_data(
|
||||
label_data,
|
||||
record,
|
||||
page_number=page_number,
|
||||
page_count=page_count,
|
||||
**extra
|
||||
)
|
||||
|
||||
# Restore printer's configuration and end the label
|
||||
if self.restore_saved_config:
|
||||
label_data.configuration_update(zpl2.CONF_RECALL_LAST_SAVED)
|
||||
label_data.label_end()
|
||||
|
||||
return label_data.output()
|
||||
|
||||
def print_label(self, printer, record, page_count=1, **extra):
|
||||
for label in self:
|
||||
if record._name != label.model_id.model:
|
||||
raise exceptions.UserError(
|
||||
_("This label cannot be used on {model}").format(model=record._name)
|
||||
)
|
||||
# Send the label to printer
|
||||
label_contents = label._generate_zpl2_data(
|
||||
record, page_count=page_count, **extra
|
||||
)
|
||||
printer.print_document(
|
||||
report=None, content=label_contents, doc_format="raw"
|
||||
)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def new_action(self, model_id):
|
||||
return self.env["ir.actions.act_window"].create(
|
||||
{
|
||||
"name": _("Print Label"),
|
||||
"binding_model_id": model_id,
|
||||
"res_model": "wizard.print.record.label",
|
||||
"view_mode": "form",
|
||||
"target": "new",
|
||||
"binding_type": "action",
|
||||
"context": "{'default_active_model_id': %s}" % model_id,
|
||||
}
|
||||
)
|
||||
|
||||
@api.model
|
||||
def add_action(self, model_id):
|
||||
action = self.env["ir.actions.act_window"].search(
|
||||
[
|
||||
("binding_model_id", "=", model_id),
|
||||
("res_model", "=", "wizard.print.record.label"),
|
||||
("view_mode", "=", "form"),
|
||||
("binding_type", "=", "action"),
|
||||
]
|
||||
)
|
||||
if not action:
|
||||
action = self.new_action(model_id)
|
||||
return action
|
||||
|
||||
def create_action(self):
|
||||
models = self.filtered(lambda record: not record.action_window_id).mapped(
|
||||
"model_id"
|
||||
)
|
||||
labels = self.with_context(active_test=False).search(
|
||||
[("model_id", "in", models.ids), ("action_window_id", "=", False)]
|
||||
)
|
||||
actions = self.env["ir.actions.act_window"].search(
|
||||
[
|
||||
("binding_model_id", "in", models.ids),
|
||||
("res_model", "=", "wizard.print.record.label"),
|
||||
("view_mode", "=", "form"),
|
||||
("binding_type", "=", "action"),
|
||||
]
|
||||
)
|
||||
for model in models:
|
||||
action = actions.filtered(lambda a: a.binding_model_id == model)
|
||||
if not action:
|
||||
action = self.new_action(model.id)
|
||||
for label in labels.filtered(lambda l: l.model_id == model):
|
||||
label.action_window_id = action
|
||||
return True
|
||||
|
||||
def unlink_action(self):
|
||||
self.mapped("action_window_id").unlink()
|
||||
|
||||
def import_zpl2(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"view_mode": "form",
|
||||
"res_model": "wizard.import.zpl2",
|
||||
"type": "ir.actions.act_window",
|
||||
"target": "new",
|
||||
"context": {"default_label_id": self.id},
|
||||
}
|
||||
|
||||
def _get_record(self):
|
||||
self.ensure_one()
|
||||
Obj = self.env[self.model_id.model]
|
||||
record = Obj.search([("id", "=", self.record_id)], limit=1)
|
||||
if not record:
|
||||
record = Obj.search([], limit=1, order="id desc")
|
||||
self.record_id = record.id
|
||||
|
||||
return record
|
||||
|
||||
def print_test_label(self):
|
||||
for label in self:
|
||||
if label.test_print_mode and label.record_id and label.printer_id:
|
||||
record = label._get_record()
|
||||
extra = safe_eval(label.extra, {"env": self.env})
|
||||
if record:
|
||||
label.print_label(label.printer_id, record, **extra)
|
||||
|
||||
@api.depends(
|
||||
"record_id",
|
||||
"labelary_dpmm",
|
||||
"labelary_width",
|
||||
"labelary_height",
|
||||
"component_ids",
|
||||
"origin_x",
|
||||
"origin_y",
|
||||
"test_labelary_mode",
|
||||
)
|
||||
def _compute_labelary_image(self):
|
||||
for label in self:
|
||||
label.labelary_image = label._generate_labelary_image()
|
||||
|
||||
def _generate_labelary_image(self):
|
||||
self.ensure_one()
|
||||
if not (
|
||||
self.test_labelary_mode
|
||||
and self.record_id
|
||||
and self.labelary_width
|
||||
and self.labelary_height
|
||||
and self.labelary_dpmm
|
||||
and self.component_ids
|
||||
):
|
||||
return False
|
||||
record = self._get_record()
|
||||
if record:
|
||||
# If case there an error (in the data field with the safe_eval
|
||||
# for exemple) the new component or the update is not lost.
|
||||
try:
|
||||
url = (
|
||||
"http://api.labelary.com/v1/printers/"
|
||||
"{dpmm}/labels/{width}x{height}/0/"
|
||||
)
|
||||
width = round(self.labelary_width / 25.4, 2)
|
||||
height = round(self.labelary_height / 25.4, 2)
|
||||
url = url.format(dpmm=self.labelary_dpmm, width=width, height=height)
|
||||
extra = safe_eval(self.extra, {"env": self.env})
|
||||
zpl_file = self._generate_zpl2_data(record, labelary_emul=True, **extra)
|
||||
files = {"file": zpl_file}
|
||||
headers = {"Accept": "image/png"}
|
||||
response = requests.post(
|
||||
url, headers=headers, files=files, stream=True, timeout=30
|
||||
)
|
||||
if response.status_code == 200:
|
||||
# Add a padd
|
||||
im = Image.open(io.BytesIO(response.content))
|
||||
im_size = im.size
|
||||
new_im = Image.new(
|
||||
"RGB", (im_size[0] + 2, im_size[1] + 2), (164, 164, 164)
|
||||
)
|
||||
new_im.paste(im, (1, 1))
|
||||
imgByteArr = io.BytesIO()
|
||||
new_im.save(imgByteArr, format="PNG")
|
||||
return base64.b64encode(imgByteArr.getvalue())
|
||||
else:
|
||||
_logger.warning(
|
||||
_("Error with Labelary API. %s") % response.status_code
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
_logger.warning(_("Error with Labelary API. %s") % e)
|
||||
return False
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
from . import zpl2
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
DEFAULT_PYTHON_CODE = """# Python One-Liners
|
||||
# - object: %s record on which the action is triggered; may be void
|
||||
# - page_number: Current Page
|
||||
# - page_count: Total Page
|
||||
# - time, datetime: Python libraries
|
||||
# - write instead 'component_not_show' to don't show this component
|
||||
# Example: object.name
|
||||
|
||||
|
||||
""
|
||||
"""
|
||||
|
||||
|
||||
class PrintingLabelZpl2Component(models.Model):
|
||||
_name = "printing.label.zpl2.component"
|
||||
_description = "ZPL II Label Component"
|
||||
_order = "sequence, id"
|
||||
|
||||
label_id = fields.Many2one(
|
||||
comodel_name="printing.label.zpl2",
|
||||
string="Label",
|
||||
required=True,
|
||||
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(
|
||||
required=True,
|
||||
default=10,
|
||||
help="Origin point of the component in the label, X coordinate.",
|
||||
)
|
||||
origin_y = fields.Integer(
|
||||
required=True,
|
||||
default=10,
|
||||
help="Origin point of the component in the label, Y coordinate.",
|
||||
)
|
||||
component_type = fields.Selection(
|
||||
selection=[
|
||||
("text", "Text"),
|
||||
("rectangle", "Rectangle / Line"),
|
||||
("diagonal", "Diagonal Line"),
|
||||
("circle", "Circle"),
|
||||
("graphic", "Graphic"),
|
||||
(str(zpl2.BARCODE_CODE_11), "Code 11"),
|
||||
(str(zpl2.BARCODE_INTERLEAVED_2_OF_5), "Interleaved 2 of 5"),
|
||||
(str(zpl2.BARCODE_CODE_39), "Code 39"),
|
||||
(str(zpl2.BARCODE_CODE_49), "Code 49"),
|
||||
(str(zpl2.BARCODE_PDF417), "PDF417"),
|
||||
(str(zpl2.BARCODE_EAN_8), "EAN-8"),
|
||||
(str(zpl2.BARCODE_UPC_E), "UPC-E"),
|
||||
(str(zpl2.BARCODE_CODE_128), "Code 128"),
|
||||
(str(zpl2.BARCODE_EAN_13), "EAN-13"),
|
||||
(str(zpl2.BARCODE_QR_CODE), "QR Code"),
|
||||
("sublabel", "Sublabel"),
|
||||
("zpl2_raw", "ZPL2"),
|
||||
],
|
||||
string="Type",
|
||||
required=True,
|
||||
default="text",
|
||||
help="Type of content, simple text or barcode.",
|
||||
)
|
||||
font = fields.Selection(
|
||||
selection=[
|
||||
(str(zpl2.FONT_DEFAULT), "Default"),
|
||||
(str(zpl2.FONT_9X5), "9x5"),
|
||||
(str(zpl2.FONT_11X7), "11x7"),
|
||||
(str(zpl2.FONT_18X10), "18x10"),
|
||||
(str(zpl2.FONT_28X15), "28x15"),
|
||||
(str(zpl2.FONT_26X13), "26x13"),
|
||||
(str(zpl2.FONT_60X40), "60x40"),
|
||||
(str(zpl2.FONT_21X13), "21x13"),
|
||||
],
|
||||
required=True,
|
||||
default=str(zpl2.FONT_DEFAULT),
|
||||
help="Font to use, for text only.",
|
||||
)
|
||||
thickness = fields.Integer(help="Thickness of the line to draw.")
|
||||
color = fields.Selection(
|
||||
selection=[(str(zpl2.COLOR_BLACK), "Black"), (str(zpl2.COLOR_WHITE), "White")],
|
||||
default=str(zpl2.COLOR_BLACK),
|
||||
help="Color of the line to draw.",
|
||||
)
|
||||
orientation = fields.Selection(
|
||||
selection=[
|
||||
(str(zpl2.ORIENTATION_NORMAL), "Normal"),
|
||||
(str(zpl2.ORIENTATION_ROTATED), "Rotated"),
|
||||
(str(zpl2.ORIENTATION_INVERTED), "Inverted"),
|
||||
(str(zpl2.ORIENTATION_BOTTOM_UP), "Read from Bottom up"),
|
||||
],
|
||||
required=True,
|
||||
default=str(zpl2.ORIENTATION_NORMAL),
|
||||
help="Orientation of the barcode.",
|
||||
)
|
||||
diagonal_orientation = fields.Selection(
|
||||
selection=[
|
||||
(str(zpl2.DIAGONAL_ORIENTATION_LEFT), "Left (\\)"),
|
||||
(str(zpl2.DIAGONAL_ORIENTATION_RIGHT), "Right (/)"),
|
||||
],
|
||||
default=str(zpl2.DIAGONAL_ORIENTATION_LEFT),
|
||||
help="Orientation of the diagonal line.",
|
||||
)
|
||||
data_autofill = fields.Boolean(
|
||||
string="Autofill Data",
|
||||
help="Change 'data' with dictionary of the object information.",
|
||||
)
|
||||
check_digits = fields.Boolean(
|
||||
help="Check if you want to compute and print the check digit."
|
||||
)
|
||||
height = fields.Integer(
|
||||
help="Height of the printed component. For a text component, height "
|
||||
"of a single character."
|
||||
)
|
||||
width = fields.Integer(
|
||||
help="Width of the printed component. For a text component, width of "
|
||||
"a single character."
|
||||
)
|
||||
rounding = fields.Integer(help="Rounding of the printed rectangle corners.")
|
||||
interpretation_line = fields.Boolean(
|
||||
help="Check if you want the interpretation line to be printed."
|
||||
)
|
||||
interpretation_line_above = fields.Boolean(
|
||||
help="Check if you want the interpretation line to be printed above "
|
||||
"the barcode."
|
||||
)
|
||||
module_width = fields.Integer(default=2, help="Module width for the barcode.")
|
||||
bar_width_ratio = fields.Float(
|
||||
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.")
|
||||
rows_count = fields.Integer(help="Number of rows to encode.")
|
||||
truncate = fields.Boolean(help="Check if you want to truncate the barcode.")
|
||||
model = fields.Selection(
|
||||
selection=[
|
||||
(str(zpl2.MODEL_ORIGINAL), "Original"),
|
||||
(str(zpl2.MODEL_ENHANCED), "Enhanced"),
|
||||
],
|
||||
default=str(zpl2.MODEL_ENHANCED),
|
||||
help="Barcode model, used by some barcode types like QR Code.",
|
||||
)
|
||||
magnification_factor = fields.Integer(
|
||||
default=1, help="Magnification Factor, from 1 to 10."
|
||||
)
|
||||
only_product_barcode = fields.Boolean("Only product barcode data")
|
||||
error_correction = fields.Selection(
|
||||
selection=[
|
||||
(str(zpl2.ERROR_CORRECTION_ULTRA_HIGH), "Ultra-high Reliability Level"),
|
||||
(str(zpl2.ERROR_CORRECTION_HIGH), "High Reliability Level"),
|
||||
(str(zpl2.ERROR_CORRECTION_STANDARD), "Standard Level"),
|
||||
(str(zpl2.ERROR_CORRECTION_HIGH_DENSITY), "High Density Level"),
|
||||
],
|
||||
required=True,
|
||||
default=str(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.")
|
||||
model_id = fields.Many2one(
|
||||
comodel_name="ir.model", compute="_compute_model_id", string="Record's model"
|
||||
)
|
||||
data = fields.Text(
|
||||
default=lambda self: self._compute_default_data(),
|
||||
required=True,
|
||||
help="Data to print on this component. Resource values can be "
|
||||
"inserted with %(object.field_name)s.",
|
||||
)
|
||||
sublabel_id = fields.Many2one(
|
||||
comodel_name="printing.label.zpl2",
|
||||
string="Sublabel",
|
||||
help="Another label to include into this one as a component. "
|
||||
"This allows to define reusable labels parts.",
|
||||
)
|
||||
repeat = fields.Boolean(
|
||||
string="Repeatable",
|
||||
help="Check this box to repeat this component on the label.",
|
||||
)
|
||||
repeat_offset = fields.Integer(
|
||||
default=0, help="Number of elements to skip when reading a list of elements."
|
||||
)
|
||||
repeat_count = fields.Integer(
|
||||
default=1, help="Maximum count of repeats of the component."
|
||||
)
|
||||
repeat_offset_x = fields.Integer(
|
||||
help="X coordinate offset between each occurence of this component on "
|
||||
"the label."
|
||||
)
|
||||
repeat_offset_y = fields.Integer(
|
||||
help="Y coordinate offset between each occurence of this component on "
|
||||
"the label."
|
||||
)
|
||||
reverse_print = fields.Boolean(
|
||||
help="If checked, the data will be printed in the inverse color of "
|
||||
"the background."
|
||||
)
|
||||
in_block = fields.Boolean(
|
||||
help="If checked, the data will be restrected in a "
|
||||
"defined block on the label."
|
||||
)
|
||||
block_width = fields.Integer(help="Width of the block.")
|
||||
block_lines = fields.Integer(
|
||||
default=1, help="Maximum number of lines to print in the block."
|
||||
)
|
||||
block_spaces = fields.Integer(
|
||||
help="Number of spaces added between lines in the block."
|
||||
)
|
||||
block_justify = fields.Selection(
|
||||
selection=[
|
||||
(str(zpl2.JUSTIFY_LEFT), "Left"),
|
||||
(str(zpl2.JUSTIFY_CENTER), "Center"),
|
||||
(str(zpl2.JUSTIFY_JUSTIFIED), "Justified"),
|
||||
(str(zpl2.JUSTIFY_RIGHT), "Right"),
|
||||
],
|
||||
string="Justify",
|
||||
required=True,
|
||||
default="L",
|
||||
help="Choose how the text will be justified in the block.",
|
||||
)
|
||||
block_left_margin = fields.Integer(
|
||||
string="Left Margin",
|
||||
help="Left margin for the second and other lines in the block.",
|
||||
)
|
||||
graphic_image = fields.Binary(
|
||||
string="Image",
|
||||
attachment=True,
|
||||
help="This field holds a static image to print. "
|
||||
"If not set, the data field is evaluated.",
|
||||
)
|
||||
|
||||
def process_model(self, model):
|
||||
# Used for expansions of this module
|
||||
return model
|
||||
|
||||
@api.depends("label_id.model_id")
|
||||
def _compute_model_id(self):
|
||||
# it's 'compute' instead of 'related' because is easier to expand it
|
||||
for component in self:
|
||||
component.model_id = self.process_model(component.label_id.model_id)
|
||||
|
||||
def _compute_default_data(self):
|
||||
model_id = self.env.context.get("default_model_id") or self.model_id.id
|
||||
model = self.env["ir.model"].browse(model_id)
|
||||
model = self.process_model(model)
|
||||
return DEFAULT_PYTHON_CODE % (model.model or "")
|
||||
|
||||
@api.onchange("model_id", "data")
|
||||
def _onchange_data(self):
|
||||
for component in self.filtered(lambda c: not c.data):
|
||||
component.data = component._compute_default_data()
|
||||
|
||||
@api.onchange("component_type")
|
||||
def _onchange_component_type(self):
|
||||
for component in self:
|
||||
if component.component_type == "qr_code":
|
||||
component.data_autofill = True
|
||||
else:
|
||||
component.data_autofill = False
|
||||
|
||||
@api.model
|
||||
def autofill_data(self, record, eval_args):
|
||||
data = {}
|
||||
usual_fields = ["id", "create_date", record.display_name]
|
||||
for field in usual_fields:
|
||||
if hasattr(record, field):
|
||||
data[field] = getattr(record, field)
|
||||
return data
|
||||
|
||||
def action_plus_origin_x(self):
|
||||
self.ensure_one()
|
||||
self.origin_x += 10
|
||||
|
||||
def action_minus_origin_x(self):
|
||||
self.ensure_one()
|
||||
self.origin_x -= 10
|
||||
|
||||
def action_plus_origin_y(self):
|
||||
self.ensure_one()
|
||||
self.origin_y += 10
|
||||
|
||||
def action_minus_origin_y(self):
|
||||
self.ensure_one()
|
||||
self.origin_y -= 10
|
||||
|
|
@ -0,0 +1,515 @@
|
|||
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
# Copied from https://github.com/subteno-it/python-zpl2, as there has been new releases
|
||||
# that breaks current code without clear source (no commit on the repo). As the amount
|
||||
# of code is not too much, we put it on the module itself, being able to control the
|
||||
# whole chain, and to reduce the code with the considerations of current Odoo version
|
||||
|
||||
import binascii
|
||||
import math
|
||||
|
||||
from PIL import ImageOps
|
||||
|
||||
# Constants for the printer configuration management
|
||||
CONF_RELOAD_FACTORY = "F"
|
||||
CONF_RELOAD_NETWORK_FACTORY = "N"
|
||||
CONF_RECALL_LAST_SAVED = "R"
|
||||
CONF_SAVE_CURRENT = "S"
|
||||
|
||||
# Command arguments names
|
||||
ARG_FONT = "font"
|
||||
ARG_HEIGHT = "height"
|
||||
ARG_WIDTH = "width"
|
||||
ARG_ORIENTATION = "orientation"
|
||||
ARG_THICKNESS = "thickness"
|
||||
ARG_BLOCK_WIDTH = "block_width"
|
||||
ARG_BLOCK_LINES = "block_lines"
|
||||
ARG_BLOCK_SPACES = "block_spaces"
|
||||
ARG_BLOCK_JUSTIFY = "block_justify"
|
||||
ARG_BLOCK_LEFT_MARGIN = "block_left_margin"
|
||||
ARG_CHECK_DIGITS = "check_digits"
|
||||
ARG_INTERPRETATION_LINE = "interpretation_line"
|
||||
ARG_INTERPRETATION_LINE_ABOVE = "interpretation_line_above"
|
||||
ARG_STARTING_MODE = "starting_mode"
|
||||
ARG_SECURITY_LEVEL = "security_level"
|
||||
ARG_COLUMNS_COUNT = "columns_count"
|
||||
ARG_ROWS_COUNT = "rows_count"
|
||||
ARG_TRUNCATE = "truncate"
|
||||
ARG_MODE = "mode"
|
||||
ARG_MODULE_WIDTH = "module_width"
|
||||
ARG_BAR_WIDTH_RATIO = "bar_width_ratio"
|
||||
ARG_REVERSE_PRINT = "reverse_print"
|
||||
ARG_IN_BLOCK = "in_block"
|
||||
ARG_COLOR = "color"
|
||||
ARG_ROUNDING = "rounding"
|
||||
ARG_DIAMETER = "diameter"
|
||||
ARG_DIAGONAL_ORIENTATION = "diagonal_orientation"
|
||||
ARG_MODEL = "model"
|
||||
ARG_MAGNIFICATION_FACTOR = "magnification_factor"
|
||||
ARG_ERROR_CORRECTION = "error_correction"
|
||||
ARG_MASK_VALUE = "mask_value"
|
||||
|
||||
# Model values
|
||||
MODEL_ORIGINAL = 1
|
||||
MODEL_ENHANCED = 2
|
||||
|
||||
# Error Correction
|
||||
ERROR_CORRECTION_ULTRA_HIGH = "H"
|
||||
ERROR_CORRECTION_HIGH = "Q"
|
||||
ERROR_CORRECTION_STANDARD = "M"
|
||||
ERROR_CORRECTION_HIGH_DENSITY = "L"
|
||||
|
||||
# Boolean values
|
||||
BOOL_YES = "Y"
|
||||
BOOL_NO = "N"
|
||||
|
||||
# Orientation values
|
||||
ORIENTATION_NORMAL = "N"
|
||||
ORIENTATION_ROTATED = "R"
|
||||
ORIENTATION_INVERTED = "I"
|
||||
ORIENTATION_BOTTOM_UP = "B"
|
||||
|
||||
# Diagonal lines orientation values
|
||||
DIAGONAL_ORIENTATION_LEFT = "L"
|
||||
DIAGONAL_ORIENTATION_RIGHT = "R"
|
||||
|
||||
# Justify values
|
||||
JUSTIFY_LEFT = "L"
|
||||
JUSTIFY_CENTER = "C"
|
||||
JUSTIFY_JUSTIFIED = "J"
|
||||
JUSTIFY_RIGHT = "R"
|
||||
|
||||
# Font values
|
||||
FONT_DEFAULT = "0"
|
||||
FONT_9X5 = "A"
|
||||
FONT_11X7 = "B"
|
||||
FONT_18X10 = "D"
|
||||
FONT_28X15 = "E"
|
||||
FONT_26X13 = "F"
|
||||
FONT_60X40 = "G"
|
||||
FONT_21X13 = "H"
|
||||
|
||||
# Color values
|
||||
COLOR_BLACK = "B"
|
||||
COLOR_WHITE = "W"
|
||||
|
||||
# Barcode types
|
||||
BARCODE_CODE_11 = "code_11"
|
||||
BARCODE_INTERLEAVED_2_OF_5 = "interleaved_2_of_5"
|
||||
BARCODE_CODE_39 = "code_39"
|
||||
BARCODE_CODE_49 = "code_49"
|
||||
BARCODE_PDF417 = "pdf417"
|
||||
BARCODE_EAN_8 = "ean-8"
|
||||
BARCODE_UPC_E = "upc-e"
|
||||
BARCODE_CODE_128 = "code_128"
|
||||
BARCODE_EAN_13 = "ean-13"
|
||||
BARCODE_QR_CODE = "qr_code"
|
||||
|
||||
|
||||
class Zpl2(object):
|
||||
"""ZPL II management class
|
||||
Allows to generate data for Zebra printers
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.encoding = "utf-8"
|
||||
self.initialize()
|
||||
|
||||
def initialize(self):
|
||||
self._buffer = []
|
||||
|
||||
def output(self):
|
||||
"""Return the full contents to send to the printer"""
|
||||
return "\n".encode(self.encoding).join(self._buffer)
|
||||
|
||||
def _enforce(self, value, minimum=1, maximum=32000):
|
||||
"""Returns the value, forced between minimum and maximum"""
|
||||
return min(max(minimum, value), maximum)
|
||||
|
||||
def _write_command(self, data):
|
||||
"""Adds a complete command to buffer"""
|
||||
self._buffer.append(str(data).encode(self.encoding))
|
||||
|
||||
def _generate_arguments(self, arguments, kwargs):
|
||||
"""Generate a zebra arguments from an argument names list and a dict of
|
||||
values for these arguments
|
||||
@param arguments : list of argument names, ORDER MATTERS
|
||||
@param kwargs : list of arguments values
|
||||
"""
|
||||
command_arguments = []
|
||||
# Add all arguments in the list, if they exist
|
||||
for argument in arguments:
|
||||
if kwargs.get(argument, None) is not None:
|
||||
if isinstance(kwargs[argument], bool):
|
||||
kwargs[argument] = kwargs[argument] and BOOL_YES or BOOL_NO
|
||||
command_arguments.append(kwargs[argument])
|
||||
|
||||
# Return a zebra formatted string, with a comma between each argument
|
||||
return ",".join(map(str, command_arguments))
|
||||
|
||||
def print_width(self, label_width):
|
||||
"""Defines the print width setting on the printer"""
|
||||
self._write_command("^PW%d" % label_width)
|
||||
|
||||
def configuration_update(self, active_configuration):
|
||||
"""Set the active configuration on the printer"""
|
||||
self._write_command("^JU%s" % active_configuration)
|
||||
|
||||
def label_start(self):
|
||||
"""Adds the label start command to the buffer"""
|
||||
self._write_command("^XA")
|
||||
|
||||
def label_encoding(self):
|
||||
"""Adds the label encoding command to the buffer
|
||||
Fixed value defined to UTF-8
|
||||
"""
|
||||
self._write_command("^CI28")
|
||||
|
||||
def label_end(self):
|
||||
"""Adds the label start command to the buffer"""
|
||||
self._write_command("^XZ")
|
||||
|
||||
def label_home(self, left, top):
|
||||
"""Define the label top left corner"""
|
||||
self._write_command("^LH%d,%d" % (left, top))
|
||||
|
||||
def _field_origin(self, right, down):
|
||||
"""Define the top left corner of the data, from the top left corner of
|
||||
the label
|
||||
"""
|
||||
return "^FO%d,%d" % (right, down)
|
||||
|
||||
def _font_format(self, font_format):
|
||||
"""Send the commands which define the font to use for the current data"""
|
||||
arguments = [ARG_FONT, ARG_HEIGHT, ARG_WIDTH]
|
||||
# Add orientation in the font name (only place where there is
|
||||
# no comma between values)
|
||||
font_format[ARG_FONT] += font_format.get(ARG_ORIENTATION, ORIENTATION_NORMAL)
|
||||
# Check that the height value fits in the allowed values
|
||||
if font_format.get(ARG_HEIGHT) is not None:
|
||||
font_format[ARG_HEIGHT] = self._enforce(font_format[ARG_HEIGHT], minimum=10)
|
||||
# Check that the width value fits in the allowed values
|
||||
if font_format.get(ARG_WIDTH) is not None:
|
||||
font_format[ARG_WIDTH] = self._enforce(font_format[ARG_WIDTH], minimum=10)
|
||||
# Generate the ZPL II command
|
||||
return "^A" + self._generate_arguments(arguments, font_format)
|
||||
|
||||
def _field_block(self, block_format):
|
||||
"""Define a maximum width to print some data"""
|
||||
arguments = [
|
||||
ARG_BLOCK_WIDTH,
|
||||
ARG_BLOCK_LINES,
|
||||
ARG_BLOCK_SPACES,
|
||||
ARG_BLOCK_JUSTIFY,
|
||||
ARG_BLOCK_LEFT_MARGIN,
|
||||
]
|
||||
return "^FB" + self._generate_arguments(arguments, block_format)
|
||||
|
||||
def _barcode_format(self, barcodeType, barcode_format):
|
||||
"""Generate the commands to print a barcode
|
||||
Each barcode type needs a specific function
|
||||
"""
|
||||
|
||||
def _code11(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_CHECK_DIGITS,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
return "1" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _interleaved2of5(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_INTERPRETATION_LINE_ABOVE,
|
||||
ARG_CHECK_DIGITS,
|
||||
]
|
||||
return "2" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _code39(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_CHECK_DIGITS,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
return "3" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _code49(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_STARTING_MODE,
|
||||
]
|
||||
# Use interpretation_line and interpretation_line_above to generate
|
||||
# a specific interpretation_line value
|
||||
if kwargs.get(ARG_INTERPRETATION_LINE) is not None:
|
||||
if kwargs[ARG_INTERPRETATION_LINE]:
|
||||
if kwargs[ARG_INTERPRETATION_LINE_ABOVE]:
|
||||
# Interpretation line after
|
||||
kwargs[ARG_INTERPRETATION_LINE] = "A"
|
||||
else:
|
||||
# Interpretation line before
|
||||
kwargs[ARG_INTERPRETATION_LINE] = "B"
|
||||
else:
|
||||
# No interpretation line
|
||||
kwargs[ARG_INTERPRETATION_LINE] = "N"
|
||||
return "4" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _pdf417(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_HEIGHT,
|
||||
ARG_SECURITY_LEVEL,
|
||||
ARG_COLUMNS_COUNT,
|
||||
ARG_ROWS_COUNT,
|
||||
ARG_TRUNCATE,
|
||||
]
|
||||
return "7" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _ean8(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
return "8" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _upce(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_INTERPRETATION_LINE_ABOVE,
|
||||
ARG_CHECK_DIGITS,
|
||||
]
|
||||
return "9" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _code128(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_INTERPRETATION_LINE_ABOVE,
|
||||
ARG_CHECK_DIGITS,
|
||||
ARG_MODE,
|
||||
]
|
||||
return "C" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _ean13(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_HEIGHT,
|
||||
ARG_INTERPRETATION_LINE,
|
||||
ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
return "E" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
def _qrcode(**kwargs):
|
||||
arguments = [
|
||||
ARG_ORIENTATION,
|
||||
ARG_MODEL,
|
||||
ARG_MAGNIFICATION_FACTOR,
|
||||
ARG_ERROR_CORRECTION,
|
||||
ARG_MASK_VALUE,
|
||||
]
|
||||
return "Q" + self._generate_arguments(arguments, kwargs)
|
||||
|
||||
barcodeTypes = {
|
||||
BARCODE_CODE_11: _code11,
|
||||
BARCODE_INTERLEAVED_2_OF_5: _interleaved2of5,
|
||||
BARCODE_CODE_39: _code39,
|
||||
BARCODE_CODE_49: _code49,
|
||||
BARCODE_PDF417: _pdf417,
|
||||
BARCODE_EAN_8: _ean8,
|
||||
BARCODE_UPC_E: _upce,
|
||||
BARCODE_CODE_128: _code128,
|
||||
BARCODE_EAN_13: _ean13,
|
||||
BARCODE_QR_CODE: _qrcode,
|
||||
}
|
||||
return "^B" + barcodeTypes[barcodeType](**barcode_format)
|
||||
|
||||
def _barcode_field_default(self, barcode_format):
|
||||
"""Add the data start command to the buffer"""
|
||||
arguments = [
|
||||
ARG_MODULE_WIDTH,
|
||||
ARG_BAR_WIDTH_RATIO,
|
||||
]
|
||||
return "^BY" + self._generate_arguments(arguments, barcode_format)
|
||||
|
||||
def _field_data_start(self):
|
||||
"""Add the data start command to the buffer"""
|
||||
return "^FD"
|
||||
|
||||
def _field_reverse_print(self):
|
||||
"""Allows the printed data to appear white over black, or black over white"""
|
||||
return "^FR"
|
||||
|
||||
def _field_data_stop(self):
|
||||
"""Add the data stop command to the buffer"""
|
||||
return "^FS"
|
||||
|
||||
def _field_data(self, data):
|
||||
"""Add data to the buffer, between start and stop commands"""
|
||||
command = "{start}{data}{stop}".format(
|
||||
start=self._field_data_start(),
|
||||
data=data,
|
||||
stop=self._field_data_stop(),
|
||||
)
|
||||
return command
|
||||
|
||||
def font_data(self, right, down, field_format, data):
|
||||
"""Add a full text in the buffer, with needed formatting commands"""
|
||||
reverse = ""
|
||||
if field_format.get(ARG_REVERSE_PRINT, False):
|
||||
reverse = self._field_reverse_print()
|
||||
block = ""
|
||||
if field_format.get(ARG_IN_BLOCK, False):
|
||||
block = self._field_block(field_format)
|
||||
command = "{origin}{font_format}{reverse}{block}{data}".format(
|
||||
origin=self._field_origin(right, down),
|
||||
font_format=self._font_format(field_format),
|
||||
reverse=reverse,
|
||||
block=block,
|
||||
data=self._field_data(data),
|
||||
)
|
||||
self._write_command(command)
|
||||
|
||||
def barcode_data(self, right, down, barcodeType, barcode_format, data):
|
||||
"""Add a full barcode in the buffer, with needed formatting commands"""
|
||||
command = "{default}{origin}{barcode_format}{data}".format(
|
||||
default=self._barcode_field_default(barcode_format),
|
||||
origin=self._field_origin(right, down),
|
||||
barcode_format=self._barcode_format(barcodeType, barcode_format),
|
||||
data=self._field_data(data),
|
||||
)
|
||||
self._write_command(command)
|
||||
|
||||
def graphic_box(self, right, down, graphic_format):
|
||||
"""Send the commands to draw a rectangle"""
|
||||
arguments = [
|
||||
ARG_WIDTH,
|
||||
ARG_HEIGHT,
|
||||
ARG_THICKNESS,
|
||||
ARG_COLOR,
|
||||
ARG_ROUNDING,
|
||||
]
|
||||
# Check that the thickness value fits in the allowed values
|
||||
if graphic_format.get(ARG_THICKNESS) is not None:
|
||||
graphic_format[ARG_THICKNESS] = self._enforce(graphic_format[ARG_THICKNESS])
|
||||
# Check that the width value fits in the allowed values
|
||||
if graphic_format.get(ARG_WIDTH) is not None:
|
||||
graphic_format[ARG_WIDTH] = self._enforce(
|
||||
graphic_format[ARG_WIDTH], minimum=graphic_format[ARG_THICKNESS]
|
||||
)
|
||||
# Check that the height value fits in the allowed values
|
||||
if graphic_format.get(ARG_HEIGHT) is not None:
|
||||
graphic_format[ARG_HEIGHT] = self._enforce(
|
||||
graphic_format[ARG_HEIGHT], minimum=graphic_format[ARG_THICKNESS]
|
||||
)
|
||||
# Check that the rounding value fits in the allowed values
|
||||
if graphic_format.get(ARG_ROUNDING) is not None:
|
||||
graphic_format[ARG_ROUNDING] = self._enforce(
|
||||
graphic_format[ARG_ROUNDING], minimum=0, maximum=8
|
||||
)
|
||||
# Generate the ZPL II command
|
||||
command = "{origin}{data}{stop}".format(
|
||||
origin=self._field_origin(right, down),
|
||||
data="^GB" + self._generate_arguments(arguments, graphic_format),
|
||||
stop=self._field_data_stop(),
|
||||
)
|
||||
self._write_command(command)
|
||||
|
||||
def graphic_diagonal_line(self, right, down, graphic_format):
|
||||
"""Send the commands to draw a rectangle"""
|
||||
arguments = [
|
||||
ARG_WIDTH,
|
||||
ARG_HEIGHT,
|
||||
ARG_THICKNESS,
|
||||
ARG_COLOR,
|
||||
ARG_DIAGONAL_ORIENTATION,
|
||||
]
|
||||
# Check that the thickness value fits in the allowed values
|
||||
if graphic_format.get(ARG_THICKNESS) is not None:
|
||||
graphic_format[ARG_THICKNESS] = self._enforce(graphic_format[ARG_THICKNESS])
|
||||
# Check that the width value fits in the allowed values
|
||||
if graphic_format.get(ARG_WIDTH) is not None:
|
||||
graphic_format[ARG_WIDTH] = self._enforce(
|
||||
graphic_format[ARG_WIDTH], minimum=3
|
||||
)
|
||||
# Check that the height value fits in the allowed values
|
||||
if graphic_format.get(ARG_HEIGHT) is not None:
|
||||
graphic_format[ARG_HEIGHT] = self._enforce(
|
||||
graphic_format[ARG_HEIGHT], minimum=3
|
||||
)
|
||||
# Check the given orientation
|
||||
graphic_format[ARG_DIAGONAL_ORIENTATION] = graphic_format.get(
|
||||
ARG_DIAGONAL_ORIENTATION, DIAGONAL_ORIENTATION_LEFT
|
||||
)
|
||||
# Generate the ZPL II command
|
||||
command = "{origin}{data}{stop}".format(
|
||||
origin=self._field_origin(right, down),
|
||||
data="^GD" + self._generate_arguments(arguments, graphic_format),
|
||||
stop=self._field_data_stop(),
|
||||
)
|
||||
self._write_command(command)
|
||||
|
||||
def graphic_circle(self, right, down, graphic_format):
|
||||
"""Send the commands to draw a circle"""
|
||||
arguments = [ARG_DIAMETER, ARG_THICKNESS, ARG_COLOR]
|
||||
# Check that the diameter value fits in the allowed values
|
||||
if graphic_format.get(ARG_DIAMETER) is not None:
|
||||
graphic_format[ARG_DIAMETER] = self._enforce(
|
||||
graphic_format[ARG_DIAMETER], minimum=3, maximum=4095
|
||||
)
|
||||
# Check that the thickness value fits in the allowed values
|
||||
if graphic_format.get(ARG_THICKNESS) is not None:
|
||||
graphic_format[ARG_THICKNESS] = self._enforce(
|
||||
graphic_format[ARG_THICKNESS], minimum=2, maximum=4095
|
||||
)
|
||||
# Generate the ZPL II command
|
||||
command = "{origin}{data}{stop}".format(
|
||||
origin=self._field_origin(right, down),
|
||||
data="^GC" + self._generate_arguments(arguments, graphic_format),
|
||||
stop=self._field_data_stop(),
|
||||
)
|
||||
self._write_command(command)
|
||||
|
||||
def graphic_field(self, right, down, pil_image):
|
||||
"""Encode a PIL image into an ASCII string suitable for ZPL printers"""
|
||||
width, height = pil_image.size
|
||||
rounded_width = int(math.ceil(width / 8.0) * 8)
|
||||
# Transform the image :
|
||||
# - Invert the colors (PIL uses 0 for black, ZPL uses 0 for white)
|
||||
# - Convert to monochrome in case it is not already
|
||||
# - Round the width to a multiple of 8 because ZPL needs an integer
|
||||
# count of bytes per line (each pixel is a bit)
|
||||
pil_image = (
|
||||
ImageOps.invert(pil_image).convert("1").crop((0, 0, rounded_width, height))
|
||||
)
|
||||
# Convert the image to a two-character hexadecimal values string
|
||||
ascii_data = binascii.hexlify(pil_image.tobytes()).upper()
|
||||
# Each byte is composed of two characters
|
||||
bytes_per_row = rounded_width / 8
|
||||
total_bytes = bytes_per_row * height
|
||||
graphic_image_command = (
|
||||
"^GFA,{total_bytes},{total_bytes},{bytes_per_row},{ascii_data}".format(
|
||||
total_bytes=total_bytes,
|
||||
bytes_per_row=bytes_per_row,
|
||||
ascii_data=ascii_data,
|
||||
)
|
||||
)
|
||||
# Generate the ZPL II command
|
||||
command = "{origin}{data}{stop}".format(
|
||||
origin=self._field_origin(right, down),
|
||||
data=graphic_image_command,
|
||||
stop=self._field_data_stop(),
|
||||
)
|
||||
self._write_command(command)
|
||||
|
|
@ -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"/>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
* Sylvain Garancher <sylvain.garancher@syleam.fr>
|
||||
* Florent de Labarre
|
||||
* Jos De Graeve <Jos.DeGraeve@apertoso.be>
|
||||
* Rod Schouteden <rod.schouteden@dynapps.be>
|
||||
* Miquel Raïch <miquel.raich@forgeflow.com>
|
||||
* Lois Rilo <lois.rilo@forgeflow.com>
|
||||
* Tran Quoc Duong <duontq@trobz.com>
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
Nothing special, just install the module.
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
printing_label_zpl2_user,Printing Label ZPL2 User,model_printing_label_zpl2,base_report_to_printer.printing_group_user,1,0,0,0
|
||||
printing_label_zpl2_manager,Printing Label ZPL2 Manager,model_printing_label_zpl2,base_report_to_printer.printing_group_manager,1,1,1,1
|
||||
printing_label_zpl2_component_user,Printing Label ZPL2 Component User,model_printing_label_zpl2_component,base_report_to_printer.printing_group_user,1,0,0,0
|
||||
printing_label_zpl2_component_manager,Printing Label ZPL2 Component Manager,model_printing_label_zpl2_component,base_report_to_printer.printing_group_manager,1,1,1,1
|
||||
access_wizard_print_record_label_user,Print Record Label user,model_wizard_print_record_label,base_report_to_printer.printing_group_user,1,1,1,1
|
||||
access_wizard_print_record_label_line_user,Print Record Label Line user,model_wizard_print_record_label_line,base_report_to_printer.printing_group_user,1,1,1,1
|
||||
access_wizard_import_zpl2_user,Import ZPL2 user,model_wizard_import_zpl2,base_report_to_printer.printing_group_user,1,1,1,1
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -0,0 +1,493 @@
|
|||
<!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: https://docutils.sourceforge.io/" />
|
||||
<title>Printer ZPL II</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
Despite the name, some widely supported CSS2 features are used.
|
||||
|
||||
See https://docutils.sourceforge.io/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: gray; } /* 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, pre.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. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:959cd2e93d084b3fb9c30ec5d95b216eed7d1d372aa195ec5126d9c06031815e
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external image-reference" 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 image-reference" 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 image-reference" href="https://github.com/OCA/report-print-send/tree/16.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 image-reference" href="https://translation.odoo-community.org/projects/report-print-send-16-0/report-print-send-16-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 image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/report-print-send&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-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="toc-entry-1">Installation</a></li>
|
||||
<li><a class="reference internal" href="#configuration" id="toc-entry-2">Configuration</a></li>
|
||||
<li><a class="reference internal" href="#usage" id="toc-entry-3">Usage</a></li>
|
||||
<li><a class="reference internal" href="#changelog" id="toc-entry-4">Changelog</a><ul>
|
||||
<li><a class="reference internal" href="#section-1" id="toc-entry-5">13.0.1.0.0 (2019-09-30)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-6">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="toc-entry-7">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="toc-entry-8">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="toc-entry-9">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="toc-entry-10">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="installation">
|
||||
<h1><a class="toc-backref" href="#toc-entry-1">Installation</a></h1>
|
||||
<p>Nothing special, just install the module.</p>
|
||||
</div>
|
||||
<div class="section" id="configuration">
|
||||
<h1><a class="toc-backref" href="#toc-entry-2">Configuration</a></h1>
|
||||
<p>To configure this module, you need to:</p>
|
||||
<ol class="arabic simple">
|
||||
<li>Go to <em>Settings > Printing > Labels > 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>It’s 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">
|
||||
<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"/>
|
||||
</pre>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#toc-entry-3">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="#toc-entry-4">Changelog</a></h1>
|
||||
<div class="section" id="section-1">
|
||||
<h2><a class="toc-backref" href="#toc-entry-5">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="#toc-entry-6">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 to smash 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:%2016.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="#toc-entry-7">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-8">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="#toc-entry-9">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Sylvain Garancher <<a class="reference external" href="mailto:sylvain.garancher@syleam.fr">sylvain.garancher@syleam.fr</a>></li>
|
||||
<li>Florent de Labarre</li>
|
||||
<li>Jos De Graeve <<a class="reference external" href="mailto:Jos.DeGraeve@apertoso.be">Jos.DeGraeve@apertoso.be</a>></li>
|
||||
<li>Rod Schouteden <<a class="reference external" href="mailto:rod.schouteden@dynapps.be">rod.schouteden@dynapps.be</a>></li>
|
||||
<li>Miquel Raïch <<a class="reference external" href="mailto:miquel.raich@forgeflow.com">miquel.raich@forgeflow.com</a>></li>
|
||||
<li>Lois Rilo <<a class="reference external" href="mailto:lois.rilo@forgeflow.com">lois.rilo@forgeflow.com</a>></li>
|
||||
<li>Tran Quoc Duong <<a class="reference external" href="mailto:duontq@trobz.com">duontq@trobz.com</a>></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#toc-entry-10">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/16.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>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import test_printing_label_zpl2
|
||||
from . import test_wizard_print_record_label
|
||||
from . import test_generate_action
|
||||
from . import test_test_mode
|
||||
from . import test_wizard_import_zpl2
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Copyright (C) 2018 Florent de Labarre (<https://github.com/fmdl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
model = "odoo.addons.base_report_to_printer.models.printing_server"
|
||||
|
||||
|
||||
class TestWizardPrintRecordLabel(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestWizardPrintRecordLabel, self).setUp()
|
||||
self.Model = self.env["wizard.print.record.label"]
|
||||
self.server = self.env["printing.server"].create({})
|
||||
self.printer = self.env["printing.printer"].create(
|
||||
{
|
||||
"name": "Printer",
|
||||
"server_id": self.server.id,
|
||||
"system_name": "Sys Name",
|
||||
"default": True,
|
||||
"status": "unknown",
|
||||
"status_message": "Msg",
|
||||
"model": "res.users",
|
||||
"location": "Location",
|
||||
"uri": "URI",
|
||||
}
|
||||
)
|
||||
self.label = self.env["printing.label.zpl2"].create(
|
||||
{
|
||||
"name": "ZPL II Label",
|
||||
"model_id": self.env.ref(
|
||||
"base_report_to_printer.model_printing_printer"
|
||||
).id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_create_action(self):
|
||||
"""Check the creation of action"""
|
||||
self.label.create_action()
|
||||
self.assertTrue(self.label.action_window_id)
|
||||
|
||||
def test_unlink_action(self):
|
||||
"""Check the unlink of action"""
|
||||
self.label.unlink_action()
|
||||
self.assertFalse(self.label.action_window_id)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,94 @@
|
|||
# Copyright (C) 2018 Florent de Labarre (<https://github.com/fmdl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
model = "odoo.addons.base_report_to_printer.models.printing_server"
|
||||
|
||||
|
||||
class TestWizardPrintRecordLabel(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestWizardPrintRecordLabel, self).setUp()
|
||||
self.Model = self.env["wizard.print.record.label"]
|
||||
self.server = self.env["printing.server"].create({})
|
||||
self.printer = self.env["printing.printer"].create(
|
||||
{
|
||||
"name": "Printer",
|
||||
"server_id": self.server.id,
|
||||
"system_name": "Sys Name",
|
||||
"default": True,
|
||||
"status": "unknown",
|
||||
"status_message": "Msg",
|
||||
"model": "res.users",
|
||||
"location": "Location",
|
||||
"uri": "URI",
|
||||
}
|
||||
)
|
||||
self.label = self.env["printing.label.zpl2"].create(
|
||||
{
|
||||
"name": "ZPL II Label",
|
||||
"model_id": self.env.ref(
|
||||
"base_report_to_printer.model_printing_printer"
|
||||
).id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_get_record(self):
|
||||
"""Check if return a record"""
|
||||
self.label.record_id = 10
|
||||
res = self.label._get_record()
|
||||
|
||||
Obj = self.env[self.label.model_id.model]
|
||||
record = Obj.search([("id", "=", self.label.record_id)], limit=1)
|
||||
if not record:
|
||||
record = Obj.search([], limit=1, order="id desc")
|
||||
self.assertEqual(res, record)
|
||||
|
||||
@patch("%s.cups" % model)
|
||||
def test_print_label_test(self, cups):
|
||||
"""Check if print test"""
|
||||
self.label.test_print_mode = True
|
||||
self.label.printer_id = self.printer
|
||||
self.label.record_id = 10
|
||||
self.label.print_test_label()
|
||||
cups.Connection().printFile.assert_called_once()
|
||||
|
||||
def test_emulation_without_params(self):
|
||||
"""Check if not execute next if not in this mode"""
|
||||
self.label.test_labelary_mode = False
|
||||
self.assertIs(self.label.labelary_image, False)
|
||||
|
||||
def test_emulation_with_bad_header(self):
|
||||
"""Check if bad header"""
|
||||
self.label.test_labelary_mode = True
|
||||
self.label.labelary_width = 80
|
||||
self.label.labelary_dpmm = "8dpmm"
|
||||
self.label.labelary_height = 10000000
|
||||
self.env["printing.label.zpl2.component"].create(
|
||||
{"name": "ZPL II Label", "label_id": self.label.id, "data": '"Test"'}
|
||||
)
|
||||
self.assertFalse(self.label.labelary_image)
|
||||
|
||||
def test_emulation_with_bad_data_compute(self):
|
||||
"""Check if bad data compute"""
|
||||
self.label.test_labelary_mode = True
|
||||
self.label.labelary_width = 80
|
||||
self.label.labelary_height = 30
|
||||
self.label.labelary_dpmm = "8dpmm"
|
||||
component = self.env["printing.label.zpl2.component"].create(
|
||||
{"name": "ZPL II Label", "label_id": self.label.id, "data": "wrong_data"}
|
||||
)
|
||||
component.unlink()
|
||||
self.assertIs(self.label.labelary_image, False)
|
||||
|
||||
def test_emulation_with_good_data(self):
|
||||
"""Check if ok"""
|
||||
self.label.test_labelary_mode = True
|
||||
self.label.labelary_width = 80
|
||||
self.label.labelary_height = 30
|
||||
self.label.labelary_dpmm = "8dpmm"
|
||||
self.env["printing.label.zpl2.component"].create(
|
||||
{"name": "ZPL II Label", "label_id": self.label.id, "data": '"good_data"'}
|
||||
)
|
||||
self.assertTrue(self.label.labelary_image)
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Copyright (C) 2018 Florent de Labarre (<https://github.com/fmdl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestWizardImportZpl2(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestWizardImportZpl2, self).setUp()
|
||||
self.Model = self.env["wizard.print.record.label"]
|
||||
self.server = self.env["printing.server"].create({})
|
||||
self.printer = self.env["printing.printer"].create(
|
||||
{
|
||||
"name": "Printer",
|
||||
"server_id": self.server.id,
|
||||
"system_name": "Sys Name",
|
||||
"default": True,
|
||||
"status": "unknown",
|
||||
"status_message": "Msg",
|
||||
"model": "res.users",
|
||||
"location": "Location",
|
||||
"uri": "URI",
|
||||
}
|
||||
)
|
||||
self.label = self.env["printing.label.zpl2"].create(
|
||||
{
|
||||
"name": "ZPL II Label",
|
||||
"model_id": self.env.ref(
|
||||
"base_report_to_printer.model_printing_printer"
|
||||
).id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_open_wizard(self):
|
||||
"""open wizard from label"""
|
||||
res = self.label.import_zpl2()
|
||||
self.assertEqual(res.get("context").get("default_label_id"), self.label.id)
|
||||
|
||||
def test_wizard_import_zpl2(self):
|
||||
"""Import ZPL2 from wizard"""
|
||||
zpl_data = (
|
||||
"^XA\n"
|
||||
"^CI28\n"
|
||||
"^LH0,0\n"
|
||||
"^CF0\n"
|
||||
"^CFA,10\n"
|
||||
"^CFB,10,10\n"
|
||||
"^FO10,10^A0N,30,30^FDTEXT^FS\n"
|
||||
"^BY2,3.0^FO600,60^BCN,30,N,N,N"
|
||||
"^FDAJFGJAGJVJVHK^FS\n"
|
||||
"^FO10,40^A0N,20,40^FB150,2,1,J,0^FDTEXT BLOCK^FS\n"
|
||||
"^FO300,10^GC100,3,B^FS\n"
|
||||
"^FO10,200^GB200,200,100,B,0^FS\n"
|
||||
"^FO10,60^GFA,16.0,16.0,2.0,"
|
||||
"b'FFC0FFC0FFC0FFC0FFC0FFC0FFC0FFC0'^FS\n"
|
||||
"^FO10,200^GB300,100,6,W,0^FS\n"
|
||||
"^BY2,3.0^FO300,10^B1N,N,30,N,N^FD678987656789^FS\n"
|
||||
"^BY2,3.0^FO300,70^B2N,30,Y,Y,N^FD567890987768^FS\n"
|
||||
"^BY2,3.0^FO300,120^B3N,N,30,N,N^FD98765456787656^FS\n"
|
||||
"^BY2,3.0^FO300,200^BQN,2,5,Q,7"
|
||||
"^FDMM,A876567897656787658654645678^FS\n"
|
||||
"^BY2,3.0^FO400,250^BER,40,Y,Y^FD9876789987654567^FS\n"
|
||||
"^BY2,3.0^FO350,250^B7N,20,0,0,0,N^FD8765678987656789^FS\n"
|
||||
"^BY2,3.0^FO700,10^B9N,20,N,N,N^FD87657890987654^FS\n"
|
||||
"^BY2,3.0^FO600,200^B4N,50,N^FD7654567898765678^FS\n"
|
||||
"^BY2,3.0^FO600,300^BEN,50,Y,Y^FD987654567890876567^FS\n"
|
||||
"^FO300,300^AGI,50,50^FR^FDINVERTED^FS\n"
|
||||
"^BY2,3.0^FO700,200^B8,50,N,N^FD987609876567^FS\n"
|
||||
"^JUR\n"
|
||||
"^XZ"
|
||||
)
|
||||
|
||||
vals = {"label_id": self.label.id, "delete_component": True, "data": zpl_data}
|
||||
wizard = self.env["wizard.import.zpl2"].create(vals)
|
||||
wizard.import_zpl2()
|
||||
self.assertEqual(18, len(self.label.component_ids))
|
||||
|
||||
def test_wizard_import_zpl2_add(self):
|
||||
"""Import ZPL2 from wizard ADD"""
|
||||
self.env["printing.label.zpl2.component"].create(
|
||||
{
|
||||
"name": "ZPL II Label",
|
||||
"label_id": self.label.id,
|
||||
"data": '"data"',
|
||||
"sequence": 10,
|
||||
}
|
||||
)
|
||||
zpl_data = (
|
||||
"^XA\n" "^CI28\n" "^LH0,0\n" "^FO10,10^A0N,30,30^FDTEXT^FS\n" "^JUR\n" "^XZ"
|
||||
)
|
||||
|
||||
vals = {"label_id": self.label.id, "delete_component": False, "data": zpl_data}
|
||||
wizard = self.env["wizard.import.zpl2"].create(vals)
|
||||
wizard.import_zpl2()
|
||||
self.assertEqual(2, len(self.label.component_ids))
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
# Copyright 2016 LasLabs Inc.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
model = "odoo.addons.base_report_to_printer.models.printing_server"
|
||||
|
||||
|
||||
class TestWizardPrintRecordLabel(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestWizardPrintRecordLabel, self).setUp()
|
||||
self.Model = self.env["wizard.print.record.label"]
|
||||
self.server = self.env["printing.server"].create({})
|
||||
self.printer = self.env["printing.printer"].create(
|
||||
{
|
||||
"name": "Printer",
|
||||
"server_id": self.server.id,
|
||||
"system_name": "Sys Name",
|
||||
"default": True,
|
||||
"status": "unknown",
|
||||
"status_message": "Msg",
|
||||
"model": "res.users",
|
||||
"location": "Location",
|
||||
"uri": "URI",
|
||||
}
|
||||
)
|
||||
self.label = self.env["printing.label.zpl2"].create(
|
||||
{
|
||||
"name": "ZPL II Label",
|
||||
"model_id": self.env.ref(
|
||||
"base_report_to_printer.model_printing_printer"
|
||||
).id,
|
||||
}
|
||||
)
|
||||
|
||||
@patch("%s.cups" % model)
|
||||
def test_print_record_label(self, cups):
|
||||
"""Check that printing a label using the generic wizard works"""
|
||||
wizard_obj = self.Model.with_context(
|
||||
active_model="printing.printer",
|
||||
active_id=self.printer.id,
|
||||
active_ids=[self.printer.id],
|
||||
printer_zpl2_id=self.printer.id,
|
||||
)
|
||||
wizard = wizard_obj.create({})
|
||||
self.assertEqual(wizard.printer_id, self.printer)
|
||||
self.assertEqual(wizard.label_id, self.label)
|
||||
wizard.print_label()
|
||||
cups.Connection().printFile.assert_called_once()
|
||||
|
||||
def test_wizard_multiple_printers_and_labels(self):
|
||||
"""Check that printer_id and label_id are not automatically filled
|
||||
when there are multiple possible values
|
||||
"""
|
||||
self.env["printing.printer"].create(
|
||||
{
|
||||
"name": "Other_Printer",
|
||||
"server_id": self.server.id,
|
||||
"system_name": "Sys Name",
|
||||
"default": True,
|
||||
"status": "unknown",
|
||||
"status_message": "Msg",
|
||||
"model": "res.users",
|
||||
"location": "Location",
|
||||
"uri": "URI",
|
||||
}
|
||||
)
|
||||
self.env["printing.label.zpl2"].create(
|
||||
{
|
||||
"name": "Other ZPL II Label",
|
||||
"model_id": self.env.ref(
|
||||
"base_report_to_printer.model_printing_printer"
|
||||
).id,
|
||||
}
|
||||
)
|
||||
wizard_obj = self.Model.with_context(
|
||||
active_model="printing.printer",
|
||||
active_id=self.printer.id,
|
||||
active_ids=[self.printer.id],
|
||||
)
|
||||
values = wizard_obj.default_get(["printer_id", "label_id"])
|
||||
self.assertEqual(values.get("printer_id", False), False)
|
||||
self.assertEqual(values.get("label_id", False), False)
|
||||
|
||||
def test_wizard_multiple_labels_but_on_different_models(self):
|
||||
"""Check that label_id is automatically filled when there are multiple
|
||||
labels, but only one on the right model
|
||||
"""
|
||||
self.env["printing.label.zpl2"].create(
|
||||
{
|
||||
"name": "Other ZPL II Label",
|
||||
"model_id": self.env.ref("base.model_res_users").id,
|
||||
}
|
||||
)
|
||||
wizard_obj = self.Model.with_context(
|
||||
active_model="printing.printer",
|
||||
active_id=self.printer.id,
|
||||
active_ids=[self.printer.id],
|
||||
printer_zpl2_id=self.printer.id,
|
||||
)
|
||||
wizard = wizard_obj.create({})
|
||||
self.assertEqual(wizard.label_id, self.label)
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2016 SUBTENO-IT
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<odoo>
|
||||
<record id="view_printing_label_zpl2_tree" model="ir.ui.view">
|
||||
<field name="model">printing.label.zpl2</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name" />
|
||||
<field name="model_id" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_printing_label_zpl2_form" model="ir.ui.view">
|
||||
<field name="model">printing.label.zpl2</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="ZPL II Label">
|
||||
<header>
|
||||
<button name="import_zpl2" string="Import ZPL2" type="object" />
|
||||
</header>
|
||||
<sheet>
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Archived"
|
||||
bg_color="bg-danger"
|
||||
attrs="{'invisible': [('active', '=', True)]}"
|
||||
/>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<field name="active" invisible="1" />
|
||||
<field name="action_window_id" invisible="1" />
|
||||
<button
|
||||
name="create_action"
|
||||
string="Add in the 'Action' menu"
|
||||
type="object"
|
||||
attrs="{'invisible':[('action_window_id','!=',False)]}"
|
||||
icon="fa-plus-square"
|
||||
help="Display an option on related documents."
|
||||
class="oe_stat_button"
|
||||
/>
|
||||
<button
|
||||
name="unlink_action"
|
||||
string="Remove from the 'Action' menu"
|
||||
type="object"
|
||||
attrs="{'invisible':[('action_window_id','=',False)]}"
|
||||
icon="fa-minus-square"
|
||||
help="Remove the contextual action."
|
||||
class="oe_stat_button"
|
||||
/>
|
||||
</div>
|
||||
<group col="4">
|
||||
<field name="name" />
|
||||
<field name="model_id" />
|
||||
<field name="description" />
|
||||
<field name="width" />
|
||||
<field name="origin_x" />
|
||||
<field name="origin_y" />
|
||||
<field name="restore_saved_config" />
|
||||
</group>
|
||||
<group attrs="{'invisible':[('test_print_mode', '=', False)]}">
|
||||
<button
|
||||
name="print_test_label"
|
||||
string="Print Test"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Components">
|
||||
<field
|
||||
name="component_ids"
|
||||
nolabel="1"
|
||||
colspan="4"
|
||||
context="{'default_model_id': model_id}"
|
||||
>
|
||||
<tree>
|
||||
<field name="sequence" />
|
||||
<field name="name" />
|
||||
<field name="component_type" />
|
||||
<field name="origin_x" />
|
||||
<button
|
||||
name="action_minus_origin_x"
|
||||
type="object"
|
||||
string="-"
|
||||
icon="fa-minus-square"
|
||||
/>
|
||||
<button
|
||||
name="action_plus_origin_x"
|
||||
type="object"
|
||||
string="+"
|
||||
icon="fa-plus-square"
|
||||
/>
|
||||
<field name="origin_y" />
|
||||
<button
|
||||
name="action_minus_origin_y"
|
||||
type="object"
|
||||
string="-"
|
||||
icon="fa-minus-square"
|
||||
/>
|
||||
<button
|
||||
name="action_plus_origin_y"
|
||||
type="object"
|
||||
string="+"
|
||||
icon="fa-plus-square"
|
||||
/>
|
||||
</tree>
|
||||
<form string="Label Component">
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="sequence" />
|
||||
<field name="model_id" invisible="1" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="component_type" />
|
||||
<field
|
||||
name="repeat"
|
||||
attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}"
|
||||
/>
|
||||
<field
|
||||
name="data_autofill"
|
||||
attrs="{'invisible': [('component_type', '!=', 'qr_code')]}"
|
||||
/>
|
||||
</group>
|
||||
<group
|
||||
attrs="{'invisible': [('component_type', '=', 'zpl2_raw')]}"
|
||||
>
|
||||
<field name="origin_x" />
|
||||
<field name="origin_y" />
|
||||
</group>
|
||||
<group>
|
||||
<field
|
||||
name="graphic_image"
|
||||
attrs="{'invisible': [('component_type', '!=', 'graphic')]}"
|
||||
/>
|
||||
<field
|
||||
name="sublabel_id"
|
||||
attrs="{'invisible': [('component_type', '!=', 'sublabel')]}"
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
<group
|
||||
attrs="{'invisible': ['|', ('data_autofill', '=', True), ('component_type', 'in', ('rectangle', 'diagonal', 'circle'))]}"
|
||||
string="Data"
|
||||
>
|
||||
<field
|
||||
name="data"
|
||||
widget="ace"
|
||||
options="{'mode': 'python'}"
|
||||
nolabel="1"
|
||||
colspan="2"
|
||||
/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page
|
||||
string="Format"
|
||||
attrs="{'invisible': [('component_type', 'in', ('sublabel', 'qr_code', 'zpl2_raw'))]}"
|
||||
>
|
||||
<group>
|
||||
<field name="height" />
|
||||
<field name="only_product_barcode" />
|
||||
<field
|
||||
name="width"
|
||||
attrs="{'invisible': [('component_type', 'not in', ('text', 'rectangle', 'diagonal', 'circle', 'graphic'))]}"
|
||||
/>
|
||||
<field name="reverse_print" />
|
||||
<field
|
||||
name="orientation"
|
||||
attrs="{'invisible': [('component_type', 'in', ('rectangle', 'diagonal', 'circle'))]}"
|
||||
/>
|
||||
<field
|
||||
name="font"
|
||||
attrs="{'invisible': [('component_type', '!=', 'text')]}"
|
||||
/>
|
||||
<field
|
||||
name="in_block"
|
||||
attrs="{'invisible': [('component_type', '!=', 'text')]}"
|
||||
/>
|
||||
<field
|
||||
name="thickness"
|
||||
attrs="{'invisible': [('component_type', 'not in', ('rectangle', 'diagonal', 'circle'))]}"
|
||||
/>
|
||||
<field
|
||||
name="color"
|
||||
attrs="{'invisible': [('component_type', 'not in', ('rectangle', 'diagonal', 'circle'))]}"
|
||||
/>
|
||||
<field
|
||||
name="diagonal_orientation"
|
||||
attrs="{'invisible': [('component_type', '!=', 'diagonal')], 'required': [('component_type', '=', 'diagonal')]}"
|
||||
/>
|
||||
</group>
|
||||
</page>
|
||||
<!-- Barcode specific arguments -->
|
||||
<page
|
||||
string="Barcode Format"
|
||||
attrs="{'invisible': [('component_type', 'in', ('text', 'rectangle', 'diagonal', 'circle', 'sublabel', 'graphic', 'qr_code', 'zpl2_raw'))]}"
|
||||
>
|
||||
<group>
|
||||
<field name="check_digits" />
|
||||
<field name="interpretation_line" />
|
||||
<field
|
||||
name="interpretation_line_above"
|
||||
/>
|
||||
<field name="module_width" />
|
||||
<field name="bar_width_ratio" />
|
||||
<field name="security_level" />
|
||||
<field name="columns_count" />
|
||||
<field name="rows_count" />
|
||||
<field name="truncate" />
|
||||
</group>
|
||||
</page>
|
||||
<!-- 2D Barcode arguments -->
|
||||
<page
|
||||
string="2D Barcode Arguments"
|
||||
attrs="{'invisible': [('component_type', '!=', 'qr_code')]}"
|
||||
>
|
||||
<group>
|
||||
<field name="model" />
|
||||
<field name="magnification_factor" />
|
||||
<field name="error_correction" />
|
||||
<field name="mask_value" />
|
||||
</group>
|
||||
</page>
|
||||
<!-- Text block specific arguments -->
|
||||
<page
|
||||
string="Text Block Format"
|
||||
attrs="{'invisible': ['|', ('component_type', '!=', 'text'), ('in_block', '=', False)]}"
|
||||
>
|
||||
<group>
|
||||
<field name="block_width" />
|
||||
<field name="block_lines" />
|
||||
<field name="block_spaces" />
|
||||
<field name="block_justify" />
|
||||
<field name="block_left_margin" />
|
||||
</group>
|
||||
</page>
|
||||
<!-- Repeat specific arguments -->
|
||||
<page
|
||||
string="Repeat"
|
||||
attrs="{'invisible': [('repeat', '=', False)]}"
|
||||
>
|
||||
<group>
|
||||
<field name="repeat_offset" />
|
||||
<field name="repeat_count" />
|
||||
<field name="repeat_offset_x" />
|
||||
<field name="repeat_offset_y" />
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
<group
|
||||
string="Emulation"
|
||||
attrs="{'invisible':[('test_labelary_mode', '=', False)]}"
|
||||
>
|
||||
<field
|
||||
name="labelary_image"
|
||||
widget="image"
|
||||
nolabel="1"
|
||||
force_save="1"
|
||||
/>
|
||||
<p class="oe_grey" colspan="4">
|
||||
Note : It is an emulation from http://labelary.com/, the result on printer can be different.
|
||||
</p>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Test Mode">
|
||||
<group>
|
||||
<group>
|
||||
<field name="test_print_mode" />
|
||||
<field name="test_labelary_mode" />
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field
|
||||
name="record_id"
|
||||
attrs="{'invisible':[('test_print_mode', '=', False), ('test_labelary_mode', '=', False)], 'required':['|', ('test_print_mode', '=', True), ('test_labelary_mode', '=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="printer_id"
|
||||
attrs="{'invisible':[('test_print_mode', '=', False)], 'required':[('test_print_mode', '=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="labelary_dpmm"
|
||||
attrs="{'invisible':[('test_labelary_mode', '=', False)], 'required':[('test_labelary_mode', '=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="labelary_width"
|
||||
attrs="{'invisible':[('test_labelary_mode', '=', False)], 'required':[('test_labelary_mode', '=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="labelary_height"
|
||||
attrs="{'invisible':[('test_labelary_mode', '=', False)], 'required':[('test_labelary_mode', '=', True)]}"
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Extra">
|
||||
<field
|
||||
name="extra"
|
||||
nolabel="1"
|
||||
widget="ace"
|
||||
options="{'mode': 'python'}"
|
||||
attrs="{'invisible':[('test_print_mode', '=', False), ('test_labelary_mode', '=', False)]}"
|
||||
colspan="2"
|
||||
/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_printing_label_zpl2_search" model="ir.ui.view">
|
||||
<field name="model">printing.label.zpl2</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="ZPL II Label">
|
||||
<field name="name" />
|
||||
<field name="model_id" />
|
||||
<filter
|
||||
string="Archived"
|
||||
name="inactive"
|
||||
domain="[('active','=',False)]"
|
||||
/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="act_open_printing_label_zpl2_view">
|
||||
<field name="name">ZPL II Labels</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">printing.label.zpl2</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_printing_label_zpl2_search" />
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
<record
|
||||
model="ir.actions.act_window.view"
|
||||
id="act_open_printing_label_zpl2_view_form"
|
||||
>
|
||||
<field name="act_window_id" ref="act_open_printing_label_zpl2_view" />
|
||||
<field name="sequence" eval="20" />
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_printing_label_zpl2_form" />
|
||||
</record>
|
||||
<record
|
||||
model="ir.actions.act_window.view"
|
||||
id="act_open_printing_label_zpl2_view_tree"
|
||||
>
|
||||
<field name="act_window_id" ref="act_open_printing_label_zpl2_view" />
|
||||
<field name="sequence" eval="10" />
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="view_printing_label_zpl2_tree" />
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_printing_label_zpl2"
|
||||
parent="base_report_to_printer.printing_menu"
|
||||
sequence="20"
|
||||
action="act_open_printing_label_zpl2_view"
|
||||
/>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import print_record_label
|
||||
from . import wizard_import_zpl2
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# Copyright (C) 2016 SYLEAM (<http://www.syleam.fr>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class PrintRecordLabel(models.TransientModel):
|
||||
_name = "wizard.print.record.label"
|
||||
_description = "Print Record Label"
|
||||
|
||||
printer_id = fields.Many2one(
|
||||
comodel_name="printing.printer",
|
||||
string="Printer",
|
||||
required=True,
|
||||
help="Printer used to print the labels.",
|
||||
)
|
||||
label_id = fields.Many2one(
|
||||
comodel_name="printing.label.zpl2",
|
||||
string="Label",
|
||||
required=True,
|
||||
domain=lambda self: [
|
||||
("model_id.model", "=", self.env.context.get("active_model"))
|
||||
],
|
||||
help="Label to print.",
|
||||
)
|
||||
active_model_id = fields.Many2one(
|
||||
comodel_name="ir.model",
|
||||
string="Model",
|
||||
domain=lambda self: [("model", "=", self.env.context.get("active_model"))],
|
||||
)
|
||||
model = fields.Char(related="active_model_id.model", string="Model Name")
|
||||
line_ids = fields.One2many("wizard.print.record.label.line", "label_header_id")
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
values = super(PrintRecordLabel, self).default_get(fields_list)
|
||||
|
||||
# Automatically select the printer and label, if only one is available
|
||||
printers = self.env["printing.printer"].search(
|
||||
[("id", "=", self.env.context.get("printer_zpl2_id"))]
|
||||
)
|
||||
if not printers:
|
||||
printers = self.env["printing.printer"].search([])
|
||||
if len(printers) == 1:
|
||||
values["printer_id"] = printers.id
|
||||
|
||||
labels = self.env["printing.label.zpl2"].search(
|
||||
[("model_id.model", "=", self.env.context.get("active_model"))]
|
||||
)
|
||||
if len(labels) == 1:
|
||||
values["label_id"] = labels.id
|
||||
|
||||
return values
|
||||
|
||||
def print_label(self):
|
||||
"""Prints a label per selected record"""
|
||||
record_model = self.env.context["active_model"]
|
||||
for record_id in self.env.context["active_ids"]:
|
||||
record = self.env[record_model].browse(record_id)
|
||||
self.label_id.print_label(self.printer_id, record)
|
||||
|
||||
|
||||
class PrintRecordLabelLines(models.TransientModel):
|
||||
_name = "wizard.print.record.label.line"
|
||||
_description = "Print Record Label Line"
|
||||
|
||||
label_no = fields.Integer(string="# labels")
|
||||
label_header_id = fields.Many2one(comodel_name="wizard.print.record.label")
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2016 SYLEAM
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
-->
|
||||
<odoo>
|
||||
<record id="view_wizard_print_product_label_form" model="ir.ui.view">
|
||||
<field name="name">wizard.print.record.label.form</field>
|
||||
<field name="model">wizard.print.record.label</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Print Label">
|
||||
<group>
|
||||
<field name="printer_id" />
|
||||
<field
|
||||
name="label_id"
|
||||
context="{'default_model_id': active_model_id}"
|
||||
/>
|
||||
<field name="active_model_id" invisible="1" />
|
||||
</group>
|
||||
<field name="model" invisible="1" />
|
||||
<footer>
|
||||
<button type="special" special="cancel" string="Cancel" />
|
||||
<button
|
||||
string="Print label"
|
||||
type="object"
|
||||
name="print_label"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="action_wizard_print_record_label_view">
|
||||
<field name="name">Print Label</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">wizard.print.record.label</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,427 @@
|
|||
# Copyright (C) 2018 Florent de Labarre (<https://github.com/fmdl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
from odoo import _, fields, models
|
||||
|
||||
from ..models import zpl2
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _compute_arg(data, arg):
|
||||
vals = {}
|
||||
for i, d in enumerate(data.split(",")):
|
||||
vals[arg[i]] = d
|
||||
return vals
|
||||
|
||||
|
||||
def _field_origin(data):
|
||||
if data[:2] == "FO":
|
||||
position = data[2:]
|
||||
vals = _compute_arg(position, ["origin_x", "origin_y"])
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _font_format(data):
|
||||
if data[:1] == "A":
|
||||
data = data.split(",")
|
||||
vals = {}
|
||||
if len(data[0]) > 1:
|
||||
vals[zpl2.ARG_FONT] = data[0][1]
|
||||
if len(data[0]) > 2:
|
||||
vals[zpl2.ARG_ORIENTATION] = data[0][2]
|
||||
|
||||
if len(data) > 1:
|
||||
vals[zpl2.ARG_HEIGHT] = data[1]
|
||||
if len(data) > 2:
|
||||
vals[zpl2.ARG_WIDTH] = data[2]
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _default_font_format(data):
|
||||
if data[:2] == "CF":
|
||||
args = [zpl2.ARG_FONT, zpl2.ARG_HEIGHT, zpl2.ARG_WIDTH]
|
||||
vals = _compute_arg(data[2:], args)
|
||||
if vals.get(zpl2.ARG_HEIGHT, False) and not vals.get(zpl2.ARG_WIDTH, False):
|
||||
vals.update({zpl2.ARG_WIDTH: vals.get(zpl2.ARG_HEIGHT)})
|
||||
else:
|
||||
vals.update({zpl2.ARG_HEIGHT: 10})
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _field_block(data):
|
||||
if data[:2] == "FB":
|
||||
vals = {zpl2.ARG_IN_BLOCK: True}
|
||||
args = [
|
||||
zpl2.ARG_BLOCK_WIDTH,
|
||||
zpl2.ARG_BLOCK_LINES,
|
||||
zpl2.ARG_BLOCK_SPACES,
|
||||
zpl2.ARG_BLOCK_JUSTIFY,
|
||||
zpl2.ARG_BLOCK_LEFT_MARGIN,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _code11(data):
|
||||
if data[:2] == "B1":
|
||||
vals = {"component_type": zpl2.BARCODE_CODE_11}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_CHECK_DIGITS,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _interleaved2of5(data):
|
||||
if data[:2] == "B2":
|
||||
vals = {"component_type": zpl2.BARCODE_INTERLEAVED_2_OF_5}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
zpl2.ARG_CHECK_DIGITS,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _code39(data):
|
||||
if data[:2] == "B3":
|
||||
vals = {"component_type": zpl2.BARCODE_CODE_39}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_CHECK_DIGITS,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _code49(data):
|
||||
if data[:2] == "B4":
|
||||
vals = {"component_type": zpl2.BARCODE_CODE_49}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_STARTING_MODE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _pdf417(data):
|
||||
if data[:2] == "B7":
|
||||
vals = {"component_type": zpl2.BARCODE_PDF417}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_SECURITY_LEVEL,
|
||||
zpl2.ARG_COLUMNS_COUNT,
|
||||
zpl2.ARG_ROWS_COUNT,
|
||||
zpl2.ARG_TRUNCATE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _ean8(data):
|
||||
if data[:2] == "B8":
|
||||
vals = {"component_type": zpl2.BARCODE_EAN_8}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _upce(data):
|
||||
if data[:2] == "B9":
|
||||
vals = {"component_type": zpl2.BARCODE_UPC_E}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
zpl2.ARG_CHECK_DIGITS,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _code128(data):
|
||||
if data[:2] == "BC":
|
||||
vals = {"component_type": zpl2.BARCODE_CODE_128}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
zpl2.ARG_CHECK_DIGITS,
|
||||
zpl2.ARG_MODE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _ean13(data):
|
||||
if data[:2] == "BE":
|
||||
vals = {"component_type": zpl2.BARCODE_EAN_13}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_INTERPRETATION_LINE,
|
||||
zpl2.ARG_INTERPRETATION_LINE_ABOVE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _qrcode(data):
|
||||
if data[:2] == "BQ":
|
||||
vals = {"component_type": zpl2.BARCODE_QR_CODE}
|
||||
args = [
|
||||
zpl2.ARG_ORIENTATION,
|
||||
zpl2.ARG_MODEL,
|
||||
zpl2.ARG_MAGNIFICATION_FACTOR,
|
||||
zpl2.ARG_ERROR_CORRECTION,
|
||||
zpl2.ARG_MASK_VALUE,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _default_barcode_field(data):
|
||||
if data[:2] == "BY":
|
||||
args = [zpl2.ARG_MODULE_WIDTH, zpl2.ARG_BAR_WIDTH_RATIO, zpl2.ARG_HEIGHT]
|
||||
return _compute_arg(data[2:], args)
|
||||
return {}
|
||||
|
||||
|
||||
def _field_reverse_print(data):
|
||||
if data[:2] == "FR":
|
||||
return {zpl2.ARG_REVERSE_PRINT: True}
|
||||
return {}
|
||||
|
||||
|
||||
def _graphic_box(data):
|
||||
if data[:2] == "GB":
|
||||
vals = {"component_type": "rectangle"}
|
||||
args = [
|
||||
zpl2.ARG_WIDTH,
|
||||
zpl2.ARG_HEIGHT,
|
||||
zpl2.ARG_THICKNESS,
|
||||
zpl2.ARG_COLOR,
|
||||
zpl2.ARG_ROUNDING,
|
||||
]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _graphic_circle(data):
|
||||
if data[:2] == "GC":
|
||||
vals = {"component_type": "circle"}
|
||||
args = [zpl2.ARG_WIDTH, zpl2.ARG_THICKNESS, zpl2.ARG_COLOR]
|
||||
vals.update(_compute_arg(data[2:], args))
|
||||
return vals
|
||||
return {}
|
||||
|
||||
|
||||
def _graphic_field(data):
|
||||
if data[:3] == "GFA":
|
||||
vals = {}
|
||||
args = [
|
||||
"compression",
|
||||
"total_bytes",
|
||||
"total_bytes",
|
||||
"bytes_per_row",
|
||||
"ascii_data",
|
||||
]
|
||||
vals.update(_compute_arg(data[3:], args))
|
||||
|
||||
# Image
|
||||
rawData = re.sub("[^A-F0-9]+", "", vals["ascii_data"])
|
||||
rawData = binascii.unhexlify(rawData)
|
||||
|
||||
width = int(float(vals["bytes_per_row"]) * 8)
|
||||
height = int(float(vals["total_bytes"]) / width) * 8
|
||||
|
||||
img = Image.frombytes("1", (width, height), rawData, "raw").convert("L")
|
||||
img = ImageOps.invert(img)
|
||||
|
||||
imgByteArr = io.BytesIO()
|
||||
img.save(imgByteArr, format="PNG")
|
||||
image = base64.b64encode(imgByteArr.getvalue())
|
||||
|
||||
return {
|
||||
"component_type": "graphic",
|
||||
"graphic_image": image,
|
||||
zpl2.ARG_WIDTH: width,
|
||||
zpl2.ARG_HEIGHT: height,
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
def _get_data(data):
|
||||
if data[:2] == "FD":
|
||||
return {"data": '"%s"' % data[2:]}
|
||||
return {}
|
||||
|
||||
|
||||
SUPPORTED_CODE = {
|
||||
"FO": {"method": _field_origin},
|
||||
"FD": {"method": _get_data},
|
||||
"A": {"method": _font_format},
|
||||
"FB": {"method": _field_block},
|
||||
"B1": {"method": _code11},
|
||||
"B2": {"method": _interleaved2of5},
|
||||
"B3": {"method": _code39},
|
||||
"B4": {"method": _code49},
|
||||
"B7": {"method": _pdf417},
|
||||
"B8": {"method": _ean8},
|
||||
"B9": {"method": _upce},
|
||||
"BC": {"method": _code128},
|
||||
"BE": {"method": _ean13},
|
||||
"BQ": {"method": _qrcode},
|
||||
"BY": {
|
||||
"method": _default_barcode_field,
|
||||
"default": [
|
||||
zpl2.BARCODE_CODE_11,
|
||||
zpl2.BARCODE_INTERLEAVED_2_OF_5,
|
||||
zpl2.BARCODE_CODE_39,
|
||||
zpl2.BARCODE_CODE_49,
|
||||
zpl2.BARCODE_PDF417,
|
||||
zpl2.BARCODE_EAN_8,
|
||||
zpl2.BARCODE_UPC_E,
|
||||
zpl2.BARCODE_CODE_128,
|
||||
zpl2.BARCODE_EAN_13,
|
||||
zpl2.BARCODE_QR_CODE,
|
||||
],
|
||||
},
|
||||
"CF": {"method": _default_font_format, "default": ["text"]},
|
||||
"FR": {"method": _field_reverse_print},
|
||||
"GB": {"method": _graphic_box},
|
||||
"GC": {"method": _graphic_circle},
|
||||
"GFA": {"method": _graphic_field},
|
||||
}
|
||||
|
||||
|
||||
class WizardImportZPl2(models.TransientModel):
|
||||
_name = "wizard.import.zpl2"
|
||||
_description = "Import ZPL2"
|
||||
|
||||
label_id = fields.Many2one(
|
||||
comodel_name="printing.label.zpl2", string="Label", required=True, readonly=True
|
||||
)
|
||||
data = fields.Text(required=True, help="Printer used to print the labels.")
|
||||
delete_component = fields.Boolean(
|
||||
string="Delete existing components", default=False
|
||||
)
|
||||
|
||||
def _start_sequence(self):
|
||||
sequences = self.mapped("label_id.component_ids.sequence")
|
||||
if sequences:
|
||||
return max(sequences) + 1
|
||||
return 0
|
||||
|
||||
def import_zpl2(self):
|
||||
self.ensure_one()
|
||||
Zpl2Component = self.env["printing.label.zpl2.component"]
|
||||
|
||||
if self.delete_component:
|
||||
self.mapped("label_id.component_ids").unlink()
|
||||
|
||||
sequence = self._start_sequence()
|
||||
default = {}
|
||||
|
||||
for i, line in enumerate(self.data.split("\n")):
|
||||
vals = {}
|
||||
|
||||
args = line.split("^")
|
||||
for arg in args:
|
||||
for _key, code in SUPPORTED_CODE.items():
|
||||
component_arg = code["method"](arg)
|
||||
if component_arg:
|
||||
if code.get("default", False):
|
||||
for deft in code.get("default"):
|
||||
default.update({deft: component_arg})
|
||||
else:
|
||||
vals.update(component_arg)
|
||||
break
|
||||
|
||||
if vals:
|
||||
if "component_type" not in vals.keys():
|
||||
vals.update({"component_type": "text"})
|
||||
|
||||
if vals["component_type"] in default.keys():
|
||||
vals.update(default[vals["component_type"]])
|
||||
|
||||
vals = self._update_vals(vals)
|
||||
|
||||
seq = sequence + i * 10
|
||||
vals.update(
|
||||
{
|
||||
"name": _("Import %s") % seq,
|
||||
"sequence": seq,
|
||||
"model": str(zpl2.MODEL_ENHANCED),
|
||||
"label_id": self.label_id.id,
|
||||
}
|
||||
)
|
||||
Zpl2Component.create(vals)
|
||||
|
||||
def _update_vals(self, vals):
|
||||
if "orientation" in vals.keys() and vals["orientation"] == "":
|
||||
vals["orientation"] = "N"
|
||||
|
||||
# Field
|
||||
Zpl2Component = self.env["printing.label.zpl2.component"]
|
||||
model_fields = Zpl2Component.fields_get()
|
||||
component = {}
|
||||
for field, value in vals.items():
|
||||
if field in model_fields.keys():
|
||||
field_type = model_fields[field].get("type", False)
|
||||
if field_type == "boolean":
|
||||
if not value or value == zpl2.BOOL_NO:
|
||||
value = False
|
||||
else:
|
||||
value = True
|
||||
if field_type in ("integer", "float"):
|
||||
value = float(value)
|
||||
if field == "model":
|
||||
value = int(float(value))
|
||||
component.update({field: value})
|
||||
return component
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_wizard_import_zpl2_form" model="ir.ui.view">
|
||||
<field name="name">wizard.import.zpl2.form</field>
|
||||
<field name="model">wizard.import.zpl2</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Print Label">
|
||||
<group>
|
||||
<group>
|
||||
<field name="label_id" />
|
||||
<field name="delete_component" />
|
||||
</group>
|
||||
</group>
|
||||
<group string="ZPL2">
|
||||
<field name="data" widget="ace" nolabel="1" colspan="2" />
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Cancel" class="btn-default" special="cancel" />
|
||||
<button
|
||||
string="Import"
|
||||
type="object"
|
||||
name="import_zpl2"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-report-print-send-printer_zpl2"
|
||||
version = "16.0.0"
|
||||
description = "Printer ZPL II - Add a ZPL II label printing feature"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-report-print-send-base_report_to_printer>=16.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["printer_zpl2"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue