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 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
@@ -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
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
@@ -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,3 @@
1
+ from .bill import *
2
+
3
+ __version__ = "1.0.0"
@@ -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,2 @@
1
+ tagrepo>=1.0.16
2
+ invoiceum>=1.0.0
@@ -0,0 +1 @@
1
+ billium