diff --git a/backend/openapi.yml b/backend/openapi.yml index c8a8dc20aa..9c19d3515c 100644 --- a/backend/openapi.yml +++ b/backend/openapi.yml @@ -1190,9 +1190,11 @@ paths: type: object properties: available_funding: - type: string + type: number + format: float carry_forward_funding: - type: string + type: number + format: float cans: type: array properties: @@ -1219,27 +1221,35 @@ paths: expiration_date: type: string expected_funding: - type: string + type: number + format: float in_draft_funding: - type: string + type: number + format: float in_execution_funding: - type: string + type: number + format: float new_funding: - type: string + type: number + format: float obligated_funding: - type: string + type: number + format: float planned_funding: - type: string + type: number + format: float received_funding: - type: string + type: number + format: float total_funding: - type: string + type: number + format: float examples: "0": value: | { - "available_funding": "8000000.00", - "carry_forward_funding": 0, + "available_funding": 8000000.00, + "carry_forward_funding": 8000000.00, "cans": [{ "can":{ "appropriation_date": "01/10/2022", @@ -1258,14 +1268,14 @@ paths: "carry_forward_label": "Carry-Forward", "expiration_date": "09/01/2023", }], - "expected_funding": "4000000.00", - "in_draft_funding": "500000.00", - "in_execution_funding": "2000000.00", - "new_funding": "2000000.00", + "expected_funding": 4000000.00, + "in_draft_funding": 500000.00, + "in_execution_funding": 2000000.00, + "new_funding": 2000000.00, "obligated_funding": 0, "planned_funding": 0, - "received_funding": "6000000.00", - "total_funding": "10000000.00" + "received_funding": 6000000.00, + "total_funding": 10000000.00 } /api/v1/users/: get: diff --git a/backend/ops_api/ops/schemas/can_funding_summary.py b/backend/ops_api/ops/schemas/can_funding_summary.py index 382994e3f3..fddbd262eb 100644 --- a/backend/ops_api/ops/schemas/can_funding_summary.py +++ b/backend/ops_api/ops/schemas/can_funding_summary.py @@ -18,14 +18,14 @@ class CANSFundingSourceSchema(Schema): class GetCANFundingSummaryResponseSchema(Schema): - available_funding = fields.String(allow_none=True) + available_funding = fields.Float(allow_none=True) cans = fields.List(fields.Nested(CANSFundingSourceSchema()), default=[]) - carry_forward_funding = fields.String(allow_none=True) - expected_funding = fields.String(allow_none=True) - in_draft_funding = fields.String(allow_none=True) - in_execution_funding = fields.String(allow_none=True) - new_funding = fields.String(allow_none=True) - obligated_funding = fields.String(allow_none=True) - planned_funding = fields.String(allow_none=True) - received_funding = fields.String(allow_none=True) - total_funding = fields.String(allow_none=True) + carry_forward_funding = fields.Float(allow_none=True) + expected_funding = fields.Float(allow_none=True) + in_draft_funding = fields.Float(allow_none=True) + in_execution_funding = fields.Float(allow_none=True) + new_funding = fields.Float(allow_none=True) + obligated_funding = fields.Float(allow_none=True) + planned_funding = fields.Float(allow_none=True) + received_funding = fields.Float(allow_none=True) + total_funding = fields.Float(allow_none=True) diff --git a/backend/ops_api/ops/utils/cans.py b/backend/ops_api/ops/utils/cans.py index 3ab42ac268..7ced3e308b 100644 --- a/backend/ops_api/ops/utils/cans.py +++ b/backend/ops_api/ops/utils/cans.py @@ -189,24 +189,24 @@ def get_filtered_cans(cans, fiscal_year=None, active_period=None, transfer=None, return cans -def aggregate_funding_summaries(funding_summaries: List[dict]) -> dict: +def aggregate_funding_summaries(funding_summaries: List[dict]) -> CanFundingSummary: """ Aggregates the funding summaries for multiple cans into a single total funding summary. :param funding_summaries: List of funding summaries to aggregate :return: A single total funding summary """ - totals = { - "available_funding": "0.0", + totals: CanFundingSummary = { + "available_funding": 0.0, "cans": [], - "carry_forward_funding": "0.0", - "expected_funding": "0.0", - "in_draft_funding": "0.0", - "in_execution_funding": "0.0", - "new_funding": "0.0", - "obligated_funding": "0.0", - "planned_funding": "0.0", - "received_funding": "0.0", - "total_funding": "0.0", + "carry_forward_funding": 0.0, + "expected_funding": 0.0, + "in_draft_funding": 0.0, + "in_execution_funding": 0.0, + "new_funding": 0.0, + "obligated_funding": 0.0, + "planned_funding": 0.0, + "received_funding": 0.0, + "total_funding": 0.0, } for summary in funding_summaries: @@ -214,10 +214,10 @@ def aggregate_funding_summaries(funding_summaries: List[dict]) -> dict: if key != "cans": current_value = summary.get(key, None) if current_value is None: - current_value = "0.0" + current_value = 0.0 if isinstance(current_value, (int, float, Decimal)): - current_value = str(Decimal(current_value)) - totals[key] = str(Decimal(totals[key]) + Decimal(current_value)) + current_value = Decimal(current_value) + totals[key] = Decimal(totals[key]) + Decimal(current_value) totals["cans"].append(summary.get("cans", [])[0]) diff --git a/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py b/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py index 7690d2d412..32868179d3 100644 --- a/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py +++ b/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py @@ -55,9 +55,9 @@ def test_can_get_can_funding_summary_cost_share_transfer(auth_client: FlaskClien assert response.status_code == 200 assert len(response.json["cans"]) == 1 - assert response.json["expected_funding"] == "0.0" - assert response.json["received_funding"] == "200000.0" - assert response.json["total_funding"] == "200000.0" + assert response.json["expected_funding"] == 0.0 + assert response.json["received_funding"] == 200000.0 + assert response.json["total_funding"] == 200000.0 def test_can_get_can_funding_summary_invalid_transfer(auth_client: FlaskClient): @@ -85,16 +85,16 @@ def test_can_get_can_funding_summary_all_cans_no_fiscal_year_match( assert response.status_code == 200 assert len(response.json["cans"]) == 0 - assert response.json["available_funding"] == "0.0" - assert response.json["carry_forward_funding"] == "0.0" - assert response.json["expected_funding"] == "0.0" - assert response.json["in_draft_funding"] == "0.0" - assert response.json["in_execution_funding"] == "0.0" - assert response.json["new_funding"] == "0.0" - assert response.json["obligated_funding"] == "0.0" - assert response.json["planned_funding"] == "0.0" - assert response.json["received_funding"] == "0.0" - assert response.json["total_funding"] == "0.0" + assert response.json["available_funding"] == 0.0 + assert response.json["carry_forward_funding"] == 0.0 + assert response.json["expected_funding"] == 0.0 + assert response.json["in_draft_funding"] == 0.0 + assert response.json["in_execution_funding"] == 0.0 + assert response.json["new_funding"] == 0.0 + assert response.json["obligated_funding"] == 0.0 + assert response.json["planned_funding"] == 0.0 + assert response.json["received_funding"] == 0.0 + assert response.json["total_funding"] == 0.0 @pytest.mark.usefixtures("app_ctx") @@ -292,7 +292,7 @@ def test_can_get_can_funding_summary(auth_client: FlaskClient, test_can: CAN) -> assert response.status_code == 200 assert response.json["cans"][0]["can"]["id"] == test_can.id assert "new_funding" in response.json - assert isinstance(response.json["new_funding"], str) + assert isinstance(response.json["new_funding"], float) assert "expiration_date" in response.json["cans"][0] assert "carry_forward_label" in response.json["cans"][0] @@ -308,9 +308,9 @@ def test_cans_get_can_funding_summary(auth_client: FlaskClient, test_cans: list[ assert response.status_code == 200 assert len(response.json["cans"]) == 2 - assert available_funding == "3340000.00" - assert carry_forward_funding == "3340000.00" - assert response.json["new_funding"] == "1340000.0" + assert available_funding == 3340000.00 + assert carry_forward_funding == 3340000.00 + assert response.json["new_funding"] == 1340000.0 def test_can_get_can_funding_summary_filter(auth_client: FlaskClient, test_cans: list[Type[CAN]]) -> None: @@ -330,9 +330,9 @@ def test_can_get_can_funding_summary_transfer_filter(auth_client: FlaskClient) - assert response.status_code == 200 assert len(response.json["cans"]) == 6 - assert response.json["expected_funding"] == "4520000.0" - assert response.json["received_funding"] == "8760000.0" - assert response.json["total_funding"] == "13280000.0" + assert response.json["expected_funding"] == 4520000.0 + assert response.json["received_funding"] == 8760000.0 + assert response.json["total_funding"] == 13280000.0 def test_can_get_can_funding_summary_complete_filter(auth_client: FlaskClient, test_cans: list[Type[CAN]]) -> None: @@ -351,7 +351,7 @@ def test_can_get_can_funding_summary_complete_filter(auth_client: FlaskClient, t assert response.status_code == 200 assert len(response.json["cans"]) == 0 assert "new_funding" in response.json - assert response.json["obligated_funding"] == "0.0" + assert response.json["obligated_funding"] == 0.0 def test_get_nested_attribute_existing_attribute(): @@ -488,7 +488,7 @@ def test_aggregate_funding_summaries(): "expiration_date": "10/01/2026", } ], - "carry_forward_funding": 30000, + "carry_forward_funding": 150000, "received_funding": 100000, "expected_funding": 180000 - 100000, "in_draft_funding": 0, @@ -503,7 +503,7 @@ def test_aggregate_funding_summaries(): result = aggregate_funding_summaries(funding_sums) assert result == { - "available_funding": "250000.0", + "available_funding": Decimal("250000"), "cans": [ { "can": {"amount": 50000, "description": "Grant for educational projects", "id": 1, "obligate_by": 2025}, @@ -521,15 +521,15 @@ def test_aggregate_funding_summaries(): "expiration_date": "10/01/2026", }, ], - "carry_forward_funding": "50000.0", - "expected_funding": "130000.0", - "in_draft_funding": "0.0", - "in_execution_funding": "130000.0", - "new_funding": "300000.0", - "obligated_funding": "80000.0", - "planned_funding": "280000.0", - "received_funding": "175000.0", - "total_funding": "305000.0", + "carry_forward_funding": Decimal("170000"), + "expected_funding": Decimal("130000"), + "in_draft_funding": Decimal("0"), + "in_execution_funding": Decimal("130000"), + "new_funding": Decimal("300000"), + "obligated_funding": Decimal("80000"), + "planned_funding": Decimal("280000"), + "received_funding": Decimal("175000"), + "total_funding": Decimal("305000"), } diff --git a/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.test.jsx b/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.test.jsx index 2c8f95887a..b1d2feb286 100644 --- a/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.test.jsx +++ b/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.test.jsx @@ -1235,7 +1235,7 @@ const canData = [ ]; const canFundingCardData = { - available_funding: "14300000.00", + available_funding: 14300000.0, cans: [ { can: { @@ -1273,19 +1273,19 @@ const canFundingCardData = { expiration_date: "09/01/2024" } ], - carry_forward_funding: "14300000.00", - expected_funding: "5000000.00", + carry_forward_funding: 14300000.0, + expected_funding: 5000000.0, in_draft_funding: 0, - in_execution_funding: "2000000.00", + in_execution_funding: 2000000.0, new_funding: 0, obligated_funding: 0, - planned_funding: "7700000.00", - received_funding: "19000000.00", - total_funding: "24000000.00" + planned_funding: 7700000.0, + received_funding: 19000000.0, + total_funding: 24000000.0 }; const canFundingCardData2 = { - available_funding: "1979500.00", + available_funding: 1979500.0, cans: [ { can: { @@ -1323,17 +1323,17 @@ const canFundingCardData2 = { expiration_date: "09/01/2023" } ], - carry_forward_funding: "1979500.00", - expected_funding: "520000.00", + carry_forward_funding: 1979500.0, + expected_funding: 520000.0, in_execution_funding: 0, - obligated_funding: "500.00", - planned_funding: "300000.00", - received_funding: "1760000.00", - total_funding: "2280000.00" + obligated_funding: 500.0, + planned_funding: 300000.0, + received_funding: 1760000.0, + total_funding: 2280000.0 }; const canFundingCard_G994426 = { - available_funding: "37000000.00", + available_funding: 37000000.0, cans: [ { can: { @@ -1371,13 +1371,13 @@ const canFundingCard_G994426 = { expiration_date: "09/01/2024" } ], - carry_forward_funding: "37000000.00", - expected_funding: "16000000.00", - in_execution_funding: "2000000.00", + carry_forward_funding: 37000000.0, + expected_funding: 16000000.0, + in_execution_funding: 2000000.0, obligated_funding: 0, - planned_funding: "1000000.00", - received_funding: "24000000.00", - total_funding: "40000000.00" + planned_funding: 1000000.0, + received_funding: 24000000.0, + total_funding: 40000000.0 }; const selectedBudgetLinesToAmount = [ diff --git a/frontend/src/components/CANs/CANFundingCard/CanFundingCard.test.jsx b/frontend/src/components/CANs/CANFundingCard/CanFundingCard.test.jsx index 9116b40909..9e56cf4819 100644 --- a/frontend/src/components/CANs/CANFundingCard/CanFundingCard.test.jsx +++ b/frontend/src/components/CANs/CANFundingCard/CanFundingCard.test.jsx @@ -53,7 +53,7 @@ const canData = { }; const canFundingCardData = { - available_funding: "14300000.00", + available_funding: 14300000.0, cans: [ { can: { @@ -91,13 +91,13 @@ const canFundingCardData = { expiration_date: "09/01/2024" } ], - carry_forward_funding: "14300000.00", - expected_funding: "5000000.00", + carry_forward_funding: 14300000.0, + expected_funding: 5000000.0, in_draft_funding: 0, - in_execution_funding: "2000000.00", + in_execution_funding: 2000000.0, new_funding: 0, obligated_funding: 0, - planned_funding: "7700000.00", - received_funding: "19000000.00", - total_funding: "24000000.00" + planned_funding: 7700000.0, + received_funding: 19000000.0, + total_funding: 24000000.0 }; diff --git a/frontend/src/components/CANs/CANTypes.d.ts b/frontend/src/components/CANs/CANTypes.d.ts index 1f3380ab08..7a115ec212 100644 --- a/frontend/src/components/CANs/CANTypes.d.ts +++ b/frontend/src/components/CANs/CANTypes.d.ts @@ -109,17 +109,17 @@ export type FundingReceived = { }; export type FundingSummary = { - available_funding: string; + available_funding: number; cans: FundingSummaryCAN[]; - carry_forward_funding: string; - expected_funding: string; - in_draft_funding: string; - in_execution_funding: string; - new_funding: string; - obligated_funding: string; - planned_funding: string; - received_funding: string; - total_funding: string; + carry_forward_funding: number; + expected_funding: number; + in_draft_funding: number; + in_execution_funding: number; + new_funding: number; + obligated_funding: number; + planned_funding: number; + received_funding: number; + total_funding: number; }; export type FundingSummaryCAN = { diff --git a/frontend/src/pages/cans/detail/Can.hooks.js b/frontend/src/pages/cans/detail/Can.hooks.js index d5983269e6..7eda10f0a7 100644 --- a/frontend/src/pages/cans/detail/Can.hooks.js +++ b/frontend/src/pages/cans/detail/Can.hooks.js @@ -93,7 +93,7 @@ export default function useCan() { ); const toggleEditMode = () => { - if (isFundingPage && CANFunding?.total_funding === "0") { + if (isFundingPage && CANFunding?.total_funding === 0) { setModalProps({ heading: `Welcome to FY ${fiscalYear}! The new fiscal year started on October 1, ${fiscalYear - 1} and it's time to add the FY budget for this CAN. Data from the previous fiscal year can no longer be edited, but can be viewed by changing the FY dropdown on the CAN details page.`, actionButtonText: "Edit CAN", @@ -135,12 +135,12 @@ export default function useCan() { teamLeaders: can?.portfolio?.team_leaders ?? [], portfolioName: can?.portfolio?.name, portfolioId: can?.portfolio_id ?? -1, - totalFunding: CANFunding?.total_funding ?? "0", - plannedFunding: CANFunding?.planned_funding ?? "0", - obligatedFunding: CANFunding?.obligated_funding ?? "0", - inExecutionFunding: CANFunding?.in_execution_funding ?? "0", - inDraftFunding: CANFunding?.in_draft_funding ?? "0", - receivedFunding: CANFunding?.received_funding ?? "0", + totalFunding: CANFunding?.total_funding ?? 0, + plannedFunding: CANFunding?.planned_funding ?? 0, + obligatedFunding: CANFunding?.obligated_funding ?? 0, + inExecutionFunding: CANFunding?.in_execution_funding ?? 0, + inDraftFunding: CANFunding?.in_draft_funding ?? 0, + receivedFunding: CANFunding?.received_funding ?? 0, carryForwardFunding: previousFYfundingSummary?.available_funding ?? 0, subTitle: can?.nick_name ?? "", projectTypesCount,