forked from brmlab/brmbar-github
Compare commits
No commits in common. "master" and "db-reconnect" have entirely different histories.
master
...
db-reconne
74 changed files with 289 additions and 3812 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1 @@
|
|||
.*.sw?
|
||||
*~
|
||||
|
|
2
barcode-generator/.gitignore
vendored
2
barcode-generator/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
barcodes*.svg
|
||||
barcode-generator.txt
|
|
@ -1,51 +0,0 @@
|
|||
#!/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()
|
|
@ -1,9 +0,0 @@
|
|||
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,3 +1 @@
|
|||
__pycache__
|
||||
*.log
|
||||
brmbar/*.pyc
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
BrmBar v3 Installation
|
||||
======================
|
||||
|
||||
This is woefully incomplete; if you are deploying BrmBar, at this
|
||||
point you will likely need to learn at least something aobut its
|
||||
structure and internals. Some code modifications might even be
|
||||
required too. Patches enhancing user configurability are welcome!
|
||||
|
||||
Maybe some things are missing. Ask the developers if you get in trouble,
|
||||
e.g. in #brmlab on FreeNode.
|
||||
|
||||
Hardware Requirements
|
||||
---------------------
|
||||
|
||||
* Display. Current UI is optimized for 4:3, 1024x768 display.
|
||||
* Touchscreen. In emergency, you can use a mouse too, but it's
|
||||
clumsy and scrolling is not too intuitive.
|
||||
* Barcode reader. We want the kind that will behave as a HID device
|
||||
and on scanning a barcode, it will send a CR-terminated scanned string.
|
||||
* Physical keyboard stashed in vicinity will help. It is possible
|
||||
to enter text (inventory names, receipt reasons) on the touchscreen,
|
||||
but it's a bit frustrating.
|
||||
* You will want to print a sheet of barcodes with names of all user
|
||||
accounts; these will be then used by people to buy stuff using their
|
||||
accounts - first scan barcode of the item, then scan your barcode,
|
||||
voila. Scanning your barcode directly can bring the user to a screen
|
||||
where they can see their credit and charge it too. See also USAGE.
|
||||
|
||||
Software Requirements
|
||||
---------------------
|
||||
|
||||
* Developed and tested on Debian, but should work on other systems too.
|
||||
* Python 3.
|
||||
* QT4 with Python bindings:
|
||||
* QT4 with the "Declarative" module, e.g. libqt4-declarative package.
|
||||
* The PySide Qt4 bindings, e.g. python3-pyside.qtdeclarative package.
|
||||
* Installing the qtcreator program may be helpful for QML testing
|
||||
and development.
|
||||
* PostgreSQL with Python pindings:
|
||||
* The database server itself, e.g. postgresql package.
|
||||
* PsyCoPg2, e.g. python3-psycopg2 package.
|
||||
|
||||
Ubuntu packages installation instructions
|
||||
-----------------------------------------
|
||||
* sudo apt-get install postgresql libqt4-declarative python3 python3-pyside.qtdeclarative python3-psycopg2 python3-pyqt4
|
||||
|
||||
Software Setup
|
||||
--------------
|
||||
|
||||
* Create psql user and `brmbar` database.
|
||||
|
||||
brmuser@host:~> su postgres
|
||||
postgres@host:/home/user> createuser -D brmuser
|
||||
postgres@host:/home/user> su brmuser
|
||||
brmuser@host:~> createdb brmbar
|
||||
|
||||
* The SQL schema in file `SQL` contains the required SQL tables,
|
||||
but also INSERTs that add some rows essential for proper operation;
|
||||
base currency and two base accounts. You *will* want to tweak the
|
||||
currency name; default is `Kč` (the Czech crown), replace it with
|
||||
your currency symbol. Then do `git grep 'Kč'` and replace all other
|
||||
occurences of `Kč` in brmbar source with your currency name.
|
||||
* Load the SQL schema stored in file `SQL` in the database.
|
||||
|
||||
brmuser@host:~/brmbar/brmbar3> psql brmbar
|
||||
psql (9.1.8)
|
||||
Type "help" for help.
|
||||
|
||||
brmbar=# \i SQL
|
||||
|
||||
* You should be able to fire up the GUI now and start entering data.
|
||||
If you want to make sure all works as expected, execute the SQL
|
||||
statements in file `SQL.test` (revisit for currency names too) which
|
||||
will populate the database with a bit of sample data for testing.
|
||||
* Regarding adding users at this point and for other usage instructions,
|
||||
refer to the USAGE file.
|
||||
|
||||
TODO: Mention the actual commands to execute.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Assuming that you run brmbar from a terminal, if something gets
|
||||
stuck, you can switch to the terminal by Alt-TAB, then kill brmbar
|
||||
by the Ctrl-\ shortcut (sends SIGQUIT) and restart it.
|
|
@ -1,64 +0,0 @@
|
|||
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.
|
|
@ -1,81 +0,0 @@
|
|||
BrmBar v3
|
||||
=========
|
||||
|
||||
BrmBar is a management system for running a tiny hackerspace shop
|
||||
with self-service usage based on trust and support for user accounts
|
||||
and inventory tracking.
|
||||
|
||||
BrmBar offers a touchscreen-based user interface, identifies items
|
||||
and users by barcodes scanned by a barcode reader, should run on any
|
||||
decent Linux machine and stores its data in PostgreSQL database.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Very simple user interface (using big touchscreen buttons and
|
||||
barcode reader) that should enable even non-technical users to
|
||||
do basic shopping with little to no training.
|
||||
* Users may have their accounts they can load with money by
|
||||
depositing larger sum of money in advance, then charging their
|
||||
account when buying stuff. Of course, paying direct for cash
|
||||
is also supported.
|
||||
* Inventory and cash accounts are tracked so that you can make sure
|
||||
there is no Club Mate mysteriously disappearing or if the amount
|
||||
of cash in the cash box is not less than expected by the system.
|
||||
* You can enter receipts for duct tapes and other necessities to be
|
||||
financed by cash surplus generated by brmbar.
|
||||
* Simple management operations (depositing and withdrawing money
|
||||
from user accounts, entering receipts, stocking in new inventory)
|
||||
can be also performed in the user interface even by non-technical
|
||||
users with basic training.
|
||||
* The database is based on the classical accounting paradigm.
|
||||
This means no information is needlessly lost, you could even
|
||||
make a GNUCash export and your accounting geeks will feel warm
|
||||
and fuzzy.
|
||||
* Multiple user interfaces available (and possible). The primary
|
||||
user interface is based on QtQuick (Qt4 QML QtDeclarative).
|
||||
|
||||
User Interfaces
|
||||
---------------
|
||||
|
||||
These UIs are provided:
|
||||
|
||||
* **brmbar-gui-qt4**: The default touchscreen-based UI. The Python side
|
||||
provides an adapter object whose methods can be executed by the QML
|
||||
code; ad-hoc directionary objects are used to exchange complex data
|
||||
like account information.
|
||||
* **brmbar-tui**: A trivial text-based "shell" UI that mimics a historic
|
||||
interface used in the Brmlab hackerspace in the past. It supports only
|
||||
selling items, querying item price and user account balance and
|
||||
depositing money for the user accounts.
|
||||
* **brmbar-cli**: A command-line interface intended for use in scripts
|
||||
and remote usage when fixing problems. It is also meant to provide
|
||||
advanced functionality like inventory revision that is too tedious
|
||||
to implement in the Qt4 GUI and only the brmbar admins are expected
|
||||
to do these tasks.
|
||||
* **brmbar-web**: A simple read-only web interface to the stock list.
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
* The user interface needs some improvements, mainly regarding
|
||||
scrolling in large lists.
|
||||
* The brmbar-cli.py admin script for advanced/remote management
|
||||
operations is largely unfinished. In the meantime, you need to use
|
||||
SQL statements, sorry. Or finish it yourself. :-)
|
||||
* It is common to have two stashes of cash, one in a cash box
|
||||
in the shop, another in a vault (sometimes called "overflow")
|
||||
where extra cash is stored. The brmbar model supports this,
|
||||
but UI support needs to be added.
|
||||
* Bitcoin support, somehow...
|
||||
|
||||
Some more TODO items may be listed in the GitHub issue tracker;
|
||||
missing brmbar-gui-qt4 features are listed in the `TODO` file.
|
||||
|
||||
Other Resources
|
||||
---------------
|
||||
|
||||
See the INSTALL file for setup instructions and USAGE file for
|
||||
basic usage instructions. The doc/architecture file describes
|
||||
the brmbar object model and briefly explains the brmbar Python
|
||||
package.
|
41
brmbar3/SQL
41
brmbar3/SQL
|
@ -1,11 +1,10 @@
|
|||
CREATE SEQUENCE currencies_id_seq START WITH 2 INCREMENT BY 1;
|
||||
CREATE SEQUENCE currencies_id_seq START WITH 1 INCREMENT BY 1;
|
||||
CREATE TABLE currencies (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('currencies_id_seq'::regclass),
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
-- Some code depends on the primary physical currency to have id 1.
|
||||
INSERT INTO currencies (id, name) VALUES (1, 'Kč');
|
||||
INSERT INTO currencies (name) VALUES ('Kč');
|
||||
|
||||
CREATE TYPE exchange_rate_direction AS ENUM ('source_to_target', 'target_to_source');
|
||||
CREATE TABLE exchange_rates (
|
||||
|
@ -37,14 +36,10 @@ CREATE TABLE accounts (
|
|||
currency INTEGER NOT NULL,
|
||||
FOREIGN KEY (currency) REFERENCES currencies (id),
|
||||
|
||||
acctype account_type NOT NULL,
|
||||
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
acctype account_type NOT NULL
|
||||
);
|
||||
INSERT INTO accounts (name, currency, acctype) VALUES ('BrmBar Cash', (SELECT id FROM currencies WHERE name='Kč'), 'cash');
|
||||
INSERT INTO accounts (name, currency, acctype) VALUES ('BrmBar Profits', (SELECT id FROM currencies WHERE name='Kč'), 'income');
|
||||
INSERT INTO accounts (name, currency, acctype) VALUES ('BrmBar Excess', (SELECT id FROM currencies WHERE name='Kč'), 'income');
|
||||
INSERT INTO accounts (name, currency, acctype) VALUES ('BrmBar Deficit', (SELECT id FROM currencies WHERE name='Kč'), 'expense');
|
||||
|
||||
|
||||
CREATE SEQUENCE barcodes_id_seq START WITH 1 INCREMENT BY 1;
|
||||
|
@ -54,9 +49,6 @@ CREATE TABLE barcodes (
|
|||
account INTEGER NOT NULL,
|
||||
FOREIGN KEY (account) REFERENCES accounts (id)
|
||||
);
|
||||
-- Barcode for cash
|
||||
-- XXX Silently assume there is only one.
|
||||
INSERT INTO barcodes (barcode, account) VALUES ('_cash_', (SELECT id FROM accounts WHERE acctype = 'cash'));
|
||||
|
||||
|
||||
CREATE SEQUENCE transactions_id_seq START WITH 1 INCREMENT BY 1;
|
||||
|
@ -98,30 +90,3 @@ CREATE VIEW account_balances AS
|
|||
LEFT JOIN accounts ON accounts.id = ts.account
|
||||
GROUP BY ts.account, accounts.name, accounts.acctype
|
||||
ORDER BY crbalance ASC;
|
||||
|
||||
-- Transaction splits in a form that's nicer to query during manual inspection
|
||||
CREATE VIEW transaction_nicesplits AS
|
||||
SELECT ts.id AS id, ts.transaction AS transaction, ts.account AS account,
|
||||
(CASE WHEN ts.side = 'credit' THEN -ts.amount ELSE ts.amount END) AS amount,
|
||||
a.currency AS currency, ts.memo AS memo
|
||||
FROM transaction_splits AS ts LEFT JOIN accounts AS a ON a.id = ts.account
|
||||
ORDER BY ts.id;
|
||||
|
||||
-- List transactions with summary information regarding their cash element.
|
||||
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, a.name AS responsible, t.description AS description
|
||||
FROM transactions AS t
|
||||
LEFT JOIN (SELECT cts.amount AS credit_cash, cts.transaction AS cts_t
|
||||
FROM transaction_nicesplits AS cts
|
||||
LEFT JOIN accounts AS a ON a.id = cts.account OR a.id = cts.account
|
||||
WHERE a.currency = (SELECT currency FROM accounts WHERE name = 'BrmBar Cash')
|
||||
AND a.acctype IN ('cash', 'debt')
|
||||
AND cts.amount < 0) credit ON cts_t = t.id
|
||||
LEFT JOIN (SELECT dts.amount AS debit_cash, dts.transaction AS dts_t
|
||||
FROM transaction_nicesplits AS dts
|
||||
LEFT JOIN accounts AS a ON a.id = dts.account OR a.id = dts.account
|
||||
WHERE a.currency = (SELECT currency FROM accounts WHERE name = 'BrmBar Cash')
|
||||
AND a.acctype IN ('cash', 'debt')
|
||||
AND dts.amount > 0) debit ON dts_t = t.id
|
||||
LEFT JOIN accounts AS a ON a.id = t.responsible
|
||||
GROUP BY t.id, a.name ORDER BY t.id DESC;
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
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;
|
||||
$$;
|
||||
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
--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 :
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
--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 :
|
25
brmbar3/TODO
25
brmbar3/TODO
|
@ -1,5 +1,26 @@
|
|||
This reprensents some generic features that would need to be implemented
|
||||
in brmbar-gui-qt4 to have a fully fledged user interface.
|
||||
+ Management view
|
||||
+ Stock management link
|
||||
+ User management link
|
||||
+ Bilance overview (Cash, Profit, Credit total)
|
||||
+ Item picker from list
|
||||
+ User management
|
||||
+ List of users
|
||||
+ Withdrawal of user credit
|
||||
+ Numerical manual entry support
|
||||
+ Use for credit charge
|
||||
+ Use for withdrawal
|
||||
+ Restocking view (Stock management)
|
||||
+ Item picker with edit button
|
||||
+ Item editor (name, buy price, sale price, quantity)
|
||||
+ Item-barcode assignment
|
||||
+ Support for adding new items
|
||||
+ Alphanumeric manual entry support
|
||||
+ Use in item editor
|
||||
+ Withdrawal for brmbar receipts
|
||||
1. User responsible
|
||||
2. Amount and description
|
||||
|
||||
** At this point it should be good enough to deploy **
|
||||
|
||||
. User management
|
||||
- Add user
|
||||
|
|
153
brmbar3/USAGE.md
153
brmbar3/USAGE.md
|
@ -1,153 +0,0 @@
|
|||
Quick Guide
|
||||
-----------
|
||||
|
||||
* I want to buy for cash: I scan item's barcode, press **Pay by Cash** and pour
|
||||
money into the cash box.
|
||||
|
||||
* I want to buy from credit: I scan item's barcode, then my barcode.
|
||||
(If you don't have your barcode printed out, you can also type your
|
||||
username on a physical keyboard.)
|
||||
|
||||
* I want to put money on credit: press **Charge**, I scan my barcode,
|
||||
type some amount, press **Charge** and put money in the cash box.
|
||||
|
||||
|
||||
Advanced Operations
|
||||
-------------------
|
||||
|
||||
* I want to withdraw funds from my (positive) credit:
|
||||
Press **Management**, choose **User Mgmt**, scan your barcode,
|
||||
press the Withdraw Amount and type the amount. Then take the money
|
||||
from the cash box.
|
||||
|
||||
* I want to stock in some inventory (that's been in brmbar before):
|
||||
Press **Management**, **Stock Mgmt**, scan barcode of the item, edit
|
||||
the purchase price (or also the selling price and label), press
|
||||
**Restock** and enter the quantity of stocked in piece. Press **Save**.
|
||||
Toss the bill (if possible with the current written date, to allow
|
||||
pairing) to brmbar.
|
||||
|
||||
* I want to stock in some new inventory: Press **Management**, **Stock
|
||||
Mgmt**, press **Add new item**, enter the name, purchase and selling price,
|
||||
press **Create**. Then press **Restock**, enter the quantity stocked in.
|
||||
Scan the item's barcode and press **Save**. Toss the bill in brmbar.
|
||||
|
||||
* I want to bill the brmbar with some small expenses like duct tape:
|
||||
Press **Management** and **Receipt**. Press **Description** and write
|
||||
a brief description of the bill. Press **Edit** near the **Money Amount**
|
||||
and enter the amount. Scan *your* barcode. The operation is finished
|
||||
by pressing **Create**. Toss bill (inscribed with the current date
|
||||
to ease pairing) to brmbar.
|
||||
|
||||
|
||||
General Notes
|
||||
-------------
|
||||
|
||||
The system expects that we take money from the cash box right away.
|
||||
If you don't want to (or there is e.g. not enough money), put money
|
||||
on your credit account instead (see above). Please always do that
|
||||
(never *I'll remember and I'll take money later*) so that there is
|
||||
a record that the cash box and system records are not in sync and
|
||||
there are no irregularities.
|
||||
|
||||
To enter text (or numbers too), you can use both the on-screen keyboard
|
||||
and the physical keyboard nearby.
|
||||
|
||||
|
||||
Administrative Usage
|
||||
--------------------
|
||||
|
||||
* The most common administrative action you will need to do is adding
|
||||
new user (also called debt or credit) accounts. The GUI support for
|
||||
this is not implemented yet, but the `brmbar-cli.py` UI allows it:
|
||||
|
||||
./brmbar-cli.py adduser joehacker
|
||||
|
||||
Afterwards, print out a barcode saying "joehacker" and stick that
|
||||
somewhere nearby; scanning that barcode will allow access to this
|
||||
account (and so will typing "joehacker" on a physical keyboard).
|
||||
|
||||
* If your inventory stock count or cash box amount does not match
|
||||
the in-system data, you will need to make a corrective transaction.
|
||||
To fix cash amount to reality in which you counted 1234Kč, use
|
||||
|
||||
./brmbar-cli.py fixcash 1234
|
||||
|
||||
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
|
||||
------------------
|
||||
|
||||
* Compute sum of sold stock:
|
||||
|
||||
select sum(amount) from transactions
|
||||
left join transaction_splits on transaction_splits.transaction = transactions.id
|
||||
where description like '% sale %' and side = 'debit';
|
||||
|
||||
* List of items not covered by inventory check:
|
||||
|
||||
select * from account_balances
|
||||
where id not in (select account from transactions
|
||||
left join transaction_splits on transaction_splits.transaction = transactions.id
|
||||
where description like '% inventory %')
|
||||
and acctype = 'inventory';
|
||||
|
||||
* List all cash transactions:
|
||||
|
||||
select time, transactions.id, description, responsible, amount from transactions
|
||||
left join transaction_splits on transaction_splits.transaction = transactions.id
|
||||
where transaction_splits.account = 1;
|
||||
|
||||
* List all inventory items ordered by their cummulative worth:
|
||||
|
||||
select foo.*, foo.rate * -foo.crbalance as worth from
|
||||
(select account_balances.*,
|
||||
(select exchange_rates.rate from exchange_rates, accounts
|
||||
where exchange_rates.target = accounts.currency
|
||||
and accounts.id = account_balances.id
|
||||
order by exchange_rates.valid_since limit 1) as rate
|
||||
from account_balances where account_balances.acctype = 'inventory')
|
||||
as foo order by worth;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
case $1 in
|
||||
alert) mplayer -really-quiet ~/trombone.wav & ;;
|
||||
limit) mplayer -really-quiet ~/much.wav & ;;
|
||||
charge) mplayer -really-quiet ~/charge.wav & ;;
|
||||
esac
|
|
@ -1,42 +0,0 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import brmbar
|
||||
import math
|
||||
from brmbar import Database
|
||||
|
||||
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__":
|
||||
main()
|
||||
|
|
@ -1,266 +1,61 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
|
||||
from brmbar import Database
|
||||
import psycopg2
|
||||
|
||||
import brmbar
|
||||
|
||||
|
||||
def help():
|
||||
print("""BrmBar v3 (c) Petr Baudis <pasky@ucw.cz> 2012-2013
|
||||
|
||||
Usage: brmbar-cli.py COMMAND ARGS...
|
||||
|
||||
1. Commands pertaining the standard operation
|
||||
showcredit USER
|
||||
changecredit USER +-AMT
|
||||
sellitem {USER|"cash"} ITEM +-AMT
|
||||
You can use negative AMT to undo a sale.
|
||||
restock ITEM AMT
|
||||
userinfo USER
|
||||
userlog USER TIMESTAMP
|
||||
iteminfo ITEM
|
||||
|
||||
2. Management commands
|
||||
listusers
|
||||
List all user accounts in the system.
|
||||
listitems
|
||||
List all item accounts in the system.
|
||||
stats
|
||||
A set of various balances as shown in the Management
|
||||
screen of the GUI.
|
||||
adduser USER
|
||||
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 recounting (fixing the number of items)
|
||||
inventory-interactive
|
||||
Launches interactive mode for performing inventory with barcode reader
|
||||
fixcash AMT
|
||||
Fixes the cash and puts money difference into excess or deficit account
|
||||
consolidate
|
||||
Wraps up inventory + cash recounting, transferring the excess and
|
||||
deficit accounts balance to the profits account and resetting them
|
||||
|
||||
USER and ITEM may be barcodes or account ids. AMT may be
|
||||
both positive and negative amount (big difference to other
|
||||
user interfaces; you can e.g. undo a sale!).
|
||||
|
||||
For users, you can use their name as USER as their username
|
||||
is also the barcode. For items, use listitems command first
|
||||
to find out the item id.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def load_acct(inp):
|
||||
acct = None
|
||||
if inp.isdigit():
|
||||
acct = brmbar.Account.load(db, id = inp)
|
||||
if acct is None:
|
||||
acct = brmbar.Account.load_by_barcode(db, inp)
|
||||
if acct is None:
|
||||
print("Cannot map account " + inp, file=sys.stderr)
|
||||
exit(1)
|
||||
return acct
|
||||
|
||||
def load_user(inp):
|
||||
acct = load_acct(inp)
|
||||
if acct.acctype != "debt":
|
||||
print("Bad account " + inp + " type " + acct.acctype, file=sys.stderr)
|
||||
exit(1)
|
||||
return acct
|
||||
|
||||
def load_item(inp):
|
||||
acct = load_acct(inp)
|
||||
if acct.acctype != "inventory":
|
||||
print("Bad account " + inp + " type " + acct.acctype, file=sys.stderr)
|
||||
exit(1)
|
||||
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 = psycopg2.connect("dbname=brmbar")
|
||||
shop = brmbar.Shop.new_with_defaults(db)
|
||||
currency = shop.currency
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
help()
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
|
||||
for line in sys.stdin:
|
||||
barcode = line.rstrip()
|
||||
|
||||
if sys.argv[1] == "showcredit":
|
||||
acct = load_user(sys.argv[2])
|
||||
print("{}: {}".format(acct.name, acct.negbalance_str()))
|
||||
if barcode[0] == "$":
|
||||
credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000}
|
||||
credit = credits[barcode]
|
||||
if credit is None:
|
||||
print("Unknown barcode: " + barcode)
|
||||
continue
|
||||
print("CREDIT " + str(credit))
|
||||
active_inv_item = None
|
||||
active_credit = credit
|
||||
continue
|
||||
|
||||
elif sys.argv[1] == "changecredit":
|
||||
acct = load_user(sys.argv[2])
|
||||
amt = int(sys.argv[3])
|
||||
if amt > 0:
|
||||
shop.add_credit(credit = amt, user = acct)
|
||||
elif amt < 0:
|
||||
shop.withdraw_credit(credit = -amt, user = acct)
|
||||
print("{}: {}".format(acct.name, acct.negbalance_str()))
|
||||
if barcode == "SCR":
|
||||
print("SHOW CREDIT")
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
continue
|
||||
|
||||
elif sys.argv[1] == "sellitem":
|
||||
if sys.argv[2] == "cash":
|
||||
uacct = shop.cash
|
||||
else:
|
||||
uacct = load_user(sys.argv[2])
|
||||
iacct = load_item(sys.argv[3])
|
||||
amt = int(sys.argv[4])
|
||||
if amt > 0:
|
||||
if uacct == shop.cash:
|
||||
shop.sell_for_cash(item = iacct, amount = amt)
|
||||
acct = brmbar.Account.load_by_barcode(db, barcode)
|
||||
if acct is None:
|
||||
print("Unknown barcode: " + barcode)
|
||||
continue
|
||||
|
||||
if acct.acctype == 'debt':
|
||||
if active_inv_item is not None:
|
||||
cost = shop.sell(item = active_inv_item, user = acct)
|
||||
print("{} has bought {} for {} and now has {} balance".format(acct.name, active_inv_item.name, currency.str(cost), acct.negbalance_str()))
|
||||
elif active_credit is not None:
|
||||
shop.add_credit(credit = active_credit, user = acct)
|
||||
print("{} has added {} credit and now has {} balance".format(acct.name, currency.str(active_credit), acct.negbalance_str()))
|
||||
else:
|
||||
shop.sell(item = iacct, user = uacct, amount = amt)
|
||||
elif amt < 0:
|
||||
shop.undo_sale(item = iacct, user = uacct, amount = -amt)
|
||||
print("{}: {}".format(uacct.name, uacct.balance_str() if uacct == shop.cash else uacct.negbalance_str()))
|
||||
print("{}: {}".format(iacct.name, iacct.balance_str()))
|
||||
print("{} has {} balance".format(acct.name, acct.negbalance_str()))
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
|
||||
elif sys.argv[1] == "userinfo":
|
||||
acct = load_user(sys.argv[2])
|
||||
print("{} (id {}): {}".format(acct.name, acct.id, acct.negbalance_str()))
|
||||
elif acct.acctype == 'inventory':
|
||||
buy, sell = acct.currency.rates(currency)
|
||||
print("{} costs {} with {} in stock".format(acct.name, currency.str(sell), int(acct.balance())))
|
||||
active_inv_item = acct
|
||||
active_credit = None
|
||||
|
||||
res = db.execute_and_fetchall("SELECT barcode FROM barcodes WHERE account = %s", [acct.id])
|
||||
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":
|
||||
acct = load_item(sys.argv[2])
|
||||
print("{} (id {}): {} pcs".format(acct.name, acct.id, acct.balance()))
|
||||
|
||||
(buy, sell) = acct.currency.rates(currency)
|
||||
print("Buy: " + currency.str(buy) + " Sell: " + currency.str(sell));
|
||||
|
||||
res = db.execute_and_fetchall("SELECT barcode FROM barcodes WHERE account = %s", [acct.id])
|
||||
print("Barcodes: " + ", ".join(map((lambda r: r[0]), res)))
|
||||
|
||||
elif sys.argv[1] == "listusers":
|
||||
for acct in shop.account_list("debt"):
|
||||
print("{}\t{}\t{}".format(acct.name, acct.id, acct.negbalance_str()))
|
||||
|
||||
elif sys.argv[1] == "listitems":
|
||||
for acct in shop.account_list("inventory"):
|
||||
print("{}\t{}\t{} pcs".format(acct.name, acct.id, acct.balance()))
|
||||
|
||||
elif sys.argv[1] == "stats":
|
||||
print("--- Material Assets ---")
|
||||
print("Cash: {}".format(shop.cash.balance_str()))
|
||||
print("Overflow: {}".format(shop.currency.str(shop.credit_balance(overflow='only'))))
|
||||
print("Inventory: {}".format(shop.inventory_balance_str()))
|
||||
print("--- Logical Accounts ---")
|
||||
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":
|
||||
acct = brmbar.Account.create(db, sys.argv[2], brmbar.Currency.load(db, id = 1), 'debt')
|
||||
acct.add_barcode(sys.argv[2]) # will commit
|
||||
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":
|
||||
if (len(sys.argv) % 2 != 0 or len(sys.argv) < 4):
|
||||
print ("Invalid number of parameters, count your parameters.")
|
||||
else:
|
||||
for i in range(2, len(sys.argv), 2):
|
||||
iacct = load_item(sys.argv[i])
|
||||
iamt = int(sys.argv[i+1])
|
||||
print("Current state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
||||
if shop.fix_inventory(item = iacct, amount = iamt):
|
||||
print("New state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
||||
else:
|
||||
print ("No action needed amount is correct.")
|
||||
|
||||
|
||||
elif sys.argv[1] == "inventory-interactive":
|
||||
print("Inventory interactive mode. To exit interactive mode just enter empty barcode")
|
||||
|
||||
while True:
|
||||
barcode = str(input("Enter barcode:"))
|
||||
fuckyou = input("fuckyou")
|
||||
if barcode == "":
|
||||
break
|
||||
iacct = brmbar.Account.load_by_barcode(db, barcode)
|
||||
amount = str(input("What is the amount of {} in reality (expected: {} pcs):".format(iacct.name, iacct.balance())))
|
||||
if amount == "":
|
||||
break
|
||||
elif int(amount) > 10000:
|
||||
print("Ignoring too high amount {}, assuming barcode was mistakenly scanned instead".format(amount))
|
||||
else:
|
||||
iamt = int(amount)
|
||||
print("Current state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
||||
if shop.fix_inventory(item = iacct, amount = iamt):
|
||||
print("New state {} (id {}): {} pcs".format(iacct.name, iacct.id, iacct.balance()))
|
||||
else:
|
||||
print("No action needed, amount is correct.")
|
||||
print("End of processing. Bye")
|
||||
|
||||
elif sys.argv[1] == "fixcash" or sys.argv[1] == "changecash":
|
||||
if (len(sys.argv) != 3):
|
||||
print ("Invalid number of parameters, check your parameters.")
|
||||
else:
|
||||
print("Current Cash is : {}".format(shop.cash.balance_str()))
|
||||
iamt = int(sys.argv[2])
|
||||
if shop.fix_cash(amount = iamt):
|
||||
print("New Cash is : {}".format(shop.cash.balance_str()))
|
||||
else:
|
||||
print ("No action needed amount is the same.")
|
||||
|
||||
elif sys.argv[1] == "consolidate":
|
||||
if (len(sys.argv) != 2):
|
||||
print ("Invalid number of parameters, check your parameters.")
|
||||
else:
|
||||
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:
|
||||
help()
|
||||
print("invalid account type {}".format(acct.acctype))
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
from PySide import QtCore, QtGui, QtDeclarative
|
||||
|
||||
|
@ -9,15 +8,6 @@ from brmbar import Database
|
|||
|
||||
import brmbar
|
||||
|
||||
# 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):
|
||||
""" Interface between QML and the brmbar package """
|
||||
|
@ -39,18 +29,6 @@ class ShopAdapter(QtCore.QObject):
|
|||
map["price"] = str(sell)
|
||||
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):
|
||||
map = acct.__dict__.copy()
|
||||
return map
|
||||
|
||||
def acct_map(self, acct):
|
||||
if acct is None:
|
||||
return None
|
||||
|
@ -58,8 +36,6 @@ class ShopAdapter(QtCore.QObject):
|
|||
return self.acct_debt_map(acct)
|
||||
elif acct.acctype == "inventory":
|
||||
return self.acct_inventory_map(acct)
|
||||
elif acct.acctype == "cash":
|
||||
return self.acct_cash_map(acct)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -88,18 +64,6 @@ class ShopAdapter(QtCore.QObject):
|
|||
db.commit()
|
||||
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')
|
||||
def sellItem(self, itemid, userid):
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
|
@ -115,7 +79,6 @@ class ShopAdapter(QtCore.QObject):
|
|||
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def chargeCredit(self, credit, userid):
|
||||
subprocess.call(["sh", ALERT_SCRIPT, "charge"])
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.add_credit(credit = credit, user = user)
|
||||
balance = user.negbalance_str()
|
||||
|
@ -130,19 +93,6 @@ class ShopAdapter(QtCore.QObject):
|
|||
db.commit()
|
||||
return balance
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', 'QVariant', result='QVariant')
|
||||
def newTransfer(self, uidfrom, uidto, amount):
|
||||
ufrom = brmbar.Account.load(db, id=uidfrom)
|
||||
uto = brmbar.Account.load(db, id=uidto)
|
||||
shop.transfer_credit(ufrom, uto, amount = amount)
|
||||
db.commit()
|
||||
return currency.str(float(amount))
|
||||
|
||||
@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')
|
||||
def balance_cash(self):
|
||||
balance = shop.cash.balance_str()
|
||||
|
@ -170,9 +120,9 @@ class ShopAdapter(QtCore.QObject):
|
|||
db.commit()
|
||||
return alist
|
||||
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def itemList(self, query):
|
||||
alist = [ self.acct_inventory_map2(a) for a in shop.account_list("inventory", like_str="%%"+query+"%%") ]
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def itemList(self):
|
||||
alist = [ self.acct_inventory_map(a) for a in shop.account_list("inventory") ]
|
||||
db.commit()
|
||||
return alist
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Rectangle {
|
||||
|
@ -9,7 +10,7 @@ Rectangle {
|
|||
|
||||
property string text: "Button"
|
||||
property int fontSize: 0.768 * 60
|
||||
property string btnColor: "#aaaaaa"
|
||||
property variant btnColor: "#aaaaaa"
|
||||
|
||||
signal buttonClick
|
||||
onButtonClick: { /* Supplied by component user. */ }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Rectangle {
|
||||
|
@ -5,8 +6,8 @@ Rectangle {
|
|||
width: 320
|
||||
height: 65
|
||||
property variant now: new Date()
|
||||
property string textColor: "#000000"
|
||||
property real textSize: 0.768 * 16
|
||||
property variant textColor: "#000000"
|
||||
property variant textSize: 0.768 * 16
|
||||
Timer {
|
||||
id: clockUpdater
|
||||
interval: 1000 // update clock every second
|
||||
|
|
|
@ -4,6 +4,6 @@ BarKeyboard {
|
|||
keys: "0123456789<qwertyuiop-asdfghjkl/+^zxcvbnm ,."
|
||||
gridRows: 5
|
||||
gridColumns: 11
|
||||
buttonWidth: 90
|
||||
buttonHeight: 80
|
||||
buttonWidth: 80
|
||||
buttonHeight: 70
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ Grid {
|
|||
property string enteredText: ""
|
||||
property int gridRows: 0
|
||||
property int gridColumns: 0
|
||||
property int buttonWidth: 80
|
||||
property int buttonHeight: 80
|
||||
property int buttonWidth: 70
|
||||
property int buttonHeight: 70
|
||||
|
||||
property bool shift: false
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ Rectangle {
|
|||
|
||||
property int scrollbarWidth: 20
|
||||
|
||||
property string color: "white"
|
||||
property real baseOpacityOff: 0.4
|
||||
property real baseOpacityOn: 0.9
|
||||
property variant color: "white"
|
||||
property variant baseOpacityOff: 0.4
|
||||
property variant baseOpacityOn: 0.9
|
||||
|
||||
radius: vertical ? width/2 : height/2
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Item {
|
||||
id: main_hint
|
||||
width: 1150
|
||||
width: 894
|
||||
height: 80
|
||||
|
||||
property string hint_goal: ""
|
||||
property string hint_action: ""
|
||||
property variant hint_goal: ""
|
||||
property variant hint_action: ""
|
||||
|
||||
Text {
|
||||
id: text1
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
TextInput {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Rectangle {
|
||||
width: 1280
|
||||
height: 1024
|
||||
width: 1024
|
||||
height: 768
|
||||
color: "#000000"
|
||||
Text {
|
||||
id: title
|
||||
|
@ -16,7 +18,7 @@ Rectangle {
|
|||
}
|
||||
BarClock {
|
||||
id: clock
|
||||
x: 456
|
||||
x: 328
|
||||
y: 35
|
||||
color: "#000000"
|
||||
textColor: "#217777"
|
||||
|
@ -25,7 +27,7 @@ Rectangle {
|
|||
|
||||
Image {
|
||||
id: image1
|
||||
x: 944
|
||||
x: 688
|
||||
y: 41
|
||||
height: 65
|
||||
smooth: true
|
||||
|
@ -36,7 +38,7 @@ Rectangle {
|
|||
property alias status_text: status_text_id
|
||||
Text {
|
||||
id: status_text_id
|
||||
x: 193
|
||||
x: 65
|
||||
y: 112
|
||||
width: 894
|
||||
color: "#ff4444"
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property string username: ""
|
||||
property string userdbid: ""
|
||||
property string amount: credit_pad.enteredText
|
||||
property variant username: ""
|
||||
property variant userdbid: ""
|
||||
property variant amount: credit_pad.enteredText
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -62,7 +64,7 @@ Item {
|
|||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype === "debt") {
|
||||
if (acct.acctype == "debt") {
|
||||
username = acct.name
|
||||
userdbid = acct.id
|
||||
} else {
|
||||
|
@ -77,7 +79,7 @@ Item {
|
|||
BarButton {
|
||||
id: charge_button
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Charge"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -89,8 +91,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
@ -100,17 +102,8 @@ Item {
|
|||
}
|
||||
|
||||
function chargeCredit() {
|
||||
var balance=0
|
||||
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")
|
||||
}
|
||||
}
|
||||
var balance = shop.chargeCredit(amount, userdbid)
|
||||
status_text.setStatus("Charged! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||
loadPage("MainPage")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property string item_name: item_name_pad.enteredText
|
||||
property string dbid: ""
|
||||
property variant item_name: item_name_pad.enteredText
|
||||
property variant dbid: ""
|
||||
property variant info: ""
|
||||
property string buy_price: item_buyprice_pad.enteredText
|
||||
property string price: item_sellprice_pad.enteredText
|
||||
property variant buy_price: item_buyprice_pad.enteredText
|
||||
property variant price: item_sellprice_pad.enteredText
|
||||
|
||||
property string barcode: ""
|
||||
state: "normal"
|
||||
|
@ -26,7 +28,7 @@ Item {
|
|||
/* TODO: Allow override. */
|
||||
return
|
||||
}
|
||||
if (info.dbid === "") {
|
||||
if (info.dbid == "") {
|
||||
status_text.setStatus("Press [Create] first", "#ff4444")
|
||||
return
|
||||
}
|
||||
|
@ -58,7 +60,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_name_edit
|
||||
x: 790
|
||||
x: 534
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -113,7 +115,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_buyprice_edit
|
||||
x: 790
|
||||
x: 534
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -168,7 +170,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_sellprice_edit
|
||||
x: 790
|
||||
x: 534
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -223,7 +225,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_balance_restock
|
||||
x: 790
|
||||
x: 534
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -304,46 +306,46 @@ Item {
|
|||
BarButton {
|
||||
id: save
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: dbid == "" ? "Create" : "Save"
|
||||
onButtonClick: {
|
||||
var xi = info;
|
||||
xi["name"] = page.item_name;
|
||||
xi["buy_price"] = page.buy_price;
|
||||
xi["price"] = page.price;
|
||||
info = xi
|
||||
var xi = info;
|
||||
xi["name"] = page.item_name;
|
||||
xi["buy_price"] = page.buy_price;
|
||||
xi["price"] = page.price;
|
||||
info = xi
|
||||
|
||||
var res;
|
||||
if (dbid == "") {
|
||||
res = shop.newItem(info)
|
||||
if (!res) {
|
||||
status_text.setStatus("Please fill all values first.", "#ff4444")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
res = shop.saveItem(dbid, info)
|
||||
}
|
||||
var res;
|
||||
if (dbid == "") {
|
||||
res = shop.newItem(info)
|
||||
if (!res) {
|
||||
status_text.setStatus("Please fill all values first.", "#ff4444")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
res = shop.saveItem(dbid, info)
|
||||
}
|
||||
|
||||
if (res.cost) {
|
||||
status_text.setStatus((dbid == "" ? "Stocked!" : "Restocked!") + " Take " + res.cost + " from the money box.", "#ffff7c")
|
||||
} else {
|
||||
status_text.setStatus(dbid == "" ? "Item created" : "Changes saved", "#ffff7c")
|
||||
}
|
||||
if (res.cost) {
|
||||
status_text.setStatus((dbid == "" ? "Stocked!" : "Restocked!") + " Take " + res.cost + " from the money box.", "#ffff7c")
|
||||
} else {
|
||||
status_text.setStatus(dbid == "" ? "Item created" : "Changes saved", "#ffff7c")
|
||||
}
|
||||
|
||||
if (dbid == "") {
|
||||
dbid = res.dbid
|
||||
xi = info; xi["dbid"] = page.dbid; info = xi
|
||||
} else {
|
||||
loadPage("StockMgmt")
|
||||
}
|
||||
if (dbid == "") {
|
||||
dbid = res.dbid
|
||||
var xi = info; xi["dbid"] = page.dbid; info = xi
|
||||
} else {
|
||||
loadPage("StockMgmt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property string name: ""
|
||||
property string dbid: ""
|
||||
property string price: ""
|
||||
property variant name: ""
|
||||
property variant dbid: ""
|
||||
property variant price: ""
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -23,7 +25,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: text3
|
||||
x: 867
|
||||
x: 611
|
||||
y: 156
|
||||
height: 160
|
||||
width: 348
|
||||
|
@ -50,20 +52,12 @@ Item {
|
|||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype !== "debt" && acct.acctype !== "cash") {
|
||||
if (acct.acctype != "debt") {
|
||||
loadPageByAcct(acct)
|
||||
return
|
||||
}
|
||||
|
||||
if (acct.acctype == "cash") { //Copied from BarButton.onButtonClick
|
||||
shop.sellItemCash(dbid)
|
||||
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 {
|
||||
var balance = shop.sellItem(dbid, acct.id)
|
||||
status_text.setStatus("Sold! "+acct.name+"'s credit is "+balance+".", "#ffff7c")
|
||||
}
|
||||
var balance = shop.sellItem(dbid, acct.id)
|
||||
status_text.setStatus("Sold! "+acct.name+"'s credit is "+balance+".", "#ffff7c")
|
||||
loadPage("MainPage")
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +65,7 @@ Item {
|
|||
BarButton {
|
||||
id: pay_cash
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Pay by cash"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -84,8 +78,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -25,7 +27,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Charge"
|
||||
onButtonClick: {
|
||||
|
@ -33,31 +35,14 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
x: 450
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Transfer"
|
||||
onButtonClick: {
|
||||
loadPage("Transfer")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: management
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Management"
|
||||
onButtonClick: {
|
||||
loadPage("Management")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
x: 65
|
||||
y: 438
|
||||
width: 1150
|
||||
text: "* Za uklid brmlabu vam nabijeme kredit. *"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -63,7 +65,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: credit_name
|
||||
x: 791
|
||||
x: 535
|
||||
y: 156
|
||||
width: 337
|
||||
height: 160
|
||||
|
@ -76,7 +78,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: credit_amount
|
||||
x: 961
|
||||
x: 705
|
||||
y: 156
|
||||
height: 160
|
||||
width: 254
|
||||
|
@ -89,7 +91,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: inv_name
|
||||
x: 791
|
||||
x: 535
|
||||
y: 266
|
||||
width: 337
|
||||
height: 160
|
||||
|
@ -102,7 +104,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: inv_amount
|
||||
x: 961
|
||||
x: 705
|
||||
y: 266
|
||||
height: 160
|
||||
width: 254
|
||||
|
@ -116,7 +118,7 @@ Item {
|
|||
BarButton {
|
||||
id: stock_manager
|
||||
x: 65
|
||||
y: 686
|
||||
y: 430
|
||||
width: 360
|
||||
text: "Stock Mgmt"
|
||||
onButtonClick: {
|
||||
|
@ -126,8 +128,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: user_manager
|
||||
x: 855
|
||||
y: 686
|
||||
x: 599
|
||||
y: 430
|
||||
width: 360
|
||||
text: "User Mgmt"
|
||||
onButtonClick: {
|
||||
|
@ -138,7 +140,7 @@ Item {
|
|||
BarButton {
|
||||
id: select_item
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Receipt"
|
||||
onButtonClick: {
|
||||
|
@ -148,8 +150,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property variant user
|
||||
property string description: item_name_pad.enteredText
|
||||
property string amount: amount_pad.enteredText
|
||||
property variant description: item_name_pad.enteredText
|
||||
property variant amount: amount_pad.enteredText
|
||||
|
||||
state: "normal"
|
||||
|
||||
|
@ -47,7 +49,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: description_edit
|
||||
x: 847
|
||||
x: 591
|
||||
y: 0
|
||||
width: 300
|
||||
height: 60
|
||||
|
@ -102,7 +104,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: amount_edit
|
||||
x: 906
|
||||
x: 650
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -148,7 +150,7 @@ Item {
|
|||
BarButton {
|
||||
id: save
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Create"
|
||||
onButtonClick: {
|
||||
|
@ -169,8 +171,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -6,8 +8,6 @@ Item {
|
|||
|
||||
property variant item_list_model
|
||||
|
||||
state: "normal"
|
||||
|
||||
BarcodeInput {
|
||||
color: "#00ff00" /* just for debugging */
|
||||
onAccepted: {
|
||||
|
@ -29,8 +29,8 @@ Item {
|
|||
id: item_list_container
|
||||
x: 65
|
||||
y: 166
|
||||
width: 1155
|
||||
height: 656
|
||||
width: 899
|
||||
height: 400
|
||||
|
||||
ListView {
|
||||
id: item_list
|
||||
|
@ -49,7 +49,7 @@ Item {
|
|||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 556
|
||||
x: 300
|
||||
width: 254
|
||||
color: "#ffff7c"
|
||||
text: modelData.price
|
||||
|
@ -59,7 +59,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 856
|
||||
x: 600
|
||||
width: 240
|
||||
height: 68
|
||||
text: "Edit"
|
||||
|
@ -81,14 +81,12 @@ Item {
|
|||
}
|
||||
|
||||
BarButton {
|
||||
id: new_item
|
||||
id: add_item
|
||||
x: 65
|
||||
y: 838
|
||||
width: 281
|
||||
height: 83
|
||||
text: "New Item"
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Add Item"
|
||||
fontSize: 0.768 * 60
|
||||
visible: page.state == "normal"
|
||||
onButtonClick: {
|
||||
loadPage("ItemEdit", { dbid: "" })
|
||||
}
|
||||
|
@ -96,96 +94,16 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
if (page.state == "search")
|
||||
page.state = "normal"
|
||||
else
|
||||
loadPage("MainPage")
|
||||
loadPage("MainPage")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: search_button
|
||||
x: 353
|
||||
y: 838
|
||||
text: "Search"
|
||||
visible: page.state == "normal"
|
||||
onButtonClick: { page.state = "search" }
|
||||
}
|
||||
|
||||
BarKeyPad {
|
||||
id: search_pad
|
||||
x: 193
|
||||
y: 554
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
Text {
|
||||
id: search_text
|
||||
x: 65
|
||||
y: 602
|
||||
color: "#ffff7c"
|
||||
text: search_pad.enteredText
|
||||
visible: page.state == "search"
|
||||
font.pixelSize: 0.768 * 46
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: query_button
|
||||
x: 353
|
||||
y: 838
|
||||
text: "Search"
|
||||
visible: page.state == "search"
|
||||
onButtonClick: {
|
||||
page.item_list_model = shop.itemList(search_pad.enteredText)
|
||||
item_list.model = page.item_list_model
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "normal"
|
||||
},
|
||||
State {
|
||||
name: "search"
|
||||
|
||||
PropertyChanges {
|
||||
target: item_list_container
|
||||
x: 66
|
||||
y: 166
|
||||
width: 1155
|
||||
height: 348
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: search_pad
|
||||
x: 83
|
||||
y: 514
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: cancel
|
||||
text: "Back"
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: search_text
|
||||
x: 65
|
||||
y: 838
|
||||
width: 528
|
||||
height: 83
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Component.onCompleted: {
|
||||
item_list_model = shop.itemList("")
|
||||
item_list_model = shop.itemList()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property string name: ""
|
||||
property string dbid: ""
|
||||
property string negbalance: ""
|
||||
property variant name: ""
|
||||
property variant dbid: ""
|
||||
property variant negbalance: ""
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -42,7 +43,7 @@ Item {
|
|||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype === "recharge") {
|
||||
if (acct.acctype == "recharge") {
|
||||
loadPage("ChargeCredit", { "username": name, "userdbid": dbid, "amount": acct.amount })
|
||||
return
|
||||
}
|
||||
|
@ -54,7 +55,7 @@ Item {
|
|||
BarButton {
|
||||
id: charge_credit
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Charge"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -65,8 +66,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -15,7 +17,7 @@ Item {
|
|||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype !== "debt") {
|
||||
if (acct.acctype != "debt") {
|
||||
loadPageByAcct(acct)
|
||||
return
|
||||
}
|
||||
|
@ -28,8 +30,8 @@ Item {
|
|||
id: user_list_container
|
||||
x: 65
|
||||
y: 166
|
||||
width: 1155
|
||||
height: 656
|
||||
width: 899
|
||||
height: 400
|
||||
|
||||
ListView {
|
||||
id: user_list
|
||||
|
@ -48,7 +50,7 @@ Item {
|
|||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 556
|
||||
x: 300
|
||||
width: 254
|
||||
color: "#ffff7c"
|
||||
text: modelData.negbalance_str
|
||||
|
@ -58,7 +60,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 856
|
||||
x: 600
|
||||
width: 240
|
||||
height: 68
|
||||
text: "Withdraw"
|
||||
|
@ -82,7 +84,7 @@ Item {
|
|||
BarButton {
|
||||
id: add_user
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Add User"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -91,8 +93,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property string username: ""
|
||||
property string userdbid: ""
|
||||
property string amount: withdraw_pad.enteredText
|
||||
property variant username: ""
|
||||
property variant userdbid: ""
|
||||
property variant amount: withdraw_pad.enteredText
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -37,7 +39,7 @@ Item {
|
|||
|
||||
BarTextHint {
|
||||
x: 65
|
||||
y: 686
|
||||
y: 430
|
||||
hint_goal: (parent.username ? "" : parent.amount ? "Withdraw:" : "Withdraw amount?")
|
||||
hint_action: (parent.username ? (parent.amount ? "" : "(or scan barcode now)") : "Scan barcode now")
|
||||
}
|
||||
|
@ -77,7 +79,7 @@ Item {
|
|||
BarButton {
|
||||
id: withdraw_button
|
||||
x: 65
|
||||
y: 838
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Withdraw"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -89,8 +91,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
x: 599
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
@ -100,17 +102,8 @@ Item {
|
|||
}
|
||||
|
||||
function withdrawCredit() {
|
||||
var balance=0
|
||||
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")
|
||||
}
|
||||
}
|
||||
var balance = shop.withdrawCredit(amount, userdbid)
|
||||
status_text.setStatus("Withdrawn! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||
loadPage("MainPage")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by Qt Creator 2.5.0, 2013-01-31T18:05:42. -->
|
||||
<!-- Written by Qt Creator 2.5.0, 2012-09-05T02:10:11. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||
|
@ -112,7 +112,7 @@
|
|||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.EnvironmentId</variable>
|
||||
<value type="QString">{524378aa-09e0-4345-892b-1bd47313bcaf}</value>
|
||||
<value type="QString">{a277f310-b549-4ad7-87ca-cd03f76f19ff}</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
BasePage {
|
||||
|
@ -16,11 +17,11 @@ BasePage {
|
|||
}
|
||||
|
||||
function loadPageByAcct(acct) {
|
||||
if (acct.acctype === "inventory") {
|
||||
if (acct.acctype == "inventory") {
|
||||
loadPage("ItemInfo", { name: acct["name"], dbid: acct["id"], price: acct["price"] })
|
||||
} else if (acct.acctype === "debt") {
|
||||
} else if (acct.acctype == "debt") {
|
||||
loadPage("UserInfo", { name: acct["name"], dbid: acct["id"], negbalance: acct["negbalance"] })
|
||||
} else if (acct.acctype === "recharge") {
|
||||
} else if (acct.acctype == "recharge") {
|
||||
loadPage("ChargeCredit", { amount: acct["amount"] })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
|
||||
from brmbar import Database
|
||||
|
||||
import brmbar
|
||||
|
||||
db = Database.Database("dbname=brmbar")
|
||||
shop = brmbar.Shop.new_with_defaults(db)
|
||||
currency = shop.currency
|
||||
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
|
||||
for line in sys.stdin:
|
||||
barcode = line.rstrip()
|
||||
|
||||
if barcode[0] == "$":
|
||||
credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000}
|
||||
credit = credits[barcode]
|
||||
if credit is None:
|
||||
print("Unknown barcode: " + barcode)
|
||||
continue
|
||||
print("CREDIT " + str(credit))
|
||||
active_inv_item = None
|
||||
active_credit = credit
|
||||
continue
|
||||
|
||||
if barcode == "SCR":
|
||||
print("SHOW CREDIT")
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
continue
|
||||
|
||||
acct = brmbar.Account.load_by_barcode(db, barcode)
|
||||
if acct is None:
|
||||
print("Unknown barcode: " + barcode)
|
||||
continue
|
||||
|
||||
if acct.acctype == 'debt':
|
||||
if active_inv_item is not None:
|
||||
cost = shop.sell(item = active_inv_item, user = acct)
|
||||
print("{} has bought {} for {} and now has {} balance".format(acct.name, active_inv_item.name, currency.str(cost), acct.negbalance_str()))
|
||||
elif active_credit is not None:
|
||||
shop.add_credit(credit = active_credit, user = acct)
|
||||
print("{} has added {} credit and now has {} balance".format(acct.name, currency.str(active_credit), acct.negbalance_str()))
|
||||
else:
|
||||
print("{} has {} balance".format(acct.name, acct.negbalance_str()))
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
|
||||
elif acct.acctype == 'inventory':
|
||||
buy, sell = acct.currency.rates(currency)
|
||||
print("{} costs {} with {} in stock".format(acct.name, currency.str(sell), int(acct.balance())))
|
||||
active_inv_item = acct
|
||||
active_credit = None
|
||||
|
||||
else:
|
||||
print("invalid account type {}".format(acct.acctype))
|
||||
active_inv_item = None
|
||||
active_credit = None
|
|
@ -1,45 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
|
||||
from brmbar import Database
|
||||
|
||||
import brmbar
|
||||
|
||||
from flask import *
|
||||
app = Flask(__name__)
|
||||
#app.debug = True
|
||||
|
||||
@app.route('/stock/')
|
||||
def stock(show_all=False):
|
||||
# TODO: Use a fancy template.
|
||||
# FIXME: XSS protection.
|
||||
response = '<table border="1"><tr><th>Id</th><th>Item Name</th><th>Bal.</th></tr>'
|
||||
for a in shop.account_list("inventory"):
|
||||
style = ''
|
||||
balance = a.balance()
|
||||
if balance == 0:
|
||||
if not show_all:
|
||||
continue
|
||||
style = 'color: grey; font-style: italic'
|
||||
elif balance < 0:
|
||||
style = 'color: red'
|
||||
response += '<tr style="%s"><td>%d</td><td>%s</td><td>%d</td></tr>' % (style, a.id, a.name, balance)
|
||||
response += '</table>'
|
||||
if show_all:
|
||||
response += '<p><a href=".">(hide out-of-stock items)</a></p>'
|
||||
else:
|
||||
response += '<p><a href="all">(show all items)</a></p>'
|
||||
return response
|
||||
|
||||
@app.route('/stock/all')
|
||||
def stockall():
|
||||
return stock(show_all=True)
|
||||
|
||||
|
||||
db = Database.Database("dbname=brmbar")
|
||||
shop = brmbar.Shop.new_with_defaults(db)
|
||||
currency = shop.currency
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0')
|
|
@ -40,22 +40,16 @@ class Account:
|
|||
@classmethod
|
||||
def create(cls, db, name, currency, acctype):
|
||||
""" Constructor for new account """
|
||||
# id = db.execute_and_fetch("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype])
|
||||
id = db.execute_and_fetch("SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype])
|
||||
# id = id[0]
|
||||
id = db.execute_and_fetch("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype])
|
||||
id = id[0]
|
||||
return cls(db, name = name, id = id, currency = currency, acctype = acctype)
|
||||
|
||||
def balance(self):
|
||||
bal = self.db.execute_and_fetch(
|
||||
"SELECT public.compute_account_balance(%s)",
|
||||
[self.id]
|
||||
)[0]
|
||||
return bal
|
||||
#debit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit'])
|
||||
#debit = debit[0] or 0
|
||||
#credit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit'])
|
||||
#credit = credit[0] or 0
|
||||
#return debit - credit
|
||||
debit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit'])
|
||||
debit = debit[0] or 0
|
||||
credit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit'])
|
||||
credit = credit[0] or 0
|
||||
return debit - credit
|
||||
|
||||
def balance_str(self):
|
||||
return self.currency.str(self.balance())
|
||||
|
@ -74,11 +68,9 @@ class Account:
|
|||
self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, self.id, amount, memo])
|
||||
|
||||
def add_barcode(self, barcode):
|
||||
# self.db.execute("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.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode])
|
||||
self.db.commit()
|
||||
|
||||
def rename(self, name):
|
||||
# self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
|
||||
self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name])
|
||||
self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
|
||||
self.name = name
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# vim: set fileencoding=utf8
|
||||
|
||||
class Currency:
|
||||
""" Currency
|
||||
|
@ -31,34 +30,15 @@ class Currency:
|
|||
@classmethod
|
||||
def create(cls, db, name):
|
||||
""" 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]
|
||||
id = db.execute_and_fetch("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name])
|
||||
id = id[0]
|
||||
return cls(db, name = name, id = id)
|
||||
|
||||
def rates(self, other):
|
||||
""" Return tuple ($buy, $sell) of rates of $self in relation to $other (brmbar.Currency):
|
||||
$buy is the price of $self in means of $other when buying it (into brmbar)
|
||||
$sell is the price of $self in means of $other when selling it (from brmbar) """
|
||||
# buy rate
|
||||
res = self.db.execute_and_fetch("SELECT public.find_buy_rate(%s, %s)",[self.id, other.id])
|
||||
if res is None:
|
||||
raise NameError("Something fishy in find_buy_rate.");
|
||||
buy = res[0]
|
||||
if buy < 0:
|
||||
raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name())
|
||||
# sell rate
|
||||
res = self.db.execute_and_fetch("SELECT public.find_sell_rate(%s, %s)",[self.id, other.id])
|
||||
if res is None:
|
||||
raise NameError("Something fishy in find_sell_rate.");
|
||||
sell = res[0]
|
||||
if sell < 0:
|
||||
raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name())
|
||||
|
||||
return (buy, sell)
|
||||
|
||||
def rates2(self, other):
|
||||
# the original code for compare testing
|
||||
res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [self.id, other.id])
|
||||
if res is None:
|
||||
raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name())
|
||||
|
@ -73,7 +53,6 @@ class Currency:
|
|||
|
||||
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:
|
||||
|
@ -89,10 +68,6 @@ class Currency:
|
|||
return "{:.2f} {}".format(amount, self.name)
|
||||
|
||||
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])
|
||||
self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [self.id, target.id, rate, "source_to_target"])
|
||||
def update_buy_rate(self, source, rate):
|
||||
# self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [source.id, self.id, rate, "target_to_source"])
|
||||
self.db.execute("SELECT public.update_currency_buy_rate(%s, %s, %s)",
|
||||
[source.id, self.id, rate])
|
||||
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"])
|
||||
|
|
|
@ -36,12 +36,6 @@ class Database:
|
|||
else:
|
||||
cur.execute(query, attrs)
|
||||
return cur
|
||||
except psycopg2.DataError as error: # when biitr comes and enters '99999999999999999999' for amount
|
||||
print("We have invalid input data (SQLi?): level %s (%s) @%s" % (
|
||||
level, error, time.strftime("%Y%m%d %a %I:%m %p")
|
||||
))
|
||||
self.db_conn.rollback()
|
||||
raise RuntimeError("Unsanitized data entered again... BOBBY TABLES")
|
||||
except psycopg2.OperationalError as error:
|
||||
print("Sleeping: level %s (%s) @%s" % (
|
||||
level, error, time.strftime("%Y%m%d %a %I:%m %p")
|
||||
|
|
|
@ -7,141 +7,75 @@ class Shop:
|
|||
|
||||
Business logic so that only interaction is left in the hands
|
||||
of the frontend scripts. """
|
||||
def __init__(self, db, currency, profits, cash, excess, deficit):
|
||||
def __init__(self, db, currency, profits, cash):
|
||||
self.db = db
|
||||
self.currency = currency # brmbar.Currency
|
||||
self.profits = profits # income brmbar.Account for brmbar profit margins on items
|
||||
self.cash = cash # our operational ("wallet") cash account
|
||||
self.excess = excess # account from which is deducted cash during inventory item fixing (when system contains less items than is the reality)
|
||||
self.deficit = deficit # account where is put cash during inventory item fixing (when system contains more items than is the reality)
|
||||
|
||||
@classmethod
|
||||
def new_with_defaults(cls, db):
|
||||
return cls(db,
|
||||
currency = Currency.default(db),
|
||||
profits = Account.load(db, name = "BrmBar Profits"),
|
||||
cash = Account.load(db, name = "BrmBar Cash"),
|
||||
excess = Account.load(db, name = "BrmBar Excess"),
|
||||
deficit = Account.load(db, name = "BrmBar Deficit"))
|
||||
cash = Account.load(db, name = "BrmBar Cash"))
|
||||
|
||||
def sell(self, item, user, amount = 1):
|
||||
# Call the stored procedure for the sale
|
||||
cost = self.db.execute_and_fetch(
|
||||
"SELECT public.sell_item(%s, %s, %s, %s, %s)",
|
||||
[item.id, amount, user.id, self.currency.id, f"BrmBar sale of {amount}x {item.name} to {user.name}"]
|
||||
)[0]#[0]
|
||||
|
||||
self.db.commit()
|
||||
return cost
|
||||
# Sale: Currency conversion from item currency to shop currency
|
||||
#(buy, sell) = item.currency.rates(self.currency)
|
||||
#cost = amount * sell
|
||||
#profit = amount * (sell - buy)
|
||||
(buy, sell) = item.currency.rates(self.currency)
|
||||
cost = amount * sell
|
||||
profit = amount * (sell - buy)
|
||||
|
||||
#transaction = self._transaction(responsible = user, description = "BrmBar sale of {}x {} to {}".format(amount, item.name, user.name))
|
||||
#item.credit(transaction, amount, user.name)
|
||||
#user.debit(transaction, cost, item.name) # debit (increase) on a _debt_ account
|
||||
#self.profits.debit(transaction, profit, "Margin on " + item.name)
|
||||
#self.db.commit()
|
||||
#return cost
|
||||
transaction = self._transaction(responsible = user, description = "BrmBar sale of {}x {} to {}".format(amount, item.name, user.name))
|
||||
item.credit(transaction, amount, user.name)
|
||||
user.debit(transaction, cost, item.name) # debit (increase) on a _debt_ account
|
||||
self.profits.debit(transaction, profit, "Margin on " + item.name)
|
||||
self.db.commit()
|
||||
|
||||
return cost
|
||||
|
||||
def sell_for_cash(self, item, amount = 1):
|
||||
cost = self.db.execute_and_fetch(
|
||||
"SELECT public.sell_item_for_cash(%s, %s, %s, %s, %s)",
|
||||
[item.id, amount, user.id, self.currency.id, f"BrmBar sale of {amount}x {item.name} for cash"]
|
||||
)[0]#[0]
|
||||
|
||||
self.db.commit()
|
||||
return cost
|
||||
## Sale: Currency conversion from item currency to shop currency
|
||||
#(buy, sell) = item.currency.rates(self.currency)
|
||||
#cost = amount * sell
|
||||
#profit = amount * (sell - buy)
|
||||
|
||||
#transaction = self._transaction(description = "BrmBar sale of {}x {} for cash".format(amount, item.name))
|
||||
#item.credit(transaction, amount, "Cash")
|
||||
#self.cash.debit(transaction, cost, item.name)
|
||||
#self.profits.debit(transaction, profit, "Margin on " + item.name)
|
||||
#self.db.commit()
|
||||
|
||||
#return cost
|
||||
|
||||
def undo_sale(self, item, user, amount = 1):
|
||||
# Undo sale; rarely needed
|
||||
#(buy, sell) = item.currency.rates(self.currency)
|
||||
#cost = amount * sell
|
||||
#profit = amount * (sell - buy)
|
||||
|
||||
#transaction = self._transaction(responsible = user, description = "BrmBar sale UNDO of {}x {} to {}".format(amount, item.name, user.name))
|
||||
#item.debit(transaction, amount, user.name + " (sale undo)")
|
||||
#user.credit(transaction, cost, item.name + " (sale undo)")
|
||||
#self.profits.credit(transaction, profit, "Margin repaid on " + item.name)
|
||||
# Call the stored procedure for undoing a sale
|
||||
cost = self.db.execute_and_fetch(
|
||||
"SELECT public.undo_sale_of_item(%s, %s, %s, %s)",
|
||||
[item.id, amount, user.id, user.currency.id, f"BrmBar sale UNDO of {amount}x {item.name} to {user.name}"]
|
||||
)[0]#[0]
|
||||
# Sale: Currency conversion from item currency to shop currency
|
||||
(buy, sell) = item.currency.rates(self.currency)
|
||||
cost = amount * sell
|
||||
profit = amount * (sell - buy)
|
||||
|
||||
transaction = self._transaction(description = "BrmBar sale of {}x {} for cash".format(amount, item.name))
|
||||
item.credit(transaction, amount, "Cash")
|
||||
self.cash.debit(transaction, cost, item.name)
|
||||
self.profits.debit(transaction, profit, "Margin on " + item.name)
|
||||
self.db.commit()
|
||||
|
||||
return cost
|
||||
|
||||
def add_credit(self, credit, user):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.add_credit(%s, %s, %s, %s)",
|
||||
[self.cash.id, credit, user.id, user.name]
|
||||
)
|
||||
transaction = self._transaction(responsible = user, description = "BrmBar credit replenishment for " + user.name)
|
||||
self.cash.debit(transaction, credit, user.name)
|
||||
user.credit(transaction, credit, "Credit replenishment")
|
||||
self.db.commit()
|
||||
|
||||
#transaction = self._transaction(responsible = user, description = "BrmBar credit replenishment for " + user.name)
|
||||
#self.cash.debit(transaction, credit, user.name)
|
||||
#user.credit(transaction, credit, "Credit replenishment")
|
||||
#self.db.commit()
|
||||
|
||||
def withdraw_credit(self, credit, user):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.withdraw_credit(%s, %s, %s, %s)",
|
||||
[self.cash.id, credit, user.id, user.name]
|
||||
)
|
||||
transaction = self._transaction(responsible = user, description = "BrmBar credit withdrawal for " + user.name)
|
||||
self.cash.credit(transaction, credit, user.name)
|
||||
user.debit(transaction, credit, "Credit withdrawal")
|
||||
self.db.commit()
|
||||
#transaction = self._transaction(responsible = user, description = "BrmBar credit withdrawal for " + user.name)
|
||||
#self.cash.credit(transaction, credit, user.name)
|
||||
#user.debit(transaction, credit, "Credit withdrawal")
|
||||
#self.db.commit()
|
||||
|
||||
def transfer_credit(self, userfrom, userto, amount):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.transfer_credit(%s, %s, %s, %s)",
|
||||
[self.cash.id, credit, user.id, user.name]
|
||||
)
|
||||
self.db.commit()
|
||||
#self.add_credit(amount, userto)
|
||||
#self.withdraw_credit(amount, userfrom)
|
||||
|
||||
def buy_for_cash(self, item, amount = 1):
|
||||
cost = self.db.execute_and_fetch(
|
||||
"SELECT public.buy_for_cash(%s, %s, %s, %s, %s)",
|
||||
[self.cash.id, item.id, amount, self.currency.id, item.name]
|
||||
)[0]
|
||||
# Buy: Currency conversion from item currency to shop currency
|
||||
#(buy, sell) = item.currency.rates(self.currency)
|
||||
#cost = amount * buy
|
||||
(buy, sell) = item.currency.rates(self.currency)
|
||||
cost = amount * buy
|
||||
|
||||
#transaction = self._transaction(description = "BrmBar stock replenishment of {}x {} for cash".format(amount, item.name))
|
||||
#item.debit(transaction, amount, "Cash")
|
||||
#self.cash.credit(transaction, cost, item.name)
|
||||
transaction = self._transaction(description = "BrmBar stock replenishment of {}x {} for cash".format(amount, item.name))
|
||||
item.debit(transaction, amount, "Cash")
|
||||
self.cash.credit(transaction, cost, item.name)
|
||||
self.db.commit()
|
||||
|
||||
return cost
|
||||
|
||||
def receipt_to_credit(self, user, credit, description):
|
||||
#transaction = self._transaction(responsible = user, description = "Receipt: " + description)
|
||||
#self.profits.credit(transaction, credit, user.name)
|
||||
#user.credit(transaction, credit, "Credit from receipt: " + description)
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.buy_for_cash(%s, %s, %s, %s, %s)",
|
||||
[self.profits.id, user.id, user.name, credit, description]
|
||||
)[0]
|
||||
transaction = self._transaction(responsible = user, description = "Receipt: " + description)
|
||||
self.profits.credit(transaction, credit, user.name)
|
||||
user.credit(transaction, credit, "Credit from receipt: " + description)
|
||||
self.db.commit()
|
||||
|
||||
def _transaction(self, responsible = None, description = None):
|
||||
|
@ -150,7 +84,7 @@ class Shop:
|
|||
transaction = transaction[0]
|
||||
return transaction
|
||||
|
||||
def credit_balance(self, overflow=None):
|
||||
def credit_balance(self):
|
||||
# We assume all debt accounts share a currency
|
||||
sumselect = """
|
||||
SELECT SUM(ts.amount)
|
||||
|
@ -158,17 +92,14 @@ class Shop:
|
|||
LEFT JOIN transaction_splits AS ts ON a.id = ts.account
|
||||
WHERE a.acctype = %s AND ts.side = %s
|
||||
"""
|
||||
if overflow is not None:
|
||||
sumselect += ' AND a.name ' + ('NOT ' if overflow == 'exclude' else '') + ' LIKE \'%%-overflow\''
|
||||
cur = self.db.execute_and_fetch(sumselect, ["debt", 'debit'])
|
||||
debit = cur[0] or 0
|
||||
credit = self.db.execute_and_fetch(sumselect, ["debt", 'credit'])
|
||||
credit = credit[0] or 0
|
||||
return debit - credit
|
||||
def credit_negbalance_str(self, overflow=None):
|
||||
return self.currency.str(-self.credit_balance(overflow=overflow))
|
||||
def credit_negbalance_str(self):
|
||||
return self.currency.str(-self.credit_balance())
|
||||
|
||||
# XXX causing extra heavy delay ( thousands of extra SQL queries ), disabled
|
||||
def inventory_balance(self):
|
||||
balance = 0
|
||||
# Each inventory account has its own currency,
|
||||
|
@ -181,116 +112,15 @@ class Shop:
|
|||
# might have been bought for a different price! Therefore,
|
||||
# we need to replace the command below with a complex SQL
|
||||
# statement that will... ugh, accounting is hard!
|
||||
b = inv.balance() * inv.currency.rates(self.currency)[0]
|
||||
# if b != 0:
|
||||
# print(str(b) + ',' + inv.name)
|
||||
balance += b
|
||||
balance += inv.currency.convert(inv.balance(), self.currency)
|
||||
return balance
|
||||
|
||||
# XXX bypass hack
|
||||
def inventory_balance_str(self):
|
||||
# return self.currency.str(self.inventory_balance())
|
||||
return "XXX"
|
||||
return self.currency.str(self.inventory_balance())
|
||||
|
||||
def account_list(self, acctype, like_str="%%"):
|
||||
def account_list(self, acctype):
|
||||
"""list all accounts (people or items, as per acctype)"""
|
||||
accts = []
|
||||
cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s AND name ILIKE %s ORDER BY name ASC", [acctype, like_str])
|
||||
#FIXME: sanitize input like_str ^
|
||||
cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s ORDER BY name ASC", [acctype])
|
||||
for inventory in cur:
|
||||
accts += [ Account.load(self.db, id = inventory[0]) ]
|
||||
return accts
|
||||
|
||||
def fix_inventory(self, item, amount):
|
||||
rv = self.db.execute_and_fetch(
|
||||
"SELECT public.fix_inventory(%s, %s, %s, %s, %s, %s)",
|
||||
[item.id, item.currency.id, self.excess.id, self.deficit.id, self.currency.id, amount]
|
||||
)[0]
|
||||
|
||||
self.db.commit()
|
||||
return rv
|
||||
#amount_in_reality = amount
|
||||
#amount_in_system = item.balance()
|
||||
#(buy, sell) = item.currency.rates(self.currency)
|
||||
|
||||
#diff = abs(amount_in_reality - amount_in_system)
|
||||
#buy_total = buy * diff
|
||||
#if amount_in_reality > amount_in_system:
|
||||
# transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality))
|
||||
# item.debit(transaction, diff, "Inventory fix excess")
|
||||
# self.excess.credit(transaction, buy_total, "Inventory fix excess " + item.name)
|
||||
# self.db.commit()
|
||||
# return True
|
||||
#elif amount_in_reality < amount_in_system:
|
||||
# transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality))
|
||||
# item.credit(transaction, diff, "Inventory fix deficit")
|
||||
# self.deficit.debit(transaction, buy_total, "Inventory fix deficit " + item.name)
|
||||
# self.db.commit()
|
||||
# return True
|
||||
#else:
|
||||
# transaction = self._transaction(description = "BrmBar inventory fix of {}pcs {} in system to {}pcs in reality".format(amount_in_system, item.name,amount_in_reality))
|
||||
# item.debit(transaction, 0, "Inventory fix - amount was correct")
|
||||
# item.credit(transaction, 0, "Inventory fix - amount was correct")
|
||||
# self.db.commit()
|
||||
# return False
|
||||
|
||||
def fix_cash(self, amount):
|
||||
rv = self.db.execute_and_fetch(
|
||||
"SELECT public.fix_cash(%s, %s, %s, %s)",
|
||||
[self.excess.id, self.deficit.id, self.currency.id, amount]
|
||||
)[0]
|
||||
|
||||
self.db.commit()
|
||||
return rv
|
||||
#amount_in_reality = amount
|
||||
#amount_in_system = self.cash.balance()
|
||||
|
||||
#diff = abs(amount_in_reality - amount_in_system)
|
||||
#if amount_in_reality > amount_in_system:
|
||||
# transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality))
|
||||
# self.cash.debit(transaction, diff, "Inventory fix excess")
|
||||
# self.excess.credit(transaction, diff, "Inventory cash fix excess.")
|
||||
# self.db.commit()
|
||||
# return True
|
||||
#elif amount_in_reality < amount_in_system:
|
||||
# transaction = self._transaction(description = "BrmBar cash inventory fix of {} in system to {} in reality".format(amount_in_system, amount_in_reality))
|
||||
# self.cash.credit(transaction, diff, "Inventory fix deficit")
|
||||
# self.deficit.debit(transaction, diff, "Inventory fix deficit.")
|
||||
# self.db.commit()
|
||||
# return True
|
||||
#else:
|
||||
# return False
|
||||
|
||||
def consolidate(self):
|
||||
msg = self.db.execute_and_fetch(
|
||||
"SELECT public.make_consolidate_transaction(%s, %s, %s)",
|
||||
[self.excess.id, self.deficit.id, self.profits.id]
|
||||
)[0]
|
||||
#transaction = self._transaction(description = "BrmBar inventory consolidation")
|
||||
#excess_balance = self.excess.balance()
|
||||
#if excess_balance != 0:
|
||||
# print("Excess balance {} debited to profit".format(-excess_balance))
|
||||
# self.excess.debit(transaction, -excess_balance, "Excess balance added to profit.")
|
||||
# self.profits.debit(transaction, -excess_balance, "Excess balance added to profit.")
|
||||
#deficit_balance = self.deficit.balance()
|
||||
#if deficit_balance != 0:
|
||||
# print("Deficit balance {} credited to profit".format(deficit_balance))
|
||||
# self.deficit.credit(transaction, deficit_balance, "Deficit balance removed from profit.")
|
||||
# self.profits.credit(transaction, deficit_balance, "Deficit balance removed from profit.")
|
||||
if msg != None:
|
||||
print(msg)
|
||||
self.db.commit()
|
||||
|
||||
def undo(self, oldtid):
|
||||
#description = self.db.execute_and_fetch("SELECT description FROM transactions WHERE id = %s", [oldtid])[0]
|
||||
#description = 'undo %d (%s)' % (oldtid, description)
|
||||
|
||||
#transaction = self._transaction(description=description)
|
||||
#for split in self.db.execute_and_fetchall("SELECT id, side, account, amount, memo FROM transaction_splits WHERE transaction = %s", [oldtid]):
|
||||
# splitid, side, account, amount, memo = split
|
||||
# memo = 'undo %d (%s)' % (splitid, memo)
|
||||
# amount = -amount
|
||||
# self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, account, amount, memo])
|
||||
transaction = self.db.execute_and_fetch("SELECT public.undo_transaction(%s)",[oldtid])[0]
|
||||
self.db.commit()
|
||||
return transaction
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# 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
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/sh
|
||||
# Add a crontab entry like:
|
||||
# 5 4 * * * ~/brmbar/brmbar3/daily-summary.sh | mail -s "daily brmbar summary" rada@brmlab.cz
|
||||
cd ~/brmbar/brmbar3
|
||||
./brmbar-cli.py stats
|
||||
echo
|
||||
echo "Time since last full inventory check: $(echo "select now()-time from transactions where description = 'BrmBar inventory consolidation' order by time desc limit 1;" | psql brmbar | tail -n +3 | head -n 1 | tr -s " ")"
|
||||
echo
|
||||
echo "Overflows: $(echo "SELECT name, -crbalance FROM account_balances WHERE name LIKE '%overflow%' AND crbalance != 0 ORDER BY name" | psql brmbar | tail -n +3 | grep '|' | tr -s " " | sed -e "s/ |/:/g" -e "s/$/;/" | tr -d "\n") TOTAL: $(echo "SELECT -SUM(crbalance) FROM account_balances WHERE name LIKE '%overflow%' AND crbalance != 0" | psql brmbar | tail -n +3 | head -n 1 | tr -s " ")"
|
||||
echo
|
||||
echo "Club Mate sold in last 24 hours: $(echo "select count(*) from transaction_cashsums where time > now() - '1 day'::INTERVAL and (description like '%Club Mate%' or description like '%granatove mate%')" | psql brmbar | tail -n +3 | head -n 1 | tr -s " ") bottles"
|
|
@ -1,3 +0,0 @@
|
|||
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
|
|
@ -1,106 +0,0 @@
|
|||
BrmBar v3 - Architectural Overview
|
||||
==================================
|
||||
|
||||
BrmBar v3 is written in Python, with the database stored in PostgreSQL
|
||||
and the primary user interface modelled in QtQuick. All user interfaces
|
||||
share a common *brmbar* package that provides few Python classes for
|
||||
manipulation with the base objects.
|
||||
|
||||
Objects and Database Schema
|
||||
---------------------------
|
||||
|
||||
### Account ###
|
||||
|
||||
The most essential brmbar object is an Account, which can track
|
||||
balances of various kinds (described by *acctype* column) in the
|
||||
classical accounting paradigm:
|
||||
|
||||
* **Cash**: A physical stash of cash. One cash account is created
|
||||
by default, corresponding to the cash box where people put money
|
||||
when buying stuff (or depositing money in their user accounts).
|
||||
Often, that's the only cash account you need.
|
||||
* **Debt**: Represents brmbar's debt to some person. These accounts
|
||||
are actually the "user accounts" where people deposit money. When
|
||||
a deposit of 100 is made, 100 is *subtracted* from the balance,
|
||||
the balance is -100 and brmbar is in debt of 100 to the user.
|
||||
When the user buys something for 200, 200 is *added* to the balance,
|
||||
the balance is 100 and the user is in debt of 100 to the brmbar.
|
||||
This is correct notation from accounting point of view, but somewhat
|
||||
confusing for the users, so in the user interface (and crbalance
|
||||
column of some views), this balance is *negated*!
|
||||
* **Inventory**: Represents inventory items (e.g. Club Mate bottles).
|
||||
The account balance represents the quantity of items.
|
||||
* **Income**: Represents pure income of brmbar, i.e. the profit;
|
||||
there is usually just a single account of this type where all the
|
||||
profit (sell price of an item minus the buy price of an item)
|
||||
is accumulated.
|
||||
* **Expense**: This type is currently not used.
|
||||
* **Starting balance** and **ending balance**: This may be used
|
||||
in the future when transaction book needs to be compressed.
|
||||
|
||||
As you can see, the amount of cash, user accounts, inventory items
|
||||
etc. are all represented as **Account** objects that are of various
|
||||
**types**, are **named** and have a certain balance (calculated
|
||||
from a transaction book). That balance is a number represented
|
||||
in certain **currency**. It also has a set of **barcodes** associated.
|
||||
|
||||
### Currency, Exchange rate ###
|
||||
|
||||
Usually, all accounts that deal with cash (the cash, debt, income, ...
|
||||
accounts) share a single currency that corresponds to the physical
|
||||
currency locally in use (the default is `Kč`). However, inventory
|
||||
items have balances corresponding to item quantities - to deal with
|
||||
this correctly, each inventory item *has its own currency*; i.e.
|
||||
`Club Mate` bottle is a currency associated with the `Club Mate`
|
||||
account.
|
||||
|
||||
Currencies have defined (uni-directional) exchange rates. The exchange
|
||||
rate of "Kč to Club Mate bottles" is the buy price of Club Mate, how
|
||||
much you pay for one bottle of Club Mate from the cash box when you
|
||||
are stocking in Club Mate. The exchange rate of "Club Mate bottle to Kč"
|
||||
is the sell price of Club Mate, how much you pay for one bottle of Club
|
||||
Mate to the cash box when you are buying it from brmbar (sell price
|
||||
should be higher than buy price if you want to make a profit).
|
||||
|
||||
Exchange rate is valid since some defined time; historical exchange
|
||||
rates are therefore kept and this allows to account for changing prices
|
||||
of inventory items. (Unfortunately, at the time of writing this, the
|
||||
profit calculation actually didn't make use of that yet.)
|
||||
|
||||
### Transactions, Transaction splits ###
|
||||
|
||||
A transaction book is used to determine current account balances and
|
||||
stores all operations related to accounts - depositing or withdrawing
|
||||
money, stocking in items, and most importantly buying stuff (either for
|
||||
cash or from a debt account). A transaction happenned at some **time**
|
||||
and was performed by certain **responsible** person.
|
||||
|
||||
The actual accounts involved in a transaction are specified by a list of
|
||||
transaction splits that either put balance into the transaction (*credit*
|
||||
side) or grab balance from it (*debit* side). For example, a typical
|
||||
transaction representing a sale of Club Mate bottle to user "pasky"
|
||||
would be split like this:
|
||||
|
||||
* *credit* of 1 Club Mate on Club Mate account with memo "pasky".
|
||||
* *debit* of 35 Kč on "pasky" account with memo "Club Mate"
|
||||
(indeed we _add_ 35Kč to the debt account for pasky buying
|
||||
the Club Mate; if this seems weird, refer to the "debt" account
|
||||
type description).
|
||||
* *debit* of 5 Kč on income account Profits with memo "Margin
|
||||
on Club Mate" (this represents the sale price - buy price delta,
|
||||
i.e. the profit we made in brmbar by selling this Club Mate).
|
||||
|
||||
The brmbar Python Package
|
||||
-------------------------
|
||||
|
||||
The **brmbar** package (in brmbar/ subdirectory) provides common brmbar
|
||||
functionality for the various user interfaces:
|
||||
|
||||
* **Database**: Layer for performing SQL queries with some error handling.
|
||||
* **Currency**: Class for querying and manipulating currency objects and
|
||||
converting between them based on stored exchange rates.
|
||||
* **Account**: Class for querying and manipulating the account objects
|
||||
and their current balance.
|
||||
* **Shop**: Class providing the "business logic" of all the actual user
|
||||
operations: selling stuff, depositing and withdrawing moeny, adding
|
||||
stock, retrieving list of accounts of given type, etc.
|
|
@ -1,6 +0,0 @@
|
|||
#!/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
|
|
@ -1,313 +0,0 @@
|
|||
--
|
||||
-- 0001-init.sql
|
||||
--
|
||||
-- Initial SQL schema construction as of 2025-04-20 (or so)
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
-- Privileged schema with protected data
|
||||
CREATE SCHEMA IF NOT EXISTS brmbar_privileged;
|
||||
|
||||
-- Initial versioning
|
||||
CREATE TABLE IF NOT EXISTS brmbar_privileged.brmbar_schema(
|
||||
ver INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- Legacy Schema Initialization
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
DO $$
|
||||
DECLARE v INTEGER;
|
||||
BEGIN
|
||||
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v;
|
||||
IF v IS NULL THEN
|
||||
-- --------------------------------
|
||||
-- Legacy Types
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='exchange_rate_direction';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type exchange_rate_direction';
|
||||
CREATE TYPE public.exchange_rate_direction
|
||||
AS ENUM ('source_to_target', 'target_to_source');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type exchange_rate_direction already exists';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='account_type';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type account_type';
|
||||
CREATE TYPE public.account_type
|
||||
AS ENUM ('cash', 'debt', 'inventory', 'income', 'expense',
|
||||
'starting_balance', 'ending_balance');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type account_type already exists';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='transaction_split_side';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type transaction_split_side';
|
||||
CREATE TYPE public.transaction_split_side
|
||||
AS ENUM ('credit', 'debit');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type transaction_split_side already exists';
|
||||
END IF;
|
||||
|
||||
-- --------------------------------
|
||||
-- Currencies sequence, table and potential initial data
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.currencies_id_seq
|
||||
START WITH 2 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.currencies (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.currencies_id_seq'::regclass),
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
INSERT INTO public.currencies (id, name) VALUES (1, 'Kč')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Exchange rates table - no initial data required
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.exchange_rates (
|
||||
valid_since TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
|
||||
target INTEGER NOT NULL,
|
||||
FOREIGN KEY (target) REFERENCES public.currencies (id),
|
||||
|
||||
source INTEGER NOT NULL,
|
||||
FOREIGN KEY (source) REFERENCES public.currencies (id),
|
||||
|
||||
rate DECIMAL(12,2) NOT NULL,
|
||||
rate_dir public.exchange_rate_direction NOT NULL
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Accounts sequence and table and 4 initial accounts
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.accounts_id_seq
|
||||
START WITH 2 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.accounts (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.accounts_id_seq'::regclass),
|
||||
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE (name),
|
||||
|
||||
currency INTEGER NOT NULL,
|
||||
FOREIGN KEY (currency) REFERENCES public.currencies (id),
|
||||
|
||||
acctype public.account_type NOT NULL,
|
||||
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
INSERT INTO public.accounts (id, name, currency, acctype)
|
||||
VALUES (1, 'BrmBar Cash', (SELECT id FROM public.currencies WHERE name='Kč'), 'cash')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Profits', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Excess', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Deficit', (SELECT id FROM public.currencies WHERE name='Kč'), 'expense')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Barcodes
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.barcodes (
|
||||
barcode VARCHAR(128) PRIMARY KEY NOT NULL,
|
||||
|
||||
account INTEGER NOT NULL,
|
||||
FOREIGN KEY (account) REFERENCES public.accounts (id)
|
||||
);
|
||||
INSERT INTO public.barcodes (barcode, account)
|
||||
VALUES ('_cash_', (SELECT id FROM public.accounts WHERE acctype = 'cash'))
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Transactions
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.transactions_id_seq
|
||||
START WITH 1 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.transactions (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transactions_id_seq'::regclass),
|
||||
time TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
|
||||
responsible INTEGER,
|
||||
FOREIGN KEY (responsible) REFERENCES public.accounts (id),
|
||||
|
||||
description TEXT
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction splits
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.transaction_splits_id_seq
|
||||
START WITH 1 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.transaction_splits (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transaction_splits_id_seq'::regclass),
|
||||
|
||||
transaction INTEGER NOT NULL,
|
||||
FOREIGN KEY (transaction) REFERENCES public.transactions (id),
|
||||
|
||||
side public.transaction_split_side NOT NULL,
|
||||
|
||||
account INTEGER NOT NULL,
|
||||
FOREIGN KEY (account) REFERENCES public.accounts (id),
|
||||
amount DECIMAL(12,2) NOT NULL,
|
||||
|
||||
memo TEXT
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Account balances view
|
||||
|
||||
CREATE OR REPLACE VIEW public.account_balances AS
|
||||
SELECT ts.account AS id,
|
||||
accounts.name,
|
||||
accounts.acctype,
|
||||
- sum(
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END) AS crbalance
|
||||
FROM public.transaction_splits ts
|
||||
LEFT JOIN public.accounts ON accounts.id = ts.account
|
||||
GROUP BY ts.account, accounts.name, accounts.acctype
|
||||
ORDER BY (- sum(
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END));
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction nice splits view
|
||||
|
||||
CREATE OR REPLACE VIEW public.transaction_nicesplits AS
|
||||
SELECT ts.id,
|
||||
ts.transaction,
|
||||
ts.account,
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END AS amount,
|
||||
a.currency,
|
||||
ts.memo
|
||||
FROM public.transaction_splits ts
|
||||
LEFT JOIN public.accounts a ON a.id = ts.account
|
||||
ORDER BY ts.id;
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction cash sums view
|
||||
|
||||
CREATE OR REPLACE VIEW public.transaction_cashsums AS
|
||||
SELECT t.id,
|
||||
t."time",
|
||||
sum(credit.credit_cash) AS cash_credit,
|
||||
sum(debit.debit_cash) AS cash_debit,
|
||||
a.name AS responsible,
|
||||
t.description
|
||||
FROM public.transactions t
|
||||
LEFT JOIN ( SELECT cts.amount AS credit_cash,
|
||||
cts.transaction AS cts_t
|
||||
FROM public.transaction_nicesplits cts
|
||||
LEFT JOIN public.accounts a_1 ON a_1.id = cts.account OR a_1.id = cts.account
|
||||
WHERE a_1.currency = (( SELECT accounts.currency
|
||||
FROM public.accounts
|
||||
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||
AND cts.amount < 0::numeric) credit ON credit.cts_t = t.id
|
||||
LEFT JOIN ( SELECT dts.amount AS debit_cash,
|
||||
dts.transaction AS dts_t
|
||||
FROM public.transaction_nicesplits dts
|
||||
LEFT JOIN public.accounts a_1 ON a_1.id = dts.account OR a_1.id = dts.account
|
||||
WHERE a_1.currency = (( SELECT accounts.currency
|
||||
FROM public.accounts
|
||||
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||
AND dts.amount > 0::numeric) debit ON debit.dts_t = t.id
|
||||
LEFT JOIN public.accounts a ON a.id = t.responsible
|
||||
GROUP BY t.id, a.name
|
||||
ORDER BY t.id DESC;
|
||||
|
||||
-- --------------------------------
|
||||
-- Function to check schema version (used in migrations)
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.has_exact_schema_version(
|
||||
IN i_ver INTEGER
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT ver INTO v_ver FROM brmbar_privileged.brmbar_schema;
|
||||
IF v_ver is NULL THEN
|
||||
RETURN false;
|
||||
ELSE
|
||||
RETURN v_ver = i_ver;
|
||||
END IF;
|
||||
END;
|
||||
$x$;
|
||||
|
||||
-- --------------------------------
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.upgrade_schema_version_to(
|
||||
IN i_ver INTEGER
|
||||
) RETURNS VOID
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v_ver;
|
||||
IF v_ver=(i_ver-1) THEN
|
||||
UPDATE brmbar_privileged.brmbar_schema SET ver = i_ver;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Invalid brmbar schema version transition (% -> %)', v_ver, i_ver;
|
||||
END IF;
|
||||
END;
|
||||
$x$;
|
||||
|
||||
-- Initialize version 1
|
||||
INSERT INTO brmbar_privileged.brmbar_schema(ver) VALUES(1);
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
|
@ -1,40 +0,0 @@
|
|||
--
|
||||
-- 0002-trading-accounts.sql
|
||||
--
|
||||
-- #2 - add trading accounts to account type type
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(1) THEN
|
||||
|
||||
ALTER TYPE public.account_type ADD VALUE 'trading';
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(2);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
|
@ -1,52 +0,0 @@
|
|||
--
|
||||
-- 0003-new-account.sql
|
||||
--
|
||||
-- #3 - stored procedure for creating new account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(2) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.create_account(
|
||||
IN i_name public.accounts.name%TYPE,
|
||||
IN i_currency public.accounts.currency%TYPE,
|
||||
IN i_acctype public.accounts.acctype%TYPE
|
||||
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES (i_name, i_currency, i_acctype) RETURNING id INTO r_id;
|
||||
RETURN r_id;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(3);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
|
@ -1,50 +0,0 @@
|
|||
--
|
||||
-- 0004-add-account-barcode.sql
|
||||
--
|
||||
-- #4 - stored procedure for adding barcode to account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(3) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.add_barcode_to_account(
|
||||
IN i_account public.barcodes.account%TYPE,
|
||||
IN i_barcode public.barcodes.barcode%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.barcodes (account, barcode)
|
||||
VALUES (i_account, i_barcode);
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(4);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
|
@ -1,51 +0,0 @@
|
|||
--
|
||||
-- 0005-rename-account.sql
|
||||
--
|
||||
-- #5 - stored procedure for renaming account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(4) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rename_account(
|
||||
IN i_account public.accounts.id%TYPE,
|
||||
IN i_name public.accounts.name%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
UPDATE public.accounts
|
||||
SET name = i_name
|
||||
WHERE id = i_account;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(5);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
|
@ -1,50 +0,0 @@
|
|||
--
|
||||
-- 0006-new-currency.sql
|
||||
--
|
||||
-- #6 - stored procedure for creating new currency
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(5) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.create_currency(
|
||||
IN i_name public.currencies.name%TYPE
|
||||
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.currencies (name)
|
||||
VALUES (i_name) RETURNING id INTO r_id;
|
||||
RETURN r_id;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(6);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
|
@ -1,49 +0,0 @@
|
|||
--
|
||||
-- 0007-update-currency-sell-rate.sql
|
||||
--
|
||||
-- #7 - stored procedure for updating sell rate
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(6) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_currency_sell_rate(
|
||||
IN i_currency public.exchange_rates.source%TYPE,
|
||||
IN i_target public.exchange_rates.target%TYPE,
|
||||
IN i_rate public.exchange_rates.rate%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||
VALUES (i_currency, i_target, i_rate, 'source_to_target');
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(7);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
|
@ -1,49 +0,0 @@
|
|||
--
|
||||
-- 0008-update-currency-buy-rate.sql
|
||||
--
|
||||
-- #8 - stored procedure for updating buy rate
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(7) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_currency_buy_rate(
|
||||
IN i_currency public.exchange_rates.target%TYPE,
|
||||
IN i_source public.exchange_rates.source%TYPE,
|
||||
IN i_rate public.exchange_rates.rate%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||
VALUES (i_source, i_currency, i_rate, 'target_to_source');
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(8);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
|
@ -1,149 +0,0 @@
|
|||
--
|
||||
-- 0009-shop-sell.sql
|
||||
--
|
||||
-- #9 - stored function for sell transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(8) THEN
|
||||
|
||||
-- return negative number on rate not found
|
||||
CREATE OR REPLACE FUNCTION public.find_buy_rate(
|
||||
IN i_item_id public.accounts.id%TYPE;
|
||||
IN i_other_id public.accounts.id%TYPE;
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rate public.exchange_rates.rate%TYPE;
|
||||
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||
BEGIN
|
||||
SELECT rate INTO STRICT v_rate, rate_dir INTO STRICT 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 INTO STRICT v_rate, rate_dir INTO STRICT 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%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
INSERT INTO public.transactions (responsible, description)
|
||||
VALUES (i_responsible_id, i_description)
|
||||
RETURNING id INTO new_transaction_id;
|
||||
-- Return the new transaction ID
|
||||
RETURN new_transaction_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.sell_item(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_sell_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_profit NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Get the buy and sell rates from the stored functions
|
||||
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||
v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_sell_rate;
|
||||
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||
|
||||
-- Create a new transaction
|
||||
v_transaction_id := public.create_transaction(i_user_id, i_description);
|
||||
|
||||
-- the item (decrease stock)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_item_id, i_amount,
|
||||
(SELECT "name" FROM public.accounts WHERE id = i_user_id));
|
||||
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_user_id, v_cost,
|
||||
(SELECT "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- the profit
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', (SELECT account_id FROM accounts WHERE name = 'BrmBar Profits'), v_profit, (SELECT 'Margin on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(9);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,143 +0,0 @@
|
|||
--
|
||||
-- 0010-shop-sell-for-cash.sql
|
||||
--
|
||||
-- #10 - stored function for cash sell transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(9) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.create_transaction(
|
||||
i_responsible_id public.accounts.id%TYPE,
|
||||
i_description public.transactions.description%TYPE
|
||||
) RETURNS public.transactions.id%TYPE AS $$
|
||||
DECLARE
|
||||
new_transaction_id public.transactions%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;
|
||||
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, 'credit', i_item_id, i_amount,
|
||||
i_other_memo);
|
||||
|
||||
-- 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;
|
||||
$$;
|
||||
|
||||
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_other_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_other_id,
|
||||
i_target_currency_id,
|
||||
'Cash',
|
||||
i_description);
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP FUNCTION public.create_transaction;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(10);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,102 +0,0 @@
|
|||
--
|
||||
-- 0011-shop-undo-sale.sql
|
||||
--
|
||||
-- #11 - stored function for sale undo transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(10) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.create_transaction(
|
||||
i_responsible_id public.accounts.id%TYPE,
|
||||
i_description public.transactions.description%TYPE
|
||||
) RETURNS public.transactions.id%TYPE AS $$
|
||||
DECLARE
|
||||
new_transaction_id public.transactions%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
INSERT INTO public.transactions (responsible, description)
|
||||
VALUES (i_responsible_id, i_description)
|
||||
RETURNING id INTO new_transaction_id;
|
||||
-- Return the new transaction ID
|
||||
RETURN new_transaction_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.undo_sale_of_item(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_sell_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_profit NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Get the buy and sell rates from the stored functions
|
||||
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||
v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_sell_rate;
|
||||
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, i_description);
|
||||
|
||||
-- the item (decrease stock)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_item_id, i_amount,
|
||||
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_user_id));
|
||||
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_user_id, v_cost,
|
||||
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- the profit
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', (SELECT account_id FROM accounts WHERE name = 'BrmBar Profits'), v_profit, (SELECT 'Margin repaid on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(11);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0012-shop-add-credit.sql
|
||||
--
|
||||
-- #12 - stored function for cash deposit transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(11) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.add_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit replenishment for ' || i_user_name);
|
||||
-- Debit cash (credit replenishment)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_cash_account_id, i_credit, i_user_name);
|
||||
-- Credit the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_user_id, i_credit, 'Credit replenishment');
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(12);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0013-shop-withdraw-credit.sql
|
||||
--
|
||||
-- #13 - stored function for cash withdrawal transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(12) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.withdraw_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit withdrawal for ' || i_user_name);
|
||||
-- Debit cash (credit replenishment)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_cash_account_id, i_credit, i_user_name);
|
||||
-- Credit the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_user_id, i_credit, 'Credit withdrawal');
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(13);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,58 +0,0 @@
|
|||
--
|
||||
-- 0014-shop-transfer-credit.sql
|
||||
--
|
||||
-- #14 - stored function for "credit" transfer transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(13) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.transfer_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_userfrom_id public.accounts.id%TYPE,
|
||||
i_userfrom_name TEXT,
|
||||
i_userto_id public.accounts.id%TYPE,
|
||||
i_userto_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM public.add_credit(i_cash_account_id, i_credit, i_userto_id, i_userto_name);
|
||||
PERFORM public.withdraw_credit(i_cash_account_id, i_credit, i_userfrom_id, i_userfrom_name);
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(14);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,82 +0,0 @@
|
|||
--
|
||||
-- 0015-shop-buy-for-cash.sql
|
||||
--
|
||||
-- #15 - stored function for cash-based stock replenishment transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(14) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.buy_for_cash(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_item_name TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- this could fail and it would generate exception in python
|
||||
-- FIXME: convert v_buy_rate < 0 into python exception
|
||||
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||
-- this could fail and it would generate exception in python, even though it is not used
|
||||
--v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_buy_rate;
|
||||
|
||||
-- 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 (i_transaction_id, 'debit', i_item_id, i_amount,
|
||||
'Cash');
|
||||
|
||||
-- the cash
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_cash_account_id, v_cost,
|
||||
i_item_name);
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(15);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0016-shop-buy-for-cash.sql
|
||||
--
|
||||
-- #16 - stored function for receipt reimbursement transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(15) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.receipt_reimbursement(
|
||||
i_profits_id public.accounts.id%TYPE,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name public.accounts.name%TYPE,
|
||||
i_amount NUMERIC,
|
||||
i_description TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id,
|
||||
'Receipt: ' || i_description);
|
||||
-- the "profit"
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_profits_id, i_amount, i_user_name);
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_user_id, i_amount, 'Credit from receipt: ' || i_description);
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(16);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,157 +0,0 @@
|
|||
--
|
||||
-- 0017-shop-fix-inventory.sql
|
||||
--
|
||||
-- #17 - stored function for "fixing" inventory transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(16) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.compute_account_balance(
|
||||
i_account_id public.accounts.id%TYPE
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_crsum NUMERIC;
|
||||
v_dbsum NUMERIC;
|
||||
BEGIN
|
||||
SELECT COALESCE(SUM(CASE WHEN side='credit' THEN amount ELSE 0 END),0) crsum INTO v_crsum,
|
||||
COALESCE(SUM(CASE WHEN side='debit' THEN amount ELSE 0 END),0) dbsum into v_dbsum
|
||||
FROM public.transaction_splits ts WHERE ts.account=4
|
||||
RETURN v_dbsum - v_crsum;
|
||||
END; $$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.fix_account_balance(
|
||||
IN i_account_id public.acounts.id%TYPE,
|
||||
IN i_account_currency_id public.currencies.id%TYPE,
|
||||
IN i_excess_id public.acounts.id%TYPE,
|
||||
IN i_deficit_id public.acounts.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.acounts.id%TYPE,
|
||||
IN i_account_currency_id public.currencies.id%TYPE,
|
||||
IN i_excess_id public.acounts.id%TYPE,
|
||||
IN i_deficit_id public.acounts.id%TYPE,
|
||||
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||
IN i_amount_in_reality NUMERIC
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
BEGIN
|
||||
RETURN brmbar_privileged.fix_account_balance(
|
||||
i_account_id,
|
||||
i_account_currency_id,
|
||||
i_excess_id,
|
||||
i_deficit_id,
|
||||
i_shop_currency_id,
|
||||
i_amount_in_reality
|
||||
);
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(17);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,60 +0,0 @@
|
|||
--
|
||||
-- 0018-shop-fix-cash.sql
|
||||
--
|
||||
-- #18 - stored function for "fixing cash" transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(17) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fix_cash(
|
||||
IN i_excess_id public.acounts.id%TYPE,
|
||||
IN i_deficit_id public.acounts.id%TYPE,
|
||||
IN i_shop_currency_id public.currencies.id%TYPE,
|
||||
IN i_amount_in_reality NUMERIC
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
BEGIN
|
||||
RETURN brmbar_privileged.fix_account_balance(
|
||||
1,
|
||||
1,
|
||||
i_excess_id,
|
||||
i_deficit_id,
|
||||
i_shop_currency_id,
|
||||
i_amount_in_reality
|
||||
);
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(18);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,82 +0,0 @@
|
|||
--
|
||||
-- 0019-shop-consolidate.sql
|
||||
--
|
||||
-- #19 - stored function for "consolidation" transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(18) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.make_consolidate_transaction(
|
||||
i_excess_id public.accounts.id%TYPE,
|
||||
i_deficit_id public.accounts.id%TYPE,
|
||||
i_profits_id public.accounts.id%TYPE
|
||||
) RETURNS TEXT
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
v_excess_balance NUMERIC;
|
||||
v_deficit_balance NUMERIC;
|
||||
v_ret TEXT;
|
||||
BEGIN
|
||||
v_ret := NULL;
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(NULL,
|
||||
'BrmBar inventory consolidation');
|
||||
v_excess_balance := public.compute_account_balance(i_excess_id);
|
||||
v_deficit_balance := public.compute_account_balance(i_deficit_id);
|
||||
IF v_excess_balance <> 0 THEN
|
||||
v_ret := 'Excess balance ' || -v_excess_balance || ' debited to profit';
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_excess_id, -v_excess_balance,
|
||||
'Excess balance added to profit.');
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_profits_id, -v_excess_balance,
|
||||
'Excess balance added to profit.');
|
||||
END IF;
|
||||
IF v_deficit_balance <> 0 THEN
|
||||
v_ret := COALESCE(v_ret, '');
|
||||
v_ret := v_ret || 'Deficit balance ' || v_deficit_balance || ' credited to profit';
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_deficit_id, v_deficit_balance,
|
||||
'Deficit balance removed from profit.');
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_profits_id, v_deficit_balance,
|
||||
'Deficit balance removed from profit.');
|
||||
END IF;
|
||||
RETURN v_ret;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(19);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,64 +0,0 @@
|
|||
--
|
||||
-- 0020-shop-undo.sql
|
||||
--
|
||||
-- #20 - stored function for undo transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(19) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.undo_transaction(
|
||||
IN i_id public.transactions.id%TYPE)
|
||||
RETURNS public.transactions.id%TYPE
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
DECLARE
|
||||
v_ntrn_id public.transactions.id%TYPE;
|
||||
v_old_trn public.transactions%ROWTYPE;
|
||||
v_old_split public.transaction_splits%ROWTYPE;
|
||||
BEGIN
|
||||
SELECT * INTO v_old_trn FROM public.transactions WHERE id = i_id;
|
||||
INSERT INTO transactions ("description") VALUES ('undo '||o_id||' ('||v_old_trn.description||')') RETURNING id into v_ntrn_id;
|
||||
FOR v_old_split IN
|
||||
SELECT * FROM transaction_splits WHERE "transaction" = i_id
|
||||
LOOP
|
||||
INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo")
|
||||
VALUES (v_ntrn_id, v_old_split.side, v_old_split.account, -v_old_split.amount,
|
||||
'undo ' || v_old_split.id || ' (' || v_old_split.memo || ')' );
|
||||
END LOOP;
|
||||
RETURN v_ntrn_id;
|
||||
END;
|
||||
$fn$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(20);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
|
@ -1,73 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
#from brmbar import Database
|
||||
#from brmbar import Currency
|
||||
|
||||
from contextlib import closing
|
||||
import psycopg2
|
||||
from brmbar.Database import Database
|
||||
from brmbar.Currency import Currency
|
||||
import math
|
||||
|
||||
#import brmbar
|
||||
|
||||
|
||||
|
||||
def approx_equal(a, b, tol=1e-6):
|
||||
"""Check if two (buy, sell) rate tuples are approximately equal."""
|
||||
return (
|
||||
isinstance(a, tuple) and isinstance(b, tuple) and
|
||||
math.isclose(a[0], b[0], abs_tol=tol) and
|
||||
math.isclose(a[1], b[1], abs_tol=tol)
|
||||
)
|
||||
|
||||
def compare_exceptions(e1, e2):
|
||||
"""Compare exception types and messages."""
|
||||
return type(e1) == type(e2) and str(e1) == str(e2)
|
||||
|
||||
def main():
|
||||
db = Database("dbname=brmbar")
|
||||
|
||||
# Get all currencies
|
||||
with closing(db.db_conn.cursor()) as cur:
|
||||
cur.execute("SELECT id, name FROM currencies")
|
||||
currencies = cur.fetchall()
|
||||
|
||||
# Build Currency objects
|
||||
currency_objs = [Currency(db, id, name) for id, name in currencies]
|
||||
|
||||
# Test all currency pairs
|
||||
for c1 in currency_objs:
|
||||
for c2 in currency_objs:
|
||||
#if c1.id == c2.id:
|
||||
# continue
|
||||
|
||||
try:
|
||||
rates1 = c1.rates(c2)
|
||||
exc1 = None
|
||||
except (RuntimeError, NameError) as e1:
|
||||
rates1 = None
|
||||
exc1 = e1
|
||||
|
||||
try:
|
||||
rates2 = c1.rates2(c2)
|
||||
exc2 = None
|
||||
except (RuntimeError, NameError) as e2:
|
||||
rates2 = None
|
||||
exc2 = e2
|
||||
|
||||
if exc1 or exc2:
|
||||
if not compare_exceptions(exc1, exc2):
|
||||
print(f"[EXCEPTION DIFFERENCE] {c1.name} -> {c2.name}")
|
||||
print(f" rates() exception: {type(exc1).__name__}: {exc1}")
|
||||
print(f" rates2() exception: {type(exc2).__name__}: {exc2}")
|
||||
elif not approx_equal(rates1, rates2):
|
||||
print(f"[VALUE DIFFERENCE] {c1.name} -> {c2.name}")
|
||||
print(f" rates(): {rates1}")
|
||||
print(f" rates2(): {rates2}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,6 +0,0 @@
|
|||
#!/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
|
|
@ -1,18 +0,0 @@
|
|||
#!/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