forked from brmlab/brmbar-github
		
	Compare commits
	
		
			22 commits
		
	
	
		
			1db6a32ceb
			...
			114e67c445
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 114e67c445 | |||
| 8487202b8f | |||
| a1ec2bdb6b | |||
| 7a301379f0 | |||
| 39fe8d97fd | |||
| 2e328e4fb3 | |||
| 4f9611727b | |||
| 3933514e86 | |||
| 04f98147fd | |||
| dde8bd764d | |||
| a8e4b3216d | |||
| 01491deec3 | |||
| 76a484ea5e | |||
| f962586e3a | |||
| be0b50fedd | |||
| 95e55aef23 | |||
| e31165d668 | |||
| 5d658a7406 | |||
| f8a265f1d2 | |||
| 6f945c3a0f | |||
| 9f63a6760e | |||
| ff68817129 | 
					 14 changed files with 514 additions and 296 deletions
				
			
		|  | @ -4,6 +4,7 @@ import argparse | ||||||
| import brmbar | import brmbar | ||||||
| import math | import math | ||||||
| from brmbar import Database | from brmbar import Database | ||||||
|  | import sys | ||||||
| 
 | 
 | ||||||
| def main(): | def main(): | ||||||
|     parser = argparse.ArgumentParser(usage = "File format: EAN amount total_price name, e.g. 4001242002377 6 167.40 Chio Tortillas") |     parser = argparse.ArgumentParser(usage = "File format: EAN amount total_price name, e.g. 4001242002377 6 167.40 Chio Tortillas") | ||||||
|  | @ -38,5 +39,7 @@ def main(): | ||||||
|     print("Total is {}".format(total)) |     print("Total is {}".format(total)) | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|  |     print("!!! THIS PROGRAM NO LONGER WORKS !!!") | ||||||
|  |     sys.exit(1) | ||||||
|     main() |     main() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ from brmbar import Database | ||||||
| 
 | 
 | ||||||
| import brmbar | import brmbar | ||||||
| 
 | 
 | ||||||
|  | print("!!! THIS PROGRAM NO LONGER WORKS !!!") | ||||||
|  | sys.exit(1) | ||||||
| 
 | 
 | ||||||
| def help(): | def help(): | ||||||
|     print("""BrmBar v3 (c) Petr Baudis <pasky@ucw.cz> 2012-2013 |     print("""BrmBar v3 (c) Petr Baudis <pasky@ucw.cz> 2012-2013 | ||||||
|  |  | ||||||
|  | @ -150,11 +150,16 @@ class ShopAdapter(QtCore.QObject): | ||||||
| 
 | 
 | ||||||
|     @QtCore.Slot('QVariant', 'QVariant', 'QVariant', result='QVariant') |     @QtCore.Slot('QVariant', 'QVariant', 'QVariant', result='QVariant') | ||||||
|     def newTransfer(self, uidfrom, uidto, amount): |     def newTransfer(self, uidfrom, uidto, amount): | ||||||
|  |         logger.debug("newTransfer %s %s %s", uidfrom, uidto, amount) | ||||||
|         ufrom = brmbar.Account.load(db, id=uidfrom) |         ufrom = brmbar.Account.load(db, id=uidfrom) | ||||||
|  |         logger.debug("  ufrom = %s", ufrom) | ||||||
|         uto = brmbar.Account.load(db, id=uidto) |         uto = brmbar.Account.load(db, id=uidto) | ||||||
|  |         logger.debug("  uto = %s", uto) | ||||||
|         shop.transfer_credit(ufrom, uto, amount = amount) |         shop.transfer_credit(ufrom, uto, amount = amount) | ||||||
|         db.commit() |         db.commit() | ||||||
|         return currency.str(float(amount)) |         csfa = currency.str(float(amount)) | ||||||
|  |         logger.debug("  csfa = '%s'", csfa) | ||||||
|  |         return csfa | ||||||
| 
 | 
 | ||||||
|     @QtCore.Slot('QVariant', result='QVariant') |     @QtCore.Slot('QVariant', result='QVariant') | ||||||
|     def balance_user(self, userid): |     def balance_user(self, userid): | ||||||
|  |  | ||||||
|  | @ -6,6 +6,9 @@ from brmbar import Database | ||||||
| 
 | 
 | ||||||
| import brmbar | import brmbar | ||||||
| 
 | 
 | ||||||
|  | print("!!! THIS PROGRAM NO LONGER WORKS !!!") | ||||||
|  | sys.exit(1) | ||||||
|  | 
 | ||||||
| db = Database.Database("dbname=brmbar") | db = Database.Database("dbname=brmbar") | ||||||
| shop = brmbar.Shop.new_with_defaults(db) | shop = brmbar.Shop.new_with_defaults(db) | ||||||
| currency = shop.currency | currency = shop.currency | ||||||
|  |  | ||||||
|  | @ -6,6 +6,9 @@ from brmbar import Database | ||||||
| 
 | 
 | ||||||
| import brmbar | import brmbar | ||||||
| 
 | 
 | ||||||
|  | print("!!! THIS PROGRAM NO LONGER WORKS !!!") | ||||||
|  | sys.exit(1) | ||||||
|  | 
 | ||||||
| from flask import * | from flask import * | ||||||
| app = Flask(__name__) | app = Flask(__name__) | ||||||
| #app.debug = True | #app.debug = True | ||||||
|  |  | ||||||
|  | @ -1,12 +1,15 @@ | ||||||
| from .Currency import Currency | from .Currency import Currency | ||||||
| import logging | import logging | ||||||
|  | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Account: | class Account: | ||||||
|     """ BrmBar Account |     """BrmBar Account | ||||||
| 
 | 
 | ||||||
|     Both users and items are accounts. So is the money box, etc. |     Both users and items are accounts. So is the money box, etc. | ||||||
|     Each account has a currency.""" |     Each account has a currency.""" | ||||||
|  | 
 | ||||||
|     def __init__(self, db, id, name, currency, acctype): |     def __init__(self, db, id, name, currency, acctype): | ||||||
|         self.db = db |         self.db = db | ||||||
|         self.id = id |         self.id = id | ||||||
|  | @ -17,48 +20,38 @@ class Account: | ||||||
|     @classmethod |     @classmethod | ||||||
|     def load_by_barcode(cls, db, barcode): |     def load_by_barcode(cls, db, barcode): | ||||||
|         logger.debug("load_by_barcode: '%s'", barcode) |         logger.debug("load_by_barcode: '%s'", barcode) | ||||||
|         res = db.execute_and_fetch("SELECT account FROM barcodes WHERE barcode = %s", [barcode]) |         res = db.execute_and_fetch( | ||||||
|  |             "SELECT account FROM barcodes WHERE barcode = %s", [barcode] | ||||||
|  |         ) | ||||||
|         if res is None: |         if res is None: | ||||||
|             return None |             return None | ||||||
|         id = res[0] |         id = res[0] | ||||||
|         return cls.load(db, id = id) |         return cls.load(db, id=id) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def load(cls, db, id = None, name = None): |     def load(cls, db, id=None): | ||||||
|         """ Constructor for existing account """ |         """Constructor for existing account""" | ||||||
|         if id is not None: |         if id is None: | ||||||
|             name = db.execute_and_fetch("SELECT name FROM accounts WHERE id = %s", [id]) |             raise NameError("Account.load(): Specify id") | ||||||
|             name = name[0] |         name, currid, acctype = db.execute_and_fetch( | ||||||
|         elif name is not None: |             "SELECT name, currency, acctype FROM accounts WHERE id = %s", [id] | ||||||
|             id = db.execute_and_fetch("SELECT id FROM accounts WHERE name = %s", [name]) |         ) | ||||||
|             id = id[0] |         currency = Currency.load(db, id=currid) | ||||||
|         else: |         return cls(db, name=name, id=id, currency=currency, acctype=acctype) | ||||||
|             raise NameError("Account.load(): Specify either id or name") |  | ||||||
| 
 |  | ||||||
|         currid, acctype = db.execute_and_fetch("SELECT currency, acctype FROM accounts WHERE id = %s", [id]) |  | ||||||
|         currency = Currency.load(db, id = currid) |  | ||||||
| 
 |  | ||||||
|         return cls(db, name = name, id = id, currency = currency, acctype = acctype) |  | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def create(cls, db, name, currency, acctype): |     def create(cls, db, name, currency, acctype): | ||||||
|         """ Constructor for new account """ |         """Constructor for new account""" | ||||||
|         # id = db.execute_and_fetch("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype]) |         id = db.execute_and_fetch( | ||||||
|         id = db.execute_and_fetch("SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype]) |             "SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype] | ||||||
|         # id = id[0] |         ) | ||||||
|         return cls(db, name = name, id = id, currency = currency, acctype = acctype) |         return cls(db, name=name, id=id, currency=currency, acctype=acctype) | ||||||
| 
 | 
 | ||||||
|     def balance(self): |     def balance(self): | ||||||
|         bal = self.db.execute_and_fetch( |         bal = self.db.execute_and_fetch( | ||||||
|             "SELECT public.compute_account_balance(%s)", |             "SELECT public.compute_account_balance(%s)", [self.id] | ||||||
|             [self.id] |  | ||||||
|         )[0] |         )[0] | ||||||
|         return bal |         return bal | ||||||
|         #debit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit']) |  | ||||||
|         #debit = debit[0] or 0 |  | ||||||
|         #credit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit']) |  | ||||||
|         #credit = credit[0] or 0 |  | ||||||
|         #return debit - credit |  | ||||||
| 
 | 
 | ||||||
|     def balance_str(self): |     def balance_str(self): | ||||||
|         return self.currency.str(self.balance()) |         return self.currency.str(self.balance()) | ||||||
|  | @ -66,22 +59,12 @@ class Account: | ||||||
|     def negbalance_str(self): |     def negbalance_str(self): | ||||||
|         return self.currency.str(-self.balance()) |         return self.currency.str(-self.balance()) | ||||||
| 
 | 
 | ||||||
|     def debit(self, transaction, amount, memo): |  | ||||||
|         return self._transaction_split(transaction, 'debit', amount, memo) |  | ||||||
| 
 |  | ||||||
|     def credit(self, transaction, amount, memo): |  | ||||||
|         return self._transaction_split(transaction, 'credit', amount, memo) |  | ||||||
| 
 |  | ||||||
|     def _transaction_split(self, transaction, side, amount, memo): |  | ||||||
|         """ Common part of credit() and debit(). """ |  | ||||||
|         self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, self.id, amount, memo]) |  | ||||||
| 
 |  | ||||||
