From 7555663f4a9fa48d8fce287311953712c13261a0 Mon Sep 17 00:00:00 2001 From: John DeAngelis Date: Thu, 30 Jan 2025 15:37:20 -0500 Subject: [PATCH 1/6] feat: add attributes to agreement and contracts Signed-off-by: John DeAngelis --- ...dd_start_end_date_to_agreement_add_psc_.py | 52 +++++++++++++++++++ backend/models/agreements.py | 12 +++-- 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py diff --git a/backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py b/backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py new file mode 100644 index 0000000000..fc4ca5c896 --- /dev/null +++ b/backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py @@ -0,0 +1,52 @@ +"""add start/end date to agreement;add psc_contract_specialist and cotr_id to contract;fix direct_obligation + +Revision ID: baeefc8305f1 +Revises: 6e35b193a666 +Create Date: 2025-01-30 20:12:00.747620+00:00 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from alembic_postgresql_enum import TableReference + +# revision identifiers, used by Alembic. +revision: str = 'baeefc8305f1' +down_revision: Union[str, None] = '6e35b193a666' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('agreement', sa.Column('start_date', sa.Date(), nullable=True)) + op.add_column('agreement', sa.Column('end_date', sa.Date(), nullable=True)) + op.add_column('agreement_version', sa.Column('start_date', sa.Date(), autoincrement=False, nullable=True)) + op.add_column('agreement_version', sa.Column('end_date', sa.Date(), autoincrement=False, nullable=True)) + op.add_column('contract_agreement', sa.Column('psc_contract_specialist', sa.String(), nullable=True)) + op.add_column('contract_agreement', sa.Column('cotr_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'contract_agreement', 'ops_user', ['cotr_id'], ['id']) + op.add_column('contract_agreement_version', sa.Column('psc_contract_specialist', sa.String(), autoincrement=False, nullable=True)) + op.add_column('contract_agreement_version', sa.Column('cotr_id', sa.Integer(), autoincrement=False, nullable=True)) + op.sync_enum_values('ops', 'agreementtype', ['CONTRACT', 'GRANT', 'DIRECT_OBLIGATION', 'IAA', 'IAA_AA', 'MISCELLANEOUS'], + [TableReference(table_schema='ops', table_name='agreement', column_name='agreement_type'), TableReference(table_schema='ops', table_name='agreement_version', column_name='agreement_type')], + enum_values_to_rename=[]) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.sync_enum_values('ops', 'agreementtype', ['CONTRACT', 'GRANT', 'DIRECT_ALLOCATION', 'IAA', 'IAA_AA', 'MISCELLANEOUS'], + [TableReference(table_schema='ops', table_name='agreement', column_name='agreement_type'), TableReference(table_schema='ops', table_name='agreement_version', column_name='agreement_type')], + enum_values_to_rename=[]) + op.drop_column('contract_agreement_version', 'cotr_id') + op.drop_column('contract_agreement_version', 'psc_contract_specialist') + op.drop_constraint(None, 'contract_agreement', type_='foreignkey') + op.drop_column('contract_agreement', 'cotr_id') + op.drop_column('contract_agreement', 'psc_contract_specialist') + op.drop_column('agreement_version', 'end_date') + op.drop_column('agreement_version', 'start_date') + op.drop_column('agreement', 'end_date') + op.drop_column('agreement', 'start_date') + # ### end Alembic commands ### diff --git a/backend/models/agreements.py b/backend/models/agreements.py index c3f5f7608e..a1f5a1a103 100644 --- a/backend/models/agreements.py +++ b/backend/models/agreements.py @@ -1,9 +1,10 @@ """Agreement models.""" +from datetime import date from enum import Enum, auto from typing import List, Optional -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Table, Text, select +from sqlalchemy import Boolean, Column, Date, ForeignKey, Integer, String, Table, Text, select from sqlalchemy.dialects.postgresql import ENUM from sqlalchemy.orm import Mapped, mapped_column, object_session, relationship @@ -25,7 +26,7 @@ class ContractCategory(Enum): class AgreementType(Enum): CONTRACT = auto() GRANT = auto() - DIRECT_ALLOCATION = auto() + DIRECT_OBLIGATION = auto() IAA = auto() IAA_AA = auto() MISCELLANEOUS = auto() @@ -120,6 +121,9 @@ class Agreement(BaseModel): procurement_shop = relationship("ProcurementShop", back_populates="agreements") notes: Mapped[str] = mapped_column(Text, default="") + start_date: Mapped[Optional[date]] = mapped_column(Date) + end_date: Mapped[Optional[date]] = mapped_column(Date) + @BaseModel.display_name.getter def display_name(self): return self.name @@ -205,6 +209,8 @@ class ContractAgreement(Agreement): contract_category: Mapped[Optional[ContractCategory]] = mapped_column( ENUM(ContractCategory) ) + psc_contract_specialist: Mapped[Optional[str]] = mapped_column(String) + cotr_id: Mapped[Optional[User]] = mapped_column(ForeignKey("ops_user.id")) __mapper_args__ = { "polymorphic_identity": AgreementType.CONTRACT, @@ -264,7 +270,7 @@ class DirectAgreement(Agreement): payee: Mapped[str] = mapped_column(String) __mapper_args__ = { - "polymorphic_identity": AgreementType.DIRECT_ALLOCATION, + "polymorphic_identity": AgreementType.DIRECT_OBLIGATION, } From b17bea2b75ff3e2328eb1de032368c1a33e8f0c4 Mon Sep 17 00:00:00 2001 From: John DeAngelis Date: Thu, 30 Jan 2025 15:48:46 -0500 Subject: [PATCH 2/6] feat: add attributes to agreement and contracts Signed-off-by: John DeAngelis --- backend/ops_api/ops/resources/agreements_constants.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/ops_api/ops/resources/agreements_constants.py b/backend/ops_api/ops/resources/agreements_constants.py index c36bd50876..0f60ad7798 100644 --- a/backend/ops_api/ops/resources/agreements_constants.py +++ b/backend/ops_api/ops/resources/agreements_constants.py @@ -19,7 +19,7 @@ AgreementType.CONTRACT: ContractAgreement, AgreementType.GRANT: GrantAgreement, AgreementType.IAA: IaaAgreement, - AgreementType.DIRECT_ALLOCATION: DirectAgreement, + AgreementType.DIRECT_OBLIGATION: DirectAgreement, AgreementType.IAA_AA: IaaAaAgreement, } @@ -28,7 +28,7 @@ AgreementType.CONTRACT: ContractAgreementData, AgreementType.GRANT: GrantAgreementData, AgreementType.IAA: IaaAgreementData, - AgreementType.DIRECT_ALLOCATION: DirectAgreementData, + AgreementType.DIRECT_OBLIGATION: DirectAgreementData, AgreementType.IAA_AA: IaaAaAgreementData, } @@ -36,7 +36,7 @@ AgreementType.CONTRACT: ContractAgreementResponse, AgreementType.GRANT: GrantAgreementResponse, AgreementType.IAA: IaaAgreementResponse, - AgreementType.DIRECT_ALLOCATION: DirectAgreementResponse, + AgreementType.DIRECT_OBLIGATION: DirectAgreementResponse, AgreementType.IAA_AA: IaaAaAgreementResponse, } @@ -44,7 +44,7 @@ AgreementType.CONTRACT: AGREEMENT_TYPE_TO_DATACLASS_MAPPING.get(AgreementType.CONTRACT)(), AgreementType.GRANT: AGREEMENT_TYPE_TO_DATACLASS_MAPPING.get(AgreementType.GRANT)(), AgreementType.IAA: AGREEMENT_TYPE_TO_DATACLASS_MAPPING.get(AgreementType.IAA)(), - AgreementType.DIRECT_ALLOCATION: AGREEMENT_TYPE_TO_DATACLASS_MAPPING.get(AgreementType.DIRECT_ALLOCATION)(), + AgreementType.DIRECT_OBLIGATION: AGREEMENT_TYPE_TO_DATACLASS_MAPPING.get(AgreementType.DIRECT_OBLIGATION)(), AgreementType.IAA_AA: AGREEMENT_TYPE_TO_DATACLASS_MAPPING.get(AgreementType.IAA_AA)(), } @@ -52,7 +52,7 @@ AgreementType.CONTRACT: AGREEMENT_TYPE_TO_RESPONSE_MAPPING.get(AgreementType.CONTRACT)(), AgreementType.GRANT: AGREEMENT_TYPE_TO_RESPONSE_MAPPING.get(AgreementType.GRANT)(), AgreementType.IAA: AGREEMENT_TYPE_TO_RESPONSE_MAPPING.get(AgreementType.IAA)(), - AgreementType.DIRECT_ALLOCATION: AGREEMENT_TYPE_TO_RESPONSE_MAPPING.get(AgreementType.DIRECT_ALLOCATION)(), + AgreementType.DIRECT_OBLIGATION: AGREEMENT_TYPE_TO_RESPONSE_MAPPING.get(AgreementType.DIRECT_OBLIGATION)(), AgreementType.IAA_AA: AGREEMENT_TYPE_TO_RESPONSE_MAPPING.get(AgreementType.IAA_AA)(), # IAA_AA as the Miscellaneous type type under its mapper args in cans.py so mapping misc to IAA_AA AgreementType.MISCELLANEOUS: AGREEMENT_TYPE_TO_RESPONSE_MAPPING.get(AgreementType.IAA_AA)(), From fda7135d826bbff3b5c0ceb4a89b107143f07d23 Mon Sep 17 00:00:00 2001 From: John DeAngelis Date: Thu, 30 Jan 2025 15:49:17 -0500 Subject: [PATCH 3/6] test: add test data for new attributes Signed-off-by: John DeAngelis --- .../data/agreements_and_blin_data.json5 | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/data_tools/data/agreements_and_blin_data.json5 b/backend/data_tools/data/agreements_and_blin_data.json5 index d545deca11..b15a2153e9 100644 --- a/backend/data_tools/data/agreements_and_blin_data.json5 +++ b/backend/data_tools/data/agreements_and_blin_data.json5 @@ -6,7 +6,7 @@ product_service_code_id: 1, agreement_reason: "NEW_REQ", project_officer_id: 500, - agreement_type: "DIRECT_ALLOCATION", + agreement_type: "DIRECT_OBLIGATION", project_id: 1000, awarding_entity_id: 3, created_by: 503, @@ -86,6 +86,10 @@ contract_type: "FIRM_FIXED_PRICE", contract_category: "RESEARCH", support_contacts: [], + start_date: "2043-06-13T14:00:00", + end_date: "2044-06-13T14:00:00", + psc_contract_specialist: "PSC Contract Specialist", + cotr_id: 520, }, { name: "MIHOPE Check-In", @@ -104,6 +108,10 @@ contract_category: "SERVICE", support_contacts: [], service_requirement_type: "SEVERABLE", + start_date: "2043-06-13T14:00:00", + end_date: "2044-06-13T14:00:00", + psc_contract_specialist: "PSC Contract Specialist", + cotr_id: 520 }, { name: "MIHOPE Long-Term", @@ -122,6 +130,10 @@ support_contacts: [], contract_category: "SERVICE", service_requirement_type: "NON_SEVERABLE", + start_date: "2043-06-13T14:00:00", + end_date: "2044-06-13T14:00:00", + psc_contract_specialist: "", + cotr_id: 520 }, { name: "Interoperability Initiatives", @@ -150,6 +162,10 @@ id: 520, }, ], + start_date: null, + end_date: null, + psc_contract_specialist: "", + cotr_id: null }, { name: "Contract Workflow Test", @@ -186,6 +202,10 @@ id: 522, }, ], + start_date: null, + end_date: null, + psc_contract_specialist: "", + cotr_id: null }, { name: "Support Contract #1", @@ -214,6 +234,10 @@ id: 520, }, ], + start_date: null, + end_date: null, + psc_contract_specialist: "", + cotr_id: null }, ], clin: [ From df3be78667bc001278bf0cab9f18cdd230cc02b9 Mon Sep 17 00:00:00 2001 From: John DeAngelis Date: Thu, 30 Jan 2025 15:49:30 -0500 Subject: [PATCH 4/6] test: fix tests Signed-off-by: John DeAngelis --- backend/ops_api/tests/ops/can/test_can.py | 2 +- backend/ops_api/tests/ops/features/test_delete_agreement.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/ops_api/tests/ops/can/test_can.py b/backend/ops_api/tests/ops/can/test_can.py index e64fbc3e59..00bdbe52ca 100644 --- a/backend/ops_api/tests/ops/can/test_can.py +++ b/backend/ops_api/tests/ops/can/test_can.py @@ -87,7 +87,7 @@ def test_can_get_all(auth_client, mocker, test_can): mocker_get_can = mocker.patch("ops_api.ops.services.cans.CANService.get_list") mocker_get_can.return_value = [test_can] mock_simple_agreement = { - "agreement_type": "AgreementType.DIRECT_ALLOCATION", + "agreement_type": "AgreementType.DIRECT_OBLIGATION", "name": "DIRECT ALLOCATION #2: African American Child and Family Research Center", "awarding_entity_id": 3, } diff --git a/backend/ops_api/tests/ops/features/test_delete_agreement.py b/backend/ops_api/tests/ops/features/test_delete_agreement.py index 994b498361..9956bab497 100644 --- a/backend/ops_api/tests/ops/features/test_delete_agreement.py +++ b/backend/ops_api/tests/ops/features/test_delete_agreement.py @@ -178,7 +178,7 @@ def direct_agreement(loaded_db, test_project): direct_agreement = DirectAgreement( name="Feature Test Direct", payee="Somebody who needs money", - agreement_type=AgreementType.DIRECT_ALLOCATION, + agreement_type=AgreementType.DIRECT_OBLIGATION, project_id=test_project.id, created_by=503, ) From 4afae39de6af7b3dfe6b342c9a37d811457209e9 Mon Sep 17 00:00:00 2001 From: John DeAngelis Date: Thu, 30 Jan 2025 15:50:14 -0500 Subject: [PATCH 5/6] chore: fe to use new contract type direct obligation Signed-off-by: John DeAngelis --- .../ServicesComponents/ServicesComponents.constants.js | 2 +- frontend/src/helpers/utils.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ServicesComponents/ServicesComponents.constants.js b/frontend/src/components/ServicesComponents/ServicesComponents.constants.js index 3add0e49f2..55c8497654 100644 --- a/frontend/src/components/ServicesComponents/ServicesComponents.constants.js +++ b/frontend/src/components/ServicesComponents/ServicesComponents.constants.js @@ -1,7 +1,7 @@ export const AGREEMENT_TYPES = { CONTRACT: "CONTRACT", GRANT: "GRANT", - DIRECT_ALLOCATION: "DIRECT_ALLOCATION", + DIRECT_OBLIGATION: "DIRECT_OBLIGATION", IAA: "IAA", IAA_AA: "IAA_AA", MISCELLANEOUS: "MISCELLANEOUS" diff --git a/frontend/src/helpers/utils.js b/frontend/src/helpers/utils.js index 6b894de479..f2620baca8 100644 --- a/frontend/src/helpers/utils.js +++ b/frontend/src/helpers/utils.js @@ -96,7 +96,7 @@ export const codesToDisplayText = { agreementType: { CONTRACT: "Contract", GRANT: "Grant", - DIRECT_ALLOCATION: "Direct Allocation", + DIRECT_OBLIGATION: "Direct Obligation", IAA: "IAA", MISCELLANEOUS: "Misc" }, @@ -197,7 +197,7 @@ export const codesToDisplayText = { agreement: { "AgreementType.CONTRACT": "Contract", "AgreementType.GRANT": "Grant", - "AgreementType.DIRECT_ALLOCATION": "Direct Allocation", + "AgreementType.DIRECT_OBLIGATION": "Direct Obligation", "AgreementType.IAA": "IAA", "AgreementType.MISCELLANEOUS": "Misc" } From 42dec171ea7c70bcc33aa70e75aa35b228ee0d28 Mon Sep 17 00:00:00 2001 From: John DeAngelis Date: Mon, 3 Feb 2025 09:31:56 -0500 Subject: [PATCH 6/6] feat: add maps_sys_id to agreement model Signed-off-by: John DeAngelis --- ...dd_start_end_date_to_agreement_add_psc_.py | 105 +++++++++++++----- backend/models/agreements.py | 1 + 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py b/backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py index fc4ca5c896..f1ac000df4 100644 --- a/backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py +++ b/backend/alembic/versions/2025_01_30_2012-baeefc8305f1_add_start_end_date_to_agreement_add_psc_.py @@ -5,6 +5,7 @@ Create Date: 2025-01-30 20:12:00.747620+00:00 """ + from typing import Sequence, Union import sqlalchemy as sa @@ -12,41 +13,93 @@ from alembic_postgresql_enum import TableReference # revision identifiers, used by Alembic. -revision: str = 'baeefc8305f1' -down_revision: Union[str, None] = '6e35b193a666' +revision: str = "baeefc8305f1" +down_revision: Union[str, None] = "6e35b193a666" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.add_column('agreement', sa.Column('start_date', sa.Date(), nullable=True)) - op.add_column('agreement', sa.Column('end_date', sa.Date(), nullable=True)) - op.add_column('agreement_version', sa.Column('start_date', sa.Date(), autoincrement=False, nullable=True)) - op.add_column('agreement_version', sa.Column('end_date', sa.Date(), autoincrement=False, nullable=True)) - op.add_column('contract_agreement', sa.Column('psc_contract_specialist', sa.String(), nullable=True)) - op.add_column('contract_agreement', sa.Column('cotr_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'contract_agreement', 'ops_user', ['cotr_id'], ['id']) - op.add_column('contract_agreement_version', sa.Column('psc_contract_specialist', sa.String(), autoincrement=False, nullable=True)) - op.add_column('contract_agreement_version', sa.Column('cotr_id', sa.Integer(), autoincrement=False, nullable=True)) - op.sync_enum_values('ops', 'agreementtype', ['CONTRACT', 'GRANT', 'DIRECT_OBLIGATION', 'IAA', 'IAA_AA', 'MISCELLANEOUS'], - [TableReference(table_schema='ops', table_name='agreement', column_name='agreement_type'), TableReference(table_schema='ops', table_name='agreement_version', column_name='agreement_type')], - enum_values_to_rename=[]) + op.add_column("agreement", sa.Column("start_date", sa.Date(), nullable=True)) + op.add_column("agreement", sa.Column("end_date", sa.Date(), nullable=True)) + op.add_column( + "agreement_version", + sa.Column("start_date", sa.Date(), autoincrement=False, nullable=True), + ) + op.add_column( + "agreement_version", + sa.Column("end_date", sa.Date(), autoincrement=False, nullable=True), + ) + op.add_column( + "contract_agreement", + sa.Column("psc_contract_specialist", sa.String(), nullable=True), + ) + op.add_column( + "contract_agreement", sa.Column("cotr_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key(None, "contract_agreement", "ops_user", ["cotr_id"], ["id"]) + op.add_column( + "contract_agreement_version", + sa.Column( + "psc_contract_specialist", sa.String(), autoincrement=False, nullable=True + ), + ) + op.add_column( + "contract_agreement_version", + sa.Column("cotr_id", sa.Integer(), autoincrement=False, nullable=True), + ) + op.add_column("agreement", sa.Column("maps_sys_id", sa.Integer(), nullable=True)) + op.add_column( + "agreement_version", + sa.Column("maps_sys_id", sa.Integer(), autoincrement=False, nullable=True), + ) + op.sync_enum_values( + "ops", + "agreementtype", + ["CONTRACT", "GRANT", "DIRECT_OBLIGATION", "IAA", "IAA_AA", "MISCELLANEOUS"], + [ + TableReference( + table_schema="ops", table_name="agreement", column_name="agreement_type" + ), + TableReference( + table_schema="ops", + table_name="agreement_version", + column_name="agreement_type", + ), + ], + enum_values_to_rename=[], + ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.sync_enum_values('ops', 'agreementtype', ['CONTRACT', 'GRANT', 'DIRECT_ALLOCATION', 'IAA', 'IAA_AA', 'MISCELLANEOUS'], - [TableReference(table_schema='ops', table_name='agreement', column_name='agreement_type'), TableReference(table_schema='ops', table_name='agreement_version', column_name='agreement_type')], - enum_values_to_rename=[]) - op.drop_column('contract_agreement_version', 'cotr_id') - op.drop_column('contract_agreement_version', 'psc_contract_specialist') - op.drop_constraint(None, 'contract_agreement', type_='foreignkey') - op.drop_column('contract_agreement', 'cotr_id') - op.drop_column('contract_agreement', 'psc_contract_specialist') - op.drop_column('agreement_version', 'end_date') - op.drop_column('agreement_version', 'start_date') - op.drop_column('agreement', 'end_date') - op.drop_column('agreement', 'start_date') + op.sync_enum_values( + "ops", + "agreementtype", + ["CONTRACT", "GRANT", "DIRECT_ALLOCATION", "IAA", "IAA_AA", "MISCELLANEOUS"], + [ + TableReference( + table_schema="ops", table_name="agreement", column_name="agreement_type" + ), + TableReference( + table_schema="ops", + table_name="agreement_version", + column_name="agreement_type", + ), + ], + enum_values_to_rename=[], + ) + op.drop_column("contract_agreement_version", "cotr_id") + op.drop_column("contract_agreement_version", "psc_contract_specialist") + op.drop_constraint(None, "contract_agreement", type_="foreignkey") + op.drop_column("contract_agreement", "cotr_id") + op.drop_column("contract_agreement", "psc_contract_specialist") + op.drop_column("agreement_version", "end_date") + op.drop_column("agreement_version", "start_date") + op.drop_column("agreement", "end_date") + op.drop_column("agreement", "start_date") + op.drop_column("agreement_version", "maps_sys_id") + op.drop_column("agreement", "maps_sys_id") # ### end Alembic commands ### diff --git a/backend/models/agreements.py b/backend/models/agreements.py index a1f5a1a103..e10a76c362 100644 --- a/backend/models/agreements.py +++ b/backend/models/agreements.py @@ -123,6 +123,7 @@ class Agreement(BaseModel): start_date: Mapped[Optional[date]] = mapped_column(Date) end_date: Mapped[Optional[date]] = mapped_column(Date) + maps_sys_id: Mapped[Optional[int]] = mapped_column(Integer) @BaseModel.display_name.getter def display_name(self):