stc

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

commit eec13cfd30c02e265056fe2bb2ea32a53e4e4b2e
parent 4ca9acc214ed47aeca65e743c8811aa76d3fd423
Author: Youth Employment Program Production <youthemployment22@gmail.com>
Date:   Tue, 19 Sep 2023 11:02:05 -0600

added hours project_report aggregate, added display times by users and project to templates, refined employee reports, linked generate time select for reports, abstracted get_all_users

Diffstat:
Aapp/TODO.md | 1+
Mapp/routes.py | 246+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Aapp/templates/admin/bound_timedata_report/widget.html | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/templates/admin/employee_report/index.html | 3+++
Mapp/templates/admin/employee_report/widget.html | 3+++
Aapp/templates/admin/reports/bound_timedata_report.html | 14++++++++++++++
Mapp/templates/admin/reports/employee_report.html | 2+-
Mapp/templates/admin/reports/widget.html | 2+-
Mapp/templates/admin/total_timedata_report/widget.html | 28++++++++++++++++++++++++----
Mapp/templates/dashboard/punchclock/index.html | 4++++
10 files changed, 420 insertions(+), 11 deletions(-)

diff --git a/app/TODO.md b/app/TODO.md @@ -0,0 +1 @@ +[] hours page change project linking diff --git a/app/routes.py b/app/routes.py @@ -774,7 +774,37 @@ def hours(username):#userid goes into call to db to get user[] -> then returns f return redirect(url_for('hours',username=user['username'])) #hours = mongo.db.time_collection.find({'modified_by.0':user.username}) - return render_template ('dashboard/punchclock/index.html',form=form,hours=hours,availableProjects=availableProjects,total_hours=total_hours,statement_hours=statement_hours,user=user,ORGNAME=OrganizationName) + + + tspp = mongo.db.time_collection.aggregate( [ + { + "$lookup":{ + 'from':'projects_collection', + 'localField':'project', + 'foreignField':'_id', + 'as':'project_data' + } + }, + { + "$group": { + "_id":"$project_data", + "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }, + "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } }, + "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } } + } + }, + { + "$addFields": { + "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] } + } + }, + { + "$sort": { "_id": -1 } + } + ] )# 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) # Don't really need this until additional functionality is added, not in current project scope #@app.route("/fleet") @@ -1133,7 +1163,74 @@ def agreement_report(): @app.route('/admin/reports/agreements') @login_required def project_report(): - return render_template('admin/reports/project.html') + tspp = mongo.db.time_collection.aggregate( [ + { + "$group": { + "_id":{"projectId": "$project"}, + "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }, + "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } }, + "perdiemCount": { "$sum" :{'$cond':["$perdiem",1,0] } } + } + }, + { + "$addFields": { + "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] } + } + }, + { + "$sort": { "_id": -1 } + } + ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project) + ptl = mongo.db.time_collection.aggregate( [ + { + "$lookup":{ # TODO TODO TODO THIS WILL REQUIRE CHANGING ALL DB WRITES TO time_collection['project'] TO BECOME ObjectId() OBJECTS THIS WILL LIKELY BREAK THINGS!!!! NEED TO ITERATE THROUGH DB ENTRIES AS WELL AS ENSURE READ OPERATIONS STILL GUNCTION PROPERLY AFTERWARDS + 'from':'projects_collection', + 'localField':'project', + 'foreignField':'_id', + 'as':'project_data' + } + }, + { + "$addFields": { + "totalTime": { "$cond":{ "$if":"$clock_out","$then":{"$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }},"$else":{'Clocked in'}}, + "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }, + "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } }, + "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } } + } + }, + { + "$addFields": { + "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] } + } + }, + { + "$sort": {'date': -1} + } + ] ) + + by_project = tspp +# by_project = {} +# for project in tspp: +# by_project[project['_id'][0]['project_name']]=project['_id'][0] +# by_project[project['_id'][0]['project_name']]['totalHoursWorked']=project['totalHoursWorked'] +# by_project[project['_id'][0]['project_name']]['lunchCount']=project['lunchCount'] +# by_project[project['_id'][0]['project_name']]['perdiemCount']=project['perdiemCount'] + +# for time in ptl: +# for user in by_user: +# if time['modified_by'][0] in user: +# if by_user[user].get('times'): +# by_user[user]['times'].append(time) +# else: +# by_user[user].update({'times':[time]}) +# for project in by_project: +# if time['project_data'][0]['project_name'] in project: +# if by_project[project].get('times'): +# by_project[project]['times'].append(time) +# else: +# by_project[project].update({'times':[time]}) + + return render_template('admin/reports/project.html',by_project=by_project,tspp=tspp,projectlookup=ptl,ORGNAME=OrganizationName) # Payperiod Routes #TODO marked 06.29,23 @@ -1191,7 +1288,7 @@ def time_data_total_report(): everyhours_by_project = mongo.db.time_collection.aggregate([ {'$sort':{'project':1,'modified_by.0':1}} ]) - return render_template ('admin/reports/total_timedata_report.html', usors = everyhours_by_user, projours = everyhours_by_project, hours=hours, tspp=tspp, ORGNAME=OrganizationName) + return render_template ('admin/reports/total_timedata_report.html', by_user = everyhours_by_user, by_project = everyhours_by_project, hours=hours, tspp=tspp, ORGNAME=OrganizationName) ####### TESTING END ####### ###### TESTING Period selection START ####### @app.route('/dev/select-date-range', methods=["GET","POST"]) @@ -1392,7 +1489,145 @@ def time_bound_report(startday,endday): #return json_util.dumps(by_user) #return json_util.dumps(by_project) - return render_template ('admin/reports/total_timedata_report.html', by_project=by_project, by_user=by_user, usertimes=usertimes, allhours=allhours, hours=hours, tspp=tspp, projectlookup=ptl, ORGNAME=OrganizationName) + return render_template ('admin/reports/bound_timedata_report.html', by_project=by_project, by_user=by_user, usertimes=usertimes, allhours=allhours, hours=hours, tspp=tspp, projectlookup=ptl, ORGNAME=OrganizationName) +## Refactoring fn(s) START ## +def get_all_user_times(): + usertimes = mongo.db.time_collection.aggregate([ + { + "$lookup":{ + 'from':'user_collection', + 'localField':'modified_by.0', + 'foreignField':"username", + 'as':'userinfo' + } + }, + { + "$sort":{"userinfo.username":1} + } + ]) + + allhours = mongo.db.time_collection.aggregate( [ + { + "$group": { + "_id":{"_id":'$_id', + "project":"$project"}, + "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }, + "lunchCount":{ "$sum":{'$cond':["$lunch",1,0] } }, + "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0] } } + } + }, + { + "$addFields": { + "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] } + } + }, + { + "$sort": { "_id": 1 } + } + ] )# Total hours worked + + + hours = mongo.db.time_collection.aggregate( [ + { + "$group": { + "_id": { + "$first":"$modified_by", + }, + "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }, + "lunchCount":{ "$sum":{'$cond':["$lunch",1,0] } }, + "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0] } } + } + }, + { + "$addFields": { + "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] } + } + }, + { + "$sort": { "_id": 1 } + } + ] )# Total hours worked + tspp = mongo.db.time_collection.aggregate( [ + { + "$lookup":{ + 'from':'projects_collection', + 'localField':'project', + 'foreignField':'_id', + 'as':'project_data' + } + }, + { + "$group": { + "_id":"$project_data", + "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }, + "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } }, + "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } } + } + }, + { + "$addFields": { + "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] } + } + }, + { + "$sort": { "_id": -1 } + } + ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project) + + ptl = mongo.db.time_collection.aggregate( [ + { + "$lookup":{ # TODO TODO TODO THIS WILL REQUIRE CHANGING ALL DB WRITES TO time_collection['project'] TO BECOME ObjectId() OBJECTS THIS WILL LIKELY BREAK THINGS!!!! NEED TO ITERATE THROUGH DB ENTRIES AS WELL AS ENSURE READ OPERATIONS STILL GUNCTION PROPERLY AFTERWARDS + 'from':'projects_collection', + 'localField':'project', + 'foreignField':'_id', + 'as':'project_data' + } + }, + { + "$addFields": { + "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }, + "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } }, + "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } } + } + }, + { + "$addFields": { + "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] } + } + }, + { + "$sort": {'date': -1} + } + ] ) + + by_project = {} + for project in tspp: + by_project[project['_id'][0]['project_name']]=project['_id'][0] + by_project[project['_id'][0]['project_name']]['totalHoursWorked']=project['totalHoursWorked'] + by_project[project['_id'][0]['project_name']]['lunchCount']=project['lunchCount'] + by_project[project['_id'][0]['project_name']]['perdiemCount']=project['perdiemCount'] + + by_user ={} + for user in hours: + by_user[user['_id']]=user + + for time in ptl: + for user in by_user: + if time['modified_by'][0] in user: + if by_user[user].get('times'): + by_user[user]['times'].append(time) + else: + by_user[user].update({'times':[time]}) + for project in by_project: + if time['project_data'][0]['project_name'] in project: + if by_project[project].get('times'): + by_project[project]['times'].append(time) + else: + by_project[project].update({'times':[time]}) + return by_user + +## Refartoring fn(s) END ## + ####### TESTING END ####### @app.route('/admin/reports/pay-period', methods=['GET']) @@ -1455,11 +1690,12 @@ def calculateHours(username=current_user): @app.route('/admin/reports/employees') @login_required def report_employees(): + by_user = get_all_user_times() users = mongo.db.user_collection.find() hours = mongo.db.time_collection.find() for user in users: user['total_hours']=calculateHours(user['username']) - return render_template ('admin/employee_report/index.html', hours=hours, users=users, ORGNAME=OrganizationName) + return render_template ('admin/employee_report/index.html', by_user=by_user, hours=hours, users=users, ORGNAME=OrganizationName) @app.route('/admin/reports/employee/<username>') @login_required diff --git a/app/templates/admin/bound_timedata_report/widget.html b/app/templates/admin/bound_timedata_report/widget.html @@ -0,0 +1,128 @@ +<section class="agreements"> + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + <div id='messagebanner'><p>{{ message }}</p></div> + {% endfor %} + {% endif %} + {% endwith %} + <section class="employee-overlook"> + <!--{#<div class="employee-hours"> + <table> + <tr> + <th>Employee</th> + <th>Total(hrs)</th> + <th>Lunch(#)</th> + <th>Per Diem(#)</th> + <th>Billable(hrs)</th> + </tr> + {% for entry in allhours %} + <tr> + <td><a href="{{url_for('hours',username=entry._id)}}">{{ entry['_id'] }}</a></td> + <td><a href="{{url_for('hours',username=entry._id)}}">{{ (entry['totalTime']/(1000*60*60))|round(2) }}</a></td> + <td><a href="{{url_for('hours',username=entry._id)}}">{{ entry['lunchCount'] }}</a></td> + <td><a href="{{url_for('hours',username=entry._id)}}">{{ entry['perdiemCount'] }}</a></td> + <td><a href="{{url_for('hours',username=entry._id)}}">{{ (entry['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td> + </tr> + {% endfor %} + </table> + </div>#}--> + <!--{#<div>Test Div</br> + {% for elmnt in usors %} + {{elmnt}}</br> + {% endfor %} + + </div>#}--> + <!--{#<div>Projours Div</br> + {% for elmnt in projours %} + {{elmnt}}</br> + {% endfor %} + </div>#}--> + <!--{#<div>Project Lookup Div</br> + {% for elmnt in projectlookup %} + {{elmnt['modified_by'][0]}} + {{elmnt['date']}} + {{elmnt['project_data'][0]['project_name']}} + {{elmnt}}</br></br> + {% endfor %} + </div>#}--> + <div class="pagebreak"><h2>Reports by User</h2></div> + <!-- <div class="pagebreak"></div> --> + {% for user, times in by_user.items() %} + <div class="pagebreak"> + <dl class="user-summary"> + <dt><h3>{{ user }}</h3></dt> + <dd>Total Hours(hrs):<a href="{{url_for('hours',username=user)}}">{{ (times['totalHoursWorked']/(1000*60*60))|round(2) }}</a></dd> + <dd>Total Lunches:<a href="{{url_for('hours',username=user)}}">{{ times['lunchCount'] }}</a></dd> + <dd>Total Per Diem:<a href="{{url_for('hours',username=user)}}">{{ times['perdiemCount'] }}</a></dd> + </dl> + <table> + <tr> + <th>Date</th> + <th>Project</th> + <th>Clocked In</th> + <th>Clocked Out</th> + <th>Lunch</th> + <th>Per Diem</th> + <th>Time(hrs)</th> + </tr> + {% for time in times['times'] %} + <tr> + <td>{{time['date'].date().isoformat()}}</td> + <td>{{time['project_data'][0]['project_name']}}</td> + <td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td> + {% if time['clock_out'] is defined %} + <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td> + {% else %} + <td>Clocked In</td> + {% endif %} + <td>{{time['lunchCount']}}</td> + <td>{{time['perdiemCount']}}</td> + <td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td> + </tr> + {% endfor %} + </table></div> + {% endfor %} + + <!-- </section> + <section class="project-overlook"> --> + <div class="pagebreak"><h2>Reports by Project</h2></div> + <!-- <div class="pagebreak"></div> --> + {% for project, times in by_project.items() %} + <div class="pagebreak"> + <dl class="user-summary"> + <dt><h3>{{ project }}</h3></dt> + <dd>Total Hours(hrs):<a href="{{url_for('hours',username='brennentmazur')}}">{{ (times['totalHoursWorked']/(1000*60*60))|round(2) }}</a></dd> + <dd>Total Lunches:<a href="{{url_for('hours',username='brennentmazur')}}">{{ times['lunchCount'] }}</a></dd> + <dd>Total Per Diem:<a href="{{url_for('hours',username='brennentmazur')}}">{{ times['perdiemCount'] }}</a></dd> + </dl> + <table> + <tr> + <th>Date</th> + <th>Employee</th> + <th>Clocked In</th> + <th>Clocked Out</th> + <th>Lunch</th> + <th>Per Diem</th> + <th>Time(hrs)</th> + </tr> + {% for time in times['times'] %} + <tr> + <td>{{time['date'].date().isoformat()}}</td> + <td>{{time['modified_by'][0]}}</td> + <td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td> + {% if time['clock_out'] is defined %} + <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td> + {% else %} + <td>Clocked In</td> + {% endif %} + <td>{{time['lunchCount']}}</td> + <td>{{time['perdiemCount']}}</td> + <td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td> + </tr> + {% endfor %} + </table></div> + {% endfor %} + + </section> +</section> diff --git a/app/templates/admin/employee_report/index.html b/app/templates/admin/employee_report/index.html @@ -7,6 +7,9 @@ <!-- returned values from admin check is array of permissive ACCESS else return 'missing permissions response' --> <section class="agreements"> <h3>Hours by Employee</h3> + {% for user in by_user %} + {{ user }} + {% endfor %} <table> <tr><th>First</th><th>Middle</th><th>Last</th><th>Pay</th><th>Hours</th></tr> {%- for user in users %} diff --git a/app/templates/admin/employee_report/widget.html b/app/templates/admin/employee_report/widget.html @@ -1,6 +1,9 @@ <section class="agreements"> <section class="employee-overlook"> <h3>Employee Overlook</h3> + {% for user in by_user %} + {{ user }} + {% endfor %} <div class="usercard"> <h4>{{ user.fname }} {{ user.mname }}. {{ user.lname }}</h3> <table> diff --git a/app/templates/admin/reports/bound_timedata_report.html b/app/templates/admin/reports/bound_timedata_report.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block title %}Employee Reports{% endblock %} + +{% block content %} + <section class="admin-grid"> + <!-- returned values from admin check is array of permissive ACCESS else return 'missing permissions response' --> + {%- for x in ['reports','bound_timedata_report','roles','users'] %} + {% include 'admin/'~x~'/widget.html' %} + {%- else-%} + {{ 'You do not have permission to access this page' }} + {%- endfor %} + </section> +{% endblock %} diff --git a/app/templates/admin/reports/employee_report.html b/app/templates/admin/reports/employee_report.html @@ -5,7 +5,7 @@ {% block content %} <section class="admin-grid"> <!-- returned values from admin check is array of permissive ACCESS else return 'missing permissions response' --> - {%- for x in ['reports','total_timedata_report','roles','users'] %} + {%- for x in ['reports','employee_report','roles','users'] %} {% include 'admin/'~x~'/widget.html' %} {%- else-%} {{ 'You do not have permission to access this page' }} diff --git a/app/templates/admin/reports/widget.html b/app/templates/admin/reports/widget.html @@ -1,6 +1,6 @@ <section class="reportswidget"> <h3>Reports</h3> - <a href="{{ url_for('pay_period_report') }}"><input type="submit" value="Payperiod"></a> + <a href="{{ url_for('select_date_range') }}"><input type="submit" value="Generate"></a> <a href="{{ url_for('project_report') }}"><input type="submit" value="Agreements"></a> <a href="{{ url_for('report_employees') }}"><input type="submit" value="Employees"></a> </section> diff --git a/app/templates/admin/total_timedata_report/widget.html b/app/templates/admin/total_timedata_report/widget.html @@ -48,7 +48,12 @@ </div>#}--> <div class="pagebreak"><h2>Reports by User</h2></div> <!-- <div class="pagebreak"></div> --> - {% for user, times in by_user.items() %} + {% for user in by_user %} + {{ user }} + {% endfor %} + + {# + {% for user, times in by_user %} <div class="pagebreak"> <dl class="user-summary"> <dt><h3>{{ user }}</h3></dt> @@ -71,7 +76,11 @@ <td>{{time['date'].date().isoformat()}}</td> <td>{{time['project_data'][0]['project_name']}}</td> <td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td> - <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td> + {% if time['clock_out'] is defined %} + <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td> + {% else %} + <td>Clocked In</td> + {% endif %} <td>{{time['lunchCount']}}</td> <td>{{time['perdiemCount']}}</td> <td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td> @@ -79,11 +88,17 @@ {% endfor %} </table></div> {% endfor %} - + #} + <!-- </section> <section class="project-overlook"> --> <div class="pagebreak"><h2>Reports by Project</h2></div> <!-- <div class="pagebreak"></div> --> + {% for project in by_project %} + {{ project }} + {% endfor %} + + {# {% for project, times in by_project.items() %} <div class="pagebreak"> <dl class="user-summary"> @@ -107,7 +122,11 @@ <td>{{time['date'].date().isoformat()}}</td> <td>{{time['modified_by'][0]}}</td> <td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td> - <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td> + {% if time['clock_out'] is defined %} + <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td> + {% else %} + <td>Clocked In</td> + {% endif %} <td>{{time['lunchCount']}}</td> <td>{{time['perdiemCount']}}</td> <td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td> @@ -115,6 +134,7 @@ {% endfor %} </table></div> {% endfor %} + #} </section> </section> diff --git a/app/templates/dashboard/punchclock/index.html b/app/templates/dashboard/punchclock/index.html @@ -65,4 +65,8 @@ </table> </form> </section> + {% for time in tspp %} + {{ time }} + </br> + {% endfor %} {% endblock %}