Big cleanup ദ്ദി^._.^)
This commit is contained in:
parent
6ab66771f2
commit
c25e5cec27
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
result
|
||||
result-*
|
||||
result-*
|
||||
|
||||
dist
|
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=>
|
||||
|
@ -1,7 +1,4 @@
|
||||
{ pkgs ? import <nixos-unstable> { } }:
|
||||
|
||||
rec {
|
||||
|
||||
{
|
||||
dibbler = pkgs.callPackage ./nix/dibbler.nix { };
|
||||
|
||||
}
|
||||
|
0
dibbler/__init__.py
Normal file
0
dibbler/__init__.py
Normal file
24
dibbler/cli.py
Normal file
24
dibbler/cli.py
Normal 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
6
dibbler/conf.py
Normal 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()
|
@ -1,10 +1,12 @@
|
||||
from db import *
|
||||
from sqlalchemy import or_, and_
|
||||
import pwd
|
||||
import subprocess
|
||||
import os
|
||||
import signal
|
||||
|
||||
from sqlalchemy import or_, and_
|
||||
|
||||
from .models.db import *
|
||||
|
||||
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()
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
import db
|
||||
from .models.db import db
|
||||
|
||||
db.Base.metadata.create_all(db.engine)
|
@ -1,8 +1,15 @@
|
||||
from math import ceil
|
||||
|
||||
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):
|
@ -1,7 +1,15 @@
|
||||
import conf
|
||||
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):
|
||||
@ -29,7 +37,7 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||
"""
|
||||
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):
|
||||
assert isinstance(user, User)
|
||||
@ -49,7 +57,7 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||
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("CONSIDER PUTTING MONEY IN THE BOX TO AVOID THIS.")
|
||||
print("")
|
||||
@ -158,8 +166,8 @@ When finished, write an empty line to confirm the purchase.\n'''
|
||||
for t in self.purchase.transactions:
|
||||
if not t.user.is_anonymous():
|
||||
print(f"User {t.user.name}'s credit is now {t.user.credit:d} kr")
|
||||
if t.user.credit < conf.low_credit_warning_limit:
|
||||
print(f'USER {t.user.name} HAS LOWER CREDIT THAN {conf.low_credit_warning_limit:d},',
|
||||
if t.user.credit < config.getint('limits', 'low_credit_warning_limit'):
|
||||
print(f'USER {t.user.name} HAS LOWER CREDIT THAN {config.getint("limits", "low_credit_warning_limit"):d},',
|
||||
'AND SHOULD CONSIDER PUTTING SOME MONEY IN THE BOX.')
|
||||
|
||||
# Superfast mode skips a linebreak for some reason.
|
@ -1,6 +1,7 @@
|
||||
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"]
|
||||
|
@ -5,10 +5,20 @@ import re
|
||||
import sys
|
||||
from select import select
|
||||
|
||||
import conf
|
||||
from db import User, Session
|
||||
from helpers import search_user, search_product, guess_data_type, argmax
|
||||
from text_interface import context_commands, local_help_commands, help_commands, exit_commands
|
||||
from dibbler.models.db import User, Session
|
||||
from dibbler.helpers import (
|
||||
search_user,
|
||||
search_product,
|
||||
guess_data_type,
|
||||
argmax,
|
||||
)
|
||||
|
||||
from . import (
|
||||
context_commands,
|
||||
local_help_commands,
|
||||
help_commands,
|
||||
exit_commands,
|
||||
)
|
||||
|
||||
|
||||
class ExitMenu(Exception):
|
@ -4,11 +4,12 @@ import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
from db import Session
|
||||
from text_interface import faq_commands, restart_commands
|
||||
from text_interface.buymenu import BuyMenu
|
||||
from text_interface.faq import FAQMenu
|
||||
from text_interface.helpermenus import Menu
|
||||
from dibbler.models.db import Session
|
||||
|
||||
from . import faq_commands, restart_commands
|
||||
from .buymenu import BuyMenu
|
||||
from .faq import FAQMenu
|
||||
from .helpermenus import Menu
|
||||
|
||||
|
||||
def restart():
|
@ -1,8 +1,10 @@
|
||||
import conf
|
||||
import sqlalchemy
|
||||
from db import Transaction, Product, User
|
||||
from helpers import less
|
||||
from text_interface.helpermenus import Menu, Selector
|
||||
|
||||
from dibbler.conf import config
|
||||
from dibbler.models.db import Transaction, Product, User
|
||||
from dibbler.helpers import less
|
||||
|
||||
from .helpermenus import Menu, Selector
|
||||
|
||||
|
||||
class TransferMenu(Menu):
|
||||
@ -53,12 +55,12 @@ class ShowUserMenu(Menu):
|
||||
print(f'Credit: {user.credit} kr')
|
||||
selector = Selector(f'What do you want to know about {user.name}?',
|
||||
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'),
|
||||
('transactions-all', 'Everything (List of all transactions)')])
|
||||
what = selector.execute()
|
||||
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':
|
||||
self.print_purchased_products(user)
|
||||
elif what == 'transactions-all':
|
45
dibbler/menus/printermenu.py
Normal file
45
dibbler/menus/printermenu.py
Normal 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"
|
||||
)
|
@ -1,10 +1,10 @@
|
||||
import sqlalchemy
|
||||
from db import PurchaseEntry, Product, User
|
||||
from helpers import less
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import func
|
||||
from statistikkHelpers import statisticsTextOnly
|
||||
from text_interface.helpermenus import Menu
|
||||
from sqlalchemy import desc, func
|
||||
|
||||
from dibbler.helpers import less
|
||||
from dibbler.models.db import PurchaseEntry, Product, User
|
||||
from dibbler.statistikkHelpers import statisticsTextOnly
|
||||
|
||||
from .helpermenus import Menu
|
||||
|
||||
__all__ = ["ProductPopularityMenu", "ProductRevenueMenu", "BalanceMenu", "LoggedStatisticsMenu"]
|
||||
|
||||
@ -77,8 +77,8 @@ class BalanceMenu(Menu):
|
||||
for p in product_list:
|
||||
total_value += p.stock * p.price
|
||||
|
||||
total_positive_credit = self.session.query(sqlalchemy.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_positive_credit = self.session.query(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_balance = total_value - total_credit
|
0
dibbler/models/__init__.py
Normal file
0
dibbler/models/__init__.py
Normal 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.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)
|
||||
from dibbler.conf import config
|
||||
|
||||
engine = create_engine(config.get('database', 'url'))
|
||||
Base = declarative_base()
|
||||
Session = sessionmaker(bind=engine)
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
name = Column(String(10), primary_key=True)
|
@ -1,16 +1,13 @@
|
||||
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",):
|
0
dibbler/scripts/__init__.py
Normal file
0
dibbler/scripts/__init__.py
Normal file
15
dibbler/scripts/slabbedasker.py
Normal file
15
dibbler/scripts/slabbedasker.py
Normal 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()
|
203
dibbler/scripts/statistikk.py
Normal file
203
dibbler/scripts/statistikk.py
Normal 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()
|
@ -1,11 +1,11 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
import operator
|
||||
from helpers import *
|
||||
import sys
|
||||
import db
|
||||
|
||||
from .helpers import *
|
||||
from .models.db import *;
|
||||
|
||||
def getUser():
|
||||
while 1:
|
@ -5,26 +5,36 @@ import random
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from helpers import *
|
||||
from text_interface.addstock import AddStockMenu
|
||||
from text_interface.buymenu import BuyMenu
|
||||
from text_interface.editing import *
|
||||
from text_interface.faq import FAQMenu
|
||||
from text_interface.helpermenus import Menu
|
||||
from text_interface.mainmenu import MainMenu
|
||||
from text_interface.miscmenus import ProductSearchMenu, TransferMenu, AdjustCreditMenu, UserListMenu, ShowUserMenu, \
|
||||
ProductListMenu
|
||||
from text_interface.printermenu import PrintLabelMenu
|
||||
from text_interface.stats import *
|
||||
from .helpers import *
|
||||
from .menus.addstock import AddStockMenu
|
||||
from .menus.buymenu import BuyMenu
|
||||
from .menus.editing import *
|
||||
from .menus.faq import FAQMenu
|
||||
from .menus.helpermenus import Menu
|
||||
from .menus.mainmenu import MainMenu
|
||||
|
||||
from .menus.miscmenus import (
|
||||
ProductSearchMenu,
|
||||
TransferMenu,
|
||||
AdjustCreditMenu,
|
||||
UserListMenu,
|
||||
ShowUserMenu,
|
||||
ProductListMenu,
|
||||
)
|
||||
|
||||
from .menus.printermenu import PrintLabelMenu
|
||||
from .menus.stats import *
|
||||
|
||||
from .conf import config
|
||||
|
||||
random.seed()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not conf.stop_allowed:
|
||||
def main():
|
||||
if not config.getboolean('general', 'stop_allowed'):
|
||||
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)
|
||||
|
||||
main = MainMenu('Dibbler main menu',
|
||||
@ -53,7 +63,7 @@ if __name__ == '__main__':
|
||||
],
|
||||
exit_msg='happy happy joy joy',
|
||||
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.'
|
||||
while True:
|
||||
# noinspection PyBroadException
|
||||
@ -65,8 +75,12 @@ if __name__ == '__main__':
|
||||
except:
|
||||
print('Something went wrong.')
|
||||
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])
|
||||
else:
|
||||
break
|
||||
print('Restarting main menu.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
18
example-config.ini
Normal file
18
example-config.ini
Normal 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
|
30
flake.lock
generated
30
flake.lock
generated
@ -1,12 +1,15 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1692799911,
|
||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -17,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1674839022,
|
||||
"narHash": "sha256-8F1U06t9glkgBC8gBfjoA2eeUb9MYRRp6NMKY3c0VEI=",
|
||||
"lastModified": 1693145325,
|
||||
"narHash": "sha256-Gat9xskErH1zOcLjYMhSDBo0JTBZKfGS0xJlIRnj6Rc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "14b6cf7602c341e525a8fe17ac2349769376515e",
|
||||
"rev": "cddebdb60de376c1bdb7a4e6ee3d98355453fe56",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -34,6 +37,21 @@
|
||||
"flake-utils": "flake-utils",
|
||||
"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",
|
||||
|
139
flake.nix
139
flake.nix
@ -4,125 +4,33 @@
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
{
|
||||
overlays.default = final: prev: {
|
||||
dibbler = prev.callPackage ./nix/dibbler.nix { };
|
||||
};
|
||||
} //
|
||||
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
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;
|
||||
};
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
packages = {
|
||||
default = self.packages.${system}.dibbler;
|
||||
dibbler = pkgs.callPackage ./nix/dibbler.nix {
|
||||
python3Packages = pkgs.python311Packages;
|
||||
};
|
||||
};
|
||||
} //
|
||||
|
||||
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 {
|
||||
system = "aarch64-linux";
|
||||
modules = [
|
||||
@ -155,6 +63,5 @@
|
||||
})
|
||||
];
|
||||
};
|
||||
images.skrot = self.nixosConfigurations.skrot.config.system.build.sdImage;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
@ -1,38 +1,20 @@
|
||||
{ lib, python3Packages, fetchFromGitHub
|
||||
, conf ? ../conf.py
|
||||
{ lib
|
||||
, python3Packages
|
||||
, fetchFromGitHub
|
||||
}:
|
||||
|
||||
python3Packages.buildPythonApplication {
|
||||
pname = "dibbler";
|
||||
version = "unstable-2021-09-07";
|
||||
|
||||
format = "other";
|
||||
|
||||
src = lib.cleanSource ../.;
|
||||
|
||||
format = "pyproject";
|
||||
|
||||
nativeBuildInputs = with python3Packages; [ setuptools ];
|
||||
propagatedBuildInputs = with python3Packages; [
|
||||
brother-ql
|
||||
sqlalchemy
|
||||
matplotlib
|
||||
psycopg2
|
||||
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
84
nix/module.nix
Normal 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
30
pyproject.toml
Normal 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"
|
@ -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}")
|
@ -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}
|
@ -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
197
statistikk.py
197
statistikk.py
@ -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
62
test.py
@ -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)
|
@ -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")
|
@ -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
265
ui.py
@ -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)
|
@ -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)
|
Loading…
Reference in New Issue
Block a user