Commit 53fff28c authored by Robin Trioux's avatar Robin Trioux
Browse files

Refilling

parent 84445021
......@@ -176,6 +176,7 @@ class Product(Atom):
self.name = None # pretty print
self.code = None # short name
self.price = None # The price is either updated when it's in a the selctor, either retreived from database when it's in history
self.defaultPrice = None # Unit price without any happy hours
self.quantity = None # When product is used as selector, quantity is 1 and when used in a basket, it may vary
self.happyHours = None # List of happy hours
self.category = None # string e.g "Drinks.Alcool.Wine"
......@@ -191,8 +192,16 @@ class Product(Atom):
return self
def setPrice(self, price): # price is never a float but something inherited from Decimal (Money.Eur)
if isinstance(price, float):
printWW("Price should not be float !")
self.price = price
return self
def setDefaultPrice(self, price):
if isinstance(price, float):
printWW("Price should not be float !")
self.defaultPrice = price
return self
def setQuantity(self, quantity):
self.quantity = quantity
......@@ -215,6 +224,9 @@ class Product(Atom):
def getPrice(self):
return self.price
def getDefaultPrice(self):
return self.defaultPrice
def getQuantity(self):
return self.quantity
......@@ -242,7 +254,6 @@ class Product(Atom):
class Operation(Atom):
def __init__(self):
super().__init__()
self.label = None # human readable description gave by the server
self.refounded = None
# Could be usefull to show where the product has been bought
self.counterId = None
......@@ -283,6 +294,7 @@ class Buying(Operation):
self.price = None # price the customer(s) actually paid
self.payments = None # List of all payment for this order since several users maybe concerned
self.basketItems = None # List of "Product", their unit price should be download from the history
self.label = None # human readable description gave by the server
def setPrice(self, price):
self.price = price
......@@ -309,6 +321,7 @@ class Buying(Operation):
class Refilling(Operation):
def __init__(self):
super().__init__()
self.customerId = None
self.amount = None
self.newBalance = None
......@@ -326,6 +339,13 @@ class Refilling(Operation):
def getNewBalance(self):
return self.newBalance
def setCustomerId(self, uid):
self.customerId = uid
return self
def getCustomerId(self):
return self.customerId
class Counter(Atom):
def __init__(self):
......
......@@ -41,7 +41,7 @@ class Client(metaclass=ClientSingleton):
self.serverAddress="127.0.0.1:50051"
self.channel = grpc.insecure_channel(self.serverAddress)
self.stub = com_pb2_grpc.PaymentProtocolStub(self.channel)
self.now = None
self.now = None #datetime
def setServerAddress(self, address):
self.serverAddress = address
......@@ -83,8 +83,8 @@ class Client(metaclass=ClientSingleton):
print(type(kwargs['amount']))
refillingRequest = com_pb2.RefillingRequest(**kwargs)
refillingReply = self.stub.Refill(refillingRequest)
self.now = refillingReply.now
newBalance = unpackMoney(refillingReply.amount)
self.now = unpackTime(refillingReply.now)
newBalance = unpackMoney(refillingReply.customer_balance)
refilling = unpackRefilling(refillingReply.refilling)
refilling.setNewBalance(newBalance)
return refilling
......@@ -108,7 +108,7 @@ class Client(metaclass=ClientSingleton):
try:
productsRequest = com_pb2.ProductsRequest(**kwargs)
productsReply = self.stub.Products(productsRequest)
self.now = productsReply.now
self.now = unpackTime(productsReply.now)
# Fill product List
pbProductList = productsReply.products # get protobuff products
......@@ -132,7 +132,7 @@ class Client(metaclass=ClientSingleton):
try:
balanceRequest = com_pb2.BalanceRequest(customer_id = kwargs['customer_id'])
balanceReply = self.stub.Balance(balanceRequest)
self.now = balanceReply.now
self.now = unpackTime(balanceReply.now)
return unpackMoney(balanceReply.balance)
except RpcError:
printE("Unable to get customer balance")
......@@ -148,7 +148,7 @@ class Client(metaclass=ClientSingleton):
counterListRequest = com_pb2.CounterListRequest()
counterListReply = self.stub.CounterList(counterListRequest)
pbCounterList = counterListReply.counters #get the payload
self.now = counterListReply.now #update the time
self.now = unpackTime(counterListReply.now) #update the time
for pb_counter in pbCounterList:
newCounter = unpackCounter(pb_counter)
......
......@@ -3,6 +3,7 @@ from Atoms import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from QUtils import *
from QItemTree import *
from QUIManager import QUIManager
......@@ -17,6 +18,45 @@ from QUIManager import QUIManager
######################
class QBuyingInfo(QWidget):
def __init__(self, productDict = None, parent=None):
super().__init__(parent)
# Définitons
self.mainLayout = QGridLayout()
self.productTree = QProductSelector(productDict)
self.userInfoGroupBox = QGroupBox()
self.userInfoLayout = QVBoxLayout()
self.userRowInfo = QRowInfo()
self.buttonLayout = QHBoxLayout()
self.editButton = QPushButton()
self.deleteButton = QPushButton()
self.okButton = QPushButton()
#Layout
self.mainLayout.addWidget(self.productTree, 0,0)
self.mainLayout.addWidget(self.userInfoGroupBox,0,1)
self.mainLayout.addLayout(self.buttonLayout,1,0,1,2)
self.userRowInfo.addRow("UID",)
self.buttonLayout.addWidget(self.editButton)
self.buttonLayout.addWidget(self.deleteButton)
self.buttonLayout.addWidget(self.okButton)
self.setLayout(self.mainLayout)
#Settings
self.editButton.setText("Éditer")
self.deleteButton.setText("Rembourser")
self.okButton.setText("Retour")
class QDelButton(QToolButton):
deleted = pyqtSignal(QToolButton)
......@@ -177,13 +217,19 @@ class QProductInfo(QWidget):
#________/\\\___________/\\\\\\\\\_________________________________________________________________
# _____/\\\\/\\\\______/\\\\\\\\\\\\\_______________________________________________________________
# ___/\\\//\////\\\___/\\\/////////\\\_____/\\\_____________________________________________________
# __/\\\______\//\\\_\/\\\_______\/\\\__/\\\\\\\\\\\_____/\\\\\_______/\\\\\__/\\\\\____/\\\\\\\\\\_
# _\//\\\______/\\\__\/\\\\\\\\\\\\\\\_\////\\\////____/\\\///\\\___/\\\///\\\\\///\\\_\/\\\//////__
# __\///\\\\/\\\\/___\/\\\/////////\\\____\/\\\_______/\\\__\//\\\_\/\\\_\//\\\__\/\\\_\/\\\\\\\\\\_
# ____\////\\\//_____\/\\\_______\/\\\____\/\\\_/\\__\//\\\__/\\\__\/\\\__\/\\\__\/\\\_\////////\\\_
# _______\///\\\\\\__\/\\\_______\/\\\____\//\\\\\____\///\\\\\/___\/\\\__\/\\\__\/\\\__/\\\\\\\\\\_
# _________\//////___\///________\///______\/////_______\/////_____\///___\///___\///__\//////////__
#
####################################
########### QATOMS ##########
##################################
class QAtom(QObject,Atom):
def __init__(self,atom = None):
......@@ -227,8 +273,8 @@ class QAtom(QObject,Atom):
class QUser(QAtom, User):
def __init__(self):
super().__init__()
def __init__(self, user):
super().__init__(user)
self.infoPannel = None
class QProduct(QAtom, Product):
......@@ -279,21 +325,25 @@ class QProduct(QAtom, Product):
self.deleted.emit()
class QCounter(QAtom, Counter):
def __init__(self):
super().__init__()
def __init__(self, counter):
super().__init__(counter)
self.infoPannel = None
class QBuying(QAtom, Buying):
def __init__(self):
super().__init__()
class QOperation(QAtom, Operation):
def __init__(self, operation):
super().__init__(operation)
class QBuying(QOperation, Buying):
def __init__(self, buying):
super().__init__(buying)
self.infoPannel = None
class QRefilling(QAtom, Refilling):
def __init__(self):
super().__init__()
class QRefilling(QOperation, Refilling):
def __init__(self, refilling):
super().__init__(refilling)
self.infoPannel = None
class QDistribution(QAtom, Distribution):
def __init__(self):
super().__init__()
def __init__(self, distribution):
super().__init__(distribution)
self.infoPannel = None
......@@ -58,6 +58,7 @@ class QSearchBar(QWidget):
class QCounterTab(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
dm = QDataManager()
#Definition
self.mainLayout = QHBoxLayout()
......@@ -65,7 +66,7 @@ class QCounterTab(QWidget):
#Left pannel
self.productSelectionLayout = QVBoxLayout()
self.searchBar = QSearchBar()
self.itemSelector = QProductSelector()
self.itemSelector = QProductSelector(dm.productDict)
#Mid pannel
self.basket = QBasket()
......
......@@ -28,25 +28,25 @@ from pickle import PickleError
#EDIT: NOW MANAGERS ARE NOT DEPENDENT TO QATOMS ANYMORE SO THE TEXT ABOVE IS IRELEVANT BUT IT MUST BE TESTED
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ________ _____ ______ ________ ________ ________ ________ _______ ________
#|\ __ \|\ _ \ _ \|\ __ \|\ ___ \|\ __ \|\ ____\|\ ___ \ |\ __ \
#\ \ \|\ \ \ \\\__\ \ \ \ \|\ \ \ \\ \ \ \ \|\ \ \ \___|\ \ __/|\ \ \|\ \
# \ \ \\\ \ \ \\|__| \ \ \ __ \ \ \\ \ \ \ __ \ \ \ __\ \ \_|/_\ \ _ _\
# \ \ \\\ \ \ \ \ \ \ \ \ \ \ \ \\ \ \ \ \ \ \ \ \|\ \ \ \_|\ \ \ \\ \|
# \ \_____ \ \__\ \ \__\ \__\ \__\ \__\\ \__\ \__\ \__\ \_______\ \_______\ \__\\ _\
# \|___| \__\|__| \|__|\|__|\|__|\|__| \|__|\|__|\|__|\|_______|\|_______|\|__|\|__|
# \|__|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#________/\\\________/\\\\____________/\\\\____________________________________________________________________________________________________
# _____/\\\\/\\\\____\/\\\\\\________/\\\\\\____________________________________________________________________________________________________
# ___/\\\//\////\\\__\/\\\//\\\____/\\\//\\\_______________________________________________/\\\\\\\\____________________________________________
# __/\\\______\//\\\_\/\\\\///\\\/\\\/_\/\\\__/\\\\\\\\\_____/\\/\\\\\\____/\\\\\\\\\_____/\\\////\\\_____/\\\\\\\\___/\\/\\\\\\\___/\\\\\\\\\\_
# _\//\\\______/\\\__\/\\\__\///\\\/___\/\\\_\////////\\\___\/\\\////\\\__\////////\\\___\//\\\\\\\\\___/\\\/////\\\_\/\\\/////\\\_\/\\\//////__
# __\///\\\\/\\\\/___\/\\\____\///_____\/\\\___/\\\\\\\\\\__\/\\\__\//\\\___/\\\\\\\\\\___\///////\\\__/\\\\\\\\\\\__\/\\\___\///__\/\\\\\\\\\\_
# ____\////\\\//_____\/\\\_____________\/\\\__/\\\/////\\\__\/\\\___\/\\\__/\\\/////\\\___/\\_____\\\_\//\\///////___\/\\\_________\////////\\\_
# _______\///\\\\\\__\/\\\_____________\/\\\_\//\\\\\\\\/\\_\/\\\___\/\\\_\//\\\\\\\\/\\_\//\\\\\\\\___\//\\\\\\\\\\_\/\\\__________/\\\\\\\\\\_
# _________\//////___\///______________\///___\////////\//__\///____\///___\////////\//___\////////_____\//////////__\///__________\//////////__
# Need to be seriously tested !
def parseProductDict(productList: [Product]):
productDict={"root":{},"prod":[]}
productDict={"root":{},"Product":[]}
def addToDictionnary(productDict, product, relativePath):
if(len(relativePath) == 0): # End node
productDict["prod"].append(product)
productDict["Product"].append(product)
return productDict
else:
......@@ -56,7 +56,7 @@ def parseProductDict(productList: [Product]):
productDict[categoryName] = addToDictionnary(productDict[categoryName],product,newRelPath)
return productDict
except KeyError:
productDict[categoryName]={"prod":[]}
productDict[categoryName]={"Product":[]}
productDict[categoryName] = addToDictionnary(productDict[categoryName],product,newRelPath)
return productDict
......@@ -65,7 +65,7 @@ def parseProductDict(productList: [Product]):
categoryList.insert(0,"root")
productDict = addToDictionnary(productDict,product, categoryList)
productDict["root"]["prod"] = productDict["prod"] #remove useless root key
productDict["root"]["Product"] = productDict["Product"] #remove useless root key
productDict = productDict["root"]
return productDict
......@@ -97,7 +97,9 @@ class QDataManagerSingleton(type(QObject)):
class QDataManager(QObject, metaclass=QDataManagerSingleton):
priceUpdated = pyqtSignal(Product)
def __init__(self,parent=None):
super().__init__(parent)
client = Client()
# Definition
self.productList = [] # list of "Products"
......@@ -106,11 +108,17 @@ class QDataManager(QObject, metaclass=QDataManagerSingleton):
self.refillingList = []
self.counterList = []
self.clock = QTime()
self.clock = QDateTime.currentDateTimeUtc()
self.timer = QTimer(self)
self.counter = None # Atomic Counter
self.uid = None #string uid machine
self.serverAddress = None #string format ipv4 e.g 192.168.0.1:50051
self.timer.timeout.connect(self.update)
self.timer.start(500)
self.clock.time().start()
# Initialisation
printI("Data initialization")
......@@ -123,10 +131,12 @@ class QDataManager(QObject, metaclass=QDataManagerSingleton):
printW("uid file corrupted, a new uid will be generated")
except FileNotFoundError:
printW("uid file not found in ./data, a new uid will be generated")
finally:
if not self.uid:
with open("data/uid",'w') as file:
self.uid = str(uuid.uuid4())
file.write(self.uid)
printI("New machine uid generated")
#counter initialisation
printI("Request counter list")
......@@ -160,7 +170,9 @@ class QDataManager(QObject, metaclass=QDataManagerSingleton):
def getPrice(self, product):
pass
for prod in self.productList:
if product.getId() == prod.getId():
return prod.getPrice()
def getCounter(self):
return self.counter
......@@ -168,3 +180,12 @@ class QDataManager(QObject, metaclass=QDataManagerSingleton):
def getUID(self):
return self.uid
def update(self):
currentTime = self.clock.toPyDateTime()
for product in self.productList:
for happyHour in product.getHappyHours():
if happyHour.getStart() < currentTime and currentTime < happyHour.getEnd():
printN("Happy hour sur {0}, {1} au lieu de {2}".format(product, happyHour.getPrice(), product.getDefaultPrice()))
product.setPrice(happyHour.getPrice())
self.priceUpdated.emit(product)
......@@ -10,30 +10,43 @@ class QProductSelectorModel(QTreeModel):
def __init__(self, headers, data=None, parent=None):
super().__init__(headers, data=data, parent=parent)
dm = QDataManager()
self.qProductList = []
if data is not None:
self.setupModelData(data)
dm.priceUpdated[Product].connect(self.updatePrice)
def setupModelData(self, data):
dm = QDataManager()
#print(data)
# productDict look like this {"root":{"cat1:{"prod":[]}","prod":[]},"prod":[]}
# productDict look like this {"root":{"cat1:{"Product":[]}","Product":[]},"Product":[]}
def exploreDict(productDict, parent:TreeItem):
# add new categories
for key in productDict:
if key != 'prod':
if key != "Product":
atom = Atom([key],key)
child = TreeItem( QAtom(atom),parent)
parent.appendChild(child)
exploreDict(productDict[key],child)
# add products
for product in productDict['prod']:
for product in productDict["Product"]:
product.setTexts([product.getName(),product.getPrice()])
#print(product.getCode())
child = TreeItem(QProduct(product),parent)
qProduct = QProduct(product)
self.qProductList.append(qProduct)
child = TreeItem(qProduct,parent)
parent.appendChild(child)
exploreDict(dm.productDict, self.rootItem)
def updatePrice(self, product):
#We could play with memory tricks so that when it's updated in the manager it's updated here but Master Foo's Zen says:
# explicit is better that implicit ...
# So the manager will explicity send update signals to Widgets...
qProduct = QProduct(product)
index = self.qProductList.index(qProduct)
# ???
class QBasketModel(QTreeModel):
modelChanged = pyqtSignal()
......@@ -54,7 +67,7 @@ class QBasketModel(QTreeModel):
if product in productList:
#I use my own reseach function because the 'match' function from Qt bases it research on the text field
#by default, I should reimplement 'match', but I prefer use my own search function
index,item,data = self.searchQProduct(qProduct)
index,item,data = self.searchQAtom(qProduct)
data.incQuantity()
......@@ -92,27 +105,29 @@ class QBasketModel(QTreeModel):
pass
else:
qProduct = self.sender()
index,item,data = self.searchQProduct(qProduct)
index,item,data = self.searchQAtom(qProduct)
self.removeRow(index.row())
def searchQProduct(self, qProduct:QProduct):
n_row = self.rowCount()
for i in range(n_row):
item = self.getItem(self.index(i,0))
data = item.getData()
if data == qProduct: # == means same id
return self.index(i,0),item,data
class QUserModel(QTreeModel):
def __init__(self, headers, data =None, parent = None):
super().__init__(headers, data, parent)
def getQProductList(self):
qProductList = []
n_row = self.rowCount()
for i in range(n_row):
item = self.getItem(self.index(i,0))
data = item.getData()
qProductList.append(data)
return qProductList
class QHistoryModel(QTreeModel):
def __init__(self, headers, data = None, parent = None):
super().__init__(headers, data, parent)
def addOperation(self, operation):
if isinstance(operation, Refilling):
qRefilling= QRefilling(operation)
self.insertAtom(0,qRefilling)
else:
pass
def setupModelData(self):
pass
......@@ -74,7 +74,7 @@ class QItemTree(QWidget):
class QProductSelector(QItemTree):
itemSelected = pyqtSignal(Product)
def __init__(self, parent=None):
def __init__(self, productDict=None, parent=None):
super().__init__(parent)
dm = QDataManager()
......@@ -82,7 +82,10 @@ class QProductSelector(QItemTree):
#Definition
self.mainLayout = QVBoxLayout()
self.treeView = QSuperTreeView()
self.treeModel = QProductSelectorModel(["Produits","Prix"], dm.productDict)
if productDict:
self.treeModel = QProductSelectorModel(["Produits","Prix"], dm.productDict)
else:
self.treeModel = None
#Layout
......@@ -128,3 +131,27 @@ class QBasket(QItemTree):
newProduct.setTexts(['@name','','@quantity * price','']) #The price should be handled inside basket model
self.treeModel.addProduct(newProduct, self.treeView) #Insert the atom to the top. (No choice but give the treeview to add indexWidget...)
self.forceRefresh() #Resize column to content for each columns
class QHistory(QItemTree):
def __init__(self, parent = None):
super().__init__(parent)
#Definition
self.mainLayout = QVBoxLayout()
self.treeView = QSuperTreeView()
self.treeModel = QHistoryModel(["Utilisateur","Montant"])
#Layout
self.mainLayout.addWidget(self.treeView)
self.treeView.setModel(self.treeModel)
self.setLayout(self.mainLayout)
def addOperation(self, operation:Operation):
newOperation = copy.deepcopy(operation)
if isinstance(operation,Refilling):
newOperation.setTexts(["@customerId","@amount"])
self.treeModel.addOperation(newOperation)
self.forceRefresh()
else:
pass
\ No newline at end of file
......@@ -232,6 +232,7 @@ class QCheckPayment(QCreditCardPayment):
class QRefillerTab(QWidget):
balanceUpdated = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
......@@ -265,7 +266,7 @@ class QRefillerTab(QWidget):
self.rightLayout = QVBoxLayout()
self.nfcInfo = QNFCInfo()
self.history = None
self.history = QHistory()
#layout
#Left pannel
......@@ -286,6 +287,10 @@ class QRefillerTab(QWidget):
self.paymentLayout.addWidget(self.transfertPayement)
self.paymentLayout.addWidget(self.otherPayment)
#Right pannel
self.rightLayout.addWidget(self.nfcInfo)
self.rightLayout.addWidget(self.history)
#Settings
......@@ -313,6 +318,7 @@ class QRefillerTab(QWidget):
self.mainLayout.addWidget(self.paymentMethodGroupBox)
self.mainLayout.addLayout(self.paymentLayout)
self.mainLayout.addLayout(self.rightLayout)
self.setLayout(self.mainLayout)
self.cashPaymentRadio.toggled.connect(self.selectCash)
......@@ -327,6 +333,8 @@ class QRefillerTab(QWidget):
self.otherPayment.credited[Eur].connect(self.credit)
self.aePayment.credited[Eur].connect(self.credit)
self.balanceUpdated.connect(self.nfcInfo.update)
def selectCreditCard(self):
if self.cardPaymentRadio.isChecked():
self.paymentLayout.setCurrentWidget(self.cardPayment)
......@@ -357,5 +365,7 @@ class QRefillerTab(QWidget):
counterId = dm.getCounter().getId()
machineUID = dm.getUID()
paymentMethod = self.sender().getPaymentMethod()
client.requestRefilling(customer_id=uid,counter_id=counterId,device_uuid = machineUID,payment_method=paymentMethod,amount=amount)
refilling = client.requestRefilling(customer_id=uid,counter_id=counterId,device_uuid = machineUID,payment_method=paymentMethod,amount=amount)
printI("User {} credited of {}".format(uid,amount))
self.balanceUpdated.emit()
self.history.addOperation(refilling)
......@@ -342,6 +342,22 @@ class QTreeModel(QAbstractItemModel):
newIndex = self.index(position,0)
self.setData(newIndex,Atom)
def searchQAtom(self, qAtom:QAtom, parent = QModelIndex()):
n_row = self.rowCount()
for i in range(n_row):
item = self.getItem(self.index(i,0,parent))
data = item.getData()
if data == qAtom: # == means same id
return self.index(i,0),item,data
def getQAtomList(self,parent=QModelIndex()):
qAtomList = []
n_row = self.rowCount()
for i in range(n_row):
item = self.getItem(self.index(i,0,parent))
data = item.getData()
qAtomList.append(data)
return qAtomList
......
......@@ -278,3 +278,10 @@ class QNFCInfo(QWidget):
def cardRemoved(self):
self.rowInfo.setRow(1,1,Eur(0))
self.rowInfo.setRow(0,1,"00 00 00 00")
def update(self):
nfcm = QNFCManager()
client = Client()
cardUID = nfcm.getCardUID()
balance = client.requestUserBalance(customer_id=cardUID)
self.rowInfo.setRow(1,1,balance)
......@@ -13,6 +13,7 @@ import decimal
#Help to convert google timestamp into QTime ...
from datetime import datetime