diff --git a/.gitignore b/.gitignore index f7f8a408be13..dfac18670795 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ build/ develop-eggs/ dist/ eggs/ -lib/ lib64/ parts/ sdist/ diff --git a/fiscal_epos_print/__init__.py b/fiscal_epos_print/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/fiscal_epos_print/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/fiscal_epos_print/__manifest__.py b/fiscal_epos_print/__manifest__.py new file mode 100644 index 000000000000..331aeac062c2 --- /dev/null +++ b/fiscal_epos_print/__manifest__.py @@ -0,0 +1,30 @@ +# Leonardo Donelli - Creativi Quadrati +# © 2016 Alessio Gerace - Agile Business Group +# © 2018-2019 Lorenzo Battistini +# © 2019-2020 Roberto Fichera - Level Prime Srl +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl.html). + +{ + 'name': 'Driver for ePOS-Print XML compatible fiscal printers', + 'version': '12.0.1.0.0', + 'category': 'POS, Fiscal, Hardware, Driver', + 'summary': 'ePOS-Print XML Fiscal Printer Driver - Compatible Epson printers: ' + 'FP81II, FP90III', + 'author': ( + 'Odoo Community Association (OCA), Agile Business Group, ' + 'Leonardo Donelli, TAKOBI, Level Prime Srl' + ), + 'license': 'GPL-3', + 'website': 'https://github.com/OCA/l10n-italy', + 'depends': ['point_of_sale', 'pos_order_mgmt'], + 'data': [ + 'views/account.xml', + 'views/point_of_sale.xml', + 'views/assets.xml', + ], + 'qweb': [ + 'static/src/xml/pos.xml' + ], + 'installable': True, + 'auto_install': False, +} diff --git a/fiscal_epos_print/i18n/it.po b/fiscal_epos_print/i18n/it.po new file mode 100644 index 000000000000..f63d6a12af31 --- /dev/null +++ b/fiscal_epos_print/i18n/it.po @@ -0,0 +1,388 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fiscal_epos_print +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-12-12 08:14+0000\n" +"PO-Revision-Date: 2019-12-12 08:14+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:34 +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:277 +#, python-format +msgid "\n" +"Old files: " +msgstr "\n" +"File vecchi: " + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:34 +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:277 +#, python-format +msgid "\n" +"Rejected files: " +msgstr "\n" +"File rifiutati: " + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:644 +#, python-format +msgid "ADE files status" +msgstr "Stato dei file ADE" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:6 +#, python-format +msgid "ADE status" +msgstr "Stato ADE" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:62 +#, python-format +msgid "Apply" +msgstr "Applica" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:65 +#, python-format +msgid "Cancel" +msgstr "Annulla" + +#. module: fiscal_epos_print +#: selection:account.journal,fiscalprinter_payment_type:0 +msgid "Cash" +msgstr "Contanti" + +#. module: fiscal_epos_print +#: selection:account.journal,fiscalprinter_payment_type:0 +msgid "Cheque" +msgstr "Assegni" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:285 +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:624 +#, python-format +msgid "Connecting to the fiscal printer" +msgstr "Connessione alla stampante fiscale" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_account_journal__fiscalprinter_payment_index +msgid "Credit Card/Ticket Index" +msgstr "Index carta di credito / ticket" + +#. module: fiscal_epos_print +#: selection:account.journal,fiscalprinter_payment_type:0 +msgid "Credit/Credit Card" +msgstr "Carta di credito" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:53 +#, python-format +msgid "DDMMYYYY" +msgstr "GGMMAAAA" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_account_tax__fpdeptax +msgid "Department group tax on fiscal printer 1~99" +msgstr "Reparto sulla stampante fiscale 1~99" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:412 +#, python-format +msgid "Discount" +msgstr "Sconto" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_pos_order__refund_doc_num +msgid "Document Number" +msgstr "Numero documento" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:26 +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:269 +#, python-format +msgid "Error missing paper." +msgstr "Errore Manca La carta." + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:18 +#, python-format +msgid "Fattura registrata" +msgstr "" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:34 +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:277 +#, python-format +msgid "Files waiting to be sent: " +msgstr "File in attesa di essere inviati: " + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:455 +#, python-format +msgid "Fiscal Closing" +msgstr "Chiusura fiscale" + +#. module: fiscal_epos_print +#: model_terms:ir.ui.view,arch_db:fiscal_epos_print.view_pos_config_printer_form +msgid "Fiscal printer" +msgstr "Stampante fiscale" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:15 +#, python-format +msgid "Invoice recorded" +msgstr "Fattura registrata" + +#. module: fiscal_epos_print +#: model:ir.model,name:fiscal_epos_print.model_account_journal +msgid "Journal" +msgstr "Registro" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:43 +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:48 +#, python-format +msgid "Max 4 numbers" +msgstr "Massimo 4 numeri" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:41 +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:284 +#, python-format +msgid "Network error. Printer can not be reached" +msgstr "Errore di rete. La stampante non può essere raggiunta" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:164 +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:485 +#, python-format +msgid "No taxes found" +msgstr "Nessuna imposta trovata" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:46 +#, python-format +msgid "Num. Scontrino" +msgstr "" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:350 +#, python-format +msgid "Payment" +msgstr "Pagamento" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_account_journal__fiscalprinter_payment_type +msgid "Payment type" +msgstr "Tipo di pagamento" + +#. module: fiscal_epos_print +#: model:ir.model,name:fiscal_epos_print.model_pos_config +msgid "Point of Sale Configuration" +msgstr "Configurazione punto vendita" + +#. module: fiscal_epos_print +#: model:ir.model,name:fiscal_epos_print.model_pos_order +msgid "Point of Sale Orders" +msgstr "Ordini punto vendita" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_pos_config__printer_ip +msgid "Printer IP Address" +msgstr "Indirizzo IP stampante" + +#. module: fiscal_epos_print +#: code:addons/fiscal_epos_print/models/product.py:12 +#, python-format +msgid "Product %s must have 1 tax" +msgstr "Il prodotto %s deve avere un'imposta" + +#. module: fiscal_epos_print +#: model:ir.model,name:fiscal_epos_print.model_product_template +msgid "Product Template" +msgstr "Modello prodotto" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:56 +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:58 +#, python-format +msgid "RT Serial" +msgstr "Seriale RT" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:51 +#, python-format +msgid "Receipt Date" +msgstr "Data scontrino" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:12 +#, python-format +msgid "Receipt sent to the printer" +msgstr "Scontrino inviato alla stampante" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:424 +#, python-format +msgid "Refund >>> " +msgstr "Reso >>> " + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_pos_order__refund_cash_fiscal_serial +msgid "Refund Cash Serial" +msgstr "Matricola RT" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:28 +#, python-format +msgid "Refund Data" +msgstr "Dati reso" + +#. module: fiscal_epos_print +#: model_terms:ir.ui.view,arch_db:fiscal_epos_print.view_pos_pos_form_refund_info +msgid "Refund Info" +msgstr "Informazioni reso" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:126 +#, python-format +msgid "Refund Information Details" +msgstr "Dettagli reso" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:47 +#, python-format +msgid "Refund Information Not Present" +msgstr "Informazioni reso non presenti" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_pos_order__refund_date +msgid "Refund date reference" +msgstr "Data riferimento reso" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_pos_order__refund_report +msgid "Report reference" +msgstr "Riferimento report" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:15 +#, python-format +msgid "Scontrino inviato alla stampante" +msgstr "" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:41 +#, python-format +msgid "Seq. Closing" +msgstr "Seq. Chiusura" + +#. module: fiscal_epos_print +#: model:ir.model.fields,help:fiscal_epos_print.field_account_journal__fiscalprinter_payment_index +msgid "Set the index of the given payment type to specify the detail. Such index of the payment type must programmed on the fiscal printer" +msgstr "Impostare l'index del relativo tipo di pagamento, per specificare il dettaglio. Tale index deve essere impostato sulla stampante fiscale." + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_pos_config__show_receipt_when_printing +msgid "Show receipt on screen when printing" +msgstr "Mostrare a schermo lo scontrino in fase di stampa" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:69 +#, python-format +msgid "Some fields are empty." +msgstr "Alcuni campi sono vuoti." + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:6 +#, python-format +msgid "Status of files to be sent to ADE" +msgstr "Stato dei file da inviare all'ADE" + +#. module: fiscal_epos_print +#: model:ir.model,name:fiscal_epos_print.model_account_tax +msgid "Tax" +msgstr "Imposta" + +#. module: fiscal_epos_print +#: code:addons/fiscal_epos_print/models/product.py:14 +#, python-format +msgid "Tax %s must be included in price" +msgstr "L'imposta %s deve essere inclusa nel prezzo" + +#. module: fiscal_epos_print +#: model:ir.model.fields,help:fiscal_epos_print.field_pos_config__printer_ip +msgid "The hostname or ip address of the hardware printer, please fill this field if you want use you receipts printer" +msgstr "" + +#. module: fiscal_epos_print +#: model:ir.model.fields,help:fiscal_epos_print.field_account_journal__fiscalprinter_payment_type +msgid "The payment type to send to the Fiscal Printer." +msgstr "Il tipo di pagamento da inviare alla stampante fiscale." + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/js/fp90iii.js:48 +#, python-format +msgid "The refund information aren't present. Please insert them before printing the receipt" +msgstr "Le informazioni di reso non sono presenti. Prego inserirle prima di stampare lo scontrino" + +#. module: fiscal_epos_print +#: selection:account.journal,fiscalprinter_payment_type:0 +msgid "Ticket" +msgstr "" + +#. module: fiscal_epos_print +#: model:ir.model.fields,field_description:fiscal_epos_print.field_pos_config__use_https +msgid "Use https" +msgstr "Usa HTTPS" + +#. module: fiscal_epos_print +#. openerp-web +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:12 +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:22 +#: code:addons/fiscal_epos_print/static/src/xml/pos.xml:25 +#, python-format +msgid "display:none" +msgstr "" + diff --git a/fiscal_epos_print/models/__init__.py b/fiscal_epos_print/models/__init__.py new file mode 100644 index 000000000000..ff3a2d6bd6e6 --- /dev/null +++ b/fiscal_epos_print/models/__init__.py @@ -0,0 +1,4 @@ +from . import account_journal +from . import point_of_sale +from . import account +from . import pos_order diff --git a/fiscal_epos_print/models/account.py b/fiscal_epos_print/models/account.py new file mode 100644 index 000000000000..1833ddd6de93 --- /dev/null +++ b/fiscal_epos_print/models/account.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class AccountTax(models.Model): + _inherit = 'account.tax' + + fpdeptax = fields.Char( + 'Department group tax on fiscal printer 1~99', + size=1, default="1" + ) diff --git a/fiscal_epos_print/models/account_journal.py b/fiscal_epos_print/models/account_journal.py new file mode 100644 index 000000000000..7d4e272ec43f --- /dev/null +++ b/fiscal_epos_print/models/account_journal.py @@ -0,0 +1,23 @@ +from odoo import fields, models + + +class AccountJournal(models.Model): + _inherit = 'account.journal' + + fiscalprinter_payment_type = fields.Selection( + [ + ('0', 'Cash'), + ('1', 'Cheque'), + ('2', 'Credit/Credit Card'), + ('3', 'Ticket') + ], + 'Payment type', + help='The payment type to send to the Fiscal Printer.', + default='0' + ) + + fiscalprinter_payment_index = fields.Integer( + string='Credit Card/Ticket Index', + help='Set the index of the given payment type to specify the detail. ' + 'Such index of the payment type must programmed on the fiscal ' + 'printer') diff --git a/fiscal_epos_print/models/point_of_sale.py b/fiscal_epos_print/models/point_of_sale.py new file mode 100644 index 000000000000..a019f10d0bff --- /dev/null +++ b/fiscal_epos_print/models/point_of_sale.py @@ -0,0 +1,19 @@ +from odoo import fields, models + + +class PosConfig(models.Model): + _inherit = 'pos.config' + + printer_ip = fields.Char( + 'Printer IP Address', + help='The hostname or ip address of the hardware printer, ' + 'please fill this field if you want use you receipts printer', + size=45 + ) + use_https = fields.Boolean( + string='Use https', + default=False, + ) + show_receipt_when_printing = fields.Boolean( + string='Show receipt on screen when printing', default=True) + fiscal_printer_serial = fields.Char(string='Fiscal Printer Serial') diff --git a/fiscal_epos_print/models/pos_order.py b/fiscal_epos_print/models/pos_order.py new file mode 100644 index 000000000000..3df785183fb8 --- /dev/null +++ b/fiscal_epos_print/models/pos_order.py @@ -0,0 +1,81 @@ +from datetime import datetime +from odoo import fields, models, api + + +class PosOrder(models.Model): + _inherit = 'pos.order' + + refund_date = fields.Date(string='Refund date reference') + refund_report = fields.Integer(string='Report reference', digits=(4, 0)) + refund_doc_num = fields.Integer(string='Document Number', digits=(4, 0)) + refund_cash_fiscal_serial = fields.Char(string='Refund Cash Serial') + + fiscal_receipt_number = fields.Integer( + string='Fiscal receipt number', digits=(4, 0)) + fiscal_receipt_amount = fields.Float("Fiscal receipt amount") + fiscal_receipt_date = fields.Date( + "Fiscal receipt date", digits=(4, 0)) + fiscal_z_rep_number = fields.Integer("Fiscal closure number") + fiscal_printer_serial = fields.Char(string='Fiscal Printer Serial') + + @api.model + def _order_fields(self, ui_order): + res = super(PosOrder, self)._order_fields(ui_order) + res['refund_date'] = ui_order['refund_date'] or False + res['refund_report'] = ui_order['refund_report'] or False + res['refund_doc_num'] = ui_order['refund_doc_num'] or False + res['refund_cash_fiscal_serial'] = \ + ui_order['refund_cash_fiscal_serial'] or False + res['fiscal_receipt_number'] = \ + ui_order['fiscal_receipt_number'] or False + res['fiscal_receipt_amount'] = \ + ui_order['fiscal_receipt_amount'] or False + res['fiscal_receipt_date'] = ui_order['fiscal_receipt_date'] or False + res['fiscal_z_rep_number'] = ui_order['fiscal_z_rep_number'] or False + res['fiscal_printer_serial'] = \ + ui_order['fiscal_printer_serial'] or False + return res + + # This is on pos_order_mgmt to send back the fields of already existing + # pos.order + @api.multi + def _prepare_done_order_for_pos(self): + res = super(PosOrder, self)._prepare_done_order_for_pos() + res['refund_date'] = self.refund_date + res['refund_report'] = self.refund_report + res['refund_doc_num'] = self.refund_doc_num + res['refund_cash_fiscal_serial'] = self.refund_cash_fiscal_serial + res['fiscal_receipt_number'] = self.fiscal_receipt_number + res['fiscal_receipt_amount'] = self.fiscal_receipt_amount + res['fiscal_receipt_date'] = self.fiscal_receipt_date + res['fiscal_z_rep_number'] = self.fiscal_z_rep_number + res['fiscal_printer_serial'] = self.fiscal_printer_serial + return res + + @api.model + def update_fiscal_receipt_values(self, pos_order): + po = self.search([('pos_reference', '=', pos_order.get('name'))]) + receipt_no = int(pos_order.get('fiscal_receipt_number')) + receipt_date = datetime.strptime(pos_order.get( + 'fiscal_receipt_date'), '%d/%m/%Y').date().strftime('%Y-%m-%d') + receipt_amount = float(pos_order.get('fiscal_receipt_amount')) + fiscal_z_rep_number = int(pos_order.get('fiscal_z_rep_number')) + fiscal_printer_serial = pos_order.get('fiscal_printer_serial') or \ + self.config_id.fiscal_printer_serial + if po: + po.write({ + 'fiscal_receipt_number': receipt_no, + 'fiscal_receipt_date': receipt_date, + 'fiscal_receipt_amount': receipt_amount, + 'fiscal_z_rep_number': fiscal_z_rep_number, + 'fiscal_printer_serial': fiscal_printer_serial, + }) + return True + + @api.model + def create_from_ui(self, orders): + res = super(PosOrder, self).create_from_ui(orders) + for order in orders: + if order['data'].get('fiscal_receipt_number'): + self.update_fiscal_receipt_values(order['data']) + return res diff --git a/fiscal_epos_print/readme/CONFIGURE.rst b/fiscal_epos_print/readme/CONFIGURE.rst new file mode 100644 index 000000000000..9ed99d8d874e --- /dev/null +++ b/fiscal_epos_print/readme/CONFIGURE.rst @@ -0,0 +1,6 @@ +- print list departments of your fiscal printer +- mapping odoo sale taxes with group taxes - departments of fiscal printer, for each sale tax on odoo, using field "Department group tax on fiscal printer 1~99" +- In odoo, use taxes included in price +- connect your fiscal printer on network and find IP +- open point_of_sale configuration and fill Printer IP Address field, with printer IP +- that's all, at validation of payment on POS session system print fiscal receipt. diff --git a/fiscal_epos_print/readme/CONTRIBUTORS.rst b/fiscal_epos_print/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..bfba346293a9 --- /dev/null +++ b/fiscal_epos_print/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Leonardo Donelli +* Lorenzo Battistini +* Alessio Gerace +* Roberto Fichera diff --git a/fiscal_epos_print/readme/DESCRIPTION.rst b/fiscal_epos_print/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..6eafa67be6fb --- /dev/null +++ b/fiscal_epos_print/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This module allows to print receipt of point_of_sale, +on fiscal printer Epson via EPos protocol. + +Supported printers: + +- FP81II +- FP90III diff --git a/fiscal_epos_print/readme/ROADMAP.rst b/fiscal_epos_print/readme/ROADMAP.rst new file mode 100644 index 000000000000..c6e3f2993281 --- /dev/null +++ b/fiscal_epos_print/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +* Resi: + - Aggiungere controllo "rendibilità" + - Stampare sullo scontrino un barcode identificativo, in modo da generare il reso facendone la scansione diff --git a/fiscal_epos_print/static/lib/fiscalprint/fiscalprint.js b/fiscal_epos_print/static/lib/fiscalprint/fiscalprint.js new file mode 100644 index 000000000000..00763f0eb36f --- /dev/null +++ b/fiscal_epos_print/static/lib/fiscalprint/fiscalprint.js @@ -0,0 +1,1623 @@ +// +// ePOS-Print and Fiscal Print API +// +// Version 1.1.0 +// +// Copyright (C) SEIKO EPSON CORPORATION 2018. All rights reserved. +// + +// 06/12/2012 1.0.0 +// First release. + +// 01/09/2014 1.0.1 +// 1. Added send timeout parameter (argument). +// 2. Added onreceive res_add argument so that complete response can also be returned. +// 3. onerror response generates FP_NO_ANSWER_NETWORK fixed text instead of xhr response. +// 4. encodeBase64Binary object added for future use. + + +// 23/03/2018 1.1.0 +// 1. Added empty node exception handling. +// 2. Added "statusText" string field in result variable as "status" field is Integer which doesn't manage text +// present in certain replies. +// 3. Added callMode argument in send method so that either asynchronous or synchronous mode can be selected. +// Use "async" for asynchronous otherwise defaults to synchronous. +// If null, defaults to async. +// Timeout only valid for async mode. + + +(function (window) +{ + + // + // Function: ePOSBuilder constructor + // Description: initialize an ePOS-Print XML Builder object + // Parameters: none + // Return: none + // + function ePOSBuilder() { + // properties + this.message = ''; + // constants + this.FONT_A = 'font_a'; + this.FONT_B = 'font_b'; + this.FONT_C = 'font_c'; + this.FONT_SPECIAL_A = 'special_a'; + this.FONT_SPECIAL_B = 'special_b'; + this.ALIGN_LEFT = 'left'; + this.ALIGN_CENTER = 'center'; + this.ALIGN_RIGHT = 'right'; + this.COLOR_NONE = 'none'; + this.COLOR_1 = 'color_1'; + this.COLOR_2 = 'color_2'; + this.COLOR_3 = 'color_3'; + this.COLOR_4 = 'color_4'; + this.BARCODE_UPC_A = 'upc_a'; + this.BARCODE_UPC_E = 'upc_e'; + this.BARCODE_EAN13 = 'ean13'; + this.BARCODE_JAN13 = 'jan13'; + this.BARCODE_EAN8 = 'ean8'; + this.BARCODE_JAN8 = 'jan8'; + this.BARCODE_CODE39 = 'code39'; + this.BARCODE_ITF = 'itf'; + this.BARCODE_CODABAR = 'codabar'; + this.BARCODE_CODE93 = 'code93'; + this.BARCODE_CODE128 = 'code128'; + this.BARCODE_GS1_128 = 'gs1_128'; + this.BARCODE_GS1_DATABAR_OMNIDIRECTIONAL = 'gs1_databar_omnidirectional'; + this.BARCODE_GS1_DATABAR_TRUNCATED = 'gs1_databar_truncated'; + this.BARCODE_GS1_DATABAR_LIMITED = 'gs1_databar_limited'; + this.BARCODE_GS1_DATABAR_EXPANDED = 'gs1_databar_expanded'; + this.HRI_NONE = 'none'; + this.HRI_ABOVE = 'above'; + this.HRI_BELOW = 'below'; + this.HRI_BOTH = 'both'; + this.SYMBOL_PDF417_STANDARD = 'pdf417_standard'; + this.SYMBOL_PDF417_TRUNCATED = 'pdf417_truncated'; + this.SYMBOL_QRCODE_MODEL_1 = 'qrcode_model_1'; + this.SYMBOL_QRCODE_MODEL_2 = 'qrcode_model_2'; + this.SYMBOL_MAXICODE_MODE_2 = 'maxicode_mode_2'; + this.SYMBOL_MAXICODE_MODE_3 = 'maxicode_mode_3'; + this.SYMBOL_MAXICODE_MODE_4 = 'maxicode_mode_4'; + this.SYMBOL_MAXICODE_MODE_5 = 'maxicode_mode_5'; + this.SYMBOL_MAXICODE_MODE_6 = 'maxicode_mode_6'; + this.SYMBOL_GS1_DATABAR_STACKED = 'gs1_databar_stacked'; + this.SYMBOL_GS1_DATABAR_STACKED_OMNIDIRECTIONAL = 'gs1_databar_stacked_omnidirectional'; + this.SYMBOL_GS1_DATABAR_EXPANDED_STACKED = 'gs1_databar_expanded_stacked'; + this.LEVEL_0 = 'level_0'; + this.LEVEL_1 = 'level_1'; + this.LEVEL_2 = 'level_2'; + this.LEVEL_3 = 'level_3'; + this.LEVEL_4 = 'level_4'; + this.LEVEL_5 = 'level_5'; + this.LEVEL_6 = 'level_6'; + this.LEVEL_7 = 'level_7'; + this.LEVEL_8 = 'level_8'; + this.LEVEL_L = 'level_l'; + this.LEVEL_M = 'level_m'; + this.LEVEL_Q = 'level_q'; + this.LEVEL_H = 'level_h'; + this.LEVEL_DEFAULT = 'default'; + this.LINE_THIN = 'thin'; + this.LINE_MEDIUM = 'medium'; + this.LINE_THICK = 'thick'; + this.LINE_THIN_DOUBLE = 'thin_double'; + this.LINE_MEDIUM_DOUBLE = 'medium_double'; + this.LINE_THICK_DOUBLE = 'thick_double'; + this.DIRECTION_LEFT_TO_RIGHT = 'left_to_right'; + this.DIRECTION_BOTTOM_TO_TOP = 'bottom_to_top'; + this.DIRECTION_RIGHT_TO_LEFT = 'right_to_left'; + this.DIRECTION_TOP_TO_BOTTOM = 'top_to_bottom'; + this.CUT_NO_FEED = 'no_feed'; + this.CUT_FEED = 'feed'; + this.CUT_RESERVE = 'reserve'; + this.DRAWER_1 = 'drawer_1'; + this.DRAWER_2 = 'drawer_2'; + this.PULSE_100 = 'pulse_100'; + this.PULSE_200 = 'pulse_200'; + this.PULSE_300 = 'pulse_300'; + this.PULSE_400 = 'pulse_400'; + this.PULSE_500 = 'pulse_500'; + this.PATTERN_NONE = 'none'; + this.PATTERN_A = 'pattern_a'; + this.PATTERN_B = 'pattern_b'; + this.PATTERN_C = 'pattern_c'; + this.PATTERN_D = 'pattern_d'; + this.PATTERN_E = 'pattern_e'; + this.PATTERN_ERROR = 'error'; + this.PATTERN_PAPER_END = 'paper_end'; + } + + // + // Function: addFeedUnit method + // Description: append the XML element to print and feed paper + // Parameters: + // unit unsignedbyte paper feed amount (units) + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addFeedUnit = function (unit) { + // create empty string + var s = ''; + // check parameter + s += getUByteAttr('unit', unit); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addFeedLine method + // Description: append the XML element to print and feed n lines + // Parameters: + // line unsignedbyte paper feed amount (lines) + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addFeedLine = function (line) { + // create empty string + var s = ''; + // check parameter + s += getUByteAttr('line', line); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addText method + // Description: append the XML element to print characters + // Parameters: + // data string characters + // Return: object ePOSBuilder object + // + ePOSBuilder.prototype.addText = function (data) { + // append element + this.message += '' + encodeXmlEntity(data) + ''; + // return builder object + return this; + } + + // + // Function: addTextLang method + // Description: append the XML element to select language + // Parameters: + // lang string language code and country code (en, en-US, ja, ja-JP, etc.) + // Return: object ePOSBuilder object + // + ePOSBuilder.prototype.addTextLang = function (lang) { + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextAlign method + // Description: append the XML element to set alignment + // Parameters: + // align string alignment (ALIGN_* constants) + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextAlign = function (align) { + // create empty string + var s = ''; + // check parameter + s += getEnumAttr('align', align, regexAlign); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextRotate method + // Description: append the XML element to turn upside-down print mode on/off + // Parameters: + // rotate boolean when true, upside-down print mode is turned on + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextRotate = function (rotate) { + // create empty string + var s = ''; + // check parameter + s += getBoolAttr('rotate', rotate); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextLineSpace method + // Description: append the XML element to set line spacing + // Parameters: + // linespc unsignedByte the amount of line spacing + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextLineSpace = function (linespc) { + // create empty string + var s = ''; + // check parameter + s += getUByteAttr('linespc', linespc); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextFont method + // Description: append the XML element to select character font + // Parameters: + // font string font (FONT_* constants) + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextFont = function (font) { + // create empty string + var s = ''; + // check parameter + s += getEnumAttr('font', font, regexFont); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextSmooth method + // Description: append the XML element to turn smoothing mode on/off + // Parameters: + // smooth boolean when true, smoothing mode is turned on + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextSmooth = function (smooth) { + // create empty string + var s = ''; + // check parameter + s += getBoolAttr('smooth', smooth); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextDouble method + // Description: append the XML element to turn double-wide/double-high mode on/off + // Parameters: + // dw boolean when true, double-wide mode is turned on [option] + // dh boolean when true, double-high mode is turned on [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextDouble = function (dw, dh) { + // create empty string + var s = ''; + // check parameter (option) + if (dw !== undefined) { + s += getBoolAttr('dw', dw); + } + // check parameter (option) + if (dh !== undefined) { + s += getBoolAttr('dh', dh); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextSize method + // Description: append the XML element to select character size + // Parameters: + // width unsignedByte character width (1 to 8) [option] + // height unsignedByte character height (1 to 8) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextSize = function (width, height) { + // create empty string + var s = ''; + // check parameter (option) + if (width !== undefined) { + s += getIntAttr('width', width, 1, 8); + } + // check parameter (option) + if (height !== undefined) { + s += getIntAttr('height', height, 1, 8); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextStyle method + // Description: append the XML element to select character style + // Parameters: + // reverse boolean when true, black/white reverse print mode is turned on [option] + // ul boolean when true, underline mode is turned on [option] + // em boolean when true, emphasized mode is turned on [option] + // color string color (COLOR_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextStyle = function (reverse, ul, em, color) { + // create empty string + var s = ''; + // check parameter (option) + if (reverse !== undefined) { + s += getBoolAttr('reverse', reverse); + } + // check parameter (option) + if (ul !== undefined) { + s += getBoolAttr('ul', ul); + } + // check parameter (option) + if (em !== undefined) { + s += getBoolAttr('em', em); + } + // check parameter (option) + if (color !== undefined) { + s += getEnumAttr('color', color, regexColor); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addTextPosition method + // Description: append the XML element to set absolute print position + // Parameters: + // x unsignedShort X start position + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addTextPosition = function (x) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x', x); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addImage method + // Description: append the XML element to print the graphics data (raster format) + // Parameters: + // context object the 2-D context of HTML 5 Canvas + // x unsignedShort X start position + // y unsignedShort Y start position + // width unsignedShort horizontal size + // height unsignedShort vertical size + // color string color (COLOR_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addImage = function (context, x, y, width, height, color) { + // create empty string + var s = ''; + // check parameter + getUShortAttr('x', x); + // check parameter + getUShortAttr('y', y); + // check parameter + s += getUShortAttr('width', width); + // check parameter + s += getUShortAttr('height', height); + // check parameter (option) + if (color !== undefined) { + s += getEnumAttr('color', color, regexColor); + } + // create image data + var image = encodeRasterImage(context.getImageData(x, y, width, height).data, width, height); + // append element + this.message += '' + encodeBase64Binary(image) + ''; + // return builder object + return this; + } + + // + // Function: addLogo method + // Description: append the XML element to print specified NV graphics data + // Parameters: + // key1 unsignedShort key code 1 + // key2 unsignedShort key code 2 + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addLogo = function (key1, key2) { + // create empty string + var s = ''; + // check parameter + s += getUByteAttr('key1', key1); + // check parameter + s += getUByteAttr('key2', key2); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addBarcode method + // Description: append the XML element to print bar code + // Parameters: + // data object bar code data (characters, escape sequences '\xnn', '\\') + // type string bar code type (BARCODE_* constants) + // hri string print position of HRI characters (HRI_* constants) [option] + // font string font for HRI characters (FONT_* constants) [option] + // width unsignedByte bar code module width [option] + // height unsignedByte bar code module height [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addBarcode = function (data, type, hri, font, width, height) { + // create empty string + var s = ''; + // check parameter + s += getEnumAttr('type', type, regexBarcode); + // check parameter (option) + if (hri !== undefined) { + s += getEnumAttr('hri', hri, regexHri); + } + // check parameter (option) + if (font !== undefined) { + s += getEnumAttr('font', font, regexFont); + } + // check parameter (option) + if (width !== undefined) { + s += getUByteAttr('width', width); + } + // check parameter (option) + if (height !== undefined) { + s += getUByteAttr('height', height); + } + // append element + this.message += '' + escapeText(encodeXmlEntity(data)) + ''; + // return builder object + return this; + } + + // + // Function: addSymbol method + // Description: append the XML element to print two dimension code + // Parameters: + // data object symbol data (characters, escape sequences '\xnn', '\\') + // type string symbol type (SYMBOL_* constants) + // level string error correction level (LEVEL_* constants) [option] + // width unsignedByte module width (PDF417, QR Code, GS1 DataBar) [option] + // height unsignedByte module height (PDF417) [option] + // size unsignedShort the number of columns (PDF417), maximum width (GS1 DataBar) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addSymbol = function (data, type, level, width, height, size) { + // create empty string + var s = ''; + // check parameter + s += getEnumAttr('type', type, regexSymbol); + // check parameter (option) + if (level !== undefined) { + s += getEnumAttr('level', level, regexLevel); + } + // check parameter (option) + if (width !== undefined) { + s += getUByteAttr('width', width); + } + // check parameter (option) + if (height !== undefined) { + s += getUByteAttr('height', height); + } + // check parameter (option) + if (size !== undefined) { + s += getUShortAttr('size', size); + } + // append element + this.message += '' + escapeText(encodeXmlEntity(data)) + ''; + // return builder object + return this; + } + + // + // Function: addCommand method + // Description: append the XML element to send commands + // Parameters: + // data string commands + // Return: object ePOSBuilder object + // + ePOSBuilder.prototype.addCommand = function (data) { + // append element + this.message += '' + encodeHexBinary(data) + ''; + // return builder object + return this; + } + + // + // Function: addHLine method + // Description: append the XML element to draw horizontal line + // Parameters: + // x1 unsignedShort X start position + // x2 unsignedShort X end position + // style string the style of line (LINE_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addHLine = function (x1, x2, style) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x1', x1); + // check parameter + s += getUShortAttr('x2', x2); + // check parameter (option) + if (style !== undefined) { + s += getEnumAttr('style', style, regexLine); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addVLineBegin method + // Description: append the XML element to draw vertical line + // Parameters: + // x unsignedShort X start position + // style string the style of line (LINE_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addVLineBegin = function (x, style) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x', x); + // check parameter (option) + if (style !== undefined) { + s += getEnumAttr('style', style, regexLine); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addVLineEnd method + // Description: append the XML element to draw vertical line + // Parameters: + // x unsignedShort X end position + // style string the style of line (LINE_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addVLineEnd = function (x, style) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x', x); + // check parameter (option) + if (style !== undefined) { + s += getEnumAttr('style', style, regexLine); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPageBegin method + // Description: append the XML element to select page mode + // Parameters: none + // Return: object ePOSBuilder object + // + ePOSBuilder.prototype.addPageBegin = function () { + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPageEnd method + // Description: append the XML element to print and return to standard mode (in page mode) + // Parameters: none + // Return: object ePOSBuilder object + // + ePOSBuilder.prototype.addPageEnd = function () { + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPageArea method + // Description: append the XML element to set print area in page mode + // Parameters: + // x unsignedShort horizontal logical origin + // y unsignedShort vertical logical origin + // width unsignedShort print area width + // height unsignedShort print area height + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addPageArea = function (x, y, width, height) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x', x); + // check parameter + s += getUShortAttr('y', y); + // check parameter + s += getUShortAttr('width', width); + // check parameter + s += getUShortAttr('height', height); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPageDirection method + // Description: append the XML element to select print direction in page mode + // Parameters: + // dir string direction (DIRECTION_* constants) + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addPageDirection = function (dir) { + // create empty string + var s = ''; + // check parameter + s += getEnumAttr('dir', dir, regexDirection); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPagePosition method + // Description: append the XML element to set absolute print position in page mode + // Parameters: + // x unsignedShort horizontal position + // y unsignedShort vertical position + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addPagePosition = function (x, y) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x', x); + // check parameter + s += getUShortAttr('y', y); + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPageLine method + // Description: append the XML element to draw line in page mode + // Parameters: + // x1 unsignedShort X start position + // y1 unsignedShort Y start position + // x2 unsignedShort X end position + // y2 unsignedShort Y end position + // style string the style of line (LINE_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addPageLine = function (x1, y1, x2, y2, style) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x1', x1); + // check parameter + s += getUShortAttr('y1', y1); + // check parameter + s += getUShortAttr('x2', x2); + // check parameter + s += getUShortAttr('y2', y2); + // check parameter (option) + if (style !== undefined) { + s += getEnumAttr('style', style, regexLine); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPageRectangle method + // Description: append the XML element to draw rectangle in page mode + // Parameters: + // x1 unsignedShort X start position + // y1 unsignedShort Y start position + // x2 unsignedShort X end position + // y2 unsignedShort Y end position + // style string the style of line (LINE_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addPageRectangle = function (x1, y1, x2, y2, style) { + // create empty string + var s = ''; + // check parameter + s += getUShortAttr('x1', x1); + // check parameter + s += getUShortAttr('y1', y1); + // check parameter + s += getUShortAttr('x2', x2); + // check parameter + s += getUShortAttr('y2', y2); + // check parameter (option) + if (style !== undefined) { + s += getEnumAttr('style', style, regexLine); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addCut method + // Description: append the XML element to cut paper + // Parameters: + // type string cut mode (CUT_* constants) [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addCut = function (type) { + // create empty string + var s = ''; + // check parameter (option) + if (type !== undefined) { + s += getEnumAttr('type', type, regexCut); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addPulse method + // Description: append the XML element to generate pulse + // Parameters: + // drawer string drawer kick-out connector pin (DRAWER_* constants) [option] + // time string the pulse on/off time [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addPulse = function (drawer, time) { + // create empty string + var s = ''; + // check parameter (option) + if (drawer !== undefined) { + s += getEnumAttr('drawer', drawer, regexDrawer); + } + // check parameter (option) + if (time !== undefined) { + s += getEnumAttr('time', time, regexPulse); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Function: addSound method + // Description: append the XML element to sound buzzer + // Parameters: + // pattern string a pattern (PATTERN_* constants) [option] + // repeat unsignedByte the number of times [option] + // Return: object ePOSBuilder object + // Throws: object invalid parameter + // + ePOSBuilder.prototype.addSound = function (pattern, repeat) { + // create empty string + var s = ''; + // check parameter (option) + if (pattern !== undefined) { + s += getEnumAttr('pattern', pattern, regexPattern); + } + // check parameter (option) + if (repeat !== undefined) { + s += getUByteAttr('repeat', repeat); + } + // append element + this.message += ''; + // return builder object + return this; + } + + // + // Method: toString + // Description: get the ePOS-Print XML message + // Parameters: none + // Return: string XML message + // + ePOSBuilder.prototype.toString = function () { + // append root element + var epos = '' + + this.message + ''; + // return message + return epos; + } + + // + // Function: encodeHexBinary method + // Description: encode binary data to hex binary data + // Parameters: + // s string binary data + // Return: string hex binary data + // + function encodeHexBinary(s) { + var r = ''; + for (i = 0; i < s.length; i++) { + r += ('0' + s.charCodeAt(i).toString(16)).slice(-2); + } + return r; + } + + // + // Function: encodeBase64Binary method + // Description: encode binary data to base64 binary data + // Parameters: + // s string binary data + // Return: string base64 binary data + // + function encodeBase64Binary(s) { + var r = ''; + var l = s.length; + var t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + s += '\x00\x00'; + for (var i = 0; i < l; i += 3) { + var n = (s.charCodeAt(i) << 16) | (s.charCodeAt(i + 1) << 8) | s.charCodeAt(i + 2); + r += t.charAt((n >> 18) & 63) + t.charAt((n >> 12) & 63) + t.charAt((n >> 6) & 63) + t.charAt(n & 63); + } + var p = (3 - l % 3) % 3; + return r.slice(0, r.length - p) + '=='.slice(0, p); + } + + // + // Function: encodeRasterImage method + // Description: encode image data to raster bit image data + // Parameters: + // data byte[] RGBA image data + // w int image width + // h int image height + // Return: string raster bit image data + // + function encodeRasterImage(data, w, h) { + var d8 = [ + [0, 32, 8, 40, 2, 34, 10, 42], + [48, 16, 56, 24, 50, 18, 58, 26], + [12, 44, 4, 36, 14, 46, 6, 38], + [60, 28, 52, 20, 62, 30, 54, 22], + [3, 35, 11, 43, 1, 33, 9, 41], + [51, 19, 59, 27, 49, 17, 57, 25], + [15, 47, 7, 39, 13, 45, 5, 37], + [63, 31, 55, 23, 61, 29, 53, 21] + ]; + var s = '', n = 0, p = 0; + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var r = data[p++], g = data[p++], b = data[p++], a = data[p++]; + var v = 255 - a + ((r * 29891 + g * 58661 + b * 11448) * a + 12750000) / 25500000; + var d = (d8[y & 7][x & 7] << 2) + 2; + if (v < d) { + n |= 0x80 >> (x & 7); + } + if ((x & 7) == 7 || x == w - 1) { + s += String.fromCharCode(n == 16 ? 32 : n); + n = 0; + } + } + } + return s; + } + + // + // Function: encodeXmlEntity method + // Description: encode markup character to XML entity + // Parameters: + // s string text data + // Return: string text data with XML entity + // + function encodeXmlEntity(s) { + var markup = /[<>&'"\t\n\r]/g; + if (markup.test(s)) { + s = s.replace(markup, function (c) { + var r = ''; + switch (c) { + case '<': + r = '<'; + break; + case '>': + r = '>'; + break; + case '&': + r = '&'; + break; + case "'": + r = '''; + break; + case '"': + r = '"'; + break; + case '\t': + r = ' '; + break; + case '\n': + r = ' '; + break; + case '\r': + r = ' '; + break; + default: + break; + } + return r; + }); + } + return s; + } + + // + // Function: escapeText method + // Description: escape sequence for bar code and symbol + // Parameters: + // s string text data + // Return: string text data with escape sequence + // + function escapeText(s) { + var escape = /[\\\x00-\x1f\x7f-\xff]/g; + if (escape.test(s)) { + s = s.replace(escape, function (c) { + return (c == '\\') ? '\\\\' : '\\x' + ('0' + c.charCodeAt(0).toString(16)).slice(-2); + }); + } + return s; + } + + // + // Function: regular expressions + // Description: enumeration check pattern + // + var regexFont = /^(font_[abc]|special_[ab])$/; + var regexAlign = /^(left|center|right)$/; + var regexColor = /^(none|color_[1-4])$/; + var regexBarcode = /^(upc_[ae]|[ej]an13|[ej]an8|code(39|93|128)|itf|codabar|gs1_128|gs1_databar_(omnidirectional|truncated|limited|expanded))$/; + var regexHri = /^(none|above|below|both)$/; + var regexSymbol = /^(pdf417_(standard|truncated)|qrcode_model_[12]|maxicode_mode_[2-6]|gs1_databar_(stacked(_omnidirectional)?|expanded_stacked))$/; + var regexLevel = /^(level_[0-8lmqh]|default)$/; + var regexLine = /^(thin|medium|thick)(_double)?$/; + var regexDirection = /^(left_to_right|bottom_to_top|right_to_left|top_to_bottom)$/; + var regexCut = /^(no_feed|feed|reserve)$/; + var regexDrawer = /^(drawer_1|drawer_2)$/; + var regexPulse = /^pulse_[1-5]00$/; + var regexPattern = /^(none|pattern_[a-e]|error|paper_end)$/; + + // + // Function: getEnumAttr method + // Description: get a XML attribute from a parameter (enumration) + // Parameters: + // name string parameter name + // value string parameter value + // regex regex check pattern + // Return: string XML attribute (' name="value"') + // Throws: object invalid parameter + // + function getEnumAttr(name, value, regex) { + if (!regex.test(value)) { + throw new Error('Parameter "' + name + '" is invalid'); + } + return ' ' + name + '="' + value + '"'; + } + + // + // Function: getBoolAttr method + // Description: get a XML attribute from a parameter (boolean) + // Parameters: + // name string parameter name + // value boolean parameter value + // Return: string XML attribute (' name="value"') + // + function getBoolAttr(name, value) { + return ' ' + name + '="' + !!value + '"'; + } + + // + // Function: getIntAttr method + // Description: get a XML attribute from a parameter (integer) + // Parameters: + // name string parameter name + // value integer parameter value + // min integer minumum value + // max integer maximum value + // Return: string XML attribute (' name="value"') + // Throws: object invalid parameter + // + function getIntAttr(name, value, min, max) { + if (isNaN(value) || value < min || value > max) { + throw new Error('Parameter "' + name + '" is invalid'); + } + return ' ' + name + '="' + value + '"'; + } + + // + // Function: getUByteAttr method + // Description: get a XML attribute from a parameter (unsigned byte) + // Parameters: + // name string parameter name + // value integer parameter value + // Return: string XML attribute (' name="value"') + // Throws: object invalid parameter + // + function getUByteAttr(name, value) { + return getIntAttr(name, value, 0, 255); + } + + // + // Function: getUShortAttr method + // Description: get a XML attribute from a parameter (unsigned short) + // Parameters: + // name string parameter name + // value integer parameter value + // Return: string XML attribute (' name="value"') + // Throws: object invalid parameter + // + function getUShortAttr(name, value) { + return getIntAttr(name, value, 0, 65535); + } + + // + // Function: ePOSPrint constructor + // Description: initialize an ePOS-Print object + // Parameters: none + // Return: none + // + function ePOSPrint() { + // events + this.onreceive = null; + this.onerror = null; + // constants + this.ASB_NO_RESPONSE = 0x00000001; + this.ASB_PRINT_SUCCESS = 0x00000002; + this.ASB_DRAWER_KICK = 0x00000004; + this.ASB_OFF_LINE = 0x00000008; + this.ASB_COVER_OPEN = 0x00000020; + this.ASB_PAPER_FEED = 0x00000040; + this.ASB_WAIT_ON_LINE = 0x00000100; + this.ASB_PANEL_SWITCH = 0x00000200; + this.ASB_MECHANICAL_ERR = 0x00000400; + this.ASB_AUTOCUTTER_ERR = 0x00000800; + this.ASB_UNRECOVER_ERR = 0x00002000; + this.ASB_AUTORECOVER_ERR = 0x00004000; + this.ASB_RECEIPT_NEAR_END = 0x00020000; + this.ASB_RECEIPT_END = 0x00080000; + this.ASB_BUZZER = 0x01000000; + this.ASB_SPOOLER_IS_STOPPED = 0x80000000; + } + + // + // Function: ePOSprint send method + // Description: send the ePOS-Print XML message + // Parameters: + // address string the address of ePOS-Print service + // request string request message + // Return: none + // Throws: object the browser does not equip XMLHttpRequest + // + ePOSPrint.prototype.send = function (address, request) { + // create SOAP envelope + var soap = '' + + '' + + request + ''; + // create XMLHttpRequest object + var xhr = createXMLHttpRequest(); + xhr.open('POST', address, true); + // set headers + xhr.setRequestHeader('Content-Type', 'text/xml; charset=UTF-8'); + xhr.setRequestHeader('If-Modified-Since', 'Thu, 01 Jan 1970 00:00:00 GMT'); + xhr.setRequestHeader('SOAPAction', '""'); + // receive event + var epos = this; + xhr.onreadystatechange = function () { + // receive response message + if (xhr.readyState == 4) { + if (xhr.status == 200) { + fireReceiveEvent(epos, xhr); + } + else { + fireErrorEvent(epos, xhr); + } + } + } + // send request message + xhr.send(soap); + } + + + /* + Function: createXMLHttpRequest method + Description: create an XMLHttpRequest object + Parameters: none + Return: object XMLHttpRequest object + Throws: object the browser does not equip XMLHttpRequest + */ + + function createXMLHttpRequest() + { + var xhr = null; + if (window.XMLHttpRequest) + { + xhr = new XMLHttpRequest(); + } + else if (window.ActiveXObject) + { + xhr = new ActiveXObject('Msxml2.XMLHTTP'); + } + else + { + throw new Error('XMLHttpRequest is not supported'); + } + return xhr; + } + + + // + // Function: fireReceiveEvent method + // Description: generate the onreceive event + // Parameters: + // epos object ePOSPrint object + // xhr object XMLHttpRequest object + // Return: none + // + function fireReceiveEvent(epos, xhr) { + if (epos.onreceive) { + var res = xhr.responseXML.getElementsByTagName('response'); + if (res.length > 0) { + // fire onreceive event + epos.onreceive({ + success: /^(1|true)$/.test(res[0].getAttribute('success')), + code: res[0].getAttribute('code'), + status: parseInt(res[0].getAttribute('status')) + }); + } + else { + fireErrorEvent(epos, xhr); + } + } + } + + // + // Function: fireErrorEvent method + // Description: generate the onerror event + // Parameters: + // epos object ePOSPrint object + // xhr object XMLHttpRequest object + // Return: none + // + function fireErrorEvent(epos, xhr) { + // fire onerror event + if (epos.onerror) { + epos.onerror({ + status: xhr.status, + responseText: xhr.responseText + }); + } + } + + +// F I S C A L --- F I S C A L --- F I S C A L --- F I S C A L --- F I S C A L --- F I S C A L --- F I S C A L --- F I S C A L + + + /* + Function: fiscalPrint constructor + Description: initialize a fiscalPrint object + Parameters: none + Return: none + */ + + function fiscalPrint() + { + // events + this.onreceive = null; + this.onerror = null; + + // constants + this.ASB_NO_RESPONSE = 0x00000001; + this.ASB_PRINT_SUCCESS = 0x00000002; + this.ASB_DRAWER_KICK = 0x00000004; + this.ASB_OFF_LINE = 0x00000008; + this.ASB_COVER_OPEN = 0x00000020; + this.ASB_PAPER_FEED = 0x00000040; + this.ASB_WAIT_ON_LINE = 0x00000100; + this.ASB_PANEL_SWITCH = 0x00000200; + this.ASB_MECHANICAL_ERR = 0x00000400; + this.ASB_AUTOCUTTER_ERR = 0x00000800; + this.ASB_UNRECOVER_ERR = 0x00002000; + this.ASB_AUTORECOVER_ERR = 0x00004000; + this.ASB_RECEIPT_NEAR_END = 0x00020000; + this.ASB_RECEIPT_END = 0x00080000; + this.ASB_BUZZER = 0x01000000; + this.ASB_SPOOLER_IS_STOPPED = 0x80000000; + } + + + /* + Function: fiscalPrint send method + Description: send the fiscal ePOS-Print XML message + Parameters: + address string The address where fpmate.cgi resides + request string Request message + Return: none + Throws: object The browser does not equip XMLHttpRequest + */ + + fiscalPrint.prototype.send = function (address, request, timeout, callMode) + { + timeout = timeout || 0; + callMode = callMode || "async"; + + // create SOAP envelope + var soap = '\n' + + '\n' + + '\n' + + request + + '\n' + + '\n'; + // create XMLHttpRequest object + var xhr = createXMLHttpRequest(); + if (callMode == "async") + { + xhr.open('POST', address, true); + } + else + { + xhr.open('POST', address, false); // PHIL false = sincrono + } + // set headers + xhr.setRequestHeader('Content-Type', 'text/xml; charset=UTF-8'); + xhr.setRequestHeader('If-Modified-Since', 'Thu, 01 Jan 1970 00:00:00 GMT'); + xhr.setRequestHeader('SOAPAction', '""'); + // receive event + var epos = this; + + // PHIL timeout non va con le richieste sincrone + if (callMode == "async") + { + xhr.timeout = timeout; + xhr.ontimeout = function () { + console.log("Timed out!!!"); + fireFiscalErrorEvent(epos, xhr); + } + } + + xhr.onreadystatechange = function () + { + // receive response message + // alert("xhr.readyState = " + xhr.readyState + "\n" + "xhr.status = " + xhr.status); + if (xhr.readyState == 4) + { + if (xhr.status == 200) + { + fireFiscalReceiveEvent(epos, xhr); + } + else + { + fireFiscalErrorEvent(epos, xhr); + } + } + } + + // send request message + xhr.send(soap); + } + + /* + Function: fireFiscalReceiveEvent method + Description: generate the onreceive event + Parameters: + epos object ePOSPrint object + xhr object XMLHttpRequest object + Return: none + */ + + function fireFiscalReceiveEvent(epos, xhr) + { + if (epos.onreceive) + { + // alert ("xhr.responseXML.xml = " + xhr.responseXML.xml); + var res = xhr.responseXML.getElementsByTagName('response'); + if (res.length > 0) + { + // fire onreceive event + var result = + { + success: /^(1|true)$/.test(res[0].getAttribute('success')), + code: res[0].getAttribute('code'), + status: parseInt(res[0].getAttribute('status')), + statusText: res[0].getAttribute('status') + }; + + // look for additional info + var res_add = res[0].getElementsByTagName('addInfo'); + if (res_add.length > 0) + { + var list = res_add[0].getElementsByTagName('elementList'); + var list_len = list.length; + var tag_names_list = list[0].childNodes[0].nodeValue; + var tag_names_array = tag_names_list.split(','); + var add_info = {}; + + for (var tnai = 0; tnai < tag_names_array.length; tnai++) + { + var node = res_add[0].getElementsByTagName(tag_names_array[tnai])[0]; + var node_child = node.childNodes[0]; + var node_val = ""; + // 21/02/2018 Philip Barnett. Alcuni comandi tornano con responseData vuoto. Senza la verifica, possono vericarsi i null Exceptiom. + // Questa riga non ha risolto il problema - if(node_child.nodeValue != null && node_child.nodeValue != "") + try + { + node_val = node_child.nodeValue; + } + catch(err) // Blank lines generate exceptions + { + // node_val = "Elemento " + node.childNodes[0] + " vuoto"; + } + add_info[tag_names_array[tnai]] = node_val; + } + } + else { + var tag_names_array = ""; + var add_info = ""; + } + + epos.onreceive(result, tag_names_array, add_info, res_add) + } + else // res.length <= 0 + { + // alert ("res.length = " + res.length); + } // end if (res.length > 0) + } // end if (epos.onreceive) + } // end function fireFiscalReceiveEvent(epos, xhr) + + + /* + Function: fireFiscalErrorEvent method + Description: generate the onerror event + Parameters: + epos object ePOSPrint object + xhr object XMLHttpRequest object + Return: none + */ + + function fireFiscalErrorEvent(epos, xhr) + { + // fire onerror event + // alert("Error event called"); + if (epos.onerror) + { + var result = + { + success: 'false', + code: "FP_NO_ANSWER_NETWORK", + status: 0 + }; + + epos.onerror(result) + } + } + + + // + // Function: CanvasPrint constructor + // Description: initialize a Canvas-Print object + // Parameters: none + // Return: none + // + + function CanvasPrint() { + } + // inherit from ePOSPrint object + CanvasPrint.prototype = new ePOSPrint(); + CanvasPrint.prototype.constructor = CanvasPrint; + + // + // Function: print method + // Description: print the HTML 5 Canvas + // Parameters: + // address string the address of ePOS-Print service + // canvas object HTML 5 Canvas object + // cut boolean when true, cut paper [option] + // Return: none + // Throws: object the browser does not equip Canvas + // + CanvasPrint.prototype.print = function (address, canvas, cut) { + // check parameter + if (!canvas.getContext) { + throw new Error('Canvas is not supported'); + } + // get HTML 5 Canvas + var context = canvas.getContext('2d'); + var width = canvas.getAttribute('width'); + var height = canvas.getAttribute('height'); + // create ePOS-Print XML message + var builder = new ePOSBuilder(); + builder.addImage(context, 0, 0, width, height); + if (cut) { + builder.addCut(builder.CUT_FEED); + } + // send request message + this.send(address, builder.toString()); + }; + + + /* + Function: epson object + Description: append constructors to window object + */ + + window.epson = { + ePOSBuilder: ePOSBuilder, + ePOSPrint: ePOSPrint, + fiscalPrint: fiscalPrint, + CanvasPrint: CanvasPrint, + encodeBase64Binary: encodeBase64Binary + }; + + +})(window); + + +/* +Function: decodeFpStatus +Description: Decodes the five printer status bytes +Parameters: add_info.fpstatus +Return: printer, ej, receipt, cash_drawer and mode +*/ + + +/* +function decodeFpStatus(add_info.fpStatus) +{ + + var printer = ""; + var ej = ""; + var cash_drawer = ""; + var receipt = ""; + var mode = ""; + + switch(add_info.fpStatus.substring(0,1)) { + case "0": + printer = "OK"; + break; + case "2": + printer = "Carta in esaurimento"; + break; + case "3": + printer = "Offline (fine carta o coperchio aperto)"; + break; + default: + printer = "Risposta errata"; + } + + switch(add_info.fpStatus.substring(1,2)) { + case "0": + ej = "OK"; + break; + case "1": + ej = "Prossimo ad Esaurimento"; + break; + case "2": + ej = "Da formattare"; + break; + case "3": + ej = "Precedente"; + break; + case "4": + ej = "Di altro misuratore"; + break; + case "5": + ej = "Esaurito"; + break; + default: + ej = "Risposta errata"; + } + + switch(add_info.fpStatus.substring(2,3)) { + case "0": + cash_drawer = "Aperto"; + break; + case "1": + cash_drawer = "Chiuso"; + break; + default: + cash_drawer = "Risposta errata"; + } + + switch(add_info.fpStatus.substring(3,4)) { + case "0": + receipt = "Fiscale aperto"; + break; + case "1": + receipt = "Fiscale/Non fiscale chiuso"; + break; + case "2": + receipt = "Non fiscale aperto"; + break; + case "3": + receipt = "Pagamento in corso"; + break; + case "4": + receipt = "Errore ultimo comando ESC/POS con Fiscale/Non fiscale chiuso"; + break; + case "5": + receipt = "Scontrino in negativo"; + break; + case "6": + receipt = "Errore ultimo comando ESC/POS con Non fiscale aperto"; + break; + case "7": + receipt = "Attesa chiusura scontrino modalità JAVAPOS"; + break; + case "8": + receipt = "Documento fiscale aperto"; + break; + case "A": + receipt = "Titolo aperto"; + break; + case "2": + receipt = "Titolo chiuso"; + break; + default: + receipt = "Risposta errata"; + } + + switch(add_info.fpStatus.substring(4,5)) { + case "0": + mode = "Stato registrazione"; + break; + case "1": + mode = "Stato X"; + break; + case "2": + mode = "Stato Z"; + break; + case "3": + mode = "Stato Set"; + break; + default: + mode = "Risposta errata"; + } +} +*/ diff --git a/fiscal_epos_print/static/lib/pikaday/pikaday.min.css b/fiscal_epos_print/static/lib/pikaday/pikaday.min.css new file mode 100644 index 000000000000..92f41e33c90b --- /dev/null +++ b/fiscal_epos_print/static/lib/pikaday/pikaday.min.css @@ -0,0 +1,5 @@ +@charset "UTF-8";/*! + * Pikaday + * Copyright © 2014 David Bushell | BSD & MIT license | https://dbushell.com/ + */.pika-single{z-index:9999;display:block;position:relative;color:#333;background:#fff;border:1px solid #ccc;border-bottom-color:#bbb;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.pika-single:after,.pika-single:before{content:" ";display:table}.pika-single:after{clear:both}.pika-single.is-hidden{display:none}.pika-single.is-bound{position:absolute;box-shadow:0 5px 15px -5px rgba(0,0,0,.5)}.pika-lendar{float:left;width:240px;margin:8px}.pika-title{position:relative;text-align:center}.pika-label{display:inline-block;position:relative;z-index:9999;overflow:hidden;margin:0;padding:5px 3px;font-size:14px;line-height:20px;font-weight:700;background-color:#fff}.pika-title select{cursor:pointer;position:absolute;z-index:9998;margin:0;left:0;top:5px;opacity:0}.pika-next,.pika-prev{display:block;cursor:pointer;position:relative;outline:0;border:0;padding:0;width:20px;height:30px;text-indent:20px;white-space:nowrap;overflow:hidden;background-color:transparent;background-position:center center;background-repeat:no-repeat;background-size:75% 75%;opacity:.5}.pika-next:hover,.pika-prev:hover{opacity:1}.is-rtl .pika-next,.pika-prev{float:left;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==)}.is-rtl .pika-prev,.pika-next{float:right;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=)}.pika-next.is-disabled,.pika-prev.is-disabled{cursor:default;opacity:.2}.pika-select{display:inline-block}.pika-table{width:100%;border-collapse:collapse;border-spacing:0;border:0}.pika-table td,.pika-table th{width:14.285714285714286%;padding:0}.pika-table th{color:#999;font-size:12px;line-height:25px;font-weight:700;text-align:center}.pika-button{cursor:pointer;display:block;box-sizing:border-box;-moz-box-sizing:border-box;outline:0;border:0;margin:0;width:100%;padding:5px;color:#666;font-size:12px;line-height:15px;text-align:right;background:#f5f5f5}.pika-week{font-size:11px;color:#999}.is-today .pika-button{color:#3af;font-weight:700}.has-event .pika-button,.is-selected .pika-button{color:#fff;font-weight:700;background:#3af;box-shadow:inset 0 1px 3px #178fe5;border-radius:3px}.has-event .pika-button{background:#005da9;box-shadow:inset 0 1px 3px #0076c9}.is-disabled .pika-button,.is-inrange .pika-button{background:#d5e9f7}.is-startrange .pika-button{color:#fff;background:#6cb31d;box-shadow:none;border-radius:3px}.is-endrange .pika-button{color:#fff;background:#3af;box-shadow:none;border-radius:3px}.is-disabled .pika-button{pointer-events:none;cursor:default;color:#999;opacity:.3}.is-outside-current-month .pika-button{color:#999;opacity:.3}.is-selection-disabled{pointer-events:none;cursor:default}.pika-button:hover,.pika-row.pick-whole-week:hover .pika-button{color:#fff;background:#ff8000;box-shadow:none;border-radius:3px}.pika-table abbr{border-bottom:none;cursor:help} +/*# sourceMappingURL=pikaday.min.css.map */ \ No newline at end of file diff --git a/fiscal_epos_print/static/lib/pikaday/pikaday.min.js b/fiscal_epos_print/static/lib/pikaday/pikaday.min.js new file mode 100644 index 000000000000..078c1b5d6238 --- /dev/null +++ b/fiscal_epos_print/static/lib/pikaday/pikaday.min.js @@ -0,0 +1 @@ +!function(e,t){"use strict";var n;if("object"==typeof exports){try{n=require("moment")}catch(e){}module.exports=t(n)}else"function"==typeof define&&define.amd?define(function(e){try{n=e("moment")}catch(e){}return t(n)}):e.Pikaday=t(e.moment)}(this,function(n){"use strict";var s="function"==typeof n,o=!!window.addEventListener,c=window.document,h=window.setTimeout,r=function(e,t,n,a){o?e.addEventListener(t,n,!!a):e.attachEvent("on"+t,n)},t=function(e,t,n,a){o?e.removeEventListener(t,n,!!a):e.detachEvent("on"+t,n)},l=function(e,t){return-1!==(" "+e.className+" ").indexOf(" "+t+" ")},f=function(e,t){l(e,t)||(e.className=""===e.className?t:e.className+" "+t)},g=function(e,t){var n;e.className=(n=(" "+e.className+" ").replace(" "+t+" "," ")).trim?n.trim():n.replace(/^\s+|\s+$/g,"")},y=function(e){return/Array/.test(Object.prototype.toString.call(e))},F=function(e){return/Date/.test(Object.prototype.toString.call(e))&&!isNaN(e.getTime())},L=function(e,t){return[31,(n=e,n%4==0&&n%100!=0||n%400==0?29:28),31,30,31,30,31,31,30,31,30,31][t];var n},P=function(e){F(e)&&e.setHours(0,0,0,0)},B=function(e,t){return e.getTime()===t.getTime()},d=function(e,t,n){var a,i;for(a in t)(i=void 0!==e[a])&&"object"==typeof t[a]&&null!==t[a]&&void 0===t[a].nodeName?F(t[a])?n&&(e[a]=new Date(t[a].getTime())):y(t[a])?n&&(e[a]=t[a].slice(0)):e[a]=d({},t[a],n):!n&&i||(e[a]=t[a]);return e},i=function(e,t,n){var a;c.createEvent?((a=c.createEvent("HTMLEvents")).initEvent(t,!0,!1),a=d(a,n),e.dispatchEvent(a)):c.createEventObject&&(a=c.createEventObject(),a=d(a,n),e.fireEvent("on"+t,a))},a=function(e){return e.month<0&&(e.year-=Math.ceil(Math.abs(e.month)/12),e.month+=12),11';t.push("is-outside-current-month"),e.enableSelectionDaysInNextAndPreviousMonths||t.push("is-selection-disabled")}return e.isDisabled&&t.push("is-disabled"),e.isToday&&t.push("is-today"),e.isSelected&&(t.push("is-selected"),n="true"),e.hasEvent&&t.push("has-event"),e.isInRange&&t.push("is-inrange"),e.isStartRange&&t.push("is-startrange"),e.isEndRange&&t.push("is-endrange"),'"},p=function(e,t,n,a,i,s){var o,r,l,h,d,u=e._o,c=n===u.minYear,f=n===u.maxYear,g='
',m=!0,p=!0;for(l=[],o=0;o<12;o++)l.push('");for(h='
'+u.i18n.months[a]+'
",y(u.yearRange)?(o=u.yearRange[0],r=u.yearRange[1]+1):(o=n-u.yearRange,r=1+n+u.yearRange),l=[];o=u.minYear&&l.push('");return d='
'+n+u.yearSuffix+'
",u.showMonthAfterYear?g+=d+h:g+=h+d,c&&(0===a||u.minMonth>=a)&&(m=!1),f&&(11===a||u.maxMonth<=a)&&(p=!1),0===t&&(g+='"),t===e._o.numberOfMonths-1&&(g+='"),g+"
"},V=function(e,t,n){return''+function(e){var t,n=[];for(e.showWeekNumber&&n.push(""),t=0;t<7;t++)n.push('");return""+(e.isRTL?n.reverse():n).join("")+""}(e)+(""+t.join("")+"")+"
'+m(e,t,!0)+"
"},e=function(e){var a=this,i=a.config(e);a._onMouseDown=function(e){if(a._v){var t=(e=e||window.event).target||e.srcElement;if(t)if(l(t,"is-disabled")||(!l(t,"pika-button")||l(t,"is-empty")||l(t.parentNode,"is-disabled")?l(t,"pika-prev")?a.prevMonth():l(t,"pika-next")&&a.nextMonth():(a.setDate(new Date(t.getAttribute("data-pika-year"),t.getAttribute("data-pika-month"),t.getAttribute("data-pika-day"))),i.bound&&h(function(){a.hide(),i.blurFieldOnSelect&&i.field&&i.field.blur()},100))),l(t,"pika-select"))a._c=!0;else{if(!e.preventDefault)return e.returnValue=!1;e.preventDefault()}}},a._onChange=function(e){var t=(e=e||window.event).target||e.srcElement;t&&(l(t,"pika-select-month")?a.gotoMonth(t.value):l(t,"pika-select-year")&&a.gotoYear(t.value))},a._onKeyChange=function(e){if(e=e||window.event,a.isVisible())switch(e.keyCode){case 13:case 27:i.field&&i.field.blur();break;case 37:e.preventDefault(),a.adjustDate("subtract",1);break;case 38:a.adjustDate("subtract",7);break;case 39:a.adjustDate("add",1);break;case 40:a.adjustDate("add",7)}},a._onInputChange=function(e){var t;e.firedBy!==a&&(t=i.parse?i.parse(i.field.value,i.format):s?(t=n(i.field.value,i.format,i.formatStrict))&&t.isValid()?t.toDate():null:new Date(Date.parse(i.field.value)),F(t)&&a.setDate(t),a._v||a.show())},a._onInputFocus=function(){a.show()},a._onInputClick=function(){a.show()},a._onInputBlur=function(){var e=c.activeElement;do{if(l(e,"pika-single"))return}while(e=e.parentNode);a._c||(a._b=h(function(){a.hide()},50)),a._c=!1},a._onClick=function(e){var t=(e=e||window.event).target||e.srcElement,n=t;if(t){!o&&l(t,"pika-select")&&(t.onchange||(t.setAttribute("onchange","return;"),r(t,"change",a._onChange)));do{if(l(n,"pika-single")||n===i.trigger)return}while(n=n.parentNode);a._v&&t!==i.trigger&&n!==i.trigger&&a.hide()}},a.el=c.createElement("div"),a.el.className="pika-single"+(i.isRTL?" is-rtl":"")+(i.theme?" "+i.theme:""),r(a.el,"mousedown",a._onMouseDown,!0),r(a.el,"touchend",a._onMouseDown,!0),r(a.el,"change",a._onChange),i.keyboardInput&&r(c,"keydown",a._onKeyChange),i.field&&(i.container?i.container.appendChild(a.el):i.bound?c.body.appendChild(a.el):i.field.parentNode.insertBefore(a.el,i.field.nextSibling),r(i.field,"change",a._onInputChange),i.defaultDate||(s&&i.field.value?i.defaultDate=n(i.field.value,i.format).toDate():i.defaultDate=new Date(Date.parse(i.field.value)),i.setDefaultDate=!0));var t=i.defaultDate;F(t)?i.setDefaultDate?a.setDate(t,!0):a.gotoDate(t):a.gotoDate(new Date),i.bound?(this.hide(),a.el.className+=" is-bound",r(i.trigger,"click",a._onInputClick),r(i.trigger,"focus",a._onInputFocus),r(i.trigger,"blur",a._onInputBlur)):this.show()};return e.prototype={config:function(e){this._o||(this._o=d({},u,!0));var t=d(this._o,e,!0);t.isRTL=!!t.isRTL,t.field=t.field&&t.field.nodeName?t.field:null,t.theme="string"==typeof t.theme&&t.theme?t.theme:null,t.bound=!!(void 0!==t.bound?t.field&&t.bound:t.field),t.trigger=t.trigger&&t.trigger.nodeName?t.trigger:t.field,t.disableWeekends=!!t.disableWeekends,t.disableDayFn="function"==typeof t.disableDayFn?t.disableDayFn:null;var n=parseInt(t.numberOfMonths,10)||1;if(t.numberOfMonths=4=i&&(this._y=i,!isNaN(o)&&this._m>o&&(this._m=o)),t="pika-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l'+p(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,t)+this.render(this.calendars[l].year,this.calendars[l].month,t)+"";this.el.innerHTML=r,n.bound&&"hidden"!==n.field.type&&h(function(){n.trigger.focus()},1),"function"==typeof this._o.onDraw&&this._o.onDraw(this),n.bound&&n.field.setAttribute("aria-label",n.ariaLabel)}},adjustPosition:function(){var e,t,n,a,i,s,o,r,l,h,d,u;if(!this._o.container){if(this.el.style.position="absolute",t=e=this._o.trigger,n=this.el.offsetWidth,a=this.el.offsetHeight,i=window.innerWidth||c.documentElement.clientWidth,s=window.innerHeight||c.documentElement.clientHeight,o=window.pageYOffset||c.body.scrollTop||c.documentElement.scrollTop,u=d=!0,"function"==typeof e.getBoundingClientRect)r=(h=e.getBoundingClientRect()).left+window.pageXOffset,l=h.bottom+window.pageYOffset;else for(r=t.offsetLeft,l=t.offsetTop+t.offsetHeight;t=t.offsetParent;)r+=t.offsetLeft,l+=t.offsetTop;(this._o.reposition&&ia.maxDate||a.disableWeekends&&(void 0,0===(w=R.getDay())||6===w)||a.disableDayFn&&a.disableDayFn(R),isEmpty:I,isStartRange:O,isEndRange:j,isInRange:W,showDaysInNextAndPreviousMonths:a.showDaysInNextAndPreviousMonths,enableSelectionDaysInNextAndPreviousMonths:a.enableSelectionDaysInNextAndPreviousMonths};a.pickWholeWeek&&N&&(M=!0),l.push(H(A)),7==++x&&(a.showWeekNumber&&l.unshift((D=k-o,v=t,b=e,_=void 0,_=new Date(b,0,1),''+Math.ceil(((new Date(b,v,D)-_)/864e5+_.getDay()+1)/7)+"")),r.push((p=l,y=a.isRTL,''+(y?p.reverse():p).join("")+"")),x=0,M=!(l=[]))}return V(a,r,n)},isVisible:function(){return this._v},show:function(){this.isVisible()||(this._v=!0,this.draw(),g(this.el,"is-hidden"),this._o.bound&&(r(c,"click",this._onClick),this.adjustPosition()),"function"==typeof this._o.onOpen&&this._o.onOpen.call(this))},hide:function(){var e=this._v;!1!==e&&(this._o.bound&&t(c,"click",this._onClick),this.el.style.position="static",this.el.style.left="auto",this.el.style.top="auto",f(this.el,"is-hidden"),this._v=!1,void 0!==e&&"function"==typeof this._o.onClose&&this._o.onClose.call(this))},destroy:function(){var e=this._o;this.hide(),t(this.el,"mousedown",this._onMouseDown,!0),t(this.el,"touchend",this._onMouseDown,!0),t(this.el,"change",this._onChange),e.keyboardInput&&t(c,"keydown",this._onKeyChange),e.field&&(t(e.field,"change",this._onInputChange),e.bound&&(t(e.trigger,"click",this._onInputClick),t(e.trigger,"focus",this._onInputFocus),t(e.trigger,"blur",this._onInputBlur))),this.el.parentNode&&this.el.parentNode.removeChild(this.el)}},e}); \ No newline at end of file diff --git a/fiscal_epos_print/static/src/img/X.png b/fiscal_epos_print/static/src/img/X.png new file mode 100644 index 000000000000..92a1d55b05b9 Binary files /dev/null and b/fiscal_epos_print/static/src/img/X.png differ diff --git a/fiscal_epos_print/static/src/img/Z.png b/fiscal_epos_print/static/src/img/Z.png new file mode 100644 index 000000000000..5358fe8872e7 Binary files /dev/null and b/fiscal_epos_print/static/src/img/Z.png differ diff --git a/fiscal_epos_print/static/src/img/ade-logo.png b/fiscal_epos_print/static/src/img/ade-logo.png new file mode 100644 index 000000000000..2a01e0fa9751 Binary files /dev/null and b/fiscal_epos_print/static/src/img/ade-logo.png differ diff --git a/fiscal_epos_print/static/src/img/reprint.png b/fiscal_epos_print/static/src/img/reprint.png new file mode 100644 index 000000000000..bebef86039ad Binary files /dev/null and b/fiscal_epos_print/static/src/img/reprint.png differ diff --git a/fiscal_epos_print/static/src/js/chrome.js b/fiscal_epos_print/static/src/js/chrome.js new file mode 100644 index 000000000000..19573e91c198 --- /dev/null +++ b/fiscal_epos_print/static/src/js/chrome.js @@ -0,0 +1,173 @@ +odoo.define("fiscal_epos_print.chrome", function (require) { + "use strict"; + + var core = require("web.core"); + var PosBaseWidget = require('point_of_sale.BaseWidget'); + var chrome = require('point_of_sale.chrome'); + var epson_epos_print = require('fiscal_epos_print.epson_epos_print'); + var _t = core._t; + var eposDriver = epson_epos_print.eposDriver; + + var FiscalPrinterADEFilesButtonWidget = PosBaseWidget.extend({ + template: 'FiscalPrinterADEFilesButtonWidget', + + button_click: function () { + this.chrome.loading_show(); + this.chrome.loading_message(_t('Connecting to the fiscal printer')); + var protocol = ((this.pos.config.use_https) ? 'https://' : 'http://'); + var printer_url = protocol + this.pos.config.printer_ip + '/cgi-bin/fpmate.cgi'; + var printer_options = {url: printer_url}; + var fp90 = new eposDriver(printer_options, this); + fp90.getStatusOfFilesForADE(); + }, + + renderElement: function () { + var self = this; + this._super(); + this.$el.click(function () { + self.button_click(); + }); + }, + + }); + + var PrinterFiscalClosure = PosBaseWidget.extend({ + template: 'PrinterFiscalClosure', + + button_click: function () { + var self = this; + this.chrome.loading_show(); + this.chrome.loading_message(_t('Connecting to the fiscal printer')); + var protocol = ((this.pos.config.use_https) ? 'https://' : 'http://'); + var printer_url = protocol + this.pos.config.printer_ip + '/cgi-bin/fpmate.cgi'; + var printer_options = {url: printer_url, requested_report: true}; + var fp90 = new eposDriver(printer_options, this); + this.gui.show_popup('confirm',{ + 'title': _t('Confirm Printer Fiscal Closure (Report Z)?'), + 'body': _t('Please confirm to execute the Printer Fiscal Closure'), + confirm: function(){ + fp90.printFiscalReport(); + }, + cancel: function(){ + self.chrome.loading_hide(); + }, + }); + }, + + renderElement: function () { + var self = this; + this._super(); + this.$el.click(function () { + self.button_click(); + }); + }, + }); + + var PrinterFiscalXReport = PosBaseWidget.extend({ + template: 'PrinterFiscalXReport', + + button_click: function () { + var self = this; + this.chrome.loading_show(); + this.chrome.loading_message(_t('Connecting to the fiscal printer')); + var protocol = ((this.pos.config.use_https) ? 'https://' : 'http://'); + var printer_url = protocol + this.pos.config.printer_ip + '/cgi-bin/fpmate.cgi'; + var printer_options = {url: printer_url, requested_report: true}; + var fp90 = new eposDriver(printer_options, this); + this.gui.show_popup('confirm',{ + 'title': _t('Confirm Printer Daily Financial Report (Report X)?'), + 'body': _t('Please confirm to execute the Printer Daily Financial Report'), + confirm: function(){ + fp90.printFiscalXReport(); + }, + cancel: function(){ + self.chrome.loading_hide(); + }, + }); + }, + + renderElement: function () { + var self = this; + this._super(); + this.$el.click(function () { + self.button_click(); + }); + }, + }); + + var PrinterFiscalReprintLast = PosBaseWidget.extend({ + template: 'PrinterFiscalReprintLast', + + button_click: function () { + var self = this; + this.chrome.loading_show(); + this.chrome.loading_message(_t('Connecting to the fiscal printer')); + var protocol = ((this.pos.config.use_https) ? 'https://' : 'http://'); + var printer_url = protocol + this.pos.config.printer_ip + '/cgi-bin/fpmate.cgi'; + var printer_options = {url: printer_url}; + var fp90 = new eposDriver(printer_options, this); + + this.gui.show_popup('confirm',{ + 'title': _t('Confirm Print Last Receipt?'), + 'body': _t('Please confirm to print the last receipt'), + confirm: function(){ + fp90.printFiscalReprintLast(); + }, + cancel: function(){ + self.chrome.loading_hide(); + }, + }); + }, + + renderElement: function () { + var self = this; + this._super(); + this.$el.click(function () { + self.button_click(); + }); + }, + }); + + var widgets = chrome.Chrome.prototype.widgets; + widgets.push({ + 'name': _t('ADE files status'), + 'widget': FiscalPrinterADEFilesButtonWidget, + 'append': '.pos-rightheader', + 'args': { + 'label': 'ADE files status', + }, + }); + widgets.push({ + 'name': _t('Printer Fiscal Closure'), + 'widget': PrinterFiscalClosure, + 'append': '.pos-rightheader', + 'args': { + 'label': 'Printer Fiscal Closure', + }, + }); + widgets.push({ + 'name': _t('Printer Fiscal X Report'), + 'widget': PrinterFiscalXReport, + 'append': '.pos-rightheader', + 'args': { + 'label': 'Printer Fiscal X Report', + }, + }); + + widgets.push({ + 'name': _t('Reprinter Last Receipt'), + 'widget': PrinterFiscalReprintLast, + 'append': '.pos-rightheader', + 'args': { + 'label': 'Reprinter Last Receipt', + }, + }); + + return { + FiscalPrinterADEFilesButtonWidget: FiscalPrinterADEFilesButtonWidget, + PrinterFiscalClosure: PrinterFiscalClosure, + PrinterFiscalXReport: PrinterFiscalXReport, + PrinterFiscalReprintLast: PrinterFiscalReprintLast, + }; + +}); diff --git a/fiscal_epos_print/static/src/js/epson_epos_print.js b/fiscal_epos_print/static/src/js/epson_epos_print.js new file mode 100644 index 000000000000..6a5c7e20844b --- /dev/null +++ b/fiscal_epos_print/static/src/js/epson_epos_print.js @@ -0,0 +1,454 @@ +odoo.define("fiscal_epos_print.epson_epos_print", function (require) { + "use strict"; + + var core = require("web.core"); + var utils = require('web.utils'); + var PosDB = require('point_of_sale.DB'); + var rpc = require('web.rpc'); + var _t = core._t; + var round_pr = utils.round_precision; + + function addPadding(str, padding=4) { + var pad = new Array(padding).fill(0).join('') + str; + return pad.substr(pad.length - padding, padding); + } + + function isErrorStatus(printerStatus) { + var error = false; + switch (printerStatus.substring(0, 2)) { + case "00": + case "01": + case "20": + case "21": + error = false; + break; + default: + error = true; + } + return error; + } + + function decodeFpStatus(printerStatus) { + var printer = ""; + var ej = ""; + var receipt = ""; + + switch (printerStatus.substring(0, 1)) { + case "0": + printer = false; + break; + case "2": + printer = _t("Paper running low"); + break; + case "3": + printer = _t("Offline (end of paper or open cover)"); + break; + default: + printer = _t("Wrong answer"); + } + + switch (printerStatus.substring(1, 2)) { + case "0": + ej = false; + break; + case "1": + ej = _t("Running low"); + break; + case "2": + ej = _t("To format"); + break; + case "3": + ej = _t("Previous"); + break; + case "4": + ej = _t("From other measurement device"); + break; + case "5": + ej = _t("Finished"); + break; + default: + ej = _t("Wrong answer"); + } + + switch (printerStatus.substring(3, 4)) { + case "0": + receipt = _t("Fiscal open"); + break; + case "1": + receipt = false; + //receipt = "Fiscale/Non fiscale chiuso"; + break; + case "2": + receipt = _t("Non fiscal open"); + break; + case "3": + receipt = _t("Payment in progress"); + break; + case "4": + receipt = _t("Error on last ESC/POS command with Fiscal/Non fiscal closed"); + break; + case "5": + receipt = _t("Negative receipt"); + break; + case "6": + receipt = _t("Error on last ESC/POS command with Non fiscal open"); + break; + case "7": + receipt = _t("Waiting for receipt closing in JAVAPOS mode"); + break; + case "8": + receipt = _t("Fiscal document open"); + break; + case "A": + receipt = _t("Title open"); + break; + case "B": + receipt = _t("Title closed"); + break; + default: + receipt = _t("Wrong answer"); + } + + return printer || ej || receipt; + } + + function getStatusField(tag){ + return tag === 'printerStatus' || tag === 'fsStatus'; + } + + var eposDriver = core.Class.extend({ + init: function(options, sender) { + var self = this; + options = options || {}; + this.url = options.url || 'http://192.168.1.1/cgi-bin/fpmate.cgi'; + this.fiscalPrinter = new epson.fiscalPrint(); + this.fpresponse = false; + this.sender = sender; + this.order = options.order || null; + this.requested_report = options.requested_report || false; + this.fiscalPrinter.onreceive = function(res, tag_list_names, add_info) { + sender.chrome.loading_hide(); + self.fpresponse = tag_list_names + var tagStatus = tag_list_names.filter(getStatusField); + if (res['code'] == "EPTR_REC_EMPTY"){ + sender.chrome.screens['receipt'].lock_screen(true); + sender.pos.gui.show_popup('error', { + 'title': _t('Error'), + 'body': _t('Missing paper'), + }); + } + else if (add_info.responseCommand == "1138") { + // coming from FiscalPrinterADEFilesButtonWidget + var to_be_sent = add_info.responseData[9] + add_info.responseData[10] + add_info.responseData[11] + add_info.responseData[12]; + var old = add_info.responseData[13] + add_info.responseData[14] + add_info.responseData[15] + add_info.responseData[16]; + var rejected = add_info.responseData[17] + add_info.responseData[18] + add_info.responseData[19] + add_info.responseData[20]; + var msg = _t("Files waiting to be sent: ") + to_be_sent + "; " + _t("Old files: ") + old + "; " + _t("Rejected files: ") + rejected; + sender.pos.gui.show_popup('alert', { + 'title': _t('ADE files'), + 'body': msg, + }); + } + else if (tagStatus.length > 0 && res.success) { + var info = add_info[tagStatus[0]]; + var msgPrinter = decodeFpStatus(info); + if (!isErrorStatus(info)) { + // Z report can be printed from header bar: we just need to hide the loading screen, done above + if (!self.requested_report) { + sender.chrome.screens['receipt'].lock_screen(false); + var order = self.order; + order._printed = true; + if (!order.fiscal_receipt_number) { + order.fiscal_receipt_number = parseInt(add_info.fiscalReceiptNumber); + order.fiscal_receipt_amount = parseFloat(add_info.fiscalReceiptAmount.replace(',', '.')); + order.fiscal_receipt_date = add_info.fiscalReceiptDate; + order.fiscal_z_rep_number = add_info.zRepNumber; + order.fiscal_printer_serial = sender.pos.config.fiscal_printer_serial; + sender.pos.db.add_order(order.export_as_JSON()); + // try to save the order + sender.pos.push_order(); + } + if (!sender.pos.config.show_receipt_when_printing) { + sender.chrome.screens['receipt'].click_next(); + } + } + } + else { + sender.chrome.screens['receipt'].lock_screen(true); + sender.pos.gui.show_popup('error', { + 'title': _t('Connection to the printer failed'), + 'body': msgPrinter, + }); + }; + } + else if (!res.success) { + sender.chrome.screens['receipt'].lock_screen(true); + sender.pos.gui.show_popup('error', { + 'title': _t('Connection to the printer failed'), + 'body': _t('An error happened while sending data to the printer. Error code: ') + res.code, + }); + }; + } + this.fiscalPrinter.onerror = function() { + sender.chrome.loading_hide(); + sender.chrome.screens['receipt'].lock_screen(true); + sender.pos.gui.show_popup('error', { + 'title': _t('Network error'), + 'body': _t('Printer can not be reached') + }); + } + }, + + encodeXml: function (string) { + var xml_special_to_escaped_one_map = { + '&': '&', + '"': '"', + '<': '<', + '>': '>' + }; + + return string.replace(/([\&"<>])/g, function(str, item) { + return xml_special_to_escaped_one_map[item]; + }); + }, + + /* + Prints a sale item line. + */ + printRecItem: function(args) { + var tag = ''; + return tag; + }, + + /* + Prints a sale refund item line. + */ + printFiscalRefundDetails: function(args) { + var message = 'REFUND ' + + addPadding(args.refund_report) + ' ' + + addPadding(args.refund_doc_num) + ' ' + + args.refund_date.substr(8, 2) + // day + args.refund_date.substr(5, 2) + // month + args.refund_date.substr(0, 4) + // year + ' ' + args.refund_cash_fiscal_serial; + + var tag = '\n'; + return tag; + }, + + /* + Prints a sale refund item line. + */ + printRecRefund: function(args) { + var tag = ''; + return tag; + }, + + /* + Adds a discount to the last line. + */ + printRecItemAdjustment: function(args) { + var tag = ''; + return tag; + }, + + /* + Prints a payment. + */ + printRecTotal: function(args) { + var tag = ''; + return tag; + }, + + printRecTotalRefund: function(args) { + var tag = ''; + return tag; + }, + + // Remember that the header goes after + // but before otherwise it will not be printed + // as additional header messageType=1 + printFiscalReceiptHeader: function(receipt){ + var msg = ''; + if (receipt.header != '' && receipt.header.length > 0) { + var hdr = receipt.header.split(/\r\n|\r|\n/); + _.each(hdr, function(m, i) { + msg += '' + }); + } + return msg; + }, + + // Remember that the footer goes within + // as PROMO code messageType=3 + printFiscalReceiptFooter: function(receipt){ + var msg = ''; + if (receipt.footer != '' && receipt.footer.length > 0) { + var hdr = receipt.footer.split(/\r\n|\r|\n/); + _.each(hdr, function(m, i) { + msg += '' + }); + } + return msg; + }, + + printDisplayText: function(msg) { + var xml = '' + + ''; + this.fiscalPrinter.send(this.url, xml); + }, + + /* + Prints a receipt + */ + printFiscalReceipt: function(receipt) { + var self = this; + var has_refund = _.every(receipt.orderlines, function(line) { + return line.quantity < 0; + }); + var xml = ''; + // header must be printed before beginning a fiscal receipt + xml += this.printFiscalReceiptHeader(receipt); + if (!has_refund) { + xml += ''; + } + // footer can go only as promo code so within a fiscal receipt body + xml += this.printFiscalReceiptFooter(receipt); + if (has_refund) + { + xml += this.printFiscalRefundDetails({ + refund_date: receipt.refund_date, + refund_report: receipt.refund_report, + refund_doc_num: receipt.refund_doc_num, + refund_cash_fiscal_serial: receipt.refund_cash_fiscal_serial}); + } + _.each(receipt.orderlines, function(l, i, list) { + if (l.price >= 0) { + if(l.quantity>=0) { + var full_price = l.price; + if (l.discount) { + full_price = round_pr(l.price / (1 - (l.discount / 100)), self.sender.pos.currency.rounding); + } + xml += self.printRecItem({ + description: l.product_name, + quantity: l.quantity, + unitPrice: full_price, + department: l.tax_department.code + }); + if (l.discount) { + xml += self.printRecItemAdjustment({ + adjustmentType: 0, + description: _t('Discount') + ' ' + l.discount + '%', + amount: round_pr((l.quantity * full_price) - (l.quantity * l.price), self.sender.pos.currency.rounding), + }); + } + } + else + { + xml += self.printRecRefund({ + description: _t('Refund >>> ') + l.product_name, + quantity: l.quantity * -1.0, + unitPrice: l.price, + department: l.tax_department.code + }); + } + } + else { + xml += self.printRecItemAdjustment({ + adjustmentType: 3, + description: l.product_name, + department: l.tax_department.code, + amount: -l.price, + }); + } + }); + if (has_refund) { + xml += self.printRecTotalRefund({}); + } + else { + _.each(receipt.paymentlines, function(l, i, list) { + xml += self.printRecTotal({ + payment: l.amount, + paymentType: l.type, + paymentIndex: l.type_index, + description: l.journal, + }); + }); + } + xml += ''; + this.fiscalPrinter.send(this.url, xml); + console.log(xml); + }, + + printFiscalReport: function() { + var xml = ''; + xml += ''; + xml += ''; + this.fiscalPrinter.send(this.url, xml); + }, + + printFiscalXReport: function() { + var xml = ''; + xml += ''; + xml += ''; + this.fiscalPrinter.send(this.url, xml); + }, + + getStatusOfFilesForADE: function() { + var xml = ''; + xml += ''; + xml += ''; + this.fiscalPrinter.send(this.url, xml); + }, + + printFiscalReprintLast: function() { + var xml = ''; + xml += ''; + xml += ''; + this.fiscalPrinter.send(this.url, xml); + }, + + }); + + return { + eposDriver: eposDriver + } + +}); diff --git a/fiscal_epos_print/static/src/js/models.js b/fiscal_epos_print/static/src/js/models.js new file mode 100644 index 000000000000..af4c03f34cb5 --- /dev/null +++ b/fiscal_epos_print/static/src/js/models.js @@ -0,0 +1,175 @@ +odoo.define('fiscal_epos_print.models', function (require) { + "use strict"; + + var models = require('point_of_sale.models'); + var core = require("web.core"); + var utils = require('web.utils'); + var _t = core._t; + var round_pr = utils.round_precision; + var OrderSuper = models.Order; + + models.load_fields("account.journal", + ["fiscalprinter_payment_type", "fiscalprinter_payment_index"]); + + models.Order = models.Order.extend({ + initialize: function(attributes, options){ + OrderSuper.prototype.initialize.call(this, attributes, options); + this.refund_report = null; + this.refund_date = null; + this.refund_doc_num = null; + this.refund_cash_fiscal_serial = null; + this.has_refund = false; + this.fiscal_receipt_number = null; + this.fiscal_receipt_amount = null; + this.fiscal_receipt_date = null; + this.fiscal_z_rep_number = null; + this.fiscal_printer_serial = this.pos.config.fiscal_printer_serial || null; + }, + check_order_has_refund: function() { + var order = this.pos.get_order(); + if (order) { + var lines = order.orderlines; + order.has_refund = lines.find(function(line){ return line.quantity < 0.0;}) != undefined; + } + }, + + init_from_JSON: function (json) { + OrderSuper.prototype.init_from_JSON.apply(this, arguments); + this.refund_report = json.refund_report; + this.refund_date = json.refund_date; + this.refund_doc_num = json.refund_doc_num; + this.refund_cash_fiscal_serial = json.refund_cash_fiscal_serial; + this.check_order_has_refund(); + this.fiscal_receipt_number = json.fiscal_receipt_number; + this.fiscal_receipt_amount = json.fiscal_receipt_amount; + this.fiscal_receipt_date = json.fiscal_receipt_date; + this.fiscal_z_rep_number = json.fiscal_z_rep_number; + this.fiscal_printer_serial = json.fiscal_printer_serial; + }, + + export_as_JSON: function() { + var result = OrderSuper.prototype.export_as_JSON.call(this); + result.refund_report = this.refund_report; + result.refund_date = this.refund_date; + result.refund_doc_num = this.refund_doc_num; + result.refund_cash_fiscal_serial = this.refund_cash_fiscal_serial; + result.fiscal_receipt_number = this.fiscal_receipt_number; + result.fiscal_receipt_amount = this.fiscal_receipt_amount; + result.fiscal_receipt_date = this.fiscal_receipt_date; // parsed by backend + result.fiscal_z_rep_number = this.fiscal_z_rep_number; + result.fiscal_printer_serial = this.fiscal_printer_serial || null; + return result; + }, + + export_for_printing: function(){ + var receipt = OrderSuper.prototype.export_for_printing.call(this); + + receipt.refund_date = this.refund_date; + receipt.refund_report = this.refund_report; + receipt.refund_doc_num = this.refund_doc_num; + receipt.refund_cash_fiscal_serial = this.refund_cash_fiscal_serial; + receipt.fiscal_receipt_number = this.fiscal_receipt_number; + receipt.fiscal_receipt_amount = this.fiscal_receipt_amount; + receipt.fiscal_receipt_date = this.fiscal_receipt_date; + receipt.fiscal_z_rep_number = this.fiscal_z_rep_number; + receipt.fiscal_printer_serial = this.fiscal_printer_serial; + + return receipt + }, + + getPrinterOptions: function (){ + var protocol = ((this.pos.config.use_https) ? 'https://' : 'http://'); + var printer_url = protocol + this.pos.config.printer_ip + '/cgi-bin/fpmate.cgi'; + return {url: printer_url}; + }, + }); + + var _orderline_super = models.Orderline.prototype; + models.Orderline = models.Orderline.extend({ + export_for_printing: function(){ + var res = _orderline_super.export_for_printing.call(this, arguments); + res['tax_department'] = this.get_tax_details_r(); + return res; + }, + get_tax_details_r: function(){ + var details = this.get_all_prices(); + for (var i in details.taxDetails){ + return { + code: this.pos.taxes_by_id[i].fpdeptax, + taxname: this.pos.taxes_by_id[i].name, + } + } + this.pos.gui.show_popup('error', { + 'title': _t('Error'), + 'body': _t('No taxes found'), + }); + }, + compute_all: function(taxes, price_unit, quantity, currency_rounding, no_map_tax) { + var res = _orderline_super.compute_all.call(this, taxes, price_unit, quantity, currency_rounding, no_map_tax); + var self = this; + + var total_excluded = round_pr(price_unit * quantity, currency_rounding); + var total_included = total_excluded; + var base = total_excluded; + var list_taxes = res.taxes; + // amount_type 'group' not handled (used only for purchases, in Italy) + _(taxes).each(function(tax) { + if (!no_map_tax){ + tax = self._map_tax_fiscal_position(tax); + } + if (!tax){ + return; + } + var tax_amount = self._compute_all(tax, base, quantity); + tax_amount = round_pr(tax_amount, currency_rounding); + if (!tax_amount){ + // Intervene here: also add taxes with 0 amount + if (tax.price_include) { + total_excluded -= tax_amount; + base -= tax_amount; + } + else { + total_included += tax_amount; + } + if (tax.include_base_amount) { + base += tax_amount; + } + var data = { + id: tax.id, + amount: tax_amount, + name: tax.name, + }; + list_taxes.push(data); + } + }); + res.taxes = list_taxes; + + return res; + }, + }); + + /* + Overwrite Paymentline.export_for_printing() in order + to make it export the payment type that must be passed + to the fiscal printer. + */ + var original = models.Paymentline.prototype.export_for_printing; + models.Paymentline = models.Paymentline.extend({ + export_for_printing: function() { + var res = original.apply(this, arguments); + res.type = this.cashregister.journal.fiscalprinter_payment_type; + res.type_index = this.cashregister.journal.fiscalprinter_payment_index; + return res; + } + }); + + var _super_posmodel = models.PosModel.prototype; + models.PosModel = models.PosModel.extend({ + initialize: function (session, attributes) { + var tax_model = _.find(this.models, function(model){ return model.model === 'account.tax'; }); + tax_model.fields.push('fpdeptax'); + return _super_posmodel.initialize.call(this, session, attributes); + }, + }); + +}); diff --git a/fiscal_epos_print/static/src/js/popups.js b/fiscal_epos_print/static/src/js/popups.js new file mode 100644 index 000000000000..c2e14c4eab14 --- /dev/null +++ b/fiscal_epos_print/static/src/js/popups.js @@ -0,0 +1,82 @@ +odoo.define("fiscal_epos_print.popups", function (require) { + "use strict"; + + var core = require("web.core"); + var popups = require('point_of_sale.popups'); + var gui = require('point_of_sale.gui'); + var _t = core._t; + + function addPadding(str, padding=4) { + var pad = new Array(padding).fill(0).join('') + str; + return pad.substr(pad.length - padding, padding); + } + + var RefundInfoPopupWidget = popups.extend({ + template: 'RefundInfoPopupWidget', + init: function(parent) { + this.refund_report = null; + this.refund_date = null; + this.refund_doc_num = null; + this.refund_cash_fiscal_serial = null; + this.datepicker = null; + return this._super(parent); + }, + show: function(options){ + options = options || {}; + this._super(options); + this.update_refund_info_button = options.update_refund_info_button; + this.renderElement(); + this.datepicker = null; + this.$('refund_report').focus(); + this.initializeDatePicker(); + }, + click_confirm: function(){ + var self = this; + function allValid() { + return self.$('input').toArray().every(function(element) { + return element.value && element.value != '' + }) + } + + if (allValid()) { + this.$('#error-message-dialog').hide() + + var order = this.pos.get_order(); + order.refund_report = this.$('#refund_report').val(); + order.refund_date = this.$('#refund_date').val(); + order.refund_doc_num = this.$('#refund_doc_num').val(); + order.refund_cash_fiscal_serial = this.$('#refund_cash_fiscal_serial').val(); + this.gui.close_popup(); + if (this.update_refund_info_button && this.update_refund_info_button instanceof Function) { + this.update_refund_info_button(); + } + } else { + this.$('#error-message-dialog').show() + } + }, + initializeDatePicker: function() { + var self = this, + element = this.$('#refund_date').get(0); + + if (element && !this.datepicker) { + this.datepicker = new Pikaday({ + field: element, + parse: function(str) { + return new Date(str.slice(4, 8), + str.slice(2, 4), + str.slice(0, 2)) + }, + toString: function(date) { + var str = date.toLocaleDateString().split('/'); + return addPadding(str[1], 2) + + addPadding(str[0], 2) + + addPadding(str[2]); + } + }); + } + }, + }); + + gui.define_popup({name:'refundinfo', widget: RefundInfoPopupWidget}); + +}); diff --git a/fiscal_epos_print/static/src/js/pos_order_mgmt.js b/fiscal_epos_print/static/src/js/pos_order_mgmt.js new file mode 100644 index 000000000000..e060df06509e --- /dev/null +++ b/fiscal_epos_print/static/src/js/pos_order_mgmt.js @@ -0,0 +1,54 @@ +odoo.define("fiscal_epos_print.pos_order_mgmt", function (require) { + "use strict"; + + var core = require("web.core"); + var pos_order_mgmt = require('pos_order_mgmt.widgets'); + var epson_epos_print = require('fiscal_epos_print.epson_epos_print'); + var _t = core._t; + var OrderListScreenWidget = pos_order_mgmt.OrderListScreenWidget; + var eposDriver = epson_epos_print.eposDriver; + + OrderListScreenWidget.include({ + _prepare_order_from_order_data: function (order_data, action) { + var order = this._super(order_data, action); + if (action === 'print') { + order.refund_report = order_data.refund_report; + order.refund_date = order_data.refund_date; + order.refund_doc_num = order_data.refund_doc_num; + order.refund_cash_fiscal_serial = order_data.refund_cash_fiscal_serial; + } + else if (action === 'return') { + order.refund_report = order_data.fiscal_z_rep_number; + order.refund_date = order_data.fiscal_receipt_date; + order.refund_doc_num = order_data.fiscal_receipt_number; + order.refund_cash_fiscal_serial = order_data.fiscal_printer_serial; + } + // for action === 'copy' we don't need to do anything + return order; + }, + // copiato da screens.PaymentScreenWidget + sendToFP90Printer: function(receipt, printer_options) { + var fp90 = new eposDriver(printer_options, this); + fp90.printFiscalReceipt(receipt); + }, + action_print: function (order_data, order) { + if (this.pos.config.printer_ip) { + if (order_data.fiscal_receipt_number) { + this.pos.gui.show_popup('error', { + 'title': _t('Order already printed'), + 'body': order_data.pos_reference + _t(": order already has a fiscal number, ") + order_data.fiscal_receipt_number, + }); + return; + } + this.chrome.loading_show(); + this.chrome.loading_message(_t('Connecting to the fiscal printer')); + var receipt = order.export_for_printing(); + var printer_options = order.getPrinterOptions(); + printer_options.order = order; + this.sendToFP90Printer(receipt, printer_options); + } + return this._super(order_data, order); + }, + }); + +}); diff --git a/fiscal_epos_print/static/src/js/screens.js b/fiscal_epos_print/static/src/js/screens.js new file mode 100644 index 000000000000..982a47c76034 --- /dev/null +++ b/fiscal_epos_print/static/src/js/screens.js @@ -0,0 +1,197 @@ +odoo.define("fiscal_epos_print.screens", function (require) { + "use strict"; + + var core = require("web.core"); + var screens = require('point_of_sale.screens'); + var epson_epos_print = require('fiscal_epos_print.epson_epos_print'); + var _t = core._t; + var PaymentScreenWidget = screens.PaymentScreenWidget; + var ReceiptScreenWidget = screens.ReceiptScreenWidget; + var eposDriver = epson_epos_print.eposDriver; + + ReceiptScreenWidget.include({ + + lock_screen: function(locked) { + this._super.apply(this, arguments); + if (locked) { + this.$('.receipt-sent').hide(); + this.$('.printing-error').show(); + this.$('.printing-retry').show(); + } else { + this.$('.receipt-sent').show(); + this.$('.printing-error').hide(); + this.$('.printing-retry').hide(); + } + }, + + sendToFP90Printer: function(receipt, printer_options) { + var fp90 = new eposDriver(printer_options, this); + fp90.printFiscalReceipt(receipt); + }, + + render_receipt: function() { + var self = this; + this._super(); + this.$('.printing-retry').click(function(){ + if (self._locked) { + var currentOrder = self.pos.get_order(); + self.chrome.loading_show(); + self.chrome.loading_message(_t('Connecting to the fiscal printer')); + var printer_options = currentOrder.getPrinterOptions(); + printer_options.order = currentOrder; + var receipt = currentOrder.export_for_printing(); + self.sendToFP90Printer(receipt, printer_options); + } + }); + } + }); + + PaymentScreenWidget.include({ + show: function() { + this._super.apply(this, arguments); + if (this.pos.config.printer_ip) { + var currentOrder = this.pos.get_order(); + var printer_options = currentOrder.getPrinterOptions(); + var fp90 = new eposDriver(printer_options, this); + var amount = this.format_currency(currentOrder.get_total_with_tax()); + fp90.printDisplayText(_t("SubTotal") + " " + amount); + } + }, + sendToFP90Printer: function(receipt, printer_options) { + var fp90 = new eposDriver(printer_options, this); + fp90.printFiscalReceipt(receipt); + }, + finalize_validation: function() { + // we need to get currentOrder before calling the _super() + // otherwise we will likely get a empty order when we want to skip + // the receipt preview + var currentOrder = this.pos.get('selectedOrder'); + this._super.apply(this, arguments); + if (this.pos.config.printer_ip && !currentOrder.is_to_invoice()) { + this.chrome.loading_show(); + this.chrome.loading_message(_t('Connecting to the fiscal printer')); + var printer_options = currentOrder.getPrinterOptions(); + printer_options.order = currentOrder; + var receipt = currentOrder.export_for_printing(); + this.sendToFP90Printer(receipt, printer_options); + } + }, + order_is_valid: function(force_validation) { + if (this.pos.config.iface_tax_included == 'subtotal') { + this.gui.show_popup('error',{ + 'title': _t('Wrong tax configuration'), + 'body': _t("Product prices on receipts must be set to 'Tax-Included Price' in POS configuration"), + }); + return false; + } + var self = this; + var receipt = this.pos.get_order(); + if (receipt.has_refund && (receipt.refund_date == null || receipt.refund_date === '' || + receipt.refund_doc_num == null || receipt.refund_doc_num == '' || + receipt.refund_cash_fiscal_serial == null || receipt.refund_cash_fiscal_serial == '' || + receipt.refund_report == null || receipt.refund_report == '')) { + this.gui.show_popup('error',{ + 'title': _t('Refund Information Not Present'), + 'body': _t("The refund information aren't present. Please insert them before printing the receipt"), + }); + return false; + } + return this._super(force_validation); + } + }); + + var set_refund_info_button = screens.ActionButtonWidget.extend({ + template: 'SetRefundInfoButton', + init: function(parent, options) { + var self = this; + this._super(parent, options); + this.pos.bind('change:selectedOrder',function(){ + this.orderline_change(); + this.bind_order_events(); + },this); + this.bind_order_events(); + this.orderline_change(); + }, + renderElement: function() { + this._super(); + var color = this.refund_get_button_color(); + this.$el.css('background', color); + }, + button_click: function () { + var self = this; + var current_order = self.pos.get_order(); + self.gui.show_popup('refundinfo', { + title: _t('Refund Information Details'), + refund_date: current_order.refund_date, + refund_report: current_order.refund_report, + refund_doc_num: current_order.refund_doc_num, + refund_cash_fiscal_serial: current_order.refund_cash_fiscal_serial, + update_refund_info_button: function(){ + self.renderElement(); + }, + }); + }, + bind_order_events: function() { + var self = this; + var order = this.pos.get_order(); + + if (!order) { + return; + } + + if(this.old_order) { + this.old_order.unbind(null,null,this); + } + + this.pos.bind('change:selectedOrder', this.orderline_change, this); + + var lines = order.orderlines; + lines.unbind('add', this.orderline_change, this); + lines.bind('add', this.orderline_change, this); + lines.unbind('remove', this.orderline_change, this); + lines.bind('remove', this.orderline_change, this); + lines.unbind('change', this.orderline_change, this); + lines.bind('change', this.orderline_change, this); + + this.old_order = order; + }, + refund_get_button_color: function() { + var order = this.pos.get_order(); + var color = '#e2e2e2'; + if(order) { + var lines = order.orderlines; + var has_refund = lines.find(function(line){ return line.quantity < 0.0;}) != undefined; + if (has_refund == true) + { + if (order.refund_date && order.refund_date != '' && order.refund_doc_num && order.refund_doc_num != '' && + order.refund_cash_fiscal_serial && order.refund_cash_fiscal_serial != '' && order.refund_report && order.refund_report != '') { + color = 'lightgreen'; + } + else + { + color = 'red'; + } + } + } + return color; + }, + orderline_change: function(){ + var order = this.pos.get_order(); + if (order) { + var lines = order.orderlines; + order.has_refund = lines.find(function(line){ return line.quantity < 0.0;}) != undefined; + } + this.renderElement(); + }, + }); + + screens.define_action_button({ + 'name': 'set_refund_info', + 'widget': set_refund_info_button, + }); + + return { + RefundInfoButtonActionWidget: set_refund_info_button + }; + +}); diff --git a/fiscal_epos_print/static/src/xml/pos.xml b/fiscal_epos_print/static/src/xml/pos.xml new file mode 100644 index 000000000000..cb0764886bb9 --- /dev/null +++ b/fiscal_epos_print/static/src/xml/pos.xml @@ -0,0 +1,102 @@ + + + + +
+ ADE status +
+
+ + +
+ Fiscal closure +
+
+ + +
+ Fiscal closure +
+
+ + +
+ Reprint Last Receipt +
+
+ + + +
+ Receipt sent to the printer +
+
+ Invoice recorded +
+ + +
+
+ + + display:none + + + + +
+ + Refund Data +
+
+ + + + + +
diff --git a/fiscal_epos_print/views/account.xml b/fiscal_epos_print/views/account.xml new file mode 100644 index 000000000000..600767866192 --- /dev/null +++ b/fiscal_epos_print/views/account.xml @@ -0,0 +1,33 @@ + + + + + + POS Journal - Fiscal Printer field + account.journal + + + + + + + + + + + + + account.tax.printer.form.view + account.tax + + + + + + + + + + diff --git a/fiscal_epos_print/views/assets.xml b/fiscal_epos_print/views/assets.xml new file mode 100644 index 000000000000..ccce1fa1f5c6 --- /dev/null +++ b/fiscal_epos_print/views/assets.xml @@ -0,0 +1,19 @@ + + + +