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