Save as draft
This commit is contained in:
parent
c0da5d482f
commit
d672f5792c
43
alembic/versions/3d3c71b03e98_nullable_fields_for_clins.py
Normal file
43
alembic/versions/3d3c71b03e98_nullable_fields_for_clins.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""Nullable fields for CLINs
|
||||||
|
|
||||||
|
|
||||||
|
Revision ID: 3d3c71b03e98
|
||||||
|
Revises: b565531a1e82
|
||||||
|
Create Date: 2019-06-17 11:04:03.294913
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '3d3c71b03e98'
|
||||||
|
down_revision = 'b565531a1e82'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('clins', 'end_date',
|
||||||
|
existing_type=sa.DATE(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'jedi_clin_type',
|
||||||
|
existing_type=sa.VARCHAR(length=11),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'loas',
|
||||||
|
existing_type=postgresql.ARRAY(sa.VARCHAR()),
|
||||||
|
nullable=True,
|
||||||
|
existing_server_default=sa.text("'{}'::character varying[]"))
|
||||||
|
op.alter_column('clins', 'number',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'obligated_amount',
|
||||||
|
existing_type=sa.NUMERIC(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('clins', 'start_date',
|
||||||
|
existing_type=sa.DATE(),
|
||||||
|
nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ from wtforms.fields import (
|
|||||||
StringField,
|
StringField,
|
||||||
)
|
)
|
||||||
from wtforms.fields.html5 import DateField
|
from wtforms.fields.html5 import DateField
|
||||||
from wtforms.validators import Required
|
from wtforms.validators import Required, Optional
|
||||||
from flask_wtf.file import FileAllowed
|
from flask_wtf.file import FileAllowed
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
|
||||||
@ -30,18 +30,20 @@ class CLINForm(FlaskForm):
|
|||||||
"CLIN type", choices=JEDI_CLIN_TYPES, coerce=coerce_enum
|
"CLIN type", choices=JEDI_CLIN_TYPES, coerce=coerce_enum
|
||||||
)
|
)
|
||||||
|
|
||||||
number = StringField(label="CLIN", validators=[Required()])
|
number = StringField(label="CLIN", validators=[Optional()])
|
||||||
start_date = DateField(
|
start_date = DateField(
|
||||||
translate("forms.task_order.start_date_label"),
|
translate("forms.task_order.start_date_label"),
|
||||||
format="%m/%d/%Y",
|
format="%m/%d/%Y",
|
||||||
validators=[Required()],
|
validators=[Optional()],
|
||||||
)
|
)
|
||||||
end_date = DateField(
|
end_date = DateField(
|
||||||
translate("forms.task_order.end_date_label"),
|
translate("forms.task_order.end_date_label"),
|
||||||
format="%m/%d/%Y",
|
format="%m/%d/%Y",
|
||||||
validators=[Required()],
|
validators=[Optional()],
|
||||||
|
)
|
||||||
|
obligated_amount = DecimalField(
|
||||||
|
label="Funds obligated for cloud", validators=[Optional()]
|
||||||
)
|
)
|
||||||
obligated_amount = DecimalField(label="Funds obligated for cloud")
|
|
||||||
loas = FieldList(StringField())
|
loas = FieldList(StringField())
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ class CLIN(Base, mixins.TimestampsMixin):
|
|||||||
task_order_id = Column(ForeignKey("task_orders.id"), nullable=False)
|
task_order_id = Column(ForeignKey("task_orders.id"), nullable=False)
|
||||||
task_order = relationship("TaskOrder")
|
task_order = relationship("TaskOrder")
|
||||||
|
|
||||||
number = Column(String, nullable=False)
|
number = Column(String, nullable=True)
|
||||||
loas = Column(ARRAY(String), server_default="{}", nullable=False)
|
loas = Column(ARRAY(String), server_default="{}", nullable=True)
|
||||||
start_date = Column(Date, nullable=False)
|
start_date = Column(Date, nullable=True)
|
||||||
end_date = Column(Date, nullable=False)
|
end_date = Column(Date, nullable=True)
|
||||||
obligated_amount = Column(Numeric(scale=2), nullable=False)
|
obligated_amount = Column(Numeric(scale=2), nullable=True)
|
||||||
jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=False)
|
jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=True)
|
||||||
|
|
||||||
#
|
#
|
||||||
# NOTE: For now obligated CLINS are CLIN 1 + CLIN 3
|
# NOTE: For now obligated CLINS are CLIN 1 + CLIN 3
|
||||||
|
@ -86,11 +86,11 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_begun(self):
|
def has_begun(self):
|
||||||
return Clock.today() >= self.start_date
|
return self.start_date is not None and Clock.today() >= self.start_date
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_ended(self):
|
def has_ended(self):
|
||||||
return Clock.today() >= self.end_date
|
return self.start_date is not None and Clock.today() >= self.end_date
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_completed(self):
|
def is_completed(self):
|
||||||
@ -133,7 +133,7 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
|||||||
def total_obligated_funds(self):
|
def total_obligated_funds(self):
|
||||||
total = 0
|
total = 0
|
||||||
for clin in self.clins:
|
for clin in self.clins:
|
||||||
if clin.jedi_clin_type in [
|
if clin.obligated_amount is not None and clin.jedi_clin_type in [
|
||||||
JEDICLINType.JEDI_CLIN_1,
|
JEDICLINType.JEDI_CLIN_1,
|
||||||
JEDICLINType.JEDI_CLIN_3,
|
JEDICLINType.JEDI_CLIN_3,
|
||||||
]:
|
]:
|
||||||
@ -144,6 +144,7 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
|||||||
def total_contract_amount(self):
|
def total_contract_amount(self):
|
||||||
total = 0
|
total = 0
|
||||||
for clin in self.clins:
|
for clin in self.clins:
|
||||||
|
if clin.obligated_amount is not None:
|
||||||
total += clin.obligated_amount
|
total += clin.obligated_amount
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
@ -14,6 +14,9 @@ from atst.utils.flash import formatted_flash as flash
|
|||||||
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="review task order details")
|
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="review task order details")
|
||||||
def review_task_order(task_order_id):
|
def review_task_order(task_order_id):
|
||||||
task_order = TaskOrders.get(task_order_id)
|
task_order = TaskOrders.get(task_order_id)
|
||||||
|
if task_order.is_draft:
|
||||||
|
return redirect(url_for("task_orders.edit", task_order_id=task_order.id))
|
||||||
|
else:
|
||||||
signature_form = SignatureForm()
|
signature_form = SignatureForm()
|
||||||
return render_template(
|
return render_template(
|
||||||
"portfolios/task_orders/review.html",
|
"portfolios/task_orders/review.html",
|
||||||
|
@ -57,13 +57,14 @@ def update(portfolio_id=None, task_order_id=None):
|
|||||||
else:
|
else:
|
||||||
task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data)
|
task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data)
|
||||||
|
|
||||||
if http_request.args.get("review"):
|
if task_order.is_completed and http_request.args.get("review"):
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for("task_orders.review_task_order", task_order_id=task_order.id)
|
url_for("task_orders.review_task_order", task_order_id=task_order.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
flash("task_order_draft")
|
flash("task_order_draft")
|
||||||
|
|
||||||
|
if task_order.is_completed:
|
||||||
return redirect(url_for("task_orders.edit", task_order_id=task_order.id))
|
return redirect(url_for("task_orders.edit", task_order_id=task_order.id))
|
||||||
else:
|
|
||||||
return render_task_orders_edit(portfolio_id, task_order_id, form), 400
|
return render_task_orders_edit(portfolio_id, task_order_id, form), 400
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro TaskOrderEditButton(task_order, text="Edit", secondary=False) %}
|
{% macro TaskOrderEditButton(task_order, text="Edit", secondary=False) %}
|
||||||
<a href="{{ url_for('task_orders.edit', portfolio_id=task_order.portfolio_id, task_order_id=task_order.id) }}" class="usa-button {{ 'usa-button-secondary' if secondary else '' }}">
|
<a href="{{ url_for('task_orders.edit', task_order_id=task_order.id) }}" class="usa-button {{ 'usa-button-secondary' if secondary else '' }}">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</a>
|
</a>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
@ -121,6 +121,9 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
formaction="{{ review_action }}"
|
formaction="{{ review_action }}"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
{% if task_order and task_order.is_draft %}
|
||||||
|
disabled="disabled"
|
||||||
|
{% endif %}
|
||||||
:disabled="invalid"
|
:disabled="invalid"
|
||||||
value="Review task order"
|
value="Review task order"
|
||||||
form="new-task-order"
|
form="new-task-order"
|
||||||
|
@ -33,7 +33,8 @@ def task_order():
|
|||||||
return TaskOrderFactory.create(creator=user, portfolio=portfolio)
|
return TaskOrderFactory.create(creator=user, portfolio=portfolio)
|
||||||
|
|
||||||
|
|
||||||
def test_review_task_order(client, user_session, task_order):
|
def test_review_task_order_not_draft(client, user_session, task_order):
|
||||||
|
TaskOrders.sign(task_order=task_order, signer_dod_id=random_dod_id())
|
||||||
user_session(task_order.portfolio.owner)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
url_for("task_orders.review_task_order", task_order_id=task_order.id)
|
url_for("task_orders.review_task_order", task_order_id=task_order.id)
|
||||||
@ -41,6 +42,18 @@ def test_review_task_order(client, user_session, task_order):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_review_task_order_draft(client, user_session, task_order):
|
||||||
|
TaskOrders.update(
|
||||||
|
task_order_id=task_order.id, number="1234567890", clins=[], pdf=None
|
||||||
|
)
|
||||||
|
user_session(task_order.portfolio.owner)
|
||||||
|
response = client.get(
|
||||||
|
url_for("task_orders.review_task_order", task_order_id=task_order.id)
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert url_for("task_orders.edit", task_order_id=task_order.id) in response.location
|
||||||
|
|
||||||
|
|
||||||
def test_submit_task_order(client, user_session, task_order):
|
def test_submit_task_order(client, user_session, task_order):
|
||||||
user_session(task_order.portfolio.owner)
|
user_session(task_order.portfolio.owner)
|
||||||
response = client.post(
|
response = client.post(
|
||||||
|
@ -108,8 +108,8 @@ def test_task_orders_update(client, user_session, portfolio, pdf_upload):
|
|||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
||||||
)
|
)
|
||||||
assert response.status_code == 302
|
|
||||||
assert task_order.number == data["number"]
|
assert task_order.number == data["number"]
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_update_pdf(
|
def test_task_orders_update_pdf(
|
||||||
@ -121,8 +121,8 @@ def test_task_orders_update_pdf(
|
|||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
||||||
)
|
)
|
||||||
assert response.status_code == 302
|
|
||||||
assert task_order.pdf.filename == pdf_upload2.filename
|
assert task_order.pdf.filename == pdf_upload2.filename
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_update_delete_pdf(client, user_session, portfolio, pdf_upload):
|
def test_task_orders_update_delete_pdf(client, user_session, portfolio, pdf_upload):
|
||||||
@ -132,8 +132,19 @@ def test_task_orders_update_delete_pdf(client, user_session, portfolio, pdf_uplo
|
|||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
||||||
)
|
)
|
||||||
assert response.status_code == 302
|
|
||||||
assert task_order.pdf is None
|
assert task_order.pdf is None
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_cannot_get_to_review_screen_with_incomplete_data(
|
||||||
|
client, user_session, portfolio
|
||||||
|
):
|
||||||
|
user_session(portfolio.owner)
|
||||||
|
data = {"number": "0123456789"}
|
||||||
|
response = client.post(
|
||||||
|
url_for("task_orders.update", portfolio_id=portfolio.id, review=True), data=data
|
||||||
|
)
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Update after implementing new TO form")
|
@pytest.mark.skip(reason="Update after implementing new TO form")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user