639 lines
27 KiB
PHP
639 lines
27 KiB
PHP
<?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 lead’s 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 lead’s 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 lead’s 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 lead’s 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 product’s 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 lead’s <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 & 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 2–7 — 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 ~297–343) 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}&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}&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}&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}&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 4–6 — 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>
|