AR Aging Workflow
1. Executive Summary
Purpose
The AR Aging workflow provides accounts receivable aging reports that show how long outstanding balances have been owed across the billing item portfolio. Users search and filter the receivable population, then view results in either a Summary view (one row per billing item, combining REV and PAY) or a Detail view (separate rows for each REV and PAY detail). Each row classifies the outstanding balance into one of five time buckets — Current, 1–30 days, 31–60 days, 61–90 days, and 90+ days — based on billing_item.billing_item_due_dt. From within the report, users can also open a deduction management dialog to update deductions on a billing item without leaving the report context.
Scope
Covered:
- Filtering the billing item population by client, buyer, deal, department, currency, collection style, open/closed status, and write-off inclusion
- Summary view: one row per billing item with combined REV + PAY totals, aging bucket classification, and write-off status
- Detail view: separate rows per
billing_item_detailtype (REV and PAY) with per-detail balances and aging bucket classification - Deduction management: inline dialog to view and update
billing_item_deductionrecords for a selected billing item - Export of report data to file
Not covered (documented separately):
- Billing item creation and lifecycle management — see Billing Items Workflow
- Write-off packet submission and approval — see Write-Offs Workflow
- Cash application against receivables — see Worksheets Workflow
Key Objectives
- Give finance teams a real-time view of the outstanding receivable portfolio, organized by aging bucket
- Identify overdue items by client, buyer, deal, or department for collections follow-up
- Surface write-off status on individual receivables so users can navigate to the write-off packet
- Allow targeted deduction adjustments directly from the aging report without navigating away
2. Process Overview
flowchart TD
A[User opens AR Aging Report] --> B[Enter search criteria]
B --> C{Run Search}
C --> D[Summary View loaded\none row per billing item]
C --> E[Detail View loaded\none row per detail line]
D --> F{Select view tab}
E --> F
F -->|Summary tab| G[Review combined REV+PAY balances\nand aging buckets]
F -->|Detail tab| H[Review per-detail REV / PAY balances\nand aging buckets]
G --> I{Row has deductions?}
H --> I
I -->|Yes| J[Click row to open\nDeduction Management dialog]
I -->|No| K[Toast: no deductions]
J --> L[Edit deduction amounts\nand types]
L --> M[Save Deductions]
M --> N[Deductions updated\nin-place on billing item]
G --> O[Export to file]
H --> OWalkthrough
- Enter search criteria — The user sets any combination of filters: client, buyer, deal, department, currency, collection style, date range, free-text term, open-items-only flag, and include-written-off flag. The default state is open items only, written-off excluded.
- Run search — Executing the search fires two parallel queries:
getArAgingSummaryandgetArAgingDetail. Both return immediately after the server action completes. - Review Summary tab — The default active tab shows one row per
billing_item. Balances shown are combined REV + PAY; the balance is distributed into exactly one aging bucket per row based onbilling_item.billing_item_due_dtcompared to the current date. - Switch to Detail tab — The user can switch to the Detail tab to see separate rows for the REV and PAY
billing_item_detailrecords, each with their own per-detail balance and aging bucket. - Manage deductions — Clicking any row that has deductions opens the Deduction Management dialog. The user can view, add, edit, or delete deduction rows for both the REV and PAY details, then save. Deductions are saved directly on the existing billing item without triggering a rebill.
- Export — The user can export the currently visible grid to a file using the built-in export control on either tab.
3. Business Rules
3.1 Aging Date Source
Business rule: Aging is calculated from billing_item.billing_item_due_dt. If no due date is set on the billing item, the balance is classified as Current.
Foundation reference: AR Aging Summary query — Computed Values
Workflow context: When billing_item.billing_item_due_dt is NULL, daysOverdue resolves to NULL and the aging bucket calculation places the full balance in the agingCurrent bucket. The due date column in the grid displays a dash for such rows.
**PoC Artifact:**
The data model defines a separate billing_item.billing_item_aging_dt field intended as the aging date, which defaults to billing_item_due_dt but can be manually adjusted by a user to override aging bucket placement. The PoC implementation drives all aging bucket calculations directly from billing_item_due_dt. A production implementation should use billing_item_aging_dt as the aging date source so that user overrides are respected.
3.2 Aging Bucket Classification
Business rule: Each billing item's outstanding balance is placed in exactly one of five aging buckets: Current (not yet due), 1–30 days, 31–60 days, 61–90 days, or 90+ days past due. A balance cannot appear in more than one bucket simultaneously.
Foundation reference: Aging Bucket Classification (3.9)
3.3 Balance Calculation Scope
Business rule: Outstanding balance is computed as the total billed amount minus cash applied and deductions applied, but only from worksheets that are current and in Approved ('A') or Submitted ('S') status. Draft, returned, and superseded worksheets do not affect the displayed balance.
Foundation reference: AR Aging Summary query — totalBalance computed value
IMPORTANT
The status filter IN ('A', 'S') means that cash applied on a Draft worksheet ('D') is not subtracted from the aging balance until that worksheet is approved. This is intentional: unapproved cash has not yet been committed.
3.4 Net After Deductions Computation
Business rule: "Net After Deductions" subtracts only deductions flagged as net-impacting (billing_item_deduction.billing_item_deduction_update_net_ind = true) from the gross amount. Informational-only deductions do not reduce the displayed net.
Foundation reference: Net After Deductions (3.8)
3.5 Write-Off Visibility Filter
Business rule: By default, billing items whose REV detail has write_off_status_cd = 'WRITTEN_OFF' are excluded from the aging report. Users can include written-off items by checking the "Include Written Off" filter before running the search.
Foundation reference: AR Aging Summary — Filters
Workflow context: When a row has write-off status WRITTEN_OFF or RECOVERED, the report displays a badge in the Write-Off column. If a write-off packet ID is present, the badge is clickable and opens the write-off packet in a new browser tab.
3.6 Open Items Filter Default
Business rule: The search panel defaults to showing only open items (billing_item.open_item_ind = true). Users can uncheck "Open Items Only" to include fully-collected receivables in the results.
Foundation reference: Billing Items Data Model — open_item_ind
Workflow context: The default is intentional for collections workflows — users typically want to see only what remains outstanding.
3.7 Deduction Management Saves In-Place
Business rule: Saving deductions from within the aging report updates deduction records directly on the existing billing item. No rebill, reversal, or new billing item is created.
Foundation reference: Manage Billing Item Deductions (2.7)
Workflow context: After a successful deduction save, the aging grid is not automatically refreshed. The user sees a success toast but must re-run the search to see the updated net amounts. This is intentional — deductions do not affect billing_item_detail_amt or billing_item_detail_total_amt directly; they affect the net display at the query level.
**PoC Artifact:**
The comment in the client code explicitly notes that refreshing the AR aging data after deduction save is not implemented because "deductions don't affect the AR aging calculations (only cash receipts do)." However, deductions do affect the deductionTotal and netAfterDeductions columns displayed in the grid, so a production implementation should refresh the relevant rows after save.
3.8 Summary vs. Detail Balance Scope
Business rule: In the Summary view, totalBalance is the combined outstanding balance across both the REV and PAY details. In the Detail view, detailBalance is the outstanding balance for that specific REV or PAY detail only.
Foundation reference: AR Aging Detail query (2.6.2)
3.9 Only REV Details Carry Write-Off Status
Business rule: Only billing_item_detail rows with billing_item_detail_type_cd = 'REV' can have a non-null write_off_status_cd. The Detail view suppresses the Write-Off badge for PAY rows regardless of the underlying value.
Foundation reference: Billing Items Data Model — write_off_status_cd
4. Data Access & Operations References
4.1 Queries Used
| Operation | Foundation Doc | Purpose in This Workflow |
|---|---|---|
getArAgingSummary | AR Aging Summary (2.6.1) | Loads Summary tab data — one row per billing item with combined REV+PAY balance and aging buckets |
getArAgingDetail | AR Aging Detail (2.6.2) | Loads Detail tab data — separate rows for each REV and PAY detail with per-detail balance and aging buckets |
getBillingItemById | Get Billing Item by ID (2.1.1) | Loads billing item header for the Deduction Management dialog header display |
searchClients | Party Queries | Autocomplete lookup for Client filter field |
searchBuyers | Party Queries | Autocomplete lookup for Buyer filter field |
searchDeals | Deals Queries | Autocomplete lookup for Deal filter field |
searchDepartments | TODO: Document in foundation/queries/parties.md | Autocomplete lookup for Department filter field |
getBillingItemDeductionData | Get Flattened Billing Item Display | Loads billing item, details, and existing deductions into the Deduction Management dialog |
getBillingItemDeductionTypeOptions | TODO: Document in foundation/queries/billing-items.md | Loads code master values for deduction type dropdown in the Deduction Management dialog |
4.2 Procedures Used
| Operation | Foundation Doc | Trigger in This Workflow |
|---|---|---|
manageBillingItemDeductions | Manage Billing Item Deductions (2.7) | User saves changes in the Deduction Management dialog |
5. Key User Actions
5.1 Run AR Aging Search
Preconditions:
- User has navigated to the AR Aging Report page at
/reports/ar-aging - No role restriction applies — the page is accessible to all authenticated users
Procedure reference: AR Aging Summary (2.6.1) and AR Aging Detail (2.6.2)
Steps:
- User optionally sets filter values: client, buyer, deal, department, currency, collection style, due date range, or free-text search term
- User adjusts the Open Items Only and Include Written Off checkboxes as needed
- User clicks the Search button
- The system fires
getArAgingSummaryandgetArAgingDetailin parallel; both tab result sets are populated simultaneously - Results appear; the Summary tab is active by default and shows the count of returned rows
Postconditions:
summaryDatapopulated with rows where each row maps to onebilling_itemrecord and includes five aging bucket amountsdetailDatapopulated with rows where each row maps to onebilling_item_detailrecord
UI trigger: Search button in the Search Criteria panel. Visible always. Enabled always (no required fields enforce a minimum filter).
5.2 Switch Between Summary and Detail Views
Preconditions:
- A search has been executed (results are present)
Procedure reference: No data mutation — display-only tab switch.
Steps:
- User clicks the Summary tab or the Detail tab
- The corresponding data grid becomes visible; the inactive tab's grid is hidden
Postconditions:
- Active tab label shows the row count for the selected view
UI trigger: Summary tab trigger (labeled "Summary (N)") or Detail tab trigger (labeled "Detail (N)"). Visible after at least one search has been executed. Enabled always once results exist.
5.3 Open Deduction Management Dialog
Preconditions:
- A search has been executed and results are present
- The selected row has
hasDeductions = true
Procedure reference: Get Flattened Billing Item Display (2.3) — used to load dialog data.
Steps:
- User clicks a row in either the Summary or Detail grid
- If
row.hasDeductionsis true, the Deduction Management dialog opens and loads the billing item's details and existing deductions - If
row.hasDeductionsis false, a toast notification informs the user that the billing item has no deductions; the dialog does not open
Postconditions:
- Deduction Management dialog displays with billing item header context and editable deduction rows grouped by REV and PAY detail
UI trigger: Row click on any row in the Summary or Detail grid. Visible always. When hasDeductions = false, the click triggers a toast notification instead of opening the dialog.
5.4 Save Deductions
Preconditions:
- Deduction Management dialog is open with a valid
billing_item_id - Both REV and PAY details must be present on the billing item
Procedure reference: Manage Billing Item Deductions (2.7)
Steps:
- User adds, edits, or removes deduction rows in the REV section or PAY section of the dialog
- For each deduction row the user sets:
billing_item_deduction_type_cd,billing_item_deduction_amt,billing_item_deduction_update_net_ind, and optionally a comment - User clicks Save Changes
- The system compares the new deduction set against the existing set; if no changes are detected, the procedure returns without mutating the database
- If changes are detected: a reversal is created, a replacement billing item is created with new detail IDs, new
billing_item_deductionrecords are inserted, cash applications are migrated, andopen_item_indis recalculated - Success toast is displayed; the dialog closes
Postconditions:
- Original
billing_itemhascurrent_item_ind = false - New replacement
billing_itemexists withcurrent_item_ind = trueand newbilling_item_detailandbilling_item_deductionrecords cash_receipt_applicationrecords point to the newbilling_item_detail_idvaluesbilling_item.open_item_indrecalculated on the new record
UI trigger: Save Changes button in the Deduction Management dialog. Visible always when the dialog is open. Disabled while the save operation is in progress.
5.5 Export Report Data
Preconditions:
- A search has been executed and results are present on the active tab
Procedure reference: No server-side procedure — client-side export of in-memory data.
Steps:
- User clicks the Export button on the Summary or Detail grid toolbar
- The system exports the currently visible grid data to a file named
ar-aging-summaryorar-aging-detailrespectively
Postconditions:
- File downloaded to the user's browser; no server-side mutation occurs
UI trigger: Export button in the DataTable toolbar. Visible when the grid has data. Enabled always when visible.
6. Permissions & Role-Based Access
| Action | CASH_MANAGER | CASH_PROCESSOR | SETTLEMENT_APPROVER | IT |
|---|---|---|---|---|
| View AR Aging Report | Yes | Yes | Yes | Yes |
| Run AR Aging Search | Yes | Yes | Yes | Yes |
| Open Deduction Management dialog | Yes | Yes | Yes | Yes |
| Save Deductions | Yes | Yes | Yes | Yes |
| Export report data | Yes | Yes | Yes | Yes |
Field-level restrictions:
- No field-level restrictions apply to the AR Aging report itself. All columns are read-only display fields.
- The Deduction Management dialog allows all authenticated users to modify deductions. Production implementations may wish to restrict deduction editing to users with a specific role such as
CASH_MANAGERorIT, as deduction changes affect downstream balance calculations.
**PoC Artifact:**
Role-based access control is not enforced at the server action level in the PoC. The searchArAging server action does not perform any role check before executing the query. A production implementation should enforce at minimum a read permission check.
7. Integration Points
7.1 Upstream
| Source | Data Provided | Mechanism |
|---|---|---|
| Billing items and revenue items | billing_item, billing_item_detail, billing_item_deduction records that form the receivable population | FK lookup at query time |
| Deal engine | deal.deal_name, billing_item.deal_id — deal context displayed in report columns | FK join on billing_item.deal_id |
| Party registry | party.display_name for client and buyer names displayed in report columns | FK join on billing_item.client_id and billing_item.buyer_id |
| Department registry | department.department_name for grouping and filtering | FK join on billing_item.department_id |
| Cash worksheets | cash_receipt_application and cash_receipt_application_deduction records that reduce the aging balance | Correlated subqueries in aging balance calculation |
| Write-off packets | billing_item_detail.write_off_status_cd and write_off_packet_id for write-off badge display | Carried on the billing_item_detail record |
7.2 Downstream
| Consumer | Data Consumed | Mechanism |
|---|---|---|
| Write-Offs Workflow | User clicks write-off badge to navigate to the packet | Browser navigation to /write-offs/packets/:packetId opened in a new tab |
| Billing Items (deductions) | Deduction Management dialog saves create, update, and delete billing_item_deduction records on the existing billing item | Server action → manageBillingItemDeductions procedure |
7.3 External Integrations
No external integrations for this workflow. The AR Aging report is an internal read-only analytical view of data that already exists within the Client Processing database.
8. Functional Screen Requirements
8.1 AR Aging Report Page
Route: /reports/ar-aging
Data loading:
- No data is loaded on page entry. All data is loaded in response to an explicit user-initiated search action.
Search Criteria Region
Collapsible panel that accepts filter inputs. Expanded by default.
| Field / Column | Source | Editable? | Condition |
|---|---|---|---|
| Search (free text) | User input; matched against billing_item.billing_item_name, party.display_name (client), party.display_name (buyer), deal.deal_name | Yes | Always visible |
| Client | Autocomplete against party table; stored as ArAgingSearchCriteria.clientId mapped to billing_item.client_id | Yes | Always visible |
| Buyer | Autocomplete against party table; stored as ArAgingSearchCriteria.buyerId mapped to billing_item.buyer_id | Yes | Always visible |
| Deal | Autocomplete against deal table; stored as ArAgingSearchCriteria.dealId mapped to billing_item.deal_id | Yes | Always visible |
| Department | Autocomplete against department table; stored as ArAgingSearchCriteria.departmentId mapped to billing_item.department_id | Yes | Always visible |
| Currency | Dropdown: USD, EUR, GBP; mapped to billing_item.currency_cd | Yes | Always visible |
| Collection Style | Dropdown: All / Client / Buyer; mapped to billing_item.collection_style_cd | Yes | Always visible |
| Open Items Only | Checkbox; when checked maps to billing_item.open_item_ind = true filter | Yes | Always visible; default: checked |
| Include Written Off | Checkbox; when unchecked excludes revDetail.write_off_status_cd = 'WRITTEN_OFF' | Yes | Always visible; default: unchecked |
Grid features:
- No grid in this region; inputs only
- Search button triggers the data fetch
- Clear button resets all criteria to defaults (
openItemOnly = true,includeWrittenOff = false, all others empty)
Conditional display:
- Search button displays a loading state while the search is in progress
- The results region below is hidden until the first search is executed
Summary Tab Region
Displays one row per billing_item. Shown when the Summary tab is active.
| Field / Column | Source | Editable? | Condition |
|---|---|---|---|
| Department | department.department_name via billing_item.department_id | No | Always visible |
| Deal | deal.deal_name via billing_item.deal_id | No | Always visible |
| Client | party.display_name via billing_item.client_id | No | Always visible |
| Buyer | party.display_name via billing_item.buyer_id | No | Always visible |
| Collection | billing_item.collection_style_cd; displayed as "Client" or "Buyer" | No | Always visible |
| Billing Item | billing_item.billing_item_name | No | Always visible |
| Open | billing_item.open_item_ind; displayed as "Yes" or "No" | No | Always visible |
| Write-Off | billing_item_detail.write_off_status_cd (from REV detail); displayed as badge when WRITTEN_OFF or RECOVERED | No | Badge visible only when status is not NOT_WRITTEN_OFF |
| Deductions | Computed: hasDeductions boolean; displayed as "Yes" or "No" | No | Always visible |
| Gross Amount | Computed: payDetail.billing_item_detail_gross_amt | No | Always visible |
| Deduction Total | Computed: sum of billing_item_deduction.billing_item_deduction_amt where billing_item_deduction_update_net_ind = true for both REV and PAY details | No | Always visible |
| Net After Ded | Computed: billingGrossAmt - deductionTotal | No | Always visible |
| Comm % | revDetail.billing_item_detail_percent formatted as percentage | No | Always visible |
| Total Balance | Computed: combined REV + PAY outstanding balance after cash applied and deductions applied (Approved/Submitted worksheets only) | No | Always visible |
| Revenue | revDetail.billing_item_detail_total_amt | No | Always visible |
| Cash Collected | Computed: sum of cash_receipt_application.cash_receipt_amt_applied for both details on Approved/Submitted worksheets | No | Always visible |
| Currency | billing_item.currency_cd | No | Always visible |
| Due Date | billing_item.billing_item_due_dt | No | Always visible |
| Current | Computed: totalBalance when not yet due or no due date, else 0 | No | Always visible |
| 1-30 Days | Computed: totalBalance when 1–30 days past due, else 0 | No | Always visible |
| 31-60 Days | Computed: totalBalance when 31–60 days past due, else 0 | No | Always visible |
| 61-90 Days | Computed: totalBalance when 61–90 days past due, else 0 | No | Always visible |
| 90+ Days | Computed: totalBalance when more than 90 days past due, else 0 | No | Always visible |
Grid features:
- Sortable columns: all columns
- Filters: global text filter across all visible columns
- Row selection: single-row click — opens Deduction Management dialog if
hasDeductions = true - Pagination: yes
- Export: yes; filename
ar-aging-summary
Conditional display:
- Empty state ("No results found. Try adjusting your search criteria") visible when search returned zero rows
- Write-Off badge clickable (opens write-off packet in new tab) when
writeOffPacketIdis not null - Summary tab label shows count: "Summary (N)"
Detail Tab Region
Displays one row per billing_item_detail (separate rows for REV and PAY). Shown when the Detail tab is active.
| Field / Column | Source | Editable? | Condition |
|---|---|---|---|
| Department | department.department_name via billing_item.department_id | No | Always visible |
| Deal | deal.deal_name via billing_item.deal_id | No | Always visible |
| Client | party.display_name via billing_item.client_id | No | Always visible |
| Buyer | party.display_name via billing_item.buyer_id | No | Always visible |
| Billing Item | billing_item.billing_item_name | No | Always visible |
| Type | billing_item_detail.billing_item_detail_type_cd; displayed as "Revenue" (REV) or "Payment" (PAY) | No | Always visible |
| Open | billing_item.open_item_ind; displayed as "Yes" or "No" | No | Always visible |
| Write-Off | billing_item_detail.write_off_status_cd; badge only for REV rows | No | Badge visible only for REV rows with status WRITTEN_OFF or RECOVERED |
| Deductions | Computed: hasDeductions across both details of the parent billing item; displayed as "Yes" or "No" | No | Always visible |
| Gross Amount | billing_item_detail.billing_item_detail_gross_amt | No | Always visible |
| Deduction Total | Computed: sum of billing_item_deduction.billing_item_deduction_amt where billing_item_deduction_update_net_ind = true for this specific detail | No | Always visible |
| Net After Ded | Computed: billing_item_detail_gross_amt - deductionTotal for this detail | No | Always visible |
| Comm % | billing_item_detail.billing_item_detail_percent; displayed as percentage; shown as dash for PAY rows | No | Always visible |
| Detail Balance | Computed: billing_item_detail_total_amt - deductionsApplied(A/S) - cashApplied(A/S) for this detail | No | Always visible |
| Total Amount | billing_item_detail.billing_item_detail_total_amt | No | Always visible |
| Cash Applied | Computed: sum of cash_receipt_application.cash_receipt_amt_applied for this detail on Approved worksheets | No | Always visible |
| Due Date | billing_item.billing_item_due_dt | No | Always visible |
| Current | Computed: detailBalance when not yet due or no due date, else 0 | No | Always visible |
| 1-30 Days | Computed: detailBalance when 1–30 days past due, else 0 | No | Always visible |
| 31-60 Days | Computed: detailBalance when 31–60 days past due, else 0 | No | Always visible |
| 61-90 Days | Computed: detailBalance when 61–90 days past due, else 0 | No | Always visible |
| 90+ Days | Computed: detailBalance when more than 90 days past due, else 0 | No | Always visible |
Grid features:
- Sortable columns: all columns
- Filters: global text filter across all visible columns
- Row selection: single-row click — opens Deduction Management dialog if
hasDeductions = true - Pagination: yes
- Export: yes; filename
ar-aging-detail
Conditional display:
- Empty state visible when search returned zero detail rows
- Write-Off badge suppressed on PAY rows (type check: only shown when
billingItemDetailTypeCd = 'REV') - Detail tab label shows count: "Detail (N)"
**PoC Artifact:**
The PoC shows an empty state message "Detail view is coming soon. Use the Summary tab for now." for the Detail tab when detailData.length === 0. In practice, the getArAgingDetail query runs in parallel with the Summary query and will return data when billing items are found. The empty state message wording is a PoC artifact and should be updated in production.
8.2 Deduction Management Dialog
Route: Modal dialog overlaying /reports/ar-aging (no dedicated route)
Data loading:
getBillingItemDeductionData— loadsbilling_item, bothbilling_item_detailrecords, and allbilling_item_deductionrecords for the selectedbilling_item_idgetBillingItemDeductionTypeOptions— loads available deduction type codes for the type dropdown
Dialog Header Region
Summary of the billing item being edited.
| Field / Column | Source | Editable? | Condition |
|---|---|---|---|
| Billing Item name | billing_item.billing_item_name | No | Always visible |
| Gross Amount | revDetail.billing_item_detail_gross_amt | No | Always visible |
| Total Net | Computed: (revDetail.gross * revDetail.percent) + (payDetail.gross * payDetail.percent) | No | Always visible |
| Total Deduction | Computed: sum of all net-impacting deductions across REV and PAY | No | Always visible; live-updated as user edits |
| Total Billing | Computed: Total Net - Total Deduction | No | Always visible; live-updated as user edits |
| Currency | billing_item.currency_cd | No | Always visible |
REV Deductions Region
Editable deduction rows for the REV (billing_item_detail_type_cd = 'REV') detail.
| Field / Column | Source | Editable? | Condition |
|---|---|---|---|
| REV section header (Percent, Net Amt, Total Ded, Billing Amt) | Computed from revDetail fields and deduction rows | No | Always visible when REV detail exists |
| Deduction Type | billing_item_deduction.billing_item_deduction_type_cd; dropdown from code master | Yes | Per deduction row |
| Amount | billing_item_deduction.billing_item_deduction_amt | Yes | Per deduction row |
| Affects Net (checkbox) | billing_item_deduction.billing_item_deduction_update_net_ind | Yes | Per deduction row |
| Comment | billing_item_deduction.comment | Yes | Per deduction row |
| Delete button | Removes deduction row from edit state | N/A | Per deduction row |
| Add row button ("+") | Adds a blank deduction row for the REV detail | N/A | Visible on last row only |
PAY Deductions Region
Editable deduction rows for the PAY (billing_item_detail_type_cd = 'PAY') detail. Same structure as REV Deductions Region.
| Field / Column | Source | Editable? | Condition |
|---|---|---|---|
| PAY section header (Percent, Net Amt, Total Ded, Billing Amt) | Computed from payDetail fields and deduction rows | No | Always visible when PAY detail exists |
| Deduction Type | billing_item_deduction.billing_item_deduction_type_cd | Yes | Per deduction row |
| Amount | billing_item_deduction.billing_item_deduction_amt | Yes | Per deduction row |
| Affects Net (checkbox) | billing_item_deduction.billing_item_deduction_update_net_ind | Yes | Per deduction row |
| Comment | billing_item_deduction.comment | Yes | Per deduction row |
| Delete button | Removes deduction row from edit state | N/A | Per deduction row |
| Add row button ("+") | Adds a blank deduction row for the PAY detail | N/A | Visible on last row only |
Grid features:
- No pagination — all deduction rows for the billing item shown at once
- No export
Conditional display:
- Each detail section always shows at least one row (a blank empty row is pre-populated if no deductions exist for that detail)
- Save Changes button disabled while a save operation is in progress
- Loading indicator shown while initial data loads; error message shown if load fails
9. Additional Diagrams
Aging Bucket Calculation Logic
flowchart TD
A[billing_item.billing_item_due_dt] --> B{Due date null?}
B -->|Yes| C[agingCurrent = totalBalance\nAll other buckets = 0]
B -->|No| D[daysOverdue = CURRENT_DATE - billing_item_due_dt]
D --> E{daysOverdue <= 0?}
E -->|Yes - not yet due| F[agingCurrent = totalBalance]
E -->|No - overdue| G{1 to 30?}
G -->|Yes| H[aging1to30 = totalBalance]
G -->|No| I{31 to 60?}
I -->|Yes| J[aging31to60 = totalBalance]
I -->|No| K{61 to 90?}
K -->|Yes| L[aging61to90 = totalBalance]
K -->|No| M[aging90Plus = totalBalance]Balance Exclusion Scope
flowchart LR
subgraph "Included in Balance"
A[Approved worksheets\nstatus = 'A'\ncurrent_item_ind = true]
B[Submitted worksheets\nstatus = 'S'\ncurrent_item_ind = true]
end
subgraph "Excluded from Balance"
C[Draft worksheets\nstatus = 'D']
D[Applied worksheets\nstatus = 'P']
E[Settled worksheets\nstatus = 'T']
F[Returned worksheets\nstatus = 'R']
G[Superseded worksheets\ncurrent_item_ind = false]
end
A --> H[Cash applied reduces\naging balance]
B --> H10. Cross-References
| Document | Relationship |
|---|---|
| Billing Items Data Model | Defines billing_item, billing_item_detail, billing_item_deduction, and key fields billing_item.open_item_ind, billing_item.billing_item_due_dt, and billing_item.billing_item_aging_dt (production aging date override) used throughout this workflow |
| Billing Items Queries | Specifies getArAgingSummary (2.6.1), getArAgingDetail (2.6.2), aging bucket classification (3.9), and net-after-deductions (3.8) |
| Billing Items Procedures | Specifies manageBillingItemDeductions (2.7) triggered by the Deduction Management dialog save |
| Worksheets Workflow | Upstream: approved worksheet cash applications are what reduce the aging balance displayed here |
| Write-Offs Workflow | The AR Aging report surfaces write_off_status_cd and links to write-off packets; write-offs are created and managed in that workflow |
| Billing Items Workflow | The billing items visible in the aging report are created and managed in this workflow |
11. Gherkin Scenarios
Feature: AR Aging Report - Search and Filtering
Scenario: Default search shows only open items, excluding written-off
Given the user navigates to /reports/ar-aging
And the search panel shows "Open Items Only" checked and "Include Written Off" unchecked
When the user clicks Search without changing any criteria
Then the Summary tab displays rows only where billing_item.open_item_ind = true
And no rows are displayed where billing_item_detail.write_off_status_cd = 'WRITTEN_OFF' on the REV detail
Scenario: Filtering by client narrows results to that client's receivables
Given the user is on the AR Aging Report page
And there exist billing items for client "Taylor Swift" (party.party_id = 101) and "Adele" (party.party_id = 202)
When the user selects "Taylor Swift" in the Client autocomplete filter
And the user clicks Search
Then all rows in the Summary tab have clientName = "Taylor Swift"
And no rows appear for "Adele"
And the filter applied is billing_item.client_id = 101
Scenario: Currency filter limits results to single currency
Given multiple billing items exist in both USD and GBP
When the user selects "GBP" from the Currency dropdown
And the user clicks Search
Then all Summary rows have billing_item.currency_cd = 'GBP'
And no USD rows appear in the results
Scenario: Including written-off items reveals WRITTEN_OFF receivables
Given billing item 500 has its REV detail with write_off_status_cd = 'WRITTEN_OFF'
And the user has left "Include Written Off" unchecked
When the user runs a search without the written-off item appearing
And the user checks "Include Written Off"
And the user clicks Search again
Then billing item 500 appears in the Summary results
And the Write-Off column for that row shows a "Written Off" badge
Feature: AR Aging Report - Aging Bucket Classification
Scenario: Not-yet-due item appears in Current bucket
Given billing item 200 has billing_item.billing_item_due_dt = '2026-03-15' (today is 2026-03-02)
And the outstanding totalBalance for billing item 200 is $10,000.00
When the user runs a search that includes billing item 200
Then the Summary row for billing item 200 shows agingCurrent = $10,000.00
And aging1to30 = $0.00
And aging31to60 = $0.00
And aging61to90 = $0.00
And aging90Plus = $0.00
Scenario: Item overdue by 45 days appears in 31-60 bucket
Given billing item 300 has billing_item.billing_item_due_dt = '2026-01-16' (45 days before 2026-03-02)
And the totalBalance for billing item 300 is $5,000.00
When the user runs a search that includes billing item 300
Then the Summary row for billing item 300 shows aging31to60 = $5,000.00
And agingCurrent = $0.00
And aging1to30 = $0.00
And aging61to90 = $0.00
And aging90Plus = $0.00
Scenario: Item with no due date defaults to Current bucket
Given billing item 400 has billing_item.billing_item_due_dt = NULL
And the totalBalance for billing item 400 is $2,500.00
When the user runs a search that includes billing item 400
Then the Summary row for billing item 400 shows agingCurrent = $2,500.00
And all other aging buckets for that row show $0.00
Scenario: Item overdue more than 90 days appears in 90+ bucket
Given billing item 600 has billing_item.billing_item_due_dt = '2025-11-01' (122 days before 2026-03-02)
And the totalBalance for billing item 600 is $75,000.00
When the user runs a search that includes billing item 600
Then the Summary row for billing item 600 shows aging90Plus = $75,000.00
And agingCurrent = $0.00
And aging1to30 = $0.00
And aging31to60 = $0.00
And aging61to90 = $0.00
Feature: AR Aging Report - Detail View
Scenario: Detail tab shows separate rows for REV and PAY
Given billing item 250 has a REV detail (billing_item_detail_id = 501) and a PAY detail (billing_item_detail_id = 502)
When the user runs a search that includes billing item 250
And the user clicks the Detail tab
Then two rows appear for billing item 250: one with billingItemDetailTypeCd = 'REV' and one with billingItemDetailTypeCd = 'PAY'
And each row shows its own detailBalance computed from billing_item_detail_id-specific cash applications
Scenario: Write-Off badge suppressed on PAY detail rows
Given billing item 350 has its REV detail (billing_item_detail_id = 601) with write_off_status_cd = 'WRITTEN_OFF'
And "Include Written Off" is checked in the search criteria
When the user runs a search and switches to the Detail tab
Then the REV row for billing item 350 shows the "Written Off" badge
And the PAY row for billing item 350 shows no Write-Off badge
Feature: AR Aging Report - Deduction Management
Scenario: Clicking a row with deductions opens the Deduction Management dialog
Given billing item 700 has billing_item_deduction records on its REV detail
And the user has run a search that returns billing item 700
When the user clicks the row for billing item 700 in the Summary grid
Then the Deduction Management dialog opens
And the dialog loads billing_item.billing_item_name for item 700
And the existing billing_item_deduction records appear in the REV section
Scenario: Clicking a row without deductions shows a toast
Given billing item 800 has no billing_item_deduction records on either detail
And the user has run a search that returns billing item 800 (row shows hasDeductions = false)
When the user clicks the row for billing item 800 in the Summary grid
Then the Deduction Management dialog does not open
And a toast notification appears: "This billing item has no deductions"
Scenario: Adding a new deduction and saving updates deductions in-place
Given billing item 900 exists with current_item_ind = true and no existing billing_item_deduction records
And the user opens the Deduction Management dialog for billing item 900
When the user enters deduction type 'B' (Bank Charge), amount $250.00, Affects Net checked
And the user clicks Save Changes
Then manageBillingItemDeductions is invoked for billing_item_id = 900
And billing_item.current_item_ind for item 900 remains true
And no new billing_item is created
And the REV detail has a billing_item_deduction with billing_item_deduction_type_cd = 'B' and billing_item_deduction_amt = 250.00
And a success toast appears: "Deductions saved successfully"
Scenario: Saving with no changes does not mutate the database
Given billing item 950 has one existing billing_item_deduction on its REV detail with type 'D' and amount $100.00
And the user opens the Deduction Management dialog for billing item 950
And the user makes no changes to the deduction rows
When the user clicks Save Changes
Then manageBillingItemDeductions is invoked with the same deduction set
And no deduction records are changed
And billing_item.current_item_ind for item 950 remains true
And a success toast appears
Feature: AR Aging Report - Balance Accuracy
Scenario: Draft worksheet cash is not subtracted from aging balance
Given billing item 1000 has a REV detail with billing_item_detail_total_amt = $5,000.00
And there is a cash_receipt_application for $2,000.00 against that REV detail on a worksheet with cash_receipt_worksheet_status_cd = 'D'
When the user runs a search that includes billing item 1000
Then the Total Balance for billing item 1000 reflects the full $5,000.00 (plus PAY detail balance)
And the $2,000.00 Draft application is not subtracted
Scenario: Approved worksheet cash reduces the aging balance
Given billing item 1000 has a REV detail with billing_item_detail_total_amt = $5,000.00
And there is a cash_receipt_application for $2,000.00 against that REV detail on a worksheet with cash_receipt_worksheet_status_cd = 'A' and current_item_ind = true
When the user runs a search that includes billing item 1000
Then the REV portion of the Total Balance for billing item 1000 is $3,000.00