← Back to team overview

openerp-community-reviewer team mailing list archive

[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