product_print_zpl_barcode: Add support for barcode generation
Add support for EAN-8 (in additional to EAN-13)
This commit is contained in:
@@ -1,3 +1,2 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from . import models
|
||||||
|
|
||||||
from . import wizard
|
from . import wizard
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ This module has been written by Alexis de Lattre from Akretion
|
|||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'wizard/product_print_zpl_barcode_view.xml',
|
'wizard/product_print_zpl_barcode_view.xml',
|
||||||
'views/product.xml',
|
'views/product.xml',
|
||||||
|
'data/barcode_sequence.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
}
|
}
|
||||||
|
|||||||
26
product_print_zpl_barcode/data/barcode_sequence.xml
Normal file
26
product_print_zpl_barcode/data/barcode_sequence.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GS1 prefixes : https://www.gs1.org/standards/id-keys/company-prefix
|
||||||
|
200 - 299 : Used to issue GS1 Restricted Circulation Numbers
|
||||||
|
within a geographic region (MO defined)
|
||||||
|
21, 22 and 23 are already used by Odoo for Weight, Price & Discount barcodes
|
||||||
|
So I use 298 by default
|
||||||
|
Another option would to be use a small country at the other side of the world,
|
||||||
|
for example 623 : Brunei
|
||||||
|
-->
|
||||||
|
<record id="private_product_barcode_seq" model="ir.sequence">
|
||||||
|
<field name="name">Private Product Barcode</field>
|
||||||
|
<field name="code">private.product.barcode</field>
|
||||||
|
<field name="prefix">298</field>
|
||||||
|
<field name="padding">9</field>
|
||||||
|
<field name="number_next">1</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
1
product_print_zpl_barcode/models/__init__.py
Normal file
1
product_print_zpl_barcode/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import product
|
||||||
67
product_print_zpl_barcode/models/product.py
Normal file
67
product_print_zpl_barcode/models/product.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Akretion France (http://www.akretion.com/)
|
||||||
|
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from stdnum.ean import calc_check_digit
|
||||||
|
|
||||||
|
|
||||||
|
class ProductTemplate(models.Model):
|
||||||
|
_inherit = "product.template"
|
||||||
|
|
||||||
|
must_print_barcode = fields.Boolean(
|
||||||
|
string="Must Print Barcode",
|
||||||
|
help="Enable that option for products for which you must "
|
||||||
|
"print a barcode upon reception in stock.")
|
||||||
|
|
||||||
|
def generate_barcode_from_product_template(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.product_variant_count != 1:
|
||||||
|
raise UserError(_(
|
||||||
|
"You cannot call the method "
|
||||||
|
"generate_barcode_from_product_template on product '%s' "
|
||||||
|
"because it has %d variants and not just one.")
|
||||||
|
% (self.display_name, self.product_variant_count))
|
||||||
|
return self.product_variant_ids[0].generate_barcode_from_product_product()
|
||||||
|
|
||||||
|
def print_zpl_barcode_from_product_template(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.product_variant_count != 1:
|
||||||
|
raise UserError(_(
|
||||||
|
"You cannot call the method "
|
||||||
|
"print_zpl_barcode_from_product_template on product '%s' "
|
||||||
|
"because it has %d variants and not just one.")
|
||||||
|
% (self.display_name, self.product_variant_count))
|
||||||
|
action = self.env.ref(
|
||||||
|
'product_print_zpl_barcode.product_print_zpl_barcode_action').read()[0]
|
||||||
|
action['context'] = {
|
||||||
|
'active_id': self.product_variant_ids[0].id,
|
||||||
|
'active_model': 'product.product',
|
||||||
|
}
|
||||||
|
return action
|
||||||
|
|
||||||
|
|
||||||
|
class ProductProduct(models.Model):
|
||||||
|
_inherit = 'product.product'
|
||||||
|
|
||||||
|
def generate_barcode_from_product_product(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.barcode:
|
||||||
|
raise UserError(_(
|
||||||
|
"The product '%s' already has a barcode.") % self.display_name)
|
||||||
|
barcode_without_checksum = self.env['ir.sequence'].next_by_code(
|
||||||
|
'private.product.barcode')
|
||||||
|
if len(barcode_without_checksum) not in (7, 12):
|
||||||
|
raise UserError(_(
|
||||||
|
"The sequence 'private.product.barcode' is not properly "
|
||||||
|
"configured. The generated sequence should have 7 digits "
|
||||||
|
"(for EAN-8) or 12 digits (for EAN-13). "
|
||||||
|
"It currently has %d digits." % len(barcode_without_checksum)))
|
||||||
|
checksum = calc_check_digit(barcode_without_checksum)
|
||||||
|
barcode = barcode_without_checksum + str(checksum)
|
||||||
|
self.write({
|
||||||
|
'barcode': barcode,
|
||||||
|
'must_print_barcode': True,
|
||||||
|
})
|
||||||
@@ -1,20 +1,45 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
© 2016 Akretion (http://www.akretion.com/)
|
Copyright 2020-2021 Akretion France (http://www.akretion.com/)
|
||||||
@author Alexis de Lattre <alexis.delattre@akretion.com>
|
@author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
|
<record id="product_template_form_view" model="ir.ui.view">
|
||||||
|
<field name="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<div name="options" position="inside">
|
||||||
|
<div id="must_print_barcode">
|
||||||
|
<field name="must_print_barcode"/>
|
||||||
|
<label for="must_print_barcode"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="product_template_only_form_view" model="ir.ui.view">
|
||||||
|
<field name="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="product.product_template_only_form_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<header position="inside">
|
||||||
|
<button name="generate_barcode_from_product_template" type="object" string="Generate Barcode" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '!=', False)]}"/>
|
||||||
|
<button name="print_zpl_barcode_from_product_template" type="object" string="Print Barcode" groups="base_report_to_printer.printing_group_user" attrs="{'invisible': ['|', ('product_variant_count', '>', 1), ('barcode', '=', False)]}"/>
|
||||||
|
</header>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<record id="product_normal_form_view" model="ir.ui.view">
|
<record id="product_normal_form_view" model="ir.ui.view">
|
||||||
<field name="name">generate.weight.price.barcode.product.product.form</field>
|
|
||||||
<field name="model">product.product</field>
|
<field name="model">product.product</field>
|
||||||
<field name="inherit_id" ref="product.product_normal_form_view" />
|
<field name="inherit_id" ref="product.product_normal_form_view" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<header position="inside">
|
<header position="inside">
|
||||||
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcode" groups="base_report_to_printer.printing_group_user"/>
|
<button name="generate_barcode_from_product_product" type="object" string="Generate Barcode" attrs="{'invisible': [('barcode', '!=', False)]}"/>
|
||||||
|
<button name="%(product_print_zpl_barcode.product_print_zpl_barcode_action)d" type="action" string="Print Barcode" groups="base_report_to_printer.printing_group_user" attrs="{'invisible': [('barcode', '=', False)]}"/>
|
||||||
</header>
|
</header>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from odoo.tools import float_compare, float_is_zero
|
from odoo.tools import float_compare, float_is_zero
|
||||||
|
from stdnum.ean import is_valid
|
||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -172,7 +173,8 @@ class ProductPrintZplBarcode(models.TransientModel):
|
|||||||
# print "barcode FINAL=", barcode
|
# print "barcode FINAL=", barcode
|
||||||
zpl_unicode = self._price_weight_barcode_type_zpl() % {
|
zpl_unicode = self._price_weight_barcode_type_zpl() % {
|
||||||
'product_name': self.product_name,
|
'product_name': self.product_name,
|
||||||
'ean13_no_checksum': barcode[:12],
|
'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE',
|
||||||
|
'ean_no_checksum': barcode[:-1],
|
||||||
'price_uom': self.price_uom,
|
'price_uom': self.price_uom,
|
||||||
'price': self.price,
|
'price': self.price,
|
||||||
'currency_symbol': self.currency_id.symbol,
|
'currency_symbol': self.currency_id.symbol,
|
||||||
@@ -189,7 +191,7 @@ class ProductPrintZplBarcode(models.TransientModel):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _price_weight_barcode_type_zpl(self):
|
def _price_weight_barcode_type_zpl(self):
|
||||||
label = u"""
|
label = """
|
||||||
^XA
|
^XA
|
||||||
^CI28
|
^CI28
|
||||||
^PW304
|
^PW304
|
||||||
@@ -201,7 +203,7 @@ class ProductPrintZplBarcode(models.TransientModel):
|
|||||||
^FO15,30^FB270,3,0,C^FD%(product_name)s^FS
|
^FO15,30^FB270,3,0,C^FD%(product_name)s^FS
|
||||||
^CF0,25
|
^CF0,25
|
||||||
^FO15,75^FB270,1,0,C^FD%(quantity).3f %(uom_name)s %(price_uom).2f %(currency_symbol)s/%(uom_name)s^FS
|
^FO15,75^FB270,1,0,C^FD%(quantity).3f %(uom_name)s %(price_uom).2f %(currency_symbol)s/%(uom_name)s^FS
|
||||||
^FO60,110^BEN,50^FD%(ean13_no_checksum)s^FS
|
^FO60,110^%(ean_zpl_command)sN,50^FD%(ean_no_checksum)s^FS
|
||||||
^PQ%(copies)s
|
^PQ%(copies)s
|
||||||
^XZ
|
^XZ
|
||||||
"""
|
"""
|
||||||
@@ -209,7 +211,7 @@ class ProductPrintZplBarcode(models.TransientModel):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _product_barcode_type_zpl(self):
|
def _product_barcode_type_zpl(self):
|
||||||
label = u"""
|
label = """
|
||||||
^XA
|
^XA
|
||||||
^CI28
|
^CI28
|
||||||
^PW304
|
^PW304
|
||||||
@@ -219,7 +221,7 @@ class ProductPrintZplBarcode(models.TransientModel):
|
|||||||
^FO15,0^FB270,1,0,C^FD%(price_uom).2f %(currency_symbol)s^FS
|
^FO15,0^FB270,1,0,C^FD%(price_uom).2f %(currency_symbol)s^FS
|
||||||
^CF0,20
|
^CF0,20
|
||||||
^FO15,30^FB270,3,0,C^FD%(product_name)s^FS
|
^FO15,30^FB270,3,0,C^FD%(product_name)s^FS
|
||||||
^FO60,100^BEN,60^FD%(ean13_no_checksum)s^FS
|
^FO60,100^%(ean_zpl_command)sN,60^FD%(ean_no_checksum)s^FS
|
||||||
^PQ%(copies)s
|
^PQ%(copies)s
|
||||||
^XZ
|
^XZ
|
||||||
"""
|
"""
|
||||||
@@ -228,7 +230,8 @@ class ProductPrintZplBarcode(models.TransientModel):
|
|||||||
def _prepare_product_barcode_type(self):
|
def _prepare_product_barcode_type(self):
|
||||||
zpl_unicode = self._product_barcode_type_zpl() % {
|
zpl_unicode = self._product_barcode_type_zpl() % {
|
||||||
'product_name': self.product_name,
|
'product_name': self.product_name,
|
||||||
'ean13_no_checksum': self.barcode[:12],
|
'ean_zpl_command': len(self.barcode) == 8 and 'B8' or 'BE',
|
||||||
|
'ean_no_checksum': self.barcode[:-1],
|
||||||
'price_uom': self.price_uom,
|
'price_uom': self.price_uom,
|
||||||
'currency_symbol': self.currency_id.symbol, # symbol is a required field
|
'currency_symbol': self.currency_id.symbol, # symbol is a required field
|
||||||
'copies': self.copies,
|
'copies': self.copies,
|
||||||
@@ -242,12 +245,16 @@ class ProductPrintZplBarcode(models.TransientModel):
|
|||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
assert self.barcode
|
assert self.barcode
|
||||||
if len(self.barcode) != 13:
|
if len(self.barcode) not in (8, 13):
|
||||||
raise UserError(_(
|
raise UserError(_(
|
||||||
"This wizard only supports EAN13 for the moment. Barcode '%s' "
|
"This wizard only supports EAN8 and EAN13 for the moment. "
|
||||||
"has %d digits instead of 13") % (
|
"Barcode '%s' has %d digits.") % (
|
||||||
self.barcode,
|
self.barcode,
|
||||||
len(self.barcode)))
|
len(self.barcode)))
|
||||||
|
if not is_valid(self.barcode):
|
||||||
|
raise UserError(_(
|
||||||
|
"The barcode '%s' is not a valid EAN barcode "
|
||||||
|
"(wrong checksum).") % self.barcode)
|
||||||
if not self.copies:
|
if not self.copies:
|
||||||
raise UserError(_("The number of copies cannot be 0"))
|
raise UserError(_("The number of copies cannot be 0"))
|
||||||
if self.barcode_type in ('price', 'weight'):
|
if self.barcode_type in ('price', 'weight'):
|
||||||
|
|||||||
Reference in New Issue
Block a user