forked from brmlab/brmbar-github
Compare commits
132 commits
Author | SHA1 | Date | |
---|---|---|---|
0bff27da29 | |||
f0058aad68 | |||
c21b394b42 | |||
69c405f715 | |||
c8892f825c | |||
96027ca66a | |||
aad79dafa6 | |||
1df7db93e7 | |||
629b35655d | |||
ad832fc71b | |||
e15f1646ce | |||
7b11a8c954 | |||
f0cd8361d1 | |||
7ed7417492 | |||
028d4d98db | |||
deb3faa717 | |||
4c7012c517 | |||
cef95c4313 | |||
aded7a5769 | |||
66870bbc8c | |||
9235607d4c | |||
8f42145bee | |||
58ab1d00be | |||
c5d1fc3402 | |||
111a8c9b63 | |||
c04f934340 | |||
40f4abe37f | |||
900264469a | |||
f6ec2be215 | |||
3000731ac7 | |||
2f601a0b1a | |||
5f9292fd02 | |||
e04d614e15 | |||
![]() |
dbd3835dfb | ||
![]() |
6749b2c97a | ||
![]() |
a1c37cb695 | ||
![]() |
f061cc7f7b | ||
![]() |
6f3fc6767a | ||
![]() |
e2e6632df0 | ||
![]() |
1f3f1cdd8f | ||
![]() |
466b92e7c2 | ||
![]() |
6208ae6027 | ||
![]() |
8f6776f3d0 | ||
![]() |
a9e72d736c | ||
![]() |
0e306912d7 | ||
![]() |
c419c91a40 | ||
![]() |
c5becfcabe | ||
![]() |
2bfd1796d0 | ||
![]() |
9a70088591 | ||
![]() |
11eaecfccf | ||
![]() |
0c349f6c6a | ||
![]() |
e3a4fe880d | ||
![]() |
4494dafbb8 | ||
![]() |
9e95724556 | ||
![]() |
2bbb46d4ea | ||
![]() |
6b67dd372e | ||
![]() |
edc99f1ff9 | ||
![]() |
7a8e4ef794 | ||
![]() |
3ea61f01d7 | ||
![]() |
feec2c9ac4 | ||
![]() |
76bb2cac98 | ||
![]() |
099d775102 | ||
![]() |
5c84bd6a8f | ||
![]() |
a162e544c1 | ||
![]() |
906a3b62ed | ||
![]() |
fe80200e49 | ||
![]() |
48c583a81c | ||
![]() |
934873c2fc | ||
![]() |
6cc95ab680 | ||
![]() |
1c0e51a246 | ||
![]() |
84881dece3 | ||
![]() |
5de7bf7458 | ||
![]() |
4604cd36c3 | ||
![]() |
d79dcb5a5c | ||
![]() |
4c6558015f | ||
![]() |
664e7a5b32 | ||
![]() |
8229b44774 | ||
![]() |
6ed53b92aa | ||
![]() |
65741c31f0 | ||
![]() |
bf3fd3e842 | ||
![]() |
ae82675a60 | ||
![]() |
400ef9e969 | ||
![]() |
e6870f8b2a | ||
![]() |
10d8a17723 | ||
![]() |
22503c242f | ||
![]() |
617b466c77 | ||
![]() |
4f3bc87733 | ||
![]() |
e567d36e77 | ||
![]() |
d74ca2b6c0 | ||
![]() |
ac4f2f2c62 | ||
![]() |
0528a50769 | ||
![]() |
06e1e15b7e | ||
![]() |
7ac0031a8d | ||
![]() |
0706054e81 | ||
![]() |
32f76b290c | ||
![]() |
4ae814179d | ||
![]() |
12f5079381 | ||
![]() |
3ecbf192e2 | ||
![]() |
d44dbad313 | ||
![]() |
ba4d8686f6 | ||
![]() |
fb6eadf031 | ||
![]() |
35c3534575 | ||
![]() |
55e6a5226c | ||
![]() |
13bc4d6f60 | ||
![]() |
bacb9b1791 | ||
![]() |
1bb05e6935 | ||
![]() |
dab9388703 | ||
![]() |
c8a0483401 | ||
![]() |
76cb5ba4c7 | ||
![]() |
be74f723d2 | ||
![]() |
f5ffeeae97 | ||
![]() |
3e8f44e5e0 | ||
![]() |
1ba8099e8e | ||
![]() |
ff915072a0 | ||
![]() |
5f4c4cb5ca | ||
![]() |
cc3009c813 | ||
![]() |
ae3ad22d59 | ||
![]() |
385393fdb7 | ||
![]() |
18633fa596 | ||
![]() |
a4dbf3b77c | ||
![]() |
ffa119e7f0 | ||
![]() |
ec94f1d034 | ||
![]() |
1c050d7da5 | ||
![]() |
e21f06b9c1 | ||
![]() |
4ea03e665e | ||
![]() |
2d134c829a | ||
![]() |
3bf0e15af1 | ||
![]() |
4983fea31e | ||
![]() |
48bfde333a | ||
![]() |
8ae6dfb604 | ||
![]() |
8a8d53f8e6 | ||
![]() |
ff217b53b0 |
77 changed files with 4567 additions and 670 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
.*.sw?
|
||||
*~
|
||||
|
|
|
@ -7,16 +7,16 @@ 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" width="1052.3622" height="744.09448" version="1.1" id="svg2" inkscape:version="0.47 r22583" sodipodi:docname="barcodes.svg">
|
||||
<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 = 4
|
||||
scalex = 1.2
|
||||
scaley = 1.2
|
||||
width = 5
|
||||
scalex = 0.8
|
||||
scaley = 0.8
|
||||
|
||||
p = 0
|
||||
i = 0
|
||||
|
@ -27,7 +27,7 @@ lines = sys.stdin.readlines()
|
|||
|
||||
for idx in xrange(len(lines)):
|
||||
items = lines[idx].strip().split(';')
|
||||
if idx % 8 == 0:
|
||||
if idx % 30 == 0:
|
||||
if f and not f.closed:
|
||||
f.write(svgfoot)
|
||||
f.close()
|
||||
|
@ -36,9 +36,9 @@ for idx in xrange(len(lines)):
|
|||
i = 0
|
||||
j = 0
|
||||
f.write(svghead)
|
||||
elem = Popen(('zint','--directsvg','--notext', '-d', items[1]), stdout = PIPE).communicate()[0].split('\n')
|
||||
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*285 , 180+j*285) )
|
||||
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
|
||||
|
|
9
barcode-generator/howto.txt
Normal file
9
barcode-generator/howto.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
on brmbar:
|
||||
select distinct barcode from barcodes b, transactions t, accounts a where t.responsible=a.id and time>'2015-01-01' and b.account=a.id order by barcode asc;
|
||||
|
||||
run this locally and paste output of previous command:
|
||||
|
||||
while read tmp; do echo "$tmp;$tmp";done|grep -v overflow|python2 ./barcode-generator.py
|
||||
|
||||
print resulting SVG files
|
||||
|
2
brmbar3/.gitignore
vendored
2
brmbar3/.gitignore
vendored
|
@ -1 +1,3 @@
|
|||
__pycache__
|
||||
*.log
|
||||
brmbar/*.pyc
|
||||
|
|
343
brmbar3/COPYING
Normal file
343
brmbar3/COPYING
Normal file
|
@ -0,0 +1,343 @@
|
|||
Distribution under GPLv2 or any later version is acceptable for this
|
||||
software.
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
85
brmbar3/INSTALL.md
Normal file
85
brmbar3/INSTALL.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
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.
|
64
brmbar3/PURGE.txt
Normal file
64
brmbar3/PURGE.txt
Normal file
|
@ -0,0 +1,64 @@
|
|||
How to "reset" the database - drop all history and keep only accounts with non-zero balance.
|
||||
|
||||
Legend:
|
||||
> - SQL commands
|
||||
$ - shell commands
|
||||
|
||||
Run the (full) inventory.
|
||||
|
||||
Get number of the first inventory TX.
|
||||
|
||||
> select id from account_balances where id in (select id from accounts where currency not in (select distinct currency from
|
||||
transaction_nicesplits where transaction >= NUMBER_HERE and currency != 1 and memo like '%Inventory fix%') and acctype = 'inventory') and crbalance != 0 \g 'vynulovat'
|
||||
$ ./brmbar-cli.py inventory `cat vynulovat | while read x; do echo $x 0; done`
|
||||
|
||||
Backup the database
|
||||
$ pg_dump brmbar > backup.sql
|
||||
|
||||
Dump "> SELECT * FROM account_balances;" to file N.
|
||||
|
||||
Dump inventory to file nastavit FIXME.
|
||||
|
||||
Drop all transactions:
|
||||
> delete from transaction_splits;
|
||||
> delete from transactions;
|
||||
|
||||
Restore inventory:
|
||||
$ cat nastavit | while read acc p amt; do ./brmbar-cli.py inventory $acc `echo $amt | grep -oE "^[0-9-]+"`; done
|
||||
|
||||
Restore cash balance:
|
||||
$ cat N | grep debt | tr -s " " |cut -d \| -f 2,4 | while read acc p amt; do ./brmbar-cli.py changecredit $acc `echo $amt | grep -oE "^[0-9-]+"`; done
|
||||
|
||||
Delete zero-balance accounts:
|
||||
> delete from accounts where accounts.id not in (select id from account_balances);
|
||||
|
||||
Delete orphaned barcodes:
|
||||
> delete from barcodes where barcodes.account not in (select id from account_balances);
|
||||
|
||||
Delete orphaned currencies and exchange rates:
|
||||
> CREATE OR REPLACE VIEW "a_tmp" AS
|
||||
SELECT ts.account AS id, accounts.name, accounts.acctype, accounts.currency AS fff, (- sum(CASE WHEN (ts.side = 'credit'::transaction_split_side) THEN (- ts.amount) ELSE ts.amount END)) AS crbalance FROM (transaction_splits ts LEFT JOIN accounts ON ((accounts.id = ts.account))) GROUP BY ts.account, accounts.name, accounts.id, accounts.acctype ORDER BY (- sum(CASE WHEN (ts.side = 'credit'::transaction_split_side) THEN (- ts.amount) ELSE ts.amount END));
|
||||
|
||||
> delete from exchange_rates where source not in (select fff from a_tmp);
|
||||
> delete from currencies where id not in (select fff from a_tmp);
|
||||
|
||||
> DROP VIEW "a_tmp";
|
||||
|
||||
Drop obsolete exchange rates:
|
||||
|
||||
> delete from exchange_rates where
|
||||
valid_since <> (SELECT max(valid_since)
|
||||
FROM exchange_rates e
|
||||
WHERE e.target = exchange_rates.target and e.source = exchange_rates.source)
|
||||
|
||||
Restore system accounts:
|
||||
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||
VALUES ('BrmBar Profits', '1', 'income', '1');
|
||||
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||
VALUES ('BrmBar Excess', '1', 'income', '1');
|
||||
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||
VALUES ('BrmBar Deficit', '1', 'expense', '1');
|
||||
> INSERT INTO "accounts" ("name", "currency", "acctype", "active")
|
||||
VALUES ('BrmBar Cash', '1', 'cash', '1');
|
||||
|
||||
Restart brmbar.
|
81
brmbar3/README.md
Normal file
81
brmbar3/README.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
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.
|
52
brmbar3/SQL
52
brmbar3/SQL
|
@ -1,10 +1,11 @@
|
|||
CREATE SEQUENCE currencies_id_seq START WITH 1 INCREMENT BY 1;
|
||||
CREATE SEQUENCE currencies_id_seq START WITH 2 INCREMENT BY 1;
|
||||
CREATE TABLE currencies (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('currencies_id_seq'::regclass),
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
INSERT INTO currencies (name) VALUES ('Kč');
|
||||
-- Some code depends on the primary physical currency to have id 1.
|
||||
INSERT INTO currencies (id, name) VALUES (1, 'Kč');
|
||||
|
||||
CREATE TYPE exchange_rate_direction AS ENUM ('source_to_target', 'target_to_source');
|
||||
CREATE TABLE exchange_rates (
|
||||
|
@ -36,10 +37,14 @@ CREATE TABLE accounts (
|
|||
currency INTEGER NOT NULL,
|
||||
FOREIGN KEY (currency) REFERENCES currencies (id),
|
||||
|
||||
acctype account_type NOT NULL
|
||||
acctype account_type NOT NULL,
|
||||
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
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;
|
||||
|
@ -49,6 +54,9 @@ 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;
|
||||
|
@ -79,3 +87,41 @@ CREATE TABLE transaction_splits (
|
|||
|
||||
memo TEXT
|
||||
);
|
||||
|
||||
-- List balances of accounts computed based on transactions
|
||||
-- Note that currency information is currently not supplied; inventory items
|
||||
-- have balances in stock amounts.
|
||||
CREATE VIEW account_balances AS
|
||||
SELECT ts.account AS id, accounts.name AS name, accounts.acctype AS acctype,
|
||||
-SUM(CASE WHEN ts.side = 'credit' THEN -ts.amount ELSE ts.amount END) AS crbalance
|
||||
FROM transaction_splits AS ts
|
||||
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;
|
||||
|
|
57
brmbar3/SQL-for-RO-access.sql
Normal file
57
brmbar3/SQL-for-RO-access.sql
Normal file
|
@ -0,0 +1,57 @@
|
|||
CREATE OR REPLACE FUNCTION accounts_id_seq_value()
|
||||
RETURNS bigint
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
result bigint;
|
||||
BEGIN
|
||||
SELECT last_value FROM accounts_id_seq
|
||||
INTO result;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION transactions_id_seq_value()
|
||||
RETURNS bigint
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
result bigint;
|
||||
BEGIN
|
||||
SELECT last_value FROM transactions_id_seq
|
||||
INTO result;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION transaction_splits_id_seq_value()
|
||||
RETURNS bigint
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
result bigint;
|
||||
BEGIN
|
||||
SELECT last_value FROM transaction_splits_id_seq
|
||||
INTO result;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION currencies_id_seq_value()
|
||||
RETURNS bigint
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
result bigint;
|
||||
BEGIN
|
||||
SELECT last_value FROM currencies_id_seq
|
||||
INTO result;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
59
brmbar3/SQL-schema-v001.sql
Normal file
59
brmbar3/SQL-schema-v001.sql
Normal file
|
@ -0,0 +1,59 @@
|
|||
--RESET search_path;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
-- intoduce implementation schema
|
||||
CREATE SCHEMA IF NOT EXISTS brmbar_implementation;
|
||||
-- version table (with initialization)
|
||||
CREATE TABLE IF NOT EXISTS brmbar_implementation.brmbar_schema (
|
||||
ver INTEGER NOT NULL
|
||||
);
|
||||
DO $$
|
||||
DECLARE v INTEGER;
|
||||
BEGIN
|
||||
SELECT ver FROM brmbar_implementation.brmbar_schema INTO v;
|
||||
IF v IS NULL THEN
|
||||
INSERT INTO brmbar_implementation.brmbar_schema (ver) VALUES (1);
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_implementation.has_exact_schema_version(
|
||||
IN i_ver INTEGER NOT NULL
|
||||
) RETURNS INTEGER
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT ver INTO STRICT v_ver FROM brmbar_implementation.brmbar_schema;
|
||||
IF v_ver IS NULL or v_ver <> i_ver THEN
|
||||
RAISE EXCEPTION 'Invalid brmbar schema version';
|
||||
END IF;
|
||||
RETURN v_ver;
|
||||
/*
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RAISE EXCEPTION 'PID % not found';
|
||||
WHEN TOO_MANY_ROWS THEN
|
||||
RAISE EXCEPTION 'PID % not unique';
|
||||
*/
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_implementation.upgrade_schema_version_to(
|
||||
IN i_ver INTEGER NOT NULL
|
||||
) RETURNS INTEGER
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT brmbar_implementation.has_exact_schema_version(i_ver) INTO v_ver;
|
||||
IF v_ver + 1 = i_ver THEN
|
||||
UPDATE brmbar_implementation.brmbar_schema SET ver = i_ver;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Invalid brmbar schema version';
|
||||
END IF;
|
||||
RETURN i_ver;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- vim: set ft=plsql :
|
||||
|
61
brmbar3/SQL-schema-v002.sql
Normal file
61
brmbar3/SQL-schema-v002.sql
Normal file
|
@ -0,0 +1,61 @@
|
|||
--RESET search_path
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
--- upgrade schema
|
||||
DO $upgrade_block$
|
||||
DECLARE
|
||||
current_ver INTEGER;
|
||||
BEGIN
|
||||
|
||||
-- confirm that we are upgrading from version 1
|
||||
SELECT brmbar_implementation.has_exact_schema_version(1) INTO current_ver;
|
||||
IF current_ver <> 1 THEN
|
||||
RAISE EXCEPTION 'BrmBar schema version % cannot be upgraded to version 2.', current_ver;
|
||||
END IF;
|
||||
|
||||
-- structural changes
|
||||
|
||||
-- TRADING ACCOUNTS
|
||||
--START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
||||
|
||||
-- currency trading accounts - account type
|
||||
ALTER TYPE public.account_type ADD VALUE IF NOT EXISTS 'trading';
|
||||
|
||||
-- constraint needed for foreign key in currencies table
|
||||
ALTER TABLE public.accounts ADD CONSTRAINT accounts_id_acctype_key UNIQUE(id, acctype);
|
||||
|
||||
-- add columns to currencies to record the trading account associated with the currency
|
||||
ALTER TABLE public.currencies
|
||||
ADD COLUMN IF NOT EXISTS trading_account integer,
|
||||
ADD COLUMN IF NOT EXISTS trading_account_type account_type GENERATED ALWAYS AS ('trading'::public.account_type) STORED;
|
||||
|
||||
-- make trading accounts (without making duplicates)
|
||||
INSERT INTO public.accounts ("name", "currency", acctype)
|
||||
SELECT
|
||||
'Currency Trading Account: ' || c."name",
|
||||
c.id,
|
||||
'trading'::public.account_type
|
||||
FROM public.currencies AS c
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.accounts a
|
||||
WHERE a.currency = c.id AND a.acctype = 'trading'::public.account_type
|
||||
);
|
||||
|
||||
|
||||
-- record the trading account IDs in currencies table
|
||||
UPDATE public.currencies AS c SET (trading_account) = (SELECT a.id FROM public.accounts AS a WHERE a.currency = c.id AND c.acctype = 'trading'::public.account_type);
|
||||
|
||||
-- foreign key to check the validity of currency trading account reference
|
||||
ALTER TABLE public.currencies
|
||||
ADD CONSTRAINT currencies_trading_fkey FOREIGN KEY (trading_account, trading_account_type)
|
||||
REFERENCES xaccounts(id,acctype) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
--COMMIT AND CHAIN;
|
||||
|
||||
SELECT brmbar_implementation.upgrade_schema_version_to(2) INTO current_ver;
|
||||
-- end of upgrade do block
|
||||
end
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
25
brmbar3/TODO
25
brmbar3/TODO
|
@ -1,26 +1,5 @@
|
|||
+ 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 **
|
||||
This reprensents some generic features that would need to be implemented
|
||||
in brmbar-gui-qt4 to have a fully fledged user interface.
|
||||
|
||||
. User management
|
||||
- Add user
|
||||
|
|
153
brmbar3/USAGE.md
Normal file
153
brmbar3/USAGE.md
Normal file
|
@ -0,0 +1,153 @@
|
|||
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;
|
||||
|
8
brmbar3/USEFUL.txt
Normal file
8
brmbar3/USEFUL.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
Accounts with multiple barcodes:
|
||||
|
||||
SELECT accounts.name,barcodes.account,barcodes.barcode
|
||||
FROM "barcodes"
|
||||
join accounts on accounts.id = barcodes.account
|
||||
where barcodes.account in (select a from (select count(*) as c, account as a from barcodes group by account) as dt where c > 1)
|
||||
ORDER BY "account" DESC
|
||||
|
7
brmbar3/alert.sh
Executable file
7
brmbar3/alert.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
case $1 in
|
||||
alert) mplayer -really-quiet ~/trombone.wav & ;;
|
||||
limit) mplayer -really-quiet ~/much.wav & ;;
|
||||
charge) mplayer -really-quiet ~/charge.wav & ;;
|
||||
esac
|
42
brmbar3/autostock.py
Executable file
42
brmbar3/autostock.py
Executable file
|
@ -0,0 +1,42 @@
|
|||
#! /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,61 +1,266 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import psycopg2
|
||||
|
||||
from brmbar import Database
|
||||
|
||||
import brmbar
|
||||
|
||||
db = psycopg2.connect("dbname=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")
|
||||
shop = brmbar.Shop.new_with_defaults(db)
|
||||
currency = shop.currency
|
||||
|
||||
active_inv_item = None
|
||||
active_credit = None
|
||||
if len(sys.argv) <= 1:
|
||||
help()
|
||||
|
||||
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 sys.argv[1] == "showcredit":
|
||||
acct = load_user(sys.argv[2])
|
||||
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] == "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()))
|
||||
|
||||
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 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)
|
||||
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()))
|
||||
|
||||
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
|
||||
elif sys.argv[1] == "userinfo":
|
||||
acct = load_user(sys.argv[2])
|
||||
print("{} (id {}): {}".format(acct.name, acct.id, acct.negbalance_str()))
|
||||
|
||||
else:
|
||||
print("invalid account type {}".format(acct.acctype))
|
||||
active_inv_item = None
|
||||
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()
|
||||
|
|
|
@ -1,154 +1,233 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import psycopg2
|
||||
import subprocess
|
||||
|
||||
from PySide import QtCore, QtGui, QtDeclarative
|
||||
|
||||
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 """
|
||||
def __init__(self):
|
||||
QtCore.QObject.__init__(self)
|
||||
""" Interface between QML and the brmbar package """
|
||||
def __init__(self):
|
||||
QtCore.QObject.__init__(self)
|
||||
|
||||
def acct_debt_map(self, acct):
|
||||
map = acct.__dict__.copy()
|
||||
map["balance"] = str(acct.balance())
|
||||
map["negbalance"] = str(-acct.balance())
|
||||
map["negbalance_str"] = acct.negbalance_str()
|
||||
return map
|
||||
def acct_debt_map(self, acct):
|
||||
map = acct.__dict__.copy()
|
||||
map["balance"] = str(acct.balance())
|
||||
map["negbalance"] = str(-acct.balance())
|
||||
map["negbalance_str"] = acct.negbalance_str()
|
||||
return map
|
||||
|
||||
def acct_inventory_map(self, acct):
|
||||
buy, sell = acct.currency.rates(currency)
|
||||
map = acct.__dict__.copy()
|
||||
map["balance"] = "{:.0f}".format(acct.balance())
|
||||
map["buy_price"] = str(buy)
|
||||
map["price"] = str(sell)
|
||||
return map
|
||||
def acct_inventory_map(self, acct):
|
||||
buy, sell = acct.currency.rates(currency)
|
||||
map = acct.__dict__.copy()
|
||||
map["balance"] = "{:.0f}".format(acct.balance())
|
||||
map["buy_price"] = str(buy)
|
||||
map["price"] = str(sell)
|
||||
return map
|
||||
|
||||
def acct_map(self, acct):
|
||||
if acct is None:
|
||||
return None
|
||||
if acct.acctype == 'debt':
|
||||
return self.acct_debt_map(acct)
|
||||
elif acct.acctype == "inventory":
|
||||
return self.acct_inventory_map(acct)
|
||||
else:
|
||||
return None
|
||||
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
|
||||
|
||||
@QtCore.Slot(str, result='QVariant')
|
||||
def barcodeInput(self, barcode):
|
||||
""" Evaluate barcode received on input
|
||||
def acct_cash_map(self, acct):
|
||||
map = acct.__dict__.copy()
|
||||
return map
|
||||
|
||||
Normally, we would return just the account object, but
|
||||
passing that to QML appears to be very non-trivial.
|
||||
Therefore, we construct a map that we can pass around easily.
|
||||
We return None on unrecognized barcode. """
|
||||
barcode = str(barcode)
|
||||
if barcode and barcode[0] == "$":
|
||||
credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000}
|
||||
credit = credits[barcode]
|
||||
if credit is None:
|
||||
return None
|
||||
return { "acctype": "recharge", "amount": str(credit)+".00" }
|
||||
return self.acct_map(brmbar.Account.load_by_barcode(db, barcode))
|
||||
def acct_map(self, acct):
|
||||
if acct is None:
|
||||
return None
|
||||
if acct.acctype == 'debt':
|
||||
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
|
||||
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def loadAccount(self, dbid):
|
||||
return self.acct_map(brmbar.Account.load(db, id = dbid))
|
||||
@QtCore.Slot(str, result='QVariant')
|
||||
def barcodeInput(self, barcode):
|
||||
""" Evaluate barcode received on input
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def sellItem(self, itemid, userid):
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.sell(item = brmbar.Account.load(db, id = itemid), user = user)
|
||||
return user.negbalance_str()
|
||||
Normally, we would return just the account object, but
|
||||
passing that to QML appears to be very non-trivial.
|
||||
Therefore, we construct a map that we can pass around easily.
|
||||
We return None on unrecognized barcode. """
|
||||
barcode = str(barcode)
|
||||
if barcode and barcode[0] == "$":
|
||||
credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000}
|
||||
credit = credits[barcode]
|
||||
if credit is None:
|
||||
return None
|
||||
return { "acctype": "recharge", "amount": str(credit)+".00" }
|
||||
acct = self.acct_map(brmbar.Account.load_by_barcode(db, barcode))
|
||||
db.commit()
|
||||
return acct
|
||||
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def sellItemCash(self, itemid):
|
||||
shop.sell_for_cash(item = brmbar.Account.load(db, id = itemid))
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def loadAccount(self, dbid):
|
||||
acct = self.acct_map(brmbar.Account.load(db, id = dbid))
|
||||
db.commit()
|
||||
return acct
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def chargeCredit(self, credit, userid):
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.add_credit(credit = credit, user = user)
|
||||
return user.negbalance_str()
|
||||
@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 withdrawCredit(self, credit, userid):
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.withdraw_credit(credit = credit, user = user)
|
||||
return user.negbalance_str()
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def sellItem(self, itemid, userid):
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.sell(item = brmbar.Account.load(db, id = itemid), user = user)
|
||||
balance = user.negbalance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_cash(self):
|
||||
return shop.cash.balance_str()
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_profit(self):
|
||||
return shop.profits.balance_str()
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_inventory(self):
|
||||
return shop.inventory_balance_str()
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_credit(self):
|
||||
return shop.credit_negbalance_str()
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def sellItemCash(self, itemid):
|
||||
shop.sell_for_cash(item = brmbar.Account.load(db, id = itemid))
|
||||
db.commit()
|
||||
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def userList(self):
|
||||
return [ self.acct_debt_map(a) for a in shop.account_list("debt") ]
|
||||
@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()
|
||||
db.commit()
|
||||
return balance
|
||||
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def itemList(self):
|
||||
return [ self.acct_inventory_map(a) for a in shop.account_list("inventory") ]
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def withdrawCredit(self, credit, userid):
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.withdraw_credit(credit = credit, user = user)
|
||||
balance = user.negbalance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def addBarcode(self, dbid, barcode):
|
||||
return brmbar.Account.load(db, id = dbid).add_barcode(barcode)
|
||||
@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', 'QVariant', result='QVariant')
|
||||
def saveItem(self, dbid, invmap):
|
||||
acct = brmbar.Account.load(db, id = dbid)
|
||||
if (acct.name != invmap["name"]):
|
||||
acct.rename(invmap["name"])
|
||||
buy, sell = acct.currency.rates(currency)
|
||||
if (sell != invmap["price"]):
|
||||
acct.currency.update_sell_rate(currency, invmap["price"])
|
||||
if (buy != invmap["buy_price"]):
|
||||
acct.currency.update_buy_rate(currency, invmap["buy_price"])
|
||||
cost = ""
|
||||
if (acct.balance() < int(invmap["balance"])):
|
||||
cost = shop.buy_for_cash(acct, invmap["balance"] - acct.balance())
|
||||
else:
|
||||
db.commit()
|
||||
return { "dbid": dbid, "cost": (currency.str(cost) if cost != "" else "") }
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def balance_user(self, userid):
|
||||
user = brmbar.Account.load(db, id=userid)
|
||||
return user.negbalance_str()
|
||||
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def newItem(self, invmap):
|
||||
if (invmap["name"] == "" or invmap["price"] == "" or invmap["buy_price"] == ""):
|
||||
return None
|
||||
invcurrency = brmbar.Currency.create(db, invmap["name"])
|
||||
invcurrency.update_sell_rate(currency, invmap["price"])
|
||||
invcurrency.update_buy_rate(currency, invmap["buy_price"])
|
||||
acct = brmbar.Account.create(db, invmap["name"], invcurrency, "inventory")
|
||||
cost = ""
|
||||
if (int(invmap["balance"]) > 0):
|
||||
cost = shop.buy_for_cash(acct, invmap["balance"]) # implicit db.commit()
|
||||
else:
|
||||
db.commit()
|
||||
return { "dbid": acct.id, "cost": (currency.str(cost) if cost != "" else "") }
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_cash(self):
|
||||
balance = shop.cash.balance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_profit(self):
|
||||
balance = shop.profits.balance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_inventory(self):
|
||||
balance = shop.inventory_balance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def balance_credit(self):
|
||||
balance = shop.credit_negbalance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', 'QVariant', result='QVariant')
|
||||
def newReceipt(self, userid, description, amount):
|
||||
if (description == "" or amount == ""):
|
||||
return None
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.receipt_to_credit(user, amount, description)
|
||||
return user.negbalance_str()
|
||||
@QtCore.Slot(result='QVariant')
|
||||
def userList(self):
|
||||
alist = [ self.acct_debt_map(a) for a in shop.account_list("debt") ]
|
||||
db.commit()
|
||||
return alist
|
||||
|
||||
db = psycopg2.connect("dbname=brmbar")
|
||||
@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+"%%") ]
|
||||
db.commit()
|
||||
return alist
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def addBarcode(self, dbid, barcode):
|
||||
acct = brmbar.Account.load(db, id = dbid).add_barcode(barcode)
|
||||
db.commit()
|
||||
return acct
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', result='QVariant')
|
||||
def saveItem(self, dbid, invmap):
|
||||
acct = brmbar.Account.load(db, id = dbid)
|
||||
if (acct.name != invmap["name"]):
|
||||
acct.rename(invmap["name"])
|
||||
buy, sell = acct.currency.rates(currency)
|
||||
if (sell != invmap["price"]):
|
||||
acct.currency.update_sell_rate(currency, invmap["price"])
|
||||
if (buy != invmap["buy_price"]):
|
||||
acct.currency.update_buy_rate(currency, invmap["buy_price"])
|
||||
cost = ""
|
||||
if (acct.balance() < int(invmap["balance"])):
|
||||
cost = shop.buy_for_cash(acct, invmap["balance"] - acct.balance())
|
||||
else:
|
||||
db.commit()
|
||||
return { "dbid": dbid, "cost": (currency.str(cost) if cost != "" else "") }
|
||||
|
||||
@QtCore.Slot('QVariant', result='QVariant')
|
||||
def newItem(self, invmap):
|
||||
if (invmap["name"] == "" or invmap["price"] == "" or invmap["buy_price"] == ""):
|
||||
return None
|
||||
invcurrency = brmbar.Currency.create(db, invmap["name"])
|
||||
invcurrency.update_sell_rate(currency, invmap["price"])
|
||||
invcurrency.update_buy_rate(currency, invmap["buy_price"])
|
||||
acct = brmbar.Account.create(db, invmap["name"], invcurrency, "inventory")
|
||||
cost = ""
|
||||
if (int(invmap["balance"]) > 0):
|
||||
cost = shop.buy_for_cash(acct, invmap["balance"]) # implicit db.commit()
|
||||
else:
|
||||
db.commit()
|
||||
return { "dbid": acct.id, "cost": (currency.str(cost) if cost != "" else "") }
|
||||
|
||||
@QtCore.Slot('QVariant', 'QVariant', 'QVariant', result='QVariant')
|
||||
def newReceipt(self, userid, description, amount):
|
||||
if (description == "" or amount == ""):
|
||||
return None
|
||||
user = brmbar.Account.load(db, id = userid)
|
||||
shop.receipt_to_credit(user, amount, description)
|
||||
balance = user.negbalance_str()
|
||||
db.commit()
|
||||
return balance
|
||||
|
||||
db = Database.Database("dbname=brmbar")
|
||||
shop = brmbar.Shop.new_with_defaults(db)
|
||||
currency = shop.currency
|
||||
db.commit()
|
||||
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Rectangle {
|
||||
|
@ -10,7 +9,7 @@ Rectangle {
|
|||
|
||||
property string text: "Button"
|
||||
property int fontSize: 0.768 * 60
|
||||
property variant btnColor: "#ffffff"
|
||||
property string btnColor: "#aaaaaa"
|
||||
|
||||
signal buttonClick
|
||||
onButtonClick: { /* Supplied by component user. */ }
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Rectangle {
|
||||
|
@ -6,8 +5,8 @@ Rectangle {
|
|||
width: 320
|
||||
height: 65
|
||||
property variant now: new Date()
|
||||
property variant textColor: "#000000"
|
||||
property variant textSize: 0.768 * 16
|
||||
property string textColor: "#000000"
|
||||
property real 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: 80
|
||||
buttonHeight: 70
|
||||
buttonWidth: 90
|
||||
buttonHeight: 80
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ Grid {
|
|||
property string enteredText: ""
|
||||
property int gridRows: 0
|
||||
property int gridColumns: 0
|
||||
property int buttonWidth: 70
|
||||
property int buttonHeight: 70
|
||||
property int buttonWidth: 80
|
||||
property int buttonHeight: 80
|
||||
|
||||
property bool shift: false
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ Rectangle {
|
|||
|
||||
property int scrollbarWidth: 20
|
||||
|
||||
property variant color: "white"
|
||||
property variant baseOpacityOff: 0.4
|
||||
property variant baseOpacityOn: 0.9
|
||||
property string color: "white"
|
||||
property real baseOpacityOff: 0.4
|
||||
property real baseOpacityOn: 0.9
|
||||
|
||||
radius: vertical ? width/2 : height/2
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Item {
|
||||
id: main_hint
|
||||
width: 894
|
||||
width: 1150
|
||||
height: 80
|
||||
|
||||
property variant hint_goal: ""
|
||||
property variant hint_action: ""
|
||||
property string hint_goal: ""
|
||||
property string hint_action: ""
|
||||
|
||||
Text {
|
||||
id: text1
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
TextInput {
|
||||
|
|
|
@ -1,33 +1,31 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Rectangle {
|
||||
width: 1024
|
||||
height: 768
|
||||
width: 1280
|
||||
height: 1024
|
||||
color: "#000000"
|
||||
Text {
|
||||
id: title
|
||||
x: 65
|
||||
y: 35
|
||||
height: 65
|
||||
color: "#71cccc"
|
||||
color: "#217777"
|
||||
text: "brmbar v3"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 0.768 * 49
|
||||
}
|
||||
BarClock {
|
||||
id: clock
|
||||
x: 328
|
||||
x: 456
|
||||
y: 35
|
||||
color: "#000000"
|
||||
textColor: "#71cccc"
|
||||
textColor: "#217777"
|
||||
textSize: 0.768 * 49
|
||||
}
|
||||
|
||||
Image {
|
||||
id: image1
|
||||
x: 688
|
||||
x: 944
|
||||
y: 41
|
||||
height: 65
|
||||
smooth: true
|
||||
|
@ -38,7 +36,7 @@ Rectangle {
|
|||
property alias status_text: status_text_id
|
||||
Text {
|
||||
id: status_text_id
|
||||
x: 65
|
||||
x: 193
|
||||
y: 112
|
||||
width: 894
|
||||
color: "#ff4444"
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
// 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 username: ""
|
||||
property variant userdbid: ""
|
||||
property variant amount: credit_pad.enteredText
|
||||
property string username: ""
|
||||
property string userdbid: ""
|
||||
property string amount: credit_pad.enteredText
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -17,7 +15,7 @@ Item {
|
|||
width: 537
|
||||
height: 80
|
||||
color: "#ffffff"
|
||||
text: parent.username ? parent.username : "Credit charge"
|
||||
text: parent.username ? parent.username : ""
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
@ -41,7 +39,7 @@ Item {
|
|||
x: 65
|
||||
y: 430
|
||||
hint_goal: (parent.username ? "" : parent.amount ? "Charge user:" : "Charge credit:")
|
||||
hint_action: (parent.username ? (parent.amount ? "" : "(or scan barcode now)") : "Scan barcode now")
|
||||
hint_action: (parent.username ? "" : "Scan barcode now")
|
||||
}
|
||||
|
||||
BarNumPad {
|
||||
|
@ -64,7 +62,7 @@ Item {
|
|||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype == "debt") {
|
||||
if (acct.acctype === "debt") {
|
||||
username = acct.name
|
||||
userdbid = acct.id
|
||||
} else {
|
||||
|
@ -79,7 +77,7 @@ Item {
|
|||
BarButton {
|
||||
id: charge_button
|
||||
x: 65
|
||||
y: 582
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Charge"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -91,8 +89,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
@ -102,8 +100,17 @@ Item {
|
|||
}
|
||||
|
||||
function chargeCredit() {
|
||||
var balance = shop.chargeCredit(amount, userdbid)
|
||||
status_text.setStatus("Charged! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||
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")
|
||||
}
|
||||
}
|
||||
loadPage("MainPage")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +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 item_name: item_name_pad.enteredText
|
||||
property variant dbid: ""
|
||||
property string item_name: item_name_pad.enteredText
|
||||
property string dbid: ""
|
||||
property variant info: ""
|
||||
property variant buy_price: item_buyprice_pad.enteredText
|
||||
property variant price: item_sellprice_pad.enteredText
|
||||
property string buy_price: item_buyprice_pad.enteredText
|
||||
property string price: item_sellprice_pad.enteredText
|
||||
|
||||
property string barcode: ""
|
||||
state: "normal"
|
||||
|
@ -28,7 +26,7 @@ Item {
|
|||
/* TODO: Allow override. */
|
||||
return
|
||||
}
|
||||
if (info.dbid == "") {
|
||||
if (info.dbid === "") {
|
||||
status_text.setStatus("Press [Create] first", "#ff4444")
|
||||
return
|
||||
}
|
||||
|
@ -60,7 +58,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_name_edit
|
||||
x: 534
|
||||
x: 790
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -115,7 +113,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_buyprice_edit
|
||||
x: 534
|
||||
x: 790
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -170,7 +168,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_sellprice_edit
|
||||
x: 534
|
||||
x: 790
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -225,7 +223,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: item_balance_restock
|
||||
x: 534
|
||||
x: 790
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -306,46 +304,46 @@ Item {
|
|||
BarButton {
|
||||
id: save
|
||||
x: 65
|
||||
y: 582
|
||||
y: 838
|
||||
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
|
||||
var xi = info; xi["dbid"] = page.dbid; info = xi
|
||||
} else {
|
||||
loadPage("StockMgmt")
|
||||
}
|
||||
if (dbid == "") {
|
||||
dbid = res.dbid
|
||||
xi = info; xi["dbid"] = page.dbid; info = xi
|
||||
} else {
|
||||
loadPage("StockMgmt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
// 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 name: ""
|
||||
property variant dbid: ""
|
||||
property variant price: ""
|
||||
property string name: ""
|
||||
property string dbid: ""
|
||||
property string price: ""
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -25,7 +23,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: text3
|
||||
x: 611
|
||||
x: 867
|
||||
y: 156
|
||||
height: 160
|
||||
width: 348
|
||||
|
@ -52,12 +50,20 @@ Item {
|
|||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype != "debt") {
|
||||
if (acct.acctype !== "debt" && acct.acctype !== "cash") {
|
||||
loadPageByAcct(acct)
|
||||
return
|
||||
}
|
||||
var balance = shop.sellItem(dbid, acct.id)
|
||||
status_text.setStatus("Sold! "+acct.name+"'s credit is "+balance+".", "#ffff7c")
|
||||
|
||||
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")
|
||||
}
|
||||
loadPage("MainPage")
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +71,7 @@ Item {
|
|||
BarButton {
|
||||
id: pay_cash
|
||||
x: 65
|
||||
y: 582
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Pay by cash"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -78,8 +84,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -26,44 +24,40 @@ Item {
|
|||
}
|
||||
|
||||
BarButton {
|
||||
id: withdraw
|
||||
x: 65
|
||||
y: 430
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Select Item"
|
||||
fontSize: 0.768 * 60
|
||||
btnColor: "#666666"
|
||||
}
|
||||
|
||||
BarButton {
|
||||
x: 599
|
||||
y: 430
|
||||
width: 360
|
||||
text: "Credit"
|
||||
text: "Charge"
|
||||
onButtonClick: {
|
||||
loadPage("ChargeCredit")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: select_item
|
||||
x: 65
|
||||
y: 582
|
||||
x: 450
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Receipt"
|
||||
text: "Transfer"
|
||||
onButtonClick: {
|
||||
loadPage("Receipt")
|
||||
loadPage("Transfer")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: management
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Management"
|
||||
onButtonClick: {
|
||||
loadPage("Management")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
x: 65
|
||||
y: 438
|
||||
width: 1150
|
||||
text: "* Za uklid brmlabu vam nabijeme kredit. *"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -65,7 +63,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: credit_name
|
||||
x: 535
|
||||
x: 791
|
||||
y: 156
|
||||
width: 337
|
||||
height: 160
|
||||
|
@ -78,7 +76,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: credit_amount
|
||||
x: 705
|
||||
x: 961
|
||||
y: 156
|
||||
height: 160
|
||||
width: 254
|
||||
|
@ -91,7 +89,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: inv_name
|
||||
x: 535
|
||||
x: 791
|
||||
y: 266
|
||||
width: 337
|
||||
height: 160
|
||||
|
@ -104,7 +102,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: inv_amount
|
||||
x: 705
|
||||
x: 961
|
||||
y: 266
|
||||
height: 160
|
||||
width: 254
|
||||
|
@ -118,7 +116,7 @@ Item {
|
|||
BarButton {
|
||||
id: stock_manager
|
||||
x: 65
|
||||
y: 430
|
||||
y: 686
|
||||
width: 360
|
||||
text: "Stock Mgmt"
|
||||
onButtonClick: {
|
||||
|
@ -128,8 +126,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: user_manager
|
||||
x: 599
|
||||
y: 430
|
||||
x: 855
|
||||
y: 686
|
||||
width: 360
|
||||
text: "User Mgmt"
|
||||
onButtonClick: {
|
||||
|
@ -137,10 +135,21 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: select_item
|
||||
x: 65
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Receipt"
|
||||
onButtonClick: {
|
||||
loadPage("Receipt")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
// 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 variant description: item_name_pad.enteredText
|
||||
property variant amount: amount_pad.enteredText
|
||||
property string description: item_name_pad.enteredText
|
||||
property string amount: amount_pad.enteredText
|
||||
|
||||
state: "normal"
|
||||
|
||||
|
@ -49,7 +47,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: description_edit
|
||||
x: 591
|
||||
x: 847
|
||||
y: 0
|
||||
width: 300
|
||||
height: 60
|
||||
|
@ -104,7 +102,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: amount_edit
|
||||
x: 650
|
||||
x: 906
|
||||
y: 0
|
||||
width: 240
|
||||
height: 60
|
||||
|
@ -150,7 +148,7 @@ Item {
|
|||
BarButton {
|
||||
id: save
|
||||
x: 65
|
||||
y: 582
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Create"
|
||||
onButtonClick: {
|
||||
|
@ -171,8 +169,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -8,6 +6,8 @@ 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: 899
|
||||
height: 400
|
||||
width: 1155
|
||||
height: 656
|
||||
|
||||
ListView {
|
||||
id: item_list
|
||||
|
@ -49,7 +49,7 @@ Item {
|
|||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 300
|
||||
x: 556
|
||||
width: 254
|
||||
color: "#ffff7c"
|
||||
text: modelData.price
|
||||
|
@ -59,7 +59,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 600
|
||||
x: 856
|
||||
width: 240
|
||||
height: 68
|
||||
text: "Edit"
|
||||
|
@ -81,12 +81,14 @@ Item {
|
|||
}
|
||||
|
||||
BarButton {
|
||||
id: add_item
|
||||
id: new_item
|
||||
x: 65
|
||||
y: 582
|
||||
width: 360
|
||||
text: "Add Item"
|
||||
y: 838
|
||||
width: 281
|
||||
height: 83
|
||||
text: "New Item"
|
||||
fontSize: 0.768 * 60
|
||||
visible: page.state == "normal"
|
||||
onButtonClick: {
|
||||
loadPage("ItemEdit", { dbid: "" })
|
||||
}
|
||||
|
@ -94,16 +96,96 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
loadPage("MainPage")
|
||||
if (page.state == "search")
|
||||
page.state = "normal"
|
||||
else
|
||||
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("")
|
||||
}
|
||||
}
|
||||
|
|
174
brmbar3/brmbar-gui-qt4/Transfer.qml
Normal file
174
brmbar3/brmbar-gui-qt4/Transfer.qml
Normal file
|
@ -0,0 +1,174 @@
|
|||
import QtQuick 1.1
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property variant userfrom: ""
|
||||
property variant uidfrom: ""
|
||||
property variant userto: ""
|
||||
property variant uidto: ""
|
||||
property string amount: amount_pad.enteredText
|
||||
|
||||
BarcodeInput {
|
||||
color: "#00ff00" /* just for debugging */
|
||||
focus: !(parent.userfrom != "" && parent.userto != "")
|
||||
onAccepted: {
|
||||
var acct = shop.barcodeInput(text)
|
||||
text = ""
|
||||
if (typeof(acct) == "undefined") {
|
||||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype == "debt") {
|
||||
if (userfrom == "") {
|
||||
userfrom = acct.name
|
||||
uidfrom = acct.id
|
||||
} else {
|
||||
userto = acct.name
|
||||
uidto = acct.id
|
||||
}
|
||||
} else if (acct.acctype == "recharge") {
|
||||
amount = acct.amount
|
||||
} else {
|
||||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: amount_row
|
||||
visible: parent.userfrom != "" && parent.userto != ""
|
||||
x: 65;
|
||||
y: 166;
|
||||
width: 890
|
||||
height: 60
|
||||
|
||||
Text {
|
||||
id: item_sellprice_label
|
||||
x: 0
|
||||
y: 0
|
||||
height: 60
|
||||
width: 200
|
||||
color: "#ffffff"
|
||||
text: "Money Amount:"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 0.768 * 46
|
||||
}
|
||||
|
||||
Text {
|
||||
id: amount_input
|
||||
x: 320
|
||||
y: 0
|
||||
height: 60
|
||||
width: 269
|
||||
color: "#ffff7c"
|
||||
text: amount
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 0.768 * 122
|
||||
}
|
||||
}
|
||||
|
||||
BarNumPad {
|
||||
id: amount_pad
|
||||
x: 65
|
||||
y: 239
|
||||
visible: parent.userfrom != "" && parent.userto != ""
|
||||
focus: parent.userfrom != "" && parent.userto != ""
|
||||
Keys.onReturnPressed: { transfer.buttonClick() }
|
||||
Keys.onEscapePressed: { cancel.buttonClick() }
|
||||
}
|
||||
|
||||
BarTextHint {
|
||||
id: barcode_row
|
||||
x: 65
|
||||
y: parent.userfrom == "" ? 314 : 414
|
||||
hint_goal: (parent.userfrom == "" ? "Take money from:" : parent.userto == "" ? "Give money to:" : parent.amount == "" ? "Specify amount" : "")
|
||||
hint_action: (parent.userfrom == "" || parent.userto == "" ? "Scan barcode now" : (parent.amount ? "" : "(or scan barcode now)"))
|
||||
}
|
||||
|
||||
Text {
|
||||
id: legend
|
||||
visible: !(parent.userfrom != "" && parent.userto != "")
|
||||
x: 65
|
||||
y: 611
|
||||
height: 154
|
||||
width: 894
|
||||
color: "#71cccc"
|
||||
text: "This is for transfering credit between two brmbar users.\n May be used instead of *check next club-mate to me*."
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 0.768 * 27
|
||||
}
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
x: 422
|
||||
y: 156
|
||||
width: 537
|
||||
height: 80
|
||||
color: "#ffffff"
|
||||
text: parent.userfrom ? parent.userfrom + " →" : "Money Transfer"
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 0.768 * 60
|
||||
}
|
||||
|
||||
Text {
|
||||
id: item_name2
|
||||
x: 422
|
||||
y: 256
|
||||
width: 537
|
||||
height: 80
|
||||
color: "#ffffff"
|
||||
text: parent.userto ? "→ " + parent.userto : ""
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 0.768 * 60
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: transfer
|
||||
x: 65
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Transfer"
|
||||
onButtonClick: {
|
||||
if (userfrom == "") {
|
||||
status_text.setStatus("Select FROM account.", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (userto == "") {
|
||||
status_text.setStatus("Select TO account.", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (amount == "") {
|
||||
status_text.setStatus("Enter amount.", "#ff4444")
|
||||
return
|
||||
}
|
||||
var amount_str = shop.newTransfer(uidfrom, uidto, amount)
|
||||
if (typeof(amount_str) == "undefined") {
|
||||
status_text.setStatus("Transfer error.", "#ff4444")
|
||||
return
|
||||
}
|
||||
|
||||
status_text.setStatus("Transferred " + amount_str + " from " + userfrom + " to " + userto, "#ffff7c")
|
||||
loadPage("MainPage")
|
||||
}
|
||||
}
|
||||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
status_text.setStatus("Transfer cancelled", "#ff4444")
|
||||
loadPage("MainPage")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
|
||||
property variant name: ""
|
||||
property variant dbid: ""
|
||||
property variant negbalance: ""
|
||||
property string name: ""
|
||||
property string dbid: ""
|
||||
property string negbalance: ""
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -43,7 +42,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
|
||||
}
|
||||
|
@ -55,7 +54,7 @@ Item {
|
|||
BarButton {
|
||||
id: charge_credit
|
||||
x: 65
|
||||
y: 582
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Charge"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -66,8 +65,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
import QtQuick 1.0
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
@ -17,12 +15,12 @@ Item {
|
|||
status_text.setStatus("Unknown barcode", "#ff4444")
|
||||
return
|
||||
}
|
||||
if (acct.acctype != "debt") {
|
||||
if (acct.acctype !== "debt") {
|
||||
loadPageByAcct(acct)
|
||||
return
|
||||
}
|
||||
/* TODO: This should be UserEdit when implemented. */
|
||||
loadPage("Withdraw", { name: acct["name"], dbid: acct["id"], negbalance: acct["negbalance"] })
|
||||
loadPage("Withdraw", { username: acct["name"], userdbid: acct["id"] })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,8 +28,8 @@ Item {
|
|||
id: user_list_container
|
||||
x: 65
|
||||
y: 166
|
||||
width: 899
|
||||
height: 400
|
||||
width: 1155
|
||||
height: 656
|
||||
|
||||
ListView {
|
||||
id: user_list
|
||||
|
@ -50,7 +48,7 @@ Item {
|
|||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 300
|
||||
x: 556
|
||||
width: 254
|
||||
color: "#ffff7c"
|
||||
text: modelData.negbalance_str
|
||||
|
@ -60,7 +58,7 @@ Item {
|
|||
|
||||
BarButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: 600
|
||||
x: 856
|
||||
width: 240
|
||||
height: 68
|
||||
text: "Withdraw"
|
||||
|
@ -84,7 +82,7 @@ Item {
|
|||
BarButton {
|
||||
id: add_user
|
||||
x: 65
|
||||
y: 582
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Add User"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -93,8 +91,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Main Screen"
|
||||
onButtonClick: {
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
// 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 username: ""
|
||||
property variant userdbid: ""
|
||||
property variant amount: withdraw_pad.enteredText
|
||||
property string username: ""
|
||||
property string userdbid: ""
|
||||
property string amount: withdraw_pad.enteredText
|
||||
|
||||
Text {
|
||||
id: item_name
|
||||
|
@ -39,7 +37,7 @@ Item {
|
|||
|
||||
BarTextHint {
|
||||
x: 65
|
||||
y: 430
|
||||
y: 686
|
||||
hint_goal: (parent.username ? "" : parent.amount ? "Withdraw:" : "Withdraw amount?")
|
||||
hint_action: (parent.username ? (parent.amount ? "" : "(or scan barcode now)") : "Scan barcode now")
|
||||
}
|
||||
|
@ -79,7 +77,7 @@ Item {
|
|||
BarButton {
|
||||
id: withdraw_button
|
||||
x: 65
|
||||
y: 582
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Withdraw"
|
||||
fontSize: 0.768 * 60
|
||||
|
@ -91,8 +89,8 @@ Item {
|
|||
|
||||
BarButton {
|
||||
id: cancel
|
||||
x: 599
|
||||
y: 582
|
||||
x: 855
|
||||
y: 838
|
||||
width: 360
|
||||
text: "Cancel"
|
||||
onButtonClick: {
|
||||
|
@ -102,8 +100,17 @@ Item {
|
|||
}
|
||||
|
||||
function withdrawCredit() {
|
||||
var balance = shop.withdrawCredit(amount, userdbid)
|
||||
status_text.setStatus("Withdrawn! "+username+"'s credit is "+balance+".", "#ffff7c")
|
||||
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")
|
||||
}
|
||||
}
|
||||
loadPage("MainPage")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by Qt Creator 2.5.0, 2012-09-05T02:10:11. -->
|
||||
<!-- Written by Qt Creator 2.5.0, 2013-01-31T18:05:42. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||
|
@ -112,7 +112,7 @@
|
|||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.EnvironmentId</variable>
|
||||
<value type="QString">{a277f310-b549-4ad7-87ca-cd03f76f19ff}</value>
|
||||
<value type="QString">{524378aa-09e0-4345-892b-1bd47313bcaf}</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
|
||||
|
|
|
@ -83,106 +83,106 @@
|
|||
|
||||
</defs>
|
||||
<path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 138.5235,47.9225 c -0.80875,2.085 -1.82875,3.32875 -2.725,3.32875 -7.195,0 -24.94,0.0225 -31.97375,0.03125 l 0,-27.4475 31.97375,0.0012 c 1.815,0 4.34625,5.215 4.34625,13.7075 0,3.94625 -0.5925,7.73 -1.62125,10.37875 m 5.475,-22.88625 c -2.39375,-6.155 -6.16375,-7.075 -8.2,-7.075 l -31.97375,-0.0025 0,-12.80625 -5.875,0 0,52.0125 2.9425,-0.0037 c 0,0 25.835,-0.03625 34.90625,-0.03625 2.03625,0 5.80625,-0.9175 8.2,-7.0725 1.30375,-3.35125 2.02,-7.7925 2.02,-12.50875 0,-4.715 -0.71625,-9.1575 -2.02,-12.5075"
|
||||
id="path10" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 389.4235,47.9225 c -0.80875,2.085 -1.82875,3.32875 -2.7225,3.32875 -7.1975,0 -24.94125,0.0225 -31.97625,0.03125 l 0,-27.4475 31.97625,0.0012 c 1.81375,0 4.345,5.215 4.345,13.7075 0,3.94625 -0.5925,7.73 -1.6225,10.37875 m 5.47625,-22.88625 c -2.39375,-6.155 -6.165,-7.075 -8.19875,-7.075 l -31.97625,-0.0025 0,-12.80625 -5.87375,0 0,52.0125 2.94125,-0.0037 c 0,0 25.83625,-0.03625 34.90875,-0.03625 2.03375,0 5.805,-0.9175 8.19875,-7.0725 1.30375,-3.35125 2.02,-7.7925 2.02,-12.50875 0,-4.715 -0.71625,-9.1575 -2.02,-12.5075"
|
||||
id="path12" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 301.7235,23.835 c -0.89625,0 -1.91375,1.24375 -2.725,3.32875 -1.03,2.64875 -1.62125,6.43125 -1.62125,10.37875 0,8.4925 2.53125,13.7075 4.34625,13.7075 7.19625,0 24.94,0.0225 31.975,0.03125 l 0,-27.44625 -31.975,0 z m 37.84875,33.32875 -2.94,-0.0038 c 0,0 -25.83625,-0.035 -34.90875,-0.035 -2.035,0 -5.80625,-0.91875 -8.2,-7.07375 -1.3025,-3.35125 -2.02,-7.7925 -2.02,-12.50875 0,-4.715 0.7175,-9.1575 2.02,-12.5075 2.39375,-6.155 6.165,-7.075 8.2,-7.075 l 34.9125,0 2.91979,0.0058 0.0165,39.198 z"
|
||||
id="path14"
|
||||
sodipodi:nodetypes="cssscccccssssccc" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 277.2735,57.16 5.874,0 0,-52.0075 -5.874,0 0,52.0075 z"
|
||||
id="path16" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 191.061,23.06 c -1.0075,-1.47125 -2.7675,-2.98 -5.79125,-4.15625 -3.35,-1.3025 -7.79375,-2.02 -12.50875,-2.02 -4.715,0 -9.15875,0.7175 -12.5075,2.02 -6.155,2.39375 -7.07375,6.16375 -7.07375,8.19875 0,9.87875 -0.0175,30.04875 -0.0175,30.04875 l 5.87375,0.005 c 0,0 0.0175,-20.17375 0.0175,-30.05375 0,-1.81375 5.21375,-4.345 13.7075,-4.345 3.9475,0 7.73,0.59 10.37875,1.62125 1.58875,0.6175 2.6875,1.355 3.1225,2.0675 L 191.061,23.06 z"
|
||||
id="path18" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 234.6235,27.11 c 0,-1.815 5.24,-4.345 13.73375,-4.345 3.94875,0 7.73,0.59 10.37875,1.62 2.085,0.81125 3.32875,1.83 3.32875,2.725 0,9.0725 0.0188,30.05375 0.0188,30.05375 L 267.956,57.16 c 0,-0.0012 -0.0175,-20.97875 -0.0175,-30.05 0,-2.035 -0.9175,-5.805 -7.0725,-8.2 -3.35125,-1.3025 -7.7925,-2.01875 -12.50875,-2.01875 -4.71625,0 -9.15875,0.71625 -12.50875,2.01875 -1.7975,0.7 -3.16375,1.50625 -4.175,2.3625 -1.01,-0.8475 -2.3675,-1.66875 -4.15,-2.3625 -3.34875,-1.3025 -7.79125,-2.01875 -12.50875,-2.01875 -4.715,0 -9.15625,0.71625 -12.50625,2.01875 -6.15625,2.395 -7.075,6.165 -7.075,8.2 0,9.8775 -0.0163,30.04875 -0.0163,30.04875 l 5.87375,0.005 c 0,0 0.0175,-20.17375 0.0175,-30.05375 0,-1.815 5.215,-4.345 13.70625,-4.345 3.94875,0 7.73125,0.59 10.38,1.62 2.08375,0.81125 3.32875,1.83 3.32875,2.725 0,9.0725 0.0175,30.0575 0.0175,30.0575 l 5.87375,-0.005 c 0,0 0.009,-20.98125 0.009,-30.0525"
|
||||
id="path20" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 44.7185,8.4475 c -19.99875,0 -36.2675,16.27125 -36.2675,36.27 0,9.6875 3.7725,18.795 10.6225,25.645 6.85125,6.85 15.9575,10.6225 25.645,10.6225 19.99875,0 36.27,-16.27 36.27,-36.2675 -0.0012,-20 -16.27125,-36.27 -36.27,-36.27 m 0,77.91 c -11.12125,0 -21.58,-4.33125 -29.44375,-12.19625 C 7.40975,66.29625 3.0785,55.84 3.0785,44.7175 c 0,-22.96125 18.68,-41.64125 41.64,-41.6425 22.96125,0 41.64125,18.68 41.64125,41.6425 0,22.96 -18.68,41.64 -41.64125,41.64"
|
||||
id="path22" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 29.611,69.135 c 4.505,2.77875 9.67125,4.24125 15.015,4.24125 5.34375,0 10.51125,-1.4625 15.01625,-4.24125 -1.4825,-2.65375 -1.06625,-6.01 1.16125,-8.2375 1.305,-1.305 3.04,-2.02375 4.885,-2.02375 1.1775,0 2.33125,0.3025 3.34875,0.865 6.91875,-11.21625 5.2525,-25.8825 -4.14625,-35.2825 -3.775,-3.775 -8.46375,-6.3925 -13.61875,-7.615 -0.82875,2.92375 -3.49625,5.0025 -6.645,5.0025 -3.14875,0 -5.81625,-2.07875 -6.645,-5.0025 -5.155,1.2225 -9.845,3.84125 -13.61875,7.61625 -9.40125,9.4 -11.0675,24.065 -4.15,35.28125 1.01875,-0.5625 2.17375,-0.865 3.35,-0.865 1.84625,0 3.58,0.71875 4.88625,2.02375 2.22875,2.2275 2.645,5.58375 1.16125,8.2375 m 15.01375,6.50125 c -6.17625,0 -12.135,-1.8125 -17.2325,-5.24125 l -1.0375,-0.69625 0.795,-0.96125 C 28.681,66.88125 28.55225,64.2 26.8485,62.49625 25.971,61.61875 24.8035,61.135 23.56225,61.135 c -1.0775,0 -2.12625,0.37625 -2.95375,1.05875 L 19.646,62.9875 18.94975,61.9525 C 10.7285,49.73 12.331,33.2875 22.76225,22.8575 27.12475,18.4925 32.621,15.56125 38.656,14.37875 l 1.225,-0.23875 0.12,1.24125 c 0.22875,2.39375 2.2175,4.19875 4.625,4.19875 2.4075,0 4.39625,-1.805 4.62625,-4.19875 l 0.12,-1.24125 1.22375,0.23875 c 6.0325,1.1825 11.53,4.11375 15.89375,8.47625 10.43,10.43125 12.03375,26.875 3.81125,39.0975 l -0.69625,1.035 -0.96125,-0.795 C 67.8135,61.51 66.766,61.135 65.68725,61.135 c -1.24125,0 -2.4075,0.4825 -3.28625,1.36 -1.7025,1.70375 -1.83125,4.38625 -0.30125,6.2425 l 0.79375,0.96125 -1.035,0.69625 C 56.761,73.82375 50.801,75.63625 44.62475,75.63625"
|
||||
id="path24" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 29.361,29.9225 c -1.17375,0 -2.12875,0.955 -2.12875,2.13 0,1.17375 0.955,2.12875 2.12875,2.12875 1.1725,0 2.12875,-0.955 2.12875,-2.12875 0,-1.175 -0.95625,-2.13 -2.12875,-2.13 m 0,6.52 c -2.4225,0 -4.3925,-1.97 -4.3925,-4.39125 0,-2.42125 1.97,-4.3925 4.3925,-4.3925 2.42125,0 4.39125,1.97125 4.39125,4.3925 0,2.42125 -1.97,4.39125 -4.39125,4.39125"
|
||||
id="path26" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 25.501,45.1975 c -1.17375,0 -2.12875,0.955 -2.12875,2.13 0,1.175 0.955,2.12875 2.12875,2.12875 1.17375,0 2.13,-0.95375 2.13,-2.12875 0,-1.175 -0.95625,-2.13 -2.13,-2.13 m 0,6.52125 c -2.4225,0 -4.39125,-1.97 -4.39125,-4.39125 0,-2.42125 1.96875,-4.3925 4.39125,-4.3925 2.4225,0 4.3925,1.97125 4.3925,4.3925 0,2.42125 -1.97,4.39125 -4.3925,4.39125"
|
||||
id="path28" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 63.7485,45.1975 c -1.1725,0 -2.13,0.955 -2.13,2.13 0,1.175 0.9575,2.12875 2.13,2.12875 1.17375,0 2.13,-0.95375 2.13,-2.12875 0,-1.175 -0.95625,-2.13 -2.13,-2.13 m 0,6.52125 c -2.42125,0 -4.3925,-1.97 -4.3925,-4.39125 0,-2.42125 1.97125,-4.3925 4.3925,-4.3925 2.4225,0 4.3925,1.97125 4.3925,4.3925 0,2.42125 -1.97,4.39125 -4.3925,4.39125"
|
||||
id="path30" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 36.84225,60.385 c -1.17375,0 -2.1275,0.955 -2.1275,2.13 0,1.17375 0.95375,2.12875 2.1275,2.12875 1.17375,0 2.13,-0.955 2.13,-2.12875 0,-1.175 -0.95625,-2.13 -2.13,-2.13 m 0,6.52125 c -2.42125,0 -4.39,-1.97 -4.39,-4.39125 0,-2.42125 1.96875,-4.3925 4.39,-4.3925 2.4225,0 4.3925,1.97125 4.3925,4.3925 0,2.42125 -1.97,4.39125 -4.3925,4.39125"
|
||||
id="path32" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 59.886,29.9225 c -1.17375,0 -2.13,0.955 -2.13,2.13 0,1.17375 0.95625,2.12875 2.13,2.12875 1.17375,0 2.1275,-0.955 2.1275,-2.12875 0,-1.175 -0.95375,-2.13 -2.1275,-2.13 m 0,6.52 c -2.4225,0 -4.3925,-1.97 -4.3925,-4.39125 0,-2.42125 1.97,-4.3925 4.3925,-4.3925 2.42125,0 4.39,1.97125 4.39,4.3925 0,2.42125 -1.96875,4.39125 -4.39,4.39125"
|
||||
id="path34" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 52.411,60.385 c -1.17375,0 -2.13,0.95625 -2.13,2.13 0,1.17375 0.95625,2.12875 2.13,2.12875 1.1725,0 2.12875,-0.955 2.12875,-2.12875 0,-1.17375 -0.95625,-2.13 -2.12875,-2.13 m 0,6.52125 c -2.4225,0 -4.3925,-1.97 -4.3925,-4.39125 0,-2.42125 1.97,-4.3925 4.3925,-4.3925 2.42125,0 4.39125,1.97125 4.39125,4.3925 0,2.42125 -1.97,4.39125 -4.39125,4.39125"
|
||||
id="path36" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 44.636,29.9225 c -1.17375,0 -2.13,0.955 -2.13,2.13 0,1.17375 0.95625,2.12875 2.13,2.12875 1.1725,0 2.12875,-0.955 2.12875,-2.12875 0,-1.175 -0.95625,-2.13 -2.12875,-2.13 m 0,6.52 c -2.4225,0 -4.3925,-1.97 -4.3925,-4.39125 0,-2.42125 1.97,-4.3925 4.3925,-4.3925 2.42125,0 4.39125,1.97125 4.39125,4.3925 0,2.42125 -1.97,4.39125 -4.39125,4.39125"
|
||||
id="path38" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 38.596,47.2975 12.0775,0 0,-5.075 -12.0775,0 0,5.075 z m 14.34,2.2625 -16.60125,0 0,-9.59875 16.60125,0 0,9.59875 z"
|
||||
id="path40" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 98.0985,67.81 2.72625,0 0,6.13125 c 1.13875,-1.38875 2.5525,-2.0825 4.23875,-2.0825 0.92,0 1.74375,0.23 2.47375,0.69 0.7275,0.45875 1.27,1.0925 1.625,1.9025 0.355,0.8075 0.53125,2.0125 0.53125,3.61 l 0,7.85 -2.72625,0 0,-8.52375 c 0,-1.00875 -0.2475,-1.82125 -0.74125,-2.435 -0.495,-0.61375 -1.14625,-0.92125 -1.955,-0.92125 -0.59875,0 -1.16375,0.155 -1.69375,0.465 -0.53,0.30875 -1.11375,0.82375 -1.7525,1.5425 l 0,9.8725 -2.72625,0 0,-18.10125 z"
|
||||
id="path42" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 122.2485,83.185 0,-4.08875 L 120.421,79.8 c -0.92875,0.36875 -1.585,0.74125 -1.97,1.11625 -0.385,0.37375 -0.5775,0.84125 -0.5775,1.4 0,0.56875 0.1825,1.03375 0.54625,1.39375 0.365,0.35875 0.8375,0.53875 1.41625,0.53875 0.87,0 1.6725,-0.355 2.4125,-1.06375 m 2.6825,-5.55875 0,5.85875 c 0,0.47 0.15875,0.70375 0.47875,0.70375 0.33,0 0.84375,-0.245 1.54375,-0.73375 l 0,1.6625 c -0.61875,0.4 -1.11625,0.67125 -1.4925,0.8175 -0.37375,0.14375 -0.76625,0.21625 -1.175,0.21625 -1.16875,0 -1.8575,-0.45875 -2.0675,-1.3775 -1.15875,0.8975 -2.3925,1.3475 -3.7,1.3475 -0.95875,0 -1.75875,-0.31625 -2.39625,-0.95125 -0.64,-0.63375 -0.96,-1.43125 -0.96,-2.38875 0,-0.87 0.3125,-1.64625 0.9375,-2.33 0.62375,-0.68375 1.51,-1.22625 2.65875,-1.625 l 3.49,-1.2 0,-0.73375 c 0,-1.6575 -0.82875,-2.48625 -2.48625,-2.48625 -1.49,0 -2.93625,0.76875 -4.345,2.3075 l 0,-2.9825 c 1.05875,-1.24875 2.5825,-1.8725 4.56875,-1.8725 1.48875,0 2.6825,0.38875 3.58125,1.16875 0.3,0.25 0.57,0.5825 0.80875,0.99625 0.24125,0.415 0.3925,0.82875 0.45625,1.24375 0.065,0.415 0.0987,1.20125 0.0987,2.35875"
|
||||
id="path44" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 142.461,82.685 0,2.6975 c -1.36875,0.50875 -2.7075,0.76375 -4.015,0.76375 -2.1575,0 -3.87625,-0.64 -5.16125,-1.9175 -1.2825,-1.27875 -1.925,-2.9925 -1.925,-5.13875 0,-2.1675 0.62375,-3.915 1.8725,-5.24375 1.2475,-1.32875 2.8925,-1.9925 4.93,-1.9925 0.7075,0 1.34625,0.0675 1.91,0.20125 0.56375,0.135 1.26,0.3875 2.09,0.7575 l 0,2.90625 c -1.37875,-0.87875 -2.6575,-1.3175 -3.83625,-1.3175 -1.2275,0 -2.2375,0.43125 -3.02625,1.295 -0.78875,0.865 -1.18375,1.965 -1.18375,3.30375 0,1.4075 0.4275,2.52625 1.2825,3.355 0.8525,0.83 2.00375,1.24375 3.45125,1.24375 1.05,0 2.2525,-0.30375 3.61125,-0.91375"
|
||||
id="path46" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 157.2985,72.1225 3.2375,0 -5.88,6.7425 7.07875,7.04125 -3.65875,0 -6.9025,-7.035 6.125,-6.74875 z m -8.9725,-4.31875 2.7275,0 0,18.10125 -2.7275,0 0,-18.10125 z"
|
||||
id="path48" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 167.8985,77.6225 6.93625,0 c -0.07,-1.08875 -0.395,-1.9275 -0.97375,-2.51625 -0.57875,-0.58875 -1.3575,-0.885 -2.3375,-0.885 -0.97875,0 -1.78,0.29625 -2.40375,0.885 -0.62375,0.58875 -1.0325,1.4275 -1.22125,2.51625 m -0.0613,1.63375 c 0.07,1.3175 0.51125,2.36625 1.32625,3.14625 0.81375,0.77875 1.865,1.1675 3.1525,1.1675 1.79875,0 3.45625,-0.55875 4.975,-1.6775 l 0,2.66625 c -0.83875,0.56 -1.67125,0.96 -2.49375,1.19875 -0.82625,0.24 -1.79125,0.36 -2.9,0.36 -1.51875,0 -2.74625,-0.315 -3.685,-0.94375 -0.94,-0.62875 -1.69125,-1.47625 -2.25625,-2.53875 -0.56375,-1.065 -0.845,-2.295 -0.845,-3.69375 0,-2.0975 0.59375,-3.8025 1.7825,-5.11625 1.1875,-1.31375 2.73125,-1.97 4.62875,-1.97 1.8275,0 3.285,0.64 4.375,1.9175 1.08875,1.27875 1.62692,3.336068 1.62692,5.483568 l -9.68692,1.82e-4 z"
|
||||
id="path50"
|
||||
sodipodi:nodetypes="ccsssccssccssssssssc" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 186.161,72.1225 0,3.16125 0.15125,-0.24 c 1.31875,-2.1275 2.635,-3.19 3.95375,-3.19 1.02875,0 2.1025,0.51875 3.22125,1.5575 l -1.4375,2.3975 c -0.95,-0.9 -1.82875,-1.34875 -2.6375,-1.34875 -0.87875,0 -1.64,0.42 -2.285,1.2575 -0.6425,0.84 -0.96625,1.83375 -0.96625,2.9825 l 0,7.20625 -2.74,0 0,-13.78375 2.74,0 z"
|
||||
id="path52" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 197.4735,84.9225 0,-2.93625 c 0.7675,0.53875 1.55375,0.97625 2.35875,1.31 0.80375,0.335 1.48,0.5025 2.03,0.5025 0.57,0 1.05875,-0.14 1.46875,-0.42 0.40875,-0.27875 0.61375,-0.61375 0.61375,-1.00375 0,-0.39875 -0.1325,-0.73125 -0.3975,-0.995 -0.26375,-0.265 -0.83625,-0.6475 -1.715,-1.14625 -1.7575,-0.98 -2.90875,-1.81625 -3.4525,-2.51 -0.545,-0.69375 -0.8175,-1.45 -0.8175,-2.27 0,-1.0575 0.4125,-1.9225 1.2375,-2.59125 0.82375,-0.67 1.88375,-1.00375 3.1825,-1.00375 1.34875,0 2.73125,0.38 4.15125,1.1375 l 0,2.6975 C 204.51475,74.715 203.18975,74.225 202.16225,74.225 c -0.53,0 -0.95625,0.1125 -1.28125,0.3375 -0.325,0.22625 -0.48625,0.5225 -0.48625,0.8925 0,0.32125 0.14625,0.625 0.44,0.915 0.295,0.29 0.81125,0.64 1.55125,1.04875 l 0.97375,0.55375 c 2.2975,1.29875 3.44625,2.73625 3.44625,4.315 0,1.1275 -0.4425,2.05375 -1.32625,2.7775 -0.8825,0.72375 -2.01875,1.085 -3.4075,1.085 -0.82,0 -1.55,-0.08625 -2.1875,-0.26125 -0.63875,-0.175 -1.44375,-0.4975 -2.41125,-0.96625"
|
||||
id="path54" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 217.2485,74.6725 -1.92875,0 0,8.48 c 0.8375,0.42875 1.71375,0.64375 2.6325,0.64375 1.275,0 2.31875,-0.445 3.13,-1.33375 0.81375,-0.88875 1.21875,-2.0325 1.21875,-3.43 0,-0.89875 -0.19125,-1.6925 -0.575,-2.38125 -0.38375,-0.69 -0.9075,-1.1925 -1.57,-1.50625 -0.6625,-0.315 -1.63125,-0.4725 -2.9075,-0.4725 m -4.71375,16.56625 0,-19.11375 4.7725,0 c 2.44375,0 4.34625,0.61 5.7075,1.82875 1.3625,1.21875 2.04375,2.92125 2.04375,5.10875 0,2.0675 -0.6425,3.765 -1.9225,5.0925 -1.28125,1.32875 -2.915,1.99375 -4.89875,1.99375 -0.8775,0 -1.84875,-0.19625 -2.9175,-0.585 l 0,5.675 -2.785,0 z"
|
||||
id="path56" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 236.7985,83.185 0,-4.08875 -1.82625,0.70375 c -0.92875,0.36875 -1.585,0.74125 -1.97,1.11625 -0.385,0.37375 -0.5775,0.84125 -0.5775,1.4 0,0.56875 0.1825,1.03375 0.54625,1.39375 0.365,0.35875 0.83625,0.53875 1.41625,0.53875 0.87,0 1.67375,-0.355 2.41125,-1.06375 m 2.6825,-5.55875 0,5.85875 c 0,0.47 0.15875,0.70375 0.48,0.70375 0.33,0 0.84375,-0.245 1.54375,-0.73375 l 0,1.6625 c -0.61875,0.4 -1.1175,0.67125 -1.4925,0.8175 -0.375,0.14375 -0.76625,0.21625 -1.175,0.21625 -1.16875,0 -1.8575,-0.45875 -2.0675,-1.3775 -1.15875,0.8975 -2.3925,1.3475 -3.7,1.3475 -0.95875,0 -1.75875,-0.31625 -2.39625,-0.95125 -0.64,-0.63375 -0.96,-1.43125 -0.96,-2.38875 0,-0.87 0.3125,-1.64625 0.9375,-2.33 0.62375,-0.68375 1.51,-1.22625 2.65875,-1.625 l 3.48875,-1.2 0,-0.73375 c 0,-1.6575 -0.8275,-2.48625 -2.485,-2.48625 -1.49,0 -2.9375,0.76875 -4.34625,2.3075 l 0,-2.9825 c 1.06,-1.24875 2.58375,-1.8725 4.57,-1.8725 1.48875,0 2.68125,0.38875 3.58125,1.16875 0.3,0.25 0.57,0.5825 0.80875,0.99625 0.24125,0.415 0.3925,0.82875 0.45625,1.24375 0.065,0.415 0.0975,1.20125 0.0975,2.35875"
|
||||
id="path58" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 257.011,82.685 0,2.6975 c -1.36875,0.50875 -2.7075,0.76375 -4.015,0.76375 -2.1575,0 -3.87625,-0.64 -5.16125,-1.9175 -1.2825,-1.27875 -1.925,-2.9925 -1.925,-5.13875 0,-2.1675 0.62375,-3.915 1.8725,-5.24375 1.2475,-1.32875 2.8925,-1.9925 4.93,-1.9925 0.70875,0 1.34625,0.0675 1.91,0.20125 0.56375,0.135 1.26,0.3875 2.09,0.7575 l 0,2.90625 c -1.38,-0.87875 -2.6575,-1.3175 -3.83625,-1.3175 -1.2275,0 -2.2375,0.43125 -3.02625,1.295 -0.78875,0.865 -1.18375,1.965 -1.18375,3.30375 0,1.4075 0.4275,2.52625 1.2825,3.355 0.8525,0.83 2.00375,1.24375 3.45125,1.24375 1.05,0 2.2525,-0.30375 3.61125,-0.91375"
|
||||
id="path60" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 264.986,77.6225 6.93625,0 c -0.07,-1.08875 -0.395,-1.9275 -0.9725,-2.51625 -0.58,-0.58875 -1.35875,-0.885 -2.33875,-0.885 -0.97875,0 -1.77875,0.29625 -2.40375,0.885 -0.62375,0.58875 -1.03125,1.4275 -1.22125,2.51625 m -0.06,1.63375 c 0.07,1.3175 0.5125,2.36625 1.32625,3.14625 0.81375,0.77875 1.865,1.1675 3.15375,1.1675 1.79875,0 3.45625,-0.55875 4.97375,-1.6775 l 0,2.66625 c -0.8375,0.56 -1.67,0.96 -2.49375,1.19875 -0.825,0.24 -1.79,0.36 -2.9,0.36 -1.51875,0 -2.74625,-0.315 -3.685,-0.94375 -0.94,-0.62875 -1.69125,-1.47625 -2.25625,-2.53875 -0.5625,-1.065 -0.845,-2.295 -0.845,-3.69375 0,-2.0975 0.59375,-3.8025 1.7825,-5.11625 1.18875,-1.31375 2.7325,-1.97 4.62875,-1.97 1.82875,0 3.28625,0.64 4.375,1.9175 1.08875,1.27875 1.64156,3.342812 1.64156,5.490312 l -9.70156,-0.0066 z"
|
||||
id="path62"
|
||||
sodipodi:nodetypes="ccsssccssccssssssssc" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 296.1485,74.6725 -1.92875,0 0,8.48 c 0.8375,0.42875 1.71375,0.64375 2.6325,0.64375 1.275,0 2.31875,-0.445 3.13,-1.33375 0.8125,-0.88875 1.22,-2.0325 1.22,-3.43 0,-0.89875 -0.1925,-1.6925 -0.57625,-2.38125 -0.385,-0.69 -0.9075,-1.1925 -1.57,-1.50625 -0.6625,-0.315 -1.63125,-0.4725 -2.9075,-0.4725 m -4.71375,16.56625 0,-19.11375 4.7725,0 c 2.44375,0 4.34625,0.61 5.7075,1.82875 1.3625,1.21875 2.0425,2.92125 2.0425,5.10875 0,2.0675 -0.64125,3.765 -1.92125,5.0925 -1.28125,1.32875 -2.91375,1.99375 -4.89875,1.99375 -0.8775,0 -1.85,-0.19625 -2.9175,-0.585 l 0,5.675 -2.785,0 z"
|
||||
id="path64" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 312.636,72.1225 0,3.16125 0.15,-0.24 c 1.31875,-2.1275 2.63625,-3.19 3.955,-3.19 1.0275,0 2.1025,0.51875 3.22125,1.5575 l -1.43875,2.3975 c -0.94875,-0.9 -1.82625,-1.34875 -2.63625,-1.34875 -0.87875,0 -1.64,0.42 -2.28625,1.2575 -0.6425,0.84 -0.965,1.83375 -0.965,2.9825 l 0,7.20625 -2.74,0 0,-13.78375 2.74,0 z"
|
||||
id="path66" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 330.611,83.185 0,-4.08875 -1.82625,0.70375 c -0.93,0.36875 -1.58625,0.74125 -1.97125,1.11625 -0.385,0.37375 -0.5775,0.84125 -0.5775,1.4 0,0.56875 0.1825,1.03375 0.54625,1.39375 0.365,0.35875 0.8375,0.53875 1.4175,0.53875 0.86875,0 1.6725,-0.355 2.41125,-1.06375 m 2.68125,-5.55875 0,5.85875 c 0,0.47 0.15875,0.70375 0.47875,0.70375 0.33,0 0.845,-0.245 1.54375,-0.73375 l 0,1.6625 c -0.61875,0.4 -1.11625,0.67125 -1.49125,0.8175 -0.375,0.14375 -0.7675,0.21625 -1.17625,0.21625 -1.16875,0 -1.85625,-0.45875 -2.06625,-1.3775 -1.16,0.8975 -2.39375,1.3475 -3.7,1.3475 -0.96,0 -1.76,-0.31625 -2.3975,-0.95125 -0.63875,-0.63375 -0.95875,-1.43125 -0.95875,-2.38875 0,-0.87 0.31125,-1.64625 0.93625,-2.33 0.62375,-0.68375 1.51,-1.22625 2.65875,-1.625 l 3.49,-1.2 0,-0.73375 c 0,-1.6575 -0.82875,-2.48625 -2.48625,-2.48625 -1.48875,0 -2.93625,0.76875 -4.345,2.3075 l 0,-2.9825 c 1.05875,-1.24875 2.5825,-1.8725 4.56875,-1.8725 1.48875,0 2.6825,0.38875 3.58125,1.16875 0.3,0.25 0.57,0.5825 0.81,0.99625 0.24,0.415 0.39125,0.82875 0.455,1.24375 0.065,0.415 0.0988,1.20125 0.0988,2.35875"
|
||||
id="path68" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 344.911,74.485 c -0.73875,0 -1.3675,0.25 -1.8875,0.74875 -0.51875,0.5 -0.77875,1.09875 -0.77875,1.7975 0,0.71 0.25375,1.29625 0.765,1.76 0.50875,0.465 1.1525,0.6975 1.93125,0.6975 0.77,0 1.41125,-0.2375 1.925,-0.71125 0.515,-0.475 0.7725,-1.06625 0.7725,-1.775 0,-0.72 -0.26,-1.31875 -0.77875,-1.79875 C 346.341,74.72375 345.691,74.485 344.911,74.485 m -0.64375,12.06 c -0.91875,0 -1.67,0.195 -2.25375,0.58375 -0.58375,0.39 -0.8775,0.88875 -0.8775,1.49875 0,1.4175 1.27875,2.1275 3.83625,2.1275 1.20875,0 2.145,-0.1775 2.8075,-0.5325 0.665,-0.35375 0.9975,-0.85625 0.9975,-1.505 0,-0.64 -0.41875,-1.16125 -1.25875,-1.56625 C 346.67975,86.7475 345.596,86.545 344.26725,86.545 m -4.48,-9.66375 c 0,-1.4675 0.5375,-2.62875 1.61125,-3.4825 1.07375,-0.85375 2.54,-1.28125 4.3975,-1.28125 l 5.6775,0 0,2.1275 -2.78625,0 c 0.53875,0.55 0.91375,1.04875 1.12375,1.4975 0.21,0.45 0.315,0.965 0.315,1.54375 0,0.71875 -0.205,1.42625 -0.615,2.12 -0.40875,0.69375 -0.935,1.225 -1.58125,1.595 -0.64375,0.37 -1.7,0.665 -3.1675,0.88375 -1.02875,0.15125 -1.54375,0.505 -1.54375,1.06375 0,0.32 0.1925,0.5825 0.57625,0.7875 0.385,0.20375 1.0825,0.41625 2.09,0.63625 1.68875,0.37 2.77375,0.66 3.26,0.86875 0.48375,0.21 0.92,0.51 1.31125,0.89875 0.65875,0.66 0.98875,1.48875 0.98875,2.4875 0,1.3075 -0.5825,2.35125 -1.74625,3.13125 -1.165,0.77875 -2.72,1.1675 -4.66625,1.1675 -1.97,0 -3.53875,-0.39125 -4.71125,-1.175 -1.17375,-0.785 -1.76125,-1.835 -1.76125,-3.15375 0,-1.8675 1.15375,-3.07125 3.46,-3.61125 -0.91875,-0.58875 -1.3775,-1.17375 -1.3775,-1.7525 0,-0.43875 0.19625,-0.83875 0.5925,-1.1975 0.39375,-0.36 0.925,-0.625 1.59375,-0.795 -2.02625,-0.8975 -3.04125,-2.3525 -3.04125,-4.36"
|
||||
id="path70" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 365.1235,85.91 0,-1.76875 c -0.57875,0.635 -1.24,1.1275 -1.98375,1.48 -0.745,0.3525 -1.485,0.5275 -2.225,0.5275 -0.87,0 -1.67,-0.21625 -2.405,-0.65125 -0.735,-0.435 -1.28875,-1.02375 -1.6625,-1.7675 -0.375,-0.74375 -0.56125,-1.98 -0.56125,-3.7075 l 0,-7.89625 2.725,0 0,7.855 c 0,1.4475 0.2075,2.4575 0.62125,3.03125 0.415,0.5725 1.1425,0.86 2.1825,0.86 1.2975,0 2.4,-0.635 3.30875,-1.90375 l 0,-9.8425 2.7275,0 0,13.78375 -2.7275,0 z"
|
||||
id="path72" /><path
|
||||
style="fill:#71cccc;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
style="fill:#217777;fill-rule:nonzero;stroke:none;stroke-opacity:1;fill-opacity:1"
|
||||
d="m 376.561,77.6225 6.9375,0 c -0.0725,-1.08875 -0.39625,-1.9275 -0.975,-2.51625 -0.58,-0.58875 -1.35875,-0.885 -2.33875,-0.885 -0.9775,0 -1.77875,0.29625 -2.40375,0.885 -0.62375,0.58875 -1.03125,1.4275 -1.22,2.51625 m -0.06,1.63375 c 0.07,1.3175 0.51125,2.36625 1.32625,3.14625 0.81375,0.77875 1.865,1.1675 3.1525,1.1675 1.79875,0 3.45625,-0.55875 4.975,-1.6775 l 0,2.66625 c -0.83875,0.56 -1.67125,0.96 -2.495,1.19875 -0.825,0.24 -1.79,0.36 -2.89875,0.36 -1.52,0 -2.74625,-0.315 -3.68625,-0.94375 -0.9375,-0.62875 -1.69,-1.47625 -2.255,-2.53875 -0.5625,-1.065 -0.845,-2.295 -0.845,-3.69375 0,-2.0975 0.59375,-3.8025 1.78125,-5.11625 1.18875,-1.31375 2.7325,-1.97 4.62875,-1.97 1.82875,0 3.28625,0.64 4.375,1.9175 1.09,1.27875 1.62984,3.342813 1.62984,5.490313 l -9.68859,-0.0066 z"
|
||||
id="path74"
|
||||
sodipodi:nodetypes="ccsssccssccssssssssc" />
|
||||
|
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
@ -1,4 +1,3 @@
|
|||
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
|
||||
import QtQuick 1.1
|
||||
|
||||
BasePage {
|
||||
|
@ -17,11 +16,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"] })
|
||||
}
|
||||
}
|
||||
|
|
62
brmbar3/brmbar-tui.py
Executable file
62
brmbar3/brmbar-tui.py
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/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
|
45
brmbar3/brmbar-web.py
Executable file
45
brmbar3/brmbar-web.py
Executable file
|
@ -0,0 +1,45 @@
|
|||
#!/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')
|
|
@ -1,90 +1,84 @@
|
|||
from .Currency import Currency
|
||||
|
||||
import psycopg2
|
||||
from contextlib import closing
|
||||
|
||||
class Account:
|
||||
""" BrmBar Account
|
||||
""" BrmBar Account
|
||||
|
||||
Both users and items are accounts. So is the money box, etc.
|
||||
Each account has a currency."""
|
||||
def __init__(self, db, id, name, currency, acctype):
|
||||
self.db = db
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.currency = currency
|
||||
self.acctype = acctype
|
||||
Both users and items are accounts. So is the money box, etc.
|
||||
Each account has a currency."""
|
||||
def __init__(self, db, id, name, currency, acctype):
|
||||
self.db = db
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.currency = currency
|
||||
self.acctype = acctype
|
||||
|
||||
@classmethod
|
||||
def load_by_barcode(cls, db, barcode):
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("SELECT account FROM barcodes WHERE barcode = %s", [barcode])
|
||||
res = cur.fetchone()
|
||||
if res is None:
|
||||
return None
|
||||
id = res[0]
|
||||
return cls.load(db, id = id)
|
||||
@classmethod
|
||||
def load_by_barcode(cls, db, barcode):
|
||||
res = db.execute_and_fetch("SELECT account FROM barcodes WHERE barcode = %s", [barcode])
|
||||
if res is None:
|
||||
return None
|
||||
id = res[0]
|
||||
return cls.load(db, id = id)
|
||||
|
||||
@classmethod
|
||||
def load(cls, db, id = None, name = None):
|
||||
""" Constructor for existing account """
|
||||
if id is not None:
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("SELECT name FROM accounts WHERE id = %s", [id])
|
||||
name = cur.fetchone()[0]
|
||||
elif name is not None:
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("SELECT id FROM accounts WHERE name = %s", [name])
|
||||
id = cur.fetchone()[0]
|
||||
else:
|
||||
raise NameError("Account.load(): Specify either id or name")
|
||||
@classmethod
|
||||
def load(cls, db, id = None, name = None):
|
||||
""" Constructor for existing account """
|
||||
if id is not None:
|
||||
name = db.execute_and_fetch("SELECT name FROM accounts WHERE id = %s", [id])
|
||||
name = name[0]
|
||||
elif name is not None:
|
||||
id = db.execute_and_fetch("SELECT id FROM accounts WHERE name = %s", [name])
|
||||
id = id[0]
|
||||
else:
|
||||
raise NameError("Account.load(): Specify either id or name")
|
||||
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("SELECT currency, acctype FROM accounts WHERE id = %s", [id])
|
||||
currid, acctype = cur.fetchone()
|
||||
currency = Currency.load(db, id = currid)
|
||||
currid, acctype = db.execute_and_fetch("SELECT currency, acctype FROM accounts WHERE id = %s", [id])
|
||||
currency = Currency.load(db, id = currid)
|
||||
|
||||
return cls(db, name = name, id = id, currency = currency, acctype = acctype)
|
||||
return cls(db, name = name, id = id, currency = currency, acctype = acctype)
|
||||
|
||||
@classmethod
|
||||
def create(cls, db, name, currency, acctype):
|
||||
""" Constructor for new account """
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype])
|
||||
id = cur.fetchone()[0]
|
||||
return cls(db, name = name, id = id, currency = currency, acctype = acctype)
|
||||
@classmethod
|
||||
def create(cls, db, name, currency, acctype):
|
||||
""" Constructor for new account """
|
||||
# id = db.execute_and_fetch("INSERT INTO accounts (name, currency, acctype) VALUES (%s, %s, %s) RETURNING id", [name, currency.id, acctype])
|
||||
id = db.execute_and_fetch("SELECT public.create_account(%s, %s, %s)", [name, currency.id, acctype])
|
||||
# id = id[0]
|
||||
return cls(db, name = name, id = id, currency = currency, acctype = acctype)
|
||||
|
||||
def balance(self):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit'])
|
||||
debit = cur.fetchone()[0] or 0
|
||||
cur.execute("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit'])
|
||||
credit = cur.fetchone()[0] or 0
|
||||
return debit - credit
|
||||
def balance(self):
|
||||
bal = self.db.execute_and_fetch(
|
||||
"SELECT public.compute_account_balance(%s)",
|
||||
[self.id]
|
||||
)[0]
|
||||
return bal
|
||||
#debit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'debit'])
|
||||
#debit = debit[0] or 0
|
||||
#credit = self.db.execute_and_fetch("SELECT SUM(amount) FROM transaction_splits WHERE account = %s AND side = %s", [self.id, 'credit'])
|
||||
#credit = credit[0] or 0
|
||||
#return debit - credit
|
||||
|
||||
def balance_str(self):
|
||||
return self.currency.str(self.balance())
|
||||
def balance_str(self):
|
||||
return self.currency.str(self.balance())
|
||||
|
||||
def negbalance_str(self):
|
||||
return self.currency.str(-self.balance())
|
||||
def negbalance_str(self):
|
||||
return self.currency.str(-self.balance())
|
||||
|
||||
def debit(self, transaction, amount, memo):
|
||||
return self._transaction_split(transaction, 'debit', amount, memo)
|
||||
def debit(self, transaction, amount, memo):
|
||||
return self._transaction_split(transaction, 'debit', amount, memo)
|
||||
|
||||
def credit(self, transaction, amount, memo):
|
||||
return self._transaction_split(transaction, 'credit', amount, memo)
|
||||
def credit(self, transaction, amount, memo):
|
||||
return self._transaction_split(transaction, 'credit', amount, memo)
|
||||
|
||||
def _transaction_split(self, transaction, side, amount, memo):
|
||||
""" Common part of credit() and debit(). """
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, self.id, amount, memo])
|
||||
def _transaction_split(self, transaction, side, amount, memo):
|
||||
""" Common part of credit() and debit(). """
|
||||
self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, self.id, amount, memo])
|
||||
|
||||
def add_barcode(self, barcode):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode])
|
||||
self.db.commit()
|
||||
def add_barcode(self, barcode):
|
||||
# self.db.execute("INSERT INTO barcodes (account, barcode) VALUES (%s, %s)", [self.id, barcode])
|
||||
self.db.execute("SELECT public.add_barcode_to_account(%s, %s)", [self.id, barcode])
|
||||
self.db.commit()
|
||||
|
||||
def rename(self, name):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
|
||||
self.name = name
|
||||
def rename(self, name):
|
||||
# self.db.execute("UPDATE accounts SET name = %s WHERE id = %s", [name, self.id])
|
||||
self.db.execute("SELECT public.rename_account(%s, %s)", [self.id, name])
|
||||
self.name = name
|
||||
|
|
|
@ -1,85 +1,98 @@
|
|||
import psycopg2
|
||||
from contextlib import closing
|
||||
# vim: set fileencoding=utf8
|
||||
|
||||
class Currency:
|
||||
""" Currency
|
||||
|
||||
Each account has a currency (1 Kč, 1 Club Maté, ...), pairs of
|
||||
currencies have (asymmetric) exchange rates. """
|
||||
def __init__(self, db, id, name):
|
||||
self.db = db
|
||||
self.id = id
|
||||
self.name = name
|
||||
""" Currency
|
||||
|
||||
Each account has a currency (1 Kč, 1 Club Maté, ...), pairs of
|
||||
currencies have (asymmetric) exchange rates. """
|
||||
def __init__(self, db, id, name):
|
||||
self.db = db
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
||||
@classmethod
|
||||
def default(cls, db):
|
||||
""" Default wallet currency """
|
||||
return cls.load(db, name = "Kč")
|
||||
@classmethod
|
||||
def default(cls, db):
|
||||
""" Default wallet currency """
|
||||
return cls.load(db, name = "Kč")
|
||||
|
||||
@classmethod
|
||||
def load(cls, db, id = None, name = None):
|
||||
""" Constructor for existing currency """
|
||||
if id is not None:
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("SELECT name FROM currencies WHERE id = %s", [id])
|
||||
name = cur.fetchone()[0]
|
||||
elif name is not None:
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("SELECT id FROM currencies WHERE name = %s", [name])
|
||||
id = cur.fetchone()[0]
|
||||
else:
|
||||
raise NameError("Currency.load(): Specify either id or name")
|
||||
return cls(db, name = name, id = id)
|
||||
@classmethod
|
||||
def load(cls, db, id = None, name = None):
|
||||
""" Constructor for existing currency """
|
||||
if id is not None:
|
||||
name = db.execute_and_fetch("SELECT name FROM currencies WHERE id = %s", [id])
|
||||
name = name[0]
|
||||
elif name is not None:
|
||||
id = db.execute_and_fetch("SELECT id FROM currencies WHERE name = %s", [name])
|
||||
id = id[0]
|
||||
else:
|
||||
raise NameError("Currency.load(): Specify either id or name")
|
||||
return cls(db, name = name, id = id)
|
||||
|
||||
@classmethod
|
||||
def create(cls, db, name):
|
||||
""" Constructor for new currency """
|
||||
with closing(db.cursor()) as cur:
|
||||
cur.execute("INSERT INTO currencies (name) VALUES (%s) RETURNING id", [name])
|
||||
id = cur.fetchone()[0]
|
||||
return cls(db, name = name, id = id)
|
||||
@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]
|
||||
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) """
|
||||
with closing(self.db.cursor()) as cur:
|
||||
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())
|
||||
|
||||
cur.execute("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [self.id, other.id])
|
||||
res = cur.fetchone()
|
||||
if res is None:
|
||||
raise NameError("Currency.rate(): Unknown conversion " + other.name() + " to " + self.name())
|
||||
buy_rate, buy_rate_dir = res
|
||||
buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate
|
||||
return (buy, sell)
|
||||
|
||||
cur.execute("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [other.id, self.id])
|
||||
res = cur.fetchone()
|
||||
if res is None:
|
||||
raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name())
|
||||
sell_rate, sell_rate_dir = res
|
||||
sell = sell_rate if sell_rate_dir == "source_to_target" else 1/sell_rate
|
||||
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())
|
||||
buy_rate, buy_rate_dir = res
|
||||
buy = buy_rate if buy_rate_dir == "target_to_source" else 1/buy_rate
|
||||
|
||||
return (buy, sell)
|
||||
res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [other.id, self.id])
|
||||
if res is None:
|
||||
raise NameError("Currency.rate(): Unknown conversion " + self.name() + " to " + other.name())
|
||||
sell_rate, sell_rate_dir = res
|
||||
sell = sell_rate if sell_rate_dir == "source_to_target" else 1/sell_rate
|
||||
|
||||
def convert(self, amount, target):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [target.id, self.id])
|
||||
res = cur.fetchone()
|
||||
if res is None:
|
||||
raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + other.name())
|
||||
rate, rate_dir = res
|
||||
if rate_dir == "source_to_target":
|
||||
resamount = amount * rate
|
||||
else:
|
||||
resamount = amount / rate
|
||||
return resamount
|
||||
return (buy, sell)
|
||||
|
||||
def str(self, amount):
|
||||
return "{:.2f} {}".format(amount, self.name)
|
||||
|
||||
def update_sell_rate(self, target, rate):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.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):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [source.id, self.id, rate, "target_to_source"])
|
||||
def convert(self, amount, target):
|
||||
res = self.db.execute_and_fetch("SELECT rate, rate_dir FROM exchange_rates WHERE target = %s AND source = %s AND valid_since <= NOW() ORDER BY valid_since DESC LIMIT 1", [target.id, self.id])
|
||||
if res is None:
|
||||
raise NameError("Currency.convert(): Unknown conversion " + self.name() + " to " + target.name())
|
||||
rate, rate_dir = res
|
||||
if rate_dir == "source_to_target":
|
||||
resamount = amount * rate
|
||||
else:
|
||||
resamount = amount / rate
|
||||
return resamount
|
||||
|
||||
def str(self, amount):
|
||||
return "{:.2f} {}".format(amount, self.name)
|
||||
|
||||
def update_sell_rate(self, target, rate):
|
||||
# self.db.execute("INSERT INTO exchange_rates (source, target, rate, rate_dir) VALUES (%s, %s, %s, %s)", [self.id, target.id, rate, "source_to_target"])
|
||||
self.db.execute("SELECT public.update_currency_sell_rate(%s, %s, %s)",
|
||||
[self.id, target.id, rate])
|
||||
def update_buy_rate(self, source, rate):
|
||||
# self.db.execute("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])
|
||||
|
|
61
brmbar3/brmbar/Database.py
Normal file
61
brmbar3/brmbar/Database.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
"""self-reconnecting database object"""
|
||||
|
||||
import psycopg2
|
||||
from contextlib import closing
|
||||
import time
|
||||
|
||||
class Database:
|
||||
"""self-reconnecting database object"""
|
||||
def __init__(self, dsn):
|
||||
self.db_conn = psycopg2.connect(dsn)
|
||||
self.dsn = dsn
|
||||
|
||||
def execute(self, query, attrs = None):
|
||||
"""execute a query and return one result"""
|
||||
with closing(self.db_conn.cursor()) as cur:
|
||||
cur = self._execute(cur, query, attrs)
|
||||
|
||||
def execute_and_fetch(self, query, attrs = None):
|
||||
"""execute a query and return one result"""
|
||||
with closing(self.db_conn.cursor()) as cur:
|
||||
cur = self._execute(cur, query, attrs)
|
||||
return cur.fetchone()
|
||||
|
||||
def execute_and_fetchall(self, query, attrs = None):
|
||||
"""execute a query and return all results"""
|
||||
with closing(self.db_conn.cursor()) as cur:
|
||||
cur = self._execute(cur, query, attrs)
|
||||
return cur.fetchall()
|
||||
|
||||
def _execute(self, cur, query, attrs, level=1):
|
||||
"""execute a query, and in case of OperationalError (db restart)
|
||||
reconnect to database. Recurses with increasig pause between tries"""
|
||||
try:
|
||||
if attrs is None:
|
||||
cur.execute(query)
|
||||
else:
|
||||
cur.execute(query, attrs)
|
||||
return cur
|
||||
except psycopg2.DataError as error: # when biitr comes and enters '99999999999999999999' for amount
|
||||
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")
|
||||
))
|
||||
#TODO: emit message "db conn failed, reconnecting
|
||||
time.sleep(2 ** level)
|
||||
try:
|
||||
self.db_conn = psycopg2.connect(self.dsn)
|
||||
except psycopg2.OperationalError:
|
||||
#TODO: emit message "psql not running to interface
|
||||
time.sleep(1)
|
||||
cur = self.db_conn.cursor() #how ugly is this?
|
||||
return self._execute(cur, query, attrs, level+1)
|
||||
|
||||
def commit(self):
|
||||
"""passes commit to db"""
|
||||
self.db_conn.commit()
|
|
@ -2,131 +2,295 @@ import brmbar
|
|||
from .Currency import Currency
|
||||
from .Account import Account
|
||||
|
||||
import psycopg2
|
||||
from contextlib import closing
|
||||
|
||||
class Shop:
|
||||
""" BrmBar Shop
|
||||
""" BrmBar Shop
|
||||
|
||||
Business logic so that only interaction is left in the hands
|
||||
of the frontend scripts. """
|
||||
def __init__(self, db, currency, 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
|
||||
Business logic so that only interaction is left in the hands
|
||||
of the frontend scripts. """
|
||||
def __init__(self, db, currency, profits, cash, excess, deficit):
|
||||
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"))
|
||||
@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"))
|
||||
|
||||
def sell(self, item, user, amount = 1):
|
||||
# Sale: Currency conversion from item currency to shop currency
|
||||
(buy, sell) = item.currency.rates(self.currency)
|
||||
cost = amount * sell
|
||||
profit = amount * (sell - buy)
|
||||
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]
|
||||
|
||||
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()
|
||||
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)
|
||||
|
||||
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):
|
||||
# Sale: Currency conversion from item currency to shop currency
|
||||
(buy, sell) = item.currency.rates(self.currency)
|
||||
cost = amount * sell
|
||||
profit = amount * (sell - buy)
|
||||
def sell_for_cash(self, item, amount = 1):
|
||||
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]
|
||||
|
||||
transaction = self._transaction(description = "BrmBar sale of {}x {} for cash".format(amount, item.name))
|
||||
item.credit(transaction, amount, "Cash")
|
||||
self.cash.debit(transaction, cost, item.name)
|
||||
self.profits.debit(transaction, profit, "Margin on " + item.name)
|
||||
self.db.commit()
|
||||
self.db.commit()
|
||||
return cost
|
||||
## Sale: Currency conversion from item currency to shop currency
|
||||
#(buy, sell) = item.currency.rates(self.currency)
|
||||
#cost = amount * sell
|
||||
#profit = amount * (sell - buy)
|
||||
|
||||
return cost
|
||||
#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()
|
||||
|
||||
def add_credit(self, credit, user):
|
||||
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()
|
||||
#return cost
|
||||
|
||||
def withdraw_credit(self, credit, user):
|
||||
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 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)
|
||||
|
||||
def buy_for_cash(self, item, amount = 1):
|
||||
# Buy: Currency conversion from item currency to shop currency
|
||||
(buy, sell) = item.currency.rates(self.currency)
|
||||
cost = amount * buy
|
||||
#transaction = self._transaction(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]
|
||||
|
||||
transaction = self._transaction(description = "BrmBar stock replenishment of {}x {} for cash".format(amount, item.name))
|
||||
item.debit(transaction, amount, "Cash")
|
||||
self.cash.credit(transaction, cost, item.name)
|
||||
self.db.commit()
|
||||
self.db.commit()
|
||||
|
||||
return cost
|
||||
return cost
|
||||
|
||||
def receipt_to_credit(self, user, credit, description):
|
||||
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 add_credit(self, credit, user):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.add_credit(%s, %s, %s, %s)",
|
||||
[self.cash.id, credit, user.id, user.name]
|
||||
)
|
||||
self.db.commit()
|
||||
|
||||
def _transaction(self, responsible = None, description = None):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id",
|
||||
[responsible.id if responsible else None, description])
|
||||
transaction = cur.fetchone()[0]
|
||||
return transaction
|
||||
#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 credit_balance(self):
|
||||
with closing(self.db.cursor()) as cur:
|
||||
# We assume all debt accounts share a currency
|
||||
sumselect = """
|
||||
SELECT SUM(ts.amount)
|
||||
FROM accounts AS a
|
||||
LEFT JOIN transaction_splits AS ts ON a.id = ts.account
|
||||
WHERE a.acctype = %s AND ts.side = %s
|
||||
"""
|
||||
cur.execute(sumselect, ["debt", 'debit'])
|
||||
debit = cur.fetchone()[0] or 0
|
||||
cur.execute(sumselect, ["debt", 'credit'])
|
||||
credit = cur.fetchone()[0] or 0
|
||||
return debit - credit
|
||||
def credit_negbalance_str(self):
|
||||
return self.currency.str(-self.credit_balance())
|
||||
def withdraw_credit(self, credit, user):
|
||||
self.db.execute_and_fetch(
|
||||
"SELECT public.withdraw_credit(%s, %s, %s, %s)",
|
||||
[self.cash.id, credit, user.id, user.name]
|
||||
)
|
||||
self.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 inventory_balance(self):
|
||||
balance = 0
|
||||
with closing(self.db.cursor()) as cur:
|
||||
# Each inventory account has its own currency,
|
||||
# so we just do this ugly iteration
|
||||
cur.execute("SELECT id FROM accounts WHERE acctype = %s", ["inventory"])
|
||||
for inventory in cur:
|
||||
invid = inventory[0]
|
||||
inv = Account.load(self.db, id = invid)
|
||||
# FIXME: This is not correct as each instance of inventory
|
||||
# might have been bought for a different price! Therefore,
|
||||
# we need to replace the command below with a complex SQL
|
||||
# statement that will... ugh, accounting is hard!
|
||||
balance += inv.currency.convert(inv.balance(), self.currency)
|
||||
return balance
|
||||
def inventory_balance_str(self):
|
||||
return self.currency.str(self.inventory_balance())
|
||||
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 account_list(self, acctype):
|
||||
accts = []
|
||||
with closing(self.db.cursor()) as cur:
|
||||
cur.execute("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 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
|
||||
|
||||
#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]
|
||||
self.db.commit()
|
||||
|
||||
def _transaction(self, responsible = None, description = None):
|
||||
transaction = self.db.execute_and_fetch("INSERT INTO transactions (responsible, description) VALUES (%s, %s) RETURNING id",
|
||||
[responsible.id if responsible else None, description])
|
||||
transaction = transaction[0]
|
||||
return transaction
|
||||
|
||||
def credit_balance(self, overflow=None):
|
||||
# We assume all debt accounts share a currency
|
||||
sumselect = """
|
||||
SELECT SUM(ts.amount)
|
||||
FROM accounts AS a
|
||||
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))
|
||||
|
||||
# XXX causing extra heavy delay ( thousands of extra SQL queries ), disabled
|
||||
def inventory_balance(self):
|
||||
balance = 0
|
||||
# Each inventory account has its own currency,
|
||||
# so we just do this ugly iteration
|
||||
cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s", ["inventory"])
|
||||
for inventory in cur:
|
||||
invid = inventory[0]
|
||||
inv = Account.load(self.db, id = invid)
|
||||
# FIXME: This is not correct as each instance of inventory
|
||||
# might have been bought for a different price! Therefore,
|
||||
# we need to replace the command below with a complex SQL
|
||||
# statement that will... ugh, accounting is hard!
|
||||
b = inv.balance() * inv.currency.rates(self.currency)[0]
|
||||
# if b != 0:
|
||||
# print(str(b) + ',' + inv.name)
|
||||
balance += b
|
||||
return balance
|
||||
|
||||
# XXX bypass hack
|
||||
def inventory_balance_str(self):
|
||||
# return self.currency.str(self.inventory_balance())
|
||||
return "XXX"
|
||||
|
||||
def account_list(self, acctype, like_str="%%"):
|
||||
"""list all accounts (people or items, as per acctype)"""
|
||||
accts = []
|
||||
cur = self.db.execute_and_fetchall("SELECT id FROM accounts WHERE acctype = %s AND name ILIKE %s ORDER BY name ASC", [acctype, like_str])
|
||||
#FIXME: sanitize input like_str ^
|
||||
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
|
||||
|
|
15
brmbar3/crontab
Normal file
15
brmbar3/crontab
Normal file
|
@ -0,0 +1,15 @@
|
|||
# cleanup bounty
|
||||
*/5 * * * * ~/brmbar/brmbar3/uklid-watchdog.sh
|
||||
0 0 * * 1 ~/brmbar/brmbar3/uklid-refill.sh
|
||||
# overall summary
|
||||
5 4 * * * ~/brmbar/brmbar3/daily-summary.sh | mail -s "daily brmbar summary" yyy@yyy
|
||||
# debt track
|
||||
5 0 * * * ~/brmbar/brmbar3/dluhy.sh 2>/dev/null
|
||||
|
||||
# per-user summary
|
||||
1 0 * * * /home/brmlab/brmbar/brmbar3/log.sh yyy yyy@yyy
|
||||
|
||||
# backup
|
||||
6 * * * * echo "SELECT * FROM account_balances;" | psql brmbar | gzip -9 | ssh -Tp 110 -i /home/brmlab/.ssh/id_ecdsa jenda@coralmyn.hrach.eu
|
||||
16 1 * * * pg_dump brmbar | gzip -9 | ssh -Tp 110 -i /home/brmlab/.ssh/id_ecdsa jenda@coralmyn.hrach.eu
|
||||
|
11
brmbar3/daily-summary.sh
Executable file
11
brmbar3/daily-summary.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/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"
|
3
brmbar3/dluhy.sh
Executable file
3
brmbar3/dluhy.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
p1=`echo -n "brmbar - dluhy: "; echo "SELECT name, crbalance FROM account_balances WHERE acctype = 'debt' AND crbalance < -100 AND name NOT LIKE '%overflow%' AND name NOT LIKE 'sachyo' ORDER BY crbalance ASC" | psql brmbar | tail -n +3 | grep '|' | tr -s " " | sed -e "s/ |/:/g" -e "s/$/;/" | tr -d "\n"`
|
||||
p2=`echo "SELECT sum(crbalance) FROM account_balances WHERE acctype = 'debt' AND crbalance < 0 AND name NOT LIKE '%overflow%' AND name NOT LIKE 'sachyo'" | psql brmbar | tail -n +3 | head -n 1 | tr -s " "`
|
||||
echo "$p1 total$p2 Kc. https://www.elektro-obojky.cz/" | ssh -p 110 -i /home/brmlab/.ssh/id_rsa jenda@coralmyn.hrach.eu
|
106
brmbar3/doc/architecture.md
Normal file
106
brmbar3/doc/architecture.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
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.
|
15
brmbar3/import/import-items.pl
Normal file
15
brmbar3/import/import-items.pl
Normal file
|
@ -0,0 +1,15 @@
|
|||
# stdin in format: 8594002931643;Brmburky - hov.na cibul;-15
|
||||
use v5.12;
|
||||
while (<>) {
|
||||
chomp;
|
||||
say STDERR "--- $_";
|
||||
my ($barcode, $name, $price) = split(/;/);
|
||||
$price = -$price;
|
||||
print <<EOT
|
||||
INSERT INTO currencies (name) VALUES ('$name');
|
||||
INSERT INTO accounts (name, currency, acctype) VALUES ('$name', (SELECT id FROM currencies WHERE name = '$name'), 'inventory');
|
||||
INSERT INTO exchange_rates (target, source, rate, rate_dir) VALUES ((SELECT id FROM currencies WHERE name = '$name'), (SELECT id FROM currencies WHERE name = 'Kč'), $price, 'target_to_source');
|
||||
INSERT INTO exchange_rates (target, source, rate, rate_dir) VALUES ((SELECT id FROM currencies WHERE name = 'Kč'), (SELECT id FROM currencies WHERE name = '$name'), $price, 'source_to_target');
|
||||
INSERT INTO barcodes (barcode, account) VALUES ('$barcode', (SELECT id FROM accounts WHERE name = '$name'));
|
||||
EOT
|
||||
}
|
16
brmbar3/import/import-people.pl
Normal file
16
brmbar3/import/import-people.pl
Normal file
|
@ -0,0 +1,16 @@
|
|||
# text files containing credit amount
|
||||
my @users = qw(abyssal alexka axtheb aym b00lean biiter chido czestmyr da3m0n22 denisakera jam jenda jerry joe johny kappi kiki kxt lenka lui lukash nephirus niekt0 pasky pborky prusajr rainbof ramus ruza sachy sargon shady specz stevko stick sysop timthelion tlapka tma tomsuch trip tutchek uiop urcher vtec wenza zombie zufik zviratko);
|
||||
print "INSERT INTO transactions (description) VALUES ('Initial balance import');\n"; # assumed to be id 1
|
||||
for my $name (@users) {
|
||||
my $balance = `cat BRMBAR/DATA/$name.txt 2>/dev/null || echo -n 0`;
|
||||
my $side = 'credit';
|
||||
if ($balance < 0) {
|
||||
$balance = -$balance;
|
||||
$side = 'debit';
|
||||
}
|
||||
print <<EOT
|
||||
INSERT INTO accounts (name, currency, acctype) VALUES ('$name', 1, 'debt');
|
||||
INSERT INTO barcodes (barcode, account) VALUES ('$name', (SELECT id FROM accounts WHERE name = '$name'));
|
||||
INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (1, '$side', (SELECT id FROM accounts WHERE name = '$name'), $balance, 'brmbar v1 balance');
|
||||
EOT
|
||||
}
|
6
brmbar3/log.sh
Executable file
6
brmbar3/log.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
p=`/home/brmlab/brmbar/brmbar3/brmbar-cli.py userlog "$1" yesterday`
|
||||
|
||||
if [ -n "$p" ]; then
|
||||
echo "$p" | mail -s "brmbar report" "$2"
|
||||
fi
|
313
brmbar3/schema/0001-init.sql
Normal file
313
brmbar3/schema/0001-init.sql
Normal file
|
@ -0,0 +1,313 @@
|
|||
--
|
||||
-- 0001-init.sql
|
||||
--
|
||||
-- Initial SQL schema construction as of 2025-04-20 (or so)
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
-- Privileged schema with protected data
|
||||
CREATE SCHEMA IF NOT EXISTS brmbar_privileged;
|
||||
|
||||
-- Initial versioning
|
||||
CREATE TABLE IF NOT EXISTS brmbar_privileged.brmbar_schema(
|
||||
ver INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- Legacy Schema Initialization
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
DO $$
|
||||
DECLARE v INTEGER;
|
||||
BEGIN
|
||||
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v;
|
||||
IF v IS NULL THEN
|
||||
-- --------------------------------
|
||||
-- Legacy Types
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='exchange_rate_direction';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type exchange_rate_direction';
|
||||
CREATE TYPE public.exchange_rate_direction
|
||||
AS ENUM ('source_to_target', 'target_to_source');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type exchange_rate_direction already exists';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='account_type';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type account_type';
|
||||
CREATE TYPE public.account_type
|
||||
AS ENUM ('cash', 'debt', 'inventory', 'income', 'expense',
|
||||
'starting_balance', 'ending_balance');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type account_type already exists';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v
|
||||
FROM pg_catalog.pg_type typ
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = typ.typnamespace
|
||||
WHERE nsp.nspname = 'public'
|
||||
AND typ.typname='transaction_split_side';
|
||||
IF v=0 THEN
|
||||
RAISE NOTICE 'Creating type transaction_split_side';
|
||||
CREATE TYPE public.transaction_split_side
|
||||
AS ENUM ('credit', 'debit');
|
||||
ELSE
|
||||
RAISE NOTICE 'Type transaction_split_side already exists';
|
||||
END IF;
|
||||
|
||||
-- --------------------------------
|
||||
-- Currencies sequence, table and potential initial data
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.currencies_id_seq
|
||||
START WITH 2 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.currencies (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.currencies_id_seq'::regclass),
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
INSERT INTO public.currencies (id, name) VALUES (1, 'Kč')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Exchange rates table - no initial data required
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.exchange_rates (
|
||||
valid_since TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
|
||||
target INTEGER NOT NULL,
|
||||
FOREIGN KEY (target) REFERENCES public.currencies (id),
|
||||
|
||||
source INTEGER NOT NULL,
|
||||
FOREIGN KEY (source) REFERENCES public.currencies (id),
|
||||
|
||||
rate DECIMAL(12,2) NOT NULL,
|
||||
rate_dir public.exchange_rate_direction NOT NULL
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Accounts sequence and table and 4 initial accounts
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.accounts_id_seq
|
||||
START WITH 2 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.accounts (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.accounts_id_seq'::regclass),
|
||||
|
||||
name VARCHAR(128) NOT NULL,
|
||||
UNIQUE (name),
|
||||
|
||||
currency INTEGER NOT NULL,
|
||||
FOREIGN KEY (currency) REFERENCES public.currencies (id),
|
||||
|
||||
acctype public.account_type NOT NULL,
|
||||
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
INSERT INTO public.accounts (id, name, currency, acctype)
|
||||
VALUES (1, 'BrmBar Cash', (SELECT id FROM public.currencies WHERE name='Kč'), 'cash')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Profits', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Excess', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES ('BrmBar Deficit', (SELECT id FROM public.currencies WHERE name='Kč'), 'expense')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Barcodes
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.barcodes (
|
||||
barcode VARCHAR(128) PRIMARY KEY NOT NULL,
|
||||
|
||||
account INTEGER NOT NULL,
|
||||
FOREIGN KEY (account) REFERENCES public.accounts (id)
|
||||
);
|
||||
INSERT INTO public.barcodes (barcode, account)
|
||||
VALUES ('_cash_', (SELECT id FROM public.accounts WHERE acctype = 'cash'))
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- --------------------------------
|
||||
-- Transactions
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.transactions_id_seq
|
||||
START WITH 1 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.transactions (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transactions_id_seq'::regclass),
|
||||
time TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
|
||||
responsible INTEGER,
|
||||
FOREIGN KEY (responsible) REFERENCES public.accounts (id),
|
||||
|
||||
description TEXT
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction splits
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.transaction_splits_id_seq
|
||||
START WITH 1 INCREMENT BY 1;
|
||||
CREATE TABLE IF NOT EXISTS public.transaction_splits (
|
||||
id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transaction_splits_id_seq'::regclass),
|
||||
|
||||
transaction INTEGER NOT NULL,
|
||||
FOREIGN KEY (transaction) REFERENCES public.transactions (id),
|
||||
|
||||
side public.transaction_split_side NOT NULL,
|
||||
|
||||
account INTEGER NOT NULL,
|
||||
FOREIGN KEY (account) REFERENCES public.accounts (id),
|
||||
amount DECIMAL(12,2) NOT NULL,
|
||||
|
||||
memo TEXT
|
||||
);
|
||||
|
||||
-- --------------------------------
|
||||
-- Account balances view
|
||||
|
||||
CREATE OR REPLACE VIEW public.account_balances AS
|
||||
SELECT ts.account AS id,
|
||||
accounts.name,
|
||||
accounts.acctype,
|
||||
- sum(
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END) AS crbalance
|
||||
FROM public.transaction_splits ts
|
||||
LEFT JOIN public.accounts ON accounts.id = ts.account
|
||||
GROUP BY ts.account, accounts.name, accounts.acctype
|
||||
ORDER BY (- sum(
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END));
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction nice splits view
|
||||
|
||||
CREATE OR REPLACE VIEW public.transaction_nicesplits AS
|
||||
SELECT ts.id,
|
||||
ts.transaction,
|
||||
ts.account,
|
||||
CASE
|
||||
WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
|
||||
ELSE ts.amount
|
||||
END AS amount,
|
||||
a.currency,
|
||||
ts.memo
|
||||
FROM public.transaction_splits ts
|
||||
LEFT JOIN public.accounts a ON a.id = ts.account
|
||||
ORDER BY ts.id;
|
||||
|
||||
-- --------------------------------
|
||||
-- Transaction cash sums view
|
||||
|
||||
CREATE OR REPLACE VIEW public.transaction_cashsums AS
|
||||
SELECT t.id,
|
||||
t."time",
|
||||
sum(credit.credit_cash) AS cash_credit,
|
||||
sum(debit.debit_cash) AS cash_debit,
|
||||
a.name AS responsible,
|
||||
t.description
|
||||
FROM public.transactions t
|
||||
LEFT JOIN ( SELECT cts.amount AS credit_cash,
|
||||
cts.transaction AS cts_t
|
||||
FROM public.transaction_nicesplits cts
|
||||
LEFT JOIN public.accounts a_1 ON a_1.id = cts.account OR a_1.id = cts.account
|
||||
WHERE a_1.currency = (( SELECT accounts.currency
|
||||
FROM public.accounts
|
||||
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||
AND cts.amount < 0::numeric) credit ON credit.cts_t = t.id
|
||||
LEFT JOIN ( SELECT dts.amount AS debit_cash,
|
||||
dts.transaction AS dts_t
|
||||
FROM public.transaction_nicesplits dts
|
||||
LEFT JOIN public.accounts a_1 ON a_1.id = dts.account OR a_1.id = dts.account
|
||||
WHERE a_1.currency = (( SELECT accounts.currency
|
||||
FROM public.accounts
|
||||
WHERE accounts.name::text = 'BrmBar Cash'::text))
|
||||
AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
|
||||
AND dts.amount > 0::numeric) debit ON debit.dts_t = t.id
|
||||
LEFT JOIN public.accounts a ON a.id = t.responsible
|
||||
GROUP BY t.id, a.name
|
||||
ORDER BY t.id DESC;
|
||||
|
||||
-- --------------------------------
|
||||
-- Function to check schema version (used in migrations)
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.has_exact_schema_version(
|
||||
IN i_ver INTEGER
|
||||
) RETURNS BOOLEAN
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT ver INTO v_ver FROM brmbar_privileged.brmbar_schema;
|
||||
IF v_ver is NULL THEN
|
||||
RETURN false;
|
||||
ELSE
|
||||
RETURN v_ver = i_ver;
|
||||
END IF;
|
||||
END;
|
||||
$x$;
|
||||
|
||||
-- --------------------------------
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.upgrade_schema_version_to(
|
||||
IN i_ver INTEGER
|
||||
) RETURNS VOID
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
|
||||
DECLARE
|
||||
v_ver INTEGER;
|
||||
BEGIN
|
||||
SELECT ver FROM brmbar_privileged.brmbar_schema INTO v_ver;
|
||||
IF v_ver=(i_ver-1) THEN
|
||||
UPDATE brmbar_privileged.brmbar_schema SET ver = i_ver;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Invalid brmbar schema version transition (% -> %)', v_ver, i_ver;
|
||||
END IF;
|
||||
END;
|
||||
$x$;
|
||||
|
||||
-- Initialize version 1
|
||||
INSERT INTO brmbar_privileged.brmbar_schema(ver) VALUES(1);
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
40
brmbar3/schema/0002-trading-accounts.sql
Normal file
40
brmbar3/schema/0002-trading-accounts.sql
Normal file
|
@ -0,0 +1,40 @@
|
|||
--
|
||||
-- 0002-trading-accounts.sql
|
||||
--
|
||||
-- #2 - add trading accounts to account type type
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(1) THEN
|
||||
|
||||
ALTER TYPE public.account_type ADD VALUE 'trading';
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(2);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
52
brmbar3/schema/0003-new-account.sql
Normal file
52
brmbar3/schema/0003-new-account.sql
Normal file
|
@ -0,0 +1,52 @@
|
|||
--
|
||||
-- 0003-new-account.sql
|
||||
--
|
||||
-- #3 - stored procedure for creating new account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(2) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.create_account(
|
||||
IN i_name public.accounts.name%TYPE,
|
||||
IN i_currency public.accounts.currency%TYPE,
|
||||
IN i_acctype public.accounts.acctype%TYPE
|
||||
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.accounts (name, currency, acctype)
|
||||
VALUES (i_name, i_currency, i_acctype) RETURNING id INTO r_id;
|
||||
RETURN r_id;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(3);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
50
brmbar3/schema/0004-add-account-barcode.sql
Normal file
50
brmbar3/schema/0004-add-account-barcode.sql
Normal file
|
@ -0,0 +1,50 @@
|
|||
--
|
||||
-- 0004-add-account-barcode.sql
|
||||
--
|
||||
-- #4 - stored procedure for adding barcode to account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(3) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.add_barcode_to_account(
|
||||
IN i_account public.barcodes.account%TYPE,
|
||||
IN i_barcode public.barcodes.barcode%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.barcodes (account, barcode)
|
||||
VALUES (i_account, i_barcode);
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(4);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
51
brmbar3/schema/0005-rename-account.sql
Normal file
51
brmbar3/schema/0005-rename-account.sql
Normal file
|
@ -0,0 +1,51 @@
|
|||
--
|
||||
-- 0005-rename-account.sql
|
||||
--
|
||||
-- #5 - stored procedure for renaming account
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(4) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rename_account(
|
||||
IN i_account public.accounts.id%TYPE,
|
||||
IN i_name public.accounts.name%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
UPDATE public.accounts
|
||||
SET name = i_name
|
||||
WHERE id = i_account;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(5);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
50
brmbar3/schema/0006-new-currency.sql
Normal file
50
brmbar3/schema/0006-new-currency.sql
Normal file
|
@ -0,0 +1,50 @@
|
|||
--
|
||||
-- 0006-new-currency.sql
|
||||
--
|
||||
-- #6 - stored procedure for creating new currency
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(5) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.create_currency(
|
||||
IN i_name public.currencies.name%TYPE
|
||||
) RETURNS INTEGER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
r_id INTEGER;
|
||||
BEGIN
|
||||
INSERT INTO public.currencies (name)
|
||||
VALUES (i_name) RETURNING id INTO r_id;
|
||||
RETURN r_id;
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(6);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
49
brmbar3/schema/0007-update-currency-sell-rate.sql
Normal file
49
brmbar3/schema/0007-update-currency-sell-rate.sql
Normal file
|
@ -0,0 +1,49 @@
|
|||
--
|
||||
-- 0007-update-currency-sell-rate.sql
|
||||
--
|
||||
-- #7 - stored procedure for updating sell rate
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(6) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_currency_sell_rate(
|
||||
IN i_currency public.exchange_rates.source%TYPE,
|
||||
IN i_target public.exchange_rates.target%TYPE,
|
||||
IN i_rate public.exchange_rates.rate%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||
VALUES (i_currency, i_target, i_rate, 'source_to_target');
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(7);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
49
brmbar3/schema/0008-update-currency-buy-rate.sql
Normal file
49
brmbar3/schema/0008-update-currency-buy-rate.sql
Normal file
|
@ -0,0 +1,49 @@
|
|||
--
|
||||
-- 0008-update-currency-buy-rate.sql
|
||||
--
|
||||
-- #8 - stored procedure for updating buy rate
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- Dominik Pantůček <dominik.pantucek@trustica.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- Require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(7) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_currency_buy_rate(
|
||||
IN i_currency public.exchange_rates.target%TYPE,
|
||||
IN i_source public.exchange_rates.source%TYPE,
|
||||
IN i_rate public.exchange_rates.rate%TYPE
|
||||
) RETURNS VOID LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.exchange_rates(source, target, rate, rate_dir)
|
||||
VALUES (i_source, i_currency, i_rate, 'target_to_source');
|
||||
END
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(8);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
149
brmbar3/schema/0009-shop-sell.sql
Normal file
149
brmbar3/schema/0009-shop-sell.sql
Normal file
|
@ -0,0 +1,149 @@
|
|||
--
|
||||
-- 0009-shop-sell.sql
|
||||
--
|
||||
-- #9 - stored function for sell transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(8) THEN
|
||||
|
||||
-- return negative number on rate not found
|
||||
CREATE OR REPLACE FUNCTION public.find_buy_rate(
|
||||
IN i_item_id public.accounts.id%TYPE;
|
||||
IN i_other_id public.accounts.id%TYPE;
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rate public.exchange_rates.rate%TYPE;
|
||||
v_rate_dir public.exchange_rates.rate_dir%TYPE;
|
||||
BEGIN
|
||||
SELECT rate 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 :
|
143
brmbar3/schema/0010-shop-sell-for-cash.sql
Normal file
143
brmbar3/schema/0010-shop-sell-for-cash.sql
Normal file
|
@ -0,0 +1,143 @@
|
|||
--
|
||||
-- 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 :
|
102
brmbar3/schema/0011-shop-undo-sale.sql
Normal file
102
brmbar3/schema/0011-shop-undo-sale.sql
Normal file
|
@ -0,0 +1,102 @@
|
|||
--
|
||||
-- 0011-shop-undo-sale.sql
|
||||
--
|
||||
-- #11 - stored function for sale undo transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(10) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION brmbar_privileged.create_transaction(
|
||||
i_responsible_id public.accounts.id%TYPE,
|
||||
i_description public.transactions.description%TYPE
|
||||
) RETURNS public.transactions.id%TYPE AS $$
|
||||
DECLARE
|
||||
new_transaction_id public.transactions%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
INSERT INTO public.transactions (responsible, description)
|
||||
VALUES (i_responsible_id, i_description)
|
||||
RETURNING id INTO new_transaction_id;
|
||||
-- Return the new transaction ID
|
||||
RETURN new_transaction_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.undo_sale_of_item(
|
||||
i_item_id public.accounts.id%TYPE,
|
||||
i_amount INTEGER,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_target_currency_id public.currencies.id%TYPE,
|
||||
i_description TEXT
|
||||
) RETURNS NUMERIC
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_buy_rate NUMERIC;
|
||||
v_sell_rate NUMERIC;
|
||||
v_cost NUMERIC;
|
||||
v_profit NUMERIC;
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Get the buy and sell rates from the stored functions
|
||||
v_buy_rate := public.find_buy_rate(i_item_id, i_target_currency_id);
|
||||
v_sell_rate := public.find_sell_rate(i_item_id, i_target_currency_id);
|
||||
|
||||
-- Calculate cost and profit
|
||||
v_cost := i_amount * v_sell_rate;
|
||||
v_profit := i_amount * (v_sell_rate - v_buy_rate);
|
||||
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, i_description);
|
||||
|
||||
-- the item (decrease stock)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_item_id, i_amount,
|
||||
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_user_id));
|
||||
|
||||
-- the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_user_id, v_cost,
|
||||
(SELECT "name" || ' (sale undo)' FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- the profit
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', (SELECT account_id FROM accounts WHERE name = 'BrmBar Profits'), v_profit, (SELECT 'Margin repaid on ' || "name" FROM public.accounts WHERE id = i_item_id));
|
||||
|
||||
-- Return the cost
|
||||
RETURN v_cost;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(11);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
64
brmbar3/schema/0012-shop-add-credit.sql
Normal file
64
brmbar3/schema/0012-shop-add-credit.sql
Normal file
|
@ -0,0 +1,64 @@
|
|||
--
|
||||
-- 0012-shop-add-credit.sql
|
||||
--
|
||||
-- #12 - stored function for cash deposit transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(11) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.add_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit replenishment for ' || i_user_name);
|
||||
-- Debit cash (credit replenishment)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_cash_account_id, i_credit, i_user_name);
|
||||
-- Credit the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_user_id, i_credit, 'Credit replenishment');
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(12);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
64
brmbar3/schema/0013-shop-withdraw-credit.sql
Normal file
64
brmbar3/schema/0013-shop-withdraw-credit.sql
Normal file
|
@ -0,0 +1,64 @@
|
|||
--
|
||||
-- 0013-shop-withdraw-credit.sql
|
||||
--
|
||||
-- #13 - stored function for cash withdrawal transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(12) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.withdraw_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id, 'BrmBar credit withdrawal for ' || i_user_name);
|
||||
-- Debit cash (credit replenishment)
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'credit', i_cash_account_id, i_credit, i_user_name);
|
||||
-- Credit the user
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (v_transaction_id, 'debit', i_user_id, i_credit, 'Credit withdrawal');
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(13);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
58
brmbar3/schema/0014-shop-transfer-credit.sql
Normal file
58
brmbar3/schema/0014-shop-transfer-credit.sql
Normal file
|
@ -0,0 +1,58 @@
|
|||
--
|
||||
-- 0014-shop-transfer-credit.sql
|
||||
--
|
||||
-- #14 - stored function for "credit" transfer transactions
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(13) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.transfer_credit(
|
||||
i_cash_account_id public.accounts.id%TYPE,
|
||||
i_credit NUMERIC,
|
||||
i_userfrom_id public.accounts.id%TYPE,
|
||||
i_userfrom_name TEXT,
|
||||
i_userto_id public.accounts.id%TYPE,
|
||||
i_userto_name TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM public.add_credit(i_cash_account_id, i_credit, i_userto_id, i_userto_name);
|
||||
PERFORM public.withdraw_credit(i_cash_account_id, i_credit, i_userfrom_id, i_userfrom_name);
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(14);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
82
brmbar3/schema/0015-shop-buy-for-cash.sql
Normal file
82
brmbar3/schema/0015-shop-buy-for-cash.sql
Normal file
|
@ -0,0 +1,82 @@
|
|||
--
|
||||
-- 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 :
|
64
brmbar3/schema/0016-shop-receipt-to-credit.sql
Normal file
64
brmbar3/schema/0016-shop-receipt-to-credit.sql
Normal file
|
@ -0,0 +1,64 @@
|
|||
--
|
||||
-- 0016-shop-buy-for-cash.sql
|
||||
--
|
||||
-- #16 - stored function for receipt reimbursement transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(15) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.receipt_reimbursement(
|
||||
i_profits_id public.accounts.id%TYPE,
|
||||
i_user_id public.accounts.id%TYPE,
|
||||
i_user_name public.accounts.name%TYPE,
|
||||
i_amount NUMERIC,
|
||||
i_description TEXT
|
||||
) RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
BEGIN
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(i_user_id,
|
||||
'Receipt: ' || i_description);
|
||||
-- the "profit"
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (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 :
|
157
brmbar3/schema/0017-shop-fix-inventory.sql
Normal file
157
brmbar3/schema/0017-shop-fix-inventory.sql
Normal file
|
@ -0,0 +1,157 @@
|
|||
--
|
||||
-- 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 :
|
60
brmbar3/schema/0018-shop-fix-cash.sql
Normal file
60
brmbar3/schema/0018-shop-fix-cash.sql
Normal file
|
@ -0,0 +1,60 @@
|
|||
--
|
||||
-- 0018-shop-fix-cash.sql
|
||||
--
|
||||
-- #18 - stored function for "fixing cash" transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(17) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fix_cash(
|
||||
IN i_excess_id public.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 :
|
82
brmbar3/schema/0019-shop-consolidate.sql
Normal file
82
brmbar3/schema/0019-shop-consolidate.sql
Normal file
|
@ -0,0 +1,82 @@
|
|||
--
|
||||
-- 0019-shop-consolidate.sql
|
||||
--
|
||||
-- #19 - stored function for "consolidation" transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(18) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.make_consolidate_transaction(
|
||||
i_excess_id public.accounts.id%TYPE,
|
||||
i_deficit_id public.accounts.id%TYPE,
|
||||
i_profits_id public.accounts.id%TYPE
|
||||
) RETURNS TEXT
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_transaction_id public.transactions.id%TYPE;
|
||||
v_excess_balance NUMERIC;
|
||||
v_deficit_balance NUMERIC;
|
||||
v_ret TEXT;
|
||||
BEGIN
|
||||
v_ret := NULL;
|
||||
-- Create a new transaction
|
||||
v_transaction_id := brmbar_privileged.create_transaction(NULL,
|
||||
'BrmBar inventory consolidation');
|
||||
v_excess_balance := public.compute_account_balance(i_excess_id);
|
||||
v_deficit_balance := public.compute_account_balance(i_deficit_id);
|
||||
IF v_excess_balance <> 0 THEN
|
||||
v_ret := 'Excess balance ' || -v_excess_balance || ' debited to profit';
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_excess_id, -v_excess_balance,
|
||||
'Excess balance added to profit.');
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'debit', i_profits_id, -v_excess_balance,
|
||||
'Excess balance added to profit.');
|
||||
END IF;
|
||||
IF v_deficit_balance <> 0 THEN
|
||||
v_ret := COALESCE(v_ret, '');
|
||||
v_ret := v_ret || 'Deficit balance ' || v_deficit_balance || ' credited to profit';
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_deficit_id, v_deficit_balance,
|
||||
'Deficit balance removed from profit.');
|
||||
INSERT INTO public.transaction_splits (transaction, side, account, amount, memo)
|
||||
VALUES (i_transaction_id, 'credit', i_profits_id, v_deficit_balance,
|
||||
'Deficit balance removed from profit.');
|
||||
END IF;
|
||||
RETURN v_ret;
|
||||
END;
|
||||
$$;
|
||||
|
||||
PERFORM brmbar_privileged.upgrade_schema_version_to(19);
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$upgrade_block$;
|
||||
|
||||
-- vim: set ft=plsql :
|
64
brmbar3/schema/0020-shop-undo.sql
Normal file
64
brmbar3/schema/0020-shop-undo.sql
Normal file
|
@ -0,0 +1,64 @@
|
|||
--
|
||||
-- 0020-shop-undo.sql
|
||||
--
|
||||
-- #20 - stored function for undo transaction
|
||||
--
|
||||
-- ISC License
|
||||
--
|
||||
-- Copyright 2023-2025 Brmlab, z.s.
|
||||
-- TMA <tma+hs@jikos.cz>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software
|
||||
-- for any purpose with or without fee is hereby granted, provided
|
||||
-- that the above copyright notice and this permission notice appear
|
||||
-- in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
||||
-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
|
||||
-- To require fully-qualified names
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
|
||||
DO $upgrade_block$
|
||||
BEGIN
|
||||
|
||||
IF brmbar_privileged.has_exact_schema_version(19) THEN
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.undo_transaction(
|
||||
IN i_id public.transactions.id%TYPE)
|
||||
RETURNS public.transactions.id%TYPE
|
||||
VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$
|
||||
DECLARE
|
||||
v_ntrn_id public.transactions.id%TYPE;
|
||||
v_old_trn public.transactions%ROWTYPE;
|
||||
v_old_split public.transaction_splits%ROWTYPE;
|
||||
BEGIN
|
||||
SELECT * INTO v_old_trn FROM public.transactions WHERE id = i_id;
|
||||
INSERT INTO transactions ("description") VALUES ('undo '||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 :
|
73
brmbar3/test--currency-rates.py
Normal file
73
brmbar3/test--currency-rates.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
#from brmbar import Database
|
||||
#from brmbar import Currency
|
||||
|
||||
from contextlib import closing
|
||||
import psycopg2
|
||||
from brmbar.Database import Database
|
||||
from brmbar.Currency import Currency
|
||||
import math
|
||||
|
||||
#import brmbar
|
||||
|
||||
|
||||
|
||||
def approx_equal(a, b, tol=1e-6):
|
||||
"""Check if two (buy, sell) rate tuples are approximately equal."""
|
||||
return (
|
||||
isinstance(a, tuple) and isinstance(b, tuple) and
|
||||
math.isclose(a[0], b[0], abs_tol=tol) and
|
||||
math.isclose(a[1], b[1], abs_tol=tol)
|
||||
)
|
||||
|
||||
def compare_exceptions(e1, e2):
|
||||
"""Compare exception types and messages."""
|
||||
return type(e1) == type(e2) and str(e1) == str(e2)
|
||||
|
||||
def main():
|
||||
db = Database("dbname=brmbar")
|
||||
|
||||
# Get all currencies
|
||||
with closing(db.db_conn.cursor()) as cur:
|
||||
cur.execute("SELECT id, name FROM currencies")
|
||||
currencies = cur.fetchall()
|
||||
|
||||
# Build Currency objects
|
||||
currency_objs = [Currency(db, id, name) for id, name in currencies]
|
||||
|
||||
# Test all currency pairs
|
||||
for c1 in currency_objs:
|
||||
for c2 in currency_objs:
|
||||
#if c1.id == c2.id:
|
||||
# continue
|
||||
|
||||
try:
|
||||
rates1 = c1.rates(c2)
|
||||
exc1 = None
|
||||
except (RuntimeError, NameError) as e1:
|
||||
rates1 = None
|
||||
exc1 = e1
|
||||
|
||||
try:
|
||||
rates2 = c1.rates2(c2)
|
||||
exc2 = None
|
||||
except (RuntimeError, NameError) as e2:
|
||||
rates2 = None
|
||||
exc2 = e2
|
||||
|
||||
if exc1 or exc2:
|
||||
if not compare_exceptions(exc1, exc2):
|
||||
print(f"[EXCEPTION DIFFERENCE] {c1.name} -> {c2.name}")
|
||||
print(f" rates() exception: {type(exc1).__name__}: {exc1}")
|
||||
print(f" rates2() exception: {type(exc2).__name__}: {exc2}")
|
||||
elif not approx_equal(rates1, rates2):
|
||||
print(f"[VALUE DIFFERENCE] {c1.name} -> {c2.name}")
|
||||
print(f" rates(): {rates1}")
|
||||
print(f" rates2(): {rates2}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
6
brmbar3/uklid-refill.sh
Normal file
6
brmbar3/uklid-refill.sh
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ `./brmbar-cli.py iteminfo uklid|grep -o '[0-9]*.[0-9]* pcs'|cut -d '.' -f 1` -eq 0 ]; then
|
||||
BOUNTY=`./brmbar-cli.py restock uklid 1 | grep -o 'take -[0-9]*'|grep -o '[0-9]*'`
|
||||
echo "Brmlab cleanup bounty for ${BOUNTY}CZK!!!"|ssh jenda@fry.hrach.eu
|
||||
fi
|
18
brmbar3/uklid-watchdog.sh
Normal file
18
brmbar3/uklid-watchdog.sh
Normal file
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
LASTIDF=/home/brmlab/uklid.last
|
||||
|
||||
LASTID=`cat $LASTIDF 2>/dev/null || echo 0`
|
||||
|
||||
|
||||
RES=`psql brmbar -Atq -c "select id,description from transactions where id>$LASTID and description like 'BrmBar sale of 1x uklid%' LIMIT 1;"`
|
||||
if [ ! -z "$RES" ]; then
|
||||
LASTID=`echo "$RES"|cut -d '|' -f 1`
|
||||
echo $LASTID > $LASTIDF
|
||||
|
||||
WINNER=`echo "$RES"|grep -o 'to [^ ]*'|cut -d ' ' -f 2`
|
||||
if [ -z "$WINNER" ]; then
|
||||
WINNER="anonymous hunter"
|
||||
fi
|
||||
echo "Brmlab cleanup bounty was claimed by $WINNER! Thanks!"|ssh -p 110 jenda@coralmyn.hrach.eu
|
||||
fi
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue