invoiceum 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.
- invoiceum-1.0.0/LICENSE +21 -0
- invoiceum-1.0.0/PKG-INFO +54 -0
- invoiceum-1.0.0/README.md +36 -0
- invoiceum-1.0.0/pyproject.toml +3 -0
- invoiceum-1.0.0/setup.cfg +31 -0
- invoiceum-1.0.0/src/invoiceum/__init__.py +3 -0
- invoiceum-1.0.0/src/invoiceum/cinvoice.py +251 -0
- invoiceum-1.0.0/src/invoiceum/data.py +117 -0
- invoiceum-1.0.0/src/invoiceum/environ.py +17 -0
- invoiceum-1.0.0/src/invoiceum/param.py +18 -0
- invoiceum-1.0.0/src/invoiceum/schema.py +97 -0
- invoiceum-1.0.0/src/invoiceum/syslogin.py +72 -0
- invoiceum-1.0.0/src/invoiceum/task.py +301 -0
- invoiceum-1.0.0/src/invoiceum/template.py +140 -0
- invoiceum-1.0.0/src/invoiceum/title.py +70 -0
- invoiceum-1.0.0/src/invoiceum.egg-info/PKG-INFO +54 -0
- invoiceum-1.0.0/src/invoiceum.egg-info/SOURCES.txt +19 -0
- invoiceum-1.0.0/src/invoiceum.egg-info/dependency_links.txt +1 -0
- invoiceum-1.0.0/src/invoiceum.egg-info/requires.txt +2 -0
- invoiceum-1.0.0/src/invoiceum.egg-info/top_level.txt +1 -0
invoiceum-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.
|
invoiceum-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: invoiceum
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Literp Invoicing Web Tool
|
|
5
|
+
Home-page: https://github.com/asinerum/invoiceum
|
|
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.15
|
|
16
|
+
Requires-Dist: orderum>=1.0.1
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Literp Invoicing Tool
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
```bash
|
|
23
|
+
pip install invoiceum
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Sample Usage [Nov 1, 2026]
|
|
27
|
+
```bash
|
|
28
|
+
## nano invoiceum.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 invoiceum [-p <PORT>] [-H <HOST>]
|
|
45
|
+
## The <PORT> is 21911 by default, but can be edited
|
|
46
|
+
## The <HOST> should be 127.0.0.1 for security reason
|
|
47
|
+
## chmod +x invoiceum.sh
|
|
48
|
+
## ./invoiceum.sh
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Detailed tips, tricks, and examples, can be found at project's repository
|
|
52
|
+
https://github.com/asinerum/invoiceum
|
|
53
|
+
|
|
54
|
+
(C) 2026 Asinerum Conlang Project
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Literp Invoicing Tool
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
```bash
|
|
5
|
+
pip install invoiceum
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## Sample Usage [Nov 1, 2026]
|
|
9
|
+
```bash
|
|
10
|
+
## nano invoiceum.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 invoiceum [-p <PORT>] [-H <HOST>]
|
|
27
|
+
## The <PORT> is 21911 by default, but can be edited
|
|
28
|
+
## The <HOST> should be 127.0.0.1 for security reason
|
|
29
|
+
## chmod +x invoiceum.sh
|
|
30
|
+
## ./invoiceum.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Detailed tips, tricks, and examples, can be found at project's repository
|
|
34
|
+
https://github.com/asinerum/invoiceum
|
|
35
|
+
|
|
36
|
+
(C) 2026 Asinerum Conlang Project
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = invoiceum
|
|
3
|
+
version = 1.0.0
|
|
4
|
+
author = Asinerum Conlang Project
|
|
5
|
+
author_email = asinerum.com@gmail.com
|
|
6
|
+
description = Literp Invoicing Web Tool
|
|
7
|
+
long_description = file: README.md
|
|
8
|
+
long_description_content_type = text/markdown
|
|
9
|
+
url = https://github.com/asinerum/invoiceum
|
|
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.15
|
|
23
|
+
orderum >= 1.0.1
|
|
24
|
+
|
|
25
|
+
[options.packages.find]
|
|
26
|
+
where = src
|
|
27
|
+
|
|
28
|
+
[egg_info]
|
|
29
|
+
tag_build =
|
|
30
|
+
tag_date = 0
|
|
31
|
+
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Literp Invoicing Tool
|
|
3
|
+
Dependencies:
|
|
4
|
+
pip install [tagrepo] uvia vapp invoiceum
|
|
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 invoiceum [-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 tagrepo.format import first_date_of_current_year, first_date_of_current_month
|
|
30
|
+
|
|
31
|
+
from orderum import *
|
|
32
|
+
|
|
33
|
+
from .param import get_request_invoice_options, get_request_payment_options, get_request_tax_options
|
|
34
|
+
|
|
35
|
+
from .task import *
|
|
36
|
+
from .title import *
|
|
37
|
+
|
|
38
|
+
APP_PORT = 21911 ## for <uvia> and <vapp>
|
|
39
|
+
|
|
40
|
+
## core <uvia> functions
|
|
41
|
+
|
|
42
|
+
def homepage():
|
|
43
|
+
async def submit(request: Request):
|
|
44
|
+
return HTMLResponse(Template(APP_TMP_DIRECTORY_MENU).render(
|
|
45
|
+
APP_CHARSET=APP_CHARSET,
|
|
46
|
+
APP_VIEWPORT=APP_VIEWPORT,
|
|
47
|
+
APP_TITLE=APP_DESC,
|
|
48
|
+
FORM_TITLE=FORM_TITLE,
|
|
49
|
+
directory_items=[
|
|
50
|
+
{"href": "/login", "name": LABEL_MENU_LOGIN},
|
|
51
|
+
{"href": call_function(FUNCTION_INVOICE_INSERT), "name": LABEL_INVOICE_INSERT},
|
|
52
|
+
{"href": call_function(FUNCTION_DRAFT_INVOICES_SELECT), "name": LABEL_DRAFT_INVOICES_SELECT},
|
|
53
|
+
{"href": call_function(FUNCTION_NO_DRAFT_INVOICES_SELECT), "name": LABEL_NO_DRAFT_INVOICES_SELECT},
|
|
54
|
+
{"href": call_function(FUNCTION_INVOICES_REVIEW), "name": LABEL_INVOICES_REVIEW},
|
|
55
|
+
{"href": call_function(FUNCTION_PAYMENT_INSERT), "name": LABEL_PAYMENT_INSERT},
|
|
56
|
+
{"href": call_function(FUNCTION_DRAFT_PAYMENTS_SELECT), "name": LABEL_DRAFT_PAYMENTS_SELECT},
|
|
57
|
+
{"href": call_function(FUNCTION_NO_DRAFT_PAYMENTS_SELECT), "name": LABEL_NO_DRAFT_PAYMENTS_SELECT},
|
|
58
|
+
{"href": call_function(FUNCTION_PAYMENTS_REVIEW), "name": LABEL_PAYMENTS_REVIEW},
|
|
59
|
+
{"href": call_function(FUNCTION_TAX_COLLECT), "name": LABEL_TAX_COLLECT},
|
|
60
|
+
{"href": call_function(FUNCTION_INVOICES_SEARCH), "name": LABEL_INVOICES_SEARCH},
|
|
61
|
+
{"href": call_function("logout"), "name": LABEL_MENU_LOGOUT},
|
|
62
|
+
]
|
|
63
|
+
))
|
|
64
|
+
return submit
|
|
65
|
+
|
|
66
|
+
def callpage():
|
|
67
|
+
async def submit(request: Request):
|
|
68
|
+
return await process_function_call(request, auth_functions, HTMLResponse, _callpage)
|
|
69
|
+
return submit
|
|
70
|
+
|
|
71
|
+
async def _callpage(request, func: str):
|
|
72
|
+
if func == FUNCTION_CUSTOMERS_POPUP:
|
|
73
|
+
search = request.query_params.get("search")
|
|
74
|
+
return HTMLResponse(popup_customers_page(request, search))
|
|
75
|
+
elif func == FUNCTION_PRODUCTS_POPUP:
|
|
76
|
+
search = request.query_params.get("search")
|
|
77
|
+
return HTMLResponse(popup_products_page(request, search))
|
|
78
|
+
elif func == FUNCTION_INVOICES_POPUP:
|
|
79
|
+
search = request.query_params.get("search")
|
|
80
|
+
return HTMLResponse(popup_invoices_page(request, search))
|
|
81
|
+
elif func == FUNCTION_TYPE_POPUP:
|
|
82
|
+
return HTMLResponse(popup_type_page(request))
|
|
83
|
+
elif func == FUNCTION_INVOICE_INSERT:
|
|
84
|
+
if request.method == 'GET':
|
|
85
|
+
return HTMLResponse(insert_invoice_page(request))
|
|
86
|
+
else:
|
|
87
|
+
form = await request.form()
|
|
88
|
+
dated = form.get(DATED) or nowdate()
|
|
89
|
+
due = form.get(DUE) or ""
|
|
90
|
+
form = inputfilter(dstrip(form), Invoices, excludes=[])
|
|
91
|
+
form[DATED] = dated
|
|
92
|
+
form[DUE] = due
|
|
93
|
+
insert = insert_invoice_page(request, **form)
|
|
94
|
+
if insert == CALLBACK: return RedirectResponseGet(url=call_function(FUNCTION_INVOICES_SELECT))
|
|
95
|
+
return HTMLResponse(insert)
|
|
96
|
+
elif func == FUNCTION_INVOICES_SELECT:
|
|
97
|
+
return HTMLResponse(select_invoices_page(request, **get_request_invoice_options(request)))
|
|
98
|
+
elif func == FUNCTION_DRAFT_INVOICES_SELECT:
|
|
99
|
+
return HTMLResponse(select_draft_invoices_page(request, **get_request_invoice_options(request)))
|
|
100
|
+
elif func == FUNCTION_NO_DRAFT_INVOICES_SELECT:
|
|
101
|
+
return HTMLResponse(select_no_draft_invoices_page(request, **get_request_invoice_options(request)))
|
|
102
|
+
elif func == FUNCTION_INVOICE_SELECT:
|
|
103
|
+
invoice_id = request.query_params.get("id")
|
|
104
|
+
return HTMLResponse(select_invoice_page(request, invoice_id))
|
|
105
|
+
elif func == FUNCTION_CUSTOMER_SELECT:
|
|
106
|
+
customer_id = request.query_params.get("id")
|
|
107
|
+
return HTMLResponse(select_customer_page(request, customer_id))
|
|
108
|
+
elif func == FUNCTION_PRODUCT_SELECT:
|
|
109
|
+
product_id = request.query_params.get("id")
|
|
110
|
+
return HTMLResponse(select_product_page(request, product_id))
|
|
111
|
+
elif func == FUNCTION_INVOICE_NOTE_UPDATE: ## hidden
|
|
112
|
+
invoice_id = request.query_params.get("id")
|
|
113
|
+
if request.method == 'GET':
|
|
114
|
+
return HTMLResponse(update_invoice_note_page(request, invoice_id))
|
|
115
|
+
else:
|
|
116
|
+
form = await request.form()
|
|
117
|
+
form = inputfilter(dstrip(form), Invoices, includes=UPDATE_INPUTS["Invoices"])
|
|
118
|
+
update = update_invoice_note_page(request, invoice_id, **form)
|
|
119
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_INVOICE_SELECT}&id={invoice_id}"))
|
|
120
|
+
return HTMLResponse(update)
|
|
121
|
+
elif func == FUNCTION_INVOICE_UPDATE: ## hidden
|
|
122
|
+
invoice_id = request.query_params.get("id")
|
|
123
|
+
if request.method == 'GET':
|
|
124
|
+
return HTMLResponse(update_invoice_page(request, invoice_id))
|
|
125
|
+
else:
|
|
126
|
+
form = await request.form()
|
|
127
|
+
form = inputfilter(dstrip(form), Invoices, includes=UPDATE_INPUTS[Invoices])
|
|
128
|
+
update = update_invoice_page(request, invoice_id, **form)
|
|
129
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_INVOICE_SELECT}&id={invoice_id}"))
|
|
130
|
+
return HTMLResponse(update)
|
|
131
|
+
elif func == FUNCTION_INVOICE_UPGRADE:
|
|
132
|
+
invoice_id = request.query_params.get("id")
|
|
133
|
+
if request.method == 'GET':
|
|
134
|
+
return HTMLResponse(upgrade_invoice_page(request, invoice_id))
|
|
135
|
+
else:
|
|
136
|
+
form = await request.form()
|
|
137
|
+
status = form.get(STATUS)
|
|
138
|
+
update = upgrade_invoice_page(request, invoice_id, status=status)
|
|
139
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_INVOICE_SELECT}&id={invoice_id}"))
|
|
140
|
+
return HTMLResponse(update)
|
|
141
|
+
elif func == FUNCTION_INVOICEITEM_INSERT:
|
|
142
|
+
invoice_id = request.query_params.get("invoice_id")
|
|
143
|
+
if request.method == 'GET':
|
|
144
|
+
return HTMLResponse(insert_invoiceitem_page(request, invoice_id=invoice_id))
|
|
145
|
+
else:
|
|
146
|
+
form = await request.form()
|
|
147
|
+
form = inputfilter(dstrip(form), Invoiceitems, excludes=[DATED])
|
|
148
|
+
insert = insert_invoiceitem_page(request, invoice_id=invoice_id, **form)
|
|
149
|
+
if insert == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_INVOICEITEMS_SELECT}&invoice_id={invoice_id}"))
|
|
150
|
+
return HTMLResponse(insert)
|
|
151
|
+
elif func == FUNCTION_INVOICEITEMS_SELECT:
|
|
152
|
+
invoice_id = request.query_params.get("invoice_id")
|
|
153
|
+
return HTMLResponse(select_invoiceitems_page(request, invoice_id))
|
|
154
|
+
elif func == FUNCTION_INVOICEITEM_SELECT:
|
|
155
|
+
invoiceitem_id = request.query_params.get("id")
|
|
156
|
+
return HTMLResponse(select_invoiceitem_page(request, invoiceitem_id))
|
|
157
|
+
elif func == FUNCTION_INVOICEITEM_UPDATE: ## hidden
|
|
158
|
+
invoiceitem_id = request.query_params.get("id")
|
|
159
|
+
if request.method == 'GET':
|
|
160
|
+
return HTMLResponse(update_invoiceitem_page(request, invoiceitem_id))
|
|
161
|
+
else:
|
|
162
|
+
form = await request.form()
|
|
163
|
+
form = inputfilter(dstrip(form), Invoiceitems, includes=UPDATE_INPUTS[Invoiceitems])
|
|
164
|
+
update = update_invoiceitem_page(request, invoiceitem_id, **form) ## refresh subtotal
|
|
165
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_INVOICEITEM_SELECT}&id={invoiceitem_id}"))
|
|
166
|
+
return HTMLResponse(update)
|
|
167
|
+
elif func == FUNCTION_INVOICES_REVIEW:
|
|
168
|
+
if request.method == 'GET':
|
|
169
|
+
return HTMLResponse(review_invoices_page(request))
|
|
170
|
+
else:
|
|
171
|
+
form = await request.form()
|
|
172
|
+
status = form.get(STATUS)
|
|
173
|
+
return RedirectResponseGet(url=call_function(f"{FUNCTION_INVOICES_SELECT}&status={status}"))
|
|
174
|
+
elif func == FUNCTION_INVOICES_SEARCH:
|
|
175
|
+
author = request.query_params.get("author")
|
|
176
|
+
if request.method == 'GET':
|
|
177
|
+
return HTMLResponse(search_invoices_page(request))
|
|
178
|
+
else:
|
|
179
|
+
form = await request.form()
|
|
180
|
+
search = form.get("search")
|
|
181
|
+
seek = form.get("seek")
|
|
182
|
+
reset = form.get("reset")
|
|
183
|
+
search = search_invoices_page(request, search=search, reset=boolean(reset), seek=seek, author=author)
|
|
184
|
+
return HTMLResponse(search)
|
|
185
|
+
elif func == FUNCTION_INVOICES_AUTHOR_SEARCH: ## hidden
|
|
186
|
+
if request.method == 'GET':
|
|
187
|
+
return HTMLResponse(search_invoices_author_page(request))
|
|
188
|
+
else:
|
|
189
|
+
form = await request.form()
|
|
190
|
+
search = form.get("search")
|
|
191
|
+
seek = form.get("seek")
|
|
192
|
+
reset = form.get("reset")
|
|
193
|
+
search = search_invoices_author_page(request, search=search, reset=boolean(reset), seek=seek)
|
|
194
|
+
return HTMLResponse(search)
|
|
195
|
+
elif func == FUNCTION_PAYMENT_INSERT:
|
|
196
|
+
if request.method == 'GET':
|
|
197
|
+
return HTMLResponse(insert_payment_page(request))
|
|
198
|
+
else:
|
|
199
|
+
form = await request.form()
|
|
200
|
+
dated = form.get(DATED) or nowdate()
|
|
201
|
+
form = inputfilter(dstrip(form), Payments, excludes=[])
|
|
202
|
+
form[DATED] = dated
|
|
203
|
+
insert = insert_payment_page(request, **form)
|
|
204
|
+
if insert == CALLBACK: return RedirectResponseGet(url=call_function(FUNCTION_PAYMENTS_SELECT))
|
|
205
|
+
return HTMLResponse(insert)
|
|
206
|
+
elif func == FUNCTION_PAYMENTS_SELECT:
|
|
207
|
+
return HTMLResponse(select_payments_page(request, **get_request_payment_options(request)))
|
|
208
|
+
elif func == FUNCTION_PAYMENT_SELECT:
|
|
209
|
+
payment_id = request.query_params.get("id")
|
|
210
|
+
return HTMLResponse(select_payment_page(request, payment_id))
|
|
211
|
+
elif func == FUNCTION_PAYMENT_UPDATE: ## hidden
|
|
212
|
+
payment_id = request.query_params.get("id")
|
|
213
|
+
if request.method == 'GET':
|
|
214
|
+
return HTMLResponse(update_payment_page(request, payment_id))
|
|
215
|
+
else:
|
|
216
|
+
form = await request.form()
|
|
217
|
+
form = inputfilter(dstrip(form), Payments, includes=UPDATE_INPUTS[Payments])
|
|
218
|
+
update = update_payment_page(request, payment_id, **form)
|
|
219
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_PAYMENT_SELECT}&id={payment_id}"))
|
|
220
|
+
return HTMLResponse(update)
|
|
221
|
+
elif func == FUNCTION_PAYMENT_UPGRADE:
|
|
222
|
+
payment_id = request.query_params.get("id")
|
|
223
|
+
if request.method == 'GET':
|
|
224
|
+
return HTMLResponse(upgrade_payment_page(request, payment_id))
|
|
225
|
+
else:
|
|
226
|
+
form = await request.form()
|
|
227
|
+
status = form.get(STATUS)
|
|
228
|
+
update = upgrade_payment_page(request, payment_id, status=status)
|
|
229
|
+
if update == CALLBACK: return RedirectResponseGet(url=call_function(f"{FUNCTION_PAYMENT_SELECT}&id={payment_id}"))
|
|
230
|
+
return HTMLResponse(update)
|
|
231
|
+
elif func == FUNCTION_DRAFT_PAYMENTS_SELECT:
|
|
232
|
+
return HTMLResponse(select_draft_payments_page(request, **get_request_payment_options(request)))
|
|
233
|
+
elif func == FUNCTION_NO_DRAFT_PAYMENTS_SELECT:
|
|
234
|
+
return HTMLResponse(select_no_draft_payments_page(request, **get_request_payment_options(request)))
|
|
235
|
+
elif func == FUNCTION_PAYMENTS_REVIEW:
|
|
236
|
+
if request.method == 'GET':
|
|
237
|
+
return HTMLResponse(review_payments_page(request))
|
|
238
|
+
else:
|
|
239
|
+
form = await request.form()
|
|
240
|
+
status = form.get(STATUS)
|
|
241
|
+
return RedirectResponseGet(url=call_function(f"{FUNCTION_PAYMENTS_SELECT}&status={status}"))
|
|
242
|
+
elif func == FUNCTION_TAX_COLLECT:
|
|
243
|
+
if request.method == 'GET':
|
|
244
|
+
return HTMLResponse(collect_tax_invoices_page(request))
|
|
245
|
+
else:
|
|
246
|
+
form = await request.form()
|
|
247
|
+
dated = form.get(DATED) or first_date_of_current_month()
|
|
248
|
+
dated_end = form.get(DATED_END) or nowdate()
|
|
249
|
+
return HTMLResponse(collect_tax_invoices_page(request, dated, dated_end))
|
|
250
|
+
else:
|
|
251
|
+
return HTMLResponse("Function not found or not accessible")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from tagrepo.dataframe import _get_row_html, _get_row_dict
|
|
2
|
+
from tagrepo.database import avro_sql_select_combo
|
|
3
|
+
|
|
4
|
+
from orderum.data import *
|
|
5
|
+
|
|
6
|
+
from .syslogin import *
|
|
7
|
+
from .environ import *
|
|
8
|
+
from .schema import *
|
|
9
|
+
|
|
10
|
+
## data constants
|
|
11
|
+
|
|
12
|
+
INVOICE_INSERT_FIELDS = arrayfilter(INSERT_INPUTS[Invoices], excludes=[])
|
|
13
|
+
## EX_INVOICE_INSERT_FIELDS = arrayfilter(["customer"]+INSERT_INPUTS[Invoices], excludes=[])
|
|
14
|
+
INVOICEITEM_INSERT_FIELDS = arrayfilter(INSERT_INPUTS[Invoiceitems], excludes=[INVOICE_ID, DATED, SUBTOTAL])
|
|
15
|
+
## EX_INVOICEITEM_INSERT_FIELDS = arrayfilter(["product"]+INSERT_INPUTS[Invoiceitems], excludes=[INVOICE_ID, DATED, SUBTOTAL])
|
|
16
|
+
PAYMENT_INSERT_FIELDS = arrayfilter(INSERT_INPUTS[Payments], excludes=[])
|
|
17
|
+
|
|
18
|
+
## selecting derivatives
|
|
19
|
+
|
|
20
|
+
def calculate_invoice_amount(invoice_id: str) -> float:
|
|
21
|
+
df = sql_simple_query_model("Invoiceitems", select="quantity,price,subtotal", where=f"invoice_id='{invoice_id}'")
|
|
22
|
+
return 0 if df is None or not len(df) else numfloat((df['quantity'] * df['price']).sum())
|
|
23
|
+
|
|
24
|
+
def get_invoice_name(invoice_id: str) -> str:
|
|
25
|
+
return select_name_by_id(invoice_id, model="Invoices")
|
|
26
|
+
|
|
27
|
+
def get_invoice(invoice_id: str) -> dict:
|
|
28
|
+
return _get_row_dict("Invoices", invoice_id)
|
|
29
|
+
|
|
30
|
+
def select_pure_invoice(invoice_id: str) -> str:
|
|
31
|
+
return _get_row_html("Invoices", invoice_id)
|
|
32
|
+
|
|
33
|
+
def select_invoice(invoice_id: str) -> str:
|
|
34
|
+
return select_pure_row("Invoices",
|
|
35
|
+
linkfields=SELECT_LINK_FIELDS[Invoices],
|
|
36
|
+
linkrefs={CUSTOMER_ID: call_function(f"{FUNCTION_CUSTOMER_SELECT}&id=")},
|
|
37
|
+
where=f"id='{invoice_id}'", limit=1)
|
|
38
|
+
|
|
39
|
+
def select_invoiceitem(invoiceitem_id: str) -> str:
|
|
40
|
+
return select_pure_row("Invoiceitems",
|
|
41
|
+
linkfields=SELECT_LINK_FIELDS[Invoiceitems],
|
|
42
|
+
linkrefs={INVOICE_ID: call_function(f"{FUNCTION_INVOICE_SELECT}&id="), PRODUCT_ID: call_function(f"{FUNCTION_PRODUCT_SELECT}&id=")},
|
|
43
|
+
where=f"id='{invoiceitem_id}'", limit=1)
|
|
44
|
+
|
|
45
|
+
def get_invoiceitem(invoiceitem_id: str) -> dict:
|
|
46
|
+
return _get_row_dict("Invoiceitems", invoiceitem_id)
|
|
47
|
+
|
|
48
|
+
def invoice_not_updatable(invoice: any) -> bool:
|
|
49
|
+
if type(invoice) is dict: return invoice.get(STATUS) not in UPDATABLE_INVOICES
|
|
50
|
+
return not check_record_exist(invoice, model="Invoices", field=ID, where=INVOICE_IS_UPDATABLE)
|
|
51
|
+
|
|
52
|
+
def invoice_closed(invoice: any) -> bool:
|
|
53
|
+
if type(invoice) is dict: return invoice.get(STATUS) in CLOSED_INVOICES
|
|
54
|
+
return check_record_exist(invoice, model="Invoices", field=ID, where=INVOICE_CLOSED)
|
|
55
|
+
|
|
56
|
+
def invoice_item_duplicated(name: str, invoice_id: str) -> bool:
|
|
57
|
+
return check_record_exist(name, model="Invoiceitems", field=NAME, where=f"invoice_id='{invoice_id}'")
|
|
58
|
+
|
|
59
|
+
def select_payment(payment_id: str) -> str:
|
|
60
|
+
return select_pure_row("Payments",
|
|
61
|
+
linkfields=SELECT_LINK_FIELDS[Payments],
|
|
62
|
+
linkrefs={INVOICE_ID: call_function(f"{FUNCTION_INVOICE_SELECT}&id=")},
|
|
63
|
+
where=f"id='{payment_id}'", limit=1)
|
|
64
|
+
|
|
65
|
+
def get_payment(payment_id: str) -> dict:
|
|
66
|
+
return _get_row_dict("Payments", payment_id)
|
|
67
|
+
|
|
68
|
+
def payment_not_updatable(payment: any) -> bool:
|
|
69
|
+
if type(payment) is dict: return payment.get(STATUS) not in UPDATABLE_PAYMENTS
|
|
70
|
+
return not check_record_exist(payment, model="Payments", field=ID, where=PAYMENT_IS_UPDATABLE)
|
|
71
|
+
|
|
72
|
+
def payment_closed(payment: any) -> bool:
|
|
73
|
+
if type(payment) is dict: return payment.get(STATUS) in CLOSED_PAYMENTS
|
|
74
|
+
return check_record_exist(payment, model="Payments", field=ID, where=PAYMENT_CLOSED)
|
|
75
|
+
|
|
76
|
+
## listing derivatives
|
|
77
|
+
|
|
78
|
+
def list_invoices(popindex: int=1, **kwargs) -> str:
|
|
79
|
+
model = "Invoices"
|
|
80
|
+
show = LISTING_FIELDS[Invoices][popindex:]
|
|
81
|
+
df = sql_simple_query_model(model, **kwargs)
|
|
82
|
+
return df_list_insert_rows(df, show,
|
|
83
|
+
header=LISTING_HEADERS[Invoices][popindex:],
|
|
84
|
+
open_func=LISTING_LINK_BUTTON_FUNCTIONS[Invoices][popindex:],
|
|
85
|
+
callfunction=LISTING_LINK_CALL_FUNCTIONS[Invoices][popindex:],
|
|
86
|
+
idfield=LISTING_LINK_ID_FIELDS[Invoices][popindex:],
|
|
87
|
+
linkfield=LISTING_LINK_REF_FIELDS[Invoices][popindex:])
|
|
88
|
+
|
|
89
|
+
def list_invoices_to_insert_invoiceitem(**kwargs) -> str:
|
|
90
|
+
return list_invoices(0, **kwargs) ## remove add_item
|
|
91
|
+
|
|
92
|
+
def list_invoiceitems(invoice_id: str) -> str:
|
|
93
|
+
return list_review_rows("Invoiceitems",
|
|
94
|
+
show=LISTING_FIELDS[Invoiceitems],
|
|
95
|
+
callfunction="select-invoiceitem",
|
|
96
|
+
where=f"invoice_id='{invoice_id}'")
|
|
97
|
+
|
|
98
|
+
def list_pick_invoices(**kwargs) -> str:
|
|
99
|
+
return list_select_rows(model="Invoices", show=SELECT_LISTING_FIELDS[Invoices], header=LISTING_HEADERS["Invoices"], **kwargs) ## ambiguous "Invoices"
|
|
100
|
+
|
|
101
|
+
def list_select_payments(**kwargs) -> str:
|
|
102
|
+
return list_review_rows(model="Payments", show=LISTING_FIELDS[Payments], callfunction=FUNCTION_PAYMENT_SELECT, **kwargs)
|
|
103
|
+
|
|
104
|
+
list_payments = list_select_payments
|
|
105
|
+
|
|
106
|
+
def list_tax_invoices(dated: str, dated_end: str, statuses: str=INVOICE_TAXABLE_STATUSES, join_clause="db_main.customer_id=db_sub.id") -> str:
|
|
107
|
+
df = avro_sql_select_combo(model_main="Invoices", model_sub="Customers",
|
|
108
|
+
select=f"db_main.id, db_main.dated, db_sub.name AS customer, db_sub.{CUSTOMER_TAX_CODE} AS taxcode, db_main.tax, db_main.taxrate",
|
|
109
|
+
where=f"{join_clause} AND db_main.status IN {statuses} AND db_main.dated BETWEEN '{dated}' AND '{dated_end}'",
|
|
110
|
+
sort="db_main.dated,db_sub.name", asc=True, limit=LIST_MAX_ROWS)
|
|
111
|
+
if df is None or not len(df): return NO_DATA_AVAILABLE
|
|
112
|
+
return df_list_review_rows(df, show=REVIEW_LISTING_FIELDS[Invoices], callfunction=FUNCTION_INVOICE_SELECT)
|
|
113
|
+
|
|
114
|
+
## vector search
|
|
115
|
+
|
|
116
|
+
def get_search_invoice_option(request, seek: str="", author: str="") -> dict:
|
|
117
|
+
return get_search_status_option(request=request, seek=seek, author=author, status_list=INVOICE_STATUS_LIST)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from orderum.environ import *
|
|
2
|
+
|
|
3
|
+
set_environ('CONFIRM_INVOICE_COMPLETED', 'Confirm: this invoice was completed?')
|
|
4
|
+
set_environ('CONFIRM_INVOICE_CANCELLED', 'Confirm: this invoice was cancelled?')
|
|
5
|
+
|
|
6
|
+
set_environ('WRONG_INVOICE_ID', 'Error: invalid invoice id')
|
|
7
|
+
set_environ('BAD_INVOICE_ID', 'Error: invalid invoice id, or invoice is not updatable')
|
|
8
|
+
set_environ('INVOICE_ITEM_DUPLICATED', 'Error: invoice line item is duplicated')
|
|
9
|
+
|
|
10
|
+
set_environ('INVOICE_NOT_EXIST', 'Warning: invoice does not exist')
|
|
11
|
+
set_environ('INVOICE_NOT_EXIST_OR_CLOSED', 'Warning: invoice does not exist or was closed')
|
|
12
|
+
set_environ('INVOICE_NO_REVERSE_TO_DRAFT', 'Warning: invoice cannot be reversed to draft')
|
|
13
|
+
set_environ('INVOICE_ALREADY_EXISTS', 'Warning: invoice with this name already exists')
|
|
14
|
+
|
|
15
|
+
set_environ('PAYMENT_ALREADY_EXISTS', 'Warning: payment with this name already exists')
|
|
16
|
+
set_environ('PAYMENT_NOT_EXIST_OR_CLOSED', 'Warning: payment does not exist or was closed')
|
|
17
|
+
set_environ('BAD_PAYMENT_ID', 'Error: invalid payment id, or payment is not updatable')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from orderum.param import *
|
|
2
|
+
|
|
3
|
+
from .schema import INVOICE_SORT_FIELDS, PAYMENT_SORT_FIELDS, TAX_SORT_FIELDS, NAME
|
|
4
|
+
|
|
5
|
+
def adjust_invoice_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=INVOICE_SORT_FIELDS, request=request)
|
|
7
|
+
|
|
8
|
+
def adjust_payment_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=PAYMENT_SORT_FIELDS, request=request)
|
|
10
|
+
|
|
11
|
+
def adjust_tax_listing_options(where: str, author: str, sort: str, asc: str, limit: str, request=None) -> tuple:
|
|
12
|
+
return adjust_listing_options(where, author, sort, asc, limit, sort_fields=TAX_SORT_FIELDS, request=request)
|
|
13
|
+
|
|
14
|
+
get_request_invoice_options = get_request_data_options ## sort:DATED.desc
|
|
15
|
+
|
|
16
|
+
get_request_payment_options = get_request_data_options ## sort:DATED.desc
|
|
17
|
+
|
|
18
|
+
get_request_tax_options = get_request_data_options ## sort:DATED.desc
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from tagrepo.schema15 import *
|
|
2
|
+
from orderum.schema import *
|
|
3
|
+
|
|
4
|
+
from .syslogin import *
|
|
5
|
+
|
|
6
|
+
_CUSTOMER = "customer"
|
|
7
|
+
_TAXCODE = "taxcode"
|
|
8
|
+
|
|
9
|
+
INVOICE_SORT_FIELDS = [NAME, DATED, DUE, AMOUNT, DISCOUNT, TAXRATE, TAX, AUTHOR_ID, STATUS]
|
|
10
|
+
PAYMENT_SORT_FIELDS = [NAME, DATED, AMOUNT, REF, TYPE, AUTHOR_ID, STATUS]
|
|
11
|
+
TAX_SORT_FIELDS = [_CUSTOMER, _TAXCODE, TAX, TAXRATE, DATED]
|
|
12
|
+
|
|
13
|
+
UPDATABLE_INVOICES = [INVOICE_DRAFT]
|
|
14
|
+
CLOSED_INVOICES = [INVOICE_PAID, INVOICE_CANCELLED]
|
|
15
|
+
OPEN_INVOICES = [INVOICE_SENT, INVOICE_OVERDUE] ## USABLE
|
|
16
|
+
|
|
17
|
+
INVOICE_CLOSED = f"status IN ('{INVOICE_PAID}', '{INVOICE_CANCELLED}')"
|
|
18
|
+
INVOICE_NOT_CLOSED = f"status NOT IN ('{INVOICE_PAID}', '{INVOICE_CANCELLED}')"
|
|
19
|
+
INVOICE_IS_LISTABLE = f"status NOT IN ('{INVOICE_DRAFT}', '{INVOICE_CANCELLED}')"
|
|
20
|
+
INVOICE_IS_UPDATABLE = f"status IN ('{INVOICE_DRAFT}')"
|
|
21
|
+
INVOICE_IS_USABLE = f"status IN ('{INVOICE_SENT}', '{INVOICE_OVERDUE}')"
|
|
22
|
+
|
|
23
|
+
UPDATABLE_PAYMENTS = [PAYMENT_DRAFT]
|
|
24
|
+
CLOSED_PAYMENTS = [PAYMENT_COMPLETED, PAYMENT_CANCELLED]
|
|
25
|
+
|
|
26
|
+
PAYMENT_CLOSED = f"status IN ('{PAYMENT_COMPLETED}', '{PAYMENT_CANCELLED}')"
|
|
27
|
+
PAYMENT_NOT_CLOSED = f"status NOT IN ('{PAYMENT_COMPLETED}', '{PAYMENT_CANCELLED}')"
|
|
28
|
+
PAYMENT_IS_LISTABLE = f"status NOT IN ('{PAYMENT_DRAFT}', '{PAYMENT_CANCELLED}')"
|
|
29
|
+
PAYMENT_IS_UPDATABLE = f"status IN ('{PAYMENT_DRAFT}')"
|
|
30
|
+
|
|
31
|
+
INVOICE_TAXABLE_STATUSES = f"('{INVOICE_SENT}', '{INVOICE_OVERDUE}', '{INVOICE_PAID}')"
|
|
32
|
+
|
|
33
|
+
## complex data view: use with caution
|
|
34
|
+
|
|
35
|
+
REVIEW_LISTING_FIELDS |= { ## complex view
|
|
36
|
+
Invoices: [HEADER_REVIEW, DATED, _CUSTOMER, _TAXCODE, TAXRATE, TAX],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
LISTING_LINK_BUTTON_FUNCTIONS |= { ## complex view
|
|
40
|
+
Invoices: [add_button, view_button],
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
LISTING_LINK_CALL_FUNCTIONS |= { ## complex view
|
|
44
|
+
Invoices: [FUNCTION_INVOICEITEM_INSERT, FUNCTION_INVOICE_SELECT],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
LISTING_LINK_ID_FIELDS |= { ## complex view
|
|
48
|
+
Invoices: [ID, ID],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
LISTING_LINK_REF_FIELDS |= { ## complex view
|
|
52
|
+
Invoices: [INVOICE_ID, ID], ## add-item-line/view-item
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
## common data frame show
|
|
56
|
+
|
|
57
|
+
LISTING_FIELDS |= { ## dataset viewer: all columns
|
|
58
|
+
Invoices: [HEADER_INSERT, HEADER_REVIEW, DATED, NAME, AMOUNT, DISCOUNT, DUE, TAXRATE, STATUS],
|
|
59
|
+
Invoiceitems: [HEADER_REVIEW, NAME, QUANTITY, PRICE, SUBTOTAL],
|
|
60
|
+
Payments: [HEADER_REVIEW, DATED, NAME, AMOUNT, TYPE, REF, STATUS],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
SELECT_LINK_FIELDS |= { ## single record viewer: vertical view with links
|
|
64
|
+
Invoices: [CUSTOMER_ID],
|
|
65
|
+
Invoiceitems: [INVOICE_ID, PRODUCT_ID],
|
|
66
|
+
Payments: [INVOICE_ID],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
SELECT_LISTING_FIELDS |= { ## dataset viewer for popup picker
|
|
70
|
+
Invoices: [HEADER_SELECT, NAME, STATUS, ID],
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
LISTING_HEADERS |= { ## dataset viewer: headers of extra columns
|
|
74
|
+
Invoices: [HEADER_INSERT, HEADER_REVIEW],
|
|
75
|
+
"Invoices": HEADER_SELECT, ## popup picker
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
## common data input options
|
|
79
|
+
|
|
80
|
+
INSERT_INPUTS |= {
|
|
81
|
+
Invoices: [DATED, DUE, CUSTOMER_ID, NAME, AMOUNT, DISCOUNT, TAXRATE, ORDER_ID, NOTE],
|
|
82
|
+
InvoicesStatus: [ID, STATUS],
|
|
83
|
+
Invoiceitems: [INVOICE_ID, PRODUCT_ID, DATED, NAME, QUANTITY, PRICE, SUBTOTAL, NOTE],
|
|
84
|
+
Payments: [DATED, INVOICE_ID, NAME, AMOUNT, TYPE, REF, NOTE],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
UPDATE_INPUTS |= {
|
|
88
|
+
Invoices: [AUTHOR_ID, NOTE, AMOUNT, DISCOUNT, TAXRATE],
|
|
89
|
+
"Invoices": [AUTHOR_ID, NOTE],
|
|
90
|
+
InvoicesStatus: [STATUS],
|
|
91
|
+
Invoiceitems: [NAME, QUANTITY, PRICE, NOTE],
|
|
92
|
+
Payments: [NAME, AUTHOR_ID, REF, TYPE, NOTE],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
SEARCH_INPUTS |= {
|
|
96
|
+
Invoices: [NAME, NOTE, AUTHOR_ID],
|
|
97
|
+
}
|