nhance/app/Views/docs/eb-rack-rate-config.php
2026-05-18 12:28:19 +05:30

464 lines
26 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* EB rack rate config — content only
* app/Views/docs/eb-rack-rate-config.php
*/
?>
<h2 id="purpose">Purpose</h2>
<p>
<strong>EB rack rate config</strong> is how Nhance authors <strong>premium rack rates</strong> for a client policy:
rate tables (SI, age, grade, relationship, etc.) plus two <strong>policy-wide behaviours</strong> that apply to
<strong>every</strong> grid type (113): <a href="#premium-calculation-modes">Premium calculation</a> and
<a href="#applicable-family-members">Applicable family members</a>. GMC policies may also define multiple
named rack rates (Primary + additional tabs). Technical save/load paths reference
<code>ClientController</code>, <code>policy_grid.php</code>, and <code>policy_grid_excel.php</code>.
For the <strong>Excel employee onboarding</strong> pipeline that consumes this configuration and runs
<code>calculate_premium_new</code>, see
<a href="<?= base_url('docs/eb-rack-rate-calculation') ?>">EB rack rate calculation</a>.
</p>
<h2 id="premium-calculation-modes">Premium calculation (all grid types)</h2>
<p>
The modal exposes <strong>Premium calculation</strong> as three radios: <strong>Individual</strong>,
<strong>Family floater</strong>, and <strong>Family floater cum Individual</strong>. This choice is stored
with the rack (e.g. <code>premium_type</code> on premium rows where used) and interpreted <strong>when
premiums are calculated in downstream flows</strong> (enrollment, endorsements, etc.) — not re-derived from
the grid layout alone.
</p>
<table>
<thead>
<tr><th>Mode</th><th>Meaning (at calculation time)</th></tr>
</thead>
<tbody>
<tr>
<td><strong>Individual</strong></td>
<td>
Sum insured is covered <strong>per individual</strong> family member, and <strong>premium is calculated
(and applied) per member</strong> according to the rack rules and member attributes.
</td>
</tr>
<tr>
<td><strong>Family floater</strong></td>
<td>
Sum insured applies to the <strong>whole family</strong> as one floater cover. Premium is calculated for
the family unit, but the amount is <strong>stored / represented against the self member only</strong>
(single premium bucket for the floater).
</td>
</tr>
<tr>
<td><strong>Family floater cum Individual</strong></td>
<td>
<strong>Combines both</strong> behaviours where the product rules require floater cover together with
individually rated members (exact split depends on policy / insurer rules in the calculation engine).
</td>
</tr>
</tbody>
</table>
<div class="callout info">
<span>i</span>
<div>
For developers: the rack modal captures the <strong>mode</strong> and the <strong>rate table</strong>; always
trace how <code>premium_type</code> (and policy terms) are read in the <strong>premium calculation</strong> path
you are debugging — not only in <code>createClientPolicyPremium</code>.
</div>
</div>
<h2 id="applicable-family-members">Applicable family members (all grid types)</h2>
<p>
The section <strong>“Choose applicable family members”</strong> is driven by what the <strong>policy
terms</strong> allow for that client policy (who can exist on the cover). The user then selects which of
those relationships are <strong>in scope for this specific rack rate</strong> (Self / Spouse / Children /
Parents / Parents in law) using the radio options below.
</p>
<h3 id="applicable-members-radio-meanings">Radio option meanings (per relationship row)</h3>
<p>
Each relationship (Self, Spouse, Children, Parents, Parents in law) uses the same vocabulary of choices.
These define <strong>eligibility rules for this rack rate</strong>, not the member list itself.
</p>
<table>
<thead>
<tr><th>Choice</th><th>Meaning for this rack rate</th></tr>
</thead>
<tbody>
<tr>
<td><strong>Yes</strong></td>
<td>That relationship <strong>must</strong> be present in the incoming family for this rack rate to apply.</td>
</tr>
<tr>
<td><strong>No</strong></td>
<td>That relationship <strong>must not</strong> be present — if it is, this rack rate does not match.</td>
</tr>
<tr>
<td><strong>Maybe</strong></td>
<td>If that relationship is <strong>available</strong> on the family, it is <strong>included in</strong> this rack rates match; if not present, the rack can still match without it.</td>
</tr>
<tr>
<td><strong>NA</strong></td>
<td>Even if members of that relationship exist on the policy, they are <strong>not applicable</strong> to this rack rate (this rack never prices or targets them).</td>
</tr>
<tr>
<td><strong>Any</strong></td>
<td><strong>Any number</strong> of members with that same relationship is allowed for a match (no fixed count).</td>
</tr>
<tr>
<td><strong>1, 2, 3, 4, …</strong> (numeric)</td>
<td>An <strong>exact count</strong> of members with that relationship must be present for this rack rate to match (e.g. exactly two parents).</td>
</tr>
</tbody>
</table>
<h3 id="applicable-members-matching">How rack rates are picked at premium time</h3>
<p>
At calculation time (enrollment / endorsement / etc.), the engine compares the <strong>incoming familys
members and relationships</strong> (who is on the cover and in what roles) against <strong>each configured rack
rates applicable members</strong> (the rules saved in <code>additional_relationship</code> for that tabs rack).
Racks whose rules <strong>match</strong> the family pattern are candidates for premium.
</p>
<p>
<strong>More than one rack rate can match</strong> the same family (e.g. a base employee rack and a separate
parents top-up). In those cases <strong>multiple rack rates may apply</strong> and <strong>premium is calculated
accordingly</strong> (combined according to product rules in the calculation path — not in the modal UI alone).
</p>
<p>
The diagram below is <strong>conceptual</strong>: the exact class or function name lives in your premium /
onboarding pipeline, but the decision order is what new developers should internalize.
</p>
<div class="mermaid-wrapper" id="rack-rate-family-matching-flowchart">
<div class="mermaid">
flowchart TD
Start([Start premium run]) --> Family["Incoming family snapshot:<br/>members, relationship, counts"]
Family --> Load["Load active rack config for policy<br/>policy_premium_1 / 2, is_active = 1<br/>each rack_rate_name + rules + grid rows"]
Load --> Compare["For each rack rate:<br/>compare family vs applicable members<br/>Yes / No / Maybe / NA / Any / count"]
Compare --> Matched["Build matched rack list<br/>0, 1, or many racks"]
Matched --> Calc["Premium engine:<br/>use premium_type + rate table per matched rack"]
Calc --> Multi{"Several racks matched?"}
Multi -->|Yes| Combine["Combine premium<br/>per product rules"]
Multi -->|No| Single["Single-rack premium"]
Combine --> Done([Allocate to members / floater])
Single --> Done
</div>
</div>
<ul>
<li><strong>Why multiple tabs exist:</strong> A single policy may define <strong>several rack rates</strong> (GMC
Primary + “Add Rack Rate” tabs). Each tab encodes a different applicable-member pattern and/or rate table.</li>
<li><strong>Persisted as:</strong> On save, the selections are stored in <code>additional_relationship</code>
JSON on premium rows (keys such as <code>self</code>, <code>spouse</code>, <code>childrens</code>,
<code>parents</code>, <code>parents-in-law</code>). Grid ids <strong>1</strong> and <strong>2</strong> force a
simplified relation map in the controller (self-only path).</li>
<li><strong>Reload in UI:</strong> For GMC, <code>getpolicyGridData</code> returns <code>jsonArray</code> keyed by
<code>rack_rate_name</code> so the modal can restore checkbox state per tab.</li>
</ul>
<h2 id="how-to-configure-rack-rate">How to configure a rack rate (checklist)</h2>
<ol>
<li><strong>Policy terms first.</strong> Ensure <code>client_policy.policy_terms</code> reflects allowed members,
family floater flags, SI ladders, etc. The rack UI inherits what is allowed.</li>
<li><strong>Open the Rack Rate modal</strong> (<code>.btnPolicyModel</code>) for the target <code>client_policy_id</code>.</li>
<li><strong>Set Premium calculation</strong> — pick Individual, Family floater, or Family floater cum Individual
per product rules (see <a href="#premium-calculation-modes">Premium calculation</a>).</li>
<li><strong>Set applicable family members</strong> for <em>this</em> rack rate tab — narrow from policy-allowed
members to who this table applies to (see <a href="#applicable-family-members">Applicable family members</a>).</li>
<li><strong>Choose Policy premium type</strong> — maps to <code>policy_grid_id</code> 113 from
<code>policy_grid_master</code> (see <a href="#grid-types-1-13">Grid types</a>).</li>
<li><strong>Fill the grid</strong> — manual rows, “+” rows, and/or <strong>Copy from excel</strong> using headers
from <code>policy_grid_excel.php</code>.</li>
<li><strong>Save</strong> — POST to <code>client/premimum/create</code>; confirm no validation errors (Parsley,
SI vs policy terms, duplicate family composition across tabs where enforced).</li>
<li><strong>Repeat for additional GMC tabs</strong> if the product uses more than one named rack rate.</li>
</ol>
<h2 id="ui-entry">Where it appears in the UI</h2>
<ul>
<li><strong>View files:</strong> <code>app/Views/policy_grid.php</code> (modal shell, tabs, forms) and
<code>app/Views/policy_grid_excel.php</code> (Excel header maps, paste helpers, <code>copyHeaders</code> / <code>generateTable</code>).</li>
<li><strong>Open modal:</strong> A control with class <code>.btnPolicyModel</code> passes <code>data-id</code> (client policy id) and policy type context; the script loads grid definitions and any saved premiums (see AJAX below).</li>
<li><strong>GMC vs GPA in the modal:</strong> For GPA (<code>policy_type_string == 'GPA'</code> or policy type ids 6 / 7), “Add Rack Rate” and the primary tab chrome are hidden and the submit button label is “Submit”. For GMC, multiple rack-rate tabs are supported (<code>appendNewTab</code>, rename).</li>
<li><strong>Forms:</strong> Each tab uses a form whose id starts with <code>GridForm_</code> (primary <code>GridForm_</code>, additional tabs <code>GridForm_{tabId}</code>). Hidden fields carry <code>client_id</code>, <code>client_policy_id</code>, <code>rack_rate_name</code> (e.g. Primary), and the selected <code>policy_grid_id</code> from the “Policy Premium Type” dropdown.</li>
</ul>
<h2 id="getpolicy-grid-data">Load grid data — <code>ClientController::getpolicyGridData</code></h2>
<p><strong>HTTP:</strong> <code>GET</code> <code>util/policy-premium</code> with query <code>client_policy_id</code> (see <code>app/Config/Routes.php</code> under the <code>/util</code> group). The front end calls <code>base_url("util/policy-premium")</code> with that query parameter.</p>
<p><strong>What it does:</strong></p>
<ol>
<li>Loads <code>client_policy</code> joined to <code>policy_type</code> and reads <code>policy_terms</code> JSON (family floater flags, <code>family_floaters</code>, etc.).</li>
<li>Resolves <code>terms_si_amount_array</code> via <code>getPolicyTerms($client_policy_id)</code> and branch units via <code>getBranchUnitsByBranchId</code>.</li>
<li>Counts active employees on the policy for UI gating (<code>hideShowSubmitButton</code>).</li>
<li>Derives a search token from policy type name / id: <strong>GMC</strong> (regex on type name, or type id 72) vs <strong>GPA</strong> (regex or type ids 6 / 7).</li>
<li>Fetches grid <strong>templates</strong> from <code>policy_grid_master</code> via <code>PolicyGridModel::like('policy_type', $search_term)</code> — these rows drive which “Policy Premium Type” options exist and what columns each grid id expects.</li>
<li><strong>GPA:</strong> Loads saved rows from <code>policy_premium_1</code> for this client + client_policy where <code>is_active = 1</code>.</li>
<li><strong>GMC:</strong> Loads saved rows from <code>policy_premium_2</code> for the same scope. Also builds <code>jsonArray</code>: grouped by <code>rack_rate_name</code>, each value is <code>json_decode(additional_relationship)</code> (family-composition flags used when re-rendering checkboxes).</li>
<li><strong>GMC family floater filter:</strong> If <code>family_floater == 0</code>, when existing data exists it keeps premiums whose <code>policy_grid_id</code> is in 39; if <code>family_floater == 1</code>, it prefers grid ids 1011 when data exists. Empty data passes through unchanged.</li>
</ol>
<p><strong>Response shape (success):</strong> <code>data</code> (grid master rows), <code>premiumData</code> (JSON string of premium rows for the UI), <code>jsonArray</code>, <code>branch_units</code>, <code>family_floater</code>, <code>self</code>, <code>count</code>, <code>terms_si_amount_array</code>, <code>client_policy_id</code>. GPA and GMC branches return the same keys; non-GPA/GMC types still return grid templates but premium payload may be empty.</p>
<h2 id="create-premium">Save rack rate — <code>ClientController::createClientPolicyPremium</code></h2>
<p><strong>HTTP:</strong> <code>POST</code> <code>client/premimum/create</code> (spelling <code>premimum</code> matches routes). Body is multipart form data from the modal (<code>FormData</code> in JS).</p>
<p><strong>What it does:</strong></p>
<ol>
<li>Sanitizes POST via <code>sanitizeInputArrayAdvanced</code>; requires a valid <code>client_policy_id</code> and loads <code>client_policy</code> for <code>client_id</code> / branch.</li>
<li>Builds <code>additional_relationship</code> JSON from checkboxes: <code>self</code>, <code>spouse</code>, <code>childrens</code>, <code>parents</code>, <code>parents-in-law</code>. For grid ids <strong>1</strong> or <strong>2</strong> it forces a fixed relation map (self only).</li>
<li><strong>Deactivate old rows (soft replace):</strong> For <code>policy_grid_id</code> 1 or 2, sets <code>is_active = 0</code> on <strong>all</strong> <code>policy_premium_1</code> rows for that client policy. Otherwise sets <code>is_active = 0</code> on <code>policy_premium_2</code> rows matching the same <code>rack_rate_name</code> (so additional GMC tabs do not wipe other rack rates).</li>
<li><strong>Insert new active rows:</strong> One insert per SI/premium row (arrays in POST). Units default from branch if a slot is empty or <code>undefined</code>.</li>
</ol>
<p><strong>Branching by <code>policy_grid_id</code> (high level):</strong></p>
<ul>
<li><strong>1 (GPA-style primary):</strong> Uses <code>si_or_bp</code>: <code>1</code> = sum insured + premium rows (<code>gpa_sum_si[]</code>, <code>gpa_sum_premium[]</code>, …); <code>2</code> = basic pay ladder; <code>3</code> = band/grade + SI + premium. Writes to <code>policy_premium_1</code>.</li>
<li><strong>2 or 9:</strong> Simple SI + premium columns (<code>gpa_si29[]</code>, <code>gpa_premium29[]</code>). Grid <strong>2</strong> uses <code>policy_premium_1</code>; grid <strong>9</strong> uses <code>policy_premium_2</code>.</li>
<li><strong>38, 1013:</strong> Prefix <code>{id}_</code> on POST keys (e.g. <code>3_premium[]</code>, <code>3_si[]</code>, age from/to, grade, relationship, <code>max_si</code>). All go to <code>policy_premium_2</code>.</li>
</ul>
<p><strong>Success response:</strong> <code>status: true</code>, <code>rack_rate_json</code> — a small JSON map used by the UI to prevent duplicate family-floater combinations across tabs (<code>rarc_rate_json_array</code> in <code>policy_grid.php</code>).</p>
<h2 id="frontend-flow">Frontend flow (<code>policy_grid.php</code>)</h2>
<ol>
<li><strong>Open:</strong> Click <code>.btnPolicyModel</code> → GET <code>util/policy-premium?client_policy_id=…</code>.</li>
<li><strong>Populate dropdown:</strong> <code>appendGridData(res.data, …)</code> fills “Policy Premium Type” from grid master rows.</li>
<li><strong>Build inputs:</strong> Changing the dropdown calls <code>addGridHTML</code>, which injects large HTML templates for grid ids 113 (SI/basic/grade layouts, GMC age bands, etc.). Existing <code>premiumData</code> pre-fills values when editing.</li>
<li><strong>Family composition:</strong> <code>createCheckboxes</code> uses <code>res.self</code> / policy terms and, for GMC, saved <code>additional_relationship</code> from <code>jsonArray</code>.</li>
<li><strong>Submit:</strong> Delegated submit handler on <code>form[id^="GridForm_"]</code> — Parsley validation, duplicate SI checks against <code>terms_si_amount_array</code> (<code>checkPolicyTermsSI</code>), unit checks, then <code>POST client/premimum/create</code> with <code>FormData</code>. On success, appends <code>res.rack_rate_json</code> for duplicate-tab prevention; for grid 1 or 2 the modal may auto-close.</li>
<li><strong>Excel:</strong> “Copy from excel” toggles a textarea; <code>policy_grid_excel.php</code> defines per-grid header order and parsing to fill the grid.</li>
</ol>
<h2 id="grid-ids-storage">Grid IDs and database tables</h2>
<table>
<thead>
<tr><th>Artifact</th><th>Role</th></tr>
</thead>
<tbody>
<tr>
<td><code>policy_grid_master</code></td>
<td>Catalog of available premium grid layouts filtered by policy type (GPA vs GMC).</td>
</tr>
<tr>
<td><code>policy_premium_1</code></td>
<td>Stores GPA primary grid (id 1), GPA-style grid 2, and other rows where the controller routes to <code>PolicyPremium1Model</code>.</td>
</tr>
<tr>
<td><code>policy_premium_2</code></td>
<td>Stores most GMC grids (3+), grid 9, and additional rack rates distinguished by <code>rack_rate_name</code>.</td>
</tr>
<tr>
<td><code>client_policy.policy_terms</code></td>
<td>JSON: drives family floater behaviour in <code>getpolicyGridData</code> and which checkbox defaults appear.</td>
</tr>
</tbody>
</table>
<h2 id="grid-types-1-13">Grid types (113) — <code>policy_grid_master</code></h2>
<p>
Every grid below uses the same two layers documented above:
<a href="#premium-calculation-modes">Premium calculation</a> and
<a href="#applicable-family-members">Applicable family members</a>.
The <strong>only</strong> difference between ids 113 is the <strong>shape of the rate table</strong> (which columns
appear and how POST fields are named). Downstream premium logic must combine <code>premium_type</code>,
<code>additional_relationship</code>, and these rows.
</p>
<h3 id="grid-4-vs-5-ui">UI pattern: grid 4 vs grid 5 (age vs age + SI per row)</h3>
<p>
These two GMC layouts are easy to confuse; the modal layout differs as follows (reviewed UI):
</p>
<ul>
<li><strong>Grid 4 — Employees Age band:</strong> One <strong>Sum insured</strong> field applies to the whole table
block; each row is only <strong>From age</strong>, <strong>To age</strong>, and <strong>Premium</strong>. You are
building age bands under a single SI.</li>
<li><strong>Grid 5 — Employees Age + SI:</strong> Each row includes <strong>Sum insured</strong>,
<strong>From age</strong>, <strong>To age</strong>, and <strong>Premium</strong>. SI can change per row together
with the age band.</li>
</ul>
<table>
<thead>
<tr>
<th>Grid ID</th>
<th>Line</th>
<th>Master label</th>
<th>Rate table (what differs)</th>
<th>Persisted in</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>1</code></td>
<td>GPA</td>
<td>Sum Insured (SI) * Multiplier</td>
<td>
GPA primary: sub-mode <code>si_or_bp</code> — <strong>1</strong> SI × multiplier rows, <strong>2</strong> basic pay ladder with multipliers,
<strong>3</strong> band/grade + SI + premium. Multiple unit/SI/premium lines. Controller forces <code>additional_relationship</code> to self-only for grids 12.
Same <a href="#premium-calculation-modes">premium calculation</a> radios apply when shown.
</td>
<td><code>policy_premium_1</code></td>
</tr>
<tr>
<td><code>2</code></td>
<td>GPA</td>
<td>Flat Rate for all SI</td>
<td>
Simple ladder: <code>gpa_unit29[]</code>, <code>gpa_si29[]</code>, <code>gpa_premium29[]</code> per row — no age/grade columns.
</td>
<td><code>policy_premium_1</code></td>
</tr>
<tr>
<td><code>3</code></td>
<td>GMC</td>
<td>SI</td>
<td>
Unit + SI + premium per row (no age/relationship in the standard template). Prefix <code>3_</code> on POST keys.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>4</code></td>
<td>GMC</td>
<td>Employees Age band</td>
<td>
<strong>One SI</strong> for the table; rows = age band + premium only (see <a href="#grid-4-vs-5-ui">Grid 4 vs 5</a>).
Backend still stores <code>si</code>, <code>age_from</code>, <code>age_to</code>, <code>premium</code> per insert; UI collects one SI context then many age rows.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>5</code></td>
<td>GMC</td>
<td>Employees Age + SI</td>
<td>
<strong>Each row</strong>: SI + from age + to age + premium (see <a href="#grid-4-vs-5-ui">Grid 4 vs 5</a>). Prefix <code>5_</code>.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>6</code></td>
<td>GMC</td>
<td>Employees + Dependent Age band</td>
<td>
Age-band table where dependents are in product scope (<code>policy_grid_master</code> dependent flags). Same POST pattern as other GMC age grids with prefix <code>6_</code>.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>7</code></td>
<td>GMC</td>
<td>Employees + Dependent Age + SI</td>
<td>
Dependent-aware age bands <strong>and</strong> SI on each row (prefix <code>7_</code>) for combined pricing dimensions.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>8</code></td>
<td>GMC</td>
<td>SI as per Grade or Band</td>
<td>
Adds <strong>grade/band</strong> per row with SI and premium (<code>8_grade[]</code>, etc.). For corporate gradebased insurer tables.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>9</code></td>
<td>GMC</td>
<td>Flat Rate for all</td>
<td>
Same row shape as grid <strong>2</strong> (<code>gpa_si29[]</code> / <code>gpa_premium29[]</code>) but saved to <code>policy_premium_2</code> for GMC.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>10</code></td>
<td>GMC</td>
<td>Maximum age of Dependents</td>
<td>
Floater-oriented table (age + SI + premium); <code>getpolicyGridData</code> prefers ids <strong>1011</strong> when <code>family_floater = 1</code> and premium data exists. Prefix <code>10_</code>.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>11</code></td>
<td>GMC</td>
<td>Maximum count per Family</td>
<td>
Similar family/floater use case as 10; includes <strong>max SI</strong> column (<code>11_max_si[]</code>) for family-count / cap rules. Prefix <code>11_</code>.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>12</code></td>
<td>GMC</td>
<td>Employees + relationship</td>
<td>
Each row carries a <strong>relationship</strong> value plus SI/premium (and unit) so rates differ by member type. Prefix <code>12_</code>.
</td>
<td><code>policy_premium_2</code></td>
</tr>
<tr>
<td><code>13</code></td>
<td>GMC</td>
<td>Employees age + relationship age</td>
<td>
Full row: relationship + age from/to + SI + premium. Prefix <code>13_</code>.
</td>
<td><code>policy_premium_2</code></td>
</tr>
</tbody>
</table>
<div class="callout info">
<span>i</span>
<div>
<strong>POST naming for GMC grids 313:</strong> fields use the <code>{gridId}_</code> prefix, e.g.
<code>5_age_from[]</code>, <code>5_age_to[]</code>, <code>5_si[]</code>, <code>5_premium[]</code>, <code>5_unit[]</code>.
See <code>policy_grid_excel.php</code> → <code>excel_headers</code> for paste column order per id.
</div>
</div>
<h2 id="excel-paste">Excel paste path</h2>
<p>
<code>policy_grid_excel.php</code> defines <code>excel_headers</code> keyed by grid id (and nested keys for GPA grid 1 variants).
Users paste tab-separated data; helpers build a preview table and map columns into the same field names the manual grid uses.
Use this when bulk-entering many SI/premium lines instead of row-by-row “+” buttons.
</p>
<h2 id="related-routes">Related routes and follow-ups</h2>
<ul>
<li><code>POST client/premimum/edit</code> — <code>ClientController::editClientPolicyPremium</code> (edit path; not detailed on this page).</li>
<li><code>GET util/delete-additional-rack-rate/(:any)</code> — referenced elsewhere for removing extra GMC rack-rate tabs / data.</li>
<li><strong>Premium calculation at enrollment:</strong> Employee flows (e.g. onboarding) read slab / rack configuration through existing policy services — this page documents <strong>where rack rows are authored</strong>, not every consumer.</li>
</ul>
<div class="callout warning">
<span>!</span>
<div>
<strong>Production caution:</strong> Saving grid <strong>1</strong> or <strong>2</strong> deactivates <strong>all</strong> rows in
<code>policy_premium_1</code> for the policy before insert. Other grids deactivate only rows sharing the same
<code>rack_rate_name</code> in <code>policy_premium_2</code>. Test on a copy of client policy data first.
</div>
</div>