---
title: Credit-card payment approval
summary: Presents a supervisor with the credit-card purchases charged by one EAIP specialist and applies an approve-or-return decision to each selected expense.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.EAIP
reference_type: package
operation_id: OP-EAIP-04
trigger: Supervisor calls clsCreditCardPayment.GetCreditPayments for an EAIP user, selects rows, and calls SaveChanges(approved)
---

# Credit-card payment approval

Purchases charged to a SAIF credit card cannot be paid on the specialist's own authority: entering a credit-card transaction date forces the expense into supervisor approval regardless of amount. This operation is the supervisor's side of that gate. It reads the credit-card purchases charged by one EAIP user, shows the supervisor a review list stamped with their own formatted name, and applies a single approve-or-return decision to every row the supervisor selected — advancing approved expenses to `PAY` and returning the rest to `NEW`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-EAIP-04 |
| **Trigger** | Supervisor invokes `clsCreditCardPayment.GetCreditPayments(dpi, eaipUser)`, sets `SupervisorApproved` on the rows to act on, and calls `clsCreditCardPayments.SaveChanges(approved)` |
| **Inputs** | `IDataPortalInfo` (acting supervisor), the EAIP user whose credit-card charges are under review, the per-row supervisor-approved selections, and the single approved/returned decision flag |
| **Outputs** | A read-only review collection carrying claim number, worker name, expense type, description, paid amount, credit-card paid date, vendor invoice number, and update number; expense status updates to `PAY` or `NEW`; the total of the selected amounts; a DB contention error when a status update affects no rows |
| **Primary components** | `clsCreditCardPayment` (`Fill`, `Load`, `UpdateExpenseStatus`), `clsCreditCardPayments` (`SaveChanges`, `TotalIncluded`, `Approver`), `clsPurchase` (`CreditCardPaidDate` setter, `NeedsApproval` override) |
| **Entry point** | `Business\SAIF.Business.Claim.EAIP\Period\clsCreditCardPayments.vb` |

## Steps

1. **Stamp the charge on the purchase.** Upstream of this operation, the specialist sets `CreditCardPaidDate` on a purchase. That setter also writes the acting user's identifier, upper-cased, into `CreditCardUser` — or clears it when the date is removed. Those two columns are what the review query filters on, so a purchase charged by one user never appears in another user's review list ([BR-PROC-003](../business-rules.md#br-proc-003-return-only-credit-card-purchases-paid-for-the-selected-eaip-user)).
2. **Force the expense into approval.** `clsPurchase.NeedsApproval()` returns true immediately — before any authority, adjustment, or limit checks — when a credit-card transaction date is present and the purchase sits in `Pay` status, adding the warning "Supervisor approval is required when credit card trans date is entered." The expense save path then demotes the purchase to `PendingSupervisorApproval` ([BR-PROC-006](../business-rules.md#br-proc-006-credit-card-purchases-always-require-supervisor-approval)). Full detail of that gate is on [Expense lifecycle and payment approval](expense-lifecycle.md).
3. **Open the review list.** `GetCreditPayments(inDPI, inEaipUser)` creates the collection and loads it from `pk82500_EAIP.credit_payment_select`, binding only the EAIP user whose charges are being reviewed ([Details](../procedures/pk82500_eaip.md#credit_payment_select)). The package restricts the result to purchases with a non-null credit-card paid date whose credit-card user matches the parameter ([BR-PROC-003](../business-rules.md#br-proc-003-return-only-credit-card-purchases-paid-for-the-selected-eaip-user)).
4. **Resolve the approver's display name.** Before loading the rows, the factory reads the acting supervisor's employee record by mail identifier, sets the name format to standard-first, and stores the formatted name on the collection as `Approver`. That value exists purely so the review screen and any printed approval record show who reviewed the charges rather than a mail identifier.*
5. **Materialize the rows.** Each row is filled into a `clsCreditCardPayment` carrying the credit-card paid date, claim number, worker name, expense type description, paid amount, expense identifier, item description, vendor invoice number, and — critically — the current update number, which is the row version the later status update will compare against. Each item is then marked old. All properties except the supervisor-approved flag are read-only: this object cannot edit an expense, only decide it.
6. **Select rows and total them.** The supervisor sets `SupervisorApproved` on the rows to act on. `TotalIncluded` sums the paid amounts of exactly those rows, giving the supervisor the value of the batch they are about to release.
7. **Apply the decision.** `SaveChanges(inApproved)` resolves one status code for the whole batch — `PAY` when approved, `NEW` when not — and then loops the collection, acting only on rows the supervisor selected. A returned (not approved) charge therefore goes back to `NEW` for the specialist to rework rather than being deleted or denied.
8. **Update each expense status.** For each selected row, `UpdateExpenseStatus(newStatusCode)` builds a scalar stored-procedure request against `pk82500_eaip.update_expense_status` and binds the expense identifier, the new status code, the acting supervisor as update user, the current timestamp, the incremented update number, the original update number as `LastUpdateNum`, and a `RowsAffected` return parameter ([SQL-027](../sql/SQL-027.md), [Details](../procedures/pk82500_eaip.md#update_expense_status)). The package updates only when the stored update number still matches, and returns the affected row count ([BR-PROC-001](../business-rules.md#br-proc-001-protect-expense-status-changes-with-optimistic-concurrency)).
9. **Stop on the first contention failure.** `UpdateExpenseStatus` returns true only when exactly one row was affected. On anything else, `SaveChanges` adds an `UpdateError` carrying the DB contention message to the collection and returns false immediately — the remaining selected rows are not attempted, so a partially applied batch is possible.
10. **Refresh.** Because the collection holds the update numbers captured at load time and does not re-read them after a successful update, the caller must reload through `GetCreditPayments` before acting on the same rows again; a second decision applied to the stale collection compares an out-of-date update number and fails the concurrency check.*

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start([Specialist enters credit card<br>transaction date on a purchase]) --> Stamp[CreditCardUser set to<br>acting user, upper-cased]
	Stamp --> Force[NeedsApproval returns true;<br>purchase demoted to<br>PendingSupervisorApproval]
	Force --> Open([Supervisor opens review list])
	Open --> Emp[Resolve supervisor employee record;<br>store formatted name as Approver]
	Emp --> Load[credit_payment_select filtered by<br>credit card user and non-null paid date]
	Load --> Rows[Rows filled read-only with<br>amount, description, update number]
	Rows --> Select[Supervisor sets SupervisorApproved<br>on chosen rows; TotalIncluded sums them]
	Select --> Decide{Approved?}
	Decide -->|Yes| Code[New status code = PAY]
	Decide -->|No| Code2[New status code = NEW]
	Code --> Loop[For each selected row:<br>update_expense_status with<br>LastUpdateNum check]
	Code2 --> Loop
	Loop --> Rows1{RowsAffected = 1?}
	Rows1 -->|No| Stop([UpdateError with DB contention message;<br>remaining rows not attempted])
	Rows1 -->|Yes| Next{More selected rows?}
	Next -->|Yes| Loop
	Next -->|No| Done([Batch applied; caller must reload<br>before acting again])
```

## Failure and edge-case behavior

- The batch is not transactional in this code. `SaveChanges` returns false at the first contention failure, leaving every earlier row already updated and every later row untouched.
- Update numbers are captured when the list is loaded and never refreshed. Any change to an expense between load and decision — including one made by the specialist in another session — invalidates the pending decision for that row.
- The item class registers no business rules at all; its rule-registration method is empty. Nothing in this operation re-checks authority, amount limits, or expense type, so the decision rests entirely on the package's filter and the supervisor's judgement.
- Both criteria classes throw an invalid-statement exception from every statement property, so no create, retrieve, update, or delete can be issued through the ordinary data-portal path; the only database writes are the explicit status-update calls, and the only read is the package select.
- Returning a charge sets the expense back to `NEW`, which clears it from the supervisor's queue but leaves the credit-card transaction date and credit-card user in place; unless the specialist clears them, the expense re-enters the same queue on the next attempt to pay.*
- A row that the supervisor did not select is skipped entirely, so an unattended row stays in `PendingSupervisorApproval` indefinitely.

## Related documentation

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.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.EAIP\Period\clsCreditCardPayments.vb`
- `Business\SAIF.Business.Claim.EAIP\Period\clsPurchase.vb`
- `Business\SAIF.Business.Claim.EAIP\Period\clsExpense.vb`
