Merge pull request #3 from Programvareverkstedet/restructure-project

Restructure project
This commit is contained in:
Oystein Kristoffer Tveit 2023-09-02 21:18:04 +02:00 committed by GitHub
commit 8a6a0c12ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 2687 additions and 11798 deletions

8
.gitignore vendored
View File

@ -1,2 +1,8 @@
result
result-*
result-*
dist
test.db
.ruff_cache

30
ALTER
View File

@ -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=>

View File

@ -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

BIN
data

Binary file not shown.

176
db.py
View File

@ -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()

View File

@ -1,7 +1,4 @@
{ pkgs ? import <nixos-unstable> { } }:
rec {
{
dibbler = pkgs.callPackage ./nix/dibbler.nix { };
}

0
dibbler/__init__.py Normal file
View File

6
dibbler/conf.py Normal file
View File

@ -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()

7
dibbler/db.py Normal file
View File

@ -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)

View File

@ -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

130
dibbler/lib/helpers.py Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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)

46
dibbler/main.py Normal file
View File

@ -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()

53
dibbler/menus/__init__.py Normal file
View File

@ -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,
)

View File

@ -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}")