Preemptively upload task order PDF

This commit is contained in:
richard-dds 2018-10-19 10:57:18 -04:00
parent 3cf0907d0d
commit 5d2b976e5f
8 changed files with 217 additions and 32 deletions

View File

@ -0,0 +1,32 @@
"""resource attachments
Revision ID: 9c24c609878a
Revises: 903d7c66ff1d
Create Date: 2018-10-18 17:14:25.566215
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '9c24c609878a'
down_revision = '903d7c66ff1d'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('attachments', sa.Column('resource', sa.String(), nullable=True))
op.add_column('attachments', sa.Column('resource_id', postgresql.UUID(as_uuid=True), nullable=True))
op.create_index(op.f('ix_attachments_resource_id'), 'attachments', ['resource_id'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_attachments_resource_id'), table_name='attachments')
op.drop_column('attachments', 'resource_id')
op.drop_column('attachments', 'resource')
# ### end Alembic commands ###

View File

@ -13,6 +13,11 @@ from .query import RequestsQuery
from .authorization import RequestsAuthorization
def pick(keys, d):
_keys = set(keys)
return {k: v for (k, v) in d.items() if k in _keys}
def create_revision_from_request_body(body):
body = {k: v for p in body.values() for k, v in p.items()}
DATES = ["start_date", "date_latest_training"]
@ -156,33 +161,51 @@ class Requests(object):
return Requests.status_count(RequestStatus.APPROVED)
@classmethod
def update_financial_verification(cls, request_id, financial_data):
def update_financial_verification(cls, request_id, financial_data, task_order=None):
request = RequestsQuery.get_with_lock(request_id)
request_data = financial_data.copy()
task_order_data = {
k: request_data.pop(k)
for (k, v) in financial_data.items()
if k in TaskOrders.TASK_ORDER_DATA
}
# request_data = financial_data.copy()
# task_order_data = {
# k: request_data.pop(k)
# for (k, v) in financial_data.items()
# if k in TaskOrders.TASK_ORDER_DATA
# }
if task_order_data:
task_order_number = request_data.pop("task_order_number")
else:
task_order_number = request_data.get("task_order_number")
# if task_order_data:
# task_order_number = request_data.pop("task_order_number")
# else:
# task_order_number = request_data.get("task_order_number")
task_order_file = request_data.pop("task_order", None)
if isinstance(task_order_file, FileStorage):
task_order_data["pdf"] = task_order_file
# task_order_file = request_data.pop("task_order", None)
# if isinstance(task_order_file, FileStorage):
# task_order_data["pdf"] = task_order_file
task_order = TaskOrders.get_or_create_task_order(
task_order_number, task_order_data
# task_order = TaskOrders.get_or_create_task_order(
# task_order_number, task_order_data
# )
delta = pick(
[
"uii_ids",
"pe_id",
"treasury_code",
"ba_code",
"fname_co",
"lname_co",
"email_co",
"office_co",
"fname_cor",
"lname_cor",
"email_cor",
"office_cor",
],
financial_data,
)
if task_order:
request.task_order = task_order
request = Requests.update(request.id, {"financial_verification": request_data})
request = Requests.update(request.id, {"financial_verification": delta})
return request

View File

@ -67,3 +67,13 @@ class TaskOrders(object):
source=Source.MANUAL,
pdf=attachment,
)
@classmethod
def get_or_create(cls, number, attachment=None, data=None):
try:
return TaskOrders.get(number)
except NotFoundError:
data = data or {}
return TaskOrders.create(
**data, number=number, pdf=attachment, source=Source.MANUAL
)

View File

@ -1,9 +1,12 @@
from sqlalchemy import Column, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm.exc import NoResultFound
from flask import current_app as app
from atst.models import Base, types, mixins
from atst.database import db
from atst.uploader import UploadError
from atst.domain.exceptions import NotFoundError
class AttachmentError(Exception):
@ -16,20 +19,56 @@ class Attachment(Base, mixins.TimestampsMixin):
id = types.Id()
filename = Column(String, nullable=False)
object_name = Column(String, unique=True, nullable=False)
resource = Column(String)
resource_id = Column(UUID(as_uuid=True), index=True)
@classmethod
def attach(cls, fyle):
def attach(cls, fyle, resource=None, resource_id=None):
try:
filename, object_name = app.uploader.upload(fyle)
except UploadError as e:
raise AttachmentError("Could not add attachment. " + str(e))
attachment = Attachment(filename=filename, object_name=object_name)
attachment = Attachment(
filename=filename,
object_name=object_name,
resource=resource,
resource_id=resource_id,
)
db.session.add(attachment)
db.session.commit()
return attachment
@classmethod
def get(cls, id_):
try:
return db.session.query(Attachment).filter_by(id=id_).one()
except NoResultFound:
raise NotFoundError("attachment")
@classmethod
def get_for_resource(cls, resource, resource_id):
try:
return (
db.session.query(Attachment)
.filter_by(resource=resource, resource_id=resource_id)
.one()
)
except NoResultFound:
raise NotFoundError("attachment")
@classmethod
def delete_for_resource(cls, resource, resource_id):
try:
return (
db.session.query(Attachment)
.filter_by(resource=resource, resource_id=resource_id)
.delete()
)
except NoResultFound:
raise NotFoundError("attachment")
def __repr__(self):
return "<Attachment(name='{}', id='{}')>".format(self.filename, self.id)

View File

@ -1,15 +1,18 @@
from flask import g, render_template, redirect, url_for
from flask import request as http_request
from werkzeug.datastructures import ImmutableMultiDict
from werkzeug.datastructures import ImmutableMultiDict, FileStorage
from . import requests_bp
from atst.domain.requests import Requests
from atst.forms.financial import FinancialForm, ExtendedFinancialForm
from atst.forms.exceptions import FormValidationError
from atst.domain.exceptions import NotFoundError
from atst.domain.requests.financial_verification import (
PENumberValidator,
TaskOrderNumberValidator,
)
from atst.models.attachment import Attachment
from atst.domain.task_orders import TaskOrders
class FinancialVerificationBase(object):
@ -28,10 +31,46 @@ class FinancialVerificationBase(object):
mdict = ImmutableMultiDict(formdata) if formdata is not None else None
if is_extended:
try:
attachment = Attachment.get_for_resource("task_order", self.request.id)
existing_fv_data["task_order"] = attachment.filename
except NotFoundError:
pass
return ExtendedFinancialForm(formdata=mdict, data=existing_fv_data)
else:
return FinancialForm(formdata=mdict, data=existing_fv_data)
def _process_attachment(self, is_extended, form):
attachment = None
if self.is_extended:
attachment = None
if isinstance(form.task_order.data, FileStorage):
Attachment.delete_for_resource("task_order", self.request.id)
attachment = Attachment.attach(
form.task_order.data, "task_order", self.request.id
)
elif isinstance(form.task_order.data, str):
attachment = Attachment.get_for_resource("task_order", self.request.id)
if attachment:
form.task_order.data = attachment.id
return attachment
def _try_create_task_order(self, form, attachment):
form_data = form.data
task_order_number = form_data.get("task_order_number")
if task_order_number:
task_order_data = {
k: v for (k, v) in form_data.items() if k in TaskOrders.TASK_ORDER_DATA
}
return TaskOrders.get_or_create(
task_order_number, attachment=attachment, data=task_order_data
)
return None
def _apply_pe_number_error(self, field):
suggestion = self.pe_validator.suggest_pe_id(field.data)
error_str = (
@ -56,7 +95,8 @@ class GetFinancialVerificationForm(FinancialVerificationBase):
self.is_extended = is_extended
def execute(self):
return self._get_form(self.request, self.is_extended)
form = self._get_form(self.request, self.is_extended)
return form
class UpdateFinancialVerification(FinancialVerificationBase):
@ -94,9 +134,12 @@ class UpdateFinancialVerification(FinancialVerificationBase):
self._apply_task_order_number_error(form.task_order_number)
should_submit = False
attachment = self._process_attachment(self.is_extended, form)
if should_update:
task_order = self._try_create_task_order(form, attachment)
updated_request = Requests.update_financial_verification(
self.request.id, form.data
self.request.id, form.data, task_order=task_order
)
if should_submit:
return Requests.submit_financial_verification(updated_request)
@ -140,8 +183,10 @@ class SaveFinancialVerificationDraft(FinancialVerificationBase):
valid = False
self._apply_task_order_number_error(form.task_order_number)
attachment = self._process_attachment(self.is_extended, form)
task_order = self._try_create_task_order(form, attachment)
updated_request = Requests.update_financial_verification(
self.request.id, form.data
self.request.id, form.data, task_order=task_order
)
if valid:
@ -205,7 +250,7 @@ def update_financial_verification(request_id):
@requests_bp.route("/requests/verify/<string:request_id>/draft", methods=["POST"])
def save_financial_verification_draft(request_id):
request = Requests.get(g.current_user, request_id)
fv_data = http_request.form
fv_data = {**http_request.form, **http_request.files}
is_extended = http_request.args.get("extended")
try:

View File

@ -25,7 +25,22 @@ export default {
} = this.initialData
return {
funding_type
funding_type,
shouldForceShowTaskOrder: false
}
},
computed: {
showTaskOrder: function() {
return !this.initialData.task_order || this.shouldForceShowTaskOrder
}
},
methods: {
forceShowTaskOrder: function(e) {
console.log("forceShowTaskOrder", e)
e.preventDefault()
this.shouldForceShowTaskOrder = true
}
}
}

View File

@ -106,13 +106,21 @@
validation='dollars'
) }}
<div class="usa-input usa-input--validation--anything {% if f.task_order.errors %} usa-input--error {% endif %}">
<template v-if="showTaskOrder">
<div class="usa-input {% if f.task_order.errors %} usa-input--error {% endif %}">
{{ f.task_order.label }}
{{ f.task_order }}
{% for error in f.task_order.errors %}
<span class="usa-input__message">{{error}}</span>
{% endfor %}
</div>
</template>
<template v-else>
<p>Uploaded {{ f.task_order.data }}.</p>
<div>
<button v-on:click="forceShowTaskOrder($event)">Change</button>
</div>
</template>
</fieldset>
{% endif %}

View File

@ -4,6 +4,7 @@ from flask import url_for
from atst.eda_client import MockEDAClient
from atst.routes.requests.financial_verification import (
GetFinancialVerificationForm,
UpdateFinancialVerification,
SaveFinancialVerificationDraft,
)
@ -213,6 +214,18 @@ def test_updated_request_has_pdf(fv_data, extended_financial_verification_data):
assert updated_request.task_order.pdf
def test_can_save_draft_with_just_pdf(extended_financial_verification_data):
request = RequestFactory.create()
user = UserFactory.create()
data = {"task_order": extended_financial_verification_data["task_order"]}
SaveFinancialVerificationDraft(
TrueValidator, TrueValidator, user, request, data, is_extended=True
).execute()
form = GetFinancialVerificationForm(user, request, is_extended=True).execute()
assert form.task_order
def test_update_fv_route(client, user_session, fv_data):
user = UserFactory.create()
request = RequestFactory.create(creator=user)