OneyLaunch

This commit is contained in:
Flatlogic Bot 2026-01-27 17:02:41 +00:00
parent ed94998364
commit eaaf07fb5e
7 changed files with 710 additions and 1773 deletions

View File

@ -0,0 +1,8 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.sequelize.query(`ALTER TYPE "enum_leads_status" ADD VALUE IF NOT EXISTS 'FinalApproval' AFTER 'Validation'`);
},
down: async (queryInterface, Sequelize) => {
// ENUM values cannot be easily removed in Postgres without dropping the type
}
};

View File

@ -85,7 +85,7 @@ status: {
"UnderwritingReview",
"Validation",
"Validation", "FinalApproval",
"KickOff",

View File

@ -7,14 +7,15 @@ const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class LeadsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
// Auto-calculate Fast Track
if (data.threshold_amount !== undefined) {
data.is_fast_track = Number(data.threshold_amount) < 250000;
}
await LeadsDBApi.create(
data,
{
@ -79,6 +80,109 @@ module.exports = class LeadsService {
);
}
// Auto-calculate Fast Track
if (data.threshold_amount !== undefined) {
data.is_fast_track = Number(data.threshold_amount) < 250000;
}
// Workflow Transition Logic: Move to Step 2 (Pricing)
if (data.status === 'PricingReview' && (leads.status === 'Draft' || leads.status === 'Submitted')) {
const existingTask = await db.tasks.findOne({ where: { leadId: id, title: { [db.Sequelize.Op.like]: 'Pricing Review%' } }, transaction });
if (!existingTask) {
await db.tasks.create({
title: 'Pricing Review for ' + (data.title || leads.title),
description: 'Provide pricing for Actuarial or Underwriter review.',
status: 'Todo',
leadId: id,
createdById: currentUser.id,
updatedById: currentUser.id,
}, { transaction });
}
}
// Workflow Transition Logic: Move to Step 3 (Validation)
if (data.status === 'Validation' && leads.status === 'PricingReview') {
const approvalTitle = leads.is_fast_track
? 'Fast Track Validation (CPO/CEO/Underwriter)'
: 'Regular Track Validation (MNPC Pre-validation)';
const existingApproval = await db.approvals.findOne({ where: { leadId: id, approval_title: approvalTitle }, transaction });
if (!existingApproval) {
await db.approvals.create({
approval_title: approvalTitle,
status: 'Pending',
leadId: id,
createdById: currentUser.id,
updatedById: currentUser.id,
}, { transaction });
}
}
// Workflow Transition Logic: Move to Step 5 (Dept Tasks)
if (data.status === 'UnderwritingReview' && leads.status === 'KickOff') {
const departmentTasks = [
{ title: 'Pricing Validation', description: 'Pricing Validation by Actuarial/Underwriting' },
{ title: 'Operations Check', description: 'Operations check and High Level review of GTCs, Policy subscription (premium collection, refunds etc)' },
{ title: 'Claims Handling Validation', description: 'Claims Handling processes for validation' },
{ title: 'Finance Check', description: 'Finance Check and validation regarding Financial Flows, Tax Payment, Commission Distribution, Claims Fund and Frequency of payments' },
{ title: 'Compliance Check', description: 'Check and validation of GTC, GDPR and any relevant Compliance topic' },
{ title: 'Risk Review', description: 'Review and Validation of adherence to risk profile of OIL and underwriting guidelines' },
{ title: 'Legal Creation/Review', description: 'Creation/Review of general terms and conditions and validation of contracts and annexes' },
{ title: 'IT Connectivity Confirm', description: 'Confirm connectivity, SAP, data templates agreed or if changes are required' },
];
for (const taskData of departmentTasks) {
const existingTask = await db.tasks.findOne({
where: {
leadId: id,
title: taskData.title
},
transaction
});
if (!existingTask) {
await db.tasks.create({
...taskData,
status: 'Todo',
leadId: id,
createdById: currentUser.id,
updatedById: currentUser.id,
}, { transaction });
}
}
}
// Workflow Transition Logic: Move to Step 6 (Final MNPC Approval)
if (data.status === 'FinalApproval' && leads.status === 'UnderwritingReview') {
const approvalTitle = 'Final MNPC Approval (Post-Dept Tasks)';
const existingApproval = await db.approvals.findOne({ where: { leadId: id, approval_title: approvalTitle }, transaction });
if (!existingApproval) {
await db.approvals.create({
approval_title: approvalTitle,
status: 'Pending',
leadId: id,
createdById: currentUser.id,
updatedById: currentUser.id,
}, { transaction });
}
}
// Workflow Transition Logic: Move to Step 7 (Monitoring / Launched)
if (data.status === 'Launched' && leads.status === 'FinalApproval') {
const reportTitle = 'Initial After-Launch Monitoring';
const existingReport = await db.monitoring_reports.findOne({ where: { leadId: id, report_title: reportTitle }, transaction });
if (!existingReport) {
await db.monitoring_reports.create({
report_title: reportTitle,
leadId: id,
period_start: new Date(),
adherence_score: 100,
created_byId: currentUser.id,
organizationsId: currentUser.organizationId || currentUser.organizationsId,
}, { transaction });
}
}
const updatedLeads = await LeadsDBApi.update(
id,
data,
@ -132,7 +236,5 @@ module.exports = class LeadsService {
}
}
};

View File

@ -0,0 +1,71 @@
import React from 'react'
import { mdiCheckCircle, mdiCircleOutline, mdiClockOutline } from '@mdi/js'
import BaseIcon from '../BaseIcon'
const workflowSteps = [
{ label: 'Documentation', status: 'Submitted' },
{ label: 'Pricing', status: 'PricingReview' },
{ label: 'Validation', status: 'Validation' },
{ label: 'Kick Off', status: 'KickOff' },
{ label: 'Dept Tasks', status: 'UnderwritingReview' },
{ label: 'Final Approval', status: 'FinalApproval' },
{ label: 'Monitoring', status: 'Launched' },
]
interface Props {
currentStatus: string
}
export default function ProgramWorkflow({ currentStatus }: Props) {
const currentIndex = workflowSteps.findIndex((s) => s.status === currentStatus)
// Fallback for Draft
const effectiveIndex = currentStatus === 'Draft' ? 0 : currentIndex
return (
<div className="mb-6 overflow-x-auto pb-4">
<div className="flex items-center min-w-max">
{workflowSteps.map((step, index) => {
const isCompleted = index < effectiveIndex
const isActive = index === effectiveIndex
const isPending = index > effectiveIndex
return (
<React.Fragment key={step.label}>
<div className="flex flex-col items-center relative">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center border-2 ${
isActive
? 'border-blue-600 bg-blue-50 text-blue-600'
: isCompleted
? 'border-green-500 bg-green-50 text-green-500'
: 'border-gray-300 bg-gray-50 text-gray-400'
}`}
>
<BaseIcon
path={isCompleted ? mdiCheckCircle : isActive ? mdiClockOutline : mdiCircleOutline}
size={24}
/>
</div>
<div
className={`absolute top-12 text-xs font-semibold whitespace-nowrap ${
isActive ? 'text-blue-600' : isCompleted ? 'text-green-600' : 'text-gray-500'
}`}
>
{step.label}
</div>
</div>
{index < workflowSteps.length - 1 && (
<div
className={`h-0.5 w-16 mx-2 mt-[-1rem] ${
index < effectiveIndex ? 'bg-green-500' : 'bg-gray-300'
}`}
/>
)}
</React.Fragment>
)
})}
</div>
<div className="h-8" /> {/* Spacer for labels */}
</div>
)
}

View File

@ -1,4 +1,4 @@
import { mdiAccount, mdiChartTimelineVariant, mdiMail, mdiUpload } from '@mdi/js'
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import CardBox from '../../components/CardBox'
@ -12,399 +12,88 @@ import FormField from '../../components/FormField'
import BaseDivider from '../../components/BaseDivider'
import BaseButtons from '../../components/BaseButtons'
import BaseButton from '../../components/BaseButton'
import FormCheckRadio from '../../components/FormCheckRadio'
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'
import FormFilePicker from '../../components/FormFilePicker'
import FormImagePicker from '../../components/FormImagePicker'
import { SwitchField } from '../../components/SwitchField'
import { SelectField } from '../../components/SelectField'
import { SelectFieldMany } from "../../components/SelectFieldMany";
import {RichTextField} from "../../components/RichTextField";
import { create } from '../../stores/lead_checklists/lead_checklistsSlice'
import { useAppDispatch } from '../../stores/hooks'
import { useRouter } from 'next/router'
import moment from 'moment';
const initialValues = {
lead: '',
item: '',
detail: '',
completed: false,
completed_by: '',
due_date: '',
organizations: '',
}
const Lead_checklistsNew = () => {
const router = useRouter()
const dispatch = useAppDispatch()
const { leadId } = router.query
const initialValues = {
lead: leadId || '',
item: '',
detail: '',
completed: false,
completed_by: '',
due_date: '',
organizations: '',
}
const handleSubmit = async (data) => {
await dispatch(create(data))
await router.push('/lead_checklists/lead_checklists-list')
if (leadId) {
await router.push(`/leads/${leadId}`)
} else {
await router.push('/lead_checklists/lead_checklists-list')
}
}
return (
<>
<Head>
<title>{getPageTitle('New Item')}</title>
<title>{getPageTitle('New Checklist Item')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Checklist Item" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik
initialValues={
initialValues
}
enableReinitialize
initialValues={initialValues}
onSubmit={(values) => handleSubmit(values)}
>
<Form>
<FormField label="Lead" labelFor="lead">
<Field name="lead" id="lead" component={SelectField} options={[]} itemRef={'leads'}></Field>
</FormField>
<FormField label="Checklist Item">
<Field name="item" placeholder="Task or item name" />
</FormField>
<FormField label="Detail" hasTextareaHeight>
<Field name="detail" as="textarea" placeholder="Detailed description of the requirement" />
</FormField>
<FormField label='Completed' labelFor='completed'>
<Field name='completed' id='completed' component={SwitchField}></Field>
</FormField>
<FormField label="Completed By" labelFor="completed_by">
<Field name="completed_by" id="completed_by" component={SelectField} options={[]} itemRef={'users'}></Field>
</FormField>
<FormField label="Lead" labelFor="lead">
<Field name="lead" id="lead" component={SelectField} options={[]} itemRef={'leads'}></Field>
</FormField>
<FormField
label="ChecklistItem"
>
<Field
name="item"
placeholder="ChecklistItem"
/>
</FormField>
<FormField label="Detail" hasTextareaHeight>
<Field name="detail" as="textarea" placeholder="Detail" />
</FormField>
<FormField label='Completed' labelFor='completed'>
<Field
name='completed'
id='completed'
component={SwitchField}
></Field>
</FormField>
<FormField label="CompletedBy" labelFor="completed_by">
<Field name="completed_by" id="completed_by" component={SelectField} options={[]} itemRef={'users'}></Field>
</FormField>
<FormField
label="DueDate"
>
<Field
type="datetime-local"
name="due_date"
placeholder="DueDate"
/>
</FormField>
<FormField label="organizations" labelFor="organizations">
<Field name="organizations" id="organizations" component={SelectField} options={[]} itemRef={'organizations'}></Field>
</FormField>
<FormField label="Due Date">
<Field type="datetime-local" name="due_date" />
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="submit" color="info" label="Create Item" />
<BaseButton type="reset" color="info" outline label="Reset" />
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/lead_checklists/lead_checklists-list')}/>
<BaseButton
type='button'
color='danger'
outline
label='Cancel'
onClick={() => leadId ? router.push(`/leads/${leadId}`) : router.push('/lead_checklists/lead_checklists-list')}
/>
</BaseButtons>
</Form>
</Formik>
@ -416,14 +105,10 @@ const Lead_checklistsNew = () => {
Lead_checklistsNew.getLayout = function getLayout(page: ReactElement) {
return (
<LayoutAuthenticated
permission={'CREATE_LEAD_CHECKLISTS'}
>
{page}
</LayoutAuthenticated>
<LayoutAuthenticated permission={'CREATE_LEAD_CHECKLISTS'}>
{page}
</LayoutAuthenticated>
)
}
export default Lead_checklistsNew
export default Lead_checklistsNew

View File

@ -1,4 +1,4 @@
import { mdiAccount, mdiChartTimelineVariant, mdiMail, mdiUpload } from '@mdi/js'
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import CardBox from '../../components/CardBox'
@ -12,505 +12,104 @@ import FormField from '../../components/FormField'
import BaseDivider from '../../components/BaseDivider'
import BaseButtons from '../../components/BaseButtons'
import BaseButton from '../../components/BaseButton'
import FormCheckRadio from '../../components/FormCheckRadio'
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'
import FormFilePicker from '../../components/FormFilePicker'
import FormImagePicker from '../../components/FormImagePicker'
import { SwitchField } from '../../components/SwitchField'
import { SelectField } from '../../components/SelectField'
import { SelectFieldMany } from "../../components/SelectFieldMany";
import {RichTextField} from "../../components/RichTextField";
import { create } from '../../stores/lead_evaluations/lead_evaluationsSlice'
import { useAppDispatch } from '../../stores/hooks'
import { useRouter } from 'next/router'
import moment from 'moment';
const initialValues = {
lead: '',
part: 'Part1',
evaluator: '',
summary: '',
notes: '',
score: '',
approved: false,
evaluation_date: '',
organizations: '',
}
const Lead_evaluationsNew = () => {
const router = useRouter()
const dispatch = useAppDispatch()
const { leadId } = router.query
const initialValues = {
lead: leadId || '',
part: 'Part1',
evaluator: '',
summary: '',
notes: '',
score: '',
approved: false,
evaluation_date: '',
organizations: '',
}
const handleSubmit = async (data) => {
await dispatch(create(data))
await router.push('/lead_evaluations/lead_evaluations-list')
if (leadId) {
await router.push(`/leads/${leadId}`)
} else {
await router.push('/lead_evaluations/lead_evaluations-list')
}
}
return (
<>
<Head>
<title>{getPageTitle('New Item')}</title>
<title>{getPageTitle('New Evaluation')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Lead Evaluation" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik
initialValues={
initialValues
}
enableReinitialize
initialValues={initialValues}
onSubmit={(values) => handleSubmit(values)}
>
<Form>
<FormField label="Lead" labelFor="lead">
<Field name="lead" id="lead" component={SelectField} options={[]} itemRef={'leads'}></Field>
</FormField>
<FormField label="EvaluationPart" labelFor="part">
<Field name="part" id="part" component="select">
<option value="Part1">Part1</option>
<option value="Part2">Part2</option>
<option value="Part3">Part3</option>
</Field>
</FormField>
<FormField label="Evaluator" labelFor="evaluator">
<Field name="evaluator" id="evaluator" component={SelectField} options={[]} itemRef={'users'}></Field>
</FormField>
<FormField label="Summary" hasTextareaHeight>
<Field name="summary" as="textarea" placeholder="Summary" />
</FormField>
<FormField label='Notes' hasTextareaHeight>
<Field
name='notes'
id='notes'
component={RichTextField}
></Field>
</FormField>
<FormField
label="Score"
>
<Field
type="number"
name="score"
placeholder="Score"
/>
</FormField>
<FormField label='Approved' labelFor='approved'>
<Field
name='approved'
id='approved'
component={SwitchField}
></Field>
</FormField>
<FormField
label="EvaluationDate"
>
<Field
type="datetime-local"
name="evaluation_date"
placeholder="EvaluationDate"
/>
</FormField>
<FormField label="organizations" labelFor="organizations">
<Field name="organizations" id="organizations" component={SelectField} options={[]} itemRef={'organizations'}></Field>
</FormField>
<FormField label="Lead" labelFor="lead">
<Field name="lead" id="lead" component={SelectField} options={[]} itemRef={'leads'}></Field>
</FormField>
<FormField label="Evaluation Part" labelFor="part">
<Field name="part" id="part" component="select" className="w-full border-gray-300 rounded">
<option value="Part1">Part 1 - Summary Info</option>
<option value="Part2">Part 2 - Market Analysis</option>
<option value="Part3">Part 3 - Financial Projections</option>
</Field>
</FormField>
<FormField label="Evaluator" labelFor="evaluator">
<Field name="evaluator" id="evaluator" component={SelectField} options={[]} itemRef={'users'}></Field>
</FormField>
<FormField label="Evaluation Summary" hasTextareaHeight>
<Field name="summary" as="textarea" placeholder="Provide a brief summary of the evaluation part" />
</FormField>
<FormField label='Detailed Notes' hasTextareaHeight>
<Field name='notes' id='notes' component={RichTextField}></Field>
</FormField>
<FormField label="Risk Score (1-10)">
<Field type="number" name="score" placeholder="Score" />
</FormField>
<FormField label='Approved for this Part' labelFor='approved'>
<Field name='approved' id='approved' component={SwitchField}></Field>
</FormField>
<FormField label="Evaluation Date">
<Field type="datetime-local" name="evaluation_date" />
</FormField>
<BaseDivider />
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="submit" color="info" label="Create Evaluation" />
<BaseButton type="reset" color="info" outline label="Reset" />
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/lead_evaluations/lead_evaluations-list')}/>
<BaseButton
type='button'
color='danger'
outline
label='Cancel'
onClick={() => leadId ? router.push(`/leads/${leadId}`) : router.push('/lead_evaluations/lead_evaluations-list')}
/>
</BaseButtons>
</Form>
</Formik>
@ -522,14 +121,10 @@ const Lead_evaluationsNew = () => {
Lead_evaluationsNew.getLayout = function getLayout(page: ReactElement) {
return (
<LayoutAuthenticated
permission={'CREATE_LEAD_EVALUATIONS'}
>
{page}
</LayoutAuthenticated>
<LayoutAuthenticated permission={'CREATE_LEAD_EVALUATIONS'}>
{page}
</LayoutAuthenticated>
)
}
export default Lead_evaluationsNew
export default Lead_evaluationsNew

File diff suppressed because it is too large Load Diff