Merge pull request #3 from Programvareverkstedet/restructure-project
Restructure project
This commit is contained in:
commit
8a6a0c12ba
|
@ -1,2 +1,8 @@
|
|||
result
|
||||
result-*
|
||||
result-*
|
||||
|
||||
dist
|
||||
|
||||
test.db
|
||||
|
||||
.ruff_cache
|
30
ALTER
30
ALTER
|
@ -1,30 +0,0 @@
|
|||
torjehoa_dibblerdummy=> ALTER TABLE products ADD stock integer;
|
||||
|
||||
Table "torjehoa_dibblerdummy.products"
|
||||
Column | Type | Modifiers
|
||||
----------+-----------------------+-----------
|
||||
bar_code | character varying(13) | not null
|
||||
name | character varying(45) |
|
||||
price | integer |
|
||||
Indexes:
|
||||
"products_pkey" PRIMARY KEY, btree (bar_code)
|
||||
|
||||
torjehoa_dibblerdummy=> ALTER TABLE products ADD stock integer;
|
||||
ALTER TABLE
|
||||
torjehoa_dibblerdummy=> UPDATE products SET stock = 0;
|
||||
UPDATE 102
|
||||
torjehoa_dibblerdummy=> ALTER TABLE products ALTER stock SET NOT NULL;
|
||||
ALTER TABLE
|
||||
torjehoa_dibblerdummy=> \d products
|
||||
Table "torjehoa_dibblerdummy.products"
|
||||
Column | Type | Modifiers
|
||||
----------+-----------------------+-----------
|
||||
bar_code | character varying(13) | not null
|
||||
name | character varying(45) |
|
||||
price | integer |
|
||||
stock | integer | not null
|
||||
Indexes:
|
||||
"products_pkey" PRIMARY KEY, btree (bar_code)
|
||||
|
||||
torjehoa_dibblerdummy=>
|
||||
|
4
conf.py
4
conf.py
|
@ -1,8 +1,8 @@
|
|||
db_url = 'postgresql://robertem@127.0.0.1/pvvvv'
|
||||
db_url = "postgresql://robertem@127.0.0.1/pvvvv"
|
||||
quit_allowed = True
|
||||
stop_allowed = False
|
||||
show_tracebacks = True
|
||||
input_encoding = 'utf8'
|
||||
input_encoding = "utf8"
|
||||
|
||||
low_credit_warning_limit = -100
|
||||
user_recent_transaction_limit = 100
|
||||
|
|
176
db.py
176
db.py
|
@ -1,176 +0,0 @@
|
|||
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine, DateTime, Boolean
|
||||
from sqlalchemy.orm import sessionmaker, relationship, backref
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from math import ceil, floor
|
||||
import datetime
|
||||
import conf
|
||||
|
||||
engine = create_engine(conf.db_url)
|
||||
Base = declarative_base()
|
||||
Session = sessionmaker(bind=engine)
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
name = Column(String(10), primary_key=True)
|
||||
card = Column(String(20))
|
||||
rfid = Column(String(20))
|
||||
credit = Column(Integer)
|
||||
|
||||
name_re = r"[a-z]+"
|
||||
card_re = r"(([Nn][Tt][Nn][Uu])?[0-9]+)?"
|
||||
rfid_re = r"[0-9a-fA-F]*"
|
||||
|
||||
def __init__(self, name, card, rfid=None, credit=0):
|
||||
self.name = name
|
||||
if card == '':
|
||||
card = None
|
||||
self.card = card
|
||||
if rfid == '':
|
||||
rfid = None
|
||||
self.rfid = rfid
|
||||
self.credit = credit
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User('{self.name}')>"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def is_anonymous(self):
|
||||
return self.card == '11122233'
|
||||
|
||||
|
||||
class Product(Base):
|
||||
__tablename__ = 'products'
|
||||
|
||||
product_id = Column(Integer, primary_key=True)
|
||||
bar_code = Column(String(13))
|
||||
name = Column(String(45))
|
||||
price = Column(Integer)
|
||||
stock = Column(Integer)
|
||||
hidden = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
bar_code_re = r"[0-9]+"
|
||||
name_re = r".+"
|
||||
name_length = 45
|
||||
|
||||
def __init__(self, bar_code, name, price, stock=0, hidden = False):
|
||||
self.name = name
|
||||
self.bar_code = bar_code
|
||||
self.price = price
|
||||
self.stock = stock
|
||||
self.hidden = hidden
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Product('{self.name}', '{self.bar_code}', '{self.price}', '{self.stock}', '{self.hidden}')>"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class UserProducts(Base):
|
||||
__tablename__ = 'user_products'
|
||||
user_name = Column(String(10), ForeignKey('users.name'), primary_key=True)
|
||||
product_id = Column(Integer, ForeignKey("products.product_id"), primary_key=True)
|
||||
count = Column(Integer)
|
||||
sign = Column(Integer)
|
||||
|
||||
user = relationship(User, backref=backref('products', order_by=count.desc()), lazy='joined')
|
||||
product = relationship(Product, backref="users", lazy='joined')
|
||||
|
||||
|
||||
class PurchaseEntry(Base):
|
||||
__tablename__ = 'purchase_entries'
|
||||
id = Column(Integer, primary_key=True)
|
||||
purchase_id = Column(Integer, ForeignKey("purchases.id"))
|
||||
product_id = Column(Integer, ForeignKey("products.product_id"))
|
||||
amount = Column(Integer)
|
||||
|
||||
product = relationship(Product, backref="purchases")
|
||||
|
||||
def __init__(self, purchase, product, amount):
|
||||
self.product = product
|
||||
self.product_bar_code = product.bar_code
|
||||
self.purchase = purchase
|
||||
self.amount = amount
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PurchaseEntry('{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'))
|
||||
penalty = Column(Integer)
|
||||
|
||||
user = relationship(User, backref=backref('transactions', order_by=time))
|
||||
|
||||
def __init__(self, user, amount=0, description=None, purchase=None, penalty=1):
|
||||
self.user = user
|
||||
self.amount = amount
|
||||
self.description = description
|
||||
self.purchase = purchase
|
||||
self.penalty = penalty
|
||||
|
||||
def perform_transaction(self, ignore_penalty=False):
|
||||
self.time = datetime.datetime.now()
|
||||
if not ignore_penalty:
|
||||
self.amount *= self.penalty
|
||||
self.user.credit -= self.amount
|
||||
|
||||
|
||||
class Purchase(Base):
|
||||
__tablename__ = 'purchases'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
time = Column(DateTime)
|
||||
price = Column(Integer)
|
||||
|
||||
transactions = relationship(Transaction, order_by=Transaction.user_name, backref='purchase')
|
||||
entries = relationship(PurchaseEntry, backref=backref("purchase"))
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Purchase({int(self.id):d}, {self.price:d}, '{self.time.strftime('%c')}')>"
|
||||
|
||||
def is_complete(self):
|
||||
return len(self.transactions) > 0 and len(self.entries) > 0
|
||||
|
||||
def price_per_transaction(self, round_up=True):
|
||||
if round_up:
|
||||
return int(ceil(float(self.price)/len(self.transactions)))
|
||||
else:
|
||||
return int(floor(float(self.price)/len(self.transactions)))
|
||||
|
||||
def set_price(self, round_up=True):
|
||||
self.price = 0
|
||||
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(round_up=round_up)
|
||||
|
||||
def perform_purchase(self, ignore_penalty=False, round_up=True):
|
||||
self.time = datetime.datetime.now()
|
||||
self.set_price(round_up=round_up)
|
||||
for t in self.transactions:
|
||||
t.perform_transaction(ignore_penalty=ignore_penalty)
|
||||
for entry in self.entries:
|
||||
entry.product.stock -= entry.amount
|
||||
|
||||
def perform_soft_purchase(self, price, round_up=True):
|
||||
self.time = datetime.datetime.now()
|
||||
self.price = price
|
||||
for t in self.transactions:
|
||||
t.amount = self.price_per_transaction(round_up=round_up)
|
||||
for t in self.transactions:
|
||||
t.perform_transaction()
|
|
@ -1,7 +1,4 @@
|
|||
{ pkgs ? import <nixos-unstable> { } }:
|
||||
|
||||
rec {
|
||||
|
||||
{
|
||||
dibbler = pkgs.callPackage ./nix/dibbler.nix { };
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# This module is supposed to act as a singleton and be filled
|
||||
# with config variables by cli.py
|
||||
|
||||
import configparser
|
||||
|
||||
config = configparser.ConfigParser()
|
|
@ -0,0 +1,7 @@
|
|||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from dibbler.conf import config
|
||||
|
||||
engine = create_engine(config.get("database", "url"))
|
||||
Session = sessionmaker(bind=engine)
|
|
@ -6,20 +6,20 @@ from brother_ql.devicedependent import label_type_specs
|
|||
|
||||
|
||||
def px2mm(px, dpi=300):
|
||||
return (25.4 * px)/dpi
|
||||
return (25.4 * px) / dpi
|
||||
|
||||
|
||||
class BrotherLabelWriter(ImageWriter):
|
||||
def __init__(self, typ='62', max_height=350, rot=False, text=None):
|
||||
def __init__(self, typ="62", max_height=350, rot=False, text=None):
|
||||
super(BrotherLabelWriter, self).__init__()
|
||||
assert typ in label_type_specs
|
||||
self.rot = rot
|
||||
if self.rot:
|
||||
self._h, self._w = label_type_specs[typ]['dots_printable']
|
||||
self._h, self._w = label_type_specs[typ]["dots_printable"]
|
||||
if self._w == 0 or self._w > max_height:
|
||||
self._w = min(max_height, self._h / 2)
|
||||
else:
|
||||
self._w, self._h = label_type_specs[typ]['dots_printable']
|
||||
self._w, self._h = label_type_specs[typ]["dots_printable"]
|
||||
if self._h == 0 or self._h > max_height:
|
||||
self._h = min(max_height, self._w / 2)
|
||||
self._xo = 0.0
|
||||
|
@ -31,36 +31,40 @@ class BrotherLabelWriter(ImageWriter):
|
|||
super(BrotherLabelWriter, self)._init(code)
|
||||
|
||||
def calculate_size(self, modules_per_line, number_of_lines, dpi=300):
|
||||
x, y = super(BrotherLabelWriter, self).calculate_size(modules_per_line, number_of_lines, dpi)
|
||||
x, y = super(BrotherLabelWriter, self).calculate_size(
|
||||
modules_per_line, number_of_lines, dpi
|
||||
)
|
||||
|
||||
self._xo = (px2mm(self._w)-px2mm(x))/2
|
||||
self._yo = (px2mm(self._h)-px2mm(y))
|
||||
self._xo = (px2mm(self._w) - px2mm(x)) / 2
|
||||
self._yo = px2mm(self._h) - px2mm(y)
|
||||
assert self._xo >= 0
|
||||
assert self._yo >= 0
|
||||
|
||||
return int(self._w), int(self._h)
|
||||
|
||||
def _paint_module(self, xpos, ypos, width, color):
|
||||
super(BrotherLabelWriter, self)._paint_module(xpos+self._xo, ypos+self._yo, width, color)
|
||||
super(BrotherLabelWriter, self)._paint_module(
|
||||
xpos + self._xo, ypos + self._yo, width, color
|
||||
)
|
||||
|
||||
def _paint_text(self, xpos, ypos):
|
||||
super(BrotherLabelWriter, self)._paint_text(xpos+self._xo, ypos+self._yo)
|
||||
super(BrotherLabelWriter, self)._paint_text(xpos + self._xo, ypos + self._yo)
|
||||
|
||||
def _finish(self):
|
||||
if self._title:
|
||||
width = self._w+1
|
||||
width = self._w + 1
|
||||
height = 0
|
||||
max_h = self._h - mm2px(self._yo, self.dpi)
|
||||
fs = int(max_h / 1.2)
|
||||
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "Stranger back in the Night.ttf")
|
||||
font_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
"Stranger back in the Night.ttf",
|
||||
)
|
||||
font = ImageFont.truetype(font_path, 10)
|
||||
while width > self._w or height > max_h:
|
||||
font = ImageFont.truetype(font_path, fs)
|
||||
width, height = font.getsize(self._title)
|
||||
fs -= 1
|
||||
pos = (
|
||||
(self._w-width)//2,
|
||||
0 - (height // 8)
|
||||
)
|
||||
pos = ((self._w - width) // 2, 0 - (height // 8))
|
||||
self._draw.text(pos, self._title, font=font, fill=self.foreground)
|
||||
return self._image
|
|
@ -0,0 +1,130 @@
|
|||
import pwd
|
||||
import subprocess
|
||||
import os
|
||||
import signal
|
||||
|
||||
from sqlalchemy import or_, and_
|
||||
|
||||
from ..models import User, Product
|
||||
|
||||
|
||||
def search_user(string, session, ignorethisflag=None):
|
||||
string = string.lower()
|
||||
exact_match = (
|
||||
session.query(User)
|
||||
.filter(or_(User.name == string, User.card == string, User.rfid == string))
|
||||
.first()
|
||||
)
|
||||
if exact_match:
|
||||
return exact_match
|
||||
user_list = (
|
||||
session.query(User)
|
||||
.filter(
|
||||
or_(
|
||||
User.name.ilike(f"%{string}%"),
|
||||
User.card.ilike(f"%{string}%"),
|
||||
User.rfid.ilike(f"%{string}%"),
|
||||
)
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return user_list
|
||||
|
||||
|
||||
def search_product(string, session, find_hidden_products=True):
|
||||
if find_hidden_products:
|
||||
exact_match = (
|
||||
session.query(Product)
|
||||
.filter(or_(Product.bar_code == string, Product.name == string))
|
||||
.first()
|
||||
)
|
||||
else:
|
||||
exact_match = (
|
||||
session.query(Product)
|
||||
.filter(
|
||||
or_(
|
||||
Product.bar_code == string,
|
||||
and_(Product.name == string, Product.hidden is False),
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if exact_match:
|
||||
return exact_match
|
||||
if find_hidden_products:
|
||||
product_list = (
|
||||
session.query(Product)
|
||||
.filter(
|
||||
or_(
|
||||
Product.bar_code.ilike(f"%{string}%"),
|
||||
Product.name.ilike(f"%{string}%"),
|
||||
)
|
||||
)
|
||||
.all()
|
||||
)
|
||||
else:
|
||||
product_list = (
|
||||
session.query(Product)
|
||||
.filter(
|
||||
or_(
|
||||
Product.bar_code.ilike(f"%{string}%"),
|
||||
and_(Product.name.ilike(f"%{string}%"), Product.hidden is False),
|
||||
)
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return product_list
|
||||
|
||||
|
||||
def system_user_exists(username):
|
||||
try:
|
||||
pwd.getpwnam(username)
|
||||
except KeyError:
|
||||
return False
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def guess_data_type(string):
|
||||
if string.startswith("ntnu") and string[4:].isdigit():
|
||||
return "card"
|
||||
if string.isdigit() and len(string) == 10:
|
||||
return "rfid"
|
||||
if string.isdigit() and len(string) in [8, 13]:
|
||||
return "bar_code"
|
||||
# if string.isdigit() and len(string) > 5:
|
||||
# return 'card'
|
||||
if string.isalpha() and string.islower() and system_user_exists(string):
|
||||
return "username"
|
||||
return None
|
||||
|
||||
|
||||
def argmax(d, all=False, value=None):
|
||||
maxarg = None
|
||||
if value is not None:
|
||||
dd = d
|
||||
d = {}
|
||||
for key in list(dd.keys()):
|
||||
d[key] = value(dd[key])
|
||||
for key in list(d.keys()):
|
||||
if maxarg is None or d[key] > d[maxarg]:
|
||||
maxarg = key
|
||||
if all:
|
||||
return [k for k in list(d.keys()) if d[k] == d[maxarg]]
|
||||
return maxarg
|
||||
|
||||
|
||||
def less(string):
|
||||
"""
|
||||
Run less with string as input; wait until it finishes.
|
||||
"""
|
||||
# If we don't ignore SIGINT while running the `less` process,
|
||||
# it will become a zombie when someone presses C-c.
|
||||
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
env = dict(os.environ)
|
||||
env["LESSSECURE"] = "1"
|
||||
proc = subprocess.Popen("less", env=env, encoding="utf-8", stdin=subprocess.PIPE)
|
||||
proc.communicate(string)
|
||||
signal.signal(signal.SIGINT, int_handler)
|
|
@ -1,47 +1,50 @@
|
|||
import os
|
||||
import datetime
|
||||
|
||||
import barcode
|
||||
import datetime
|
||||
from PIL import Image
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageFont
|
||||
from brother_ql import BrotherQLRaster
|
||||
from brother_ql import create_label
|
||||
from brother_ql import BrotherQLRaster, create_label
|
||||
from brother_ql.backends import backend_factory
|
||||
from brother_ql.devicedependent import label_type_specs
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from barcode_helpers import BrotherLabelWriter
|
||||
from .barcode_helpers import BrotherLabelWriter
|
||||
|
||||
|
||||
def print_name_label(text, margin=10, rotate=False, label_type="62", printer_type="QL-700",):
|
||||
def print_name_label(
|
||||
text,
|
||||
margin=10,
|
||||
rotate=False,
|
||||
label_type="62",
|
||||
printer_type="QL-700",
|
||||
):
|
||||
if not rotate:
|
||||
width, height = label_type_specs[label_type]['dots_printable']
|
||||
width, height = label_type_specs[label_type]["dots_printable"]
|
||||
else:
|
||||
height, width = label_type_specs[label_type]['dots_printable']
|
||||
height, width = label_type_specs[label_type]["dots_printable"]
|
||||
|
||||
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ChopinScript.ttf")
|
||||
fs = 2000
|
||||
tw, th = width, height
|
||||
if width == 0:
|
||||
while th + 2*margin > height:
|
||||
while th + 2 * margin > height:
|
||||
font = ImageFont.truetype(font_path, fs)
|
||||
tw, th = font.getsize(text)
|
||||
fs -= 1
|
||||
width = tw+2*margin
|
||||
width = tw + 2 * margin
|
||||
elif height == 0:
|
||||
while tw + 2*margin > width:
|
||||
while tw + 2 * margin > width:
|
||||
font = ImageFont.truetype(font_path, fs)
|
||||
tw, th = font.getsize(text)
|
||||
fs -= 1
|
||||
height = th+2*margin
|
||||
height = th + 2 * margin
|
||||
else:
|
||||
while tw + 2*margin > width or th + 2*margin > height:
|
||||
while tw + 2 * margin > width or th + 2 * margin > height:
|
||||
font = ImageFont.truetype(font_path, fs)
|
||||
tw, th = font.getsize(text)
|
||||
fs -= 1
|
||||
|
||||
xp = (width//2)-(tw//2)
|
||||
yp = (height//2)-(th//2)
|
||||
xp = (width // 2) - (tw // 2)
|
||||
yp = (height // 2) - (th // 2)
|
||||
|
||||
im = Image.new("RGB", (width, height), (255, 255, 255))
|
||||
dr = ImageDraw.Draw(im)
|
||||
|
@ -58,8 +61,14 @@ def print_name_label(text, margin=10, rotate=False, label_type="62", printer_typ
|
|||
print_image(fn, printer_type, label_type)
|
||||
|
||||
|
||||
def print_bar_code(barcode_value, barcode_text, barcode_type="ean13", rotate=False, printer_type="QL-700",
|
||||
label_type="62"):
|
||||
def print_bar_code(
|
||||
barcode_value,
|
||||
barcode_text,
|
||||
barcode_type="ean13",
|
||||
rotate=False,
|
||||
printer_type="QL-700",
|
||||
label_type="62",
|
||||
):
|
||||
bar_coder = barcode.get_barcode_class(barcode_type)
|
||||
wr = BrotherLabelWriter(typ=label_type, rot=rotate, text=barcode_text, max_height=1000)
|
||||
|
||||
|
@ -75,12 +84,12 @@ def print_image(fn, printer_type="QL-700", label_type="62"):
|
|||
create_label(qlr, fn, label_type, threshold=70, cut=True)
|
||||
|
||||
be = backend_factory("pyusb")
|
||||
list_available_devices = be['list_available_devices']
|
||||
BrotherQLBackend = be['backend_class']
|
||||
list_available_devices = be["list_available_devices"]
|
||||
BrotherQLBackend = be["backend_class"]
|
||||
|
||||
ad = list_available_devices()
|
||||
assert ad
|
||||
string_descr = ad[0]['string_descr']
|
||||
string_descr = ad[0]["string_descr"]
|
||||
|
||||
printer = BrotherQLBackend(string_descr)
|
||||
|
|
@ -0,0 +1,512 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
from .helpers import *
|
||||
from ..models import Transaction
|
||||
from ..db import Session
|
||||
|
||||
|
||||
def getUser():
|
||||
while 1:
|
||||
string = input("user? ")
|
||||
session = Session()
|
||||
user = search_user(string, session)
|
||||
session.close()
|
||||
if not isinstance(user, list):
|
||||
return user.name
|
||||
i = 0
|
||||
if len(user) == 0:
|
||||
print("no matching string")
|
||||
if len(user) == 1:
|
||||
print("antar: ", user[0].name, "\n")
|
||||
return user[0].name
|
||||
if len(user) > 10:
|
||||
continue
|
||||
for u in user:
|
||||
print(i, u.name)
|
||||
i += 1
|
||||
try:
|
||||
n = int(input("enter number:"))
|
||||
except:
|
||||
print("invalid input, restarting")
|
||||
continue
|
||||
if (n > -1) and (n < i):
|
||||
return user[n].name
|
||||
|
||||
|
||||
def getProduct():
|
||||
while 1:
|
||||
string = input("product? ")
|
||||
session = Session()
|
||||
product = search_product(string, session)
|
||||
session.close()
|
||||
if not isinstance(product, list):
|
||||
return product.name
|
||||
i = 0
|
||||
if len(product) == 0:
|
||||
print("no matching string")
|
||||
if len(product) == 1:
|
||||
print("antar: ", product[0].name, "\n")
|
||||
return product[0].name
|
||||
if len(product) > 10:
|
||||
continue
|
||||
for u in product:
|
||||
print(i, u.name)
|
||||
i += 1
|
||||
try:
|
||||
n = int(input("enter number:"))
|
||||
except:
|
||||
print("invalid input, restarting")
|
||||
continue
|
||||
if (n > -1) and (n < i):
|
||||
return product[n].name
|
||||
|
||||
|
||||
class Database:
|
||||
# for varer
|
||||
varePersonAntall = defaultdict(dict) # varePersonAntall[Oreo][trygvrad] == 3
|
||||
vareDatoAntall = defaultdict(list) # dict->array
|
||||
vareUkedagAntall = defaultdict(list)
|
||||
# for personer
|
||||
personVareAntall = defaultdict(dict) # personVareAntall[trygvrad][Oreo] == 3
|
||||
personVareVerdi = defaultdict(dict) # personVareVerdi[trygvrad][Oreo] == 30 #[kr]
|
||||
personDatoVerdi = defaultdict(list) # dict->array
|
||||
personUkedagVerdi = defaultdict(list)
|
||||
# for global
|
||||
personPosTransactions = (
|
||||
{}
|
||||
) # personPosTransactions[trygvrad] == 100 #trygvrad har lagt 100kr i boksen
|
||||
personNegTransactions = (
|
||||
{}
|
||||
) # personNegTransactions[trygvrad» == 70 #trygvrad har tatt 70kr fra boksen
|
||||
globalVareAntall = {} # globalVareAntall[Oreo] == 3
|
||||
globalVareVerdi = {} # globalVareVerdi[Oreo] == 30 #[kr]
|
||||
globalPersonAntall = {} # globalPersonAntall[trygvrad] == 3
|
||||
globalPersonForbruk = {} # globalPersonVerdi == 30 #[kr]
|
||||
globalUkedagForbruk = []
|
||||
globalDatoVarer = []
|
||||
globalDatoForbruk = []
|
||||
pengebeholdning = []
|
||||
|
||||
|
||||
class InputLine:
|
||||
def __init__(self, u, p, t):
|
||||
self.inputUser = u
|
||||
self.inputProduct = p
|
||||
self.inputType = t
|
||||
|
||||
|
||||
def getDateDb(date, inp):
|
||||
try:
|
||||
year = inp.partition("-")
|
||||
month = year[2].partition("-")
|
||||
return datetime.datetime(int(year[0]), int(month[0]), int(month[2]))
|
||||
except:
|
||||
print("invalid date, setting date to date found in db")
|
||||
print(date)
|
||||
return date
|
||||
|
||||
|
||||
def dateToDateNumDb(date, startDate):
|
||||
deltaDays = date - startDate
|
||||
return int(deltaDays.days), date.weekday()
|
||||
|
||||
|
||||
def getInputType():
|
||||
inp = 0
|
||||
while not (inp == "1" or inp == "2" or inp == "3" or inp == "4"):
|
||||
print("type 1 for user-statistics")
|
||||
print("type 2 for product-statistics")
|
||||
print("type 3 for global-statistics")
|
||||
print("type 4 to enter loop-mode")
|
||||
inp = input("")
|
||||
return int(inp)
|
||||
|
||||
|
||||
def getProducts(products):
|
||||
product = []
|
||||
products = products.partition("¤")
|
||||
product.append(products[0])
|
||||
while products[1] == "¤":
|
||||
products = products[2].partition("¤")
|
||||
product.append(products[0])
|
||||
return product
|
||||
|
||||
|
||||
def getDateFile(date, inp):
|
||||
try:
|
||||
year = inp.partition("-")
|
||||
month = year[2].partition("-")
|
||||
return datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
||||
except:
|
||||
print("invalid date, setting date to date found on file file")
|
||||
print(date)
|
||||
return datetime.date(
|
||||
int(date.partition("-")[0]),
|
||||
int(date.partition("-")[2].partition("-")[0]),
|
||||
int(date.partition("-")[2].partition("-")[2]),
|
||||
)
|
||||
|
||||
|
||||
def dateToDateNumFile(date, startDate):
|
||||
year = date.partition("-")
|
||||
month = year[2].partition("-")
|
||||
day = datetime.date(int(year[0]), int(month[0]), int(month[2]))
|
||||
deltaDays = day - startDate
|
||||
return int(deltaDays.days), day.weekday()
|
||||
|
||||
|
||||
def clearDatabase(database):
|
||||
database.varePersonAntall.clear()
|
||||
database.vareDatoAntall.clear()
|
||||
database.vareUkedagAntall.clear()
|
||||
database.personVareAntall.clear()
|
||||
database.personVareVerdi.clear()
|
||||
database.personDatoVerdi.clear()
|
||||
database.personUkedagVerdi.clear()
|
||||
database.personPosTransactions.clear()
|
||||
database.personNegTransactions.clear()
|
||||
database.globalVareAntall.clear()
|
||||
database.globalVareVerdi.clear()
|
||||
database.globalPersonAntall.clear()
|
||||
database.globalPersonForbruk.clear()
|
||||
return database
|
||||
|
||||
|
||||
def addLineToDatabase(database, inputLine):
|
||||
if abs(inputLine.price) > 90000:
|
||||
return database
|
||||
# fyller inn for varer
|
||||
if (not inputLine.product == "") and (
|
||||
(inputLine.inputProduct == "") or (inputLine.inputProduct == inputLine.product)
|
||||
):
|
||||
database.varePersonAntall[inputLine.product][inputLine.user] = (
|
||||
database.varePersonAntall[inputLine.product].setdefault(inputLine.user, 0) + 1
|
||||
)
|
||||
if inputLine.product not in database.vareDatoAntall:
|
||||
database.vareDatoAntall[inputLine.product] = [0] * (inputLine.numberOfDays + 1)
|
||||
database.vareDatoAntall[inputLine.product][inputLine.dateNum] += 1
|
||||
if inputLine.product not in database.vareUkedagAntall:
|
||||
database.vareUkedagAntall[inputLine.product] = [0] * 7
|
||||
database.vareUkedagAntall[inputLine.product][inputLine.weekday] += 1
|
||||
# fyller inn for personer
|
||||
if (inputLine.inputUser == "") or (inputLine.inputUser == inputLine.user):
|
||||
if not inputLine.product == "":
|
||||
database.personVareAntall[inputLine.user][inputLine.product] = (
|
||||
database.personVareAntall[inputLine.user].setdefault(inputLine.product, 0) + 1
|
||||
)
|
||||
database.personVareVerdi[inputLine.user][inputLine.product] = (
|
||||
database.personVareVerdi[inputLine.user].setdefault(inputLine.product, 0)
|
||||
+ inputLine.price
|
||||
)
|
||||
if inputLine.user not in database.personDatoVerdi:
|
||||
database.personDatoVerdi[inputLine.user] = [0] * (inputLine.numberOfDays + 1)
|
||||
database.personDatoVerdi[inputLine.user][inputLine.dateNum] += inputLine.price
|
||||
if inputLine.user not in database.personUkedagVerdi:
|
||||
database.personUkedagVerdi[inputLine.user] = [0] * 7
|
||||
database.personUkedagVerdi[inputLine.user][inputLine.weekday] += inputLine.price
|
||||
# fyller inn delt statistikk (genereres uansett)
|
||||
if inputLine.product == "":
|
||||
if inputLine.price > 0:
|
||||
database.personPosTransactions[inputLine.user] = (
|
||||
database.personPosTransactions.setdefault(inputLine.user, 0) + inputLine.price
|
||||
)
|
||||
else:
|
||||
database.personNegTransactions[inputLine.user] = (
|
||||
database.personNegTransactions.setdefault(inputLine.user, 0) + inputLine.price
|
||||
)
|
||||
elif not (inputLine.inputType == 1):
|
||||
database.globalVareAntall[inputLine.product] = (
|
||||
database.globalVareAntall.setdefault(inputLine.product, 0) + 1
|
||||
)
|
||||
database.globalVareVerdi[inputLine.product] = (
|
||||
database.globalVareVerdi.setdefault(inputLine.product, 0) + inputLine.price
|
||||
)
|
||||
|
||||
# fyller inn for global statistikk
|
||||
if (inputLine.inputType == 3) or (inputLine.inputType == 4):
|
||||
database.pengebeholdning[inputLine.dateNum] += inputLine.price
|
||||
if not (inputLine.product == ""):
|
||||
database.globalPersonAntall[inputLine.user] = (
|
||||
database.globalPersonAntall.setdefault(inputLine.user, 0) + 1
|
||||
)
|
||||
database.globalPersonForbruk[inputLine.user] = (
|
||||
database.globalPersonForbruk.setdefault(inputLine.user, 0) + inputLine.price
|
||||
)
|
||||
database.globalDatoVarer[inputLine.dateNum] += 1
|
||||
database.globalDatoForbruk[inputLine.dateNum] += inputLine.price
|
||||
database.globalUkedagForbruk[inputLine.weekday] += inputLine.price
|
||||
return database
|
||||
|
||||
|
||||
def buildDatabaseFromDb(inputType, inputProduct, inputUser):
|
||||
sdate = input("enter start date (yyyy-mm-dd)? ")
|
||||
edate = input("enter end date (yyyy-mm-dd)? ")
|
||||
print("building database...")
|
||||
session = Session()
|
||||
transaction_list = session.query(Transaction).all()
|
||||
inputLine = InputLine(inputUser, inputProduct, inputType)
|
||||
startDate = getDateDb(transaction_list[0].time, sdate)
|
||||
endDate = getDateDb(transaction_list[-1].time, edate)
|
||||
inputLine.numberOfDays = (endDate - startDate).days
|
||||
database = Database()
|
||||
database = clearDatabase(database)
|
||||
|
||||
if (inputType == 3) or (inputType == 4):
|
||||
database.globalDatoVarer = [0] * (inputLine.numberOfDays + 1)
|
||||
database.globalDatoForbruk = [0] * (inputLine.numberOfDays + 1)
|
||||
database.globalUkedagForbruk = [0] * 7
|
||||
database.pengebeholdning = [0] * (inputLine.numberOfDays + 1)
|
||||
print("wait for it.... ")
|
||||
for transaction in transaction_list:
|
||||
if transaction.purchase:
|
||||
products = [ent.product.name for ent in transaction.purchase.entries]
|
||||
else:
|
||||
products = []
|
||||
products.append("")
|
||||
inputLine.dateNum, inputLine.weekday = dateToDateNumDb(transaction.time, startDate)
|
||||
if inputLine.dateNum < 0 or inputLine.dateNum > (inputLine.numberOfDays):
|
||||
continue
|
||||
inputLine.user = transaction.user.name
|
||||
inputLine.price = transaction.amount
|
||||
for inputLine.product in products:
|
||||
database = addLineToDatabase(database, inputLine)
|
||||
inputLine.price = 0
|
||||
|
||||
print("saving as default.dibblerlog...", end=" ")
|
||||
f = open("default.dibblerlog", "w")
|
||||
line_format = "%s|%s|%s|%s|%s|%s\n"
|
||||
transaction_list = session.query(Transaction).all()
|
||||
for transaction in transaction_list:
|
||||
if transaction.purchase:
|
||||
products = "¤".join([ent.product.name for ent in transaction.purchase.entries])
|
||||
else:
|
||||
products = ""
|
||||
line = line_format % (
|
||||
"purchase",
|
||||
transaction.time,
|
||||
products,
|
||||
transaction.user.name,
|
||||
transaction.amount,
|
||||
transaction.description,
|
||||
)
|
||||
f.write(line.encode("utf8"))
|
||||
session.close()
|
||||
f.close
|
||||
# bygg database.pengebeholdning
|
||||
if (inputType == 3) or (inputType == 4):
|
||||
for i in range(inputLine.numberOfDays + 1):
|
||||
if i > 0:
|
||||
database.pengebeholdning[i] += database.pengebeholdning[i - 1]
|
||||
# bygg dateLine
|
||||
day = datetime.timedelta(days=1)
|
||||
dateLine = []
|
||||
dateLine.append(startDate)
|
||||
for n in range(inputLine.numberOfDays):
|
||||
dateLine.append(startDate + n * day)
|
||||
print("done")
|
||||
return database, dateLine
|
||||
|
||||
|
||||
def buildDatabaseFromFile(inputFile, inputType, inputProduct, inputUser):
|
||||
sdate = input("enter start date (yyyy-mm-dd)? ")
|
||||
edate = input("enter end date (yyyy-mm-dd)? ")
|
||||
|
||||
f = open(inputFile)
|
||||
try:
|
||||
fileLines = f.readlines()
|
||||
finally:
|
||||
f.close()
|
||||
inputLine = InputLine(inputUser, inputProduct, inputType)
|
||||
startDate = getDateFile(fileLines[0].partition("|")[2].partition(" ")[0], sdate)
|
||||
endDate = getDateFile(fileLines[-1].partition("|")[2].partition(" ")[0], edate)
|
||||
inputLine.numberOfDays = (endDate - startDate).days
|
||||
database = Database()
|
||||
database = clearDatabase(database)
|
||||
|
||||
if (inputType == 3) or (inputType == 4):
|
||||
database.globalDatoVarer = [0] * (inputLine.numberOfDays + 1)
|
||||
database.globalDatoForbruk = [0] * (inputLine.numberOfDays + 1)
|
||||
database.globalUkedagForbruk = [0] * 7
|
||||
database.pengebeholdning = [0] * (inputLine.numberOfDays + 1)
|
||||
for linje in fileLines:
|
||||
if not (linje[0] == "#") and not (linje == "\n"):
|
||||
# henter dateNum, products, user, price
|
||||
restDel = linje.partition("|")
|
||||
restDel = restDel[2].partition(" ")
|
||||
inputLine.dateNum, inputLine.weekday = dateToDateNumFile(restDel[0], startDate)
|
||||
if inputLine.dateNum < 0 or inputLine.dateNum > (inputLine.numberOfDays):
|
||||
continue
|
||||
restDel = restDel[2].partition("|")
|
||||
restDel = restDel[2].partition("|")
|
||||
products = restDel[0]
|
||||
restDel = restDel[2].partition("|")
|
||||
inputLine.user = restDel[0]
|
||||
inputLine.price = int(restDel[2].partition("|")[0])
|
||||
for inputLine.product in getProducts(products):
|
||||
database = addLineToDatabase(database, inputLine)
|
||||
inputLine.price = 0
|
||||
# bygg database.pengebeholdning
|
||||
if (inputType == 3) or (inputType == 4):
|
||||
for i in range(inputLine.numberOfDays + 1):
|
||||
if i > 0:
|
||||
database.pengebeholdning[i] += database.pengebeholdning[i - 1]
|
||||
# bygg dateLine
|
||||
day = datetime.timedelta(days=1)
|
||||
dateLine = []
|
||||
dateLine.append(startDate)
|
||||
for n in range(inputLine.numberOfDays):
|
||||
dateLine.append(startDate + n * day)
|
||||
return database, dateLine
|
||||
|
||||
|
||||
def printTopDict(dictionary, n, k):
|
||||
i = 0
|
||||
for key in sorted(dictionary, key=dictionary.get, reverse=k):
|
||||
print(key, ": ", dictionary[key])
|
||||
if i < n:
|
||||
i += 1
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def printTopDict2(dictionary, dictionary2, n):
|
||||
print("")
|
||||
print("product : price[kr] ( number )")
|
||||
i = 0
|
||||
for key in sorted(dictionary, key=dictionary.get, reverse=True):
|
||||
print(key, ": ", dictionary[key], " (", dictionary2[key], ") ")
|
||||
if i < n:
|
||||
i += 1
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def printWeekdays(week, days):
|
||||
if week == [] or days == 0:
|
||||
return
|
||||
print(
|
||||
"mon: ",
|
||||
"%.2f" % (week[0] * 7.0 / days),
|
||||
" tue: ",
|
||||
"%.2f" % (week[1] * 7.0 / days),
|
||||
" wen: ",
|
||||
"%.2f" % (week[2] * 7.0 / days),
|
||||
" thu: ",
|
||||
"%.2f" % (week[3] * 7.0 / days),
|
||||
" fri: ",
|
||||
"%.2f" % (week[4] * 7.0 / days),
|
||||
" sat: ",
|
||||
"%.2f" % (week[5] * 7.0 / days),
|
||||
" sun: ",
|
||||
"%.2f" % (week[6] * 7.0 / days),
|
||||
)
|
||||
print("forbruk per dag (snitt): ", "%.2f" % (sum(week) * 1.0 / days))
|
||||
print("")
|
||||
|
||||
|
||||
def printBalance(database, user):
|
||||
forbruk = 0
|
||||
if user in database.personVareVerdi:
|
||||
forbruk = sum([i for i in list(database.personVareVerdi[user].values())])
|
||||
print("totalt kjøpt for: ", forbruk, end=" ")
|
||||
if user in database.personNegTransactions:
|
||||
print("kr, totalt lagt til: ", -database.personNegTransactions[user], end=" ")
|
||||
forbruk = -database.personNegTransactions[user] - forbruk
|
||||
if user in database.personPosTransactions:
|
||||
print("kr, totalt tatt fra boks: ", database.personPosTransactions[user], end=" ")
|
||||
forbruk = forbruk - database.personPosTransactions[user]
|
||||
print("balanse: ", forbruk, "kr", end=" ")
|
||||
print("")
|
||||
|
||||
|
||||
def printUser(database, dateLine, user, n):
|
||||
printTopDict2(database.personVareVerdi[user], database.personVareAntall[user], n)
|
||||
print("\nforbruk per ukedag [kr/dag],", end=" ")
|
||||
printWeekdays(database.personUkedagVerdi[user], len(dateLine))
|
||||
printBalance(database, user)
|
||||
|
||||
|
||||
def printProduct(database, dateLine, product, n):
|
||||
printTopDict(database.varePersonAntall[product], n, 1)
|
||||
print("\nforbruk per ukedag [antall/dag],", end=" ")
|
||||
printWeekdays(database.vareUkedagAntall[product], len(dateLine))
|
||||
print(
|
||||
"Det er solgt: ",
|
||||
database.globalVareAntall[product],
|
||||
product,
|
||||
"til en verdi av: ",
|
||||
database.globalVareVerdi[product],
|
||||
"kr",
|
||||
)
|
||||
|
||||
|
||||
def printGlobal(database, dateLine, n):
|
||||
print("\nmest lagt til: ")
|
||||
printTopDict(database.personNegTransactions, n, 0)
|
||||
print("\nmest tatt fra:")
|
||||
printTopDict(database.personPosTransactions, n, 1)
|
||||
print("\nstørst forbruk:")
|
||||
printTopDict(database.globalPersonForbruk, n, 1)
|
||||
printTopDict2(database.globalVareVerdi, database.globalVareAntall, n)
|
||||
print("\nforbruk per ukedag [kr/dag],", end=" ")
|
||||
printWeekdays(database.globalUkedagForbruk, len(dateLine))
|
||||
print(
|
||||
"Det er solgt varer til en verdi av: ",
|
||||
sum(database.globalDatoForbruk),
|
||||
"kr, det er lagt til",
|
||||
-sum([i for i in list(database.personNegTransactions.values())]),
|
||||
"og tatt fra",
|
||||
sum([i for i in list(database.personPosTransactions.values())]),
|
||||
end=" ",
|
||||
)
|
||||
print(
|
||||
"balansen blir:",
|
||||
database.pengebeholdning[len(dateLine) - 1],
|
||||
"der negative verdier representerer at brukere har kreditt tilgjengelig",
|
||||
)
|
||||
|
||||
|
||||
def alt4menuTextOnly(database, dateLine):
|
||||
n = 10
|
||||
while 1:
|
||||
print(
|
||||
"\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit"
|
||||
)
|
||||
inp = input("")
|
||||
if inp == "q":
|
||||
break
|
||||
elif inp == "1":
|
||||
try:
|
||||
printUser(database, dateLine, getUser(), n)
|
||||
except:
|
||||
print("\n\nSomething is not right, (last date prior to first date?)")
|
||||
elif inp == "2":
|
||||
try:
|
||||
printProduct(database, dateLine, getProduct(), n)
|
||||
except:
|
||||
print("\n\nSomething is not right, (last date prior to first date?)")
|
||||
elif inp == "3":
|
||||
try:
|
||||
printGlobal(database, dateLine, n)
|
||||
except:
|
||||
print("\n\nSomething is not right, (last date prior to first date?)")
|
||||
elif inp == "n":
|
||||
n = int(input("set number to show "))
|
||||
|
||||
|
||||
def statisticsTextOnly():
|
||||
inputType = 4
|
||||
product = ""
|
||||
user = ""
|
||||
print("\n0: from file, 1: from database, q:quit")
|
||||
inp = input("")
|
||||
if inp == "1":
|
||||
database, dateLine = buildDatabaseFromDb(inputType, product, user)
|
||||
elif inp == "0" or inp == "":
|
||||
database, dateLine = buildDatabaseFromFile("default.dibblerlog", inputType, product, user)
|
||||
if not inp == "q":
|
||||
alt4menuTextOnly(database, dateLine)
|
|
@ -0,0 +1,46 @@
|
|||
import argparse
|
||||
|
||||
from dibbler.conf import config
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
help="Path to the config file",
|
||||
type=str,
|
||||
required=False,
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(
|
||||
title="subcommands",
|
||||
dest="subcommand",
|
||||
required=True,
|
||||
)
|
||||
subparsers.add_parser("loop", help="Run the dibbler loop")
|
||||
subparsers.add_parser("create-db", help="Create the database")
|
||||
subparsers.add_parser("slabbedasker", help="Find out who is slabbedasker")
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
config.read(args.config)
|
||||
|
||||
if args.subcommand == "loop":
|
||||
import dibbler.subcommands.loop as loop
|
||||
|
||||
loop.main()
|
||||
|
||||
elif args.subcommand == "create-db":
|
||||
import dibbler.subcommands.makedb as makedb
|
||||
|
||||
makedb.main()
|
||||
|
||||
elif args.subcommand == "slabbedasker":
|
||||
import dibbler.subcommands.slabbedasker as slabbedasker
|
||||
|
||||
slabbedasker.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,53 @@
|
|||
__all__ = [
|
||||
"AddProductMenu",
|
||||
"AddStockMenu",
|
||||
"AddUserMenu",
|
||||
"AdjustCreditMenu",
|
||||
"AdjustStockMenu",
|
||||
"BalanceMenu",
|
||||
"BuyMenu",
|
||||
"CleanupStockMenu",
|
||||
"EditProductMenu",
|
||||
"EditUserMenu",
|
||||
"FAQMenu",
|
||||
"LoggedStatisticsMenu",
|
||||
"MainMenu",
|
||||
"Menu",
|
||||
"PrintLabelMenu",
|
||||
"ProductListMenu",
|
||||
"ProductPopularityMenu",
|
||||
"ProductRevenueMenu",
|
||||
"ProductSearchMenu",
|
||||
"ShowUserMenu",
|
||||
"TransferMenu",
|
||||
"UserListMenu",
|
||||
]
|
||||
|
||||
from .addstock import AddStockMenu
|
||||
from .buymenu import BuyMenu
|
||||
from .editing import (
|
||||
AddUserMenu,
|
||||
EditUserMenu,
|
||||
AddProductMenu,
|
||||
EditProductMenu,
|
||||
AdjustStockMenu,
|
||||
CleanupStockMenu,
|
||||
)
|
||||
from .faq import FAQMenu
|
||||
from .helpermenus import Menu
|
||||
from .mainmenu import MainMenu
|
||||
from .miscmenus import (
|
||||
ProductSearchMenu,
|
||||
TransferMenu,
|
||||
AdjustCreditMenu,
|
||||
UserListMenu,
|
||||
ShowUserMenu,
|
||||
ProductListMenu,
|
||||
)
|
||||
from .printermenu import PrintLabelMenu
|
||||
from .stats import (
|
||||
ProductPopularityMenu,
|
||||
ProductRevenueMenu,
|
||||
BalanceMenu,
|
||||
LoggedStatisticsMenu,
|
||||
)
|
|
@ -1,16 +1,23 @@
|
|||
from math import ceil
|
||||
|
||||
import sqlalchemy
|
||||
from db import Product, User, Transaction, PurchaseEntry, Purchase
|
||||
from text_interface.helpermenus import Menu
|
||||
|
||||
from dibbler.models import (
|
||||
Product,
|
||||
Purchase,
|
||||
PurchaseEntry,
|
||||
Transaction,
|
||||
User,
|
||||
)
|
||||
from .helpermenus import Menu
|
||||
|
||||
|
||||
class AddStockMenu(Menu):
|
||||
def __init__(self):
|
||||
Menu.__init__(self, 'Add stock and adjust credit', uses_db=True)
|
||||
self.help_text = '''
|
||||
Menu.__init__(self, "Add stock and adjust credit", uses_db=True)
|
||||
self.help_text = """
|
||||
Enter what you have bought for PVVVV here, along with your user name and how
|
||||
much money you're due in credits for the purchase when prompted.\n'''
|
||||
much money you're due in credits for the purchase when prompted.\n"""
|
||||
self.users = []
|
||||
self.users = []
|
||||
self.products = {}
|
||||
|
@ -18,10 +25,19 @@ much money you're due in credits for the purchase when prompted.\n'''
|
|||
|
||||
def _execute(self):
|
||||
questions = {
|
||||
(False, False): 'Enter user id or a string of the form "<number> <product>"',
|
||||
(False, True): 'Enter user id or more strings of the form "<number> <product>"',
|
||||
(
|
||||
False,
|
||||
False,
|
||||
): 'Enter user id or a string of the form "<number> <product>"',
|
||||
(
|
||||
False,
|
||||
True,
|
||||
): 'Enter user id or more strings of the form "<number> <product>"',
|
||||
(True, False): 'Enter a string of the form "<number> <product>"',
|
||||
(True, True): 'Enter more strings of the form "<number> <product>", or an empty line to confirm'
|
||||
(
|
||||
True,
|
||||
True,
|
||||
): 'Enter more strings of the form "<number> <product>", or an empty line to confirm',
|
||||
}
|
||||
|
||||
self.users = []
|
||||
|
@ -34,24 +50,33 @@ much money you're due in credits for the purchase when prompted.\n'''
|
|||
thing_price = 0
|
||||
|
||||
# Read in a 'thing' (product or user):
|
||||
line = self.input_multiple(add_nonexisting=('user', 'product'), empty_input_permitted=True,
|
||||
find_hidden_products=False)
|
||||
line = self.input_multiple(
|
||||
add_nonexisting=("user", "product"),
|
||||
empty_input_permitted=True,
|
||||
find_hidden_products=False,
|
||||
)
|
||||
|
||||
if line:
|
||||
(thing, amount) = line
|
||||
|
||||
if isinstance(thing, Product):
|
||||
self.printc(f"{amount:d} of {thing.name} registered")
|
||||
thing_price = self.input_int('What did you pay a piece?', 1, 100000, default=thing.price) * amount
|
||||
thing_price = (
|
||||
self.input_int("What did you pay a piece?", 1, 100000, default=thing.price)
|
||||
* amount
|
||||
)
|
||||
self.price += thing_price
|
||||
|
||||
# once we get something in the
|
||||
# purchase, we want to protect the
|
||||
# user from accidentally killing it
|
||||
self.exit_confirm_msg = 'Abort transaction?'
|
||||
self.exit_confirm_msg = "Abort transaction?"
|
||||
else:
|
||||
if not self.complete_input():
|
||||
if self.confirm('Not enough information entered. Abort transaction?', default=True):
|
||||
if self.confirm(
|
||||
"Not enough information entered. Abort transaction?",
|
||||
default=True,
|
||||
):
|
||||
return False
|
||||
continue
|
||||
break
|
||||
|
@ -67,7 +92,7 @@ much money you're due in credits for the purchase when prompted.\n'''
|
|||
def print_info(self):
|
||||
width = 6 + Product.name_length
|
||||
print()
|
||||
print(width * '-')
|
||||
print(width * "-")
|
||||
if self.price:
|
||||
print(f"Amount to be credited:{self.price:>{width - 22}}")
|
||||
if self.users:
|
||||
|
@ -77,41 +102,47 @@ much money you're due in credits for the purchase when prompted.\n'''
|
|||
print()
|
||||
print("Products", end="")
|
||||
print("Amount".rjust(width - 8))
|
||||
print(width * '-')
|
||||
print(width * "-")
|
||||
if len(self.products):
|
||||
for product in list(self.products.keys()):
|
||||
print(f"{product.name}", end="")
|
||||
print(f"{self.products[product][0]}".rjust(width - len(product.name)))
|
||||
print(width * '-')
|
||||
print(width * "-")
|
||||
|
||||
def add_thing_to_pending(self, thing, amount, price):
|
||||
if isinstance(thing, User):
|
||||
self.users.append(thing)
|
||||
elif thing in list(self.products.keys()):
|
||||
print('Already added this product, adding amounts')
|
||||
print("Already added this product, adding amounts")
|
||||
self.products[thing][0] += amount
|
||||
self.products[thing][1] += price
|
||||
else:
|
||||
self.products[thing] = [amount, price]
|
||||
|
||||
def perform_transaction(self):
|
||||
print('Did you pay a different price?')
|
||||
if self.confirm('>', default=False):
|
||||
self.price = self.input_int('How much did you pay?', 0, self.price, default=self.price)
|
||||
print("Did you pay a different price?")
|
||||
if self.confirm(">", default=False):
|
||||
self.price = self.input_int("How much did you pay?", 0, self.price, default=self.price)
|
||||
|
||||
description = self.input_str('Log message', length_range=(0, 50))
|
||||
if description == '':
|
||||
description = 'Purchased products for PVVVV, adjusted credit ' + str(self.price)
|
||||
description = self.input_str("Log message", length_range=(0, 50))
|
||||
if description == "":
|
||||
description = "Purchased products for PVVVV, adjusted credit " + str(self.price)
|
||||
for product in self.products:
|
||||
value = max(product.stock, 0) * product.price + self.products[product][1]
|
||||
old_price = product.price
|
||||
old_hidden = product.hidden
|
||||
product.price = int(ceil(float(value) / (max(product.stock, 0) + self.products[product][0])))
|
||||
product.stock = max(self.products[product][0], product.stock + self.products[product][0])
|
||||
product.price = int(
|
||||
ceil(float(value) / (max(product.stock, 0) + self.products[product][0]))
|
||||
)
|
||||
product.stock = max(
|
||||
self.products[product][0], product.stock + self.products[product][0]
|
||||
)
|
||||
product.hidden = False
|
||||
print(f"New stock for {product.name}: {product.stock:d}",
|
||||
f"- New price: {product.price}" if old_price != product.price else "",
|
||||
"- Removed hidden status" if old_hidden != product.hidden else "")
|
||||
print(
|
||||
f"New stock for {product.name}: {product.stock:d}",
|
||||
f"- New price: {product.price}" if old_price != product.price else "",
|
||||
"- Removed hidden status" if old_hidden != product.hidden else "",
|
||||
)
|
||||
|
||||
purchase = Purchase()
|
||||
for user in self.users:
|
||||
|
@ -129,4 +160,4 @@ much money you're due in credits for the purchase when prompted.\n'''
|
|||
for user in self.users:
|
||||
print(f"User {user.name}'s credit is now {user.credit:d}")
|
||||
except sqlalchemy.exc.SQLAlchemyError as e:
|
||||
print(f'Could not perform transaction: {e}')
|
||||
print(f"Could not perform transaction: {e}")
|