forked from brmlab/brmbar-github
		
	Compare commits
	
		
			No commits in common. "114e67c44510ad9d6a4451344e776beaf86eae49" and "1db6a32ceb60e5802ef3e7fec388ecc98b203253" have entirely different histories.
		
	
	
		
			114e67c445
			...
			1db6a32ceb
		
	
		
					 14 changed files with 296 additions and 514 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 | ||||
|  |  | |||
|  | @ -150,16 +150,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): | ||||
|  |  | |||
|  | @ -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,12 @@ | |||
| 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 | ||||
|  | @ -20,38 +17,48 @@ class Account: | |||
|     @classmethod | ||||
|     def load_by_barcode(cls, db, barcode): | ||||
|         logger.debug("load_by_barcode: '%s'", barcode) | ||||
|         res = db.execute_and_fetch( | ||||
|             "SELECT account FROM barcodes WHERE barcode = %s", [barcode] | ||||
|         ) | ||||
|         res = db.execute_and_fetch("SELECT account FROM barcodes WHERE barcode = %s", [barcode]) | ||||
|         if res is None: | ||||
|             return None | ||||
|         id = res[0] | ||||
|         return cls.load(db, id=id) | ||||
|         return cls.load(db, id = id) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def load(cls, db, id=None): | ||||
|         """Constructor for existing account""" | ||||
|         if id is None: | ||||
|             raise NameError("Account.load(): Specify id") | ||||
|         name, currid, acctype = db.execute_and_fetch( | ||||
|             "SELECT name, 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) | ||||
|     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 = db.execute_and_fetch("SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype]) | ||||
|         # id = id[0] | ||||
|         return cls(db, name = name, id = id, currency = currency, acctype = acctype) | ||||
| 
 | ||||
|     def balance(self): | ||||
|         bal = self.db.execute_and_fetch( | ||||
|             "SELECT public.compute_account_balance(%s)", [self.id] | ||||
|             "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()) | ||||
|  | @ -59,12 +66,22 @@ 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.execute("SELECT public.add_barcode_to_account(%s, %s)", [self.id, barcode]) | ||||
|         self.db.commit() | ||||
| 
 | ||||
|     def rename(self, name): | ||||
|         # self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id]) | ||||
|         self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name]) | ||||
|         self.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,103 +12,72 @@ 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""" | ||||
|         """ Constructor for new currency """ | ||||
|         # id = db.execute_and_fetch("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name]) | ||||
|         id = db.execute_and_fetch("SELECT public.create_currency(%s)", [name]) | ||||
|         return cls(db, id=id, name=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)""" | ||||
|         $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] | ||||
|         ) | ||||
|         res = self.db.execute_and_fetch("SELECT public.find_buy_rate(%s, %s)",[self.id, other.id]) | ||||
|         if res is None: | ||||
|             raise NameError("Something fishy in find_buy_rate.") | ||||
|             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() | ||||
|             ) | ||||
|             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] | ||||
|         ) | ||||
|         res = self.db.execute_and_fetch("SELECT public.find_sell_rate(%s, %s)",[self.id, other.id]) | ||||
|         if res is None: | ||||
|             raise NameError("Something fishy in find_sell_rate.") | ||||
|             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()) | ||||
| 
 | ||||
|         return (buy, sell) | ||||
| 
 | ||||
|     def rates2(self, other): | ||||
|         # the original code for compare testing | ||||
|         res = self.db.execute_and_fetch( | ||||
|             "SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", | ||||
|             [self.id, other.id], | ||||
|         ) | ||||
|         res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [self.id, other.id]) | ||||
|         if res is None: | ||||
|             raise NameError( | ||||
|                 "Currency.rate(): Unknown conversion " | ||||
|                 + other.name() | ||||
|                 + " to " | ||||
|                 + self.name() | ||||
|             ) | ||||
|             raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name()) | ||||
|         buy_rate, buy_rate_dir = res | ||||
|         buy = buy_rate if buy_rate_dir == "target_to_source" else 1 / buy_rate | ||||
|         buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate | ||||
| 
 | ||||
