Merge lp:~andrei-levin/openerp-pos/openerp-pos into lp:openerp-pos

Proposed by Andrei Levin
Status: Needs review
Proposed branch: lp:~andrei-levin/openerp-pos/openerp-pos
Merge into: lp:openerp-pos
Diff against target: 857 lines (+791/-0)
12 files modified
pos_fiscal_printer/__init__.py (+23/-0)
pos_fiscal_printer/__openerp__.py (+47/-0)
pos_fiscal_printer/ecr.py (+75/-0)
pos_fiscal_printer/fiscal_printer.py (+211/-0)
pos_fiscal_printer/fiscal_printer_view.xml (+99/-0)
pos_fiscal_printer/readme.txt (+52/-0)
pos_fiscal_printer/security/ir.model.access.csv (+7/-0)
pos_fiscal_printer/static/src/js/pos_fiscal_printer.js (+65/-0)
pos_fp_dummy/__init__.py (+22/-0)
pos_fp_dummy/__openerp__.py (+41/-0)
pos_fp_dummy/driver.py (+139/-0)
pos_fp_dummy/dummy.xml (+10/-0)
To merge this branch: bzr merge lp:~andrei-levin/openerp-pos/openerp-pos
Reviewer Review Type Date Requested Status
Yannick Vaucher @ Camptocamp description Needs Fixing
Review via email: mp+196201@code.launchpad.net

Description of the change

Added modules that gives possibility to print on a Fiscal Printer

To post a comment you must log in.
Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

Please improve the description

Anything to do to setup the fiscol printer ?

review: Needs Fixing (description)
Revision history for this message
Andrei Levin (andrei-levin) wrote :

This module creates a base for printing to fiscal printer. It contains
all the functions that can be useful for creating a "driver" - a
simple module which contains functions that should be different for
various printer models.
The second module is a "dummy" driver that shows how to write a driver.

2014-03-14 10:49 GMT+01:00 Yannick Vaucher @ Camptocamp
<email address hidden>:
> Review: Needs Fixing description
>
> Please improve the description
>
> Anything to do to setup the fiscol printer ?
> --
> https://code.launchpad.net/~andrei-levin/openerp-pos/openerp-pos/+merge/196201
> You are the owner of lp:~andrei-levin/openerp-pos/openerp-pos.

--
Didotech Srl

Via T.Aspetti, 248
35133 Padova (PD)

Tel 049 8592286
Cell.: 347-2426694
www.didotech.com

3. By Andrei Levin

Fix: Resolved problems with non administrator user

4. By Andrei Levin

[pos_fiscal_printer]: Refactoring: removed duplicated code, added new function dry_print() to base Ecr class. This function save receipt as text file every time "dry run" is selected.

5. By Andrei Levin

[pos_fp_dummy]: Changed to use payments instead of journals

Unmerged revisions

5. By Andrei Levin

[pos_fp_dummy]: Changed to use payments instead of journals

4. By Andrei Levin

[pos_fiscal_printer]: Refactoring: removed duplicated code, added new function dry_print() to base Ecr class. This function save receipt as text file every time "dry run" is selected.

3. By Andrei Levin

Fix: Resolved problems with non administrator user

2. By Andrei Levin

Added readme file

1. By Andrei Levin

