Skip to content

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

  1. Select the payee implementation (BR-CP-016). clsPayeeFactory.GetClaimPayee/NewClaimPayee dispatch 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.
  2. Create a new payee. The base constructor draws a new BENEFIT_PAYEE_SEQ value only when the parent BenefitPaymentID is non-zero, then each subtype's OnInstantiation() sets its payee type and, for Worker/Beneficiary/Employer, applies default special handling.
  3. Apply default special handling (BR-CP-017). SetDefaultSpecialHandling sets SpecialHandlingCode = "FGNML" when the payment address is valid, non-empty, and foreign; clears it otherwise. Re-evaluated whenever a provider payee's location changes.
  4. Determine automatic payees (BR-CP-021). GetAutoPayees evaluates eligibility per type: Worker always; Support only with an active support order (cross-library read of clsClaimData, 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 against CBP_BILL/PHARMACY_TPA/MCO_PLAN); one Beneficiary per claim beneficiary (clsClaimPersonView, same not-yet-migrated library).
  5. Materialize and de-duplicate automatic payees (BR-CP-021). GenerateAutoPayees creates 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.
  6. Resolve payee name and payment address. Each subtype overrides PayeeName/PaymentAddress/ValidatePayeeAddress polymorphically: Worker and Beneficiary read the person's address through ClaimServices.getPersonAddress (SAIF.Business.View.Claim); Employer reads the policyholder address through clsPolicyAddress (SAIF.Business.View.Policy); Provider and Attorney read through SAIF.Business.View.Provider's clsProviderLocation/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's operations-processes.md for the address-resolution logic they own.
  7. 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.
  8. 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.
  9. Attach and validate award-info child, if applicable (BR-CP-025). MonthlyAwardInfo lazily loads CL_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.
  10. Support-payee sub-flow. See below.
  11. Save the payee. Save() returns False immediately when BenefitPayeeID = 0 — a payee without an assigned parent silently fails to save.
  12. 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/updateBenefitPayee against CL_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) or DBContentionError (update) (BR-CP-001); success saves any materialized award-info and remark children.
  13. 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 WHERE clause; the delete statement carries no update-number guard at all.
  14. Delete the payee. BeforeDelete first cascades a delete of the materialized award-info child, then deletes the CL_BENEFIT_PAYEE row itself; the benefit remark child is not cascaded, a possible orphan.
    • On a rows-affected value other than 1: DBContentionError (BR-CP-001).

Support payee sub-flow (clsSupportPayee)

  1. 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.
  2. 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.
  3. Look up the SED account number (SQL-087, CL_CLAIM_SUPPORT_ORDER). Wrapped in a catch-all Try/Catch that swallows any lookup error and returns an empty string — a lookup failure is indistinguishable from a genuinely missing SED number.
  4. Format the EDI support-order addendum. Builds a fixed-format DED*WT/DED*WA string 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.
  5. 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: clsSupportPayee reads CL_CLAIM_SUPPORT_ORDER directly with its own inline SQL and never calls into SAIF.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 > Origin callout 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() returns False, 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.BeforeRetrieve is overridden but unreachable in practice, because the base Criteria.RetrieveSQL always throws InvalidSQLProperty — payees must be loaded through the parent payment's collection, not individually.
  • Rule_CheckValidPersonID on the beneficiary payee is registered under the property name "BeneficiaryID" even though the actual property is PersonID — 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 InstantiateReadonlyPayee are assigned a process-wide static incrementing ID unrelated to the real BENEFIT_PAYEE_SEQ sequence.

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.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsPayeeCollection.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsPayeeFactory.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsAwardPayeeInfo.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsSupportPayee.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsWorkerPayee.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsBeneficiaryPayee.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsEmployerPayee.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsProviderPayee.vb
  • Business\SAIF.Business.Claim.Payment\Payees\clsRecoveryPayee.vb
  • Business\SAIF.Business.Claim.Payment\Payees\IPayee.vb