|     def add_barcode(self, barcode): |     def add_barcode(self, barcode): | ||||||
|         # self.db.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode]) |         self.db.execute( | ||||||
|         self.db.execute("SELECT public.add_barcode_to_account(%s, %s)", [self.id, barcode]) |             "SELECT public.add_barcode_to_account(%s, %s)", [self.id, barcode] | ||||||
|  |         ) | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
| 
 | 
 | ||||||
|     def rename(self, name): |     def rename(self, name): | ||||||
|         # self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id]) |  | ||||||
|         self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name]) |         self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name]) | ||||||
|         self.name = name |         self.name = name | ||||||
|  |  | ||||||
|  | @ -1,10 +1,12 @@ | ||||||
| # vim: set fileencoding=utf8 | # vim: set fileencoding=utf8 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Currency: | class Currency: | ||||||
|     """ Currency |     """Currency | ||||||
| 
 | 
 | ||||||
|     Each account has a currency (1 Kč, 1 Club Maté, ...), pairs of |     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): |     def __init__(self, db, id, name): | ||||||
|         self.db = db |         self.db = db | ||||||
|         self.id = id |         self.id = id | ||||||
|  | @ -12,72 +14,103 @@ class Currency: | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def default(cls, db): |     def default(cls, db): | ||||||
|         """ Default wallet currency """ |         """Default wallet currency""" | ||||||
|         return cls.load(db, name = "Kč") |         return cls.load(db, name="Kč") | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def load(cls, db, id = None, name = None): |     def load(cls, db, id=None): | ||||||
|         """ Constructor for existing currency """ |         """Constructor for existing currency""" | ||||||
|         if id is not None: |         if id is None: | ||||||
|             name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id]) |             raise NameError("Currency.load(): Specify id") | ||||||
|             name = name[0] |         name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id]) | ||||||
|         elif name is not None: |         name = name[0] | ||||||
|             id = db.execute_and_fetch("SELECT id FROM currencies WHERE name = %s", [name]) |         return cls(db, id=id, name=name) | ||||||
|             id = id[0] |  | ||||||
|         else: |  | ||||||
|             raise NameError("Currency.load(): Specify either id or name") |  | ||||||
|         return cls(db, name = name, id = id) |  | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def create(cls, db, name): |     def create(cls, db, name): | ||||||
|         """ Constructor for new currency """ |         """Constructor for new currency""" | ||||||
|         # id = db.execute_and_fetch("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name]) |  | ||||||
|         id = db.execute_and_fetch("SELECT public.create_currency(%s)", [name]) |         id = db.execute_and_fetch("SELECT public.create_currency(%s)", [name]) | ||||||
|         # id = id[0] |         return cls(db, id=id, name=name) | ||||||
|         return cls(db, name = name, id = id) |  | ||||||
| 
 | 
 | ||||||
