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.

59
db.py
View File

@ -54,41 +54,74 @@ class PurchaseEntry(Base):
def __init__(self, purchase, product, amount):
self.product = product
self.product_bar_code = product.bar_code
self.purchase_id = purchase.id
self.purchase = purchase
self.amount = amount
def __repr__(self):
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):
__tablename__ = 'purchases'
id = Column(Integer, primary_key=True)
time = Column(DateTime)
user_name = Column(Integer, ForeignKey('users.name'))
# user_name = Column(Integer, ForeignKey('users.name'))
price = Column(Integer)
performed = Column(Boolean)
# performed = Column(Boolean)
user = relationship(User, backref=backref('purchases', order_by=id))
products = relationship(PurchaseEntry, backref=backref("purchase"))
# user = relationship(User, backref=backref('purchases', order_by=id))
# 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):
self.performed = performed
def __init__(self):
pass
def __repr__(self):
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):
self.price = 0
for entry in self.products:
for entry in self.entries:
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):
if self.performed:
print "This transaction has already been performed"
else:
self.time = datetime.datetime.now()
self.set_price()
self.user.credit -= self.price
self.performed = True
for t in self.transactions:
t.perform_transaction()
# self.user.credit -= self.price
# self.performed = True

View File

@ -69,3 +69,18 @@ def select_from_list(list):
return list[int(choice)-1]
else:
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:
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):
print
print '[%s]' % self.name
@ -136,21 +183,63 @@ class ChargeMenu(Menu):
self.session.close()
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):
def __init__(self):
Menu.__init__(self, 'Show user')
def _execute(self):
self.session = Session()
self.print_header()
user = None
while user == None:
search_str = self.input_str('User name or card number> ')
user = search_ui(search_user, search_str, 'user', Session())
user = self.input_user('User name or card number> ')
print 'User name: %s' % user.name
print 'Card number: %s' % user.card
print 'Credit: %s' % user.credit
self.print_transactions(user)
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):
def __init__(self):
@ -158,49 +247,75 @@ class BuyMenu(Menu):
def _execute(self):
self.print_header()
session = Session()
user = None
products = []
self.session = Session()
self.purchase = Purchase()
while True:
self.print_partial_purchase(user, products)
self.print_purchase()
print {(False,False): 'Enter user or product identification',
(False,True): 'Enter user identification or more products',
(True,False): 'Enter product identification',
(True,True): 'Enter more products, or an empty line to confirm'
}[(user != None, len(products) > 0)]
string = self.input_str()
if string == '':
if user == None or len(products) == 0:
(True,False): 'Enter product identification or more users',
(True,True): 'Enter more products or users, or an empty line to confirm'
}[(len(self.purchase.transactions) > 0,
len(self.purchase.entries) > 0)]
thing = self.input_thing(empty_input_permitted=True)
if thing == None:
if not self.complete_input():
if confirm('Not enough information entered. Abort purchase? (y/n) '):
return False
continue
break
(value_type,value) = dwim_search(string, session)
if value != None:
if value_type == 'user':
user = value
elif value_type == 'product':
products.append(value)
print 'OK purchase'
print self.format_partial_purchase(user, products)
# TODO build Purchase object and commit
if isinstance(thing, User):
Transaction(thing, purchase=self.purchase)
elif isinstance(thing, Product):
PurchaseEntry(self.purchase, thing, 1)
# for user in self.users:
# Transaction(user, purchase=self.purchase)
# for product in self.products:
# 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()
return True
def complete_input(self):
return self.purchase.is_complete()
def format_partial_purchase(self, user, products):
if user == None and len(products) == 0:
return
strings = []
if user != None:
strings.append(' user: ' + user.name)
if len(products) > 0:
strings.append(' products: ' + ', '.join(map(lambda p: p.name, products)))
return '\n'.join(strings)
def format_purchase(self):
self.purchase.set_price()
transactions = self.purchase.transactions
entries = self.purchase.entries
if len(transactions) == 0 and len(entries) == 0:
return None
string = 'Purchase:'
string += '\n buyers: '
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):
info = self.format_partial_purchase(user, products)
def print_purchase(self):
info = self.format_purchase()
if info != None:
print 'Your purchase:\n' + info
print info
class ProductListMenu(Menu):
@ -258,6 +373,9 @@ def dwim_search(string, session):
def search_ui(search_fun, search_str, thing, 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):
return result
if len(result) == 0:
@ -282,7 +400,7 @@ def retrieve_product(search_str, session):
#main = MainMenu()
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()],
exit_msg='happy happy joy joy')
main.execute()