DCBS bill generation and billing records¶
SAIF pays EAIP expenses first and bills the Department of Consumer and Business Services for reimbursement afterwards. This operation is that billing run. It gathers the paid, unbilled expenses on one service period, decides whether the bill is initial, subsequent, amended, or corrected, attaches the administrative fee to the first bill only, assembles the DCBS billing XML (with a cover letter for amendments and corrections), writes the EAIP_BILLING header and one EAIP_EXPENSE_BILLING line per expense, and flips each billed expense to Billed or Reimbursed. The clsBilling and clsExpenseBilling persistence classes are documented here rather than separately because they exist to serve this run and its reconciliation counterpart.
| Field | Value |
|---|---|
| Operation ID | OP-EAIP-06 |
| Trigger | clsGenerateBill.NewBillGenerator(dpi) followed by ProcessBills(periodId, reportNode, xPrintDocCollection) from the batch print process, or GenerateBill(period, billAmended:=True) for an on-demand amended or corrected bill |
| Inputs | Service period identifier; the period's paid purchases and wage subsidies; existing billings for the period; the admin-fee limit amount from the EAIP type code table; the claims.eaip.fs2360.rate configuration setting; worker, claim, and policy data; the print-document collection and report node |
| Outputs | An EAIP_BILLING row with type code, billed date, billed amount, and admin fee; one EAIP_EXPENSE_BILLING row per billed expense; expense status transitions to Billed or Reimbursed with appended history rows; a DCBS billing XML document plus an optional cover-letter fragment; a print-document entry with file name, sort values, duplex mode, and an image-import index record; an empty string when nothing is billable |
| Primary components | clsGenerateBill (ProcessBills, GenerateBill, GetBillXml, GenCoverLetter, GetDetail, GetPolicyName, GetPeriodEndDate, GetOriginalBillDate, DeriveNcciPolicyNumber, UpdateOriginalBill, SetupImageRecord, FindBill), clsBilling (Create, AddInsertUpdateParms, GetBillings, GetBillingsByPeriod, GetUnreconciledAdminFees), clsExpenseBilling and clsExpenseBillingCollection, UnreconciledAdminFees |
| Entry point | Business\SAIF.Business.Claim.EAIP\Billing\clsGenerateBill.vb |
Steps¶
- Enter the run.
ProcessBills(inPeriodId, inReportNode, inXPrintDocColl)retrieves the service period, adds a print document to the collection, and callsGenerateBillwith the defaultbillAmendedof false — the batch mode.GenerateBill(period, billAmended:=True)is the on-demand path used when a specialist explicitly bills an amendment or correction. - Load the period's existing bills.
clsBilling.GetBillingsByPeriod(dpi, periodID)reads every prior bill for the period throughPk82500_eaip.GetBillingsByPeriod(SQL-018, Details). These rows determine whether the admin fee has already been charged and, for amendments, which bill the amended expense originally appeared on. - Detect amendments. Two filters select paid expenses that carry a non-zero original-expense identifier: paid wage subsidies and paid purchases that amend or correct an earlier expense. If either filter matches, the run is an amendment run: the original expense identifier of the first match is captured, and the entered bill type is
A(amended) when any expense on the period sits inAmendedstatus, otherwiseC(corrected). - Skip amendments during the batch run. When
billAmendedis false and amendments were detected, the method returns an empty string immediately. The batch never bills amendments; they are billed only through the explicit on-demand path (BR-PROC-011). - Create the bill header.
clsBilling.NewBilling(dpi)runsCreate(), which takes the next value ofeaip_billing_seq, defaults the type code toI, and records the create stamp (SQL-001, EAIP_BILLING_SEQ). The claim identifier and period identifier are copied from the service period. - Decide the bill type and the admin fee. The previously charged admin fee is the sum of
AdminFeeAmountacross the period's existing bills. The decision then splits four ways (BR-PROC-010): - No amendments, no prior admin fee → type
I(initial); the admin fee is taken from the first admin-fee entry in the EAIP type code table and seeded as the opening billed amount. - No amendments, admin fee already charged → type
S(subsequent); no admin fee. - Amendments, no prior admin fee → the amendment is treated as an original (
treatAmendasOrig), typeI, admin fee charged. - Amendments, admin fee already charged →
FindBill(originalExpenseId)walks the loaded bills looking for one whose expense-billing lines include the original expense. If found, the bill takes the entered type (AorC); if not found, it becomes typeSand is treated as an original. - Re-select the expenses to bill. The billed date is set to today, and the wage-subsidy and purchase filters are re-run without the original-expense condition, so the bill covers every paid expense on the period. If both collections are empty, the method returns an empty string and nothing is written.
- Accumulate the billed amount. For each paid wage subsidy and purchase, the bill accumulates either
PaidAmount + WriteOffAmount(when the amendment is treated as an original, because the full value must be re-billed) orAmountToBill(the amendment-aware net, which is the adjustment amount plus write-off for an amendment). Note that in the treat-as-original branch the first wage subsidy assigns rather than adds to the billed amount, discarding the admin fee seeded in step 6 and any earlier accumulation.* - Generate the cover letter for amendments. When the on-demand path produced an
AorCbill,GenCoverLetter(billType)computes the net adjustment as the summed amount-to-bill across paid wage subsidies and purchases, and the requested day change as the summed reportable days (each amendment's days minus its original's days). Then: - positive adjustment → "Additional reimbursement of n is requested.";
- negative adjustment → the sign is flipped and
UpdateOriginalBillis called to determine whether the administrative fee must also be refunded; the refund is added to the amount and the closing reads "A check for n is enclosed.", noting the admin fee when one was refunded; - the day change appends "This amendment/correction adds/deducts n days to/from the original request."
The cover XML carries the original bill date — read by an inline join of
EAIP_BILLINGtoEAIP_EXPENSE_BILLINGon the original expense identifier — the closing text, a wage flag, and paired Original/Revised detail rows for each amended wage subsidy and purchase, each reading its original throughGetOriginalWageSubsidy/GetOriginalPurchase. - Refund the administrative fee when the amendment reverses the whole bill.
UpdateOriginalBill(expenseId, adjAmount)reads the bill identifier, billed amount, and admin fee for the bill carrying that expense where the admin fee is positive. It refunds only when the billed amount minus the admin fee exactly equals the adjustment being credited — that is, when the amendment reverses the entire non-fee content of the original bill. In that case an inline update zeroesADMIN_FEE_AMOUNT, nullsAF_EAIP_REIMB_DETAIL_SEQ_NUM, stampssysdate, incrementsupdate_num, and sets the update user; otherwise it returns zero and changes nothing. This inline update deliberately does not check the prior update number, so it overwrites concurrent changes to the original bill.* - Build the billing XML.
GetBillXml(newBill)computes four totals — worksite-modification total, other-purchase total, wage gross paid, and wage total (each as paid amount plus write-off) — and the request total as wages plus purchases plus worksite modifications plus the admin fee. The calculation factor shown on the bill comes from theclaims.eaip.fs2360.rateconfiguration setting when wages are present, and otherwise falls back to the wage-subsidy rate lookup evaluated at the minimum date. The document element carries banner and routing text, print-document sequence and group, worker name/birth date/tax ID/address, injury date, WCD claim number, SAIF claim number, an accepted indicator derived from claim status and claim type (DEFfor a new claim,NONfor non-disabling,DISotherwise), accepted and denied dates, the employer, policy, and WCD identifiers, the period start and end dates, the request type, the wage start/end/day figures, the concurrent-injury interruption values, all monetary totals, and onePurchaseelement per purchase typed(a)or(b)for purchase versus worksite modification. The cover-letter fragment, when present, is appended to the same document. - Resolve the employer and policy identifiers. When the period carries an aggravation employer, the employer name and WCD identifier come from the aggravation contact information and the policy number is left blank. Otherwise
GetPolicyName()reads the claim's policy; if that policy is a leasing company, it follows the parent association and reports the parent's name and WCD number instead. The same method records whether the policy is cancelled (status code5). The policy number on the bill is the NCCI-format number derived by an inline four-table join acrossPOLICY_PERIOD_MASTER,POLICY,POLICY_PERIOD, andCL_INJURY_CLAIM:Afor a voluntary-market policy andCotherwise, followed by the policy number (left-padded to six characters when shorter), the account sub-code, and the policy suffix, selecting the policy period covering the injury date.Origin: Policy attributes, association types, and parent-policy lookup are implemented by
clsPolicyDatainSAIF.BOF.Business.Policy, and the worker's home address is resolved byClaimServices.getWorkerAddressinSAIF.Business.Claim. Both are executed on behalf of this operation. Those libraries are documented in the previous format, so no per-operation page exists; see Operations and processes in theSAIF.Business.Claimdocumentation set. - Report the period end date only for qualifying reasons.
GetPeriodEndDate()returns the period expiration date only when the end reason code is one ofCC,CD,TERM,ELIG,INS, orSAN; for any other end reason the bill shows no end date, so DCBS does not read the period as closed. - Create the expense-billing lines and move the expenses. For each paid wage subsidy and then each paid purchase,
clsExpenseBilling.NewBillingExpense(dpi, billingID, expenseID)creates a line carrying the billed amount (again either paid-plus-write-off or amount-to-bill), a zero reimbursed amount, and the expense type code. Then: - when the amount to bill is zero or negative and the amendment is not treated as an original, a new expense-history row is appended with the
Billedstatus, the line's reimbursed amount is set to the (non-positive) amount to bill, and the expense itself becomesBilledif paid-plus-write-off is positive orReimbursedif it is zero — a fully credited expense is considered settled without waiting for DCBS; - otherwise the expense simply becomes
Billedwith a status date of now. Each line is added to the bill's expense-billing collection. - Save. The service period is saved first — which cascades the new statuses and history rows onto the expenses — and then the bill collection is saved, which inserts the new bill header and cascades its expense-billing lines.
- Persist the bill header.
clsBilling.AddInsertUpdateParmsbinds the billing identifier, billed date, claim identifier, period identifier, admin fee amount, the admin-fee reimbursement-detail identifier, billed amount, type code, and the create/update stamp, blanking the update user and date while the update number is still zero.BeforeInserttargetsPk82500_eaip.EAIP_Billing_Insert(SQL-008, Details) andBeforeUpdatethe update procedure after bindingLastUpdateNumand incrementing the update info (SQL-028, Details). EveryAfter*handler checks the affected row count and adds an insert, update, or delete error carrying the DB contention message when it is not one; the delete path targetsPk82500_eaip.EAIP_Billing_Delete(SQL-038, Details) and relies on database triggers to remove the child lines. - Persist the expense-billing lines.
clsExpenseBillingis keyed by the billing identifier plus the expense identifier, so it has no sequence of its own and its criteria throws an invalid-statement exception from the create statement property. Insert, update, and delete targetpk82500_eaip.eaip_expense_billing_insert,_update, and_delete(SQL-009, SQL-029, SQL-039, Details), with the same row-count checks. Lines are read by bill throughGetExpenseBillingByBillingIDor by expense throughGetBillingsByExpenseID(SQL-019, Details). - Finish the print document. Back in
ProcessBills, a non-empty XML string is added to the report node, and the print document is stamped with a file name built from its sequence number plus.pdf, matching start and sort values, auto-select bin type, and vertical duplex.SetupImageRecord()builds the index record that files the printed bill in imaging — batch identifierCMN0084, comment "DCBS Billing - Batch", create userSYSTEM, discovery flagY, the worker name as the second display field, today's document date, image categoryE, document typeCOM(compliance), file codeEAIP, and the claim number and identifier — and its string form becomes the document's index contents. When the XML came back empty, the print document is removed from the collection and the method reports failure. - Expose reconciliation reads. For downstream reconciliation the billing class also offers
GetBillings(dpi, claimID)overPk82500_eaip.getBillingsByClaim(Details), the collection helpersTotalBilled,TotalReimbursed, andGetFilteredBillings(expenseTypeCode), and two non-reconciled views: an in-memory filter over a claim's bills where the billed amount differs from the reimbursed amount, and a package-wide read throughPk82500_eaip.GetNonReconciledBillings(Details).ReimbursedTotalon a bill is the sum of its lines' reimbursed amounts. - Expose the unreconciled admin-fee feed.
GetUnreconciledAdminFeescalls the EAIP REST API rather than the database, using the configuredEaipApiclient and the routev1/Integration/UnreconciledAdminFees, and deserializes the response intoUnreconciledAdminFeesrecords (billing identifier, billed date, claim identifier, admin fee amount, admin-fee reimbursement-detail identifier, billed amount, type code, audit stamp, and period identifier). Failures are swallowed: the exception is written through the logging service and an empty result is returned, so a service outage silently produces an empty feed rather than an error.
Process flow¶
The following diagram visualizes this operation's control flow.
flowchart TD
Start([Batch or user starts a bill run]) --> Load[Retrieve period;<br>load existing bills for the period]
Load --> Amend{Any paid expenses with<br>an original expense id?}
Amend -->|Yes| Type[Entered type = A if any Amended,<br>else C]
Amend -->|No| NewBill
Type --> Batch{Batch run?}
Batch -->|Yes| Skip([Return empty string;<br>amendments are not batch-billed])
Batch -->|No| NewBill[NewBilling: eaip_billing_seq nextval,<br>type defaults to I]
NewBill --> Fee{Prior admin fee charged?}
Fee -->|No| Init[Type I; admin fee from<br>type code table seeds billed amount]
Fee -->|Yes, no amendments| Sub[Type S; no admin fee]
Fee -->|Yes, amendments| Find{FindBill on original expense?}
Find -->|Found| Entered[Type = entered A or C]
Find -->|Not found| Sub2[Type S; treat amendment as original]
Init --> Sel
Sub --> Sel
Entered --> Sel
Sub2 --> Sel[Select all paid wage subsidies<br>and purchases on the period]
Sel --> Any{Anything to bill?}
Any -->|No| Empty([Return empty string])
Any -->|Yes| Acc[Accumulate billed amount:<br>paid+writeoff or AmountToBill]
Acc --> Cover{On-demand A or C bill?}
Cover -->|Yes| Letter[GenCoverLetter: adjustment, days,<br>admin fee refund check]
Cover -->|No| Xml
Letter --> Xml[GetBillXml: worker, claim, policy,<br>totals, purchases, cover fragment]
Xml --> Lines[Per expense: create expense-billing line,<br>append history, set Billed or Reimbursed]
Lines --> Save[Save period, then save bills]
Save --> Rows{RowsAffected = 1<br>on each statement?}
Rows -->|No| Err([Insert/Update error with<br>DB contention message])
Rows -->|Yes| Print[Stamp print document; build<br>CMN0084 image index record]
Print --> Done([Bill XML returned to the report])
Failure and edge-case behavior¶
- In the treat-amendment-as-original branch the first wage subsidy assigns the billed amount instead of adding to it, discarding the admin fee seeded moments earlier. A period whose first billed expense is a wage subsidy therefore bills without the fee in that specific branch, while the fee is still recorded on the bill header's own admin-fee column.*
UpdateOriginalBillrefunds the administrative fee only on an exact match between the credited amount and the original bill's non-fee content. A partial credit leaves the fee in place with no message explaining why.*- That same update runs inline with no update-number predicate, so it will overwrite a concurrent change to the original bill rather than detect it.
- Nothing prevents the same paid expense from being billed twice: the run selects on
Paidstatus only, and expenses are moved toBilledafter the lines are created, so a failure between line creation and save leaves the expenses payable and billable again on the next run.* - The generator's criteria class throws an invalid-statement exception from every statement property — it has no persistence of its own and exists purely to orchestrate other objects.
GetOriginalBillDateandDeriveNcciPolicyNumberreturn the first matching row and a single blank space respectively when nothing matches, so a missing policy period produces a bill with a blank policy number rather than a failure.- The unreconciled admin-fee API call swallows every exception into the logging service and returns nothing, so callers cannot distinguish "no unreconciled fees" from "service unavailable".
- Bills and their expense-billing lines are deleted only through database triggers on the parent delete; this library issues no explicit child deletes.
- The period save runs before the bill save. If the bill save fails, the expenses have already been moved to
BilledorReimbursedwithout a corresponding billing row.*
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.EAIP\Billing\clsGenerateBill.vbBusiness\SAIF.Business.Claim.EAIP\Billing\clsBilling.vbBusiness\SAIF.Business.Claim.EAIP\Billing\clsExpenseBilling.vbBusiness\SAIF.Business.Claim.EAIP\UnreconciledAdminFees.vbBusiness\SAIF.Business.Claim.EAIP\Period\clsPeriod.vbBusiness\SAIF.Business.Claim.EAIP\Period\clsWageSubsidy.vbBusiness\SAIF.Business.Claim.EAIP\Period\clsPurchase.vbBusiness\SAIF.Business.Claim.EAIP\Period\clsExpenseHistory.vbBusiness\SAIF.Business.Claim.EAIP\mdlCodeTables.vb