Skip to content

feat(OPS-5772): enforce BLI obligate-by date within SC Period of Performance#5828

Draft
rajohnson90 wants to merge 26 commits into
mainfrom
OPS-5772/obligate-by-date-logic
Draft

feat(OPS-5772): enforce BLI obligate-by date within SC Period of Performance#5828
rajohnson90 wants to merge 26 commits into
mainfrom
OPS-5772/obligate-by-date-logic

Conversation

@rajohnson90

@rajohnson90 rajohnson90 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

What changed

Enforces that a Budget Line Item's Need By Date (obligate-by date) falls within the Period of Performance (PoP) window defined by the agreement's Service Components, and prevents SC edits that would push non-draft BLIs outside that window.

backend/ops_api/ops/services/budget_line_items.py

  • Refactored the monolithic _validation_change_status_higher_than_draft into three focused helpers: _validate_agreement_for_status_change, _validate_amount_and_date_for_status_change, and _validate_date_within_sc_window.
  • _validate_date_within_sc_window: for non-superusers, rejects any non-DRAFT BLI whose date_needed falls outside [sc_start_date, sc_end_date] (inclusive). Both bounds are optional — a missing bound places no constraint.

backend/ops_api/ops/services/services_component.py

  • _validate_bli_pop_window: called on SC period_start/period_end updates; rejects the change if any non-draft BLI would fall outside the resulting aggregate window across all SCs.
  • _validate_bli_pop_window_for_create: called when a new SC is created with dates; same check against the combined window of new + existing SCs.

backend/models/agreements.py

  • sc_start_date property: min(period_start) across all SCs (None if no SC has a start date).
  • sc_end_date property: max(period_end) across all SCs (None if no SC has an end date).

frontend/src/components/BudgetLineItems/BudgetLinesForm/datePickerSuite.js

  • Adds a third needByDate Vest test: "Date must fall within the agreement's period of performance." Guards on scStartDate/scEndDate being present and uses string comparison on YYYY-MM-DD to avoid timezone issues. Each bound is independently optional (one-sided window supported).

frontend/src/components/BudgetLineItems/BudgetLinesForm/BudgetLinesForm.jsx

  • Accepts scStartDate and scEndDate props, passes them through to the date picker suite on every run. A useEffect re-runs the suite whenever the SC window changes so the PoP error appears without the user touching the date field.

frontend/src/components/ServicesComponents/ServicesComponentForm/suite.js

  • New Vest suite. Validates servicesComponentSelect is non-zero. In add/edit mode, computes the aggregate SC window from allServicesComponents (pre-merged with live form values by the hook) and rejects changes that would leave any non-draft BLI outside the window. Each bound guards independently — absent bound = no constraint.

frontend/src/components/ServicesComponents/ServicesComponentForm/ServicesComponentForm.jsx

  • Wires up scFormSuite, allServicesComponentsForSuite, and nonDraftBudgetLines props. Runs the suite on every form-data change and surfaces errors on servicesComponentSelect, popStartDate, and popEndDate fields. SC number field errors only shown after the field has been touched.

frontend/src/components/ServicesComponents/ServicesComponents.hooks.js

  • Accepts scFormSuite param. Computes allServicesComponentsForSuite (memos the SC array with live form dates merged in for the SC being edited). Blocks handleSubmit if the suite has errors. Resets the suite on cancel.

frontend/src/components/CreateBLIsAndSCs/CreateBLIsAndSCs.jsx / .hooks.js

  • Threads scStartDate/scEndDate from the agreement's SC window down to BudgetLinesForm.

backend/openapi.yml

  • Adds response body schemas for SC PUT/PATCH/DELETE endpoints; adds 403 responses; corrects ServicesComponent schema (nullable fields, proper required placement).

frontend/src/types/AgreementTypes.d.ts

  • Adds sc_start_date and sc_end_date to the Agreement type.

Tests

  • test_budget_line_item.py: SC window boundary tests (before/on/after start and end), one-sided window (start-only, end-only), undefined window (multi-SC all-null), DRAFT→PLANNED transitions under each scenario, superuser bypass.
  • test_budget_line_item_power_user.py: Superusers can set date_needed outside the SC PoP window.
  • test_services_component.py: SC update/create validations that protect existing non-draft BLIs.
  • datePickerSuite.test.js / BudgetLinesForm.test.jsx: PoP boundary validation in the BLI date picker.
  • ServicesComponentForm/suite.test.js: Full suite — required-field validation, add/edit mode PoP checks, one-sided windows, undefined windows, two-SC scenarios, draft BLI exemption.
  • Cypress E2E: updated selectors/assertions for the revised BLI and agreement workflows.

Issue

#5772

How to test

Backend (from backend/ops_api/):

# Full BLI validation suite
pipenv run pytest tests/ops/budget_line_items/test_budget_line_item.py -v

# SC window protection
pipenv run pytest tests/ops/services_components/test_services_component.py -v

# Superuser bypass
pipenv run pytest tests/ops/budget_line_items/test_budget_line_item_power_user.py -v

Frontend (from frontend/):

bun run test --watch=false src/components/BudgetLineItems/BudgetLinesForm/datePickerSuite.test.js
bun run test --watch=false src/components/ServicesComponents/ServicesComponentForm/suite.test.js
bun run test --watch=false src/components/BudgetLineItems/BudgetLinesForm/BudgetLinesForm.test.jsx

Manual (UI)

  1. Create a Contract agreement with a Service Component (SC) with period_start = 2025-01-01 and period_end = 2025-12-31.
  2. Add a non-draft BLI. Verify that setting date_needed outside [2025-01-01, 2025-12-31] shows an inline error and blocks save.
  3. Verify that a date inside the window saves successfully.
  4. Edit the SC and try to shrink period_end to before the BLI's date_needed. Verify the form shows an error and blocks the save.
  5. As a superuser, verify that the PoP-bounds check is bypassed and any date_needed is accepted.

A11y impact

  • No accessibility-impacting changes in this PR
  • Accessibility changes included and validated against WCAG 2.1 AA intent
  • Any temporary suppression includes A11Y-SUPPRESSION metadata (owner, expires, rationale)

Storybook

  • No UI component changes in this PR

Screenshots

Please add screenshots showing the inline validation error on the BLI date picker and the SC form PoP error.

Definition of Done Checklist

  • OESA: Code refactored for clarity
  • OESA: Dependency rules followed
  • Automated unit tests updated and passed
  • Automated integration tests updated and passed
  • Automated quality tests updated and passed
  • Automated load tests updated and passed
  • Automated a11y tests updated and passed
  • Automated security tests updated and passed
  • 90%+ Code coverage achieved
  • Form validations updated

Links

N/A

@rajohnson90 rajohnson90 marked this pull request as draft June 18, 2026 13:12
@rajohnson90 rajohnson90 changed the title SC and BLI Validation Logic test: validate obligate-by date PoP-bounds for one-sided and undefined SC windows Jul 7, 2026
@rajohnson90

Copy link
Copy Markdown
Contributor Author

Hold on sorry I messed up. The description as is is NOT correct.

@rajohnson90 rajohnson90 changed the title test: validate obligate-by date PoP-bounds for one-sided and undefined SC windows feat(OPS-5772): enforce BLI obligate-by date within SC Period of Performance Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant