[FIX] hw_escops, point_of_sale: backporting fixes from the pos-restaurant branch
[odoo/odoo.git] / addons / hw_escpos / controllers / main.py
1 # -*- coding: utf-8 -*-
2 import commands
3 import logging
4 import simplejson
5 import os
6 import io
7 import base64
8 import openerp
9 import time
10 import random
11 import math
12 import md5
13 import openerp.addons.hw_proxy.controllers.main as hw_proxy
14 import subprocess
15 import traceback
16 from threading import Thread, Lock
17 from Queue import Queue, Empty
18
19 try:
20     import usb.core
21 except ImportError:
22     usb = None
23
24 try:
25     from .. import escpos
26     from ..escpos import printer
27     from ..escpos import supported_devices
28 except ImportError:
29     escpos = printer = None
30
31 from PIL import Image
32
33 from openerp import http
34 from openerp.http import request
35 from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
36 from openerp.tools.translate import _
37
38 _logger = logging.getLogger(__name__)
39
40
41 class EscposDriver(Thread):
42     def __init__(self):
43         Thread.__init__(self)
44         self.queue = Queue()
45         self.lock  = Lock()
46         self.status = {'status':'connecting', 'messages':[]}
47
48     def connected_usb_devices(self):
49         connected = []
50         
51         for device in supported_devices.device_list:
52             if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
53                 connected.append(device)
54         return connected
55
56     def lockedstart(self):
57         self.lock.acquire()
58         if not self.isAlive():
59             self.daemon = True
60             self.start()
61         self.lock.release()
62     
63     def get_escpos_printer(self):
64         try:
65             printers = self.connected_usb_devices()
66             if len(printers) > 0:
67                 self.set_status('connected','Connected to '+printers[0]['name'])
68                 return escpos.printer.Usb(printers[0]['vendor'], printers[0]['product'])
69             else:
70                 self.set_status('disconnected','Printer Not Found')
71                 return None
72         except Exception as e:
73             self.set_status('error',str(e))
74             return None
75
76     def get_status(self):
77         self.push_task('status')
78         return self.status
79
80
81
82     def open_cashbox(self,printer):
83         printer.cashdraw(2)
84         printer.cashdraw(5)
85
86     def set_status(self, status, message = None):
87         if status == self.status['status']:
88             if message != None and message != self.status['messages'][-1]:
89                 self.status['messages'].append(message)
90         else:
91             self.status['status'] = status
92             if message:
93                 self.status['messages'] = [message]
94             else:
95                 self.status['messages'] = []
96
97         if status == 'error' and message:
98             _logger.error('ESC/POS Error: '+message)
99         elif status == 'disconnected' and message:
100             _logger.warning('ESC/POS Device Disconnected: '+message)
101
102     def run(self):
103         if not escpos:
104             _logger.error('ESC/POS cannot initialize, please verify system dependencies.')
105             return
106         while True:
107             try:
108                 timestamp, task, data = self.queue.get(True)
109
110                 printer = self.get_escpos_printer()
111
112                 if printer == None:
113                     if task != 'status':
114                         self.queue.put((timestamp,task,data))
115                     time.sleep(5)
116                     continue
117                 elif task == 'receipt': 
118                     if timestamp >= time.time() - 1 * 60 * 60:
119                         self.print_receipt_body(printer,data)
120                         printer.cut()
121                 elif task == 'xml_receipt':
122                     if timestamp >= time.time() - 1 * 60 * 60:
123                         printer.receipt(data)
124                 elif task == 'cashbox':
125                     if timestamp >= time.time() - 12:
126                         self.open_cashbox(printer)
127                 elif task == 'printstatus':
128                     self.print_status(printer)
129                 elif task == 'status':
130                     pass
131
132             except Exception as e:
133                 self.set_status('error', str(e))
134                 errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n'
135                 _logger.error(errmsg);
136
137     def push_task(self,task, data = None):
138         self.lockedstart()
139         self.queue.put((time.time(),task,data))
140
141     def print_status(self,eprint):
142         localips = ['0.0.0.0','127.0.0.1','127.0.1.1']
143         ips =  [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
144         ips =  [ ip for ip in ips if ip not in localips ] 
145         eprint.text('\n\n')
146         eprint.set(align='center',type='b',height=2,width=2)
147         eprint.text('PosBox Status\n')
148         eprint.text('\n')
149         eprint.set(align='center')
150
151         if len(ips) == 0:
152             eprint.text('ERROR: Could not connect to LAN\n\nPlease check that the PosBox is correc-\ntly connected with a network cable,\n that the LAN is setup with DHCP, and\nthat network addresses are available')
153         elif len(ips) == 1:
154             eprint.text('IP Address:\n'+ips[0]+'\n')
155         else:
156             eprint.text('IP Addresses:\n')
157             for ip in ips:
158                 eprint.text(ip+'\n')
159
160         if len(ips) >= 1:
161             eprint.text('\nHomepage:\nhttp://'+ips[0]+':8069\n')
162
163         eprint.text('\n\n')
164         eprint.cut()
165
166     def print_receipt_body(self,eprint,receipt):
167
168         def check(string):
169             return string != True and bool(string) and string.strip()
170         
171         def price(amount):
172             return ("{0:."+str(receipt['precision']['price'])+"f}").format(amount)
173         
174         def money(amount):
175             return ("{0:."+str(receipt['precision']['money'])+"f}").format(amount)
176
177         def quantity(amount):
178             if math.floor(amount) != amount:
179                 return ("{0:."+str(receipt['precision']['quantity'])+"f}").format(amount)
180             else:
181                 return str(amount)
182
183         def printline(left, right='', width=40, ratio=0.5, indent=0):
184             lwidth = int(width * ratio) 
185             rwidth = width - lwidth 
186             lwidth = lwidth - indent
187             
188             left = left[:lwidth]
189             if len(left) != lwidth:
190                 left = left + ' ' * (lwidth - len(left))
191
192             right = right[-rwidth:]
193             if len(right) != rwidth:
194                 right = ' ' * (rwidth - len(right)) + right
195
196             return ' ' * indent + left + right + '\n'
197         
198         def print_taxes():
199             taxes = receipt['tax_details']
200             for tax in taxes:
201                 eprint.text(printline(tax['tax']['name'],price(tax['amount']), width=40,ratio=0.6))
202
203         # Receipt Header
204         if receipt['company']['logo']:
205             eprint.set(align='center')
206             eprint.print_base64_image(receipt['company']['logo'])
207             eprint.text('\n')
208         else:
209             eprint.set(align='center',type='b',height=2,width=2)
210             eprint.text(receipt['company']['name'] + '\n')
211
212         eprint.set(align='center',type='b')
213         if check(receipt['shop']['name']):
214             eprint.text(receipt['shop']['name'] + '\n')
215         if check(receipt['company']['contact_address']):
216             eprint.text(receipt['company']['contact_address'] + '\n')
217         if check(receipt['company']['phone']):
218             eprint.text('Tel:' + receipt['company']['phone'] + '\n')
219         if check(receipt['company']['vat']):
220             eprint.text('VAT:' + receipt['company']['vat'] + '\n')
221         if check(receipt['company']['email']):
222             eprint.text(receipt['company']['email'] + '\n')
223         if check(receipt['company']['website']):
224             eprint.text(receipt['company']['website'] + '\n')
225         if check(receipt['header']):
226             eprint.text(receipt['header']+'\n')
227         if check(receipt['cashier']):
228             eprint.text('-'*32+'\n')
229             eprint.text('Served by '+receipt['cashier']+'\n')
230
231         # Orderlines
232         eprint.text('\n\n')
233         eprint.set(align='center')
234         for line in receipt['orderlines']:
235             pricestr = price(line['price_display'])
236             if line['discount'] == 0 and line['unit_name'] == 'Unit(s)' and line['quantity'] == 1:
237                 eprint.text(printline(line['product_name'],pricestr,ratio=0.6))
238             else:
239                 eprint.text(printline(line['product_name'],ratio=0.6))
240                 if line['discount'] != 0:
241                     eprint.text(printline('Discount: '+str(line['discount'])+'%', ratio=0.6, indent=2))
242                 if line['unit_name'] == 'Unit(s)':
243                     eprint.text( printline( quantity(line['quantity']) + ' x ' + price(line['price']), pricestr, ratio=0.6, indent=2))
244                 else:
245                     eprint.text( printline( quantity(line['quantity']) + line['unit_name'] + ' x ' + price(line['price']), pricestr, ratio=0.6, indent=2))
246
247         # Subtotal if the taxes are not included
248         taxincluded = True
249         if money(receipt['subtotal']) != money(receipt['total_with_tax']):
250             eprint.text(printline('','-------'));
251             eprint.text(printline(_('Subtotal'),money(receipt['subtotal']),width=40, ratio=0.6))
252             print_taxes()
253             #eprint.text(printline(_('Taxes'),money(receipt['total_tax']),width=40, ratio=0.6))
254             taxincluded = False
255
256
257         # Total
258         eprint.text(printline('','-------'));
259         eprint.set(align='center',height=2)
260         eprint.text(printline(_('         TOTAL'),money(receipt['total_with_tax']),width=40, ratio=0.6))
261         eprint.text('\n\n');
262         
263         # Paymentlines
264         eprint.set(align='center')
265         for line in receipt['paymentlines']:
266             eprint.text(printline(line['journal'], money(line['amount']), ratio=0.6))
267
268         eprint.text('\n');
269         eprint.set(align='center',height=2)
270         eprint.text(printline(_('        CHANGE'),money(receipt['change']),width=40, ratio=0.6))
271         eprint.set(align='center')
272         eprint.text('\n');
273
274         # Extra Payment info
275         if receipt['total_discount'] != 0:
276             eprint.text(printline(_('Discounts'),money(receipt['total_discount']),width=40, ratio=0.6))
277         if taxincluded:
278             print_taxes()
279             #eprint.text(printline(_('Taxes'),money(receipt['total_tax']),width=40, ratio=0.6))
280
281         # Footer
282         if check(receipt['footer']):
283             eprint.text('\n'+receipt['footer']+'\n\n')
284         eprint.text(receipt['name']+'\n')
285         eprint.text(      str(receipt['date']['date']).zfill(2)
286                     +'/'+ str(receipt['date']['month']+1).zfill(2)
287                     +'/'+ str(receipt['date']['year']).zfill(4)
288                     +' '+ str(receipt['date']['hour']).zfill(2)
289                     +':'+ str(receipt['date']['minute']).zfill(2) )
290
291
292 driver = EscposDriver()
293
294 driver.push_task('printstatus')
295
296 hw_proxy.drivers['escpos'] = driver
297
298 class EscposProxy(hw_proxy.Proxy):
299     
300     @http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*')
301     def open_cashbox(self):
302         _logger.info('ESC/POS: OPEN CASHBOX') 
303         driver.push_task('cashbox')
304         
305     @http.route('/hw_proxy/print_receipt', type='json', auth='none', cors='*')
306     def print_receipt(self, receipt):
307         _logger.info('ESC/POS: PRINT RECEIPT') 
308         driver.push_task('receipt',receipt)
309
310     @http.route('/hw_proxy/print_xml_receipt', type='json', auth='none', cors='*')
311     def print_receipt(self, receipt):
312         _logger.info('ESC/POS: PRINT XML RECEIPT') 
313         driver.push_task('xml_receipt',receipt)
314