- 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>
195 lines
8.0 KiB
TypeScript
195 lines
8.0 KiB
TypeScript
// 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', // 0–100
|
||
},
|
||
} 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));
|
||
}
|