Initial commit

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'pos_fiscal_printer'
2=== added file 'pos_fiscal_printer/__init__.py'
3--- pos_fiscal_printer/__init__.py 1970-01-01 00:00:00 +0000
4+++ pos_fiscal_printer/__init__.py 2014-07-10 14:31:27 +0000
5@@ -0,0 +1,23 @@
6+# -*- coding: utf-8 -*-
7+##############################################################################
8+#
9+# Copyright (C) 2013 Didotech srl (<http://www.didotech.com>)
10+# All Rights Reserved
11+#
12+# This program is free software: you can redistribute it and/or modify
13+# it under the terms of the GNU Affero General Public License as published
14+# by the Free Software Foundation, either version 3 of the License, or
15+# (at your option) any later version.
16+#
17+# This program is distributed in the hope that it will be useful,
18+# but WITHOUT ANY WARRANTY; without even the implied warranty of
19+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+# GNU General Public License for more details.
21+#
22+# You should have received a copy of the GNU Affero General Public License
23+# along with this program. If not, see <http://www.gnu.org/licenses/>.
24+#
25+##############################################################################
26+
27+import fiscal_printer
28+
29
30=== added file 'pos_fiscal_printer/__openerp__.py'
31--- pos_fiscal_printer/__openerp__.py 1970-01-01 00:00:00 +0000
32+++ pos_fiscal_printer/__openerp__.py 2014-07-10 14:31:27 +0000
33@@ -0,0 +1,47 @@
34+# -*- coding: utf-8 -*-
35+##############################################################################
36+#
37+# Copyright (C) 2013-2014 Didotech srl (<http://www.didotech.com>)
38+# All Rights Reserved
39+#
40+# This program is free software: you can redistribute it and/or modify
41+# it under the terms of the GNU Affero General Public License as published
42+# by the Free Software Foundation, either version 3 of the License, or
43+# (at your option) any later version.
44+#
45+# This program is distributed in the hope that it will be useful,
46+# but WITHOUT ANY WARRANTY; without even the implied warranty of
47+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48+# GNU General Public License for more details.
49+#
50+# You should have received a copy of the GNU Affero General Public License
51+# along with this program. If not, see <http://www.gnu.org/licenses/>.
52+#
53+##############################################################################
54+{
55+ 'name': 'Fiscal Printer Management',
56+ 'version': '0.0.3',
57+ 'category': 'Point Of Sale',
58+ 'description': """
59+ This module adds possibility to print a receipt on fiscal printer
60+ It prepares all values you may need when writing driver for your printer.
61+
62+ """,
63+ 'author': 'Andrei Levin',
64+ 'depends': [
65+ 'point_of_sale',
66+ ],
67+ 'init_xml': [
68+ ],
69+ 'update_xml': [
70+ 'security/ir.model.access.csv', # load access rights after groups
71+ 'fiscal_printer_view.xml',
72+ ],
73+ 'demo_xml': [],
74+ 'installable': True,
75+ 'active': False,
76+ 'js': [
77+ 'static/src/js/pos_fiscal_printer.js',
78+ ],
79+}
80+
81
82=== added file 'pos_fiscal_printer/ecr.py'
83--- pos_fiscal_printer/ecr.py 1970-01-01 00:00:00 +0000
84+++ pos_fiscal_printer/ecr.py 2014-07-10 14:31:27 +0000
85@@ -0,0 +1,75 @@
86+# -*- coding: utf-8 -*-
87+###############################################################################
88+#
89+# Copyright (c) 2013-2014 Andrei Levin (andrei.levin at didotech.com)
90+# All Rights Reserved.
91+#
92+# This program is free software: you can redistribute it and/or modify
93+# it under the terms of the GNU General Public License as published by
94+# the Free Software Foundation, either version 3 of the License, or
95+# (at your option) any later version.
96+#
97+# This program is distributed in the hope that it will be useful,
98+# but WITHOUT ANY WARRANTY; without even the implied warranty of
99+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100+# GNU General Public License for more details.
101+#
102+# You should have received a copy of the GNU General Public License
103+# along with this program. If not, see <http://www.gnu.org/licenses/>.
104+#
105+###############################################################################
106+
107+import tempfile
108+import os
109+from datetime import datetime
110+
111+
112+class Ecr():
113+ reference = ''
114+
115+ def __init__(self, config, cash_register_name):
116+ self.__name__ = config.name
117+ self.config = config
118+ self.cash_register_name = cash_register_name
119+
120+ def get_product_line(self):
121+ if self.product_lines:
122+ line = self.product_lines.pop(0)
123+ self.subtotal += line.price_subtotal
124+ if line.discount:
125+ self.discount += line.product_id.list_price * line.qty - line.price_subtotal
126+ return line
127+ else:
128+ return False
129+
130+ def create(self, receipt):
131+ self.discount = 0.0
132+ self.subtotal = 0.0
133+ self.product_lines = receipt.lines
134+ self.receipt_data = receipt
135+ self.receipt_data.cash_register_name = self.cash_register_name
136+
137+ self.receipt = self.compose()
138+
139+ def __unicode__(self):
140+ return u'\r\n'.join(self.receipt) + u'\r\n\r\n'
141+
142+ def __str__(self):
143+ return u'\r\n'.join(self.receipt) + u'\r\n\r\n'
144+
145+ def compose(self):
146+ pass
147+
148+ def print_receipt(self):
149+ pass
150+
151+ def dry_print(self):
152+ destination = os.path.join(tempfile.gettempdir(), 'fp_tmp')
153+ if not os.path.exists(destination):
154+ os.makedirs(destination)
155+ ticket = datetime.now().strftime("%Y%m%d.%H%M") + '.txt'
156+
157+ file(os.path.join(destination, ticket), 'w').write(
158+ unicode(self).encode('utf8'))
159+
160+ return True
161
162=== added file 'pos_fiscal_printer/fiscal_printer.py'
163--- pos_fiscal_printer/fiscal_printer.py 1970-01-01 00:00:00 +0000
164+++ pos_fiscal_printer/fiscal_printer.py 2014-07-10 14:31:27 +0000
165@@ -0,0 +1,211 @@
166+# -*- coding: utf-8 -*-
167+###############################################################################
168+#
169+# Copyright (c) 2013-2014 Andrei Levin (andrei.levin at didotech.com)
170+# All Rights Reserved.
171+#
172+# This program is free software: you can redistribute it and/or modify
173+# it under the terms of the GNU General Public License as published by
174+# the Free Software Foundation, either version 3 of the License, or
175+# (at your option) any later version.
176+#
177+# This program is distributed in the hope that it will be useful,
178+# but WITHOUT ANY WARRANTY; without even the implied warranty of
179+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
180+# GNU General Public License for more details.
181+#
182+# You should have received a copy of the GNU General Public License
183+# along with this program. If not, see <http://www.gnu.org/licenses/>.
184+#
185+###############################################################################
186+
187+from openerp import netsvc
188+from openerp.osv import fields, osv
189+from openerp.tools.translate import _
190+
191+import time
192+import datetime
193+import pooler
194+
195+import logging
196+_logger = logging.getLogger(__name__)
197+_logger.setLevel(logging.INFO)
198+
199+
200+class pos_order(osv.osv):
201+ _inherit = "pos.order"
202+
203+ def create_from_ui(self, cr, uid, orders, context=None):
204+ #_logger.info("orders: %r", orders)
205+ order_ids = []
206+ # print orders
207+
208+ for tmp_order in orders:
209+ order = tmp_order['data']
210+ order_id = self.create(cr, uid, {
211+ 'name': order['name'],
212+ 'user_id': order['user_id'] or False,
213+ 'session_id': order['pos_session_id'],
214+ 'lines': order['lines'],
215+ 'pos_reference': order['name']
216+ }, context)
217+
218+ for payments in order['statement_ids']:
219+ payment = payments[2]
220+ self.add_payment(cr, uid, order_id, {
221+ 'amount': payment['amount'] or 0.0,
222+ 'payment_date': payment['name'],
223+ 'statement_id': payment['statement_id'],
224+ 'payment_name': payment.get('note', False),
225+ 'journal': payment['journal_id']
226+ }, context=context)
227+
228+ if order['amount_return']:
229+ session = self.pool['pos.session'].browse(
230+ cr, uid, order['pos_session_id'], context=context)
231+ cash_journal = session.cash_journal_id
232+
233+ if not cash_journal:
234+ cash_journal_ids = filter(
235+ lambda st: st.journal_id.type == 'cash', session.statement_ids)
236+ if not len(cash_journal_ids):
237+ raise osv.except_osv(_('error!'),
238+ _("No cash statement found for this session. Unable to record returned cash."))
239+ cash_journal = cash_journal_ids[0].journal_id
240+ self.add_payment(cr, uid, order_id, {
241+ 'amount': -order['amount_return'],
242+ 'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'),
243+ 'payment_name': _('return'),
244+ 'journal': cash_journal.id,
245+ }, context=context)
246+ order_ids.append(order_id)
247+ wf_service = netsvc.LocalService("workflow")
248+ wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr)
249+
250+ self.print_receipt(cr, uid, order_id, order['pos_session_id'], context)
251+
252+ return order_ids
253+
254+ def print_receipt(self, cr, uid, order_id, pos_session_id, context):
255+ receipt = Receipt(cr, uid, order_id, context)
256+ printer = fiscal_printer(cr, uid, pos_session_id)
257+ printer.print_receipt(receipt)
258+
259+
260+class Receipt():
261+ def __init__(self, cr, uid, order_id, context=None):
262+ self.pool = pooler.get_pool(cr.dbname)
263+
264+ self.order = self.pool['pos.order'].browse(cr, uid, order_id, context)
265+
266+ utc_date_order = datetime.datetime.strptime(
267+ self.order.date_order, '%Y-%m-%d %H:%M:%S')
268+ self.date = fields.datetime.context_timestamp(
269+ cr, uid, utc_date_order, context=context)
270+ self.name = self.order.name
271+ self.reference = self.order.pos_reference
272+ self.user = self.pool['res.users'].browse(cr, uid, uid)
273+
274+ self.lines = self.order.lines
275+ self.amount_total = self.order.amount_total
276+ self.amount_paid = self.order.amount_paid
277+ self.amount_tax = self.order.amount_tax
278+ self.amount_return = self.order.amount_return
279+ self.payments = self.order.statement_ids
280+
281+
282+class fiscal_printer():
283+ def __init__(self, cr, uid, pos_session_id):
284+ self.pool = pooler.get_pool(cr.dbname)
285+ fp_config_obj = self.pool['fp.config']
286+ pos_session = self.pool['pos.session'].browse(cr, uid, pos_session_id)
287+ cash_register_id = pos_session.config_id.id
288+
289+ printer_configs = fp_config_obj.search(
290+ cr, uid, [('cash_register_id', '=', cash_register_id)])
291+ self.cash_register = self.pool['pos.config'].browse(cr, uid, cash_register_id)
292+
293+ if printer_configs:
294+ self.config = fp_config_obj.browse(cr, uid, printer_configs[0])
295+ else:
296+ raise osv.except_osv(
297+ 'Warning', _('Cash register {cash_register} has no fiscal printers'.format(cash_register=self.cash_register.name)))
298+
299+ module = __import__(self.config.driver_id.module + '.driver', fromlist=[str(self.config.driver_id.class_name)])
300+ self.driver = getattr(module, str(self.config.driver_id.class_name))(self.config, self.cash_register.name)
301+
302+ def print_receipt(self, receipt):
303+ self.driver.create(receipt)
304+ if self.config.dry:
305+ _logger.info('Discarding receipt for order {0}'.format(receipt.reference))
306+ self.driver.dry_print()
307+ else:
308+ if self.driver.print_receipt():
309+ _logger.info('Receipt for {0} printed successfully'.format(receipt.reference))
310+ else:
311+ _logger.error('There are problems with printing Receipt for {0}'.format(receipt.reference))
312+
313+
314+class fp_config(osv.osv):
315+ _name = 'fp.config'
316+ _description = "Fiscal Printer configuration"
317+
318+ def _get_name(self, cr, uid, ids, field_name, arg, context=None):
319+ if not ids:
320+ return {}
321+
322+ res = {}
323+
324+ configs = self.browse(cr, uid, ids)
325+
326+ for config in configs:
327+ res[config.id] = config.cash_register_id.name + \
328+ ' : ' + config.driver_id.name
329+
330+ return res
331+
332+ _columns = {
333+ "name": fields.function(_get_name, string='Name', type='char', method=True),
334+ 'cash_register_id': fields.many2one('pos.config', 'Cash Register', required=True),
335+ "driver_id": fields.many2one('fp.driver', "Driver", required=True, help="Printer driver used for this client"),
336+ 'ecr_password': fields.char("Password ECR", size=16, required=False, help="Password, if needed to print to fiscal printer"),
337+ 'host': fields.char("Fiscal Printer Host Address", size=15, required=False, help="Fiscal Printer IP Address or Hostname (if Hostname can be resolved)"),
338+ 'port': fields.integer("Port", required=False, help="Fiscal Printer Port"),
339+ 'user': fields.char("Username", size=15, required=False, help="Username, if needed to access fiscal printer"),
340+ 'password': fields.char("Password", size=15, required=False, help="Password, if needed to access fiscal printer"),
341+ 'destination': fields.char("Destination", size=256, required=False, help="Destination directory, where receipt should be placed"),
342+ 'dry': fields.boolean('Dry Run')
343+ }
344+
345+ _sql_constraints = [
346+ ('cash_register_uniq', 'unique(cash_register_id)', 'Cash register must be unique!')
347+ ]
348+
349+
350+class fp_driver(osv.osv):
351+ _name = 'fp.driver'
352+ _description = "Fiscal Printer Drivers"
353+
354+ _columns = {
355+ 'name': fields.char("FP Description", size=32, required=True, help="The Description of the Fiscal Printer"),
356+ 'class_name': fields.char("Class Name", size=32, required=True, help="Name of the class that contain driver for printing on Fiscal Printer"),
357+ 'module': fields.char("Driver module name", size=32, required=True, help="The name of the driver module"),
358+ }
359+
360+
361+class department(osv.osv):
362+ _name = 'department'
363+ _description = "Tax department"
364+
365+ _columns = {
366+ 'name': fields.char("Department description", size=32, required=True, help="The Description of the department"),
367+ 'department': fields.integer("Department", required=True)
368+ }
369+
370+
371+class account_tax(osv.osv):
372+ _inherit = 'account.tax'
373+
374+ _columns = {
375+ 'department': fields.many2one('department', 'Department', required=False)
376+ }
377
378=== added file 'pos_fiscal_printer/fiscal_printer_view.xml'
379--- pos_fiscal_printer/fiscal_printer_view.xml 1970-01-01 00:00:00 +0000
380+++ pos_fiscal_printer/fiscal_printer_view.xml 2014-07-10 14:31:27 +0000
381@@ -0,0 +1,99 @@
382+<openerp>
383+ <data>
384+
385+ <record id="view_fp_config_form" model="ir.ui.view">
386+ <field name="name">fp.config.form</field>
387+ <field name="model">fp.config</field>
388+ <field name="type">form</field>
389+ <field name="arch" type="xml">
390+ <form string="Configure Fiscal Printer">
391+ <field name="name"/>
392+ <field name="cash_register_id"/>
393+ <field name="driver_id" />
394+ <field name="ecr_password" />
395+ <field name="host" />
396+ <field name="port" />
397+ <field name="user" />
398+ <field name="password" />
399+ <field name="destination" />
400+ <separator colspan="4" />
401+ <field name="dry" />
402+ </form>
403+ </field>
404+ </record>
405+
406+ <record id="view_fp_config_tree" model="ir.ui.view">
407+ <field name="name">fp.config.tree</field>
408+ <field name="model">fp.config</field>
409+ <field name="type">tree</field>
410+ <field name="arch" type="xml">
411+ <tree string="Cash Registers">
412+ <field name="name"/>
413+ <field name="host" />
414+ <field name="cash_register_id"/>
415+ <field name="driver_id" />
416+ </tree>
417+ </field>
418+ </record>
419+
420+ <record id="action_fiscal_printer" model="ir.actions.act_window">
421+ <field name="name">Fiscal Printer Configuration</field>
422+ <field name="res_model">fp.config</field>
423+ <field name="view_type">form</field>
424+ <field name="view_mode">tree,form</field>
425+ <field name="view_id" ref="view_fp_config_tree"/>
426+ </record>
427+
428+ <!-- Menu Definitions -->
429+ <menuitem id="cash_register" name="Fiscal Printer" parent="point_of_sale.menu_point_config_product" action="action_fiscal_printer"/>
430+
431+ <!-- Add field department to account.tax -->
432+ <record id="view_account_tax_department_form" model="ir.ui.view">
433+ <field name="name">account.tax.department.form</field>
434+ <field name="model">account.tax</field>
435+ <field name="inherit_id" ref="account.view_tax_form" />
436+ <field name="type">form</field>
437+ <field name="arch" type="xml">
438+ <field name="description" position="after">
439+ <field name="department"/>
440+ </field>
441+ </field>
442+ </record>
443+
444+ <record id="view_department_form" model="ir.ui.view">
445+ <field name="name">department.form</field>
446+ <field name="model">department</field>
447+ <field name="type">form</field>
448+ <field name="arch" type="xml">
449+ <form string="Configure Department">
450+ <field name="name"/>
451+ <field name="department"/>
452+ </form>
453+ </field>
454+ </record>
455+
456+ <record id="view_department_tree" model="ir.ui.view">
457+ <field name="name">department.tree</field>
458+ <field name="model">department</field>
459+ <field name="type">tree</field>
460+ <field name="arch" type="xml">
461+ <tree string="Departments">
462+ <field name="name"/>
463+ <field name="department"/>
464+ </tree>
465+ </field>
466+ </record>
467+
468+ <record id="action_department" model="ir.actions.act_window">
469+ <field name="name">Department Configuration</field>
470+ <field name="res_model">department</field>
471+ <field name="view_type">form</field>
472+ <field name="view_mode">tree,form</field>
473+ <field name="view_id" ref="view_department_tree"/>
474+ </record>
475+
476+ <!-- Menu Definitions -->
477+ <menuitem id="department" name="Department Configuration" parent="point_of_sale.menu_point_config_product" action="action_department"/>
478+
479+ </data>
480+</openerp>
481\ No newline at end of file
482
483=== added file 'pos_fiscal_printer/readme.txt'
484--- pos_fiscal_printer/readme.txt 1970-01-01 00:00:00 +0000
485+++ pos_fiscal_printer/readme.txt 2014-07-10 14:31:27 +0000
486@@ -0,0 +1,52 @@
487+This module creates all that is necessary to print on a Fiscal Printer.
488+
489+The solution is composed of 2 modules:
490+- pos_fiscal_printer - a core module
491+- pos_fp_<driver name> - driver module
492+
493+Core module:
494+- disable printing from browser
495+- create configuration menu inside POS
496+- add departments
497+- load driver
498+- prepare all the data needed for creating a receipt
499+
500+Driver module:
501+- compose a receipt
502+- sent receipt to printer
503+
504+Menu Fiscal Printer gives you a possibility to match a Fiscal Printer
505+to a POS. Very often Fiscal Printers has only program for Windows,
506+which they call "Driver". This resident program expects to find a
507+receipt file in some directory. When it finds a new receipt it is
508+interpreted and send to a printer. We can't write to a file system
509+directly from JavaScript, so Client start printing and then Server
510+send a receipt to a computer which has fiscal printer attached.
511+Sometimes we also need to wright a password for the Fiscal Printer
512+inside receipt. This is the reason for 2 password fields.
513+
514+Another type of printers accept printing via ethernet. Again we need
515+host/user/password.
516+
517+An ECR (Electronic Cash Register) should register a
518+"department". Department depends on tax paid, so inside my Dummy
519+driver you will see:
520+reparto = line.product_id.taxes_id[0].department.department
521+
522+Core module creates Department table, which should be compiled (in
523+Italy there are 3 main Departments: Reparto 1, Reparto 2, Reparto 3)
524+with corresponding numbers. These numbers are used to tell ECR to
525+which department a product belongs. After compiling the table, one
526+should go to Accounting / Configuration and select a right department
527+for the tax.
528+
529+There is another option which can sound strange - "Dry Run". When we
530+print a receipt and something goes wrong, receipt will not disappear,
531+every time we try to print it will be send to a Fiscal Printer, so if
532+for any reason a receipt can't be printed it will block our POS until
533+the problem is resolved (a red ball appears in the right upper angle
534+of the POS). In this case one can select "Dry Run" option and when one
535+enters POS the queue will be emptied and a red ball became green. This
536+option can also be used if some receipts were printed directly from
537+ECR and one want to register this selling without reprinting a
538+receipt.
539
540=== added directory 'pos_fiscal_printer/security'
541=== added file 'pos_fiscal_printer/security/ir.model.access.csv'
542--- pos_fiscal_printer/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
543+++ pos_fiscal_printer/security/ir.model.access.csv 2014-07-10 14:31:27 +0000
544@@ -0,0 +1,7 @@
545+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
546+access_fp_config_user,fp.config_user,model_fp_config,point_of_sale.group_pos_user,1,1,1,1
547+access_fp_config_manager,fp.config_manager,model_fp_config,point_of_sale.group_pos_manager,1,1,1,1
548+access_fp_driver_user,fp.driver_user,model_fp_driver,point_of_sale.group_pos_user,1,1,1,1
549+access_fp_driver_manager,fp.driver_manager,model_fp_driver,point_of_sale.group_pos_manager,1,1,1,1
550+access_department_user,fp.department_user,model_department,point_of_sale.group_pos_user,1,1,1,1
551+access_department,fp.department_manager,model_department,point_of_sale.group_pos_manager,1,1,1,1
552
553=== added directory 'pos_fiscal_printer/static'
554=== added directory 'pos_fiscal_printer/static/src'
555=== added directory 'pos_fiscal_printer/static/src/js'
556=== added file 'pos_fiscal_printer/static/src/js/pos_fiscal_printer.js'
557--- pos_fiscal_printer/static/src/js/pos_fiscal_printer.js 1970-01-01 00:00:00 +0000
558+++ pos_fiscal_printer/static/src/js/pos_fiscal_printer.js 2014-07-10 14:31:27 +0000
559@@ -0,0 +1,65 @@
560+openerp.pos_fiscal_printer = function(instance) {
561+ // loading the namespace of the 'point_of_sale' module
562+ var module = instance.point_of_sale;
563+ var _t = instance.web._t;
564+
565+ module.ReceiptScreenWidget.include({
566+
567+ show: function(){
568+ var self = this;
569+
570+ this.hidden = false;
571+ if(this.$el){
572+ this.$el.show();
573+ }
574+
575+ if(this.pos_widget.action_bar.get_button_count() > 0){
576+ this.show_action_bar();
577+ }else{
578+ this.hide_action_bar();
579+ }
580+
581+ // we add the help button by default. we do this because the buttons are cleared on each refresh so that
582+ // the button stay local to each screen
583+ this.pos_widget.left_action_bar.add_new_button({
584+ label: _t('Help'),
585+ icon: '/point_of_sale/static/src/img/icons/png48/help.png',
586+ click: function(){ self.help_button_action(); },
587+ });
588+
589+ var self = this;
590+ var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
591+
592+ this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode);
593+ this.pos_widget.set_leftpane_visible(this.show_leftpane);
594+ this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !cashier_mode);
595+ this.pos_widget.set_cashier_controls_visible(cashier_mode);
596+
597+ if(cashier_mode && this.pos.iface_self_checkout){
598+ this.pos_widget.client_button.show();
599+ }else{
600+ this.pos_widget.client_button.hide();
601+ }
602+ if(cashier_mode){
603+ this.pos_widget.close_button.show();
604+ }else{
605+ this.pos_widget.close_button.hide();
606+ }
607+
608+ this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
609+
610+ this.pos.barcode_reader.set_action_callback({
611+ 'cashier': self.barcode_cashier_action ? function(ean){ self.barcode_cashier_action(ean); } : undefined ,
612+ 'product': self.barcode_product_action ? function(ean){ self.barcode_product_action(ean); } : undefined ,
613+ 'client' : self.barcode_client_action ? function(ean){ self.barcode_client_action(ean); } : undefined ,
614+ 'discount': self.barcode_discount_action ? function(ean){ self.barcode_discount_action(ean); } : undefined,
615+ });
616+
617+ this.add_action_button({
618+ label: _t('Next Order'),
619+ icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
620+ click: function() { self.finishOrder(); },
621+ });
622+ },
623+ });
624+}
625
626=== added directory 'pos_fp_dummy'
627=== added file 'pos_fp_dummy/__init__.py'
628--- pos_fp_dummy/__init__.py 1970-01-01 00:00:00 +0000
629+++ pos_fp_dummy/__init__.py 2014-07-10 14:31:27 +0000
630@@ -0,0 +1,22 @@
631+# -*- coding: utf-8 -*-
632+##############################################################################
633+#
634+# Copyright (C) 2013 Didotech srl (<http://www.didotech.com>)
635+# All Rights Reserved
636+#
637+# This program is free software: you can redistribute it and/or modify
638+# it under the terms of the GNU Affero General Public License as published
639+# by the Free Software Foundation, either version 3 of the License, or
640+# (at your option) any later version.
641+#
642+# This program is distributed in the hope that it will be useful,
643+# but WITHOUT ANY WARRANTY; without even the implied warranty of
644+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645+# GNU General Public License for more details.
646+#
647+# You should have received a copy of the GNU Affero General Public License
648+# along with this program. If not, see <http://www.gnu.org/licenses/>.
649+#
650+##############################################################################
651+
652+
653
654=== added file 'pos_fp_dummy/__openerp__.py'
655--- pos_fp_dummy/__openerp__.py 1970-01-01 00:00:00 +0000
656+++ pos_fp_dummy/__openerp__.py 2014-07-10 14:31:27 +0000
657@@ -0,0 +1,41 @@
658+# -*- coding: utf-8 -*-
659+##############################################################################
660+#
661+# Copyright (C) 2013-2014 Didotech.com (<http://www.didotech.com>)
662+# All Rights Reserved
663+#
664+# This program is free software: you can redistribute it and/or modify
665+# it under the terms of the GNU Affero General Public License as published
666+# by the Free Software Foundation, either version 3 of the License, or
667+# (at your option) any later version.
668+#
669+# This program is distributed in the hope that it will be useful,
670+# but WITHOUT ANY WARRANTY; without even the implied warranty of
671+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
672+# GNU General Public License for more details.
673+#
674+# You should have received a copy of the GNU Affero General Public License
675+# along with this program. If not, see <http://www.gnu.org/licenses/>.
676+#
677+##############################################################################
678+{
679+ 'name': 'Dummy FP driver',
680+ 'version': '0.0.2',
681+ 'category': 'Point Of Sale',
682+ 'description': """
683+ This module contains Fiscal Printer "Dummy" driver.
684+ It can be used as an example and for testing purpose.
685+ """,
686+ 'author': 'Andrei Levin',
687+ 'depends': [
688+ 'pos_fiscal_printer',
689+ ],
690+ 'init_xml': [
691+ 'dummy.xml',
692+ ],
693+ 'update_xml': [
694+ ],
695+ 'demo_xml': [],
696+ 'installable': True,
697+ 'active': False,
698+}
699
700=== added file 'pos_fp_dummy/driver.py'
701--- pos_fp_dummy/driver.py 1970-01-01 00:00:00 +0000
702+++ pos_fp_dummy/driver.py 2014-07-10 14:31:27 +0000
703@@ -0,0 +1,139 @@
704+# -*- coding: utf-8 -*-
705+###############################################################################
706+#
707+# Copyright (c) 2013-2014 Andrei Levin (andrei.levin at didotech.com)
708+# All Rights Reserved.
709+#
710+# This program is free software: you can redistribute it and/or modify
711+# it under the terms of the GNU General Public License as published by
712+# the Free Software Foundation, either version 3 of the License, or
713+# (at your option) any later version.
714+#
715+# This program is distributed in the hope that it will be useful,
716+# but WITHOUT ANY WARRANTY; without even the implied warranty of
717+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
718+# GNU General Public License for more details.
719+#
720+# You should have received a copy of the GNU General Public License
721+# along with this program. If not, see <http://www.gnu.org/licenses/>.
722+#
723+###############################################################################
724+
725+import os
726+import tempfile
727+
728+from pos_fiscal_printer.ecr import Ecr
729+
730+
731+def italian_number(number, precision=1, no_zero=False):
732+ if not number:
733+ return '0,00'
734+
735+ if number < 0:
736+ sign = '-'
737+ else:
738+ sign = ''
739+ # Requires Python >= 2.7:
740+ # before, after = "{:.{digits}f}".format(number, digits=precision).split('.')
741+ # Works with Python 2.6:
742+ before, after = "{0:10.{digits}f}".format(
743+ number, digits=precision).strip('- ').split('.')
744+
745+ belist = []
746+ end = len(before)
747+ for i in range(3, len(before) + 3, 3):
748+ start = len(before) - i
749+ if start < 0:
750+ start = 0
751+ belist.append(before[start: end])
752+ end = len(before) - i
753+ before = '.'.join(reversed(belist))
754+
755+ if no_zero and int(number) == float(number):
756+ return sign + before
757+ else:
758+ return sign + before + ',' + after
759+
760+
761+class Dummy(Ecr):
762+ def compose(self):
763+ '''
764+ line attributes:
765+ product_id - Product
766+ order_id - Order Ref
767+ price_unit - Unit Price
768+ price_subtotal - Subtotal w/o Tax
769+ company_id - Company
770+ price_subtotal_incl - Subtotal
771+ qty - Quantity
772+ discount - Discount (%)
773+ name - Line No
774+ receipt attributes:
775+ payments
776+ '''
777+ receipt = self.receipt_data
778+ ticket = []
779+ ticket.append(receipt.date.strftime(
780+ '%d/%m/%Y %H:%M:%S') + ' ' + receipt.reference)
781+ ticket.append(u'')
782+ ticket.append(receipt.user.company_id.name)
783+ ticket.append(u'Phone: ' + str(receipt.user.company_id.phone))
784+ ticket.append(u'User: ' + receipt.user.name)
785+ ticket.append(u'POS: ' + receipt.cash_register_name)
786+ ticket.append(u'')
787+
788+ line = self.get_product_line()
789+ while line:
790+ qty = italian_number(line.qty, 1, True)
791+
792+ if line.product_id and line.product_id.taxes_id and line.product_id.taxes_id[0]:
793+ reparto = line.product_id.taxes_id[0].department.department
794+ else:
795+ reparto = 1
796+
797+ ticket.append(u"rep={reparto} {name:<24}{qty: ^3}{price:>6.2f} €".format(
798+ reparto=reparto, name=line.product_id.name, qty=qty, price=line.price_subtotal_incl))
799+
800+ if line.discount:
801+ ticket.append(
802+ u'With a {discount}% discount'.format(discount=line.discount))
803+
804+ line = self.get_product_line()
805+
806+ ticket.append(u'')
807+
808+ ticket.append(u'{text:<30}{subtotal:>9.2f} €'.format(
809+ text='Subtotal: ', subtotal=self.subtotal))
810+ ticket.append(
811+ u'Tax: {tax:9.2f} €'.format(tax=receipt.amount_tax))
812+ ticket.append(
813+ u'Discount: {discount:9.2f} €'.format(discount=self.discount))
814+ ticket.append(
815+ u'Total: {total:9.2f} €'.format(total=receipt.amount_total))
816+
817+ ticket.append(u'')
818+
819+ for payment in receipt.payments:
820+ ticket.append(u'{name:<30}{amount:9.2f} €'.format(
821+ name=payment.name_get()[0][1], amount=payment.amount))
822+
823+ ticket.append(u'')
824+
825+ ticket.append(
826+ u'Change: {a_return:9.2f} €'.format(a_return=receipt.amount_return))
827+
828+ return ticket
829+
830+ def print_receipt(self):
831+ if self.config.destination:
832+ destination = self.config.destination
833+ else:
834+ destination = os.path.join(tempfile.gettempdir(), 'opentmp')
835+ if not os.path.exists(destination):
836+ os.makedirs(destination)
837+ ticket = 'ticket.txt'
838+
839+ file(os.path.join(destination, ticket), 'w').write(
840+ unicode(self).encode('utf8'))
841+
842+ return True
843
844=== added file 'pos_fp_dummy/dummy.xml'
845--- pos_fp_dummy/dummy.xml 1970-01-01 00:00:00 +0000
846+++ pos_fp_dummy/dummy.xml 2014-07-10 14:31:27 +0000
847@@ -0,0 +1,10 @@
848+<?xml version="1.0"?>
849+<openerp>
850+ <data>
851+ <record id="fp_driver" model="fp.driver">
852+ <field name="class_name">Dummy</field>
853+ <field name="name">Dummy driver</field>
854+ <field name="module">pos_fp_dummy</field>
855+ </record>
856+ </data>
857+</openerp>

Subscribers

People subscribed via source and target branches