Merge lp:~andrei-levin/openerp-pos/openerp-pos into lp:openerp-pos
- openerp-pos
- Merge into 7.0
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 |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Yannick Vaucher @ Camptocamp | description | Needs Fixing | |
Review via email: mp+196201@code.launchpad.net |
Commit message
Description of the change
Added modules that gives possibility to print on a Fiscal Printer
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote : | # |
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:/
> 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
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> |
Please improve the description
Anything to do to setup the fiscol printer ?