Restructure project #3
|
@ -1,2 +1,4 @@
|
||||||
result
|
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> { } }:
|
{ pkgs ? import <nixos-unstable> { } }:
|
||||||
|
{
|
||||||
rec {
|
|
||||||
|
|
||||||
dibbler = pkgs.callPackage ./nix/dibbler.nix { };
|
dibbler = pkgs.callPackage ./nix/dibbler.nix { };
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
@ -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 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()
|
|
@ -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)
|
|
@ -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):
|
|
@ -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.
|
|
@ -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"]
|
||||||
|
|
|
@ -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):
|
|
@ -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():
|
|
@ -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':
|
|
@ -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 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
|
|
@ -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)
|
|
@ -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",):
|
|
@ -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()
|
|
@ -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
|
#! /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:
|
|
@ -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()
|
|
@ -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
30
flake.lock
|
@ -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
139
flake.nix
|
@ -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;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
{ 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
|
|
||||||
'';
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -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