task-d.5.6

This commit is contained in:
phaichayon
2026-06-25 13:09:21 +07:00
parent c2a74b6764
commit 65dd0e2e56
27 changed files with 1583 additions and 179 deletions

View File

@@ -342,6 +342,7 @@ export const crmOpportunities = pgTable(
projectLocation: text('project_location'),
productType: text('product_type').notNull(),
status: text('status').notNull(),
outcomeStatus: text('outcome_status').default('open').notNull(),
priority: text('priority').notNull(),
leadChannel: text('lead_channel'),
estimatedValue: doublePrecision('estimated_value'),
@@ -353,6 +354,7 @@ export const crmOpportunities = pgTable(
isHotProject: boolean('is_hot_project').default(false).notNull(),
isActive: boolean('is_active').default(true).notNull(),
pipelineStage: text('pipeline_stage').default('lead').notNull(),
closedAt: timestamp('closed_at', { withTimezone: true }),
closedWonAt: timestamp('closed_won_at', { withTimezone: true }),
closedLostAt: timestamp('closed_lost_at', { withTimezone: true }),
closedByUserId: text('closed_by_user_id'),
@@ -361,8 +363,11 @@ export const crmOpportunities = pgTable(
poAmount: doublePrecision('po_amount'),
poCurrency: text('po_currency'),
lostReason: text('lost_reason'),
lostDetail: text('lost_detail'),
lostCompetitor: text('lost_competitor'),
lostRemark: text('lost_remark'),
cancelReason: text('cancel_reason'),
noQuotationReason: text('no_quotation_reason'),
assignedToUserId: text('assigned_to_user_id'),
assignedAt: timestamp('assigned_at', { withTimezone: true }),
assignedBy: text('assigned_by'),
@@ -804,4 +809,3 @@ export const crmDocumentTemplateTableColumns = pgTable(
)
})
);

View File

