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

333 lines
8.0 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, ToGoCommand
import colorama
from colorama import Fore, Style
import sys
from decimal import Decimal
import os
import time
import urllib2
COLOR_WARN = Fore.YELLOW
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
def clear():
os.system('clear')
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),
'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():
13 years ago
print('Kontostrand im Minus, bitte Geld aufladen.')
def error_page(message):
clear()
print(COLOR_ERROR + message + COLOR_RESET)
print()
delay('Weiter', 3)
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 color_command(command):
diff = command.difference()
if diff >= 0:
return COLOR_DEPOSIT
return COLOR_TO_GO if isinstance(command, ToGoCommand) else COLOR_WITHDRAW
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, command):
diff = command.difference()
label = command.label()
print('%2d) %-40s %s%c %6.2f Euro%s' \
% (position, label, color_command(command), 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()
if self.transfers:
initial_command, initial_balance = self.transfers[0]
print('Geplante Änderungen:')
show_total(initial_balance)
for i, (command, balance_backup) in enumerate(self.transfers):
show_item(i + 1, command)
show_bar()
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()
print(COLOR_SOME + 'Committen nicht vergessen.' + COLOR_RESET)
else:
print('Kontostand beträgt: %s%.2f Euro%s' % (COLOR_MUCH, self.balance, COLOR_RESET))
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()
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:
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()
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:
after = list()
dummy, initial_balance = self.transfers[0]
balance_before = initial_balance
def body(prev, command, balance_before):
if isinstance(prev, BuyCommand) \
and prev.deposit() > 0 \
and not isinstance(command, ToGoCommand):
to_go = ToGoCommand(prev)
prev.set_to_go(True)
after.append((to_go, balance_before))
balance_before += to_go.difference()
if command is not None:
after.append((command, balance_before))
balance_before += command.difference()
return balance_before
prev = None
for command, dummy in list(self.transfers):
balance_before = body(prev, command, balance_before)
prev = command
balance_before = body(prev, None, balance_before)
self.balance = balance_before
self.transfers = after
elif scope == TO_GO_NONE:
after = list()
first_command, initial_balance = self.transfers[0]
balance_before = initial_balance
for command, dummy in list(self.transfers):
if isinstance(command, BuyCommand) \
and command.is_to_go():
command.set_to_go(False)
elif isinstance(command, ToGoCommand):
continue
after.append((command, balance_before))
balance_before += command.difference()
self.balance = balance_before
self.transfers = after
elif scope == TO_GO_PREV:
prev, balance_backup = self.transfers[-1]
if not isinstance(prev, BuyCommand):
error_page(_PRODUCT_FIRST)
return
to_go = ToGoCommand(prev)
prev.set_to_go(True)
self.transfers.append((to_go, self.balance))
self.balance += to_go.difference()
def undo(self):
assert(self.logged_in())
if self.transfers:
(last_command, balance_backup) = self.transfers[-1]
if isinstance(last_command, ToGoCommand) \
and self.transfers:
second_last_command = self.transfers[-2]
if isinstance(second_last_command, BuyCommand):
second_last_command.set_to_go(False)
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
error_page('FEHLER: Aktion oder Ware "%s" nicht bekannt' % line)
else:
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
else:
status.buy(item)
else:
try:
status.login(line)
except urllib2.URLError as e:
error_page('FEHLER bei Kommunikation mit Server "%s"' % str(e))
def main():
colorama.init()
status = Status()
while True:
clear()
status.dump()
print_prompt()
l = sys.stdin.readline()
if not l:
break
line = l.rstrip()
handle(line, status)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
pass