diff --git a/alembic/versions/6512aa8d4641_default_boolean_fields_to_false.py b/alembic/versions/6512aa8d4641_default_boolean_fields_to_false.py new file mode 100644 index 00000000..85612afa --- /dev/null +++ b/alembic/versions/6512aa8d4641_default_boolean_fields_to_false.py @@ -0,0 +1,27 @@ +"""Default Boolean fields to False + +Revision ID: 6512aa8d4641 +Revises: ec1ba2363191 +Create Date: 2019-02-27 13:22:03.863516 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6512aa8d4641' +down_revision = 'ec1ba2363191' +branch_labels = None +depends_on = None + + +def upgrade(): + op.alter_column('task_orders', 'ko_invite', type_=sa.Boolean(), server_default=False) + op.alter_column('task_orders', 'so_invite', type_=sa.Boolean(), server_default=False) + op.alter_column('task_orders', 'cor_invite', type_=sa.Boolean(), server_default=False) + +def downgrade(): + op.alter_column('task_orders', 'ko_invite', server_default=sa.Boolean()) + op.alter_column('task_orders', 'so_invite', server_default=sa.Boolean()) + op.alter_column('task_orders', 'cor_invite', server_default=sa.Boolean()) diff --git a/atst/forms/officers.py b/atst/forms/officers.py index b0b0e61d..4f98da69 100644 --- a/atst/forms/officers.py +++ b/atst/forms/officers.py @@ -35,6 +35,7 @@ class EditTaskOrderOfficersForm(CacheableForm): "email", "phone_number", "dod_id", + "invite", ] def process(self, formdata=None, obj=None, data=None, **kwargs): diff --git a/atst/models/task_order.py b/atst/models/task_order.py index b8ff5db5..73e090f9 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -74,19 +74,19 @@ class TaskOrder(Base, mixins.TimestampsMixin): ko_email = Column(String) # Email ko_phone_number = Column(String) # Phone Number ko_dod_id = Column(String) # DOD ID - ko_invite = Column(Boolean) + ko_invite = Column(Boolean, default=False) cor_first_name = Column(String) # First Name cor_last_name = Column(String) # Last Name cor_email = Column(String) # Email cor_phone_number = Column(String) # Phone Number cor_dod_id = Column(String) # DOD ID - cor_invite = Column(Boolean) + cor_invite = Column(Boolean, default=False) so_first_name = Column(String) # First Name so_last_name = Column(String) # Last Name so_email = Column(String) # Email so_phone_number = Column(String) # Phone Number so_dod_id = Column(String) # DOD ID - so_invite = Column(Boolean) + so_invite = Column(Boolean, default=False) pdf_attachment_id = Column(ForeignKey("attachments.id")) _pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id]) number = Column(String, unique=True) # Task Order Number @@ -95,7 +95,7 @@ class TaskOrder(Base, mixins.TimestampsMixin): signer_dod_id = Column(String) signed_at = Column(DateTime) level_of_warrant = Column(Numeric(scale=2)) - unlimited_level_of_warrant = Column(Boolean) + unlimited_level_of_warrant = Column(Boolean, default=False) @hybrid_property def csp_estimate(self): diff --git a/atst/routes/portfolios/task_orders.py b/atst/routes/portfolios/task_orders.py index 27a8fdaa..a0b539b4 100644 --- a/atst/routes/portfolios/task_orders.py +++ b/atst/routes/portfolios/task_orders.py @@ -12,6 +12,7 @@ from atst.forms.officers import EditTaskOrderOfficersForm from atst.models.task_order import Status as TaskOrderStatus from atst.forms.ko_review import KOReviewForm from atst.forms.dd_254 import DD254Form +from atst.services.invitation import update_officer_invitations @portfolios_bp.route("/portfolios//task_orders") @@ -157,6 +158,7 @@ def edit_task_order_invitations(portfolio_id, task_order_id): form.populate_obj(task_order) db.session.add(task_order) db.session.commit() + update_officer_invitations(g.current_user, task_order) return redirect( url_for( diff --git a/atst/routes/task_orders/invite.py b/atst/routes/task_orders/invite.py index 8d453951..f3a29d01 100644 --- a/atst/routes/task_orders/invite.py +++ b/atst/routes/task_orders/invite.py @@ -3,49 +3,7 @@ from flask import redirect, url_for, g from . import task_orders_bp from atst.domain.task_orders import TaskOrders from atst.utils.flash import formatted_flash as flash -from atst.domain.portfolio_roles import PortfolioRoles -from atst.services.invitation import Invitation as InvitationService - - -OFFICER_INVITATIONS = [ - { - "field": "ko_invite", - "role": "contracting_officer", - "subject": "Review a task order", - "template": "emails/invitation.txt", - }, - { - "field": "cor_invite", - "role": "contracting_officer_representative", - "subject": "Help with a task order", - "template": "emails/invitation.txt", - }, - { - "field": "so_invite", - "role": "security_officer", - "subject": "Review security for a task order", - "template": "emails/invitation.txt", - }, -] - - -def update_officer_invitations(user, task_order): - for officer_type in OFFICER_INVITATIONS: - field = officer_type["field"] - if getattr(task_order, field) and not getattr(task_order, officer_type["role"]): - officer_data = task_order.officer_dictionary(officer_type["role"]) - officer = TaskOrders.add_officer( - user, task_order, officer_type["role"], officer_data - ) - pf_officer_member = PortfolioRoles.get(task_order.portfolio.id, officer.id) - invite_service = InvitationService( - user, - pf_officer_member, - officer_data["email"], - subject=officer_type["subject"], - email_template=officer_type["template"], - ) - invite_service.invite() +from atst.services.invitation import update_officer_invitations @task_orders_bp.route("/task_orders/invite/", methods=["POST"]) diff --git a/atst/services/invitation.py b/atst/services/invitation.py index 259dc2d0..1a18f4f8 100644 --- a/atst/services/invitation.py +++ b/atst/services/invitation.py @@ -2,6 +2,48 @@ from flask import render_template from atst.domain.invitations import Invitations from atst.queue import queue +from atst.domain.task_orders import TaskOrders +from atst.domain.portfolio_roles import PortfolioRoles + +OFFICER_INVITATIONS = [ + { + "field": "ko_invite", + "role": "contracting_officer", + "subject": "Review a task order", + "template": "emails/invitation.txt", + }, + { + "field": "cor_invite", + "role": "contracting_officer_representative", + "subject": "Help with a task order", + "template": "emails/invitation.txt", + }, + { + "field": "so_invite", + "role": "security_officer", + "subject": "Review security for a task order", + "template": "emails/invitation.txt", + }, +] + + +def update_officer_invitations(user, task_order): + for officer_type in OFFICER_INVITATIONS: + field = officer_type["field"] + if getattr(task_order, field) and not getattr(task_order, officer_type["role"]): + officer_data = task_order.officer_dictionary(officer_type["role"]) + officer = TaskOrders.add_officer( + user, task_order, officer_type["role"], officer_data + ) + pf_officer_member = PortfolioRoles.get(task_order.portfolio.id, officer.id) + invite_service = Invitation( + user, + pf_officer_member, + officer_data["email"], + subject=officer_type["subject"], + email_template=officer_type["template"], + ) + invite_service.invite() class Invitation: diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index f6bf3c2c..f11727a8 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -6,6 +6,7 @@ from atst.domain.roles import Roles from atst.domain.task_orders import TaskOrders from atst.models.portfolio_role import Status as PortfolioStatus from atst.utils.localization import translate +from atst.queue import queue from tests.factories import ( PortfolioFactory, @@ -136,6 +137,7 @@ class TestTaskOrderInvitations: ) def test_editing_with_partial_data(self, user_session, client): + queue_length = len(queue.get_queue()) user_session(self.portfolio.owner) response = self._post( client, @@ -151,8 +153,34 @@ class TestTaskOrderInvitations: assert updated_task_order.ko_last_name == "Skywalker" assert updated_task_order.so_first_name == "Boba" assert updated_task_order.so_last_name == "Fett" + assert len(queue.get_queue()) == queue_length + + def test_editing_with_complete_data(self, user_session, client): + queue_length = len(queue.get_queue()) + + user_session(self.portfolio.owner) + response = self._post( + client, + { + "contracting_officer-first_name": "Luke", + "contracting_officer-last_name": "Skywalker", + "contracting_officer-dod_id": "0123456789", + "contracting_officer-email": "luke@skywalker.mil", + "contracting_officer-phone_number": "0123456789", + "contracting_officer-invite": "y", + }, + ) + updated_task_order = TaskOrders.get(self.portfolio.owner, self.task_order.id) + + assert updated_task_order.ko_invite == True + assert updated_task_order.ko_first_name == "Luke" + assert updated_task_order.ko_last_name == "Skywalker" + assert updated_task_order.ko_email == "luke@skywalker.mil" + assert updated_task_order.ko_phone_number == "0123456789" + assert len(queue.get_queue()) == queue_length + 1 def test_editing_with_invalid_data(self, user_session, client): + queue_length = len(queue.get_queue()) user_session(self.portfolio.owner) response = self._post( client, @@ -167,6 +195,7 @@ class TestTaskOrderInvitations: updated_task_order = TaskOrders.get(self.portfolio.owner, self.task_order.id) assert updated_task_order.so_first_name != "Boba" + assert len(queue.get_queue()) == queue_length def test_ko_can_view_task_order(client, user_session):