Add application file
Browse files- app.py +500 -0
- generate_keys.py +11 -0
- hashed_pw.pkl +0 -0
- rc_logo.ico +0 -0
- requirements.txt +8 -0
- streamlit_authenticator/__init__.py +267 -0
- streamlit_authenticator/__pycache__/__init__.cpython-38.pyc +0 -0
app.py
ADDED
@@ -0,0 +1,500 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import print_function
|
2 |
+
from imaplib import _Authenticator
|
3 |
+
from itertools import count
|
4 |
+
import json
|
5 |
+
import pickle
|
6 |
+
from pathlib import Path
|
7 |
+
from unicodedata import name
|
8 |
+
import streamlit_authenticator as stauth
|
9 |
+
from re import X
|
10 |
+
import pandas as pd # pip install pandas openpyxl
|
11 |
+
import plotly.express as px # pip install plotly-express
|
12 |
+
import plotly.graph_objects as go
|
13 |
+
import numpy as np
|
14 |
+
import requests
|
15 |
+
import timeit
|
16 |
+
import altair as alt
|
17 |
+
import streamlit as st # pip install streamlit
|
18 |
+
from streamlit_lottie import st_lottie
|
19 |
+
from streamlit_lottie import st_lottie_spinner
|
20 |
+
import datetime as dt
|
21 |
+
import time
|
22 |
+
from datetime import datetime
|
23 |
+
from datetime import timedelta
|
24 |
+
from dateutil.relativedelta import relativedelta # to add days or years
|
25 |
+
# emojis: https://www.webfx.com/tools/emoji-cheat-sheet/
|
26 |
+
|
27 |
+
start = timeit.default_timer()
|
28 |
+
st.set_page_config(page_title="1 Nav sync Zoho", page_icon="rc_logo.ico")
|
29 |
+
|
30 |
+
|
31 |
+
CLIENT_ID = '1000.0G3DK4Y761UL7AHEKYKIMHQLTXWJLO'
|
32 |
+
CLIENT_SECRET = '7752cf983497614b4144c1fa482b21fd378db6a0fa'
|
33 |
+
ZOHO_DATA = {
|
34 |
+
"access_token": "1000.cc8e2f20b4b26629fd81166df1c745e5.e223cc6c2d4f877eff83c24eaaaca5ab",
|
35 |
+
"refresh_token": "1000.5b482d0f8da0e2aa6773b97f0cc9d7f9.29b3cbfd1dc7314dc3a4ec12394434b1",
|
36 |
+
"api_domain": "https://www.zohoapis.com",
|
37 |
+
"token_type": "Bearer",
|
38 |
+
"expires_in": 3600
|
39 |
+
}
|
40 |
+
|
41 |
+
def refresh_auth():
|
42 |
+
|
43 |
+
url = "https://accounts.zoho.com/oauth/v2/token?refresh_token=1000.5b482d0f8da0e2aa6773b97f0cc9d7f9.29b3cbfd1dc7314dc3a4ec12394434b1&client_id=1000.0G3DK4Y761UL7AHEKYKIMHQLTXWJLO&client_secret=7752cf983497614b4144c1fa482b21fd378db6a0fa&grant_type=refresh_token"
|
44 |
+
r = requests.post(url)
|
45 |
+
data = json.loads(r.text)
|
46 |
+
if 'access_token' in data:
|
47 |
+
ZOHO_DATA['access_token'] = data['access_token']
|
48 |
+
|
49 |
+
return data['access_token']
|
50 |
+
|
51 |
+
|
52 |
+
access_token = refresh_auth()
|
53 |
+
|
54 |
+
not_found_order =[]
|
55 |
+
not_found_invoiced =[]
|
56 |
+
not_found_return =[]
|
57 |
+
time_out =[]
|
58 |
+
#----------User AUth----------
|
59 |
+
names = ["Simphiwe Fakude", "Robert Jacobs", "Robert joubert","Jean-Pierre Myburg","Paul Oosthuizen", "Lee Douglas Webster", "Nazley Miranda", "Cindy Santamaria","Natasha Naidoo", "Carla kolbe", "RC Admin"]
|
60 |
+
usernames = ["simphiwef", "robertj","robert", "jp","paulo","leew","nazleym","cindys","natashan","carla", "rcadmin"]
|
61 |
+
file_path = Path(__file__).parent / "hashed_pw.pkl"
|
62 |
+
with file_path.open("rb") as file:
|
63 |
+
hashed_passwords = pickle.load(file)
|
64 |
+
authenticator = stauth.Authenticate(names, usernames, hashed_passwords,"rc_dashboard", "abcdef", cookie_expiry_days=30 )# cookie
|
65 |
+
name, authentication_status, username = authenticator.login('Please Login', 'main')
|
66 |
+
|
67 |
+
|
68 |
+
if authentication_status == False:
|
69 |
+
st.error('Username/password is incorrect')
|
70 |
+
# elif authentication_status == None:
|
71 |
+
# st.warning('Please enter your username and password')
|
72 |
+
elif authentication_status:
|
73 |
+
|
74 |
+
def load_lottieurl(url: str): #load from the web
|
75 |
+
|
76 |
+
r = requests.get(url)
|
77 |
+
if r.status_code != 200:
|
78 |
+
return None
|
79 |
+
return r.json()
|
80 |
+
st.write(f'Welcome *{name}*')
|
81 |
+
lottie_dog=load_lottieurl("https://assets7.lottiefiles.com/packages/lf20_xBGyhl.json")
|
82 |
+
with st_lottie_spinner(lottie_dog, width= 300, key="dog"):
|
83 |
+
|
84 |
+
@st.cache()
|
85 |
+
def read_file(data_file):
|
86 |
+
|
87 |
+
xls = pd.ExcelFile(data_file)
|
88 |
+
|
89 |
+
try:
|
90 |
+
df_Order = pd.read_excel(xls, 'Open Released')
|
91 |
+
except Exception as e:
|
92 |
+
st.error("Incorect Sheet name for Open Released:(")
|
93 |
+
st.stop()
|
94 |
+
try:
|
95 |
+
df_Invoiced = pd.read_excel(xls, 'Posted Invoices')
|
96 |
+
except Exception as e:
|
97 |
+
st.error("Incorect Sheet name for Posted Invoices:(")
|
98 |
+
st.stop()
|
99 |
+
try:
|
100 |
+
df_Return = pd.read_excel(xls, 'SRT')
|
101 |
+
except Exception as e:
|
102 |
+
st.error("Incorect Sheet name for SRT:(")
|
103 |
+
st.stop()
|
104 |
+
return df_Order, df_Invoiced, df_Return
|
105 |
+
|
106 |
+
|
107 |
+
|
108 |
+
|
109 |
+
#time.sleep(4)
|
110 |
+
def main():
|
111 |
+
st.title("1 Nav + Zoho Integration Test")
|
112 |
+
st.subheader("The file should contain this 3 sheets:")
|
113 |
+
data_file = st.file_uploader("[Open Released, Posted Invoices ,SRT]",type=['xlsx'])
|
114 |
+
# lottie_nodata=load_lottieurl("https://assets6.lottiefiles.com/packages/lf20_5awivhzm.json")
|
115 |
+
# st_lottie(lottie_nodata, key="load", width=600)
|
116 |
+
if st.button("Process"):
|
117 |
+
|
118 |
+
if data_file is not None:
|
119 |
+
file_details = {"Filename":data_file.name,"FileType":data_file.type,"FileSize":data_file.size}
|
120 |
+
|
121 |
+
df_Order = read_file(data_file)[0]
|
122 |
+
df_Invoiced = read_file(data_file)[1]
|
123 |
+
df_Return =read_file(data_file)[2]
|
124 |
+
|
125 |
+
# st.write(file_details)
|
126 |
+
# df = pd.read_csv(data_file)
|
127 |
+
# left_column, middle_column, right_column = st.columns(3)
|
128 |
+
# with left_column:
|
129 |
+
# st.dataframe(df_Order)
|
130 |
+
# with middle_column:
|
131 |
+
# st.dataframe(df_Invoiced)
|
132 |
+
# with right_column:
|
133 |
+
# st.dataframe(df_Return)
|
134 |
+
|
135 |
+
|
136 |
+
headers = {"Authorization" : "Zoho-oauthtoken "+access_token, "orgId": "725575894"}
|
137 |
+
latest_iteration = st.empty()
|
138 |
+
print("-----------------------Open Released-------------------")
|
139 |
+
len_df_Order =len(df_Order.index)
|
140 |
+
for i, j in df_Order.iterrows():
|
141 |
+
so_number = j[1]
|
142 |
+
if len_df_Order - i == 1:
|
143 |
+
latest_iteration.text('Done updating Open Released spreadsheet')
|
144 |
+
else:
|
145 |
+
latest_iteration.text(f'Open Released: {len_df_Order - i} records left - {j[1]}')
|
146 |
+
if pd.isna(so_number) ==True:
|
147 |
+
break
|
148 |
+
else:
|
149 |
+
|
150 |
+
so_number = so_number[5:]
|
151 |
+
dateTime= str(j[2])
|
152 |
+
dateTime = dateTime.replace(" ", "T")
|
153 |
+
dateTime = dateTime[:19]+".000Z"
|
154 |
+
cf_1nav_customer_name= j[4]
|
155 |
+
nav_overdue_bal = j[6]
|
156 |
+
nav_credit_hold =j[5]
|
157 |
+
cf_1nav_cus_price_grp =j[8]
|
158 |
+
cf_1nav_sales_resp = j[9]
|
159 |
+
cf_1nav_net_weight = j[11]
|
160 |
+
cf_1nav_amount = j[18]
|
161 |
+
cf_1nav_location_code = j[10]
|
162 |
+
now = datetime.now()
|
163 |
+
|
164 |
+
|
165 |
+
dt_string = now.strftime("%Y-%m-%d %H:%M:%S")
|
166 |
+
sync_date=dt_string.replace(" ", "T")+ ".000Z"
|
167 |
+
status_ = j[0]
|
168 |
+
if status_ == "Open":
|
169 |
+
#desk_status = "Approval Rejected"
|
170 |
+
desk_status = "Pending - finance query"
|
171 |
+
else:
|
172 |
+
desk_status = "Pending - awaiting shipment"
|
173 |
+
|
174 |
+
|
175 |
+
if nav_overdue_bal <1 or str(nav_overdue_bal) =="FALSE" :
|
176 |
+
cf_1nav_overdue_bal = "false"
|
177 |
+
|
178 |
+
else:
|
179 |
+
cf_1nav_overdue_bal= "true"
|
180 |
+
|
181 |
+
|
182 |
+
|
183 |
+
if nav_credit_hold <1 or str(nav_credit_hold) =="FALSE" :
|
184 |
+
cf_1nav_credit_hold = "false"
|
185 |
+
else:
|
186 |
+
cf_1nav_credit_hold ="true"
|
187 |
+
|
188 |
+
|
189 |
+
URL = "https://desk.zoho.com/api/v1/tickets/search?limit=1&customField1=cf_s_o_number:"+so_number
|
190 |
+
|
191 |
+
|
192 |
+
|
193 |
+
|
194 |
+
try:
|
195 |
+
req = requests.get(url = URL, headers= headers)
|
196 |
+
|
197 |
+
except:
|
198 |
+
print("Connection refused by the server..")
|
199 |
+
print("Let me sleep for 5 seconds")
|
200 |
+
print("ZZzzzz...")
|
201 |
+
time.sleep(2)
|
202 |
+
print("Was a nice sleep, now let me continue...")
|
203 |
+
|
204 |
+
|
205 |
+
|
206 |
+
if req.status_code == 200:
|
207 |
+
data_respo = json.loads(req.text)
|
208 |
+
ticket_id = data_respo['data'][0]['id']
|
209 |
+
# So Number founf then update fields
|
210 |
+
url = "https://desk.zoho.com/api/v1/tickets/"+ticket_id
|
211 |
+
data ={ "status":desk_status,
|
212 |
+
"cf":{
|
213 |
+
"cf_1_nav_sync":"true",
|
214 |
+
"cf_1nav_status":status_,
|
215 |
+
"cf_1nav_customer_name":cf_1nav_customer_name,
|
216 |
+
"cf_1nav_cus_price_grp":cf_1nav_cus_price_grp,
|
217 |
+
"cf_1nav_sales_resp":cf_1nav_sales_resp,
|
218 |
+
"cf_1nav_date_time":dateTime,
|
219 |
+
"cf_1nav_amount":cf_1nav_amount,
|
220 |
+
"cf_1nav_net_weight":cf_1nav_net_weight,
|
221 |
+
"cf_1nav_overdue_bal":cf_1nav_overdue_bal,
|
222 |
+
"cf_1nav_credit_hold":cf_1nav_credit_hold,
|
223 |
+
"cf_1nav_location_code": cf_1nav_location_code,
|
224 |
+
"cf_1nav_sync_time": sync_date,
|
225 |
+
|
226 |
+
"cf_added_by": name
|
227 |
+
}
|
228 |
+
}
|
229 |
+
try:
|
230 |
+
r = requests.patch(url, headers=headers, json=data)
|
231 |
+
except:
|
232 |
+
print("Connection refused by the server..")
|
233 |
+
print("Let me sleep for 5 seconds")
|
234 |
+
print("ZZzzzz...")
|
235 |
+
time_out.append(so_number)
|
236 |
+
time.sleep(3)
|
237 |
+
print("Was a nice sleep, now let me continue...")
|
238 |
+
|
239 |
+
|
240 |
+
else:
|
241 |
+
not_found_order.append(so_number)
|
242 |
+
print("Search: ", req.status_code, " Update: ",req.status_code, " - ", so_number)
|
243 |
+
|
244 |
+
print("-----------------------Invoiced-------------------")
|
245 |
+
latest_iteration = st.empty()
|
246 |
+
len_df_Invoiced =len(df_Invoiced.index)
|
247 |
+
for i, j in df_Invoiced.iterrows():
|
248 |
+
if len_df_Invoiced - i == 1:
|
249 |
+
latest_iteration.text('Done updating Invoiced spreadsheet')
|
250 |
+
else:
|
251 |
+
latest_iteration.text(f'Invoiced: {((len_df_Invoiced - i))} records left - {j[6]}')
|
252 |
+
so_number = j[6]
|
253 |
+
if pd.isna(so_number) ==True:
|
254 |
+
break
|
255 |
+
else:
|
256 |
+
|
257 |
+
so_number = so_number[5:]
|
258 |
+
cf_1nav_customer_name= j[2]
|
259 |
+
#cf_1nav_cus_price_grp =j[8]
|
260 |
+
cf_1nav_sales_resp = j[7]
|
261 |
+
cf_1nav_net_weight = j[9]
|
262 |
+
cf_1nav_amount = j[3]
|
263 |
+
cf_1nav_location_code = j[8]
|
264 |
+
cf_1nav_req_del_date = str(j[5])[:10]
|
265 |
+
cf_1nav_shipping_date = str(j[11])[:10]
|
266 |
+
cf_1nav_doc_date =str(j[10])[:10]
|
267 |
+
now = datetime.now()
|
268 |
+
|
269 |
+
|
270 |
+
dt_string = now.strftime("%Y-%m-%d %H:%M:%S")
|
271 |
+
sync_date = dt_string.replace(" ", "T")+ ".000Z"
|
272 |
+
|
273 |
+
|
274 |
+
URL = "https://desk.zoho.com/api/v1/tickets/search?limit=1&customField1=cf_s_o_number:"+so_number
|
275 |
+
headers = {"Authorization" : "Zoho-oauthtoken "+access_token, "orgId": "725575894"}
|
276 |
+
|
277 |
+
|
278 |
+
try:
|
279 |
+
req = requests.get(url = URL, headers= headers)
|
280 |
+
|
281 |
+
except:
|
282 |
+
print("Connection refused by the server..")
|
283 |
+
print("Let me sleep for 3 seconds")
|
284 |
+
print("ZZzzzz...")
|
285 |
+
time.sleep(3)
|
286 |
+
print("Was a nice sleep, now let me continue...")
|
287 |
+
|
288 |
+
|
289 |
+
if req.status_code == 200:
|
290 |
+
data_resp = json.loads(req.text)
|
291 |
+
ticket_id = data_resp['data'][0]['id']
|
292 |
+
url = "https://desk.zoho.com/api/v1/tickets/"+ticket_id
|
293 |
+
# So Number founf then update fields
|
294 |
+
data ={
|
295 |
+
"cf":{"status": "Closed",
|
296 |
+
"cf_1_nav_sync":"true",
|
297 |
+
"cf_1nav_status":"Invoiced",
|
298 |
+
"cf_1nav_customer_name":cf_1nav_customer_name,
|
299 |
+
"cf_1nav_sales_resp":cf_1nav_sales_resp,
|
300 |
+
"cf_1nav_amount":cf_1nav_amount,
|
301 |
+
"cf_1nav_net_weight":cf_1nav_net_weight,
|
302 |
+
"cf_1nav_req_del_date":cf_1nav_req_del_date,
|
303 |
+
"cf_1nav_shipping_date":cf_1nav_shipping_date,
|
304 |
+
"cf_1nav_location_code": cf_1nav_location_code,
|
305 |
+
"cf_1nav_doc_date": cf_1nav_doc_date,
|
306 |
+
"cf_1nav_overdue_bal":"false",
|
307 |
+
"cf_1nav_credit_hold":"false",
|
308 |
+
"cf_1nav_sync_time": sync_date,
|
309 |
+
"cf_added_by": name
|
310 |
+
}
|
311 |
+
}
|
312 |
+
try:
|
313 |
+
r = requests.patch(url, headers=headers, json=data)
|
314 |
+
|
315 |
+
except:
|
316 |
+
print("Connection refused by the server..")
|
317 |
+
print("Let me sleep for 3 seconds")
|
318 |
+
print("ZZzzzz...")
|
319 |
+
time.sleep(3)
|
320 |
+
time_out.append(so_number)
|
321 |
+
print("Was a nice sleep, now let me continue...")
|
322 |
+
|
323 |
+
|
324 |
+
else:
|
325 |
+
not_found_invoiced.append(so_number)
|
326 |
+
print("Search: ", req.status_code, " Update: ",req.status_code, " - ", so_number)
|
327 |
+
|
328 |
+
|
329 |
+
print("-----------------------SRT-------------------")
|
330 |
+
len_df_Return =len(df_Return.index)
|
331 |
+
for i, j in df_Return.iterrows():
|
332 |
+
so_number = j[0]
|
333 |
+
if len_df_Return - i == 1:
|
334 |
+
latest_iteration.text('Done updating SRT spreadsheet ')
|
335 |
+
else:
|
336 |
+
latest_iteration.text(f'SRT: {len_df_Return - i} records left - {j[0]}')
|
337 |
+
if pd.isna(so_number) ==True:
|
338 |
+
break
|
339 |
+
else:
|
340 |
+
|
341 |
+
cf_1nav_customer_name = j[2]
|
342 |
+
cf_1nav_sales_resp = j[3]
|
343 |
+
so_number = so_number[6:]
|
344 |
+
cf_1nav_location_code =j[4]
|
345 |
+
cf_1nav_doc_date =str(j[8])[:10]
|
346 |
+
now = datetime.now()
|
347 |
+
|
348 |
+
|
349 |
+
dt_string = now.strftime("%Y-%m-%d %H:%M:%S")
|
350 |
+
sync_date = dt_string.replace(" ", "T")+ ".000Z"
|
351 |
+
|
352 |
+
URL = "https://desk.zoho.com/api/v1/tickets/search?limit=1&customField1=cf_s_o_number:"+so_number
|
353 |
+
headers = {"Authorization" : "Zoho-oauthtoken "+access_token, "orgId": "725575894"}
|
354 |
+
|
355 |
+
|
356 |
+
try:
|
357 |
+
req = requests.get(url = URL, headers= headers)
|
358 |
+
|
359 |
+
except:
|
360 |
+
print("Connection refused by the server..")
|
361 |
+
print("Let me sleep for 3 seconds")
|
362 |
+
print("ZZzzzz...")
|
363 |
+
time.sleep(3)
|
364 |
+
print("Was a nice sleep, now let me continue...")
|
365 |
+
|
366 |
+
|
367 |
+
if req.status_code == 200:
|
368 |
+
data_resp = json.loads(req.text)
|
369 |
+
ticket_id = data_resp['data'][0]['id']
|
370 |
+
# So Number founf then update fields
|
371 |
+
url = "https://desk.zoho.com/api/v1/tickets/"+ticket_id
|
372 |
+
|
373 |
+
data ={
|
374 |
+
"cf":{
|
375 |
+
"cf_1nav_status":"Processed",
|
376 |
+
"cf_1_nav_sync":"true",
|
377 |
+
"cf_1nav_customer_name":cf_1nav_customer_name,
|
378 |
+
"cf_1nav_sales_resp":cf_1nav_sales_resp,
|
379 |
+
"cf_1nav_location_code": cf_1nav_location_code,
|
380 |
+
"cf_1nav_doc_date": cf_1nav_doc_date,
|
381 |
+
"cf_1nav_overdue_bal":"false",
|
382 |
+
"cf_1nav_credit_hold":"false",
|
383 |
+
"cf_1nav_sync_time": sync_date,
|
384 |
+
"cf_added_by": name
|
385 |
+
|
386 |
+
}
|
387 |
+
}
|
388 |
+
try:
|
389 |
+
r = requests.patch(url, headers=headers, json=data)
|
390 |
+
|
391 |
+
except:
|
392 |
+
print("Connection refused by the server..")
|
393 |
+
print("Let me sleep for 5 seconds")
|
394 |
+
print("ZZzzzz...")
|
395 |
+
time.sleep(5)
|
396 |
+
time_out.append(so_number)
|
397 |
+
print("Was a nice sleep, now let me continue...")
|
398 |
+
|
399 |
+
else:
|
400 |
+
not_found_return.append(so_number)
|
401 |
+
print("Search: ", req.status_code, " Update: ",req.status_code, " - ", so_number)
|
402 |
+
stop = timeit.default_timer()
|
403 |
+
execution_time = stop - start
|
404 |
+
c = """<html>
|
405 |
+
<head></head>
|
406 |
+
<body><p>Hi Naz and Cindy, <br><br></p></body>
|
407 |
+
</html>"""
|
408 |
+
space= """<html>
|
409 |
+
<br><br>
|
410 |
+
</html>"""
|
411 |
+
content = c+"Here is the lists of all the SO Number that are not on Zoho Desk, but in 1 Nav: " + space +"""<html>
|
412 |
+
<head></head><body><p>------------------ <strong>Open Released</strong>------------------- <br> </p></body>
|
413 |
+
</html>"""+str(not_found_order)+"""<html>
|
414 |
+
<head></head>
|
415 |
+
<br>
|
416 |
+
<body><p>---------------------<strong>Posted Invoices</strong>--------------------- <br></p></body>
|
417 |
+
</html>"""+ str(not_found_invoiced)+"""<html>
|
418 |
+
<head></head>
|
419 |
+
<br>
|
420 |
+
<body><p>-----------------------<strong>SRT</strong>---------------------------- <br></p></body>
|
421 |
+
</html>"""+ str(not_found_return)+"""<html>
|
422 |
+
<head></head>
|
423 |
+
<br>
|
424 |
+
<body><p>-----------------------<strong>Time Out</strong>---------------------------- <br></p></body>
|
425 |
+
</html>"""+ str(time_out)
|
426 |
+
|
427 |
+
|
428 |
+
|
429 |
+
|
430 |
+
email_body = """<html>
|
431 |
+
<head></head>
|
432 |
+
<body><p>Hi, <br><br></p> The Integration has been completed<br><br> </body>
|
433 |
+
</html>""" +"Runtime: "+ str(round(execution_time,2))+" seconds."
|
434 |
+
|
435 |
+
url = "https://desk.zoho.com/api/v1/tickets"
|
436 |
+
data ={ "subject":"SO Number not Found in CRM",
|
437 |
+
"departmentId":"541303000000434081",
|
438 |
+
|
439 |
+
"status" : "Open",
|
440 |
+
"teamId":"541303000031453272",
|
441 |
+
"contactId":"541303000024559001",
|
442 |
+
"description":content,
|
443 |
+
|
444 |
+
"cf":{
|
445 |
+
"cf_ticket_type":"1Nav error",
|
446 |
+
}
|
447 |
+
}
|
448 |
+
r = requests.post(url, headers=headers, json=data)
|
449 |
+
|
450 |
+
url = 'https://mail.zoho.com/api/accounts/6014958000000008002/messages'
|
451 |
+
|
452 |
+
data = {
|
453 |
+
"fromAddress":"simphiwef@boomerangsa.co.za",
|
454 |
+
"toAddress": username+"@boomerangsa.co.za",
|
455 |
+
"ccAddress": "simphiwef@boomerangsa.co.za",
|
456 |
+
"bccAddress": "",
|
457 |
+
"subject": "Zoho Desk Integration completed",
|
458 |
+
"content": email_body,
|
459 |
+
"askReceipt": "no"
|
460 |
+
}
|
461 |
+
headers = {
|
462 |
+
'Authorization': 'Zoho-oauthtoken ' + ZOHO_DATA['access_token']
|
463 |
+
}
|
464 |
+
r = requests.post(url, headers=headers, json=data)
|
465 |
+
|
466 |
+
|
467 |
+
|
468 |
+
|
469 |
+
|
470 |
+
st.markdown("<h2 style='text-align: center; color: white;'>Synchronization completed!</h2>", unsafe_allow_html=True)
|
471 |
+
lottie_nodata=load_lottieurl("https://assets7.lottiefiles.com/private_files/lf30_rjqwaenm.json")
|
472 |
+
#st_lottie(lottie_nodata, key="done", width=270)
|
473 |
+
st.balloons()
|
474 |
+
print("-----------------------------------------------")
|
475 |
+
|
476 |
+
print (f"Run Time: {execution_time:.2f} Seconds")
|
477 |
+
st.subheader(f"Run Time: {execution_time:.2f} Seconds")
|
478 |
+
|
479 |
+
|
480 |
+
|
481 |
+
if __name__ == '__main__':
|
482 |
+
main()
|
483 |
+
|
484 |
+
|
485 |
+
|
486 |
+
|
487 |
+
# ---- HIDE STREAMLIT STYLE ----
|
488 |
+
hide_st_style = """
|
489 |
+
<style>
|
490 |
+
#MainMenu {visibility: hidden;}
|
491 |
+
footer {visibility: hidden;}
|
492 |
+
header {visibility: hidden;}
|
493 |
+
#title {
|
494 |
+
text-align: center
|
495 |
+
</style>
|
496 |
+
"""
|
497 |
+
|
498 |
+
st.markdown(hide_st_style, unsafe_allow_html=True)
|
499 |
+
|
500 |
+
|
generate_keys.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pickle
|
2 |
+
from pathlib import Path
|
3 |
+
from argon2 import hash_password
|
4 |
+
import streamlit_authenticator as stauth
|
5 |
+
names = ["Simphiwe Fakude", "Robert Jacobs", "Robert joubert","Jean-Pierre Myburg","Paul Oosthuizen", "Lee Douglas Webster", "Nazley Miranda", "Cindy Santamaria","Natasha Naidoo", "Carla kolbe", "RC Admin"]
|
6 |
+
usernames = ["simphiwef", "robertj","robert", "jp","paulo","leew","nazleym","cindys","natashan","carla", "rcadmin"]
|
7 |
+
passwords = ["sleepingdog","Boom@123","master","doubleclick", "justdoit","tryme", "letmein","hello","master", "letmein" ,"2Birds1Stone"]
|
8 |
+
hashed_passwords = stauth.Hasher(passwords).generate() # bycrat algor
|
9 |
+
file_path = Path(__file__).parent / "hashed_pw.pkl"
|
10 |
+
with file_path.open("wb") as file:
|
11 |
+
pickle.dump(hashed_passwords, file)
|
hashed_pw.pkl
ADDED
Binary file (709 Bytes). View file
|
|
rc_logo.ico
ADDED
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
plotly
|
2 |
+
pip
|
3 |
+
pandas
|
4 |
+
streamlit
|
5 |
+
openpyxl
|
6 |
+
protobuf
|
7 |
+
streamlit-lottie
|
8 |
+
streamlit-authenticator
|
streamlit_authenticator/__init__.py
ADDED
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import jwt
|
2 |
+
import yaml
|
3 |
+
import bcrypt
|
4 |
+
import streamlit as st
|
5 |
+
from yaml.loader import SafeLoader
|
6 |
+
from datetime import datetime, timedelta
|
7 |
+
import extra_streamlit_components as stx
|
8 |
+
import streamlit.components.v1 as components
|
9 |
+
|
10 |
+
_RELEASE = True
|
11 |
+
|
12 |
+
class Hasher:
|
13 |
+
def __init__(self, passwords):
|
14 |
+
"""Create a new instance of "Hasher".
|
15 |
+
Parameters
|
16 |
+
----------
|
17 |
+
passwords: list
|
18 |
+
The list of plain text passwords to be hashed.
|
19 |
+
Returns
|
20 |
+
-------
|
21 |
+
list
|
22 |
+
The list of hashed passwords.
|
23 |
+
"""
|
24 |
+
self.passwords = passwords
|
25 |
+
|
26 |
+
def hash(self, password):
|
27 |
+
"""
|
28 |
+
Parameters
|
29 |
+
----------
|
30 |
+
password: str
|
31 |
+
The plain text password to be hashed.
|
32 |
+
Returns
|
33 |
+
-------
|
34 |
+
str
|
35 |
+
The hashed password.
|
36 |
+
"""
|
37 |
+
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
38 |
+
|
39 |
+
def generate(self):
|
40 |
+
"""
|
41 |
+
Returns
|
42 |
+
-------
|
43 |
+
list
|
44 |
+
The list of hashed passwords.
|
45 |
+
"""
|
46 |
+
hashedpw = []
|
47 |
+
|
48 |
+
for password in self.passwords:
|
49 |
+
hashedpw.append(self.hash(password))
|
50 |
+
return hashedpw
|
51 |
+
|
52 |
+
class Authenticate:
|
53 |
+
def __init__(self, names, usernames, passwords, cookie_name, key, cookie_expiry_days=30):
|
54 |
+
"""Create a new instance of "Authenticate".
|
55 |
+
Parameters
|
56 |
+
----------
|
57 |
+
names: list
|
58 |
+
The list of names of users.
|
59 |
+
usernames: list
|
60 |
+
The list of usernames in the same order as names.
|
61 |
+
passwords: list
|
62 |
+
The list of hashed passwords in the same order as names.
|
63 |
+
cookie_name: str
|
64 |
+
The name of the JWT cookie stored on the client's browser for passwordless reauthentication.
|
65 |
+
key: str
|
66 |
+
The key to be used for hashing the signature of the JWT cookie.
|
67 |
+
cookie_expiry_days: int
|
68 |
+
The number of days before the cookie expires on the client's browser.
|
69 |
+
Returns
|
70 |
+
-------
|
71 |
+
str
|
72 |
+
Name of authenticated user.
|
73 |
+
boolean
|
74 |
+
The status of authentication, None: no credentials entered, False: incorrect credentials, True: correct credentials.
|
75 |
+
str
|
76 |
+
Username of authenticated user.
|
77 |
+
"""
|
78 |
+
self.names = names
|
79 |
+
self.usernames = usernames
|
80 |
+
self.passwords = passwords
|
81 |
+
self.cookie_name = cookie_name
|
82 |
+
self.key = key
|
83 |
+
self.cookie_expiry_days = cookie_expiry_days
|
84 |
+
self.cookie_manager = stx.CookieManager()
|
85 |
+
|
86 |
+
if 'name' not in st.session_state:
|
87 |
+
st.session_state['name'] = None
|
88 |
+
if 'authentication_status' not in st.session_state:
|
89 |
+
st.session_state['authentication_status'] = None
|
90 |
+
if 'username' not in st.session_state:
|
91 |
+
st.session_state['username'] = None
|
92 |
+
if 'logout' not in st.session_state:
|
93 |
+
st.session_state['logout'] = None
|
94 |
+
|
95 |
+
def token_encode(self):
|
96 |
+
"""
|
97 |
+
Returns
|
98 |
+
-------
|
99 |
+
str
|
100 |
+
The JWT cookie for passwordless reauthentication.
|
101 |
+
"""
|
102 |
+
return jwt.encode({'name':st.session_state['name'],
|
103 |
+
'username':st.session_state['username'],
|
104 |
+
'exp_date':self.exp_date}, self.key, algorithm='HS256')
|
105 |
+
|
106 |
+
def token_decode(self):
|
107 |
+
"""
|
108 |
+
Returns
|
109 |
+
-------
|
110 |
+
str
|
111 |
+
The decoded JWT cookie for passwordless reauthentication.
|
112 |
+
"""
|
113 |
+
try:
|
114 |
+
return jwt.decode(self.token, self.key, algorithms=['HS256'])
|
115 |
+
except:
|
116 |
+
return False
|
117 |
+
|
118 |
+
def exp_date(self):
|
119 |
+
"""
|
120 |
+
Returns
|
121 |
+
-------
|
122 |
+
str
|
123 |
+
The JWT cookie's expiry timestamp in Unix epoch.
|
124 |
+
"""
|
125 |
+
return (datetime.utcnow() + timedelta(days=self.cookie_expiry_days)).timestamp()
|
126 |
+
|
127 |
+
def check_pw(self):
|
128 |
+
"""
|
129 |
+
Returns
|
130 |
+
-------
|
131 |
+
boolean
|
132 |
+
The validation state for the input password by comparing it to the hashed password on disk.
|
133 |
+
"""
|
134 |
+
return bcrypt.checkpw(self.password.encode(), self.passwords[self.index].encode())
|
135 |
+
|
136 |
+
def login(self, form_name, location='main'):
|
137 |
+
"""Create a new instance of "authenticate".
|
138 |
+
Parameters
|
139 |
+
----------
|
140 |
+
form_name: str
|
141 |
+
The rendered name of the login form.
|
142 |
+
location: str
|
143 |
+
The location of the login form i.e. main or sidebar.
|
144 |
+
Returns
|
145 |
+
-------
|
146 |
+
str
|
147 |
+
Name of authenticated user.
|
148 |
+
boolean
|
149 |
+
The status of authentication, None: no credentials entered, False: incorrect credentials, True: correct credentials.
|
150 |
+
str
|
151 |
+
Username of authenticated user.
|
152 |
+
"""
|
153 |
+
if location not in ['main', 'sidebar']:
|
154 |
+
raise ValueError("Location must be one of 'main' or 'sidebar'")
|
155 |
+
|
156 |
+
if not st.session_state['authentication_status']:
|
157 |
+
self.token = self.cookie_manager.get(self.cookie_name)
|
158 |
+
if self.token is not None:
|
159 |
+
self.token = self.token_decode()
|
160 |
+
if self.token is not False:
|
161 |
+
if not st.session_state['logout']:
|
162 |
+
if self.token['exp_date'] > datetime.utcnow().timestamp():
|
163 |
+
if 'name' and 'username' in self.token:
|
164 |
+
st.session_state['name'] = self.token['name']
|
165 |
+
st.session_state['username'] = self.token['username']
|
166 |
+
st.session_state['authentication_status'] = True
|
167 |
+
|
168 |
+
if st.session_state['authentication_status'] != True:
|
169 |
+
if location == 'main':
|
170 |
+
login_form = st.form('Login')
|
171 |
+
elif location == 'sidebar':
|
172 |
+
login_form = st.sidebar.form('Login')
|
173 |
+
|
174 |
+
login_form.subheader(form_name)
|
175 |
+
self.username = login_form.text_input('Username')
|
176 |
+
st.session_state['username'] = self.username
|
177 |
+
self.password = login_form.text_input('Password', type='password')
|
178 |
+
|
179 |
+
if login_form.form_submit_button('Login'):
|
180 |
+
self.index = None
|
181 |
+
for i in range(0, len(self.usernames)):
|
182 |
+
if self.usernames[i] == self.username:
|
183 |
+
self.index = i
|
184 |
+
if self.index is not None:
|
185 |
+
try:
|
186 |
+
if self.check_pw():
|
187 |
+
st.session_state['name'] = self.names[self.index]
|
188 |
+
self.exp_date = self.exp_date()
|
189 |
+
self.token = self.token_encode()
|
190 |
+
self.cookie_manager.set(self.cookie_name, self.token,
|
191 |
+
expires_at=datetime.now() + timedelta(days=self.cookie_expiry_days))
|
192 |
+
st.session_state['authentication_status'] = True
|
193 |
+
else:
|
194 |
+
st.session_state['authentication_status'] = False
|
195 |
+
except Exception as e:
|
196 |
+
print(e)
|
197 |
+
else:
|
198 |
+
st.session_state['authentication_status'] = False
|
199 |
+
|
200 |
+
return st.session_state['name'], st.session_state['authentication_status'], st.session_state['username']
|
201 |
+
|
202 |
+
def logout(self, button_name, location='main'):
|
203 |
+
"""Creates a logout button.
|
204 |
+
Parameters
|
205 |
+
----------
|
206 |
+
button_name: str
|
207 |
+
The rendered name of the logout button.
|
208 |
+
location: str
|
209 |
+
The location of the logout button i.e. main or sidebar.
|
210 |
+
"""
|
211 |
+
if location not in ['main', 'sidebar']:
|
212 |
+
raise ValueError("Location must be one of 'main' or 'sidebar'")
|
213 |
+
|
214 |
+
if location == 'main':
|
215 |
+
if st.button(button_name):
|
216 |
+
self.cookie_manager.delete(self.cookie_name)
|
217 |
+
st.session_state['logout'] = True
|
218 |
+
st.session_state['name'] = None
|
219 |
+
st.session_state['username'] = None
|
220 |
+
st.session_state['authentication_status'] = None
|
221 |
+
elif location == 'sidebar':
|
222 |
+
if st.sidebar.button(button_name):
|
223 |
+
self.cookie_manager.delete(self.cookie_name)
|
224 |
+
st.session_state['logout'] = True
|
225 |
+
st.session_state['name'] = None
|
226 |
+
st.session_state['username'] = None
|
227 |
+
st.session_state['authentication_status'] = None
|
228 |
+
|
229 |
+
if not _RELEASE:
|
230 |
+
|
231 |
+
#hashed_passwords = Hasher(['123', '456']).generate()
|
232 |
+
|
233 |
+
with open('../config.yaml') as file:
|
234 |
+
config = yaml.load(file, Loader=SafeLoader)
|
235 |
+
|
236 |
+
authenticator = Authenticate(
|
237 |
+
config['credentials']['names'],
|
238 |
+
config['credentials']['usernames'],
|
239 |
+
config['credentials']['passwords'],
|
240 |
+
config['cookie']['name'],
|
241 |
+
config['cookie']['key'],
|
242 |
+
config['cookie']['expiry_days']
|
243 |
+
)
|
244 |
+
|
245 |
+
name, authentication_status, username = authenticator.login('Login', 'main')
|
246 |
+
|
247 |
+
if authentication_status:
|
248 |
+
authenticator.logout('Logout', 'main')
|
249 |
+
st.write(f'Welcome *{name}*')
|
250 |
+
st.title('Some content')
|
251 |
+
elif authentication_status == False:
|
252 |
+
st.error('Username/password is incorrect')
|
253 |
+
elif authentication_status == None:
|
254 |
+
st.warning('Please enter your username and password')
|
255 |
+
|
256 |
+
# Alternatively you use st.session_state['name'] and
|
257 |
+
# st.session_state['authentication_status'] to access the name and
|
258 |
+
# authentication_status.
|
259 |
+
|
260 |
+
# if st.session_state['authentication_status']:
|
261 |
+
# authenticator.logout('Logout', 'main')
|
262 |
+
# st.write(f'Welcome *{st.session_state["name"]}*')
|
263 |
+
# st.title('Some content')
|
264 |
+
# elif st.session_state['authentication_status'] == False:
|
265 |
+
# st.error('Username/password is incorrect')
|
266 |
+
# elif st.session_state['authentication_status'] == None:
|
267 |
+
# st.warning('Please enter your username and password')
|
streamlit_authenticator/__pycache__/__init__.cpython-38.pyc
ADDED
Binary file (7.47 kB). View file
|
|