Use new shop init interface and black all brmbar/*.py.

This commit is contained in:
Dominik Pantůček 2025-07-21 15:42:40 +02:00
parent a8e4b3216d
commit dde8bd764d
4 changed files with 253 additions and 268 deletions

View file

@ -1,12 +1,15 @@
from .Currency import Currency from .Currency import Currency
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Account: class Account:
""" BrmBar Account """BrmBar Account
Both users and items are accounts. So is the money box, etc. Both users and items are accounts. So is the money box, etc.
Each account has a currency.""" Each account has a currency."""
def __init__(self, db, id, name, currency, acctype): def __init__(self, db, id, name, currency, acctype):
self.db = db self.db = db
self.id = id self.id = id
@ -17,48 +20,38 @@ class Account:
@classmethod @classmethod
def load_by_barcode(cls, db, barcode): def load_by_barcode(cls, db, barcode):
logger.debug("load_by_barcode: '%s'", barcode) logger.debug("load_by_barcode: '%s'", barcode)
res = db.execute_and_fetch("SELECT account FROM barcodes WHERE barcode = %s", [barcode]) res = db.execute_and_fetch(
"SELECT account FROM barcodes WHERE barcode = %s", [barcode]
)
if res is None: if res is None:
return None return None
id = res[0] id = res[0]
return cls.load(db, id = id) return cls.load(db, id=id)
@classmethod @classmethod
def load(cls, db, id = None, name = None): def load(cls, db, id=None):
""" Constructor for existing account """ """Constructor for existing account"""
if id is not None: if id is None:
name = db.execute_and_fetch("SELECT name FROM accounts WHERE id = %s", [id]) raise NameError("Account.load(): Specify id")
name = name[0] name, currid, acctype = db.execute_and_fetch(
elif name is not None: "SELECT name, currency, acctype FROM accounts WHERE id = %s", [id]
id = db.execute_and_fetch("SELECT id FROM accounts WHERE name = %s", [name]) )
id = id[0] currency = Currency.load(db, id=currid)
else: return cls(db, name=name, id=id, currency=currency, acctype=acctype)
raise NameError("Account.load(): Specify either id or name")
currid, acctype = db.execute_and_fetch("SELECT currency, acctype FROM accounts WHERE id = %s", [id])
currency = Currency.load(db, id = currid)
return cls(db, name = name, id = id, currency = currency, acctype = acctype)
@classmethod @classmethod
def create(cls, db, name, currency, acctype): def create(cls, db, name, currency, acctype):
""" Constructor for new account """ """Constructor for new account"""
# id = db.execute_and_fetch("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype]) id = db.execute_and_fetch(
id = db.execute_and_fetch("SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype]) "SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype]
# id = id[0] )
return cls(db, name = name, id = id, currency = currency, acctype = acctype) return cls(db, name=name, id=id, currency=currency, acctype=acctype)
def balance(self): def balance(self):
bal = self.db.execute_and_fetch( bal = self.db.execute_and_fetch(
"SELECT public.compute_account_balance(%s)", "SELECT public.compute_account_balance(%s)", [self.id]
[self.id]
)[0] )[0]
return bal return bal
#debit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit'])
#debit = debit[0] or 0
#credit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit'])
#credit = credit[0] or 0
#return debit - credit
def balance_str(self): def balance_str(self):
return self.currency.str(self.balance()) return self.currency.str(self.balance())
@ -66,22 +59,12 @@ class Account:
def negbalance_str(self): def negbalance_str(self):
return self.currency.str(-self.balance()) return self.currency.str(-self.balance())
#def debit(self, transaction, amount, memo):
# return self._transaction_split(transaction, 'debit', amount, memo)
#def credit(self, transaction, amount, memo):
# return self._transaction_split(transaction, 'credit', amount, memo)
#def _transaction_split(self, transaction, side, amount, memo):
# """ Common part of credit() and debit(). """
# self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, self.id, amount, memo])
def add_barcode(self, barcode): def add_barcode(self, barcode):
# self.db.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode]) self.db.execute(
self.db.execute("SELECT public.add_barcode_to_account(%s, %s)", [self.id, barcode]) "SELECT public.add_barcode_to_account(%s, %s)", [self.id, barcode]
)
self.db.commit() self.db.commit()
def rename(self, name): def rename(self, name):
# self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name]) self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name])
self.name = name self.name = name

View file

@ -1,10 +1,12 @@
# vim: set fileencoding=utf8 # vim: set fileencoding=utf8
class Currency: class Currency:
""" Currency """Currency
Each account has a currency (1 , 1 Club Maté, ...), pairs of Each account has a currency (1 , 1 Club Maté, ...), pairs of
currencies have (asymmetric) exchange rates. """ currencies have (asymmetric) exchange rates."""
def __init__(self, db, id, name): def __init__(self, db, id, name):
self.db = db self.db = db
self.id = id self.id = id
@ -12,72 +14,103 @@ class Currency:
@classmethod @classmethod
def default(cls, db): def default(cls, db):
""" Default wallet currency """ """Default wallet currency"""
return cls.load(db, name = "") return cls.load(db, name="")
@classmethod @classmethod
def load(cls, db, id = None, name = None): def load(cls, db, id=None):
""" Constructor for existing currency """ """Constructor for existing currency"""
if id is not None: if id is None:
name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id]) raise NameError("Currency.load(): Specify id")
name = name[0] name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id])
elif name is not None: name = name[0]
id = db.execute_and_fetch("SELECT id FROM currencies WHERE name = %s", [name]) return cls(db, id=id, name=name)
id = id[0]
else:
raise NameError("Currency.load(): Specify either id or name")
return cls(db, name = name, id = id)
@classmethod @classmethod
def create(cls, db, name): def create(cls, db, name):
""" Constructor for new currency """ """Constructor for new currency"""
# id = db.execute_and_fetch("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name])
id = db.execute_and_fetch("SELECT public.create_currency(%s)", [name]) id = db.execute_and_fetch("SELECT public.create_currency(%s)", [name])
# id = id[0] return cls(db, id=id, name=name)
return cls(db, name = name, id = id)
def rates(self, other): def rates(self, other):
""" Return tuple ($buy, $sell) of rates of $self in relation to $other (brmbar.Currency): """Return tuple ($buy, $sell) of rates of $self in relation to $other (brmbar.Currency):
$buy is the price of $self in means of $other when buying it (into brmbar) $buy is the price of $self in means of $other when buying it (into brmbar)
$sell is the price of $self in means of $other when selling it (from brmbar) """ $sell is the price of $self in means of $other when selling it (from brmbar)"""
# buy rate # buy rate
res = self.db.execute_and_fetch("SELECT public.find_buy_rate(%s, %s)",[self.id, other.id]) res = self.db.execute_and_fetch(
"SELECT public.find_buy_rate(%s, %s)", [self.id, other.id]
)
if res is None: if res is None:
raise NameError("Something fishy in find_buy_rate."); raise NameError("Something fishy in find_buy_rate.")
buy = res[0] buy = res[0]
if buy < 0: if buy < 0:
raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name()) raise NameError(
"Currency.rate(): Unknown conversion "
+ other.name()
+ " to "
+ self.name()
)
# sell rate # sell rate
res = self.db.execute_and_fetch("SELECT public.find_sell_rate(%s, %s)",[self.id, other.id]) res = self.db.execute_and_fetch(
"SELECT public.find_sell_rate(%s, %s)", [self.id, other.id]
)
if res is None: if res is None:
raise NameError("Something fishy in find_sell_rate."); raise NameError("Something fishy in find_sell_rate.")
sell = res[0] sell = res[0]
if sell < 0: if sell < 0:
raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name()) raise NameError(
"Currency.rate(): Unknown conversion "
+ self.name()
+ " to "
+ other.name()
)
return (buy, sell) return (buy, sell)
def rates2(self, other): def rates2(self, other):
# the original code for compare testing # the original code for compare testing
res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [self.id, other.id]) res = self.db.execute_and_fetch(
"SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1",
[self.id, other.id],
)
if res is None: if res is None:
raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name()) raise NameError(
"Currency.rate(): Unknown conversion "
+ other.name()
+ " to "
+ self.name()
)
buy_rate, buy_rate_dir = res buy_rate, buy_rate_dir = res
buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate buy = buy_rate if buy_rate_dir == "target_to_source" else 1 / buy_rate
res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [other.id, self.id]) res = self.db.execute_and_fetch(
"SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1",
[other.id, self.id],
)
if res is None: if res is None:
raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name()) raise NameError(
"Currency.rate(): Unknown conversion "
+ self.name()
+ " to "
+ other.name()
)
sell_rate, sell_rate_dir = res sell_rate, sell_rate_dir = res
sell = sell_rate if sell_rate_dir == "source_to_target" else 1/sell_rate sell = sell_rate if sell_rate_dir == "source_to_target" else 1 / sell_rate
return (buy, sell) return (buy, sell)
def convert(self, amount, target): def convert(self, amount, target):
res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [target.id, self.id]) res = self.db.execute_and_fetch(
"SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1",
[target.id, self.id],
)
if res is None: if res is None:
raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + target.name()) raise NameError(
"Currency.convert(): Unknown conversion "
+ self.name()
+ " to "
+ target.name()
)
rate, rate_dir = res rate, rate_dir = res
if rate_dir == "source_to_target": if rate_dir == "source_to_target":
resamount = amount * rate resamount = amount * rate
@ -89,10 +122,13 @@ class Currency:
return "{:.2f} {}".format(amount, self.name) return "{:.2f} {}".format(amount, self.name)
def update_sell_rate(self, target, rate): def update_sell_rate(self, target, rate):
# self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [self.id, target.id, rate, "source_to_target"]) self.db.execute(
self.db.execute("SELECT public.update_currency_sell_rate(%s, %s, %s)", "SELECT public.update_currency_sell_rate(%s, %s, %s)",
[self.id, target.id, rate]) [self.id, target.id, rate],
)
def update_buy_rate(self, source, rate): def update_buy_rate(self, source, rate):
# self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [source.id, self.id, rate, "target_to_source"]) self.db.execute(
self.db.execute("SELECT public.update_currency_buy_rate(%s, %s, %s)", "SELECT public.update_currency_buy_rate(%s, %s, %s)",
[source.id, self.id, rate]) [source.id, self.id, rate],
)

View file

@ -4,26 +4,29 @@ import psycopg2
from contextlib import closing from contextlib import closing
import time import time
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Database: class Database:
"""self-reconnecting database object""" """self-reconnecting database object"""
def __init__(self, dsn): def __init__(self, dsn):
self.db_conn = psycopg2.connect(dsn) self.db_conn = psycopg2.connect(dsn)
self.dsn = dsn self.dsn = dsn
def execute(self, query, attrs = None): def execute(self, query, attrs=None):
"""execute a query and return one result""" """execute a query and return one result"""
with closing(self.db_conn.cursor()) as cur: with closing(self.db_conn.cursor()) as cur:
cur = self._execute(cur, query, attrs) cur = self._execute(cur, query, attrs)
def execute_and_fetch(self, query, attrs = None): def execute_and_fetch(self, query, attrs=None):
"""execute a query and return one result""" """execute a query and return one result"""
with closing(self.db_conn.cursor()) as cur: with closing(self.db_conn.cursor()) as cur:
cur = self._execute(cur, query, attrs) cur = self._execute(cur, query, attrs)
return cur.fetchone() return cur.fetchone()
def execute_and_fetchall(self, query, attrs = None): def execute_and_fetchall(self, query, attrs=None):
"""execute a query and return all results""" """execute a query and return all results"""
with closing(self.db_conn.cursor()) as cur: with closing(self.db_conn.cursor()) as cur:
cur = self._execute(cur, query, attrs) cur = self._execute(cur, query, attrs)
@ -38,25 +41,29 @@ class Database:
else: else:
cur.execute(query, attrs) cur.execute(query, attrs)
return cur return cur
except psycopg2.DataError as error: # when biitr comes and enters '99999999999999999999' for amount except (
logger.debug("We have invalid input data (SQLi?): level %s (%s) @%s" % ( psycopg2.DataError
level, error, time.strftime("%Y%m%d %a %I:%m %p") ) as error: # when biitr comes and enters '99999999999999999999' for amount
)) logger.debug(
"We have invalid input data (SQLi?): level %s (%s) @%s"
% (level, error, time.strftime("%Y%m%d %a %I:%m %p"))
)
self.db_conn.rollback() self.db_conn.rollback()
raise RuntimeError("Unsanitized data entered again... BOBBY TABLES") raise RuntimeError("Unsanitized data entered again... BOBBY TABLES")
except psycopg2.OperationalError as error: except psycopg2.OperationalError as error:
logger.debug("Sleeping: level %s (%s) @%s" % ( logger.debug(
level, error, time.strftime("%Y%m%d %a %I:%m %p") "Sleeping: level %s (%s) @%s"
)) % (level, error, time.strftime("%Y%m%d %a %I:%m %p"))
#TODO: emit message "db conn failed, reconnecting )
time.sleep(2 ** level) # TODO: emit message "db conn failed, reconnecting
time.sleep(2**level)
try: try:
self.db_conn = psycopg2.connect(self.dsn) self.db_conn = psycopg2.connect(self.dsn)
except psycopg2.OperationalError: except psycopg2.OperationalError:
#TODO: emit message "psql not running to interface # TODO: emit message "psql not running to interface
time.sleep(1) time.sleep(1)
cur = self.db_conn.cursor() #how ugly is this? cur = self.db_conn.cursor() # how ugly is this?
return self._execute(cur, query, attrs, level+1) return self._execute(cur, query, attrs, level + 1)
except Exception as ex: except Exception as ex:
logger.debug("_execute exception: %s", ex) logger.debug("_execute exception: %s", ex)
self.db_conn.rollback() self.db_conn.rollback()

View file

@ -2,161 +2,163 @@ import brmbar
from .Currency import Currency from .Currency import Currency
from .Account import Account from .Account import Account
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Shop: class Shop:
""" BrmBar Shop """BrmBar Shop
Business logic so that only interaction is left in the hands Business logic so that only interaction is left in the hands
of the frontend scripts. """ of the frontend scripts."""
def __init__(self, db, currency, profits, cash, excess, deficit):
def __init__(self, db, currency_id, profits_id, cash_id, excess_id, deficit_id):
# Keep db as-is
self.db = db self.db = db
self.currency = currency # brmbar.Currency
self.profits = profits # income brmbar.Account for brmbar profit margins on items # Store all ids
self.cash = cash # our operational ("wallet") cash account self.currency_id = currency_id
self.excess = excess # account from which is deducted cash during inventory item fixing (when system contains less items than is the reality) self.profits_id = profits_id
self.deficit = deficit # account where is put cash during inventory item fixing (when system contains more items than is the reality) self.cash_id = cash_id
self.excess_id = excess_id
self.deficit_id = deficit_id
# Create objects where needed for legacy code
# brmbar.Currency
self.currency = Currency.load(self.db, id=self.currency_id)
# income brmbar.Account for brmbar profit margins on items
self.profits = Account.load(db, id=self.profits_id)
# our operational ("wallet") cash account
self.cash = Account.load(db, id=self.cash_id)
# account from which is deducted cash during inventory item
# fixing (when system contains less items than is the
# reality)
self.excess = Account.load(db, id=self.excess_id)
# account where is put cash during inventory item fixing (when
# system contains more items than is the reality)
self.deficit = Account.load(db, id=self.deficit_id)
@classmethod @classmethod
def new_with_defaults(cls, db): def new_with_defaults(cls, db):
return cls(db, # shop_class_initialization_data
currency = Currency.default(db), currency_id, profits_id, cash_id, excess_id, deficit_id = db.execute_and_fetch(
profits = Account.load(db, name = "BrmBar Profits"), "select currency_id, profits_id, cash_id, excess_id, deficit_id from public.shop_class_initialization_data()"
cash = Account.load(db, name = "BrmBar Cash"), )
excess = Account.load(db, name = "BrmBar Excess"), return cls(
deficit = Account.load(db, name = "BrmBar Deficit")) db,
currency_id=currency_id,
profits=profits_id,
cash=cash_id,
excess=excess_id,
deficit=deficit_id,
)
def sell(self, item, user, amount = 1): def sell(self, item, user, amount=1):
# Call the stored procedure for the sale # Call the stored procedure for the sale
logger.debug("sell: item.id=%s amount=%s user.id=%s self.currency.id=%s", logger.debug(
item.id, amount, user.id, self.currency.id) "sell: item.id=%s amount=%s user.id=%s self.currency.id=%s",
item.id,
amount,
user.id,
self.currency.id,
)
res = self.db.execute_and_fetch( res = self.db.execute_and_fetch(
"SELECT public.sell_item(%s, %s, %s, %s, %s)", "SELECT public.sell_item(%s, %s, %s, %s, %s)",
[item.id, amount, user.id, self.currency.id, [
"BrmBar sale of {0}x {1} to {2}".format(amount, item.name, user.name)] item.id,
)#[0] amount,
user.id,
self.currency.id,
"BrmBar sale of {0}x {1} to {2}".format(amount, item.name, user.name),
],
)
logger.debug("sell: res[0]=%s", res[0]) logger.debug("sell: res[0]=%s", res[0])
cost = res[0] cost = res[0]
self.db.commit() self.db.commit()
return cost return cost
# Sale: Currency conversion from item currency to shop currency
#(buy, sell) = item.currency.rates(self.currency)
#cost = amount * sell
#profit = amount * (sell - buy)
#transaction = self._transaction(responsible = user, description = "BrmBar sale of {}x {} to {}".format(amount, item.name, user.name)) def sell_for_cash(self, item, amount=1):
#item.credit(transaction, amount, user.name)
#user.debit(transaction, cost, item.name) # debit (increase) on a _debt_ account
#self.profits.debit(transaction, profit, "Margin on " + item.name)
#self.db.commit()
#return cost
def sell_for_cash(self, item, amount = 1):
cost = self.db.execute_and_fetch( cost = self.db.execute_and_fetch(
"SELECT public.sell_item_for_cash(%s, %s, %s, %s, %s)", "SELECT public.sell_item_for_cash(%s, %s, %s, %s, %s)",
[item.id, amount, user.id, self.currency.id, [
"BrmBar sale of {0}x {1} for cash".format(amount, item.name)] item.id,
)[0]#[0] amount,
user.id,
self.currency.id,
"BrmBar sale of {0}x {1} for cash".format(amount, item.name),
],
)[0]
self.db.commit() self.db.commit()
return cost return cost
## Sale: Currency conversion from item currency to shop currency
#(buy, sell) = item.currency.rates(self.currency)
#cost = amount * sell
#profit = amount * (sell - buy)
#transaction = self._transaction(description = "BrmBar sale of {}x {} for cash".format(amount, item.name)) def undo_sale(self, item, user, amount=1):
#item.credit(transaction, amount, "Cash")
#self.cash.debit(transaction, cost, item.name)
#self.profits.debit(transaction, profit, "Margin on " + item.name)
#self.db.commit()
#return cost
def undo_sale(self, item, user, amount = 1):
# Undo sale; rarely needed
#(buy, sell) = item.currency.rates(self.currency)
#cost = amount * sell
#profit = amount * (sell - buy)
#transaction = self._transaction(responsible = user, description = "BrmBar sale UNDO of {}x {} to {}".format(amount, item.name, user.name))
#item.debit(transaction, amount, user.name + " (sale undo)")
#user.credit(transaction, cost, item.name + " (sale undo)")
#self.profits.credit(transaction, profit, "Margin repaid on " + item.name)
# Call the stored procedure for undoing a sale # Call the stored procedure for undoing a sale
cost = self.db.execute_and_fetch( cost = self.db.execute_and_fetch(
"SELECT public.undo_sale_of_item(%s, %s, %s, %s)", "SELECT public.undo_sale_of_item(%s, %s, %s, %s)",
[item.id, amount, user.id, user.currency.id, "BrmBar sale UNDO of {0}x {1} to {2}".format(amount, item.name, user.name)] [
)[0]#[0] item.id,
amount,
user.id,
user.currency.id,
"BrmBar sale UNDO of {0}x {1} to {2}".format(
amount, item.name, user.name
),
],
)[0]
self.db.commit() self.db.commit()
return cost return cost
def add_credit(self, credit, user): def add_credit(self, credit, user):
self.db.execute_and_fetch( self.db.execute_and_fetch(
"SELECT public.add_credit(%s, %s, %s, %s)", "SELECT public.add_credit(%s, %s, %s, %s)",
[self.cash.id, credit, user.id, user.name] [self.cash.id, credit, user.id, user.name],
) )
self.db.commit() self.db.commit()
#transaction = self._transaction(responsible = user, description = "BrmBar credit replenishment for " + user.name)
#self.cash.debit(transaction, credit, user.name)
#user.credit(transaction, credit, "Credit replenishment")
#self.db.commit()
def withdraw_credit(self, credit, user): def withdraw_credit(self, credit, user):
self.db.execute_and_fetch( self.db.execute_and_fetch(
"SELECT public.withdraw_credit(%s, %s, %s, %s)", "SELECT public.withdraw_credit(%s, %s, %s, %s)",
[self.cash.id, credit, user.id, user.name] [self.cash.id, credit, user.id, user.name],
) )
self.db.commit() self.db.commit()
#transaction = self._transaction(responsible = user, description = "BrmBar credit withdrawal for " + user.name)
#self.cash.credit(transaction, credit, user.name)
#user.debit(transaction, credit, "Credit withdrawal")
#self.db.commit()
def transfer_credit(self, userfrom, userto, amount): def transfer_credit(self, userfrom, userto, amount):
self.db.execute_and_fetch( self.db.execute_and_fetch(
"SELECT public.transfer_credit(%s, %s, %s, %s, %s, %s)", "SELECT public.transfer_credit(%s, %s, %s, %s, %s, %s)",
[self.cash.id, amount, userfrom.id, userfrom.name, userto.id, userto.name] [self.cash.id, amount, userfrom.id, userfrom.name, userto.id, userto.name],
) )
self.db.commit() self.db.commit()
#self.add_credit(amount, userto)
#self.withdraw_credit(amount, userfrom)
def buy_for_cash(self, item, amount = 1): def buy_for_cash(self, item, amount=1):
iamount = int(amount) iamount = int(amount)
famount = float(iamount) famount = float(iamount)
assert famount == amount, "amount is not integer value %s".format(amount) assert famount == amount, "amount is not integer value %s".format(amount)
cost = self.db.execute_and_fetch( cost = self.db.execute_and_fetch(
"SELECT public.buy_for_cash(%s, %s, %s, %s, %s)", "SELECT public.buy_for_cash(%s, %s, %s, %s, %s)",
[self.cash.id, item.id, iamount, self.currency.id, item.name] [self.cash.id, item.id, iamount, self.currency.id, item.name],
)[0] )[0]
# Buy: Currency conversion from item currency to shop currency
#(buy, sell) = item.currency.rates(self.currency)
#cost = amount * buy
#transaction = self._transaction(description = "BrmBar stock replenishment of {}x {} for cash".format(amount, item.name))
#item.debit(transaction, amount, "Cash")
#self.cash.credit(transaction, cost, item.name)
self.db.commit() self.db.commit()
return cost return cost
def receipt_to_credit(self, user, credit, description): def receipt_to_credit(self, user, credit, description):
#transaction = self._transaction(responsible = user, description = "Receipt: " + description)
#self.profits.credit(transaction, credit, user.name)
#user.credit(transaction, credit, "Credit from receipt: " + description)
self.db.execute_and_fetch( self.db.execute_and_fetch(
"SELECT public.receipt_reimbursement(%s, %s, %s, %s, %s)", "SELECT public.receipt_reimbursement(%s, %s, %s, %s, %s)",
[self.profits.id, user.id, user.name, credit, description] [self.profits.id, user.id, user.name, credit, description],
)[0] )[0]
self.db.commit() self.db.commit()
def _transaction(self, responsible = None, description = None): def _transaction(self, responsible=None, description=None):
transaction = self.db.execute_and_fetch("INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id", transaction = self.db.execute_and_fetch(
[responsible.id if responsible else None, description]) "INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id",
[responsible.id if responsible else None, description],
)
transaction = transaction[0] transaction = transaction[0]
return transaction return transaction
@ -169,24 +171,31 @@ class Shop:
WHERE a.acctype = %s AND ts.side = %s WHERE a.acctype = %s AND ts.side = %s
""" """
if overflow is not None: if overflow is not None:
sumselect += ' AND a.name ' + ('NOT ' if overflow == 'exclude' else '') + ' LIKE \'%%-overflow\'' sumselect += (
cur = self.db.execute_and_fetch(sumselect, ["debt", 'debit']) " AND a.name "
+ ("NOT " if overflow == "exclude" else "")
+ " LIKE '%%-overflow'"
)
cur = self.db.execute_and_fetch(sumselect, ["debt", "debit"])
debit = cur[0] or 0 debit = cur[0] or 0
credit = self.db.execute_and_fetch(sumselect, ["debt", 'credit']) credit = self.db.execute_and_fetch(sumselect, ["debt", "credit"])
credit = credit[0] or 0 credit = credit[0] or 0
return debit - credit return debit - credit
def credit_negbalance_str(self, overflow=None): def credit_negbalance_str(self, overflow=None):
return self.currency.str(-self.credit_balance(overflow=overflow)) return self.currency.str(-self.credit_balance(overflow=overflow))
# XXX causing extra heavy delay ( thousands of extra SQL queries ), disabled # XXX causing extra heavy delay ( thousands of extra SQL queries ), disabled
def inventory_balance(self): def inventory_balance(self):
balance = 0 balance = 0
# Each inventory account has its own currency, # Each inventory account has its own currency,
# so we just do this ugly iteration # so we just do this ugly iteration
cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s", ["inventory"]) cur = self.db.execute_and_fetchall(
"SELECT id FROM accounts WHERE acctype = %s", ["inventory"]
)
for inventory in cur: for inventory in cur:
invid = inventory[0] invid = inventory[0]
inv = Account.load(self.db, id = invid) inv = Account.load(self.db, id=invid)
# FIXME: This is not correct as each instance of inventory # FIXME: This is not correct as each instance of inventory
# might have been bought for a different price! Therefore, # might have been bought for a different price! Therefore,
# we need to replace the command below with a complex SQL # we need to replace the command below with a complex SQL
@ -197,7 +206,7 @@ class Shop:
balance += b balance += b
return balance return balance
# XXX bypass hack # XXX bypass hack
def inventory_balance_str(self): def inventory_balance_str(self):
# return self.currency.str(self.inventory_balance()) # return self.currency.str(self.inventory_balance())
return "XXX" return "XXX"
@ -205,102 +214,52 @@ class Shop:
def account_list(self, acctype, like_str="%%"): def account_list(self, acctype, like_str="%%"):
"""list all accounts (people or items, as per acctype)""" """list all accounts (people or items, as per acctype)"""
accts = [] accts = []
cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s AND name ILIKE %s ORDER BY name ASC", [acctype, like_str]) cur = self.db.execute_and_fetchall(
#FIXME: sanitize input like_str ^ "SELECT id FROM accounts WHERE acctype = %s AND name ILIKE %s ORDER BY name ASC",
[acctype, like_str],
)
# FIXME: sanitize input like_str ^
for inventory in cur: for inventory in cur:
accts += [ Account.load(self.db, id = inventory[0]) ] accts += [Account.load(self.db, id=inventory[0])]
return accts return accts
def fix_inventory(self, item, amount): def fix_inventory(self, item, amount):
rv = self.db.execute_and_fetch( rv = self.db.execute_and_fetch(
"SELECT public.fix_inventory(%s, %s, %s, %s, %s, %s)", "SELECT public.fix_inventory(%s, %s, %s, %s, %s, %s)",
[item.id, item.currency.id, self.excess.id, self.deficit.id, self.currency.id, amount] [
item.id,
item.currency.id,
self.excess.id,
self.deficit.id,
self.currency.id,
amount,
],
)[0] )[0]
self.db.commit() self.db.commit()
return rv return rv
#amount_in_reality = amount
#amount_in_system = item.balance()
#(buy, sell) = item.currency.rates(self.currency)
#diff = abs(amount_in_reality - amount_in_system)
#buy_total = buy * diff
#if amount_in_reality > amount_in_system:
# transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality))
# item.debit(transaction, diff, "Inventory fix excess")
# self.excess.credit(transaction, buy_total, "Inventory fix excess " + item.name)
# self.db.commit()
# return True
#elif amount_in_reality < amount_in_system:
# transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality))
# item.credit(transaction, diff, "Inventory fix deficit")
# self.deficit.debit(transaction, buy_total, "Inventory fix deficit " + item.name)
# self.db.commit()
# return True
#else:
# transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality))
# item.debit(transaction, 0, "Inventory fix - amount was correct")
# item.credit(transaction, 0, "Inventory fix - amount was correct")
# self.db.commit()
# return False
def fix_cash(self, amount): def fix_cash(self, amount):
rv = self.db.execute_and_fetch( rv = self.db.execute_and_fetch(
"SELECT public.fix_cash(%s, %s, %s, %s)", "SELECT public.fix_cash(%s, %s, %s, %s)",
[self.excess.id, self.deficit.id, self.currency.id, amount] [self.excess.id, self.deficit.id, self.currency.id, amount],
)[0] )[0]
self.db.commit() self.db.commit()
return rv return rv
#amount_in_reality = amount
#amount_in_system = self.cash.balance()
#diff = abs(amount_in_reality - amount_in_system)
#if amount_in_reality > amount_in_system:
# transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality))
# self.cash.debit(transaction, diff, "Inventory fix excess")
# self.excess.credit(transaction, diff, "Inventory cash fix excess.")
# self.db.commit()
# return True
#elif amount_in_reality < amount_in_system:
# transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality))
# self.cash.credit(transaction, diff, "Inventory fix deficit")
# self.deficit.debit(transaction, diff, "Inventory fix deficit.")
# self.db.commit()
# return True
#else:
# return False
def consolidate(self): def consolidate(self):
msg = self.db.execute_and_fetch( msg = self.db.execute_and_fetch(
"SELECT public.make_consolidate_transaction(%s, %s, %s)", "SELECT public.make_consolidate_transaction(%s, %s, %s)",
[self.excess.id, self.deficit.id, self.profits.id] [self.excess.id, self.deficit.id, self.profits.id],
)[0] )[0]
#transaction = self._transaction(description = "BrmBar inventory consolidation")
#excess_balance = self.excess.balance()
#if excess_balance != 0:
# print("Excess balance {} debited to profit".format(-excess_balance))
# self.excess.debit(transaction, -excess_balance, "Excess balance added to profit.")
# self.profits.debit(transaction, -excess_balance, "Excess balance added to profit.")
#deficit_balance = self.deficit.balance()
#if deficit_balance != 0:
# print("Deficit balance {} credited to profit".format(deficit_balance))
# self.deficit.credit(transaction, deficit_balance, "Deficit balance removed from profit.")
# self.profits.credit(transaction, deficit_balance, "Deficit balance removed from profit.")
if msg != None: if msg != None:
print(msg) print(msg)
self.db.commit() self.db.commit()
def undo(self, oldtid): def undo(self, oldtid):
#description = self.db.execute_and_fetch("SELECT description FROM transactions WHERE id = %s", [oldtid])[0] transaction = self.db.execute_and_fetch(
#description = 'undo %d (%s)' % (oldtid, description) "SELECT public.undo_transaction(%s)", [oldtid]
)[0]
#transaction = self._transaction(description=description)
#for split in self.db.execute_and_fetchall("SELECT id, side, account, amount, memo FROM transaction_splits WHERE transaction = %s", [oldtid]):
# splitid, side, account, amount, memo = split
# memo = 'undo %d (%s)' % (splitid, memo)
# amount = -amount
# self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, account, amount, memo])
transaction = self.db.execute_and_fetch("SELECT public.undo_transaction(%s)",[oldtid])[0]
self.db.commit() self.db.commit()
return transaction return transaction