|         res = self.db.execute_and_fetch( | ||||
|             "SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", | ||||
|             [other.id, self.id], | ||||
|         ) | ||||
|         res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [other.id, self.id]) | ||||
|         if res is None: | ||||
|             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 | ||||
|         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], | ||||
|         ) | ||||
|         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() | ||||
|             ) | ||||
|             raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + target.name()) | ||||
|         rate, rate_dir = res | ||||
|         if rate_dir == "source_to_target": | ||||
|             resamount = amount * rate | ||||
|  | @ -122,13 +89,10 @@ class Currency: | |||
|         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"]) | ||||
|         self.db.execute("SELECT public.update_currency_sell_rate(%s, %s, %s)", | ||||
|                         [self.id, target.id, rate]) | ||||
|     def update_buy_rate(self, source, rate): | ||||
|         self.db.execute( | ||||
|             "SELECT public.update_currency_buy_rate(%s, %s, %s)", | ||||
|             [source.id, self.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"]) | ||||
|         self.db.execute("SELECT public.update_currency_buy_rate(%s, %s, %s)", | ||||
|                         [source.id, self.id, rate]) | ||||
|  |  | |||
|  | @ -4,29 +4,26 @@ 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) | ||||
|  | @ -41,32 +38,27 @@ class Database: | |||
|             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) | ||||
|             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() | ||||
| 
 | ||||
|     def commit(self): | ||||
|         """passes commit to db""" | ||||
|  |  | |||
|  | @ -2,163 +2,158 @@ 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): | ||||
|     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, | ||||
|         ) | ||||
|         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), | ||||
|             ], | ||||
|         ) | ||||
|             [item.id, amount, user.id, self.currency.id, | ||||
|              "BrmBar sale of {0}x {1} to {2}".format(amount, item.name, user.name)] | ||||
|         )#[0] | ||||
|         logger.debug("sell: res[0]=%s", res[0]) | ||||
|         cost = res[0] | ||||
|         self.db.commit() | ||||
|         return cost | ||||
|         # Sale: Currency conversion from item currency to shop currency | ||||
|         #(buy, sell) = item.currency.rates(self.currency) | ||||
|         #cost = amount * sell | ||||
|         #profit = amount * (sell - buy) | ||||
| 
 | ||||
|     def sell_for_cash(self, item, amount=1): | ||||
|         #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] | ||||
|             [item.id, amount, user.id, self.currency.id, | ||||
|              "BrmBar sale of {0}x {1} for cash".format(amount, item.name)] | ||||
|         )[0]#[0] | ||||
| 
 | ||||
|         self.db.commit() | ||||
|         return cost | ||||
|         ## Sale: Currency conversion from item currency to shop currency | ||||
|         #(buy, sell) = item.currency.rates(self.currency) | ||||
|         #cost = amount * sell | ||||
|         #profit = amount * (sell - buy) | ||||
| 
 | ||||
|     def undo_sale(self, item, user, amount=1): | ||||
|         #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): | ||||
|         # Undo sale; rarely needed | ||||
|         #(buy, sell) = item.currency.rates(self.currency) | ||||
|         #cost = amount * sell | ||||
|         #profit = amount * (sell - buy) | ||||
| 
 | ||||
|         #transaction = self._transaction(responsible = user, description = "BrmBar sale UNDO of {}x {} to {}".format(amount, item.name, user.name)) | ||||
|         #item.debit(transaction, amount, user.name + " (sale undo)") | ||||
|         #user.credit(transaction, cost, item.name + " (sale undo)") | ||||
|         #self.profits.credit(transaction, profit, "Margin repaid on " + item.name) | ||||
|         # Call the stored procedure for undoing a sale | ||||
|         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] | ||||
|             [item.id, amount, user.id, user.currency.id, "BrmBar sale UNDO of {0}x {1} to {2}".format(amount, item.name, user.name)] | ||||
|         )[0]#[0] | ||||
| 
 | ||||
|         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], | ||||
|             [self.cash.id, credit, user.id, user.name] | ||||
|         ) | ||||
|         self.db.commit() | ||||
| 
 | ||||
