Policy Servicing · Surrender

Policy Surrender — Full Lifecycle

How a Surrender Service Request actually moves through LifeX today: automatic eligibility analysis, a server-computed calculation dispatched by real product type, a genuine maker≠checker approval gate, a final recalculation, and settlement — fund redemption, a Finance payable, and the policy status change, in that order.

Workflow code: SURRENDER_MCA Request types: Surrender + SurrenderReversal Product types covered: 6 Effective: 8 September 2026

The one rule that governs this flow: the browser never sends a financial figure and the server never trusts one it was sent. Eligibility, Fund Value, Surrender Charge and the Net Surrender Value are all computed fresh — once at Submit as a quote, and again at Approval as the figure that actually settles — and the checker approving a request can never be the same person who submitted it.

Surrender reuses the existing after-sales Service Request framework and the existing generic Workflow engine rather than introducing either a second request type or a second approval engine — see Database & API Reference for exactly what was reused versus newly built.

01 End to End

The Full Lifecycle

From the maker selecting Surrender in the New Service Request screen through to the policy actually changing status — nothing here is skipped or short-circuited.

flowchart TD
  classDef startEnd fill:#7c3aed,stroke:#4c1d95,color:#ffffff,stroke-width:1px;
  classDef process fill:#eef0f6,stroke:#94a0c2,color:#1b2033,stroke-width:1px;
  classDef decision fill:#fdf1de,stroke:#92400e,color:#1b2033,stroke-width:1px;
  classDef gate fill:#cffafe,stroke:#0e7490,color:#083344,stroke-width:1px;
  classDef success fill:#e4f7ee,stroke:#047857,color:#052e21,stroke-width:1px;
  classDef fail fill:#fde8ed,stroke:#9f1239,color:#3f0a17,stroke-width:1px;

  A(["Maker selects Surrender"]):::startEnd --> B["Automatic Eligibility Check
policy status, pending claim,
conflicting request, KYC, IBAN,
min. duration"]:::process B --> C{"Mandatory checks pass?"}:::decision C -- "No" --> C1(["Blocked before submission"]):::fail C -- "Yes" --> D["Automatic Calculation
Fund Value / Cash Value / Not Applicable"]:::process D --> E["Submit — Service Request +
Surrender Transaction created atomically
Status: Submitted"]:::process E --> F["SURRENDER_MCA Workflow"]:::gate F --> G{"Checker decision"}:::decision G -- "Reject" --> G1(["Status: Rejected
no policy / fund / finance effect"]):::fail G -- "Approve" --> H{"Checker ≠ Maker?"}:::gate H -- "Same person" --> H1(["Blocked: cannot approve
own submission"]):::fail H -- "Different, authorized user" --> I["Final Recalculation
(never trusts the quoted figure)"]:::process I --> J{"Matches quote within SAR 1.00?"}:::decision J -- "Yes" --> K["Settlement"]:::process J -- "No, differs" --> L(["Status: Approved
awaiting explicit settlement"]):::fail L -. "Checker: Confirm & Settle" .-> K K --> M["Redeem fund units at current NAV
(fund-linked types only)"]:::process M --> N["Post SurrenderPayable voucher
GL 5202 / 2102"]:::process N --> O["Policy.Status → Surrendered"]:::process O --> Z(["Service Request: Completed"]):::success
Start
System step
Decision
Maker≠checker gate
Completed
Blocked / rejected
Nothing is atomic-fragileThe Service Request row and its Surrender Transaction header are created in one SQL transaction (SPLI_ServiceRequest_CreateSurrender) — a failure partway rolls back both rather than leaving an orphan, a real bug hit and fixed while building this.
The policy stays untouched until settlementSubmitting, and even Approving, never flips Policy.Status by itself — only the settlement step at the very end does, and only after the figure has been recalculated fresh.
Maker≠checker is enforced in code, not just roleThe generic Workflow engine only checks that the approver holds the required role; ServiceRequestService.ApproveSurrenderAsync separately checks the workflow transaction's own submitter and blocks a match.
02 Before Submission

Eligibility Checks

Every check is a real, live read against the policy, its claims, and its customer — never a static rule. A failed mandatory check blocks Submit with the exact reason shown.

CodeCheckReal source
PolicyStatusPolicy is currently ActivetblPolicy.Status
NoConflictingTransactionNo other Surrender already pending on this policySPLI_ServiceRequest_HasPendingSurrender
NoPendingClaimNo claim on this policy outside Rejected / Repudiated / ClosedIClaimService.GetAllAsync
KycVerifiedPolicyholder's KYC status is Verified (Individual only)tblCustomer.KycStatus
IbanPresentA bank IBAN exists for the payable's referencetblPolicy.IBAN
MinimumDurationPolicy year has reached the plan's configured minimum, if anytblProductPlan.MinSurrenderPolicyYear
03 Never Fabricated

Calculation by Product Type

The engine dispatches on the policy's real ProductTypeCode. A product type with no configured cash-value rule shows Configuration Required — it never invents a rate.

Product TypeBasisStatus today
TermNo cash value by designNot Applicable
Whole LifeGuaranteed / Special Surrender Value % · tblProductPlan_SurrenderValueConfig Required
EndowmentGuaranteed / Special Surrender Value, Bonuses · tblProductPlan_SurrenderValueConfig Required
SavingsUnits × current NAV, real fund holdingsLive
Unit-LinkedUnits × current NAV, real fund holdingsLive
RetirementUnits × current NAV where fund-linkedLive
Fund Value is realIFundService.GetPolicyFundValueAsync — the same NAV engine every other fund-linked request type already uses, not a separate calculation.
The Surrender Charge % is real, tooRead from the plan's own declining tblProductPlan_SurrenderCharge schedule for the current policy year — the same table the Illustration engine reads.
Policy Loan is honestly zeroNo policy-loan subsystem exists anywhere in LifeX. The field is carried in every calculation as a real, always-zero line — ready to consume a real loan feature later, never hidden.
04 Separate Request Type

Surrender Reversal

SurrenderReversal is its own, pre-existing Service Request type — explicitly out of scope for this build and left untouched.

What it doesGuardEffect
SPLI_Policy_ReverseSurrenderPolicy must currently be Surrendered, else it raises an errorFlips Policy.Status back to Active — nothing else
Yes, it's implemented — but it no longer mirrors what forward Surrender now does

Reversal only ever flipped the status back, on the old assumption that forward Surrender "only flips Status, with no fund/finance side-effect." That assumption is now false: this build's Surrender settlement genuinely redeems fund units and posts a real SurrenderPayable voucher. Reversing a surrender completed through the new engine leaves the policy Active again with its fund units still at zero and the payable still sitting in Finance — a real gap, not yet closed.

05 For Engineers

Database & API Reference

What's genuinely new versus what this build reused as-is.

ObjectKind
tblPolicy_TransactionSurrender Transaction header (TransTypeCode='Surrender', already seeded, never used until now)Reused
tblProductPlan_SurrenderChargeDeclining surrender-charge scheduleReused
IFundServiceFund valuation & unit redemptionReused
IWorkflowServiceGeneric maker-checker engineReused
tblProductPlan_SurrenderValueWhole Life / Endowment cash-value config, governed via PRICING_MCANew
tblPolicy_TransactionCalculationFull calculation snapshot per attempt (Quotation / Final)New
SPLI_Fin_RecordSurrenderPayableGL 5202 (Expense) / 2102 (Payable)New
SURRENDER_MCAWorkflow definition, default stage: Operations ManagerNew
POST /api/service-requests/surrenderSubmitNew
POST .../surrender/approve · /reject · /settleMaker≠checker lifecycleNew
06 Honest Status

Known Gaps

Flagged deliberately, not discovered later.

Whole Life & Endowment need real actuarial rates

The calculation framework and config schema (tblProductPlan_SurrenderValue) are live and governed, but no plan has rates entered yet — every calculation for these two types correctly returns "Surrender Value Configuration Required" until your actuarial team supplies real Guaranteed/Special Surrender Value percentages.

Surrender Reversal doesn't undo fund/finance effects

See Surrender Reversal above — reversing a settled surrender does not restore fund units or reverse the posted payable.

Everything else was verified live, end-to-end

A real Unit-Linked policy was submitted, blocked on self-approval, approved by a different authorized user, and settled — fund units redeemed to zero, a real SurrenderPayable voucher posted, policy flipped to Surrendered only at that point.