forked from brmlab/brmbar-github
Compare commits
No commits in common. "master" and "sql-refactor" have entirely different histories.
master
...
sql-refact
35 changed files with 260 additions and 2604 deletions
|
|
@ -4,7 +4,6 @@ import argparse
|
|||
import brmbar
|
||||
import math
|
||||
from brmbar import Database
|
||||
import sys
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(usage = "File format: EAN amount total_price name, e.g. 4001242002377 6 167.40 Chio Tortillas")
|
||||
|
|
@ -39,7 +38,5 @@ def main():
|
|||
print("Total is {}".format(total))
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("!!! THIS PROGRAM NO LONGER WORKS !!!")
|
||||
sys.exit(1)
|
||||
main()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ from brmbar import Database
|
|||
|
||||
import brmbar
|
||||
|
||||
print("!!! THIS PROGRAM NO LONGER WORKS !!!")
|
||||
sys.exit(1)
|
||||
|
||||
def help():
|
||||
print("""BrmBar v3 (c) Petr Baudis <pasky@ucw.cz> 2012-2013
|
||||
|
|
|
|||
|
|
@ -9,20 +9,6 @@ from brmbar import Database
|
|||
|
||||
import brmbar
|
||||
|
||||
import argparse
|
||||
|
||||
import logging
|
||||
|
||||
root = logging.getLogger()
|
||||
root.setLevel(logging.DEBUG)
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
root.addHandler(handler)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# User credit balance limit; sale will fail when balance is below this limit.
|
||||
LIMIT_BALANCE = -200
|
||||
# When below this credit balance, an alert hook script (see below) is run.
|
||||
|
|
@ -67,9 +53,7 @@ class ShopAdapter(QtCore.QObject):
|
|||
|
||||
def acct_map(self, acct):
|
||||
if acct is None:
|
||||
logger.debug("acct_map: acct is None")
|
||||
return None
|
||||
logger.debug("acct_map: acct.acctype=%s", acct.acctype)
|
||||
if acct.acctype == 'debt':
|
||||
return self.acct_debt_map(acct)
|
||||
elif acct.acctype == "inventory":
|
||||
|
|
@ -88,14 +72,12 @@ class ShopAdapter(QtCore.QObject):
|
|||
Therefore, we construct a map that we can pass around easily.
|
||||
We return None on unrecognized barcode. """
|
||||
barcode = str(barcode)
|
||||
logger.debug("barcodeInput: barcode='%s'", barcode)
|
||||
if barcode and barcode[0] == "$":
|
||||
credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000}
|
||||
credit = credits[barcode]
|
||||
if credit is None:
|
||||
return None
|
||||
return { "acctype": "recharge", "amount": str(credit)+".00" }
|
||||
logger.debug("barcodeInput: before load_by_barcode")
|
||||
acct = self.acct_map(brmbar.Account.load_by_barcode(db, barcode))
|
||||
db.commit()
|
||||
return acct
|
||||
|
|
@ -150,16 +132,11 @@ class ShopAdapter(QtCore.QObject):
|
|||
|
||||
@QtCore.Slot('QVariant', 'QVariant', 'QVariant', result='QVariant')
|
||||
def newTransfer(self, uidfrom, uidto, amount):
|
||||
logger.debug("newTransfer %s %s %s", uidfrom, uidto, amount)
|
||||
ufrom = brmbar.Account.load(db, id=uidfrom)
|
||||
logger.debug(" ufrom = %s", ufrom)
|
||||
uto = brmbar.Account.load(db, id=uidto)
|
||||
logger.debug(" uto = %s", uto)
|
||||
shop.transfer_credit(ufrom, uto, amount = amount)
|
||||
db.commit()
|
||||
csfa = currency.str(float(amount))
|
||||
logger.debug(" csfa = '%s'", csfa)
|
||||
return csfa
|
||||
return currency.str(float(amount))
|
||||
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def balance_user(self, userid):
|
||||
|
|
@ -168,25 +145,21 @@ class ShopAdapter(QtCore.QObject):
|
|||
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_cash(self):
|
||||
return "N/A"
|
||||
balance = shop.cash.balance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_profit(self):
|
||||
return "N/A"
|
||||
balance = shop.profits.balance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_inventory(self):
|
||||
return "N/A"
|
||||
balance = shop.inventory_balance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_credit(self):
|
||||
return "N/A"
|
||||
balance = shop.credit_negbalance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
|
|
@ -251,23 +224,7 @@ class ShopAdapter(QtCore.QObject):
|
|||
db.commit()
|
||||
return balance
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dbname", help="Database name", type=str)
|
||||
parser.add_argument("--dbuser", help="Database user", type=str)
|
||||
parser.add_argument("--dbhost", help="Database host", type=str)
|
||||
parser.add_argument("--dbpass", help="Database user password", type=str)
|
||||
args = parser.parse_args()
|
||||
argdbname = args.dbname
|
||||
argdbuser = args.dbuser
|
||||
argdbhost = args.dbhost
|
||||
argdbpass = args.dbpass
|
||||
|
||||
db = Database.Database(
|
||||
"dbname={0} user={1} host={2} password={3}".format(
|
||||
argdbname,argdbuser,argdbhost,argdbpass
|
||||
)
|
||||
)
|
||||
|
||||
db = Database.Database("dbname=brmbar")
|
||||
shop = brmbar.Shop.new_with_defaults(db)
|
||||
currency = shop.currency
|
||||
db.commit()
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ from brmbar import Database
|
|||
|
||||
import brmbar
|
||||
|
||||
print("!!! THIS PROGRAM NO LONGER WORKS !!!")
|
||||
sys.exit(1)
|
||||
|
||||
db = Database.Database("dbname=brmbar")
|
||||
shop = brmbar.Shop.new_with_defaults(db)
|
||||
currency = shop.currency
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ from brmbar import Database
|
|||
|
||||
import brmbar
|
||||
|
||||
print("!!! THIS PROGRAM NO LONGER WORKS !!!")
|
||||
sys.exit(1)
|
||||
|
||||
from flask import *
|
||||
app = Flask(__name__)
|
||||
#app.debug = True
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
from .Currency import Currency
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Account:
|
||||
"""BrmBar Account
|
||||
""" BrmBar Account
|
||||
|
||||
Both users and items are accounts. So is the money box, etc.
|
||||
Each account has a currency."""
|
||||
|
||||
def __init__(self, db, id, name, currency, acctype):
|
||||
self.db = db
|
||||
self.id = id
|
||||
|
|
@ -19,35 +14,42 @@ class Account:
|
|||
|
||||
@classmethod
|
||||
def load_by_barcode(cls, db, barcode):
|
||||
logger.debug("load_by_barcode: '%s'", barcode)
|
||||
account_id, account_name, account_acctype, currency_id, currency_name = db.execute_and_fetch(
|
||||
"SELECT account_id, account_name, account_acctype, currency_id, currency_name FROM public.account_class_initialization_data('by_barcode', NULL, %s)",
|
||||
[barcode])
|
||||
currency = Currency(db, currency_id, currency_name)
|
||||
return cls(db, account_id, account_name, currency, account_acctype)
|
||||
res = db.execute_and_fetch("SELECT account FROM barcodes WHERE barcode = %s", [barcode])
|
||||
if res is None:
|
||||
return None
|
||||
id = res[0]
|
||||
return cls.load(db, id = id)
|
||||
|
||||
@classmethod
|
||||
def load(cls, db, id=None):
|
||||
"""Constructor for existing account"""
|
||||
account_id, account_name, account_acctype, currency_id, currency_name = db.execute_and_fetch(
|
||||
"SELECT account_id, account_name, account_acctype, currency_id, currency_name FROM public.account_class_initialization_data('by_id', %s, NULL)",
|
||||
[id])
|
||||
currency = Currency(db, currency_id, currency_name)
|
||||
return cls(db, account_id, account_name, currency, account_acctype)
|
||||
def load(cls, db, id = None, name = None):
|
||||
""" Constructor for existing account """
|
||||
if id is not None:
|
||||
name = db.execute_and_fetch("SELECT name FROM accounts WHERE id = %s", [id])
|
||||
name = name[0]
|
||||
elif name is not None:
|
||||
id = db.execute_and_fetch("SELECT id FROM accounts WHERE name = %s", [name])
|
||||
id = id[0]
|
||||
else:
|
||||
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
|
||||
def create(cls, db, name, currency, acctype):
|
||||
"""Constructor for new account"""
|
||||
id = db.execute_and_fetch(
|
||||
"SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype]
|
||||
)
|
||||
return cls(db, name=name, id=id, currency=currency, acctype=acctype)
|
||||
""" 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 = id[0]
|
||||
return cls(db, name = name, id = id, currency = currency, acctype = acctype)
|
||||
|
||||
def balance(self):
|
||||
bal = self.db.execute_and_fetch(
|
||||
"SELECT public.compute_account_balance(%s)", [self.id]
|
||||
)[0]
|
||||
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):
|
||||
return self.currency.str(self.balance())
|
||||
|
|
@ -55,12 +57,20 @@ class Account:
|
|||
def negbalance_str(self):
|
||||
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):
|
||||
self.db.execute(
|
||||
"SELECT public.add_barcode_to_account(%s, %s)", [self.id, barcode]
|
||||
)
|
||||
self.db.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode])
|
||||
self.db.commit()
|
||||
|
||||
def rename(self, name):
|
||||
self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name])
|
||||
self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
|
||||
self.name = name
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
# vim: set fileencoding=utf8
|
||||
|
||||
|
||||
class Currency:
|
||||
"""Currency
|
||||
|
||||
""" Currency
|
||||
|
||||
Each account has a currency (1 Kč, 1 Club Maté, ...), pairs of
|
||||
currencies have (asymmetric) exchange rates."""
|
||||
|
||||
currencies have (asymmetric) exchange rates. """
|
||||
def __init__(self, db, id, name):
|
||||
self.db = db
|
||||
self.id = id
|
||||
|
|
@ -14,70 +12,63 @@ class Currency:
|
|||
|
||||
@classmethod
|
||||
def default(cls, db):
|
||||
"""Default wallet currency"""
|
||||
return cls.load(db, name="Kč")
|
||||
""" Default wallet currency """
|
||||
return cls.load(db, name = "Kč")
|
||||
|
||||
@classmethod
|
||||
def load(cls, db, id=None):
|
||||
"""Constructor for existing currency"""
|
||||
if id is None:
|
||||
raise NameError("Currency.load(): Specify id")
|
||||
name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id])
|
||||
name = name[0]
|
||||
return cls(db, id=id, name=name)
|
||||
def load(cls, db, id = None, name = None):
|
||||
""" Constructor for existing currency """
|
||||
if id is not None:
|
||||
name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id])
|
||||
name = name[0]
|
||||
elif name is not None:
|
||||
id = db.execute_and_fetch("SELECT id FROM currencies WHERE name = %s", [name])
|
||||
id = id[0]
|
||||
else:
|
||||
raise NameError("Currency.load(): Specify either id or name")
|
||||
return cls(db, name = name, id = id)
|
||||
|
||||
@classmethod
|
||||
def create(cls, db, name):
|
||||
"""Constructor for new currency"""
|
||||
id = db.execute_and_fetch("SELECT public.create_currency(%s)", [name])
|
||||
return cls(db, id=id, name=name)
|
||||
""" Constructor for new currency """
|
||||
id = db.execute_and_fetch("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name])
|
||||
id = id[0]
|
||||
return cls(db, name = name, id = id)
|
||||
|
||||
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)
|
||||
$sell is the price of $self in means of $other when selling it (from brmbar)"""
|
||||
# buy rate
|
||||
res = self.db.execute_and_fetch(
|
||||
"SELECT public.find_buy_rate(%s, %s)", [self.id, other.id]
|
||||
)
|
||||
$sell is the price of $self in means of $other when selling it (from brmbar) """
|
||||
|
||||
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:
|
||||
raise NameError("Something fishy in find_buy_rate.")
|
||||
buy = res[0]
|
||||
if buy < 0:
|
||||
raise NameError(
|
||||
"Currency.rate(): Unknown conversion "
|
||||
+ other.name()
|
||||
+ " to "
|
||||
+ self.name()
|
||||
)
|
||||
# sell rate
|
||||
res = self.db.execute_and_fetch(
|
||||
"SELECT public.find_sell_rate(%s, %s)", [self.id, other.id]
|
||||
)
|
||||
raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name())
|
||||
buy_rate, buy_rate_dir = res
|
||||
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])
|
||||
if res is None:
|
||||
raise NameError("Something fishy in find_sell_rate.")
|
||||
sell = res[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())
|
||||
sell_rate, sell_rate_dir = res
|
||||
sell = sell_rate if sell_rate_dir == "source_to_target" else 1/sell_rate
|
||||
|
||||
return (buy, sell)
|
||||
|
||||
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])
|
||||
if res is None:
|
||||
raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + target.name())
|
||||
rate, rate_dir = res
|
||||
if rate_dir == "source_to_target":
|
||||
resamount = amount * rate
|
||||
else:
|
||||
resamount = amount / rate
|
||||
return resamount
|
||||
|
||||
def str(self, amount):
|
||||
return "{:.2f} {}".format(amount, self.name)
|
||||
|
||||
def update_sell_rate(self, target, rate):
|
||||
self.db.execute(
|
||||
"SELECT public.update_currency_sell_rate(%s, %s, %s)",
|
||||
[self.id, target.id, 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"])
|
||||
def update_buy_rate(self, source, rate):
|
||||
self.db.execute(
|
||||
"SELECT public.update_currency_buy_rate(%s, %s, %s)",
|
||||
[self.id, source.id, 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"])
|
||||
|
|
|
|||
|
|
@ -3,30 +3,25 @@
|
|||
import psycopg2
|
||||
from contextlib import closing
|
||||
import time
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Database:
|
||||
"""self-reconnecting database object"""
|
||||
|
||||
def __init__(self, dsn):
|
||||
self.db_conn = psycopg2.connect(dsn)
|
||||
self.dsn = dsn
|
||||
|
||||
def execute(self, query, attrs=None):
|
||||
def execute(self, query, attrs = None):
|
||||
"""execute a query and return one result"""
|
||||
with closing(self.db_conn.cursor()) as cur:
|
||||
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"""
|
||||
with closing(self.db_conn.cursor()) as cur:
|
||||
cur = self._execute(cur, query, attrs)
|
||||
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"""
|
||||
with closing(self.db_conn.cursor()) as cur:
|
||||
cur = self._execute(cur, query, attrs)
|
||||
|
|
@ -35,39 +30,31 @@ class Database:
|
|||
def _execute(self, cur, query, attrs, level=1):
|
||||
"""execute a query, and in case of OperationalError (db restart)
|
||||
reconnect to database. Recurses with increasig pause between tries"""
|
||||
logger.debug("SQL: (%s) @%s" % (query, time.strftime("%Y%m%d %a %I:%m %p")))
|
||||
try:
|
||||
if attrs is None:
|
||||
cur.execute(query)
|
||||
else:
|
||||
cur.execute(query, attrs)
|
||||
return cur
|
||||
except (
|
||||
psycopg2.DataError
|
||||
) 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"))
|
||||
)
|
||||
except psycopg2.DataError as error: # when biitr comes and enters '99999999999999999999' for amount
|
||||
print("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()
|
||||
raise RuntimeError("Unsanitized data entered again... BOBBY TABLES")
|
||||
except psycopg2.OperationalError as error:
|
||||
logger.debug(
|
||||
"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)
|
||||
print("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)
|
||||
try:
|
||||
self.db_conn = psycopg2.connect(self.dsn)
|
||||
except psycopg2.OperationalError:
|
||||
# TODO: emit message "psql not running to interface
|
||||
#TODO: emit message "psql not running to interface
|
||||
time.sleep(1)
|
||||
cur = self.db_conn.cursor() # how ugly is this?
|
||||
return self._execute(cur, query, attrs, level + 1)
|
||||
except Exception as ex:
|
||||
logger.debug("_execute exception: %s", ex)
|
||||
self.db_conn.rollback()
|
||||
cur = self.db_conn.cursor() #how ugly is this?
|
||||
return self._execute(cur, query, attrs, level+1)
|
||||
|
||||
def commit(self):
|
||||
"""passes commit to db"""
|
||||
|
|
|
|||
|
|
@ -1,164 +1,108 @@
|
|||
import brmbar
|
||||
from .Currency import Currency
|
||||
from .Account import Account
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Shop:
|
||||
"""BrmBar Shop
|
||||
""" BrmBar Shop
|
||||
|
||||
Business logic so that only interaction is left in the hands
|
||||
of the frontend scripts."""
|
||||
|
||||
def __init__(self, db, currency_id, profits_id, cash_id, excess_id, deficit_id):
|
||||
# Keep db as-is
|
||||
of the frontend scripts. """
|
||||
def __init__(self, db, currency, profits, cash, excess, deficit):
|
||||
self.db = db
|
||||
|
||||
# Store all ids
|
||||
self.currency_id = currency_id
|
||||
self.profits_id = profits_id
|
||||
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)
|
||||
self.currency = currency # brmbar.Currency
|
||||
self.profits = profits # income brmbar.Account for brmbar profit margins on items
|
||||
self.cash = cash # our operational ("wallet") cash account
|
||||
self.excess = excess # account from which is deducted cash during inventory item fixing (when system contains less items than is the reality)
|
||||
self.deficit = deficit # account where is put cash during inventory item fixing (when system contains more items than is the reality)
|
||||
|
||||
@classmethod
|
||||
def new_with_defaults(cls, db):
|
||||
# shop_class_initialization_data
|
||||
currency_id, profits_id, cash_id, excess_id, deficit_id = db.execute_and_fetch(
|
||||
"select currency_id, profits_id, cash_id, excess_id, deficit_id from public.shop_class_initialization_data()"
|
||||
)
|
||||
return cls(
|
||||
db,
|
||||
currency_id=currency_id,
|
||||
profits_id=profits_id,
|
||||
cash_id=cash_id,
|
||||
excess_id=excess_id,
|
||||
deficit_id=deficit_id,
|
||||
)
|
||||
return cls(db,
|
||||
currency = Currency.default(db),
|
||||
profits = Account.load(db, name = "BrmBar Profits"),
|
||||
cash = Account.load(db, name = "BrmBar Cash"),
|
||||
excess = Account.load(db, name = "BrmBar Excess"),
|
||||
deficit = Account.load(db, name = "BrmBar Deficit"))
|
||||
|
||||
def sell(self, item, user, amount=1):
|
||||
# Call the stored procedure for the sale
|
||||
logger.debug(
|
||||
"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(
|
||||
"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),
|
||||
],
|
||||
)
|
||||
logger.debug("sell: res[0]=%s", res[0])
|
||||
cost = res[0]
|
||||
def sell(self, item, user, amount = 1):
|
||||
# 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))
|
||||
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(
|
||||
"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),
|
||||
],
|
||||
)[0]
|
||||
def sell_for_cash(self, item, amount = 1):
|
||||
# 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))
|
||||
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):
|
||||
# Call the stored procedure for undoing a sale
|
||||
cost = self.db.execute_and_fetch(
|
||||
"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]
|
||||
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)
|
||||
self.db.commit()
|
||||
|
||||
return cost
|
||||
|
||||
def add_credit(self, credit, user):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.add_credit(%s, %s, %s, %s)",
|
||||
[self.cash.id, credit, user.id, user.name],
|
||||
)
|
||||
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):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.withdraw_credit(%s, %s, %s, %s)",
|
||||
[self.cash.id, credit, user.id, user.name],
|
||||
)
|
||||
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):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.transfer_credit(%s, %s, %s, %s, %s, %s)",
|
||||
[self.cash.id, amount, userfrom.id, userfrom.name, userto.id, userto.name],
|
||||
)
|
||||
self.db.commit()
|
||||
self.add_credit(amount, userto)
|
||||
self.withdraw_credit(amount, userfrom)
|
||||
|
||||
def buy_for_cash(self, item, amount=1):
|
||||
iamount = int(amount)
|
||||
famount = float(iamount)
|
||||
assert famount == amount, "amount is not integer value %s".format(amount)
|
||||
cost = self.db.execute_and_fetch(
|
||||
"SELECT public.buy_for_cash(%s, %s, %s, %s, %s)",
|
||||
[self.cash.id, item.id, iamount, self.currency.id, item.name],
|
||||
)[0]
|
||||
def buy_for_cash(self, item, amount = 1):
|
||||
# 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()
|
||||
|
||||
return cost
|
||||
|
||||
def receipt_to_credit(self, user, credit, description):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.receipt_reimbursement(%s, %s, %s, %s, %s)",
|
||||
[self.profits.id, user.id, user.name, credit, description],
|
||||
)[0]
|
||||
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.commit()
|
||||
|
||||
def _transaction(self, responsible=None, description=None):
|
||||
transaction = self.db.execute_and_fetch(
|
||||
"INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id",
|
||||
[responsible.id if responsible else None, description],
|
||||
)
|
||||
def _transaction(self, responsible = None, description = None):
|
||||
transaction = self.db.execute_and_fetch("INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id",
|
||||
[responsible.id if responsible else None, description])
|
||||
transaction = transaction[0]
|
||||
return transaction
|
||||
|
||||
|
|
@ -171,79 +115,119 @@ class Shop:
|
|||
WHERE a.acctype = %s AND ts.side = %s
|
||||
"""
|
||||
if overflow is not None:
|
||||
sumselect += (
|
||||
" AND a.name "
|
||||
+ ("NOT " if overflow == "exclude" else "")
|
||||
+ " LIKE '%%-overflow'"
|
||||
)
|
||||
cur = self.db.execute_and_fetch(sumselect, ["debt", "debit"])
|
||||
sumselect += ' AND a.name ' + ('NOT ' if overflow == 'exclude' else '') + ' LIKE \'%%-overflow\''
|
||||
cur = self.db.execute_and_fetch(sumselect, ["debt", 'debit'])
|
||||
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
|
||||
return debit - credit
|
||||
|
||||
def credit_negbalance_str(self, overflow=None):
|
||||
return self.currency.str(-self.credit_balance(overflow=overflow))
|
||||
|
||||
# XXX causing extra heavy delay ( thousands of extra SQL queries ), disabled
|
||||
def inventory_balance(self):
|
||||
resa = self.db.execute_and_fetch("SELECT * FROM public.inventory_balance()")
|
||||
res = resa[0]
|
||||
logger.debug("inventory_balance resa = %s", resa)
|
||||
return res
|
||||
balance = 0
|
||||
# Each inventory account has its own currency,
|
||||
# so we just do this ugly iteration
|
||||
cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s", ["inventory"])
|
||||
for inventory in cur:
|
||||
invid = inventory[0]
|
||||
inv = Account.load(self.db, id = invid)
|
||||
# FIXME: This is not correct as each instance of inventory
|
||||
# might have been bought for a different price! Therefore,
|
||||
# we need to replace the command below with a complex SQL
|
||||
# statement that will... ugh, accounting is hard!
|
||||
b = inv.balance() * inv.currency.rates(self.currency)[0]
|
||||
# if b != 0:
|
||||
# print(str(b) + ',' + inv.name)
|
||||
balance += b
|
||||
return balance
|
||||
|
||||
# XXX bypass hack
|
||||
def inventory_balance_str(self):
|
||||
return self.currency.str(self.inventory_balance())
|
||||
# return self.currency.str(self.inventory_balance())
|
||||
return "XXX"
|
||||
|
||||
def account_list(self, acctype, like_str="%%"):
|
||||
"""list all accounts (people or items, as per acctype)"""
|
||||
accts = []
|
||||
cur = self.db.execute_and_fetchall(
|
||||
"SELECT a.id, a.name aname, a.currency, a.acctype, c.name cname FROM accounts a JOIN currencies c ON c.id=a.currency WHERE a.acctype = %s AND a.name ILIKE %s ORDER BY a.name ASC",
|
||||
[acctype, like_str],
|
||||
)
|
||||
# FIXME: sanitize input like_str ^
|
||||
cur = self.db.execute_and_fetchall("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:
|
||||
curr = Currency(db=self.db, id=inventory[2], name=inventory[4]);
|
||||
accts += [Account(self.db, id=inventory[0], name=inventory[1], currency=curr, acctype=inventory[3])]
|
||||
accts += [ Account.load(self.db, id = inventory[0]) ]
|
||||
return accts
|
||||
|
||||
def fix_inventory(self, item, amount):
|
||||
rv = self.db.execute_and_fetch(
|
||||
"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,
|
||||
],
|
||||
)[0]
|
||||
amount_in_reality = amount
|
||||
amount_in_system = item.balance()
|
||||
(buy, sell) = item.currency.rates(self.currency)
|
||||
|
||||
self.db.commit()
|
||||
return rv
|
||||
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):
|
||||
rv = self.db.execute_and_fetch(
|
||||
"SELECT public.fix_cash(%s, %s, %s, %s)",
|
||||
[self.excess.id, self.deficit.id, self.currency.id, amount],
|
||||
)[0]
|
||||
amount_in_reality = amount
|
||||
amount_in_system = self.cash.balance()
|
||||
|
||||
self.db.commit()
|
||||
return rv
|
||||
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):
|
||||
msg = self.db.execute_and_fetch(
|
||||
"SELECT public.make_consolidate_transaction(%s, %s, %s)",
|
||||
[self.excess.id, self.deficit.id, self.profits.id],
|
||||
)[0]
|
||||
if msg != None:
|
||||
print(msg)
|
||||
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.")
|
||||
self.db.commit()
|
||||
|
||||
def undo(self, oldtid):
|
||||
transaction = self.db.execute_and_fetch(
|
||||
"SELECT public.undo_transaction(%s)", [oldtid]
|
||||
)[0]
|
||||
#description = self.db.execute_and_fetch("SELECT description FROM transactions WHERE id = %s", [oldtid])[0]
|
||||
#description = 'undo %d (%s)' % (oldtid, description)
|
||||
|
||||
#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("CALL undo_transaction(%s)",[oldtid])[0]
|
||||
self.db.commit()
|
||||
return transaction
|
||||
|
|
|
|||
|
|
@ -1,313 +0,0 @@
|
|||
--
|
||||
-- 0001-init.sql
|
||||
--
|
||||
-- Initial SQL schema construction as of 2025-04-20 (or so)
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
-- Privileged schema with protected data
|
||||
CREATE SCHEMA IF NOT EXISTS brmbar_privileged;
|
||||
|
||||
-- Initial versioning
|
||||
CREATE TABLE IF NOT EXISTS brmbar_privileged.brmbar_schema(
|
||||
ver INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- Legacy Schema Initialization
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
DO $$
|
||||
DECLARE v INTEGER;
|
||||
BEGIN
|
||||
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v;
|
||||
IF v IS NULL THEN
|
||||
-- --------------------------------
|
||||
-- Legacy Types
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='exchange_rate_direction';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type exchange_rate_direction';
|
||||
CREATE TYPE public.exchange_rate_direction
|
||||
AS ENUM ('source_to_target', 'target_to_source');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type exchange_rate_direction already exists';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='account_type';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type account_type';
|
||||
CREATE TYPE public.account_type
|
||||
AS ENUM ('cash', 'debt', 'inventory', 'income', 'expense',
|
||||
'starting_balance', 'ending_balance');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type account_type already exists';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='transaction_split_side';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type transaction_split_side';
|
||||
CREATE TYPE public.transaction_split_side
|
||||
AS ENUM ('credit', 'debit');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type transaction_split_side already exists';
|
||||
END IF;
|
||||
|
||||
-- --------------------------------
|
||||
-- Currencies sequence, table and potential initial data
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.currencies_id_seq
|
||||
START WITH 2 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.currencies (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.currencies_id_seq'::regclass),
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
INSERT INTO public.currencies (id, name) VALUES (1, 'Kč')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Exchange rates table - no initial data required
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.exchange_rates (
|
||||
valid_since TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
|
||||
target INTEGER NOT NULL,
|
||||
FOREIGN KEY (target) REFERENCES public.currencies (id),
|
||||
|
||||
source INTEGER NOT NULL,
|
||||
FOREIGN KEY (source) REFERENCES public.currencies (id),
|
||||
|
||||
rate DECIMAL(12,2) NOT NULL,
|
||||
rate_dir public.exchange_rate_direction NOT NULL
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Accounts sequence and table and 4 initial accounts
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.accounts_id_seq
|
||||
START WITH 2 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.accounts (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.accounts_id_seq'::regclass),
|
||||
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE (name),
|
||||
|
||||
currency INTEGER NOT NULL,
|
||||
FOREIGN KEY (currency) REFERENCES public.currencies (id),
|
||||
|
||||
acctype public.account_type NOT NULL,
|
||||
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
INSERT INTO public.accounts (id, name, currency, acctype)
|
||||
VALUES (1, 'BrmBar Cash', (SELECT id FROM public.currencies WHERE name='Kč'), 'cash')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Profits', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Excess', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Deficit', (SELECT id FROM public.currencies WHERE name='Kč'), 'expense')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Barcodes
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.barcodes (
|
||||
barcode VARCHAR(128) PRIMARY KEY NOT NULL,
|
||||
|
||||
account INTEGER NOT NULL,
|
||||
FOREIGN KEY (account) REFERENCES public.accounts (id)
|
||||
);
|
||||
INSERT INTO public.barcodes (barcode, account)
|
||||
VALUES ('_cash_', (SELECT id FROM public.accounts WHERE acctype = 'cash'))
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Transactions
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.transactions_id_seq
|
||||
START WITH 1 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.transactions (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transactions_id_seq'::regclass),
|
||||
time TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
|
||||
responsible INTEGER,
|
||||
FOREIGN KEY (responsible) REFERENCES public.accounts (id),
|
||||
|
||||
description TEXT
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction splits
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.transaction_splits_id_seq
|
||||
START WITH 1 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.transaction_splits (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transaction_splits_id_seq'::regclass),
|
||||
|
||||
transaction INTEGER NOT NULL,
|
||||
FOREIGN KEY (transaction) REFERENCES public.transactions (id),
|
||||
|
||||
side public.transaction_split_side NOT NULL,
|
||||
|
||||
account INTEGER NOT NULL,
|
||||
FOREIGN KEY (account) REFERENCES public.accounts (id),
|
||||
amount DECIMAL(12,2) NOT NULL,
|
||||
|
||||
memo TEXT
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Account balances view
|
||||
|
||||
CREATE OR REPLACE VIEW public.account_balances AS
|
||||
SELECT ts.account AS id,
|
||||
accounts.name,
|
||||
accounts.acctype,
|
||||
- sum(
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END) AS crbalance
|
||||
FROM public.transaction_splits ts
|
||||
LEFT JOIN public.accounts ON accounts.id = ts.account
|
||||
GROUP BY ts.account, accounts.name, accounts.acctype
|
||||
ORDER BY (- sum(
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END));
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction nice splits view
|
||||
|
||||
CREATE OR REPLACE VIEW public.transaction_nicesplits AS
|
||||
SELECT ts.id,
|
||||
ts.transaction,
|
||||
ts.account,
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END AS amount,
|
||||
a.currency,
|
||||
ts.memo
|
||||
FROM public.transaction_splits ts
|
||||
LEFT JOIN public.accounts a ON a.id = ts.account
|
||||
ORDER BY ts.id;
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction cash sums view
|
||||
|
||||
CREATE OR REPLACE VIEW public.transaction_cashsums AS
|
||||
SELECT t.id,
|
||||
t."time",
|
||||
sum(credit.credit_cash) AS cash_credit,
|
||||
sum(debit.debit_cash) AS cash_debit,
|
||||
a.name AS responsible,
|
||||
t.description
|
||||
FROM public.transactions t
|
||||
LEFT JOIN ( SELECT cts.amount AS credit_cash,
|
||||
cts.transaction AS cts_t
|
||||
FROM public.transaction_nicesplits cts
|
||||
LEFT JOIN public.accounts a_1 ON a_1.id = cts.account OR a_1.id = cts.account
|
||||
WHERE a_1.currency = (( SELECT accounts.currency
|
||||
FROM public.accounts
|
||||
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||
AND cts.amount < 0::numeric) credit ON credit.cts_t = t.id
|
||||
LEFT JOIN ( SELECT dts.amount AS debit_cash,
|
||||
dts.transaction AS dts_t
|
||||
FROM public.transaction_nicesplits dts
|
||||
LEFT JOIN public.accounts a_1 ON a_1.id = dts.account OR a_1.id = dts.account
|
||||
WHERE a_1.currency = (( SELECT accounts.currency
|
||||
FROM public.accounts
|
||||
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||
AND dts.amount > 0::numeric) debit ON debit.dts_t = t.id
|
||||
LEFT JOIN public.accounts a ON a.id = t.responsible
|
||||
GROUP BY t.id, a.name
|
||||
ORDER BY t.id DESC;
|
||||
|
||||
-- --------------------------------
|
||||
-- Function to check schema version (used in migrations)
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.has_exact_schema_version(
|
||||
IN i_ver INTEGER
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT ver INTO v_ver FROM brmbar_privileged.brmbar_schema;
|
||||
IF v_ver is NULL THEN
|
||||
RETURN false;
|
||||
ELSE
|
||||
RETURN v_ver = i_ver;
|
||||
END IF;
|
||||
END;
|
||||
$x$;
|
||||
|
||||
-- --------------------------------
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.upgrade_schema_version_to(
|
||||
IN i_ver INTEGER
|
||||
) RETURNS VOID
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v_ver;
|
||||
IF v_ver=(i_ver-1) THEN
|
||||
UPDATE brmbar_privileged.brmbar_schema SET ver = i_ver;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Invalid brmbar schema version transition (% -> %)', v_ver, i_ver;
|
||||
END IF;
|
||||
END;
|
||||
$x$;
|
||||
|
||||
-- Initialize version 1
|
||||
INSERT INTO brmbar_privileged.brmbar_schema(ver) VALUES(1);
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
--
|
||||
-- 0002-trading-accounts.sql
|
||||
--
|
||||
-- #2 - add trading accounts to account type type
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(1) THEN
|
||||
|
||||
ALTER TYPE public.account_type ADD VALUE 'trading';
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(2);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
--
|
||||
-- 0003-new-account.sql
|
||||
--
|
||||
-- #3 - stored procedure for creating new account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(2) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.create_account(
|
||||
IN i_name public.accounts.name%TYPE,
|
||||
IN i_currency public.accounts.currency%TYPE,
|
||||
IN i_acctype public.accounts.acctype%TYPE
|
||||
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES (i_name, i_currency, i_acctype) RETURNING id INTO r_id;
|
||||
RETURN r_id;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(3);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
--
|
||||
-- 0004-add-account-barcode.sql
|
||||
--
|
||||
-- #4 - stored procedure for adding barcode to account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(3) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.add_barcode_to_account(
|
||||
IN i_account public.barcodes.account%TYPE,
|
||||
IN i_barcode public.barcodes.barcode%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.barcodes (account, barcode)
|
||||
VALUES (i_account, i_barcode);
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(4);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
--
|
||||
-- 0005-rename-account.sql
|
||||
--
|
||||
-- #5 - stored procedure for renaming account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(4) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rename_account(
|
||||
IN i_account public.accounts.id%TYPE,
|
||||
IN i_name public.accounts.name%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
UPDATE public.accounts
|
||||
SET name = i_name
|
||||
WHERE id = i_account;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(5);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
--
|
||||
-- 0006-new-currency.sql
|
||||
--
|
||||
-- #6 - stored procedure for creating new currency
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(5) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.create_currency(
|
||||
IN i_name public.currencies.name%TYPE
|
||||
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.currencies (name)
|
||||
VALUES (i_name) RETURNING id INTO r_id;
|
||||
RETURN r_id;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(6);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
--
|
||||
-- 0007-update-currency-sell-rate.sql
|
||||
--
|
||||
-- #7 - stored procedure for updating sell rate
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(6) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_currency_sell_rate(
|
||||
IN i_currency public.exchange_rates.source%TYPE,
|
||||
IN i_target public.exchange_rates.target%TYPE,
|
||||
IN i_rate public.exchange_rates.rate%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||
VALUES (i_currency, i_target, i_rate, 'source_to_target');
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(7);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
--
|
||||
-- 0008-update-currency-buy-rate.sql
|
||||
--
|
||||
-- #8 - stored procedure for updating buy rate
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(7) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_currency_buy_rate(
|
||||
IN i_currency public.exchange_rates.target%TYPE,
|
||||
IN i_source public.exchange_rates.source%TYPE,
|
||||
IN i_rate public.exchange_rates.rate%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||
VALUES (i_source, i_currency, i_rate, 'target_to_source');
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(8);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
--
|
||||
-- 0009-shop-sell.sql
|
||||
--
|
||||
-- #9 - stored function for sell transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(8) THEN
|
||||
|
||||
-- return negative number on rate not found
|
||||
CREATE OR REPLACE FUNCTION public.find_buy_rate(
|
||||
IN i_item_id public.accounts.id%TYPE,
|
||||
IN i_other_id public.accounts.id%TYPE
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rate public.exchange_rates.rate%TYPE;
|
||||
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||
BEGIN
|
||||
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_item_id AND source = i_other_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||
IF v_rate_dir = 'target_to_source'::public.exchange_rate_direction THEN
|
||||
RETURN v_rate;
|
||||
ELSE
|
||||
RETURN 1/v_rate;
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RETURN -1;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
-- return negative number on rate not found
|
||||
CREATE OR REPLACE FUNCTION public.find_sell_rate(
|
||||
IN i_item_id public.accounts.id%TYPE,
|
||||
IN i_other_id public.accounts.id%TYPE
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rate public.exchange_rates.rate%TYPE;
|
||||
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||
BEGIN
|
||||
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_other_id AND source = i_item_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||
IF v_rate_dir = 'source_to_target'::public.exchange_rate_direction THEN
|
||||
RETURN v_rate;
|
||||
ELSE
|
||||
RETURN 1/v_rate;
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RETURN -1;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.create_transaction(
|
||||
i_responsible_id public.accounts.id%TYPE,
|
||||
i_description public.transactions.description%TYPE
|
||||
) RETURNS public.transactions.id%TYPE AS $$
|
||||
DECLARE
|
||||
new_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
INSERT INTO public.transactions (responsible, description)
|
||||
VALUES (i_responsible_id, i_description)
|
||||
RETURNING id INTO new_transaction_id;
|
||||
-- Return the new transaction ID
|
||||
RETURN new_transaction_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.sell_item(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_sell_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_profit NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Get the buy and sell rates from the stored functions
|
||||
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||
v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_sell_rate;
|
||||
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||
|
||||
-- Create a new transaction
|
||||
v_transaction_id := public.create_transaction(i_user_id, i_description);
|
||||
|
||||
-- the item (decrease stock)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_item_id, i_amount,
|
||||
(SELECT "name" FROM public.accounts WHERE id = i_user_id));
|
||||
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_user_id, v_cost,
|
||||
(SELECT "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- the profit
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', (SELECT account_id FROM accounts WHERE name = 'BrmBar Profits'), v_profit, (SELECT 'Margin on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(9);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
--
|
||||
-- 0010-shop-sell-for-cash.sql
|
||||
--
|
||||
-- #10 - stored function for cash sell transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(9) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.create_transaction(
|
||||
i_responsible_id public.accounts.id%TYPE,
|
||||
i_description public.transactions.description%TYPE
|
||||
) RETURNS public.transactions.id%TYPE AS $$
|
||||
DECLARE
|
||||
new_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
INSERT INTO public.transactions (responsible, description)
|
||||
VALUES (i_responsible_id, i_description)
|
||||
RETURNING id INTO new_transaction_id;
|
||||
-- Return the new transaction ID
|
||||
RETURN new_transaction_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.sell_item_internal(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_other_memo TEXT,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_sell_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_profit NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
v_item_currency_id public.accounts.currency%TYPE;
|
||||
BEGIN
|
||||
-- Get item's currency
|
||||
SELECT currency
|
||||
INTO v_item_currency_id
|
||||
FROM public.accounts
|
||||
WHERE id=i_item_id;
|
||||
|
||||
-- Get the buy and sell rates from the stored functions
|
||||
v_buy_rate := public.find_buy_rate(v_item_currency_id, i_target_currency_id);
|
||||
v_sell_rate := public.find_sell_rate(v_item_currency_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_sell_rate;
|
||||
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, i_description);
|
||||
|
||||
-- the item (decrease stock)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_item_id, i_amount,
|
||||
i_other_memo);
|
||||
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_user_id, v_cost,
|
||||
(SELECT "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- the profit
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id,
|
||||
'debit',
|
||||
(SELECT id FROM public.accounts WHERE name = 'BrmBar Profits'),
|
||||
v_profit,
|
||||
(SELECT 'Margin on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.sell_item(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN brmbar_privileged.sell_item_internal(i_item_id,
|
||||
i_amount,
|
||||
i_user_id,
|
||||
i_target_currency_id,
|
||||
(SELECT "name" FROM public.accounts WHERE id = i_user_id),
|
||||
i_description);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.sell_item_for_cash(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN brmbar_privileged.sell_item_internal(i_item_id,
|
||||
i_amount,
|
||||
i_user_id,
|
||||
i_target_currency_id,
|
||||
'Cash',
|
||||
i_description);
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP FUNCTION public.create_transaction;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(10);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
--
|
||||
-- 0011-shop-undo-sale.sql
|
||||
--
|
||||
-- #11 - stored function for sale undo transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(10) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.create_transaction(
|
||||
i_responsible_id public.accounts.id%TYPE,
|
||||
i_description public.transactions.description%TYPE
|
||||
) RETURNS public.transactions.id%TYPE AS $$
|
||||
DECLARE
|
||||
new_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
INSERT INTO public.transactions (responsible, description)
|
||||
VALUES (i_responsible_id, i_description)
|
||||
RETURNING id INTO new_transaction_id;
|
||||
-- Return the new transaction ID
|
||||
RETURN new_transaction_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.undo_sale_of_item(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_sell_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_profit NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Get the buy and sell rates from the stored functions
|
||||
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||
v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_sell_rate;
|
||||
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, i_description);
|
||||
|
||||
-- the item (decrease stock)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_item_id, i_amount,
|
||||
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_user_id));
|
||||
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_user_id, v_cost,
|
||||
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- the profit
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', (SELECT account_id FROM accounts WHERE name = 'BrmBar Profits'), v_profit, (SELECT 'Margin repaid on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(11);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0012-shop-add-credit.sql
|
||||
--
|
||||
-- #12 - stored function for cash deposit transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(11) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.add_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit replenishment for ' || i_user_name);
|
||||
-- Debit cash (credit replenishment)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_cash_account_id, i_credit, i_user_name);
|
||||
-- Credit the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_user_id, i_credit, 'Credit replenishment');
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(12);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0013-shop-withdraw-credit.sql
|
||||
--
|
||||
-- #13 - stored function for cash withdrawal transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(12) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.withdraw_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit withdrawal for ' || i_user_name);
|
||||
-- Debit cash (credit replenishment)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_cash_account_id, i_credit, i_user_name);
|
||||
-- Credit the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_user_id, i_credit, 'Credit withdrawal');
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(13);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
--
|
||||
-- 0014-shop-transfer-credit.sql
|
||||
--
|
||||
-- #14 - stored function for "credit" transfer transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(13) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.transfer_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_userfrom_id public.accounts.id%TYPE,
|
||||
i_userfrom_name TEXT,
|
||||
i_userto_id public.accounts.id%TYPE,
|
||||
i_userto_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM public.add_credit(i_cash_account_id, i_credit, i_userto_id, i_userto_name);
|
||||
PERFORM public.withdraw_credit(i_cash_account_id, i_credit, i_userfrom_id, i_userfrom_name);
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(14);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
--
|
||||
-- 0015-shop-buy-for-cash.sql
|
||||
--
|
||||
-- #15 - stored function for cash-based stock replenishment transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(14) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.buy_for_cash(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_item_name TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
v_item_currency_id public.accounts.currency%TYPE;
|
||||
BEGIN
|
||||
-- Get item's currency
|
||||
SELECT currency
|
||||
INTO STRICT v_item_currency_id
|
||||
FROM public.accounts
|
||||
WHERE id=i_item_id;
|
||||
-- Get the buy rates from the stored functions
|
||||
v_buy_rate := public.find_buy_rate(v_item_currency_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_buy_rate;
|
||||
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(NULL,
|
||||
'BrmBar stock replenishment of ' || i_amount || 'x ' || i_item_name || ' for cash');
|
||||
|
||||
-- the item
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_item_id, i_amount,
|
||||
'Cash');
|
||||
|
||||
-- the cash
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_cash_account_id, v_cost,
|
||||
i_item_name);
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(15);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0016-shop-buy-for-cash.sql
|
||||
--
|
||||
-- #16 - stored function for receipt reimbursement transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(15) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.receipt_reimbursement(
|
||||
i_profits_id public.accounts.id%TYPE,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name public.accounts.name%TYPE,
|
||||
i_amount NUMERIC,
|
||||
i_description TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id,
|
||||
'Receipt: ' || i_description);
|
||||
-- the "profit"
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_profits_id, i_amount, i_user_name);
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_user_id, i_amount, 'Credit from receipt: ' || i_description);
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(16);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
--
|
||||
-- 0017-shop-fix-inventory.sql
|
||||
--
|
||||
-- #17 - stored function for "fixing" inventory transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(16) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.compute_account_balance(
|
||||
i_account_id public.accounts.id%TYPE
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_crsum NUMERIC;
|
||||
v_dbsum NUMERIC;
|
||||
BEGIN
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN side='credit' THEN amount ELSE 0 END),0) crsum,
|
||||
COALESCE(SUM(CASE WHEN side='debit' THEN amount ELSE 0 END),0) dbsum
|
||||
INTO v_crsum, v_dbsum
|
||||
FROM public.transaction_splits ts WHERE ts.account=i_account_id;
|
||||
RETURN v_dbsum - v_crsum;
|
||||
END; $$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.fix_account_balance(
|
||||
IN i_account_id public.accounts.id%TYPE,
|
||||
IN i_account_currency_id public.currencies.id%TYPE,
|
||||
IN i_excess_id public.accounts.id%TYPE,
|
||||
IN i_deficit_id public.accounts.id%TYPE,
|
||||
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||
IN i_amount_in_reality NUMERIC
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
DECLARE
|
||||
v_amount_in_system NUMERIC;
|
||||
v_buy_rate NUMERIC;
|
||||
v_currency_id public.currencies.id%TYPE;
|
||||
v_diff NUMERIC;
|
||||
v_buy_total NUMERIC;
|
||||
v_ntrn_id public.transactions.id%TYPE;
|
||||
v_transaction_memo TEXT;
|
||||
v_item_name TEXT;
|
||||
v_excess_memo TEXT;
|
||||
v_deficit_memo TEXT;
|
||||
|
||||
v_old_trn public.transactions%ROWTYPE;
|
||||
v_old_split public.transaction_splits%ROWTYPE;
|
||||
BEGIN
|
||||
v_amount_in_system := public.compute_account_balance(i_account_id);
|
||||
IF i_account_currency_id <> i_shop_currency_id THEN
|
||||
v_buy_rate := public.find_buy_rate(i_item_id, i_shop_currency_id);
|
||||
ELSE
|
||||
v_buy_rate := 1;
|
||||
END IF;
|
||||
|
||||
v_diff := ABS(i_amount_in_reality - v_amount_in_system);
|
||||
v_buy_total := v_buy_rate * v_diff;
|
||||
-- compute memo strings
|
||||
IF i_item_id = 1 THEN -- cash account recognized by magic id
|
||||
-- fixing cash
|
||||
v_transaction_memo :=
|
||||
'BrmBar cash inventory fix of ' || v_amount_in_system
|
||||
|| ' in system to ' || i_amount_in_reality || ' in reality';
|
||||
v_excess_memo := 'Inventory cash fix excess.';
|
||||
v_deficit_memo := 'Inventory fix deficit.';
|
||||
ELSE
|
||||
-- fixing other account
|
||||
SELECT "name" INTO v_item_name FROM public.accounts WHERE id = i_account_id;
|
||||
v_transaction_memo :=
|
||||
'BrmBar inventory fix of ' || v_amount_in_system || 'pcs '
|
||||
|| v_item_name
|
||||
|| ' in system to ' || i_amount_in_reality || 'pcs in reality';
|
||||
v_excess_memo := 'Inventory fix excess ' || v_item_name;
|
||||
v_deficit_memo := 'Inventory fix deficit ' || v_item_name;
|
||||
END IF;
|
||||
-- create transaction based on the relation between counting and accounting
|
||||
IF i_amount_in_reality > v_amount_in_system THEN
|
||||
v_ntrn_id := brmbar_privileged.create_transaction(NULL, v_transaction_memo);
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, 'debit', i_item_id, v_diff, 'Inventory fix excess');
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, 'credit', i_excess_id, v_buy_total, v_excess_memo);
|
||||
RETURN TRUE;
|
||||
ELSIF i_amount_in_reality < v_amount_in_system THEN
|
||||
v_ntrn_id := brmbar_privileged.create_transaction(NULL, v_transaction_memo);
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, 'credit', i_item_id, v_diff, 'Inventory fix deficit');
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, 'debit', i_deficit_id, v_buy_total, v_deficit_memo);
|
||||
RETURN TRUE;
|
||||
ELSIF i_account_id <> 1 THEN -- cash account recognized by magic id
|
||||
-- record that everything is going on swimmingly only for noncash accounts (WTF)
|
||||
v_ntrn_id := brmbar_privileged.create_transaction(NULL, v_transaction_memo);
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, 'debit', i_item_id, 0, 'Inventory fix - amount was correct');
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, 'credit', i_item_id, 0, 'Inventory fix - amount was correct');
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
RETURN FALSE;
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fix_inventory(
|
||||
IN i_account_id public.accounts.id%TYPE,
|
||||
IN i_account_currency_id public.currencies.id%TYPE,
|
||||
IN i_excess_id public.accounts.id%TYPE,
|
||||
IN i_deficit_id public.accounts.id%TYPE,
|
||||
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||
IN i_amount_in_reality NUMERIC
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
BEGIN
|
||||
RETURN brmbar_privileged.fix_account_balance(
|
||||
i_account_id,
|
||||
i_account_currency_id,
|
||||
i_excess_id,
|
||||
i_deficit_id,
|
||||
i_shop_currency_id,
|
||||
i_amount_in_reality
|
||||
);
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(17);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
--
|
||||
-- 0018-shop-fix-cash.sql
|
||||
--
|
||||
-- #18 - stored function for "fixing cash" transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(17) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fix_cash(
|
||||
IN i_excess_id public.accounts.id%TYPE,
|
||||
IN i_deficit_id public.accounts.id%TYPE,
|
||||
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||
IN i_amount_in_reality NUMERIC
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
BEGIN
|
||||
RETURN brmbar_privileged.fix_account_balance(
|
||||
1,
|
||||
1,
|
||||
i_excess_id,
|
||||
i_deficit_id,
|
||||
i_shop_currency_id,
|
||||
i_amount_in_reality
|
||||
);
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(18);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
--
|
||||
-- 0019-shop-consolidate.sql
|
||||
--
|
||||
-- #19 - stored function for "consolidation" transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(18) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.make_consolidate_transaction(
|
||||
i_excess_id public.accounts.id%TYPE,
|
||||
i_deficit_id public.accounts.id%TYPE,
|
||||
i_profits_id public.accounts.id%TYPE
|
||||
) RETURNS TEXT
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
v_excess_balance NUMERIC;
|
||||
v_deficit_balance NUMERIC;
|
||||
v_ret TEXT;
|
||||
BEGIN
|
||||
v_ret := NULL;
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(NULL,
|
||||
'BrmBar inventory consolidation');
|
||||
v_excess_balance := public.compute_account_balance(i_excess_id);
|
||||
v_deficit_balance := public.compute_account_balance(i_deficit_id);
|
||||
IF v_excess_balance <> 0 THEN
|
||||
v_ret := 'Excess balance ' || -v_excess_balance || ' debited to profit';
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_excess_id, -v_excess_balance,
|
||||
'Excess balance added to profit.');
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_profits_id, -v_excess_balance,
|
||||
'Excess balance added to profit.');
|
||||
END IF;
|
||||
IF v_deficit_balance <> 0 THEN
|
||||
v_ret := COALESCE(v_ret, '');
|
||||
v_ret := v_ret || 'Deficit balance ' || v_deficit_balance || ' credited to profit';
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_deficit_id, v_deficit_balance,
|
||||
'Deficit balance removed from profit.');
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_profits_id, v_deficit_balance,
|
||||
'Deficit balance removed from profit.');
|
||||
END IF;
|
||||
RETURN v_ret;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(19);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0020-shop-undo.sql
|
||||
--
|
||||
-- #20 - stored function for undo transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(19) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.undo_transaction(
|
||||
IN i_id public.transactions.id%TYPE)
|
||||
RETURNS public.transactions.id%TYPE
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
DECLARE
|
||||
v_ntrn_id public.transactions.id%TYPE;
|
||||
v_old_trn public.transactions%ROWTYPE;
|
||||
v_old_split public.transaction_splits%ROWTYPE;
|
||||
BEGIN
|
||||
SELECT * INTO v_old_trn FROM public.transactions WHERE id = i_id;
|
||||
INSERT INTO transactions ("description") VALUES ('undo '||i_id||' ('||v_old_trn.description||')') RETURNING id into v_ntrn_id;
|
||||
FOR v_old_split IN
|
||||
SELECT * FROM transaction_splits WHERE "transaction" = i_id
|
||||
LOOP
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, v_old_split.side, v_old_split.account, -v_old_split.amount,
|
||||
'undo ' || v_old_split.id || ' (' || v_old_split.memo || ')' );
|
||||
END LOOP;
|
||||
RETURN v_ntrn_id;
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(20);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
--
|
||||
-- 0021-constraints-on-numeric-columns.sql
|
||||
--
|
||||
-- #21 - stored function for adding constraints to numeric columns
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(20) THEN
|
||||
|
||||
ALTER TABLE public.transaction_splits ADD CONSTRAINT amount_check
|
||||
CHECK (amount NOT IN ('Infinity'::numeric, '-Infinity'::numeric, 'NaN'::numeric));
|
||||
|
||||
ALTER TABLE public.exchange_rates ADD CONSTRAINT rate_check
|
||||
CHECK (rate NOT IN ('Infinity'::numeric, '-Infinity'::numeric, 'NaN'::numeric));
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(21);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
--
|
||||
-- 0022-shop-init.sql
|
||||
--
|
||||
-- #22 - stored function for initializing Shop.py
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
DECLARE
|
||||
v INTEGER;
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(21) THEN
|
||||
|
||||
|
||||
SELECT COUNT(1) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'brmbar_privileged'
|
||||
AND typ.typname='shop_class_initialization_data_type';
|
||||
|
||||
IF v>0 THEN
|
||||
RAISE NOTICE 'Changing type shop_class_initialization_data_type';
|
||||
DROP TYPE brmbar_privileged.shop_class_initialization_data_type CASCADE;
|
||||
ELSE
|
||||
RAISE NOTICE 'Creating type shop_class_initialization_data_type';
|
||||
END IF;
|
||||
|
||||
CREATE TYPE brmbar_privileged.shop_class_initialization_data_type
|
||||
AS (
|
||||
currency_id INTEGER, --public.currencies.id%TYPE,
|
||||
profits_id INTEGER, --public.accounts.id%TYPE,
|
||||
cash_id INTEGER, --public.accounts.id%TYPE,
|
||||
excess_id INTEGER, --public.accounts.id%TYPE,
|
||||
deficit_id INTEGER --public.accounts.id%TYPE
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.shop_class_initialization_data()
|
||||
RETURNS brmbar_privileged.shop_class_initialization_data_type
|
||||
LANGUAGE plpgsql
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
rv brmbar_privileged.shop_class_initialization_data_type;
|
||||
BEGIN
|
||||
rv.currency_id := 1;
|
||||
SELECT id INTO rv.profits_id FROM public.accounts WHERE name = 'BrmBar Profits';
|
||||
SELECT id INTO rv.cash_id FROM public.accounts WHERE name = 'BrmBar Cash';
|
||||
SELECT id INTO rv.excess_id FROM public.accounts WHERE name = 'BrmBar Excess';
|
||||
SELECT id INTO rv.deficit_id FROM public.accounts WHERE name = 'BrmBar Deficit';
|
||||
RETURN rv;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(22);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
--
|
||||
-- 0023-inventory-balance.sql
|
||||
--
|
||||
-- #23 - stored function for total inventory balance
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(22) THEN
|
||||
|
||||
CREATE OR REPLACE VIEW brmbar_privileged.debit_balances AS
|
||||
SELECT
|
||||
ts.account AS debit_account,
|
||||
SUM(ts.amount) AS debit_sum
|
||||
FROM public.transaction_splits ts
|
||||
WHERE (ts.side = 'debit'::public.transaction_split_side)
|
||||
GROUP BY ts.account;
|
||||
|
||||
CREATE OR REPLACE VIEW brmbar_privileged.credit_balances AS
|
||||
SELECT
|
||||
ts.account AS credit_account,
|
||||
SUM(ts.amount) AS credit_sum
|
||||
FROM public.transaction_splits ts
|
||||
WHERE (ts.side = 'credit'::public.transaction_split_side)
|
||||
GROUP BY ts.account;
|
||||
|
||||
/*
|
||||
CASE
|
||||
WHEN (ts.side = 'credit'::public.transaction_split_side) THEN (- ts.amount)
|
||||
ELSE ts.amount
|
||||
END AS amount,
|
||||
a.currency,
|
||||
ts.memo
|
||||
FROM (public.transaction_splits ts
|
||||
LEFT JOIN public.accounts a ON ((a.id = ts.account)))
|
||||
ORDER BY ts.id;
|
||||
*/
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.inventory_balance()
|
||||
RETURNS DECIMAL(12,2)
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql SECURITY DEFINER AS $fn$
|
||||
DECLARE
|
||||
rv DECIMAL(12,2);
|
||||
BEGIN
|
||||
WITH inventory_balances AS (
|
||||
SELECT COALESCE(credit_sum, 0) * public.find_buy_rate(a.currency, 1) as credit_sum,
|
||||
COALESCE(debit_sum, 0) * public.find_buy_rate(a.currency, 1) as debit_sum,
|
||||
COALESCE(credit_account, debit_account) as cd_account
|
||||
FROM brmbar_privileged.credit_balances cb
|
||||
FULL OUTER JOIN brmbar_privileged.debit_balances db
|
||||
ON (debit_account = credit_account)
|
||||
LEFT JOIN public.accounts a
|
||||
ON (a.id = COALESCE(credit_account, debit_account))
|
||||
WHERE a.acctype = 'inventory'::public.account_type
|
||||
)
|
||||
SELECT SUM(debit_sum) - SUM(credit_sum) INTO rv
|
||||
FROM inventory_balances;
|
||||
|
||||
RETURN rv;
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(23);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
--
|
||||
-- 0024-find-rates-fix.sql
|
||||
--
|
||||
-- #24 - fix stored functions find_buy_rate and find_sell_rate
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(23) THEN
|
||||
|
||||
DROP FUNCTION IF EXISTS public.find_buy_rate(integer,integer);
|
||||
DROP FUNCTION IF EXISTS public.find_sell_rate(integer,integer);
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.find_buy_rate(
|
||||
IN i_item_currency_id public.accounts.id%TYPE,
|
||||
IN i_other_currency_id public.accounts.id%TYPE
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rate public.exchange_rates.rate%TYPE;
|
||||
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||
BEGIN
|
||||
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_item_currency_id AND source = i_other_currency_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||
IF v_rate_dir = 'target_to_source'::public.exchange_rate_direction THEN
|
||||
RETURN v_rate;
|
||||
ELSE
|
||||
RETURN 1/v_rate;
|
||||
END IF;
|
||||
/* propagate error
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RETURN -1;
|
||||
*/
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- return negative number on rate not found
|
||||
CREATE OR REPLACE FUNCTION public.find_sell_rate(
|
||||
IN i_item_currency_id public.accounts.id%TYPE,
|
||||
IN i_other_currency_id public.accounts.id%TYPE
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rate public.exchange_rates.rate%TYPE;
|
||||
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||
BEGIN
|
||||
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_other_currency_id AND source = i_item_currency_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||
IF v_rate_dir = 'source_to_target'::public.exchange_rate_direction THEN
|
||||
RETURN v_rate;
|
||||
ELSE
|
||||
RETURN 1/v_rate;
|
||||
END IF;
|
||||
/* propagate error
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RETURN -1;
|
||||
*/
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(24);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
--
|
||||
-- 0025-load-account.sql
|
||||
--
|
||||
-- #25 - stored procedures for account loading
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
DECLARE
|
||||
v INTEGER;
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(24) THEN
|
||||
|
||||
SELECT COUNT(1) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'brmbar_privileged'
|
||||
AND typ.typname='account_class_initialization_data_type';
|
||||
|
||||
IF v>0 THEN
|
||||
RAISE NOTICE 'Changing type account_class_initialization_data_type';
|
||||
DROP TYPE brmbar_privileged.account_class_initialization_data_type CASCADE;
|
||||
ELSE
|
||||
RAISE NOTICE 'Creating type account_class_initialization_data_type';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(1) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'brmbar_privileged'
|
||||
AND typ.typname='account_load_type';
|
||||
|
||||
IF v>0 THEN
|
||||
RAISE NOTICE 'Changing type account_load_type';
|
||||
DROP TYPE brmbar_privileged.account_load_type CASCADE;
|
||||
ELSE
|
||||
RAISE NOTICE 'Creating type account_load_type';
|
||||
END IF;
|
||||
|
||||
CREATE TYPE brmbar_privileged.account_load_type
|
||||
AS ENUM ('by_id', 'by_barcode');
|
||||
|
||||
CREATE TYPE brmbar_privileged.account_class_initialization_data_type
|
||||
AS (
|
||||
account_id INTEGER, --public.accounts.id%TYPE,
|
||||
account_name TEXT, --public.accounts.id%TYPE,
|
||||
account_acctype public.account_type,
|
||||
currency_id INTEGER, --public.currencies.id%TYPE,
|
||||
currency_name TEXT
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.account_class_initialization_data(
|
||||
load_by brmbar_privileged.account_load_type,
|
||||
i_id INTEGER,
|
||||
i_barcode TEXT)
|
||||
RETURNS brmbar_privileged.account_class_initialization_data_type
|
||||
LANGUAGE plpgsql
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
rv brmbar_privileged.account_class_initialization_data_type;
|
||||
BEGIN
|
||||
IF load_by = 'by_id' THEN
|
||||
SELECT a.id, a.name, a.acctype, c.id, c.name
|
||||
INTO STRICT rv.account_id, rv.account_name, rv.account_acctype, rv.currency_id, rv.currency_name
|
||||
FROM public.accounts a JOIN public.currencies c ON a.currency = c.id
|
||||
WHERE a.id = i_id;
|
||||
ELSE -- by_barcode
|
||||
SELECT a.id, a.name, a.acctype, c.id, c.name
|
||||
INTO STRICT rv.account_id, rv.account_name, rv.account_acctype, rv.currency_id, rv.currency_name
|
||||
FROM public.accounts a JOIN public.currencies c ON a.currency = c.id JOIN public.barcodes b ON b.account = a.id
|
||||
WHERE b.barcode = i_barcode;
|
||||
END IF;
|
||||
--
|
||||
RETURN rv;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(25);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
#from brmbar import Database
|
||||
#from brmbar import Currency
|
||||
|
||||
from contextlib import closing
|
||||
import psycopg2
|
||||
from brmbar.Database import Database
|
||||
from brmbar.Currency import Currency
|
||||
import math
|
||||
|
||||
#import brmbar
|
||||
|
||||
|
||||
|
||||
def approx_equal(a, b, tol=1e-6):
|
||||
"""Check if two (buy, sell) rate tuples are approximately equal."""
|
||||
return (
|
||||
isinstance(a, tuple) and isinstance(b, tuple) and
|
||||
math.isclose(a[0], b[0], abs_tol=tol) and
|
||||
math.isclose(a[1], b[1], abs_tol=tol)
|
||||
)
|
||||
|
||||
def compare_exceptions(e1, e2):
|
||||
"""Compare exception types and messages."""
|
||||
return type(e1) == type(e2) and str(e1) == str(e2)
|
||||
|
||||
def main():
|
||||
db = Database("dbname=brmbar")
|
||||
|
||||
# Get all currencies
|
||||
with closing(db.db_conn.cursor()) as cur:
|
||||
cur.execute("SELECT id, name FROM currencies")
|
||||
currencies = cur.fetchall()
|
||||
|
||||
# Build Currency objects
|
||||
currency_objs = [Currency(db, id, name) for id, name in currencies]
|
||||
|
||||
# Test all currency pairs
|
||||
for c1 in currency_objs:
|
||||
for c2 in currency_objs:
|
||||
#if c1.id == c2.id:
|
||||
# continue
|
||||
|
||||
try:
|
||||
rates1 = c1.rates(c2)
|
||||
exc1 = None
|
||||
except (RuntimeError, NameError) as e1:
|
||||
rates1 = None
|
||||
exc1 = e1
|
||||
|
||||
try:
|
||||
rates2 = c1.rates2(c2)
|
||||
exc2 = None
|
||||
except (RuntimeError, NameError) as e2:
|
||||
rates2 = None
|
||||
exc2 = e2
|
||||
|
||||
if exc1 or exc2:
|
||||
if not compare_exceptions(exc1, exc2):
|
||||
print(f"[EXCEPTION DIFFERENCE] {c1.name} -> {c2.name}")
|
||||
print(f" rates() exception: {type(exc1).__name__}: {exc1}")
|
||||
print(f" rates2() exception: {type(exc2).__name__}: {exc2}")
|
||||
elif not approx_equal(rates1, rates2):
|
||||
print(f"[VALUE DIFFERENCE] {c1.name} -> {c2.name}")
|
||||
print(f" rates(): {rates1}")
|
||||
print(f" rates2(): {rates2}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue