Payee management¶
Every benefit payment distributes money across one or more payees — worker, beneficiary, employer, attorney, medical/other provider, recovery (overpayment offset), and support (child-support withholding). This operation covers the shared payee base class, the factory that resolves the concrete payee type, automatic-payee generation, and the support-payee sub-flow that reads Oregon DHS support-order data.
| Field | Value |
|---|---|
| Operation ID | OP-CP-04 |
| Trigger | clsPayeeCollection.CreatePayee(payeeType, personID); clsPayeeCollection.GetAutoPayees(dpi, claimData, paymentType, payeeTypes) followed by GenerateAutoPayees |
| Inputs | IDataPortalInfo, BenefitPaymentID, claim data, payee type, person/provider identifiers, payment amount, cash card ID, special handling code |
| Outputs | Persisted CL_BENEFIT_PAYEE rows (plus CL_STATE_AWARD_MONTHLY_PAYEE for award payees); resolved payee name/address; broken-rule/error messages |
| Primary components | clsPayeeBase, clsPayeeFactory, clsPayeeCollection, clsAwardPayeeInfo, clsSupportPayee |
| Entry point | Business\SAIF.Business.Claim.Payment\Payees\clsPayeeBase.vb |
Steps¶
- Select the payee implementation (BR-CP-016).
clsPayeeFactory.GetClaimPayee/NewClaimPayeedispatch by payee type code: Attorney/MedicalProvider/Other/Unknown resolve to the provider payee; Beneficiary, Employer, Recovery, Support, and Worker each have their own class; any unmapped type falls back to the provider payee. - Create a new payee. The base constructor draws a new
BENEFIT_PAYEE_SEQvalue only when the parentBenefitPaymentIDis non-zero, then each subtype'sOnInstantiation()sets its payee type and, for Worker/Beneficiary/Employer, applies default special handling. - Apply default special handling (BR-CP-017).
SetDefaultSpecialHandlingsetsSpecialHandlingCode = "FGNML"when the payment address is valid, non-empty, and foreign; clears it otherwise. Re-evaluated whenever a provider payee's location changes. - Determine automatic payees (BR-CP-021).
GetAutoPayeesevaluates eligibility per type: Worker always; Support only with an active support order (cross-library read ofclsClaimData,SAIF.Business.View.Claim— not yet migrated); Recovery based on non-deferred overpayment balance for TD or any overpayment balance otherwise; Attorney only with a legal-representative provider location; one Medical Provider per non-disputed, non-pharmacy-TPA, non-MCO-plan billing provider (uncatalogued inline read againstCBP_BILL/PHARMACY_TPA/MCO_PLAN); one Beneficiary per claim beneficiary (clsClaimPersonView, same not-yet-migrated library). - Materialize and de-duplicate automatic payees (BR-CP-021).
GenerateAutoPayeescreates each eligible candidate only if no existing payee of the same type (Worker/Support), same type plus provider location (Attorney/Medical Provider), or same type plus person (Beneficiary) already exists; a new Recovery payee's award-info balance is seeded from the claim's current overpayment balance. - Resolve payee name and payment address. Each subtype overrides
PayeeName/PaymentAddress/ValidatePayeeAddresspolymorphically: Worker and Beneficiary read the person's address throughClaimServices.getPersonAddress(SAIF.Business.View.Claim); Employer reads the policyholder address throughclsPolicyAddress(SAIF.Business.View.Policy); Provider and Attorney read throughSAIF.Business.View.Provider'sclsProviderLocation/clsProviderAddress; Recovery uses a fixed "SAIF Recovery" identity with no address validation; Support reads the DHS provider's active address. None of these three view libraries are in the already-migrated set, so their logic is not embedded further here — see each library'soperations-processes.mdfor the address-resolution logic they own. - Provider addresses at a parent location have their lines shifted down one position with the parent's name inserted at line 1; the tax ID used for payment is the parent's when the address is a parent location.
- Validate the payee (BR-CP-017, BR-CP-018, BR-CP-019). A non-award, non-zero payee must have a foreign or complete payment address; a used cash card ID must contain no decimal point and be ≤ 16 digits; a provider payee with a non-zero payment plus monthly amount must reference a valid provider location, and a non-zero immediate payment additionally requires a non-blank tax ID.
- Attach and validate award-info child, if applicable (BR-CP-025).
MonthlyAwardInfolazily loadsCL_STATE_AWARD_MONTHLY_PAYEE(SQL-045, CL_STATE_AWARD_MONTHLY_PAYEE); its rules require the next monthly payment not to exceed the current balance due and require the retro reserve amount not to exceed the monthly payment, both non-negative. - Support-payee sub-flow. See below.
- Save the payee.
Save()returnsFalseimmediately whenBenefitPayeeID = 0— a payee without an assigned parent silently fails to save. - Insert/update the payee. The shared parameter block (SQL-070 covers the related collection-level candidate read; the payee's own insert/update procedures were traced by name —
pk82394_claim_benefit.insertBenefitPayee/updateBenefitPayeeagainstCL_BENEFIT_PAYEE— but have no distinct catalog entry and are described here without a link) is extended per subtype: Worker adds person ID and form flags; Beneficiary adds person ID; Employer sets person ID to the worker's ID; Provider adds provider location IDs (zero coerced to NULL); Support adds the support order ID and the DHS provider location ID.- On a rows-affected value other than 1:
"Insert Failed"(insert) orDBContentionError(update) (BR-CP-001); success saves any materialized award-info and remark children.
- On a rows-affected value other than 1:
- Persist the award-info child (SQL-012 create, SQL-095 update, SQL-118 delete, CL_STATE_AWARD_MONTHLY_PAYEE). The update statement enforces optimistic concurrency in its
WHEREclause; the delete statement carries no update-number guard at all. - Delete the payee.
BeforeDeletefirst cascades a delete of the materialized award-info child, then deletes theCL_BENEFIT_PAYEErow itself; the benefit remark child is not cascaded, a possible orphan.- On a rows-affected value other than 1:
DBContentionError(BR-CP-001).
- On a rows-affected value other than 1:
Support payee sub-flow (clsSupportPayee)¶
- Resolve the active support order. On create,
GetSupportOrderID()reads the worker's untermed support orders (SQL-084, CL_CLAIM_SUPPORT_ORDER) and takes the first row when one exists. - Revalidate on load (BR-CP-020). If the stored order is no longer active (validated via SQL-085/SQL-086), it is silently replaced by the claim's currently active order when one exists.
- Look up the SED account number (SQL-087, CL_CLAIM_SUPPORT_ORDER). Wrapped in a catch-all
Try/Catchthat swallows any lookup error and returns an empty string — a lookup failure is indistinguishable from a genuinely missing SED number. - Format the EDI support-order addendum. Builds a fixed-format
DED*WT/DED*WAstring encoding the multi-order marker, SED account number, issue date, payment amount in cents, worker tax ID digits, and a truncated payee name; any payment type other than TD payment, award, or DCS settlement throws an exception. - Validate the support order requirement (BR-CP-020). A non-award support payee with a positive payment amount requires an active support order on the claim; an award-mode payment accepts a terminated order as long as it is on file.
Cross-library data note:
clsSupportPayeereadsCL_CLAIM_SUPPORT_ORDERdirectly with its own inline SQL and never calls intoSAIF.Business.Claim.Maintenance. That library's already-migrated "Support order management" operation (../../saif-business-claim-maintenance/operations/support-order-management.md) owns the same table from the maintenance side (creating/updating/deleting orders and checking whether a benefit payee references one before allowing changes). This is a candidate cross-library data coupling through a shared table, not a code delegation, so no> Origincallout is used.
Process flow¶
flowchart TD
Start([Payee created or<br>auto-payee pass requested]) --> Type{Payee type?}
Type -->|Attorney/Medical/Other/Unknown| Provider[Provider payee]
Type -->|Beneficiary/Employer/Recovery/Support/Worker| Concrete[Concrete payee class]
Provider --> Addr[Resolve payee name<br>and payment address]
Concrete --> Addr
Addr --> Special[Apply default special handling:<br>foreign address to FGNML]
Special --> Validate{Address, cash card,<br>provider rules pass?}
Validate -->|No| Blocked([Broken rules returned])
Validate -->|Yes| Award{Award-info<br>materialized?}
Award -->|Yes| AwardRules{Monthly payment<br>within balance?}
AwardRules -->|No| AwardBlocked([Broken rule])
AwardRules -->|Yes| Save
Award -->|No| Save[Save payee]
Save --> HasId{BenefitPayeeID<br>assigned?}
HasId -->|No| SilentFail([Save returns False,<br>no error added])
HasId -->|Yes| Persist[Insert/update CL_BENEFIT_PAYEE]
Persist --> Rows{RowsAffected = 1?}
Rows -->|No| Contention([Insert Failed / DB contention error])
Rows -->|Yes| Done([Payee persisted;<br>award-info/remark children saved])
Failure and edge-case behavior¶
- A payee constructed without a parent payment silently fails to save (
Save()returnsFalse, no error message). clsAwardPayeeInfo's delete statement carries no update-number guard, unlike its insert and update, so a stale-object delete cannot be detected as contention.- Deleting a payee does not cascade to its benefit remark child, only to its award-info child — a remark row can be orphaned.
SEDAccountNumber's catch-all exception handling makes a database error indistinguishable from a genuinely missing SED number.clsBeneficiaryPayee.BeforeRetrieveis overridden but unreachable in practice, because the baseCriteria.RetrieveSQLalways throwsInvalidSQLProperty— payees must be loaded through the parent payment's collection, not individually.Rule_CheckValidPersonIDon the beneficiary payee is registered under the property name"BeneficiaryID"even though the actual property isPersonID— a likely defect in rule wiring that may cause the broken-rule message to attach to the wrong field.- Read-only "candidate" payees built by
InstantiateReadonlyPayeeare assigned a process-wide static incrementing ID unrelated to the realBENEFIT_PAYEE_SEQsequence.
Related documentation¶
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\Payees\clsPayeeBase.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsPayeeCollection.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsPayeeFactory.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsAwardPayeeInfo.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsSupportPayee.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsWorkerPayee.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsBeneficiaryPayee.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsEmployerPayee.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsProviderPayee.vbBusiness\SAIF.Business.Claim.Payment\Payees\clsRecoveryPayee.vbBusiness\SAIF.Business.Claim.Payment\Payees\IPayee.vb