Write-Off
Executive Summary
Purpose & Scope
- Support AR write-off processes that comply with GAAP, streamline approvals, and provide a robust audit trail.
- Scope includes: Eligibility review, packet creation and management, multi-level approvals (based on commission thresholds), NetSuite GL posting, packet and recovery workflows, status tracking, email notifications, and audit documentation.
- Out of scope: Commission booking, general AR collections, client onboarding, and tax reporting.
Objectives
- Establish standardized eligibility and documentation requirements
- Enforce dual-approval controls and proper authorization
- Ensure consistent execution and data retention for reliable reporting and audits
Process Overview
Approval Flow
graph TD
Draft((Draft Packet))
Submitted((Submitted))
Agent((Agent Approval))
DH((Dept Head Approval))
VP((VP Client Acct Approval))
CFO((CFO Approval))
MD((MD Approval))
Complete((Complete - Executed))
Rejected((Rejected))
Cancelled((Cancelled))
Draft --> Submitted
Submitted --> Agent
Agent --> DH
DH --> VP
VP -->|< $50K| Complete
VP -->|>= $50K| CFO
CFO -->|$50K - $250K| Complete
CFO -->|> $250K| MD
MD --> Complete
Agent -->|Reject| Rejected
DH -->|Reject| Rejected
VP -->|Reject| Rejected
CFO -->|Reject| Rejected
MD -->|Reject| Rejected
Rejected -->|Resubmit| Submitted
Rejected -->|Cancel| CancelledFigure 1: Packet Approval Flow with Commission-Based Routing
Core Rules & Requirements
Eligibility & Packet Creation
- Every receivable in a packet must have one eligibility criterion and supporting documentation—either attached at the receivable level or to the packet as a whole (if the 'Packet Document' flag is set).
| Eligibility Criterion | Documentation Required |
|---|---|
| Aged | Collection activity log |
| Uncollectible | Client communication or legal documentation |
| Bankruptcy | Court document |
| Agent Request | Formal written agent request |
All receivables in a packet must relate to a single client, and packet names must be unique.
Receivable Assignment Rule:
- A receivable can only be part of one packet at a time unless that packet has been Cancelled or Recovered.
Draft packets support add/remove/edit, mass-update, justification text, full delete, and multiple saves.
Packet submission is blocked unless all above requirements are satisfied.
Approval Matrix & Workflow
IMPORTANT
Future Enhancement: Threshold-based routing is documented below but not yet implemented in code. Current implementation uses simplified approval (DRAFT/SUBMITTED → APPROVED).
| Total Commission | Route/Required Approvers |
|---|---|
| < $50,000 | Agent → Dept Head → VP, Client Accounting |
| $50,000–$250,000 | Agent → Dept Head → VP CA → CFO |
| > $250,000 | Agent → Dept Head → VP CA → CFO → MD |
- Approval is sequential/role-driven as per above.
- Approver actions: approve (advance), or reject (return to Client Accounting with comments).
- All actions/time/reason/actor logged. Each transition triggers email notifications with actionable links.
- On resubmission, packet resumes workflow at appropriate stage.
Write-Off Execution & Integration
- Final approval triggers automated execution:
- The system creates a dedicated Cash Receipt of type
WRITE_OFF.- Amount: Sum of all receivable balances (Revenue + Payables) in the packet.
- Status: Auto-approved.
- A corresponding Cash Receipt Worksheet is created and auto-approved (
Status = 'A'). - Applications are created for each receivable in the packet:
- Applied Amount equals the outstanding balance (clearing the receivable).
- Both Revenue and Payable sides of the billing item are addressed if balances exist.
- Updates:
billing_item.openItemIndset tofalse.billing_item_detail.writeOffStatusCdset to'WRITTEN_OFF'.billing_item_detail.excludeFromCeclIndset totrue.write_off_packet.cashReceiptIdlinked to the created receipt.
- GL Posting:
- Summarized AR posting to NetSuite via the Cash Receipt sync.
- DR: Bad Debt Expense / CR: Accounts Receivable.
- The system creates a dedicated Cash Receipt of type
Recovery & Audit
- Recovery (After Packet Completion):
- When a receivable that was written off is to be recovered, the user initiates the Recover action.
- System Action:
- Verifies packet is in
APPROVEDorCOMPLETEstatus. - Creates a Reversal Worksheet linked to the original Write-Off Cash Receipt.
- Creates negative applications to reverse the original write-off amounts.
- Updates
billing_item.openItemIndtotrue(Reopens receivable). - Updates
billing_item_detail.writeOffStatusCdto'RECOVERED'. - Updates
write_off_packet.packetStatusCdto'RECOVERED'.
- Verifies packet is in
- Result: Receivables become available for standard cash application processing (e.g., applying a new check).
- Process:
- Partial Recovery is not permitted: Recovery reverses all receivables in the packet; individual receivable recovery within a packet is not supported.
- If only some receivables in a packet require recovery, the entire packet must still be recovered.
- Any receivables within the recovered packet that do not receive a recovery payment must be re-written off by creating a new packet and following the standard approval process.
User Interface Specifications
Write-Off Packet Screens
- Key Features:
- Receivable search (multiple fields/filters):
- Entity
- Department
- Deal
- Client
- Buyer
- Agent
- Invoice Number
- Invoice Date Range
- Commission Amount Min-Max Range
- Age (Days) Min-Max Range
- Packet Name
- Packet Status
- Write-Off Recommended Flag
- Search results grid with multi-select and “Create Packet” from selection
- Entity
- Department
- Deal
- Client
- Buyer
- Agent
- Invoice Number
- Invoice Date
- Commission Amount
- Age (Days)
- Write-Off Recommended Flag
- Packet Name Link to Packet Detail View
- Packet Status
- Validations: all must be for one client; checks for packet name uniqueness/required docs
- Receivable search (multiple fields/filters):
- Draft Mode:
- Packet header (name, client, totals, status)
- Packet Name
- Client
- Total Commission
- Packet Status
- Mass eligibility criteria updates and per-row override
- Document upload per row or for packet
- “Submit for Approval” only enabled once all docs/criteria present
- Packet header (name, client, totals, status)
Approval Screen (All Roles)
- Approvers access this screen either by:
- Clicking actionable links in their email notifications (sent when a packet requires their review/approval), or
- Searching for packets on the Write-Off Packet screen that are awaiting their approval (filtered by status).
- The Approval screen is functionally identical to the Packet Detail View, displaying all the same fields and information.
- The only difference is that the Packet Status field is editable, limited to the status options valid for the current approver’s role (e.g., Approve, Reject).
- Packet Status Comment field will be required when rejecting.
- All non-approval edits (e.g., modifying receivables, documentation, or eligibility) remain restricted to Client Accounting; approvers cannot change packet content—only status and comments.
- After a status change, the system displays a completion/confirmation message and triggers appropriate workflow updates (e.g., advancing to next approver or notifying Client Accounting if rejected).
Recovery Processing Screen
- Client Accounting users must search for receivables associated with a completed packet using the Write-Off Packet search screen.
- When a completed packet is opened from the search results, the screen shows the Packet Detail View, with all fields read-only except for the Recover action.
- The Recover action is available only to users with the Client Accounting role and only for completed packets.
- Clicking Recover initiates the actions outlined in Recovery & Audit, including reversal of all receivables in the packet and triggering standard cash application processing.
Packet Detail View
Collapsible header showing all packet-level properties:
- Packet Name
- Client
- Total Commission
- Packet Status
- Approval History (all status changes, actions, approvers, timestamps, comments)
- Created By / Creation Date
- Upload Documentation [Button] (for packet-level documentation)
Receivables in Packet List:
- Invoice Number
- Invoice Date
- Commission Amount
- Age (Days)
- Eligibility Criterion
- Comments:
- Field to add or display justification for each receivable.
- Comments are versioned: The full history for each receivable’s comments is available, showing every previous comment along with the user and timestamp.
- Docs [Action]:
- Button/icon for uploading, downloading, or viewing documents at the receivable level.
- Delete (to remove individual receivable, if allowed by workflow/state)
Receivables-Level Actions (buttons for batch/packet operations):
- Add Receivables
- Remove Selected
- Cancel
- Delete Packet (entire packet)
- Save as Draft
- Submit for Approval (enabled only when all validations pass)
Data Requirements
All tables below have tracking for creation, last update, user, and status—all actions auditable.
Write-Off Packet Table (write_off_packet)
| Field Name | Data Type | Description |
|---|---|---|
write_off_packet_id (PK) | GUID | Unique identifier for the packet. |
packet_name | String | User-entered name; unique constraint. |
client_id (FK) | Integer | Reference to Client (party_id). |
packet_eligibility_criteria_cd | String | Default eligibility for packet. |
cash_receipt_id (FK) | Integer | Links to the cash_receipt created upon approval. |
total_commission_amt | Decimal | Denormalized sum of commission. |
receivable_count | Integer | Number of receivables. |
packet_status_cd | Enum | Current workflow state. |
current_approver_role | String | Role currently required to approve. |
submitted_dt | DateTime | Timestamp of submission. |
submitted_by_user_id (FK) | Integer | User who submitted the packet. |
completed_dt | DateTime | Timestamp of write-off execution. |
completed_by_user_id (FK) | Integer | User who approved final completion. |
recovered_dt | DateTime | Timestamp of recovery execution. |
recovered_by_user_id (FK) | Integer | User who initiated recovery. |
rejected_dt | DateTime | Timestamp of last rejection. |
rejected_by_user_id (FK) | Integer | User who rejected the packet. |
rejection_reason | String | Reason for last rejection. |
packet_status_cd Values: DRAFT, SUBMITTED, RESUBMITTED, APPROVED, APPROVED_AGENT, APPROVED_DH, APPROVED_VP, APPROVED_CFO, APPROVED_MD, REJECTED_AGENT, REJECTED_DH, REJECTED_VP, REJECTED_CFO, REJECTED_MD, COMPLETE, RECOVERED.
Packet-Receivable Link Table (packet_receivable)
| Field Name | Data Type | Description |
|---|---|---|
packet_receivable_id (PK) | GUID | Unique ID. |
write_off_packet_id (FK) | GUID | Parent packet. |
billing_item_detail_id (FK) | Integer | Reference to the Receivable (billing_item_detail). |
eligibility_criteria_cd | String | AGED, UNCOLLECTIBLE, BANKRUPTCY, AGENT_REQUEST. |
use_packet_document_ind | Boolean | If true, uses packet-level docs. |
Packet Status History Table (packet_status_history)
| Field Name | Data Type | Description |
|---|---|---|
packet_status_history_id (PK) | GUID | Unique ID. |
write_off_packet_id (FK) | GUID | Parent packet. |
from_status_cd | String | Status before change. |
to_status_cd | String | Status after change. |
action_cd | String | Action taken (SUBMIT, APPROVE, REJECT, RECOVER). |
approver_role | String | Role of user taking action. |
comment_text | String | Rejection reason or approval comment. |
Validations Table
| Validation Rule | Context | Enforcement Method |
|---|---|---|
| Unique packet name | Packet | API: isPacketNameUnique |
| All receivables share client_id | Packet-Receivable link | API: validateForSubmission |
| One eligibility per receivable | Packet-Receivable link | Database / API Schema |
| Documentation per receivable or packet (flagged) | Packet/Packet-Receivable link | API: validateForSubmission |
| Receivable only in one packet at a time | Packet-Receivable link | App Logic / Workflow Status |
| Sequential approval enforced | Approval workflow | Service Logic |
Security & Data Retention
- Only Client Accounting users may create/edit/submit packets or process recoveries.
- Approval and status-change restricted by user’s role and workflow state.
- Initiators cannot approve their own submission.
- All actions, approvals, rejections, edits, and recoveries logged with timestamp/user.
- Data, audit trail, and docs retention per regulatory guidelines.
Gherkin Scenarios
Scenario: Successfully Write-Off an Aged Receivable
Feature: Write-Off Packet Creation and Approval
Scenario: User creates and approves a write-off packet for an aged invoice
Given I am a "Client Accounting" user
And I have a receivable for Client "XYZ Corp" with 120 days aging
When I create a new write-off packet named "XYZ-2025-Q1" for Client "XYZ Corp"
And I add the aged receivable to the packet
And I set the eligibility criterion to "Aged"
And I upload supporting "Collection Log" documentation
And I submit the packet for approval
Then the packet status should correspond to the lowest required approval level
When the "Agent" approves the packet
And the "Department Head" approves the packet
And the "VP Client Accounting" approves the packet
Then the packet status should be "APPROVED"
And a "WRITE_OFF" Cash Receipt should be created
And the receivable open balance should be 0.00
And the receivable status should be "WRITTEN_OFF"Scenario: Reject a Write-Off Packet
Feature: Write-Off Packet Rejection
Scenario: Approver rejects a packet due to insufficient documentation
Given a write-off packet "XYZ-Draft" is in "SUBMITTED" status
And the current approver is "Agent"
When the "Agent" rejects the packet with reason "Missing latest email correspondence"
Then the packet status should be "DRAFT"
And the rejection reason should be recorded in the history
And the "Client Accounting" user should be notifiedScenario: Recover a Written-Off Packet
Feature: Write-Off Recovery
Scenario: Recovering a packet that was previously written off
Given a write-off packet "XYZ-2025-Q1" is in "APPROVED" status
And the associated receivables are closed
When I click "Recover" on the packet details page
Then a "Reversal Worksheet" should be created
And the packet status should be "RECOVERED"
And the receivables should be reopened (Open Item Indicator = true)
And the receivable write-off status should be "RECOVERED"Scenario: Validation Failure on Submission
Feature: Packet Submission Validation
Scenario: User attempts to submit packet without unique name
Given a write-off packet named "Existing-Name" already exists
When I attempt to create a new packet with name "Existing-Name"
Then the system should prevent creation
And display an error "Packet name already exists"
Scenario: User attempts to submit packet with mixed clients
Given a packet created for Client A
When I attempt to add a receivable belonging to Client B
Then the system should prevent the addition
And display an error "Receivable must belong to the same client"