stc

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

commit 41c9c44abf69a64b598de5d639021b2c0e595ce8
parent 11aa0cdab455ddbe9a237069d07871e376ba1834
Author: Youth Employment Program Production <youthemployment22@gmail.com>
Date:   Thu, 26 Oct 2023 23:56:05 -0600

Add rename, move, and remove project, add general remove confirmation page

Diffstat:
Mapp/TODO.md | 20+++++++++++++++++---
Mapp/forms.py | 11+++++++++++
Mapp/routes.py | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mapp/templates/admin/agreements/projects/index.html | 5+++--
Aapp/templates/admin/agreements/projects/update/move.html | 27+++++++++++++++++++++++++++
Aapp/templates/admin/agreements/projects/update/rename.html | 27+++++++++++++++++++++++++++
Mapp/templates/admin/agreements/widget.html | 4++--
Aapp/templates/admin/confirm_remove.html | 26++++++++++++++++++++++++++
8 files changed, 221 insertions(+), 42 deletions(-)

diff --git a/app/TODO.md b/app/TODO.md @@ -1,14 +1,28 @@ +# OVERVIEW +## MANDATORY +- [ ] Agreement and Project Management Refining +- [ ] Clean up interface +- [ ] Functional Fleet management and checkin/checkout +- [ ] Navigation tweaks + +## OPTIONAL +- [ ] Active Users should be refined to Active Projects for scalability, selecting a clocked in project shows crews clocked into said project. +- [ ] Progressive Web App Implementation + +# NITTYY GRITTY ## MANDATORY - [ ] add remove agreement WARNING CONFIRM form - [ ] must move or remove child projects - [ ] remove times if project is deleted? Move to YEP General to continue to pay employees? -- [ ] add remove project +- [ ] add change agreement name +- [>] add remove project WARNING CONFIRM form - [ ] must offer to change all time entries under project to another project - - [ ] ''' + - [X] ''' db.agreements_collection.updateOne({agreement_name:project_name},{$pull:{projects:{$in:[project_id]}}}) ''' +- [X] add change project name - [ ] add move project - [ ] ''' db.agreements_collection.updateOne({agreement_name:project_current_agreement_name},{$pull:{projects:{$in:[project_id]}}}) @@ -22,7 +36,7 @@ - [ ] fleet entries must function - [ ] time entries must enforce positive time values - [ ] css sidebar for admin and accessable toolbar -- [ ] project add must check and enforce unique `project_name` +- [ ] newproject add must check and enforce unique `project_name` ## OPTIONAL diff --git a/app/forms.py b/app/forms.py @@ -53,11 +53,22 @@ class ChangePasswordForm(FlaskForm): class NewRoleForm(FlaskForm): rolename = StringField('Role Name', validators=[DataRequired()]) +class ConfirmRemove(FlaskForm): + confirm = SubmitField('YES REMOVE') + class NewProjectForm(BudgetForm): projectName = StringField('Project Name', validators=[DataRequired()]) agreement = SelectField('Part of Agreement', validators=[DataRequired()]) createNewProject = SubmitField('Create New Project') +class MoveProjectForm(FlaskForm): + newAgreement = SelectField('Parent Agreement', validators=[DataRequired()]) + moveProject = SubmitField('Update Project') + +class RenameProjectForm(FlaskForm): + newName = StringField('Project Name', validators=[DataRequired()]) + renameProject = SubmitField('Update Project') + class NewProjectNote(FlaskForm): projectNote = StringField('Project Note', validators=[DataRequired()]) diff --git a/app/routes.py b/app/routes.py @@ -4,7 +4,7 @@ from app import app from flask_pymongo import PyMongo from flask_login import LoginManager from flask import render_template, url_for, request, flash, redirect -from app.forms import LoginForm, PunchclockinWidget, PunchclockoutWidget, FleetCheckoutForm, FleetCheckinForm, NewUserForm, AdmnPermissionsForm, DashPermissionsForm, NewHoursForm, NewAgreementForm, NewProjectForm, CrewClockinWidget, NewUserHourForm, ChangePasswordForm, ChangeUserForm, upDate, updateTime, updateProject, newNote, dateRange +from app.forms import LoginForm, PunchclockinWidget, PunchclockoutWidget, FleetCheckoutForm, FleetCheckinForm, NewUserForm, AdmnPermissionsForm, DashPermissionsForm, NewHoursForm, ConfirmRemove, NewAgreementForm, NewProjectForm, MoveProjectForm, RenameProjectForm, CrewClockinWidget, NewUserHourForm, ChangePasswordForm, ChangeUserForm, upDate, updateTime, updateProject, newNote, dateRange from flask import request from werkzeug.urls import url_parse from werkzeug.security import generate_password_hash, check_password_hash @@ -1042,31 +1042,6 @@ def newagreement(): #### #### ####### TODO Need to filter out available agrements key=agreement_name:value=_id(agreement) Assign _id to agreement, and write _id(project) to agreement.projects[] -@app.route("/admin/project/<project_id>",methods=["GET"]) -@login_required -def project(project_id): - payperiod_times = [] - project_id = project_id - probject_id = ObjectId(project_id) - 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}]} - try: - project = mongo.db.projects_collection.find_one({'_id':probject_id}) - except: - flash("Issue assigning Project data for project id {}".format(probject_id)) - else: - #for project in agreement['projects']: - try: - tme = mongo.db.time_collection.find({'project':probject_id}) - if tme is None: - flash('No Current Hours submitted for project {}'.format(probject_id)) - except: - flash("Issue assigning time data for project id {}".format(probject_id)) - else: - for time in tme: - payperiod_times.append(time) - finally: - return render_template('admin/agreements/projects/index.html',project=project,payperiod_times=payperiod_times,ORGNAME=OrganizationName) - @app.route("/admin/projects/new", methods=["GET","POST"]) @login_required def newproject(): @@ -1077,16 +1052,8 @@ def newproject(): # END Available Agreements form = NewProjectForm() + # 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 form.agreement.choices = availableAgreements - # form.laborBudget.data = 2 - # form.travelBudget.data = 0 - # form.suppliesBudget.data = 0 - # form.perdiemBudget.data = 0 - # form.equipmentBudget.data = 0 - # form.indirectBudget.data = 0 - # form.contractingBudget.data = 0 - # form.lodgingBudget.data = 0 - # form.otherBudget.data = 0 if form.validate_on_submit(): # create deterministic agreement unique _id? Example being genpasswd in new user validate on submit @@ -1144,6 +1111,112 @@ def newproject(): return render_template('admin/agreements/projects/newproject.html',form=form,ORGNAME=OrganizationName) +@app.route("/admin/project/<project_id>",methods=["GET"]) +@login_required +def project(project_id): + payperiod_times = [] + project_id = project_id + probject_id = ObjectId(project_id) + 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}]} + try: + project = mongo.db.projects_collection.find_one({'_id':probject_id}) + except: + flash("Issue assigning Project data for project id {}".format(probject_id)) + else: + #for project in agreement['projects']: + try: + tme = mongo.db.time_collection.find({'project':probject_id}) + if tme is None: + flash('No Current Hours submitted for project {}'.format(probject_id)) + except: + flash("Issue assigning time data for project id {}".format(probject_id)) + else: + for time in tme: + payperiod_times.append(time) + finally: + return render_template('admin/agreements/projects/index.html',project=project,payperiod_times=payperiod_times,ORGNAME=OrganizationName) + +@app.route("/admin/rename/project/<project_id>",methods=["GET","POST"]) +@login_required +def rename_project(project_id): + form = RenameProjectForm() + if form.validate_on_submit(): + try: + project = mongo.db.projects_collection.find_one({'_id':project_id}) + except: + flash("Issue finding Project with id {}".format(project_id)) + else: + try: + mongo.db.projects_collection.update_one({'_id':ObjectId(project_id)},{'$set':{'project_name':form.newName.data}}) + except: + flash("Issue setting {} as project name for {}".format(form.newName.data,project_id)) + finally: + return redirect(url_for('project', project_id=project_id)) + return render_template('admin/agreements/projects/update/rename.html',form=form,ORGNAME=OrganizationName) + +@app.route("/admin/move/project/<project_id>",methods=["GET","POST"]) +@login_required +def move_project(project_id): + #TODO replace w/ get agreement(s) fn + availableAgreements = [] + for agreement in mongo.db.agreements_collection.find(): + availableAgreements.append((agreement['_id'],agreement['agreement_name'])) + # END + form = MoveProjectForm() + form.newAgreement.choices = availableAgreements # assign the fn as above + if form.validate_on_submit(): + try: + #TODO replace w/ get project(s) fn + project = mongo.db.projects_collection.find_one({'_id':ObjectId(project_id)}) + #END + except: + flash('Issue finding project with id {}'.format(project_id)) + else: + try: + mongo.db.agreements_collection.find_one({'_id':ObjectId(project['agreement'])}) + except: + flash('Issue finding agreement {} for project {}'.format(project['agreement'],project_id)) + else: + # 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 + try: + mongo.db.agreements_collection.update_one({'_id':ObjectId(project['agreement'])},{'$pull':{'projects':ObjectId(project_id)}}) + except: + flash('Issue removing project {} from agreement {}'.format(project_id,project['agreement'])) + else: + try: + mongo.db.agreements_collection.update_one({'_id':ObjectId(form.newAgreement.data)},{'$push':{'projects':ObjectId(project_id)}}) + except: + flash('Issue adding project {} to agreement {}'.format(project_id,form.newAgreement.data)) + else: + mongo.db.projects_collection.update_one({'_id':ObjectId(project_id)},{'$set':{'agreement':form.newAgreement.data}}) + finally: + return redirect(url_for('project', project_id=project_id)) + return render_template('admin/agreements/projects/update/move.html',form=form,ORGNAME=OrganizationName) + +@app.route("/admin/remove/project/<project_id>",methods=["GET","POST"]) +@login_required +def remove_project(project_id): + form = ConfirmRemove() + if form.validate_on_submit(): + try: + #TODO replace w/ get project(s) fn + project = mongo.db.projects_collection.find_one({'_id':ObjectId(project_id)}) + #END + except: + flash('Issue finding project with id {}'.format(project_id)) + else: + try: + mongo.db.agreements_collection.find_one({'_id':ObjectId(project['agreement'])}) + except: + flash('Issue finding agreement for project {}'.format(project_id)) + else: + #NOTE abstract to removeProject(s) fn for mass deletion. Ex {'$pull':{'projects':{'$in':passedListOfDeletableProjects}}} + if mongo.db.agreements_collection.update_one({'_id':ObjectId(project['agreement'])},{'$pull':{'projects':ObjectId(project_id)}}): + mongo.db.projects_collection.delete_one({'_id':ObjectId(project_id)}) + + return redirect(url_for('agreement',agreement_id=project['agreement'])) + + return render_template('admin/confirm_remove.html',form=form,ORGNAME=OrganizationName) #### #### ####### Knowlegebase Route ####### #### #### diff --git a/app/templates/admin/agreements/projects/index.html b/app/templates/admin/agreements/projects/index.html @@ -13,11 +13,12 @@ <section class="project-grid"> <dl> - <dt>{{ project['project_name'] }}</dt> + <dt>{{ project['project_name'] }}<a href="{{url_for('rename_project', project_id=project['_id'])}}"> change</a></dt> <dd>ID: {{ project['_id'] }}</dd> - <dd>A_ID:{{ project['agreement']}}</dd> + <dd>A_ID:{{ project['agreement']}}<a href="{{url_for('move_project',project_id=project['_id'])}}"> change</a></dd> <dd>Budget:{{ project['budget']}}</dd> <dd>Costs:{{ project['costs']}}</dd> + <dd><a href="{{url_for('remove_project',project_id=project['_id'])}}"style="color:red;">remove</a></dd> </dl> <div class="current-pay-period"> diff --git a/app/templates/admin/agreements/projects/update/move.html b/app/templates/admin/agreements/projects/update/move.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block title %}Update Project Parent{% endblock %} + +{% block content %} +<section class="new-agreement-grid"> + <h3>Update Project Parent</h3> + <form action="" method="POST" novalidate> + {{ form.hidden_tag() }} + {% for error in form.errors %} + <span style="color:red;">[{{ error }}]</span> + {% endfor %} + {% for ferror in form.form_errors %} + <span style="color:yellow;">[{{ ferror }}]</span> + {% endfor %} + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + <p style='color:red'>{{ message }}</p> + {% endfor %} + {% endif %} + {% endwith %} + {{ form.newAgreement.label }}{{ form.newAgreement() }}<br> + {{ form.moveProject() }} + </form> +</section> +{% endblock %} diff --git a/app/templates/admin/agreements/projects/update/rename.html b/app/templates/admin/agreements/projects/update/rename.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block title %}Update Project Name {% endblock %} + +{% block content %} +<section class="new-agreement-grid"> + <h3>Update Project Name</h3> + <form action="" method="POST" novalidate> + {{ form.hidden_tag() }} + {% for error in form.errors %} + <span style="color:red;">[{{ error }}]</span> + {% endfor %} + {% for ferror in form.form_errors %} + <span style="color:yellow;">[{{ ferror }}]</span> + {% endfor %} + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + <p style='color:red'>{{ message }}</p> + {% endfor %} + {% endif %} + {% endwith %} + {{ form.newName.label }}{{ form.newName() }}<br> + {{ form.renameProject() }} + </form> +</section> +{% endblock %} diff --git a/app/templates/admin/agreements/widget.html b/app/templates/admin/agreements/widget.html @@ -2,9 +2,9 @@ <h3>Agreements & Funding</h3> {% for agreement in agreements %} <a href="/admin/agreement/{{ agreement._id }}"><div class="progress">{{ agreement.agreement_name }} - <div class="total-progress" style="width:{{ (agreement.total_cost/agreement.total_budget)*100 }}%;">{{ agreement.total_cost|round(2,'ceil') }}/{{ agreement.total_budget|round(2, 'ceil') }} | {{ ((agreement.total_cost/agreement.total_budget)*100)|round|int }}%</div> + <div class="total-progress" style="width:{{ (agreement.total_cost)*100 }}%;">{# {{ agreement.total_cost|round(2,'ceil') }}/{{ agreement.total_budget|round(2, 'ceil') }} | {{ ((agreement.total_cost/agreement.total_budget)*100)|round|int }}% #}</div> {% for project in agreement['projects'] %} - <div class="progress-bar" style="width:{{ (project.total_cost/project.total_budget)*100 }}%;">{{ project['project_name'] }}: {{ project.total_cost|round(2,'ceil') }}/{{ project.total_budget|round(2, 'ceil') }} | {{ ((project.total_cost/project.total_budget)*100)|round|int }}%</div> + <div class="progress-bar" style="width:{{ (project.total_cost)*100 }}%;">{{ project['project_name'] }}: {# {{ project.total_cost|round(2,'ceil') }}/{{ project.total_budget|round(2, 'ceil') }} | {{ ((project.total_cost/project.total_budget)*100)|round|int }} #}%</div> {% endfor %} </div></a> {% endfor %} diff --git a/app/templates/admin/confirm_remove.html b/app/templates/admin/confirm_remove.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} + +{% block title %}Confirm{% endblock %} + +{% block content %} +<section class="new-agreement-grid"> + <h3>WARNING:Are you sure you want to remove this asset?</h3> + <form action="" method="POST" novalidate> + {{ form.hidden_tag() }} + {% for error in form.errors %} + <span style="color:red;">[{{ error }}]</span> + {% endfor %} + {% for ferror in form.form_errors %} + <span style="color:yellow;">[{{ ferror }}]</span> + {% endfor %} + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + <p style='color:red'>{{ message }}</p> + {% endfor %} + {% endif %} + {% endwith %} + {{ form.confirm() }} + </form> +</section> +{% endblock %}