forked from brmlab/brmbar-github
Compare commits
139 commits
test_cache
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e97f43a86 | |||
| f6128ecc4a | |||
| 92deae6775 | |||
| aee3a1d24f | |||
| e604e0000b | |||
| dd8c93e967 | |||
| 57f36dd2f5 | |||
| 1e324c0920 | |||
| c2ddab9ce8 | |||
| 89fb60bab5 | |||
| 38a4a3def1 | |||
| 2a19bd291f | |||
| 1d9a2e9fac | |||
| 54c687edf3 | |||
| 9afef5bce2 | |||
| c17aa99666 | |||
| c04d4a1f8e | |||
| 70c46bd950 | |||
| 8487202b8f | |||
| a1ec2bdb6b | |||
| 7a301379f0 | |||
| 39fe8d97fd | |||
| 2e328e4fb3 | |||
| 4f9611727b | |||
| 3933514e86 | |||
| 04f98147fd | |||
| dde8bd764d | |||
| a8e4b3216d | |||
| 01491deec3 | |||
| 76a484ea5e | |||
| f962586e3a | |||
| be0b50fedd | |||
| 95e55aef23 | |||
| e31165d668 | |||
| 5d658a7406 | |||
| f8a265f1d2 | |||
| 6f945c3a0f | |||
| 9f63a6760e | |||
| ff68817129 | |||
| afb5476c2d | |||
| 9d8827ccbd | |||
| 8ee51a23d9 | |||
| 81366fd2bb | |||
| ee2b945299 | |||
| 38838692c6 | |||
| 557d36cac3 | |||
| 343df879fb | |||
| 0a8e2b727b | |||
| 829bab66b4 | |||
| c3abceac5d | |||
| f9230ed5bf | |||
| 93f9b336b6 | |||
| 3ab537f7d9 | |||
| 15dfd0be04 | |||
| 5355eca6f5 | |||
| 3a4aaa74ce | |||
| cd9b4484c8 | |||
| 597bca87b5 | |||
| 5e348b2463 | |||
| 1eff329496 | |||
| f7f137821b | |||
| 3b0cb6472d | |||
| fba614de78 | |||
| ae16ebc51f | |||
| 903cc8f4d7 | |||
| 8d2c9cb20f | |||
| a8a124835c | |||
| 0bff27da29 | |||
| f0058aad68 | |||
| c21b394b42 | |||
| 69c405f715 | |||
| c8892f825c | |||
| 96027ca66a | |||
| aad79dafa6 | |||
| 1df7db93e7 | |||
| 629b35655d | |||
| ad832fc71b | |||
| e15f1646ce | |||
| 7b11a8c954 | |||
| f0cd8361d1 | |||
| 7ed7417492 | |||
| 028d4d98db | |||
| deb3faa717 | |||
| 4c7012c517 | |||
| cef95c4313 | |||
| aded7a5769 | |||
| 66870bbc8c | |||
| 9235607d4c | |||
| 8f42145bee | |||
| 58ab1d00be | |||
| c5d1fc3402 | |||
| 111a8c9b63 | |||
| c04f934340 | |||
| 40f4abe37f | |||
| 900264469a | |||
| f6ec2be215 | |||
| 3000731ac7 | |||
| 2f601a0b1a | |||
| 5f9292fd02 | |||
| e04d614e15 | |||
|
|
dbd3835dfb | ||
|
|
6749b2c97a | ||
|
|
a1c37cb695 | ||
|
|
f061cc7f7b | ||
|
|
6f3fc6767a | ||
|
|
e2e6632df0 | ||
|
|
1f3f1cdd8f | ||
|
|
466b92e7c2 | ||
|
|
6208ae6027 | ||
|
|
8f6776f3d0 | ||
|
|
a9e72d736c | ||
|
|
0e306912d7 | ||
|
|
c419c91a40 | ||
|
|
c5becfcabe | ||
|
|
2bfd1796d0 | ||
|
|
9a70088591 | ||
|
|
11eaecfccf | ||
|
|
0c349f6c6a | ||
|
|
e3a4fe880d | ||
|
|
4494dafbb8 | ||
|
|
9e95724556 | ||
|
|
2bbb46d4ea | ||
|
|
6b67dd372e | ||
|
|
edc99f1ff9 | ||
|
|
7a8e4ef794 | ||
|
|
3ea61f01d7 | ||
|
|
feec2c9ac4 | ||
|
|
76bb2cac98 | ||
|
|
099d775102 | ||
|
|
5c84bd6a8f | ||
|
|
a162e544c1 | ||
|
|
906a3b62ed | ||
|
|
fe80200e49 | ||
|
|
48c583a81c | ||
|
|
934873c2fc | ||
|
|
6cc95ab680 | ||
|
|
1c0e51a246 | ||
|
|
84881dece3 | ||
|
|
5de7bf7458 |
59 changed files with 3539 additions and 312 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
.*.sw?
|
.*.sw?
|
||||||
|
*~
|
||||||
|
|
|
||||||
2
barcode-generator/.gitignore
vendored
Normal file
2
barcode-generator/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
barcodes*.svg
|
||||||
|
barcode-generator.txt
|
||||||
51
barcode-generator/barcode-generator.py
Executable file
51
barcode-generator/barcode-generator.py
Executable file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# requires zint binary from zint package
|
||||||
|
#
|
||||||
|
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
import sys
|
||||||
|
|
||||||
|
svghead = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" height="1052.3622" width="744.09448" version="1.1" id="svg2" inkscape:version="0.47 r22583" sodipodi:docname="barcodes.svg">
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
svgfoot = """</svg>
|
||||||
|
"""
|
||||||
|
|
||||||
|
width = 5
|
||||||
|
scalex = 0.8
|
||||||
|
scaley = 0.8
|
||||||
|
|
||||||
|
p = 0
|
||||||
|
i = 0
|
||||||
|
j = 0
|
||||||
|
f = None
|
||||||
|
|
||||||
|
lines = sys.stdin.readlines()
|
||||||
|
|
||||||
|
for idx in xrange(len(lines)):
|
||||||
|
items = lines[idx].strip().split(';')
|
||||||
|
if idx % 30 == 0:
|
||||||
|
if f and not f.closed:
|
||||||
|
f.write(svgfoot)
|
||||||
|
f.close()
|
||||||
|
f = open('barcodes' + str(p) + '.svg','w')
|
||||||
|
p += 1
|
||||||
|
i = 0
|
||||||
|
j = 0
|
||||||
|
f.write(svghead)
|
||||||
|
elem = Popen(('./zint','--directsvg','--notext', '-d', items[1]), stdout = PIPE).communicate()[0].split('\n')
|
||||||
|
elem = elem[8:-2]
|
||||||
|
elem[0] = elem[0].replace('id="barcode"', 'transform="matrix(%f,0,0,%f,%f,%f)"' % (scalex, scaley, 50+i*140 , 180+j*140) )
|
||||||
|
elem.insert(-1, ' <text x="39.50" y="69.00" text-anchor="middle" font-family="Helvetica" font-size="14.0" fill="#000000" >%s</text>' % items[0])
|
||||||
|
f.write('\n'.join(elem)+'\n\n')
|
||||||
|
i += 1
|
||||||
|
if i >= width:
|
||||||
|
i = 0
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
if not f.closed:
|
||||||
|
f.write(svgfoot)
|
||||||
|
f.close()
|
||||||
9
barcode-generator/howto.txt
Normal file
9
barcode-generator/howto.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
on brmbar:
|
||||||
|
select distinct barcode from barcodes b, transactions t, accounts a where t.responsible=a.id and time>'2015-01-01' and b.account=a.id order by barcode asc;
|
||||||
|
|
||||||
|
run this locally and paste output of previous command:
|
||||||
|
|
||||||
|
while read tmp; do echo "$tmp;$tmp";done|grep -v overflow|python2 ./barcode-generator.py
|
||||||
|
|
||||||
|
print resulting SVG files
|
||||||
|
|
||||||
2
brmbar3/.gitignore
vendored
2
brmbar3/.gitignore
vendored
|
|
@ -1 +1,3 @@
|
||||||
__pycache__
|
__pycache__
|
||||||
|
*.log
|
||||||
|
brmbar/*.pyc
|
||||||
|
|
|
||||||
64
brmbar3/PURGE.txt
Normal file
64
brmbar3/PURGE.txt
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
How to "reset" the database - drop all history and keep only accounts with non-zero balance.
|
||||||
|
|
||||||
|
Legend:
|
||||||
|
> - SQL commands
|
||||||
|
$ - shell commands
|
||||||
|
|
||||||
|
Run the (full) inventory.
|
||||||
|
|
||||||
|
Get number of the first inventory TX.
|
||||||
|
|
||||||
|
> select id from account_balances where id in (select id from accounts where currency not in (select distinct currency from
|
||||||
|
transaction_nicesplits where transaction >= NUMBER_HERE and currency != 1 and memo like '%Inventory fix%') and acctype = 'inventory') and crbalance != 0 \g 'vynulovat'
|
||||||
|
$ ./brmbar-cli.py inventory `cat vynulovat | while read x; do echo $x 0; done`
|
||||||
|
|
||||||
|
Backup the database
|
||||||
|
$ pg_dump brmbar > backup.sql
|
||||||
|
|
||||||
|
Dump "> SELECT * FROM account_balances;" to file N.
|
||||||
|
|
||||||
|
Dump inventory to file nastavit FIXME.
|
||||||
|
|
||||||
|
Drop all transactions:
|
||||||
|
> delete from transaction_splits;
|
||||||
|
> delete from transactions;
|
||||||
|
|
||||||
|
Restore inventory:
|
||||||
|
$ cat nastavit | while read acc p amt; do ./brmbar-cli.py inventory $acc `echo $amt | grep -oE "^[0-9-]+"`; done
|
||||||
|
|
||||||
|
Restore cash balance:
|
||||||
|
$ cat N | grep debt | tr -s " " |cut -d \| -f 2,4 | while read acc p amt; do ./brmbar-cli.py changecredit $acc `echo $amt | grep -oE "^[0-9-]+"`; done
|
||||||
|
|
||||||
|
Delete zero-balance accounts:
|
||||||
|
> delete from accounts where accounts.id not in (select id from account_balances);
|
||||||
|
|
||||||
|
Delete orphaned barcodes:
|
||||||
|
> delete from barcodes where barcodes.account not in (select id from account_balances);
|
||||||
|
|
||||||
|
Delete orphaned currencies and exchange rates:
|
||||||
|
> CREATE OR REPLACE VIEW "a_tmp" AS
|
||||||
|
SELECT ts.account AS id, accounts.name, accounts.acctype, accounts.currency AS fff, (- sum(CASE WHEN (ts.side = 'credit'::transaction_split_side) THEN (- ts.amount) ELSE ts.amount END)) AS crbalance FROM (transaction_splits ts LEFT JOIN accounts ON ((accounts.id = ts.account))) GROUP BY ts.account, accounts.name, accounts.id, accounts.acctype ORDER BY (- sum(CASE WHEN (ts.side = 'credit'::transaction_split_side) THEN (- ts.amount) ELSE ts.amount END));
|
||||||
|
|
||||||
|
> delete from exchange_rates where source not in (select fff from a_tmp);
|
||||||
|
> delete from currencies where id not in (select fff from a_tmp);
|
||||||
|
|
||||||
|
> DROP VIEW "a_tmp";
|
||||||
|
|
||||||
|
Drop obsolete exchange rates:
|
||||||
|
|
||||||
|
> delete from exchange_rates where
|
||||||
|
valid_since <> (SELECT max(valid_since)
|
||||||
|
FROM exchange_rates e
|
||||||
|
WHERE e.target = exchange_rates.target and e.source = exchange_rates.source)
|
||||||
|
|
||||||
|
Restore system accounts:
|
||||||
|
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||||
|
VALUES ('BrmBar Profits', '1', 'income', '1');
|
||||||
|
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||||
|
VALUES ('BrmBar Excess', '1', 'income', '1');
|
||||||
|
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||||
|
VALUES ('BrmBar Deficit', '1', 'expense', '1');
|
||||||
|
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||||
|
VALUES ('BrmBar Cash', '1', 'cash', '1');
|
||||||
|
|
||||||
|
Restart brmbar.
|
||||||
12
brmbar3/SQL
12
brmbar3/SQL
|
|
@ -1,11 +1,11 @@
|
||||||
CREATE SEQUENCE currencies_id_seq START WITH 1 INCREMENT BY 1;
|
CREATE SEQUENCE currencies_id_seq START WITH 2 INCREMENT BY 1;
|
||||||
CREATE TABLE currencies (
|
CREATE TABLE currencies (
|
||||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('currencies_id_seq'::regclass),
|
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('currencies_id_seq'::regclass),
|
||||||
name VARCHAR(128) NOT NULL,
|
name VARCHAR(128) NOT NULL,
|
||||||
UNIQUE(name)
|
UNIQUE(name)
|
||||||
);
|
);
|
||||||
-- Some code depends on the primary physical currency to have id 1.
|
-- Some code depends on the primary physical currency to have id 1.
|
||||||
INSERT INTO currencies (name) VALUES ('Kč');
|
INSERT INTO currencies (id, name) VALUES (1, 'Kč');
|
||||||
|
|
||||||
CREATE TYPE exchange_rate_direction AS ENUM ('source_to_target', 'target_to_source');
|
CREATE TYPE exchange_rate_direction AS ENUM ('source_to_target', 'target_to_source');
|
||||||
CREATE TABLE exchange_rates (
|
CREATE TABLE exchange_rates (
|
||||||
|
|
@ -107,10 +107,9 @@ CREATE VIEW transaction_nicesplits AS
|
||||||
FROM transaction_splits AS ts LEFT JOIN accounts AS a ON a.id = ts.account
|
FROM transaction_splits AS ts LEFT JOIN accounts AS a ON a.id = ts.account
|
||||||
ORDER BY ts.id;
|
ORDER BY ts.id;
|
||||||
|
|
||||||
-- List transactions with summary information regarding their cash element
|
-- List transactions with summary information regarding their cash element.
|
||||||
-- (except in case of transfers between cash and debt accounts, which will cancel out).
|
|
||||||
CREATE VIEW transaction_cashsums AS
|
CREATE VIEW transaction_cashsums AS
|
||||||
SELECT t.id AS id, t.time AS time, SUM(credit_cash) AS cash_credit, SUM(debit_cash) AS cash_debit, t.description AS description
|
SELECT t.id AS id, t.time AS time, SUM(credit_cash) AS cash_credit, SUM(debit_cash) AS cash_debit, a.name AS responsible, t.description AS description
|
||||||
FROM transactions AS t
|
FROM transactions AS t
|
||||||
LEFT JOIN (SELECT cts.amount AS credit_cash, cts.transaction AS cts_t
|
LEFT JOIN (SELECT cts.amount AS credit_cash, cts.transaction AS cts_t
|
||||||
FROM transaction_nicesplits AS cts
|
FROM transaction_nicesplits AS cts
|
||||||
|
|
@ -124,4 +123,5 @@ CREATE VIEW transaction_cashsums AS
|
||||||
WHERE a.currency = (SELECT currency FROM accounts WHERE name = 'BrmBar Cash')
|
WHERE a.currency = (SELECT currency FROM accounts WHERE name = 'BrmBar Cash')
|
||||||
AND a.acctype IN ('cash', 'debt')
|
AND a.acctype IN ('cash', 'debt')
|
||||||
AND dts.amount > 0) debit ON dts_t = t.id
|
AND dts.amount > 0) debit ON dts_t = t.id
|
||||||
GROUP BY t.id ORDER BY t.id;
|
LEFT JOIN accounts AS a ON a.id = t.responsible
|
||||||
|
GROUP BY t.id, a.name ORDER BY t.id DESC;
|
||||||
|
|
|
||||||
57
brmbar3/SQL-for-RO-access.sql
Normal file
57
brmbar3/SQL-for-RO-access.sql
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
CREATE OR REPLACE FUNCTION accounts_id_seq_value()
|
||||||
|
RETURNS bigint
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
result bigint;
|
||||||
|
BEGIN
|
||||||
|
SELECT last_value FROM accounts_id_seq
|
||||||
|
INTO result;
|
||||||
|
RETURN result;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION transactions_id_seq_value()
|
||||||
|
RETURNS bigint
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
result bigint;
|
||||||
|
BEGIN
|
||||||
|
SELECT last_value FROM transactions_id_seq
|
||||||
|
INTO result;
|
||||||
|
RETURN result;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION transaction_splits_id_seq_value()
|
||||||
|
RETURNS bigint
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
result bigint;
|
||||||
|
BEGIN
|
||||||
|
SELECT last_value FROM transaction_splits_id_seq
|
||||||
|
INTO result;
|
||||||
|
RETURN result;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION currencies_id_seq_value()
|
||||||
|
RETURNS bigint
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
result bigint;
|
||||||
|
BEGIN
|
||||||
|
SELECT last_value FROM currencies_id_seq
|
||||||
|
INTO result;
|
||||||
|
RETURN result;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
59
brmbar3/SQL-schema-v001.sql
Normal file
59
brmbar3/SQL-schema-v001.sql
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
--RESET search_path;
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
-- intoduce implementation schema
|
||||||
|
CREATE SCHEMA IF NOT EXISTS brmbar_implementation;
|
||||||
|
-- version table (with initialization)
|
||||||
|
CREATE TABLE IF NOT EXISTS brmbar_implementation.brmbar_schema (
|
||||||
|
ver INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
DO $$
|
||||||
|
DECLARE v INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT ver FROM brmbar_implementation.brmbar_schema INTO v;
|
||||||
|
IF v IS NULL THEN
|
||||||
|
INSERT INTO brmbar_implementation.brmbar_schema (ver) VALUES (1);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_implementation.has_exact_schema_version(
|
||||||
|
IN i_ver INTEGER NOT NULL
|
||||||
|
) RETURNS INTEGER
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $$
|
||||||
|
DECLARE
|
||||||
|
v_ver INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT ver INTO STRICT v_ver FROM brmbar_implementation.brmbar_schema;
|
||||||
|
IF v_ver IS NULL or v_ver <> i_ver THEN
|
||||||
|
RAISE EXCEPTION 'Invalid brmbar schema version';
|
||||||
|
END IF;
|
||||||
|
RETURN v_ver;
|
||||||
|
/*
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
RAISE EXCEPTION 'PID % not found';
|
||||||
|
WHEN TOO_MANY_ROWS THEN
|
||||||
|
RAISE EXCEPTION 'PID % not unique';
|
||||||
|
*/
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_implementation.upgrade_schema_version_to(
|
||||||
|
IN i_ver INTEGER NOT NULL
|
||||||
|
) RETURNS INTEGER
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $$
|
||||||
|
DECLARE
|
||||||
|
v_ver INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT brmbar_implementation.has_exact_schema_version(i_ver) INTO v_ver;
|
||||||
|
IF v_ver + 1 = i_ver THEN
|
||||||
|
UPDATE brmbar_implementation.brmbar_schema SET ver = i_ver;
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'Invalid brmbar schema version';
|
||||||
|
END IF;
|
||||||
|
RETURN i_ver;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
|
|
||||||
61
brmbar3/SQL-schema-v002.sql
Normal file
61
brmbar3/SQL-schema-v002.sql
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
--RESET search_path
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
--- upgrade schema
|
||||||
|
DO $upgrade_block$
|
||||||
|
DECLARE
|
||||||
|
current_ver INTEGER;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- confirm that we are upgrading from version 1
|
||||||
|
SELECT brmbar_implementation.has_exact_schema_version(1) INTO current_ver;
|
||||||
|
IF current_ver <> 1 THEN
|
||||||
|
RAISE EXCEPTION 'BrmBar schema version % cannot be upgraded to version 2.', current_ver;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- structural changes
|
||||||
|
|
||||||
|
-- TRADING ACCOUNTS
|
||||||
|
--START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
||||||
|
|
||||||
|
-- currency trading accounts - account type
|
||||||
|
ALTER TYPE public.account_type ADD VALUE IF NOT EXISTS 'trading';
|
||||||
|
|
||||||
|
-- constraint needed for foreign key in currencies table
|
||||||
|
ALTER TABLE public.accounts ADD CONSTRAINT accounts_id_acctype_key UNIQUE(id, acctype);
|
||||||
|
|
||||||
|
-- add columns to currencies to record the trading account associated with the currency
|
||||||
|
ALTER TABLE public.currencies
|
||||||
|
ADD COLUMN IF NOT EXISTS trading_account integer,
|
||||||
|
ADD COLUMN IF NOT EXISTS trading_account_type account_type GENERATED ALWAYS AS ('trading'::public.account_type) STORED;
|
||||||
|
|
||||||
|
-- make trading accounts (without making duplicates)
|
||||||
|
INSERT INTO public.accounts ("name", "currency", acctype)
|
||||||
|
SELECT
|
||||||
|
'Currency Trading Account: ' || c."name",
|
||||||
|
c.id,
|
||||||
|
'trading'::public.account_type
|
||||||
|
FROM public.currencies AS c
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM public.accounts a
|
||||||
|
WHERE a.currency = c.id AND a.acctype = 'trading'::public.account_type
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- record the trading account IDs in currencies table
|
||||||
|
UPDATE public.currencies AS c SET (trading_account) = (SELECT a.id FROM public.accounts AS a WHERE a.currency = c.id AND c.acctype = 'trading'::public.account_type);
|
||||||
|
|
||||||
|
-- foreign key to check the validity of currency trading account reference
|
||||||
|
ALTER TABLE public.currencies
|
||||||
|
ADD CONSTRAINT currencies_trading_fkey FOREIGN KEY (trading_account, trading_account_type)
|
||||||
|
REFERENCES xaccounts(id,acctype) DEFERRABLE INITIALLY DEFERRED;
|
||||||
|
|
||||||
|
--COMMIT AND CHAIN;
|
||||||
|
|
||||||
|
SELECT brmbar_implementation.upgrade_schema_version_to(2) INTO current_ver;
|
||||||
|
-- end of upgrade do block
|
||||||
|
end
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
|
|
@ -68,12 +68,53 @@ Administrative Usage
|
||||||
account (and so will typing "joehacker" on a physical keyboard).
|
account (and so will typing "joehacker" on a physical keyboard).
|
||||||
|
|
||||||
* If your inventory stock count or cash box amount does not match
|
* If your inventory stock count or cash box amount does not match
|
||||||
the in-system data, you will need to make a corrective transaction.
|
the in-system data, you will need to make a corrective transaction.
|
||||||
In the future, brmbar-cli.py will support this, but there is no
|
To fix cash amount to reality in which you counted 1234Kč, use
|
||||||
implementation yet; it's not entirely clear yet what is the proper
|
|
||||||
way to do this from the accounting standpoint. In the meantime, you
|
./brmbar-cli.py fixcash 1234
|
||||||
can use SQL INSERTs to manually create a transaction with appropriate
|
|
||||||
transaction splits (see doc/architecture for details on splits).
|
whereas to fix amount of a particular stock, use
|
||||||
|
|
||||||
|
./brmbar-cli.py inventory-interactive
|
||||||
|
|
||||||
|
then scan the item barcode and then enter the right amount.
|
||||||
|
|
||||||
|
* If you want to view recent transactions, run
|
||||||
|
|
||||||
|
psql brmbar
|
||||||
|
select * from transaction_cashsums;
|
||||||
|
|
||||||
|
* If you want to undo a transaction, get its id (using the select above)
|
||||||
|
and run
|
||||||
|
|
||||||
|
./brmbar-cli.py undo ID
|
||||||
|
|
||||||
|
* If you want to get overview of the financial situation, run
|
||||||
|
|
||||||
|
./brmbar-cli.py stats
|
||||||
|
|
||||||
|
The following items represent "material", "tangible" assets:
|
||||||
|
|
||||||
|
* Cash - how much should be in the money box
|
||||||
|
* Overflow - how much cash is stored in overflow credit accounts (pockets of admins)
|
||||||
|
* Inventory - how much worth (buy price) is the current inventory stock
|
||||||
|
|
||||||
|
I.e., cash plus overflow plus inventory is how much brmbar is worth
|
||||||
|
and cash plus overflow is how much brmbar can spend right now.
|
||||||
|
|
||||||
|
The following items represent "virtual" accounts which determine
|
||||||
|
the logical composition of the assets:
|
||||||
|
|
||||||
|
* Credit - sum of all credit accounts, i.e. money stored in brmbar by its users;
|
||||||
|
i.e. how much of the assets is users' money
|
||||||
|
* Profit - accumulated profit made by brmbar on buy/sell margins (but receipts
|
||||||
|
and inventory deficits are subtracted); i.e. how much of the assets is brmbar's
|
||||||
|
own money
|
||||||
|
* Fixups - sum of gains and losses accrued by inventory fixups, i.e. stemming
|
||||||
|
from differences between accounting and reality - positive is good, negative
|
||||||
|
is bad; this amount is added to profit on consolidation
|
||||||
|
|
||||||
|
The total worth of the material and virtual accounts should be equal.
|
||||||
|
|
||||||
|
|
||||||
Useful SQL queries
|
Useful SQL queries
|
||||||
|
|
|
||||||
8
brmbar3/USEFUL.txt
Normal file
8
brmbar3/USEFUL.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
Accounts with multiple barcodes:
|
||||||
|
|
||||||
|
SELECT accounts.name,barcodes.account,barcodes.barcode
|
||||||
|
FROM "barcodes"
|
||||||
|
join accounts on accounts.id = barcodes.account
|
||||||
|
where barcodes.account in (select a from (select count(*) as c, account as a from barcodes group by account) as dt where c > 1)
|
||||||
|
ORDER BY "account" DESC
|
||||||
|
|
||||||
7
brmbar3/alert.sh
Executable file
7
brmbar3/alert.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
alert) mplayer -really-quiet ~/trombone.wav & ;;
|
||||||
|
limit) mplayer -really-quiet ~/much.wav & ;;
|
||||||
|
charge) mplayer -really-quiet ~/charge.wav & ;;
|
||||||
|
esac
|
||||||
45
brmbar3/autostock.py
Executable file
45
brmbar3/autostock.py
Executable file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
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")
|
||||||
|
parser.add_argument("filename")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
db = Database.Database("dbname=brmbar")
|
||||||
|
shop = brmbar.Shop.new_with_defaults(db)
|
||||||
|
currency = shop.currency
|
||||||
|
|
||||||
|
# ...
|
||||||
|
total = 0
|
||||||
|
with open(args.filename) as fin:
|
||||||
|
for line in fin:
|
||||||
|
split = line.split(" ")
|
||||||
|
ean, amount, price_total, name = split[0], int(split[1]), float(split[2]), " ".join(split[3:])
|
||||||
|
name = name.strip()
|
||||||
|
|
||||||
|
price_buy = price_total / amount
|
||||||
|
acct = brmbar.Account.load_by_barcode(db, ean)
|
||||||
|
if not acct:
|
||||||
|
print("Creating account for EAN {} '{}'".format(ean, name))
|
||||||
|
invcurr = brmbar.Currency.create(db, name)
|
||||||
|
acct = brmbar.Account.create(db, name, invcurr, "inventory")
|
||||||
|
acct.add_barcode(ean)
|
||||||
|
price_sell = max(math.ceil(price_buy * 1.15), price_buy)
|
||||||
|
acct.currency.update_sell_rate(currency, price_sell)
|
||||||
|
acct.currency.update_buy_rate(currency, price_buy)
|
||||||
|
cash = shop.buy_for_cash(acct, amount)
|
||||||
|
total += cash
|
||||||
|
print("Increased by {}, take {} from cashbox".format(amount, cash))
|
||||||
|
print("Total is {}".format(total))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("!!! THIS PROGRAM NO LONGER WORKS !!!")
|
||||||
|
sys.exit(1)
|
||||||
|
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
|
||||||
|
|
@ -15,9 +17,11 @@ Usage: brmbar-cli.py COMMAND ARGS...
|
||||||
1. Commands pertaining the standard operation
|
1. Commands pertaining the standard operation
|
||||||
showcredit USER
|
showcredit USER
|
||||||
changecredit USER +-AMT
|
changecredit USER +-AMT
|
||||||
sellitem USER ITEM +-AMT
|
sellitem {USER|"cash"} ITEM +-AMT
|
||||||
You can use negative AMT to undo a sale.
|
You can use negative AMT to undo a sale.
|
||||||
|
restock ITEM AMT
|
||||||
userinfo USER
|
userinfo USER
|
||||||
|
userlog USER TIMESTAMP
|
||||||
iteminfo ITEM
|
iteminfo ITEM
|
||||||
|
|
||||||
2. Management commands
|
2. Management commands
|
||||||
|
|
@ -30,11 +34,17 @@ Usage: brmbar-cli.py COMMAND ARGS...
|
||||||
screen of the GUI.
|
screen of the GUI.
|
||||||
adduser USER
|
adduser USER
|
||||||
Add user (debt) account with given username.
|
Add user (debt) account with given username.
|
||||||
|
undo TRANSID
|
||||||
|
Commit a transaction that reverses all splits of a transaction with
|
||||||
|
a given id (to find out that id: select * from transaction_cashsums;)
|
||||||
|
|
||||||
|
3. Inventorization
|
||||||
|
|
||||||
inventory ITEM1 NEW_AMOUNT1 ITEM2 NEW_AMOUNT2
|
inventory ITEM1 NEW_AMOUNT1 ITEM2 NEW_AMOUNT2
|
||||||
Inventory recounting (fixing the number of items)
|
Inventory recounting (fixing the number of items)
|
||||||
inventory-interactive
|
inventory-interactive
|
||||||
Launches interactive mode for performing inventory with barcode reader
|
Launches interactive mode for performing inventory with barcode reader
|
||||||
changecash AMT
|
fixcash AMT
|
||||||
Fixes the cash and puts money difference into excess or deficit account
|
Fixes the cash and puts money difference into excess or deficit account
|
||||||
consolidate
|
consolidate
|
||||||
Wraps up inventory + cash recounting, transferring the excess and
|
Wraps up inventory + cash recounting, transferring the excess and
|
||||||
|
|
@ -48,7 +58,19 @@ For users, you can use their name as USER as their username
|
||||||
is also the barcode. For items, use listitems command first
|
is also the barcode. For items, use listitems command first
|
||||||
to find out the item id.
|
to find out the item id.
|
||||||
|
|
||||||
Commands prefixed with ! are not implemented yet.""")
|
EXAMPLES:
|
||||||
|
|
||||||
|
Transfer 35Kc from pasky to sachy:
|
||||||
|
|
||||||
|
$ ./brmbar-cli.py changecredit pasky -35
|
||||||
|
$ ./brmbar-cli.py changecredit sachy +35
|
||||||
|
|
||||||
|
Buy one RaspberryPi for cash from commandline:
|
||||||
|
|
||||||
|
$ ./brmbar-cli.py listitems | grep -i raspberry
|
||||||
|
Raspberry Pi 2 1277 1.00 pcs
|
||||||
|
$ ./brmbar-cli.py sellitem cash 1277 1
|
||||||
|
""")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,6 +99,12 @@ def load_item(inp):
|
||||||
exit(1)
|
exit(1)
|
||||||
return acct
|
return acct
|
||||||
|
|
||||||
|
def load_item_by_barcode(inp):
|
||||||
|
acct = brmbar.Account.load_by_barcode(db, inp)
|
||||||
|
if acct.acctype != "inventory":
|
||||||
|
print("Bad EAN " + inp + " type " + acct.acctype, file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
return acct
|
||||||
|
|
||||||
db = Database.Database("dbname=brmbar")
|
db = Database.Database("dbname=brmbar")
|
||||||
shop = brmbar.Shop.new_with_defaults(db)
|
shop = brmbar.Shop.new_with_defaults(db)
|
||||||
|
|
@ -100,14 +128,20 @@ elif sys.argv[1] == "changecredit":
|
||||||
print("{}: {}".format(acct.name, acct.negbalance_str()))
|
print("{}: {}".format(acct.name, acct.negbalance_str()))
|
||||||
|
|
||||||
elif sys.argv[1] == "sellitem":
|
elif sys.argv[1] == "sellitem":
|
||||||
|
if sys.argv[2] == "cash":
|
||||||
|
uacct = shop.cash
|
||||||
|
else:
|
||||||
uacct = load_user(sys.argv[2])
|
uacct = load_user(sys.argv[2])
|
||||||
iacct = load_item(sys.argv[3])
|
iacct = load_item(sys.argv[3])
|
||||||
amt = int(sys.argv[4])
|
amt = int(sys.argv[4])
|
||||||
if amt > 0:
|
if amt > 0:
|
||||||
|
if uacct == shop.cash:
|
||||||
|
shop.sell_for_cash(item = iacct, amount = amt)
|
||||||
|
else:
|
||||||
shop.sell(item = iacct, user = uacct, amount = amt)
|
shop.sell(item = iacct, user = uacct, amount = amt)
|
||||||
elif amt < 0:
|
elif amt < 0:
|
||||||
shop.undo_sale(item = iacct, user = uacct, amount = -amt)
|
shop.undo_sale(item = iacct, user = uacct, amount = -amt)
|
||||||
print("{}: {}".format(uacct.name, uacct.negbalance_str()))
|
print("{}: {}".format(uacct.name, uacct.balance_str() if uacct == shop.cash else uacct.negbalance_str()))
|
||||||
print("{}: {}".format(iacct.name, iacct.balance_str()))
|
print("{}: {}".format(iacct.name, iacct.balance_str()))
|
||||||
|
|
||||||
elif sys.argv[1] == "userinfo":
|
elif sys.argv[1] == "userinfo":
|
||||||
|
|
@ -117,6 +151,14 @@ elif sys.argv[1] == "userinfo":
|
||||||
res = db.execute_and_fetchall("SELECT barcode FROM barcodes WHERE account = %s", [acct.id])
|
res = db.execute_and_fetchall("SELECT barcode FROM barcodes WHERE account = %s", [acct.id])
|
||||||
print("Barcodes: " + ", ".join(map((lambda r: r[0]), res)))
|
print("Barcodes: " + ", ".join(map((lambda r: r[0]), res)))
|
||||||
|
|
||||||
|
elif sys.argv[1] == "userlog":
|
||||||
|
acct = load_user(sys.argv[2])
|
||||||
|
timestamp = sys.argv[3]
|
||||||
|
|
||||||
|
res = db.execute_and_fetchall("SELECT * FROM transaction_cashsums WHERE responsible=%s and time > TIMESTAMP %s ORDER BY time", [acct.name,timestamp])
|
||||||
|
for transaction in res:
|
||||||
|
print('\t'.join([str(f) for f in transaction]))
|
||||||
|
|
||||||
elif sys.argv[1] == "iteminfo":
|
elif sys.argv[1] == "iteminfo":
|
||||||
acct = load_item(sys.argv[2])
|
acct = load_item(sys.argv[2])
|
||||||
print("{} (id {}): {} pcs".format(acct.name, acct.id, acct.balance()))
|
print("{} (id {}): {} pcs".format(acct.name, acct.id, acct.balance()))
|
||||||
|
|
@ -136,18 +178,27 @@ elif sys.argv[1] == "listitems":
|
||||||
print("{}\t{}\t{} pcs".format(acct.name, acct.id, acct.balance()))
|
print("{}\t{}\t{} pcs".format(acct.name, acct.id, acct.balance()))
|
||||||
|
|
||||||
elif sys.argv[1] == "stats":
|
elif sys.argv[1] == "stats":
|
||||||
|
print("--- Material Assets ---")
|
||||||
print("Cash: {}".format(shop.cash.balance_str()))
|
print("Cash: {}".format(shop.cash.balance_str()))
|
||||||
print("Profit: {}".format(shop.profits.balance_str()))
|
print("Overflow: {}".format(shop.currency.str(shop.credit_balance(overflow='only'))))
|
||||||
print("Credit: {}".format(shop.credit_negbalance_str()))
|
|
||||||
print("Inventory: {}".format(shop.inventory_balance_str()))
|
print("Inventory: {}".format(shop.inventory_balance_str()))
|
||||||
print("Excess: {}".format(shop.excess.negbalance_str()))
|
print("--- Logical Accounts ---")
|
||||||
print("Deficit: {}".format(shop.deficit.balance_str()))
|
print("Credit: {}".format(shop.credit_negbalance_str(overflow='exclude')))
|
||||||
|
print("Profit: {}".format(shop.profits.balance_str()))
|
||||||
|
print("Fixups: {} (excess {}, deficit {})".format(
|
||||||
|
-shop.excess.balance() - shop.deficit.balance(),
|
||||||
|
shop.excess.negbalance_str(),
|
||||||
|
shop.deficit.balance_str()))
|
||||||
|
|
||||||
elif sys.argv[1] == "adduser":
|
elif sys.argv[1] == "adduser":
|
||||||
acct = brmbar.Account.create(db, sys.argv[2], brmbar.Currency.load(db, id = 1), 'debt')
|
acct = brmbar.Account.create(db, sys.argv[2], brmbar.Currency.load(db, id = 1), 'debt')
|
||||||
acct.add_barcode(sys.argv[2]) # will commit
|
acct.add_barcode(sys.argv[2]) # will commit
|
||||||
print("{}: id {}".format(acct.name, acct.id));
|
print("{}: id {}".format(acct.name, acct.id));
|
||||||
|
|
||||||
|
elif sys.argv[1] == "undo":
|
||||||
|
newtid = shop.undo(int(sys.argv[2]))
|
||||||
|
print("Transaction %d undone by reverse transaction %d" % (int(sys.argv[2]), newtid))
|
||||||
|
|
||||||
elif sys.argv[1] == "inventory":
|
elif sys.argv[1] == "inventory":
|
||||||
if (len(sys.argv) % 2 != 0 or len(sys.argv) < 4):
|
if (len(sys.argv) % 2 != 0 or len(sys.argv) < 4):
|
||||||
print ("Invalid number of parameters, count your parameters.")
|
print ("Invalid number of parameters, count your parameters.")
|
||||||
|
|
@ -165,26 +216,27 @@ elif sys.argv[1] == "inventory":
|
||||||
elif sys.argv[1] == "inventory-interactive":
|
elif sys.argv[1] == "inventory-interactive":
|
||||||
print("Inventory interactive mode. To exit interactive mode just enter empty barcode")
|
print("Inventory interactive mode. To exit interactive mode just enter empty barcode")
|
||||||
|
|
||||||
keep_entering = True
|
while True:
|
||||||
while keep_entering:
|
|
||||||
barcode = str(input("Enter barcode:"))
|
barcode = str(input("Enter barcode:"))
|
||||||
fuckyou = input("fuckyou")
|
fuckyou = input("fuckyou")
|
||||||
if barcode == "":
|
if barcode == "":
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
iacct = brmbar.Account.load_by_barcode(db, barcode)
|
iacct = brmbar.Account.load_by_barcode(db, barcode)
|
||||||
amount = str(input("What is the amount of {} in reality current is {}:".format(iacct.name, iacct.balance())))
|
amount = str(input("What is the amount of {} in reality (expected: {} pcs):".format(iacct.name, iacct.balance())))
|
||||||
if amount == "":
|
if amount == "":
|
||||||
break
|
break
|
||||||
|
elif int(amount) > 10000:
|
||||||
|
print("Ignoring too high amount {}, assuming barcode was mistakenly scanned instead".format(amount))
|
||||||
else:
|
else:
|
||||||
iamt = int(amount)
|
iamt = int(amount)
|
||||||
print("Current state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
print("Current state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
||||||
if shop.fix_inventory(item = iacct, amount = iamt):
|
if shop.fix_inventory(item = iacct, amount = iamt):
|
||||||
print("New state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
print("New state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
||||||
else:
|
else:
|
||||||
print ("No action needed amount is correct.")
|
print("No action needed, amount is correct.")
|
||||||
print("End of processing. Bye")
|
print("End of processing. Bye")
|
||||||
elif sys.argv[1] == "changecash":
|
|
||||||
|
elif sys.argv[1] == "fixcash" or sys.argv[1] == "changecash":
|
||||||
if (len(sys.argv) != 3):
|
if (len(sys.argv) != 3):
|
||||||
print ("Invalid number of parameters, check your parameters.")
|
print ("Invalid number of parameters, check your parameters.")
|
||||||
else:
|
else:
|
||||||
|
|
@ -194,12 +246,23 @@ elif sys.argv[1] == "changecash":
|
||||||
print("New Cash is : {}".format(shop.cash.balance_str()))
|
print("New Cash is : {}".format(shop.cash.balance_str()))
|
||||||
else:
|
else:
|
||||||
print ("No action needed amount is the same.")
|
print ("No action needed amount is the same.")
|
||||||
|
|
||||||
elif sys.argv[1] == "consolidate":
|
elif sys.argv[1] == "consolidate":
|
||||||
if (len(sys.argv) != 2):
|
if (len(sys.argv) != 2):
|
||||||
print ("Invalid number of parameters, check your parameters.")
|
print ("Invalid number of parameters, check your parameters.")
|
||||||
else:
|
else:
|
||||||
shop.consolidate()
|
shop.consolidate()
|
||||||
|
|
||||||
|
elif sys.argv[1] in {"restock", "restock_ean"}:
|
||||||
|
if (len(sys.argv) != 4):
|
||||||
|
print ("Invalid number of parameters, check your parameters.")
|
||||||
|
else:
|
||||||
|
iacct = (load_item if sys.argv[1] == "restock" else load_item_by_barcode)(sys.argv[2])
|
||||||
|
oldbal = iacct.balance()
|
||||||
|
amt = int(sys.argv[3])
|
||||||
|
cash = shop.buy_for_cash(iacct, amt);
|
||||||
|
print("Old amount {}, increased by {}, take {} from cashbox".format(oldbal, amt, cash))
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
help()
|
help()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from PySide import QtCore, QtGui, QtDeclarative
|
from PySide import QtCore, QtGui, QtDeclarative
|
||||||
|
|
||||||
|
|
@ -8,6 +9,29 @@ from brmbar import Database
|
||||||
|
|
||||||
import brmbar
|
import brmbar
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
root = logging.getLogger()
|
||||||
|
root.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
root.addHandler(handler)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# User credit balance limit; sale will fail when balance is below this limit.
|
||||||
|
LIMIT_BALANCE = -200
|
||||||
|
# When below this credit balance, an alert hook script (see below) is run.
|
||||||
|
ALERT_BALANCE = 0
|
||||||
|
# This script is executed when a user is buying things and their balance is
|
||||||
|
# below LIMIT_BALANCE (with argument "limit") or below ALERT_BALANCE
|
||||||
|
# (with argument "alert").
|
||||||
|
ALERT_SCRIPT = "./alert.sh"
|
||||||
|
|
||||||
|
|
||||||
class ShopAdapter(QtCore.QObject):
|
class ShopAdapter(QtCore.QObject):
|
||||||
""" Interface between QML and the brmbar package """
|
""" Interface between QML and the brmbar package """
|
||||||
|
|
@ -29,13 +53,23 @@ class ShopAdapter(QtCore.QObject):
|
||||||
map["price"] = str(sell)
|
map["price"] = str(sell)
|
||||||
return map
|
return map
|
||||||
|
|
||||||
|
def acct_inventory_map2(self, acct):
|
||||||
|
buy, sell = 666, 666
|
||||||
|
map = acct.__dict__.copy()
|
||||||
|
map["balance"] = "{:.0f}".format(666)
|
||||||
|
map["buy_price"] = str(buy)
|
||||||
|
map["price"] = str(sell)
|
||||||
|
return map
|
||||||
|
|
||||||
def acct_cash_map(self, acct):
|
def acct_cash_map(self, acct):
|
||||||
map = acct.__dict__.copy()
|
map = acct.__dict__.copy()
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def acct_map(self, acct):
|
def acct_map(self, acct):
|
||||||
if acct is None:
|
if acct is None:
|
||||||
|
logger.debug("acct_map: acct is None")
|
||||||
return None
|
return None
|
||||||
|
logger.debug("acct_map: acct.acctype=%s", acct.acctype)
|
||||||
if acct.acctype == 'debt':
|
if acct.acctype == 'debt':
|
||||||
return self.acct_debt_map(acct)
|
return self.acct_debt_map(acct)
|
||||||
elif acct.acctype == "inventory":
|
elif acct.acctype == "inventory":
|
||||||
|
|
@ -54,12 +88,14 @@ class ShopAdapter(QtCore.QObject):
|
||||||
Therefore, we construct a map that we can pass around easily.
|
Therefore, we construct a map that we can pass around easily.
|
||||||
We return None on unrecognized barcode. """
|
We return None on unrecognized barcode. """
|
||||||
barcode = str(barcode)
|
barcode = str(barcode)
|
||||||
|
logger.debug("barcodeInput: barcode='%s'", barcode)
|
||||||
if barcode and barcode[0] == "$":
|
if barcode and barcode[0] == "$":
|
||||||
credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000}
|
credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000}
|
||||||
credit = credits[barcode]
|
credit = credits[barcode]
|
||||||
if credit is None:
|
if credit is None:
|
||||||
return None
|
return None
|
||||||
return { "acctype": "recharge", "amount": str(credit)+".00" }
|
return { "acctype": "recharge", "amount": str(credit)+".00" }
|
||||||
|
logger.debug("barcodeInput: before load_by_barcode")
|
||||||
acct = self.acct_map(brmbar.Account.load_by_barcode(db, barcode))
|
acct = self.acct_map(brmbar.Account.load_by_barcode(db, barcode))
|
||||||
db.commit()
|
db.commit()
|
||||||
return acct
|
return acct
|
||||||
|
|
@ -70,6 +106,18 @@ class ShopAdapter(QtCore.QObject):
|
||||||
db.commit()
|
db.commit()
|
||||||
return acct
|
return acct
|
||||||
|
|
||||||
|
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||||
|
def canSellItem(self, itemid, userid):
|
||||||
|
user = brmbar.Account.load(db, id = userid)
|
||||||
|
if -user.balance() > ALERT_BALANCE:
|
||||||
|
return True
|
||||||
|
elif -user.balance() > LIMIT_BALANCE:
|
||||||
|
subprocess.call(["sh", ALERT_SCRIPT, "alert"])
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
subprocess.call(["sh", ALERT_SCRIPT, "limit"])
|
||||||
|
return False
|
||||||
|
|
||||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||||
def sellItem(self, itemid, userid):
|
def sellItem(self, itemid, userid):
|
||||||
user = brmbar.Account.load(db, id = userid)
|
user = brmbar.Account.load(db, id = userid)
|
||||||
|
|
@ -85,6 +133,7 @@ class ShopAdapter(QtCore.QObject):
|
||||||
|
|
||||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||||
def chargeCredit(self, credit, userid):
|
def chargeCredit(self, credit, userid):
|
||||||
|
subprocess.call(["sh", ALERT_SCRIPT, "charge"])
|
||||||
user = brmbar.Account.load(db, id = userid)
|
user = brmbar.Account.load(db, id = userid)
|
||||||
shop.add_credit(credit = credit, user = user)
|
shop.add_credit(credit = credit, user = user)
|
||||||
balance = user.negbalance_str()
|
balance = user.negbalance_str()
|
||||||
|
|
@ -99,23 +148,45 @@ class ShopAdapter(QtCore.QObject):
|
||||||
db.commit()
|
db.commit()
|
||||||
return balance
|
return balance
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
@QtCore.Slot('QVariant', result='QVariant')
|
||||||
|
def balance_user(self, userid):
|
||||||
|
user = brmbar.Account.load(db, id=userid)
|
||||||
|
return user.negbalance_str()
|
||||||
|
|
||||||
@QtCore.Slot(result='QVariant')
|
@QtCore.Slot(result='QVariant')
|
||||||
def balance_cash(self):
|
def balance_cash(self):
|
||||||
|
return "N/A"
|
||||||
balance = shop.cash.balance_str()
|
balance = shop.cash.balance_str()
|
||||||
db.commit()
|
db.commit()
|
||||||
return balance
|
return balance
|
||||||
@QtCore.Slot(result='QVariant')
|
@QtCore.Slot(result='QVariant')
|
||||||
def balance_profit(self):
|
def balance_profit(self):
|
||||||
|
return "N/A"
|
||||||
balance = shop.profits.balance_str()
|
balance = shop.profits.balance_str()
|
||||||
db.commit()
|
db.commit()
|
||||||
return balance
|
return balance
|
||||||
@QtCore.Slot(result='QVariant')
|
@QtCore.Slot(result='QVariant')
|
||||||
def balance_inventory(self):
|
def balance_inventory(self):
|
||||||
|
return "N/A"
|
||||||
balance = shop.inventory_balance_str()
|
balance = shop.inventory_balance_str()
|
||||||
db.commit()
|
db.commit()
|
||||||
return balance
|
return balance
|
||||||
@QtCore.Slot(result='QVariant')
|
@QtCore.Slot(result='QVariant')
|
||||||
def balance_credit(self):
|
def balance_credit(self):
|
||||||
|
return "N/A"
|
||||||
balance = shop.credit_negbalance_str()
|
balance = shop.credit_negbalance_str()
|
||||||
db.commit()
|
db.commit()
|
||||||
return balance
|
return balance
|
||||||
|
|
@ -128,7 +199,7 @@ class ShopAdapter(QtCore.QObject):
|
||||||
|
|
||||||
@QtCore.Slot('QVariant', result='QVariant')
|
@QtCore.Slot('QVariant', result='QVariant')
|
||||||
def itemList(self, query):
|
def itemList(self, query):
|
||||||
alist = [ self.acct_inventory_map(a) for a in shop.account_list("inventory", like_str="%%"+query+"%%") ]
|
alist = [ self.acct_inventory_map2(a) for a in shop.account_list("inventory", like_str="%%"+query+"%%") ]
|
||||||
db.commit()
|
db.commit()
|
||||||
return alist
|
return alist
|
||||||
|
|
||||||
|
|
@ -180,7 +251,23 @@ class ShopAdapter(QtCore.QObject):
|
||||||
db.commit()
|
db.commit()
|
||||||
return balance
|
return balance
|
||||||
|
|
||||||
db = Database.Database("dbname=brmbar")
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--dbname", help="Database name", type=str)
|
||||||
|
parser.add_argument("--dbuser", help="Database user", type=str)
|
||||||
|
parser.add_argument("--dbhost", help="Database host", type=str)
|
||||||
|
parser.add_argument("--dbpass", help="Database user password", type=str)
|
||||||
|
args = parser.parse_args()
|
||||||
|
argdbname = args.dbname
|
||||||
|
argdbuser = args.dbuser
|
||||||
|
argdbhost = args.dbhost
|
||||||
|
argdbpass = args.dbpass
|
||||||
|
|
||||||
|
db = Database.Database(
|
||||||
|
"dbname={0} user={1} host={2} password={3}".format(
|
||||||
|
argdbname,argdbuser,argdbhost,argdbpass
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
shop = brmbar.Shop.new_with_defaults(db)
|
shop = brmbar.Shop.new_with_defaults(db)
|
||||||
currency = shop.currency
|
currency = shop.currency
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,17 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
function chargeCredit() {
|
function chargeCredit() {
|
||||||
var balance = shop.chargeCredit(amount, userdbid)
|
var balance=0
|
||||||
status_text.setStatus("Charged! "+username+"'s credit is "+balance+".", "#ffff7c")
|
if (!isNaN(amount)) {
|
||||||
|
if(amount>=0) {
|
||||||
|
balance = shop.chargeCredit(amount, userdbid)
|
||||||
|
status_text.setStatus("Charged "+amount+"! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||||
|
} else {
|
||||||
|
balance = shop.withdrawCredit((amount*(-1)), userdbid)
|
||||||
|
status_text.setStatus("Withdrawn "+amount+"! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||||
|
}
|
||||||
|
}
|
||||||
loadPage("MainPage")
|
loadPage("MainPage")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ Item {
|
||||||
if (acct.acctype == "cash") { //Copied from BarButton.onButtonClick
|
if (acct.acctype == "cash") { //Copied from BarButton.onButtonClick
|
||||||
shop.sellItemCash(dbid)
|
shop.sellItemCash(dbid)
|
||||||
status_text.setStatus("Sold! Put " + price + " Kč in the money box.", "#ffff7c")
|
status_text.setStatus("Sold! Put " + price + " Kč in the money box.", "#ffff7c")
|
||||||
|
} else if (!shop.canSellItem(dbid, acct.id)) {
|
||||||
|
status_text.setStatus("NOT SOLD! "+acct.name+"'s credit is TOO LOW: "+shop.balance_user(acct.id), "#ff4444")
|
||||||
} else {
|
} else {
|
||||||
var balance = shop.sellItem(dbid, acct.id)
|
var balance = shop.sellItem(dbid, acct.id)
|
||||||
status_text.setStatus("Sold! "+acct.name+"'s credit is "+balance+".", "#ffff7c")
|
status_text.setStatus("Sold! "+acct.name+"'s credit is "+balance+".", "#ffff7c")
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,16 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
x: 450
|
||||||
|
y: 838
|
||||||
|
width: 360
|
||||||
|
text: "Transfer"
|
||||||
|
onButtonClick: {
|
||||||
|
loadPage("Transfer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
id: management
|
id: management
|
||||||
x: 855
|
x: 855
|
||||||
|
|
@ -43,4 +53,11 @@ Item {
|
||||||
loadPage("Management")
|
loadPage("Management")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
x: 65
|
||||||
|
y: 438
|
||||||
|
width: 1150
|
||||||
|
text: "* Za uklid brmlabu vam nabijeme kredit. *"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
brmbar3/brmbar-gui-qt4/Transfer.qml
Normal file
174
brmbar3/brmbar-gui-qt4/Transfer.qml
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
import QtQuick 1.1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: page
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property variant userfrom: ""
|
||||||
|
property variant uidfrom: ""
|
||||||
|
property variant userto: ""
|
||||||
|
property variant uidto: ""
|
||||||
|
property string amount: amount_pad.enteredText
|
||||||
|
|
||||||
|
BarcodeInput {
|
||||||
|
color: "#00ff00" /* just for debugging */
|
||||||
|
focus: !(parent.userfrom != "" && parent.userto != "")
|
||||||
|
onAccepted: {
|
||||||
|
var acct = shop.barcodeInput(text)
|
||||||
|
text = ""
|
||||||
|
if (typeof(acct) == "undefined") {
|
||||||
|
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (acct.acctype == "debt") {
|
||||||
|
if (userfrom == "") {
|
||||||
|
userfrom = acct.name
|
||||||
|
uidfrom = acct.id
|
||||||
|
} else {
|
||||||
|
userto = acct.name
|
||||||
|
uidto = acct.id
|
||||||
|
}
|
||||||
|
} else if (acct.acctype == "recharge") {
|
||||||
|
amount = acct.amount
|
||||||
|
} else {
|
||||||
|
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: amount_row
|
||||||
|
visible: parent.userfrom != "" && parent.userto != ""
|
||||||
|
x: 65;
|
||||||
|
y: 166;
|
||||||
|
width: 890
|
||||||
|
height: 60
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: item_sellprice_label
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
height: 60
|
||||||
|
width: 200
|
||||||
|
color: "#ffffff"
|
||||||
|
text: "Money Amount:"
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: 0.768 * 46
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: amount_input
|
||||||
|
x: 320
|
||||||
|
y: 0
|
||||||
|
height: 60
|
||||||
|
width: 269
|
||||||
|
color: "#ffff7c"
|
||||||
|
text: amount
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: 0.768 * 122
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BarNumPad {
|
||||||
|
id: amount_pad
|
||||||
|
x: 65
|
||||||
|
y: 239
|
||||||
|
visible: parent.userfrom != "" && parent.userto != ""
|
||||||
|
focus: parent.userfrom != "" && parent.userto != ""
|
||||||
|
Keys.onReturnPressed: { transfer.buttonClick() }
|
||||||
|
Keys.onEscapePressed: { cancel.buttonClick() }
|
||||||
|
}
|
||||||
|
|
||||||
|
BarTextHint {
|
||||||
|
id: barcode_row
|
||||||
|
x: 65
|
||||||
|
y: parent.userfrom == "" ? 314 : 414
|
||||||
|
hint_goal: (parent.userfrom == "" ? "Take money from:" : parent.userto == "" ? "Give money to:" : parent.amount == "" ? "Specify amount" : "")
|
||||||
|
hint_action: (parent.userfrom == "" || parent.userto == "" ? "Scan barcode now" : (parent.amount ? "" : "(or scan barcode now)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: legend
|
||||||
|
visible: !(parent.userfrom != "" && parent.userto != "")
|
||||||
|
x: 65
|
||||||
|
y: 611
|
||||||
|
height: 154
|
||||||
|
width: 894
|
||||||
|
color: "#71cccc"
|
||||||
|
text: "This is for transfering credit between two brmbar users.\n May be used instead of *check next club-mate to me*."
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pixelSize: 0.768 * 27
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: item_name
|
||||||
|
x: 422
|
||||||
|
y: 156
|
||||||
|
width: 537
|
||||||
|
height: 80
|
||||||
|
color: "#ffffff"
|
||||||
|
text: parent.userfrom ? parent.userfrom + " →" : "Money Transfer"
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: 0.768 * 60
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: item_name2
|
||||||
|
x: 422
|
||||||
|
y: 256
|
||||||
|
width: 537
|
||||||
|
height: 80
|
||||||
|
color: "#ffffff"
|
||||||
|
text: parent.userto ? "→ " + parent.userto : ""
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.pixelSize: 0.768 * 60
|
||||||
|
}
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
id: transfer
|
||||||
|
x: 65
|
||||||
|
y: 838
|
||||||
|
width: 360
|
||||||
|
text: "Transfer"
|
||||||
|
onButtonClick: {
|
||||||
|
if (userfrom == "") {
|
||||||
|
status_text.setStatus("Select FROM account.", "#ff4444")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (userto == "") {
|
||||||
|
status_text.setStatus("Select TO account.", "#ff4444")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (amount == "") {
|
||||||
|
status_text.setStatus("Enter amount.", "#ff4444")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var amount_str = shop.newTransfer(uidfrom, uidto, amount)
|
||||||
|
if (typeof(amount_str) == "undefined") {
|
||||||
|
status_text.setStatus("Transfer error.", "#ff4444")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status_text.setStatus("Transferred " + amount_str + " from " + userfrom + " to " + userto, "#ffff7c")
|
||||||
|
loadPage("MainPage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
id: cancel
|
||||||
|
x: 855
|
||||||
|
y: 838
|
||||||
|
width: 360
|
||||||
|
text: "Cancel"
|
||||||
|
onButtonClick: {
|
||||||
|
status_text.setStatus("Transfer cancelled", "#ff4444")
|
||||||
|
loadPage("MainPage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -100,8 +100,17 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
function withdrawCredit() {
|
function withdrawCredit() {
|
||||||
var balance = shop.withdrawCredit(amount, userdbid)
|
var balance=0
|
||||||
status_text.setStatus("Withdrawn! "+username+"'s credit is "+balance+".", "#ffff7c")
|
if (!isNaN(amount)) {
|
||||||
|
amount=(amount*1)
|
||||||
|
if(amount>=0) {
|
||||||
|
balance = shop.withdrawCredit(amount, userdbid)
|
||||||
|
status_text.setStatus("Withdrawn "+amount+"! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||||
|
} else {
|
||||||
|
balance = shop.chargeCredit((amount*(-1)),userdbid)
|
||||||
|
status_text.setStatus("Charged "+amount+"! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||||
|
}
|
||||||
|
}
|
||||||
loadPage("MainPage")
|
loadPage("MainPage")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
@ -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,10 +1,15 @@
|
||||||
from .Currency import Currency
|
from .Currency import Currency
|
||||||
|
import logging
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -14,42 +19,35 @@ class Account:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_by_barcode(cls, db, barcode):
|
def load_by_barcode(cls, db, barcode):
|
||||||
res = db.execute_and_fetch("SELECT account FROM barcodes WHERE barcode = %s", [barcode])
|
logger.debug("load_by_barcode: '%s'", barcode)
|
||||||
if res is None:
|
account_id, account_name, account_acctype, currency_id, currency_name = db.execute_and_fetch(
|
||||||
return None
|
"SELECT account_id, account_name, account_acctype, currency_id, currency_name FROM public.account_class_initialization_data('by_barcode', NULL, %s)",
|
||||||
id = res[0]
|
[barcode])
|
||||||
return cls.load(db, id = id)
|
currency = Currency(db, currency_id, currency_name)
|
||||||
|
return cls(db, account_id, account_name, currency, account_acctype)
|
||||||
|
|
||||||
@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:
|
account_id, account_name, account_acctype, currency_id, currency_name = db.execute_and_fetch(
|
||||||
name = db.execute_and_fetch("SELECT name FROM accounts WHERE id = %s", [id])
|
"SELECT account_id, account_name, account_acctype, currency_id, currency_name FROM public.account_class_initialization_data('by_id', %s, NULL)",
|
||||||
name = name[0]
|
[id])
|
||||||
elif name is not None:
|
currency = Currency(db, currency_id, currency_name)
|
||||||
id = db.execute_and_fetch("SELECT id FROM accounts WHERE name = %s", [name])
|
return cls(db, account_id, account_name, currency, account_acctype)
|
||||||
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
|
@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 = id[0]
|
"SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype]
|
||||||
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):
|
||||||
debit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit'])
|
bal = self.db.execute_and_fetch(
|
||||||
debit = debit[0] or 0
|
"SELECT public.compute_account_balance(%s)", [self.id]
|
||||||
credit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit'])
|
)[0]
|
||||||
credit = credit[0] or 0
|
return bal
|
||||||
return debit - credit
|
|
||||||
|
|
||||||
def balance_str(self):
|
def balance_str(self):
|
||||||
return self.currency.str(self.balance())
|
return self.currency.str(self.balance())
|
||||||
|
|
@ -57,20 +55,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(
|
||||||
|
"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.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,63 +14,70 @@ 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:
|
||||||
|
raise NameError("Currency.load(): Specify id")
|
||||||
name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id])
|
name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id])
|
||||||
name = name[0]
|
name = name[0]
|
||||||
elif name is not None:
|
return cls(db, id=id, name=name)
|
||||||
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
|
@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 = 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
|
||||||
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 public.find_buy_rate(%s, %s)", [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("Something fishy in find_buy_rate.")
|
||||||
buy_rate, buy_rate_dir = res
|
buy = res[0]
|
||||||
buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate
|
if buy < 0:
|
||||||
|
raise NameError(
|
||||||
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])
|
"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]
|
||||||
|
)
|
||||||
if res is None:
|
if res is None:
|
||||||
raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name())
|
raise NameError("Something fishy in find_sell_rate.")
|
||||||
sell_rate, sell_rate_dir = res
|
sell = res[0]
|
||||||
sell = sell_rate if sell_rate_dir == "source_to_target" else 1/sell_rate
|
if sell < 0:
|
||||||
|
raise NameError(
|
||||||
|
"Currency.rate(): Unknown conversion "
|
||||||
|
+ self.name()
|
||||||
|
+ " to "
|
||||||
|
+ other.name()
|
||||||
|
)
|
||||||
|
|
||||||
return (buy, sell)
|
return (buy, sell)
|
||||||
|
|
||||||
def convert(self, amount, target):
|
|
||||||
res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [target.id, self.id])
|
|
||||||
if res is None:
|
|
||||||
raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + target.name())
|
|
||||||
rate, rate_dir = res
|
|
||||||
if rate_dir == "source_to_target":
|
|
||||||
resamount = amount * rate
|
|
||||||
else:
|
|
||||||
resamount = amount / rate
|
|
||||||
return resamount
|
|
||||||
|
|
||||||
def str(self, amount):
|
def str(self, amount):
|
||||||
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(
|
||||||
|
"SELECT public.update_currency_sell_rate(%s, %s, %s)",
|
||||||
|
[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(
|
||||||
|
"SELECT public.update_currency_buy_rate(%s, %s, %s)",
|
||||||
|
[self.id, source.id, rate],
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,30 @@
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
@ -30,31 +35,39 @@ class Database:
|
||||||
def _execute(self, cur, query, attrs, level=1):
|
def _execute(self, cur, query, attrs, level=1):
|
||||||
"""execute a query, and in case of OperationalError (db restart)
|
"""execute a query, and in case of OperationalError (db restart)
|
||||||
reconnect to database. Recurses with increasig pause between tries"""
|
reconnect to database. Recurses with increasig pause between tries"""
|
||||||
|
logger.debug("SQL: (%s) @%s" % (query, time.strftime("%Y%m%d %a %I:%m %p")))
|
||||||
try:
|
try:
|
||||||
if attrs is None:
|
if attrs is None:
|
||||||
cur.execute(query)
|
cur.execute(query)
|
||||||
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:
|
||||||
|
logger.debug("_execute exception: %s", ex)
|
||||||
|
self.db_conn.rollback()
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
"""passes commit to db"""
|
"""passes commit to db"""
|
||||||
|
|
|
||||||
|
|
@ -1,108 +1,168 @@
|
||||||
import brmbar
|
import brmbar
|
||||||
from .Currency import Currency
|
from .Currency import Currency
|
||||||
from .Account import Account
|
from .Account import Account
|
||||||
|
import logging
|
||||||
|
|
||||||
|
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):
|
||||||
# Sale: Currency conversion from item currency to shop currency
|
# Call the stored procedure for the sale
|
||||||
(buy, sell) = item.currency.rates(self.currency)
|
logger.debug(
|
||||||
cost = amount * sell
|
"sell: item.id=%s amount=%s user.id=%s self.currency.id=%s",
|
||||||
profit = amount * (sell - buy)
|
item.id,
|
||||||
|
amount,
|
||||||
transaction = self._transaction(responsible = user, description = "BrmBar sale of {}x {} to {}".format(amount, item.name, user.name))
|
user.id,
|
||||||
item.credit(transaction, amount, user.name)
|
self.currency.id,
|
||||||
user.debit(transaction, cost, item.name) # debit (increase) on a _debt_ account
|
)
|
||||||
self.profits.debit(transaction, profit, "Margin on " + item.name)
|
res = self.db.execute_and_fetch(
|
||||||
|
"SELECT public.sell_item(%s, %s, %s, %s, %s)",
|
||||||
|
[
|
||||||
|
item.id,
|
||||||
|
amount,
|
||||||
|
user.id,
|
||||||
|
self.currency.id,
|
||||||
|
"BrmBar sale of {0}x {1} to {2}".format(amount, item.name, user.name),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
logger.debug("sell: res[0]=%s", res[0])
|
||||||
|
cost = res[0]
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
return cost
|
return cost
|
||||||
|
|
||||||
def sell_for_cash(self, item, amount = 1):
|
def sell_for_cash(self, item, amount=1):
|
||||||
# Sale: Currency conversion from item currency to shop currency
|
cost = self.db.execute_and_fetch(
|
||||||
(buy, sell) = item.currency.rates(self.currency)
|
"SELECT public.sell_item_for_cash(%s, %s, %s, %s, %s)",
|
||||||
cost = amount * sell
|
[
|
||||||
profit = amount * (sell - buy)
|
item.id,
|
||||||
|
amount,
|
||||||
|
user.id,
|
||||||
|
self.currency.id,
|
||||||
|
"BrmBar sale of {0}x {1} for cash".format(amount, item.name),
|
||||||
|
],
|
||||||
|
)[0]
|
||||||
|
|
||||||
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()
|
self.db.commit()
|
||||||
|
|
||||||
return cost
|
return cost
|
||||||
|
|
||||||
def undo_sale(self, item, user, amount = 1):
|
def undo_sale(self, item, user, amount=1):
|
||||||
# Undo sale; rarely needed
|
# Call the stored procedure for undoing a sale
|
||||||
(buy, sell) = item.currency.rates(self.currency)
|
cost = self.db.execute_and_fetch(
|
||||||
cost = amount * sell
|
"SELECT public.undo_sale_of_item(%s, %s, %s, %s)",
|
||||||
profit = amount * (sell - buy)
|
[
|
||||||
|
item.id,
|
||||||
transaction = self._transaction(responsible = user, description = "BrmBar sale UNDO of {}x {} to {}".format(amount, item.name, user.name))
|
amount,
|
||||||
item.debit(transaction, amount, user.name + " (sale undo)")
|
user.id,
|
||||||
user.credit(transaction, cost, item.name + " (sale undo)")
|
user.currency.id,
|
||||||
self.profits.credit(transaction, profit, "Margin repaid on " + item.name)
|
"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):
|
||||||
transaction = self._transaction(responsible = user, description = "BrmBar credit replenishment for " + user.name)
|
self.db.execute_and_fetch(
|
||||||
self.cash.debit(transaction, credit, user.name)
|
"SELECT public.add_credit(%s, %s, %s, %s)",
|
||||||
user.credit(transaction, credit, "Credit replenishment")
|
[self.cash.id, credit, user.id, user.name],
|
||||||
|
)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def withdraw_credit(self, credit, user):
|
def withdraw_credit(self, credit, user):
|
||||||
transaction = self._transaction(responsible = user, description = "BrmBar credit withdrawal for " + user.name)
|
self.db.execute_and_fetch(
|
||||||
self.cash.credit(transaction, credit, user.name)
|
"SELECT public.withdraw_credit(%s, %s, %s, %s)",
|
||||||
user.debit(transaction, credit, "Credit withdrawal")
|
[self.cash.id, credit, user.id, user.name],
|
||||||
|
)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def buy_for_cash(self, item, amount = 1):
|
def transfer_credit(self, userfrom, userto, amount):
|
||||||
# Buy: Currency conversion from item currency to shop currency
|
self.db.execute_and_fetch(
|
||||||
(buy, sell) = item.currency.rates(self.currency)
|
"SELECT public.transfer_credit(%s, %s, %s, %s, %s, %s)",
|
||||||
cost = amount * buy
|
[self.cash.id, amount, userfrom.id, userfrom.name, userto.id, userto.name],
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
transaction = self._transaction(description = "BrmBar stock replenishment of {}x {} for cash".format(amount, item.name))
|
def buy_for_cash(self, item, amount=1):
|
||||||
item.debit(transaction, amount, "Cash")
|
iamount = int(amount)
|
||||||
self.cash.credit(transaction, cost, item.name)
|
famount = float(iamount)
|
||||||
|
assert famount == amount, "amount is not integer value %s".format(amount)
|
||||||
|
cost = self.db.execute_and_fetch(
|
||||||
|
"SELECT public.buy_for_cash(%s, %s, %s, %s, %s)",
|
||||||
|
[self.cash.id, item.id, iamount, self.currency.id, item.name],
|
||||||
|
)[0]
|
||||||
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.db.execute_and_fetch(
|
||||||
self.profits.credit(transaction, credit, user.name)
|
"SELECT public.receipt_reimbursement(%s, %s, %s, %s, %s)",
|
||||||
user.credit(transaction, credit, "Credit from receipt: " + description)
|
[self.profits.id, user.id, user.name, credit, description],
|
||||||
|
)[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
|
||||||
|
|
||||||
def credit_balance(self):
|
def credit_balance(self, overflow=None):
|
||||||
# We assume all debt accounts share a currency
|
# We assume all debt accounts share a currency
|
||||||
sumselect = """
|
sumselect = """
|
||||||
SELECT SUM(ts.amount)
|
SELECT SUM(ts.amount)
|
||||||
|
|
@ -110,95 +170,80 @@ class Shop:
|
||||||
LEFT JOIN transaction_splits AS ts ON a.id = ts.account
|
LEFT JOIN transaction_splits AS ts ON a.id = ts.account
|
||||||
WHERE a.acctype = %s AND ts.side = %s
|
WHERE a.acctype = %s AND ts.side = %s
|
||||||
"""
|
"""
|
||||||
cur = self.db.execute_and_fetch(sumselect, ["debt", 'debit'])
|
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"])
|
||||||
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):
|
|
||||||
return self.currency.str(-self.credit_balance())
|
def credit_negbalance_str(self, overflow=None):
|
||||||
|
return self.currency.str(-self.credit_balance(overflow=overflow))
|
||||||
|
|
||||||
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!
|
|
||||||
balance += inv.balance() * inv.currency.rates(self.currency)[0]
|
|
||||||
return balance
|
|
||||||
def inventory_balance_str(self):
|
def inventory_balance_str(self):
|
||||||
return self.currency.str(self.inventory_balance())
|
return self.currency.str(self.inventory_balance())
|
||||||
|
|
||||||
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 a.id, a.name aname, a.currency, a.acctype, c.name cname FROM accounts a JOIN currencies c ON c.id=a.currency WHERE a.acctype = %s AND a.name ILIKE %s ORDER BY a.name ASC",
|
||||||
|
[acctype, like_str],
|
||||||
|
)
|
||||||
|
# FIXME: sanitize input like_str ^
|
||||||
for inventory in cur:
|
for inventory in cur:
|
||||||
accts += [ Account.load(self.db, id = inventory[0]) ]
|
curr = Currency(db=self.db, id=inventory[2], name=inventory[4]);
|
||||||
|
accts += [Account(self.db, id=inventory[0], name=inventory[1], currency=curr, acctype=inventory[3])]
|
||||||
return accts
|
return accts
|
||||||
|
|
||||||
def fix_inventory(self, item, amount):
|
def fix_inventory(self, item, amount):
|
||||||
amount_in_reality = amount
|
rv = self.db.execute_and_fetch(
|
||||||
amount_in_system = item.balance()
|
"SELECT public.fix_inventory(%s, %s, %s, %s, %s, %s)",
|
||||||
(buy, sell) = item.currency.rates(self.currency)
|
[
|
||||||
|
item.id,
|
||||||
|
item.currency.id,
|
||||||
|
self.excess.id,
|
||||||
|
self.deficit.id,
|
||||||
|
self.currency.id,
|
||||||
|
amount,
|
||||||
|
],
|
||||||
|
)[0]
|
||||||
|
|
||||||
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()
|
self.db.commit()
|
||||||
return True
|
return rv
|
||||||
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):
|
||||||
amount_in_reality = amount
|
rv = self.db.execute_and_fetch(
|
||||||
amount_in_system = self.cash.balance()
|
"SELECT public.fix_cash(%s, %s, %s, %s)",
|
||||||
|
[self.excess.id, self.deficit.id, self.currency.id, amount],
|
||||||
|
)[0]
|
||||||
|
|
||||||
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()
|
self.db.commit()
|
||||||
return True
|
return rv
|
||||||
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):
|
||||||
transaction = self._transaction(description = "BrmBar inventory consolidation")
|
msg = self.db.execute_and_fetch(
|
||||||
|
"SELECT public.make_consolidate_transaction(%s, %s, %s)",
|
||||||
excess_balance = self.excess.balance()
|
[self.excess.id, self.deficit.id, self.profits.id],
|
||||||
if excess_balance != 0:
|
)[0]
|
||||||
print("Excess balance {} debited to profit".format(-excess_balance))
|
if msg != None:
|
||||||
self.excess.debit(transaction, -excess_balance, "Excess balance added to profit.")
|
print(msg)
|
||||||
self.profits.debit(transaction, -excess_balance, "Excess balance added to profit.")
|
|
||||||
deficit_balance = self.deficit.balance()
|
|
||||||
if deficit_balance != 0:
|
|
||||||
print("Deficit balance {} credited to profit".format(deficit_balance))
|
|
||||||
self.deficit.credit(transaction, deficit_balance, "Deficit balance removed from profit.")
|
|
||||||
self.profits.credit(transaction, deficit_balance, "Deficit balance removed from profit.")
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
def undo(self, oldtid):
|
||||||
|
transaction = self.db.execute_and_fetch(
|
||||||
|
"SELECT public.undo_transaction(%s)", [oldtid]
|
||||||
|
)[0]
|
||||||
|
self.db.commit()
|
||||||
|
return transaction
|
||||||
|
|
|
||||||
15
brmbar3/crontab
Normal file
15
brmbar3/crontab
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# cleanup bounty
|
||||||
|
*/5 * * * * ~/brmbar/brmbar3/uklid-watchdog.sh
|
||||||
|
0 0 * * 1 ~/brmbar/brmbar3/uklid-refill.sh
|
||||||
|
# overall summary
|
||||||
|
5 4 * * * ~/brmbar/brmbar3/daily-summary.sh | mail -s "daily brmbar summary" yyy@yyy
|
||||||
|
# debt track
|
||||||
|
5 0 * * * ~/brmbar/brmbar3/dluhy.sh 2>/dev/null
|
||||||
|
|
||||||
|
# per-user summary
|
||||||
|
1 0 * * * /home/brmlab/brmbar/brmbar3/log.sh yyy yyy@yyy
|
||||||
|
|
||||||
|
# backup
|
||||||
|
6 * * * * echo "SELECT * FROM account_balances;" | psql brmbar | gzip -9 | ssh -Tp 110 -i /home/brmlab/.ssh/id_ecdsa jenda@coralmyn.hrach.eu
|
||||||
|
16 1 * * * pg_dump brmbar | gzip -9 | ssh -Tp 110 -i /home/brmlab/.ssh/id_ecdsa jenda@coralmyn.hrach.eu
|
||||||
|
|
||||||
3
brmbar3/dluhy.sh
Executable file
3
brmbar3/dluhy.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
p1=`echo -n "brmbar - dluhy: "; echo "SELECT name, crbalance FROM account_balances WHERE acctype = 'debt' AND crbalance < -100 AND name NOT LIKE '%overflow%' AND name NOT LIKE 'sachyo' ORDER BY crbalance ASC" | psql brmbar | tail -n +3 | grep '|' | tr -s " " | sed -e "s/ |/:/g" -e "s/$/;/" | tr -d "\n"`
|
||||||
|
p2=`echo "SELECT sum(crbalance) FROM account_balances WHERE acctype = 'debt' AND crbalance < 0 AND name NOT LIKE '%overflow%' AND name NOT LIKE 'sachyo'" | psql brmbar | tail -n +3 | head -n 1 | tr -s " "`
|
||||||
|
echo "$p1 total$p2 Kc. https://www.elektro-obojky.cz/" | ssh -p 110 -i /home/brmlab/.ssh/id_rsa jenda@coralmyn.hrach.eu
|
||||||
6
brmbar3/log.sh
Executable file
6
brmbar3/log.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
p=`/home/brmlab/brmbar/brmbar3/brmbar-cli.py userlog "$1" yesterday`
|
||||||
|
|
||||||
|
if [ -n "$p" ]; then
|
||||||
|
echo "$p" | mail -s "brmbar report" "$2"
|
||||||
|
fi
|
||||||
313
brmbar3/schema/0001-init.sql
Normal file
313
brmbar3/schema/0001-init.sql
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
--
|
||||||
|
-- 0001-init.sql
|
||||||
|
--
|
||||||
|
-- Initial SQL schema construction as of 2025-04-20 (or so)
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
-- Privileged schema with protected data
|
||||||
|
CREATE SCHEMA IF NOT EXISTS brmbar_privileged;
|
||||||
|
|
||||||
|
-- Initial versioning
|
||||||
|
CREATE TABLE IF NOT EXISTS brmbar_privileged.brmbar_schema(
|
||||||
|
ver INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
-- Legacy Schema Initialization
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE v INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v;
|
||||||
|
IF v IS NULL THEN
|
||||||
|
-- --------------------------------
|
||||||
|
-- Legacy Types
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO v
|
||||||
|
FROM pg_catalog.pg_type typ
|
||||||
|
INNER JOIN pg_catalog.pg_namespace nsp
|
||||||
|
ON nsp.oid = typ.typnamespace
|
||||||
|
WHERE nsp.nspname = 'public'
|
||||||
|
AND typ.typname='exchange_rate_direction';
|
||||||
|
IF v=0 THEN
|
||||||
|
RAISE NOTICE 'Creating type exchange_rate_direction';
|
||||||
|
CREATE TYPE public.exchange_rate_direction
|
||||||
|
AS ENUM ('source_to_target', 'target_to_source');
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'Type exchange_rate_direction already exists';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO v
|
||||||
|
FROM pg_catalog.pg_type typ
|
||||||
|
INNER JOIN pg_catalog.pg_namespace nsp
|
||||||
|
ON nsp.oid = typ.typnamespace
|
||||||
|
WHERE nsp.nspname = 'public'
|
||||||
|
AND typ.typname='account_type';
|
||||||
|
IF v=0 THEN
|
||||||
|
RAISE NOTICE 'Creating type account_type';
|
||||||
|
CREATE TYPE public.account_type
|
||||||
|
AS ENUM ('cash', 'debt', 'inventory', 'income', 'expense',
|
||||||
|
'starting_balance', 'ending_balance');
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'Type account_type already exists';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO v
|
||||||
|
FROM pg_catalog.pg_type typ
|
||||||
|
INNER JOIN pg_catalog.pg_namespace nsp
|
||||||
|
ON nsp.oid = typ.typnamespace
|
||||||
|
WHERE nsp.nspname = 'public'
|
||||||
|
AND typ.typname='transaction_split_side';
|
||||||
|
IF v=0 THEN
|
||||||
|
RAISE NOTICE 'Creating type transaction_split_side';
|
||||||
|
CREATE TYPE public.transaction_split_side
|
||||||
|
AS ENUM ('credit', 'debit');
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'Type transaction_split_side already exists';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Currencies sequence, table and potential initial data
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS public.currencies_id_seq
|
||||||
|
START WITH 2 INCREMENT BY 1;
|
||||||
|
CREATE TABLE IF NOT EXISTS public.currencies (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.currencies_id_seq'::regclass),
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
UNIQUE(name)
|
||||||
|
);
|
||||||
|
INSERT INTO public.currencies (id, name) VALUES (1, 'Kč')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Exchange rates table - no initial data required
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.exchange_rates (
|
||||||
|
valid_since TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
|
||||||
|
|
||||||
|
target INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (target) REFERENCES public.currencies (id),
|
||||||
|
|
||||||
|
source INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (source) REFERENCES public.currencies (id),
|
||||||
|
|
||||||
|
rate DECIMAL(12,2) NOT NULL,
|
||||||
|
rate_dir public.exchange_rate_direction NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Accounts sequence and table and 4 initial accounts
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS public.accounts_id_seq
|
||||||
|
START WITH 2 INCREMENT BY 1;
|
||||||
|
CREATE TABLE IF NOT EXISTS public.accounts (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.accounts_id_seq'::regclass),
|
||||||
|
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
UNIQUE (name),
|
||||||
|
|
||||||
|
currency INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (currency) REFERENCES public.currencies (id),
|
||||||
|
|
||||||
|
acctype public.account_type NOT NULL,
|
||||||
|
|
||||||
|
active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
);
|
||||||
|
INSERT INTO public.accounts (id, name, currency, acctype)
|
||||||
|
VALUES (1, 'BrmBar Cash', (SELECT id FROM public.currencies WHERE name='Kč'), 'cash')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO public.accounts (name, currency, acctype)
|
||||||
|
VALUES ('BrmBar Profits', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO public.accounts (name, currency, acctype)
|
||||||
|
VALUES ('BrmBar Excess', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO public.accounts (name, currency, acctype)
|
||||||
|
VALUES ('BrmBar Deficit', (SELECT id FROM public.currencies WHERE name='Kč'), 'expense')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Barcodes
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.barcodes (
|
||||||
|
barcode VARCHAR(128) PRIMARY KEY NOT NULL,
|
||||||
|
|
||||||
|
account INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (account) REFERENCES public.accounts (id)
|
||||||
|
);
|
||||||
|
INSERT INTO public.barcodes (barcode, account)
|
||||||
|
VALUES ('_cash_', (SELECT id FROM public.accounts WHERE acctype = 'cash'))
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Transactions
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS public.transactions_id_seq
|
||||||
|
START WITH 1 INCREMENT BY 1;
|
||||||
|
CREATE TABLE IF NOT EXISTS public.transactions (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transactions_id_seq'::regclass),
|
||||||
|
time TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||||
|
|
||||||
|
responsible INTEGER,
|
||||||
|
FOREIGN KEY (responsible) REFERENCES public.accounts (id),
|
||||||
|
|
||||||
|
description TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Transaction splits
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS public.transaction_splits_id_seq
|
||||||
|
START WITH 1 INCREMENT BY 1;
|
||||||
|
CREATE TABLE IF NOT EXISTS public.transaction_splits (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transaction_splits_id_seq'::regclass),
|
||||||
|
|
||||||
|
transaction INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (transaction) REFERENCES public.transactions (id),
|
||||||
|
|
||||||
|
side public.transaction_split_side NOT NULL,
|
||||||
|
|
||||||
|
account INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (account) REFERENCES public.accounts (id),
|
||||||
|
amount DECIMAL(12,2) NOT NULL,
|
||||||
|
|
||||||
|
memo TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Account balances view
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW public.account_balances AS
|
||||||
|
SELECT ts.account AS id,
|
||||||
|
accounts.name,
|
||||||
|
accounts.acctype,
|
||||||
|
- sum(
|
||||||
|
CASE
|
||||||
|
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||||
|
ELSE ts.amount
|
||||||
|
END) AS crbalance
|
||||||
|
FROM public.transaction_splits ts
|
||||||
|
LEFT JOIN public.accounts ON accounts.id = ts.account
|
||||||
|
GROUP BY ts.account, accounts.name, accounts.acctype
|
||||||
|
ORDER BY (- sum(
|
||||||
|
CASE
|
||||||
|
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||||
|
ELSE ts.amount
|
||||||
|
END));
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Transaction nice splits view
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW public.transaction_nicesplits AS
|
||||||
|
SELECT ts.id,
|
||||||
|
ts.transaction,
|
||||||
|
ts.account,
|
||||||
|
CASE
|
||||||
|
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||||
|
ELSE ts.amount
|
||||||
|
END AS amount,
|
||||||
|
a.currency,
|
||||||
|
ts.memo
|
||||||
|
FROM public.transaction_splits ts
|
||||||
|
LEFT JOIN public.accounts a ON a.id = ts.account
|
||||||
|
ORDER BY ts.id;
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Transaction cash sums view
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW public.transaction_cashsums AS
|
||||||
|
SELECT t.id,
|
||||||
|
t."time",
|
||||||
|
sum(credit.credit_cash) AS cash_credit,
|
||||||
|
sum(debit.debit_cash) AS cash_debit,
|
||||||
|
a.name AS responsible,
|
||||||
|
t.description
|
||||||
|
FROM public.transactions t
|
||||||
|
LEFT JOIN ( SELECT cts.amount AS credit_cash,
|
||||||
|
cts.transaction AS cts_t
|
||||||
|
FROM public.transaction_nicesplits cts
|
||||||
|
LEFT JOIN public.accounts a_1 ON a_1.id = cts.account OR a_1.id = cts.account
|
||||||
|
WHERE a_1.currency = (( SELECT accounts.currency
|
||||||
|
FROM public.accounts
|
||||||
|
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||||
|
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||||
|
AND cts.amount < 0::numeric) credit ON credit.cts_t = t.id
|
||||||
|
LEFT JOIN ( SELECT dts.amount AS debit_cash,
|
||||||
|
dts.transaction AS dts_t
|
||||||
|
FROM public.transaction_nicesplits dts
|
||||||
|
LEFT JOIN public.accounts a_1 ON a_1.id = dts.account OR a_1.id = dts.account
|
||||||
|
WHERE a_1.currency = (( SELECT accounts.currency
|
||||||
|
FROM public.accounts
|
||||||
|
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||||
|
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||||
|
AND dts.amount > 0::numeric) debit ON debit.dts_t = t.id
|
||||||
|
LEFT JOIN public.accounts a ON a.id = t.responsible
|
||||||
|
GROUP BY t.id, a.name
|
||||||
|
ORDER BY t.id DESC;
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
-- Function to check schema version (used in migrations)
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_privileged.has_exact_schema_version(
|
||||||
|
IN i_ver INTEGER
|
||||||
|
) RETURNS BOOLEAN
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||||
|
DECLARE
|
||||||
|
v_ver INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT ver INTO v_ver FROM brmbar_privileged.brmbar_schema;
|
||||||
|
IF v_ver is NULL THEN
|
||||||
|
RETURN false;
|
||||||
|
ELSE
|
||||||
|
RETURN v_ver = i_ver;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$x$;
|
||||||
|
|
||||||
|
-- --------------------------------
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_privileged.upgrade_schema_version_to(
|
||||||
|
IN i_ver INTEGER
|
||||||
|
) RETURNS VOID
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||||
|
DECLARE
|
||||||
|
v_ver INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v_ver;
|
||||||
|
IF v_ver=(i_ver-1) THEN
|
||||||
|
UPDATE brmbar_privileged.brmbar_schema SET ver = i_ver;
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'Invalid brmbar schema version transition (% -> %)', v_ver, i_ver;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$x$;
|
||||||
|
|
||||||
|
-- Initialize version 1
|
||||||
|
INSERT INTO brmbar_privileged.brmbar_schema(ver) VALUES(1);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
40
brmbar3/schema/0002-trading-accounts.sql
Normal file
40
brmbar3/schema/0002-trading-accounts.sql
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
--
|
||||||
|
-- 0002-trading-accounts.sql
|
||||||
|
--
|
||||||
|
-- #2 - add trading accounts to account type type
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(1) THEN
|
||||||
|
|
||||||
|
ALTER TYPE public.account_type ADD VALUE 'trading';
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(2);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
52
brmbar3/schema/0003-new-account.sql
Normal file
52
brmbar3/schema/0003-new-account.sql
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
--
|
||||||
|
-- 0003-new-account.sql
|
||||||
|
--
|
||||||
|
-- #3 - stored procedure for creating new account
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(2) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.create_account(
|
||||||
|
IN i_name public.accounts.name%TYPE,
|
||||||
|
IN i_currency public.accounts.currency%TYPE,
|
||||||
|
IN i_acctype public.accounts.acctype%TYPE
|
||||||
|
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||||
|
DECLARE
|
||||||
|
r_id INTEGER;
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO public.accounts (name, currency, acctype)
|
||||||
|
VALUES (i_name, i_currency, i_acctype) RETURNING id INTO r_id;
|
||||||
|
RETURN r_id;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(3);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
50
brmbar3/schema/0004-add-account-barcode.sql
Normal file
50
brmbar3/schema/0004-add-account-barcode.sql
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
--
|
||||||
|
-- 0004-add-account-barcode.sql
|
||||||
|
--
|
||||||
|
-- #4 - stored procedure for adding barcode to account
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(3) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.add_barcode_to_account(
|
||||||
|
IN i_account public.barcodes.account%TYPE,
|
||||||
|
IN i_barcode public.barcodes.barcode%TYPE
|
||||||
|
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||||
|
DECLARE
|
||||||
|
r_id INTEGER;
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO public.barcodes (account, barcode)
|
||||||
|
VALUES (i_account, i_barcode);
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(4);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
51
brmbar3/schema/0005-rename-account.sql
Normal file
51
brmbar3/schema/0005-rename-account.sql
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
--
|
||||||
|
-- 0005-rename-account.sql
|
||||||
|
--
|
||||||
|
-- #5 - stored procedure for renaming account
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(4) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.rename_account(
|
||||||
|
IN i_account public.accounts.id%TYPE,
|
||||||
|
IN i_name public.accounts.name%TYPE
|
||||||
|
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||||
|
DECLARE
|
||||||
|
r_id INTEGER;
|
||||||
|
BEGIN
|
||||||
|
UPDATE public.accounts
|
||||||
|
SET name = i_name
|
||||||
|
WHERE id = i_account;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(5);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
50
brmbar3/schema/0006-new-currency.sql
Normal file
50
brmbar3/schema/0006-new-currency.sql
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
--
|
||||||
|
-- 0006-new-currency.sql
|
||||||
|
--
|
||||||
|
-- #6 - stored procedure for creating new currency
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(5) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.create_currency(
|
||||||
|
IN i_name public.currencies.name%TYPE
|
||||||
|
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||||
|
DECLARE
|
||||||
|
r_id INTEGER;
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO public.currencies (name)
|
||||||
|
VALUES (i_name) RETURNING id INTO r_id;
|
||||||
|
RETURN r_id;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(6);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
49
brmbar3/schema/0007-update-currency-sell-rate.sql
Normal file
49
brmbar3/schema/0007-update-currency-sell-rate.sql
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
--
|
||||||
|
-- 0007-update-currency-sell-rate.sql
|
||||||
|
--
|
||||||
|
-- #7 - stored procedure for updating sell rate
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(6) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.update_currency_sell_rate(
|
||||||
|
IN i_currency public.exchange_rates.source%TYPE,
|
||||||
|
IN i_target public.exchange_rates.target%TYPE,
|
||||||
|
IN i_rate public.exchange_rates.rate%TYPE
|
||||||
|
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||||
|
VALUES (i_currency, i_target, i_rate, 'source_to_target');
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(7);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
49
brmbar3/schema/0008-update-currency-buy-rate.sql
Normal file
49
brmbar3/schema/0008-update-currency-buy-rate.sql
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
--
|
||||||
|
-- 0008-update-currency-buy-rate.sql
|
||||||
|
--
|
||||||
|
-- #8 - stored procedure for updating buy rate
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(7) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.update_currency_buy_rate(
|
||||||
|
IN i_currency public.exchange_rates.target%TYPE,
|
||||||
|
IN i_source public.exchange_rates.source%TYPE,
|
||||||
|
IN i_rate public.exchange_rates.rate%TYPE
|
||||||
|
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||||
|
VALUES (i_source, i_currency, i_rate, 'target_to_source');
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(8);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
149
brmbar3/schema/0009-shop-sell.sql
Normal file
149
brmbar3/schema/0009-shop-sell.sql
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
--
|
||||||
|
-- 0009-shop-sell.sql
|
||||||
|
--
|
||||||
|
-- #9 - stored function for sell transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(8) THEN
|
||||||
|
|
||||||
|
-- return negative number on rate not found
|
||||||
|
CREATE OR REPLACE FUNCTION public.find_buy_rate(
|
||||||
|
IN i_item_id public.accounts.id%TYPE,
|
||||||
|
IN i_other_id public.accounts.id%TYPE
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_rate public.exchange_rates.rate%TYPE;
|
||||||
|
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_item_id AND source = i_other_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||||
|
IF v_rate_dir = 'target_to_source'::public.exchange_rate_direction THEN
|
||||||
|
RETURN v_rate;
|
||||||
|
ELSE
|
||||||
|
RETURN 1/v_rate;
|
||||||
|
END IF;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
RETURN -1;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
-- return negative number on rate not found
|
||||||
|
CREATE OR REPLACE FUNCTION public.find_sell_rate(
|
||||||
|
IN i_item_id public.accounts.id%TYPE,
|
||||||
|
IN i_other_id public.accounts.id%TYPE
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_rate public.exchange_rates.rate%TYPE;
|
||||||
|
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_other_id AND source = i_item_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||||
|
IF v_rate_dir = 'source_to_target'::public.exchange_rate_direction THEN
|
||||||
|
RETURN v_rate;
|
||||||
|
ELSE
|
||||||
|
RETURN 1/v_rate;
|
||||||
|
END IF;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
RETURN -1;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.create_transaction(
|
||||||
|
i_responsible_id public.accounts.id%TYPE,
|
||||||
|
i_description public.transactions.description%TYPE
|
||||||
|
) RETURNS public.transactions.id%TYPE AS $$
|
||||||
|
DECLARE
|
||||||
|
new_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Create a new transaction
|
||||||
|
INSERT INTO public.transactions (responsible, description)
|
||||||
|
VALUES (i_responsible_id, i_description)
|
||||||
|
RETURNING id INTO new_transaction_id;
|
||||||
|
-- Return the new transaction ID
|
||||||
|
RETURN new_transaction_id;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.sell_item(
|
||||||
|
i_item_id public.accounts.id%TYPE,
|
||||||
|
i_amount INTEGER,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_target_currency_id public.currencies.id%TYPE,
|
||||||
|
i_description TEXT
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_buy_rate NUMERIC;
|
||||||
|
v_sell_rate NUMERIC;
|
||||||
|
v_cost NUMERIC;
|
||||||
|
v_profit NUMERIC;
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Get the buy and sell rates from the stored functions
|
||||||
|
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||||
|
v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||||
|
|
||||||
|
-- Calculate cost and profit
|
||||||
|
v_cost := i_amount * v_sell_rate;
|
||||||
|
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||||
|
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := public.create_transaction(i_user_id, i_description);
|
||||||
|
|
||||||
|
-- the item (decrease stock)
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'credit', i_item_id, i_amount,
|
||||||
|
(SELECT "name" FROM public.accounts WHERE id = i_user_id));
|
||||||
|
|
||||||
|
-- the user
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'debit', i_user_id, v_cost,
|
||||||
|
(SELECT "name" FROM public.accounts WHERE id = i_item_id));
|
||||||
|
|
||||||
|
-- the profit
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'debit', (SELECT account_id FROM accounts WHERE name = 'BrmBar Profits'), v_profit, (SELECT 'Margin on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||||
|
|
||||||
|
-- Return the cost
|
||||||
|
RETURN v_cost;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(9);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
154
brmbar3/schema/0010-shop-sell-for-cash.sql
Normal file
154
brmbar3/schema/0010-shop-sell-for-cash.sql
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
--
|
||||||
|
-- 0010-shop-sell-for-cash.sql
|
||||||
|
--
|
||||||
|
-- #10 - stored function for cash sell transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(9) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_privileged.create_transaction(
|
||||||
|
i_responsible_id public.accounts.id%TYPE,
|
||||||
|
i_description public.transactions.description%TYPE
|
||||||
|
) RETURNS public.transactions.id%TYPE AS $$
|
||||||
|
DECLARE
|
||||||
|
new_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Create a new transaction
|
||||||
|
INSERT INTO public.transactions (responsible, description)
|
||||||
|
VALUES (i_responsible_id, i_description)
|
||||||
|
RETURNING id INTO new_transaction_id;
|
||||||
|
-- Return the new transaction ID
|
||||||
|
RETURN new_transaction_id;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_privileged.sell_item_internal(
|
||||||
|
i_item_id public.accounts.id%TYPE,
|
||||||
|
i_amount INTEGER,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_target_currency_id public.currencies.id%TYPE,
|
||||||
|
i_other_memo TEXT,
|
||||||
|
i_description TEXT
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_buy_rate NUMERIC;
|
||||||
|
v_sell_rate NUMERIC;
|
||||||
|
v_cost NUMERIC;
|
||||||
|
v_profit NUMERIC;
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
v_item_currency_id public.accounts.currency%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Get item's currency
|
||||||
|
SELECT currency
|
||||||
|
INTO v_item_currency_id
|
||||||
|
FROM public.accounts
|
||||||
|
WHERE id=i_item_id;
|
||||||
|
|
||||||
|
-- Get the buy and sell rates from the stored functions
|
||||||
|
v_buy_rate := public.find_buy_rate(v_item_currency_id, i_target_currency_id);
|
||||||
|
v_sell_rate := public.find_sell_rate(v_item_currency_id, i_target_currency_id);
|
||||||
|
|
||||||
|
-- Calculate cost and profit
|
||||||
|
v_cost := i_amount * v_sell_rate;
|
||||||
|
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||||
|
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, i_description);
|
||||||
|
|
||||||
|
-- the item (decrease stock)
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'credit', i_item_id, i_amount,
|
||||||
|
i_other_memo);
|
||||||
|
|
||||||
|
-- the user
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'debit', i_user_id, v_cost,
|
||||||
|
(SELECT "name" FROM public.accounts WHERE id = i_item_id));
|
||||||
|
|
||||||
|
-- the profit
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id,
|
||||||
|
'debit',
|
||||||
|
(SELECT id FROM public.accounts WHERE name = 'BrmBar Profits'),
|
||||||
|
v_profit,
|
||||||
|
(SELECT 'Margin on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||||
|
|
||||||
|
-- Return the cost
|
||||||
|
RETURN v_cost;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.sell_item(
|
||||||
|
i_item_id public.accounts.id%TYPE,
|
||||||
|
i_amount INTEGER,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_target_currency_id public.currencies.id%TYPE,
|
||||||
|
i_description TEXT
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN brmbar_privileged.sell_item_internal(i_item_id,
|
||||||
|
i_amount,
|
||||||
|
i_user_id,
|
||||||
|
i_target_currency_id,
|
||||||
|
(SELECT "name" FROM public.accounts WHERE id = i_user_id),
|
||||||
|
i_description);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.sell_item_for_cash(
|
||||||
|
i_item_id public.accounts.id%TYPE,
|
||||||
|
i_amount INTEGER,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_target_currency_id public.currencies.id%TYPE,
|
||||||
|
i_description TEXT
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN brmbar_privileged.sell_item_internal(i_item_id,
|
||||||
|
i_amount,
|
||||||
|
i_user_id,
|
||||||
|
i_target_currency_id,
|
||||||
|
'Cash',
|
||||||
|
i_description);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DROP FUNCTION public.create_transaction;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(10);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
102
brmbar3/schema/0011-shop-undo-sale.sql
Normal file
102
brmbar3/schema/0011-shop-undo-sale.sql
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
--
|
||||||
|
-- 0011-shop-undo-sale.sql
|
||||||
|
--
|
||||||
|
-- #11 - stored function for sale undo transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(10) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_privileged.create_transaction(
|
||||||
|
i_responsible_id public.accounts.id%TYPE,
|
||||||
|
i_description public.transactions.description%TYPE
|
||||||
|
) RETURNS public.transactions.id%TYPE AS $$
|
||||||
|
DECLARE
|
||||||
|
new_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Create a new transaction
|
||||||
|
INSERT INTO public.transactions (responsible, description)
|
||||||
|
VALUES (i_responsible_id, i_description)
|
||||||
|
RETURNING id INTO new_transaction_id;
|
||||||
|
-- Return the new transaction ID
|
||||||
|
RETURN new_transaction_id;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.undo_sale_of_item(
|
||||||
|
i_item_id public.accounts.id%TYPE,
|
||||||
|
i_amount INTEGER,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_target_currency_id public.currencies.id%TYPE,
|
||||||
|
i_description TEXT
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_buy_rate NUMERIC;
|
||||||
|
v_sell_rate NUMERIC;
|
||||||
|
v_cost NUMERIC;
|
||||||
|
v_profit NUMERIC;
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Get the buy and sell rates from the stored functions
|
||||||
|
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||||
|
v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||||
|
|
||||||
|
-- Calculate cost and profit
|
||||||
|
v_cost := i_amount * v_sell_rate;
|
||||||
|
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||||
|
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, i_description);
|
||||||
|
|
||||||
|
-- the item (decrease stock)
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'debit', i_item_id, i_amount,
|
||||||
|
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_user_id));
|
||||||
|
|
||||||
|
-- the user
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'credit', i_user_id, v_cost,
|
||||||
|
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_item_id));
|
||||||
|
|
||||||
|
-- the profit
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'credit', (SELECT account_id FROM accounts WHERE name = 'BrmBar Profits'), v_profit, (SELECT 'Margin repaid on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||||
|
|
||||||
|
-- Return the cost
|
||||||
|
RETURN v_cost;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(11);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
64
brmbar3/schema/0012-shop-add-credit.sql
Normal file
64
brmbar3/schema/0012-shop-add-credit.sql
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
--
|
||||||
|
-- 0012-shop-add-credit.sql
|
||||||
|
--
|
||||||
|
-- #12 - stored function for cash deposit transactions
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(11) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.add_credit(
|
||||||
|
i_cash_account_id public.accounts.id%TYPE,
|
||||||
|
i_credit NUMERIC,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_user_name TEXT
|
||||||
|
) RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit replenishment for ' || i_user_name);
|
||||||
|
-- Debit cash (credit replenishment)
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'debit', i_cash_account_id, i_credit, i_user_name);
|
||||||
|
-- Credit the user
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'credit', i_user_id, i_credit, 'Credit replenishment');
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(12);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
64
brmbar3/schema/0013-shop-withdraw-credit.sql
Normal file
64
brmbar3/schema/0013-shop-withdraw-credit.sql
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
--
|
||||||
|
-- 0013-shop-withdraw-credit.sql
|
||||||
|
--
|
||||||
|
-- #13 - stored function for cash withdrawal transactions
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(12) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.withdraw_credit(
|
||||||
|
i_cash_account_id public.accounts.id%TYPE,
|
||||||
|
i_credit NUMERIC,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_user_name TEXT
|
||||||
|
) RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit withdrawal for ' || i_user_name);
|
||||||
|
-- Debit cash (credit replenishment)
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'credit', i_cash_account_id, i_credit, i_user_name);
|
||||||
|
-- Credit the user
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'debit', i_user_id, i_credit, 'Credit withdrawal');
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(13);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
58
brmbar3/schema/0014-shop-transfer-credit.sql
Normal file
58
brmbar3/schema/0014-shop-transfer-credit.sql
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
--
|
||||||
|
-- 0014-shop-transfer-credit.sql
|
||||||
|
--
|
||||||
|
-- #14 - stored function for "credit" transfer transactions
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(13) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.transfer_credit(
|
||||||
|
i_cash_account_id public.accounts.id%TYPE,
|
||||||
|
i_credit NUMERIC,
|
||||||
|
i_userfrom_id public.accounts.id%TYPE,
|
||||||
|
i_userfrom_name TEXT,
|
||||||
|
i_userto_id public.accounts.id%TYPE,
|
||||||
|
i_userto_name TEXT
|
||||||
|
) RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
PERFORM public.add_credit(i_cash_account_id, i_credit, i_userto_id, i_userto_name);
|
||||||
|
PERFORM public.withdraw_credit(i_cash_account_id, i_credit, i_userfrom_id, i_userfrom_name);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(14);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
85
brmbar3/schema/0015-shop-buy-for-cash.sql
Normal file
85
brmbar3/schema/0015-shop-buy-for-cash.sql
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
--
|
||||||
|
-- 0015-shop-buy-for-cash.sql
|
||||||
|
--
|
||||||
|
-- #15 - stored function for cash-based stock replenishment transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(14) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.buy_for_cash(
|
||||||
|
i_cash_account_id public.accounts.id%TYPE,
|
||||||
|
i_item_id public.accounts.id%TYPE,
|
||||||
|
i_amount INTEGER,
|
||||||
|
i_target_currency_id public.currencies.id%TYPE,
|
||||||
|
i_item_name TEXT
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_buy_rate NUMERIC;
|
||||||
|
v_cost NUMERIC;
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
v_item_currency_id public.accounts.currency%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Get item's currency
|
||||||
|
SELECT currency
|
||||||
|
INTO STRICT v_item_currency_id
|
||||||
|
FROM public.accounts
|
||||||
|
WHERE id=i_item_id;
|
||||||
|
-- Get the buy rates from the stored functions
|
||||||
|
v_buy_rate := public.find_buy_rate(v_item_currency_id, i_target_currency_id);
|
||||||
|
|
||||||
|
-- Calculate cost and profit
|
||||||
|
v_cost := i_amount * v_buy_rate;
|
||||||
|
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := brmbar_privileged.create_transaction(NULL,
|
||||||
|
'BrmBar stock replenishment of ' || i_amount || 'x ' || i_item_name || ' for cash');
|
||||||
|
|
||||||
|
-- the item
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'debit', i_item_id, i_amount,
|
||||||
|
'Cash');
|
||||||
|
|
||||||
|
-- the cash
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'credit', i_cash_account_id, v_cost,
|
||||||
|
i_item_name);
|
||||||
|
|
||||||
|
-- Return the cost
|
||||||
|
RETURN v_cost;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(15);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
64
brmbar3/schema/0016-shop-receipt-to-credit.sql
Normal file
64
brmbar3/schema/0016-shop-receipt-to-credit.sql
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
--
|
||||||
|
-- 0016-shop-buy-for-cash.sql
|
||||||
|
--
|
||||||
|
-- #16 - stored function for receipt reimbursement transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(15) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.receipt_reimbursement(
|
||||||
|
i_profits_id public.accounts.id%TYPE,
|
||||||
|
i_user_id public.accounts.id%TYPE,
|
||||||
|
i_user_name public.accounts.name%TYPE,
|
||||||
|
i_amount NUMERIC,
|
||||||
|
i_description TEXT
|
||||||
|
) RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
BEGIN
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := brmbar_privileged.create_transaction(i_user_id,
|
||||||
|
'Receipt: ' || i_description);
|
||||||
|
-- the "profit"
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'credit', i_profits_id, i_amount, i_user_name);
|
||||||
|
-- the user
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (v_transaction_id, 'credit', i_user_id, i_amount, 'Credit from receipt: ' || i_description);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(16);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
159
brmbar3/schema/0017-shop-fix-inventory.sql
Normal file
159
brmbar3/schema/0017-shop-fix-inventory.sql
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
--
|
||||||
|
-- 0017-shop-fix-inventory.sql
|
||||||
|
--
|
||||||
|
-- #17 - stored function for "fixing" inventory transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(16) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.compute_account_balance(
|
||||||
|
i_account_id public.accounts.id%TYPE
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_crsum NUMERIC;
|
||||||
|
v_dbsum NUMERIC;
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
COALESCE(SUM(CASE WHEN side='credit' THEN amount ELSE 0 END),0) crsum,
|
||||||
|
COALESCE(SUM(CASE WHEN side='debit' THEN amount ELSE 0 END),0) dbsum
|
||||||
|
INTO v_crsum, v_dbsum
|
||||||
|
FROM public.transaction_splits ts WHERE ts.account=i_account_id;
|
||||||
|
RETURN v_dbsum - v_crsum;
|
||||||
|
END; $$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION brmbar_privileged.fix_account_balance(
|
||||||
|
IN i_account_id public.accounts.id%TYPE,
|
||||||
|
IN i_account_currency_id public.currencies.id%TYPE,
|
||||||
|
IN i_excess_id public.accounts.id%TYPE,
|
||||||
|
IN i_deficit_id public.accounts.id%TYPE,
|
||||||
|
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||||
|
IN i_amount_in_reality NUMERIC
|
||||||
|
) RETURNS BOOLEAN
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
v_amount_in_system NUMERIC;
|
||||||
|
v_buy_rate NUMERIC;
|
||||||
|
v_currency_id public.currencies.id%TYPE;
|
||||||
|
v_diff NUMERIC;
|
||||||
|
v_buy_total NUMERIC;
|
||||||
|
v_ntrn_id public.transactions.id%TYPE;
|
||||||
|
v_transaction_memo TEXT;
|
||||||
|
v_item_name TEXT;
|
||||||
|
v_excess_memo TEXT;
|
||||||
|
v_deficit_memo TEXT;
|
||||||
|
|
||||||
|
v_old_trn public.transactions%ROWTYPE;
|
||||||
|
v_old_split public.transaction_splits%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
v_amount_in_system := public.compute_account_balance(i_account_id);
|
||||||
|
IF i_account_currency_id <> i_shop_currency_id THEN
|
||||||
|
v_buy_rate := public.find_buy_rate(i_item_id, i_shop_currency_id);
|
||||||
|
ELSE
|
||||||
|
v_buy_rate := 1;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_diff := ABS(i_amount_in_reality - v_amount_in_system);
|
||||||
|
v_buy_total := v_buy_rate * v_diff;
|
||||||
|
-- compute memo strings
|
||||||
|
IF i_item_id = 1 THEN -- cash account recognized by magic id
|
||||||
|
-- fixing cash
|
||||||
|
v_transaction_memo :=
|
||||||
|
'BrmBar cash inventory fix of ' || v_amount_in_system
|
||||||
|
|| ' in system to ' || i_amount_in_reality || ' in reality';
|
||||||
|
v_excess_memo := 'Inventory cash fix excess.';
|
||||||
|
v_deficit_memo := 'Inventory fix deficit.';
|
||||||
|
ELSE
|
||||||
|
-- fixing other account
|
||||||
|
SELECT "name" INTO v_item_name FROM public.accounts WHERE id = i_account_id;
|
||||||
|
v_transaction_memo :=
|
||||||
|
'BrmBar inventory fix of ' || v_amount_in_system || 'pcs '
|
||||||
|
|| v_item_name
|
||||||
|
|| ' in system to ' || i_amount_in_reality || 'pcs in reality';
|
||||||
|
v_excess_memo := 'Inventory fix excess ' || v_item_name;
|
||||||
|
v_deficit_memo := 'Inventory fix deficit ' || v_item_name;
|
||||||
|
END IF;
|
||||||
|
-- create transaction based on the relation between counting and accounting
|
||||||
|
IF i_amount_in_reality > v_amount_in_system THEN
|
||||||
|
v_ntrn_id := brmbar_privileged.create_transaction(NULL, v_transaction_memo);
|
||||||
|
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||||
|
VALUES (v_ntrn_id, 'debit', i_item_id, v_diff, 'Inventory fix excess');
|
||||||
|
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||||
|
VALUES (v_ntrn_id, 'credit', i_excess_id, v_buy_total, v_excess_memo);
|
||||||
|
RETURN TRUE;
|
||||||
|
ELSIF i_amount_in_reality < v_amount_in_system THEN
|
||||||
|
v_ntrn_id := brmbar_privileged.create_transaction(NULL, v_transaction_memo);
|
||||||
|
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||||
|
VALUES (v_ntrn_id, 'credit', i_item_id, v_diff, 'Inventory fix deficit');
|
||||||
|
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||||
|
VALUES (v_ntrn_id, 'debit', i_deficit_id, v_buy_total, v_deficit_memo);
|
||||||
|
RETURN TRUE;
|
||||||
|
ELSIF i_account_id <> 1 THEN -- cash account recognized by magic id
|
||||||
|
-- record that everything is going on swimmingly only for noncash accounts (WTF)
|
||||||
|
v_ntrn_id := brmbar_privileged.create_transaction(NULL, v_transaction_memo);
|
||||||
|
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||||
|
VALUES (v_ntrn_id, 'debit', i_item_id, 0, 'Inventory fix - amount was correct');
|
||||||
|
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||||
|
VALUES (v_ntrn_id, 'credit', i_item_id, 0, 'Inventory fix - amount was correct');
|
||||||
|
RETURN FALSE;
|
||||||
|
END IF;
|
||||||
|
RETURN FALSE;
|
||||||
|
END;
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.fix_inventory(
|
||||||
|
IN i_account_id public.accounts.id%TYPE,
|
||||||
|
IN i_account_currency_id public.currencies.id%TYPE,
|
||||||
|
IN i_excess_id public.accounts.id%TYPE,
|
||||||
|
IN i_deficit_id public.accounts.id%TYPE,
|
||||||
|
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||||
|
IN i_amount_in_reality NUMERIC
|
||||||
|
) RETURNS BOOLEAN
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||||
|
BEGIN
|
||||||
|
RETURN brmbar_privileged.fix_account_balance(
|
||||||
|
i_account_id,
|
||||||
|
i_account_currency_id,
|
||||||
|
i_excess_id,
|
||||||
|
i_deficit_id,
|
||||||
|
i_shop_currency_id,
|
||||||
|
i_amount_in_reality
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(17);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
60
brmbar3/schema/0018-shop-fix-cash.sql
Normal file
60
brmbar3/schema/0018-shop-fix-cash.sql
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
--
|
||||||
|
-- 0018-shop-fix-cash.sql
|
||||||
|
--
|
||||||
|
-- #18 - stored function for "fixing cash" transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(17) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.fix_cash(
|
||||||
|
IN i_excess_id public.accounts.id%TYPE,
|
||||||
|
IN i_deficit_id public.accounts.id%TYPE,
|
||||||
|
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||||
|
IN i_amount_in_reality NUMERIC
|
||||||
|
) RETURNS BOOLEAN
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||||
|
BEGIN
|
||||||
|
RETURN brmbar_privileged.fix_account_balance(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
i_excess_id,
|
||||||
|
i_deficit_id,
|
||||||
|
i_shop_currency_id,
|
||||||
|
i_amount_in_reality
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(18);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
82
brmbar3/schema/0019-shop-consolidate.sql
Normal file
82
brmbar3/schema/0019-shop-consolidate.sql
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
--
|
||||||
|
-- 0019-shop-consolidate.sql
|
||||||
|
--
|
||||||
|
-- #19 - stored function for "consolidation" transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(18) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.make_consolidate_transaction(
|
||||||
|
i_excess_id public.accounts.id%TYPE,
|
||||||
|
i_deficit_id public.accounts.id%TYPE,
|
||||||
|
i_profits_id public.accounts.id%TYPE
|
||||||
|
) RETURNS TEXT
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_transaction_id public.transactions.id%TYPE;
|
||||||
|
v_excess_balance NUMERIC;
|
||||||
|
v_deficit_balance NUMERIC;
|
||||||
|
v_ret TEXT;
|
||||||
|
BEGIN
|
||||||
|
v_ret := NULL;
|
||||||
|
-- Create a new transaction
|
||||||
|
v_transaction_id := brmbar_privileged.create_transaction(NULL,
|
||||||
|
'BrmBar inventory consolidation');
|
||||||
|
v_excess_balance := public.compute_account_balance(i_excess_id);
|
||||||
|
v_deficit_balance := public.compute_account_balance(i_deficit_id);
|
||||||
|
IF v_excess_balance <> 0 THEN
|
||||||
|
v_ret := 'Excess balance ' || -v_excess_balance || ' debited to profit';
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'debit', i_excess_id, -v_excess_balance,
|
||||||
|
'Excess balance added to profit.');
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'debit', i_profits_id, -v_excess_balance,
|
||||||
|
'Excess balance added to profit.');
|
||||||
|
END IF;
|
||||||
|
IF v_deficit_balance <> 0 THEN
|
||||||
|
v_ret := COALESCE(v_ret, '');
|
||||||
|
v_ret := v_ret || 'Deficit balance ' || v_deficit_balance || ' credited to profit';
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'credit', i_deficit_id, v_deficit_balance,
|
||||||
|
'Deficit balance removed from profit.');
|
||||||
|
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||||
|
VALUES (i_transaction_id, 'credit', i_profits_id, v_deficit_balance,
|
||||||
|
'Deficit balance removed from profit.');
|
||||||
|
END IF;
|
||||||
|
RETURN v_ret;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(19);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
64
brmbar3/schema/0020-shop-undo.sql
Normal file
64
brmbar3/schema/0020-shop-undo.sql
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
--
|
||||||
|
-- 0020-shop-undo.sql
|
||||||
|
--
|
||||||
|
-- #20 - stored function for undo transaction
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(19) THEN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.undo_transaction(
|
||||||
|
IN i_id public.transactions.id%TYPE)
|
||||||
|
RETURNS public.transactions.id%TYPE
|
||||||
|
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
v_ntrn_id public.transactions.id%TYPE;
|
||||||
|
v_old_trn public.transactions%ROWTYPE;
|
||||||
|
v_old_split public.transaction_splits%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT * INTO v_old_trn FROM public.transactions WHERE id = i_id;
|
||||||
|
INSERT INTO transactions ("description") VALUES ('undo '||i_id||' ('||v_old_trn.description||')') RETURNING id into v_ntrn_id;
|
||||||
|
FOR v_old_split IN
|
||||||
|
SELECT * FROM transaction_splits WHERE "transaction" = i_id
|
||||||
|
LOOP
|
||||||
|
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||||
|
VALUES (v_ntrn_id, v_old_split.side, v_old_split.account, -v_old_split.amount,
|
||||||
|
'undo ' || v_old_split.id || ' (' || v_old_split.memo || ')' );
|
||||||
|
END LOOP;
|
||||||
|
RETURN v_ntrn_id;
|
||||||
|
END;
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(20);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
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 :
|
||||||
92
brmbar3/schema/0024-find-rates-fix.sql
Normal file
92
brmbar3/schema/0024-find-rates-fix.sql
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
--
|
||||||
|
-- 0024-find-rates-fix.sql
|
||||||
|
--
|
||||||
|
-- #24 - fix stored functions find_buy_rate and find_sell_rate
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(23) THEN
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS public.find_buy_rate(integer,integer);
|
||||||
|
DROP FUNCTION IF EXISTS public.find_sell_rate(integer,integer);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.find_buy_rate(
|
||||||
|
IN i_item_currency_id public.accounts.id%TYPE,
|
||||||
|
IN i_other_currency_id public.accounts.id%TYPE
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_rate public.exchange_rates.rate%TYPE;
|
||||||
|
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_item_currency_id AND source = i_other_currency_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||||
|
IF v_rate_dir = 'target_to_source'::public.exchange_rate_direction THEN
|
||||||
|
RETURN v_rate;
|
||||||
|
ELSE
|
||||||
|
RETURN 1/v_rate;
|
||||||
|
END IF;
|
||||||
|
/* propagate error
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
RETURN -1;
|
||||||
|
*/
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- return negative number on rate not found
|
||||||
|
CREATE OR REPLACE FUNCTION public.find_sell_rate(
|
||||||
|
IN i_item_currency_id public.accounts.id%TYPE,
|
||||||
|
IN i_other_currency_id public.accounts.id%TYPE
|
||||||
|
) RETURNS NUMERIC
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_rate public.exchange_rates.rate%TYPE;
|
||||||
|
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT rate, rate_dir INTO STRICT v_rate, v_rate_dir FROM public.exchange_rates WHERE target = i_other_currency_id AND source = i_item_currency_id AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1;
|
||||||
|
IF v_rate_dir = 'source_to_target'::public.exchange_rate_direction THEN
|
||||||
|
RETURN v_rate;
|
||||||
|
ELSE
|
||||||
|
RETURN 1/v_rate;
|
||||||
|
END IF;
|
||||||
|
/* propagate error
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
RETURN -1;
|
||||||
|
*/
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(24);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
111
brmbar3/schema/0025-load-account.sql
Normal file
111
brmbar3/schema/0025-load-account.sql
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
--
|
||||||
|
-- 0025-load-account.sql
|
||||||
|
--
|
||||||
|
-- #25 - stored procedures for account loading
|
||||||
|
--
|
||||||
|
-- ISC License
|
||||||
|
--
|
||||||
|
-- Copyright 2023-2025 Brmlab, z.s.
|
||||||
|
-- TMA <tma+hs@jikos.cz>
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software
|
||||||
|
-- for any purpose with or without fee is hereby granted, provided
|
||||||
|
-- that the above copyright notice and this permission notice appear
|
||||||
|
-- in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||||
|
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
--
|
||||||
|
|
||||||
|
-- To require fully-qualified names
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
|
||||||
|
DO $upgrade_block$
|
||||||
|
DECLARE
|
||||||
|
v INTEGER;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
IF brmbar_privileged.has_exact_schema_version(24) THEN
|
||||||
|
|
||||||
|
SELECT COUNT(1) INTO v
|
||||||
|
FROM pg_catalog.pg_type typ
|
||||||
|
INNER JOIN pg_catalog.pg_namespace nsp
|
||||||
|
ON nsp.oid = typ.typnamespace
|
||||||
|
WHERE nsp.nspname = 'brmbar_privileged'
|
||||||
|
AND typ.typname='account_class_initialization_data_type';
|
||||||
|
|
||||||
|
IF v>0 THEN
|
||||||
|
RAISE NOTICE 'Changing type account_class_initialization_data_type';
|
||||||
|
DROP TYPE brmbar_privileged.account_class_initialization_data_type CASCADE;
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'Creating type account_class_initialization_data_type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT COUNT(1) INTO v
|
||||||
|
FROM pg_catalog.pg_type typ
|
||||||
|
INNER JOIN pg_catalog.pg_namespace nsp
|
||||||
|
ON nsp.oid = typ.typnamespace
|
||||||
|
WHERE nsp.nspname = 'brmbar_privileged'
|
||||||
|
AND typ.typname='account_load_type';
|
||||||
|
|
||||||
|
IF v>0 THEN
|
||||||
|
RAISE NOTICE 'Changing type account_load_type';
|
||||||
|
DROP TYPE brmbar_privileged.account_load_type CASCADE;
|
||||||
|
ELSE
|
||||||
|
RAISE NOTICE 'Creating type account_load_type';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CREATE TYPE brmbar_privileged.account_load_type
|
||||||
|
AS ENUM ('by_id', 'by_barcode');
|
||||||
|
|
||||||
|
CREATE TYPE brmbar_privileged.account_class_initialization_data_type
|
||||||
|
AS (
|
||||||
|
account_id INTEGER, --public.accounts.id%TYPE,
|
||||||
|
account_name TEXT, --public.accounts.id%TYPE,
|
||||||
|
account_acctype public.account_type,
|
||||||
|
currency_id INTEGER, --public.currencies.id%TYPE,
|
||||||
|
currency_name TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.account_class_initialization_data(
|
||||||
|
load_by brmbar_privileged.account_load_type,
|
||||||
|
i_id INTEGER,
|
||||||
|
i_barcode TEXT)
|
||||||
|
RETURNS brmbar_privileged.account_class_initialization_data_type
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
rv brmbar_privileged.account_class_initialization_data_type;
|
||||||
|
BEGIN
|
||||||
|
IF load_by = 'by_id' THEN
|
||||||
|
SELECT a.id, a.name, a.acctype, c.id, c.name
|
||||||
|
INTO STRICT rv.account_id, rv.account_name, rv.account_acctype, rv.currency_id, rv.currency_name
|
||||||
|
FROM public.accounts a JOIN public.currencies c ON a.currency = c.id
|
||||||
|
WHERE a.id = i_id;
|
||||||
|
ELSE -- by_barcode
|
||||||
|
SELECT a.id, a.name, a.acctype, c.id, c.name
|
||||||
|
INTO STRICT rv.account_id, rv.account_name, rv.account_acctype, rv.currency_id, rv.currency_name
|
||||||
|
FROM public.accounts a JOIN public.currencies c ON a.currency = c.id JOIN public.barcodes b ON b.account = a.id
|
||||||
|
WHERE b.barcode = i_barcode;
|
||||||
|
END IF;
|
||||||
|
--
|
||||||
|
RETURN rv;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(25);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql :
|
||||||
86
brmbar3/schema/0026-trading-accounts-2.sql
Normal file
86
brmbar3/schema/0026-trading-accounts-2.sql
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
--
|
||||||
|
-- 0026-trading-accounts-2.sql
|
||||||
|
--
|
||||||
|
-- #26 - maintain trading accounts for each currency
|
||||||
|
--
|
||||||
|
-- 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(25) THEN
|
||||||
|
|
||||||
|
|
||||||
|
-- enforce uniqueness of trading accounts
|
||||||
|
CREATE UNIQUE INDEX uniq_trading_currency
|
||||||
|
ON public.accounts (currency)
|
||||||
|
WHERE acctype = 'trading';
|
||||||
|
|
||||||
|
-- create trading accounts for existing currencies
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
v_rec RECORD;
|
||||||
|
v_trading INTEGER;
|
||||||
|
BEGIN
|
||||||
|
-- Loop through all currencies
|
||||||
|
FOR v_rec IN SELECT id, name FROM public.currencies LOOP
|
||||||
|
SELECT public.create_account(
|
||||||
|
'Trading account: ' || v_rec.name,
|
||||||
|
v_rec.id,
|
||||||
|
'trading'
|
||||||
|
) INTO v_trading;
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.create_currency(
|
||||||
|
IN i_name public.currencies.name%TYPE
|
||||||
|
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||||
|
DECLARE
|
||||||
|
r_id INTEGER;
|
||||||
|
v_trading INTEGER;
|
||||||
|
BEGIN
|
||||||
|
-- First the currency
|
||||||
|
INSERT INTO public.currencies (name)
|
||||||
|
VALUES (i_name) RETURNING id INTO r_id;
|
||||||
|
-- Then the 'trading' account
|
||||||
|
SELECT public.create_account(
|
||||||
|
'Trading account: ' || i_name,
|
||||||
|
r_id,
|
||||||
|
'trading'
|
||||||
|
) INTO v_trading;
|
||||||
|
RETURN r_id;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
PERFORM brmbar_privileged.upgrade_schema_version_to(26);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$upgrade_block$;
|
||||||
|
|
||||||
|
-- vim: set ft=plsql sw=4 ts=4 et :
|
||||||
73
brmbar3/test--currency-rates.py
Normal file
73
brmbar3/test--currency-rates.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
#from brmbar import Database
|
||||||
|
#from brmbar import Currency
|
||||||
|
|
||||||
|
from contextlib import closing
|
||||||
|
import psycopg2
|
||||||
|
from brmbar.Database import Database
|
||||||
|
from brmbar.Currency import Currency
|
||||||
|
import math
|
||||||
|
|
||||||
|
#import brmbar
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def approx_equal(a, b, tol=1e-6):
|
||||||
|
"""Check if two (buy, sell) rate tuples are approximately equal."""
|
||||||
|
return (
|
||||||
|
isinstance(a, tuple) and isinstance(b, tuple) and
|
||||||
|
math.isclose(a[0], b[0], abs_tol=tol) and
|
||||||
|
math.isclose(a[1], b[1], abs_tol=tol)
|
||||||
|
)
|
||||||
|
|
||||||
|
def compare_exceptions(e1, e2):
|
||||||
|
"""Compare exception types and messages."""
|
||||||
|
return type(e1) == type(e2) and str(e1) == str(e2)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
db = Database("dbname=brmbar")
|
||||||
|
|
||||||
|
# Get all currencies
|
||||||
|
with closing(db.db_conn.cursor()) as cur:
|
||||||
|
cur.execute("SELECT id, name FROM currencies")
|
||||||
|
currencies = cur.fetchall()
|
||||||
|
|
||||||
|
# Build Currency objects
|
||||||
|
currency_objs = [Currency(db, id, name) for id, name in currencies]
|
||||||
|
|
||||||
|
# Test all currency pairs
|
||||||
|
for c1 in currency_objs:
|
||||||
|
for c2 in currency_objs:
|
||||||
|
#if c1.id == c2.id:
|
||||||
|
# continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
rates1 = c1.rates(c2)
|
||||||
|
exc1 = None
|
||||||
|
except (RuntimeError, NameError) as e1:
|
||||||
|
rates1 = None
|
||||||
|
exc1 = e1
|
||||||
|
|
||||||
|
try:
|
||||||
|
rates2 = c1.rates2(c2)
|
||||||
|
exc2 = None
|
||||||
|
except (RuntimeError, NameError) as e2:
|
||||||
|
rates2 = None
|
||||||
|
exc2 = e2
|
||||||
|
|
||||||
|
if exc1 or exc2:
|
||||||
|
if not compare_exceptions(exc1, exc2):
|
||||||
|
print(f"[EXCEPTION DIFFERENCE] {c1.name} -> {c2.name}")
|
||||||
|
print(f" rates() exception: {type(exc1).__name__}: {exc1}")
|
||||||
|
print(f" rates2() exception: {type(exc2).__name__}: {exc2}")
|
||||||
|
elif not approx_equal(rates1, rates2):
|
||||||
|
print(f"[VALUE DIFFERENCE] {c1.name} -> {c2.name}")
|
||||||
|
print(f" rates(): {rates1}")
|
||||||
|
print(f" rates2(): {rates2}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
6
brmbar3/uklid-refill.sh
Normal file
6
brmbar3/uklid-refill.sh
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ `./brmbar-cli.py iteminfo uklid|grep -o '[0-9]*.[0-9]* pcs'|cut -d '.' -f 1` -eq 0 ]; then
|
||||||
|
BOUNTY=`./brmbar-cli.py restock uklid 1 | grep -o 'take -[0-9]*'|grep -o '[0-9]*'`
|
||||||
|
echo "Brmlab cleanup bounty for ${BOUNTY}CZK!!!"|ssh jenda@fry.hrach.eu
|
||||||
|
fi
|
||||||
18
brmbar3/uklid-watchdog.sh
Normal file
18
brmbar3/uklid-watchdog.sh
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
LASTIDF=/home/brmlab/uklid.last
|
||||||
|
|
||||||
|
LASTID=`cat $LASTIDF 2>/dev/null || echo 0`
|
||||||
|
|
||||||
|
|
||||||
|
RES=`psql brmbar -Atq -c "select id,description from transactions where id>$LASTID and description like 'BrmBar sale of 1x uklid%' LIMIT 1;"`
|
||||||
|
if [ ! -z "$RES" ]; then
|
||||||
|
LASTID=`echo "$RES"|cut -d '|' -f 1`
|
||||||
|
echo $LASTID > $LASTIDF
|
||||||
|
|
||||||
|
WINNER=`echo "$RES"|grep -o 'to [^ ]*'|cut -d ' ' -f 2`
|
||||||
|
if [ -z "$WINNER" ]; then
|
||||||
|
WINNER="anonymous hunter"
|
||||||
|
fi
|
||||||
|
echo "Brmlab cleanup bounty was claimed by $WINNER! Thanks!"|ssh -p 110 jenda@coralmyn.hrach.eu
|
||||||
|
fi
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue