Noenlunde fungerende opplegg for kjøp, ymse små forbedringer.

* Ny tabell Transaction som brukes for alle transaksjoner; hver
  transaksjon kan være knyttet til en Purchase eller ha en tekstlig
  beskrivelse.  (BuyMenu og TransferMenu viser de to måtene å bruke
  Transaction på)

* Hvert kjøp kan ha flere brukere.  Prisen fordeles likt blant
  kjøperne (for øyeblikket antar jeg at alle pengebeløp i databasen er
  lagret i kroner, og når totalprisen for et kjøp ikke går opp i
  antall kjøpere rundes det ned til et helt antall kroner)

* Forbedret input i BuyMenu -- den gjetter på om man skriver inn et
  produkt eller en bruker basert på hvor den finner treff.  (Hvis
  treffene er like gode begge steder velges det vilkårlig -- dette kan
  endres om det viser seg å være et problem i praksis)

* BuyMenu lagrer faktisk kjøpene i databasen.

* ShowUserMenu viser alle transaksjonene til brukeren.  Dette kan bli
  mye etter hvert, så det bør sikkert begrenses på et eller annet vis
  (for eksempel at den bare viser de siste N, for et egnet naturlig
  tall N).
This commit is contained in:
Øystein Ingmar Skartsæterhagen 2010-05-08 18:05:28 +00:00
parent b9f5c39a76
commit b648c27473
4 changed files with 220 additions and 54 deletions

BIN
data

Binary file not shown.

63
db.py
View File

@ -54,41 +54,74 @@ class PurchaseEntry(Base):
def __init__(self, purchase, product, amount): def __init__(self, purchase, product, amount):
self.product = product self.product = product
self.product_bar_code = product.bar_code self.product_bar_code = product.bar_code
self.purchase_id = purchase.id self.purchase = purchase
self.amount = amount self.amount = amount
def __repr__(self): def __repr__(self):
return "<PurchaseEntry('%s', '%s', '%s')>" % (self.purchase.user.user, self.product.name, self.amount ) return "<PurchaseEntry('%s', '%s', '%s')>" % (self.purchase.user.user, self.product.name, self.amount )
class Transaction(Base):
__tablename__ = 'transactions'
id = Column(Integer, primary_key=True)
time = Column(DateTime)
user_name = Column(String(10), ForeignKey('users.name'))
amount = Column(Integer)
description = Column(String(50))
purchase_id = Column(Integer, ForeignKey('purchases.id'))
user = relationship(User, backref=backref('transactions', order_by=time))
def __init__(self, user, amount=0, description=None, purchase=None):
self.user = user
self.amount = amount
self.description = description
self.purchase = purchase
def perform_transaction(self):
self.time = datetime.datetime.now()
self.user.credit -= self.amount
class Purchase(Base): class Purchase(Base):
__tablename__ = 'purchases' __tablename__ = 'purchases'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
time = Column(DateTime) time = Column(DateTime)
user_name = Column(Integer, ForeignKey('users.name')) # user_name = Column(Integer, ForeignKey('users.name'))
price = Column(Integer) price = Column(Integer)
performed = Column(Boolean) # performed = Column(Boolean)
user = relationship(User, backref=backref('purchases', order_by=id)) # user = relationship(User, backref=backref('purchases', order_by=id))
products = relationship(PurchaseEntry, backref=backref("purchase")) # users = relationship(User, secondary=purchase_user, backref='purhcases'
transactions = relationship(Transaction, order_by=Transaction.user_name, backref='purchase')
entries = relationship(PurchaseEntry, backref=backref("purchase"))
def __init__(self ,performed=False): def __init__(self):
self.performed = performed pass
def __repr__(self): def __repr__(self):
return "<Purchase('%s', '%s', '%s')>" % (self.user.name, self.price, self.time.strftime('%c')) return "<Purchase('%s', '%s', '%s')>" % (self.user.name, self.price, self.time.strftime('%c'))
def is_complete(self):
return len(self.transactions) > 0 and len(self.entries) > 0
def price_per_transaction(self):
return self.price/len(self.transactions)
def set_price(self): def set_price(self):
self.price = 0 self.price = 0
for entry in self.products: for entry in self.entries:
self.price += entry.amount*entry.product.price self.price += entry.amount*entry.product.price
if len(self.transactions) > 0:
for t in self.transactions:
t.amount = self.price_per_transaction()
def perform_purchase(self): def perform_purchase(self):
if self.performed: self.time = datetime.datetime.now()
print "This transaction has already been performed" self.set_price()
else: for t in self.transactions:
self.time = datetime.datetime.now() t.perform_transaction()
self.set_price() # self.user.credit -= self.price
self.user.credit -= self.price # self.performed = True
self.performed = True