@@ -201,12 +201,60 @@ const FOUNDATION_OPTIONS = {
],
crm_opportunity_status: [
{ code: 'new', label: 'New', value: 'new', sortOrder: 1 },
{ code: 'qualifying', label: 'Qualifying', value: 'qualifying', sortOrder: 2 },
{ code: 'requirement', label: 'Requirement', value: 'requirement', sortOrder: 3 },
{ code: 'follow_up', label: 'Follow Up', value: 'follow_up', sortOrder: 4 },
{ code: 'converted', label: 'Converted', value: 'converted', sortOrder: 5 },
{ code: 'closed_lost', label: 'Closed Lost', value: 'closed_lost', sortOrder: 6 },
{ code: 'cancelled', label: 'Cancelled', value: 'cancelled', sortOrder: 7 }
{ code: 'qualification', label: 'Qualification', value: 'qualification', sortOrder: 2 },
{
code: 'requirement_gathering',
label: 'Requirement Gathering',
value: 'requirement_gathering',
sortOrder: 3
},
{ code: 'site_survey', label: 'Site Survey', value: 'site_survey', sortOrder: 4 },
{
code: 'proposal_preparation',
label: 'Proposal Preparation',
value: 'proposal_preparation',
sortOrder: 5
},
{
code: 'quotation_created',
label: 'Quotation Created',
value: 'quotation_created',
sortOrder: 6
},
{ code: 'negotiation', label: 'Negotiation', value: 'negotiation', sortOrder: 7 },
{ code: 'closed', label: 'Closed', value: 'closed', sortOrder: 8 }
],
crm_opportunity_stage: [
{ code: 'new', label: 'New', value: 'new', sortOrder: 1 },
{ code: 'qualification', label: 'Qualification', value: 'qualification', sortOrder: 2 },
{
code: 'requirement_gathering',
label: 'Requirement Gathering',
value: 'requirement_gathering',
sortOrder: 3
},
{ code: 'site_survey', label: 'Site Survey', value: 'site_survey', sortOrder: 4 },
{
code: 'proposal_preparation',
label: 'Proposal Preparation',
value: 'proposal_preparation',
sortOrder: 5
},
{
code: 'quotation_created',
label: 'Quotation Created',
value: 'quotation_created',
sortOrder: 6
},
{ code: 'negotiation', label: 'Negotiation', value: 'negotiation', sortOrder: 7 },
{ code: 'closed', label: 'Closed', value: 'closed', sortOrder: 8 }
],
crm_opportunity_outcome_status: [
{ code: 'open', label: 'Open', value: 'open', sortOrder: 1 },
{ code: 'won', label: 'Won', value: 'won', sortOrder: 2 },
{ code: 'lost', label: 'Lost', value: 'lost', sortOrder: 3 },
{ code: 'cancelled', label: 'Cancelled', value: 'cancelled', sortOrder: 4 },
{ code: 'no_quotation', label: 'No Quotation', value: 'no_quotation', sortOrder: 5 }
],
crm_quotation_status: [
{ code: 'draft', label: 'Draft', value: 'draft', sortOrder: 1 },
@@ -295,33 +343,111 @@ const FOUNDATION_OPTIONS = {
crm_lost_reason: [
{ code: 'competitor_won', label: 'Competitor Won', value: 'competitor_won', sortOrder: 1 },
{
code: 'budget_not_approved',
label: 'Budget Not Approved',
value: 'budget_not_approved',
code: 'budget_cancelled',
label: 'Budget Cancelled',
value: 'budget_cancelled',
sortOrder: 2
},
{
code: 'project_cancelled',
label: 'Project Cancelled',
value: 'project_cancelled',
code: 'timeline_not_match',
label: 'Timeline Not Match',
value: 'timeline_not_match',
sortOrder: 3
},
{
code: 'spec_not_match',
label: 'Spec Not Match',
value: 'spec_not_match',
sortOrder: 4
},
{
code: 'customer_no_response',
label: 'Customer No Response',
value: 'customer_no_response',
sortOrder: 4
},
{
code: 'technical_requirement_mismatch',
label: 'Technical Requirement Mismatch',
value: 'technical_requirement_mismatch',
sortOrder: 5
},
{ code: 'price_too_high', label: 'Price Too High', value: 'price_too_high', sortOrder: 6 },
{ code: 'timeline_not_fit', label: 'Timeline Not Fit', value: 'timeline_not_fit', sortOrder: 7 },
{ code: 'other', label: 'Other', value: 'other', sortOrder: 8 }
],
crm_opportunity_lost_reason: [
{ code: 'competitor_won', label: 'Competitor Won', value: 'competitor_won', sortOrder: 1 },
{
code: 'budget_cancelled',
label: 'Budget Cancelled',
value: 'budget_cancelled',
sortOrder: 2
},
{
code: 'timeline_not_match',
label: 'Timeline Not Match',
value: 'timeline_not_match',
sortOrder: 3
},
{
code: 'spec_not_match',
label: 'Spec Not Match',
value: 'spec_not_match',
sortOrder: 4
},
{
code: 'customer_no_response',
label: 'Customer No Response',
value: 'customer_no_response',
sortOrder: 5
},
{ code: 'price_too_high', label: 'Price Too High', value: 'price_too_high', sortOrder: 6 },
{ code: 'timeline_not_fit', label: 'Timeline Not Fit', value: 'timeline_not_fit', sortOrder: 7 },
{ code: 'other', label: 'Other', value: 'other', sortOrder: 8 }
],
crm_opportunity_cancel_reason: [
{
code: 'customer_cancelled',
label: 'Customer Cancelled',
value: 'customer_cancelled',
sortOrder: 1
},
{
code: 'internal_hold',
label: 'Internal Hold',
value: 'internal_hold',
sortOrder: 2
},
{
code: 'scope_changed',
label: 'Scope Changed',
value: 'scope_changed',
sortOrder: 3
},
{ code: 'other', label: 'Other', value: 'other', sortOrder: 4 }
],
crm_opportunity_no_quotation_reason: [
{
code: 'budget_not_ready',
label: 'Budget Not Ready',
value: 'budget_not_ready',
sortOrder: 1
},
{
code: 'qualification_failed',
label: 'Qualification Failed',
value: 'qualification_failed',
sortOrder: 2
},
{
code: 'duplicate_opportunity',
label: 'Duplicate Opportunity',
value: 'duplicate_opportunity',
sortOrder: 3
},
{
code: 'customer_stopped',
label: 'Customer Stopped',
value: 'customer_stopped',
sortOrder: 4
},
{ code: 'other', label: 'Other', value: 'other', sortOrder: 5 }
],
crm_job_title: [
{ code: 'sales', label: 'Sales Engineer', value: 'sales', sortOrder: 1 },
{ code: 'sales_support', label: 'Sales Support', value: 'sales_support', sortOrder: 2 },