revnext 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
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.
- revnext/__init__.py +16 -18
- revnext/common.py +196 -196
- revnext/config.py +61 -61
- revnext/parts_by_bin_report.py +250 -250
- revnext/parts_price_list_report.py +254 -254
- revnext-0.1.2.dist-info/METADATA +126 -0
- revnext-0.1.2.dist-info/RECORD +9 -0
- revnext/download_all_reports.py +0 -49
- revnext-0.1.0.dist-info/METADATA +0 -24
- revnext-0.1.0.dist-info/RECORD +0 -10
- {revnext-0.1.0.dist-info → revnext-0.1.2.dist-info}/WHEEL +0 -0
- {revnext-0.1.0.dist-info → revnext-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -1,254 +1,254 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Download Parts Price List CSV report from Revolution Next (*.revolutionnext.com.au).
|
|
3
|
-
Uses cookies (Chrome export format) and configurable base URL.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Optional
|
|
9
|
-
|
|
10
|
-
import requests
|
|
11
|
-
|
|
12
|
-
from revnext.common import create_session, run_report_flow
|
|
13
|
-
from revnext.config import get_revnext_base_url_from_env
|
|
14
|
-
|
|
15
|
-
SERVICE_OBJECT = "Revolution.Activity.IM.RPT.PartsPriceListPR"
|
|
16
|
-
ACTIVITY_TAB_ID = "N78b54de4_7cdc_43e0_9e42_71a49bec44f2"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _build_submit_body() -> dict:
|
|
20
|
-
"""Build the submitActivityTask request body. Use today's date for the trigger."""
|
|
21
|
-
tz = "+10:00"
|
|
22
|
-
now = datetime.now()
|
|
23
|
-
start_date = now.strftime("%Y-%m-%d")
|
|
24
|
-
start_time = f"{start_date}T{now.strftime('%H:%M')}:00.000{tz}"
|
|
25
|
-
return {
|
|
26
|
-
"_userContext_vg_coid": "03",
|
|
27
|
-
"_userContext_vg_divid": "1",
|
|
28
|
-
"_userContext_vg_dftdpt": "570",
|
|
29
|
-
"activityTabId": ACTIVITY_TAB_ID,
|
|
30
|
-
"dataSets": [
|
|
31
|
-
{
|
|
32
|
-
"name": "dsActivityTask",
|
|
33
|
-
"id": None,
|
|
34
|
-
"dataSet": {
|
|
35
|
-
"dsActivityTask": {
|
|
36
|
-
"prods:hasChanges": True,
|
|
37
|
-
"ttActivityTask": [
|
|
38
|
-
{
|
|
39
|
-
"prods:id": "ttActivityTask1945856",
|
|
40
|
-
"prods:rowState": "created",
|
|
41
|
-
"fldId": 1,
|
|
42
|
-
"taskID": "",
|
|
43
|
-
"loadRowidPassThrough": "dummy",
|
|
44
|
-
"executeActivityTaskNow": False,
|
|
45
|
-
"executedAt": None,
|
|
46
|
-
"logMessages": "",
|
|
47
|
-
"startTime": None,
|
|
48
|
-
"endTime": None,
|
|
49
|
-
}
|
|
50
|
-
],
|
|
51
|
-
"prods:before": {},
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"name": "dsActivityTaskTriggers",
|
|
57
|
-
"id": None,
|
|
58
|
-
"dataSet": {
|
|
59
|
-
"dsActivityTaskTriggers": {
|
|
60
|
-
"prods:hasChanges": True,
|
|
61
|
-
"ttActivityTaskTrigger": [
|
|
62
|
-
{
|
|
63
|
-
"prods:id": "ttActivityTaskTrigger1786112",
|
|
64
|
-
"prods:rowState": "created",
|
|
65
|
-
"fldId": 1,
|
|
66
|
-
"mode": "O",
|
|
67
|
-
"startDateTime": start_time,
|
|
68
|
-
"startDate": f"{start_date}T00:00:00.000{tz}",
|
|
69
|
-
"startHour": now.hour,
|
|
70
|
-
"startMinute": now.minute,
|
|
71
|
-
"recurEvery": 1,
|
|
72
|
-
"recurEveryUOM": "days",
|
|
73
|
-
"weeklySun": False,
|
|
74
|
-
"weeklyMon": False,
|
|
75
|
-
"weeklyTue": False,
|
|
76
|
-
"weeklyWed": False,
|
|
77
|
-
"weeklyThu": False,
|
|
78
|
-
"weeklyFri": False,
|
|
79
|
-
"weeklySat": False,
|
|
80
|
-
"monthsList": "",
|
|
81
|
-
"monthlyMode": "",
|
|
82
|
-
"monthlyDaysList": "",
|
|
83
|
-
"monthlyOnWeekNumber": "",
|
|
84
|
-
"monthlyOnDayOfWeek": "",
|
|
85
|
-
"triggerDescription": "",
|
|
86
|
-
"triggerNextSchedule": None,
|
|
87
|
-
"windowTimeFrom": "",
|
|
88
|
-
"windowTimeTo": "",
|
|
89
|
-
"windowAllDay": False,
|
|
90
|
-
}
|
|
91
|
-
],
|
|
92
|
-
"prods:before": {},
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
"name": "dsParams",
|
|
98
|
-
"id": None,
|
|
99
|
-
"dataSet": {
|
|
100
|
-
"dsParams": {
|
|
101
|
-
"prods:hasChanges": True,
|
|
102
|
-
"tt_params": [
|
|
103
|
-
{
|
|
104
|
-
"prods:id": "tt_paramsFldId1",
|
|
105
|
-
"prods:rowState": "modified",
|
|
106
|
-
"fldid": 1,
|
|
107
|
-
"coid": "03",
|
|
108
|
-
"divid": "1",
|
|
109
|
-
"activityid": "IM.RPT.PartsPriceListPR",
|
|
110
|
-
"taskid": "",
|
|
111
|
-
"tasksts": None,
|
|
112
|
-
"rptid": "",
|
|
113
|
-
"pdf": "",
|
|
114
|
-
"formprt": False,
|
|
115
|
-
"csvout": True,
|
|
116
|
-
"emailopt": "n",
|
|
117
|
-
"emailme": False,
|
|
118
|
-
"useremail": "lwarneke@mikecarneytoyota.com.au",
|
|
119
|
-
"emailprinter": False,
|
|
120
|
-
"prtid": "",
|
|
121
|
-
"ddpflg": False,
|
|
122
|
-
"ddpquo": 0,
|
|
123
|
-
"submitopt": "p",
|
|
124
|
-
"email_staff": False,
|
|
125
|
-
"staff_email": "",
|
|
126
|
-
"email_other": False,
|
|
127
|
-
"other_email": "",
|
|
128
|
-
"subject": "Parts Price List",
|
|
129
|
-
"attn": "",
|
|
130
|
-
"email_text": "",
|
|
131
|
-
"email_signature": "",
|
|
132
|
-
"email_sig_type": "D",
|
|
133
|
-
"prttyp": "s",
|
|
134
|
-
"dptid": "570",
|
|
135
|
-
"frnid": "",
|
|
136
|
-
"frnidto": "",
|
|
137
|
-
"binid": "",
|
|
138
|
-
"binidto": "",
|
|
139
|
-
"prctyp1": "L",
|
|
140
|
-
"prctyp2": "S",
|
|
141
|
-
"incgst1": True,
|
|
142
|
-
"incgst2": True,
|
|
143
|
-
"exportexcel": False,
|
|
144
|
-
"tasktype": "",
|
|
145
|
-
}
|
|
146
|
-
],
|
|
147
|
-
"prods:before": {
|
|
148
|
-
"tt_params": [
|
|
149
|
-
{
|
|
150
|
-
"prods:id": "tt_paramsFldId1",
|
|
151
|
-
"prods:rowState": "modified",
|
|
152
|
-
"fldid": 1,
|
|
153
|
-
"coid": "03",
|
|
154
|
-
"divid": "1",
|
|
155
|
-
"activityid": "",
|
|
156
|
-
"taskid": "",
|
|
157
|
-
"tasksts": None,
|
|
158
|
-
"rptid": "",
|
|
159
|
-
"pdf": "",
|
|
160
|
-
"formprt": False,
|
|
161
|
-
"csvout": False,
|
|
162
|
-
"emailopt": "",
|
|
163
|
-
"emailme": False,
|
|
164
|
-
"useremail": "",
|
|
165
|
-
"emailprinter": False,
|
|
166
|
-
"prtid": "",
|
|
167
|
-
"ddpflg": False,
|
|
168
|
-
"ddpquo": 0,
|
|
169
|
-
"submitopt": "",
|
|
170
|
-
"email_staff": False,
|
|
171
|
-
"staff_email": "",
|
|
172
|
-
"email_other": False,
|
|
173
|
-
"other_email": "",
|
|
174
|
-
"subject": "",
|
|
175
|
-
"attn": "",
|
|
176
|
-
"email_text": "",
|
|
177
|
-
"email_signature": "",
|
|
178
|
-
"email_sig_type": "",
|
|
179
|
-
"prttyp": "s",
|
|
180
|
-
"dptid": "130",
|
|
181
|
-
"frnid": "",
|
|
182
|
-
"frnidto": "",
|
|
183
|
-
"binid": "",
|
|
184
|
-
"binidto": "",
|
|
185
|
-
"prctyp1": "",
|
|
186
|
-
"prctyp2": "",
|
|
187
|
-
"incgst1": False,
|
|
188
|
-
"incgst2": False,
|
|
189
|
-
"exportexcel": False,
|
|
190
|
-
"tasktype": "",
|
|
191
|
-
}
|
|
192
|
-
]
|
|
193
|
-
},
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
"stopOnWarning": True,
|
|
199
|
-
"validateOnly": False,
|
|
200
|
-
"uiType": "ISC",
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _post_submit_closesubmit_factory(base_url: str):
|
|
205
|
-
"""Return a hook that calls onChoose_btn_closesubmit (required for Parts Price List flow)."""
|
|
206
|
-
def _post_submit_closesubmit(session: requests.Session) -> None:
|
|
207
|
-
url = f"{base_url}/next/rest/si/presenter/onChoose_btn_closesubmit"
|
|
208
|
-
body = {
|
|
209
|
-
"_userContext_vg_coid": "03",
|
|
210
|
-
"_userContext_vg_divid": "1",
|
|
211
|
-
"_userContext_vg_dftdpt": "570",
|
|
212
|
-
"activityTabId": ACTIVITY_TAB_ID,
|
|
213
|
-
"ctrlProp": [
|
|
214
|
-
{"name": "tt_params.submitopt", "prop": "SCREENVALUE", "value": "p"}
|
|
215
|
-
],
|
|
216
|
-
"uiType": "ISC",
|
|
217
|
-
}
|
|
218
|
-
r = session.post(url, json=body)
|
|
219
|
-
r.raise_for_status()
|
|
220
|
-
return _post_submit_closesubmit
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def download_parts_price_list_report(
|
|
224
|
-
cookies_path: Optional[Path | str] = None,
|
|
225
|
-
output_path: Optional[Path | str] = None,
|
|
226
|
-
base_url: Optional[str] = None,
|
|
227
|
-
) -> Path:
|
|
228
|
-
"""
|
|
229
|
-
Run the Parts Price List report and save CSV to output_path.
|
|
230
|
-
Returns the path where the file was saved.
|
|
231
|
-
|
|
232
|
-
Args:
|
|
233
|
-
cookies_path: Path to cookies JSON (Chrome export). Defaults to REVOLUTIONNEXT_COOKIES_PATH or current dir.
|
|
234
|
-
output_path: Where to save the CSV. Defaults to current dir / Parts_Price_List.csv.
|
|
235
|
-
base_url: Revolution Next base URL (e.g. https://yoursite.revolutionnext.com.au). Defaults to env.
|
|
236
|
-
"""
|
|
237
|
-
base_url = base_url or get_revnext_base_url_from_env()
|
|
238
|
-
default_cookies = Path.cwd() / "revnext-cookies.json"
|
|
239
|
-
cookies_path = Path(cookies_path) if cookies_path is not None else default_cookies
|
|
240
|
-
output_path = Path(output_path) if output_path is not None else (Path.cwd() / "Parts_Price_List.csv")
|
|
241
|
-
session = create_session(cookies_path, SERVICE_OBJECT, base_url)
|
|
242
|
-
return run_report_flow(
|
|
243
|
-
session,
|
|
244
|
-
SERVICE_OBJECT,
|
|
245
|
-
ACTIVITY_TAB_ID,
|
|
246
|
-
_build_submit_body,
|
|
247
|
-
output_path,
|
|
248
|
-
base_url,
|
|
249
|
-
post_submit_hook=_post_submit_closesubmit_factory(base_url),
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if __name__ == "__main__":
|
|
254
|
-
download_parts_price_list_report()
|
|
1
|
+
"""
|
|
2
|
+
Download Parts Price List CSV report from Revolution Next (*.revolutionnext.com.au).
|
|
3
|
+
Uses cookies (Chrome export format) and configurable base URL.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from revnext.common import create_session, run_report_flow
|
|
13
|
+
from revnext.config import get_revnext_base_url_from_env
|
|
14
|
+
|
|
15
|
+
SERVICE_OBJECT = "Revolution.Activity.IM.RPT.PartsPriceListPR"
|
|
16
|
+
ACTIVITY_TAB_ID = "N78b54de4_7cdc_43e0_9e42_71a49bec44f2"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _build_submit_body() -> dict:
|
|
20
|
+
"""Build the submitActivityTask request body. Use today's date for the trigger."""
|
|
21
|
+
tz = "+10:00"
|
|
22
|
+
now = datetime.now()
|
|
23
|
+
start_date = now.strftime("%Y-%m-%d")
|
|
24
|
+
start_time = f"{start_date}T{now.strftime('%H:%M')}:00.000{tz}"
|
|
25
|
+
return {
|
|
26
|
+
"_userContext_vg_coid": "03",
|
|
27
|
+
"_userContext_vg_divid": "1",
|
|
28
|
+
"_userContext_vg_dftdpt": "570",
|
|
29
|
+
"activityTabId": ACTIVITY_TAB_ID,
|
|
30
|
+
"dataSets": [
|
|
31
|
+
{
|
|
32
|
+
"name": "dsActivityTask",
|
|
33
|
+
"id": None,
|
|
34
|
+
"dataSet": {
|
|
35
|
+
"dsActivityTask": {
|
|
36
|
+
"prods:hasChanges": True,
|
|
37
|
+
"ttActivityTask": [
|
|
38
|
+
{
|
|
39
|
+
"prods:id": "ttActivityTask1945856",
|
|
40
|
+
"prods:rowState": "created",
|
|
41
|
+
"fldId": 1,
|
|
42
|
+
"taskID": "",
|
|
43
|
+
"loadRowidPassThrough": "dummy",
|
|
44
|
+
"executeActivityTaskNow": False,
|
|
45
|
+
"executedAt": None,
|
|
46
|
+
"logMessages": "",
|
|
47
|
+
"startTime": None,
|
|
48
|
+
"endTime": None,
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"prods:before": {},
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "dsActivityTaskTriggers",
|
|
57
|
+
"id": None,
|
|
58
|
+
"dataSet": {
|
|
59
|
+
"dsActivityTaskTriggers": {
|
|
60
|
+
"prods:hasChanges": True,
|
|
61
|
+
"ttActivityTaskTrigger": [
|
|
62
|
+
{
|
|
63
|
+
"prods:id": "ttActivityTaskTrigger1786112",
|
|
64
|
+
"prods:rowState": "created",
|
|
65
|
+
"fldId": 1,
|
|
66
|
+
"mode": "O",
|
|
67
|
+
"startDateTime": start_time,
|
|
68
|
+
"startDate": f"{start_date}T00:00:00.000{tz}",
|
|
69
|
+
"startHour": now.hour,
|
|
70
|
+
"startMinute": now.minute,
|
|
71
|
+
"recurEvery": 1,
|
|
72
|
+
"recurEveryUOM": "days",
|
|
73
|
+
"weeklySun": False,
|
|
74
|
+
"weeklyMon": False,
|
|
75
|
+
"weeklyTue": False,
|
|
76
|
+
"weeklyWed": False,
|
|
77
|
+
"weeklyThu": False,
|
|
78
|
+
"weeklyFri": False,
|
|
79
|
+
"weeklySat": False,
|
|
80
|
+
"monthsList": "",
|
|
81
|
+
"monthlyMode": "",
|
|
82
|
+
"monthlyDaysList": "",
|
|
83
|
+
"monthlyOnWeekNumber": "",
|
|
84
|
+
"monthlyOnDayOfWeek": "",
|
|
85
|
+
"triggerDescription": "",
|
|
86
|
+
"triggerNextSchedule": None,
|
|
87
|
+
"windowTimeFrom": "",
|
|
88
|
+
"windowTimeTo": "",
|
|
89
|
+
"windowAllDay": False,
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"prods:before": {},
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "dsParams",
|
|
98
|
+
"id": None,
|
|
99
|
+
"dataSet": {
|
|
100
|
+
"dsParams": {
|
|
101
|
+
"prods:hasChanges": True,
|
|
102
|
+
"tt_params": [
|
|
103
|
+
{
|
|
104
|
+
"prods:id": "tt_paramsFldId1",
|
|
105
|
+
"prods:rowState": "modified",
|
|
106
|
+
"fldid": 1,
|
|
107
|
+
"coid": "03",
|
|
108
|
+
"divid": "1",
|
|
109
|
+
"activityid": "IM.RPT.PartsPriceListPR",
|
|
110
|
+
"taskid": "",
|
|
111
|
+
"tasksts": None,
|
|
112
|
+
"rptid": "",
|
|
113
|
+
"pdf": "",
|
|
114
|
+
"formprt": False,
|
|
115
|
+
"csvout": True,
|
|
116
|
+
"emailopt": "n",
|
|
117
|
+
"emailme": False,
|
|
118
|
+
"useremail": "lwarneke@mikecarneytoyota.com.au",
|
|
119
|
+
"emailprinter": False,
|
|
120
|
+
"prtid": "",
|
|
121
|
+
"ddpflg": False,
|
|
122
|
+
"ddpquo": 0,
|
|
123
|
+
"submitopt": "p",
|
|
124
|
+
"email_staff": False,
|
|
125
|
+
"staff_email": "",
|
|
126
|
+
"email_other": False,
|
|
127
|
+
"other_email": "",
|
|
128
|
+
"subject": "Parts Price List",
|
|
129
|
+
"attn": "",
|
|
130
|
+
"email_text": "",
|
|
131
|
+
"email_signature": "",
|
|
132
|
+
"email_sig_type": "D",
|
|
133
|
+
"prttyp": "s",
|
|
134
|
+
"dptid": "570",
|
|
135
|
+
"frnid": "",
|
|
136
|
+
"frnidto": "",
|
|
137
|
+
"binid": "",
|
|
138
|
+
"binidto": "",
|
|
139
|
+
"prctyp1": "L",
|
|
140
|
+
"prctyp2": "S",
|
|
141
|
+
"incgst1": True,
|
|
142
|
+
"incgst2": True,
|
|
143
|
+
"exportexcel": False,
|
|
144
|
+
"tasktype": "",
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"prods:before": {
|
|
148
|
+
"tt_params": [
|
|
149
|
+
{
|
|
150
|
+
"prods:id": "tt_paramsFldId1",
|
|
151
|
+
"prods:rowState": "modified",
|
|
152
|
+
"fldid": 1,
|
|
153
|
+
"coid": "03",
|
|
154
|
+
"divid": "1",
|
|
155
|
+
"activityid": "",
|
|
156
|
+
"taskid": "",
|
|
157
|
+
"tasksts": None,
|
|
158
|
+
"rptid": "",
|
|
159
|
+
"pdf": "",
|
|
160
|
+
"formprt": False,
|
|
161
|
+
"csvout": False,
|
|
162
|
+
"emailopt": "",
|
|
163
|
+
"emailme": False,
|
|
164
|
+
"useremail": "",
|
|
165
|
+
"emailprinter": False,
|
|
166
|
+
"prtid": "",
|
|
167
|
+
"ddpflg": False,
|
|
168
|
+
"ddpquo": 0,
|
|
169
|
+
"submitopt": "",
|
|
170
|
+
"email_staff": False,
|
|
171
|
+
"staff_email": "",
|
|
172
|
+
"email_other": False,
|
|
173
|
+
"other_email": "",
|
|
174
|
+
"subject": "",
|
|
175
|
+
"attn": "",
|
|
176
|
+
"email_text": "",
|
|
177
|
+
"email_signature": "",
|
|
178
|
+
"email_sig_type": "",
|
|
179
|
+
"prttyp": "s",
|
|
180
|
+
"dptid": "130",
|
|
181
|
+
"frnid": "",
|
|
182
|
+
"frnidto": "",
|
|
183
|
+
"binid": "",
|
|
184
|
+
"binidto": "",
|
|
185
|
+
"prctyp1": "",
|
|
186
|
+
"prctyp2": "",
|
|
187
|
+
"incgst1": False,
|
|
188
|
+
"incgst2": False,
|
|
189
|
+
"exportexcel": False,
|
|
190
|
+
"tasktype": "",
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
"stopOnWarning": True,
|
|
199
|
+
"validateOnly": False,
|
|
200
|
+
"uiType": "ISC",
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _post_submit_closesubmit_factory(base_url: str):
|
|
205
|
+
"""Return a hook that calls onChoose_btn_closesubmit (required for Parts Price List flow)."""
|
|
206
|
+
def _post_submit_closesubmit(session: requests.Session) -> None:
|
|
207
|
+
url = f"{base_url}/next/rest/si/presenter/onChoose_btn_closesubmit"
|
|
208
|
+
body = {
|
|
209
|
+
"_userContext_vg_coid": "03",
|
|
210
|
+
"_userContext_vg_divid": "1",
|
|
211
|
+
"_userContext_vg_dftdpt": "570",
|
|
212
|
+
"activityTabId": ACTIVITY_TAB_ID,
|
|
213
|
+
"ctrlProp": [
|
|
214
|
+
{"name": "tt_params.submitopt", "prop": "SCREENVALUE", "value": "p"}
|
|
215
|
+
],
|
|
216
|
+
"uiType": "ISC",
|
|
217
|
+
}
|
|
218
|
+
r = session.post(url, json=body)
|
|
219
|
+
r.raise_for_status()
|
|
220
|
+
return _post_submit_closesubmit
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def download_parts_price_list_report(
|
|
224
|
+
cookies_path: Optional[Path | str] = None,
|
|
225
|
+
output_path: Optional[Path | str] = None,
|
|
226
|
+
base_url: Optional[str] = None,
|
|
227
|
+
) -> Path:
|
|
228
|
+
"""
|
|
229
|
+
Run the Parts Price List report and save CSV to output_path.
|
|
230
|
+
Returns the path where the file was saved.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
cookies_path: Path to cookies JSON (Chrome export). Defaults to REVOLUTIONNEXT_COOKIES_PATH or current dir.
|
|
234
|
+
output_path: Where to save the CSV. Defaults to current dir / Parts_Price_List.csv.
|
|
235
|
+
base_url: Revolution Next base URL (e.g. https://yoursite.revolutionnext.com.au). Defaults to env.
|
|
236
|
+
"""
|
|
237
|
+
base_url = base_url or get_revnext_base_url_from_env()
|
|
238
|
+
default_cookies = Path.cwd() / "revnext-cookies.json"
|
|
239
|
+
cookies_path = Path(cookies_path) if cookies_path is not None else default_cookies
|
|
240
|
+
output_path = Path(output_path) if output_path is not None else (Path.cwd() / "Parts_Price_List.csv")
|
|
241
|
+
session = create_session(cookies_path, SERVICE_OBJECT, base_url)
|
|
242
|
+
return run_report_flow(
|
|
243
|
+
session,
|
|
244
|
+
SERVICE_OBJECT,
|
|
245
|
+
ACTIVITY_TAB_ID,
|
|
246
|
+
_build_submit_body,
|
|
247
|
+
output_path,
|
|
248
|
+
base_url,
|
|
249
|
+
post_submit_hook=_post_submit_closesubmit_factory(base_url),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
download_parts_price_list_report()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: revnext
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Revolution Next (*.revolutionnext.com.au) report downloads via REST API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/Luen/RevNext-TUNE/
|
|
7
|
+
Project-URL: Repository, https://github.com/Luen/RevNext-TUNE/
|
|
8
|
+
Keywords: revnext,revolution-next,reports,api
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: requests
|
|
18
|
+
|
|
19
|
+
# revnext
|
|
20
|
+
|
|
21
|
+
Download Revolution Next (*.revolutionnext.com.au) reports (Parts Price List, Parts by Bin Location) via REST API using cookies/session.
|
|
22
|
+
|
|
23
|
+
Install: `pip install revnext`
|
|
24
|
+
Or from repo root: `pip install -e ./packages/revnext`
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
You need a cookies file (Chrome export for your RevNext domain). Then call the download functions with your instance URL and download location.
|
|
29
|
+
|
|
30
|
+
### Download one report with a specific file path
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from revnext import download_parts_by_bin_report, download_parts_price_list_report
|
|
35
|
+
|
|
36
|
+
base_url = "https://yoursite.revolutionnext.com.au"
|
|
37
|
+
cookies_path = Path("revnext-cookies.json")
|
|
38
|
+
|
|
39
|
+
# Parts by Bin Location
|
|
40
|
+
path1 = download_parts_by_bin_report(
|
|
41
|
+
base_url=base_url,
|
|
42
|
+
output_path=Path("C:/Reports/parts_by_bin.csv"),
|
|
43
|
+
cookies_path=cookies_path,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Parts Price List
|
|
47
|
+
path2 = download_parts_price_list_report(
|
|
48
|
+
base_url=base_url,
|
|
49
|
+
output_path=Path("C:/Reports/parts_price_list.csv"),
|
|
50
|
+
cookies_path=cookies_path,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Example: download both reports (implement in your project)
|
|
55
|
+
|
|
56
|
+
Copy this into your project to run both reports in sequence:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from pathlib import Path
|
|
60
|
+
from typing import Optional
|
|
61
|
+
|
|
62
|
+
from revnext import download_parts_by_bin_report, download_parts_price_list_report
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def download_all_reports(
|
|
66
|
+
cookies_path: Optional[Path | str] = None,
|
|
67
|
+
output_dir: Optional[Path | str] = None,
|
|
68
|
+
base_url: Optional[str] = None,
|
|
69
|
+
) -> list[Path]:
|
|
70
|
+
"""Run both reports and save CSVs. Returns the list of paths where files were saved."""
|
|
71
|
+
output_dir = Path(output_dir) if output_dir is not None else Path.cwd()
|
|
72
|
+
path1 = download_parts_by_bin_report(
|
|
73
|
+
cookies_path=cookies_path,
|
|
74
|
+
output_path=output_dir / "Parts_By_Bin_Location.csv",
|
|
75
|
+
base_url=base_url,
|
|
76
|
+
)
|
|
77
|
+
path2 = download_parts_price_list_report(
|
|
78
|
+
cookies_path=cookies_path,
|
|
79
|
+
output_path=output_dir / "Parts_Price_List.csv",
|
|
80
|
+
base_url=base_url,
|
|
81
|
+
)
|
|
82
|
+
return [path1, path2]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Example: download both reports in parallel (implement in your project)
|
|
86
|
+
|
|
87
|
+
Copy this into your project to run both reports concurrently (Promise.all-style):
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
91
|
+
from pathlib import Path
|
|
92
|
+
from typing import Optional
|
|
93
|
+
|
|
94
|
+
from revnext import download_parts_by_bin_report, download_parts_price_list_report
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def download_all_reports_parallel(
|
|
98
|
+
cookies_path: Optional[Path | str] = None,
|
|
99
|
+
output_dir: Optional[Path | str] = None,
|
|
100
|
+
base_url: Optional[str] = None,
|
|
101
|
+
) -> list[Path]:
|
|
102
|
+
"""Run both reports in parallel. Returns the list of paths where files were saved."""
|
|
103
|
+
output_dir = Path(output_dir) if output_dir is not None else Path.cwd()
|
|
104
|
+
path1 = output_dir / "Parts_By_Bin_Location.csv"
|
|
105
|
+
path2 = output_dir / "Parts_Price_List.csv"
|
|
106
|
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
107
|
+
f1 = executor.submit(
|
|
108
|
+
download_parts_by_bin_report,
|
|
109
|
+
cookies_path=cookies_path,
|
|
110
|
+
output_path=path1,
|
|
111
|
+
base_url=base_url,
|
|
112
|
+
)
|
|
113
|
+
f2 = executor.submit(
|
|
114
|
+
download_parts_price_list_report,
|
|
115
|
+
cookies_path=cookies_path,
|
|
116
|
+
output_path=path2,
|
|
117
|
+
base_url=base_url,
|
|
118
|
+
)
|
|
119
|
+
return [f1.result(), f2.result()]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Using environment variables
|
|
123
|
+
|
|
124
|
+
Set `REVOLUTIONNEXT_URL` and optionally `REVOLUTIONNEXT_COOKIES_PATH`; then you can omit `base_url` and `cookies_path` in code.
|
|
125
|
+
|
|
126
|
+
See the [main repo README](../README.md) for full configuration options.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
revnext/__init__.py,sha256=dkXhuM6Da2dqUr-nzacfPfeEhDnTvoW12ewDdpZbE8M,472
|
|
2
|
+
revnext/common.py,sha256=PkF747ILSdxRaVhj7Putp6bjSjqWzgd3LvtxAiddXqc,6944
|
|
3
|
+
revnext/config.py,sha256=twdxnQn_w3nii-ovG22Cy1E4FRe5ephLh9sZAkMm9Yo,1978
|
|
4
|
+
revnext/parts_by_bin_report.py,sha256=j8aCVMY-8XQExlBpbGWEkueCyiyowHNapnEFQO1JPeU,11158
|
|
5
|
+
revnext/parts_price_list_report.py,sha256=B5fq1lBJt_ttG1WfPaMWt8dm-AstNlLEL62TMvzlFC0,10951
|
|
6
|
+
revnext-0.1.2.dist-info/METADATA,sha256=FHRPfasfl8uTsIS67i0MK_ntuwLP-uHfxp6x_BFcLps,4222
|
|
7
|
+
revnext-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
revnext-0.1.2.dist-info/top_level.txt,sha256=fW_mpOHYljzK-IPZOiJBGDIWkMmcbsjWzqjot5KDUwk,8
|
|
9
|
+
revnext-0.1.2.dist-info/RECORD,,
|