openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #01579
[Merge] lp:~andrei-levin/openerp-pos/openerp-pos into lp:openerp-pos
Andrei Levin has proposed merging lp:~andrei-levin/openerp-pos/openerp-pos into lp:openerp-pos.
Requested reviews:
OpenERP Community Reviewer/Maintainer (openerp-community-reviewer)
For more details, see:
https://code.launchpad.net/~andrei-levin/openerp-pos/openerp-pos/+merge/196201
Added modules that gives possibility to print on a Fiscal Printer
--
https://code.launchpad.net/~andrei-levin/openerp-pos/openerp-pos/+merge/196201
Your team OpenERP Community Reviewer/Maintainer is requested to review the proposed merge of lp:~andrei-levin/openerp-pos/openerp-pos into lp:openerp-pos.
=== added directory 'pos_fiscal_printer'
=== added file 'pos_fiscal_printer/__init__.py'
--- pos_fiscal_printer/__init__.py 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/__init__.py 2013-11-21 22:34:57 +0000
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2013 Didotech srl (<http://www.didotech.com>)
+# All Rights Reserved
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import fiscal_printer
+
=== added file 'pos_fiscal_printer/__openerp__.py'
--- pos_fiscal_printer/__openerp__.py 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/__openerp__.py 2013-11-21 22:34:57 +0000
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2013 Didotech srl (<http://www.didotech.com>)
+# All Rights Reserved
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+{
+ 'name': 'Fiscal Printer Management',
+ 'version': '0.0.1',
+ 'category': 'Point Of Sale',
+ 'description': """
+ This module adds possibility to print a receipt on fiscal printer
+ """,
+ 'author': 'Andrei Levin',
+ 'depends': [
+ 'point_of_sale',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv', # load access rights after groups
+ 'fiscal_printer_view.xml',
+ ],
+ 'demo_xml': [],
+ 'installable': True,
+ 'active': False,
+ 'js': [
+ 'static/src/js/pos_fiscal_printer.js',
+ ],
+}
+
=== added file 'pos_fiscal_printer/ecr.py'
--- pos_fiscal_printer/ecr.py 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/ecr.py 2013-11-21 22:34:57 +0000
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+###############################################################################
+#
+# Copyright (c) 2013 Andrei Levin (andrei.levin at didotech.com)
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+
+class Ecr():
+
+ def __init__(self, config, cash_register_name):
+ self.__name__ = config.name
+ self.config = config
+ self.cash_register_name = cash_register_name
+
+ def get_product_line(self):
+ if self.product_lines:
+ line = self.product_lines.pop(0)
+ self.subtotal += line.price_subtotal
+ if line.discount:
+ self.discount += line.product_id.list_price * line.qty - line.price_subtotal
+ return line
+ else:
+ return False
+
+ def create(self, receipt):
+ self.discount = 0.0
+ self.subtotal = 0.0
+ self.product_lines = receipt.lines
+ self.receipt_data = receipt
+ self.receipt_data.cash_register_name = self.cash_register_name
+
+ self.receipt = self.compose()
+
+ def __unicode__(self):
+ return u'\r\n'.join(self.receipt) + u'\r\n\r\n'
+
+ def __str__(self):
+ return u'\r\n'.join(self.receipt) + u'\r\n\r\n'
+
+ def compose(self):
+ pass
+
+ def print_receipt(self):
+ pass
=== added file 'pos_fiscal_printer/fiscal_printer.py'
--- pos_fiscal_printer/fiscal_printer.py 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/fiscal_printer.py 2013-11-21 22:34:57 +0000
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+###############################################################################
+#
+# Copyright (c) 2013 Andrei Levin (andrei.levin at didotech.com)
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+from openerp import netsvc
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+import time
+import datetime
+import pooler
+
+import logging
+_logger = logging.getLogger(__name__)
+_logger.setLevel(logging.INFO)
+
+
+class pos_order(osv.osv):
+ _inherit = "pos.order"
+
+ def create_from_ui(self, cr, uid, orders, context=None):
+ #_logger.info("orders: %r", orders)
+ order_ids = []
+ # print orders
+
+ for tmp_order in orders:
+ order = tmp_order['data']
+ order_id = self.create(cr, uid, {
+ 'name': order['name'],
+ 'user_id': order['user_id'] or False,
+ 'session_id': order['pos_session_id'],
+ 'lines': order['lines'],
+ 'pos_reference': order['name']
+ }, context)
+
+ for payments in order['statement_ids']:
+ payment = payments[2]
+ self.add_payment(cr, uid, order_id, {
+ 'amount': payment['amount'] or 0.0,
+ 'payment_date': payment['name'],
+ 'statement_id': payment['statement_id'],
+ 'payment_name': payment.get('note', False),
+ 'journal': payment['journal_id']
+ }, context=context)
+
+ if order['amount_return']:
+ session = self.pool.get('pos.session').browse(
+ cr, uid, order['pos_session_id'], context=context)
+ cash_journal = session.cash_journal_id
+
+ if not cash_journal:
+ cash_journal_ids = filter(
+ lambda st: st.journal_id.type == 'cash', session.statement_ids)
+ if not len(cash_journal_ids):
+ raise osv.except_osv(_('error!'),
+ _("No cash statement found for this session. Unable to record returned cash."))
+ cash_journal = cash_journal_ids[0].journal_id
+ self.add_payment(cr, uid, order_id, {
+ 'amount': -order['amount_return'],
+ 'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'payment_name': _('return'),
+ 'journal': cash_journal.id,
+ }, context=context)
+ order_ids.append(order_id)
+ wf_service = netsvc.LocalService("workflow")
+ wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr)
+
+ self.print_receipt(cr, uid, order_id, order, context)
+
+ return order_ids
+
+ def print_receipt(self, cr, uid, order_id, order, context):
+ receipt = Receipt(cr, uid, order_id, order, context)
+ printer = fiscal_printer(cr, uid, order['pos_session_id'])
+ printer.print_receipt(receipt)
+
+
+class Receipt():
+
+ def __init__(self, cr, uid, order_id, order, context=None):
+ self.pool = pooler.get_pool(cr.dbname)
+
+ self.order = order
+
+ r_order = self.pool.get('pos.order').browse(cr, uid, order_id)
+
+ utc_date_order = datetime.datetime.strptime(
+ r_order.date_order, '%Y-%m-%d %H:%M:%S')
+ self.date = fields.datetime.context_timestamp(
+ cr, uid, utc_date_order, context=context)
+ self.name = r_order.name
+ self.reference = r_order.pos_reference
+ self.user = self.pool.get('res.users').browse(cr, uid, uid)
+
+ self.lines = r_order.lines
+ self.amount_total = order['amount_total']
+ self.amount_paid = order['amount_paid']
+ self.amount_tax = order['amount_tax']
+ self.amount_return = order['amount_return']
+
+ self.journals = []
+
+ journal_obj = self.pool.get('account.journal')
+
+ for statement in order['statement_ids']:
+ journal = journal_obj.browse(cr, uid, statement[2]['journal_id'])
+ self.journals.append({
+ 'amount': statement[2]['amount'],
+ 'name': journal.name,
+ 'extended_name': journal.name_get()[0][1]
+ })
+
+
+class fiscal_printer():
+
+ def __init__(self, cr, uid, pos_session_id):
+ self.pool = pooler.get_pool(cr.dbname)
+ fp_config_obj = self.pool.get('fp.config')
+ pos_session = self.pool.get('pos.session').browse(cr, uid, pos_session_id)
+ cash_register_id = pos_session.config_id.id
+
+ printer_configs = fp_config_obj.search(
+ cr, uid, [('cash_register_id', '=', cash_register_id)])
+ self.cash_register = self.pool.get(
+ 'pos.config').browse(cr, uid, cash_register_id)
+
+ if printer_configs:
+ self.config = fp_config_obj.browse(cr, uid, printer_configs[0])
+ else:
+ raise osv.except_osv(
+ 'Warning', _('Cash register {cash_register} has no fiscal printers'.format(cash_register=self.cash_register.name)))
+
+ module = __import__(self.config.driver_id.module + '.driver', fromlist=[str(self.config.driver_id.class_name)])
+ self.driver = getattr(module, str(self.config.driver_id.class_name))(self.config, self.cash_register.name)
+
+ def print_receipt(self, receipt):
+ if self.config.dry:
+ _logger.info('Discarding receipt for order {0}'.format(receipt.reference))
+ else:
+ self.driver.create(receipt)
+ if self.driver.print_receipt():
+ _logger.info('Receipt for {0} printed successfully'.format(receipt.reference))
+ else:
+ _logger.error('There are problems with printing Receipt for {0}'.format(receipt.reference))
+
+
+class fp_config(osv.osv):
+ _name = 'fp.config'
+ _description = "Fiscal Printer configuration"
+
+ def _get_name(self, cr, uid, ids, field_name, arg, context=None):
+ if not ids:
+ return {}
+
+ res = {}
+
+ configs = self.browse(cr, uid, ids)
+
+ for config in configs:
+ res[config.id] = config.cash_register_id.name + \
+ ' : ' + config.driver_id.name
+
+ return res
+
+ _columns = {
+ "name": fields.function(_get_name, string='Name', type='char', method=True),
+ 'cash_register_id': fields.many2one('pos.config', 'Cash Register', required=True),
+ "driver_id": fields.many2one('fp.driver', "Driver", required=True, help="Printer driver used for this client"),
+ 'ecr_password': fields.char("Password ECR", size=16, required=False, help="Password, if needed to print to fiscal printer"),
+ 'host': fields.char("Fiscal Printer Host Address", size=15, required=False, help="Fiscal Printer IP Address or Hostname (if Hostname can be resolved)"),
+ 'port': fields.integer("Port", required=False, help="Fiscal Printer Port"),
+ 'user': fields.char("Username", size=15, required=False, help="Username, if needed to access fiscal printer"),
+ 'password': fields.char("Password", size=15, required=False, help="Password, if needed to access fiscal printer"),
+ 'destination': fields.char("Destination", size=256, required=False, help="Destination directory, where receipt should be placed"),
+ 'dry': fields.boolean('Dry Run')
+ }
+
+ _sql_constraints = [
+ ('cash_register_uniq', 'unique(cash_register_id)', 'Cash register must be unique!')
+ ]
+
+
+class fp_driver(osv.osv):
+ _name = 'fp.driver'
+ _description = "Fiscal Printer Drivers"
+
+ _columns = {
+ 'name': fields.char("FP Description", size=32, required=True, help="The Description of the Fiscal Printer"),
+ 'class_name': fields.char("Class Name", size=32, required=True, help="Name of the class that contain driver for printing on Fiscal Printer"),
+ 'module': fields.char("Driver module name", size=32, required=True, help="The name of the driver module"),
+ }
+
+class department(osv.osv):
+ _name = 'department'
+ _description = "Tax department"
+
+ _columns = {
+ 'name': fields.char("Department description", size=32, required=True, help="The Description of the department"),
+ 'department': fields.integer("Department", required=True)
+ }
+
+class account_tax(osv.osv):
+ _inherit = 'account.tax'
+
+ _columns = {
+ 'department': fields.many2one('department', 'Department', required=False)
+ }
=== added file 'pos_fiscal_printer/fiscal_printer_view.xml'
--- pos_fiscal_printer/fiscal_printer_view.xml 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/fiscal_printer_view.xml 2013-11-21 22:34:57 +0000
@@ -0,0 +1,99 @@
+<openerp>
+ <data>
+
+ <record id="view_fp_config_form" model="ir.ui.view">
+ <field name="name">fp.config.form</field>
+ <field name="model">fp.config</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Configure Fiscal Printer">
+ <field name="name"/>
+ <field name="cash_register_id"/>
+ <field name="driver_id" />
+ <field name="ecr_password" />
+ <field name="host" />
+ <field name="port" />
+ <field name="user" />
+ <field name="password" />
+ <field name="destination" />
+ <separator colspan="4" />
+ <field name="dry" />
+ </form>
+ </field>
+ </record>
+
+ <record id="view_fp_config_tree" model="ir.ui.view">
+ <field name="name">fp.config.tree</field>
+ <field name="model">fp.config</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Cash Registers">
+ <field name="name"/>
+ <field name="host" />
+ <field name="cash_register_id"/>
+ <field name="driver_id" />
+ </tree>
+ </field>
+ </record>
+
+ <record id="action_fiscal_printer" model="ir.actions.act_window">
+ <field name="name">Fiscal Printer Configuration</field>
+ <field name="res_model">fp.config</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="view_fp_config_tree"/>
+ </record>
+
+ <!-- Menu Definitions -->
+ <menuitem id="cash_register" name="Fiscal Printer" parent="point_of_sale.menu_point_config_product" action="action_fiscal_printer"/>
+
+ <!-- Add field department to account.tax -->
+ <record id="view_account_tax_department_form" model="ir.ui.view">
+ <field name="name">account.tax.department.form</field>
+ <field name="model">account.tax</field>
+ <field name="inherit_id" ref="account.view_tax_form" />
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <field name="description" position="after">
+ <field name="department"/>
+ </field>
+ </field>
+ </record>
+
+ <record id="view_department_form" model="ir.ui.view">
+ <field name="name">department.form</field>
+ <field name="model">department</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Configure Department">
+ <field name="name"/>
+ <field name="department"/>
+ </form>
+ </field>
+ </record>
+
+ <record id="view_department_tree" model="ir.ui.view">
+ <field name="name">department.tree</field>
+ <field name="model">department</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Departments">
+ <field name="name"/>
+ <field name="department"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="action_department" model="ir.actions.act_window">
+ <field name="name">Department Configuration</field>
+ <field name="res_model">department</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="view_department_tree"/>
+ </record>
+
+ <!-- Menu Definitions -->
+ <menuitem id="department" name="Department Configuration" parent="point_of_sale.menu_point_config_product" action="action_department"/>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'pos_fiscal_printer/readme.txt'
--- pos_fiscal_printer/readme.txt 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/readme.txt 2013-11-21 22:34:57 +0000
@@ -0,0 +1,52 @@
+This module creates all that is necessary to print on a Fiscal Printer.
+
+The solution is composed of 2 modules:
+- pos_fiscal_printer - a core module
+- pos_fp_<driver name> - driver module
+
+Core module:
+- disable printing from browser
+- create configuration menu inside POS
+- add departments
+- load driver
+- prepare all the data needed for creating a receipt
+
+Driver module:
+- compose a receipt
+- sent receipt to printer
+
+Menu Fiscal Printer gives you a possibility to match a Fiscal Printer
+to a POS. Very often Fiscal Printers has only program for Windows,
+which they call "Driver". This resident program expects to find a
+receipt file in some directory. When it finds a new receipt it is
+interpreted and send to a printer. We can't write to a file system
+directly from JavaScript, so Client start printing and then Server
+send a receipt to a computer which has fiscal printer attached.
+Sometimes we also need to wright a password for the Fiscal Printer
+inside receipt. This is the reason for 2 password fields.
+
+Another type of printers accept printing via ethernet. Again we need
+host/user/password.
+
+An ECR (Electronic Cash Register) should register a
+"department". Department depends on tax paid, so inside my Dummy
+driver you will see:
+reparto = line.product_id.taxes_id[0].department.department
+
+Core module creates Department table, which should be compiled (in
+Italy there are 3 main Departments: Reparto 1, Reparto 2, Reparto 3)
+with corresponding numbers. These numbers are used to tell ECR to
+which department a product belongs. After compiling the table, one
+should go to Accounting / Configuration and select a right department
+for the tax.
+
+There is another option which can sound strange - "Dry Run". When we
+print a receipt and something goes wrong, receipt will not disappear,
+every time we try to print it will be send to a Fiscal Printer, so if
+for any reason a receipt can't be printed it will block our POS until
+the problem is resolved (a red ball appears in the right upper angle
+of the POS). In this case one can select "Dry Run" option and when one
+enters POS the queue will be emptied and a red ball became green. This
+option can also be used if some receipts were printed directly from
+ECR and one want to register this selling without reprinting a
+receipt.
=== added directory 'pos_fiscal_printer/security'
=== added file 'pos_fiscal_printer/security/ir.model.access.csv'
--- pos_fiscal_printer/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/security/ir.model.access.csv 2013-11-21 22:34:57 +0000
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_fp_config_user,fp.config_user,model_fp_config,point_of_sale.group_pos_user,1,1,1,1
+access_fp_config_manager,fp.config_manager,model_fp_config,point_of_sale.group_pos_manager,1,1,1,1
+access_fp_driver_user,fp.driver_user,model_fp_driver,point_of_sale.group_pos_user,1,1,1,1
+access_fp_driver_manager,fp.driver_manager,model_fp_driver,point_of_sale.group_pos_manager,1,1,1,1
+
=== added directory 'pos_fiscal_printer/static'
=== added directory 'pos_fiscal_printer/static/src'
=== added directory 'pos_fiscal_printer/static/src/js'
=== added file 'pos_fiscal_printer/static/src/js/pos_fiscal_printer.js'
--- pos_fiscal_printer/static/src/js/pos_fiscal_printer.js 1970-01-01 00:00:00 +0000
+++ pos_fiscal_printer/static/src/js/pos_fiscal_printer.js 2013-11-21 22:34:57 +0000
@@ -0,0 +1,65 @@
+openerp.pos_fiscal_printer = function(instance) {
+ // loading the namespace of the 'point_of_sale' module
+ var module = instance.point_of_sale;
+ var _t = instance.web._t;
+
+ module.ReceiptScreenWidget.include({
+
+ show: function(){
+ var self = this;
+
+ this.hidden = false;
+ if(this.$el){
+ this.$el.show();
+ }
+
+ if(this.pos_widget.action_bar.get_button_count() > 0){
+ this.show_action_bar();
+ }else{
+ this.hide_action_bar();
+ }
+
+ // we add the help button by default. we do this because the buttons are cleared on each refresh so that
+ // the button stay local to each screen
+ this.pos_widget.left_action_bar.add_new_button({
+ label: _t('Help'),
+ icon: '/point_of_sale/static/src/img/icons/png48/help.png',
+ click: function(){ self.help_button_action(); },
+ });
+
+ var self = this;
+ var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
+
+ this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode);
+ this.pos_widget.set_leftpane_visible(this.show_leftpane);
+ this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !cashier_mode);
+ this.pos_widget.set_cashier_controls_visible(cashier_mode);
+
+ if(cashier_mode && this.pos.iface_self_checkout){
+ this.pos_widget.client_button.show();
+ }else{
+ this.pos_widget.client_button.hide();
+ }
+ if(cashier_mode){
+ this.pos_widget.close_button.show();
+ }else{
+ this.pos_widget.close_button.hide();
+ }
+
+ this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
+
+ this.pos.barcode_reader.set_action_callback({
+ 'cashier': self.barcode_cashier_action ? function(ean){ self.barcode_cashier_action(ean); } : undefined ,
+ 'product': self.barcode_product_action ? function(ean){ self.barcode_product_action(ean); } : undefined ,
+ 'client' : self.barcode_client_action ? function(ean){ self.barcode_client_action(ean); } : undefined ,
+ 'discount': self.barcode_discount_action ? function(ean){ self.barcode_discount_action(ean); } : undefined,
+ });
+
+ this.add_action_button({
+ label: _t('Next Order'),
+ icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
+ click: function() { self.finishOrder(); },
+ });
+ },
+ });
+}
=== added directory 'pos_fp_dummy'
=== added file 'pos_fp_dummy/__init__.py'
--- pos_fp_dummy/__init__.py 1970-01-01 00:00:00 +0000
+++ pos_fp_dummy/__init__.py 2013-11-21 22:34:57 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2013 Didotech srl (<http://www.didotech.com>)
+# All Rights Reserved
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
=== added file 'pos_fp_dummy/__openerp__.py'
--- pos_fp_dummy/__openerp__.py 1970-01-01 00:00:00 +0000
+++ pos_fp_dummy/__openerp__.py 2013-11-21 22:34:57 +0000
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2013 Didotech.com (<http://www.didotech.com>)
+# All Rights Reserved
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+{
+ 'name': 'Dummy FP driver',
+ 'version': '0.0.1',
+ 'category': 'Point Of Sale',
+ 'description': """
+ This module contains Fiscal Printer "Dummy" driver.
+ It can be used as an example and for testing purpose.
+ """,
+ 'author': 'Andrei Levin',
+ 'depends': [
+ 'pos_fiscal_printer',
+ ],
+ 'init_xml': [
+ 'dummy.xml',
+ ],
+ 'update_xml': [
+ ],
+ 'demo_xml': [],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'pos_fp_dummy/driver.py'
--- pos_fp_dummy/driver.py 1970-01-01 00:00:00 +0000
+++ pos_fp_dummy/driver.py 2013-11-21 22:34:57 +0000
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+###############################################################################
+#
+# Copyright (c) 2013 Andrei Levin (andrei.levin at didotech.com)
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+import os
+import tempfile
+
+from pos_fiscal_printer.ecr import Ecr
+
+
+def italian_number(number, precision=1, no_zero=False):
+ if not number:
+ return '0,00'
+
+ if number < 0:
+ sign = '-'
+ else:
+ sign = ''
+ # Requires Python >= 2.7:
+ # before, after = "{:.{digits}f}".format(number, digits=precision).split('.')
+ # Works with Python 2.6:
+ before, after = "{0:10.{digits}f}".format(
+ number, digits=precision).strip('- ').split('.')
+
+ belist = []
+ end = len(before)
+ for i in range(3, len(before) + 3, 3):
+ start = len(before) - i
+ if start < 0:
+ start = 0
+ belist.append(before[start: end])
+ end = len(before) - i
+ before = '.'.join(reversed(belist))
+
+ if no_zero and int(number) == float(number):
+ return sign + before
+ else:
+ return sign + before + ',' + after
+
+
+class Dummy(Ecr):
+
+ def compose(self):
+ '''
+ line attributes:
+ product_id - Product
+ order_id - Order Ref
+ price_unit - Unit Price
+ price_subtotal - Subtotal w/o Tax
+ company_id - Company
+ price_subtotal_incl - Subtotal
+ qty - Quantity
+ discount - Discount (%)
+ name - Line No
+
+ '''
+ receipt = self.receipt_data
+ ticket = []
+ ticket.append(receipt.date.strftime(
+ '%d/%m/%Y %H:%M:%S') + ' ' + receipt.reference)
+ ticket.append(u'')
+ ticket.append(receipt.user.company_id.name)
+ ticket.append(u'Phone: ' + str(receipt.user.company_id.phone))
+ ticket.append(u'User: ' + receipt.user.name)
+ ticket.append(u'POS: ' + receipt.cash_register_name)
+ ticket.append(u'')
+
+ line = self.get_product_line()
+ while line:
+ qty = italian_number(line.qty, 1, True)
+
+ if line.product_id and line.product_id.taxes_id and line.product_id.taxes_id[0]:
+ reparto = line.product_id.taxes_id[0].department.department
+ else:
+ reparto = 1
+
+ ticket.append(u"rep={reparto} {name:<24}{qty: ^3}{price:>6.2f} €".format(
+ reparto=reparto, name=line.product_id.name, qty=qty, price=line.price_subtotal_incl))
+
+ if line.discount:
+ ticket.append(
+ u'With a {discount}% discount'.format(discount=line.discount))
+
+ line = self.get_product_line()
+
+ ticket.append(u'')
+
+ ticket.append(u'{text:<30}{subtotal:>9.2f} €'.format(
+ text='Subtotal: ', subtotal=self.subtotal))
+ ticket.append(
+ u'Tax: {tax:9.2f} €'.format(tax=receipt.amount_tax))
+ ticket.append(
+ u'Discount: {discount:9.2f} €'.format(discount=self.discount))
+ ticket.append(
+ u'Total: {total:9.2f} €'.format(total=receipt.amount_total))
+
+ ticket.append(u'')
+
+ for journal in receipt.journals:
+ ticket.append(u'{name:<30}{amount:9.2f} €'.format(
+ name=journal['extended_name'], amount=journal['amount']))
+
+ ticket.append(u'')
+
+ ticket.append(
+ u'Change: {a_return:9.2f} €'.format(a_return=receipt.amount_return))
+
+ return ticket
+
+ def print_receipt(self):
+ if self.config.destination:
+ destination = self.config.destination
+ else:
+ destination = os.path.join(tempfile.gettempdir(), 'opentmp')
+ if not os.path.exists(destination):
+ os.makedirs(destination)
+ ticket = 'ticket.txt'
+
+ file(os.path.join(destination, ticket), 'w').write(
+ unicode(self).encode('utf8'))
+
+ return True
=== added file 'pos_fp_dummy/dummy.xml'
--- pos_fp_dummy/dummy.xml 1970-01-01 00:00:00 +0000
+++ pos_fp_dummy/dummy.xml 2013-11-21 22:34:57 +0000
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<openerp>
+ <data>
+ <record id="fp_driver" model="fp.driver">
+ <field name="class_name">Dummy</field>
+ <field name="name">Dummy driver</field>
+ <field name="module">pos_fp_dummy</field>
+ </record>
+ </data>
+</openerp>
Follow ups