235 lines
9.1 KiB
Python
235 lines
9.1 KiB
Python
import sqlalchemy
|
|
|
|
from dibbler.conf import config
|
|
from dibbler.models import (
|
|
Product,
|
|
Purchase,
|
|
PurchaseEntry,
|
|
Transaction,
|
|
User,
|
|
)
|
|
|
|
from .helpermenus import Menu
|
|
|
|
|
|
class BuyMenu(Menu):
|
|
def __init__(self, session=None):
|
|
Menu.__init__(self, "Buy", uses_db=True)
|
|
if session:
|
|
self.session = session
|
|
self.superfast_mode = False
|
|
self.help_text = """
|
|
Each purchase may contain one or more products and one or more buyers.
|
|
|
|
Enter products (by name or bar code) and buyers (by name or bar code)
|
|
in any order. The information gathered so far is displayed after each
|
|
addition, and you can type 'what' at any time to redisplay it.
|
|
|
|
When finished, write an empty line to confirm the purchase.\n"""
|
|
|
|
@staticmethod
|
|
def credit_check(user):
|
|
"""
|
|
|
|
:param user:
|
|
:type user: User
|
|
:rtype: boolean
|
|
"""
|
|
assert isinstance(user, User)
|
|
|
|
return user.credit > config.getint("limits", "low_credit_warning_limit")
|
|
|
|
def low_credit_warning(self, user, timeout=False):
|
|
assert isinstance(user, User)
|
|
|
|
print("***********************************************************************")
|
|
print("***********************************************************************")
|
|
print("")
|
|
print("$$\ $$\ $$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\\")
|
|
print("$$ | $\ $$ |$$ __$$\ $$ __$$\ $$$\ $$ |\_$$ _|$$$\ $$ |$$ __$$\\")
|
|
print("$$ |$$$\ $$ |$$ / $$ |$$ | $$ |$$$$\ $$ | $$ | $$$$\ $$ |$$ / \__|")
|
|
print("$$ $$ $$\$$ |$$$$$$$$ |$$$$$$$ |$$ $$\$$ | $$ | $$ $$\$$ |$$ |$$$$\\")
|
|
print("$$$$ _$$$$ |$$ __$$ |$$ __$$< $$ \$$$$ | $$ | $$ \$$$$ |$$ |\_$$ |")
|
|
print("$$$ / \$$$ |$$ | $$ |$$ | $$ |$$ |\$$$ | $$ | $$ |\$$$ |$$ | $$ |")
|
|
print("$$ / \$$ |$$ | $$ |$$ | $$ |$$ | \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |")
|
|
print("\__/ \__|\__| \__|\__| \__|\__| \__|\______|\__| \__| \______/")
|
|
print("")
|
|
print("***********************************************************************")
|
|
print("***********************************************************************")
|
|
print("")
|
|
print(
|
|
f"USER {user.name} HAS LOWER CREDIT THAN {config.getint('limits', 'low_credit_warning_limit'):d}."
|
|
)
|
|
print("THIS PURCHASE WILL CHARGE YOUR CREDIT TWICE AS MUCH.")
|
|
print("CONSIDER PUTTING MONEY IN THE BOX TO AVOID THIS.")
|
|
print("")
|
|
print("Do you want to continue with this purchase?")
|
|
|
|
if timeout:
|
|
print("THIS PURCHASE WILL AUTOMATICALLY BE PERFORMED IN 3 MINUTES!")
|
|
return self.confirm(prompt=">", default=True, timeout=180)
|
|
else:
|
|
return self.confirm(prompt=">", default=True)
|
|
|
|
def add_thing_to_purchase(self, thing, amount=1):
|
|
if isinstance(thing, User):
|
|
if thing.is_anonymous():
|
|
print("---------------------------------------------")
|
|
print("| You are now purchasing as the user anonym.|")
|
|
print("| You have to put money in the anonym-jar. |")
|
|
print("---------------------------------------------")
|
|
|
|
if not self.credit_check(thing):
|
|
if self.low_credit_warning(user=thing, timeout=self.superfast_mode):
|
|
Transaction(thing, purchase=self.purchase, penalty=2)
|
|
else:
|
|
return False
|
|
else:
|
|
Transaction(thing, purchase=self.purchase)
|
|
elif isinstance(thing, Product):
|
|
if self.purchase.entries:
|
|
for entry in self.purchase.entries:
|
|
if entry.product == thing:
|
|
entry.amount += amount
|
|
return True
|
|
PurchaseEntry(self.purchase, thing, amount)
|
|
return True
|
|
|
|
def _execute(self, initial_contents=None):
|
|
self.print_header()
|
|
self.purchase = Purchase()
|
|
self.exit_confirm_msg = None
|
|
self.superfast_mode = False
|
|
|
|
if initial_contents is None:
|
|
initial_contents = []
|
|
|
|
for thing, num in initial_contents:
|
|
self.add_thing_to_purchase(thing, num)
|
|
|
|
def is_product(candidate):
|
|
return isinstance(candidate[0], Product)
|
|
|
|
if len(initial_contents) > 0 and all(map(is_product, initial_contents)):
|
|
self.superfast_mode = True
|
|
print("***********************************************")
|
|
print("****** Buy menu is in SUPERFASTmode[tm]! ******")
|
|
print("*** The purchase will be stored immediately ***")
|
|
print("*** when you enter a user. ***")
|
|
print("***********************************************")
|
|
|
|
while True:
|
|
self.print_purchase()
|
|
self.printc(
|
|
{
|
|
(False, False): "Enter user or product identification",
|
|
(False, True): "Enter user identification or more products",
|
|
(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)]
|
|
)
|
|
|
|
# Read in a 'thing' (product or user):
|
|
line = self.input_multiple(
|
|
add_nonexisting=("user", "product"),
|
|
empty_input_permitted=True,
|
|
find_hidden_products=False,
|
|
)
|
|
if line is not None:
|
|
thing, num = line
|
|
else:
|
|
thing, num = None, 0
|
|
|
|
# Possibly exit from the menu:
|
|
if thing is None:
|
|
if not self.complete_input():
|
|
if self.confirm(
|
|
"Not enough information entered. Abort purchase?", default=True
|
|
):
|
|
return False
|
|
continue
|
|
break
|
|
else:
|
|
# once we get something in the
|
|
# purchase, we want to protect the
|
|
# user from accidentally killing it
|
|
self.exit_confirm_msg = "Abort purchase?"
|
|
|
|
# Add the thing to our purchase object:
|
|
if not self.add_thing_to_purchase(thing, amount=num):
|
|
continue
|
|
|
|
# In super-fast mode, we complete the purchase once we get a user:
|
|
if self.superfast_mode and isinstance(thing, User):
|
|
break
|
|
|
|
self.purchase.perform_purchase()
|
|
self.session.add(self.purchase)
|
|
try:
|
|
self.session.commit()
|
|
except sqlalchemy.exc.SQLAlchemyError as e:
|
|
print(f"Could not store purchase: {e}")
|
|
else:
|
|
print("Purchase stored.")
|
|
self.print_purchase()
|
|
for t in self.purchase.transactions:
|
|
if not t.user.is_anonymous():
|
|
print(f"User {t.user.name}'s credit is now {t.user.credit:d} kr")
|
|
if t.user.credit < config.getint("limits", "low_credit_warning_limit"):
|
|
print(
|
|
f"USER {t.user.name} HAS LOWER CREDIT THAN {config.getint('limits', 'low_credit_warning_limit'):d},",
|
|
"AND SHOULD CONSIDER PUTTING SOME MONEY IN THE BOX.",
|
|
)
|
|
|
|
# Superfast mode skips a linebreak for some reason.
|
|
if self.superfast_mode:
|
|
print("")
|
|
return True
|
|
|
|
def complete_input(self):
|
|
return self.purchase.is_complete()
|
|
|
|
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(
|
|
[t.user.name + ("*" if not self.credit_check(t.user) else "") for t in transactions]
|
|
)
|
|
string += "\n products: "
|
|
if len(entries) == 0:
|
|
string += "(empty)"
|
|
else:
|
|
string += "\n "
|
|
string += "\n ".join(
|
|
[f"{e.amount:d}x {e.product.name} ({e.product.price:d} kr)" for e in entries]
|
|
)
|
|
if len(transactions) > 1:
|
|
string += f"\n price per person: {self.purchase.price_per_transaction():d} kr"
|
|
if any(t.penalty > 1 for t in transactions):
|
|
# TODO: Use penalty multiplier instead of 2
|
|
string += f" *({self.purchase.price_per_transaction() * 2:d} kr)"
|
|
|
|
string += f"\n total price: {self.purchase.price:d} kr"
|
|
|
|
if any(t.penalty > 1 for t in transactions):
|
|
total = sum(self.purchase.price_per_transaction() * t.penalty for t in transactions)
|
|
string += f"\n *total with penalty: {total} kr"
|
|
|
|
return string
|
|
|
|
def print_purchase(self):
|
|
info = self.format_purchase()
|
|
if info is not None:
|
|
self.set_context(info)
|