|     def rates(self, other): |     def rates(self, other): | ||||||
|         """ Return tuple ($buy, $sell) of rates of $self in relation to $other (brmbar.Currency): |         """Return tuple ($buy, $sell) of rates of $self in relation to $other (brmbar.Currency): | ||||||
|         $buy is the price of $self in means of $other when buying it (into brmbar) |         $buy is the price of $self in means of $other when buying it (into brmbar) | ||||||
|         $sell is the price of $self in means of $other when selling it (from brmbar) """ |         $sell is the price of $self in means of $other when selling it (from brmbar)""" | ||||||
|         # buy rate |         # buy rate | ||||||
|         res = self.db.execute_and_fetch("SELECT public.find_buy_rate(%s, %s)",[self.id, other.id]) |         res = self.db.execute_and_fetch( | ||||||
|  |             "SELECT public.find_buy_rate(%s, %s)", [self.id, other.id] | ||||||
|  |         ) | ||||||
|         if res is None: |         if res is None: | ||||||
|             raise NameError("Something fishy in find_buy_rate."); |             raise NameError("Something fishy in find_buy_rate.") | ||||||
|         buy = res[0] |         buy = res[0] | ||||||
|         if buy < 0: |         if buy < 0: | ||||||
|             raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name()) |             raise NameError( | ||||||
|  |                 "Currency.rate(): Unknown conversion " | ||||||
|  |                 + other.name() | ||||||
|  |                 + " to " | ||||||
|  |                 + self.name() | ||||||
|  |             ) | ||||||
|         # sell rate |         # sell rate | ||||||
|         res = self.db.execute_and_fetch("SELECT public.find_sell_rate(%s, %s)",[self.id, other.id]) |         res = self.db.execute_and_fetch( | ||||||
|  |             "SELECT public.find_sell_rate(%s, %s)", [self.id, other.id] | ||||||
|  |         ) | ||||||
|         if res is None: |         if res is None: | ||||||
|             raise NameError("Something fishy in find_sell_rate."); |             raise NameError("Something fishy in find_sell_rate.") | ||||||
|         sell = res[0] |         sell = res[0] | ||||||
|         if sell < 0: |         if sell < 0: | ||||||
|             raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name()) |             raise NameError( | ||||||
|  |                 "Currency.rate(): Unknown conversion " | ||||||
|  |                 + self.name() | ||||||
|  |                 + " to " | ||||||
|  |                 + other.name() | ||||||
|  |             ) | ||||||
| 
 | 
 | ||||||
|         return (buy, sell) |         return (buy, sell) | ||||||
| 
 | 
 | ||||||
|     def rates2(self, other): |     def rates2(self, other): | ||||||
|         # the original code for compare testing |         # the original code for compare testing | ||||||
|         res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [self.id, other.id]) |         res = self.db.execute_and_fetch( | ||||||
|  |             "SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", | ||||||
|  |             [self.id, other.id], | ||||||
|  |         ) | ||||||
|         if res is None: |         if res is None: | ||||||
|             raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name()) |             raise NameError( | ||||||
|  |                 "Currency.rate(): Unknown conversion " | ||||||
|  |                 + other.name() | ||||||
|  |                 + " to " | ||||||
|  |                 + self.name() | ||||||
|  |             ) | ||||||
|         buy_rate, buy_rate_dir = res |         buy_rate, buy_rate_dir = res | ||||||
|         buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate |         buy = buy_rate if buy_rate_dir == "target_to_source" else 1 / buy_rate | ||||||
| 
 | 
 | ||||||
|         res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [other.id, self.id]) |         res = self.db.execute_and_fetch( | ||||||
|  |             "SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", | ||||||
|  |             [other.id, self.id], | ||||||
|  |         ) | ||||||
|         if res is None: |         if res is None: | ||||||
|             raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name()) |             raise NameError( | ||||||
|  |                 "Currency.rate(): Unknown conversion " | ||||||
|  |                 + self.name() | ||||||
|  |                 + " to " | ||||||
|  |                 + other.name() | ||||||
|  |             ) | ||||||
|         sell_rate, sell_rate_dir = res |         sell_rate, sell_rate_dir = res | ||||||
|         sell = sell_rate if sell_rate_dir == "source_to_target" else 1/sell_rate |         sell = sell_rate if sell_rate_dir == "source_to_target" else 1 / sell_rate | ||||||
| 
 | 
 | ||||||
|         return (buy, sell) |         return (buy, sell) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def convert(self, amount, target): |     def convert(self, amount, target): | ||||||
|         res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [target.id, self.id]) |         res = self.db.execute_and_fetch( | ||||||
|  |             "SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", | ||||||
|  |             [target.id, self.id], | ||||||
|  |         ) | ||||||
|         if res is None: |         if res is None: | ||||||
|             raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + target.name()) |             raise NameError( | ||||||
|  |                 "Currency.convert(): Unknown conversion " | ||||||
|  |                 + self.name() | ||||||
|  |                 + " to " | ||||||
|  |                 + target.name() | ||||||
|  |             ) | ||||||
|         rate, rate_dir = res |         rate, rate_dir = res | ||||||
|         if rate_dir == "source_to_target": |         if rate_dir == "source_to_target": | ||||||
|             resamount = amount * rate |             resamount = amount * rate | ||||||
|  | @ -89,10 +122,13 @@ class Currency: | ||||||
|         return "{:.2f} {}".format(amount, self.name) |         return "{:.2f} {}".format(amount, self.name) | ||||||
| 
 | 
 | ||||||
|     def update_sell_rate(self, target, rate): |     def update_sell_rate(self, target, rate): | ||||||
|         # self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [self.id, target.id, rate, "source_to_target"]) |         self.db.execute( | ||||||
|         self.db.execute("SELECT public.update_currency_sell_rate(%s, %s, %s)", |             "SELECT public.update_currency_sell_rate(%s, %s, %s)", | ||||||
|                         [self.id, target.id, rate]) |             [self.id, target.id, rate], | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|     def update_buy_rate(self, source, rate): |     def update_buy_rate(self, source, rate): | ||||||
|         # self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [source.id, self.id, rate, "target_to_source"]) |         self.db.execute( | ||||||
|         self.db.execute("SELECT public.update_currency_buy_rate(%s, %s, %s)", |             "SELECT public.update_currency_buy_rate(%s, %s, %s)", | ||||||
|                         [source.id, self.id, rate]) |             [source.id, self.id, rate], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  | @ -4,26 +4,29 @@ import psycopg2 | ||||||
| from contextlib import closing | from contextlib import closing | ||||||
| import time | import time | ||||||
| import logging | import logging | ||||||
|  | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Database: | class Database: | ||||||
|     """self-reconnecting database object""" |     """self-reconnecting database object""" | ||||||
|  | 
 | ||||||
