---
title: Benefit period and schedule management
summary: Maintains the dated benefit periods, the future-payment schedule, and the payee-level pay remark that describe what a benefit payment covers.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.Payment
reference_type: embedded
operation_id: OP-CP-03
trigger: A benefit payment aggregate (TD, vocational, or supplemental payment, or an award) is created, retrieved, edited, or saved
---

# Benefit period and schedule management

Every recurring benefit payment is described by one or more dated periods, an optional future-payment schedule, and a single pay remark. This operation covers the shared classes those payments (documented separately under [Timeloss payment issuance](timeloss-payment-issuance.md) and the award operations) build on to describe what is being paid and when.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CP-03 |
| **Trigger** | `clsBenefitPeriodCollection.GetClaimBenefitPeriodCollection(dpi, claimID, benefitPaymentID)`; `clsBenefitSchedule.GetClaimBenefitSchedule(...)`; `clsBenefitRemark.GetBenefitRemark(dpi, benefitPayeeSeqNum)` |
| **Inputs** | `IDataPortalInfo`, `ClaimID`, `BenefitPaymentID`, `BenefitPayeeSeqNum`, `NextIssueDate` (schedule-by-date lookup), user-entered dates/amounts/day counts/remark code |
| **Outputs** | Persisted rows via `pk82394_claim_benefit` procedures; in-memory collections with aggregate totals; a projected payment list; broken-rule/error messages |
| **Primary components** | `clsBenefitPeriod`, `clsBenefitPeriodCollection`, `clsBenefitSchedule`, `clsBenefitScheduleCollection`, `clsBenefitRemark` |
| **Entry point** | `Business\SAIF.Business.Claim.Payment\Periods\clsBenefitPeriod.vb` |

## Steps

