diff --git a/.gitignore b/.gitignore index bf5ebe4..a2dcb2f 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,7 @@ Icon? ehthumbs.db Thumbs.db - +build # Configuration files # ####################### *.yaml @@ -49,3 +49,5 @@ Thumbs.db .project .pydevproject *.pyc +*~ +.*.sw? diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..095cef8 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include README.md +include CHANGELOG.md +include LICENSE +include VERSION +recursive-include proxypos/config * +recursive-include proxypos/init * +recursive-include proxypos/proxypos_core/controlers/logo * +recursive-include proxypos/templates/templates * diff --git a/proxypos-server b/proxypos-server deleted file mode 100755 index c37433f..0000000 --- a/proxypos-server +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python2 -import proxypos - -if __name__ == "__main__": - proxypos.main() diff --git a/proxypos/LICENSE b/proxypos/LICENSE new file mode 100644 index 0000000..6a44bf8 --- /dev/null +++ b/proxypos/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013, Agustín Cruz Lozano. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/proxypos/VERSION b/proxypos/VERSION new file mode 100644 index 0000000..1cc5f65 --- /dev/null +++ b/proxypos/VERSION @@ -0,0 +1 @@ +1.1.0 \ No newline at end of file diff --git a/proxypos/__init__.py b/proxypos/__init__.py index b735e17..e69de29 100644 --- a/proxypos/__init__.py +++ b/proxypos/__init__.py @@ -1,72 +0,0 @@ -# -*- encoding: utf-8 -*- -########################################################################### -# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx/ -# All Rights Reserved. -# Coded by: Agustín Cruz Lozano (agustin.cruz@openpyme.mx) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software") to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -############################################################################## - -import os -import logging.config - -import yaml - -from bottle import run -from app import app - - -def setup_logging( - default_path='config/logging.yaml', - default_level=logging.INFO, - env_key='LOG_CFG' -): - """Setup logging configuration - - """ - path = default_path - value = os.getenv(env_key, None) - if value: - path = value - if os.path.exists(path): - with open(path, 'rt') as f: - config = yaml.load(f.read()) - logging.config.dictConfig(config) - else: - logging.basicConfig(level=default_level) - - -def main(): - # Set default configuration - port = '8069' - path = 'config/proxypos.yaml' - # Start logg - setup_logging() - # Interactive mode - logger = logging.getLogger(__name__) - logger.info("ProxyPos server starting up...") - - if os.path.exists(path): - with open(path, 'rt') as f: - config = yaml.load(f.read()) - port = config['port'] - - logger.info("Listening on http://%s:%s/" % ('localhost', port)) - run(app, host='localhost', port=port, quiet=True) diff --git a/config/logging.yaml.example b/proxypos/config/logging.yaml.example similarity index 100% rename from config/logging.yaml.example rename to proxypos/config/logging.yaml.example diff --git a/config/printer.yaml.example b/proxypos/config/printer.yaml.example similarity index 100% rename from config/printer.yaml.example rename to proxypos/config/printer.yaml.example diff --git a/config/proxypos.yaml.example b/proxypos/config/proxypos.yaml.example similarity index 100% rename from config/proxypos.yaml.example rename to proxypos/config/proxypos.yaml.example diff --git a/proxypos/gtk-proxypos.py b/proxypos/gtk-proxypos.py new file mode 100644 index 0000000..887ff7e --- /dev/null +++ b/proxypos/gtk-proxypos.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python2 + +from proxypos import proxypos_core +from proxypos import gtkgui +#For local testing comment avobe lines +#uncoment below +#import proxypos_core +#import gtkgui + +if __name__ == "__main__": + gtkgui.run(proxypos_core) diff --git a/proxypos/gtkgui/WConfig.py b/proxypos/gtkgui/WConfig.py new file mode 100644 index 0000000..8e82751 --- /dev/null +++ b/proxypos/gtkgui/WConfig.py @@ -0,0 +1,306 @@ +import os +import sys +sys.path.append("..") + +import gtk +import ConfigParser +from os.path import expanduser + +import logging + +from proxypos import templates +from proxypos.templates import get_templates + +# Init logger +logger = logging.getLogger(__name__) + +class POSTicketWidget(gtk.VBox): + label = 'Ticket' + current_format = 'image' + current_template = 'default' + config = None + templates = None + def __init__(self,config): + self.config = config + self.current_format = config.get('Ticket','templateType') + + gtk.VBox.__init__(self) + + TicketFormatBox = gtk.HBox() + button = gtk.RadioButton(None,"image") + button.connect('toggled',self.toggled,"image") + if self.current_format == "image": + button.set_active(True) + self.current_template=config.get('Ticket','templateImage') + self.templates = self._return_templates(['image']) + TicketFormatBox.pack_start(button,0,0,5) + + button = gtk.RadioButton(button,"text") + button.connect('toggled',self.toggled,'text') + if self.current_format == "text": + button.set_active(True) + self.current_template=config.get('Ticket','templateText') + self.templates = self._return_templates(['text']) + + TicketFormatBox.pack_start(button,0,0,5) + self.pack_start(TicketFormatBox,0,0,5) + + TicketTemplateBox = gtk.VBox() + lblName = gtk.Label("Template") + buff = gtk.TextBuffer() + self.__cmbTemplate = gtk.combo_box_new_text() + for i,name in enumerate(list(self.templates)): + width = self.templates[name]['width'] + self.__cmbTemplate.insert_text(i,"%s:%s px"%(name,width)) + if name == self.current_template: + self.__cmbTemplate.set_active(i) + buff.set_text(self.templates[name]['description']) + + self.__cmbTemplate.connect('changed',self.changed_cb) + + self.__txwDescription = gtk.TextView(buff) + TicketTemplateBox.pack_start(lblName,0,0,5) + TicketTemplateBox.pack_start(self.__cmbTemplate,0,0,5) + TicketTemplateBox.pack_start(self.__txwDescription,0,0,5) + self.pack_start(TicketTemplateBox,0,0,5) + + + def _return_templates(self,ttypes): + #Default paths + paths = [os.path.dirname(os.path.abspath(templates.__file__))+"/templates", + os.path.join(expanduser("~"),".proxypos/templates")] + + temp = {} + for path in paths: + temp.update(get_templates(path,ttypes,True)) + + return temp + + + def changed_cb(self,widget): + name = widget.get_active_text().split(":")[0] + self.current_template = name + description = self.templates[name]['description'] + buff = gtk.TextBuffer() + buff.set_text(description) + self.__txwDescription.set_buffer(buff) + + def toggled(self,widget, data=None): + if widget.get_active() == True: + self.current_format = data + if self.current_format == "image": + self.templates = self._return_templates(['image']) + self.current_template = self.config.get('Ticket','templateImage') + elif self.current_format == "text": + self.templates = self.templates = self._return_templates(['text']) + self.current_template = self.config.get('Ticket','templateText') + buff = gtk.TextBuffer() + self.__cmbTemplate.get_model().clear() + for i,name in enumerate(list(self.templates)): + width = self.templates[name]['width'] + self.__cmbTemplate.insert_text(i,"%s:%s px"%(name,width)) + if name == self.current_template: + self.__cmbTemplate.set_active(i) + buff.set_text(self.templates[name]['description']) + self.__txwDescription.set_buffer(buff) + + def save(self): + settings = {} + settings['templateType'] = self.current_format + if self.current_format == 'image': + settings['templateImage'] = self.current_template + settings['templateText'] = self.config.get('Ticket','templateText') + else: + settings['templateText'] = self.current_template + settings['templateImage'] = self.config.get('Ticket','templateImage') + + return settings + +class GeneralWidget(gtk.HBox): + label = 'General' + def __init__(self,config): + gtk.HBox.__init__(self) + lblUrl = gtk.Label('url') + self.__entUrl = gtk.Entry() + self.__entUrl.set_text(config.get('General','host')) + self.pack_start(lblUrl,0,0,5) + self.pack_start(self.__entUrl,0,0,5) + lblPort = gtk.Label('Port') + self.__entPort = gtk.Entry() + self.__entPort.set_text(config.get('General','port')) + self.pack_start(lblPort,0,0,5) + self.pack_start(self.__entPort,0,0,5) + + def save(self): + settings = {} + settings['host'] = self.__entUrl.get_text() + settings['port'] = self.__entPort.get_text() + + return settings + +class PrinterWidget(gtk.VBox): + label = 'ESC/POS printer' + ptype = {'USB':['USB (idVendor:idProduct)',('idVendor','idDevice')], + 'Network':['URL (192.168.1.2)',('host',)], + 'Serial': ['Serial (/dev/ttyS0)',('dev',)] + } + current_type = 'USB' + config = None + + def __init__(self,config): + + self.config = config + self.current_type = config.get('Printer','type') + gtk.VBox.__init__(self) + PrinterTypeBox = gtk.VBox() + button = gtk.RadioButton(None,"USB") + button.connect('toggled',self.toggled,'USB') + if self.current_type == 'USB': + button.set_active(True) + + PrinterTypeBox.pack_start(button,0,0,5) + button = gtk.RadioButton(button,"Netowrk") + button.connect('toggled',self.toggled,'Network') + if self.current_type == 'Network': + button.set_active(True) + + PrinterTypeBox.pack_start(button,0,0,5) + button = gtk.RadioButton(button,"Serial") + button.connect('toggled',self.toggled,'Serial') + PrinterTypeBox.pack_start(button,0,0,5) + if self.current_type == 'Serial': + button.set_active(True) + + self.pack_start(PrinterTypeBox,0,0,5) + + PrinterConfigBox = gtk.HBox() + self.__lblTypeConfig = gtk.Label(self.ptype[self.current_type][0]) + self.__entTypeConfig = gtk.Entry() + config_value=','.join([config.get('Printer',value) + for value in self.ptype[self.current_type][1]]) + self.__entTypeConfig.set_text(config_value) + PrinterConfigBox.pack_start(self.__lblTypeConfig,0,0,5) + PrinterConfigBox.pack_start(self.__entTypeConfig,0,0,5) + self.pack_start(PrinterConfigBox,0,0,5) + + #Printer Fonts Widths and pxWidth + #labels + lblWidthA = gtk.Label("WidthA") + lblWidthB = gtk.Label("WidthB") + lblpxWidth = gtk.Label("pxWidth") + lblcharSet = gtk.Label("charSet") + #Entries + self.__entWidthA = gtk.Entry() + self.__entWidthA.set_text(self.config.get("Printer","WidthA")) + self.__entWidthB = gtk.Entry() + self.__entWidthB.set_text(self.config.get("Printer","WidthB")) + self.__entpxWidth = gtk.Entry() + self.__entpxWidth.set_text(self.config.get("Printer","pxWidth")) + self.__entcharSet = gtk.Entry() + self.__entcharSet.set_text(self.config.get("Printer","charSet")) + PrinterSizeBox = gtk.Table(4,2,True) + PrinterSizeBox.attach(lblWidthA,0,1,0,1) + PrinterSizeBox.attach(lblWidthB,0,1,1,2) + PrinterSizeBox.attach(lblpxWidth,0,1,2,3) + PrinterSizeBox.attach(lblcharSet,0,1,3,4) + PrinterSizeBox.attach(self.__entWidthA,1,2,0,1) + PrinterSizeBox.attach(self.__entWidthB,1,2,1,2) + PrinterSizeBox.attach(self.__entpxWidth,1,2,2,3) + PrinterSizeBox.attach(self.__entcharSet,1,2,3,4) + self.pack_start(PrinterSizeBox,0,0,5) + + def toggled(self,widget, data=None): + if widget.get_active() == True: + self.__lblTypeConfig.set_label(self.ptype[data][0]) + config_value=','.join([self.config.get('Printer',value) + for value in self.ptype[data][1]]) + self.__entTypeConfig.set_text(config_value) + + + def save(self): + settings = {} + settings['type'] = self.current_type + for ptype in self.ptype: + if ptype == self.current_type: + values = self.__entTypeConfig.get_text().split(',') + for i, setting in enumerate(self.ptype[ptype][1]): + settings[setting] = values[i] + else: + for i, setting in enumerate(self.ptype[ptype][1]): + settings[setting] = self.config.get('Printer',setting) + + settings['WidthA'] = self.__entWidthA.get_text() + settings['WidthB'] = self.__entWidthB.get_text() + settings['pxWidth'] = self.__entpxWidth.get_text() + settings['charSet'] = self.__entcharSet.get_text() + + return settings + +pages ={ 'General': GeneralWidget, + 'Printer': PrinterWidget, + 'Ticket': POSTicketWidget, + } + +class WConfig(gtk.Window): + widgets = {} + config_path = expanduser("~") +"/.proxypos/config" + def __init__(self): + gtk.Window.__init__(self) + self.set_title("ESC/POS printer configuration") + self.set_default_size(200,200) + vbox = gtk.VBox() + + notebook = gtk.Notebook() + vbox.pack_start(notebook,0,0,5) + ControlBox = gtk.HBox() + vbox.pack_start(ControlBox,0,0,5) + btnCancel = gtk.Button("Cancel") + btnCancel.connect('clicked',self.cancel) + btnSave = gtk.Button("Save") + btnSave.connect('clicked',self.save) + ControlBox.pack_start(btnCancel,0,0,5) + ControlBox.pack_start(btnSave,0,0,5) + #Read current configuration + config = ConfigParser.RawConfigParser() + config.read(self.config_path +"/config.cfg") + + for page in ['General','Printer','Ticket']: + widget = pages[page](config) + label = gtk.Label(widget.label) + self.widgets[page] = widget + notebook.append_page(widget,label) + + self.connect('destroy', lambda w: gtk.main_quit()) + + self.add(vbox) + self.show_all() + + def save(self, widget): + new_config = ConfigParser.RawConfigParser() + filename = self.config_path+"/config.cfg" + new_config.add_section('General') + new_config.add_section('Printer') + new_config.add_section('Ticket') + for section in ['General','Printer','Ticket']: + settings = self.widgets[section].save() + for setting in settings: + new_config.set(section,setting,settings[setting]) + + #Save new config values + try: + with open(filename,'wb') as configfile: + new_config.write(configfile) + self.destroy() + except Exception, ex: + logger.error(ex) + + def cancel(self, widget): + self.destroy() + +def run(): + w = WConfig() + gtk.main() + +if __name__ == '__main__': + w = WConfig() + gtk.main() diff --git a/proxypos/gtkgui/__init__.py b/proxypos/gtkgui/__init__.py new file mode 100644 index 0000000..b326eaf --- /dev/null +++ b/proxypos/gtkgui/__init__.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os +import sys +sys.path.append("..") +import gtk +import multiprocessing +import signal +import ConfigParser +import io +from os.path import expanduser +import logging + +from proxypos.gtkgui import WConfig + +# Init logger +logger = logging.getLogger(__name__) + +default_config = """[General] +host = localhost +port = 8069 +[Printer] +type = USB +idVendor = 0x0416 +idDevice = 0x5011 +host = 192.168.1.2 +dev = /dev/ttyS0 +WidthA = 44 +widthB = 34 +pxWidth = 206 +charSet = \\x1b\\x52\\x12 +[Ticket] +templateType = image +templateImage = default +templateText = default_text +""" + +class SystrayIconApp: + proxypos_process = None + server = None + server_label = 'Start' + def __init__(self,handler): + #Check for config file + config_path = expanduser("~") +"/.proxypos/config" + temp_path = expanduser("~")+"/.proxypos/tmp" + template_path = expanduser("~")+"/.proxypos/templates" + paths_exists = [os.path.exists(path) for path in [config_path,temp_path,template_path]] + if sum(paths_exists) < 3: + os.makedirs(config_path) + os.makedirs(temp_path) + os.makedirs(template_path) + configfile = open(config_path+'/config.cfg','wb') + configfile.write(default_config) + configfile.close() + + self.server = handler.Server() + self.tray = gtk.StatusIcon() + self.tray.set_from_stock(gtk.STOCK_ABOUT) + self.tray.connect('popup-menu', self.on_right_click) + self.tray.set_tooltip(('ProxyPoS configurator')) + + def on_right_click(self, widget, event_type,event_time): + self.make_menu(widget, event_type, event_time) + + def make_menu(self, widget,event_type, event_time): + menu = gtk.Menu() + + self.start = gtk.MenuItem(self.server_label) + self.start.show() + menu.append(self.start) + self.start.connect('activate',self.start_stop_server) + + config = gtk.MenuItem('Configure') + config.show() + menu.append(config) + config.connect('activate',self.configure_form) + + reprint = gtk.MenuItem('Reprint ticket') + reprint.show() + menu.append(reprint) + reprint.connect('activate',self.reprint_ticket) + + cashbox = gtk.MenuItem('Open CashBox') + cashbox.show() + menu.append(cashbox) + cashbox.connect('activate',self.open_cashbox) + + about = gtk.MenuItem('About') + about.show() + menu.append(about) + about.connect('activate',self.show_about_dialog) + + quit = gtk.MenuItem('Quit') + quit.show() + menu.append(quit) + quit.connect('activate', self.quit) + menu.popup(None, None, gtk.status_icon_position_menu, event_type, event_time) + + def quit(self, widget): + if self.proxypos_process != None: + if self.proxypos_process.is_alive(): + self.proxypos_process.terminate() + gtk.main_quit() + + def start_stop_server(self,widget): + if self.server_label == 'Start': + self.proxypos_process = multiprocessing.Process(target=self.server.run) + self.proxypos_process.start() + self.server_label = 'Stop' + elif self.server_label == 'Stop': + self.proxypos_process.terminate() + self.server_label = 'Start' + + def configure_form(self,widget): + WConfig.run() + + def open_cashbox(self,widget): + from proxypos.proxypos_core.controlers import printer + logger.info('Opening cashbox') + try: + device = printer.device(config_from_gui=True) + device.open_cashbox() + except (SystemExit, KeyboardInterrupt): + raise + except Exception, ex: + logger.error('Failed to open cashbox', exc_info=True) + + def reprint_ticket(self,widget): + from proxypos.proxypos_core.controlers import printer + logger.info('Reprint receipt') + try: + device = printer.device(config_from_gui=True) + device.print_receipt(None,last=True) + except (SystemExit, KeyboardInterrupt): + raise + except Exception, ex: + logger.error('Failed to print receipt', exc_info=True) + + def show_about_dialog(self, widget): + about_dialog = gtk.AboutDialog() + about_dialog.set_destroy_with_parent (True) + about_dialog.set_icon_name ("SystrayIcon") + about_dialog.set_name('ProxyPoS GUI') + about_dialog.set_version('0.1') + about_dialog.set_copyright("(C) 2014 Alejandro Armagnac") + about_dialog.set_comments(("GUI for ProxyPoS to configure\nyour ESC/POS printer to be used in\nOpenERP")) + about_dialog.set_authors(['Alejandro Armagnac ']) + about_dialog.run() + about_dialog.destroy() + +def run(handler): + SystrayIconApp(handler) + gtk.main() diff --git a/init/sysvinit/ubuntu/proxypos b/proxypos/init/sysvinit/ubuntu/proxypos similarity index 100% rename from init/sysvinit/ubuntu/proxypos rename to proxypos/init/sysvinit/ubuntu/proxypos diff --git a/proxypos/proxypos-server b/proxypos/proxypos-server new file mode 100755 index 0000000..01c5e9f --- /dev/null +++ b/proxypos/proxypos-server @@ -0,0 +1,9 @@ +#!/usr/bin/env python2 + +from proxypos import proxypos_core + +#Without install testing +#import proxypos_core + +if __name__ == "__main__": + proxypos_core.main() diff --git a/proxypos/proxypos_core/__init__.py b/proxypos/proxypos_core/__init__.py new file mode 100644 index 0000000..7f3ccee --- /dev/null +++ b/proxypos/proxypos_core/__init__.py @@ -0,0 +1,123 @@ +# -*- encoding: utf-8 -*- +########################################################################### +# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx/ +# All Rights Reserved. +# Coded by: Agustín Cruz Lozano (agustin.cruz@openpyme.mx) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software") to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################## + +import os +from os.path import expanduser +import sys +import logging.config + +import yaml +import threading, thread +import multiprocessing + +import ConfigParser + +from bottle import ServerAdapter + +from app import app, set_config_from_gui, get_config_from_gui + +class WSGIRefServerAdapter(ServerAdapter): + server = None + + def run(self, handler): + from wsgiref.simple_server import make_server, WSGIRequestHandler + if self.quiet: + class QuietHandler(WSGIRequestHandler): + def log_request(*args, **kw): pass + self.options['handler_class'] = QuietHandler + self.server = make_server(self.host, self.port, handler, **self.options) + self.server.serve_forever() + + def stop(self): + self.server.shutdown() + +def setup_logging( + default_path='config/logging.yaml', + default_level=logging.INFO, + env_key='LOG_CFG' +): + """Setup logging configuration + + """ + path = default_path + value = os.getenv(env_key, None) + if value: + path = value + if os.path.exists(path): + with open(path, 'rt') as f: + config = yaml.load(f.read()) + logging.config.dictConfig(config) + else: + logging.basicConfig(level=default_level) + +class Server: + server = None + logger = None + def __init__(self): + # Start logg + setup_logging() + # Interactive mode + self.logger = logging.getLogger(__name__) + #self.server = WSGIRefServerAdapter(host=self.host,port=self.port) + + def run(self): + config_file = expanduser("~") +"/.proxypos/config/config.cfg" + config = ConfigParser.RawConfigParser() + config.read(config_file) + # Set configuration + host = config.get('General','host') + port = config.get('General','port') + self.server = WSGIRefServerAdapter(host=host,port=port) + self.logger.info("Listening on http://%s:%s/" % (host, port)) + #Enable read configuration generated by the GUI + set_config_from_gui() + try: + app.run(server=self.server,quiet=True) + except Exception, ex: + self.logger.error(ex) + +#Stand alone server (without gui) +def main(): + # Set default configuration + port = '8069' + path = 'config/proxypos.yaml' + # Start logg + setup_logging() + # Interactive mode + logger = logging.getLogger(__name__) + logger.info("ProxyPos server starting up...") + + if os.path.exists(path): + with open(path, 'rt') as f: + config = yaml.load(f.read()) + port = config['port'] + + logger.info("Listening on http://%s:%s/" % ('localhost', port)) + server = WSGIRefServerAdapter(host='localhost',port=port) + try: + app.run(server=server,quiet=True) + except Exception, ex: + print ex diff --git a/proxypos/app.py b/proxypos/proxypos_core/app.py similarity index 94% rename from proxypos/app.py rename to proxypos/proxypos_core/app.py index 378d7f9..c2f50b4 100644 --- a/proxypos/app.py +++ b/proxypos/proxypos_core/app.py @@ -27,21 +27,28 @@ import logging import simplejson as json -from bottle import Bottle, request +from bottle import Bottle, ServerAdapter, request from controlers import printer # Main web app app = Bottle() - +config_from_gui = False # Init logger logger = logging.getLogger(__name__) +def set_config_from_gui(): + global config_from_gui + config_from_gui = True + +def get_config_from_gui(): + return config_from_gui + # Helper function to actually print the receipt def do_print(receipt): logger.info('Print receipt %s', str(receipt)) try: - device = printer.device() + device = printer.device(config_from_gui=config_from_gui) device.print_receipt(receipt) except (SystemExit, KeyboardInterrupt): raise @@ -172,7 +179,7 @@ def open_cashbox(): """ logger.info('open_cashbox') try: - device = printer.device() + device = printer.device(config_from_gui=config_from_gui) device.open_cashbox() except (SystemExit, KeyboardInterrupt): raise diff --git a/proxypos/bottle.py b/proxypos/proxypos_core/bottle.py similarity index 100% rename from proxypos/bottle.py rename to proxypos/proxypos_core/bottle.py diff --git a/proxypos/controlers/__init__.py b/proxypos/proxypos_core/controlers/__init__.py similarity index 100% rename from proxypos/controlers/__init__.py rename to proxypos/proxypos_core/controlers/__init__.py diff --git a/proxypos/controlers/logo.jpg b/proxypos/proxypos_core/controlers/logo/logo.jpg similarity index 100% rename from proxypos/controlers/logo.jpg rename to proxypos/proxypos_core/controlers/logo/logo.jpg diff --git a/proxypos/controlers/printer.py b/proxypos/proxypos_core/controlers/printer.py similarity index 70% rename from proxypos/controlers/printer.py rename to proxypos/proxypos_core/controlers/printer.py index 7189d08..ce36571 100644 --- a/proxypos/controlers/printer.py +++ b/proxypos/proxypos_core/controlers/printer.py @@ -25,10 +25,17 @@ ############################################################################## import os +from os.path import expanduser + +import sys import yaml import logging +import ConfigParser + from escpos import printer +import proxypos.templates as t +from proxypos.templates import gen_receipt logger = logging.getLogger(__name__) @@ -38,116 +45,100 @@ class device: def __init__( self, default_path='config/printer.yaml', - env_key='LOG_CFG' + env_key='LOG_CFG', + config_from_gui = False ): """Setup printer configuration """ - path = default_path - value = os.getenv(env_key, None) - if value: - path = value - if os.path.exists(path): - with open(path, 'rt') as f: - self.config = yaml.load(f.read()) - # Init printer - ptype = self.config['printer']['type'].lower() - settings = self.config['printer']['settings'] - if ptype == 'usb': - self.printer = printer.Usb(settings['idVendor'], settings['idProduct']) - elif ptype == 'serial': - self.printer = printer.Serial(settings['devfile']) - elif ptype == 'network': - self.printer = printer.Network(settings['host']) - # Assign other default values - self.printer.pxWidth = settings['pxWidth'] - # Set default widh with normal value - self.printer.width = self.printer.widthA = settings['WidthA'] - self.printer.widthB = settings['WidthB'] - # Set correc table character - self.printer._raw(settings['charSet']) + if config_from_gui == False: + path = default_path + value = os.getenv(env_key, None) + if value: + path = value + if os.path.exists(path): + with open(path, 'rt') as f: + self.config = yaml.load(f.read()) + self.config['ticket'] = {} + self.config['ticket']['template_type'] = 'text' + else: + logger.critical('Could not read printer configuration') + sys.exit() else: - logger.critical('Could not read printer configuration') + config_file = expanduser("~") +"/.proxypos/config/config.cfg" + config = ConfigParser.RawConfigParser() + config.read(config_file) + self.config= {} + self.config['printer'] = {} + self.config['printer']['type'] = config.get('Printer','type') + self.config['printer']['settings'] = {} + self.config['printer']['settings']['idVendor'] = int(config.get('Printer','idVendor'),16) + self.config['printer']['settings']['idProduct'] = int(config.get('Printer','idDevice'),16) + self.config['printer']['settings']['devfile'] = config.get('Printer','dev') + self.config['printer']['settings']['host'] = config.get('Printer','host') + self.config['printer']['settings']['WidthA'] = config.getint('Printer','WidthA') + self.config['printer']['settings']['WidthB'] = config.getint('Printer','WidthB') + self.config['printer']['settings']['pxWidth'] = config.getint('Printer','pxWidth') + #TODO: Fix charSet + self.config['printer']['settings']['charSet'] = config.get('Printer','charSet') + self.config['ticket'] = {} + self.config['ticket']['template_type'] = config.get('Ticket','templateType') + self.config['ticket']['template_image'] = config.get('Ticket','templateImage') + self.config['ticket']['template_text'] = config.get('Ticket','templateText') + # Init printer + ptype = self.config['printer']['type'].lower() + settings = self.config['printer']['settings'] + if ptype == 'usb': + self.printer = printer.Usb(settings['idVendor'], settings['idProduct']) + elif ptype == 'serial': + self.printer = printer.Serial(settings['devfile']) + elif ptype == 'network': + self.printer = printer.Network(settings['host']) + # Assign other default values + self.printer.pxWidth = settings['pxWidth'] + # Set default widh with normal value + self.printer.width = self.printer.widthA = settings['WidthA'] + self.printer.widthB = settings['WidthB'] + # Set correc table character + self.printer._raw('\x1b\x52\x12') + #self.printer._raw(settings['charSet']) def open_cashbox(self): self.printer.cashdraw(2) - def print_receipt(self, receipt): + def print_receipt(self, receipt,last=False): """ Function used for print the recipt, currently is the only place where you can customize your printout. """ - path = os.path.dirname(__file__) - - filename = path + '/logo.jpg' - - self._printImgFromFile(filename) - - date = self._format_date(receipt['date']) - self._bold(False) - self._font('a') - self._lineFeed(1) - self._write(date, None, 'right') - self._lineFeed(1) - self._write(receipt['name'] + '\n', '', 'right') - self._write(receipt['company']['name'] + '\n') - self._write('RFC: ' + str(receipt['company']['company_registry']) + '\n') - self._write(str(receipt['company']['contact_address']) + '\n') - self._write('Telefono: ' + str(receipt['company']['phone']) + '\n') - self._write('Cajero: ' + str(receipt['cashier']) + '\n') - # self._write('Tienda: ' + receipt['store']['name']) - self._lineFeed(1) - for line in receipt['orderlines']: - left = ' '.join([str(line['quantity']), - line['unit_name'], - line['product_name'] - ]).encode('utf-8') - right = self._decimal(line['price_with_tax']) - self._write(left, right) - self._lineFeed(1) + #TODO: Use a sqlite DB or use oerplib to access to the last + # receipt. - self._lineFeed(2) - self._write('Subtotal:', self._decimal(receipt['total_without_tax']) + '\n') - self._write('IVA:', self._decimal(receipt['total_tax']) + '\n') - self._write('Descuento:', self._decimal(receipt['total_discount']) + '\n') - self._bold(True) - self._font('b') - self._write('TOTAL:', '$' + self._decimal(receipt['total_with_tax']) + '\n') - - # Set space for display payment methods - self._lineFeed(1) - self._font('a') - self._bold(False) - paymentlines = receipt['paymentlines'] - if paymentlines: - for payment in paymentlines: - self._write(payment['journal'], self._decimal(payment['amount'])) - - self._bold(True) - self._write('Cambio:', '$ ' + self._decimal(receipt['change'])) - self._bold(False) + last_receipt_file = expanduser("~") +"/.proxypos/config/last_receipt" + if last==True: + if not os.path.exists(last_receipt_file): + logger.error("No last receipt found") + else: + receipt = eval(open(last_receipt_file,'rb').read()) + else: + with open(last_receipt_file,'wb') as last_receipt: + last_receipt.write(str(receipt)) - # Write customer data - client = receipt['client'] - if client: - self._lineFeed(4) - self._write('Cliente: ' + client['name'].encode('utf-8')) - self._lineFeed(1) - self._write((u'Teléfono: ' + client['phone']).encode('utf-8')) - self._lineFeed(1) - self._write('Dirección: ' + client['contact_address'].encode('utf-8')) - self._lineFeed(1) + if self.config['ticket']['template_type'] == 'image': + template_name = self.config['ticket']['template_image'] + else: + template_name = self.config['ticket']['template_text'] - # Footer space - self._write('GRACIAS POR SU COMPRA', '', 'center') - self._lineFeedCut(1, True) + paths = [os.path.dirname(os.path.abspath(t.__file__))+"/templates", + os.path.join(expanduser("~"),".proxypos/templates")] + gen_receipt(self,template_name,receipt,paths) # Helper functions to facilitate printing def _format_date(self, date): - string = str(date['date']) + '/' + str(date['month']) + '/' + str(date['year']) + ' ' + str(date['hour']) + ':' + "%02d" % date['minute'] + string = str(date['date']) + '/' + str(date['month']+1) + '/' + str(date['year']) + ' ' + str(date['hour']) + ':' + "%02d" % date['minute'] return string def _write(self, string, rcolStr=None, align="left"): diff --git a/proxypos/templates/__init__.py b/proxypos/templates/__init__.py new file mode 100644 index 0000000..0a1877d --- /dev/null +++ b/proxypos/templates/__init__.py @@ -0,0 +1,186 @@ +import os +from os.path import expanduser +import sys +import json +from jinja2 import Template +import re +from proxypos.templates import commands + +#from test import test_rec + +mapping = {"order_name":lambda r: r['name'], + "total_tax": lambda r: r['total_tax'], + "shop_name": lambda r: r['shop']['name'], + "company_name": lambda r: r['company']['name'], + "company_website": lambda r: r['company']['website'], + "company_phone": lambda r: r['company']['phone'], + "company_email": lambda r: r['company']['email'], + "company_address":lambda r: r['company']['contact_address'], + "company_vat": lambda r: r['company']['vat'], + "company_registry": lambda r: r['company']['company_registry'], + "orderlines": lambda r: r['orderlines'], + "cashier": lambda r: r['cashier'], + "client": lambda r: r['client'], + "currency": lambda r: r['currency'], + "total_discount": lambda r: r['total_discount'], + "total_without_tax": lambda r: r['total_without_tax'], + "invoice_id": lambda r: r['invoice_id'], + "date": lambda r: r['date'], + "total_paid": lambda r: r['total_paid'], + "paymentlines": lambda r: r['paymentlines'], + "payment_amount": lambda r: r['paymentlines'][0]['amount'], + "payment_journal": lambda r: r['paymentlines'][0]['journal'], + "total_with_tax": lambda r: r["total_with_tax"], + "subtotal": lambda r: r['subtotal'], + "change": lambda r: r['change'], + } + +txt_function_mapping = { + 'image': lambda printer: printer._printImgFromFile, + 'font': lambda printer: printer._font, + 'bold': lambda printer: printer._bold, + 'linefeed': lambda printer: printer._lineFeed, + 'write': lambda printer: printer._write, + 'formatdate': lambda printer: printer._format_date, + 'cut':lambda printer: printer._lineFeedCut, + 'decimal': lambda printer: printer._decimal, + 'barcode': lambda printer: printer.printer.barcode, +} + +def find_templates(path): + try: + return [f[:-4] for f in os.listdir(path) + if f.endswith('.tmp')] + except OSError: + return [] + +def html_template_obs(printer,template_name,base_path,receipt): + temporal_path = os.path.join(expanduser("~"),".proxypos/tmp") + config = json.loads(open(os.path.join(base_path,template_name+".tmp")).read()) + template_path = os.path.join(base_path,config['path']) + css = os.path.join(template_path,config['files']['css']).decode("utf-8") + html = os.path.join(template_path,config['files']['template']).decode("utf-8") + logo = os.path.join(template_path,config['files']['template']).decode("utf-8") + + query = 'esc="(.*)"' + query = '' + template_buffer = open(html,"rb").read() + if config['files']['logo']!="": + template_buffer = template_buffer.replace("logo",logo) + requested_values = re.findall(query,template_buffer) + for requested_value in requested_values: + query = '' + value = mapping[requested_value](receipt) + if value == False: + value = "False" + template_buffer = re.sub(query,value,template_buffer) + filename = os.path.join(temporal_path,"ticket.html") + try: + f = open(filename,"wb") + f.write(template_buffer) + f.close() + files = commands.wkhtmltoimage(filename,css,config["width"].decode("utf-8")) + for f in sorted(files): + printer.printer.image(os.path.join(temporal_path,f)) + printer._lineFeedCut(1,True) + except OSError: + print "Error: Cannot write to ticket.html" + +def html_template(printer,template_name,base_path,receipt): + temporal_path = os.path.join(expanduser("~"),".proxypos/tmp") + config = json.loads(open(os.path.join(base_path,template_name+".tmp")).read()) + template_path = os.path.join(base_path,config['path']) + css = os.path.join(template_path,config['files']['css']).decode("utf-8") + html = os.path.join(template_path,config['files']['template']).decode("utf-8") + logo = os.path.join(template_path,config['files']['template']).decode("utf-8") + + template = open(os.path.join(template_path,config['files']['template'])).read() + logo = os.path.join(template_path,config['files']['logo']) + if logo != "": + logo = "{{image('"+logo+"')}}" + + template = template.replace("logo",logo) + tmp = Template(template) + #Make visible mapping functions inside the jinja2 templates + for func in mapping: + tmp.globals[func] = mapping[func](receipt) + for func in txt_function_mapping: + tmp.globals[func] = txt_function_mapping[func](printer) + tmp.globals['str'] = str + + filename = os.path.join(temporal_path,"ticket.html") + new_file = tmp.render(logo=logo) + try: + f = open(filename,"wb") + f.write(new_file) + f.close() + files = commands.wkhtmltoimage(filename,css,config["width"].decode("utf-8")) + for f in sorted(files): + printer.printer.image(os.path.join(temporal_path,f)) + + printer._lineFeedCut(1,True) + except OSError: + print "Error: Cannot write to ticket.html" + + +def text_template(printer,template_name,base_path,receipt): + config = json.loads(open(os.path.join(base_path,template_name+".tmp")).read()) + template_path = os.path.join(base_path,config['path']) + template = open(os.path.join(template_path,config['files']['template'])).read() + logo = os.path.join(template_path,config['files']['logo']) + if logo != "": + logo = "{{image('"+logo+"')}}" + + template = template.replace("logo",logo) + tmp = Template(template) + #Make visible mapping functions inside the jinja2 templates + for func in mapping: + tmp.globals[func] = mapping[func](receipt) + for func in txt_function_mapping: + tmp.globals[func] = txt_function_mapping[func](printer) + tmp.globals['str'] = str + #Print the ticket + tmp.render() + + +template_types = {'image': html_template, + 'text': text_template, + } + +def get_templates(path,types=['image','text'],full=False): + _templates = {} + for tmp in find_templates(path): + f = open(path+"/"+tmp+".tmp","rb").read() + template_conf = json.loads(f) + if template_conf['type'] in types: + template_name = template_conf['name'] + template_type = template_conf['type'] + if full: + _templates[tmp] = template_conf + else: + _templates[tmp] = (template_type.lower(),path,template_name) + + return _templates + +def gen_receipt(printer,template_name,receipt,paths): + templates = {} + for path in paths: + templates.update(get_templates(path)) + if template_name in list(templates): + template_type,base_path,_ = templates[template_name] + template_types[template_type](printer,template_name,base_path,receipt) + +if __name__ == "__main__": + #paths = [os.path.dirname(os.path.abspath(__file__))+"/templates",] + #get_templates(os.path.dirname(os.path.abspath(__file__))) + #gen_receipt('default',test_rec,paths) + from proxypos.proxypos_core.controlers import printer + dev = printer.device(config_from_gui=True) + tmp = Template(open('test.txt').read()) + for func in mapping: + tmp.globals[func] = mapping[func](test_rec) + for func in txt_function_mapping: + tmp.globals[func] = txt_function_mapping[func](dev) + tmp.globals['str'] = str + + tmp.render() diff --git a/proxypos/templates/commands.py b/proxypos/templates/commands.py new file mode 100644 index 0000000..d2184f1 --- /dev/null +++ b/proxypos/templates/commands.py @@ -0,0 +1,26 @@ +import os +import sys +import subprocess + +def convert(width,img,output): + temporal_path = os.path.dirname(img) + cmd = "convert -crop %sx255 %s %s"%(width,img,output) + s = subprocess.Popen(cmd,shell=True,stderr=subprocess.PIPE) + + s1,s2 = s.communicate() + + try: + return [f for f in os.listdir(temporal_path) + if f.startswith("pticket") and f.endswith(".png")] + except OSError: + return [] + +def wkhtmltoimage(html,css,width): + temporal_path = os.path.dirname(html) + img = os.path.join(temporal_path,"ticket.png") + output = os.path.join(temporal_path,"pticket.png") + cmd = "wkhtmltoimage --user-style-sheet %s --crop-w %s %s %s"%(css,width,html,img) + s = subprocess.Popen(cmd,shell=True, stderr=subprocess.PIPE) + s1,s2 = s.communicate() + + return convert(width,img,output) diff --git a/proxypos/templates/templates/default.tmp b/proxypos/templates/templates/default.tmp new file mode 100644 index 0000000..b3fe711 --- /dev/null +++ b/proxypos/templates/templates/default.tmp @@ -0,0 +1,18 @@ +{ + "name": "Template name", + "type": "image", + "description":"Description of this template", + "path": "default", + "files": { + "css":"style.css", + "template":"default.html", + "logo":"logo.jpg" + }, + "width":"420", + "format":"html", + "author" :{ + "name":"Alejandro Armagnac", + "email": "aarmagnac@yahoo.com.mx", + "web": "some_web" + } +} diff --git a/proxypos/templates/templates/default/default.html b/proxypos/templates/templates/default/default.html new file mode 100644 index 0000000..00c9e63 --- /dev/null +++ b/proxypos/templates/templates/default/default.html @@ -0,0 +1,55 @@ + + + + + +
+ +
+ + {% for line in orderlines %} + {% set left = ' '.join([str(line['quantity']), + line['unit_name'], + line['product_name'] + ]).encode('utf-8') + %} + {% set right = decimal(line['price_with_tax'])%} + + {% endfor %} +
{{left}}{{right}}
+
+
+ Subtotal: {{ decimal(total_without_tax)}}
+ IVA: {{decimal(total_tax)}}
+ Descuento: {{decimal(total_discount)}}
+ TOTAL: $ {{decimal(total_with_tax)}}
+ {% for payment in paymentlines %} + {{payment['journal']}} {{decimal(payment['amount'])}}
+ {% endfor %}
+ + Cambio: $ {{decimal(change)}}
+ {% if client %} + Cliente: {{client['name'].encode('utf-8')}}
+ {% endif %}
+
+ +
+ + diff --git a/proxypos/templates/templates/default/logo.jpg b/proxypos/templates/templates/default/logo.jpg new file mode 100644 index 0000000..042ef21 Binary files /dev/null and b/proxypos/templates/templates/default/logo.jpg differ diff --git a/proxypos/templates/templates/default/style.css b/proxypos/templates/templates/default/style.css new file mode 100644 index 0000000..8b97079 --- /dev/null +++ b/proxypos/templates/templates/default/style.css @@ -0,0 +1,23 @@ +#pos-ticket { + padding:10px 0px 0px 0px; + width: 412px; + font-family:sans-serif; + font-size: 22px; + background: #FFF; +}; + +#pos-ticket h1{ + font-size: 22px; + font-width: bold; +} +#orderlines { + padding: 10px 0px 0px 0px; + font-size:22px; + font-weight:normal; +} +#footer { + padding: 10px 20px 0px + font-size:22px; + font-weight:bold; +} + diff --git a/proxypos/templates/templates/default_text.tmp b/proxypos/templates/templates/default_text.tmp new file mode 100644 index 0000000..2ff9ec6 --- /dev/null +++ b/proxypos/templates/templates/default_text.tmp @@ -0,0 +1,17 @@ +{ + "name": "default_text", + "type": "text", + "description":"Default Template for text type template", + "path": "default_text", + "files": { + "template":"default_text", + "logo":"logo.jpg" + }, + "width":"206", + "format":"jinja2", + "author" :{ + "name":"Alejandro Armagnac", + "email": "aarmagnac@yahoo.com.mx", + "web": "some_web" + } +} diff --git a/proxypos/templates/templates/default_text/default_text b/proxypos/templates/templates/default_text/default_text new file mode 100644 index 0000000..3f59038 --- /dev/null +++ b/proxypos/templates/templates/default_text/default_text @@ -0,0 +1,53 @@ +{# Header Section #} +logo +{{bold(False)}} +{{font('a')}} +{{write("\n"+formatdate(date)+"\n",None,'left')}} +{{write(order_name+"\n",'','left')}} + +{{write(company_name + '\n')}} +{{write('RFC: ' + str(company_registry) + '\n')}} +{{write('Telefono: ' + str(company_phone) + '\n')}} +{{write('Cajero: ' + cashier + '\n')}} +{{linefeed(1)}} +{# Order section #} +{% for line in orderlines %} +{% set left = ' '.join([str(line['quantity']), + line['unit_name'], + line['product_name'] + ]).encode('utf-8') +%} +{% set right = decimal(line['price_with_tax'])%} +{{write(left,right)}} +{{linefeed(1)}} +{% endfor %} +{{linefeed(2)}} +{{write('Subtotal:', decimal(total_without_tax) + '\n')}} +{{write('IVA:', decimal(total_tax) + '\n')}} +{{write('Descuento:', decimal(total_discount) + '\n')}} +{{bold(True)}} +{{write('TOTAL:', '$' + decimal(total_with_tax) + '\n')}} +{{linefeed(1)}} +{{font('a')}} +{{bold(False)}} + +{% for payment in paymentlines %} +{{write(payment['journal'], decimal(payment['amount'])+'\n')}} +{% endfor %}} + +{{bold(True)}} +{{write('Cambio:', '$ ' + decimal(change)+'\n')}} +{{bold(False)}} +{% if client %} +{{write('Cliente: ' + client['name'].encode('utf-8'))}} +{% endif %} + +{# Footer #} + +{{write('Recibo sin validez fiscal.\n', '', 'left')}} +{{write('Si requiere factura, solicitarla dentro de los proximos 5 dias','','left')}} +{{write('\n'+str(company_website)+'\n','','left')}} +{{write('\n'+str(company_email)+'\n','','left')}} +{{barcode(order_name[6:],'EAN13',64,2,'BELOW','A')}} + +{{cut(1,True)}} diff --git a/proxypos/templates/templates/default_text/logo.jpg b/proxypos/templates/templates/default_text/logo.jpg new file mode 100644 index 0000000..042ef21 Binary files /dev/null and b/proxypos/templates/templates/default_text/logo.jpg differ diff --git a/proxypos/templates/test.py b/proxypos/templates/test.py new file mode 100644 index 0000000..dea9d51 --- /dev/null +++ b/proxypos/templates/test.py @@ -0,0 +1,2 @@ +test_rec = {'shop': {'name': 'Your Company'}, 'total_tax': 0, 'name': 'Order 1394761832318', 'total_with_tax': 227, 'company': {'website': 'www.yourcompany.com', 'name': 'Your Company', 'phone': False, 'contact_address': '\n\n \n', 'email': 'info@yourcompany.com', 'vat': False, 'company_registry': False}, 'orderlines': [{'unit_name': 'Hora(s)', 'product_description_sale': False, 'price_display': 150, 'price': 75, 'tax': 0, 'product_description': False, 'price_with_tax': 150, 'price_without_tax': 150, 'discount': 0, 'product_name': 'Servicio', 'quantity': 2}, {'unit_name': 'Unidad(es)', 'product_description_sale': False, 'price_display': 2, 'price': 1, 'tax': 0, 'product_description': False, 'price_with_tax': 2, 'price_without_tax': 2, 'discount': 0, 'product_name': 'Unreferenced Products', 'quantity': 2}, {'unit_name': 'Hora(s)', 'product_description_sale': False, 'price_display': 75, 'price': 75, 'tax': 0, 'product_description': False, 'price_with_tax': 75, 'price_without_tax': 75, 'discount': 0, 'product_name': 'Servicio', 'quantity': 1}], 'cashier': 'Administrator', 'total_without_tax': 227, 'currency': {'rounding': 0.01, 'position': 'after', 'symbol': '$', 'id': 34, 'accuracy': 4}, 'client': None, 'total_discount': 0, 'invoice_id': None, 'date': {'hour': 19, 'month': 2, 'year': 2014, 'date': 13, 'day': 4, 'minute': 50}, 'total_paid': 230, 'paymentlines': [{'amount': 230, 'journal': 'Efectivo (MXN)'}], 'subtotal': 227, 'change': 3} + diff --git a/proxypos/templates/test.txt b/proxypos/templates/test.txt new file mode 100644 index 0000000..e577a42 --- /dev/null +++ b/proxypos/templates/test.txt @@ -0,0 +1,55 @@ +{# Header Section #} +{# logo #} +{{image('logo')}} + +{{bold(False)}} +{{font('a')}} +{{write("\n"+formatdate(date)+"\n",None,'left')}} +{{write(order_name+"\n",'','left')}} + +{{write(company_name + '\n')}} +{{write('RFC: ' + str(company_registry) + '\n')}} +{{write('Telefono: ' + str(company_phone) + '\n')}} +{{write('Cajero: ' + cashier + '\n')}} +{{linefeed(1)}} +{# Order section #} +{% for line in orderlines %} +{% set left = ' '.join([str(line['quantity']), + line['unit_name'], + line['product_name'] + ]).encode('utf-8') +%} +{% set right = decimal(line['price_with_tax'])%} +{{write(left,right)}} +{{linefeed(1)}} +{% endfor %} +{{linefeed(2)}} +{{write('Subtotal:', decimal(total_without_tax) + '\n')}} +{{write('IVA:', decimal(total_tax) + '\n')}} +{{write('Descuento:', decimal(total_discount) + '\n')}} +{{bold(True)}} +{{write('TOTAL:', '$' + decimal(total_with_tax) + '\n')}} +{{linefeed(1)}} +{{font('a')}} +{{bold(False)}} + +{% for payment in paymentlines %} +{{write(payment['journal'], decimal(payment['amount'])+'\n')}} +{% endfor %}} + +{{bold(True)}} +{{write('Cambio:', '$ ' + decimal(change)+'\n')}} +{{bold(False)}} +{% if client %} +{{write('Cliente: ' + client['name'].encode('utf-8'))}} +{% endif %} + +{# Footer #} + +{{write('Recibo sin validez fiscal.\n', '', 'left')}} +{{write('Si requiere factura, solicitarla dentro de los proximos 5 dias','','left')}} +{{write('\n'+str(company_website)+'\n','','left')}} +{{write('\n'+str(company_email)+'\n','','left')}} +{{barcode(order_name[6:],'EAN13',64,2,'BELOW','A')}} + +{{cut(1,True)}} diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..95f878b --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +from distutils.core import setup +from distutils.command.install import INSTALL_SCHEMES +import os + +packages, data_files = {},[] +root_dir = os.path.dirname(__file__) +if root_dir != "": + os.chdir(root_dir) +proxypos_dir = "proxypos" + +for dirpath, dirnames, filenames in os.walk(proxypos_dir): + if "__init__.py" in filenames: + if dirpath == proxypos_dir: + packages[dirpath] = "." + else: + packages[dirpath.replace("/",".")] = "./"+dirpath + elif filenames: + data_files.append([dirpath,[os.path.join(dirpath,f) for f in filenames]]) + + +for scheme in INSTALL_SCHEMES.values(): + scheme['data'] = scheme['purelib'] + +setup(name="proxypos", + version="1.1.0", + description = "", + author ="", + author_email = "", + license="GPL", + scripts=["proxypos/proxypos-server","proxypos/gtk-proxypos.py"], + packages=packages, + data_files = data_files, +)