---
title: BillSaveService
summary: API reference for BillSaveService.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-24
library: SAIF.Services
reference_type: package
---

# BillSaveService

Owns the create/update/approve/adjust/delete lifecycle for legal bills, validating each request via `IValidationService` and `IBillServiceHelper` date rules, resolving the correct approver through `ILegalService.DetermineAssignedAttorney` and `ILegalQueryProvider.GetAttorneyProfile`, and persisting changes inside `IUnitOfWork` transactions with optimistic-concurrency checks on `UpdateNumber`.

## Purpose
- Creates and updates legal bills (`SaveBill`), reconciling submitted bill-service lines against the database and recording status-history entries on status changes.
- Submits a bill for approval (`SubmitBill`), resolving the assigned attorney/approver or falling back to in-progress status when none can be determined.
- Approves a bill (`ApproveBill`) and auto-approves when the resolved approver's payment authority covers the billed amount, or escalates to the approver's manager otherwise (`UpdateBillStatusCode`).
- Approves a bill with changes (`ApproveWithChanges`) and returns/disapproves a bill for rework (`DisapproveBill`).
- Creates an adjusted copy of a paid bill for correction (`AdjustBill`), carrying forward only changed service lines and building an audit-trail comment describing each change.
- Deletes a bill (`DeleteBill`), un-amending any parent-bill service lines that referenced it.
- Validates a bill and its service lines against date-range and adjustment-reason business rules (`ValidateBill`/`ValidateBillServices`) before allowing a save.

## Context
- Namespace: `SAIF.Services.Legal.Bills`
- Assembly/package: `SAIF.Services` (packaged inside the `SAIF.SAIFCode.Core` NuGet package).

## Public members
| Member | Signature | Purpose |
| --- | --- | --- |
| BillSaveService (constructor) | `public BillSaveService(IUnitOfWork unitOfWork, IUserProfileService userProfileService, IValidationService validationService, ILegalQueryProvider legalQueryProvider, IBillServiceHelper billServiceHelper, ILegalService legalService)` | Wires the unit of work, user-profile, validation, legal-query, bill-service-helper, and legal-service collaborators, and captures the current user ID (as a static field shared across instances). |
| AdjustBill | `public long AdjustBill(LegalBill bill, IMessageContainer messageContainer, string newStatus)` | Builds an adjusted copy of the bill (new bill ID, `ParentBillId` set to the original, only changed service lines retained) and saves it as a new bill; adds a "no changes" error and returns `0` if no service lines changed. |
| ApproveBill | `public long ApproveBill(LegalBill bill, IMessageContainer messageContainer)` | Sets approver/approved-date (unless already `ApprovedWithChange`), determines the resulting status via authority-limit comparison, and saves the bill. |
| ApproveWithChanges | `public long ApproveWithChanges(LegalBill bill, IMessageContainer messageContainer)` | Sets approver/approved-date, forces status to `ApprovedWithChange`, appends an "[APPROVED WITH CHANGES]" comment marker, and saves the bill. |
| DeleteBill | `public bool DeleteBill(long billId, IMessageContainer messageContainer)` | Deletes the bill transactionally and, if it was an adjustment of a parent bill, clears the `IsAmended` flag on the parent's corresponding service lines; returns `false` if the bill does not exist. |
| DisapproveBill | `public long DisapproveBill(LegalBill bill, IMessageContainer messageContainer)` | Clears approver/approved-date, sets status to `InProgress`, appends a "[Returned by approver]" comment marker, and saves the bill. |
| SaveBill | `public long SaveBill(LegalBill bill, IMessageContainer messageContainer)` | Defaults a null bill status to `InProgress` and saves the bill (insert or update based on `bill.Id`). |
| SubmitBill | `public long SubmitBill(LegalBill bill, IMessageContainer messageContainer)` | Resolves an approver via `SetApproverId`; if none can be resolved, falls back to a plain `SaveBill`, otherwise sets status to `PendingApproval` and saves. |

## Behavioral notes
- `SaveLegalBill` (the shared private persistence path for all public mutation methods) skips business-rule validation for bills already in `PaymentReturned`/`Paid` status, but always enforces optimistic concurrency by comparing `bill.UpdateNumber` against the current database value and rolling back with an error if they differ.
- New bills get a fresh `BillStatusHistories`/`BillServices` collection, a create-annotation comment, today's issue date, and a status-history entry; updates append comments via append-annotation formatting and add a new status-history entry only when the status code actually changes.
- `PrepareAdjustedBillForSave` computes a human-readable comment describing each service-line change (partial return, additional payment, service code change, from/thru date changes) and reverts the new bill to `Approved` status when the amount was only partially returned or the service code changed without an amount change.
- `UpdateBillStatusCode` auto-approves when either the approver's initiate or approve authority limit covers the billed amount; otherwise it escalates the request to the attorney's manager and appends a note explaining the escalation.
- `ValidateBill`/`ValidateBillServices` combine FluentValidation results with `IBillServiceHelper` date-range checks and adjustment-reason business rules (e.g., a zero-paid, zero-total bill's adjustment reason must be one of duplicate/unknown/free-form).
- `SetApproverId` resolves the assigned attorney via `ILegalService.DetermineAssignedAttorney`, falling back to the bill's requesting employee, then looks up that attorney's profile; if the attorney profile has an expiration date, the bill is routed to the attorney's manager instead of the attorney.

## Constraints and usage notes
- Only `unitOfWork`, `userProfileService`, `validationService`, `legalQueryProvider`, `billServiceHelper`, and `legalService` are null-checked in the constructor via `Enforce.ArgumentNotNull`; the constructor also assigns a `static string _currentUser` field, so the "current user" reflects the most recently constructed instance rather than being strictly per-instance.*
- All public mutation methods require non-null `bill` and `messageContainer` arguments, enforced via `Enforce.ArgumentNotNull`.

> **Note:** Statements marked with * were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with ** could not be confirmed; treat as unknown until verified.

## Sources
- `Services/SAIF.Services/Legal/Bills/BillSaveService.cs`
