nhance/app/Views/docs/non-eb-opportunities.php
2026-05-22 15:01:14 +05:30

639 lines
27 KiB
PHP
Raw 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
/**
* Non-EB Opportunities — content only
* app/Views/docs/non-eb-opportunities.php
*
* Scope: list-view flow only — create/edit form, then table actions on /leads/list.
* Sources:
* - app/Views/leads_list.php (Non-EB action buttons)
* - app/Views/leads_non_eb.php (add/edit form)
* - app/Views/leads_form_handler.php
* - app/Controllers/LeadsController.php (line ~4793+ and createLead / sendMail paths)
*/
?>
<p>
This page documents the <strong>Non-EB Opportunities</strong> workflow from the
<strong>Opportunities list</strong> (<code>/leads/list</code>). A user creates a Non-EB opportunity,
then progresses through RFQ → QCR → mail actions → placement — all from the list row action menu.
</p>
<p>
Non-EB rows are identified by <code>leads.lead_form_type = 2</code> (EB = <code>1</code>).
RFQ and QCR are managed in <strong>Google Sheets</strong>; sheet IDs are stored in
<code>leads.misc</code> JSON.
</p>
<h2 id="overview">End-to-end flow</h2>
<div class="mermaid-wrapper">
<div class="mermaid">
flowchart TD
A["/leads/list — Add → Non-EB"] --> B["Add form leads_non_eb.php"]
B --> C["POST /leads/create"]
C --> D["status: queued"]
D --> E["Action: RFQ"]
E --> F["GET /leads/createRfqSheet"]
F --> G["status: rfq_created"]
G --> H["Action: QCR"]
H --> I["GET /leads/createQcrSheet"]
I --> J["status: qcr_created"]
J --> K["Action: Send Internal Mail"]
K --> L["POST /leads/sendMail internal"]
L --> M["Action: Send Insurer Mail"]
M --> N["POST /leads/sendMail insurer → rfq_sent"]
N --> O["Action: Send Client Mail"]
O --> P["POST /leads/sendMail client → qcr_sent"]
P --> Q["Action: Placement"]
Q --> R["POST /leads/sendMail placement → won"]
</div>
</div>
<p><strong>Typical sequence from the list:</strong></p>
<ol>
<li><strong>Create</strong> opportunity (form submit) → <code>queued</code></li>
<li><strong>RFQ</strong> — create/open Google Sheet → <code>rfq_created</code></li>
<li><strong>QCR</strong> — copy RFQ sheet → <code>qcr_created</code></li>
<li><strong>Send Internal Mail</strong> — team mail with RFQ or QCR attachment (by current status)</li>
<li><strong>Send Insurer Mail</strong> — RFQ sheet attached → <code>rfq_sent</code></li>
<li><strong>Send Client Mail</strong> — QCR sheet attached → <code>qcr_sent</code></li>
<li><strong>Placement</strong> — placement sheet + policy/payment fields → <code>won</code></li>
</ol>
<p>
<strong>Edit</strong> is available at any stage from the same action menu and reuses the add form
with pre-filled data (<code>getLeadNonEB</code> + <code>POST /leads/create</code> with lead id).
</p>
<h2 id="key-files">Key files</h2>
<table>
<thead>
<tr><th>Area</th><th>File</th></tr>
</thead>
<tbody>
<tr><td>List + action dropdown</td><td><code>app/Views/leads_list.php</code></td></tr>
<tr><td>Non-EB add/edit form</td><td><code>app/Views/leads_non_eb.php</code></td></tr>
<tr><td>EB vs Non-EB form include</td><td><code>app/Views/leads_form_handler.php</code></td></tr>
<tr><td>All backend logic</td><td><code>app/Controllers/LeadsController.php</code></td></tr>
<tr><td>Google Sheets</td><td><code>GoogleSheetLib</code>, <code>Config\RfqConfig</code></td></tr>
</tbody>
</table>
<h2 id="google-sheet-config">Google Sheet config</h2>
<p>
Non-EB RFQ, QCR, and Placement sheets are <strong>Google Drive files</strong> created at runtime by
<code>GoogleSheetLib</code> using a service account. Only <strong>RFQ</strong> needs a pre-configured
template per product; QCR and Placement are copies of the leads RFQ/QCR sheets.
</p>
<h3 id="gsheet-app-drive-flow">App ↔ Google Drive flow</h3>
<div class="mermaid-wrapper">
<div class="mermaid">
flowchart TB
subgraph setup ["One-time / per product setup"]
T["Drive: master RFQ template per product"]
PT["policy_type.misc.rfq_template_sheet_id"]
ENV[".env RFQ_PARENT_FOLDER_ID"]
SA["Service account JSON + share folder/templates"]
end
subgraph rfq ["RFQ — createRfqSheet"]
R1["Read template ID from policy_type.misc"]
R1 --> C1["Drive copyTemplate → parent folder"]
C1 --> F1["Sheets find/replace placeholders"]
F1 --> P1["Drive applyPermissions viewers"]
P1 --> PR1["Sheets applyProtections"]
PR1 --> S1["Save leads.misc.rfq_sheet_id"]
end
subgraph qcr ["QCR — createQcrSheet"]
S1 --> C2["Drive copyTemplate from rfq_sheet_id"]
C2 --> P2["applyPermissions viewers"]
P2 --> S2["Save leads.misc.qcr_sheet_id"]
end
subgraph placement ["Placement — createAndDownloadPlacementSheet"]
S2 --> C3["Drive copyTemplate from qcr_sheet_id"]
C3 --> P3["applyPermissions viewers"]
P3 --> S3["Save leads.misc.placement_sheet_id"]
S3 --> X1["Drive export .xlsx → mail attachment"]
end
subgraph mail ["Mail — downloadFileFromGoogleSheet"]
S1 --> X2["Export RFQ or QCR as Excel"]
S2 --> X2
end
PT --> R1
ENV --> C1
T --> PT
SA --> C1
</div>
</div>
<p><strong>Library:</strong> <code>app/Libraries/GoogleSheetLib.php</code> — auth via service account JSON at
<code>{project-root}/nhance-ee8d1-e3c5269b1ec7.json</code>, scopes <code>DRIVE</code> + <code>SPREADSHEETS</code>.</p>
<h3 id="gsheet-prerequisites">What must be done before RFQ / QCR</h3>
<table>
<thead>
<tr><th>Step</th><th>Before</th><th>Why</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Google service account JSON present; account email shared on template folder + each template sheet</td>
<td><code>GoogleSheetLib</code> cannot copy or edit without Drive access</td>
</tr>
<tr>
<td>2</td>
<td><code>RFQ_PARENT_FOLDER_ID</code> set in <code>.env</code></td>
<td>Target folder for every copied RFQ/QCR/Placement file</td>
</tr>
<tr>
<td>3</td>
<td>Master RFQ Google Sheet template created per product (Fire, Marine, GPA, etc.) with placeholder tokens</td>
<td>RFQ copy source — one template per <code>policy_type</code> row</td>
</tr>
<tr>
<td>4</td>
<td><code>policy_type.misc</code> JSON updated with <code>rfq_template_sheet_id</code> for that product</td>
<td><code>createRfqSheet()</code> reads this; fails with “RFQ template not found” if missing</td>
</tr>
<tr>
<td>5</td>
<td>Non-EB opportunity created with correct <code>policy_type_id</code> and <code>rfq_qcr_viewers</code> emails</td>
<td>Lead must exist; viewers become sheet editors after copy</td>
</tr>
<tr>
<td>6</td>
<td><strong>Before QCR:</strong> RFQ action completed (<code>leads.misc.rfq_sheet_id</code> set)</td>
<td>QCR copies the leads RFQ sheet, not the policy template</td>
</tr>
<tr>
<td>7</td>
<td><strong>Before Placement mail:</strong> QCR action completed (<code>leads.misc.qcr_sheet_id</code> set)</td>
<td>Placement copies the QCR sheet</td>
</tr>
</tbody>
</table>
<h3 id="gsheet-product-config">Where to configure sheet ID per product</h3>
<p>
Template sheet IDs are stored <strong>per product</strong> on the <code>policy_type</code> table —
one row per product (Fire, Marine, Burglary, etc.). The leads selected
<code>policy_type_id</code> determines which template is copied when RFQ is clicked.
</p>
<p><strong>Database column:</strong> <code>policy_type.misc</code> (JSON text)</p>
<pre><code>{
"rfq_template_sheet_id": "1GXNDNXoWriClb5HCqPYd2GAY1T8aie_Hos0yAOoTaC0"
}</code></pre>
<p>Example — set or update for policy type id <code>12</code>:</p>
<pre><code>UPDATE policy_type
SET misc = JSON_SET(COALESCE(misc, '{}'), '$.rfq_template_sheet_id', 'YOUR_GOOGLE_DRIVE_FILE_ID')
WHERE id = 12;</code></pre>
<div class="callout info">
<span>i</span>
<div>
There is no admin UI field for <code>rfq_template_sheet_id</code> today — configure via DB (or extend
<code>MasterController::editPolicyType</code> / <code>policy_type_onboarding</code> if you add a form field).
Model allow-list: <code>app/Models/PolicyTypeModel.php</code> includes <code>misc</code>.
</div>
</div>
<p><strong>How to find a template file ID:</strong></p>
<ul>
<li>From the Google Sheet URL: <code>https://docs.google.com/spreadsheets/d/<strong>{FILE_ID}</strong>/edit</code></li>
<li>CLI — list all sheets in the RFQ parent folder:
<code>php public/index.php cli/list-sheet-folder-files {RFQ_PARENT_FOLDER_ID}</code>
→ writes <code>sheetid.json</code> with <code>name</code> + <code>sheetId</code> pairs
(<code>GoogleSheetController::listFolderSheetFilesCli</code>)</li>
</ul>
<p><strong>QCR and Placement:</strong> no separate template ID per product. They always copy from the leads existing sheets:</p>
<table>
<thead>
<tr><th>Stage</th><th>Copy source</th><th>Stored on lead</th></tr>
</thead>
<tbody>
<tr><td>RFQ</td><td><code>policy_type.misc.rfq_template_sheet_id</code></td><td><code>leads.misc.rfq_sheet_id</code></td></tr>
<tr><td>QCR</td><td><code>leads.misc.rfq_sheet_id</code></td><td><code>leads.misc.qcr_sheet_id</code></td></tr>
<tr><td>Placement</td><td><code>leads.misc.qcr_sheet_id</code> (filename: QCR → Placement)</td><td><code>leads.misc.placement_sheet_id</code></td></tr>
</tbody>
</table>
<h3 id="gsheet-app-config">App-level config files</h3>
<table>
<thead>
<tr><th>Setting</th><th>Location</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr>
<td><code>RFQ_PARENT_FOLDER_ID</code></td>
<td><code>.env</code> → <code>Config\RfqConfig::$rfqParentFolderId</code></td>
<td>Google Drive folder where copied RFQ/QCR/Placement files are created</td>
</tr>
<tr>
<td><code>rfqPlaceholders</code></td>
<td><code>app/Config/RfqConfig.php</code></td>
<td>Maps template tokens like <code>{{INSURED_NAME}}</code> to lead field keys filled on RFQ create</td>
</tr>
<tr>
<td><code>rfqClaimsPlaceholder</code></td>
<td><code>app/Config/RfqConfig.php</code></td>
<td>Default <code>{{CLAIMS_DETAILS}}</code> — multi-row claims table from <code>fin_years_claims</code></td>
</tr>
<tr>
<td><code>protections</code></td>
<td><code>app/Config/RfqConfig.php</code></td>
<td>Locked cell ranges on new RFQ sheets only (e.g. <code>RFQ Page!B12:C12</code>)</td>
</tr>
<tr>
<td>Service account key</td>
<td><code>nhance-ee8d1-e3c5269b1ec7.json</code> (project root)</td>
<td>Google API authentication for all sheet operations</td>
</tr>
</tbody>
</table>
<p>Placeholder tokens to embed in each products RFQ master template:</p>
<table>
<thead>
<tr><th>Token in template</th><th>Filled from</th></tr>
</thead>
<tbody>
<tr><td><code>{{INSURED_NAME}}</code></td><td>Client name / short name</td></tr>
<tr><td><code>{{COMMUNICATION_ADDRESS}}</code></td><td>Client or custom field address</td></tr>
<tr><td><code>{{GST}}</code> / <code>{{PAN}}</code></td><td>Lead GST / PAN</td></tr>
<tr><td><code>{{POLICY_PERIOD}}</code></td><td>Policy start end dates</td></tr>
<tr><td><code>{{OPPORTUNITY_TYPE}}</code></td><td>Fresh / Renewal label</td></tr>
<tr><td><code>{{RISK_LOCATION}}</code> / <code>{{OCCUPANCY}}</code></td><td>Custom policy-type fields</td></tr>
<tr><td><code>{{CLAIMS_DETAILS}}</code></td><td>Claim history table (renewal leads)</td></tr>
</tbody>
</table>
<p>
After RFQ copy, editors are granted from the leads <code>rfq_qcr_viewers</code> JSON email list (selected on the create/edit form).
The same list is applied to QCR and Placement copies.
</p>
<h2 id="create-edit">Step 1 — Create opportunity</h2>
<h3 id="create-ui">From the list</h3>
<ol>
<li>Open <code>/leads/list</code> → click <strong>Add</strong>.</li>
<li>Modal <strong>Select Opportunity Type</strong> → choose <strong>Non-EB</strong> (<code>lead_form_type=2</code>).</li>
<li>Redirect: <code>GET /util/getLeadNonEB/2/0</code>.</li>
</ol>
<h3 id="create-form">Form (<code>leads_non_eb.php</code>)</h3>
<ul>
<li>Sections: Opportunity Details, Client Information, Branch Information, Policy Details, Sales &amp; Assignment.</li>
<li>Policy types limited to <code>allocg</code> = <strong>Non-EB</strong> or <strong>Marine</strong>.</li>
<li>Policy type change → <code>GET /util/getPolicyTypeFields</code> → dynamic fields in <code>#appendArea</code>.</li>
<li>Renewal types may show claim history rows → stored as <code>fin_years_claims</code> JSON.</li>
<li><code>rfq_qcr_viewers</code> (emails) become Google Sheet editors later.</li>
<li>Submit → AJAX <code>POST /leads/create</code> with <code>lead_form_type=2</code>.</li>
</ul>
<h3 id="create-backend">Controller: <code>createLead()</code></h3>
<ul>
<li><code>prepareLeadData()</code> → <code>prepareSingleLeadData()</code> (one lead row; EB uses multi-row).</li>
<li>Non-EB validation: policy type, DOC/DOE, claim-history rows when <code>claim_history=1</code>.</li>
<li><code>insertNewLead()</code> — no demography background job (EB-only).</li>
<li>Initial status: <code>queued</code> (In-Queued).</li>
</ul>
<h2 id="edit-flow">Edit flow</h2>
<p>From the list action menu → <strong>Edit</strong> (available for both EB and Non-EB):</p>
<ol>
<li>JS: <code>getLeadsDataForEdit(lead_id, lead_form_type, actual_lead_id)</code></li>
<li>Redirect: <code>GET /util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}</code></li>
<li>Controller: <code>getLeadNonEB($type, $actual_lead_id, $id)</code> (~line 4818)
<ul>
<li>Loads master data (issuer, policy types, sales team, etc.)</li>
<li>When <code>$id</code> present: fetches <code>lead_edit_data</code>, files, custom fields, date formatting</li>
<li>Builds dynamic policy HTML via <code>generateViewPageHtml()</code></li>
<li>Renewal Non-EB: renders <code>rfq/claims_details_non_eb</code> into <code>claims_details_html</code></li>
<li>Returns layout with <code>leads_form_handler</code> → includes <code>leads_non_eb.php</code> when <code>selected_lead_type != 1</code></li>
</ul>
</li>
<li>Form pre-fills client, branch, policy, files, viewers, status, lost reason, etc.</li>
<li>Submit same endpoint: <code>POST /leads/create</code> with hidden <code>id</code> → <code>updateOldLead()</code></li>
</ol>
<h2 id="list-actions">Steps 27 — List table actions (Non-EB)</h2>
<p>
When <code>lead_form_type === 2</code>, the row action menu in
<code>app/Views/leads_list.php</code> (lines ~297343) uses modal/AJAX flows instead of
navigating to <code>/rfq/list/{id}/1|2</code> (EB behaviour).
</p>
<div class="callout info">
<span>i</span>
<div>
<strong>Menu order on screen</strong> (after opportunity is created): Edit → RFQ → QCR →
Send Internal Mail → Send Insurer Mail → Send Client Mail → Placement → Email History.
</div>
</div>
<h3 id="action-map">Action → view handler → controller endpoint</h3>
<div class="docs-table-wrap docs-table-wrap--fluid">
<table class="docs-action-map">
<thead>
<tr>
<th>#</th>
<th>Action</th>
<th>Visible when</th>
<th>View (JS) / handler class</th>
<th>Controller endpoint(s)</th>
<th>Status after</th>
</tr>
</thead>
<tbody>
<tr>
<td>—</td>
<td><strong>Edit</strong></td>
<td>Always (EB + Non-EB)</td>
<td><code>getLeadsDataForEdit()</code></td>
<td>
<span class="badge get">GET</span>
<code>/util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}</code><br>
<span class="badge post">POST</span>
<code>/leads/create</code> (update when <code>id</code> posted)
</td>
<td>User-selected on form</td>
</tr>
<tr>
<td>1</td>
<td><strong>RFQ</strong></td>
<td><code>lead_form_type === 2</code> only</td>
<td><code>.btnRfqSheetList</code> → <code>createRfqSheetFromList()</code></td>
<td>
<span class="badge get">GET</span>
<code>/leads/createRfqSheet?lead_id={id}</code><br>
<code>LeadsController::createRfqSheet()</code> — opens Google Sheet URL in new tab
</td>
<td><code>rfq_created</code></td>
</tr>
<tr>
<td>2</td>
<td><strong>QCR</strong></td>
<td>
Non-EB; status not <code>queued</code> or <code>rfq_created</code>;
role <code>1, 5, 2, 3</code> or Business Support team
</td>
<td><code>.btnQcrSheetList</code> → <code>createQcrSheetFromList()</code></td>
<td>
<span class="badge get">GET</span>
<code>/leads/createQcrSheet?lead_id={id}</code><br>
<code>LeadsController::createQcrSheet()</code>
</td>
<td><code>qcr_created</code></td>
</tr>
<tr>
<td>3</td>
<td><strong>Send Internal Mail</strong></td>
<td>Non-EB; <code>status != queued</code></td>
<td><code>.btnInternalMailList</code> → <code>openInternalMailFromList()</code></td>
<td>
<span class="badge get">GET</span>
<code>/leads/mailTemplate?lead_id={id}&amp;template_type=rfq</code><br>
<span class="badge post">POST</span>
<code>/leads/sendMail</code> — <code>recipient_type=internal</code>
</td>
<td>—</td>
</tr>
<tr>
<td>4</td>
<td><strong>Send Insurer Mail</strong></td>
<td>Same as internal mail</td>
<td><code>.btnInsurerMailList</code> → <code>openInsurerMailFromList()</code></td>
<td>
<span class="badge get">GET</span>
<code>/leads/mailTemplate?lead_id={id}&amp;template_type=rfq</code><br>
<span class="badge post">POST</span>
<code>/leads/sendMail</code> — <code>recipient_type=insurer</code> (RFQ sheet attach)
</td>
<td><code>rfq_sent</code></td>
</tr>
<tr>
<td>5</td>
<td><strong>Send Client Mail</strong></td>
<td>Same as internal mail</td>
<td><code>.btnClientMailList</code> → <code>openClientMailFromList()</code></td>
<td>
<span class="badge get">GET</span>
<code>/leads/mailTemplate?lead_id={id}&amp;template_type=qcr</code><br>
<span class="badge post">POST</span>
<code>/leads/sendMail</code> — <code>recipient_type=client</code> (QCR sheet attach)
</td>
<td><code>qcr_sent</code></td>
</tr>
<tr>
<td>6</td>
<td><strong>Placement</strong></td>
<td>Same as internal mail</td>
<td><code>.btnPlacementList</code> → <code>openPlacementFromList()</code></td>
<td>
<span class="badge get">GET</span>
<code>/rfq/placementData/{id}</code><br>
<span class="badge get">GET</span>
<code>/leads/mailTemplate?lead_id={id}&amp;template_type=placement</code><br>
<span class="badge post">POST</span>
<code>/leads/sendMail</code> — <code>recipient_type=placement</code>
</td>
<td><code>won</code></td>
</tr>
<tr>
<td>—</td>
<td><strong>Email History</strong></td>
<td>Always (EB + Non-EB)</td>
<td><code>.btnHistory</code> → <code>getLeadsDataForMailHistory()</code></td>
<td>
<span class="badge get">GET</span>
<code>/util/getLeadEmailHistory/{id}</code> → <code>getLeadEmailHistory()</code>
</td>
<td>—</td>
</tr>
</tbody>
</table>
</div>
<p><strong>EB contrast</strong> (same menu, <code>lead_form_type === 1</code>): RFQ/QCR are links to
<code>/rfq/list/{id}/1</code> and <code>/rfq/list/{id}/2</code>; internal/insurer/client/placement mail items are not shown.</p>
<p><strong>Shared mail modal helpers</strong> (all in <code>leads_list.php</code>):</p>
<ul>
<li><code>POST /leads/uploadLeadAttachment</code> — optional extra files (<code>lead_id</code>, <code>docs_name</code>, file)</li>
<li><code>constructURL_ForInternalMailSend()</code>, insurer/client via <code>constructURL_ForInsurerOrClientMailSend()</code>, placement via <code>constructURL_ForPlacementMailSend()</code></li>
<li>External CC disclaimer modal (<code>#external_cc_disclaimer_modal</code>) before client/placement send to non-user emails</li>
</ul>
<h2 id="step-rfq">Step 2 — RFQ (<code>createRfqSheet()</code>)</h2>
<ol>
<li>Load lead + <code>policy_type.misc.rfq_template_sheet_id</code>.</li>
<li>If <code>misc.rfq_sheet_id</code> exists → return existing Google Sheet URL.</li>
<li>Copy template via <code>GoogleSheetLib::copyTemplate()</code> into <code>RfqConfig::rfqParentFolderId</code>.</li>
<li>Fill placeholders (<code>buildRfqPlaceholderData()</code>): client, GST, PAN, policy period, claims table.</li>
<li>Grant editors from <code>leads.rfq_qcr_viewers</code> email JSON.</li>
<li>Save <code>misc.rfq_sheet_id</code>; set <code>status = rfq_created</code>.</li>
<li>UI opens sheet in a new browser tab.</li>
</ol>
<h2 id="step-qcr">Step 3 — QCR (<code>createQcrSheet()</code>)</h2>
<ol>
<li>Requires <code>misc.rfq_sheet_id</code> — returns 400 if RFQ not created yet.</li>
<li>If <code>misc.qcr_sheet_id</code> exists → return existing URL.</li>
<li>Copy the RFQ sheet (not the policy template) with a QCR filename.</li>
<li>Same editor permissions from <code>rfq_qcr_viewers</code>.</li>
<li>Save <code>misc.qcr_sheet_id</code>; set <code>status = qcr_created</code>.</li>
</ol>
<h2 id="step-mails">Steps 46 — Mail actions</h2>
<h3 id="mail-template">Load template — <code>getLeadMailTemplate()</code></h3>
<p>
Called before each mail modal opens. Query params:
<code>lead_id</code> + <code>template_type</code> (<code>rfq</code> | <code>qcr</code> | <code>placement</code>).
Returns subject, HTML body, and attachment checkbox HTML from <code>lead_files</code>.
</p>
<h3 id="send-mail">Send — <code>sendMailWithAttachement()</code></h3>
<p>For Non-EB (<code>lead_form_type == 2</code>), the Excel attachment comes from Google Sheets:</p>
<table>
<thead>
<tr><th>recipient_type</th><th>Sheet used</th><th>Status after send</th></tr>
</thead>
<tbody>
<tr>
<td><code>internal</code></td>
<td>QCR if status is <code>qcr_created</code>/<code>qcr_sent</code>, else RFQ</td>
<td>Unchanged</td>
</tr>
<tr>
<td><code>insurer</code></td>
<td><code>misc.rfq_sheet_id</code></td>
<td><code>rfq_sent</code> (unless already past that stage)</td>
</tr>
<tr>
<td><code>client</code></td>
<td><code>misc.qcr_sheet_id</code></td>
<td><code>qcr_sent</code></td>
</tr>
<tr>
<td><code>placement</code></td>
<td><code>createAndDownloadPlacementSheet()</code> — copies QCR sheet</td>
<td><code>won</code> + saves placement/payment/installment fields</td>
</tr>
</tbody>
</table>
<p>Implementation detail: <code>downloadFileFromGoogleSheet()</code> exports the chosen sheet to a temp
<code>.xlsx</code> under <code>writable/tmp/</code> before <code>MailHelper::send_email()</code>.</p>
<h2 id="step-placement">Step 7 — Placement</h2>
<p>Opened from list action → <code>openPlacementFromList(leadId)</code>:</p>
<ol>
<li><code>GET /rfq/placementData/{id}</code> — pre-fills policy dates, premium, CD, installments, contacts.</li>
<li><code>GET /leads/mailTemplate?template_type=placement</code> — subject/body/attachments.</li>
<li>User fills placement modal: dates, premium/CD/total, installment rows, To/CC, optional external CC.</li>
<li><code>POST /leads/sendMail</code> with <code>recipient_type=placement</code>.</li>
<li>Backend copies QCR → placement Google Sheet, attaches Excel, updates lead to <code>won</code>,
persists <code>placement_date</code>, <code>premium_amount</code>, <code>cd_amount</code>, installments, etc.</li>
</ol>
<h2 id="status-lifecycle">Status lifecycle</h2>
<table>
<thead>
<tr><th>Status</th><th>Label</th><th>Set by</th></tr>
</thead>
<tbody>
<tr><td><code>queued</code></td><td>In-Queued</td><td>Create form (default)</td></tr>
<tr><td><code>rfq_created</code></td><td>RFQ Created</td><td><code>createRfqSheet()</code></td></tr>
<tr><td><code>rfq_sent</code></td><td>RFQ Sent</td><td>Insurer mail</td></tr>
<tr><td><code>qcr_created</code></td><td>QCR Created</td><td><code>createQcrSheet()</code></td></tr>
<tr><td><code>qcr_sent</code></td><td>QCR Sent</td><td>Client mail</td></tr>
<tr><td><code>won</code></td><td>Won</td><td>Placement mail</td></tr>
<tr><td><code>lost</code></td><td>Lost</td><td>User sets on edit form + lost reason</td></tr>
</tbody>
</table>
<h2 id="misc-json">leads.misc JSON</h2>
<pre><code>{
"rfq_sheet_id": "…",
"qcr_sheet_id": "…",
"placement_sheet_id": "…"
}</code></pre>
<h2 id="controller-reference">Controller reference (list flow)</h2>
<p>Methods in <code>LeadsController.php</code> used by the list Non-EB flow:</p>
<table>
<thead>
<tr><th>Method</th><th>Triggered from</th></tr>
</thead>
<tbody>
<tr><td><code>createLead()</code></td><td>Form submit (create + edit)</td></tr>
<tr><td><code>getLeadNonEB()</code></td><td>Add / Edit navigation</td></tr>
<tr><td><code>getPolicyTypeFields()</code></td><td>Policy type change on form</td></tr>
<tr><td><code>createRfqSheet()</code></td><td>RFQ action</td></tr>
<tr><td><code>createQcrSheet()</code></td><td>QCR action</td></tr>
<tr><td><code>getLeadMailTemplate()</code></td><td>All mail modals</td></tr>
<tr><td><code>getPlacementData()</code></td><td>Placement modal pre-fill</td></tr>
<tr><td><code>uploadLeadAttachment()</code></td><td>Attachment upload in mail modals</td></tr>
<tr><td><code>sendMailWithAttachement()</code></td><td>All mail sends + placement</td></tr>
<tr><td><code>downloadFileFromGoogleSheet()</code></td><td>Mail attachment (internal/insurer/client)</td></tr>
<tr><td><code>createAndDownloadPlacementSheet()</code></td><td>Placement mail attachment</td></tr>
<tr><td><code>getLeadEmailHistory()</code></td><td>Email History modal</td></tr>
<tr><td><code>buildRfqPlaceholderData()</code></td><td>RFQ sheet placeholder fill (helper)</td></tr>
</tbody>
</table>
<h2 id="developer-steps">Developer checklist</h2>
<ol>
<li>Complete <a href="#google-sheet-config">Google Sheet config</a> for each Non-EB/Marine <code>policy_type</code> before first RFQ.</li>
<li>Set <code>policy_type.misc.rfq_template_sheet_id</code> per product (see <a href="#gsheet-product-config">sheet ID per product</a>).</li>
<li>Configure <code>RFQ_PARENT_FOLDER_ID</code> in <code>.env</code> and share folder/templates with the service account.</li>
<li>Ensure placeholders in master templates match <code>Config\RfqConfig::$rfqPlaceholders</code>.</li>
<li>Gate new list actions with <code>$isNonEb</code> in <code>leads_list.php</code>.</li>
<li>Respect sheet order: RFQ → QCR → mails → placement.</li>
<li>QCR button: roles <code>[1, 5, 2, 3]</code> or <code>BUSINESS_SUPPORT_TEAM_ID</code> in user team.</li>
</ol>
<h2 id="pitfalls">Common pitfalls</h2>
<ul>
<li><strong>QCR before RFQ:</strong> <code>createQcrSheet</code> fails if <code>rfq_sheet_id</code> is missing.</li>
<li><strong>Queued status:</strong> Mail and placement actions are hidden until status moves past <code>queued</code>.</li>
<li><strong>Insurer mail needs RFQ sheet;</strong> client mail needs QCR sheet — create sheets before sending.</li>
<li><strong>Internal mail attachment</strong> picks RFQ or QCR based on current lead status.</li>
<li><strong>Edit vs create:</strong> same <code>POST /leads/create</code>; presence of hidden <code>id</code> triggers update.</li>
</ul>