billium 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- billium-1.0.0/LICENSE +21 -0
- billium-1.0.0/PKG-INFO +54 -0
- billium-1.0.0/README.md +36 -0
- billium-1.0.0/pyproject.toml +3 -0
- billium-1.0.0/setup.cfg +31 -0
- billium-1.0.0/src/billium/__init__.py +3 -0
- billium-1.0.0/src/billium/bill.py +249 -0
- billium-1.0.0/src/billium/data.py +114 -0
- billium-1.0.0/src/billium/environ.py +17 -0
- billium-1.0.0/src/billium/param.py +13 -0
- billium-1.0.0/src/billium/schema.py +94 -0
- billium-1.0.0/src/billium/syslogin.py +64 -0
- billium-1.0.0/src/billium/task.py +299 -0
- billium-1.0.0/src/billium/template.py +140 -0
- billium-1.0.0/src/billium/title.py +71 -0
- billium-1.0.0/src/billium.egg-info/PKG-INFO +54 -0
- billium-1.0.0/src/billium.egg-info/SOURCES.txt +19 -0
- billium-1.0.0/src/billium.egg-info/dependency_links.txt +1 -0
- billium-1.0.0/src/billium.egg-info/requires.txt +2 -0
- billium-1.0.0/src/billium.egg-info/top_level.txt +1 -0
billium-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Asinerum Conlang Project
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
billium-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: billium
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Literp Billing Web Tool
|
|
5
|
+
Home-page: https://github.com/asinerum/billium
|
|
6
|
+
Author: Asinerum Conlang Project
|
|
7
|
+
Author-email: asinerum.com@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: tagrepo>=1.0.16
|
|
16
|
+
Requires-Dist: invoiceum>=1.0.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Literp Billing Tool
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
```bash
|
|
23
|
+
pip install billium
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Sample Usage [Nov 1, 2026]
|
|
27
|
+
```bash
|
|
28
|
+
## nano billium.sh
|
|
29
|
+
export LLM_TRANSFORMER_MODEL="./all-MiniLM-L6-v2"
|
|
30
|
+
export SETUPKEY_ENCRYPT_SECRET="very-strong-password"
|
|
31
|
+
export JWT_SECRET="very-strong-password"
|
|
32
|
+
export TOKEN_EXPIRE_MINUTES=30
|
|
33
|
+
export ADMIN_AUTHENTICATOR_EMAIL_LIST=someone@company.com
|
|
34
|
+
export SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
35
|
+
export STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
36
|
+
export FINANCE_SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
37
|
+
export FINANCE_STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
38
|
+
export FILE_AVRO_DATA="./users/setupkeys.avro"
|
|
39
|
+
export FILE_AVRO_FINANCE_SUPERVISORS="./users/rolefbksupervisors.avro"
|
|
40
|
+
export FILE_AVRO_FINANCE_STAFF="./users/rolefbkstaff.avro"
|
|
41
|
+
export FILE_AVRO_SUPERVISORS="./users/rolecrmsupervisors.avro"
|
|
42
|
+
export FILE_AVRO_STAFF="./users/rolecrmstaff.avro"
|
|
43
|
+
export FILE_DUCKDB_VECTOR="./temp.db"
|
|
44
|
+
uvia -a vapp -m billium [-p <PORT>] [-H <HOST>]
|
|
45
|
+
## The <PORT> is 21921 by default, but can be edited
|
|
46
|
+
## The <HOST> should be 127.0.0.1 for security reason
|
|
47
|
+
## chmod +x billium.sh
|
|
48
|
+
## ./billium.sh
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Detailed tips, tricks, and examples, can be found at project's repository
|
|
52
|
+
https://github.com/asinerum/billium
|
|
53
|
+
|
|
54
|
+
(C) 2026 Asinerum Conlang Project
|
billium-1.0.0/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Literp Billing Tool
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
```bash
|
|
5
|
+
pip install billium
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## Sample Usage [Nov 1, 2026]
|
|
9
|
+
```bash
|
|
10
|
+
## nano billium.sh
|
|
11
|
+
export LLM_TRANSFORMER_MODEL="./all-MiniLM-L6-v2"
|
|
12
|
+
export SETUPKEY_ENCRYPT_SECRET="very-strong-password"
|
|
13
|
+
export JWT_SECRET="very-strong-password"
|
|
14
|
+
export TOKEN_EXPIRE_MINUTES=30
|
|
15
|
+
export ADMIN_AUTHENTICATOR_EMAIL_LIST=someone@company.com
|
|
16
|
+
export SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
17
|
+
export STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
18
|
+
export FINANCE_SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
19
|
+
export FINANCE_STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
20
|
+
export FILE_AVRO_DATA="./users/setupkeys.avro"
|
|
21
|
+
export FILE_AVRO_FINANCE_SUPERVISORS="./users/rolefbksupervisors.avro"
|
|
22
|
+
export FILE_AVRO_FINANCE_STAFF="./users/rolefbkstaff.avro"
|
|
23
|
+
export FILE_AVRO_SUPERVISORS="./users/rolecrmsupervisors.avro"
|
|
24
|
+
export FILE_AVRO_STAFF="./users/rolecrmstaff.avro"
|
|
25
|
+
export FILE_DUCKDB_VECTOR="./temp.db"
|
|
26
|
+
uvia -a vapp -m billium [-p <PORT>] [-H <HOST>]
|
|
27
|
+
## The <PORT> is 21921 by default, but can be edited
|
|
28
|
+
## The <HOST> should be 127.0.0.1 for security reason
|
|
29
|
+
## chmod +x billium.sh
|
|
30
|
+
## ./billium.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Detailed tips, tricks, and examples, can be found at project's repository
|
|
34
|
+
https://github.com/asinerum/billium
|
|
35
|
+
|
|
36
|
+
(C) 2026 Asinerum Conlang Project
|
billium-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = billium
|
|
3
|
+
version = 1.0.0
|
|
4
|
+
author = Asinerum Conlang Project
|
|
5
|
+
author_email = asinerum.com@gmail.com
|
|
6
|
+
description = Literp Billing Web Tool
|
|
7
|
+
long_description = file: README.md
|
|
8
|
+
long_description_content_type = text/markdown
|
|
9
|
+
url = https://github.com/asinerum/billium
|
|
10
|
+
license = MIT
|
|
11
|
+
classifiers =
|
|
12
|
+
Programming Language :: Python :: 3
|
|
13
|
+
License :: OSI Approved :: MIT License
|
|
14
|
+
Operating System :: OS Independent
|
|
15
|
+
|
|
16
|
+
[options]
|
|
17
|
+
package_dir =
|
|
18
|
+
= src
|
|
19
|
+
packages = find:
|
|
20
|
+
python_requires = >=3.7
|
|
21
|
+
install_requires =
|
|
22
|
+
tagrepo >= 1.0.16
|
|
23
|
+
invoiceum >= 1.0.0
|
|
24
|
+
|
|
25
|
+
[options.packages.find]
|
|
26
|
+
where = src
|
|
27
|
+
|
|
28
|
+
[egg_info]
|
|
29
|
+
tag_build =
|
|
30
|
+
tag_date = 0
|
|
31
|
+
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Literp Billing Tool
|
|
3
|
+
Dependencies:
|
|
4
|
+
pip install [tagrepo] uvia vapp billium
|
|
5
|
+
Start daemon:
|
|
6
|
+
export LLM_TRANSFORMER_MODEL="./all-MiniLM-L6-v2"
|
|
7
|
+
export SETUPKEY_ENCRYPT_SECRET="very-strong-password"
|
|
8
|
+
export JWT_SECRET="very-strong-password"
|
|
9
|
+
export TOKEN_EXPIRE_MINUTES=30
|
|
10
|
+
export ADMIN_AUTHENTICATOR_EMAIL_LIST=someone@company.com
|
|
11
|
+
export SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
12
|
+
export STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
13
|
+
export FINANCE_SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
14
|
+
export FINANCE_STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
15
|
+
export FILE_AVRO_DATA="./users/setupkeys.avro"
|
|
16
|
+
export FILE_AVRO_FINANCE_SUPERVISORS="./users/rolefbksupervisors.avro"
|
|
17
|
+
export FILE_AVRO_FINANCE_STAFF="./users/rolefbkstaff.avro"
|
|
18
|
+
export FILE_AVRO_SUPERVISORS="./users/rolecrmsupervisors.avro"
|
|
19
|
+
export FILE_AVRO_STAFF="./users/rolecrmstaff.avro"
|
|
20
|
+
export FILE_DUCKDB_VECTOR="./temp.db"
|
|
21
|
+
uvia -a vapp -m billium [-p <PORT>] [-H <HOST>]
|
|
22
|
+
VAPP routes:
|
|
23
|
+
Route: /callback -> callbackpage()
|
|
24
|
+
Route: /login -> loginpage()
|
|
25
|
+
Route: /call -> callpage()
|
|
26
|
+
GET -> request.query_params = {function, access_token, jsondata, module}
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from invoiceum import *
|
|
30
|
+
|
|
31
|
+
from .param import get_request_bill_options, get_request_payout_options
|
|
32
|
+
|
|
33
|
+
from .task import *
|
|
34
|
+
from .title import *
|
|
35
|
+
|
|
36
|
+
APP_PORT = 21921 ## for <uvia> and <vapp>
|
|
37
|
+
|
|
38
|
+
## core <uvia> functions
|
|
39
|
+
|
|
40
|
+
def homepage():
|
|
41
|
+
async def submit(request: Request):
|
|
42
|
+
return HTMLResponse(Template(APP_TMP_DIRECTORY_MENU).render(
|
|
43
|
+
APP_CHARSET=APP_CHARSET,
|
|
44
|
+
APP_VIEWPORT=APP_VIEWPORT,
|
|
45
|
+
APP_TITLE=APP_DESC,
|
|
46
|
+
FORM_TITLE=FORM_TITLE,
|
|
47
|
+
directory_items=[
|
|
48
|
+
{"href": "/login", "name": LABEL_MENU_LOGIN},
|
|
49
|
+
{"href": call_function(FUNCTION_BILL_INSERT), "name": LABEL_BILL_INSERT},
|
|
50
|
+
{"href": call_function(FUNCTION_DRAFT_BILLS_SELECT), "name": LABEL_DRAFT_BILLS_SELECT},
|
|
51
|
+
{"href": call_function(FUNCTION_NO_DRAFT_BILLS_SELECT), "name": LABEL_NO_DRAFT_BILLS_SELECT},
|
|
52
|
+
{"href": call_function(FUNCTION_BILLS_REVIEW), "name": LABEL_BILLS_REVIEW},
|
|
53
|
+
{"href": call_function(FUNCTION_PAYOUT_INSERT), "name": LABEL_PAYOUT_INSERT},
|
|
54
|
+
{"href": call_function(FUNCTION_DRAFT_PAYOUTS_SELECT), "name": LABEL_DRAFT_PAYOUTS_SELECT},
|
|
55
|
+
{"href": call_function(FUNCTION_NO_DRAFT_PAYOUTS_SELECT), "name": LABEL_NO_DRAFT_PAYOUTS_SELECT},
|
|
56
|
+
{"href": call_function(FUNCTION_PAYOUTS_REVIEW), "name": LABEL_PAYOUTS_REVIEW},
|
|
57
|
+
{"href": call_function(FUNCTION_TAX_COLLECT), "name": LABEL_TAX_COLLECT},
|
|
58
|
+
{"href": call_function(FUNCTION_BILLS_SEARCH), "name": LABEL_BILLS_SEARCH},
|
|
59
|
+
{"href": call_function("logout"), "name": LABEL_MENU_LOGOUT},
|
|
60
|
+
]
|
|
61
|
+
))
|
|
62
|
+
return submit
|
|
63
|
+
|
|
64
|
+
def callpage():
|
|
65
|
+
async def submit(request: Request):
|
|
66
|
+
return await process_function_call(request, auth_functions, HTMLResponse, _callpage)
|
|
67
|
+
return submit
|
|
68
|
+
|
|
69
|
+
async def _callpage(request, func: str):
|
|
70
|
+
if func == FUNCTION_CUSTOMERS_POPUP:
|
|
71
|
+
search = request.query_params.get("search")
|
|
72
|
+
return HTMLResponse(popup_customers_page(request, search))
|
|
73
|
+
elif func == FUNCTION_PRODUCTS_POPUP:
|
|
74
|
+
search = request.query_params.get("search")
|
|
75
|
+
return HTMLResponse(popup_products_page(request, search))
|
|
76
|
+
elif func == FUNCTION_BILLS_POPUP:
|
|
77
|
+
search = request.query_params.get("search")
|
|
78
|
+
return HTMLResponse(popup_bills_page(request, search))
|
|
79
|
+
elif func == FUNCTION_TYPE_POPUP:
|
|
80
|
+
return HTMLResponse(popup_type_page(request))
|
|
81
|
+
elif func == FUNCTION_BILL_INSERT:
|
|
82
|
+
if request.method == 'GET':
|
|
83
|
+
return HTMLResponse(insert_bill_page(request))
|
|
84
|
+
else:
|
|
85
|
+
form = await request.form()
|
|
86
|
+
dated = form.get(DATED) or nowdate()
|
|
87
|
+
due = form.get(DUE) or ""
|
|
88
|
+
form = inputfilter(dstrip(form), Bills, excludes=[])
|
|
89
|
+
form[DATED] = dated
|
|
90
|
+
form[DUE] = due
|
|
91
|
+
insert = insert_bill_page(request, **form)
|
|
92
|
+
if insert == CALLBACK: return RedirectResponseGet(url=call_function(FUNCTION_BILLS_SELECT))
|
|
93
|
+
return HTMLResponse(insert)
|
|
94
|
+
elif func == FUNCTION_BILLS_SELECT:
|
|
95
|
+
return HTMLResponse(select_bills_page(request, **get_request_bill_options(request)))
|
|
96
|
+
elif func == FUNCTION_DRAFT_BILLS_SELECT:
|
|
97
|
+
return HTMLResponse(select_draft_bills_page(request, **get_request_bill_options(request)))
|
|
98
|
+
elif func == FUNCTION_NO_DRAFT_BILLS_SELECT:
|
|
99
|
+
return HTMLResponse(select_no_draft_bills_page(request, **get_request_bill_options(request)))
|
|
100
|
+
elif func == FUNCTION_BILL_SELECT:
|
|
101
|
+
bill_id = request.query_params.get("id")
|
|
102
|
+
return HTMLResponse(select_bill_page(request, bill_id))
|
|
103
|
+
elif func == FUNCTION_CUSTOMER_SELECT:
|
|
104
|
+
customer_id = request.query_params.get("id")
|
|
105
|
+
return HTMLResponse(select_customer_page(request, customer_id))
|
|
106
|
+
elif func == FUNCTION_PRODUCT_SELECT:
|
|
107
|
+
product_id = request.query_params.get("id")
|
|
108
|
+
return HTMLResponse(select_product_page(request, product_id))
|
|
109
|
+
elif func == FUNCTION_BILL_NOTE_UPDATE: ## hidden
|
|
110
|
+
bill_id = request.query_params.get("id")
|
|
111
|
+
if request.method == 'GET':
|
|
112
|
+
return HTMLResponse(update_bill_note_page(request, bill_id))
|
|
113
|
+
else:
|
|
114
|
+
form = await request.form()
|
|
115
|
+
form = inputfilter(dstrip(form), Bills, includes=UPDATE_INPUTS["Bills"])
|
|
116
|
+
update = update_bill_note_page(request, bill_id, **form)
|
|
117
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_BILL_SELECT}&id={bill_id}"))
|
|
118
|
+
return HTMLResponse(update)
|
|
119
|
+
elif func == FUNCTION_BILL_UPDATE: ## hidden
|
|
120
|
+
bill_id = request.query_params.get("id")
|
|
121
|
+
if request.method == 'GET':
|
|
122
|
+
return HTMLResponse(update_bill_page(request, bill_id))
|
|
123
|
+
else:
|
|
124
|
+
form = await request.form()
|
|
125
|
+
form = inputfilter(dstrip(form), Bills, includes=UPDATE_INPUTS[Bills])
|
|
126
|
+
update = update_bill_page(request, bill_id, **form)
|
|
127
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_BILL_SELECT}&id={bill_id}"))
|
|
128
|
+
return HTMLResponse(update)
|
|
129
|
+
elif func == FUNCTION_BILL_UPGRADE:
|
|
130
|
+
bill_id = request.query_params.get("id")
|
|
131
|
+
if request.method == 'GET':
|
|
132
|
+
return HTMLResponse(upgrade_bill_page(request, bill_id))
|
|
133
|
+
else:
|
|
134
|
+
form = await request.form()
|
|
135
|
+
status = form.get(STATUS)
|
|
136
|
+
update = upgrade_bill_page(request, bill_id, status=status)
|
|
137
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_BILL_SELECT}&id={bill_id}"))
|
|
138
|
+
return HTMLResponse(update)
|
|
139
|
+
elif func == FUNCTION_BILLITEM_INSERT:
|
|
140
|
+
bill_id = request.query_params.get("bill_id")
|
|
141
|
+
if request.method == 'GET':
|
|
142
|
+
return HTMLResponse(insert_billitem_page(request, bill_id=bill_id))
|
|
143
|
+
else:
|
|
144
|
+
form = await request.form()
|
|
145
|
+
form = inputfilter(dstrip(form), Billitems, excludes=[DATED])
|
|
146
|
+
insert = insert_billitem_page(request, bill_id=bill_id, **form)
|
|
147
|
+
if insert == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_BILLITEMS_SELECT}&bill_id={bill_id}"))
|
|
148
|
+
return HTMLResponse(insert)
|
|
149
|
+
elif func == FUNCTION_BILLITEMS_SELECT:
|
|
150
|
+
bill_id = request.query_params.get("bill_id")
|
|
151
|
+
return HTMLResponse(select_billitems_page(request, bill_id))
|
|
152
|
+
elif func == FUNCTION_BILLITEM_SELECT:
|
|
153
|
+
billitem_id = request.query_params.get("id")
|
|
154
|
+
return HTMLResponse(select_billitem_page(request, billitem_id))
|
|
155
|
+
elif func == FUNCTION_BILLITEM_UPDATE: ## hidden
|
|
156
|
+
billitem_id = request.query_params.get("id")
|
|
157
|
+
if request.method == 'GET':
|
|
158
|
+
return HTMLResponse(update_billitem_page(request, billitem_id))
|
|
159
|
+
else:
|
|
160
|
+
form = await request.form()
|
|
161
|
+
form = inputfilter(dstrip(form), Billitems, includes=UPDATE_INPUTS[Billitems])
|
|
162
|
+
update = update_billitem_page(request, billitem_id, **form) ## refresh subtotal
|
|
163
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_BILLITEM_SELECT}&id={billitem_id}"))
|
|
164
|
+
return HTMLResponse(update)
|
|
165
|
+
elif func == FUNCTION_BILLS_REVIEW:
|
|
166
|
+
if request.method == 'GET':
|
|
167
|
+
return HTMLResponse(review_bills_page(request))
|
|
168
|
+
else:
|
|
169
|
+
form = await request.form()
|
|
170
|
+
status = form.get(STATUS)
|
|
171
|
+
return RedirectResponseGet(url=call_function(f"{FUNCTION_BILLS_SELECT}&status={status}"))
|
|
172
|
+
elif func == FUNCTION_BILLS_SEARCH:
|
|
173
|
+
author = request.query_params.get("author")
|
|
174
|
+
if request.method == 'GET':
|
|
175
|
+
return HTMLResponse(search_bills_page(request))
|
|
176
|
+
else:
|
|
177
|
+
form = await request.form()
|
|
178
|
+
search = form.get("search")
|
|
179
|
+
seek = form.get("seek")
|
|
180
|
+
reset = form.get("reset")
|
|
181
|
+
search = search_bills_page(request, search=search, reset=boolean(reset), seek=seek, author=author)
|
|
182
|
+
return HTMLResponse(search)
|
|
183
|
+
elif func == FUNCTION_BILLS_AUTHOR_SEARCH: ## hidden
|
|
184
|
+
if request.method == 'GET':
|
|
185
|
+
return HTMLResponse(search_bills_author_page(request))
|
|
186
|
+
else:
|
|
187
|
+
form = await request.form()
|
|
188
|
+
search = form.get("search")
|
|
189
|
+
seek = form.get("seek")
|
|
190
|
+
reset = form.get("reset")
|
|
191
|
+
search = search_bills_author_page(request, search=search, reset=boolean(reset), seek=seek)
|
|
192
|
+
return HTMLResponse(search)
|
|
193
|
+
elif func == FUNCTION_PAYOUT_INSERT:
|
|
194
|
+
if request.method == 'GET':
|
|
195
|
+
return HTMLResponse(insert_payout_page(request))
|
|
196
|
+
else:
|
|
197
|
+
form = await request.form()
|
|
198
|
+
dated = form.get(DATED) or nowdate()
|
|
199
|
+
form = inputfilter(dstrip(form), Payouts, excludes=[])
|
|
200
|
+
form[DATED] = dated
|
|
201
|
+
insert = insert_payout_page(request, **form)
|
|
202
|
+
if insert == CALLBACK: return RedirectResponseGet(url=call_function(FUNCTION_PAYOUTS_SELECT))
|
|
203
|
+
return HTMLResponse(insert)
|
|
204
|
+
elif func == FUNCTION_PAYOUTS_SELECT:
|
|
205
|
+
return HTMLResponse(select_payouts_page(request, **get_request_payout_options(request)))
|
|
206
|
+
elif func == FUNCTION_PAYOUT_SELECT:
|
|
207
|
+
payout_id = request.query_params.get("id")
|
|
208
|
+
return HTMLResponse(select_payout_page(request, payout_id))
|
|
209
|
+
elif func == FUNCTION_PAYOUT_UPDATE: ## hidden
|
|
210
|
+
payout_id = request.query_params.get("id")
|
|
211
|
+
if request.method == 'GET':
|
|
212
|
+
return HTMLResponse(update_payout_page(request, payout_id))
|
|
213
|
+
else:
|
|
214
|
+
form = await request.form()
|
|
215
|
+
form = inputfilter(dstrip(form), Payouts, includes=UPDATE_INPUTS[Payouts])
|
|
216
|
+
update = update_payout_page(request, payout_id, **form)
|
|
217
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_PAYOUT_SELECT}&id={payout_id}"))
|
|
218
|
+
return HTMLResponse(update)
|
|
219
|
+
elif func == FUNCTION_PAYOUT_UPGRADE:
|
|
220
|
+
payout_id = request.query_params.get("id")
|
|
221
|
+
if request.method == 'GET':
|
|
222
|
+
return HTMLResponse(upgrade_payout_page(request, payout_id))
|
|
223
|
+
else:
|
|
224
|
+
form = await request.form()
|
|
225
|
+
status = form.get(STATUS)
|
|
226
|
+
update = upgrade_payout_page(request, payout_id, status=status)
|
|
227
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_PAYOUT_SELECT}&id={payout_id}"))
|
|
228
|
+
return HTMLResponse(update)
|
|
229
|
+
elif func == FUNCTION_DRAFT_PAYOUTS_SELECT:
|
|
230
|
+
return HTMLResponse(select_draft_payouts_page(request, **get_request_payout_options(request)))
|
|
231
|
+
elif func == FUNCTION_NO_DRAFT_PAYOUTS_SELECT:
|
|
232
|
+
return HTMLResponse(select_no_draft_payouts_page(request, **get_request_payout_options(request)))
|
|
233
|
+
elif func == FUNCTION_PAYOUTS_REVIEW:
|
|
234
|
+
if request.method == 'GET':
|
|
235
|
+
return HTMLResponse(review_payouts_page(request))
|
|
236
|
+
else:
|
|
237
|
+
form = await request.form()
|
|
238
|
+
status = form.get(STATUS)
|
|
239
|
+
return RedirectResponseGet(url=call_function(f"{FUNCTION_PAYOUTS_SELECT}&status={status}"))
|
|
240
|
+
elif func == FUNCTION_TAX_COLLECT:
|
|
241
|
+
if request.method == 'GET':
|
|
242
|
+
return HTMLResponse(collect_tax_bills_page(request))
|
|
243
|
+
else:
|
|
244
|
+
form = await request.form()
|
|
245
|
+
dated = form.get(DATED) or first_date_of_current_month()
|
|
246
|
+
dated_end = form.get(DATED_END) or nowdate()
|
|
247
|
+
return HTMLResponse(collect_tax_bills_page(request, dated, dated_end))
|
|
248
|
+
else:
|
|
249
|
+
return HTMLResponse("Function not found or not accessible")
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from tagrepo.dataframe import _get_row_html, _get_row_dict
|
|
2
|
+
|
|
3
|
+
from invoiceum.data import *
|
|
4
|
+
|
|
5
|
+
from .syslogin import *
|
|
6
|
+
from .environ import *
|
|
7
|
+
from .schema import *
|
|
8
|
+
|
|
9
|
+
## data constants
|
|
10
|
+
|
|
11
|
+
BILL_INSERT_FIELDS = arrayfilter(INSERT_INPUTS[Bills], excludes=[])
|
|
12
|
+
BILLITEM_INSERT_FIELDS = arrayfilter(INSERT_INPUTS[Billitems], excludes=[BILL_ID, DATED, SUBTOTAL])
|
|
13
|
+
PAYOUT_INSERT_FIELDS = arrayfilter(INSERT_INPUTS[Payouts], excludes=[])
|
|
14
|
+
|
|
15
|
+
## selecting derivatives
|
|
16
|
+
|
|
17
|
+
def calculate_bill_amount(bill_id: str) -> float:
|
|
18
|
+
df = sql_simple_query_model("Billitems", select="quantity,price,subtotal", where=f"bill_id='{bill_id}'")
|
|
19
|
+
return 0 if df is None or not len(df) else numfloat((df['quantity'] * df['price']).sum())
|
|
20
|
+
|
|
21
|
+
def get_bill_name(bill_id: str) -> str:
|
|
22
|
+
return select_name_by_id(bill_id, model="Bills")
|
|
23
|
+
|
|
24
|
+
def get_bill(bill_id: str) -> dict:
|
|
25
|
+
return _get_row_dict("Bills", bill_id)
|
|
26
|
+
|
|
27
|
+
def select_pure_bill(bill_id: str) -> str:
|
|
28
|
+
return _get_row_html("Bills", bill_id)
|
|
29
|
+
|
|
30
|
+
def select_bill(bill_id: str) -> str:
|
|
31
|
+
return select_pure_row("Bills",
|
|
32
|
+
linkfields=SELECT_LINK_FIELDS[Bills],
|
|
33
|
+
linkrefs={CUSTOMER_ID: call_function(f"{FUNCTION_CUSTOMER_SELECT}&id=")},
|
|
34
|
+
where=f"id='{bill_id}'", limit=1)
|
|
35
|
+
|
|
36
|
+
def select_billitem(billitem_id: str) -> str:
|
|
37
|
+
return select_pure_row("Billitems",
|
|
38
|
+
linkfields=SELECT_LINK_FIELDS[Billitems],
|
|
39
|
+
linkrefs={BILL_ID: call_function(f"{FUNCTION_BILL_SELECT}&id="), PRODUCT_ID: call_function(f"{FUNCTION_PRODUCT_SELECT}&id=")},
|
|
40
|
+
where=f"id='{billitem_id}'", limit=1)
|
|
41
|
+
|
|
42
|
+
def get_billitem(billitem_id: str) -> dict:
|
|
43
|
+
return _get_row_dict("Billitems", billitem_id)
|
|
44
|
+
|
|
45
|
+
def bill_not_updatable(bill: any) -> bool:
|
|
46
|
+
if type(bill) is dict: return bill.get(STATUS) not in UPDATABLE_BILLS
|
|
47
|
+
return not check_record_exist(bill, model="Bills", field=ID, where=BILL_IS_UPDATABLE)
|
|
48
|
+
|
|
49
|
+
def bill_closed(bill: any) -> bool:
|
|
50
|
+
if type(bill) is dict: return bill.get(STATUS) in CLOSED_BILLS
|
|
51
|
+
return check_record_exist(bill, model="Bills", field=ID, where=BILL_CLOSED)
|
|
52
|
+
|
|
53
|
+
def bill_item_duplicated(name: str, bill_id: str) -> bool:
|
|
54
|
+
return check_record_exist(name, model="Billitems", field=NAME, where=f"bill_id='{bill_id}'")
|
|
55
|
+
|
|
56
|
+
def select_payout(payout_id: str) -> str:
|
|
57
|
+
return select_pure_row("Payouts",
|
|
58
|
+
linkfields=SELECT_LINK_FIELDS[Payouts],
|
|
59
|
+
linkrefs={BILL_ID: call_function(f"{FUNCTION_BILL_SELECT}&id=")},
|
|
60
|
+
where=f"id='{payout_id}'", limit=1)
|
|
61
|
+
|
|
62
|
+
def get_payout(payout_id: str) -> dict:
|
|
63
|
+
return _get_row_dict("Payouts", payout_id)
|
|
64
|
+
|
|
65
|
+
def payout_not_updatable(payout: any) -> bool:
|
|
66
|
+
if type(payout) is dict: return payout.get(STATUS) not in UPDATABLE_PAYOUTS
|
|
67
|
+
return not check_record_exist(payout, model="Payouts", field=ID, where=PAYOUT_IS_UPDATABLE)
|
|
68
|
+
|
|
69
|
+
def payout_closed(payout: any) -> bool:
|
|
70
|
+
if type(payout) is dict: return payout.get(STATUS) in CLOSED_PAYOUTS
|
|
71
|
+
return check_record_exist(payout, model="Payouts", field=ID, where=PAYOUT_CLOSED)
|
|
72
|
+
|
|
73
|
+
## listing derivatives
|
|
74
|
+
|
|
75
|
+
def list_bills(popindex: int=1, **kwargs) -> str:
|
|
76
|
+
model = "Bills"
|
|
77
|
+
show = LISTING_FIELDS[Bills][popindex:]
|
|
78
|
+
df = sql_simple_query_model(model, **kwargs)
|
|
79
|
+
return df_list_insert_rows(df, show,
|
|
80
|
+
header=LISTING_HEADERS[Bills][popindex:],
|
|
81
|
+
open_func=LISTING_LINK_BUTTON_FUNCTIONS[Bills][popindex:],
|
|
82
|
+
callfunction=LISTING_LINK_CALL_FUNCTIONS[Bills][popindex:],
|
|
83
|
+
idfield=LISTING_LINK_ID_FIELDS[Bills][popindex:],
|
|
84
|
+
linkfield=LISTING_LINK_REF_FIELDS[Bills][popindex:])
|
|
85
|
+
|
|
86
|
+
def list_bills_to_insert_billitem(**kwargs) -> str:
|
|
87
|
+
return list_bills(0, **kwargs) ## remove add_item
|
|
88
|
+
|
|
89
|
+
def list_billitems(bill_id: str) -> str:
|
|
90
|
+
return list_review_rows("Billitems",
|
|
91
|
+
show=LISTING_FIELDS[Billitems],
|
|
92
|
+
callfunction="select-billitem",
|
|
93
|
+
where=f"bill_id='{bill_id}'")
|
|
94
|
+
|
|
95
|
+
def list_pick_bills(**kwargs) -> str:
|
|
96
|
+
return list_select_rows(model="Bills", show=SELECT_LISTING_FIELDS[Bills], header=LISTING_HEADERS["Bills"], **kwargs) ## ambiguous "Bills"
|
|
97
|
+
|
|
98
|
+
def list_select_payouts(**kwargs) -> str:
|
|
99
|
+
return list_review_rows(model="Payouts", show=LISTING_FIELDS[Payouts], callfunction=FUNCTION_PAYOUT_SELECT, **kwargs)
|
|
100
|
+
|
|
101
|
+
list_payouts = list_select_payouts
|
|
102
|
+
|
|
103
|
+
def list_tax_bills(dated: str, dated_end: str, statuses: str=BILL_TAXABLE_STATUSES, join_clause="db_main.customer_id=db_sub.id") -> str: ## kept <db_main.customer_id> in <invoiceum.Invoices>
|
|
104
|
+
df = avro_sql_select_combo(model_main="Bills", model_sub="Customers",
|
|
105
|
+
select=f"db_main.id, db_main.dated, db_sub.name AS supplier, db_sub.{CUSTOMER_TAX_CODE} AS taxcode, db_main.tax, db_main.taxrate", ## updated <AS customer> in <invoiceum.Invoices>
|
|
106
|
+
where=f"{join_clause} AND db_main.status IN {statuses} AND db_main.dated BETWEEN '{dated}' AND '{dated_end}'",
|
|
107
|
+
sort="db_main.dated,db_sub.name", asc=True, limit=LIST_MAX_ROWS)
|
|
108
|
+
if df is None or not len(df): return NO_DATA_AVAILABLE
|
|
109
|
+
return df_list_review_rows(df, show=REVIEW_LISTING_FIELDS[Bills], callfunction=FUNCTION_BILL_SELECT)
|
|
110
|
+
|
|
111
|
+
## vector search
|
|
112
|
+
|
|
113
|
+
def get_search_bill_option(request, seek: str="", author: str="") -> dict:
|
|
114
|
+
return get_search_status_option(request=request, seek=seek, author=author, status_list=BILL_STATUS_LIST)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from invoiceum.environ import *
|
|
2
|
+
|
|
3
|
+
set_environ('CONFIRM_BILL_COMPLETED', 'Confirm: this bill was completed?')
|
|
4
|
+
set_environ('CONFIRM_BILL_CANCELLED', 'Confirm: this bill was cancelled?')
|
|
5
|
+
|
|
6
|
+
set_environ('WRONG_BILL_ID', 'Error: invalid bill id')
|
|
7
|
+
set_environ('BAD_BILL_ID', 'Error: invalid bill id, or bill is not updatable')
|
|
8
|
+
set_environ('BILL_ITEM_DUPLICATED', 'Error: bill line item is duplicated')
|
|
9
|
+
|
|
10
|
+
set_environ('BILL_NOT_EXIST', 'Warning: bill does not exist')
|
|
11
|
+
set_environ('BILL_NOT_EXIST_OR_CLOSED', 'Warning: bill does not exist or was closed')
|
|
12
|
+
set_environ('BILL_NO_REVERSE_TO_DRAFT', 'Warning: bill cannot be reversed to draft')
|
|
13
|
+
set_environ('BILL_ALREADY_EXISTS', 'Warning: bill with this name already exists')
|
|
14
|
+
|
|
15
|
+
set_environ('PAYOUT_ALREADY_EXISTS', 'Warning: payout with this name already exists')
|
|
16
|
+
set_environ('PAYOUT_NOT_EXIST_OR_CLOSED', 'Warning: payout does not exist or was closed')
|
|
17
|
+
set_environ('BAD_PAYOUT_ID', 'Error: invalid payout id, or payout is not updatable')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from invoiceum.param import *
|
|
2
|
+
|
|
3
|
+
from .schema import BILL_SORT_FIELDS, PAYOUT_SORT_FIELDS
|
|
4
|
+
|
|
5
|
+
def adjust_bill_listing_options(where: str, author: str, sort: str, asc: str, limit: str, request=None) -> tuple:
|
|
6
|
+
return adjust_listing_options(where, author, sort, asc, limit, sort_fields=BILL_SORT_FIELDS, request=request)
|
|
7
|
+
|
|
8
|
+
def adjust_payout_listing_options(where: str, author: str, sort: str, asc: str, limit: str, request=None) -> tuple:
|
|
9
|
+
return adjust_listing_options(where, author, sort, asc, limit, sort_fields=PAYOUT_SORT_FIELDS, request=request)
|
|
10
|
+
|
|
11
|
+
get_request_bill_options = get_request_data_options ## sort:DATED.desc
|
|
12
|
+
|
|
13
|
+
get_request_payout_options = get_request_data_options ## sort:DATED.desc
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from invoiceum.schema import *
|
|
2
|
+
|
|
3
|
+
from tagrepo.schema16 import * ## override <.15> things in <invoiceum>
|
|
4
|
+
|
|
5
|
+
from .syslogin import *
|
|
6
|
+
|
|
7
|
+
BILL_SORT_FIELDS = INVOICE_SORT_FIELDS
|
|
8
|
+
PAYOUT_SORT_FIELDS = PAYMENT_SORT_FIELDS
|
|
9
|
+
|
|
10
|
+
UPDATABLE_BILLS = UPDATABLE_INVOICES
|
|
11
|
+
CLOSED_BILLS = CLOSED_INVOICES
|
|
12
|
+
OPEN_BILLS = [BILL_RECEIVED, INVOICE_OVERDUE] ## USABLE
|
|
13
|
+
|
|
14
|
+
BILL_CLOSED = INVOICE_CLOSED
|
|
15
|
+
BILL_NOT_CLOSED = INVOICE_NOT_CLOSED
|
|
16
|
+
BILL_IS_LISTABLE = INVOICE_IS_LISTABLE
|
|
17
|
+
BILL_IS_UPDATABLE = INVOICE_IS_UPDATABLE
|
|
18
|
+
BILL_IS_USABLE = f"status IN ('{BILL_RECEIVED}', '{INVOICE_OVERDUE}')"
|
|
19
|
+
|
|
20
|
+
UPDATABLE_PAYOUTS = UPDATABLE_PAYMENTS
|
|
21
|
+
CLOSED_PAYOUTS = CLOSED_PAYMENTS
|
|
22
|
+
|
|
23
|
+
PAYOUT_CLOSED = PAYMENT_CLOSED
|
|
24
|
+
PAYOUT_NOT_CLOSED = PAYMENT_NOT_CLOSED
|
|
25
|
+
PAYOUT_IS_LISTABLE = PAYMENT_IS_LISTABLE
|
|
26
|
+
PAYOUT_IS_UPDATABLE = PAYMENT_IS_UPDATABLE
|
|
27
|
+
|
|
28
|
+
BILL_TAXABLE_STATUSES = f"('{BILL_RECEIVED}', '{INVOICE_OVERDUE}', '{INVOICE_PAID}')"
|
|
29
|
+
|
|
30
|
+
## complex data view: use with caution
|
|
31
|
+
|
|
32
|
+
REVIEW_LISTING_FIELDS |= { ## complex view
|
|
33
|
+
Bills: [HEADER_REVIEW, DATED, "supplier", "taxcode", TAXRATE, TAX], ## updated <invoiceum.Invoices>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
LISTING_LINK_BUTTON_FUNCTIONS |= { ## complex view
|
|
37
|
+
Bills: [add_button, view_button],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
LISTING_LINK_CALL_FUNCTIONS |= { ## complex view
|
|
41
|
+
Bills: [FUNCTION_BILLITEM_INSERT, FUNCTION_BILL_SELECT],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
LISTING_LINK_ID_FIELDS |= { ## complex view
|
|
45
|
+
Bills: [ID, ID],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
LISTING_LINK_REF_FIELDS |= { ## complex view
|
|
49
|
+
Bills: [BILL_ID, ID], ## add-item-line/view-item
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
## common data frame show
|
|
53
|
+
|
|
54
|
+
LISTING_FIELDS |= { ## dataset viewer: all columns
|
|
55
|
+
Bills: [HEADER_INSERT, HEADER_REVIEW, DATED, NAME, AMOUNT, DISCOUNT, DUE, TAXRATE, STATUS],
|
|
56
|
+
Billitems: [HEADER_REVIEW, NAME, QUANTITY, PRICE, SUBTOTAL],
|
|
57
|
+
Payouts: [HEADER_REVIEW, DATED, NAME, AMOUNT, TYPE, REF, STATUS],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
SELECT_LINK_FIELDS |= { ## single record viewer: vertical view with links
|
|
61
|
+
Bills: [CUSTOMER_ID],
|
|
62
|
+
Billitems: [BILL_ID, PRODUCT_ID], ## PRODUCT_ID maybe blank
|
|
63
|
+
Payouts: [BILL_ID],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
SELECT_LISTING_FIELDS |= { ## dataset viewer for popup picker
|
|
67
|
+
Bills: [HEADER_SELECT, NAME, STATUS, ID],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
LISTING_HEADERS |= { ## dataset viewer: headers of extra columns
|
|
71
|
+
Bills: [HEADER_INSERT, HEADER_REVIEW],
|
|
72
|
+
"Bills": HEADER_SELECT, ## popup picker
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
## common data input options
|
|
76
|
+
|
|
77
|
+
INSERT_INPUTS |= {
|
|
78
|
+
Bills: [DATED, DUE, CUSTOMER_ID, NAME, AMOUNT, DISCOUNT, TAXRATE, OFFER_ID, NOTE],
|
|
79
|
+
BillsStatus: [ID, STATUS],
|
|
80
|
+
Billitems: [BILL_ID, PRODUCT_ID, DATED, NAME, QUANTITY, PRICE, SUBTOTAL, NOTE], ## PRODUCT_ID maybe blank
|
|
81
|
+
Payouts: [DATED, BILL_ID, NAME, AMOUNT, TYPE, REF, NOTE],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
UPDATE_INPUTS |= {
|
|
85
|
+
Bills: [AUTHOR_ID, NOTE, AMOUNT, DISCOUNT, TAXRATE],
|
|
86
|
+
"Bills": [AUTHOR_ID, NOTE],
|
|
87
|
+
BillsStatus: [STATUS],
|
|
88
|
+
Billitems: [NAME, QUANTITY, PRICE, NOTE],
|
|
89
|
+
Payouts: [NAME, AUTHOR_ID, REF, TYPE, NOTE],
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
SEARCH_INPUTS |= {
|
|
93
|
+
Bills: [NAME, NOTE, AUTHOR_ID],
|
|
94
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from invoiceum.syslogin import *
|
|
2
|
+
|
|
3
|
+
FUNCTION_BILL_INSERT = 'insert-bill'
|
|
4
|
+
FUNCTION_BILL_SELECT = 'select-bill'
|
|
5
|
+
FUNCTION_BILL_NOTE_UPDATE = 'note-bill' ##RES
|
|
6
|
+
FUNCTION_BILL_UPDATE = 'update-bill' ##RES
|
|
7
|
+
FUNCTION_BILL_UPGRADE = 'upgrade-bill'
|
|
8
|
+
|
|
9
|
+
FUNCTION_BILLS_SELECT = 'select-bills'
|
|
10
|
+
FUNCTION_DRAFT_BILLS_SELECT = 'select-draft-bills'
|
|
11
|
+
FUNCTION_NO_DRAFT_BILLS_SELECT = 'select-no-draft-bills'
|
|
12
|
+
FUNCTION_BILLS_SEARCH = 'search-bills'
|
|
13
|
+
FUNCTION_BILLS_REVIEW = 'review-bills' ##HID
|
|
14
|
+
FUNCTION_BILLS_UPDATE = 'update-bills' ##HID
|
|
15
|
+
|
|
16
|
+
FUNCTION_BILLITEM_INSERT = 'insert-billitem'
|
|
17
|
+
FUNCTION_BILLITEM_SELECT = 'select-billitem'
|
|
18
|
+
FUNCTION_BILLITEM_UPDATE = 'update-billitem'
|
|
19
|
+
FUNCTION_BILLITEMS_SELECT = 'select-billitems'
|
|
20
|
+
|
|
21
|
+
FUNCTION_BILLS_AUTHOR_SEARCH = 'search-author-bills'
|
|
22
|
+
FUNCTION_BILLS_GLOBAL_SELECT = 'select-global-bills'
|
|
23
|
+
|
|
24
|
+
FUNCTION_PAYOUT_INSERT = 'insert-payout'
|
|
25
|
+
FUNCTION_PAYOUT_SELECT = 'select-payout'
|
|
26
|
+
FUNCTION_PAYOUT_UPDATE = 'update-payout'
|
|
27
|
+
FUNCTION_PAYOUT_UPGRADE = 'upgrade-payout' ## HID
|
|
28
|
+
FUNCTION_PAYOUTS_SELECT = 'select-payouts'
|
|
29
|
+
FUNCTION_PAYOUTS_REVIEW = 'review-payouts'
|
|
30
|
+
FUNCTION_PAYOUTS_SEARCH = 'search-payouts' ## HID
|
|
31
|
+
FUNCTION_DRAFT_PAYOUTS_SELECT = 'select-draft-payouts'
|
|
32
|
+
FUNCTION_NO_DRAFT_PAYOUTS_SELECT = 'select-no-draft-payouts'
|
|
33
|
+
|
|
34
|
+
FUNCTION_BILLS_POPUP = 'popup-bills'
|
|
35
|
+
|
|
36
|
+
auth_functions |= {
|
|
37
|
+
FUNCTION_BILL_INSERT: {"func": check_staff_login, "args": {}},
|
|
38
|
+
FUNCTION_BILL_SELECT: {"func": check_author_login, "args": {"model": "Bills", "id": ["id"]}},
|
|
39
|
+
FUNCTION_BILL_NOTE_UPDATE: {"func": check_author_login, "args": {"model": "Bills", "id": ["id"]}},
|
|
40
|
+
FUNCTION_BILL_UPDATE: {"func": check_author_login, "args": {"model": "Bills", "id": ["id"]}},
|
|
41
|
+
FUNCTION_BILL_UPGRADE: {"func": check_author_login, "args": {"model": "Bills", "id": ["id"]}},
|
|
42
|
+
FUNCTION_BILLS_SELECT: {"func": check_staff_login, "args": {}},
|
|
43
|
+
FUNCTION_DRAFT_BILLS_SELECT: {"func": check_staff_login, "args": {}},
|
|
44
|
+
FUNCTION_NO_DRAFT_BILLS_SELECT: {"func": check_staff_login, "args": {}},
|
|
45
|
+
FUNCTION_BILLS_SEARCH: {"func": check_staff_login, "args": {}},
|
|
46
|
+
FUNCTION_BILLS_REVIEW: {"func": check_staff_login, "args": {}},
|
|
47
|
+
FUNCTION_BILLS_UPDATE: {"func": check_staff_login, "args": {}},
|
|
48
|
+
FUNCTION_BILLITEM_INSERT: {"func": check_author_login, "args": {"model": "Bills", "id": ["bill_id"]}},
|
|
49
|
+
FUNCTION_BILLITEM_SELECT: {"func": check_staff_login, "args": {}},
|
|
50
|
+
FUNCTION_BILLITEM_UPDATE: {"func": check_supervisor_login, "args": {}},
|
|
51
|
+
FUNCTION_BILLITEMS_SELECT: {"func": check_author_login, "args": {"model": "Bills", "id": ["bill_id"]}},
|
|
52
|
+
FUNCTION_BILLS_AUTHOR_SEARCH: {"func": check_staff_login, "args": {}},
|
|
53
|
+
FUNCTION_BILLS_GLOBAL_SELECT: {"func": check_staff_login, "args": {}},
|
|
54
|
+
FUNCTION_PAYOUT_INSERT: {"func": check_finance_staff_login, "args": {}},
|
|
55
|
+
FUNCTION_PAYOUT_SELECT: {"func": [check_finance_staff_login, check_staff_login], "args": [{}, {}]},
|
|
56
|
+
FUNCTION_PAYOUT_UPDATE: {"func": check_finance_supervisor_login, "args": {}},
|
|
57
|
+
FUNCTION_PAYOUT_UPGRADE: {"func": check_finance_staff_login, "args": {}},
|
|
58
|
+
FUNCTION_PAYOUTS_SELECT: {"func": [check_finance_staff_login, check_staff_login], "args": [{}, {}]},
|
|
59
|
+
FUNCTION_PAYOUTS_REVIEW: {"func": [check_finance_staff_login, check_staff_login], "args": [{}, {}]},
|
|
60
|
+
FUNCTION_PAYOUTS_SEARCH: {"func": [check_finance_staff_login, check_staff_login], "args": [{}, {}]},
|
|
61
|
+
FUNCTION_DRAFT_PAYOUTS_SELECT: {"func": [check_finance_staff_login, check_staff_login], "args": [{}, {}]},
|
|
62
|
+
FUNCTION_NO_DRAFT_PAYOUTS_SELECT: {"func": [check_finance_staff_login, check_staff_login], "args": [{}, {}]},
|
|
63
|
+
FUNCTION_BILLS_POPUP: {"func": check_finance_staff_login, "args": {}},
|
|
64
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
from tagrepo.database import _log_insert, _log_update
|
|
2
|
+
|
|
3
|
+
from invoiceum.task import *
|
|
4
|
+
|
|
5
|
+
from .param import adjust_bill_listing_options, adjust_payout_listing_options
|
|
6
|
+
from .template import _selecting_page_, _picking_page_
|
|
7
|
+
|
|
8
|
+
from .data import *
|
|
9
|
+
from .template import *
|
|
10
|
+
|
|
11
|
+
def insert_bill_page(request, **form) -> str:
|
|
12
|
+
if not form.get(NAME):
|
|
13
|
+
return bill_creating_page()
|
|
14
|
+
elif not check_record_exist(form.get(CUSTOMER_ID), model="Customers", field=ID):
|
|
15
|
+
return WRONG_CUSTOMER_ID
|
|
16
|
+
elif check_record_exist(form[NAME], model="Bills"):
|
|
17
|
+
return BILL_ALREADY_EXISTS
|
|
18
|
+
elif not valid_percent(numfloat(form.get(DISCOUNT))) or not valid_percent(numfloat(form.get(TAXRATE))):
|
|
19
|
+
return BAD_INPUT_DATA
|
|
20
|
+
else:
|
|
21
|
+
form[AUTHOR_ID] = request.session.get(SESSION_USER)
|
|
22
|
+
refresh_form_data_tax(form)
|
|
23
|
+
insert = safe_avro_file_insert("Bills", multi=True, **form)
|
|
24
|
+
if not insert: return FAILED_SAVE_DATA
|
|
25
|
+
_log_insert(request, form, "Bills")
|
|
26
|
+
return CALLBACK
|
|
27
|
+
|
|
28
|
+
def select_bills_page(request, status: str="", sort: str="", asc: str="", limit: str="", author: str="") -> str:
|
|
29
|
+
where = BILL_NOT_CLOSED if status not in BILL_STATUS_LIST else f"status IN ('{status}')"
|
|
30
|
+
where, sort, asc, limit = adjust_bill_listing_options(where, author, sort, asc, limit, request=request)
|
|
31
|
+
return bills_listing_page(
|
|
32
|
+
title=BILLS_LISTING_TITLE,
|
|
33
|
+
action=f"{FUNCTION_BILLS_SELECT}&status={status}&author={request.session.get(SESSION_USER)}",
|
|
34
|
+
content=list_bills(where=where, sort=sort, asc=asc, limit=limit))
|
|
35
|
+
|
|
36
|
+
def select_draft_bills_page(request, status: str="", sort: str="", asc: str="", limit: str="", author: str="") -> str:
|
|
37
|
+
where = BILL_IS_UPDATABLE
|
|
38
|
+
where, sort, asc, limit = adjust_bill_listing_options(where, author, sort, asc, limit, request=request)
|
|
39
|
+
return draft_bills_listing_page(
|
|
40
|
+
title=DRAFT_BILLS_LISTING_TITLE,
|
|
41
|
+
action=f"{FUNCTION_DRAFT_BILLS_SELECT}&author={request.session.get(SESSION_USER)}",
|
|
42
|
+
content=list_bills_to_insert_billitem(where=where, sort=sort, asc=asc, limit=limit))
|
|
43
|
+
|
|
44
|
+
def select_no_draft_bills_page(request, status: str="", sort: str="", asc: str="", limit: str="", author: str="") -> str:
|
|
45
|
+
where = BILL_IS_LISTABLE
|
|
46
|
+
where, sort, asc, limit = adjust_bill_listing_options(where, author, sort, asc, limit, request=request)
|
|
47
|
+
return no_draft_bills_listing_page(
|
|
48
|
+
title=NO_DRAFT_BILLS_LISTING_TITLE,
|
|
49
|
+
action=f"{FUNCTION_NO_DRAFT_BILLS_SELECT}&author={request.session.get(SESSION_USER)}",
|
|
50
|
+
content=list_bills(where=where, sort=sort, asc=asc, limit=limit))
|
|
51
|
+
|
|
52
|
+
def select_bill_page(request, bill_id: str) -> str:
|
|
53
|
+
## <request> is reserved
|
|
54
|
+
return bill_selecting_page(
|
|
55
|
+
(select_bill(bill_id) or NO_DATA_AVAILABLE)
|
|
56
|
+
+ html_link_buttons([call_function(f"{FUNCTION_BILLITEM_INSERT}&bill_id={bill_id}")], [LABEL_CREATE_BILLITEM_BUTTON], [STYLE_SUBMIT_BUTTON])
|
|
57
|
+
+ html_link_buttons([call_function(f"{FUNCTION_BILLITEMS_SELECT}&bill_id={bill_id}")], [LABEL_SELECT_BILLITEM_BUTTON], [STYLE_SUBMIT_BUTTON])
|
|
58
|
+
+ html_link_buttons([call_function(f"{FUNCTION_BILL_UPGRADE}&id={bill_id}")], [LABEL_UPGRADE_BILL_BUTTON], [STYLE_CANCEL_BUTTON]))
|
|
59
|
+
|
|
60
|
+
def update_bill_note_page(request, bill_id: str, **form) -> str:
|
|
61
|
+
data = get_bill(bill_id)
|
|
62
|
+
if not data: return NO_DATA_AVAILABLE
|
|
63
|
+
if not len(form):
|
|
64
|
+
return bill_updating_page(
|
|
65
|
+
title=item_content_title(BILL_UPDATING_TITLE, data[NAME]),
|
|
66
|
+
action=f"{FUNCTION_BILL_NOTE_UPDATE}&id={bill_id}",
|
|
67
|
+
content=dictfilter(data, UPDATE_INPUTS["Bills"])) ## notes updated only
|
|
68
|
+
update = safe_avro_file_update("Bills", id=bill_id, **form)
|
|
69
|
+
if not update: return FAILED_SAVE_DATA
|
|
70
|
+
_log_update(request, form, "Bills", id=bill_id)
|
|
71
|
+
return CALLBACK
|
|
72
|
+
|
|
73
|
+
def update_bill_page(request, bill_id: str, **form) -> str:
|
|
74
|
+
data = get_bill(bill_id)
|
|
75
|
+
if not data: return NO_DATA_AVAILABLE
|
|
76
|
+
if bill_not_updatable(data): return BAD_BILL_ID
|
|
77
|
+
if not len(form):
|
|
78
|
+
return bill_updating_page(
|
|
79
|
+
title=item_content_title(BILL_UPDATING_TITLE, data[NAME]),
|
|
80
|
+
action=f"{FUNCTION_BILL_UPDATE}&id={bill_id}",
|
|
81
|
+
content=dictfilter(data, UPDATE_INPUTS[Bills]))
|
|
82
|
+
refresh_form_data_tax(form)
|
|
83
|
+
update = safe_avro_file_update("Bills", id=bill_id, **form)
|
|
84
|
+
if not update: return FAILED_SAVE_DATA
|
|
85
|
+
_log_update(request, form, "Bills", id=bill_id)
|
|
86
|
+
return CALLBACK
|
|
87
|
+
|
|
88
|
+
def upgrade_bill_page(request, bill_id: str, status: str=None) -> str:
|
|
89
|
+
if not status:
|
|
90
|
+
data = get_bill(bill_id)
|
|
91
|
+
if not data: return NO_DATA_AVAILABLE
|
|
92
|
+
return bill_upgrading_page(
|
|
93
|
+
title=item_content_title(BILL_UPDATING_TITLE, data[NAME]),
|
|
94
|
+
current_status=data[STATUS])
|
|
95
|
+
upgrade = update_close_bill_page(request, bill_id, complete_status=status)
|
|
96
|
+
return upgrade if upgrade not in [DONE_SAVE_DATA] else CALLBACK
|
|
97
|
+
|
|
98
|
+
def update_close_bill_page(request, bill_id: str, complete_status: str=None) -> str:
|
|
99
|
+
data = get_bill(bill_id)
|
|
100
|
+
if not data: return NO_DATA_AVAILABLE
|
|
101
|
+
if bill_closed(data): return BILL_NOT_EXIST_OR_CLOSED
|
|
102
|
+
if complete_status not in BILL_STATUS_LIST:
|
|
103
|
+
status = BILL_CANCELLED
|
|
104
|
+
else:
|
|
105
|
+
status = complete_status
|
|
106
|
+
if status in UPDATABLE_BILLS: return BILL_NO_REVERSE_TO_DRAFT
|
|
107
|
+
amount = calculate_bill_amount(bill_id=bill_id)
|
|
108
|
+
data[AMOUNT] = amount
|
|
109
|
+
refresh_form_data_tax(data)
|
|
110
|
+
tax = data[TAX]
|
|
111
|
+
update = safe_avro_file_update("Bills", id=bill_id, status=status, amount=amount, tax=tax)
|
|
112
|
+
if not update: return FAILED_SAVE_DATA
|
|
113
|
+
insert = safe_avro_file_insert("BillsStatus", multi=False, id=bill_id, status=status)
|
|
114
|
+
if not insert: return FAILED_COMMIT_TRANSACTION
|
|
115
|
+
return DONE_SAVE_DATA ## dont edit this return
|
|
116
|
+
|
|
117
|
+
## short form of <orderum.select_product_page>
|
|
118
|
+
def select_product_page(request, product_id: str) -> str:
|
|
119
|
+
return product_selecting_page(select_product(product_id) or NO_DATA_AVAILABLE)
|
|
120
|
+
|
|
121
|
+
def insert_billitem_page(request, bill_id: str, **form) -> str:
|
|
122
|
+
if bill_not_updatable(bill_id): return BAD_BILL_ID
|
|
123
|
+
if not form.get(NAME):
|
|
124
|
+
bill_name = get_bill_name(bill_id)
|
|
125
|
+
return billitem_creating_page(
|
|
126
|
+
title=sub_item_content_title(BILLITEMS_CREATING_TITLE, bill_name, f"{FUNCTION_BILL_SELECT}&id={bill_id}"),
|
|
127
|
+
action=f"{FUNCTION_BILLITEM_INSERT}&bill_id={bill_id}")
|
|
128
|
+
elif bill_item_duplicated(form.get(NAME), bill_id):
|
|
129
|
+
return BILL_ITEM_DUPLICATED
|
|
130
|
+
else:
|
|
131
|
+
form[BILL_ID] = bill_id
|
|
132
|
+
refresh_form_data_subtotal(form)
|
|
133
|
+
insert = safe_avro_file_insert("Billitems", multi=True, **form)
|
|
134
|
+
if not insert: return FAILED_SAVE_DATA
|
|
135
|
+
_log_insert(request, form, "Billitems")
|
|
136
|
+
return CALLBACK
|
|
137
|
+
|
|
138
|
+
def select_billitems_page(request, bill_id: str) -> str:
|
|
139
|
+
## <request> is reserved
|
|
140
|
+
if valid_uuid(bill_id):
|
|
141
|
+
bill_name = get_bill_name(bill_id)
|
|
142
|
+
if not bill_name: return BILL_NOT_EXIST
|
|
143
|
+
return billitems_listing_page(
|
|
144
|
+
title=sub_item_content_title(BILLITEMS_LISTING_TITLE, bill_name, f"{FUNCTION_BILL_SELECT}&id={bill_id}"),
|
|
145
|
+
action=f"{FUNCTION_BILLITEM_INSERT}&bill_id={bill_id}",
|
|
146
|
+
content=list_billitems(bill_id))
|
|
147
|
+
return MISSING_INPUT_DATA
|
|
148
|
+
|
|
149
|
+
def select_billitem_page(request, billitem_id: str) -> str:
|
|
150
|
+
## <request> is reserved
|
|
151
|
+
return billitem_selecting_page(select_billitem(billitem_id) or NO_DATA_AVAILABLE)
|
|
152
|
+
|
|
153
|
+
def update_billitem_page(request, billitem_id: str, **form) -> str:
|
|
154
|
+
data = get_billitem(billitem_id)
|
|
155
|
+
if not data: return NO_DATA_AVAILABLE
|
|
156
|
+
if bill_not_updatable(data[BILL_ID]): return BAD_BILL_ID
|
|
157
|
+
if not len(form):
|
|
158
|
+
bill_name = get_bill_name(data[BILL_ID])
|
|
159
|
+
return billitem_updating_page(
|
|
160
|
+
title=sub_item_content_title(BILLITEM_UPDATING_TITLE, bill_name, f"{FUNCTION_BILL_SELECT}&id={data[BILL_ID]}"),
|
|
161
|
+
action=f"{FUNCTION_BILLITEM_UPDATE}&id={billitem_id}",
|
|
162
|
+
content=dictfilter(data, UPDATE_INPUTS[Billitems]))
|
|
163
|
+
refresh_form_data_subtotal(form)
|
|
164
|
+
update = safe_avro_file_update("Billitems", id=billitem_id, **form)
|
|
165
|
+
if not update: return FAILED_SAVE_DATA
|
|
166
|
+
_log_update(request, form, "Billitems", id=billitem_id)
|
|
167
|
+
return CALLBACK
|
|
168
|
+
|
|
169
|
+
def review_bills_page(request) -> str:
|
|
170
|
+
## <request> is reserved
|
|
171
|
+
return bills_reviewing_page(BILLS_LISTING_TITLE)
|
|
172
|
+
|
|
173
|
+
def search_bills_page(request, search: str=None, reset=False, seek: str="", author: str="") -> str:
|
|
174
|
+
if not search:
|
|
175
|
+
return bills_searching_page()
|
|
176
|
+
return bills_fetching_page(request, search, reset=reset, seek=seek, author=author)
|
|
177
|
+
|
|
178
|
+
def search_bills_author_page(request, search: str=None, reset=False, seek: str="") -> str:
|
|
179
|
+
if not search:
|
|
180
|
+
return bills_author_searching_page()
|
|
181
|
+
return bills_author_fetching_page(request, search, reset=reset, seek=seek)
|
|
182
|
+
|
|
183
|
+
def insert_payout_page(request, **form) -> str:
|
|
184
|
+
if not form.get(NAME) or numfloat(form.get(AMOUNT)) <= 0:
|
|
185
|
+
return payout_creating_page()
|
|
186
|
+
elif not check_record_exist(form.get(BILL_ID), model="Bills", field=ID):
|
|
187
|
+
return WRONG_BILL_ID
|
|
188
|
+
elif check_record_exist(form[NAME], model="Payouts"):
|
|
189
|
+
return PAYOUT_ALREADY_EXISTS
|
|
190
|
+
else:
|
|
191
|
+
form[AUTHOR_ID] = request.session.get(SESSION_USER)
|
|
192
|
+
insert = safe_avro_file_insert("Payouts", multi=True, **form)
|
|
193
|
+
if not insert: return FAILED_SAVE_DATA
|
|
194
|
+
_log_insert(request, form, "Payouts")
|
|
195
|
+
return CALLBACK
|
|
196
|
+
|
|
197
|
+
def compact_select_bills_page() -> str:
|
|
198
|
+
return _selecting_page_(BILLS_LISTING_TITLE,
|
|
199
|
+
list_pick_bills(where=BILL_IS_USABLE, sort=NAME, limit=LIST_MAX_ROWS)
|
|
200
|
+
+ select_bill_button())
|
|
201
|
+
|
|
202
|
+
def compact_fetch_bills_page(search: str) -> str:
|
|
203
|
+
return _selecting_page_(BILLS_SEARCHING_TITLE,
|
|
204
|
+
vector_select_search(search, SEARCH_INPUTS, SEARCH_RESULTS, model="Bills")
|
|
205
|
+
+ select_bill_button())
|
|
206
|
+
|
|
207
|
+
def popup_bills_page(request, search: str) -> str:
|
|
208
|
+
## <request> is reserved
|
|
209
|
+
if search: return compact_fetch_bills_page(search)
|
|
210
|
+
return compact_select_bills_page()
|
|
211
|
+
|
|
212
|
+
def select_payouts_page(request, status: str="", sort: str="", asc: str="", limit: str="", author: str="") -> str:
|
|
213
|
+
where = PAYOUT_NOT_CLOSED if status not in PAYOUT_STATUS_LIST else f"status IN ('{status}')"
|
|
214
|
+
where, sort, asc, limit = adjust_payout_listing_options(where, author, sort, asc, limit, request=request)
|
|
215
|
+
return payouts_listing_page(
|
|
216
|
+
title=PAYOUTS_LISTING_TITLE,
|
|
217
|
+
action=f"{FUNCTION_PAYOUTS_SELECT}&status={status}&author={request.session.get(SESSION_USER)}",
|
|
218
|
+
content=list_payouts(where=where, sort=sort, asc=asc, limit=limit))
|
|
219
|
+
|
|
220
|
+
def select_payout_page(request, payout_id: str) -> str:
|
|
221
|
+
## <request> is reserved
|
|
222
|
+
return payout_selecting_page(
|
|
223
|
+
(select_payout(payout_id) or NO_DATA_AVAILABLE)
|
|
224
|
+
+ html_link_buttons([call_function(f"{FUNCTION_PAYOUT_UPGRADE}&id={payout_id}")], [LABEL_UPGRADE_PAYOUT_BUTTON], [STYLE_CANCEL_BUTTON]))
|
|
225
|
+
|
|
226
|
+
def update_payout_page(request, payout_id: str, **form) -> str:
|
|
227
|
+
data = get_payout(payout_id)
|
|
228
|
+
if not data: return NO_DATA_AVAILABLE
|
|
229
|
+
if payout_not_updatable(data): return BAD_PAYOUT_ID
|
|
230
|
+
if not len(form):
|
|
231
|
+
return payout_updating_page(
|
|
232
|
+
title=item_content_title(PAYOUT_UPDATING_TITLE, data[NAME]),
|
|
233
|
+
action=f"{FUNCTION_PAYOUT_UPDATE}&id={payout_id}",
|
|
234
|
+
content=dictfilter(data, UPDATE_INPUTS[Payouts]))
|
|
235
|
+
update = safe_avro_file_update("Payouts", id=payout_id, **form)
|
|
236
|
+
if not update: return FAILED_SAVE_DATA
|
|
237
|
+
_log_update(request, form, "Payouts", id=payout_id)
|
|
238
|
+
return CALLBACK
|
|
239
|
+
|
|
240
|
+
def upgrade_payout_page(request, payout_id: str, status: str=None) -> str:
|
|
241
|
+
if not status:
|
|
242
|
+
data = get_payout(payout_id)
|
|
243
|
+
if not data: return NO_DATA_AVAILABLE
|
|
244
|
+
return payout_upgrading_page(
|
|
245
|
+
title=item_content_title(PAYOUT_UPDATING_TITLE, data[NAME]),
|
|
246
|
+
current_status=data[STATUS])
|
|
247
|
+
upgrade = update_close_payout_page(request, payout_id, complete_status=status)
|
|
248
|
+
return upgrade if upgrade not in [DONE_SAVE_DATA] else CALLBACK
|
|
249
|
+
|
|
250
|
+
def update_close_payout_page(request, payout_id: str, complete_status: str=None) -> str:
|
|
251
|
+
data = get_payout(payout_id)
|
|
252
|
+
if not data: return NO_DATA_AVAILABLE
|
|
253
|
+
if payout_closed(data): return PAYOUT_NOT_EXIST_OR_CLOSED
|
|
254
|
+
if complete_status not in PAYOUT_STATUS_LIST:
|
|
255
|
+
status = PAYOUT_CANCELLED
|
|
256
|
+
else:
|
|
257
|
+
status = complete_status
|
|
258
|
+
update = safe_avro_file_update("Payouts", id=payout_id, status=status)
|
|
259
|
+
if not update: return FAILED_SAVE_DATA
|
|
260
|
+
insert = safe_avro_file_insert("PayoutsStatus", multi=False, id=payout_id, status=status)
|
|
261
|
+
if not insert: return FAILED_COMMIT_TRANSACTION
|
|
262
|
+
return DONE_SAVE_DATA ## dont edit this return
|
|
263
|
+
|
|
264
|
+
def popup_type_page(request) -> str:
|
|
265
|
+
## <request> is reserved
|
|
266
|
+
return compact_select_type_page(TYPE_PICKING_TITLE, LABEL_TYPE_PICKING_BUTTON)
|
|
267
|
+
|
|
268
|
+
def compact_select_type_page(title, pick_button, picker=TYPE, pick_data=PAYOUT_TYPE_LIST, opener_getter="") -> str:
|
|
269
|
+
data = escape_punctuation_for_title_list(pick_data)
|
|
270
|
+
## module <tagrepo.datatemp> must be upgraded to use <schema15> libs
|
|
271
|
+
return _picking_page_(title, data, pick_button, picker=picker, getter=opener_getter)
|
|
272
|
+
|
|
273
|
+
def select_draft_payouts_page(request, status: str="", sort: str="", asc: str="", limit: str="", author: str="") -> str:
|
|
274
|
+
where = PAYOUT_IS_UPDATABLE
|
|
275
|
+
where, sort, asc, limit = adjust_payout_listing_options(where, author, sort, asc, limit, request=request)
|
|
276
|
+
return draft_payouts_listing_page(
|
|
277
|
+
title=DRAFT_PAYOUTS_LISTING_TITLE,
|
|
278
|
+
action=f"{FUNCTION_DRAFT_PAYOUTS_SELECT}&author={request.session.get(SESSION_USER)}",
|
|
279
|
+
content=list_payouts(where=where, sort=sort, asc=asc, limit=limit))
|
|
280
|
+
|
|
281
|
+
def select_no_draft_payouts_page(request, status: str="", sort: str="", asc: str="", limit: str="", author: str="") -> str:
|
|
282
|
+
where = PAYOUT_IS_LISTABLE
|
|
283
|
+
where, sort, asc, limit = adjust_payout_listing_options(where, author, sort, asc, limit, request=request)
|
|
284
|
+
return no_draft_payouts_listing_page(
|
|
285
|
+
title=NO_DRAFT_PAYOUTS_LISTING_TITLE,
|
|
286
|
+
action=f"{FUNCTION_NO_DRAFT_PAYOUTS_SELECT}&author={request.session.get(SESSION_USER)}",
|
|
287
|
+
content=list_payouts(where=where, sort=sort, asc=asc, limit=limit))
|
|
288
|
+
|
|
289
|
+
def review_payouts_page(request) -> str:
|
|
290
|
+
## <request> is reserved
|
|
291
|
+
return payouts_reviewing_page(PAYOUTS_LISTING_TITLE)
|
|
292
|
+
|
|
293
|
+
def _date_range_fieldset(labels: list=[LABEL_TAX_START, LABEL_TAX_END], legend: str=LABEL_TAX_RANGE_INPUT_PARAMS) -> str:
|
|
294
|
+
return html_input_date_range_fieldset(DATED, DATED, labels=labels, legend=legend)
|
|
295
|
+
|
|
296
|
+
def collect_tax_bills_page(request, dated: str=None, dated_end: str=None) -> str:
|
|
297
|
+
## <request> is reserved
|
|
298
|
+
if not dated: return tax_bills_collecting_open_page(_date_range_fieldset())
|
|
299
|
+
return tax_bills_collecting_page(list_tax_bills(dated=dated, dated_end=dated_end))
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from tagrepo.datatemp import _creating_page, _selecting_page, _listing_page, _updating_page, _searching_page, _upgrading_page, _reviewing_page, _fetching_page, _picking_page
|
|
2
|
+
|
|
3
|
+
from invoiceum.template import *
|
|
4
|
+
|
|
5
|
+
from .data import *
|
|
6
|
+
from .title import *
|
|
7
|
+
|
|
8
|
+
## private functions
|
|
9
|
+
|
|
10
|
+
def _upgrading_page_(title, current_status, status_id="BILL_STATUS_LIST") -> str:
|
|
11
|
+
return _upgrading_page(title, current_status, status_id, app_desc=APP_DESC)
|
|
12
|
+
|
|
13
|
+
def _reviewing_page_(title, status_id="BILL_STATUS_LIST") -> str:
|
|
14
|
+
return _reviewing_page(title, status_id=status_id, app_desc=APP_DESC)
|
|
15
|
+
|
|
16
|
+
def _fetching_page_(request, search: str, reset: bool=False, title: str=BILLS_SEARCHING_TITLE, model: str="Bills", callfunction: str=FUNCTION_BILL_SELECT, seek: str="", app_desc: str=APP_DESC, search_inputs: dict=SEARCH_INPUTS, search_results: int=SEARCH_RESULTS, search_option=get_search_author_option) -> str:
|
|
17
|
+
return _fetching_page(request, search, reset=reset, title=title, model=model, callfunction=callfunction, seek=seek, app_desc=app_desc, search_inputs=search_inputs, search_results=search_results, search_option=search_option)
|
|
18
|
+
|
|
19
|
+
def _selecting_page_(title, content) -> str:
|
|
20
|
+
return _selecting_page(title, content, app_desc=APP_DESC)
|
|
21
|
+
|
|
22
|
+
def _listing_page_(title, action, content, submit_button) -> str:
|
|
23
|
+
return _listing_page(title, action, content, submit_button, app_desc=APP_DESC)
|
|
24
|
+
|
|
25
|
+
def _updating_page_(title, action, content: dict) -> str:
|
|
26
|
+
return _updating_page(title, action, content, app_desc=APP_DESC)
|
|
27
|
+
|
|
28
|
+
def _searching_page_(title, search_id="AUTHOR_SEARCH_OPTIONS") -> str:
|
|
29
|
+
return _searching_page(title, search_id, app_desc=APP_DESC)
|
|
30
|
+
|
|
31
|
+
def _picking_page_(title: str, content: list[dict], pick_button: str, picker: str, getter=NAME) -> str:
|
|
32
|
+
return _picking_page(title=title, content=content, pick_button=pick_button, picker=picker, getter=getter, app_desc=APP_DESC)
|
|
33
|
+
|
|
34
|
+
## public functions
|
|
35
|
+
|
|
36
|
+
def bill_creating_page() -> str:
|
|
37
|
+
return _creating_page(
|
|
38
|
+
title=BILLS_CREATING_TITLE,
|
|
39
|
+
action=FUNCTION_BILL_INSERT,
|
|
40
|
+
model="Bills",
|
|
41
|
+
input_label=LABEL_BILL_INPUT_PARAMS,
|
|
42
|
+
input_fields=BILL_INSERT_FIELDS,
|
|
43
|
+
app_desc=APP_DESC,
|
|
44
|
+
onsubmit=html_form_onsubmit(ARE_YOU_SURE),
|
|
45
|
+
customer_id=html_popup_input_wide_text(CUSTOMER_ID, CUSTOMER_ID, "customer_id", label=LABEL_BILL_CUSTOMER_ID, popup_page=FUNCTION_CUSTOMERS_POPUP),
|
|
46
|
+
dated=html_input_date(DATED, DATED, LABEL_BILL_DATED),
|
|
47
|
+
due=html_input_date(DUE, DUE, LABEL_BILL_DUE),
|
|
48
|
+
note=html_input_textarea(NOTE, NOTE))
|
|
49
|
+
|
|
50
|
+
def bills_listing_page(title: str, action: str, content: str, label: str=LABEL_SELECT_OWN_BILL_BUTTON) -> str:
|
|
51
|
+
return _listing_page_(title, action, content, submit_button=label)
|
|
52
|
+
|
|
53
|
+
draft_bills_listing_page = bills_listing_page
|
|
54
|
+
no_draft_bills_listing_page = bills_listing_page
|
|
55
|
+
|
|
56
|
+
def bill_selecting_page(content: str) -> str:
|
|
57
|
+
return _selecting_page_(title=BILL_INFO_TITLE, content=content)
|
|
58
|
+
|
|
59
|
+
bill_updating_page = _updating_page_
|
|
60
|
+
|
|
61
|
+
def bill_upgrading_page(title: str, current_status: str) -> str:
|
|
62
|
+
return _upgrading_page_(title=title, current_status=current_status, status_id="BILL_STATUS_LIST")
|
|
63
|
+
|
|
64
|
+
def billitem_creating_page(title: str, action: str) -> str:
|
|
65
|
+
return _creating_page(
|
|
66
|
+
title=title,
|
|
67
|
+
action=action,
|
|
68
|
+
model="Billitems",
|
|
69
|
+
input_label=LABEL_BILLITEM_INPUT_PARAMS,
|
|
70
|
+
input_fields=BILLITEM_INSERT_FIELDS,
|
|
71
|
+
app_desc=APP_DESC,
|
|
72
|
+
product_id=html_popup_input_wide_text(PRODUCT_ID, PRODUCT_ID, "product_id", label=LABEL_BILLITEM_PRODUCT_ID, popup_page=FUNCTION_PRODUCTS_POPUP),
|
|
73
|
+
note=html_input_textarea(NOTE, NOTE))
|
|
74
|
+
|
|
75
|
+
def billitems_listing_page(title: str, action: str, content: str, label: str=LABEL_CREATE_BILLITEM_BUTTON) -> str:
|
|
76
|
+
return _listing_page_(title, action, content, submit_button=label)
|
|
77
|
+
|
|
78
|
+
def billitem_selecting_page(content: str) -> str:
|
|
79
|
+
return _selecting_page_(title=BILLITEM_INFO_TITLE, content=content)
|
|
80
|
+
|
|
81
|
+
billitem_updating_page = _updating_page_
|
|
82
|
+
|
|
83
|
+
def bills_reviewing_page(title: str) -> str:
|
|
84
|
+
return _reviewing_page_(title)
|
|
85
|
+
|
|
86
|
+
## vector search
|
|
87
|
+
|
|
88
|
+
def bills_searching_page() -> str:
|
|
89
|
+
return _searching_page_(title=BILLS_SEARCHING_TITLE, search_id="BILL_SEARCH_OPTIONS")
|
|
90
|
+
|
|
91
|
+
def bills_fetching_page(request, search: str, reset: bool=False, seek: str="", author: str="") -> str:
|
|
92
|
+
return _fetching_page_(request, search, reset=reset, seek=seek, search_option=get_search_bill_option(request, seek, author=author))
|
|
93
|
+
|
|
94
|
+
def bills_author_searching_page() -> str:
|
|
95
|
+
return _searching_page_(title=BILLS_SEARCHING_TITLE)
|
|
96
|
+
|
|
97
|
+
def bills_author_fetching_page(request, search: str, reset: bool=False, seek: str="") -> str:
|
|
98
|
+
return _fetching_page_(request, search, reset=reset, seek=seek, title=BILLS_SEARCHING_TITLE, model="Bills", callfunction=FUNCTION_BILL_SELECT)
|
|
99
|
+
|
|
100
|
+
## bill payouts
|
|
101
|
+
|
|
102
|
+
def payout_creating_page() -> str:
|
|
103
|
+
return _creating_page(
|
|
104
|
+
title=PAYOUTS_CREATING_TITLE,
|
|
105
|
+
action=FUNCTION_PAYOUT_INSERT,
|
|
106
|
+
model="Payouts",
|
|
107
|
+
input_label=LABEL_PAYOUT_INPUT_PARAMS,
|
|
108
|
+
input_fields=PAYOUT_INSERT_FIELDS,
|
|
109
|
+
app_desc=APP_DESC,
|
|
110
|
+
onsubmit=html_form_onsubmit(ARE_YOU_SURE),
|
|
111
|
+
bill_id=html_popup_input_wide_text(BILL_ID, BILL_ID, "bill_id", label=LABEL_PAYOUT_BILL_ID, popup_page=FUNCTION_BILLS_POPUP),
|
|
112
|
+
type=html_select_options(TYPE, PAYOUT_TYPES, list_captioning("PAYOUT_TYPES")),
|
|
113
|
+
dated=html_input_date(DATED, DATED, LABEL_PAYOUT_DATED),
|
|
114
|
+
note=html_input_textarea(NOTE, NOTE))
|
|
115
|
+
|
|
116
|
+
def select_bill_button() -> str:
|
|
117
|
+
return html_pass_buttons([BILL_ID], [ID], [LABEL_SELECT_BILL_BUTTON], [STYLE_SUBMIT_BUTTON])
|
|
118
|
+
|
|
119
|
+
def payouts_listing_page(title: str, action: str, content: str, label: str=LABEL_SELECT_OWN_PAYOUT_BUTTON) -> str:
|
|
120
|
+
return _listing_page_(title, action, content, submit_button=label)
|
|
121
|
+
|
|
122
|
+
def payout_selecting_page(content: str) -> str:
|
|
123
|
+
return _selecting_page_(title=PAYOUT_INFO_TITLE, content=content)
|
|
124
|
+
|
|
125
|
+
payout_updating_page = _updating_page_
|
|
126
|
+
|
|
127
|
+
def payout_upgrading_page(title: str, current_status: str) -> str:
|
|
128
|
+
return _upgrading_page_(title=title, current_status=current_status, status_id="PAYOUT_STATUS_LIST")
|
|
129
|
+
|
|
130
|
+
draft_payouts_listing_page = payouts_listing_page
|
|
131
|
+
no_draft_payouts_listing_page = payouts_listing_page
|
|
132
|
+
|
|
133
|
+
def payouts_reviewing_page(title: str) -> str:
|
|
134
|
+
return _reviewing_page_(title, status_id="PAYOUT_STATUS_LIST")
|
|
135
|
+
|
|
136
|
+
def tax_bills_collecting_open_page(content: str) -> str:
|
|
137
|
+
return _listing_page_(BILLS_TAX_LISTING_TITLE, FUNCTION_TAX_COLLECT, content, LABEL_SUBMIT_BUTTON)
|
|
138
|
+
|
|
139
|
+
def tax_bills_collecting_page(content: str) -> str:
|
|
140
|
+
return _selecting_page_(title=BILLS_TAX_TITLE, content=content)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from invoiceum.title import *
|
|
2
|
+
|
|
3
|
+
APP_DESC = 'Literp Billing Management'
|
|
4
|
+
|
|
5
|
+
set_environ('FORM_TITLE', 'Billing Management')
|
|
6
|
+
|
|
7
|
+
set_environ('LABEL_BILL_INSERT', 'Add Bill')
|
|
8
|
+
set_environ('LABEL_BILLS_SELECT', 'List Bills')
|
|
9
|
+
set_environ('LABEL_DRAFT_BILLS_SELECT', 'Fill Bills')
|
|
10
|
+
set_environ('LABEL_NO_DRAFT_BILLS_SELECT', 'Browse Bills')
|
|
11
|
+
set_environ('LABEL_BILLS_REVIEW', 'Review Bills')
|
|
12
|
+
set_environ('LABEL_PAYOUT_INSERT', 'Add Payout')
|
|
13
|
+
set_environ('LABEL_PAYOUTS_SELECT', 'List Payouts')
|
|
14
|
+
set_environ('LABEL_DRAFT_PAYOUTS_SELECT', 'Approve Payouts')
|
|
15
|
+
set_environ('LABEL_NO_DRAFT_PAYOUTS_SELECT', 'Browse Payouts')
|
|
16
|
+
set_environ('LABEL_PAYOUTS_REVIEW', 'Review Payouts')
|
|
17
|
+
set_environ('LABEL_TAX_COLLECT', 'Taxes Deductible') ## updated <invoiceum>
|
|
18
|
+
set_environ('LABEL_BILLS_SEARCH', 'Bills')
|
|
19
|
+
|
|
20
|
+
set_environ('LABEL_UPGRADE_BILL_BUTTON', 'Update Bill Status')
|
|
21
|
+
set_environ('LABEL_SELECT_OWN_BILL_BUTTON', 'List My Bills')
|
|
22
|
+
set_environ('LABEL_CREATE_BILLITEM_BUTTON', 'Add Bill Item')
|
|
23
|
+
set_environ('LABEL_SELECT_BILLITEM_BUTTON', 'List Bill Items')
|
|
24
|
+
set_environ('LABEL_SELECT_BILL_BUTTON', 'Pick Selected')
|
|
25
|
+
|
|
26
|
+
set_environ('LABEL_BILL_INPUT_PARAMS', 'Bill metadata')
|
|
27
|
+
set_environ('LABEL_BILL_CUSTOMER_ID', 'Customer ID')
|
|
28
|
+
set_environ('LABEL_BILL_DATED', 'Issue Date')
|
|
29
|
+
set_environ('LABEL_BILL_DUE', 'Due Date')
|
|
30
|
+
set_environ('LABEL_BILLITEM_PRODUCT_ID', 'Product ID')
|
|
31
|
+
set_environ('LABEL_BILLITEM_INPUT_PARAMS', 'Bill item info')
|
|
32
|
+
|
|
33
|
+
set_environ('BILLS_CREATING_TITLE', 'Bills Creating')
|
|
34
|
+
set_environ('BILLS_LISTING_TITLE', 'Bills Listing')
|
|
35
|
+
set_environ('DRAFT_BILLS_LISTING_TITLE', 'Draft Bills Listing')
|
|
36
|
+
set_environ('NO_DRAFT_BILLS_LISTING_TITLE', 'Live Bills Listing')
|
|
37
|
+
|
|
38
|
+
set_environ('BILLS_SEARCHING_TITLE', 'Bills Searching')
|
|
39
|
+
set_environ('BILL_INFO_TITLE', 'Bill Detailed Information')
|
|
40
|
+
set_environ('BILL_UPDATING_TITLE', 'Bill Info Update')
|
|
41
|
+
|
|
42
|
+
set_environ('BILLITEMS_CREATING_TITLE', 'Bill Items Creating')
|
|
43
|
+
set_environ('BILLITEMS_LISTING_TITLE', 'Bill Items Listing')
|
|
44
|
+
set_environ('BILLITEM_INFO_TITLE', 'Bill Item Information')
|
|
45
|
+
set_environ('BILLITEM_UPDATING_TITLE', 'Bill Item Update')
|
|
46
|
+
|
|
47
|
+
set_environ('LABEL_PAYOUT_INPUT_PARAMS', 'Payout metadata')
|
|
48
|
+
set_environ('LABEL_PAYOUT_BILL_ID', 'Bill ID')
|
|
49
|
+
set_environ('LABEL_PAYOUT_DATED', 'Payout Date')
|
|
50
|
+
|
|
51
|
+
set_environ('LABEL_UPGRADE_PAYOUT_BUTTON', 'Update Payout Status')
|
|
52
|
+
set_environ('LABEL_SELECT_OWN_PAYOUT_BUTTON', 'List My Payouts')
|
|
53
|
+
|
|
54
|
+
set_environ('PAYOUTS_CREATING_TITLE', 'Payouts Creating')
|
|
55
|
+
set_environ('PAYOUTS_LISTING_TITLE', 'Payouts Listing')
|
|
56
|
+
set_environ('DRAFT_PAYOUTS_LISTING_TITLE', 'Draft Payouts Listing')
|
|
57
|
+
set_environ('NO_DRAFT_PAYOUTS_LISTING_TITLE', 'Live Payouts Listing')
|
|
58
|
+
|
|
59
|
+
set_environ('PAYOUT_INFO_TITLE', 'Payout Detailed Information')
|
|
60
|
+
set_environ('PAYOUT_UPDATING_TITLE', 'Payout Info Update')
|
|
61
|
+
|
|
62
|
+
## TYPE_PICKING_TITLE
|
|
63
|
+
## LABEL_TYPE_PICKING_BUTTON
|
|
64
|
+
|
|
65
|
+
set_environ('LABEL_TAX_RANGE_INPUT_PARAMS', 'Taxable bill date range') ## updated <invoiceum>
|
|
66
|
+
|
|
67
|
+
## LABEL_TAX_START
|
|
68
|
+
## LABEL_TAX_END
|
|
69
|
+
|
|
70
|
+
set_environ('BILLS_TAX_LISTING_TITLE', 'Taxes Deductible Listing')
|
|
71
|
+
set_environ('BILLS_TAX_TITLE', 'Taxes Deductible')
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: billium
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Literp Billing Web Tool
|
|
5
|
+
Home-page: https://github.com/asinerum/billium
|
|
6
|
+
Author: Asinerum Conlang Project
|
|
7
|
+
Author-email: asinerum.com@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: tagrepo>=1.0.16
|
|
16
|
+
Requires-Dist: invoiceum>=1.0.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Literp Billing Tool
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
```bash
|
|
23
|
+
pip install billium
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Sample Usage [Nov 1, 2026]
|
|
27
|
+
```bash
|
|
28
|
+
## nano billium.sh
|
|
29
|
+
export LLM_TRANSFORMER_MODEL="./all-MiniLM-L6-v2"
|
|
30
|
+
export SETUPKEY_ENCRYPT_SECRET="very-strong-password"
|
|
31
|
+
export JWT_SECRET="very-strong-password"
|
|
32
|
+
export TOKEN_EXPIRE_MINUTES=30
|
|
33
|
+
export ADMIN_AUTHENTICATOR_EMAIL_LIST=someone@company.com
|
|
34
|
+
export SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
35
|
+
export STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
36
|
+
export FINANCE_SUPERVISOR_AUTHENTICATOR_EMAIL_LIST=""
|
|
37
|
+
export FINANCE_STAFF_AUTHENTICATOR_EMAIL_LIST=""
|
|
38
|
+
export FILE_AVRO_DATA="./users/setupkeys.avro"
|
|
39
|
+
export FILE_AVRO_FINANCE_SUPERVISORS="./users/rolefbksupervisors.avro"
|
|
40
|
+
export FILE_AVRO_FINANCE_STAFF="./users/rolefbkstaff.avro"
|
|
41
|
+
export FILE_AVRO_SUPERVISORS="./users/rolecrmsupervisors.avro"
|
|
42
|
+
export FILE_AVRO_STAFF="./users/rolecrmstaff.avro"
|
|
43
|
+
export FILE_DUCKDB_VECTOR="./temp.db"
|
|
44
|
+
uvia -a vapp -m billium [-p <PORT>] [-H <HOST>]
|
|
45
|
+
## The <PORT> is 21921 by default, but can be edited
|
|
46
|
+
## The <HOST> should be 127.0.0.1 for security reason
|
|
47
|
+
## chmod +x billium.sh
|
|
48
|
+
## ./billium.sh
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Detailed tips, tricks, and examples, can be found at project's repository
|
|
52
|
+
https://github.com/asinerum/billium
|
|
53
|
+
|
|
54
|
+
(C) 2026 Asinerum Conlang Project
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.cfg
|
|
5
|
+
src/billium/__init__.py
|
|
6
|
+
src/billium/bill.py
|
|
7
|
+
src/billium/data.py
|
|
8
|
+
src/billium/environ.py
|
|
9
|
+
src/billium/param.py
|
|
10
|
+
src/billium/schema.py
|
|
11
|
+
src/billium/syslogin.py
|
|
12
|
+
src/billium/task.py
|
|
13
|
+
src/billium/template.py
|
|
14
|
+
src/billium/title.py
|
|
15
|
+
src/billium.egg-info/PKG-INFO
|
|
16
|
+
src/billium.egg-info/SOURCES.txt
|
|
17
|
+
src/billium.egg-info/dependency_links.txt
|
|
18
|
+
src/billium.egg-info/requires.txt
|
|
19
|
+
src/billium.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
billium
|