View File

@ -69,3 +69,18 @@ def select_from_list(list):
return list[int(choice)-1] return list[int(choice)-1]
else: else:
return None return None
def argmax(d, all=False, value=None):
maxarg = None
maxargs = []
if value != None:
dd = d
d = {}
for key in dd.keys():
d[key] = value(dd[key])
for key in d.keys():
if maxarg == None or d[key] > d[maxarg]:
maxarg = key
if all:
return filter(lambda k: d[k] == d[maxarg], d.keys())
return maxarg

View File

@ -57,6 +57,53 @@ class Menu():
except ValueError: except ValueError:
print 'Please enter an integer' print 'Please enter an integer'
def input_user(self, prompt=None):
user = None
while user == None:
user = retrieve_user(self.input_str(prompt),
self.session)
return user
def input_product(self, prompt=None):
product = None
while product == None:
product = retrieve_product(self.input_str(prompt),
self.session)
return product
def input_thing(self, prompt=None, permitted_things=('user','product'),
empty_input_permitted=False):
result = None
while result == None:
search_str = self.input_str(prompt)
if search_str == '' and empty_input_permitted:
return None
result = self.search_for_thing(search_str, permitted_things)
return result
def search_for_thing(self, search_str, permitted_things=('user','product')):
search_fun = {'user': search_user,
'product': search_product}
results = {}
result_values = {}
for thing in permitted_things:
results[thing] = search_fun[thing](search_str, self.session)
result_values[thing] = self.search_result_value(results[thing])
selected_thing = argmax(result_values)
return search_ui2(search_str, results[selected_thing],
selected_thing, self.session)
def search_result_value(self, result):
if result == None:
return 0
if not isinstance(result, list):
return 3
if len(result) == 0:
return 0
if len(result) == 1:
return 2
return 1
def print_header(self): def print_header(self):
print print
print '[%s]' % self.name print '[%s]' % self.name
@ -136,21 +183,63 @@ class ChargeMenu(Menu):
self.session.close() self.session.close()
break break
class TransferMenu(Menu):
def __init__(self):
Menu.__init__(self, 'Transfer credit between users')
def _execute(self):
self.print_header()
self.session = Session()
amount = self.input_int('Transfer amount> ')
user1 = self.input_user('From user> ')
user2 = self.input_user('To user> ')
t1 = Transaction(user1, amount,
'Transfer to '+user2.name)
t2 = Transaction(user2, -amount,
'Transfer from '+user1.name)
t1.perform_transaction()
t2.perform_transaction()
self.session.add(t1)
self.session.add(t2)
self.session.commit()
print 'Transfered %d kr from %s to %s' % (amount, user1, user2)
print 'User %s\'s credit is now %d kr' % (user1, user1.credit)
print 'User %s\'s credit is now %d kr' % (user2, user2.credit)
self.session.close()
self.pause()
class ShowUserMenu(Menu): class ShowUserMenu(Menu):
def __init__(self): def __init__(self):
Menu.__init__(self, 'Show user') Menu.__init__(self, 'Show user')
def _execute(self): def _execute(self):
self.session = Session()
self.print_header() self.print_header()
user = None user = self.input_user('User name or card number> ')
while user == None:
search_str = self.input_str('User name or card number> ')
user = search_ui(search_user, search_str, 'user', Session())
print 'User name: %s' % user.name print 'User name: %s' % user.name
print 'Card number: %s' % user.card print 'Card number: %s' % user.card
print 'Credit: %s' % user.credit print 'Credit: %s' % user.credit
self.print_transactions(user)
self.pause() self.pause()
def print_transactions(self, user):
if len(user.transactions) == 0:
print 'No transactions'
return
print 'Transactions:'
for t in user.transactions:
string = ' * %s: %d kr, ' % \
(t.time.strftime('%Y-%m-%d %H:%M'), t.amount)
if t.purchase:
string += 'purchase ('
string += ', '.join(map(lambda e: e.product.name,
t.purchase.entries))
string += ')'
else:
string += t.description
print string
class BuyMenu(Menu): class BuyMenu(Menu):
def __init__(self): def __init__(self):
@ -158,49 +247,75 @@ class BuyMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() self.print_header()
session = Session() self.session = Session()
user = None self.purchase = Purchase()
products = []
while True: while True:
self.print_partial_purchase(user, products) self.print_purchase()
print {(False,False): 'Enter user or product identification', print {(False,False): 'Enter user or product identification',
(False,True): 'Enter user identification or more products', (False,True): 'Enter user identification or more products',
(True,False): 'Enter product identification', (True,False): 'Enter product identification or more users',
(True,True): 'Enter more products, or an empty line to confirm' (True,True): 'Enter more products or users, or an empty line to confirm'
}[(user != None, len(products) > 0)] }[(len(self.purchase.transactions) > 0,
string = self.input_str() len(self.purchase.entries) > 0)]
if string == '': thing = self.input_thing(empty_input_permitted=True)
if user == None or len(products) == 0: if thing == None:
if not self.complete_input():
if confirm('Not enough information entered. Abort purchase? (y/n) '): if confirm('Not enough information entered. Abort purchase? (y/n) '):
return False return False
continue continue
break break
(value_type,value) = dwim_search(string, session) if isinstance(thing, User):
if value != None: Transaction(thing, purchase=self.purchase)
if value_type == 'user': elif isinstance(thing, Product):
user = value PurchaseEntry(self.purchase, thing, 1)
elif value_type == 'product':
products.append(value) # for user in self.users:
print 'OK purchase' # Transaction(user, purchase=self.purchase)
print self.format_partial_purchase(user, products) # for product in self.products:
# TODO build Purchase object and commit # PurchaseEntry(self.purchase, product, 1)
self.purchase.perform_purchase()
self.session.add(self.purchase)
self.session.commit()
print 'Purchase stored.' # TODO print info about purchase
self.print_purchase()
for t in self.purchase.transactions:
print 'User %s\'s credit is now %d kr' % (t.user.name, t.user.credit)
self.session.close()
self.pause() self.pause()
return True
def complete_input(self):
return self.purchase.is_complete()
def format_partial_purchase(self, user, products): def format_purchase(self):
if user == None and len(products) == 0: self.purchase.set_price()
return transactions = self.purchase.transactions
strings = [] entries = self.purchase.entries
if user != None: if len(transactions) == 0 and len(entries) == 0:
strings.append(' user: ' + user.name) return None
if len(products) > 0: string = 'Purchase:'
strings.append(' products: ' + ', '.join(map(lambda p: p.name, products))) string += '\n buyers: '
return '\n'.join(strings) if len(transactions) == 0:
string += '(empty)'
else:
string += ', '.join(map(lambda t: t.user.name, transactions))
string += '\n products: '
if len(entries) == 0:
string += '(empty)'
else:
string += ', '.join(map(lambda e: '%s (%d kr)'%(e.product.name, e.product.price),
entries))
if len(transactions) > 1:
string += '\n price per person: %d kr' % self.purchase.price_per_transaction()
string += '\n total price: %d kr' % self.purchase.price
return string
def print_partial_purchase(self, user, products): def print_purchase(self):
info = self.format_partial_purchase(user, products) info = self.format_purchase()
if info != None: if info != None:
print 'Your purchase:\n' + info print info
class ProductListMenu(Menu): class ProductListMenu(Menu):
@ -258,6 +373,9 @@ def dwim_search(string, session):
def search_ui(search_fun, search_str, thing, session): def search_ui(search_fun, search_str, thing, session):
result = search_fun(search_str, session) result = search_fun(search_str, session)
return search_ui2(search_str, result, thing, session)
def search_ui2(search_str, result, thing, session):
if not isinstance(result, list): if not isinstance(result, list):
return result return result
if len(result) == 0: if len(result) == 0:
@ -282,7 +400,7 @@ def retrieve_product(search_str, session):
#main = MainMenu() #main = MainMenu()
main = Menu('Dibbler main menu', main = Menu('Dibbler main menu',
items=[BuyMenu(), ChargeMenu(), Menu('Add user'), Menu('Add product'), items=[BuyMenu(), ChargeMenu(), TransferMenu(), Menu('Add user'), Menu('Add product'),
ShowUserMenu(), ProductListMenu()], ShowUserMenu(), ProductListMenu()],
exit_msg='happy happy joy joy') exit_msg='happy happy joy joy')
main.execute() main.execute()