|     def __init__(self, dsn): |     def __init__(self, dsn): | ||||||
|         self.db_conn = psycopg2.connect(dsn) |         self.db_conn = psycopg2.connect(dsn) | ||||||
|         self.dsn = dsn |         self.dsn = dsn | ||||||
| 
 | 
 | ||||||
|     def execute(self, query, attrs = None): |     def execute(self, query, attrs=None): | ||||||
|         """execute a query and return one result""" |         """execute a query and return one result""" | ||||||
|         with closing(self.db_conn.cursor()) as cur: |         with closing(self.db_conn.cursor()) as cur: | ||||||
|             cur = self._execute(cur, query, attrs) |             cur = self._execute(cur, query, attrs) | ||||||
| 
 | 
 | ||||||
|     def execute_and_fetch(self, query, attrs = None): |     def execute_and_fetch(self, query, attrs=None): | ||||||
|         """execute a query and return one result""" |         """execute a query and return one result""" | ||||||
|         with closing(self.db_conn.cursor()) as cur: |         with closing(self.db_conn.cursor()) as cur: | ||||||
|             cur = self._execute(cur, query, attrs) |             cur = self._execute(cur, query, attrs) | ||||||
|             return cur.fetchone() |             return cur.fetchone() | ||||||
| 
 | 
 | ||||||
|     def execute_and_fetchall(self, query, attrs = None): |     def execute_and_fetchall(self, query, attrs=None): | ||||||
|         """execute a query and return all results""" |         """execute a query and return all results""" | ||||||
|         with closing(self.db_conn.cursor()) as cur: |         with closing(self.db_conn.cursor()) as cur: | ||||||
|             cur = self._execute(cur, query, attrs) |             cur = self._execute(cur, query, attrs) | ||||||
|  | @ -38,27 +41,32 @@ class Database: | ||||||
|             else: |             else: | ||||||
|                 cur.execute(query, attrs) |                 cur.execute(query, attrs) | ||||||
|             return cur |             return cur | ||||||
|         except psycopg2.DataError as error: # when biitr comes and enters '99999999999999999999' for amount |         except ( | ||||||
|             print("We have invalid input data (SQLi?): level %s (%s) @%s" % ( |             psycopg2.DataError | ||||||
|                 level, error, time.strftime("%Y%m%d %a %I:%m %p") |         ) as error:  # when biitr comes and enters '99999999999999999999' for amount | ||||||
|                 )) |             logger.debug( | ||||||
|  |                 "We have invalid input data (SQLi?): level %s (%s) @%s" | ||||||
|  |                 % (level, error, time.strftime("%Y%m%d %a %I:%m %p")) | ||||||
|  |             ) | ||||||
|             self.db_conn.rollback() |             self.db_conn.rollback() | ||||||
|             raise RuntimeError("Unsanitized data entered again... BOBBY TABLES") |             raise RuntimeError("Unsanitized data entered again... BOBBY TABLES") | ||||||
|         except psycopg2.OperationalError as error: |         except psycopg2.OperationalError as error: | ||||||
|             print("Sleeping: level %s (%s) @%s" % ( |             logger.debug( | ||||||
|                 level, error, time.strftime("%Y%m%d %a %I:%m %p") |                 "Sleeping: level %s (%s) @%s" | ||||||
|                 )) |                 % (level, error, time.strftime("%Y%m%d %a %I:%m %p")) | ||||||
|             #TODO: emit message "db conn failed, reconnecting |             ) | ||||||
|             time.sleep(2 ** level) |             # TODO: emit message "db conn failed, reconnecting | ||||||
|  |             time.sleep(2**level) | ||||||
|             try: |             try: | ||||||
|                 self.db_conn = psycopg2.connect(self.dsn) |                 self.db_conn = psycopg2.connect(self.dsn) | ||||||
|             except psycopg2.OperationalError: |             except psycopg2.OperationalError: | ||||||
|                 #TODO: emit message "psql not running to interface |                 # TODO: emit message "psql not running to interface | ||||||
|                 time.sleep(1) |                 time.sleep(1) | ||||||
|             cur = self.db_conn.cursor()  #how ugly is this? |             cur = self.db_conn.cursor()  # how ugly is this? | ||||||
|             return self._execute(cur, query, attrs, level+1) |             return self._execute(cur, query, attrs, level + 1) | ||||||
|         except Exception as ex: |         except Exception as ex: | ||||||
|             logger.debug("_execute exception: %s", ex) |             logger.debug("_execute exception: %s", ex) | ||||||
|  |             self.db_conn.rollback() | ||||||
| 
 | 
 | ||||||
|     def commit(self): |     def commit(self): | ||||||
|         """passes commit to db""" |         """passes commit to db""" | ||||||
|  |  | ||||||
|  | @ -2,158 +2,163 @@ import brmbar | ||||||
| from .Currency import Currency | from .Currency import Currency | ||||||
| from .Account import Account | from .Account import Account | ||||||
| import logging | import logging | ||||||
|  | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Shop: | class Shop: | ||||||
|     """ BrmBar Shop |     """BrmBar Shop | ||||||
| 
 | 
 | ||||||
|     Business logic so that only interaction is left in the hands |     Business logic so that only interaction is left in the hands | ||||||
|     of the frontend scripts. """ |     of the frontend scripts.""" | ||||||
|     def __init__(self, db, currency, profits, cash, excess, deficit): | 
 | ||||||
|  |     def __init__(self, db, currency_id, profits_id, cash_id, excess_id, deficit_id): | ||||||
|  |         # Keep db as-is | ||||||
|         self.db = db |         self.db = db | ||||||
|         self.currency = currency # brmbar.Currency | 
 | ||||||
|         self.profits = profits # income brmbar.Account for brmbar profit margins on items |         # Store all ids | ||||||
|         self.cash = cash # our operational ("wallet") cash account |         self.currency_id = currency_id | ||||||
|         self.excess = excess # account from which is deducted cash during inventory item fixing (when system contains less items than is the reality) |         self.profits_id = profits_id | ||||||
|         self.deficit = deficit # account where is put cash during inventory item fixing (when system contains more items than is the reality) |         self.cash_id = cash_id | ||||||
|  |         self.excess_id = excess_id | ||||||
|  |         self.deficit_id = deficit_id | ||||||
|  | 
 | ||||||
|  |         # Create objects where needed for legacy code | ||||||
|  | 
 | ||||||
|  |         # brmbar.Currency | ||||||
|  |         self.currency = Currency.load(self.db, id=self.currency_id) | ||||||
|  | 
 | ||||||
|  |         # income brmbar.Account for brmbar profit margins on items | ||||||
|  |         self.profits = Account.load(db, id=self.profits_id) | ||||||
|  | 
 | ||||||
|  |         # our operational ("wallet") cash account | ||||||
|  |         self.cash = Account.load(db, id=self.cash_id) | ||||||
|  | 
 | ||||||
|  |         # account from which is deducted cash during inventory item | ||||||
|  |         # fixing (when system contains less items than is the | ||||||
|  |         # reality) | ||||||
|  |         self.excess = Account.load(db, id=self.excess_id) | ||||||
|  | 
 | ||||||
|  |         # account where is put cash during inventory item fixing (when | ||||||
|  |         # system contains more items than is the reality) | ||||||
|  |         self.deficit = Account.load(db, id=self.deficit_id) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new_with_defaults(cls, db): |     def new_with_defaults(cls, db): | ||||||
|         return cls(db, |         # shop_class_initialization_data | ||||||
|             currency = Currency.default(db), |         currency_id, profits_id, cash_id, excess_id, deficit_id = db.execute_and_fetch( | ||||||
|             profits = Account.load(db, name = "BrmBar Profits"), |             "select currency_id, profits_id, cash_id, excess_id,  deficit_id  from public.shop_class_initialization_data()" | ||||||
|             cash = Account.load(db, name = "BrmBar Cash"), |         ) | ||||||
|             excess = Account.load(db, name = "BrmBar Excess"), |         return cls( | ||||||
|             deficit = Account.load(db, name = "BrmBar Deficit")) |             db, | ||||||
|  |             currency_id=currency_id, | ||||||
|  |             profits_id=profits_id, | ||||||
|  |             cash_id=cash_id, | ||||||
|  |             excess_id=excess_id, | ||||||
|  |             deficit_id=deficit_id, | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|     def sell(self, item, user, amount = 1): |     def sell(self, item, user, amount=1): | ||||||
|         # Call the stored procedure for the sale |         # Call the stored procedure for the sale | ||||||
|         logger.debug("sell: item.id=%s amount=%s user.id=%s self.currency.id=%s", |         logger.debug( | ||||||
|                      item.id, amount, user.id, self.currency.id) |             "sell: item.id=%s amount=%s user.id=%s self.currency.id=%s", | ||||||
|  |             item.id, | ||||||
|  |             amount, | ||||||
|  |             user.id, | ||||||
|  |             self.currency.id, | ||||||
|  |         ) | ||||||
|         res = self.db.execute_and_fetch( |         res = self.db.execute_and_fetch( | ||||||
|             "SELECT public.sell_item(%s, %s, %s, %s, %s)", |             "SELECT public.sell_item(%s, %s, %s, %s, %s)", | ||||||
|             [item.id, amount, user.id, self.currency.id, |             [ | ||||||
|              "BrmBar sale of {0}x {1} to {2}".format(amount, item.name, user.name)] |                 item.id, | ||||||
|         )#[0] |                 amount, | ||||||
|  |                 user.id, | ||||||
|  |                 self.currency.id, | ||||||
|  |                 "BrmBar sale of {0}x {1} to {2}".format(amount, item.name, user.name), | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|         logger.debug("sell: res[0]=%s", res[0]) |         logger.debug("sell: res[0]=%s", res[0]) | ||||||
|         cost = res[0] |         cost = res[0] | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|         return cost |         return cost | ||||||
|         # Sale: Currency conversion from item currency to shop currency |  | ||||||
|         #(buy, sell) = item.currency.rates(self.currency) |  | ||||||
|         #cost = amount * sell |  | ||||||
|         #profit = amount * (sell - buy) |  | ||||||
| 
 | 
 | ||||||
|         #transaction = self._transaction(responsible = user, description = "BrmBar sale of {}x {} to {}".format(amount, item.name, user.name)) |     def sell_for_cash(self, item, amount=1): | ||||||
|         #item.credit(transaction, amount, user.name) |  | ||||||
|         #user.debit(transaction, cost, item.name) # debit (increase) on a _debt_ account |  | ||||||
|         #self.profits.debit(transaction, profit, "Margin on " + item.name) |  | ||||||
|         #self.db.commit() |  | ||||||
|         #return cost |  | ||||||
| 
 |  | ||||||
|     def sell_for_cash(self, item, amount = 1): |  | ||||||
|         cost = self.db.execute_and_fetch( |         cost = self.db.execute_and_fetch( | ||||||
|             "SELECT public.sell_item_for_cash(%s, %s, %s, %s, %s)", |             "SELECT public.sell_item_for_cash(%s, %s, %s, %s, %s)", | ||||||
|             [item.id, amount, user.id, self.currency.id, |             [ | ||||||
|              "BrmBar sale of {0}x {1} for cash".format(amount, item.name)] |                 item.id, | ||||||
|         )[0]#[0] |                 amount, | ||||||
|  |                 user.id, | ||||||
|  |                 self.currency.id, | ||||||
|  |                 "BrmBar sale of {0}x {1} for cash".format(amount, item.name), | ||||||
|  |             ], | ||||||
|  |         )[0] | ||||||
| 
 | 
 | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|         return cost |         return cost | ||||||
|         ## Sale: Currency conversion from item currency to shop currency |  | ||||||
|         #(buy, sell) = item.currency.rates(self.currency) |  | ||||||
|         #cost = amount * sell |  | ||||||
|         #profit = amount * (sell - buy) |  | ||||||
| 
 | 
 | ||||||
|         #transaction = self._transaction(description = "BrmBar sale of {}x {} for cash".format(amount, item.name)) |     def undo_sale(self, item, user, amount=1): | ||||||
|         #item.credit(transaction, amount, "Cash") |  | ||||||
|         #self.cash.debit(transaction, cost, item.name) |  | ||||||
|         #self.profits.debit(transaction, profit, "Margin on " + item.name) |  | ||||||
|         #self.db.commit() |  | ||||||
| 
 |  | ||||||
|         #return cost |  | ||||||
| 
 |  | ||||||
|     def undo_sale(self, item, user, amount = 1): |  | ||||||
|         # Undo sale; rarely needed |  | ||||||
|         #(buy, sell) = item.currency.rates(self.currency) |  | ||||||
|         #cost = amount * sell |  | ||||||
|         #profit = amount * (sell - buy) |  | ||||||
| 
 |  | ||||||
|         #transaction = self._transaction(responsible = user, description = "BrmBar sale UNDO of {}x {} to {}".format(amount, item.name, user.name)) |  | ||||||
|         #item.debit(transaction, amount, user.name + " (sale undo)") |  | ||||||
|         #user.credit(transaction, cost, item.name + " (sale undo)") |  | ||||||
|         #self.profits.credit(transaction, profit, "Margin repaid on " + item.name) |  | ||||||
|         # Call the stored procedure for undoing a sale |         # Call the stored procedure for undoing a sale | ||||||
|         cost = self.db.execute_and_fetch( |         cost = self.db.execute_and_fetch( | ||||||
|             "SELECT public.undo_sale_of_item(%s,  %s, %s, %s)", |             "SELECT public.undo_sale_of_item(%s,  %s, %s, %s)", | ||||||
|             [item.id, amount, user.id, user.currency.id, "BrmBar sale UNDO of {0}x {1} to {2}".format(amount, item.name, user.name)] |             [ | ||||||
|         )[0]#[0] |                 item.id, | ||||||
| 
 |                 amount, | ||||||
|  |                 user.id, | ||||||
|  |                 user.currency.id, | ||||||
|  |                 "BrmBar sale UNDO of {0}x {1} to {2}".format( | ||||||
|  |                     amount, item.name, user.name | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |         )[0] | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
| 
 |  | ||||||
|         return cost |         return cost | ||||||
| 
 | 
 | ||||||
|     def add_credit(self, credit, user): |     def add_credit(self, credit, user): | ||||||
|         self.db.execute_and_fetch( |         self.db.execute_and_fetch( | ||||||
|             "SELECT public.add_credit(%s,  %s, %s, %s)", |             "SELECT public.add_credit(%s,  %s, %s, %s)", | ||||||
|             [self.cash.id, credit, user.id, user.name] |             [self.cash.id, credit, user.id, user.name], | ||||||
|         ) |         ) | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
| 
 | 
 | ||||||
|         #transaction = self._transaction(responsible = user, description = "BrmBar credit replenishment for " + user.name) |  | ||||||
|         #self.cash.debit(transaction, credit, user.name) |  | ||||||
|         #user.credit(transaction, credit, "Credit replenishment") |  | ||||||
|         #self.db.commit() |  | ||||||
| 
 |  | ||||||
|     def withdraw_credit(self, credit, user): |     def withdraw_credit(self, credit, user): | ||||||
|         self.db.execute_and_fetch( |         self.db.execute_and_fetch( | ||||||
|             "SELECT public.withdraw_credit(%s,  %s, %s, %s)", |             "SELECT public.withdraw_credit(%s,  %s, %s, %s)", | ||||||
|             [self.cash.id, credit, user.id, user.name] |             [self.cash.id, credit, user.id, user.name], | ||||||
|         ) |         ) | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|         #transaction = self._transaction(responsible = user, description = "BrmBar credit withdrawal for " + user.name) |  | ||||||
|         #self.cash.credit(transaction, credit, user.name) |  | ||||||
|         #user.debit(transaction, credit, "Credit withdrawal") |  | ||||||
|         #self.db.commit() |  | ||||||
| 
 | 
 | ||||||
|     def transfer_credit(self, userfrom, userto, amount): |     def transfer_credit(self, userfrom, userto, amount): | ||||||
|         self.db.execute_and_fetch( |         self.db.execute_and_fetch( | ||||||
|             "SELECT public.transfer_credit(%s,  %s, %s, %s)", |             "SELECT public.transfer_credit(%s,  %s, %s, %s, %s, %s)", | ||||||
|             [self.cash.id, credit, user.id, user.name] |             [self.cash.id, amount, userfrom.id, userfrom.name, userto.id, userto.name], | ||||||
|         ) |         ) | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|         #self.add_credit(amount, userto) |  | ||||||
|         #self.withdraw_credit(amount, userfrom) |  | ||||||
| 
 | 
 | ||||||
|     def buy_for_cash(self, item, amount = 1): |     def buy_for_cash(self, item, amount=1): | ||||||
|  |         iamount = int(amount) | ||||||
|  |         famount = float(iamount) | ||||||
|  |         assert famount == amount, "amount is not integer value %s".format(amount) | ||||||
|         cost = self.db.execute_and_fetch( |         cost = self.db.execute_and_fetch( | ||||||
|             "SELECT public.buy_for_cash(%s, %s, %s, %s, %s)", |             "SELECT public.buy_for_cash(%s, %s, %s, %s, %s)", | ||||||
|             [self.cash.id, item.id, amount, self.currency.id, item.name] |             [self.cash.id, item.id, iamount, self.currency.id, item.name], | ||||||
|         )[0] |         )[0] | ||||||
|         # Buy: Currency conversion from item currency to shop currency |  | ||||||
|         #(buy, sell) = item.currency.rates(self.currency) |  | ||||||
|         #cost = amount * buy |  | ||||||
| 
 |  | ||||||
|         #transaction = self._transaction(description = "BrmBar stock replenishment of {}x {} for cash".format(amount, item.name)) |  | ||||||
|         #item.debit(transaction, amount, "Cash") |  | ||||||
|         #self.cash.credit(transaction, cost, item.name) |  | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
| 
 | 
 | ||||||
|         return cost |         return cost | ||||||
| 
 | 
 | ||||||
|     def receipt_to_credit(self, user, credit, description): |     def receipt_to_credit(self, user, credit, description): | ||||||
|         #transaction = self._transaction(responsible = user, description = "Receipt: " + description) |  | ||||||
|         #self.profits.credit(transaction, credit, user.name) |  | ||||||
|         #user.credit(transaction, credit, "Credit from receipt: " + description) |  | ||||||
|         self.db.execute_and_fetch( |         self.db.execute_and_fetch( | ||||||
|             "SELECT public.buy_for_cash(%s, %s, %s, %s, %s)", |             "SELECT public.receipt_reimbursement(%s, %s, %s, %s, %s)", | ||||||
|             [self.profits.id, user.id, user.name, credit, description] |             [self.profits.id, user.id, user.name, credit, description], | ||||||
|         )[0] |         )[0] | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
| 
 | 
 | ||||||
|     def _transaction(self, responsible = None, description = None): |     def _transaction(self, responsible=None, description=None): | ||||||
|         transaction = self.db.execute_and_fetch("INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id", |         transaction = self.db.execute_and_fetch( | ||||||
|                 [responsible.id if responsible else None, description]) |             "INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id", | ||||||
|  |             [responsible.id if responsible else None, description], | ||||||
|  |         ) | ||||||
|         transaction = transaction[0] |         transaction = transaction[0] | ||||||
|         return transaction |         return transaction | ||||||
| 
 | 
 | ||||||
|  | @ -166,138 +171,78 @@ class Shop: | ||||||
|                 WHERE a.acctype = %s AND ts.side = %s |                 WHERE a.acctype = %s AND ts.side = %s | ||||||
|         """ |         """ | ||||||
|         if overflow is not None: |         if overflow is not None: | ||||||
|             sumselect += ' AND a.name ' + ('NOT ' if overflow == 'exclude' else '') + ' LIKE \'%%-overflow\'' |             sumselect += ( | ||||||
|         cur = self.db.execute_and_fetch(sumselect, ["debt", 'debit']) |                 " AND a.name " | ||||||
|  |                 + ("NOT " if overflow == "exclude" else "") | ||||||
|  |                 + " LIKE '%%-overflow'" | ||||||
|  |             ) | ||||||
|  |         cur = self.db.execute_and_fetch(sumselect, ["debt", "debit"]) | ||||||
|         debit = cur[0] or 0 |         debit = cur[0] or 0 | ||||||
|         credit = self.db.execute_and_fetch(sumselect, ["debt", 'credit']) |         credit = self.db.execute_and_fetch(sumselect, ["debt", "credit"]) | ||||||
|         credit = credit[0] or 0 |         credit = credit[0] or 0 | ||||||
|         return debit - credit |         return debit - credit | ||||||
|  | 
 | ||||||
|     def credit_negbalance_str(self, overflow=None): |     def credit_negbalance_str(self, overflow=None): | ||||||
|         return self.currency.str(-self.credit_balance(overflow=overflow)) |         return self.currency.str(-self.credit_balance(overflow=overflow)) | ||||||
| 
 | 
 | ||||||
| # XXX causing extra heavy delay ( thousands of extra SQL queries ), disabled |  | ||||||
|     def inventory_balance(self): |     def inventory_balance(self): | ||||||
|         balance = 0 |         resa = self.db.execute_and_fetch("SELECT * FROM public.inventory_balance()") | ||||||
|         # Each inventory account has its own currency, |         res = resa[0] | ||||||
|         # so we just do this ugly iteration |         logger.debug("inventory_balance resa = %s", resa) | ||||||
|         cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s", ["inventory"]) |         return res | ||||||
|         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): |     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="%%"): |     def account_list(self, acctype, like_str="%%"): | ||||||
|         """list all accounts (people or items, as per acctype)""" |         """list all accounts (people or items, as per acctype)""" | ||||||
|         accts = [] |         accts = [] | ||||||
|         cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s AND name ILIKE %s ORDER BY name ASC", [acctype, like_str]) |         cur = self.db.execute_and_fetchall( | ||||||
| 		#FIXME: sanitize input like_str ^ |             "SELECT id FROM accounts WHERE acctype = %s AND name ILIKE %s ORDER BY name ASC", | ||||||
|  |             [acctype, like_str], | ||||||
|  |         ) | ||||||
|  |         # FIXME: sanitize input like_str ^ | ||||||
|         for inventory in cur: |         for inventory in cur: | ||||||
|             accts += [ Account.load(self.db, id = inventory[0]) ] |             accts += [Account.load(self.db, id=inventory[0])] | ||||||
|         return accts |         return accts | ||||||
| 
 | 
 | ||||||
|     def fix_inventory(self, item, amount): |     def fix_inventory(self, item, amount): | ||||||
|         rv = self.db.execute_and_fetch( |         rv = self.db.execute_and_fetch( | ||||||
|             "SELECT public.fix_inventory(%s, %s, %s, %s, %s, %s)", |             "SELECT public.fix_inventory(%s, %s, %s, %s, %s, %s)", | ||||||
|             [item.id, item.currency.id, self.excess.id, self.deficit.id, self.currency.id, amount] |             [ | ||||||
|  |                 item.id, | ||||||
|  |                 item.currency.id, | ||||||
|  |                 self.excess.id, | ||||||
|  |                 self.deficit.id, | ||||||
|  |                 self.currency.id, | ||||||
|  |                 amount, | ||||||
|  |             ], | ||||||
|         )[0] |         )[0] | ||||||
| 
 | 
 | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|         return rv |         return rv | ||||||
|         #amount_in_reality = amount |  | ||||||
|         #amount_in_system = item.balance() |  | ||||||
|         #(buy, sell) = item.currency.rates(self.currency) |  | ||||||
| 
 |  | ||||||
|         #diff = abs(amount_in_reality - amount_in_system) |  | ||||||
|         #buy_total = buy * diff |  | ||||||
|         #if amount_in_reality > amount_in_system: |  | ||||||
|         #    transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality)) |  | ||||||
|         #    item.debit(transaction, diff, "Inventory fix excess") |  | ||||||
|         #    self.excess.credit(transaction, buy_total, "Inventory fix excess " + item.name) |  | ||||||
|         #    self.db.commit() |  | ||||||
|         #    return True |  | ||||||
|         #elif amount_in_reality < amount_in_system: |  | ||||||
|         #    transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality)) |  | ||||||
|         #    item.credit(transaction, diff, "Inventory fix deficit") |  | ||||||
|         #    self.deficit.debit(transaction, buy_total, "Inventory fix deficit " + item.name) |  | ||||||
|         #    self.db.commit() |  | ||||||
|         #    return True |  | ||||||
|         #else: |  | ||||||
|         #    transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality)) |  | ||||||
|         #    item.debit(transaction, 0, "Inventory fix - amount was correct") |  | ||||||
|         #    item.credit(transaction, 0, "Inventory fix - amount was correct") |  | ||||||
|         #    self.db.commit() |  | ||||||
|         #    return False |  | ||||||
| 
 | 
 | ||||||
|     def fix_cash(self, amount): |     def fix_cash(self, amount): | ||||||
|         rv = self.db.execute_and_fetch( |         rv = self.db.execute_and_fetch( | ||||||
|             "SELECT public.fix_cash(%s, %s, %s, %s)", |             "SELECT public.fix_cash(%s, %s, %s, %s)", | ||||||
|             [self.excess.id, self.deficit.id, self.currency.id, amount] |             [self.excess.id, self.deficit.id, self.currency.id, amount], | ||||||
|         )[0] |         )[0] | ||||||
| 
 | 
 | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|         return rv |         return rv | ||||||
|         #amount_in_reality = amount |  | ||||||
|         #amount_in_system = self.cash.balance() |  | ||||||
| 
 |  | ||||||
|         #diff = abs(amount_in_reality - amount_in_system) |  | ||||||
|         #if amount_in_reality > amount_in_system: |  | ||||||
|         #    transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality)) |  | ||||||
|         #    self.cash.debit(transaction, diff, "Inventory fix excess") |  | ||||||
|         #    self.excess.credit(transaction, diff, "Inventory cash fix excess.") |  | ||||||
|         #    self.db.commit() |  | ||||||
|         #    return True |  | ||||||
|         #elif amount_in_reality < amount_in_system: |  | ||||||
|         #    transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality)) |  | ||||||
|         #    self.cash.credit(transaction, diff, "Inventory fix deficit") |  | ||||||
|         #    self.deficit.debit(transaction, diff, "Inventory fix deficit.") |  | ||||||
|         #    self.db.commit() |  | ||||||
|         #    return True |  | ||||||
|         #else: |  | ||||||
|         #    return False |  | ||||||
| 
 | 
 | ||||||
|     def consolidate(self): |     def consolidate(self): | ||||||
|         msg = self.db.execute_and_fetch( |         msg = self.db.execute_and_fetch( | ||||||
|             "SELECT public.make_consolidate_transaction(%s, %s, %s)", |             "SELECT public.make_consolidate_transaction(%s, %s, %s)", | ||||||
|             [self.excess.id, self.deficit.id, self.profits.id] |             [self.excess.id, self.deficit.id, self.profits.id], | ||||||
|         )[0] |         )[0] | ||||||
|         #transaction = self._transaction(description = "BrmBar inventory consolidation") |  | ||||||
|         #excess_balance = self.excess.balance() |  | ||||||
|         #if excess_balance != 0: |  | ||||||
|         #    print("Excess balance {} debited to profit".format(-excess_balance)) |  | ||||||
|         #    self.excess.debit(transaction, -excess_balance, "Excess balance added to profit.") |  | ||||||
|         #    self.profits.debit(transaction, -excess_balance, "Excess balance added to profit.") |  | ||||||
|         #deficit_balance = self.deficit.balance() |  | ||||||
|         #if deficit_balance != 0: |  | ||||||
|         #    print("Deficit balance {} credited to profit".format(deficit_balance)) |  | ||||||
|         #    self.deficit.credit(transaction, deficit_balance, "Deficit balance removed from profit.") |  | ||||||
|         #    self.profits.credit(transaction, deficit_balance, "Deficit balance removed from profit.") |  | ||||||
|         if msg != None: |         if msg != None: | ||||||
|             print(msg) |             print(msg) | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
| 
 | 
 | ||||||
|     def undo(self, oldtid): |     def undo(self, oldtid): | ||||||
|         #description = self.db.execute_and_fetch("SELECT description FROM transactions WHERE id = %s", [oldtid])[0] |         transaction = self.db.execute_and_fetch( | ||||||
|         #description = 'undo %d (%s)' % (oldtid, description) |             "SELECT public.undo_transaction(%s)", [oldtid] | ||||||
| 
 |         )[0] | ||||||
|         #transaction = self._transaction(description=description) |  | ||||||
|         #for split in self.db.execute_and_fetchall("SELECT id, side, account, amount, memo FROM transaction_splits WHERE transaction = %s", [oldtid]): |  | ||||||
|         #    splitid, side, account, amount, memo = split |  | ||||||
|         #    memo = 'undo %d (%s)' % (splitid, memo) |  | ||||||
|         #    amount = -amount |  | ||||||
|         #    self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, account, amount, memo]) |  | ||||||
|         transaction = self.db.execute_and_fetch("SELECT public.undo_transaction(%s)",[oldtid])[0] |  | ||||||
|         self.db.commit() |         self.db.commit() | ||||||
|         return transaction |         return transaction | ||||||
|  |  | ||||||
|  | @ -44,12 +44,15 @@ DECLARE | ||||||
| 	v_buy_rate NUMERIC; | 	v_buy_rate NUMERIC; | ||||||
| 	v_cost NUMERIC; | 	v_cost NUMERIC; | ||||||
| 	v_transaction_id public.transactions.id%TYPE; | 	v_transaction_id public.transactions.id%TYPE; | ||||||
|  | 	v_item_currency_id public.accounts.currency%TYPE; | ||||||
| BEGIN | BEGIN | ||||||
| 	-- this could fail and it would generate exception in python | 	-- Get item's currency | ||||||
| 	-- FIXME: convert v_buy_rate < 0 into python exception | 	SELECT currency | ||||||
| 	v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id); | 		INTO STRICT v_item_currency_id | ||||||
| 	-- this could fail and it would generate exception in python, even though it is not used | 		FROM public.accounts | ||||||
| 	--v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id); | 		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 | 	-- Calculate cost and profit | ||||||
| 	v_cost := i_amount * v_buy_rate; | 	v_cost := i_amount * v_buy_rate; | ||||||
|  | @ -60,12 +63,12 @@ BEGIN | ||||||
| 
 | 
 | ||||||