1. **Retrieve the period collection** ([SQL-047](../sql/SQL-047.md)). `GetClaimBenefitPeriodCollection` and `NewClaimBenefitPeriodCollection` both call `RetrieveObject` — "New" does not create an empty in-memory collection, it re-reads the database. (`CL_BENEFIT_PERIOD` is not separately catalogued in `database-tables.md`.)
2. **Hydrate each period.** `Load`/`Fill` map amounts, dates, interest, offset, retro-reserve, and TD-day fields, then derive display text from the payment category (TD, VocTD, SupTD).
3. **Create a new period** ([SQL-004](../sql/SQL-004.md), [DUAL](../database-tables.md#dual)). `CreatePeriod()` obtains a new sequence value only when the parent `BenefitPaymentID` is non-zero — a period created without a parent payment gets no identity.
4. **Load previously-paid history for the period** ([BR-CP-013](../business-rules.md#br-cp-013-previously-paid-history-requires-exact-gap-free-coverage)). `LoadPreviousPaidProperites` reads matching paid history from `CL_FINANCIAL_TRANS`/`CL_FINANCIAL_TRANS_DTL` (uncatalogued inline query, described here without a SQL-ID link since no matching catalog entry exists), excluding penalty and interest-cost transactions; supplemental periods are matched by reimbursement program `7` instead of payment category. A match is only honored when the paid periods' minimum begin date and maximum end date exactly equal the period's from/thru dates with no day gaps between them — otherwise previously-paid amount and days are both zeroed.
5. **Validate the period** ([BR-CP-011](../business-rules.md#br-cp-011-benefit-period-amount-must-reconcile-to-td-plus-interest-minus-previously-paid)). Seven rules run: benefit-amount reconciliation, from-date on or before thru-date, TD days and previous TD days in half-day increments, zero TD days for supplemental periods, and two previously-paid-consistency warnings.
6. **Check for overlapping periods** ([BR-CP-012](../business-rules.md#br-cp-012-overlapping-benefit-periods-of-the-same-category-are-invalid)). `clsBenefitPeriodCollection.HasOverlappingPeriods` flags any two periods sharing a payment category whose date ranges overlap; `PeriodsMatchingDates` separately finds periods of a *different* category with identical dates.
7. **Save a period.** `Save()` returns `False` immediately when `BenefitPeriodID = 0` rather than attempting a database call.
8. **Insert/update/delete the period.** All three paths call `pk82394_claim_benefit` procedures against `CL_BENEFIT_PERIOD` ([SQL-014](../sql/SQL-014.md) create, [SQL-097](../sql/SQL-097.md) update, [SQL-120](../sql/SQL-120.md) delete).
   - On a rows-affected value other than 1: `"Insert Failed"` (insert) or `DBContentionError` (update/delete) ([BR-CP-001](../business-rules.md#br-cp-001-optimistic-concurrency-on-all-stored-procedure-based-operations)).
9. **Retrieve the schedule** ([SQL-050](../sql/SQL-050.md)). A multi-row result silently keeps only the last row loaded, since each `Fill` overwrites the object in place. (`CL_BENEFIT_SCHEDULE` is not separately catalogued in `database-tables.md`.)
10. **Retrieve the schedule collection.** Branches between a claim-scoped procedure and a next-issue-date-scoped procedure depending on which parameter is populated; the DPI-only overload leaves both unset and falls into the by-date branch with an empty date.
11. **Build a payment schedule** ([BR-CP-014](../business-rules.md#br-cp-014-scheduled-future-payments-are-capped-and-roll-to-the-next-work-day)). `CreateSchedule`/`BuildPaymentProjections` computes the from/thru/issue date for each future payment; every issue date after the first rolls back to the previous work day, while the first is never adjusted.
12. **Validate the schedule** ([BR-CP-014](../business-rules.md#br-cp-014-scheduled-future-payments-are-capped-and-roll-to-the-next-work-day)). `Rule_CheckMaxFuturePayments` rejects a schedule whose remaining payment count exceeds the configured maximum (14 when loaded through the collection); the broken rule carries an empty message.
13. **Insert/update/delete the schedule** ([SQL-016](../sql/SQL-016.md) create, [SQL-099](../sql/SQL-099.md) update, [SQL-122](../sql/SQL-122.md) delete).
    - On a rows-affected value other than 1: `"Insert Failed"` / `DBContentionError` ([BR-CP-001](../business-rules.md#br-cp-001-optimistic-concurrency-on-all-stored-procedure-based-operations)). Unlike the period, a successful update does not advance the update number or mark the object clean — a likely defect.
14. **Retrieve the pay remark** ([SQL-049](../sql/SQL-049.md)). Missing remarks are not an error; `AfterRetrieve` seeds a new, empty remark placeholder.
15. **Validate and save the remark** ([BR-CP-015](../business-rules.md#br-cp-015-a-blank-benefit-remark-code-deletes-the-remark-a-non-blank-code-must-be-active)). A non-blank code must exist among unexpired rows in the remark-code table ([SQL-048](../sql/SQL-048.md)); saving with a blank code marks the remark deleted instead of updating it.
16. **Insert/update/delete the remark** ([SQL-015](../sql/SQL-015.md) create, [SQL-098](../sql/SQL-098.md) update, [SQL-121](../sql/SQL-121.md) delete).
    - On a rows-affected value other than 1: `"Insert Failed"` / `DBContentionError` ([BR-CP-001](../business-rules.md#br-cp-001-optimistic-concurrency-on-all-stored-procedure-based-operations)).

## Process flow

```mermaid
flowchart TD
	Start([Benefit payment aggregate<br>created, retrieved, or saved]) --> Which{Period, schedule,<br>or remark?}
	Which -->|Period| Retr[Retrieve/create period]
	Retr --> Prev[Load previously-paid history:<br>exact, gap-free match required]
	Prev --> Validate{Period rules pass?}
	Validate -->|No| Blocked([Broken rules returned])
	Validate -->|Yes| Overlap{Overlaps another<br>same-category period?}
	Overlap -->|Yes| Flag([Caller warned/blocked])
	Overlap -->|No| SavePeriod[Insert/update/delete period]
	Which -->|Schedule| BuildSched[Build payment projections:<br>roll issue dates to work days]
	BuildSched --> MaxCheck{RemainingPaymentCount<br>within max?}
	MaxCheck -->|No| SchedBlocked([Broken rule, empty message])
	MaxCheck -->|Yes| SaveSched[Insert/update/delete schedule]
	Which -->|Remark| RemarkCode{Remark code blank?}
	RemarkCode -->|Yes| DeleteRemark[Mark remark deleted]
	RemarkCode -->|No| CheckCode{Code active<br>and on file?}
	CheckCode -->|No| RemarkBlocked([Broken rule: invalid remark])
	CheckCode -->|Yes| SaveRemark[Insert/update remark]
	SavePeriod --> Rows{RowsAffected = 1?}
	SaveSched --> Rows
	SaveRemark --> Rows
	Rows -->|No| Contention([Insert Failed / DB contention error])
	Rows -->|Yes| Done([Persisted])
```

## Failure and edge-case behavior

- A period created without a parent payment (`BenefitPaymentID = 0`) never gets a sequence value and `Save()` silently returns `False` — no error message is added.
- The schedule's successful update path does not call `MarkOld()` or advance the update number, unlike the period and remark equivalents — repeated saves against the same in-memory object may re-send a stale update number.
- `clsBenefitPeriodCollection` and `clsBenefitScheduleCollection` are strictly retrieve-only; their `Criteria` throws `InvalidSQLProperty` for Create/Insert/Update/Delete — each period/schedule instance owns its own persistence.
- `HasPreviouslyPaid3DW` and the underlying claim-status checks depend on `clsClaimData` (`SAIF.Business.View.Claim`), which is not yet migrated to the new documentation format; see that library's `operations-processes.md` for the three-day-wait claim context this method reads.

## Related documentation

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.md)
- [Database tables](../database-tables.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Sources

- `Business\SAIF.Business.Claim.Payment\Periods\clsBenefitPeriod.vb`
- `Business\SAIF.Business.Claim.Payment\Periods\clsBenefitPeriodCollection.vb`
- `Business\SAIF.Business.Claim.Payment\Payments\TimelossPayments\clsBenefitSchedule.vb`
- `Business\SAIF.Business.Claim.Payment\Payments\TimelossPayments\clsBenefitScheduleCollection.vb`
- `Business\SAIF.Business.Claim.Payment\clsBenefitRemark.vb`
