stc

a simple time card webapp
git clone _git@git.brennen.work:stc.git
Log | Files | Refs | README

commit 1196103e66282b2dd4447e184bcd6fd24c4a2467
parent 88f792f655122eb4bee6859eb79a2094d206d524
Author: Youth Employment Program Production <youthemployment22@gmail.com>
Date:   Tue, 16 Apr 2024 15:57:32 -0600

refactor hours route, add project name and updateProjectTime. Cleanup hours dev. add remove_agreement

Diffstat:
Mapp/routes.py | 171++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Aapp/templates/dashboard/punchclock/all.html | 42++++++++++++++++++++++++++++++++++++++++++
Mapp/templates/dashboard/punchclock/index.dev.html | 18+++---------------
Mapp/templates/dashboard/punchclock/index.html | 26+++++++++++++++++++-------
Aapp/templates/dashboard/punchclock/update/project.html | 27+++++++++++++++++++++++++++
5 files changed, 199 insertions(+), 85 deletions(-)

diff --git a/app/routes.py b/app/routes.py @@ -317,7 +317,14 @@ def dashboard(): @login_required def updateProjectTime(mod_username,timeid): timeid = ObjectId(timeid) + availableProjects = [] #change to get_available_projects() -> projects where user branch == project['branch'] form = updateProject() + + for project in mongo.db.projects_collection.find(): + availableProjects.append((project['_id'],project['project_name'])) + + form.projectSel.choices = availableProjects + if form.validate_on_submit(): app.logger.info('update project route') try: @@ -328,11 +335,11 @@ def updateProjectTime(mod_username,timeid): try: mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'project':ObjectId(form.projectSel.data)}}) except: - flash("unable to set project {}".format(ObjectId(form.projectSel.data))) + flash("unable to set project for {} to {}".format(timeid, form.projectSel.data)) else: - flash("Updated project to {}".format(ObjectId(form.projectSel.data))) + flash("Updated project") finally: - return redirect(url_for('dashboard'))#change to hours and mod_username redirect + return redirect(url_for('hours',username=entry['modified_by'][0]))#change to hours and mod_username redirect return render_template('dashboard/punchclock/update/project.html',form=form, ORGNAME=OrganizationName) @app.route("/update/start/<mod_username>/<timeid>",methods=['GET','POST']) #TODO MAKE OTHER VALUE FOR LAST PAGE, RETURNS LAST PAGE ELSE DASHBOARD @@ -753,11 +760,6 @@ def admin(): @login_required def hoursd(username):#userid goes into call to db to get user[] -> then returns formatted table (punchclock/index.html - availableProjects = [] - - for project in mongo.db.projects_collection.find(): #TODO TO RESOLVE BELOW ISSUE, JUST USE aggregator QUERY, WILL ALLOW SORTING AS WELL AS PROPER SUMMATION OF HOURS - availableProjects.append((project['_id'],project['project_name']))#TODO FIND OUT WHY THIS RETURNS THE OBJECTID VALUE .str THROWS AttributeError: 'ObjectId' object has no attribute 'str' - #'completed':{'$exists':False} aghours = mongo.db.time_collection.find({'modified_by.0':username}) dbhours = mongo.db.time_collection.aggregate( [ { @@ -779,21 +781,6 @@ def hoursd(username):#userid goes into call to db to get user[] -> then returns } ] ) - hours = [] - deltas=[] -# for hour in dbhours: # Currenty acts wrong with longer than 1 day -# for x, y in availableProjects: -# if x is ObjectId(hour['project']): -# hour['projectName'] = y -# if 'clock_out' not in hour: -# hour['clock_out']=[datetime.datetime.now()] -# time = hour['clock_out'][-1] - hour['clock_in'][-1] -# hour['total_time'] = time -# hours.append(hour) -# deltas.append(time) - - total_hours = sum(deltas,datetime.timedelta()) - statement_hours = "{} Hours, {} Minutes".format(total_hours.seconds//3600,(total_hours.seconds//60)%60) tspp = mongo.db.time_collection.aggregate( [ { @@ -823,17 +810,16 @@ def hoursd(username):#userid goes into call to db to get user[] -> then returns ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project) - return render_template ('dashboard/punchclock/index.dev.html',hours=dbhours,availableProjects=availableProjects,total_hours=total_hours,statement_hours=statement_hours,tspp=tspp,ORGNAME=OrganizationName) + return render_template ('dashboard/punchclock/index.dev.html',hours=dbhours,tspp=tspp,ORGNAME=OrganizationName) #### #### ####### Hours Route ####### #### #### -@app.route('/hours/<username>', methods=['GET','POST'])#modify to take userid ex. /hours<userid> for "admin" currently pulls from current_user... simply always pass username to hours(if possible set a default to current_user) +@app.route('/hours/<username>', methods=['GET','POST']) @login_required -def hours(username):#userid goes into call to db to get user[] -> then returns formatted table (punchclock/index.html - #set var user_hours = [] +def hours(username): #query time collection for all time entries #for entry in entries: # time = entry.get('clock_out'[0],datetime.datetime.now()) - entry['clock_in'][0] @@ -851,46 +837,65 @@ def hours(username):#userid goes into call to db to get user[] -> then returns f for project in mongo.db.projects_collection.find(): #TODO TO RESOLVE BELOW ISSUE, JUST USE aggregator QUERY, WILL ALLOW SORTING AS WELL AS PROPER SUMMATION OF HOURS availableProjects.append((project['_id'],project['project_name']))#TODO FIND OUT WHY THIS RETURNS THE OBJECTID VALUE .str THROWS AttributeError: 'ObjectId' object has no attribute 'str' #'completed':{'$exists':False} - dbhours = mongo.db.time_collection.find({'modified_by.0':user['username']}) - hours = [] - deltas=[] - for hour in dbhours: # Currenty acts wrong with longer than 1 day - for x, y in availableProjects: - if x is ObjectId(hour['project']): - hour['projectName'] = y - if 'clock_out' not in hour: - hour['clock_out']=[datetime.datetime.now()] - time = hour['clock_out'][-1] - hour['clock_in'][-1] - hour['total_time'] = time - hours.append(hour) - deltas.append(time) + dbhours = mongo.db.time_collection.aggregate( [ + { + "$match":{'modified_by.0':username} + }, + { + '$lookup': { + 'from': 'projects_collection', + 'localField': 'project', + 'foreignField': '_id', + 'as': 'project' + } + }, + { + "$sort":{'clock_in':-1} + }, + { + "$limit":10 #change to ~ 30 days OR to prior 1st or 15th of month + } + + ] ) + #hours = [] + #deltas=[] + #for hour in dbhours: # Currenty acts wrong with longer than 1 day + # for x, y in availableProjects: + # if x is ObjectId(hour['project']): + # hour['projectName'] = y + # if 'clock_out' not in hour: + # hour['clock_out']=[datetime.datetime.now()] + # time = hour['clock_out'][-1] - hour['clock_in'][-1] + # hour['total_time'] = time + # hours.append(hour) + # deltas.append(time) form = NewHoursForm() form.dateSel.data = datetime.datetime.today() form.projectSel.choices = availableProjects form.startTime.data = datetime.datetime.now() - total_hours = sum(deltas,datetime.timedelta()) - statement_hours = "{} Hours, {} Minutes".format(total_hours.seconds//3600,(total_hours.seconds//60)%60) - - if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry - indt = datetime.combine(form.dateSel.data,form.startTime.data) - outdt = datetime.combine(form.dateSel.data,form.endTime.data) - if user['username'] is current_user.username and not form.perDiemSel.data and not form.lunchSel.data: - mongo.db.time_collection.insert_one({'clock_in' : [indt], - 'modified_by' : [current_user.username], - 'date' : datetime.datetime.today(), - 'project' : ObjectId(form.projectSel.data), - 'clock_out':[outdt]}) - return redirect(url_for('hours',username=user['username'])) - - if user['username'] is not current_user.username and not form.perDiemSel.data and not form.lunchSel.data: - mongo.db.time_collection.insert_one({'clock_in' : [indt], - 'modified_by' : [user['username'], current_user.username], - 'date' : datetime.datetime.today(), - 'project' : ObjectId(form.projectSel.data), - 'clock_out':[outdt]}) - - return redirect(url_for('hours',username=user['username'])) + #total_hours = sum(deltas,datetime.timedelta()) + #statement_hours = "{} Hours, {} Minutes".format(total_hours.seconds//3600,(total_hours.seconds//60)%60) + +# if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry +# indt = datetime.combine(form.dateSel.data,form.startTime.data) +# outdt = datetime.combine(form.dateSel.data,form.endTime.data) +# if user['username'] is current_user.username and not form.perDiemSel.data and not form.lunchSel.data: +# mongo.db.time_collection.insert_one({'clock_in' : [indt], +# 'modified_by' : [current_user.username], +# 'date' : datetime.datetime.today(), +# 'project' : ObjectId(form.projectSel.data), +# 'clock_out':[outdt]}) +# return redirect(url_for('hours',username=user['username'])) +# +# if user['username'] is not current_user.username and not form.perDiemSel.data and not form.lunchSel.data: +# mongo.db.time_collection.insert_one({'clock_in' : [indt], +# 'modified_by' : [user['username'], current_user.username], +# 'date' : datetime.datetime.today(), +# 'project' : ObjectId(form.projectSel.data), +# 'clock_out':[outdt]}) +# +# return redirect(url_for('hours',username=user['username'])) #hours = mongo.db.time_collection.find({'modified_by.0':user.username}) @@ -923,7 +928,33 @@ def hours(username):#userid goes into call to db to get user[] -> then returns f ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project) - return render_template ('dashboard/punchclock/index.html',form=form,hours=hours,availableProjects=availableProjects,total_hours=total_hours,statement_hours=statement_hours,user=user,tspp=tspp,ORGNAME=OrganizationName) + return render_template ('dashboard/punchclock/index.html',form=form,hours=dbhours,user=user,tspp=tspp,ORGNAME=OrganizationName) + +@app.route('/allhours/<username>', methods=['GET','POST']) +@login_required +def all_user_hours(username): + user = mongo.db.user_collection.find_one({"username": username}) + #need a function which checks for dated(archived) time collections, and appends them to list. Might look like below + # hours = [ {'current_year()":{'current pay period':{mongo_aggregate_lookup(returned doc)}},(each pay period in current year)},{'last_year().date':[{data},{data}]},etc... + # This setup might benefit from time entries being in their own db? + dbhours = mongo.db.time_collection.aggregate( [ + { + "$match":{'modified_by.0':username} + }, + { + '$lookup': { + 'from': 'projects_collection', + 'localField': 'project', + 'foreignField': '_id', + 'as': 'project' + } + }, + { + "$sort":{'clock_in':-1} + } + ] ) + + return render_template ('dashboard/punchclock/all.html',hours=dbhours,user=user,ORGNAME=OrganizationName) # Don't really need this until additional functionality is added, not in current project scope #@app.route("/fleet") @@ -1446,6 +1477,20 @@ def rename_agreement(agreement_id): @login_required def remove_agreement(agreement_id): #TODO + form = ConfirmRemove() + if form.validate_on_submit(): + try: + #TODO replace with get project(s) fn + agreement = mongo.db.agreement_collection.find_one({'_id':ObjectId(agreement_id)}) + #END + except: + flash('Issue finding agreement id {}'.format(agreement_id)) + else: + #for each project either remove or move + flash('WARNING: This action removes all projects currently associated with this agreement, AND removes all time entries tied to the projects.') +# try: + # for project in agreement['projects'] + return render_template('admin/agreements/projects/update/move.html',form=form,ORGNAME=OrganizationName)#TODO FIX @app.route('/admin/change/agreement/dates',methods=["GET","POST"]) diff --git a/app/templates/dashboard/punchclock/all.html b/app/templates/dashboard/punchclock/all.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block title %}Hours{% endblock %} + +{% block content %} + <section class="hours-grid"> + <h3>{{ user.fname }} {{ user.lname }}</h3><!-- IF logged in user has permission allow this username section to be a dropdown for modifying user time sheets. --> + <h1 id="clock"></h1> + <table> + {% for event in hours %} + <tr> + {% for entry in event %} + <td>{{ event[entry] }}</br></td> + {% endfor %} + </tr> + {# <td><a href="{{url_for('updateDate',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.date.date().isoformat() }}</a></td> + <td><a href="{{url_for('updateProjectTime',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.project[0]['project_name'] }}</a></td> + <td><a href="{{url_for('updateStartTime',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.clock_in[-1].time().isoformat(timespec='minutes') }}</a></td> + <td><a href="{{url_for('updateEndTime',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.clock_out[-1].time().isoformat(timespec='minutes') }}</a></td> + {% if entry.lunch %} + <td><a href="{{url_for('toggle_lunch',timeid=entry._id)}}">Yes</a></td> + {% else %} + <td><a href="{{url_for('toggle_lunch',timeid=entry._id)}}">No</a></td> + {% endif %} + {% if entry.per_diem %} + <td><a href="{{url_for('toggle_per_diem',timeid=entry._id)}}">Yes</a></td> + {% else %} + <td><a href="{{url_for('toggle_per_diem',timeid=entry._id)}}">No</a></td> + {% endif %} + {% if entry.note %} + <td><a href="{{url_for('updateNote',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.note }}</a></td> + {% else %} + <td><a href="{{url_for('updateNote',mod_username=current_user.username,timeid=entry._id)}}">Add Note</a></td> + {% endif %} + <td><a href="{{url_for( 'removetime',timeid=entry._id) }}" class="action-button">Remove</a></td> + #} + </tr> + {% endfor %} + </table> + </form> + </section> +{% endblock %} diff --git a/app/templates/dashboard/punchclock/index.dev.html b/app/templates/dashboard/punchclock/index.dev.html @@ -8,23 +8,9 @@ <div><!-- abstract to payPeriod() --> <h6>$payperiod range</h6> <!--<h5>Total:{# {{ statement_hours }} #}</h5> --> - {{availableProjects}} </div> <form action="" method="POST" novalidate> <table><tr> - {% for field in form %}{% if field.widget.input_type != 'hidden' and field.widget.input_type != 'submit' %} - <th>{{ field.label }}</th> - {% endif %}{% endfor %}</tr> - <!--{# <tr> - <td>{{ form.dateSel() }}</td> - <td>{{ form.projectSel() }}</td> - <td>{{ form.startTime() }}</td> - <td>{{ form.endTime() }}</td> - <td>{{ form.lunchSel() }}</td> - <td>{{ form.perDiemSel() }}</td> - <td>{{ form.note() }}</td> - <td>{{ form.submitEntr() }}</td> - </tr> #}--> {% for entry in hours %} <tr> @@ -50,13 +36,15 @@ <td><a href="{{url_for( 'removetime',timeid=entry._id) }}" class="action-button">Remove</a></td> {#{{ form.hidden_tag() }}#} </tr> - {{entry}} + {# {{entry}} #} {% endfor %} </table> </form> </section> + {# {% for time in tspp %} {{ time }} </br> {% endfor %} + #} {% endblock %} diff --git a/app/templates/dashboard/punchclock/index.html b/app/templates/dashboard/punchclock/index.html @@ -2,7 +2,7 @@ {% block title %}Hours{% endblock %} -{% block content %} +{% block content %}{# change table to div structure https://css-tricks.com/complete-guide-table-element/ #} <section class="hours-grid"> <h3>{{ user.fname }} {{ user.lname }}</h3><!-- IF logged in user has permission allow this username section to be a dropdown for modifying user time sheets. --> <h1 id="clock"></h1> @@ -39,9 +39,17 @@ {% for entry in hours %} <tr> <td><a href="{{url_for('updateDate',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.date.date().isoformat() }}</a></td> - <td>{{ entry.projectName }}</td> + {% if entry.project[0] %} + <td><a href="{{url_for('updateProjectTime',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.project[0]['project_name'] }}</a></td> + {% else %} + <td><a href="{{url_for('updateProjectTime',mod_username=current_user.username,timeid=entry._id)}}">Project not Found</a></td> + {% endif %} <td><a href="{{url_for('updateStartTime',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.clock_in[-1].time().isoformat(timespec='minutes') }}</a></td> - <td><a href="{{url_for('updateEndTime',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.clock_out[-1].time().isoformat(timespec='minutes') }}</a></td> + {% if entry.clock_out %} + <td><a href="{{url_for('updateEndTime',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.clock_out[-1].time().isoformat(timespec='minutes') }}</a></td> + {% else %} + <td><a href="{{url_for('updateEndTime',mod_username=current_user.username,timeid=entry._id)}}">Clocked In</a></td> + {% endif %} {% if entry.lunch %} <td><a href="{{url_for('toggle_lunch',timeid=entry._id)}}">Yes</a></td> {% else %} @@ -51,22 +59,26 @@ <td><a href="{{url_for('toggle_per_diem',timeid=entry._id)}}">Yes</a></td> {% else %} <td><a href="{{url_for('toggle_per_diem',timeid=entry._id)}}">No</a></td> + <td><a href="{{url_for( 'removetime',timeid=entry._id) }}" class="action-button">Remove</a></td> + </tr> + <tr> {% endif %} {% if entry.note %} - <td><a href="{{url_for('updateNote',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.note }}</a></td> + <td colspan='6'><a href="{{url_for('updateNote',mod_username=current_user.username,timeid=entry._id)}}">{{ entry.note }}</a></td> {% else %} - <td><a href="{{url_for('updateNote',mod_username=current_user.username,timeid=entry._id)}}">Add Note</a></td> + <td colspan='6'><a href="{{url_for('updateNote',mod_username=current_user.username,timeid=entry._id)}}">Add Note</a></td> {% endif %} - <td><a href="{{url_for( 'removetime',timeid=entry._id) }}" class="action-button">Remove</a></td> - {#{{ form.hidden_tag() }}#} </tr> {# {{entry}} #} {% endfor %} </table> </form> + <a href="{{url_for('all_user_hours',username=user.username)}}"class="action-button">Past Hours</a> </section> + {# {% for time in tspp %} {{ time }} </br> {% endfor %} + #} {% endblock %} diff --git a/app/templates/dashboard/punchclock/update/project.html b/app/templates/dashboard/punchclock/update/project.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block title %}Update Project{% endblock %} + +{% block content %} +<section class="new-agreement-grid"> + <h3>Update Project</h3> + <form action="" method="POST" novalidate> + {{ form.hidden_tag() }} + {% for error in form.errors %} + <span style="color:red;">[{{ error }}]</span> + {% endfor %} + {% for ferror in form.form_errors %} + <span style="color:yellow;">[{{ ferror }}]</span> + {% endfor %} + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + <p style='color:red'>{{ message }}</p> + {% endfor %} + {% endif %} + {% endwith %} + {{ form.projectSel.label }}{{ form.projectSel() }}<br> + {{ form.submitEntr() }} + </form> +</section> +{% endblock %}