stc

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

routes.py (102885B)


      1 import datetime
      2 import random, string
      3 from app import app
      4 from flask_pymongo import PyMongo
      5 from flask_login import LoginManager
      6 from flask import render_template, url_for, request, flash, redirect
      7 from app.forms import LoginForm, PunchclockinWidget, PunchclockoutWidget, FleetCheckoutForm, FleetCheckinForm, NewUserForm, AdmnPermissionsForm, DashPermissionsForm, NewHoursForm, ConfirmRemove, NewAgreementForm, RenameAgreementForm, NewProjectForm, MoveProjectForm, RenameProjectForm, CrewClockinWidget, NewUserHourForm, ChangePasswordForm, ChangeUserForm, upDate, updateTime, updateProject, newNote, dateRange, ChangeBranchForm
      8 from flask import request
      9 from werkzeug.urls import url_parse
     10 from werkzeug.security import generate_password_hash, check_password_hash
     11 from flask_login import current_user, login_user, logout_user, login_required
     12 from app.branches.routes import get_available_branches
     13 from app.equipment.equipment import get_available_equipment_type
     14 from app.models import User, Time, Fleet, Agreement, Projects
     15 from bson.objectid import ObjectId
     16 import bson.json_util as json_util
     17 #from .config import EXEMPT_METHODS
     18 import functools
     19 
     20 OrganizationName = 'Youth Employment Program' # Maybe pass this as a value though the object for relevant pages???
     21 
     22 ####                                      ####
     23 #######    Application Modules Init    #######
     24 ####                                      ####
     25 # DB Connection & LoginManager init
     26 mongo = PyMongo(app)
     27 login_manager = LoginManager(app)
     28 login_manager.login_view = 'login'
     29 
     30 class UserNotFoundError(Exception):
     31     pass
     32 
     33 def fetch_user(user_id):
     34     user = mongo.db.user_collection.find_one({"_id":ObjectId(user_id)})
     35     if user == None:
     36         raise UserNotFoundError(f"User id {user_id} returned none")
     37     else:
     38         return user
     39 
     40 def fetch_user_from_username(username):
     41     user = mongo.db.user_collection.find_one({"username":username})
     42     if user == None:
     43         raise UserNotFoundError(f"User name {username} returned none")
     44     else:
     45         return user
     46 
     47 # Add ability to get users by role type... case match statement inside if for default no arguments?
     48 # TODO currently doesn't care about active state. FIX
     49 def get_available_users(active=True,role=None,branch=None):
     50     availableUsers = [("","Select User")]
     51     for user in mongo.db.users_collection.find():
     52         if branch:
     53             if 'branch' in user:
     54                 if user['branch'] == 'Global' or user['branch'] == branch:
     55                     availableUsers.append((user['_id'],user['username']))
     56         else:
     57             availableUsers.append((user['_id'],user['username']))
     58     return availableUsers
     59 
     60 def get_available_projects(branch=None):
     61     availableProjects = [("","Select Project")]
     62     for project in mongo.db.projects_collection.find():
     63         if branch:
     64             if 'branch' in project:
     65                 if project['branch'] == 'Global' or project['branch'] == ObjectId(branch):
     66                     availableProjects.append((project['_id'],project['project_name']))
     67         else:
     68             availableProjects.append((project['_id'],project['project_name']))
     69     return availableProjects
     70 
     71 ####                                 ####
     72 #######    User Routes/Queries    #######
     73 ####                                 ####
     74 #@app.route('/user/signup', methods=['GET'])
     75 #def signup():
     76 #    return User().signup()
     77 
     78 #@app.route('/user/loginModel', methods=['GET'])
     79 #def loginModel():
     80 #    return User().loginModel()
     81 
     82 #@app.route('/user/signout')
     83 #def signout():
     84 #    return User().signout()
     85 
     86 ####                                 ####
     87 #######    Time Routes/Queries    #######
     88 ####                                 ####
     89 #@app.route('/time/clockin', methods=['GET', 'POST'])
     90 #def clockin():
     91 #    return Time().clockin()
     92 
     93 #@app.route('/time/clockout', methods=['GET', 'POST'])
     94 #def clockout():
     95 #    return Time().clockout()
     96 
     97 ####                                    ####
     98 #######    Vehicle Routes/Queries    #######
     99 ####                                    ####
    100 #@app.route('/fleet/vehicle_repair', methods=['GET', 'POST'])
    101 #def vehicle_repair():
    102 #    return Fleet().vehicle_repair()
    103 
    104 ####                                      ####
    105 #######    Agreement Routes/Queries    #######
    106 ####                                      ####
    107 #@app.route('/agreement/document', methods=['GET', 'POST'])
    108 #def document():
    109 #    return Agreement().document()
    110 
    111 ####                                    ####
    112 #######    Project Routes/Queries    #######
    113 ####                                    ####
    114 #@app.route('/projects/project', methods=['GET', 'POST', 'PUT'])
    115 
    116 ##{{{{{{{{{{{{{{{{{{{{{ Decorator Functions }}}}}}}}}}}}}}}}}}}}}##
    117 #@login_required
    118 #def admin_required(func):
    119 #    """Ensure logged in user has admin permissions"""
    120 #    @functools.wraps(func)
    121 #    def decorated_view(*args, **kwargs):
    122 #        if current_user.role in :
    123 #            pass
    124 #        elif not current_user.role in :
    125 #            return current_app.login_manager.unauthorized()
    126 #
    127 #        #if not has_permission:
    128 #        return func(*args, **kwargs)
    129 #
    130 #    return decorated_view
    131 ##{{{{{{{{{{{{{{{{{{{{{ END Decorator Functions }}}}}}}}}}}}}}}}}}}}}##
    132 
    133 
    134 #================================#
    135 ###########           ############
    136 ####       PAGE ROUTING       ####
    137 ###########           ############
    138 #================================#
    139 
    140 ####                               ####
    141 #######    Logout User Route    #######
    142 ####                               ####
    143 @app.route("/logout", methods=['GET'])
    144 def logout():
    145     logout_user()
    146     return redirect(url_for('login'))
    147 
    148 ####                              ####
    149 #######    Login/Root Route    #######
    150 ####                              ####
    151 @app.route('/', methods=['GET', 'POST'])
    152 @app.route("/login", methods=['GET', 'POST'])
    153 def login():
    154     if current_user.is_authenticated:
    155         return redirect(url_for('dashboard'))
    156     form = LoginForm()
    157     if form.validate_on_submit():
    158         # check form value for identity in db, if found AND form password matches stored hash, create User object
    159         try:
    160             u = fetch_user_from_username(form.username.data)
    161         except:
    162             flash("Invalid username or password")
    163             return redirect(url_for('login'))
    164         else:
    165             if u['is_active'] and User.check_password(u, form.password.data):
    166                 user_obj = User(fname=u['fname'],mname=u['mname'],lname=u['lname'],email=u['email'],branch=u['branch'],address=u['address'],birthday=u['birthday'],role=u['role'],phonenumber=u['phonenumber'])
    167                 #login with new user object
    168                 login_user(user_obj)
    169                 # check next redirect to stop cross-site-redirects, another example here : http://flask.pocoo.org/snippets/62/ 
    170                 next = request.args.get('next')
    171                 if not next or url_parse(next).netloc != '':
    172                     next = url_for('dashboard')
    173                 return redirect(next)
    174             else:
    175                 flash("Invalid username or password")
    176                 return redirect(url_for('login'))
    177     return render_template('login.html',form=form,ORGNAME = OrganizationName)
    178 
    179 ####                        ####
    180 #######  Load User Data  #######
    181 ####                        ####
    182 #load user_data into User object iff username == stored username
    183 @login_manager.user_loader
    184 def load_user(username):
    185     u = mongo.db.user_collection.find_one({"username": username})
    186     if not u:
    187         return None
    188     user_obj =  User(fname=u['fname'],mname=u['mname'],lname=u['lname'],email=u['email'],branch=u['branch'],address=u['address'],birthday=u['birthday'],role=u['role'],phonenumber=u['phonenumber'])
    189     user_obj.password_hash = u['password_hash']
    190     return user_obj
    191 
    192 @app.route('/update/<update_t>/<username>',methods=['GET','POST'])
    193 @login_required
    194 def update_user(update_t,username): #username=current_user):
    195     form = ChangeUserForm()
    196     form.role.choices = mongo.db.permissions_collection.find({},{'label':1})#get_available_roles()
    197 
    198     form.branch.choices = [("",'Select Branch'),("Global","Global")]
    199     for branch in get_available_branches():
    200         form.branch.choices.append(branch)
    201     
    202     user = fetch_user_from_username(username)
    203     match update_t:
    204         case "fname":
    205             if form.validate_on_submit():
    206                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'fname':form.fname.data,'username':form.fname.data.lower()+user['mname'].lower()+user['lname'].lower()}})
    207                 flash("change {}.".format(update_t))
    208                 return redirect(url_for('activeusers'))
    209         case "mname":
    210             if form.validate_on_submit():
    211                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'mname':form.mname.data,'username':user['fname'].lower()+form.mname.data.lower()+user['lname'].lower()}})
    212                 flash("change {}.".format(update_t))
    213                 return redirect(url_for('activeusers'))
    214         case "lname":
    215             if form.validate_on_submit():
    216                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'lname':form.lname.data,'username':user['fname'].lower()+user['mname'].lower()+form.lname.data.lower()}})
    217                 flash("change {}.".format(update_t))
    218                 return redirect(url_for('activeusers'))
    219         case "birthday":
    220             if form.validate_on_submit():
    221                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'birthday':form.birthday.data.strftime('%Y-%m-%d')}})
    222                 flash("change {}.".format(update_t))
    223                 return redirect(url_for('activeusers'))
    224         case "role":
    225             if form.validate_on_submit():
    226                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'role':form.role.data}})
    227                 flash("change {}.".format(update_t))
    228                 return redirect(url_for('activeusers'))
    229         case "address":
    230             if form.validate_on_submit():
    231                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'address':form.address.data}})
    232                 flash("change {}.".format(update_t))
    233                 return redirect(url_for('activeusers'))
    234         case "branch":
    235             if form.validate_on_submit():
    236                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'branch':form.branch.data}})
    237                 flash("change {}.".format(update_t))
    238                 return redirect(url_for('activeusers'))
    239         case "phonenumber":
    240             if form.validate_on_submit():
    241                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'phonenumber':form.phonenumber.data}})
    242                 flash("change {}.".format(update_t))
    243                 return redirect(url_for('activeusers'))
    244         case "email":
    245             if form.validate_on_submit():
    246                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'email':form.email.data}})
    247                 flash("change {}.".format(update_t))
    248                 return redirect(url_for('activeusers'))
    249         case "payPeriod":
    250             if form.validate_on_submit():
    251                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'pay_period':form.payPeriod.data}})
    252                 flash("change {}.".format(update_t))
    253                 return redirect(url_for('activeusers'))
    254         case "payValue":
    255             if form.validate_on_submit():
    256                 mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'pay_value':form.payValue.data}})
    257                 flash("change {}.".format(update_t))
    258                 return redirect(url_for('activeusers'))
    259 
    260     return render_template("admin/users/update_user.html",user=user,form=form,update_t=update_t)
    261 
    262 @app.route('/newpass',methods=['GET','POST'])
    263 @login_required
    264 def chgpass():
    265     form = ChangePasswordForm()
    266     if form.validate_on_submit():
    267         mongo.db.user_collection.update_one({'username':current_user.username},{'$set':{'password_hash':generate_password_hash(form.newpass.data)}})
    268         flash("Changed password for {} to {}".format(current_user.username,form.newpass.data)) #Will need to sendmail password to form.email.data later
    269 
    270         return redirect(url_for('dashboard'))
    271     
    272     return render_template('admin/users/newpass.html',form=form,ORGNAME=OrganizationName)
    273 
    274 @app.route('/newpass/<uid>',methods=['GET','POST'])
    275 @login_required
    276 def chgpass_by_uid(uid):
    277     try:
    278         user = fetch_user(uid)
    279     except:
    280         return "User not Found"
    281     else:
    282         form = ChangePasswordForm()
    283         if form.validate_on_submit():
    284             mongo.db.user_collection.update_one({'username':user['username']},{'$set':{'password_hash':generate_password_hash(form.newpass.data)}})
    285             flash("Changed password for {} to {}".format(user['username'],form.newpass.data)) #Will need to sendmail password to form.email.data later
    286 
    287             return redirect(url_for('dashboard'))
    288     
    289     return render_template('admin/users/newpass.html',form=form,ORGNAME=OrganizationName)
    290 ####                       ####
    291 ####### Dashboard Route #######
    292 ####                       ####
    293 
    294 @app.route('/index')
    295 @app.route("/dashboard", methods=['GET', 'POST'])
    296 @login_required
    297 def dashboard():
    298     
    299     currentdate = datetime.datetime.now()
    300 
    301     dashperms=mongo.db.permissions_collection.find_one({'label': current_user.role},{'dashboard':1,'_id':0})
    302     dashperms=dashperms['dashboard']
    303 
    304     clocked_in_users = mongo.db.time_collection.find({'clock_out': {'$exists':False}})
    305     def clock_user_out(time_id,notes='',lunch=False,perdiem=False):
    306         if notes != '' and notes != None:
    307             if mongo.db.time_collection.find({'_id': time_id}, {'clock_out':{'$exists':False}}):
    308                 if lunch==True and perdiem==True:
    309                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()],'note':notes,'lunch':True,'per_diem':True}})
    310                 elif lunch==True and perdiem==False:
    311                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()],'note':notes,'lunch':True}})
    312                 elif lunch==False and perdiem==True:
    313                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()],'note':notes,'per_diem':True}})
    314                 else:
    315                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()],'note':notes}})
    316                 return redirect(url_for('dashboard'))
    317             else:
    318                 flash('No time entry found, or user has checked out already')
    319                 return redirect(url_for('dashboard'))
    320         else:
    321             if mongo.db.time_collection.find({'_id': time_id}, {'clock_out':{'$exists':False}}):
    322                 if lunch==True and perdiem==True:
    323                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()],'lunch':True,'per_diem':True}})
    324                 elif lunch==True and perdiem==False:
    325                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()],'lunch':True}})
    326                 elif lunch==False and perdiem==True:
    327                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()],'per_diem':True}})
    328                 else:
    329                     mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()]}})
    330                 return redirect(url_for('dashboard'))
    331             else:
    332                 flash('No time entry found, or user has checked out already')
    333 
    334     # Move to a isUserClockedIn(default: username=current_user)
    335     if mongo.db.time_collection.find_one({'modified_by.0': current_user.username,'clock_out':{'$exists':False}}):
    336         clocked_out = False
    337         time_id = mongo.db.time_collection.find_one({'modified_by.0': current_user.username,'clock_out':{'$exists':False}})["_id"]
    338     else:
    339         clocked_out = True
    340     #End isUserClockedIn()
    341     #Start fleetCheckedOut()
    342     if mongo.db.fleet_collection.find_one({'operator': current_user.username,'end_mileage':{'$exists':False}}):
    343         fleetCheckedOut = True
    344         fleet_id = mongo.db.fleet_collection.find_one({'operator': current_user.username,'end_mileage':{'$exists':False}})["_id"]
    345         vehicle_name = mongo.db.fleet_collection.find_one({'operator': current_user.username,'end_mileage':{'$exists':False}})["vehicle"]
    346         start_mileage = mongo.db.fleet_collection.find_one({'operator': current_user.username,'end_mileage':{'$exists':False}})["start_mileage"]
    347     else:
    348         fleetCheckedOut = False
    349         vehicle_name = ''
    350     #End fleetCheckedOut()
    351     def fleet_check_in(fleet_id,start_mileage,end_mileage,notes=''):
    352         if mongo.db.fleet_collection.find({'_id': fleet_id}, {'end_mileage':{'$exists':False}}):
    353             if end_mileage <= start_mileage:
    354                 flash('end mileage less than starting mileage') 
    355             elif notes != '' and end_mileage >= start_mileage:
    356                 mongo.db.fleet_collection.update_one({'_id':fleet_id},{'$set':{'end_mileage':end_mileage,'incident_notes':{notes:False}}})#incident_note dict isResolved and string value to display on admin page
    357             elif notes == '' and end_mileage >= start_mileage:
    358                 mongo.db.fleet_collection.update_one({'_id':fleet_id},{'$set':{'end_mileage':end_mileage}})
    359             else:
    360                 flash('uncaught logic updating {} with {}'.format(fleet_id,end_mileage)) 
    361                 mongo.db.fleet_collection.update_one({'_id':fleet_id},{'$set':{'end_mileage':end_mileage}})
    362             return redirect(url_for('dashboard'))
    363         else:
    364             flash('Unable to check out vehicle')
    365             return redirect(url_for('dashboard'))
    366 
    367     #def newfleet_check_out(vehicle_id,start_milage,additional_notes=None,safety_checks=None)
    368     #availableVehicles = mongo.db.fleet_collection.find_one({'_id':'Fleet Pool'},{'available':1})['available']
    369     availableVehicles = [("","Select Vehicle")]
    370     for vehicle in get_available_equipment_type('vehicle',branch=current_user.branch):
    371         availableVehicles.append((vehicle['_id'],"Vehicle {}".format(vehicle['vehicle_id'])))
    372 
    373 #currently gets ALL projects TODO make filter by available agreements/projects
    374     availableProjects = get_available_projects(current_user.branch)
    375 #   GET_CLOCKED out users
    376     clocked_out_active_users=[]
    377     clocked_in_active_users=[]
    378     for active in mongo.db.user_collection.find({'is_active':True},{'username':1,'fname':1,'mname':1,'lname':1}):
    379         funame = active['fname']+' '+active['lname']
    380         alreadyin = []
    381         for user in clocked_in_users:
    382             alreadyin.append(user['modified_by'][0])
    383         if any(element in active['username'] for element in alreadyin):
    384             clocked_in_active_users.append((active['_id'],funame))
    385         else:
    386             clocked_out_active_users.append((active['username'],funame))
    387 
    388     #reset clocked_in_users
    389     clocked_in_users = mongo.db.time_collection.find({'clock_out': {'$exists':False}})
    390 
    391 #   END default form values
    392 
    393     clockinform=PunchclockinWidget()
    394     clockoutform=PunchclockoutWidget()
    395     fleetoutform=FleetCheckoutForm()
    396     fleetinform=FleetCheckinForm()
    397     crewform=CrewClockinWidget()
    398     
    399     clockinform.projectsSel.choices = availableProjects
    400     #clockoutform.projectsSel.choices = availableProjects
    401     fleetoutform.vehicle.choices = availableVehicles
    402     #fleetoutform.vehicle.default = availableVehicles[0]# Doesn't function
    403     #fleetoutform.start_mileage.data = lastMileage
    404     crewform.time.data = datetime.datetime.now()
    405     crewform.projectSel.choices = availableProjects
    406     crewform.projectSel.data = availableProjects[0]
    407     crewform.userSel.choices = clocked_out_active_users
    408     crewform.userSel.data = clocked_out_active_users[0]
    409 
    410 #   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
    411 #   See: https://wtforms.readthedocs.io/en/3.0.x/forms/#wtforms.form.BaseForm.__init__
    412     #clockinform.process()
    413     #clockoutform.process()
    414     #fleetoutform.process()
    415 
    416     #if clockinform.validate_on_submit(): Currently will submit all present forms... replaced w/ below
    417     if not clocked_out and request.method == 'POST' and clockoutform.validate():
    418         clock_user_out(time_id,clockoutform.recapOrNote.data,clockoutform.lunchBox.data,clockoutform.per_diemBox.data)#add clockoutform.recapOrNote.data (will take some thought for clock_user_out fn potentially?)
    419         return redirect(url_for('dashboard'))
    420 
    421     if clocked_out and request.method == 'POST' and clockinform.validate():
    422         mongo.db.time_collection.insert_one({'clock_in' : [datetime.datetime.now()],
    423                                          'modified_by' : [current_user.username],
    424                                          'date' : datetime.datetime.today(),
    425                                          'project' : ObjectId(clockinform.projectsSel.data)})
    426         return redirect(url_for('dashboard'))
    427 
    428 #   Can still clock in without checking current vehicle status!!! TODO FIX ME !!!
    429     #if fleetoutform.validate_on_submit():
    430     if not fleetCheckedOut and request.method == 'POST' and fleetoutform.validate():
    431         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
    432                                               'vehicle':fleetoutform.vehicle.data,
    433                                               'start_mileage':fleetoutform.start_mileage.data,
    434                                               'operator':current_user.username,
    435                                               'additional_notes':fleetoutform.additionalnotes.data})
    436         #TODO mongo.db.fleet_collection.update_one
    437         return redirect(url_for('dashboard'))
    438 
    439     if fleetCheckedOut and request.method == 'POST' and fleetinform.validate():
    440         fleet_check_in(fleet_id,start_mileage,fleetinform.end_mileage.data,fleetinform.incident_notes.data)
    441         return redirect(url_for('dashboard'))
    442 
    443     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)
    444 
    445 @app.route("/update/project/<mod_username>/<timeid>",methods=['GET','POST'])
    446 @login_required
    447 def updateProjectTime(mod_username,timeid):
    448     timeid = ObjectId(timeid)
    449     availableProjects = get_available_projects(current_user.branch)#[] #change to get_available_projects() -> projects where user branch == project['branch']
    450     form = updateProject()
    451 
    452 #    for project in mongo.db.projects_collection.find():
    453 #        availableProjects.append((project['_id'],project['project_name']))
    454     
    455     form.projectSel.choices = availableProjects
    456 
    457     if form.validate_on_submit():
    458         app.logger.info('update project route')
    459         try:
    460             entry = mongo.db.time_collection.find_one({'_id':timeid})
    461         except:
    462             flash("Issue finding/assigning time_id: {}".format(timeid))
    463         else:
    464             try:
    465                 mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'project':ObjectId(form.projectSel.data)}})
    466             except:
    467                 flash("unable to set project for {} to {}".format(timeid, form.projectSel.data))
    468             else:
    469                 flash("Updated project")
    470             finally:
    471                 return redirect(url_for('hours',username=entry['modified_by'][0]))#change to hours and mod_username redirect
    472     return render_template('dashboard/punchclock/update/project.html',form=form, ORGNAME=OrganizationName)
    473 
    474 @app.route("/update/start/<mod_username>/<timeid>",methods=['GET','POST']) #TODO MAKE OTHER VALUE FOR LAST PAGE, RETURNS LAST PAGE ELSE DASHBOARD
    475 @login_required
    476 def updateStartTime(mod_username,timeid):
    477 #    projects = []
    478 #    agreement_id = ObjectId(agreement_id)
    479 #    agreement = {'_id': 'defaultagreement', 'agreement_name': 'Default Agreement', 'agency': ['YEP'], 'projects': ['no projects'], 'start_date': 'No Start Date', 'end_date': 'No End Date'}
    480 #    try:
    481 #        agreement = mongo.db.agreements_collection.find_one({'_id':agreement_id})
    482 #        #mongo.db.agreements_collection.find_one({'_id':agreement_id})
    483 #    except:
    484 #        flash("Issue assigning Agreement data for agreement id {}".format(agreement_id))
    485 #    else:
    486 #        for project in agreement['projects']:
    487 #            try:
    488 #                pj = mongo.db.projects_collection.find_one({'_id':project})
    489 #            except:
    490 #                flash("Issue assigning project data for id {}".format(project))
    491 #            else:
    492 #                projects.append(pj)
    493 #    finally:
    494 #        return render_template('admin/agreements/index.html',projects=projects,agreement=agreement,ORGNAME=OrganizationName)
    495     timeid = ObjectId(timeid)
    496     form = updateTime()
    497 
    498     if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry
    499         try:
    500             entry = mongo.db.time_collection.find_one({'_id': timeid})
    501         except:
    502             flash("Issue finding/assigning time_id: {}".format(timeid))
    503         else:
    504             day = entry['date'].date()
    505             newtime = datetime.datetime.combine(day, form.timeSel.data)#TODO FINISH Creating variable for datetime.combine(date,timeSel.data)
    506             try:
    507                 mongo.db.time_collection.update_one({'_id':timeid},{'$push':{'modified_by':mod_username,'clock_in':newtime}})
    508             except:
    509                 flash("Unable to push mod_username {}".format(mod_username))
    510             else:
    511                 flash('Updated time to {}'.format(form.timeSel.data))
    512             finally:
    513                 return redirect(url_for('dashboard'))#TODO RETURN LAST PAGE HERE!
    514 
    515     return render_template('dashboard/punchclock/update/startTime.html',form=form, ORGNAME=OrganizationName)
    516 
    517 @app.route("/update/end/<mod_username>/<timeid>",methods=['GET','POST']) #TODO MAKE OTHER VALUE FOR LAST PAGE, RETURNS LAST PAGE ELSE DASHBOARD
    518 @login_required
    519 def updateEndTime(mod_username,timeid):
    520     timeid = ObjectId(timeid)
    521     form = updateTime()
    522 
    523     if form.validate_on_submit():
    524         try:
    525             entry = mongo.db.time_collection.find_one({'_id': timeid})
    526         except:
    527             flash("Issue finding/assigning time_id: {}".format(timeid))
    528         else:
    529             day = entry['date'].date()
    530             newtime = datetime.datetime.combine(day, form.timeSel.data)
    531             try:
    532                 mongo.db.time_collection.update_one({'_id':timeid},{'$push':{'modified_by':mod_username,'clock_out':newtime}})
    533             except:
    534                 flash("Unable to push mod_username {}".format(mod_username))
    535             else:
    536                 flash('Updated time to {}'.format(form.timeSel.data))
    537             finally:
    538                 return redirect(url_for('dashboard'))#TODO RETURN LAST PAGE HERE!
    539 
    540     return render_template('dashboard/punchclock/update/endTime.html',form=form, ORGNAME=OrganizationName)
    541 
    542 @app.route("/update/day/<mod_username>/<timeid>",methods=['GET','POST']) #TODO MAKE OTHER VALUE FOR LAST PAGE, RETURNS LAST PAGE ELSE DASHBOARD
    543 @login_required
    544 def updateDate(mod_username,timeid):
    545     timeid = ObjectId(timeid)
    546     form = upDate()
    547 
    548     if form.validate_on_submit():
    549         try:
    550             entry = mongo.db.time_collection.find_one({'_id': timeid})
    551         except:
    552             flash("Issue finding/assigning time_id: {}".format(timeid))
    553         else:
    554             fdate = datetime.datetime.combine(form.dateSel.data,datetime.time())
    555             newstart = entry['clock_in'][-1].replace(year = fdate.year, month = fdate.month, day = fdate.day)
    556             try:
    557                 newend = entry['clock_out'][-1].replace(year = fdate.year, month = fdate.month, day = fdate.day)
    558             except:
    559                 flash("User currently clocked in") # TODO FIGURE OUT WHAT TO DO IF USER IS CLOCKED IN AND ATTEMPTING TO CHANGE ENTRY DATE... POSSIBLY SHOULDN'T UPDATE JUST THE CLOCKIN TIME AND LEAVE CLOCK OUT... ALSO WOULD BE ODD TO CLOCK OUT USER...
    560             else:
    561                 try:
    562                     mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'date':fdate},'$push':{'modified_by':mod_username,'clock_in':newstart,'clock_out':newend}})
    563                 except:
    564                     flash("Unable to push mod_username {} date {} clockin {} and clockout {}".format(mod_username,fdate,newstart,newend))
    565                     try:
    566                         mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'date':fdate}})
    567                     except:
    568                         flash("Unable to set date {}".format(fdate))
    569                     finally:
    570                         try:
    571                             mongo.db.time_collection.update_one({'_id':timeid},{'$push':{'modified_by':mod_username,'clock_in':newstart,'clock_out':newend}})
    572                         except:
    573                             flash("Unable to push mod_username {} date {} clockin {} and clockout {}".format(mod_username,fdate,newstart,newend))
    574                 else:
    575                     flash('Updated date to {}'.format(form.dateSel.data))
    576                 finally:
    577                     return redirect(url_for('dashboard'))#TODO RETURN LAST PAGE HERE!
    578 
    579     return render_template('dashboard/punchclock/update/date.html',form=form, ORGNAME=OrganizationName)
    580 
    581 @app.route("/update/note/<mod_username>/<timeid>",methods=['GET','POST']) #TODO MAKE OTHER VALUE FOR LAST PAGE, RETURNS LAST PAGE ELSE DASHBOARD
    582 @login_required
    583 def updateNote(mod_username,timeid):
    584     timeid = ObjectId(timeid)
    585     form = newNote()
    586 
    587     if form.validate_on_submit():
    588         try:
    589             entry = mongo.db.time_collection.find_one({'_id': timeid})
    590         except:
    591             flash("Issue finding/assigning time_id: {}".format(timeid))
    592         else:
    593             if form.note.data != None and form.note.data != '':
    594                 newNoted = form.note.data
    595                 try:
    596                     mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'note':newNoted},'$push':{'modified_by':mod_username}})
    597                 except:
    598                     flash("{}: Unable to set note to {}".format(mod_username,newNoted))
    599                 else:
    600                     flash('Updated note')
    601                 finally:
    602                     return redirect(url_for('dashboard'))#TODO RETURN LAST PAGE HERE!
    603             else:
    604                 try:
    605                     mongo.db.time_collection.update_one({'_id':timeid},{'$unset':{"note":""},'$push':{'modified_by':mod_username}})
    606                 except:
    607                     flash("Unable to remove note")
    608                 else:
    609                     flash('Removed note')
    610                 finally:
    611                     return redirect(url_for('dashboard'))
    612 
    613     return render_template('dashboard/punchclock/update/note.html',form=form, ORGNAME=OrganizationName)
    614 
    615 #TODO FIGURE OUT WHY IT TAKES TWO CLICKS TO SET VALUES TO TRUE ON HOURS PAGE AND ALSO CREW CLOCKED IN LIST
    616 @app.route("/toggle-lunch/<timeid>",methods=['GET','POST'])
    617 @login_required
    618 def toggle_lunch(timeid):
    619     timeid = ObjectId(timeid)
    620 
    621     if mongo.db.time_collection.find_one({'_id': timeid, 'lunch':{'$exists':False}}):
    622         mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'lunch':True}})
    623     if mongo.db.time_collection.find_one({'_id': timeid})['lunch'] == True:
    624         mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'lunch':False}})
    625     else:
    626         mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'lunch':True}})
    627 
    628     return redirect(url_for('dashboard'))
    629 
    630 @app.route("/toggle-per-diem/<timeid>",methods=['GET','POST'])
    631 @login_required
    632 def toggle_per_diem(timeid):
    633     timeid = ObjectId(timeid)
    634 
    635     if mongo.db.time_collection.find_one({'_id': timeid, 'per_diem':{'$exists':False}}):
    636         mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'per_diem':True}})
    637     if mongo.db.time_collection.find_one({'_id': timeid})['per_diem'] == True:
    638         mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'per_diem':False}})
    639     else:
    640         mongo.db.time_collection.update_one({'_id':timeid},{'$set':{'per_diem':True}})
    641     
    642     return redirect(url_for('dashboard'))
    643 #TODO
    644 @app.route("/clockinuser",methods=['GET','POST'])
    645 @login_required
    646 def clockin_new_user():
    647     clocked_in_users = mongo.db.time_collection.find({'clock_out': {'$exists':False}})
    648     availableProjects = get_available_projects(current_user.branch)#[]
    649     #TODO Might be helpful to make available projects search for projects available to the selected user...
    650 #    for project in mongo.db.projects_collection.find():
    651 #        availableProjects.append((project['_id'],project['project_name']))
    652 #   GET_CLOCKED out users
    653     clocked_out_active_users=[]
    654     clocked_in_active_users=[]
    655     for active in mongo.db.user_collection.find({'is_active':True},{'username':1,'fname':1,'mname':1,'lname':1}):
    656         funame = active['fname']+' '+active['lname']
    657         alreadyin = []
    658         for user in clocked_in_users:
    659             alreadyin.append(user['modified_by'][0])
    660         if any(element in active['username'] for element in alreadyin):
    661             clocked_in_active_users.append((active['_id'],funame))
    662         else:
    663             clocked_out_active_users.append((active['username'],funame))
    664 
    665     form=CrewClockinWidget()
    666     form.time.data = datetime.datetime.now()
    667     form.projectSel.choices = availableProjects
    668     #form.projectSel.data = availableProjects[0]
    669     form.userSel.choices = clocked_out_active_users
    670     #form.userSel.data = clocked_out_active_users[0]
    671 
    672     if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry
    673         mongo.db.time_collection.insert_one({'clock_in' : [form.time.data],
    674                                          'modified_by' : [form.userSel.data, current_user.username],
    675                                          'date' : datetime.datetime.today(),
    676                                          'project' : ObjectId(form.projectSel.data)})
    677         return redirect(url_for('dashboard'))
    678 
    679     return render_template('dashboard/punchclock/otheruser.html',form=form,ORGNAME=OrganizationName)
    680 
    681 @app.route("/newtime/<usernm>",methods=['GET','POST'])
    682 @login_required
    683 def new_time(usernm):
    684     user = mongo.db.user_collection.find_one({"username": usernm})
    685     availableProjects = get_available_projects(current_user.branch) #[]
    686 #    for project in mongo.db.projects_collection.find():
    687 #        availableProjects.append((project['_id'],project['project_name']))
    688 #    availableProjects = [("","Select Project")]
    689 #    for project in mongo.db.projects_collection.find():
    690 #        if 'branch' in project:
    691 #            if project['branch'] == 'Global' or project['branch'] == user['branch']:
    692 #                availableProjects.append((project['_id'],project['project_name']))
    693 
    694     form=NewHoursForm()
    695     form.projectSel.choices = availableProjects
    696 
    697     if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry
    698     #    if form.projectSel.data == "" or form.projectSel.data == "Select Project":
    699     #        flash("You must Select a Project")# This part doesn't seem to function?
    700     #        return redirect(url_for('new_time',usernm=usernm))
    701         dateentry = datetime.datetime.combine(form.dateSel.data,datetime.time())
    702         starttime = datetime.datetime.combine(form.dateSel.data,form.startTime.data)
    703         endtime = datetime.datetime.combine(form.dateSel.data,form.endTime.data)
    704         entryevent = {
    705                                          'date' : dateentry,
    706                                          'clock_in' : [starttime],
    707                                          'clock_out' : [endtime],
    708                                          'project' : ObjectId(form.projectSel.data),
    709                                          'lunch': form.lunchSel.data,
    710                                          'per_diem':form.perDiemSel.data
    711                 }
    712         if usernm is current_user.username:
    713             entryevent['modified_by'] = [usernm]
    714         else:
    715             entryevent['modified_by'] = [usernm,current_user.username]
    716         if form.note.data != '':
    717             entryevent['note'] = form.note.data
    718         mongo.db.time_collection.insert_one(entryevent)
    719 
    720         return redirect(url_for('hours',username=user['username']))
    721 
    722     return render_template('dashboard/punchclock/otheruser.html',user=user,form=form,ORGNAME=OrganizationName)
    723 
    724 @app.route("/newusertime",methods=['GET','POST'])
    725 @login_required
    726 def new_user_time():
    727     clocked_in_users = mongo.db.time_collection.find({'clock_out': {'$exists':False}})
    728     availableProjects = get_available_projects(current_user.branch)
    729 #    for project in mongo.db.projects_collection.find():
    730 #        availableProjects.append((project['_id'],project['project_name']))
    731 #   GET_CLOCKED out users
    732     clocked_out_active_users=[]
    733     clocked_in_active_users=[]
    734     for active in mongo.db.user_collection.find({'is_active':True},{'username':1,'fname':1,'mname':1,'lname':1}):
    735         funame = active['fname']+' '+active['lname']
    736         alreadyin = []
    737         for user in clocked_in_users:
    738             alreadyin.append(user['modified_by'][0])
    739         if any(element in active['username'] for element in alreadyin):
    740             clocked_in_active_users.append((active['_id'],funame))
    741         else:
    742             clocked_out_active_users.append((active['username'],funame))
    743 
    744     form=NewUserHourForm()
    745     #form.startTime.data = datetime.datetime.now()
    746     form.projectSel.choices = availableProjects
    747     #form.projectSel.data = availableProjects[0]
    748     form.userSel.choices = clocked_out_active_users
    749     #form.userSel.data = clocked_out_active_users[0]
    750 
    751     if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry
    752         dateentry = datetime.datetime.combine(form.dateSel.data,datetime.time())
    753         starttime = datetime.datetime.combine(form.dateSel.data,form.startTime.data)
    754         endtime = datetime.datetime.combine(form.dateSel.data,form.endTime.data)
    755         entryevent = {
    756                                          'date' : dateentry,
    757                                          'clock_in' : [starttime],
    758                                          'clock_out' : [endtime],
    759                                          'project' : ObjectId(form.projectSel.data),
    760                                          'lunch': form.lunchSel.data,
    761                                          'per_diem':form.perDiemSel.data
    762                 }
    763         if form.userSel.data is current_user.username:
    764             entryevent['modified_by'] = [form.userSel.data]
    765         else:
    766             entryevent['modified_by'] = [form.userSel.data,current_user.username]
    767         if form.note.data != '' and form.note.data != None:
    768             entryevent['note'] = form.note.data
    769         try:
    770             mongo.db.time_collection.insert_one(entryevent)
    771         except:
    772             flash("Unhandled error occured")
    773         else:
    774             flash("Created time entry for {}".format(userSel.data))
    775         finally:
    776             return redirect(url_for('new_user_time'))
    777 
    778     return render_template('dashboard/punchclock/otheruser.html',form=form,ORGNAME=OrganizationName)
    779 
    780 @app.route("/clockinuser/<modusernm>/<usertoinid>/<project>/<intime>", methods=['GET','POST'])
    781 @login_required
    782 def clockin_by_id(modusernm,usertoinid,project,intime):
    783     timeid = ObjectId()
    784     user2 = eval(usertoinid)
    785     project = eval(project)
    786     
    787     mongo.db.time_collection.insert_one({'_id':timeid,
    788                                          'modified_by' :[user2[0], modusernm],
    789                                          'clock_in' : [datetime.datetime.strptime(intime, '%Y-%m-%d %H:%M:%S.%f')],
    790                                          'project' : ObjectId(project[0])})
    791 
    792     return redirect(url_for('dashboard'))
    793 
    794 @app.route("/clockoutuser/<modusernm>/<timeid>", methods=['GET','POST'])
    795 @login_required
    796 def clockout_by_id(modusernm,timeid):
    797     # if modified_by.last != modusernm: modified_by.append(modusernm)
    798 
    799     timeid = ObjectId(timeid)
    800 
    801     def clock_otheruser_out(time_id,mod_username):
    802         if mongo.db.time_collection.find({'_id': time_id}, {'clock_out':{'$exists':False}}):
    803             mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.now()]}})
    804             mongo.db.time_collection.update_one({'_id':time_id},{'$push':{'modified_by':mod_username}})
    805             flash('Clocked out')
    806         else:
    807             flash('No time entry found, or user has checked out already')
    808 
    809     clock_otheruser_out(timeid,modusernm)
    810 
    811     return redirect(url_for('dashboard'))
    812 
    813 ####                      ####
    814 #######  Admin Route   #######
    815 ####                      ####
    816 @app.route("/admin")
    817 @login_required
    818 #@admin_required
    819 def admin():
    820     adminperms=mongo.db.permissions_collection.find_one({'label': current_user.role},{'admin':1,'_id':0})
    821     adminperms=adminperms['admin']
    822     #AgreementsData
    823     allagreements=mongo.db.agreements_collection.find()#All agreements, including outside endDate... filter to active agreements?
    824     agreements = []
    825     for agreement in allagreements:
    826         agreement['total_budget']=0
    827         agreement['total_cost']=0
    828         for i in range(len(agreement['projects'])):
    829             try:
    830                 if mongo.db.projects_collection.find_one({'_id': agreement['projects'][i]}) is None:
    831                     flash("could not find project {} in agreement {}".format(agreement['projects'][i],agreement['_id']))
    832                     return redirect(url_for('dashboard'))
    833                 else:
    834                     agreement['projects'][i]= mongo.db.projects_collection.find_one({'_id': agreement['projects'][i]})
    835             except:
    836                 flash("could not find project {} in agreement {}".format(agreement['projects'][i],agreement['_id']))
    837                 return redirect(url_for('dashboard'))
    838             else:
    839                 try:
    840                     if agreement['projects'][i]:#['budget'][0]
    841                        agreement['projects'][i]['total_budget'] = sum(agreement['projects'][i]['budget'][0].values())
    842                     else:
    843                        agreement['projects'][i]['total_budget'] = 1
    844                 except:
    845                     flash("could not assign agreement {} total budget")
    846                     agreement['projects'][i]['total_budget'] = 1
    847                 finally:
    848                     agreement['projects'][i]['total_cost'] = sum(agreement['projects'][i]['costs'][0].values())
    849                     agreement['total_budget']=agreement['total_budget']+agreement['projects'][i]['total_budget']
    850                     agreement['total_cost']=agreement['total_cost']+agreement['projects'][i]['total_cost']
    851         agreements.append(agreement)
    852 
    853     #all_permissions=mongo.db.permissions_collection.find_one({"label":current_user.role})
    854     #admnperms=all_permissions.admin
    855     return render_template ('admin/layout.html',agreements=agreements,permissions=adminperms,ORGNAME=OrganizationName)
    856 
    857 ####                                 ####
    858 #######  Agreement Report Route   #######
    859 ####                                 ####
    860 # Report Routes
    861 
    862 #@app.route('/admin/agreement')
    863 #@login_required
    864 #def agreement_report():
    865 #    return render_template ('admin/reports/agreement_report.html', ORGNAME=OrganizationName)
    866 
    867 ####                                ####
    868 #######  Employee Report Route   #######
    869 ####                                ####
    870 #@app.route('/admin/employee')
    871 #@login_required
    872 #def employee_report():
    873 #    return render_template ('admin/reports/employee_report.html', ORGNAME=OrganizationName)
    874 
    875 ####                                  ####
    876 #######  Pay Period Report Route   #######
    877 ####                                  ####
    878 #@app.route('/admin/pay-period')
    879 #@login_required
    880 #def pay_period_report():
    881 #    return render_template ('admin/reports/pay_period_report.html', ORGNAME=OrganizationName)
    882 
    883 ####                               ####
    884 #######  Vehicle Report Route   #######
    885 ####                               ####
    886 #@app.route('/admin/vehicle')
    887 #@login_required
    888 #def vehicle_report():
    889 #    return render_template ('admin/reports/vehicle_report.html', ORGNAME=OrganizationName)
    890 
    891 #############################
    892 ###### DEV HOURS ROUTE ######
    893 #############################
    894 @app.route('/hoursd/<username>', methods=['GET','POST'])
    895 @login_required
    896 def hoursd(username):#userid goes into call to db to get user[] -> then returns formatted table (punchclock/index.html
    897 
    898     aghours = mongo.db.time_collection.find({'modified_by.0':username})
    899     dbhours = mongo.db.time_collection.aggregate( [
    900             {
    901                 "$match":{'modified_by.0':username}
    902             },
    903             { 
    904                '$lookup': {
    905                'from': 'projects_collection',
    906                'localField': 'project',
    907                'foreignField': '_id',
    908                'as': 'project'
    909                }
    910             },
    911             {
    912                 "$sort":{'clock_in':-1}
    913             },
    914             {
    915                 "$limit":10 #change to ~ 30 days OR to prior 1st or 15th of month 
    916             }
    917 
    918             ] )
    919 
    920     tspp = mongo.db.time_collection.aggregate( [
    921         {
    922             "$lookup":{
    923                     'from':'projects_collection',
    924                     'localField':'project',
    925                     'foreignField':'_id',
    926                     'as':'project_data'
    927                 }
    928         },
    929         {
    930             "$group": {
    931                 "_id":"$project_data",
    932                 "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
    933                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
    934                 "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
    935                 }
    936         },
    937         {
    938             "$addFields": {
    939                 "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
    940             }
    941         },
    942         {
    943             "$sort": { "_id": -1 }
    944         }
    945     ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
    946 
    947     #Banner, TODO create a MOTD framework, for admin messages
    948     currentdate = datetime.datetime.now()
    949     currentdate = currentdate.strftime('%Y-%m-%d')
    950     currentdate = currentdate.split('-')
    951     birthday = current_user.birthday.split("-")
    952     if currentdate[1] == birthday[1] and currentdate[2] == birthday[2]:
    953         flash("Happy Birthday {}!".format(current_user.fname))
    954     ## END Banner
    955 
    956     return render_template ('dashboard/punchclock/index.dev.html',cd=currentdate, bd=birthday,hours=dbhours,tspp=tspp,ORGNAME=OrganizationName)
    957 
    958 
    959 
    960 ####                      ####
    961 #######  Hours Route   #######
    962 ####                      ####
    963 @app.route('/hours/<username>', methods=['GET','POST'])
    964 @login_required
    965 def hours(username):
    966     #query time collection for all time entries
    967     #for entry in entries:
    968     #   time = entry.get('clock_out'[0],datetime.datetime.now()) - entry['clock_in'][0]
    969     #   if entry['modified_by'][0] in user_hours:
    970     #       user_hours[entry['modified_by'][0]] =+ time
    971     #   else:
    972     #       user_hours.append({entry['modified_by'][0],time})
    973 
    974    # user = load_user(username)
    975     #dashperms=mongo.db.permissions_collection.find_one({'label': current_user.role},{'dashboard':1,'_id':0})
    976     #dashperms=dashperms['dashboard']
    977     #total_hours=0
    978     user = mongo.db.user_collection.find_one({"username": username})
    979     availableProjects = get_available_projects(current_user.branch)
    980 #    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
    981 #        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'
    982         #'completed':{'$exists':False}
    983     dbhours = mongo.db.time_collection.aggregate( [
    984             {
    985                 "$match":{'modified_by.0':username}
    986             },
    987             { 
    988                '$lookup': {
    989                'from': 'projects_collection',
    990                'localField': 'project',
    991                'foreignField': '_id',
    992                'as': 'project'
    993                }
    994             },
    995             {
    996                 "$sort":{'clock_in':-1}
    997             },
    998             {
    999                 "$limit":10 #change to ~ 30 days OR to prior 1st or 15th of month 
   1000             }
   1001 
   1002             ] )
   1003     #hours = []
   1004     #deltas=[]
   1005     #for hour in dbhours: # Currenty acts wrong with longer than 1 day
   1006     #    for x, y in availableProjects:
   1007     #        if x is ObjectId(hour['project']):
   1008     #            hour['projectName'] = y
   1009     #    if 'clock_out' not in hour:
   1010     #        hour['clock_out']=[datetime.datetime.now()]
   1011     #    time = hour['clock_out'][-1] - hour['clock_in'][-1]
   1012     #    hour['total_time'] = time
   1013     #    hours.append(hour)
   1014     #    deltas.append(time)
   1015     
   1016     form = NewHoursForm()
   1017     form.dateSel.data = datetime.datetime.today()
   1018     form.projectSel.choices = availableProjects
   1019     form.startTime.data = datetime.datetime.now()
   1020     #total_hours = sum(deltas,datetime.timedelta())
   1021     #statement_hours = "{} Hours, {} Minutes".format(total_hours.seconds//3600,(total_hours.seconds//60)%60)
   1022 
   1023 #    if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry
   1024 #        indt = datetime.combine(form.dateSel.data,form.startTime.data)
   1025 #        outdt = datetime.combine(form.dateSel.data,form.endTime.data)
   1026 #        if user['username'] is current_user.username and not form.perDiemSel.data and not form.lunchSel.data:
   1027 #            mongo.db.time_collection.insert_one({'clock_in' : [indt],
   1028 #                                         'modified_by' : [current_user.username],
   1029 #                                         'date' : datetime.datetime.today(),
   1030 #                                         'project' : ObjectId(form.projectSel.data),
   1031 #                                         'clock_out':[outdt]})
   1032 #            return redirect(url_for('hours',username=user['username']))
   1033 #
   1034 #        if user['username'] is not current_user.username and not form.perDiemSel.data and not form.lunchSel.data:
   1035 #            mongo.db.time_collection.insert_one({'clock_in' : [indt],
   1036 #                                         'modified_by' : [user['username'], current_user.username],
   1037 #                                         'date' : datetime.datetime.today(),
   1038 #                                         'project' : ObjectId(form.projectSel.data),
   1039 #                                         'clock_out':[outdt]})
   1040 #
   1041 #            return redirect(url_for('hours',username=user['username']))
   1042  
   1043     #hours = mongo.db.time_collection.find({'modified_by.0':user.username})
   1044 
   1045 
   1046     tspp = mongo.db.time_collection.aggregate( [
   1047         {
   1048             "$lookup":{
   1049                     'from':'projects_collection',
   1050                     'localField':'project',
   1051                     'foreignField':'_id',
   1052                     'as':'project_data'
   1053                 }
   1054         },
   1055         {
   1056             "$group": {
   1057                 "_id":"$project_data",
   1058                 "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1059                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   1060                 "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
   1061                 }
   1062         },
   1063         {
   1064             "$addFields": {
   1065                 "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1066             }
   1067         },
   1068         {
   1069             "$sort": { "_id": -1 }
   1070         }
   1071     ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
   1072 
   1073 
   1074     return render_template ('dashboard/punchclock/index.html',form=form,hours=dbhours,user=user,tspp=tspp,ORGNAME=OrganizationName)
   1075 
   1076 @app.route('/allhours/<username>', methods=['GET','POST'])
   1077 @login_required
   1078 def all_user_hours(username):
   1079     user = mongo.db.user_collection.find_one({"username": username})
   1080     #need a function which checks for dated(archived) time collections, and appends them to list. Might look like below
   1081     #   hours = [ {'current_year()":{'current pay period':{mongo_aggregate_lookup(returned doc)}},(each pay period in current year)},{'last_year().date':[{data},{data}]},etc...
   1082     #   This setup might benefit from time entries being in their own db?
   1083     dbhours = mongo.db.time_collection.aggregate( [
   1084             {
   1085                 "$match":{'modified_by.0':username}
   1086             },
   1087             { 
   1088                '$lookup': {
   1089                'from': 'projects_collection',
   1090                'localField': 'project',
   1091                'foreignField': '_id',
   1092                'as': 'project'
   1093                }
   1094             },
   1095             {
   1096                 "$sort":{'clock_in':-1}
   1097             }
   1098             ] )
   1099 
   1100     return render_template ('dashboard/punchclock/all.html',hours=dbhours,user=user,ORGNAME=OrganizationName)
   1101 
   1102 # Don't really need this until additional functionality is added, not in current project scope
   1103 #@app.route("/fleet")
   1104 #def fleet():
   1105 #    return render_template('dashboard/fleet/index.html',ORGNAME=OrganizationName)
   1106 
   1107 ####                            ####
   1108 #######  Roles Admin Route   #######
   1109 ####                            ####
   1110 @app.route("/admin/roles")
   1111 @login_required
   1112 def roles():
   1113     admnform = AdmnPermissionsForm()
   1114     dashform = DashPermissionsForm()
   1115     return render_template('admin/roles/updateroles.html',dashform=dashform,admnform=admnform,ORGNAME=OrganizationName)
   1116 
   1117 ####                                   ####
   1118 #######  Active Users Admin Route   #######
   1119 ####                                   ####
   1120 @app.route("/admin/users/active")
   1121 @login_required
   1122 def activeusers():
   1123     active = mongo.db.user_collection.find({'is_active':True})
   1124     return render_template('admin/users/active.html',activeusers=active,ORGNAME=OrganizationName)
   1125 
   1126 @app.route("/user/<user_id>")
   1127 @login_required
   1128 def user(user_id):
   1129     usr = mongo.db.user_collection.find({"_id":ObjectId(user_id)})
   1130     return render_template('admin/users/active.html',activeusers=usr,ORGNAME=OrganizationName)
   1131 
   1132 @app.route("/admin/deactivate/<userid>",methods=['GET','POST'])
   1133 @login_required
   1134 def deactivate_user(userid):
   1135     userid = ObjectId(userid)
   1136 
   1137     if mongo.db.user_collection.find_one({'_id': userid})['is_active'] == True:
   1138         mongo.db.user_collection.update_one({'_id':userid},{'$set':{'is_active':False}})
   1139 
   1140     return redirect(url_for('activeusers'))
   1141 
   1142 ####                                     ####
   1143 #######  Inactive Users Admin Route   #######
   1144 ####                                     ####
   1145 @app.route("/admin/users/inactive")
   1146 @login_required
   1147 def inactiveusers():
   1148     inactive = mongo.db.user_collection.find({'is_active':False})
   1149     return render_template('admin/users/inactive.html',inactiveusers=inactive,ORGNAME=OrganizationName)
   1150 
   1151 @app.route("/admin/activate/<userid>",methods=['GET','POST'])
   1152 @login_required
   1153 def activate_user(userid):
   1154     userid = ObjectId(userid)
   1155 
   1156     if mongo.db.user_collection.find_one({'_id': userid})['is_active'] == False:
   1157         mongo.db.user_collection.update_one({'_id':userid},{'$set':{'is_active':True}})
   1158 
   1159     return redirect(url_for('inactiveusers'))
   1160 
   1161 ####                               ####
   1162 #######  New User Admin Route   #######
   1163 ####                               ####
   1164 @app.route("/removetime/<timeid>",methods=['GET','POST'])
   1165 @login_required
   1166 def removetime(timeid):
   1167     timeid = ObjectId(timeid)
   1168     try:
   1169         mongo.db.time_collection.delete_one({'_id':timeid})
   1170     except:
   1171         flash("An unhandled error occured removing time")
   1172     else:
   1173         flash("Removed Time")
   1174     finally:
   1175         return redirect(url_for('dashboard'))
   1176 #@app.route("/clockoutuser/<modusernm>/<timeid>", methods=['GET','POST'])
   1177 #@login_required
   1178 #def clockout_by_id(modusernm,timeid):
   1179 #    # if modified_by.last != modusernm: modified_by.append(modusernm)
   1180 #
   1181 #    timeid = ObjectId(timeid)
   1182 #
   1183 #    def clock_otheruser_out(time_id,mod_username):
   1184 #        if mongo.db.time_collection.find({'_id': time_id}, {'clock_out':{'$exists':False}}):
   1185 #            mongo.db.time_collection.update_one({'_id':time_id},{'$set':{'clock_out':[datetime.datetime.utcnow()]}})
   1186 #            mongo.db.time_collection.update_one({'_id':time_id},{'$push':{'modified_by':mod_username}})
   1187 #            flash('Clocked user out')
   1188 #        else:
   1189 #            flash('No time entry found, or user has checked out already')
   1190 #
   1191 #    clock_otheruser_out(timeid,modusernm)
   1192 #
   1193 #    return redirect(url_for('dashboard'))
   1194 ####                               ####
   1195 @app.route("/admin/users/modify/<uid>", methods=["GET","POST"])
   1196 @login_required
   1197 def moduser(uid):
   1198 #   Temp values, change to modular db dependent values
   1199     availableBranches = ['Dillon','Salmon']
   1200     allRoles=mongo.db.permissions_collection.find({},{'label':1})
   1201     availableRoles = []
   1202 
   1203     dashperms=mongo.db.permissions_collection.find_one({'label': current_user.role},{'dashboard':1,'_id':0})
   1204     dashperms=dashperms['dashboard']
   1205     
   1206     for perm in dashperms:
   1207         availableRoles.append((perm['_id'],perm['label']))
   1208     
   1209     defaultBranch = 'Dillon'
   1210     defaultRole = 'Crew'
   1211 #   END TMP Values
   1212 
   1213     form = ChangeUserForm()
   1214 
   1215     form.branch.choices = [("",'Select Branch'),("Global","Global")]
   1216     for branch in get_available_branches():
   1217         form.branch.choices.append(branch)
   1218     
   1219     form.role.choices = availableRoles
   1220     form.role.default = defaultRole
   1221 
   1222     if form.validate_on_submit():
   1223         mongo.db.user_collection.update_one({"_id":ObjectId(uid)},{ '$set': {
   1224             'fname':form.fname.data,
   1225             'mname':form.mname.data,
   1226             'lname':form.lname.data,
   1227             'username':form.fname.data.lower()+form.mname.data.lower()+form.lname.data.lower(),
   1228             'birthday':form.birthday.data.strftime('%Y-%m-%d'),
   1229             'role':form.role.data,
   1230             'branch':form.branch.data,
   1231             'phonenumber':form.phonenumber.data,
   1232             'address':form.address.data,
   1233             'email':form.email.data,
   1234             'pay_period':form.payPeriod.data,
   1235             'pay_value':form.role.data,
   1236             'is_active':form.setActive.data
   1237             }})
   1238         flash("Updated user information for {} {}".format(form.fname.data, form.lname.data))
   1239         return redirect(url_for('activeusers'))
   1240 
   1241     return render_template('admin/users/moduser.html',form=form,ORGNAME=OrganizationName)
   1242 
   1243 @app.route("/admin/users/new", methods=["GET","POST"])
   1244 @login_required
   1245 def newuser():
   1246 #   Temp values, change to modular db dependent values
   1247     availableBranches = ['Dillon','Salmon']
   1248 
   1249     availableRoles = []
   1250     for perm in mongo.db.permissions_collection.find():
   1251         availableRoles.append((perm['label']))
   1252 
   1253     defaultBranch = 'Dillon'
   1254 #   END TMP Values
   1255 
   1256     form = NewUserForm()
   1257     
   1258     #form.branch.choices = availableBranches
   1259     form.branch.choices = [("",'Select Branch'),("Global","Global")]
   1260     for branch in get_available_branches():
   1261         form.branch.choices.append(branch)
   1262 
   1263     form.role.choices = availableRoles
   1264     #form.process()
   1265 
   1266     if form.validate_on_submit():
   1267         genpasswd = ''.join(random.choice(string.ascii_letters) for _ in range(14))
   1268         if form.payValue.data is not None:
   1269             rolePayValue = form.payValue.data
   1270         else:
   1271             roleValue = mongo.db.permissions_collection.find_one({'label':form.role.data})
   1272             rolePayValue = roleValue['base_pay_value']
   1273         
   1274         mongo.db.user_collection.insert_one({
   1275             'fname':form.fname.data,
   1276             'mname':form.mname.data,
   1277             'lname':form.lname.data,
   1278             'username':form.fname.data.lower()+form.mname.data.lower()+form.lname.data.lower(),
   1279             'birthday':form.birthday.data.strftime('%Y-%m-%d'),
   1280             'password_hash':generate_password_hash(genpasswd),
   1281             'role':form.role.data,
   1282             'branch':form.branch.data,
   1283             'phonenumber':form.phonenumber.data,
   1284             'address':form.address.data,
   1285             'email':form.email.data,
   1286             'pay_period':form.payPeriod.data,
   1287             'pay_value':rolePayValue,
   1288             'is_active':form.setActive.data
   1289             })
   1290         flash("New user for {} {} added with a password of {}".format(form.fname.data, form.lname.data, genpasswd)) #Will need to sendmail password to form.email.data later
   1291         return redirect(url_for('newuser'))
   1292 
   1293     return render_template('admin/users/newuser.html',form=form,ORGNAME=OrganizationName)
   1294 
   1295 ####                                ####
   1296 #######  Agreement Admin Route   #######
   1297 ####                                ####
   1298 @app.route("/admin/agreement/<agreement_id>",methods=["GET"])
   1299 @login_required
   1300 def agreement(agreement_id):
   1301     projects = []
   1302     agreement_id = ObjectId(agreement_id)
   1303     agreement = {'_id': 'defaultagreement', 'agreement_name': 'Default Agreement', 'agency': ['YEP'], 'projects': ['no projects'], 'start_date': 'No Start Date', 'end_date': 'No End Date'}
   1304     try:
   1305         agreement = mongo.db.agreements_collection.find_one({'_id':agreement_id})
   1306         #mongo.db.agreements_collection.find_one({'_id':agreement_id})
   1307     except:
   1308         flash("Issue assigning Agreement data for agreement id {}".format(agreement_id))
   1309     else:
   1310         for project in agreement['projects']:
   1311             try:
   1312                 pj = mongo.db.projects_collection.find_one({'_id':project})
   1313             except:
   1314                 flash("Issue assigning project data for id {}".format(project))
   1315             else:
   1316                 projects.append(pj)
   1317     finally:
   1318         return render_template('admin/agreements/index.html',projects=projects,agreement=agreement,ORGNAME=OrganizationName)
   1319 
   1320 @app.route("/admin/agreements/new", methods=["GET","POST"])
   1321 @login_required
   1322 def newagreement():
   1323 #   Temp values, change to modular db dependent values
   1324 #   END TMP Values
   1325 
   1326     form = NewAgreementForm()
   1327 
   1328     if form.validate_on_submit():
   1329        #     create deterministic agreement unique _id? Example being genpasswd in new user validate on submit
   1330         
   1331         mongo.db.agreements_collection.insert_one({
   1332             'agreement_name':form.agreementName.data,
   1333             'agency':[form.agency.data],
   1334             'projects':[],
   1335             'start_date':form.startDate.data.strftime('%Y-%m-%d'),
   1336             'end_date':form.endDate.data.strftime('%Y-%m-%d'),
   1337            # 'budget':[{
   1338            #     'labor':form.laborBudget.data,
   1339            #     'travel':form.travelBudget.data,
   1340            #     'supplies':form.suppliesBudget.data,
   1341            #     'contact':form.contactBudget.data,
   1342            #     'equipment':form.equipmentBudget.data,
   1343            #     'other':form.otherBudget.data
   1344            #     }] # most recent labor budget accessed via budget.0.labor
   1345             })
   1346         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
   1347         return redirect(url_for('newproject'))
   1348 
   1349     return render_template('admin/agreements/newagreement.html',form=form,ORGNAME=OrganizationName)
   1350 
   1351 ####                                ####
   1352 #######    Project Admin Route   #######
   1353 ####                                ####
   1354 ####### TODO Need to filter out available agrements key=agreement_name:value=_id(agreement) Assign _id to agreement, and write _id(project) to agreement.projects[]
   1355 
   1356 #@app.route("/admin/projects/new/<agreementid>", methods=["GET","POST"])
   1357 @app.route("/admin/projects/new", methods=["GET","POST"])
   1358 @login_required
   1359 def newproject(agreementid=None):
   1360 #   Available Agreements. Move to fn()
   1361     availableAgreements = []
   1362     for agreement in mongo.db.agreements_collection.find():
   1363         availableAgreements.append((agreement['_id'],agreement['agreement_name']))
   1364 #   END Available Agreements
   1365 
   1366     form = NewProjectForm()
   1367     # TODO If statement for optional newproject('argument') if new or none return all choices, else return (agreement_id, agreement_name) for new agreement ID passed <-- What did I mean by this? OH It's a conditional to redirect to create a new agreement... this would require passing the form data to the next route as params?
   1368     form.agreement.choices = availableAgreements
   1369     #form.branch.choices = ['Dillon','Salmon']
   1370     form.branch.choices = [("",'Select Branch'),("Global","Global")]
   1371     for branch in get_available_branches():
   1372         form.branch.choices.append(branch)
   1373     #TODO MAKE BELOW WORK!!! Apply to other branch dependent areas
   1374     #branches = []
   1375     #for branch in mongo.db.branch_collection.find():
   1376     #   branches.append((branch['_id'],branch['location']))
   1377     #form.branch.choices = branches
   1378 
   1379     if form.validate_on_submit():
   1380        #     create deterministic agreement unique _id? Example being genpasswd in new user validate on submit
   1381    ### TODO MAKE THIS A FOR submit IN form IF submit.data == None, 'submit.label':0, else 'submit.label':submit.data  ## probably can use f-strings to accomplish the 'submit.label' part     
   1382         if form.laborBudget.data is None:
   1383             form.laborBudget.data = 0.0
   1384         if form.travelBudget.data is None:
   1385             form.travelBudget.data = 0.0
   1386         if form.suppliesBudget.data is None:
   1387             form.suppliesBudget.data = 0.0
   1388         if form.perdiemBudget.data is None:
   1389             form.perdiemBudget.data = 0.0
   1390         if form.equipmentBudget.data is None:
   1391             form.equipmentBudget.data = 0.0
   1392         if form.indirectBudget.data is None:
   1393             form.indirectBudget.data = 0.0
   1394         if form.contractingBudget.data is None:
   1395             form.contractingBudget.data = 0.0
   1396         if form.lodgingBudget.data is None:
   1397             form.lodgingBudget.data = 0.0
   1398         if form.otherBudget.data is None:
   1399             form.otherBudget.data = 0.0
   1400 
   1401         pjname = "FE " + str(form.fenumber.data) + ": " + form.projectName.data
   1402 
   1403         mongo.db.projects_collection.insert_one({
   1404             'project_name':pjname,
   1405             'agreement':ObjectId(form.agreement.data),
   1406             'branch':form.branch.data,
   1407             #'branch':ObjectId(form.branch.data),
   1408             'budget':[{
   1409                 'labor':form.laborBudget.data,
   1410                 'travel':form.travelBudget.data,
   1411                 'supplies':form.suppliesBudget.data,
   1412                 'perdiem':form.perdiemBudget.data,
   1413                 'equipment':form.equipmentBudget.data,
   1414                 'indirect':form.indirectBudget.data,
   1415                 'contracting':form.contractingBudget.data,
   1416                 'lodging':form.lodgingBudget.data,
   1417                 'other':form.otherBudget.data
   1418                 }], # most recent labor budget accessed via budget.0.labor
   1419             'costs':[{
   1420                 'labor':0,
   1421                 'travel':0,
   1422                 'supplies':0,
   1423                 'perdiem':0,
   1424                 'equipment':0,
   1425                 'indirect':0,
   1426                 'contracting':0,
   1427                 'lodging':0,
   1428                 'other':0
   1429                 }]
   1430             })
   1431         pj_id = mongo.db.projects_collection.find_one({'project_name':pjname})['_id']
   1432         mongo.db.agreements_collection.update_one({ '_id':ObjectId(form.agreement.data) },{ '$push':{ 'projects':pj_id }})
   1433 
   1434         flash("{} part of {} added".format(form.projectName.data, form.agreement.data )) #Will need to sendmail password to form.email.data later
   1435         return redirect(url_for('admin'))
   1436 
   1437     return render_template('admin/agreements/projects/newproject.html',form=form,ORGNAME=OrganizationName)
   1438 
   1439 @app.route("/admin/project/<project_id>",methods=["GET"])
   1440 @login_required
   1441 def project(project_id):
   1442     payperiod_times = []
   1443     #project_id = project_id
   1444     probject_id = ObjectId(project_id)
   1445     project = {'_id': 'defaultproject', 'project_name': 'Template Project', 'agreement': 'YEP General', 'budget': [{'labor':2, 'No Start Date':2, 'travel':2, 'end_date':2, 'supplies':2, 'No End Date':2}],'cost':[{'labor':1, 'No Start Date':1, 'travel':1, 'end_date':1, 'supplies':1, 'No End Date':1}]}
   1446     try:
   1447         project = mongo.db.projects_collection.find_one({'_id':probject_id})
   1448     except:
   1449         flash("Issue assigning Project data for project id {}".format(probject_id))
   1450     else:
   1451         #for project in agreement['projects']:
   1452             try:
   1453                 tme = mongo.db.time_collection.find({'project':probject_id})
   1454                 if tme is None:
   1455                     flash('No Current Hours submitted for project {}'.format(probject_id))
   1456             except:
   1457                 flash("Issue assigning time data for project id {}".format(probject_id))
   1458             else:
   1459                 for time in tme:
   1460                     payperiod_times.append(time)
   1461     finally:
   1462         return render_template('admin/agreements/projects/index.html',project=project,payperiod_times=payperiod_times,ORGNAME=OrganizationName)
   1463 
   1464 @app.route("/admin/rename/project/<project_id>",methods=["GET","POST"])
   1465 @login_required
   1466 def rename_project(project_id):
   1467     form = RenameProjectForm()
   1468     if form.validate_on_submit():
   1469         pjname = "FE " + str(form.fenumber.data) + ": " + form.newName.data
   1470         try:
   1471             project = mongo.db.projects_collection.find_one({'_id':project_id})
   1472         except:
   1473             flash("Issue finding Project with id {}".format(project_id))
   1474         else:
   1475             try:
   1476                 mongo.db.projects_collection.update_one({'_id':ObjectId(project_id)},{'$set':{'project_name':pjname}})
   1477             except:
   1478                 flash("Issue setting {} as project name for {}".format(pjname,project_id))
   1479         finally:
   1480             return redirect(url_for('project', project_id=project_id))
   1481     return render_template('admin/agreements/projects/update/rename.html',form=form,ORGNAME=OrganizationName)
   1482 
   1483 @app.route("/admin/update/branch/<collection>/<document_id>",methods=["GET","POST"])
   1484 @login_required
   1485 def change_branch(collection,document_id):
   1486     form = ChangeBranchForm()
   1487 
   1488     form.branch.choices = [("",'Select Branch'),("Global","Global")]
   1489     for branch in get_available_branches():
   1490         form.branch.choices.append(branch)
   1491 
   1492     if form.validate_on_submit():
   1493         match collection:
   1494             case "project":
   1495                 mongo.db.projects_collection.update_one({"_id":ObjectId(document_id)},{'$set':{'branch':ObjectId(form.branch.data)}})
   1496                 flash("Changed Branch for Project {} to {}".format(document_id,form.branch.data))
   1497                 return redirect(url_for('project',project_id=document_id))
   1498             case "user":
   1499                 mongo.db.user_collection.update_one({"_id":ObjectId(document_id)},{'$set':{'branch':ObjectId(form.branch.data)}})
   1500                 flash("Changed Branch for User {} to {}".format(document_id,form.branch.data))
   1501                 return redirect(url_for('user',user_id=document_id))
   1502 
   1503     return render_template('admin/update/branch.html',form=form,ORGNAME=OrganizationName)
   1504 
   1505 @app.route("/admin/move/project/<project_id>",methods=["GET","POST"])
   1506 @login_required
   1507 def move_project(project_id):
   1508     #TODO replace w/ get agreement(s) fn
   1509     availableAgreements = []
   1510     for agreement in mongo.db.agreements_collection.find():
   1511         availableAgreements.append((agreement['_id'],agreement['agreement_name']))
   1512     # END
   1513     form = MoveProjectForm()
   1514     form.newAgreement.choices = availableAgreements # assign the fn as above
   1515     if form.validate_on_submit():
   1516         try:
   1517             #TODO replace w/ get project(s) fn
   1518             project = mongo.db.projects_collection.find_one({'_id':ObjectId(project_id)})
   1519             #END
   1520         except:
   1521             flash('Issue finding project with id {}'.format(project_id))
   1522         else:
   1523             try:
   1524                 mongo.db.agreements_collection.find_one({'_id':ObjectId(project['agreement'])})
   1525             except:
   1526                 flash('Issue finding agreement {} for project {}'.format(project['agreement'],project_id))
   1527             else:
   1528                 # NOTE To refactor the below into a moveProject(s) fn I can pass a list of projects as a variable for the $pull and $push as {'$pull':{'projects'{'$in':varOfProjects}}} etcv This will be clean when I need to move all the projects from an agreement that's in queue for deletement
   1529                 try:
   1530                     mongo.db.agreements_collection.update_one({'_id':ObjectId(project['agreement'])},{'$pull':{'projects':ObjectId(project_id)}})
   1531                 except:
   1532                     flash('Issue removing project {} from agreement {}'.format(project_id,project['agreement']))
   1533                 else:
   1534                     try:
   1535                         mongo.db.agreements_collection.update_one({'_id':ObjectId(form.newAgreement.data)},{'$push':{'projects':ObjectId(project_id)}})
   1536                     except:
   1537                         flash('Issue adding project {} to agreement {}'.format(project_id,form.newAgreement.data))
   1538                     else:
   1539                         mongo.db.projects_collection.update_one({'_id':ObjectId(project_id)},{'$set':{'agreement':form.newAgreement.data}})
   1540         finally:                
   1541             return redirect(url_for('project', project_id=project_id))
   1542     return render_template('admin/agreements/projects/update/move.html',form=form,ORGNAME=OrganizationName)
   1543 
   1544 @app.route("/admin/remove/project/<project_id>",methods=["GET","POST"])
   1545 @login_required
   1546 def remove_project(project_id):
   1547     form = ConfirmRemove()
   1548     if form.validate_on_submit():
   1549         try:
   1550             #TODO replace w/ get project(s) fn
   1551             project = mongo.db.projects_collection.find_one({'_id':ObjectId(project_id)})
   1552             #END
   1553         except:
   1554             flash('Issue finding project with id {}'.format(project_id))
   1555         else:
   1556             try:
   1557                 mongo.db.agreements_collection.find_one({'_id':ObjectId(project['agreement'])})
   1558             except:
   1559                 flash('Issue finding agreement for project {}'.format(project_id))
   1560             else:
   1561                 #NOTE abstract to removeProject(s) fn for mass deletion. Ex {'$pull':{'projects':{'$in':passedListOfDeletableProjects}}}
   1562                 if mongo.db.agreements_collection.update_one({'_id':ObjectId(project['agreement'])},{'$pull':{'projects':ObjectId(project_id)}}):
   1563                     mongo.db.projects_collection.delete_one({'_id':ObjectId(project_id)})
   1564 
   1565             return redirect(url_for('agreement',agreement_id=project['agreement']))
   1566 
   1567     return render_template('admin/confirm_remove.html',form=form,ORGNAME=OrganizationName)
   1568 ####                              ####
   1569 #######   Knowlegebase Route   #######
   1570 ####                              ####
   1571 @app.route("/docs")
   1572 @login_required
   1573 def knowlegebase():
   1574     return render_template('knowlegebase/index.html',ORGNAME=OrganizationName)
   1575 
   1576 
   1577 # Report Routes
   1578 # Agreement Routes
   1579 @app.route('/admin/reports/agreement')
   1580 @login_required
   1581 def agreement_report():
   1582     return render_template('admin/reports/agreement_report.html', ORGNAME=OrganizationName)
   1583 
   1584 @app.route('/admin/reports/agreements')
   1585 @login_required
   1586 def project_report():
   1587     tspp = mongo.db.time_collection.aggregate( [
   1588         {
   1589             "$group": {
   1590                 "_id":{"projectId": "$project"},
   1591                 "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1592                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   1593                 "perdiemCount": { "$sum" :{'$cond':["$perdiem",1,0] } }
   1594                 }
   1595         },
   1596         {
   1597             "$addFields": {
   1598                 "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1599             }
   1600         },
   1601         {
   1602             "$sort": { "_id": -1 }
   1603         }
   1604     ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
   1605     ptl = mongo.db.time_collection.aggregate( [
   1606         {
   1607             "$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
   1608                     'from':'projects_collection',
   1609                     'localField':'project',
   1610                     'foreignField':'_id',
   1611                     'as':'project_data'
   1612                 }
   1613         },
   1614         {
   1615             "$addFields": {
   1616                 "totalTime": { "$cond":{ "$if":"$clock_out","$then":{"$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }},"$else":{'Clocked in'}},
   1617                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1618                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   1619                 "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
   1620                 }
   1621         },
   1622         {
   1623             "$addFields": {
   1624                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1625             }
   1626         },
   1627         {
   1628             "$sort": {'date': -1}
   1629         }
   1630         ] )
   1631 
   1632     by_project = tspp
   1633 #    by_project = {}
   1634 #    for project in tspp:
   1635 #        by_project[project['_id'][0]['project_name']]=project['_id'][0]
   1636 #        by_project[project['_id'][0]['project_name']]['totalHoursWorked']=project['totalHoursWorked']
   1637 #        by_project[project['_id'][0]['project_name']]['lunchCount']=project['lunchCount']
   1638 #        by_project[project['_id'][0]['project_name']]['perdiemCount']=project['perdiemCount']
   1639 
   1640 #    for time in ptl:
   1641 #        for user in by_user:
   1642 #            if time['modified_by'][0] in user:
   1643 #                if by_user[user].get('times'):
   1644 #                    by_user[user]['times'].append(time)
   1645 #                else:
   1646 #                    by_user[user].update({'times':[time]})
   1647 #        for project in by_project:
   1648 #            if time['project_data'][0]['project_name'] in project:
   1649 #                if by_project[project].get('times'):
   1650 #                    by_project[project]['times'].append(time)
   1651 #                else:
   1652 #                    by_project[project].update({'times':[time]})
   1653 
   1654     return render_template('admin/reports/project.html',by_project=by_project,tspp=tspp,projectlookup=ptl,ORGNAME=OrganizationName)
   1655 
   1656 @app.route('/admin/rename/agreement/<agreement_id>',methods=["GET","POST"])
   1657 @login_required
   1658 def rename_agreement(agreement_id):
   1659     form = RenameAgreementForm()
   1660     if form.validate_on_submit():
   1661         try:
   1662             agreement = mongo.db.agreements_collection.find_one({'_id':agreement_id})
   1663         except:
   1664             flash("Issue finding Agreement with id {}".format(agreement_id))
   1665             return redirect(url_for('rename_agreement',agreement_id=agreement_id))
   1666         else:
   1667             try:
   1668                 mongo.db.agreements_collection.update_one({'_id':ObjectId(agreement_id)},{'$set':{'agreement_name':form.newName.data}})
   1669             except:
   1670                 flash("Issue setting {} as agreement name for {}".format(form.newName.data,agreement_id))
   1671                 return redirect(url_for('rename_agreement',agreement_id=agreement_id))
   1672             else:
   1673                 return redirect(url_for('agreement', agreement_id=agreement_id))
   1674     return render_template('admin/agreements/update/rename.html',form=form,ORGNAME=OrganizationName)
   1675 
   1676 @app.route("/admin/remove/agreement/<agreement_id>",methods=["GET","POST"])
   1677 @login_required
   1678 def remove_agreement(agreement_id):
   1679     #TODO
   1680     form = ConfirmRemove()
   1681     if form.validate_on_submit():
   1682         try:
   1683             #TODO replace with get project(s) fn
   1684             agreement = mongo.db.agreements_collection.find_one({'_id':ObjectId(agreement_id)})
   1685             #END
   1686         except:
   1687             flash('Issue finding agreement id {}'.format(agreement_id))
   1688         else:
   1689             #for each project either remove or move
   1690             flash('WARNING: This action removes all projects currently associated with this agreement, AND removes all time entries tied to the projects.')
   1691 #            try:
   1692  #               for project in agreement['projects']
   1693 
   1694     return render_template('admin/agreements/projects/update/move.html',form=form,ORGNAME=OrganizationName)#TODO FIX
   1695 
   1696 @app.route('/admin/change/agreement/dates',methods=["GET","POST"])
   1697 @login_required
   1698 def change_agreement_dates():
   1699     #TODO
   1700     return render_template('admin/agreements/projects/update/move.html',form=form,ORGNAME=OrganizationName)#TODO FIX
   1701 
   1702 # Payperiod Routes
   1703 #TODO marked 06.29,23
   1704 ###### TESTING START #######
   1705 @app.route('/dev/time-data-total-report')
   1706 @login_required
   1707 def time_data_total_report():
   1708     hours = mongo.db.time_collection.aggregate( [
   1709 #        {
   1710 #            "$project":{"modified_by":1}
   1711 #        },
   1712 #        {
   1713 #            "$unwind":"$modified_by"
   1714 #        },
   1715 ## ADD MATCH TO LIMIT BY DATE FOR REPORTING DATE SELECTION ## 
   1716         {
   1717             "$group": {
   1718                 "_id": {
   1719                     "$first":"$modified_by",
   1720                 },
   1721                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1722                 "lunchCount":{ "$sum":{'$cond':["$lunch",1,0]  } },
   1723                 "perdiemCount":{ "$sum":{'$cond':["$perdiem",1,0]  } }
   1724             }
   1725         },
   1726         {
   1727             "$addFields": {
   1728                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1729             }
   1730         },
   1731         {
   1732             "$sort": { "_id": 1 }
   1733         }
   1734     ] )# Total hours worked
   1735     tspp = mongo.db.time_collection.aggregate( [
   1736         {
   1737             "$group": {
   1738                 "_id":{"projectId": "$project"},
   1739                 "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1740                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   1741                 "perdiemCount": { "$sum" :{'$cond':["$perdiem",1,0] } }
   1742                 }
   1743         },
   1744         {
   1745             "$addFields": {
   1746                 "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1747             }
   1748         },
   1749         {
   1750             "$sort": { "_id": -1 }
   1751         }
   1752     ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
   1753     
   1754     everyhours_by_user = mongo.db.time_collection.aggregate([ {'$sort':{'modified_by.0':1,'date':1}} ])
   1755     
   1756     everyhours_by_project = mongo.db.time_collection.aggregate([ {'$sort':{'project':1,'modified_by.0':1}} ])
   1757 
   1758     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)
   1759 ####### TESTING END #######
   1760 ###### TESTING Period selection START #######
   1761 @app.route('/dev/select-date-range', methods=["GET","POST"])
   1762 @login_required
   1763 def select_date_range():
   1764     form = dateRange()
   1765 
   1766     if form.validate_on_submit():
   1767        
   1768         try:
   1769             begin = form.lowerBound.data
   1770             end = form.upperBound.data
   1771             #begin = datetime.datetime.strptime(form.lowerBound.data,'%F-%m-%d')
   1772             #end = datetime.datetime.strptime(form.upperBound.data, '%Y-%m-%d')
   1773             #begin = datetime.datetime.combine(form.lowerBound.data,datetime.time())
   1774             #end = datetime.datetime.combine(form.upperBound.data,datetime.time())
   1775         except:
   1776             flash("Error Reporting for time entries between {} and {}.".format(begin,end))
   1777         else:
   1778             flash("Report for time entries between {} and {}.".format(form.lowerBound.data, form.upperBound.data ))
   1779             return redirect(url_for('time_bound_report',startday=begin,endday=end))
   1780 
   1781     return render_template('admin/reports/rangeSel.html',form=form,ORGNAME=OrganizationName)
   1782 
   1783 @app.route('/dev/report-range/<startday>/<endday>')
   1784 @login_required
   1785 def time_bound_report(startday,endday):
   1786     begin = datetime.datetime.strptime(startday,'%Y-%m-%d')
   1787     end = datetime.datetime.strptime(endday, '%Y-%m-%d')
   1788 
   1789     usertimes = mongo.db.time_collection.aggregate([
   1790         {
   1791             "$match": {
   1792                 "$and":[{"date":{"$gte":begin}},{"date":{"$lt":end}}]
   1793             }
   1794         },
   1795         {
   1796             "$lookup":{
   1797                     'from':'user_collection',
   1798                     'localField':'modified_by.0',
   1799                     'foreignField':"username",
   1800                     'as':'userinfo'
   1801             }
   1802         },
   1803         {
   1804             "$sort":{"userinfo.username":1}
   1805         }
   1806     ])
   1807 
   1808     allhours = mongo.db.time_collection.aggregate( [
   1809         {
   1810             "$match": {
   1811                 "$and":[{"date":{"$gte":begin}},{"date":{"$lt":end}}]
   1812                 }
   1813         },
   1814         {
   1815             "$group": {
   1816                 "_id":{"_id":'$_id',
   1817                 "project":"$project"},
   1818                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1819                 "lunchCount":{ "$sum":{'$cond':["$lunch",1,0]  } },
   1820                 "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0]  } }
   1821             }
   1822         },
   1823         {
   1824             "$addFields": {
   1825                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1826             }
   1827         },
   1828         {
   1829             "$sort": { "_id": 1 }
   1830         }
   1831     ] )# Total hours worked
   1832     
   1833 
   1834     hours = mongo.db.time_collection.aggregate( [
   1835         {
   1836             "$match": {
   1837                 "$and":[{"date":{"$gte":begin}},{"date":{"$lt":end}}]
   1838                 }
   1839         },
   1840         {
   1841             "$group": {
   1842                 "_id": {
   1843                     "$first":"$modified_by",
   1844                 },
   1845                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1846                 "lunchCount":{ "$sum":{'$cond':["$lunch",1,0]  } },
   1847                 "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0]  } }
   1848             }
   1849         },
   1850         {
   1851             "$addFields": {
   1852                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1853             }
   1854         },
   1855         {
   1856             "$sort": { "_id": 1 }
   1857         }
   1858     ] )# Total hours worked
   1859     tspp = mongo.db.time_collection.aggregate( [
   1860         {
   1861             "$match": {
   1862                 "$and":[{"date":{"$gte":begin}},{"date":{"$lt":end}}]
   1863                 }
   1864         },
   1865         {
   1866             "$lookup":{
   1867                     'from':'projects_collection',
   1868                     'localField':'project',
   1869                     'foreignField':'_id',
   1870                     'as':'project_data'
   1871                 }
   1872         },
   1873         {
   1874             "$group": {
   1875                 "_id":"$project_data",
   1876                 "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1877                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   1878                 "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
   1879                 }
   1880         },
   1881         {
   1882             "$addFields": {
   1883                 "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1884             }
   1885         },
   1886         {
   1887             "$sort": { "_id": -1 }
   1888         }
   1889     ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
   1890     
   1891     ptl = mongo.db.time_collection.aggregate( [
   1892         {
   1893             "$match": {
   1894                 "$and":[{"date":{"$gte":begin}},{"date":{"$lt":end}}]
   1895             }
   1896         },
   1897         {
   1898             "$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
   1899                     'from':'projects_collection',
   1900                     'localField':'project',
   1901                     'foreignField':'_id',
   1902                     'as':'project_data'
   1903                 }
   1904         },
   1905         {
   1906             "$addFields": {
   1907                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1908                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   1909                 "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
   1910                 }
   1911         },
   1912         {
   1913             "$addFields": {
   1914                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1915             }
   1916         },
   1917         {
   1918             "$sort": {'date': -1}
   1919         }
   1920         ] )
   1921 
   1922     by_project = {}
   1923     for project in tspp:
   1924         by_project[project['_id'][0]['project_name']]=project['_id'][0]
   1925         by_project[project['_id'][0]['project_name']]['totalHoursWorked']=project['totalHoursWorked']
   1926         by_project[project['_id'][0]['project_name']]['lunchCount']=project['lunchCount']
   1927         by_project[project['_id'][0]['project_name']]['perdiemCount']=project['perdiemCount']
   1928 
   1929     by_user ={}
   1930     for user in hours:
   1931         by_user[user['_id']]=user
   1932 
   1933     for time in ptl:
   1934         for user in by_user:
   1935             if time['modified_by'][0] in user:
   1936                 if by_user[user].get('times'):
   1937                     by_user[user]['times'].append(time)
   1938                 else:
   1939                     by_user[user].update({'times':[time]})
   1940         for project in by_project:
   1941             if time['project_data'][0]['project_name'] in project:
   1942                 if by_project[project].get('times'):
   1943                     by_project[project]['times'].append(time)
   1944                 else:
   1945                     by_project[project].update({'times':[time]})
   1946 
   1947 #    for time in ptl:
   1948 #        if time['modified_by'][0] not in by_user:
   1949 #            by_user[time['modified_by'][0]]=[time]
   1950 #        else:
   1951 #            by_user[time['modified_by'][0]].append(time)
   1952 #        if time['project_data'][0]['project_name'] not in by_project:
   1953 #            by_project[time['project_data'][0]['project_name']]=[time]
   1954 #        else:
   1955 #            by_project[time['project_data'][0]['project_name']].append(time)
   1956 
   1957     #return json_util.dumps(by_user)
   1958     #return json_util.dumps(by_project)
   1959     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)
   1960 ## Refactoring fn(s) START ##
   1961 def get_all_user_times():
   1962     usertimes = mongo.db.time_collection.aggregate([
   1963         {
   1964             "$lookup":{
   1965                     'from':'user_collection',
   1966                     'localField':'modified_by.0',
   1967                     'foreignField':"username",
   1968                     'as':'userinfo'
   1969             }
   1970         },
   1971         {
   1972             "$sort":{"userinfo.username":1}
   1973         }
   1974     ])
   1975 
   1976     allhours = mongo.db.time_collection.aggregate( [
   1977         {
   1978             "$group": {
   1979                 "_id":{"_id":'$_id',
   1980                 "project":"$project"},
   1981                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   1982                 "lunchCount":{ "$sum":{'$cond':["$lunch",1,0]  } },
   1983                 "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0]  } }
   1984             }
   1985         },
   1986         {
   1987             "$addFields": {
   1988                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   1989             }
   1990         },
   1991         {
   1992             "$sort": { "_id": 1 }
   1993         }
   1994     ] )# Total hours worked
   1995     
   1996 
   1997     hours = mongo.db.time_collection.aggregate( [
   1998         {
   1999             "$group": {
   2000                 "_id": {
   2001                     "$first":"$modified_by",
   2002                 },
   2003                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   2004                 "lunchCount":{ "$sum":{'$cond':["$lunch",1,0]  } },
   2005                 "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0]  } }
   2006             }
   2007         },
   2008         {
   2009             "$addFields": {
   2010                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   2011             }
   2012         },
   2013         {
   2014             "$sort": { "_id": 1 }
   2015         }
   2016     ] )# Total hours worked
   2017     tspp = mongo.db.time_collection.aggregate( [
   2018         {
   2019             "$lookup":{
   2020                     'from':'projects_collection',
   2021                     'localField':'project',
   2022                     'foreignField':'_id',
   2023                     'as':'project_data'
   2024                 }
   2025         },
   2026         {
   2027             "$group": {
   2028                 "_id":"$project_data",
   2029                 "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   2030                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   2031                 "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
   2032                 }
   2033         },
   2034         {
   2035             "$addFields": {
   2036                 "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   2037             }
   2038         },
   2039         {
   2040             "$sort": { "_id": -1 }
   2041         }
   2042     ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
   2043     
   2044     ptl = mongo.db.time_collection.aggregate( [
   2045         {
   2046             "$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
   2047                     'from':'projects_collection',
   2048                     'localField':'project',
   2049                     'foreignField':'_id',
   2050                     'as':'project_data'
   2051                 }
   2052         },
   2053         {
   2054             "$addFields": {
   2055                 "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
   2056                 "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
   2057                 "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
   2058                 }
   2059         },
   2060         {
   2061             "$addFields": {
   2062                 "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
   2063             }
   2064         },
   2065         {
   2066             "$sort": {'date': -1}
   2067         }
   2068         ] )
   2069 
   2070     by_project = {}
   2071     for project in tspp:
   2072         by_project[project['_id'][0]['project_name']]=project['_id'][0]
   2073         by_project[project['_id'][0]['project_name']]['totalHoursWorked']=project['totalHoursWorked']
   2074         by_project[project['_id'][0]['project_name']]['lunchCount']=project['lunchCount']
   2075         by_project[project['_id'][0]['project_name']]['perdiemCount']=project['perdiemCount']
   2076 
   2077     by_user ={}
   2078     for user in hours:
   2079         by_user[user['_id']]=user
   2080 
   2081     for time in ptl:
   2082         for user in by_user:
   2083             if time['modified_by'][0] in user:
   2084                 if by_user[user].get('times'):
   2085                     by_user[user]['times'].append(time)
   2086                 else:
   2087                     by_user[user].update({'times':[time]})
   2088         for project in by_project:
   2089             if time['project_data'][0]['project_name'] in project:
   2090                 if by_project[project].get('times'):
   2091                     by_project[project]['times'].append(time)
   2092                 else:
   2093                     by_project[project].update({'times':[time]})
   2094     return by_user
   2095 
   2096 ## Refartoring fn(s) END ##
   2097 
   2098 ####### TESTING END #######
   2099 
   2100 @app.route('/admin/reports/pay-period', methods=['GET'])
   2101 @login_required
   2102 def pay_period_report():
   2103     pay = mongo.db.time_collection.find({})
   2104     dbactiveusers = mongo.db.user_collection.find({'is_active':True})
   2105     users=[]
   2106     nouser=[]
   2107     times_by_user={}
   2108     for user in dbactiveusers:
   2109         times_by_user[user['username']]=[datetime.timedelta(seconds=0)]
   2110         users.append(user)
   2111     hours = {}
   2112     deltas=[]
   2113     for time in pay:
   2114         if time['modified_by'][0] not in times_by_user:
   2115             times_by_user[time['modified_by'][0]]=[datetime.timedelta(seconds=0)]
   2116             try:
   2117                 user_lookup = mongo.db.user_collection.find_one({'username':time['modified_by'][0]})
   2118             except:
   2119                 nouser.append(time['_id'])
   2120             else:
   2121                 users.append(user_lookup)
   2122         if 'clock_out' not in time:
   2123             time['clock_out']=[datetime.datetime.now()]
   2124         t = time['clock_out'][0] - time['clock_in'][0]
   2125         hours['total_time'] = t
   2126         times_by_user[time['modified_by'][0]].append(t)
   2127         deltas.append(time)
   2128              
   2129     for user in users:
   2130         total_hours = sum(times_by_user[user['username']],datetime.timedelta())
   2131         statement_hours = (total_hours.seconds//3600,(total_hours.seconds//60)%60)
   2132         user['total_hours']=statement_hours
   2133     return render_template('admin/reports/pay_period_report.html', nouser=nouser, users=users, pay=pay, ORGNAME=OrganizationName)
   2134 
   2135 # @app.route("/dev/fleetdata")
   2136 # @login_required
   2137 # def fleetdatalist():
   2138 #     allfleetdata = mongo.db.fleet_collection.find()
   2139 #     return render_template('dev/fleetdata.html', allfleetdata=allfleetdata)
   2140 def calculateHours(username=current_user):
   2141     if not mongo.db.time_collection.find({"modified_by.0":username}):
   2142         return 0
   2143     else:
   2144         times = mongo.db.time_collection.find({"modified_by.0":username})
   2145         deltas=[]
   2146 
   2147         for time in times:
   2148             if 'clock_out' not in time:
   2149                 time['clock_out']=[datetime.datetime.now()]
   2150             t = time['clock_out'][0] - time['clock_in'][0]
   2151             deltas.append(t)
   2152 
   2153         total_calculated_hours = sum(deltas,datetime.timedelta())
   2154 
   2155         return total_calculated_hours
   2156 
   2157 @app.route('/admin/reports/employees')
   2158 @login_required
   2159 def report_employees():
   2160     by_user = get_all_user_times()
   2161     users = mongo.db.user_collection.find()
   2162     hours = mongo.db.time_collection.find()
   2163     for user in users:
   2164         user['total_hours']=calculateHours(user['username'])
   2165     return render_template ('admin/employee_report/index.html', by_user=by_user, hours=hours, users=users, ORGNAME=OrganizationName)
   2166 
   2167 @app.route('/admin/reports/employee/<username>')
   2168 @login_required
   2169 def employee_report(username):
   2170     user = mongo.db.user_collection.find_one({"username": username})
   2171     hours = mongo.db.time_collection.aggregate( [
   2172         {
   2173             "$match": {
   2174                 "modified_by.0":username
   2175                 }
   2176         },
   2177         {
   2178             "$sort": { "date": -1 }
   2179         }
   2180     ] )# Total hours worked
   2181     #hours = mongo.db.time_collection.find({'modified_by.0':user['username']})
   2182    # hours = mongo.db.time_collection.aggregate(
   2183    #     {
   2184    #         "$match": {'modified_by.0':user['username'] }
   2185    #     },
   2186    #     {
   2187    #         "$lookup": { "from":"projects_collection", "localField":"project", "foreignField":"project_name", "as":"project_data"}
   2188    #     })
   2189     thw = mongo.db.time_collection.aggregate( [
   2190         {
   2191             "$match": {
   2192                 "modified_by.0":username
   2193                 }
   2194         },
   2195         {
   2196             "$group": {
   2197                 "_id":"$modified_by.0",
   2198                 "totalHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }
   2199                 }
   2200         },
   2201         {
   2202             "$sort": { "totalHoursWorked": -1 }
   2203         }
   2204     ] )# Total hours worked
   2205     tspp = mongo.db.time_collection.aggregate( [
   2206         {
   2207             "$match": {
   2208                 "modified_by.0":username
   2209                 }
   2210         },
   2211         {
   2212             "$group": {
   2213                 "_id":{"projectId": "$project"},
   2214                 "totalHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }
   2215                 }
   2216         },
   2217         {
   2218             "$sort": { "totalHoursWorked": -1 }
   2219         }
   2220     ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
   2221     
   2222     return render_template ('admin/reports/employee_report.html', hours=hours, user=user, thw=thw, tspp=tspp, ORGNAME=OrganizationName)
   2223 
   2224 # Vehicle Routes
   2225 @app.route('/admin/reports/vehicles')
   2226 @login_required
   2227 def vehicle_report():
   2228     return render_template ('admin/reports/vehicle_report.html', ORGNAME=OrganizationName)
   2229 
   2230 @app.route('/admin/reports/vehicles/<vehicle>')
   2231 @login_required
   2232 def vehicle_indepth():
   2233     return render_template ('admin/reports/vehicle_report.html', ORGNAME=OrganizationName)
   2234 # Report Routes End
   2235 
   2236 #====================================#
   2237 #############           ##############
   2238 ####     DEVELOPMENT ROUTES       ####
   2239 #############           ##############
   2240 #====================================#
   2241 
   2242 # DEVELOPMENT ROUTES, remove/modify permissions before production
   2243 
   2244 ####                              ####
   2245 #######    Fleet Data Route    #######
   2246 ####                              ####
   2247 @app.route("/dev/fleetdata")
   2248 @login_required
   2249 def fleetdatalist():
   2250     allfleetdata = mongo.db.fleet_collection.find()
   2251     return render_template('dev/fleetdata.html', allfleetdata=allfleetdata)
   2252 
   2253 ####                             ####
   2254 #######    User Data Route    #######
   2255 ####                             ####
   2256 @app.route("/dev/usrs")
   2257 @login_required
   2258 def userslist():
   2259     allusers = mongo.db.user_collection.find()
   2260     return render_template('dev/usrs.html', allusers=allusers)
   2261 
   2262 ####                             ####
   2263 #######    Time Data Route    #######
   2264 ####                             ####
   2265 @app.route("/dev/timedata", methods=['GET'])
   2266 @login_required
   2267 def timedata():
   2268     alltimedata = mongo.db.time_collection.find()
   2269     return render_template('dev/timedata.html', alltimedata=alltimedata)
   2270 
   2271 ####                                  ####
   2272 #######    Agreement Data Route    #######
   2273 ####                                  ####
   2274 @app.route("/dev/agreementdata", methods=['GET'])
   2275 @login_required
   2276 def agreementdata():
   2277     allagreementdata = mongo.db.agreements_collection.find()
   2278     return render_template('dev/agreementdata.html', allagreementdata=allagreementdata)
   2279 
   2280 ####                                ####
   2281 #######    Project Data Route    #######
   2282 ####                                ####
   2283 @app.route("/dev/projectdata", methods=['GET'])
   2284 @login_required
   2285 def projectdata():
   2286     allprojectsdata = mongo.db.projects_collection.find()
   2287     return render_template('dev/projectdata.html', allprojectsdata=allprojectsdata)