Cleaned up and expanded commenting
This commit is contained in:
parent
fa27a9e403
commit
07ffe59aaf
11
driver.py
11
driver.py
@ -6,10 +6,9 @@ import helperFunctions as HF
|
||||
import scheduledPayrollWindow as PWin
|
||||
|
||||
|
||||
#TODO workflow with git
|
||||
#TODO demonstrate workflow with git
|
||||
#TODO search section/date (show curr and orig people assigned)
|
||||
|
||||
#TODO code comment
|
||||
#TODO document how to setup for next semester (staff and section changes)
|
||||
#TODO document how to get through the cert expiration every week
|
||||
|
||||
@ -49,7 +48,6 @@ stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
|
||||
#remove leading/trailing whitespace
|
||||
secD = secD.apply(lambda x: x.str.strip())
|
||||
stfD = stfD.apply(lambda x: x.str.strip())
|
||||
#stfD = stfD.apply(lambda x: x.str.lower())
|
||||
|
||||
#rewrite to file (each GUI function reads/writes on its own so that the state on disk matches what the user expects while the program is running)
|
||||
secD.to_csv(sectionDatabaseFilename,index=False,index_label=False)
|
||||
@ -81,9 +79,6 @@ df2 = pd.read_csv(sectionDatabaseFilename[:-4]+"_Orig.csv",dtype=str)
|
||||
df1.fillna(value="",inplace=True)
|
||||
df2.fillna(value="",inplace=True)
|
||||
|
||||
#diff=df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]
|
||||
#diff=pd.concat([df1,df2]).drop_duplicates(keep=False)
|
||||
|
||||
diff = df1.merge(df2, indicator=True, how='outer')
|
||||
diff=diff.loc[diff['_merge'] != 'both']
|
||||
|
||||
@ -91,8 +86,4 @@ print("\n"+"-"*200+"\nTo-Date difference from default shifts:")
|
||||
print(diff)
|
||||
print("-"*200)
|
||||
|
||||
|
||||
|
||||
#PWin.scheduledPayWin(sectionDatabaseFilename,RegHoursFilename,defaultFont)
|
||||
|
||||
#HF.sendEmail("murraydr","CSE102 Substitution","This is a test message")
|
||||
|
@ -20,7 +20,8 @@ sectionDatabaseFilename="sectionsDatabase.csv"
|
||||
staffDatabaseFilename="staffDatabase.csv"
|
||||
|
||||
|
||||
#Converts full names to nedIDs and then back again based on the staff database
|
||||
#Converts full name to nedID based on the staff database
|
||||
#Uses a mildly 'fuzzy' search where the searched-for string must only occur *somewhere* in the actual full name
|
||||
def nameToID(searchName,getAllMatches=False):
|
||||
if searchName == "":
|
||||
return ""
|
||||
@ -34,6 +35,8 @@ def nameToID(searchName,getAllMatches=False):
|
||||
return ret
|
||||
else:
|
||||
return ret[0]
|
||||
|
||||
#Converts netID to full name for GUI human-readability
|
||||
def IDToName(searchID):
|
||||
if searchID=="":
|
||||
return ""
|
||||
@ -43,6 +46,7 @@ def IDToName(searchID):
|
||||
name+="*"#append asterisk to name
|
||||
return name
|
||||
|
||||
#Tests if two requests overlap i.e. have at least one day/time combo in common
|
||||
def isRequestOverlap(req1,req2):
|
||||
print(req1)
|
||||
print(req2)
|
||||
@ -59,37 +63,49 @@ def isRequestOverlap(req1,req2):
|
||||
overlap=True
|
||||
return overlap
|
||||
|
||||
#Get the time that a section takes place during
|
||||
def getTimeFromSection(section):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
return(secD.loc[secD['Section']==section]['Time'].iloc[0])
|
||||
|
||||
#Get the location that a section takes place in
|
||||
def getLocationFromSection(section):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
return(secD.loc[secD['Section']==section]['Location'].iloc[0])
|
||||
|
||||
def isAssigned(netID,period,date):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
ret= False
|
||||
filt = (secD.loc[(secD['Section']==period) & (secD['Date']==date)].apply(lambda r: r.astype('string').str.contains(netID).any(), axis=1))
|
||||
if filt.any(): #if the requestor is not assigned to that section
|
||||
ret=True
|
||||
return ret
|
||||
|
||||
def reassign(date,period,oldID,newID):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
secD.loc[(secD['Section']==period) & (secD['Date']==date)]=secD.loc[(secD['Section']==period) & (secD['Date']==date)].replace(oldID,newID)
|
||||
secD.to_csv(sectionDatabaseFilename,index=False,index_label=False)
|
||||
|
||||
def getSectionTitles():
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
sections=list(secD.drop_duplicates(subset=["Section"])["Section"].values)
|
||||
return sections
|
||||
|
||||
#Get all dates that a section takes place in
|
||||
def getDatesFromSection(sec):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
dates = list(secD.loc[secD["Section"]==sec]["Date"])
|
||||
return dates
|
||||
|
||||
#Tests if a netID is currently assigned to a given section on a given date
|
||||
def isAssigned(netID,period,date):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
ret= False
|
||||
filt = (secD.loc[(secD['Section']==period) & (secD['Date']==date)].apply(lambda r: r.astype('string').str.contains(netID).any(), axis=1))
|
||||
if filt.any(): #if the requestor is among the names in that section-date
|
||||
ret=True
|
||||
return ret
|
||||
|
||||
#Overwrite a netID in one row of the section database with another
|
||||
def reassign(date,period,oldID,newID):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
secD.loc[(secD['Section']==period) & (secD['Date']==date)]=secD.loc[(secD['Section']==period) & (secD['Date']==date)].replace(oldID,newID)
|
||||
secD.to_csv(sectionDatabaseFilename,index=False,index_label=False)
|
||||
|
||||
#Get the strings of all section titles currently in the database (dropping duplicates)
|
||||
def getAllSectionTitles():
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
sections=list(secD.drop_duplicates(subset=["Section"])["Section"].values)
|
||||
return sections
|
||||
|
||||
#Get the strings of all names currently in the staff database
|
||||
def getAllNames():
|
||||
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
|
||||
return list(stfD["Name"].values)
|
||||
|
||||
#Add or subtract from the count of a given person's sub request history in one category (e.g. rejections)
|
||||
def incrementSubCount(netID,category,amount=1):
|
||||
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
|
||||
if category==0 or category=="APP":
|
||||
@ -108,8 +124,8 @@ def incrementSubCount(netID,category,amount=1):
|
||||
stfD.loc[stfD['NetID']==netID,columnName]=str(float(stfD.loc[stfD['NetID']==netID,columnName])+amount)
|
||||
stfD.to_csv(staffDatabaseFilename,index=False,index_label=False)
|
||||
|
||||
#Get an array of the counts of each category of sub history (Approved, Accepted, Rejected, Cancelled, Fulfilled) for a given person
|
||||
def getSubCount(netID):
|
||||
|
||||
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
|
||||
APP = stfD.loc[(stfD["NetID"]==netID)]["Approved Substitutions"].values[0]
|
||||
ACC = stfD.loc[(stfD["NetID"]==netID)]["Accepted Substitutions"].values[0]
|
||||
@ -118,6 +134,9 @@ def getSubCount(netID):
|
||||
FUL = stfD.loc[(stfD["NetID"]==netID)]["Fulfilled Substitutions"].values[0]
|
||||
return (APP,ACC,REJ,CAN,FUL)
|
||||
|
||||
#Get a "high score" list or a "low score" list of names/scores based on a numeric string showing which categories to count
|
||||
#0: Approved, 1: Accepted, 2: Rejected, 3: Cancelled, 4: Fulfilled
|
||||
#E.g. "12" gives the ULAs with the most total accepted and rejected requests thusfar.
|
||||
def getTopSubs(categoryStr,fewest=False,num=1):
|
||||
if categoryStr=="":
|
||||
return ""
|
||||
@ -142,7 +161,7 @@ def getTopSubs(categoryStr,fewest=False,num=1):
|
||||
stfD[header]+=(stfD[name].astype(float))
|
||||
return stfD.sort_values(header,axis=0,ascending = fewest).head(num)[["Name",header]].to_string(index=False)
|
||||
|
||||
|
||||
#Test if a given single shift exists, i.e. if that section meets on that date.
|
||||
def shiftExists(period,date):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
try:
|
||||
@ -152,6 +171,7 @@ def shiftExists(period,date):
|
||||
return False
|
||||
return True
|
||||
|
||||
#Get a list of all names currently assigned to a given section on a given date
|
||||
def getAllNamesFromSection(periods,dates):
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
others=[]
|
||||
@ -183,7 +203,7 @@ def dateBeforeOrEqual(date1, date2):
|
||||
else:
|
||||
return False
|
||||
|
||||
#create string files for easy pasting into google forms
|
||||
#create string files for easy pasting into Google forms
|
||||
def createStrings():
|
||||
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
|
||||
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
|
||||
@ -208,10 +228,12 @@ def createStrings():
|
||||
seen=set()
|
||||
f.write('\n'.join([x for x in vals if not (x in seen or seen.add(x))]))
|
||||
|
||||
|
||||
#Create the content of form-letter emails to be sent based on the request details and the chosen approval status
|
||||
def generateEmails(requestList):
|
||||
emails=[]
|
||||
for req in requestList:
|
||||
|
||||
#Unpack the request into named variables (for readability in the string formatting below)
|
||||
timestamp = req[0]
|
||||
requestor = IDToName(req[1]).strip("*")
|
||||
section = req[2]
|
||||
@ -221,28 +243,27 @@ def generateEmails(requestList):
|
||||
status = req[6]
|
||||
statusReason = req[7]
|
||||
|
||||
#Check if a replacement was specified at all
|
||||
if replacement!="":
|
||||
replaced=True
|
||||
else:
|
||||
replaced=False
|
||||
|
||||
#Unpack sections and dates to always be a list
|
||||
if ";" in section:
|
||||
sections = section.split(";")
|
||||
else:
|
||||
sections=[section]
|
||||
|
||||
if ";" in date:
|
||||
dates = date.split(";")
|
||||
else:
|
||||
dates=[date]
|
||||
|
||||
|
||||
|
||||
times=[]
|
||||
locations=[]
|
||||
if status=="APP" or status=="ACC": #For now, don't tell the ULAs that their acceptance was begrudging. We can change this later if desired.
|
||||
if status=="APP" or status=="ACC": #For now, don't tell the ULAs that their acceptance was begrudging so they don't try and game the system by lying about the real reason. We can change this later if desired.
|
||||
status="approved"
|
||||
changed=True
|
||||
changed=True #Our database entries were changed
|
||||
for s in sections:
|
||||
times.append(getTimeFromSection(s))
|
||||
locations.append(getLocationFromSection(s))
|
||||
@ -251,7 +272,7 @@ def generateEmails(requestList):
|
||||
status="rejected"
|
||||
else:
|
||||
status="cancelled"
|
||||
changed=False
|
||||
changed=False #Our database entries were changed
|
||||
for s in sections:
|
||||
try:
|
||||
times.append(getTimeFromSection(s))
|
||||
@ -329,6 +350,7 @@ def generateEmails(requestList):
|
||||
|
||||
emails.append([recipient,subject,message])
|
||||
|
||||
#Send emails (or print to terminal if debugging and actuallySend == False)
|
||||
sendEmails(emails,actuallySend=False)
|
||||
|
||||
|
||||
@ -336,7 +358,12 @@ def generateEmails(requestList):
|
||||
|
||||
|
||||
#duumyParam is to make really sure that the author of the code that calls this functions really intends to actually send emails (by forcing them to use the keyword)
|
||||
def sendEmails(emails,dummyParam=False,actuallySend=False):
|
||||
def sendEmails(emails,dummyParam="DUMMY",actuallySend=False):
|
||||
|
||||
#Catch incorrect use of this method by someone who doesn't understand the args.
|
||||
if dummyParam!="DUMMY":
|
||||
print("DON'T USE THE helperFunctions.sendEmails() method unless you know what you're doing! This can send actual emails to people's inbox!")
|
||||
return
|
||||
|
||||
if actuallySend:
|
||||
#https://www.geeksforgeeks.org/send-mail-gmail-account-using-python/
|
||||
@ -368,8 +395,13 @@ def sendEmails(emails,dummyParam=False,actuallySend=False):
|
||||
print("\n"+"-"*80+"\nEmail would be sent to "+email[0]+":\nSubject: "+email[1]+"\nMessage:\n"+email[2]+"\n"+"-"*80)
|
||||
|
||||
|
||||
|
||||
#Get the raw data from the Google Forms API
|
||||
#FYI: The first part of this code dealing with connection/authentication was not written by me and is largely a black box.
|
||||
#This function only adds requests that are more recent than the last time it was run, so that the original data in the Google cloud never needs to be cleared
|
||||
def getForms(subRequestsFilename):
|
||||
|
||||
#BLACKBOX AUTHENTICATION MAGIC
|
||||
#--------------------------------------------------------------------------
|
||||
#Requires some installation/setup https://developers.google.com/forms/api/quickstart/python
|
||||
|
||||
SCOPES = ["https://www.googleapis.com/auth/forms.responses.readonly"]
|
||||
@ -401,21 +433,25 @@ def getForms(subRequestsFilename):
|
||||
form_id = '1x-8fkuMAcQlTl36SdsbCG0tfClKAcvNshnV8L_Hl904'
|
||||
result = service.forms().responses().list(formId=form_id).execute()
|
||||
|
||||
#END OF BLACKBOX AUTHENTICATION MAGIC
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
subs = pd.read_csv(subRequestsFilename,dtype=str)
|
||||
|
||||
|
||||
#Check when the last time the data was downloaded
|
||||
with open("lastUpdatedToken.txt",'r') as f:
|
||||
line = f.readline()
|
||||
if line != "":
|
||||
prevTime=datetime.strptime(line,"%Y-%m-%d %H:%M:%S.%f")
|
||||
else:
|
||||
prevTime=datetime.strptime("1975-01-01 01:01:01.000000","%Y-%m-%d %H:%M:%S.%f")
|
||||
prevTime=datetime.strptime("1975-01-01 01:01:01.000000","%Y-%m-%d %H:%M:%S.%f")#If the file was blank, (such as by just being created) use an aribtrary very early date
|
||||
|
||||
data=result["responses"]
|
||||
#Unpack the ugly data structure that Google Forms returns
|
||||
for req in data:
|
||||
try:
|
||||
reason=req["answers"]["22a5ae9b"]["textAnswers"]["answers"][0]["value"]
|
||||
except KeyError:
|
||||
except KeyError:#No reason specified
|
||||
reason = ""
|
||||
requestor=req["answers"]["7bb6a9dd"]["textAnswers"]["answers"][0]["value"]
|
||||
|
||||
@ -435,11 +471,18 @@ def getForms(subRequestsFilename):
|
||||
sections=sections[:-1]
|
||||
|
||||
timeStr = req["createTime"][:-1].replace("T"," ")+"000"
|
||||
|
||||
#The timestamp needs formatting adjustment and a time-zone shift to EST
|
||||
timeStamp=datetime.strptime(timeStr, '%Y-%m-%d %H:%M:%S.%f')-timedelta(hours=4,minutes=0)
|
||||
|
||||
#If the request is more recent than our last download, then our database doesn't yet 'know' about it and it needs to be added.
|
||||
if timeStamp>prevTime:
|
||||
reqDict={"Timestamp": [timeStr], "Requestor": [requestor], "Section": [sections], "Dates": [dates], "Replacement": [replacement], "Reason": [reason]}
|
||||
subs=pd.concat([subs,pd.DataFrame(reqDict)],ignore_index=True)
|
||||
subs.sort_values(by=["Timestamp"],inplace=True)
|
||||
|
||||
#Write the updated request list to file
|
||||
subs.to_csv(subRequestsFilename,index=False,index_label=False)
|
||||
#Write the timestamp to the token for checking when this function was last run
|
||||
with open("lastUpdatedToken.txt",'w') as f:
|
||||
f.write(str(datetime.now()))
|
||||
f.write(str(datetime.now()))
|
||||
|
@ -1 +1 @@
|
||||
2022-10-04 15:15:51.231447
|
||||
2023-10-11 14:08:24.903282
|
@ -1714,4 +1714,4 @@ Sec 56,13,11/21,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,
|
||||
Sec 56,14,11/28,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,,
|
||||
Sec 56,14,11/30,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,,
|
||||
Sec 56,15,12/05,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,,
|
||||
Sec 56,15,12/07,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,,
|
||||
Sec 56,15,12/07,12:40 PM-2:30 PM,403 Computer Center,john7531,imranmoh,,,,,,,,,,,,,,,,,,
|
||||
|
|
@ -10,20 +10,16 @@ pd.set_option('display.min_rows', 20)
|
||||
pd.set_option('display.expand_frame_repr', False)
|
||||
pd.set_option('max_colwidth', 20)
|
||||
|
||||
|
||||
|
||||
#This is the main window for the substitution requests interface
|
||||
def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequestsArchiveFilename,defaultFont=("Courier",11)):
|
||||
|
||||
|
||||
#read in sub requests from the provided csv filepath
|
||||
subRequests=[]
|
||||
with open(subRequestsFilename) as f:
|
||||
headerLine=f.readline()
|
||||
reader = csv.reader(f)
|
||||
for row in reader:
|
||||
subRequests.append(row+[""])
|
||||
|
||||
|
||||
subRequests.append(row+[""])
|
||||
|
||||
#format fields is Requester, Period, Date, Replacement, Reason, Times, CurrAssigned, Errors
|
||||
columnWidths=[20,16,16,20,30,50,50,20]
|
||||
@ -35,8 +31,9 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
row.append(sg.Text(headerVals[i],expand_x=True,expand_y=True,size=(columnWidths[i],1)))
|
||||
layout=[row]
|
||||
|
||||
#track how many requests are for a specific timeslot
|
||||
datetimeFreq={}
|
||||
#--------------------------------------------------------------------------
|
||||
#Basic error checking before opening display (determines which requests are in red)
|
||||
datetimeFreq={} #track how many requests are for a specific timeslot
|
||||
for i in range(len(subRequests)):
|
||||
oldID=subRequests[i][1]
|
||||
periods=subRequests[i][2].split(';')
|
||||
@ -50,12 +47,16 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
else:
|
||||
datetimeFreq[datetime]=1
|
||||
if not HF.shiftExists(period,date):
|
||||
subRequests[i][6]+="No such shift\n"
|
||||
subRequests[i][6]+="No such shift\n" #Add to the list of errors with this request
|
||||
else:
|
||||
if not HF.isAssigned(oldID,period,date):
|
||||
subRequests[i][6]+=oldID+" not assigned\n"
|
||||
subRequests[i][6]+=oldID+" not assigned\n" #Add to the list of errors with this request
|
||||
if newID != "" and HF.isAssigned(newID,period,date):
|
||||
subRequests[i][6]+=newID+" already assigned\n"
|
||||
subRequests[i][6]+=newID+" already assigned\n" #Add to the list of errors with this request
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
#Formatting the text for the main window
|
||||
for i in range(len(subRequests)):
|
||||
request=subRequests[i]
|
||||
netID=request[1]
|
||||
@ -86,7 +87,6 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
|
||||
others=str(others)[1:-1]
|
||||
others=others.replace("'","")
|
||||
#others=others.replace("],","]\n"+" "*(sum(columnWidths[:6])+16)+"|")
|
||||
|
||||
textVals=[HF.IDToName(netID),*request[2:4],newSubName,request[5],timeStr,others,request[6].strip("\n")]
|
||||
|
||||
@ -100,6 +100,7 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
layout.append(row)
|
||||
layout.append([sg.Text('-'*(sum(columnWidths)+27))])
|
||||
|
||||
#Adding buttons
|
||||
layout.append([sg.Button('Ok'), sg.Button('Cancel'), sg.Button('See Sub History'), sg.Button('Make Manual Changes')])
|
||||
layout=[[sg.Column(layout,scrollable=True,expand_y=True,expand_x=True)]]
|
||||
|
||||
@ -112,6 +113,8 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
event=-1
|
||||
while event != sg.WIN_CLOSED and completed==False:
|
||||
event, values = window.read()
|
||||
|
||||
#values.values() is an array of the input from all window elements, we use strides to get the checkboxes as "columns" instead of "rows"
|
||||
approveValues=list(values.values())[::5]
|
||||
acceptValues=list(values.values())[1::5]
|
||||
rejectValues=list(values.values())[2::5]
|
||||
@ -122,22 +125,22 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
nAcc = sum([1 for d in acceptValues if True == d])
|
||||
nRej = sum([1 for d in rejectValues if True == d])
|
||||
nCan = sum([1 for d in cancelValues if True == d])
|
||||
|
||||
|
||||
|
||||
|
||||
#event is the most recent "click" from the user
|
||||
if event == "See Sub History":
|
||||
SHW.subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont)
|
||||
SHW.subHisWin(subRequestsArchiveFilename,defaultFont) #open history window
|
||||
if event == "Make Manual Changes":
|
||||
window['Ok'].Update(disabled=True) #This is to prevent undefined behavior where manual changes are not represented in the error-checking of the main window
|
||||
SMW.subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,defaultFont)
|
||||
event = "Cancel"
|
||||
if event == 'Ok':
|
||||
window['Ok'].Update(disabled=True) #This is to prevent undefined behavior as a result of manual changes not being represented in the main window
|
||||
SMW.subManualWin(subRequestsArchiveFilename,defaultFont) #open manual change window
|
||||
event = "Cancel" #after we return from the manual change window, just close the whole program
|
||||
|
||||
|
||||
if event == 'Ok':
|
||||
|
||||
valid=True
|
||||
dialogShown = False #Do not show multiple error dialogues even if there are multiple problems.
|
||||
|
||||
#If two responses are checked in ANY single line, then it is not valid input
|
||||
#If two responses are both checked in ANY single line, then it is not valid input
|
||||
if True in [True for i, j, k, l in zip(approveValues, acceptValues, rejectValues, cancelValues) if ((i and j) or (i and k) or (j and k) or (i and l) or (j and l) or (k and l))]:
|
||||
valid=False
|
||||
if not dialogShown:
|
||||
@ -154,6 +157,7 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
otherReqCache={}
|
||||
for i in range(len(subRequests)):
|
||||
if approveValues[i] or acceptValues[i]:
|
||||
#If any of the app/acc lines have an error, then it is not valid input
|
||||
if subRequests[i][6] != "":
|
||||
valid=False
|
||||
if not dialogShown:
|
||||
@ -212,9 +216,10 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
else:
|
||||
amount = 1
|
||||
if approveValues[i] or acceptValues[i]: #If this request was checked "approved" or "accepted"
|
||||
HF.reassign(date,period,oldID,newID)
|
||||
HF.reassign(date,period,oldID,newID) #Actually make the changes in the database
|
||||
#HF.incrementSubCount is for tracking how many subs this person requested and what their outcome was
|
||||
if newID !="":
|
||||
HF.incrementSubCount(newID,4,amount=amount)
|
||||
HF.incrementSubCount(newID,4,amount=amount)#Also track how many sub requests were filled by each person
|
||||
if approveValues[i]:
|
||||
HF.incrementSubCount(oldID,0,amount=amount)
|
||||
if acceptValues[i]:
|
||||
@ -224,7 +229,7 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
if cancelValues[i]:
|
||||
HF.incrementSubCount(oldID,3,amount=amount)
|
||||
if (not approveValues[i] and not acceptValues[i] and not rejectValues[i] and not cancelValues[i]):
|
||||
trimmedSubrequests.append(subRequests[i][:-1])
|
||||
trimmedSubrequests.append(subRequests[i][:-1])#Trimmed requests is the list of requests that had no action taken and thus must be re-added to the list of pending requests
|
||||
else:
|
||||
if approveValues[i]:
|
||||
result="APP"
|
||||
@ -234,12 +239,13 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
result="REJ"
|
||||
elif cancelValues[i]:
|
||||
result="CAN"
|
||||
resolvedSubrequests.append(subRequests[i][:-1]+[result]+[values["-INPUT_REASON_"+str(i)+"-"]])
|
||||
resolvedSubrequests.append(subRequests[i][:-1]+[result]+[values["-INPUT_REASON_"+str(i)+"-"]])#Resolved requests is kept to be appended to the archived requests database
|
||||
|
||||
# If the program crashes during email sending (such as with invalid login) it will deliberately NOT reach the code where it writes to databases
|
||||
#If the program crashes during email sending (such as with invalid login) it will deliberately NOT reach the code where it writes to databases
|
||||
#KEEP THIS LINE BEFORE THE WRITING STEPS
|
||||
HF.generateEmails(resolvedSubrequests)
|
||||
|
||||
|
||||
#Write to the respective databases
|
||||
with open(subRequestsFilename,'w', newline='') as f:
|
||||
f.write(headerLine)
|
||||
writer = csv.writer(f)
|
||||
@ -253,4 +259,4 @@ def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequests
|
||||
elif event == 'Cancel':
|
||||
completed=True
|
||||
|
||||
window.close()
|
||||
window.close()
|
||||
|
@ -3,7 +3,7 @@ import csv
|
||||
import pandas as pd
|
||||
import helperFunctions as HF
|
||||
|
||||
def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
|
||||
def subHisWin(subRequestsArchiveFilename,defaultFont):
|
||||
|
||||
history = pd.read_csv(subRequestsArchiveFilename,dtype=str)
|
||||
|
||||
@ -22,6 +22,7 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
|
||||
while event != sg.WIN_CLOSED and event != "Close Window":
|
||||
event, values = window.read()
|
||||
|
||||
#If event is the pressing of an incrementing button AND the textbox search returns exactly one name
|
||||
if event in ["+APP","-APP","+ACC","-ACC","+REJ","-REJ","+CAN","-CAN","+FUL","-FUL"] and netID != -1 and len(netIDs)<=1:
|
||||
if event[0]=="+":
|
||||
change = 1
|
||||
@ -33,16 +34,6 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
|
||||
event = "Find History"
|
||||
|
||||
if event=="Find History":
|
||||
window['+APP'].Update(disabled=False)
|
||||
window['-APP'].Update(disabled=False)
|
||||
window['+ACC'].Update(disabled=False)
|
||||
window['-ACC'].Update(disabled=False)
|
||||
window['+REJ'].Update(disabled=False)
|
||||
window['-REJ'].Update(disabled=False)
|
||||
window['+CAN'].Update(disabled=False)
|
||||
window['-CAN'].Update(disabled=False)
|
||||
window['+FUL'].Update(disabled=False)
|
||||
window['-FUL'].Update(disabled=False)
|
||||
name = values[0]
|
||||
netIDs=HF.nameToID(name,getAllMatches=True)
|
||||
if not hasattr(netIDs,'__len__') or len(netIDs)==0:
|
||||
@ -58,10 +49,21 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
|
||||
names = [HF.IDToName(i) for i in netIDs[:]]
|
||||
window['OUTPUT'].update(value="Multiple matches found: ["+", ".join(names)+"]")
|
||||
else:
|
||||
|
||||
#Enable the incrementing buttons once a single valid name is found
|
||||
window['+APP'].Update(disabled=False)
|
||||
window['-APP'].Update(disabled=False)
|
||||
window['+ACC'].Update(disabled=False)
|
||||
window['-ACC'].Update(disabled=False)
|
||||
window['+REJ'].Update(disabled=False)
|
||||
window['-REJ'].Update(disabled=False)
|
||||
window['+CAN'].Update(disabled=False)
|
||||
window['-CAN'].Update(disabled=False)
|
||||
window['+FUL'].Update(disabled=False)
|
||||
window['-FUL'].Update(disabled=False)
|
||||
#Get the number of previous requests in each category for that ULA
|
||||
APP,ACC,REJ,CAN,FUL=HF.getSubCount(netID)
|
||||
|
||||
#Printing options for Pandas
|
||||
#Printing options for the Pandas dataframe
|
||||
pd.set_option('display.min_rows', 20)
|
||||
pd.set_option('display.expand_frame_repr', False)
|
||||
pd.set_option('max_colwidth', 30)
|
||||
@ -71,9 +73,11 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
|
||||
output="No history"
|
||||
else:
|
||||
output=str(output.to_string(index=False))
|
||||
#Change the textbox to include the relevant data
|
||||
window['OUTPUT'].update(value=name+"; "+netID+"\n"+"APP:"+APP+" ACC:"+ACC+" REJ:"+REJ+" CAN:"+CAN+" FUL:"+FUL+"\n"+output)
|
||||
|
||||
if event=="Hall of Fame":
|
||||
#Disable the incrementing buttons while in "Hall of Fame" mode since there is no single name attached.
|
||||
window['+APP'].Update(disabled=True)
|
||||
window['-APP'].Update(disabled=True)
|
||||
window['+ACC'].Update(disabled=True)
|
||||
@ -84,9 +88,12 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
|
||||
window['-CAN'].Update(disabled=True)
|
||||
window['+FUL'].Update(disabled=True)
|
||||
window['-FUL'].Update(disabled=True)
|
||||
|
||||
#Printing options for Pandas
|
||||
pd.set_option('display.expand_frame_repr', False)
|
||||
pd.set_option('max_colwidth', 30)
|
||||
|
||||
#Display instructions while the inputStr is not a valid input for the Hall of Fame
|
||||
output='To customize which categories to add (App,Acc,Rej,Can,Ful):\nuse 0,1,2,3,4 or any combination thereof in the text box then click "Hall of Fame" again\n(e.g. "12" yields Accepted plus Rejected)\n'
|
||||
inputStr=values[0]
|
||||
if inputStr=="" or not inputStr.isnumeric():
|
||||
@ -97,4 +104,3 @@ def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
|
||||
|
||||
|
||||
window.close()
|
||||
|
@ -4,13 +4,13 @@ import pandas as pd
|
||||
import helperFunctions as HF
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,defaultFont):
|
||||
def subManualWin(subRequestsArchiveFilename,defaultFont):
|
||||
|
||||
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
|
||||
|
||||
allnames=list(stfD["Name"].values)
|
||||
sections=HF.getSectionTitles()
|
||||
#Pull all names and section titles for the poopulation of the drop-down boxes
|
||||
allnames=HF.getAllNames()
|
||||
sections=HF.getAllSectionTitles()
|
||||
|
||||
#Define the window's layout
|
||||
layout=[[sg.Text("Email?| Sec | Date | Replacee | Replacement")],
|
||||
[sg.Checkbox('',size=(3,1),key='-EMAILCHECK-'),sg.Combo(sections,size=(6,1),key="-SECTION-",readonly=True, enable_events=True),sg.Combo([""],size=(6,1),key='-DATE-',readonly=True, enable_events=True),sg.Combo([""],size=(30,1),key="-REPLACEE-", enable_events=True,readonly=True),sg.Combo(allnames,size=(30,1),key="-REPLACEMENT-", enable_events=True,readonly=True)],
|
||||
[sg.Text("Reason for manual change: "),sg.Input(key='-REASON-')],
|
||||
@ -22,45 +22,50 @@ def subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,d
|
||||
#Event Loop to process "events" and get the "values" of the inputs
|
||||
#Cease running if they close the window
|
||||
event=-1
|
||||
|
||||
while event != sg.WIN_CLOSED and event != "Close Window":
|
||||
event, values = window.read()
|
||||
|
||||
#If the 'section' box is interacted with and is not blank (i.e. an option was selected from the dropdown box)
|
||||
if event=="-SECTION-" and values['-SECTION-']!="":
|
||||
sec=values['-SECTION-']
|
||||
dates=HF.getDatesFromSection(sec)
|
||||
window["-DATE-"].update(values=dates)
|
||||
window["-DATE-"].update(values=dates)#update the dropdown box of dates with the appropriate values for this section
|
||||
window["-REPLACEE-"].update(values=[""])
|
||||
|
||||
#If the 'date' box is interacted with and is not blank (i.e. an option was selected from the dropdown box)
|
||||
if event=="-DATE-" and values['-DATE-']!="":
|
||||
date=values["-DATE-"]
|
||||
names=list(HF.getAllNamesFromSection(sec,date))[0]
|
||||
window["-REPLACEE-"].update(values=names)
|
||||
window["-REPLACEE-"].update(values=names)#update the dropdown box of names with the appropriate values for this section/date combo
|
||||
|
||||
|
||||
#Button was pressed to process the manual change
|
||||
if event == "Make Change":
|
||||
sec=values['-SECTION-']
|
||||
date=values["-DATE-"]
|
||||
replacee=HF.nameToID(values["-REPLACEE-"])
|
||||
replacement=HF.nameToID(values["-REPLACEMENT-"])
|
||||
|
||||
timeStamp=str(datetime.now())
|
||||
request=[timeStamp,replacee,sec,date,replacement,values["-REASON-"],"APP","MANUALLY CHANGED BY COURSE ADMINS"]
|
||||
timeStamp=str(datetime.now())#Consider the submission time to be right now, when the button was pressed.
|
||||
request=[timeStamp,replacee,sec,date,replacement,values["-REASON-"],"APP","MANUALLY CHANGED BY COURSE ADMINS"]#create a request in the style of the Google form, for archiving purposes
|
||||
|
||||
HF.reassign(date,sec,replacee,replacement)
|
||||
HF.reassign(date,sec,replacee,replacement)#Actually perform the replacement
|
||||
|
||||
#Send the email confirmations out to the relevant parties
|
||||
#NOTE: This is BEFORE the writing step so that if there's an error in emails (such as failed to authenticate) the code aborts BEFORE writing chnages to the database.
|
||||
if values['-EMAILCHECK-']:
|
||||
HF.generateEmails([request])
|
||||
|
||||
#write to the archive database
|
||||
with open(subRequestsArchiveFilename,'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows([request])
|
||||
|
||||
|
||||
#Reset the dropdowns to the original options
|
||||
window["-SECTION-"].update(values=sections)
|
||||
window["-DATE-"].update(values=[""])
|
||||
window["-REPLACEE-"].update(values=[""])
|
||||
window["-REPLACEMENT-"].update(values=allnames)
|
||||
if values['-EMAILCHECK-']:
|
||||
HF.generateEmails([request])
|
||||
|
||||
|
||||
|
||||
window.close()
|
||||
window.close()
|
||||
|
@ -1 +1,27 @@
|
||||
Timestamp,Requestor,Section,Dates,Replacement,Reason
|
||||
2023-09-19 13:57:17.596000,sipahiog,Sec 46,09/19,haggart3,Sickness
|
||||
2023-09-19 18:45:55.239000,sebalyma,Sec 40,10/10,ngvivian,will be in ohio for beginning of that week
|
||||
2023-09-19 22:32:06.420000,mcgui186,Sec 6,09/21,schul769,Funeral
|
||||
2023-09-20 17:12:04.654000,austi143,Sec 11,09/20,,"Grandpa had stroke, have to go home"
|
||||
2023-09-20 17:12:30.949000,gautamya,Sec 21,09/20,,Sick
|
||||
2023-09-20 17:29:31.529000,upadhy19,Sec 4,09/20,,Strong Stomachache
|
||||
2023-09-21 13:22:29.001000,sipahiog,Sec 46,09/21,,Sickness
|
||||
2023-09-21 18:25:24.758000,mcgui186,R_HR2,09/21,sipahiog,Funeral
|
||||
2023-09-21 18:26:11.335000,mcgui186,R_HR1,09/21,haggart3,Funeral
|
||||
2023-09-23 14:25:11.349000,borekmi1,Sec 1,09/25,sebalyma,Problem with the transportation to MSU
|
||||
2023-09-25 17:05:16.919000,burgejae,Sec 46,09/26,aggarw75,Important meeting came up
|
||||
2023-09-25 17:05:25.520000,anindhos,M_HR1;M_HR2,09/25,bhardw41,Mandatory project meeting
|
||||
2023-09-25 21:37:26.276000,aggarw75,Sec 11,09/27,lnumehak,
|
||||
2023-09-26 01:51:03.453000,john7531,S_HR1;S_HR2,10/02,bhardw41,Laptop died. Replacement may not come before my shift.
|
||||
2023-09-26 14:52:30.307000,darshanv,W_HR1;W_HR2;Sec 20,09/27,micksoph,Visiting the SSN office for on campus registration and receiving SSN
|
||||
2023-09-26 16:25:19.977000,sipahiog,T_HR1;T_HR2,09/26,haggart3,Something came up with a class
|
||||
2023-09-28 14:58:15.450000,sonarsoh,Sec 17,10/02;10/04,tagaychr,broken arm
|
||||
2023-10-03 19:14:01.917000,sipahiog,Sec 30,10/09,john7531,out of town
|
||||
2023-10-04 19:47:39.642000,schne542,Sec 19,10/09,aggarw75,I have a doctors appointment on 10/9 back home.
|
||||
2023-10-04 20:02:13.852000,mcgui186,R_HR1;R_HR2,10/05,haggart3,I need to go to helproom for a project of mine
|
||||
2023-10-04 20:07:34.501000,sipahiog,Sec 46,10/10,haggart3,Out of town
|
||||
2023-10-09 17:51:15.406000,austi143,Sec 11,10/09,bhardw41,I’m sick
|
||||
2023-10-11 01:06:38.157000,pittend2,Sec 25,10/11,kumararn,Sick
|
||||
2023-10-11 16:49:39.541000,murraydr,M_HR1;M_HR2,10/09,,test1
|
||||
2023-10-11 16:50:11.365000,murraydr,M_HR1,10/02;10/09,,test2
|
||||
2023-10-11 17:24:59.666000,austi143,Sec 11,10/11,lnumehak,I’m sick
|
||||
|
|
Loading…
x
Reference in New Issue
Block a user