lead-to-policy/src/api/config.ts
Bhanu Prakash Sai Potteri a4d0f80f71 feat: Zino brand logo in sidebar + login, strip header search/bell
- inline ZinoLogo SVG (from sm2 renderer asset); replace aria badge in
  sidebar (centered) and login brand block
- remove search + notification buttons from topbar
- carry pending field prefill (field_defaults.value) + qualify RBAC trim

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:36:18 +05:30

195 lines
8.0 KiB
TypeScript
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.

// Aria Lead-to-Policy — sandbox app 385, org 53.
// Workflow `e29c3c33` (v1). All ids verified against postgres-sandbox.
export const ORG_ID = '53';
export const APP_ID = '385';
export const WORKFLOW_UUID = 'e29c3c33-e294-4051-8325-a20de9ed99fc';
// Record views (POST /app/385/view/recordview, body.rv_template_uid).
export const RECORD_VIEW_IDS = {
ALL_LEADS: '49a5d651-8055-451a-8348-b1ea65cc6357', // comprehensive — drives the pipeline
} as const;
// Detail views (GET /app/385/view/detailview/{uid}).
export const DETAIL_VIEW_IDS = {
LEAD: 'aa96f572-b480-4623-ae9c-9ce0c44b8626',
} as const;
// Onboard activity uid. The trigger writes welcome_email_status + policy_doc
// onto the lead record (read from the recordview). onboarding_notes stays a
// `local` field — it persists only in the activity submission, surfaced via the
// audit feed, where Customer 360 reads it. (welcome_pack_sent is dead.)
export const ONBOARD_ACTIVITY_UID = 'ae415f4e-a33e-432e-882f-733238de8bcc';
// Activity uids + their field ids (data{} keys). From introspect.
export const ACTIVITIES = {
RECOMMEND_PRODUCT: {
uid: 'c0e2004e-2d70-45f2-9cd5-734331010906',
fields: {
product: 'recommended_product_input', // lookup → tbl_products (see PRODUCT_LOOKUP)
sumAssured: 'sum_assured_input', // number
premiumAmount: 'premium_amount_input', // number
premiumFrequency: 'premium_frequency_input', // select
riders: 'riders_input', // multiselect
notes: 'recommendation_notes', // longtext
saValid: 'sa_valid_input', // boolean — computed (see lib/recommend.ts); gates Product Recommended
},
},
COLLECT_DOCUMENTS: {
uid: 'a8eb0faa-17f9-4491-a161-bd6b533adfd1',
fields: {
panOcr: 'pan_ocr_2', // ocr
aadhaarOcr: 'aadhar_ocr_2', // ocr
salarySlip: 'salary_slip_doc_input', // file
aadhaarNumber: 'aadhaar_number_input', // text
panNumber: 'pan_number_input', // text
verifiedIncome: 'verified_income_input', // number
docStatus: 'doc_status_input', // select (req)
},
},
SUBMIT_UNDERWRITING: {
uid: 'ed8ec0dc-4c2c-4d9e-b798-757f6a9d4730',
fields: {
status: 'underwriting_status_input', // select (req) — the verdict
ref: 'underwriting_ref_input', // id_gen (backend-generated UW-YYYY-####; do NOT send)
},
},
ISSUE_POLICY: {
uid: '7b8fb734-a780-4c6a-837b-c5e7ca28e489',
fields: {
issueDate: 'policy_issue_date_input', // date (req)
term: 'policy_term_input', // number (years)
maturity: 'maturity_date_input', // date (computed = issue + term)
premium: 'premium_amount_confirm', // number
number: 'policy_number_input', // id_gen (backend-generated POL-YYYY-####; do NOT send)
},
},
} as const;
// Select options + lookup templates are no longer hardcoded here — screens
// read them live from the form schema (api/schema.ts → fieldFromSchema).
// Canonical ordered lifecycle — matches live `current_state_name` 1:1 so a
// state name maps straight to a stepper/kanban index. Terminal "Disqualified"
// is handled out-of-band (STAGE_OF_STATE returns -1).
export const STAGES = [
'New Lead',
'Qualified',
'Assigned',
'Contacted',
'Meeting Scheduled',
'Product Recommended',
'Documents Collected',
'Eligibility Assessed',
'Underwriting',
'Policy Issued',
'Customer 360',
] as const;
export function stageOfState(stateName?: string): number {
if (!stateName) return 0;
const i = STAGES.indexOf(stateName as (typeof STAGES)[number]);
return i; // -1 for Disqualified / unknown
}
// UID -> human name maps, for rendering the audit trail (which stores raw
// activity + state UIDs).
export const STATE_NAME_BY_UID: Record<string, string> = {
'e469b82f-924e-47d9-9a9f-65b23d270047': 'New Lead',
'41c7a19f-f2fa-4b54-9dd5-310ae982d6c6': 'Qualified',
'8f038dc4-81b7-48c6-ac5b-b28c42b83214': 'Assigned',
'f2aa6dbd-2149-455a-9466-58bd5beb6997': 'Contacted',
'd5a2e33a-d2fe-4bfb-816f-61e064b77e97': 'Meeting Scheduled',
'2f33444a-1af0-4fa0-9461-eb8fc79ec175': 'Product Recommended',
'a0d60916-c332-4d0a-8d02-501e22616b8f': 'Documents Collected',
'0a2a0a66-129a-4b2a-8e4e-1defc5ef85f6': 'Eligibility Assessed',
'f8158695-5ac7-4e3a-9eda-bbab3fd8bbb3': 'Underwriting',
'c964fe5e-c2a8-4d30-825c-fefa97a74368': 'Policy Issued',
'3d783ea2-b3d0-4eca-9424-9f8451c5d986': 'Customer 360',
'0ce7794e-30ba-4f09-b81e-26d5c9fec5b7': 'Disqualified',
};
export const ACTIVITY_NAME_BY_UID: Record<string, string> = {
'cc1a73d5-61e1-4416-af30-2b1eeb623a58': 'Capture Lead',
'6b741e44-37f2-4bb0-90ab-ed139f03b7b3': 'Qualify Lead',
'da61aaa1-003b-4601-9f42-f93ff230497f': 'Assign Lead',
'fea6b3a8-846d-4f6f-8e92-033c4cf4b6e2': 'Log First Contact',
'c444d32f-ed6a-4e36-b8b7-b82e73a141d4': 'Schedule Meeting',
'c0e2004e-2d70-45f2-9cd5-734331010906': 'Recommend Product',
'a8eb0faa-17f9-4491-a161-bd6b533adfd1': 'Collect Documents',
'ba3d3e7f-0d3f-49f2-a156-6cace33c228a': 'Assess Eligibility',
'ed8ec0dc-4c2c-4d9e-b798-757f6a9d4730': 'Submit Underwriting',
'7b8fb734-a780-4c6a-837b-c5e7ca28e489': 'Issue Policy',
'ae415f4e-a33e-432e-882f-733238de8bcc': 'Onboard Customer',
'ea99fc60-caba-45c1-afc1-7386a2fa9220': 'Disqualify',
};
// AI employees (user_id -> identity). Used to label the decision stream and
// to attribute the "owner" of an in-flight lead.
export const AI_EMPLOYEES: Record<string, { name: string; role: string }> = {
'29180': { name: 'Aria', role: 'Lead Qualifier' },
'29181': { name: 'Aria Engage', role: 'Calls & Scheduling' },
'29182': { name: 'Aria Underwrite', role: 'Underwriting' },
'29188': { name: 'Aria Advisor', role: 'Product Recommendation' },
};
// Aria Advisor's internal recommendation activity. It fires from the Schedule
// Meeting trigger (after the meeting is booked) and records the product
// recommendation as a decision — surfaced in the Recommend Product form. Its
// data{} carries the ai_* fields below.
export const AI_RECOMMENDATION = {
activityUid: '14d734da-f1e2-49af-9715-2146d26c0e1a',
aiUserId: '29188',
fields: {
product: 'ai_recommended_product_input', // product name (string)
sumAssured: 'ai_suggested_sum_assured_input', // number
rationale: 'ai_recommendation_rationale_input', // text
confidence: 'ai_recommendation_confidence_input', // 0100
},
} as const;
// Which AI employee is driving a given state (for owner attribution when no
// human sales agent is set).
export function aiEmployeeForState(stateName?: string): { name: string; role: string } | null {
switch (stateName) {
case 'New Lead':
return AI_EMPLOYEES['29180'];
case 'Assigned':
case 'Contacted':
case 'Meeting Scheduled':
case 'Product Recommended':
return AI_EMPLOYEES['29181'];
case 'Documents Collected':
case 'Eligibility Assessed':
return AI_EMPLOYEES['29182'];
default:
return null;
}
}
// --- Screen RBAC (mirrors tbl_appconfig_screens.permissions for app 385) ---
// Roles that bypass screen gating entirely (platform admins see every screen).
export const SUPER_ROLES = ['Super Admin', 'Admin'];
// Route path → roles allowed to see the screen. A path absent here is open to
// all authenticated users (e.g. the contextual Lead 360 detail). `/schedule`
// is an Aria-only screen — managers oversee agent meetings, agents see theirs.
export const SCREEN_ACCESS: Record<string, string[]> = {
'/pipeline': ['Sales Agent', 'Underwriter', 'Branch Manager'],
'/qualify': ['Branch Manager'],
'/engage': ['Sales Agent'],
'/documents': ['Underwriter'],
'/underwriting': ['Underwriter', 'Branch Manager'],
'/schedule': ['Sales Agent', 'Branch Manager'],
};
/** Whether `roles` may see the screen at `path`. Super-admins always can;
* unlisted paths are open. */
export function canSeeScreen(roles: string[] | undefined, path: string): boolean {
const allowed = SCREEN_ACCESS[path];
if (!allowed) return true;
const r = roles ?? [];
if (r.some((role) => SUPER_ROLES.includes(role))) return true;
return r.some((role) => allowed.includes(role));
}