Skip to content

Commit

Permalink
load cart from mobile app (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgmax committed Jun 27, 2015
1 parent f37a24b commit 48627f1
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 86 deletions.
15 changes: 10 additions & 5 deletions FabLabKasse/UI/LoadFromMobileAppDialogCode.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,29 @@
# You should have received a copy of the GNU General Public License along with this program. If not,
# see <http://www.gnu.org/licenses/>.

"dialog for loading the cart from a mobile application. It shows a QR Code as one-time-token for authentication."

from PyQt4 import QtGui
from uic_generated.LoadFromMobileAppDialog import Ui_LoadFromMobileAppDialog
from FabLabKasse.UI.uic_generated.LoadFromMobileAppDialog import Ui_LoadFromMobileAppDialog
import qrcode
import StringIO

class LoadFromMobileAppDialog(QtGui.QDialog, Ui_LoadFromMobileAppDialog):
def __init__(self, parent, random_code, app_url):
"dialog for loading the cart from a mobile application. It shows a QR Code as one-time-token for authentication."
def __init__(self, parent, app_url):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
LoadFromMobileAppDialog.set_qr_label(self.label__qr_app, app_url)

def set_random_code(self, random_code):
"update the QR code showing the cart id"
LoadFromMobileAppDialog.set_qr_label(self.label_qr_random, random_code)


@staticmethod
def set_qr_label(label, text):
"""
set qrcode image on QLabel
@param label: QLabel
@param text: text for the QR code
"""
Expand All @@ -46,5 +51,5 @@ def set_qr_label(label, text):
qt_pixmap = QtGui.QPixmap()
qt_pixmap.loadFromData(buf.getvalue(), "PNG")
label.setPixmap(qt_pixmap)


84 changes: 4 additions & 80 deletions FabLabKasse/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@
from UI.uic_generated.Kassenterminal import Ui_Kassenterminal
from UI.PaymentMethodDialogCode import PaymentMethodDialog
from UI.KeyboardDialogCode import KeyboardDialog
from UI.LoadFromMobileAppDialogCode import LoadFromMobileAppDialog

import scriptHelper
from cashPayment.client.PaymentDevicesManager import PaymentDevicesManager

from shopping.cart_from_app.cart_gui import MobileAppCartGUI

# switching to german:
locale.setlocale(locale.LC_ALL, "de_DE.UTF-8")

Expand Down Expand Up @@ -577,85 +578,8 @@ def askUser():
return paymentmethod.successful

def payViaApp(self):
# check that current cart is empty
if self.shoppingBackend.get_current_order() != None:
if self.shoppingBackend.get_current_total() != 0:
QtGui.QMessageBox.warning(self, "Fehler",
u"Im Warenkorb am Automat liegen Produkte.\n"+
u"Bitte zahle zuerst diese Produkte oder lösche sie aus dem Warenkorb.\n")
return
self.shoppingBackend.delete_current_order()
self.shoppingBackend.set_current_order(None)

random_code = "1234"
diag = LoadFromMobileAppDialog(self, str(random_code), "http://test.de/")
poll_timer = Qt.QTimer(self)
poll_timer.setSingleShot(True)
poll_timer.setInterval(1000)

def pay_cart(cart):
""" import given cart (from server's response), let the user pay it
@param cart: response from server
@rtype: bool
@return: True if successfully paid, False otherwise.
"""
# cart received
new_order = self.shoppingBackend.create_order()
self.shoppingBackend.set_current_order(new_order)
self.shoppingBackend.add_order_line(prod_id=9999, qty=Decimal(12.34), comment="")

# check total sum
if self.shoppingBackend.get_current_total() != Decimal("12.34"):
QtGui.QMessageBox.warning(self, "Fehler",
u"Die Gesamtsumme konnte leider nicht richtig importiert werden.")
self.shoppingBackend.delete_current_order()
self.shoppingBackend.set_current_order(None)
return False

# try payup
payup_successful = (self.payup() == True)
print "feedback successful = {}".format(payup_successful)
if not payup_successful:
self.shoppingBackend.delete_current_order()
self.shoppingBackend.set_current_order(None)
QtGui.QMessageBox.information(self, "Info", u"Die Zahlung wurde abgebrochen.")

return payup_successful

def poll():
"query the server if a cart was stored"
if not diag.isVisible():
# this should not happen, maybe a race-condition
return
logging.debug(u"polling for cart {}".format(random_code))
print "TODO POLL"
response = "TODO"
if not response:
poll_timer.start()
return
diag.setEnabled(False)
pay_cart(response)
diag.reject()
self.updateOrder()

poll_timer.timeout.connect(poll)
poll_timer.start()

def dialog_finished(result):
"user somehow exited the dialog. clean up everything."
poll_timer.stop()
diag.deleteLater()
poll_timer.deleteLater()

diag.finished.connect(dialog_finished)

diag.show()





p = MobileAppCartGUI(self, "http://test.de")
p.execute()

def getSelectedOrderLineId(self):
order_idx = self.table_order.currentIndex()
Expand Down
4 changes: 4 additions & 0 deletions FabLabKasse/shopping/backend/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ def __init__(self, order_line_id, qty, unit, name, price_per_unit, price_subtota
isinstance(price_subtotal, (Decimal, int))
self.price_subtotal = price_subtotal
self.delete_if_zero_qty = delete_if_zero_qty

def __str__(self):
# TODO ugly hack: we directly call AbstractShoppingBackend here because we have no access to the real shopping backend :(
return u"{} {} {} = {}".format(float(self.qty), self.unit, self.name, AbstractShoppingBackend.format_money(None, self.price_subtotal))


class DebtLimitExceeded(Exception):
Expand Down
Empty file.
130 changes: 130 additions & 0 deletions FabLabKasse/shopping/cart_from_app/cart_gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# FabLabKasse, a Point-of-Sale Software for FabLabs and other public and trust-based workshops.
# Copyright (C) 2015 Julian Hammer <julian.hammer@fablab.fau.de>
# Maximilian Gaukler <max@fablab.fau.de>
# Timo Voigt <timo@fablab.fau.de>
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU
# General Public License as published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not,
# see <http://www.gnu.org/licenses/>.

"GUI + logic for loading the cart from a mobile application."

from PyQt4 import Qt, QtGui
from FabLabKasse.UI.LoadFromMobileAppDialogCode import LoadFromMobileAppDialog
from FabLabKasse.shopping.cart_from_app.cart_model import MobileAppCartModel
import logging

# check that current cart is empty


class MobileAppCartGUI(object):

"GUI + logic for loading the cart from a mobile application. It shows a QR Code as one-time-token for authentication."

def __init__(self, parent, appstore_url):
"""
parent: GUI main object. used as Qt GUI parent and for accessing shoppingBackend
appstore_url: URL for QRcode that leads to the app installation
"""
self.random_code = ""
self.diag = LoadFromMobileAppDialog(parent, appstore_url)
self.parent = parent

self.cart = MobileAppCartModel()
self.cart.cart_id_changed.connect(self.diag.set_random_code)

self.poll_timer = Qt.QTimer(self.parent)
self.poll_timer.setSingleShot(True)
self.poll_timer.setInterval(1000)
self.poll_timer.timeout.connect(self.poll)

def execute(self):
"""show dialog with QRcodes, process order and payment, cleanup afterwards.
This method blocks until everything is done.
"""
if self.parent.shoppingBackend.get_current_order() != None:
if self.parent.shoppingBackend.get_current_total() != 0:
QtGui.QMessageBox.warning(self.parent, "Fehler",
u"Im Warenkorb am Automat liegen Produkte.\n" +
u"Bitte zahle zuerst diese Produkte oder lösche sie aus dem Warenkorb.\n")
return
self.parent.shoppingBackend.delete_current_order()
self.parent.shoppingBackend.set_current_order(None)

self.poll_timer.start()
self.diag.finished.connect(lambda x: self.dialog_finished)
self.diag.show()

def pay_cart(self, cart):
""" import given cart (from server's response), let the user pay it
@param cart: response from server
@rtype: bool
@return: True if successfully paid, False otherwise.
"""
# cart received
new_order = self.parent.shoppingBackend.create_order()
self.parent.shoppingBackend.set_current_order(new_order)

for (product, quantity) in cart:
self.parent.shoppingBackend.add_order_line(prod_id=product, qty=quantity, comment="")

# check total sum
# TODO this is ugly and doesn't show everything when there are too many articles
# make a nice GUI out of it
infotext = u"Stimmt der Warenkorb? \n"
order_lines = self.parent.shoppingBackend.get_order_lines()
for line in order_lines[0:10]:
infotext += unicode(line) + "\n"
if len(order_lines) > 10:
infotext += "... und {} weitere Posten ...\n".format(len(order_lines - 10))
infotext += u"Gesamt: {}\n".format(self.parent.shoppingBackend.format_money(self.parent.shoppingBackend.get_current_total()))
okay = QtGui.QMessageBox.information(self.parent, "Warenkorb", infotext, QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Yes)
okay = okay == QtGui.QMessageBox.Yes
if okay:
# try payup
successful = (self.parent.payup() == True)
else:
successful = False
print "feedback successful = {}".format(successful)
self.cart.send_status_feedback(successful)
if not successful:
self.parent.shoppingBackend.delete_current_order()
self.parent.shoppingBackend.set_current_order(None)
QtGui.QMessageBox.information(self.parent, "Info", u"Die Zahlung wurde abgebrochen.")
return successful

def poll(self):
"query the server if a cart was stored"
if not self.diag.isVisible():
# this should not happen, maybe a race-condition
return
logging.debug(u"polling for cart {}".format(self.cart.cart_id))
response = self.cart.load()
if not response:
self.poll_timer.start()
return
logging.debug("received cart: {}".format(response))
self.diag.setEnabled(False)
self.pay_cart(response)
self.diag.reject()
self.parent.updateOrder()

def dialog_finished(self):
"user somehow exited the dialog. clean up everything."
self.poll_timer.stop()
self.diag.deleteLater()
self.poll_timer.deleteLater()
self.cart.deleteLater()
Loading

0 comments on commit 48627f1

Please sign in to comment.