| 	-- the item | 	-- the item | ||||||
| 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | ||||||
| 	VALUES (i_transaction_id, 'debit', i_item_id, i_amount, | 	VALUES (v_transaction_id, 'debit', i_item_id, i_amount, | ||||||
| 	'Cash'); | 	'Cash'); | ||||||
| 
 | 
 | ||||||
| 	-- the cash | 	-- the cash | ||||||
| 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | ||||||
| 	VALUES (i_transaction_id, 'credit', i_cash_account_id, v_cost, | 	VALUES (v_transaction_id, 'credit', i_cash_account_id, v_cost, | ||||||
| 	i_item_name); | 	i_item_name); | ||||||
| 
 | 
 | ||||||
| 	-- Return the cost | 	-- Return the cost | ||||||
|  |  | ||||||
|  | @ -48,10 +48,10 @@ BEGIN | ||||||
| 		'Receipt: ' || i_description); | 		'Receipt: ' || i_description); | ||||||
| 	-- the "profit" | 	-- the "profit" | ||||||
| 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | ||||||
| 	VALUES (i_transaction_id, 'credit', i_profits_id, i_amount, i_user_name); | 	VALUES (v_transaction_id, 'credit', i_profits_id, i_amount, i_user_name); | ||||||
| 	-- the user | 	-- the user | ||||||
| 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | 	INSERT INTO public.transaction_splits (transaction, side, account, amount, memo) | ||||||
| 	VALUES (i_transaction_id, 'credit', i_user_id, i_amount, 'Credit from receipt: ' || i_description); | 	VALUES (v_transaction_id, 'credit', i_user_id, i_amount, 'Credit from receipt: ' || i_description); | ||||||
| END; | END; | ||||||
| $$; | $$; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										48
									
								
								brmbar3/schema/0021-constraints-on-numeric-columns.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								brmbar3/schema/0021-constraints-on-numeric-columns.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | -- | ||||||
|  | -- 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 : | ||||||
|  | 
 | ||||||
							
								
								
									
										85
									
								
								brmbar3/schema/0022-shop-init.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								brmbar3/schema/0022-shop-init.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | ||||||
|  | -- | ||||||
|  | -- 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 : | ||||||
|  | 
 | ||||||
							
								
								
									
										94
									
								
								brmbar3/schema/0023-inventory-balance.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								brmbar3/schema/0023-inventory-balance.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | ||||||
|  | -- | ||||||
|  | -- 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