|         #transaction = self._transaction(responsible = user, description = "BrmBar credit replenishment for " + user.name) | ||||
|         #self.cash.debit(transaction, credit, user.name) | ||||
|         #user.credit(transaction, credit, "Credit replenishment") | ||||
|         #self.db.commit() | ||||
| 
 | ||||
|     def withdraw_credit(self, credit, user): | ||||
|         self.db.execute_and_fetch( | ||||
|             "SELECT public.withdraw_credit(%s,  %s, %s, %s)", | ||||
|             [self.cash.id, credit, user.id, user.name], | ||||
|             [self.cash.id, credit, user.id, user.name] | ||||
|         ) | ||||
|         self.db.commit() | ||||
|         #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], | ||||
|             "SELECT public.transfer_credit(%s,  %s, %s, %s)", | ||||
|             [self.cash.id, credit, user.id, user.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) | ||||
|     def buy_for_cash(self, item, amount = 1): | ||||
|         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], | ||||
|             [self.cash.id, item.id, amount, self.currency.id, item.name] | ||||
|         )[0] | ||||
|         # Buy: Currency conversion from item currency to shop currency | ||||
|         #(buy, sell) = item.currency.rates(self.currency) | ||||
|         #cost = amount * buy | ||||
| 
 | ||||
|         #transaction = self._transaction(description = "BrmBar stock replenishment of {}x {} for cash".format(amount, item.name)) | ||||
|         #item.debit(transaction, amount, "Cash") | ||||
|         #self.cash.credit(transaction, cost, item.name) | ||||
|         self.db.commit() | ||||
| 
 | ||||
|         return cost | ||||
| 
 | ||||
|     def receipt_to_credit(self, user, credit, description): | ||||
|         #transaction = self._transaction(responsible = user, description = "Receipt: " + description) | ||||
|         #self.profits.credit(transaction, credit, user.name) | ||||
|         #user.credit(transaction, credit, "Credit from receipt: " + description) | ||||
|         self.db.execute_and_fetch( | ||||
|             "SELECT public.receipt_reimbursement(%s, %s, %s, %s, %s)", | ||||
|             [self.profits.id, user.id, user.name, credit, description], | ||||
|             "SELECT public.buy_for_cash(%s, %s, %s, %s, %s)", | ||||
|             [self.profits.id, user.id, user.name, credit, description] | ||||
|         )[0] | ||||
|         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,78 +166,138 @@ 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 id FROM accounts WHERE acctype = %s AND name ILIKE %s ORDER BY 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: | ||||
|             accts += [Account.load(self.db, id=inventory[0])] | ||||
|             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, | ||||
|             ], | ||||
|             [item.id, item.currency.id, self.excess.id, self.deficit.id, self.currency.id, amount] | ||||
|         )[0] | ||||
| 
 | ||||
|         self.db.commit() | ||||
|         return rv | ||||
|         #amount_in_reality = amount | ||||
|         #amount_in_system = item.balance() | ||||
|         #(buy, sell) = item.currency.rates(self.currency) | ||||
| 
 | ||||
|         #diff = abs(amount_in_reality - amount_in_system) | ||||
|         #buy_total = buy * diff | ||||
|         #if amount_in_reality > amount_in_system: | ||||
|         #    transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality)) | ||||
|         #    item.debit(transaction, diff, "Inventory fix excess") | ||||
|         #    self.excess.credit(transaction, buy_total, "Inventory fix excess " + item.name) | ||||
|         #    self.db.commit() | ||||
|         #    return True | ||||
|         #elif amount_in_reality < amount_in_system: | ||||
|         #    transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality)) | ||||
|         #    item.credit(transaction, diff, "Inventory fix deficit") | ||||
|         #    self.deficit.debit(transaction, buy_total, "Inventory fix deficit " + item.name) | ||||
|         #    self.db.commit() | ||||
|         #    return True | ||||
|         #else: | ||||
|         #    transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality)) | ||||
|         #    item.debit(transaction, 0, "Inventory fix - amount was correct") | ||||
|         #    item.credit(transaction, 0, "Inventory fix - amount was correct") | ||||
|         #    self.db.commit() | ||||
|         #    return False | ||||
| 
 | ||||
