You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
k4ever/client-barcode/freitagskasse.py

409 lines
11 KiB

#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
# Licensed under GPL v3 or later
from __future__ import print_function
import freitagslib.network as net
from freitagslib.commands import BuyCommand, DepositCommand
import colorama
from colorama import Fore, Style
import sys
from decimal import Decimal
import os
import time
import urllib2
COLOR_HINT = Fore.YELLOW + Style.BRIGHT
COLOR_ERROR = Fore.RED
COLOR_DEPOSIT = Fore.GREEN + Style.BRIGHT
COLOR_TO_GO = Fore.MAGENTA + Style.BRIGHT
COLOR_WITHDRAW = Fore.RED + Style.BRIGHT
COLOR_SOME = Fore.WHITE + Style.BRIGHT
COLOR_MUCH = Fore.YELLOW + Style.BRIGHT
COLOR_RESET = Style.RESET_ALL
display_fifo = None
def print_display(s):
global display_fifo
if not display_fifo:
try:
#display_fifo = open('/tmp/display', 'w')
fd = os.open( "/tmp/display", os.O_WRONLY)
display_fifo = os.fdopen(fd,"w")
except OSError:
display_fifo = None
pass
return
except IOError:
display_fifo = None
pass
return
display_fifo.write(s)
display_fifo.flush()
def clear():
os.system('clear')
TO_GO_ONLY = 2
TO_GO_ALL = 1
TO_GO_NONE = 0
TO_GO_PREV = -1
CODES = {
'UNDO':('undo',),
'COMMIT':('commit',),
'TO GO ALL':('to_go', TO_GO_ALL),
'TO GO NONE':('to_go', TO_GO_NONE),
'TO GO PREV':('to_go', TO_GO_PREV),
'TO GO ONLY':('to_go', TO_GO_ONLY),
'DEPOSIT 0.01':('deposit', Decimal('0.01')),
'DEPOSIT 0.05':('deposit', Decimal('0.05')),
'DEPOSIT 0.10':('deposit', Decimal('0.10')),
'DEPOSIT 0.50':('deposit', Decimal('0.50')),
'DEPOSIT 1.00':('deposit', Decimal('1.00')),
'DEPOSIT 5.00':('deposit', Decimal('5.00')),
'DEPOSIT 10.00':('deposit', Decimal('10.00')),
'DEPOSIT 50.00':('deposit', Decimal('50.00')),
}
def delay(what, seconds):
for i in xrange(seconds, 0, -1):
if i < seconds:
sys.stdout.write('\r')
sys.stdout.write('%s in %d Sekunden... ' % (what, i))
sys.stdout.flush()
time.sleep(1)
def warn_balance():
print('Kontostand im Minus, bitte Geld aufladen.')
def error_page(error_message, hint_message=None):
clear()
print(COLOR_ERROR + error_message + COLOR_RESET)
print()
delay_seconds = 3
if hint_message is not None:
print(COLOR_HINT + hint_message + COLOR_RESET)
print()
delay_seconds += 3
delay('Weiter', delay_seconds)
class Status:
def __init__(self):
self._reset()
self.item_cache = dict()
def _reset(self):
self.auth_blob = None
self.login_name = None
self.balance = None
self.transfers = None
def dump(self):
def sign(amount, plus='+'):
return '-' if amount < 0 else plus
def color(amount):
return COLOR_WITHDRAW if amount < 0 else COLOR_DEPOSIT
def show_total(balance, plus=' '):
print('%3s %-40s %s%c %6.2f Euro%s' \
% ('', '', color(balance), sign(balance, plus),
abs(balance), COLOR_RESET))
def show_item(position, diff, label, color):
print('%2d) %-40s %s%c %6.2f Euro%s' \
% (position, label, color, sign(diff),
abs(diff), COLOR_RESET))
def show_bar():
print('%3s %-40s %13s' % ('', '', '============='))
if self.logged_in():
print('Eingeloggt als: %s%s%s' % (COLOR_SOME, self.login_name, COLOR_RESET))
print()
print_display('\x0cHallo %-14s' % (self.login_name[:13]+"!") )
if self.transfers:
initial_command, initial_balance = self.transfers[0]
print('Geplante Änderungen:')
show_total(initial_balance)
i = 1
for command, dummy in self.transfers:
if isinstance(command, BuyCommand):
if (command.includes_commodity()):
show_item(i, -command.commodity_value(), command.commodity_label(), COLOR_WITHDRAW)
i += 1
if (command.includes_deposit()):
show_item(i, -command.deposit_value(), command.deposit_label(), COLOR_TO_GO)
i += 1
else:
show_item(i, command.difference(), command.label(), COLOR_DEPOSIT)
i += 1
show_bar()
if isinstance(command, BuyCommand):
print_display('\x0b%-15s %4.2f' % (command.commodity_label()[:15],abs(command.difference())));
print_display('\x0b\nSUMME: {%02i} %8.2f' % ((i-1),initial_balance - self.balance));
else:
print_display('\x0b%-15s %4.2f' % (command.label()[:15],abs(command.difference())));
print_display('\x0b\nSUMME: {%02i} %8.2f' % ((i-1),initial_balance - self.balance));
if len(self.transfers) > 1:
show_total(self.balance - initial_balance, plus='+')
show_bar()
show_total(self.balance)
if self.balance < 0:
warn_balance()
print_display('\x0b\nKonto: %5.2f!' % (self.balance) )
print()
print(COLOR_SOME + 'Committen nicht vergessen.' + COLOR_RESET)
else:
print('Kontostand beträgt: %s%.2f Euro%s' % (COLOR_MUCH, self.balance, COLOR_RESET))
print_display('Kontostand: %8.2f' % self.balance)
if self.balance < 0:
print()
warn_balance()
else:
print(COLOR_MUCH + 'Bitte einloggen.' + COLOR_RESET)
print()
print('Scanne dazu deine ID-Karte mit dem Barcode-Leser.')
print_display("\e\x0cAnmeldung: Bitte ID-Karte scannen...")
print()
def logged_in(self):
return self.auth_blob is not None
def login(self, auth_blob):
assert(not self.logged_in())
user_name = net.get_user_name_from_auth_blob(auth_blob)
balance = net.get_balance(user_name)
self.auth_blob = auth_blob
self.login_name = user_name
self.balance = balance
self.transfers = list()
def commit(self):
assert(self.logged_in())
# Process command queue
for (command, balance_backup) in list(self.transfers):
try:
command.run(self.login_name)
except urllib2.HTTPError as e:
print_display('\e\x0cFEHLER: Server Error%20s' % str(e)[:20])
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
break
else:
self.transfers.pop(0)
if not self.transfers:
# Show final balance for some time
clear()
self.dump()
print_display('\x0bDanke,%-14s' % (self.login_name[:13]+"!") )
delay('Logout', 3)
# Logout
self._reset()
def find(self, barcode):
try:
return self.item_cache[barcode]
except KeyError:
item = net.get_item(barcode)
self.item_cache[barcode] = item
return item
def buy(self, item):
assert(self.logged_in())
log_entry = (BuyCommand(item), self.balance)
self.transfers.append(log_entry)
self.balance = self.balance - item.price
def deposit(self, amount):
assert(self.logged_in())
log_entry = (DepositCommand(amount), self.balance)
self.transfers.append(log_entry)
self.balance = self.balance + amount
def to_go(self, scope):
_PRODUCT_FIRST = 'FEHLER: Bitte zuerst den betreffenden Artikel scannen.'
if not self.transfers:
error_page(_PRODUCT_FIRST)
return
if scope == TO_GO_ALL:
"""
Makes all BuyCommands with deposit > 0 include deposit
...and updates future balance accordingly.
"""
dummy, initial_balance = self.transfers[0]
balance_before = initial_balance
for command, dummy in self.transfers:
if isinstance(command, BuyCommand) \
and not command.includes_deposit() \
and command.deposit_value() > 0:
command.include_deposit(True)
balance_before += command.difference()
self.balance = balance_before
elif scope == TO_GO_NONE:
"""
Makes all BuyCommands that include commodity
not include deposit any more
...and updates future balance accordingly.
"""
first_command, initial_balance = self.transfers[0]
balance_before = initial_balance
for command, dummy in self.transfers:
if isinstance(command, BuyCommand) \
and command.includes_commodity():
command.include_deposit(False)
balance_before += command.difference()
self.balance = balance_before
elif scope == TO_GO_PREV:
"""
Makes the last BuyCommand include deposit
...and updates future balance accordingly.
"""
prev, balance_backup = self.transfers[-1]
if not isinstance(prev, BuyCommand):
error_page(_PRODUCT_FIRST)
return
if prev.includes_deposit():
print_display('\x0cFEHLER: schon Pfand %20s' % prev.item_name()[:20])
error_page('FEHLER: Pfand für Produkt "%s" bereits aktiviert' % prev.item_name())
return
if prev.deposit_value() <= 0:
print_display('\x0cFEHLER: Pfandfrei! %20s' % prev.item_name()[:20])
error_page('FEHLER: Produkt "%s" hat kein Pfand' % prev.item_name())
return
before = prev.difference()
prev.include_deposit(True)
after = prev.difference()
self.balance += (after - before)
elif scope == TO_GO_ONLY:
"""
Makes all BuyCommand that include commodity
be deposit only
...and updates future balance accordingly.
"""
dummy, initial_balance = self.transfers[0]
balance_before = initial_balance
for command, dummy in self.transfers:
if isinstance(command, BuyCommand) \
and command.deposit_value() > 0 \
and command.includes_commodity():
command.include_commodity(False)
command.include_deposit(True)
balance_before += command.difference()
self.balance = balance_before
def undo(self):
assert(self.logged_in())
if self.transfers:
dummy, balance_backup = self.transfers[-1]
self.transfers.pop()
self.balance = balance_backup
else:
error_page('FEHLER: Nichts da, was ich rückgängig machen könnte.')
def print_prompt():
sys.stdout.write(">>> ")
def handle(line, status):
if line == 'exit':
clear()
sys.exit(0)
if status.logged_in():
if line in CODES:
call = CODES[line]
method = call[0]
params = call[1:]
getattr(status, method)(*params)
else:
try:
item = status.find(line)
except urllib2.HTTPError as e:
if e.code == 404: # URL not found == item not found with REST
print_display('\x0cERROR: %13sCode ist unbekannt' % ( line[:13]))
error_page('FEHLER: Aktion oder Ware "%s" nicht bekannt' % line)
else:
print_display('\x0cERROR: Server Error%20s' % str(e)[:20])
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
else:
status.buy(item)
else:
try:
status.login(line)
except urllib2.HTTPError as e:
if e.code == 404: # URL not found == user unknown
print_display('\x0cFEHLER: Nutzer ist unbekannt: "%7s"' % line[:7])
error_page('FEHLER: Benutzer "%s" nicht bekannt' % line,
hint_message='Ist in der WebApp unter "Einstellungen" ' \
'für Ihren Account Plugin "BarcodePlugin" ' \
'als erlaubt markiert?')
else:
print_display('\x0cFEHLER: Server Error%20s' % str(e)[:20])
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
except urllib2.URLError as e:
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
def main():
colorama.init()
status = Status()
print_display("\x1b\x40Kassensystem \n startet...")
while True:
clear()
status.dump()
print_prompt()
l = sys.stdin.readline()
if not l:
break
line = l.rstrip()
print_display('\x0cBarcode:\n%20s' % line[:20])
if line:
handle(line, status)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
pass
print_display("\x1b\x40Goodbye!")