stc

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

commit c33c2fa30b12a631a31af5bf878d8c91abccc82a
parent 9c9766c429b18838d0f3b2b1d0e1be83d020f310
Author: Brennen T. Mazur <brennen@madis.cool>
Date:   Sun, 26 Mar 2023 20:44:53 -0600

Added and implemented CrewClockinWidget form, fixed adding new projects, and refined agreement project creation pipeline

Diffstat:
Mapp/forms.py | 5+++++
Mapp/routes.py | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mapp/templates/admin/agreements/widget.html | 8++++----
Mapp/templates/admin/users/active.html | 3+++
Mapp/templates/dashboard/activeusers/widget.html | 10++++++----
Mseeds.py | 75++++++++++++++++++++++++++++++++++++++++-----------------------------------
6 files changed, 141 insertions(+), 71 deletions(-)

diff --git a/app/forms.py b/app/forms.py @@ -71,6 +71,11 @@ class PunchclockinWidget(FlaskForm): # IFF user.role is_in(trusted_role[]) then allow lunch minute definition clockin = SubmitField('Clock In') +class CrewClockinWidget(FlaskForm): + userSel = SelectField('User:', validators=[DataRequired()]) + projectSel = SelectField('Project:', validators=[DataRequired()]) + time = TimeField('Started:') + class FleetCheckoutForm(FlaskForm): vehicle = SelectField('Vehicle', validators = [DataRequired()]) start_mileage = IntegerField('Starting Mileage', validators=[DataRequired()])# Require some sort of validator for check... diff --git a/app/routes.py b/app/routes.py @@ -4,7 +4,7 @@ from app import app from flask_pymongo import PyMongo from flask_login import LoginManager from flask import render_template, url_for, request, flash, redirect -from app.forms import LoginForm, PunchclockinWidget, PunchclockoutWidget, FleetCheckoutForm, FleetCheckinForm, NewUserForm, AdmnPermissionsForm, DashPermissionsForm, ChangeHoursForm, NewAgreementForm, NewProjectForm +from app.forms import LoginForm, PunchclockinWidget, PunchclockoutWidget, FleetCheckoutForm, FleetCheckinForm, NewUserForm, AdmnPermissionsForm, DashPermissionsForm, ChangeHoursForm, NewAgreementForm, NewProjectForm, CrewClockinWidget from flask import request from werkzeug.urls import url_parse from werkzeug.security import generate_password_hash, check_password_hash @@ -209,29 +209,48 @@ def dashboard(): return redirect(url_for('dashboard')) # Temp values, change to db dependent values - availableVehicles = ['Vehicle 1', 'Vehicle 2', 'Vehicle 3', 'Vehicle 4'] - #availableVehicles = [] - #TODO this needs to be properly coded out - #for vehicle in mongo.db.fleet_collection.find_one('available_vehicles'): - # availableVehicles.append((vehicle['_id'],vehicle['name'])) + #availableVehicles = ['Vehicle 1', 'Vehicle 2', 'Vehicle 3', 'Vehicle 4'] + availableVehicles = mongo.db.fleet_collection.find_one({'_id':'Fleet Pool'},{'available':1})['available'] currentProject = 'Project 2' lastMileage = 103483 #currently gets ALL projects TODO make filter by available agreements/projects availableProjects = [] for project in mongo.db.projects_collection.find(): availableProjects.append((project['_id'],project['project_name'])) +# GET_CLOCKED out users + clocked_out_active_users=[] + clocked_in_active_users=[] + for active in mongo.db.user_collection.find({'is_active':True},{'username':1,'fname':1,'mname':1,'lname':1}): + funame = active['fname']+' '+active['lname'] + alreadyin = [] + for user in clocked_in_users: + alreadyin.append(user['modified_by'][0]) + if any(element in active['username'] for element in alreadyin): + clocked_in_active_users.append((active['_id'],funame)) + else: + clocked_out_active_users.append((active['username'],funame)) + + #reset clocked_in_users + clocked_in_users = mongo.db.time_collection.find({'clock_out': {'$exists':False}}) + # END default form values clockinform=PunchclockinWidget() clockoutform=PunchclockoutWidget() fleetoutform=FleetCheckoutForm() fleetinform=FleetCheckinForm() + crewform=CrewClockinWidget() clockinform.projectsSel.choices = availableProjects clockoutform.projectsSel.choices = availableProjects fleetoutform.vehicle.choices = availableVehicles - fleetoutform.vehicle.default = availableVehicles[0] + #fleetoutform.vehicle.default = availableVehicles[0]# Doesn't function fleetoutform.start_mileage.data = lastMileage + crewform.time.data = datetime.datetime.now() + crewform.projectSel.choices = availableProjects + crewform.projectSel.data = availableProjects[0] + crewform.userSel.choices = clocked_out_active_users + crewform.userSel.data = clocked_out_active_users[0] # Currently broken... appears to call constructor(wanted for setting default values) but regenerates csrf_token... clone fn without this call? or overload fn? Haven't looked at documentation yet # See: https://wtforms.readthedocs.io/en/3.0.x/forms/#wtforms.form.BaseForm.__init__ @@ -244,7 +263,6 @@ def dashboard(): clock_user_out(time_id,clockoutform.lunchBox.data,clockoutform.per_diemBox.data) return redirect(url_for('dashboard')) -# Can still clock in without checking current vehicle status!!! TODO FIX ME !!! if clocked_out and request.method == 'POST' and clockinform.validate(): mongo.db.time_collection.insert_one({'clock_in' : [datetime.datetime.utcnow()], 'modified_by' : [current_user.username], @@ -252,6 +270,7 @@ def dashboard(): 'project' : clockinform.projectsSel.data}) return redirect(url_for('dashboard')) +# Can still clock in without checking current vehicle status!!! TODO FIX ME !!! #if fleetoutform.validate_on_submit(): if not fleetCheckedOut and request.method == 'POST' and fleetoutform.validate(): mongo.db.fleet_collection.insert_one({'date':datetime.datetime.today(), # NEED to work on modular way of storing safety checks... might condence to single true if all checked. else returns false and records false datavalue.label in incident_report[] If incident report, remove vehicle from available pool and display widget in admin layout @@ -259,14 +278,42 @@ def dashboard(): 'start_mileage':fleetoutform.start_mileage.data, 'operator':current_user.username, 'additional_notes':fleetoutform.additionalnotes.data}) + #TODO mongo.db.fleet_collection.update_one return redirect(url_for('dashboard')) if fleetCheckedOut and request.method == 'POST' and fleetinform.validate(): fleet_check_in(fleet_id,start_mileage,fleetinform.end_mileage.data,fleetinform.incident_notes.data) return redirect(url_for('dashboard')) - return render_template('dashboard/layout.html',permissions=dashperms,clocked_out=clocked_out,clockoutform=clockoutform,clockinform=clockinform,fleetCheckedOut=fleetCheckedOut,vehicle_name=vehicle_name,fleetinform=fleetinform,fleetoutform=fleetoutform,clocked_in_users=clocked_in_users,ORGNAME=OrganizationName) -#TODO + return render_template('dashboard/layout.html',permissions=dashperms,clocked_out=clocked_out,clockoutform=clockoutform,clockinform=clockinform,fleetCheckedOut=fleetCheckedOut,crewform=crewform,vehicle_name=vehicle_name,fleetinform=fleetinform,fleetoutform=fleetoutform,clocked_in_users=clocked_in_users,ORGNAME=OrganizationName) + +@app.route("/toggle-lunch/<timeid>",methods=['GET','POST']) +def toggle_lunch(timeid): + #query timeid + #if lunch exists, toggle true if false, and vice versa + #else set lunch: True + return redirect(url_for('dashboard')) + +@app.route("/toggle-per-diem/<timeid>",methods=['GET','POST']) +def toggle_per_diem(timeid): + #query timeid + #if perdiem exists, toggle true if false, and vice versa + #else set perdiem: True + return redirect(url_for('dashboard')) + +@app.route("/clockinuser/<modusernm>/<usertoinid>/<project>/<intime>", methods=['GET','POST']) +def clockin_by_id(modusernm,usertoinid,project,intime): + timeid = ObjectId() + user2 = eval(usertoinid) + project = eval(project) + + mongo.db.time_collection.insert_one({'_id':timeid, + 'modified_by' :[user2[0], modusernm], + 'clock_in' : [datetime.datetime.strptime(intime, '%Y-%m-%d %H:%M:%S.%f')], + 'project' : project[0]}) + + return redirect(url_for('dashboard')) + @app.route("/clockoutuser/<modusernm>/<timeid>", methods=['GET','POST']) def clockout_by_id(modusernm,timeid): # if modified_by.last != modusernm: modified_by.append(modusernm) @@ -278,7 +325,6 @@ def clockout_by_id(modusernm,timeid): mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':datetime.datetime.utcnow()}}) mongo.db.time_collection.update_one({'_id':time_id},{'$push':{'modified_by':mod_username}}) flash('Clocked out') - return redirect(url_for('dashboard')) else: flash('No time entry found, or user has checked out already') @@ -466,21 +512,21 @@ def newagreement(): mongo.db.agreements_collection.insert_one({ 'agreement_name':form.agreementName.data, - 'agency':form.agency.data, + 'agency':[form.agency.data], 'projects':[], 'start_date':form.startDate.data.strftime('%Y-%m-%d'), 'end_date':form.endDate.data.strftime('%Y-%m-%d'), - 'budget':[{ - 'labor':form.laborBudget.data, - 'travel':form.travelBudget.data, - 'supplies':form.suppliesBudget.data, - 'contact':form.contactBudget.data, - 'equipment':form.equipmentBudget.data, - 'other':form.otherBudget.data - }] # most recent labor budget accessed via budget.0.labor + # 'budget':[{ + # 'labor':form.laborBudget.data, + # 'travel':form.travelBudget.data, + # 'supplies':form.suppliesBudget.data, + # 'contact':form.contactBudget.data, + # 'equipment':form.equipmentBudget.data, + # 'other':form.otherBudget.data + # }] # most recent labor budget accessed via budget.0.labor }) - flash("{} with {} added".format(form.agreementName.data, form.agency.data )) #Will need to sendmail password to form.email.data later - return redirect(url_for('newagreement')) + flash("{} with {} added, please create at least one project.".format(form.agreementName.data, form.agency.data )) #Will need to sendmail password to form.email.data later + return redirect(url_for('newproject')) return render_template('admin/agreements/newagreement.html',form=form,ORGNAME=OrganizationName) @@ -491,21 +537,22 @@ def newagreement(): @app.route("/admin/projects/new", methods=["GET","POST"]) @login_required def newproject(): -# Temp values, change to modular db dependent values +# Available Agreements. Move to fn() availableAgreements = [] for agreement in mongo.db.agreements_collection.find(): availableAgreements.append((agreement['_id'],agreement['agreement_name'])) -# END TMP Values +# END Available Agreements form = NewProjectForm() form.agreement.choices = availableAgreements + form.otherBudget.data = 0 if form.validate_on_submit(): # create deterministic agreement unique _id? Example being genpasswd in new user validate on submit mongo.db.projects_collection.insert_one({ 'project_name':form.projectName.data, - 'agreement':ObjectId('{}').format(form.agreement.data),#TODO FIX Causes errors + 'agreement':ObjectId(form.agreement.data), 'budget':[{ 'labor':form.laborBudget.data, 'travel':form.travelBudget.data, @@ -513,13 +560,21 @@ def newproject(): 'contact':form.contactBudget.data, 'equipment':form.equipmentBudget.data, 'other':form.otherBudget.data - }] # most recent labor budget accessed via budget.0.labor + }], # most recent labor budget accessed via budget.0.labor + 'costs':[{ + 'labor':0, + 'travel':0, + 'supplies':0, + 'contact':0, + 'equipment':0, + 'other':0 + }] }) pj_id = mongo.db.projects_collection.find_one({'project_name':form.projectName.data})['_id'] - mongo.db.agreements_collection.update_one({ '_id':ObjectId('{}').format(form.agreement.data) },{ '$push':{ 'projects':pj_id }})#TODO Fix, causes errors + mongo.db.agreements_collection.update_one({ '_id':ObjectId(form.agreement.data) },{ '$push':{ 'projects':pj_id }}) flash("{} part of {} added".format(form.projectName.data, form.agreement.data )) #Will need to sendmail password to form.email.data later - return redirect(url_for('newproject')) + return redirect(url_for('admin')) return render_template('admin/agreements/projects/newproject.html',form=form,ORGNAME=OrganizationName) diff --git a/app/templates/admin/agreements/widget.html b/app/templates/admin/agreements/widget.html @@ -12,10 +12,10 @@ {{ agreement.agreement_name }}<br> {{ agreement['budget'] }}<br> {% for project in agreement['projects'] %} - {{ project['budget'] }} - {{ project['total_budget'] }} - {{ project['costs'] }} - {{ project['total_cost'] }} + {{ project['budget'] }}<br> + {{ project['total_budget'] }}<br> + {{ project['costs'] }}<br> + {{ project['total_cost'] }}<br> {% endfor %} {{ agreement['costs'] }} <br><br> diff --git a/app/templates/admin/users/active.html b/app/templates/admin/users/active.html @@ -3,6 +3,9 @@ {% block title %}All Active Users{% endblock %} {% block content %} + {%- for user in activeusers %} + {{ user }} + {%- endfor %} <section class="activeusers-grid"> {%- for user in activeusers %} <div class="usercard"> diff --git a/app/templates/dashboard/activeusers/widget.html b/app/templates/dashboard/activeusers/widget.html @@ -11,6 +11,7 @@ {% for user in clocked_in_users %} <tr> <td><button><a href="{{ url_for('hours',username=user.modified_by.0) }}">{{ user.modified_by.0 }}</a></button></td> + <td><button><a href="{{ url_for('toggle_lunch',timeid=user._id) }}">Clock Out</a></button></td> <td><input type="checkbox" name="lunch"><label for="lunch">Lunch</label></td> <td><input type="checkbox" name="per_diem"><label for="per_diem">Per Diem</label></td> <td><button><a href="{{ url_for('hours',username=user.modified_by.0) }}">{{ user.clock_in.0.time().isoformat(timespec='minutes') }}</a></button></td><!-- can format/display non-military time with format %I:%M%p --> @@ -21,10 +22,11 @@ </form> <form> <table> - <tr><!-- clock in clocked out user --> - <th><select><option value="user3.name" selected>user3.name</option><option value="user4.name">user4.name</option><option value="user5.name">user5.name</option></select></th> - <td><input type="time" value="12:12"/></td> - <td><input type="submit" value="Clock in"></td> + <tr><!-- clock in clocked out user MIGHT NEED TO COMMENT OUT... DOES NOT GET DATA FROM FORMS ON SUBMIT... ONLY USES DEFAULT.data --> + <td>{{ crewform.userSel.label }} {{ crewform.userSel() }}</td> + <td>{{ crewform.projectSel.label }} {{ crewform.projectSel() }}</td> + <td>{{ crewform.time.label }} {{ crewform.time() }}</td> + <td><button type="submit"><a href="{{ url_for('clockin_by_id', modusernm=current_user.username, intime=crewform.time.data, project=crewform.projectSel.data, usertoinid=crewform.userSel.data) }}">Clock In</a></button></td> </tr> </table> </form> diff --git a/seeds.py b/seeds.py @@ -111,14 +111,19 @@ time3 = { } # Fleet documents +fleetpool = { + '_id':'Fleet Pool', + 'available':['Vehicle1','Vehicle2','Vehicle3','Vehicle5','Vehicle6'], + 'unavailable':[('Vehicle4','brennentmazur')] # Tuple for who checked vehicle lastOR set to 'REPAIRS' for obvious reasons + } + fleet1 = { 'date': datetime.datetime.today(), 'operator': 'brennentmazur', #forign key to userID 'start_mileage': 33, - 'end_mileage': 33, 'safety_checks': [True,True,True,True,True],#array for different safety checks 'additional_notes': 'Oil needs checked', - 'vehicle': 'The Big Truck', #vehicleID + 'vehicle': 'Vehicle4', #vehicleID 'incident_report': '' } @@ -141,22 +146,22 @@ agreement1 = { 'start_date': '2023-12-5', 'end_date': '2023-8-12', #'bid_document': '/asset/document/agreements/New-Agreement.pdf', #Filepath to document - 'budget': [{ - 'labor':1259.40, - 'travel':220.00, - 'supplies':320.00, - 'contact':420.00, - 'equipment':620.00, - 'other':20.00 - }], - 'costs': [{ - 'labor':159.40, - 'travel':20.00, - 'supplies':30.00, - 'contact':40.00, - 'equipment':60.00, - 'other':2.00 - }] +# 'budget': [{ +# 'labor':1259.40, +# 'travel':220.00, +# 'supplies':320.00, +# 'contact':420.00, +# 'equipment':620.00, +# 'other':20.00 +# }], +# 'costs': [{ +# 'labor':159.40, +# 'travel':20.00, +# 'supplies':30.00, +# 'contact':40.00, +# 'equipment':60.00, +# 'other':2.00 +# }] } agreement2 = { @@ -166,22 +171,22 @@ agreement2 = { 'start_date': '2021-2-21', 'end_date': '2022-12-13', #'bid_document': '/asset/document/agreements/Old-Agreement.pdf', #Filepath to document - 'budget': [{ - 'labor':259.40, - 'travel':220.00, - 'supplies':320.00, - 'contact':420.69, - 'equipment':20.00, - 'other':20.00 - }], - 'costs': [{ - 'labor':159.40, - 'travel':20.00, - 'supplies':30.00, - 'contact':40.00, - 'equipment':60.00, - 'other':2.00 - }] +# 'budget': [{ +# 'labor':259.40, +# 'travel':220.00, +# 'supplies':320.00, +# 'contact':420.69, +# 'equipment':20.00, +# 'other':20.00 +# }], +# 'costs': [{ +# 'labor':159.40, +# 'travel':20.00, +# 'supplies':30.00, +# 'contact':40.00, +# 'equipment':60.00, +# 'other':2.00 +# }] } # Projects documents @@ -385,7 +390,7 @@ accountant = { # Insert documents user_collection.insert_many([user1, user2, user3]) time_collection.insert_many([time1, time2,time3]) -fleet_collection.insert_many([fleet1, fleet2]) +fleet_collection.insert_many([fleetpool, fleet1, fleet2]) agreements_collection.insert_many([agreement1, agreement2]) projects_collection.insert_many([projects1, projects2, projects3, projects4, projects5]) permissions_collection.insert_many([crew,alead,lead,developer,manager,accountant])