|     def fix_cash(self, amount): | ||||
|         rv = self.db.execute_and_fetch( | ||||
|             "SELECT public.fix_cash(%s, %s, %s, %s)", | ||||
|             [self.excess.id, self.deficit.id, self.currency.id, amount], | ||||
|             [self.excess.id, self.deficit.id, self.currency.id, amount] | ||||
|         )[0] | ||||
| 
 | ||||
|         self.db.commit() | ||||
|         return rv | ||||
|         #amount_in_reality = amount | ||||
|         #amount_in_system = self.cash.balance() | ||||
| 
 | ||||
|         #diff = abs(amount_in_reality - amount_in_system) | ||||
|         #if amount_in_reality > amount_in_system: | ||||
|         #    transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality)) | ||||
|         #    self.cash.debit(transaction, diff, "Inventory fix excess") | ||||
|         #    self.excess.credit(transaction, diff, "Inventory cash fix excess.") | ||||
|         #    self.db.commit() | ||||
|         #    return True | ||||
|         #elif amount_in_reality < amount_in_system: | ||||
|         #    transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality)) | ||||
|         #    self.cash.credit(transaction, diff, "Inventory fix deficit") | ||||
|         #    self.deficit.debit(transaction, diff, "Inventory fix deficit.") | ||||
|         #    self.db.commit() | ||||
|         #    return True | ||||
|         #else: | ||||
|         #    return False | ||||
| 
 | ||||
|     def consolidate(self): | ||||
|         msg = self.db.execute_and_fetch( | ||||
|             "SELECT public.make_consolidate_transaction(%s, %s, %s)", | ||||
|             [self.excess.id, self.deficit.id, self.profits.id], | ||||
|             [self.excess.id, self.deficit.id, self.profits.id] | ||||
|         )[0] | ||||
|         #transaction = self._transaction(description = "BrmBar inventory consolidation") | ||||
|         #excess_balance = self.excess.balance() | ||||
|         #if excess_balance != 0: | ||||
|         #    print("Excess balance {} debited to profit".format(-excess_balance)) | ||||
|         #    self.excess.debit(transaction, -excess_balance, "Excess balance added to profit.") | ||||
|         #    self.profits.debit(transaction, -excess_balance, "Excess balance added to profit.") | ||||
|         #deficit_balance = self.deficit.balance() | ||||
|         #if deficit_balance != 0: | ||||
|         #    print("Deficit balance {} credited to profit".format(deficit_balance)) | ||||
|         #    self.deficit.credit(transaction, deficit_balance, "Deficit balance removed from profit.") | ||||
|         #    self.profits.credit(transaction, deficit_balance, "Deficit balance removed from profit.") | ||||
|         if msg != None: | ||||
|             print(msg) | ||||
|         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("SELECT public.undo_transaction(%s)",[oldtid])[0] | ||||
|         self.db.commit() | ||||
|         return transaction | ||||
|  |  | |||
|  | @ -44,15 +44,12 @@ 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); | ||||
| 	-- this could fail and it would generate exception in python | ||||
| 	-- FIXME: convert v_buy_rate < 0 into python exception | ||||
| 	v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id); | ||||
| 	-- this could fail and it would generate exception in python, even though it is not used | ||||
| 	--v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id); | ||||
| 
 | ||||
| 	-- Calculate cost and profit | ||||
| 	v_cost := i_amount * v_buy_rate; | ||||
|  | @ -63,12 +60,12 @@ BEGIN | |||
| 
 | ||||
| 	-- the item | ||||
| 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | ||||
| 	VALUES (v_transaction_id, 'debit', i_item_id, i_amount, | ||||
| 	VALUES (i_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, | ||||
| 	VALUES (i_transaction_id, 'credit', i_cash_account_id, v_cost, | ||||
| 	i_item_name); | ||||
| 
 | ||||
| 	-- Return the cost | ||||
|  |  | |||
|  | @ -48,10 +48,10 @@ BEGIN | |||
| 		'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); | ||||
| 	VALUES (i_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); | ||||
| 	VALUES (i_transaction_id, 'credit', i_user_id, i_amount, 'Credit from receipt: ' || i_description); | ||||
| END; | ||||
| $$; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 : | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue