Big cleanup ദ്ദി^._.^)

This commit is contained in:
Oystein Kristoffer Tveit 2023-08-29 18:32:49 +02:00
parent 6ab66771f2
commit c25e5cec27
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
49 changed files with 597 additions and 10337 deletions

4
.gitignore vendored
View File

@ -1,2 +1,4 @@
result result
result-* result-*
dist

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

BIN
data

Binary file not shown.

View File

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

0
dibbler/__init__.py Normal file
View File

24
dibbler/cli.py Normal file
View File

@ -0,0 +1,24 @@
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,
)
def main():
args = parser.parse_args()
config.read(args.config)
import dibbler.text_based as text_based
text_based.main()
if __name__ == "__main__":
main()

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

View File

@ -1,10 +1,12 @@
from db import *
from sqlalchemy import or_, and_
import pwd import pwd
import subprocess import subprocess
import os import os
import signal import signal
from sqlalchemy import or_, and_
from .models.db import *
def search_user(string, session, ignorethisflag=None): def search_user(string, session, ignorethisflag=None):
string = string.lower() string = string.lower()
exact_match = session.query(User).filter(or_(User.name == string, User.card == string, User.rfid == string)).first() exact_match = session.query(User).filter(or_(User.name == string, User.card == string, User.rfid == string)).first()

View File

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/python
import db from .models.db import db
db.Base.metadata.create_all(db.engine) db.Base.metadata.create_all(db.engine)

View File

@ -1,8 +1,15 @@
from math import ceil from math import ceil
import sqlalchemy import sqlalchemy
from db import Product, User, Transaction, PurchaseEntry, Purchase
from text_interface.helpermenus import Menu from dibbler.models.db import (
Product,
Purchase,
PurchaseEntry,
Transaction,
User,
)
from .helpermenus import Menu
class AddStockMenu(Menu): class AddStockMenu(Menu):

View File

@ -1,7 +1,15 @@
import conf
import sqlalchemy import sqlalchemy
from db import User, Purchase, PurchaseEntry, Transaction, Product
from text_interface.helpermenus import Menu from dibbler.conf import config
from dibbler.models.db import (
Product,
Purchase,
PurchaseEntry,
Transaction,
User,
)
from .helpermenus import Menu
class BuyMenu(Menu): class BuyMenu(Menu):
@ -29,7 +37,7 @@ When finished, write an empty line to confirm the purchase.\n'''
""" """
assert isinstance(user, User) assert isinstance(user, User)
return user.credit > conf.low_credit_warning_limit return user.credit > config.getint('limits', 'low_credit_warning_limit')
def low_credit_warning(self, user, timeout=False): def low_credit_warning(self, user, timeout=False):
assert isinstance(user, User) assert isinstance(user, User)
@ -49,7 +57,7 @@ When finished, write an empty line to confirm the purchase.\n'''
print("***********************************************************************") print("***********************************************************************")
print("***********************************************************************") print("***********************************************************************")
print("") print("")
print(f"USER {user.name} HAS LOWER CREDIT THAN {conf.low_credit_warning_limit:d}.") 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("THIS PURCHASE WILL CHARGE YOUR CREDIT TWICE AS MUCH.")
print("CONSIDER PUTTING MONEY IN THE BOX TO AVOID THIS.") print("CONSIDER PUTTING MONEY IN THE BOX TO AVOID THIS.")
print("") print("")
@ -158,8 +166,8 @@ When finished, write an empty line to confirm the purchase.\n'''
for t in self.purchase.transactions: for t in self.purchase.transactions:
if not t.user.is_anonymous(): if not t.user.is_anonymous():
print(f"User {t.user.name}'s credit is now {t.user.credit:d} kr") print(f"User {t.user.name}'s credit is now {t.user.credit:d} kr")
if t.user.credit < conf.low_credit_warning_limit: if t.user.credit < config.getint('limits', 'low_credit_warning_limit'):
print(f'USER {t.user.name} HAS LOWER CREDIT THAN {conf.low_credit_warning_limit:d},', 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.') 'AND SHOULD CONSIDER PUTTING SOME MONEY IN THE BOX.')
# Superfast mode skips a linebreak for some reason. # Superfast mode skips a linebreak for some reason.

View File

@ -1,6 +1,7 @@
import sqlalchemy import sqlalchemy
from db import User, Product
from text_interface.helpermenus import Menu, Selector from dibbler.models.db import User, Product
from .helpermenus import Menu, Selector
__all__ = ["AddUserMenu", "AddProductMenu", "EditProductMenu", "AdjustStockMenu", "CleanupStockMenu", "EditUserMenu"] __all__ = ["AddUserMenu", "AddProductMenu", "EditProductMenu", "AdjustStockMenu", "CleanupStockMenu", "EditUserMenu"]

View File

@ -5,10 +5,20 @@ import re
import sys import sys
from select import select from select import select
import conf from dibbler.models.db import User, Session
from db import User, Session from dibbler.helpers import (
from helpers import search_user, search_product, guess_data_type, argmax search_user,
from text_interface import context_commands, local_help_commands, help_commands, exit_commands search_product,
guess_data_type,
argmax,
)
from . import (
context_commands,
local_help_commands,
help_commands,
exit_commands,
)
class ExitMenu(Exception): class ExitMenu(Exception):

View File

@ -4,11 +4,12 @@ import os
import random import random
import sys import sys
from db import Session from dibbler.models.db import Session
from text_interface import faq_commands, restart_commands
from text_interface.buymenu import BuyMenu from . import faq_commands, restart_commands
from text_interface.faq import FAQMenu from .buymenu import BuyMenu
from text_interface.helpermenus import Menu from .faq import FAQMenu
from .helpermenus import Menu
def restart(): def restart():

View File

@ -1,8 +1,10 @@
import conf
import sqlalchemy import sqlalchemy
from db import Transaction, Product, User
from helpers import less from dibbler.conf import config
from text_interface.helpermenus import Menu, Selector from dibbler.models.db import Transaction, Product, User
from dibbler.helpers import less
from .helpermenus import Menu, Selector
class TransferMenu(Menu): class TransferMenu(Menu):
@ -53,12 +55,12 @@ class ShowUserMenu(Menu):
print(f'Credit: {user.credit} kr') print(f'Credit: {user.credit} kr')
selector = Selector(f'What do you want to know about {user.name}?', selector = Selector(f'What do you want to know about {user.name}?',
items=[('transactions', 'Recent transactions (List of last ' + str( items=[('transactions', 'Recent transactions (List of last ' + str(
conf.user_recent_transaction_limit) + ')'), config.getint('limits', 'user_recent_transaction_limit')) + ')'),
('products', f'Which products {user.name} has bought, and how many'), ('products', f'Which products {user.name} has bought, and how many'),
('transactions-all', 'Everything (List of all transactions)')]) ('transactions-all', 'Everything (List of all transactions)')])
what = selector.execute() what = selector.execute()
if what == 'transactions': if what == 'transactions':
self.print_transactions(user, conf.user_recent_transaction_limit) self.print_transactions(user, config.getint('limits', 'user_recent_transaction_limit'))
elif what == 'products': elif what == 'products':
self.print_purchased_products(user) self.print_purchased_products(user)
elif what == 'transactions-all': elif what == 'transactions-all':

View File

@ -0,0 +1,45 @@
import re
from dibbler.conf import config
from dibbler.models.db import Product, User
from dibbler.printer_helpers import print_bar_code, print_name_label
from .helpermenus import Menu
class PrintLabelMenu(Menu):
def __init__(self):
Menu.__init__(self, 'Print a label', uses_db=True)
self.help_text = '''
Prints out a product bar code on the printer
Put it up somewhere in the vicinity.
'''
def _execute(self):
self.print_header()
thing = self.input_thing('Product/User')
if isinstance(thing, Product):
if re.match(r"^[0-9]{13}$", thing.bar_code):
bar_type = "ean13"
elif re.match(r"^[0-9]{8}$", thing.bar_code):
bar_type = "ean8"
else:
bar_type = "code39"
print_bar_code(
thing.bar_code,
thing.name,
barcode_type=bar_type,
rotate=config.getboolean('printer', 'rotate'),
printer_type="QL-700",
label_type=config.get('printer', 'label_type'),
)
elif isinstance(thing, User):
print_name_label(
text=thing.name,
label_type=config.get('printer', 'label_type'),
rotate=config.getboolean('printer', 'rotate'),
printer_type="QL-700"
)

View File

@ -1,10 +1,10 @@
import sqlalchemy from sqlalchemy import desc, func
from db import PurchaseEntry, Product, User
from helpers import less from dibbler.helpers import less
from sqlalchemy import desc from dibbler.models.db import PurchaseEntry, Product, User
from sqlalchemy import func from dibbler.statistikkHelpers import statisticsTextOnly
from statistikkHelpers import statisticsTextOnly
from text_interface.helpermenus import Menu from .helpermenus import Menu
__all__ = ["ProductPopularityMenu", "ProductRevenueMenu", "BalanceMenu", "LoggedStatisticsMenu"] __all__ = ["ProductPopularityMenu", "ProductRevenueMenu", "BalanceMenu", "LoggedStatisticsMenu"]
@ -77,8 +77,8 @@ class BalanceMenu(Menu):
for p in product_list: for p in product_list:
total_value += p.stock * p.price total_value += p.stock * p.price
total_positive_credit = self.session.query(sqlalchemy.func.sum(User.credit)).filter(User.credit > 0).first()[0] total_positive_credit = self.session.query(func.sum(User.credit)).filter(User.credit > 0).first()[0]
total_negative_credit = self.session.query(sqlalchemy.func.sum(User.credit)).filter(User.credit < 0).first()[0] total_negative_credit = self.session.query(func.sum(User.credit)).filter(User.credit < 0).first()[0]
total_credit = total_positive_credit + total_negative_credit total_credit = total_positive_credit + total_negative_credit
total_balance = total_value - total_credit total_balance = total_value - total_credit

View File

View File

@ -1,15 +1,16 @@
from math import ceil, floor
import datetime
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine, DateTime, Boolean from sqlalchemy import Column, Integer, String, ForeignKey, create_engine, DateTime, Boolean
from sqlalchemy.orm import sessionmaker, relationship, backref from sqlalchemy.orm import sessionmaker, relationship, backref
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from math import ceil, floor
import datetime
import conf
engine = create_engine(conf.db_url) from dibbler.conf import config
engine = create_engine(config.get('database', 'url'))
Base = declarative_base() Base = declarative_base()
Session = sessionmaker(bind=engine) Session = sessionmaker(bind=engine)
class User(Base): class User(Base):
__tablename__ = 'users' __tablename__ = 'users'
name = Column(String(10), primary_key=True) name = Column(String(10), primary_key=True)

View File

@ -1,16 +1,13 @@
import os import os
import datetime
import barcode import barcode
import datetime from brother_ql import BrotherQLRaster, create_label
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.backends import backend_factory from brother_ql.backends import backend_factory
from brother_ql.devicedependent import label_type_specs 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",):

View File

View File

@ -0,0 +1,15 @@
#!/usr/bin/python
from dibbler.models.db import *
def main():
# Start an SQL session
session=Session()
# Let's find all users with a negative credit
slabbedasker=session.query(User).filter(User.credit<0).all()
for slubbert in slabbedasker:
print(f"{slubbert.name}, {slubbert.credit}")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,203 @@
#! /usr/bin/env python
# -*- coding: UTF-8 -*-
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from dibbler.statistikkHelpers import *
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 getDateFile(date, n):
try:
if n==0:
inp = input('start date? (yyyy-mm-dd) ')
elif n==-1:
inp = input('end date? (yyyy-mm-dd) ')
year = inp.partition('-')
month = year[2].partition('-')
return datetime.date(int(year[0]), int(month[0]), int(month[2]))
except:
print('invalid date, setting start start date')
if n==0:
print('to date found on first line')
elif n==-1:
print('to date found on last line')
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 getProducts(products):
product = []
products = products.partition('¤')
product.append(products[0])
while (products[1]=='¤'):
products = products[2].partition('¤')
product.append(products[0])
return product
def piePlot(dictionary, n):
keys = []
values = []
i=0
for key in sorted(dictionary, key=dictionary.get, reverse=True):
values.append(dictionary[key])
if i<n:
keys.append(key)
i += 1
else:
keys.append('')
plt.pie(values, labels=keys)
def datePlot(array, dateLine):
if (not array == []):
plt.bar(dateLine, array)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b'))
def dayPlot(array, days):
if (not array == []):
for i in range(7):
array[i]=array[i]*7.0/days
plt.bar(list(range(7)), array)
plt.xticks(list(range(7)),[' mon',' tue',' wed',' thu',' fri',' sat',' sun'])
def graphPlot(array, dateLine):
if (not array == []):
plt.plot(dateLine, array)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b'))
def plotUser(database, dateLine, user, n):
printUser(database, dateLine, user, n)
plt.subplot(221)
piePlot(database.personVareAntall[user], n)
plt.xlabel('antall varer kjøpt gjengitt i antall')
plt.subplot(222)
datePlot(database.personDatoVerdi[user], dateLine)
plt.xlabel('penger brukt over dato')
plt.subplot(223)
piePlot(database.personVareVerdi[user], n)
plt.xlabel('antall varer kjøpt gjengitt i verdi')
plt.subplot(224)
dayPlot(database.personUkedagVerdi[user], len(dateLine))
plt.xlabel('forbruk over ukedager')
plt.show()
def plotProduct(database, dateLine, product, n):
printProduct(database, dateLine, product, n)
plt.subplot(221)
piePlot(database.varePersonAntall[product], n)
plt.xlabel('personer som har handler produktet')
plt.subplot(222)
datePlot(database.vareDatoAntall[product], dateLine)
plt.xlabel('antall produkter handlet per dag')
#plt.subplot(223)
plt.subplot(224)
dayPlot(database.vareUkedagAntall[product], len(dateLine))
plt.xlabel('antall over ukedager')
plt.show()
def plotGlobal(database, dateLine, n):
printGlobal(database, dateLine, n)
plt.subplot(231)
piePlot(database.globalVareVerdi, n)
plt.xlabel('varer kjøpt gjengitt som verdi')
plt.subplot(232)
datePlot(database.globalDatoForbruk, dateLine)
plt.xlabel('forbruk over dato')
plt.subplot(233)
graphPlot(database.pengebeholdning, dateLine)
plt.xlabel('pengebeholdning over tid (negativ verdi utgjør samlet kreditt)')
plt.subplot(234)
piePlot(database.globalPersonForbruk, n)
plt.xlabel('penger brukt av personer')
plt.subplot(235)
dayPlot(database.globalUkedagForbruk, len(dateLine))
plt.xlabel('forbruk over ukedager')
plt.show()
def alt4menu(database, dateLine, useDatabase):
n=10
while 1:
print('\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit')
try:
inp = input('')
except:
continue
if inp == 'q':
break
elif inp == '1':
if i=='0':
user = input('input full username: ')
else:
user = getUser()
plotUser(database, dateLine, user, n)
elif inp == '2':
if i=='0':
product = input('input full product name: ')
else:
product = getProduct()
plotProduct(database, dateLine, product, n)
elif inp == '3':
plotGlobal(database, dateLine, n)
elif inp == 'n':
try:
n=int(input('set number to show '));
except:
pass
def main():
inputType=getInputType()
i = input('0:fil, 1:database \n? ')
if (inputType == 1):
if i=='0':
user = input('input full username: ')
else:
user = getUser()
product = ''
elif (inputType == 2):
if i=='0':
product = input('input full product name: ')
else:
product = getProduct()
user = ''
else :
product = ''
user = ''
if i=='0':
inputFile=input('logfil? ')
if inputFile=='':
inputFile='default.dibblerlog'
database, dateLine = buildDatabaseFromFile(inputFile, inputType, product, user)
else:
database, dateLine = buildDatabaseFromDb(inputType, product, user)
if (inputType==1):
plotUser(database, dateLine, user, 10)
if (inputType==2):
plotProduct(database, dateLine, product, 10)
if (inputType==3):
plotGlobal(database, dateLine, 10)
if (inputType==4):
alt4menu(database, dateLine, i)
if __name__ == '__main__':
main()

View File

@ -1,11 +1,11 @@
#! /usr/bin/env python #! /usr/bin/env python
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
import datetime import datetime
from collections import defaultdict from collections import defaultdict
import operator
from helpers import * from .helpers import *
import sys from .models.db import *;
import db
def getUser(): def getUser():
while 1: while 1:

View File

@ -5,26 +5,36 @@ import random
import sys import sys
import traceback import traceback
from helpers import * from .helpers import *
from text_interface.addstock import AddStockMenu from .menus.addstock import AddStockMenu
from text_interface.buymenu import BuyMenu from .menus.buymenu import BuyMenu
from text_interface.editing import * from .menus.editing import *
from text_interface.faq import FAQMenu from .menus.faq import FAQMenu
from text_interface.helpermenus import Menu from .menus.helpermenus import Menu
from text_interface.mainmenu import MainMenu from .menus.mainmenu import MainMenu
from text_interface.miscmenus import ProductSearchMenu, TransferMenu, AdjustCreditMenu, UserListMenu, ShowUserMenu, \
ProductListMenu from .menus.miscmenus import (
from text_interface.printermenu import PrintLabelMenu ProductSearchMenu,
from text_interface.stats import * TransferMenu,
AdjustCreditMenu,
UserListMenu,
ShowUserMenu,
ProductListMenu,
)
from .menus.printermenu import PrintLabelMenu
from .menus.stats import *
from .conf import config
random.seed() random.seed()
if __name__ == '__main__': def main():
if not conf.stop_allowed: if not config.getboolean('general', 'stop_allowed'):
signal.signal(signal.SIGQUIT, signal.SIG_IGN) signal.signal(signal.SIGQUIT, signal.SIG_IGN)
if not conf.stop_allowed: if not config.getboolean('general', 'stop_allowed'):
signal.signal(signal.SIGTSTP, signal.SIG_IGN) signal.signal(signal.SIGTSTP, signal.SIG_IGN)
main = MainMenu('Dibbler main menu', main = MainMenu('Dibbler main menu',
@ -53,7 +63,7 @@ if __name__ == '__main__':
], ],
exit_msg='happy happy joy joy', exit_msg='happy happy joy joy',
exit_confirm_msg='Really quit Dibbler?') exit_confirm_msg='Really quit Dibbler?')
if not conf.quit_allowed: if not config.getboolean('general', 'quit_allowed'):
main.exit_disallowed_msg = 'You can check out any time you like, but you can never leave.' main.exit_disallowed_msg = 'You can check out any time you like, but you can never leave.'
while True: while True:
# noinspection PyBroadException # noinspection PyBroadException
@ -65,8 +75,12 @@ if __name__ == '__main__':
except: except:
print('Something went wrong.') print('Something went wrong.')
print(f'{sys.exc_info()[0]}: {sys.exc_info()[1]}') print(f'{sys.exc_info()[0]}: {sys.exc_info()[1]}')
if conf.show_tracebacks: if config.getboolean('general', 'show_tracebacks'):
traceback.print_tb(sys.exc_info()[2]) traceback.print_tb(sys.exc_info()[2])
else: else:
break break
print('Restarting main menu.') print('Restarting main menu.')
if __name__ == '__main__':
main()

18
example-config.ini Normal file
View File

@ -0,0 +1,18 @@
[general]
quit_allowed = true
stop_allowed = false
show_tracebacks = true
input_encoding = 'utf8'
[database]
url = postgresql://robertem@127.0.0.1/pvvvv
[limits]
low_credit_warning_limit = -100
user_recent_transaction_limit = 100
# See https://pypi.org/project/brother_ql/ for label types
# Set rotate to False for endless labels
[printer]
label_type = "62"
label_rotate = false

View File

@ -1,12 +1,15 @@
{ {
"nodes": { "nodes": {
"flake-utils": { "flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1692799911,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -17,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1674839022, "lastModified": 1693145325,
"narHash": "sha256-8F1U06t9glkgBC8gBfjoA2eeUb9MYRRp6NMKY3c0VEI=", "narHash": "sha256-Gat9xskErH1zOcLjYMhSDBo0JTBZKfGS0xJlIRnj6Rc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "14b6cf7602c341e525a8fe17ac2349769376515e", "rev": "cddebdb60de376c1bdb7a4e6ee3d98355453fe56",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -34,6 +37,21 @@
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

139
flake.nix
View File

@ -4,125 +4,33 @@
inputs.flake-utils.url = "github:numtide/flake-utils"; inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }: outputs = { self, nixpkgs, flake-utils }:
{ flake-utils.lib.eachDefaultSystem (system: let
overlays.default = final: prev: { pkgs = nixpkgs.legacyPackages.${system};
dibbler = prev.callPackage ./nix/dibbler.nix { }; in {
}; packages = {
} // default = self.packages.${system}.dibbler;
dibbler = pkgs.callPackage ./nix/dibbler.nix {
flake-utils.lib.eachDefaultSystem (system: python3Packages = pkgs.python311Packages;
let
pkgs = import nixpkgs {
inherit system;
overlays = [ self.overlays.default ];
};
in {
packages = rec {
dibbler = pkgs.dibbler;
# dibblerCross = pkgs.pkgsCross.aarch64-multiplatform.dibbler;
default = dibbler;
};
apps = rec {
dibbler = flake-utils.lib.mkApp {
drv = self.packages.${system}.dibbler;
};
default = dibbler;
};
}
) //
{
nixosModules.default = { config, pkgs, ... }: let
inherit (nixpkgs.legacyPackages."x86_64-linux") lib;
cfg = config.services.dibbler;
in {
options.services.dibbler = {
package = lib.mkPackageOption pkgs "dibbler" { };
config = lib.mkOption {
default = ./conf.py;
};
};
config = let
screen = "${pkgs.screen}/bin/screen";
in {
nixpkgs.overlays = [ self.overlays.default ];
boot = {
consoleLogLevel = 0;
enableContainers = false;
loader.grub.enable = false;
};
users = {
groups.dibbler = { };
users.dibbler = {
group = "dibbler";
extraGroups = [ "lp" ];
isNormalUser = true;
shell = ((pkgs.writeShellScriptBin "login-shell" "${screen} -x dibbler") // {shellPath = "/bin/login-shell";});
};
};
};
systemd.services.screen-daemon = {
description = "Dibbler service screen";
wantedBy = [ "default.target" ];
serviceConfig = {
ExecStartPre = "-${screen} -X -S dibbler kill";
ExecStart = "${screen} -dmS dibbler -O -l ${cfg.package.override { conf = cfg.config; }}/bin/dibbler";
ExecStartPost = "${screen} -X -S dibbler width 42 80";
User = "dibbler";
Group = "dibbler";
Type = "forking";
RemainAfterExit = false;
Restart = "always";
RestartSec = "5s";
SuccessExitStatus = 1;
};
};
# https://github.com/NixOS/nixpkgs/issues/84105
boot.kernelParams = [
"console=ttyUSB0,9600"
"console=tty1"
];
systemd.services."serial-getty@ttyUSB0" = {
enable = true;
wantedBy = [ "getty.target" ]; # to start at boot
serviceConfig.Restart = "always"; # restart when session is closed
};
services = {
openssh = {
enable = true;
permitRootLogin = "yes";
};
getty.autologinUser = lib.mkForce "dibbler";
udisks2.enable = false;
};
networking.firewall.logRefusedConnections = false;
console.keyMap = "no";
programs.command-not-found.enable = false;
i18n.supportedLocales = [ "en_US.UTF-8/UTF-8" ];
environment.noXlibs = true;
documentation = {
info.enable = false;
man.enable = false;
};
security = {
polkit.enable = lib.mkForce false;
audit.enable = false;
};
}; };
}; };
} //
apps = {
default = self.apps.${system}.dibbler;
dibbler = flake-utils.lib.mkApp {
drv = self.packages.${system}.dibbler;
};
};
})
//
{ {
# Note: using the module requires that you have applied the
# overlay first
nixosModules.default = import ./nix/module.nix;
images.skrot = self.nixosConfigurations.skrot.config.system.build.sdImage;
nixosConfigurations.skrot = nixpkgs.lib.nixosSystem { nixosConfigurations.skrot = nixpkgs.lib.nixosSystem {
system = "aarch64-linux"; system = "aarch64-linux";
modules = [ modules = [
@ -155,6 +63,5 @@
}) })
]; ];
}; };
images.skrot = self.nixosConfigurations.skrot.config.system.build.sdImage;
}; };
} }

View File

@ -1,47 +0,0 @@
ALTER TABLE pvv_vv.products RENAME TO products_old;
CREATE TABLE pvv_vv.products
(
product_id serial,
bar_code character varying(13) NOT NULL,
name character varying(45),
price integer,
stock integer NOT NULL,
CONSTRAINT product_pkey PRIMARY KEY (product_id),
CONSTRAINT barcode_unique UNIQUE (bar_code)
)
INSERT INTO pvv_vv.products (bar_code, name, price, stock)
SELECT bar_code, name, price, stock FROM products_old;
ALTER TABLE pvv_vv.purchase_entries RENAME TO purchase_entries_old;
ALTER TABLE pvv_vv.purchase_entries_old
RENAME CONSTRAINT purchase_entries_pkey TO purchase_entries_old_pkey;
ALTER TABLE pvv_vv.purchase_entries_old
RENAME CONSTRAINT purchase_entries_purchase_id_fkey TO purchase_entries_old_purchase_id_fkey;
ALTER TABLE pvv_vv.purchase_entries_old
RENAME CONSTRAINT purchase_entries_product_bar_code_fkey TO purchase_entries_old_product_bar_code_fkey;
CREATE TABLE pvv_vv.purchase_entries
(
id serial,
purchase_id integer,
product_id integer,
amount integer,
CONSTRAINT purchase_entries_pkey PRIMARY KEY (id),
CONSTRAINT purchase_entries_product_id_fkey FOREIGN KEY (product_id)
REFERENCES pvv_vv.products (product_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT purchase_entries_purchase_id_fkey FOREIGN KEY (purchase_id)
REFERENCES pvv_vv.purchases (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
INSERT INTO purchase_entries (id, purchase_id, product_id, amount)
SELECT peo.id, peo.purchase_id, p.product_id, peo.amount
FROM purchase_entries_old AS peo
JOIN products AS p ON p.bar_code = peo.product_bar_code;
ALTER TABLE pvv_vv.transactions
ADD COLUMN penalty integer DEFAULT 1;
DROP TABLE products_old;
DROP TABLE purchase_entries_old;

View File

@ -1,38 +1,20 @@
{ lib, python3Packages, fetchFromGitHub { lib
, conf ? ../conf.py , python3Packages
, fetchFromGitHub
}: }:
python3Packages.buildPythonApplication { python3Packages.buildPythonApplication {
pname = "dibbler"; pname = "dibbler";
version = "unstable-2021-09-07"; version = "unstable-2021-09-07";
format = "other";
src = lib.cleanSource ../.; src = lib.cleanSource ../.;
format = "pyproject";
nativeBuildInputs = with python3Packages; [ setuptools ];
propagatedBuildInputs = with python3Packages; [ propagatedBuildInputs = with python3Packages; [
brother-ql brother-ql
sqlalchemy matplotlib
psycopg2 psycopg2
python-barcode python-barcode
sqlalchemy
]; ];
preInstall = ''
libdir=$out/lib/${python3Packages.python.libPrefix}/site-packages
mkdir -p $out/bin $libdir
'';
installPhase = ''
runHook preInstall
libdir=$out/lib/${python3Packages.python.libPrefix}/site-packages
mv * $libdir
cp ${conf} $libdir/
mv $libdir/text_based.py $out/bin/dibbler
runHook postInstall
'';
} }

84
nix/module.nix Normal file
View File

@ -0,0 +1,84 @@
{ config, pkgs, lib, ... }: let
cfg = config.services.dibbler;
in {
options.services.dibbler = {
package = lib.mkPackageOption pkgs "dibbler" { };
config = lib.mkOption {
default = ../conf.py;
};
};
config = let
screen = "${pkgs.screen}/bin/screen";
in {
boot = {
consoleLogLevel = 0;
enableContainers = false;
loader.grub.enable = false;
};
users = {
groups.dibbler = { };
users.dibbler = {
group = "dibbler";
extraGroups = [ "lp" ];
isNormalUser = true;
shell = ((pkgs.writeShellScriptBin "login-shell" "${screen} -x dibbler") // {shellPath = "/bin/login-shell";});
};
};
systemd.services.screen-daemon = {
description = "Dibbler service screen";
wantedBy = [ "default.target" ];
serviceConfig = {
ExecStartPre = "-${screen} -X -S dibbler kill";
ExecStart = "${screen} -dmS dibbler -O -l ${cfg.package}/bin/dibbler --config ${cfg.config}";
ExecStartPost = "${screen} -X -S dibbler width 42 80";
User = "dibbler";
Group = "dibbler";
Type = "forking";
RemainAfterExit = false;
Restart = "always";
RestartSec = "5s";
SuccessExitStatus = 1;
};
};
# https://github.com/NixOS/nixpkgs/issues/84105
boot.kernelParams = [
"console=ttyUSB0,9600"
"console=tty1"
];
systemd.services."serial-getty@ttyUSB0" = {
enable = true;
wantedBy = [ "getty.target" ]; # to start at boot
serviceConfig.Restart = "always"; # restart when session is closed
};
services = {
openssh = {
enable = true;
permitRootLogin = "yes";
};
getty.autologinUser = lib.mkForce "dibbler";
udisks2.enable = false;
};
networking.firewall.logRefusedConnections = false;
console.keyMap = "no";
programs.command-not-found.enable = false;
i18n.supportedLocales = [ "en_US.UTF-8/UTF-8" ];
environment.noXlibs = true;
documentation = {
info.enable = false;
man.enable = false;
};
security = {
polkit.enable = lib.mkForce false;
audit.enable = false;
};
};
}

30
pyproject.toml Normal file
View File

@ -0,0 +1,30 @@
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[project]
name = "dibbler"
authors = []
description = "EDB-system for PVV"
readme = "README.md"
requires-python = ">=3.11"
license = {text = "BSD-3-Clause"}
classifiers = [
"Programming Language :: Python :: 3",
]
dependencies = [
"SQLAlchemy >= 2.0, <2.1",
"brother-ql",
"matplotlib",
"psycopg2 >= 2.8, <2.10",
"python-barcode",
]
dynamic = ["version"]
[tool.setuptools.packages.find]
include = ["dibbler*"]
[project.scripts]
dibbler = "dibbler.cli:main"
slabbedasker = "dibbler.scripts.slabbedasker:main"
statistikk = "dibbler.scripts.statistikk:main"

View File

@ -1,9 +0,0 @@
#!/usr/bin/python
from db import *
# Start an SQL session
session=Session()
# Let's find all users with a negative credit
slabbedasker=session.query(User).filter(User.credit<0).all()
for slubbert in slabbedasker:
print(f"{slubbert.name}, {slubbert.credit}")

View File

@ -1,47 +0,0 @@
\documentclass[a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage{fullpage}
\usepackage{pstricks}
\usepackage{pst-barcode}
\title{Dibbler Special Barcodes}
\date{}
\begin{document}
\maketitle
\setlength\tabcolsep{25pt}
\begin{tabular}{ccc}
\begin{pspicture}(3,1.2in)
\psbarcode[scalex=0.8,scaley=0.8]{4242424242420}{includetext guardwhitespace}{ean13}
\end{pspicture}
&
\begin{pspicture}(3,1.2in)
\psbarcode[scalex=0.8,scaley=0.8]{7640140330815}{includetext guardwhitespace}{ean13}
\end{pspicture}
&
\begin{pspicture}(3,1.2in)
\psbarcode[scalex=0.8,scaley=0.8]{7024850087007}{includetext guardwhitespace}{ean13}
\end{pspicture}
\\
PVV-skjorte &
Nespresso-kapsel &
Risengrynsgraut
\\
\begin{pspicture}(3,1.2in)
\psbarcode[scalex=0.8,scaley=0.8]{7024850087014}{includetext guardwhitespace}{ean13}
\end{pspicture}
&
\begin{pspicture}(3,1.2in)
\psbarcode[scalex=0.8,scaley=0.8]{5000159410946}{includetext guardwhitespace}{ean13}
\end{pspicture}
&
\\
Rømmegraut
&
Snickers mini
&
\end{tabular}
\end{document}

View File

@ -1,45 +0,0 @@
\documentclass[a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage{fullpage}
\usepackage{pstricks}
\usepackage{pst-barcode}
\usepackage{graphicx}
\title{User Barcodes}
\date{}
\begin{document}
%
\maketitle
\setlength\tabcolsep{40pt}
%
\begin{tabular}{ccc}
\includegraphics[width=0.20\textwidth]{userimages/alfkjempestor}
&
\includegraphics[width=0.20\textwidth]{userimages/tirilane}
\\
\begin{pspicture}(3,1.0in)
\psbarcode[scalex=0.8,scaley=0.8]{NTNU457343}{includetext guardwhitespace}{code39}
\end{pspicture}
&
\begin{pspicture}(3,1.0in)
\psbarcode[scalex=0.8,scaley=0.8]{NTNU318657}{includetext guardwhitespace}{code39}
\end{pspicture}
\\
\includegraphics[width=0.20\textwidth]{userimages/oysteini}
&
\includegraphics[width=0.20\textwidth]{userimages/almelid}
\\
\begin{pspicture}(3,1.0in)
\psbarcode[scalex=0.8,scaley=0.8]{NTNU458221}{includetext guardwhitespace}{code39}
\end{pspicture}
&
\begin{pspicture}(3,1.0in)
\psbarcode[scalex=0.8,scaley=0.8]{ALMELID}{includetext guardwhitespace}{code39}
\end{pspicture}
\\
\end{tabular}
%
\end{document}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,197 +0,0 @@
#! /usr/bin/env python
# -*- coding: UTF-8 -*-
import matplotlib.pyplot as plt
from statistikkHelpers import *
import matplotlib.dates as mdates
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 getDateFile(date, n):
try:
if n==0:
inp = input('start date? (yyyy-mm-dd) ')
elif n==-1:
inp = input('end date? (yyyy-mm-dd) ')
year = inp.partition('-')
month = year[2].partition('-')
return datetime.date(int(year[0]), int(month[0]), int(month[2]))
except:
print('invalid date, setting start start date')
if n==0:
print('to date found on first line')
elif n==-1:
print('to date found on last line')
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 getProducts(products):
product = []
products = products.partition('¤')
product.append(products[0])
while (products[1]=='¤'):
products = products[2].partition('¤')
product.append(products[0])
return product
def piePlot(dictionary, n):
keys = []
values = []
i=0
for key in sorted(dictionary, key=dictionary.get, reverse=True):
values.append(dictionary[key])
if i<n:
keys.append(key)
i += 1
else:
keys.append('')
plt.pie(values, labels=keys)
def datePlot(array, dateLine):
if (not array == []):
plt.bar(dateLine, array)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b'))
def dayPlot(array, days):
if (not array == []):
for i in range(7):
array[i]=array[i]*7.0/days
plt.bar(list(range(7)), array)
plt.xticks(list(range(7)),[' mon',' tue',' wed',' thu',' fri',' sat',' sun'])
def graphPlot(array, dateLine):
if (not array == []):
plt.plot(dateLine, array)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b'))
def plotUser(database, dateLine, user, n):
printUser(database, dateLine, user, n)
plt.subplot(221)
piePlot(database.personVareAntall[user], n)
plt.xlabel('antall varer kjøpt gjengitt i antall')
plt.subplot(222)
datePlot(database.personDatoVerdi[user], dateLine)
plt.xlabel('penger brukt over dato')
plt.subplot(223)
piePlot(database.personVareVerdi[user], n)
plt.xlabel('antall varer kjøpt gjengitt i verdi')
plt.subplot(224)
dayPlot(database.personUkedagVerdi[user], len(dateLine))
plt.xlabel('forbruk over ukedager')
plt.show()
def plotProduct(database, dateLine, product, n):
printProduct(database, dateLine, product, n)
plt.subplot(221)
piePlot(database.varePersonAntall[product], n)
plt.xlabel('personer som har handler produktet')
plt.subplot(222)
datePlot(database.vareDatoAntall[product], dateLine)
plt.xlabel('antall produkter handlet per dag')
#plt.subplot(223)
plt.subplot(224)
dayPlot(database.vareUkedagAntall[product], len(dateLine))
plt.xlabel('antall over ukedager')
plt.show()
def plotGlobal(database, dateLine, n):
printGlobal(database, dateLine, n)
plt.subplot(231)
piePlot(database.globalVareVerdi, n)
plt.xlabel('varer kjøpt gjengitt som verdi')
plt.subplot(232)
datePlot(database.globalDatoForbruk, dateLine)
plt.xlabel('forbruk over dato')
plt.subplot(233)
graphPlot(database.pengebeholdning, dateLine)
plt.xlabel('pengebeholdning over tid (negativ verdi utgjør samlet kreditt)')
plt.subplot(234)
piePlot(database.globalPersonForbruk, n)
plt.xlabel('penger brukt av personer')
plt.subplot(235)
dayPlot(database.globalUkedagForbruk, len(dateLine))
plt.xlabel('forbruk over ukedager')
plt.show()
def alt4menu(database, dateLine, useDatabase):
n=10
while 1:
print('\n1: user-statistics, 2: product-statistics, 3:global-statistics, n: adjust amount of data shown q:quit')
try:
inp = input('')
except:
continue
if inp == 'q':
break
elif inp == '1':
if i=='0':
user = input('input full username: ')
else:
user = getUser()
plotUser(database, dateLine, user, n)
elif inp == '2':
if i=='0':
product = input('input full product name: ')
else:
product = getProduct()
plotProduct(database, dateLine, product, n)
elif inp == '3':
plotGlobal(database, dateLine, n)
elif inp == 'n':
try:
n=int(input('set number to show '));
except:
pass
#---------------------------------------MAIN-------------------------------------
inputType=getInputType()
i = input('0:fil, 1:database \n? ')
if (inputType == 1):
if i=='0':
user = input('input full username: ')
else:
user = getUser()
product = ''
elif (inputType == 2):
if i=='0':
product = input('input full product name: ')
else:
product = getProduct()
user = ''
else :
product = ''
user = ''
if i=='0':
inputFile=input('logfil? ')
if inputFile=='':
inputFile='default.dibblerlog'
database, dateLine = buildDatabaseFromFile(inputFile, inputType, product, user)
else:
database, dateLine = buildDatabaseFromDb(inputType, product, user)
if (inputType==1):
plotUser(database, dateLine, user, 10)
if (inputType==2):
plotProduct(database, dateLine, product, 10)
if (inputType==3):
plotGlobal(database, dateLine, 10)
if (inputType==4):
alt4menu(database, dateLine, i)

62
test.py
View File

@ -1,62 +0,0 @@
import os
import datetime
from PIL import ImageFont
from brother_ql.devicedependent import label_type_specs
from printer_helpers import print_bar_code
#label_type = "29x90"
#rotate = True
#barcode_value = "7050122105438"
#barcode_text = "Chips"
#printer_type = "QL-700"
from PIL import Image, ImageMode, ImageDraw
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']
else:
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:
font = ImageFont.truetype(font_path, fs)
tw, th = font.getsize(text)
fs -= 1
width = tw+2*margin
elif height == 0:
while tw + 2*margin > width:
font = ImageFont.truetype(font_path, fs)
tw, th = font.getsize(text)
fs -= 1
height = th+2*margin
else:
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)
im = Image.new("RGB", (width, height), (255, 255, 255))
dr = ImageDraw.Draw(im)
dr.text((xp, yp), text, fill=(0, 0, 0), font=font)
now = datetime.datetime.now()
date = now.strftime("%Y-%m-%d")
dr.text((0, 0), date, fill=(0, 0, 0))
base_path = os.path.dirname(os.path.realpath(__file__))
fn = os.path.join(base_path, "bar_codes", text + ".png")
im.save(fn, "PNG")
print_name_label("chrivi", label_type="29x90", rotate=True)

View File

@ -1,34 +0,0 @@
import re
from db import Product, User
from printer_helpers import print_bar_code, print_name_label
from text_interface.helpermenus import Menu
import conf
class PrintLabelMenu(Menu):
def __init__(self):
Menu.__init__(self, 'Print a label', uses_db=True)
self.help_text = '''
Prints out a product bar code on the printer
Put it up somewhere in the vicinity.
'''
def _execute(self):
self.print_header()
thing = self.input_thing('Product/User')
if isinstance(thing, Product):
if re.match(r"^[0-9]{13}$", thing.bar_code):
bar_type = "ean13"
elif re.match(r"^[0-9]{8}$", thing.bar_code):
bar_type = "ean8"
else:
bar_type = "code39"
print_bar_code(thing.bar_code, thing.name, barcode_type=bar_type, rotate=conf.label_rotate,
printer_type="QL-700", label_type=conf.label_type)
elif isinstance(thing, User):
print_name_label(text=thing.name, label_type=conf.label_type, rotate=conf.label_rotate,
printer_type="QL-700")

View File

@ -1,5 +0,0 @@
db_url = 'postgresql://torjehoa_dibblerdummy:1234@postgres/torjehoa_dibblerdummy'
quit_allowed = True
stop_allowed = False
show_tracebacks = True
input_encoding = 'utf8'

265
ui.py
View File

@ -1,265 +0,0 @@
import curses
#from copy import deepcopy
#import curses.panel
import curses.textpad
#import time
import curses.ascii
from db import *
def cycle(list, index, up):
if index <= 0 and up:
return len(list)-1
elif up:
return index - 1
elif index >= len(list)-1:
return 0
else:
return index + 1
class MainMenu():
def __init__(self, screen):
self.screen = screen
curses.curs_set(0) # hide the cursor
self.size = screen.getmaxyx() # get screen size
self.choices = [SubMenu("Purchase"), ChargeMenu(self.screen), SubMenu("View Transactions")]
self.selected = 0
self.execute()
def execute(self):
while 1:
self.screen.clear()
self.screen.border()
for i in range(len(self.choices)):
if i == self.selected:
self.screen.addstr(i+1,1, str(i+1) + ") " + self.choices[i].text, curses.A_REVERSE)
else:
self.screen.addstr(i+1,1, str(i+1) + ") " + self.choices[i].text)
self.screen.refresh()
c = self.screen.getch()
if c == ord('q') or c == 27:
break
elif c == 10: #return key
self.choices[self.selected].execute()
elif c == curses.KEY_UP:
self.selected = cycle(self.choices, self.selected, True)
elif c == curses.KEY_DOWN:
self.selected = cycle(self.choices, self.selected, False)
elif c >= 49 and c <= 48+len(self.choices): #number key
self.choices[c-49].execute()
class SubMenu():
def __init__(self, text):
self.text = text
class ChargeMenu(SubMenu):
def __init__(self, screen):
self.text = "Charge"
self.screen = screen
# self.size = self.screen.getmaxyx()
# self.marked = 0
# self.textbox = False
# self.textpad = False
# self.textwindow = False
# self.edit_area = False
# self.search_text = ""
# self.session = False
def execute(self):
self.make_windows()
self.resultview = Selectable(self.resultwindow)
# Initialize the variables
self.marked = 0
self.search_text = ""
self.amount = ""
# curses.curs_set(1)
self.screen.move(2,2)
self.screen.leaveok(1)
self.session = Session()
while 1:
self.draw()
c = self.screen.getch()
if c == 27:
break
elif c == 9:
self.switch_cursor()
elif c == curses.KEY_RESIZE:
self.resize()
elif self.marked == 0:
self.textpad_edit(c)
self.textwindow.cursyncup()
elif self.marked == 1:
# self.amountpad.do_command(curses.ascii.SOH)
# for char in self.amount:
# self.amountpad.do_command(ord(char))
# self.amountpad.do_command(curses.KEY_LEFT)
self.amountpad_edit(c)
self.amountwindow.cursyncup()
self.check_calculation()
elif self.marked == 2:
self.resultview.do_command(c)
self.check_calculation()
self.session.close()
def check_calculation(self):
if self.amount and self.resultview.list:
self.set_calculation()
else:
self.calculation.clear()
def draw(self):
# if self.marked == 0:
# (y,x) = self.textwindow.getyx()
# y += 1
# x += 1
# else:
# (y,x) = self.screen.getyx()
self.screen.clear()
self.screen.border()
self.textwindow.border()
self.amountwindow.border()
if self.marked == 0:
self.textwindow.addstr(0,1, "[Username, card number or RFID]",curses.A_REVERSE)
self.amountwindow.addstr(0,1,"[Amount to be transferred]")
elif self.marked == 1:
self.textwindow.addstr(0,1, "[Username, card number or RFID]")
self.amountwindow.addstr(0,1,"[Amount to be transferred]",curses.A_REVERSE)
else:
self.textwindow.addstr(0,1, "[Username, card number or RFID]")
self.amountwindow.addstr(0,1,"[Amount to be transferred]")
self.resultview.draw()
self.textwindow.addstr(1,1,self.search_text)
self.amountwindow.addstr(1,1,self.amount)
self.calculation.draw()
# curses.curs_set(1)
# self.screen.move(y,x)
# curses.setsyx(y,x)
# self.textwindow.move(y-2,x-2)
self.screen.refresh()
def make_windows(self):
self.size = self.screen.getmaxyx()
self.textwindow = self.screen.subwin(3,self.size[1]/2-1,1,1)
self.amountwindow = self.screen.subwin(3,self.size[1]/2-1,1,self.size[1]/2)
self.edit_area = self.textwindow.subwin(1,self.size[1]/2-3,2,2)
self.amount_area = self.amountwindow.subwin(1,self.size[1]/2-3,2,self.size[1]/2+1)
self.resultwindow = self.screen.subwin(self.size[0]-5,self.size[1]/2-1,4,1)
self.textpad = curses.textpad.Textbox(self.edit_area)
self.textpad.stripspaces = True
self.amountpad = curses.textpad.Textbox(self.amount_area)
self.amountpad.stripspaces = True
self.calcwindow = self.screen.subwin(self.size[0]-8,self.size[1]/2-1,4,self.size[1]/2)
self.calculation = Calculation(self.calcwindow)
def resize(self):
self.make_windows()
self.resultview.window = self.resultwindow
self.calculation.window = self.calcwindow
def switch_cursor(self):
if self.marked == 4:
# curses.curs_set(1)
self.screen.move(2,1+len(self.search_text))
self.marked = 0
# self.textpad.do_command(curses.ascii.SOH)
elif self.marked == 0:
self.marked += 1
self.screen.move(2,self.size[1]/2+2)
else:
curses.curs_set(0)
self.marked += 1
def textpad_edit(self, ch):
self.textpad.do_command(ch)
self.search_text = self.textpad.gather().strip()
self.resultview.set_list(self.session.query(User).filter(or_(User.user.like(str('%'+self.search_text+'%')),User.id.like('%'+self.search_text+'%'))).all())
# self.resultview.draw()
# self.resultwindow.refresh()
def amountpad_edit(self,ch):
if ch >= 48 and ch <= 57:
self.amountpad.do_command(ch)
elif ch <= 31 or ch > 255:
self.amountpad.do_command(ch)
else:
pass
self.amount = self.amountpad.gather().strip()
def set_calculation(self):
self.calculation.set_numbers([self.resultview.list[self.resultview.selected].credit, int(self.amount)])
class Selectable():
def __init__(self, window, list = [], selected = 0):
self.list=list
self.window = window
self.selected = selected
# self.attribute = attribute
def draw(self):
self.window.border()
for i in range(len(self.list)):
if i == self.selected:
self.window.addstr(i+1,1,self.list[i].user,curses.A_REVERSE)
else:
self.window.addstr(i+1,1,self.list[i].user)
self.window.addstr(0,1,"[Search results]")
def do_command(self,c):
if c == curses.KEY_UP:
self.selected = cycle(self.list, self.selected, True)
if c == curses.KEY_DOWN:
self.selected = cycle(self.list, self.selected, False)
def set_list(self,list):
if len(list)-1 < self.selected:
self.selected = len(list)-1
self.list = list
else:
self.list = list
class Calculation():
def __init__(self, window):
self.window = window
self.numbers = []
self.size = self.window.getmaxyx()
def draw(self):
self.window.clear()
self.window.border()
self.length = len(self.numbers)
if self.length > 0:
if self.size[0] >= self.length:
for i in range(self.length-1):
self.window.addstr((self.size[0]-self.length)/2+i,(self.size[1]+4-len(str(abs(self.numbers[i]))))/2,str(abs(self.numbers[i])))
if i > 0:
if self.numbers[i] >= 0:
self.window.addstr((self.size[0]-self.length)/2+i,(self.size[1]-8)/2,'+')
else:
self.window.addstr((self.size[0]-self.length)/2+i,(self.size[1]-8)/2,'-')
if self.numbers[self.length-1] >= 0:
self.window.addstr((self.size[0]+self.length)/2-1,(self.size[1]-8)/2,'+'+(7-len(str(self.numbers[self.length-1])))*" "+str(self.numbers[self.length-1]),curses.A_UNDERLINE)
else:
self.window.addstr((self.size[0]+self.length)/2-1,(self.size[1]-8)/2,'-'+(7-len(str(abs(self.numbers[self.length-1]))))*" "+str(abs(self.numbers[self.length-1])),curses.A_UNDERLINE)
self.window.addstr((self.size[0]+self.length)/2,(self.size[1]-8)/2,'='+(7-len(str(self.sum)))*" "+str(self.sum),curses.A_UNDERLINE)
def add(self):
self.sum = 0
for item in self.numbers:
self.sum += item
def clear(self):
self.numbers = []
self.sum = 0
def set_numbers(self, list):
self.numbers = list
self.add()
curses.wrapper(MainMenu)

View File

@ -1,37 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import db
from helpers import *
# Writes a log of all transactions to a text file.
#
# Usage:
# ./write_logfile.py filename
# or (writing to stdout):
# ./write_logfile.py
def write_log(f):
session = Session()
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])
description = ''
else:
products = ''
description = transaction.description
line = line_format % ('purchase', transaction.time, products,
transaction.user.name, transaction.amount, transaction.description)
f.write(line.encode('utf8'))
session.close()
if len(sys.argv) < 2:
write_log(sys.stdout)
else:
filename = sys.argv[1]
print('Writing log to ' + filename)
with open(filename, 'w') as f:
write_log(f)