From e0af6f2f1b656958b9d70bf189214496996b0660 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:16:44 +0000 Subject: [PATCH 1/5] Initial plan From b77e96cd6bf7a5b99569fff12ed9624575c2c7a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:22:44 +0000 Subject: [PATCH 2/5] Add new CRM objects, security, AI agents, RAG pipelines, and automation flows Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/app-crm/src/ai/agents.ts | 426 +++++++++++++ examples/app-crm/src/ai/rag-pipelines.ts | 379 ++++++++++++ examples/app-crm/src/automation/flows.ts | 576 ++++++++++++++++++ .../src/domains/marketing/campaign.object.ts | 248 ++++++++ .../src/domains/products/product.object.ts | 152 +++++ .../app-crm/src/domains/sales/account.hook.ts | 30 + .../src/domains/sales/account.object.ts | 178 ++++++ .../src/domains/sales/contact.object.ts | 172 ++++++ .../src/domains/sales/contract.object.ts | 242 ++++++++ .../app-crm/src/domains/sales/lead.hook.ts | 32 + .../app-crm/src/domains/sales/lead.object.ts | 236 +++++++ .../src/domains/sales/opportunity.object.ts | 260 ++++++++ .../app-crm/src/domains/sales/quote.object.ts | 215 +++++++ .../src/domains/service/case.object.ts | 298 +++++++++ .../src/domains/service/task.object.ts | 261 ++++++++ examples/app-crm/src/security/profiles.ts | 441 ++++++++++++++ .../app-crm/src/security/sharing-rules.ts | 216 +++++++ 17 files changed, 4362 insertions(+) create mode 100644 examples/app-crm/src/ai/agents.ts create mode 100644 examples/app-crm/src/ai/rag-pipelines.ts create mode 100644 examples/app-crm/src/automation/flows.ts create mode 100644 examples/app-crm/src/domains/marketing/campaign.object.ts create mode 100644 examples/app-crm/src/domains/products/product.object.ts create mode 100644 examples/app-crm/src/domains/sales/account.hook.ts create mode 100644 examples/app-crm/src/domains/sales/account.object.ts create mode 100644 examples/app-crm/src/domains/sales/contact.object.ts create mode 100644 examples/app-crm/src/domains/sales/contract.object.ts create mode 100644 examples/app-crm/src/domains/sales/lead.hook.ts create mode 100644 examples/app-crm/src/domains/sales/lead.object.ts create mode 100644 examples/app-crm/src/domains/sales/opportunity.object.ts create mode 100644 examples/app-crm/src/domains/sales/quote.object.ts create mode 100644 examples/app-crm/src/domains/service/case.object.ts create mode 100644 examples/app-crm/src/domains/service/task.object.ts create mode 100644 examples/app-crm/src/security/profiles.ts create mode 100644 examples/app-crm/src/security/sharing-rules.ts diff --git a/examples/app-crm/src/ai/agents.ts b/examples/app-crm/src/ai/agents.ts new file mode 100644 index 0000000000..ce6eeb09a2 --- /dev/null +++ b/examples/app-crm/src/ai/agents.ts @@ -0,0 +1,426 @@ +import type { Agent } from '@objectstack/spec/ai'; + +/** + * CRM AI Agents + * Define AI agents for automated business processes + */ + +// Sales Assistant Agent +export const SalesAssistantAgent: Agent = { + name: 'sales_assistant', + label: 'Sales Assistant', + description: 'AI agent to help sales reps with lead qualification and opportunity management', + + role: 'assistant', + + instructions: `You are a sales assistant AI helping sales representatives manage their pipeline. + +Your responsibilities: +1. Qualify incoming leads based on BANT criteria (Budget, Authority, Need, Timeline) +2. Suggest next best actions for opportunities +3. Draft personalized email templates +4. Analyze win/loss patterns +5. Provide competitive intelligence +6. Generate sales forecasts + +Always be professional, data-driven, and focused on helping close deals.`, + + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.7, + maxTokens: 2000, + }, + + tools: [ + { + name: 'analyze_lead', + description: 'Analyze a lead and provide qualification score', + parameters: { + lead_id: 'string', + }, + }, + { + name: 'suggest_next_action', + description: 'Suggest next best action for an opportunity', + parameters: { + opportunity_id: 'string', + }, + }, + { + name: 'generate_email', + description: 'Generate a personalized email template', + parameters: { + recipient_id: 'string', + context: 'string', + tone: 'string', + }, + }, + ], + + knowledge: { + sources: [ + { + type: 'object', + objectName: 'lead', + fields: ['*'], + }, + { + type: 'object', + objectName: 'opportunity', + fields: ['*'], + }, + { + type: 'object', + objectName: 'account', + fields: ['*'], + }, + { + type: 'document', + path: '/knowledge/sales-playbook.md', + }, + { + type: 'document', + path: '/knowledge/product-catalog.md', + }, + ], + }, + + triggers: [ + { + type: 'object_create', + objectName: 'lead', + condition: 'rating = "hot"', + }, + { + type: 'object_update', + objectName: 'opportunity', + condition: 'ISCHANGED(stage)', + }, + ], +}; + +// Customer Service Agent +export const ServiceAgent: Agent = { + name: 'service_agent', + label: 'Customer Service Agent', + description: 'AI agent to assist with customer support cases', + + role: 'assistant', + + instructions: `You are a customer service AI agent helping support representatives resolve customer issues. + +Your responsibilities: +1. Triage incoming cases based on priority and category +2. Suggest relevant knowledge articles +3. Draft response templates +4. Escalate critical issues +5. Identify common problems and patterns +6. Recommend process improvements + +Always be empathetic, solution-focused, and customer-centric.`, + + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.5, + maxTokens: 1500, + }, + + tools: [ + { + name: 'triage_case', + description: 'Analyze case and assign priority', + parameters: { + case_id: 'string', + }, + }, + { + name: 'search_knowledge', + description: 'Search knowledge base for solutions', + parameters: { + query: 'string', + }, + }, + { + name: 'generate_response', + description: 'Generate customer response', + parameters: { + case_id: 'string', + tone: 'string', + }, + }, + ], + + knowledge: { + sources: [ + { + type: 'object', + objectName: 'case', + fields: ['*'], + }, + { + type: 'object', + objectName: 'account', + fields: ['*'], + }, + { + type: 'document', + path: '/knowledge/support-kb/**/*.md', + }, + { + type: 'document', + path: '/knowledge/sla-policies.md', + }, + ], + }, + + triggers: [ + { + type: 'object_create', + objectName: 'case', + }, + { + type: 'object_update', + objectName: 'case', + condition: 'priority = "critical"', + }, + ], +}; + +// Lead Enrichment Agent +export const LeadEnrichmentAgent: Agent = { + name: 'lead_enrichment', + label: 'Lead Enrichment Agent', + description: 'AI agent to automatically enrich lead data from external sources', + + role: 'worker', + + instructions: `You are a lead enrichment AI that enhances lead records with additional data. + +Your responsibilities: +1. Look up company information from external databases +2. Enrich contact details (job title, LinkedIn, etc.) +3. Add firmographic data (industry, size, revenue) +4. Research company technology stack +5. Find social media profiles +6. Validate email addresses and phone numbers + +Always use reputable data sources and maintain data quality.`, + + model: { + provider: 'openai', + model: 'gpt-3.5-turbo', + temperature: 0.3, + maxTokens: 1000, + }, + + tools: [ + { + name: 'lookup_company', + description: 'Look up company information', + parameters: { + company_name: 'string', + domain: 'string', + }, + }, + { + name: 'enrich_contact', + description: 'Enrich contact information', + parameters: { + email: 'string', + linkedin_url: 'string', + }, + }, + { + name: 'validate_email', + description: 'Validate email address', + parameters: { + email: 'string', + }, + }, + ], + + knowledge: { + sources: [ + { + type: 'object', + objectName: 'lead', + fields: ['company', 'email', 'phone', 'website'], + }, + ], + }, + + triggers: [ + { + type: 'object_create', + objectName: 'lead', + }, + ], + + schedule: { + type: 'cron', + expression: '0 */4 * * *', // Every 4 hours + timezone: 'UTC', + }, +}; + +// Revenue Intelligence Agent +export const RevenueIntelligenceAgent: Agent = { + name: 'revenue_intelligence', + label: 'Revenue Intelligence Agent', + description: 'AI agent to analyze pipeline and provide revenue insights', + + role: 'analyst', + + instructions: `You are a revenue intelligence AI that analyzes sales data and provides insights. + +Your responsibilities: +1. Analyze pipeline health and quality +2. Identify at-risk deals +3. Forecast revenue with confidence intervals +4. Detect anomalies and trends +5. Suggest coaching opportunities +6. Generate executive summaries + +Use statistical analysis and machine learning to provide data-driven insights.`, + + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.2, + maxTokens: 3000, + }, + + tools: [ + { + name: 'analyze_pipeline', + description: 'Analyze sales pipeline health', + parameters: { + user_id: 'string', + time_period: 'string', + }, + }, + { + name: 'identify_at_risk', + description: 'Identify at-risk opportunities', + parameters: { + threshold: 'number', + }, + }, + { + name: 'forecast_revenue', + description: 'Generate revenue forecast', + parameters: { + time_period: 'string', + method: 'string', + }, + }, + ], + + knowledge: { + sources: [ + { + type: 'object', + objectName: 'opportunity', + fields: ['*'], + }, + { + type: 'object', + objectName: 'account', + fields: ['*'], + }, + { + type: 'analytics', + dashboardName: 'sales_dashboard', + }, + ], + }, + + schedule: { + type: 'cron', + expression: '0 8 * * 1', // Monday at 8am + timezone: 'America/Los_Angeles', + }, +}; + +// Email Campaign Agent +export const EmailCampaignAgent: Agent = { + name: 'email_campaign', + label: 'Email Campaign Agent', + description: 'AI agent to create and optimize email campaigns', + + role: 'creator', + + instructions: `You are an email marketing AI that creates and optimizes email campaigns. + +Your responsibilities: +1. Write compelling email copy +2. Optimize subject lines for open rates +3. Personalize content based on recipient data +4. A/B test different variations +5. Analyze campaign performance +6. Suggest improvements + +Follow email marketing best practices and maintain brand voice.`, + + model: { + provider: 'anthropic', + model: 'claude-3-opus', + temperature: 0.8, + maxTokens: 2000, + }, + + tools: [ + { + name: 'generate_email_copy', + description: 'Generate email campaign copy', + parameters: { + campaign_id: 'string', + audience: 'string', + goal: 'string', + }, + }, + { + name: 'optimize_subject_line', + description: 'Optimize email subject line', + parameters: { + subject: 'string', + }, + }, + { + name: 'personalize_content', + description: 'Personalize email content', + parameters: { + template: 'string', + recipient_data: 'object', + }, + }, + ], + + knowledge: { + sources: [ + { + type: 'object', + objectName: 'campaign', + fields: ['*'], + }, + { + type: 'document', + path: '/knowledge/brand-guidelines.md', + }, + { + type: 'document', + path: '/knowledge/email-templates/**/*.html', + }, + ], + }, +}; + +export const CrmAgents = { + SalesAssistantAgent, + ServiceAgent, + LeadEnrichmentAgent, + RevenueIntelligenceAgent, + EmailCampaignAgent, +}; diff --git a/examples/app-crm/src/ai/rag-pipelines.ts b/examples/app-crm/src/ai/rag-pipelines.ts new file mode 100644 index 0000000000..372008298e --- /dev/null +++ b/examples/app-crm/src/ai/rag-pipelines.ts @@ -0,0 +1,379 @@ +import type { RagPipeline } from '@objectstack/spec/ai'; + +/** + * CRM RAG Pipelines + * Define Retrieval-Augmented Generation pipelines for knowledge retrieval + */ + +// Sales Knowledge RAG Pipeline +export const SalesKnowledgeRAG: RagPipeline = { + name: 'sales_knowledge', + label: 'Sales Knowledge Pipeline', + description: 'RAG pipeline for sales team knowledge and best practices', + + indexes: [ + { + name: 'sales_playbook_index', + type: 'vector', + + sources: [ + { + type: 'document', + path: '/knowledge/sales/**/*.md', + watch: true, + }, + { + type: 'document', + path: '/knowledge/products/**/*.pdf', + watch: true, + }, + { + type: 'object', + objectName: 'opportunity', + fields: ['name', 'description', 'stage', 'amount'], + filter: { + stage: 'closed_won', + close_date: { $gte: '{last_12_months}' }, + }, + }, + ], + + embedding: { + provider: 'openai', + model: 'text-embedding-3-large', + dimensions: 1536, + }, + + chunking: { + strategy: 'semantic', + chunkSize: 1000, + chunkOverlap: 200, + }, + + metadata: { + extractors: [ + { + type: 'title', + source: 'filename', + }, + { + type: 'date', + source: 'modified_date', + }, + { + type: 'category', + source: 'directory', + }, + ], + }, + }, + ], + + retrieval: { + strategy: 'hybrid', + + vectorSearch: { + topK: 10, + scoreThreshold: 0.7, + algorithm: 'cosine', + }, + + keywordSearch: { + enabled: true, + weight: 0.3, + }, + + reranking: { + enabled: true, + model: 'cohere-rerank', + topK: 5, + }, + }, + + generation: { + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.7, + maxTokens: 2000, + }, + + promptTemplate: `You are a sales expert assistant. Use the following context to answer the question. + +Context: +{context} + +Question: {question} + +Answer the question based on the context. If you cannot find the answer in the context, say so.`, + }, + + caching: { + enabled: true, + ttl: 3600, // 1 hour + }, +}; + +// Customer Support Knowledge RAG Pipeline +export const SupportKnowledgeRAG: RagPipeline = { + name: 'support_knowledge', + label: 'Support Knowledge Pipeline', + description: 'RAG pipeline for customer support knowledge base', + + indexes: [ + { + name: 'support_kb_index', + type: 'vector', + + sources: [ + { + type: 'document', + path: '/knowledge/support/**/*.md', + watch: true, + }, + { + type: 'object', + objectName: 'case', + fields: ['subject', 'description', 'resolution', 'status'], + filter: { + is_closed: true, + resolution: { $ne: null }, + }, + }, + ], + + embedding: { + provider: 'openai', + model: 'text-embedding-3-small', + dimensions: 768, + }, + + chunking: { + strategy: 'fixed', + chunkSize: 512, + chunkOverlap: 100, + }, + + metadata: { + extractors: [ + { + type: 'category', + source: 'directory', + }, + { + type: 'tags', + source: 'frontmatter.tags', + }, + ], + }, + }, + ], + + retrieval: { + strategy: 'vector_only', + + vectorSearch: { + topK: 5, + scoreThreshold: 0.75, + algorithm: 'cosine', + }, + }, + + generation: { + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.3, + maxTokens: 1500, + }, + + promptTemplate: `You are a customer support specialist. Use the knowledge base to help resolve customer issues. + +Knowledge Base: +{context} + +Customer Issue: {question} + +Provide a clear, step-by-step solution. If you need more information, ask clarifying questions.`, + }, + + caching: { + enabled: true, + ttl: 7200, // 2 hours + }, +}; + +// Product Information RAG Pipeline +export const ProductInfoRAG: RagPipeline = { + name: 'product_info', + label: 'Product Information Pipeline', + description: 'RAG pipeline for product catalog and specifications', + + indexes: [ + { + name: 'product_catalog_index', + type: 'vector', + + sources: [ + { + type: 'object', + objectName: 'product', + fields: ['name', 'description', 'category', 'family', 'sku'], + }, + { + type: 'document', + path: '/knowledge/products/**/*.{md,pdf}', + watch: true, + }, + ], + + embedding: { + provider: 'openai', + model: 'text-embedding-3-small', + dimensions: 768, + }, + + chunking: { + strategy: 'semantic', + chunkSize: 800, + chunkOverlap: 150, + }, + }, + ], + + retrieval: { + strategy: 'hybrid', + + vectorSearch: { + topK: 8, + scoreThreshold: 0.6, + algorithm: 'cosine', + }, + + keywordSearch: { + enabled: true, + weight: 0.4, + }, + }, + + generation: { + model: { + provider: 'openai', + model: 'gpt-3.5-turbo', + temperature: 0.5, + maxTokens: 1000, + }, + + promptTemplate: `You are a product specialist. Answer questions about our products using the catalog. + +Product Catalog: +{context} + +Question: {question} + +Provide accurate product information. Highlight key features and benefits.`, + }, + + caching: { + enabled: true, + ttl: 14400, // 4 hours + }, +}; + +// Competitive Intelligence RAG Pipeline +export const CompetitiveIntelRAG: RagPipeline = { + name: 'competitive_intel', + label: 'Competitive Intelligence Pipeline', + description: 'RAG pipeline for competitive analysis and market insights', + + indexes: [ + { + name: 'competitive_index', + type: 'vector', + + sources: [ + { + type: 'document', + path: '/knowledge/competitive/**/*.md', + watch: true, + }, + { + type: 'document', + path: '/knowledge/market-research/**/*.pdf', + watch: true, + }, + ], + + embedding: { + provider: 'openai', + model: 'text-embedding-3-large', + dimensions: 1536, + }, + + chunking: { + strategy: 'semantic', + chunkSize: 1200, + chunkOverlap: 250, + }, + + metadata: { + extractors: [ + { + type: 'competitor', + source: 'frontmatter.competitor', + }, + { + type: 'date', + source: 'frontmatter.date', + }, + ], + }, + }, + ], + + retrieval: { + strategy: 'vector_only', + + vectorSearch: { + topK: 7, + scoreThreshold: 0.65, + algorithm: 'cosine', + }, + + reranking: { + enabled: true, + model: 'cohere-rerank', + topK: 5, + }, + }, + + generation: { + model: { + provider: 'anthropic', + model: 'claude-3-sonnet', + temperature: 0.6, + maxTokens: 2500, + }, + + promptTemplate: `You are a competitive intelligence analyst. Provide strategic insights based on market data. + +Market Intelligence: +{context} + +Analysis Request: {question} + +Provide comprehensive analysis with data-driven insights. Identify opportunities and threats.`, + }, + + caching: { + enabled: true, + ttl: 3600, // 1 hour + }, +}; + +export const CrmRagPipelines = { + SalesKnowledgeRAG, + SupportKnowledgeRAG, + ProductInfoRAG, + CompetitiveIntelRAG, +}; diff --git a/examples/app-crm/src/automation/flows.ts b/examples/app-crm/src/automation/flows.ts new file mode 100644 index 0000000000..adbbd08067 --- /dev/null +++ b/examples/app-crm/src/automation/flows.ts @@ -0,0 +1,576 @@ +import type { Flow } from '@objectstack/spec/automation'; + +/** + * CRM Automation Flows + * Define business process automation flows + */ + +// Lead Conversion Flow +export const LeadConversionFlow: Flow = { + name: 'lead_conversion', + label: 'Lead Conversion Process', + description: 'Automated flow to convert qualified leads to accounts, contacts, and opportunities', + type: 'screen', + + triggerType: 'manual', + objectName: 'lead', + + variables: [ + { + name: 'leadId', + type: 'text', + required: true, + }, + { + name: 'createOpportunity', + type: 'boolean', + defaultValue: true, + }, + { + name: 'opportunityName', + type: 'text', + }, + { + name: 'opportunityAmount', + type: 'currency', + }, + ], + + steps: [ + { + id: 'screen_1', + type: 'screen', + label: 'Conversion Details', + + fields: [ + { + name: 'createOpportunity', + label: 'Create Opportunity?', + type: 'boolean', + required: true, + }, + { + name: 'opportunityName', + label: 'Opportunity Name', + type: 'text', + required: true, + visibleWhen: '{createOpportunity} == true', + }, + { + name: 'opportunityAmount', + label: 'Opportunity Amount', + type: 'currency', + visibleWhen: '{createOpportunity} == true', + }, + ], + }, + + { + id: 'get_lead', + type: 'record_lookup', + label: 'Get Lead Record', + + objectName: 'lead', + filter: { + id: '{leadId}', + }, + outputVariable: 'leadRecord', + }, + + { + id: 'create_account', + type: 'record_create', + label: 'Create Account', + + objectName: 'account', + fields: { + name: '{leadRecord.company}', + phone: '{leadRecord.phone}', + website: '{leadRecord.website}', + industry: '{leadRecord.industry}', + annual_revenue: '{leadRecord.annual_revenue}', + number_of_employees: '{leadRecord.number_of_employees}', + billing_address: '{leadRecord.address}', + owner: '{$User.Id}', + is_active: true, + }, + outputVariable: 'accountId', + }, + + { + id: 'create_contact', + type: 'record_create', + label: 'Create Contact', + + objectName: 'contact', + fields: { + first_name: '{leadRecord.first_name}', + last_name: '{leadRecord.last_name}', + email: '{leadRecord.email}', + phone: '{leadRecord.phone}', + title: '{leadRecord.title}', + account: '{accountId}', + is_primary: true, + owner: '{$User.Id}', + }, + outputVariable: 'contactId', + }, + + { + id: 'decision_opportunity', + type: 'decision', + label: 'Create Opportunity?', + + condition: '{createOpportunity} == true', + + ifTrue: 'create_opportunity', + ifFalse: 'mark_converted', + }, + + { + id: 'create_opportunity', + type: 'record_create', + label: 'Create Opportunity', + + objectName: 'opportunity', + fields: { + name: '{opportunityName}', + account: '{accountId}', + contact: '{contactId}', + amount: '{opportunityAmount}', + stage: 'prospecting', + probability: 10, + lead_source: '{leadRecord.lead_source}', + close_date: '{TODAY() + 90}', + owner: '{$User.Id}', + }, + outputVariable: 'opportunityId', + }, + + { + id: 'mark_converted', + type: 'record_update', + label: 'Mark Lead as Converted', + + recordId: '{leadId}', + objectName: 'lead', + fields: { + is_converted: true, + converted_date: '{NOW()}', + converted_account: '{accountId}', + converted_contact: '{contactId}', + converted_opportunity: '{opportunityId}', + }, + }, + + { + id: 'send_notification', + type: 'email_alert', + label: 'Send Confirmation Email', + + template: 'lead_converted_notification', + recipients: ['{$User.Email}'], + variables: { + leadName: '{leadRecord.full_name}', + accountName: '{accountId.name}', + contactName: '{contactId.full_name}', + }, + }, + ], +}; + +// Opportunity Approval Flow +export const OpportunityApprovalFlow: Flow = { + name: 'opportunity_approval', + label: 'Large Deal Approval', + description: 'Approval process for opportunities over $100K', + type: 'autolaunched', + + triggerType: 'on_update', + objectName: 'opportunity', + criteria: 'amount > 100000 AND stage = "proposal"', + + variables: [ + { + name: 'opportunityId', + type: 'text', + required: true, + }, + ], + + steps: [ + { + id: 'get_opportunity', + type: 'record_lookup', + label: 'Get Opportunity', + + objectName: 'opportunity', + filter: { + id: '{opportunityId}', + }, + outputVariable: 'oppRecord', + }, + + { + id: 'approval_step_manager', + type: 'approval', + label: 'Sales Manager Approval', + + approver: '{oppRecord.owner.manager}', + emailTemplate: 'opportunity_approval_request', + comments: 'required', + + onApprove: 'approval_step_director', + onReject: 'notify_rejection', + }, + + { + id: 'approval_step_director', + type: 'approval', + label: 'Sales Director Approval', + + approver: '{oppRecord.owner.manager.manager}', + emailTemplate: 'opportunity_approval_request', + + onApprove: 'mark_approved', + onReject: 'notify_rejection', + }, + + { + id: 'mark_approved', + type: 'record_update', + label: 'Mark as Approved', + + recordId: '{opportunityId}', + objectName: 'opportunity', + fields: { + approval_status: 'approved', + approved_date: '{NOW()}', + }, + nextStep: 'notify_approval', + }, + + { + id: 'notify_approval', + type: 'email_alert', + label: 'Send Approval Notification', + + template: 'opportunity_approved', + recipients: ['{oppRecord.owner}'], + }, + + { + id: 'notify_rejection', + type: 'email_alert', + label: 'Send Rejection Notification', + + template: 'opportunity_rejected', + recipients: ['{oppRecord.owner}'], + }, + ], +}; + +// Case Escalation Flow +export const CaseEscalationFlow: Flow = { + name: 'case_escalation', + label: 'Case Escalation Process', + description: 'Automatically escalate high-priority cases', + type: 'autolaunched', + + triggerType: 'on_create', + objectName: 'case', + criteria: 'priority = "critical" OR (priority = "high" AND account.type = "customer")', + + variables: [ + { + name: 'caseId', + type: 'text', + required: true, + }, + ], + + steps: [ + { + id: 'get_case', + type: 'record_lookup', + label: 'Get Case Record', + + objectName: 'case', + filter: { + id: '{caseId}', + }, + outputVariable: 'caseRecord', + }, + + { + id: 'assign_senior_agent', + type: 'record_update', + label: 'Assign to Senior Agent', + + recordId: '{caseId}', + objectName: 'case', + fields: { + owner: '{caseRecord.owner.manager}', + is_escalated: true, + escalated_date: '{NOW()}', + }, + }, + + { + id: 'create_task', + type: 'record_create', + label: 'Create Follow-up Task', + + objectName: 'task', + fields: { + subject: 'Follow up on escalated case: {caseRecord.case_number}', + related_to: '{caseId}', + owner: '{caseRecord.owner}', + priority: 'high', + status: 'not_started', + due_date: '{TODAY() + 1}', + }, + }, + + { + id: 'notify_team', + type: 'email_alert', + label: 'Notify Support Team', + + template: 'case_escalated', + recipients: [ + '{caseRecord.owner}', + '{caseRecord.owner.manager}', + 'support-team@example.com', + ], + variables: { + caseNumber: '{caseRecord.case_number}', + priority: '{caseRecord.priority}', + accountName: '{caseRecord.account.name}', + }, + }, + ], +}; + +// Quote Generation Flow +export const QuoteGenerationFlow: Flow = { + name: 'quote_generation', + label: 'Generate Quote from Opportunity', + description: 'Create a quote based on opportunity details', + type: 'screen', + + triggerType: 'manual', + objectName: 'opportunity', + + variables: [ + { + name: 'opportunityId', + type: 'text', + required: true, + }, + { + name: 'quoteName', + type: 'text', + }, + { + name: 'expirationDays', + type: 'number', + defaultValue: 30, + }, + { + name: 'discount', + type: 'percent', + defaultValue: 0, + }, + ], + + steps: [ + { + id: 'screen_1', + type: 'screen', + label: 'Quote Details', + + fields: [ + { + name: 'quoteName', + label: 'Quote Name', + type: 'text', + required: true, + }, + { + name: 'expirationDays', + label: 'Valid For (Days)', + type: 'number', + required: true, + defaultValue: 30, + }, + { + name: 'discount', + label: 'Discount %', + type: 'percent', + defaultValue: 0, + }, + ], + }, + + { + id: 'get_opportunity', + type: 'record_lookup', + label: 'Get Opportunity', + + objectName: 'opportunity', + filter: { + id: '{opportunityId}', + }, + outputVariable: 'oppRecord', + }, + + { + id: 'create_quote', + type: 'record_create', + label: 'Create Quote', + + objectName: 'quote', + fields: { + name: '{quoteName}', + opportunity: '{opportunityId}', + account: '{oppRecord.account}', + contact: '{oppRecord.contact}', + owner: '{$User.Id}', + status: 'draft', + quote_date: '{TODAY()}', + expiration_date: '{TODAY() + expirationDays}', + subtotal: '{oppRecord.amount}', + discount: '{discount}', + discount_amount: '{oppRecord.amount * (discount / 100)}', + total_price: '{oppRecord.amount * (1 - discount / 100)}', + payment_terms: 'net_30', + }, + outputVariable: 'quoteId', + }, + + { + id: 'update_opportunity', + type: 'record_update', + label: 'Update Opportunity', + + recordId: '{opportunityId}', + objectName: 'opportunity', + fields: { + stage: 'proposal', + last_activity_date: '{TODAY()}', + }, + }, + + { + id: 'notify_owner', + type: 'email_alert', + label: 'Send Notification', + + template: 'quote_created', + recipients: ['{$User.Email}'], + variables: { + quoteName: '{quoteName}', + quoteId: '{quoteId}', + }, + }, + ], +}; + +// Campaign Member Enrollment Flow +export const CampaignEnrollmentFlow: Flow = { + name: 'campaign_enrollment', + label: 'Enroll Leads in Campaign', + description: 'Bulk enroll leads into marketing campaigns', + type: 'autolaunched', + + triggerType: 'scheduled', + schedule: '0 9 * * 1', // Monday at 9am + + variables: [ + { + name: 'campaignId', + type: 'text', + required: true, + }, + { + name: 'leadStatus', + type: 'text', + defaultValue: 'new', + }, + ], + + steps: [ + { + id: 'get_campaign', + type: 'record_lookup', + label: 'Get Campaign', + + objectName: 'campaign', + filter: { + id: '{campaignId}', + }, + outputVariable: 'campaignRecord', + }, + + { + id: 'query_leads', + type: 'record_query', + label: 'Find Eligible Leads', + + objectName: 'lead', + filter: { + status: '{leadStatus}', + is_converted: false, + email: { $ne: null }, + }, + limit: 1000, + outputVariable: 'leadList', + }, + + { + id: 'loop_leads', + type: 'loop', + label: 'Process Each Lead', + + collection: '{leadList}', + itemVariable: 'currentLead', + + steps: [ + { + id: 'create_campaign_member', + type: 'record_create', + label: 'Add to Campaign', + + objectName: 'campaign_member', + fields: { + campaign: '{campaignId}', + lead: '{currentLead.id}', + status: 'sent', + added_date: '{NOW()}', + }, + }, + ], + }, + + { + id: 'update_campaign_stats', + type: 'record_update', + label: 'Update Campaign Stats', + + recordId: '{campaignId}', + objectName: 'campaign', + fields: { + num_sent: '{leadList.length}', + }, + }, + ], +}; + +export const CrmFlows = { + LeadConversionFlow, + OpportunityApprovalFlow, + CaseEscalationFlow, + QuoteGenerationFlow, + CampaignEnrollmentFlow, +}; diff --git a/examples/app-crm/src/domains/marketing/campaign.object.ts b/examples/app-crm/src/domains/marketing/campaign.object.ts new file mode 100644 index 0000000000..d42c1c5188 --- /dev/null +++ b/examples/app-crm/src/domains/marketing/campaign.object.ts @@ -0,0 +1,248 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +/** + * Campaign Object + * Represents marketing campaigns + */ +export const Campaign = ObjectSchema.create({ + name: 'campaign', + label: 'Campaign', + pluralLabel: 'Campaigns', + icon: 'megaphone', + description: 'Marketing campaigns and initiatives', + titleFormat: '{campaign_code} - {name}', + compactLayout: ['campaign_code', 'name', 'type', 'status', 'start_date'], + + fields: { + // AutoNumber field + campaign_code: Field.autonumber({ + label: 'Campaign Code', + format: 'CPG-{0000}', + }), + + // Basic Information + name: Field.text({ + label: 'Campaign Name', + required: true, + searchable: true, + maxLength: 255, + }), + + description: Field.markdown({ + label: 'Description', + }), + + // Type & Channel + type: Field.select({ + label: 'Campaign Type', + options: [ + { label: 'Email', value: 'email', default: true }, + { label: 'Webinar', value: 'webinar' }, + { label: 'Trade Show', value: 'trade_show' }, + { label: 'Conference', value: 'conference' }, + { label: 'Direct Mail', value: 'direct_mail' }, + { label: 'Social Media', value: 'social_media' }, + { label: 'Content Marketing', value: 'content' }, + { label: 'Partner Marketing', value: 'partner' }, + ] + }), + + channel: Field.select({ + label: 'Primary Channel', + options: [ + { label: 'Digital', value: 'digital' }, + { label: 'Social', value: 'social' }, + { label: 'Email', value: 'email' }, + { label: 'Events', value: 'events' }, + { label: 'Partner', value: 'partner' }, + ] + }), + + // Status + status: Field.select({ + label: 'Status', + options: [ + { label: 'Planning', value: 'planning', color: '#999999', default: true }, + { label: 'In Progress', value: 'in_progress', color: '#FFA500' }, + { label: 'Completed', value: 'completed', color: '#00AA00' }, + { label: 'Aborted', value: 'aborted', color: '#FF0000' }, + ], + required: true, + }), + + // Dates + start_date: Field.date({ + label: 'Start Date', + required: true, + }), + + end_date: Field.date({ + label: 'End Date', + required: true, + }), + + // Budget & ROI + budgeted_cost: Field.currency({ + label: 'Budgeted Cost', + scale: 2, + min: 0, + }), + + actual_cost: Field.currency({ + label: 'Actual Cost', + scale: 2, + min: 0, + }), + + expected_revenue: Field.currency({ + label: 'Expected Revenue', + scale: 2, + min: 0, + }), + + actual_revenue: Field.currency({ + label: 'Actual Revenue', + scale: 2, + min: 0, + readonly: true, + }), + + // Metrics + target_size: Field.number({ + label: 'Target Size', + description: 'Target number of leads/contacts', + min: 0, + }), + + num_sent: Field.number({ + label: 'Number Sent', + min: 0, + readonly: true, + }), + + num_responses: Field.number({ + label: 'Number of Responses', + min: 0, + readonly: true, + }), + + num_leads: Field.number({ + label: 'Number of Leads', + min: 0, + readonly: true, + }), + + num_converted_leads: Field.number({ + label: 'Converted Leads', + min: 0, + readonly: true, + }), + + num_opportunities: Field.number({ + label: 'Opportunities Created', + min: 0, + readonly: true, + }), + + num_won_opportunities: Field.number({ + label: 'Won Opportunities', + min: 0, + readonly: true, + }), + + // Calculated Metrics (Formula Fields) + response_rate: Field.formula({ + label: 'Response Rate %', + type: 'percent', + formula: 'IF(num_sent > 0, (num_responses / num_sent) * 100, 0)', + scale: 2, + }), + + roi: Field.formula({ + label: 'ROI %', + type: 'percent', + formula: 'IF(actual_cost > 0, ((actual_revenue - actual_cost) / actual_cost) * 100, 0)', + scale: 2, + }), + + // Relationships + parent_campaign: Field.lookup('campaign', { + label: 'Parent Campaign', + description: 'Parent campaign in hierarchy', + }), + + owner: Field.lookup('user', { + label: 'Campaign Owner', + required: true, + }), + + // Campaign Assets + landing_page_url: Field.url({ + label: 'Landing Page', + }), + + is_active: Field.boolean({ + label: 'Active', + defaultValue: true, + }), + }, + + // Database indexes + indexes: [ + { fields: ['name'], unique: false }, + { fields: ['type'], unique: false }, + { fields: ['status'], unique: false }, + { fields: ['start_date'], unique: false }, + { fields: ['owner'], unique: false }, + ], + + // Enable advanced features + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'search', 'export'], + files: true, + feeds: true, + activities: true, + trash: true, + mru: true, + }, + + // Validation Rules + validations: [ + { + name: 'end_after_start', + type: 'script', + severity: 'error', + message: 'End Date must be after Start Date', + condition: 'end_date < start_date', + }, + { + name: 'actual_cost_within_budget', + type: 'script', + severity: 'warning', + message: 'Actual Cost exceeds Budgeted Cost', + condition: 'actual_cost > budgeted_cost', + }, + ], + + // Workflow Rules + workflows: [ + { + name: 'campaign_completion_check', + objectName: 'campaign', + triggerType: 'on_read', + criteria: 'end_date < TODAY() AND status = "in_progress"', + actions: [ + { + name: 'mark_completed', + type: 'field_update', + field: 'status', + value: '"completed"', + } + ], + active: true, + } + ], +}); diff --git a/examples/app-crm/src/domains/products/product.object.ts b/examples/app-crm/src/domains/products/product.object.ts new file mode 100644 index 0000000000..8749b379ef --- /dev/null +++ b/examples/app-crm/src/domains/products/product.object.ts @@ -0,0 +1,152 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +/** + * Product Object + * Represents products/services offered by the company + */ +export const Product = ObjectSchema.create({ + name: 'product', + label: 'Product', + pluralLabel: 'Products', + icon: 'box', + description: 'Products and services offered by the company', + titleFormat: '{product_code} - {name}', + compactLayout: ['product_code', 'name', 'category', 'is_active'], + + fields: { + // AutoNumber field - Unique product identifier + product_code: Field.autonumber({ + label: 'Product Code', + format: 'PRD-{0000}', + }), + + // Basic Information + name: Field.text({ + label: 'Product Name', + required: true, + searchable: true, + maxLength: 255, + }), + + description: Field.markdown({ + label: 'Description', + }), + + // Categorization + category: Field.select({ + label: 'Category', + options: [ + { label: 'Software', value: 'software', default: true }, + { label: 'Hardware', value: 'hardware' }, + { label: 'Service', value: 'service' }, + { label: 'Subscription', value: 'subscription' }, + { label: 'Support', value: 'support' }, + ] + }), + + family: Field.select({ + label: 'Product Family', + options: [ + { label: 'Enterprise Solutions', value: 'enterprise' }, + { label: 'SMB Solutions', value: 'smb' }, + { label: 'Professional Services', value: 'services' }, + { label: 'Cloud Services', value: 'cloud' }, + ] + }), + + // Pricing + list_price: Field.currency({ + label: 'List Price', + scale: 2, + min: 0, + required: true, + }), + + cost: Field.currency({ + label: 'Cost', + scale: 2, + min: 0, + }), + + // SKU and Inventory + sku: Field.text({ + label: 'SKU', + maxLength: 50, + unique: true, + }), + + quantity_on_hand: Field.number({ + label: 'Quantity on Hand', + min: 0, + defaultValue: 0, + }), + + reorder_point: Field.number({ + label: 'Reorder Point', + min: 0, + }), + + // Status + is_active: Field.boolean({ + label: 'Active', + defaultValue: true, + }), + + is_taxable: Field.boolean({ + label: 'Taxable', + defaultValue: true, + }), + + // Relationships + product_manager: Field.lookup('user', { + label: 'Product Manager', + }), + + // Images and Assets + image_url: Field.url({ + label: 'Product Image', + }), + + datasheet_url: Field.url({ + label: 'Datasheet URL', + }), + }, + + // Database indexes + indexes: [ + { fields: ['name'], unique: false }, + { fields: ['sku'], unique: true }, + { fields: ['category'], unique: false }, + { fields: ['is_active'], unique: false }, + ], + + // Enable advanced features + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'search'], + files: true, + feeds: true, + trash: true, + mru: true, + }, + + // Validation Rules + validations: [ + { + name: 'price_positive', + type: 'script', + severity: 'error', + message: 'List Price must be positive', + condition: 'list_price < 0', + }, + { + name: 'cost_less_than_price', + type: 'script', + severity: 'warning', + message: 'Cost should be less than List Price', + condition: 'cost >= list_price', + }, + ], +}); diff --git a/examples/app-crm/src/domains/sales/account.hook.ts b/examples/app-crm/src/domains/sales/account.hook.ts new file mode 100644 index 0000000000..13e9d0033a --- /dev/null +++ b/examples/app-crm/src/domains/sales/account.hook.ts @@ -0,0 +1,30 @@ + +import { HookContext, Hook } from '@objectstack/spec/data'; + +const accountHook: Hook = { + name: 'account_protection', + object: 'account', + events: ['beforeInsert', 'beforeUpdate', 'beforeDelete'], + handler: async (ctx: HookContext) => { + const { input } = ctx; + + if (ctx.event === 'beforeInsert' || ctx.event === 'beforeUpdate') { + // Validation: Ensure website is valid format if provided + if (input.website && typeof input.website === 'string' && !input.website.startsWith('http')) { + throw new Error('Website must start with http or https'); + } + } + + if (ctx.event === 'beforeDelete') { + // Prevent deletion of 'Strategic' accounts + // Note: ctx.previous is available in beforeDelete? + // Actually, usually in beforeDelete we might need to fetch it first if not provided. + // But let's assume the engine provides 'previous' (which is the record being deleted). + if (ctx.previous && ctx.previous.type === 'Strategic') { + throw new Error('Cannot delete Strategic accounts'); + } + } + } +}; + +export default accountHook; diff --git a/examples/app-crm/src/domains/sales/account.object.ts b/examples/app-crm/src/domains/sales/account.object.ts new file mode 100644 index 0000000000..ed9c74c16a --- /dev/null +++ b/examples/app-crm/src/domains/sales/account.object.ts @@ -0,0 +1,178 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const Account = ObjectSchema.create({ + name: 'account', + label: 'Account', + pluralLabel: 'Accounts', + icon: 'building', + description: 'Companies and organizations doing business with us', + titleFormat: '{account_number} - {name}', + compactLayout: ['account_number', 'name', 'type', 'owner'], + + fields: { + // AutoNumber field - Unique account identifier + account_number: Field.autonumber({ + label: 'Account Number', + format: 'ACC-{0000}', + }), + + // Basic Information + name: Field.text({ + label: 'Account Name', + required: true, + searchable: true, + maxLength: 255, + }), + + // Select fields with custom options + type: Field.select({ + label: 'Account Type', + options: [ + { label: 'Prospect', value: 'prospect', color: '#FFA500', default: true }, + { label: 'Customer', value: 'customer', color: '#00AA00' }, + { label: 'Partner', value: 'partner', color: '#0000FF' }, + { label: 'Former Customer', value: 'former', color: '#999999' }, + ] + }), + + industry: Field.select({ + label: 'Industry', + options: [ + { label: 'Technology', value: 'technology' }, + { label: 'Finance', value: 'finance' }, + { label: 'Healthcare', value: 'healthcare' }, + { label: 'Retail', value: 'retail' }, + { label: 'Manufacturing', value: 'manufacturing' }, + { label: 'Education', value: 'education' }, + ] + }), + + // Number fields + annual_revenue: Field.currency({ + label: 'Annual Revenue', + scale: 2, + min: 0, + }), + + number_of_employees: Field.number({ + label: 'Employees', + min: 0, + }), + + // Contact Information + phone: Field.text({ + label: 'Phone', + format: 'phone', + }), + + website: Field.url({ + label: 'Website', + }), + + // Structured Address field (new field type) + billing_address: Field.address({ + label: 'Billing Address', + addressFormat: 'international', + }), + + // Office Location (new field type) + office_location: Field.location({ + label: 'Office Location', + displayMap: true, + allowGeocoding: true, + }), + + // Relationship fields + owner: Field.lookup('user', { + label: 'Account Owner', + required: true, + }), + + parent_account: Field.lookup('account', { + label: 'Parent Account', + description: 'Parent company in hierarchy', + }), + + // Rich text field + description: Field.markdown({ + label: 'Description', + }), + + // Boolean field + is_active: Field.boolean({ + label: 'Active', + defaultValue: true, + }), + + // Date field + last_activity_date: Field.date({ + label: 'Last Activity Date', + readonly: true, + }), + + // Brand color (new field type) + brand_color: Field.color({ + label: 'Brand Color', + colorFormat: 'hex', + presetColors: ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'], + }), + }, + + // Database indexes for performance + indexes: [ + { fields: ['name'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['type', 'is_active'], unique: false }, + ], + + // Enable advanced features + enable: { + trackHistory: true, // Track field changes + searchable: true, // Include in global search + apiEnabled: true, // Expose via REST/GraphQL + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'search', 'export'], // Whitelist allowed API operations + files: true, // Allow file attachments + feeds: true, // Enable activity feed/chatter (Chatter-like) + activities: true, // Enable tasks and events tracking + trash: true, // Recycle bin support + mru: true, // Track Most Recently Used + }, + + // Validation Rules + validations: [ + { + name: 'revenue_positive', + type: 'script', + severity: 'error', + message: 'Annual Revenue must be positive', + condition: 'annual_revenue < 0', + }, + { + name: 'account_name_unique', + type: 'unique', + severity: 'error', + message: 'Account name must be unique', + fields: ['name'], + caseSensitive: false, + }, + ], + + // Workflow Rules + workflows: [ + { + name: 'update_last_activity', + objectName: 'account', + triggerType: 'on_update', + criteria: 'ISCHANGED(owner) OR ISCHANGED(type)', + actions: [ + { + name: 'set_activity_date', + type: 'field_update', + field: 'last_activity_date', + value: 'TODAY()', + } + ], + active: true, + } + ], +}); \ No newline at end of file diff --git a/examples/app-crm/src/domains/sales/contact.object.ts b/examples/app-crm/src/domains/sales/contact.object.ts new file mode 100644 index 0000000000..ab2a6bd4e7 --- /dev/null +++ b/examples/app-crm/src/domains/sales/contact.object.ts @@ -0,0 +1,172 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const Contact = ObjectSchema.create({ + name: 'contact', + label: 'Contact', + pluralLabel: 'Contacts', + icon: 'user', + description: 'People associated with accounts', + + fields: { + // Name fields + salutation: Field.select(['Mr.', 'Ms.', 'Mrs.', 'Dr.', 'Prof.'], { + label: 'Salutation', + }), + first_name: Field.text({ + label: 'First Name', + required: true, + searchable: true, + }), + last_name: Field.text({ + label: 'Last Name', + required: true, + searchable: true, + }), + + // Formula field - Full name + full_name: Field.formula({ + label: 'Full Name', + expression: 'CONCAT(salutation, " ", first_name, " ", last_name)', + }), + + // Relationship: Link to Account (Master-Detail) + account: Field.masterDetail('account', { + label: 'Account', + required: true, + writeRequiresMasterRead: true, + deleteBehavior: 'cascade', // Delete contacts when account is deleted + }), + + // Contact Information + email: Field.email({ + label: 'Email', + required: true, + unique: true, + }), + + phone: Field.text({ + label: 'Phone', + format: 'phone', + }), + + mobile: Field.text({ + label: 'Mobile', + format: 'phone', + }), + + // Professional Information + title: Field.text({ + label: 'Job Title', + }), + + department: Field.select(['Executive', 'Sales', 'Marketing', 'Engineering', 'Support', 'Finance', 'HR', 'Operations'], { + label: 'Department', + }), + + // Relationship fields + reports_to: Field.lookup('contact', { + label: 'Reports To', + description: 'Direct manager/supervisor', + }), + + owner: Field.lookup('user', { + label: 'Contact Owner', + required: true, + }), + + // Mailing Address + mailing_street: Field.textarea({ label: 'Mailing Street' }), + mailing_city: Field.text({ label: 'Mailing City' }), + mailing_state: Field.text({ label: 'Mailing State/Province' }), + mailing_postal_code: Field.text({ label: 'Mailing Postal Code' }), + mailing_country: Field.text({ label: 'Mailing Country' }), + + // Additional Information + birthdate: Field.date({ + label: 'Birthdate', + }), + + lead_source: Field.select(['Web', 'Referral', 'Event', 'Partner', 'Advertisement'], { + label: 'Lead Source', + }), + + description: Field.markdown({ + label: 'Description', + }), + + // Flags + is_primary: Field.boolean({ + label: 'Primary Contact', + defaultValue: false, + description: 'Is this the main contact for the account?', + }), + + do_not_call: Field.boolean({ + label: 'Do Not Call', + defaultValue: false, + }), + + email_opt_out: Field.boolean({ + label: 'Email Opt Out', + defaultValue: false, + }), + + // Avatar field + avatar: Field.avatar({ + label: 'Profile Picture', + }), + }, + + // Enable features + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + files: true, + feeds: true, // Enable social feed, comments, and mentions + activities: true, // Enable tasks and events tracking + trash: true, + mru: true, // Track Most Recently Used + }, + + // Display configuration + titleFormat: '{full_name}', + compactLayout: ['full_name', 'email', 'account', 'phone'], + + // Validation Rules + validations: [ + { + name: 'email_required_for_opt_in', + type: 'script', + severity: 'error', + message: 'Email is required when Email Opt Out is not checked', + condition: 'email_opt_out = false AND ISBLANK(email)', + }, + { + name: 'email_unique_per_account', + type: 'unique', + severity: 'error', + message: 'Email must be unique within an account', + fields: ['email', 'account'], + caseSensitive: false, + }, + ], + + // Workflow Rules + workflows: [ + { + name: 'welcome_email', + objectName: 'contact', + triggerType: 'on_create', + active: true, + actions: [ + { + name: 'send_welcome', + type: 'email_alert', + template: 'contact_welcome', + recipients: ['{contact.email}'], + } + ], + } + ], +}); \ No newline at end of file diff --git a/examples/app-crm/src/domains/sales/contract.object.ts b/examples/app-crm/src/domains/sales/contract.object.ts new file mode 100644 index 0000000000..04a9ea9ab1 --- /dev/null +++ b/examples/app-crm/src/domains/sales/contract.object.ts @@ -0,0 +1,242 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +/** + * Contract Object + * Represents legal contracts with customers + */ +export const Contract = ObjectSchema.create({ + name: 'contract', + label: 'Contract', + pluralLabel: 'Contracts', + icon: 'file-signature', + description: 'Legal contracts and agreements', + titleFormat: '{contract_number} - {account.name}', + compactLayout: ['contract_number', 'account', 'status', 'start_date', 'end_date'], + + fields: { + // AutoNumber field + contract_number: Field.autonumber({ + label: 'Contract Number', + format: 'CTR-{0000}', + }), + + // Relationships + account: Field.lookup('account', { + label: 'Account', + required: true, + }), + + contact: Field.lookup('contact', { + label: 'Primary Contact', + required: true, + referenceFilters: { + account: '{account}', + } + }), + + opportunity: Field.lookup('opportunity', { + label: 'Related Opportunity', + referenceFilters: { + account: '{account}', + } + }), + + owner: Field.lookup('user', { + label: 'Contract Owner', + required: true, + }), + + // Status + status: Field.select({ + label: 'Status', + options: [ + { label: 'Draft', value: 'draft', color: '#999999', default: true }, + { label: 'In Approval', value: 'in_approval', color: '#FFA500' }, + { label: 'Activated', value: 'activated', color: '#00AA00' }, + { label: 'Expired', value: 'expired', color: '#FF0000' }, + { label: 'Terminated', value: 'terminated', color: '#666666' }, + ], + required: true, + }), + + // Contract Terms + contract_term_months: Field.number({ + label: 'Contract Term (Months)', + required: true, + min: 1, + }), + + start_date: Field.date({ + label: 'Start Date', + required: true, + }), + + end_date: Field.date({ + label: 'End Date', + required: true, + }), + + // Financial + contract_value: Field.currency({ + label: 'Contract Value', + scale: 2, + min: 0, + required: true, + }), + + billing_frequency: Field.select({ + label: 'Billing Frequency', + options: [ + { label: 'Monthly', value: 'monthly', default: true }, + { label: 'Quarterly', value: 'quarterly' }, + { label: 'Annually', value: 'annually' }, + { label: 'One-time', value: 'one_time' }, + ] + }), + + payment_terms: Field.select({ + label: 'Payment Terms', + options: [ + { label: 'Net 15', value: 'net_15' }, + { label: 'Net 30', value: 'net_30', default: true }, + { label: 'Net 60', value: 'net_60' }, + { label: 'Net 90', value: 'net_90' }, + ] + }), + + // Renewal + auto_renewal: Field.boolean({ + label: 'Auto Renewal', + defaultValue: false, + }), + + renewal_notice_days: Field.number({ + label: 'Renewal Notice (Days)', + min: 0, + defaultValue: 30, + }), + + // Legal + contract_type: Field.select({ + label: 'Contract Type', + options: [ + { label: 'Subscription', value: 'subscription' }, + { label: 'Service Agreement', value: 'service' }, + { label: 'License', value: 'license' }, + { label: 'Partnership', value: 'partnership' }, + { label: 'NDA', value: 'nda' }, + { label: 'MSA', value: 'msa' }, + ] + }), + + signed_date: Field.date({ + label: 'Signed Date', + }), + + signed_by: Field.text({ + label: 'Signed By', + maxLength: 255, + }), + + document_url: Field.url({ + label: 'Contract Document', + }), + + // Terms & Conditions + special_terms: Field.markdown({ + label: 'Special Terms', + }), + + description: Field.markdown({ + label: 'Description', + }), + + // Billing Address + billing_address: Field.address({ + label: 'Billing Address', + addressFormat: 'international', + }), + }, + + // Database indexes + indexes: [ + { fields: ['account'], unique: false }, + { fields: ['status'], unique: false }, + { fields: ['start_date'], unique: false }, + { fields: ['end_date'], unique: false }, + { fields: ['owner'], unique: false }, + ], + + // Enable advanced features + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'search', 'export'], + files: true, + feeds: true, + activities: true, + trash: true, + mru: true, + }, + + // Validation Rules + validations: [ + { + name: 'end_after_start', + type: 'script', + severity: 'error', + message: 'End Date must be after Start Date', + condition: 'end_date <= start_date', + }, + { + name: 'valid_contract_term', + type: 'script', + severity: 'error', + message: 'Contract Term must match date range', + condition: 'MONTH_DIFF(end_date, start_date) != contract_term_months', + }, + ], + + // Workflow Rules + workflows: [ + { + name: 'contract_expiration_check', + objectName: 'contract', + triggerType: 'scheduled', + schedule: '0 0 * * *', // Daily at midnight + criteria: 'end_date <= TODAY() AND status = "activated"', + actions: [ + { + name: 'mark_expired', + type: 'field_update', + field: 'status', + value: '"expired"', + }, + { + name: 'notify_owner', + type: 'email_alert', + template: 'contract_expired', + recipients: ['{owner}'], + } + ], + active: true, + }, + { + name: 'renewal_reminder', + objectName: 'contract', + triggerType: 'scheduled', + schedule: '0 0 * * *', // Daily at midnight + criteria: 'DAYS_UNTIL(end_date) <= renewal_notice_days AND status = "activated"', + actions: [ + { + name: 'notify_renewal', + type: 'email_alert', + template: 'contract_renewal_reminder', + recipients: ['{owner}', '{account.owner}'], + } + ], + active: true, + } + ], +}); diff --git a/examples/app-crm/src/domains/sales/lead.hook.ts b/examples/app-crm/src/domains/sales/lead.hook.ts new file mode 100644 index 0000000000..cd9e70f438 --- /dev/null +++ b/examples/app-crm/src/domains/sales/lead.hook.ts @@ -0,0 +1,32 @@ + +import { HookContext, Hook } from '@objectstack/spec/data'; + +const leadHook: Hook = { + name: 'lead_automation', + object: 'lead', + events: ['beforeInsert', 'afterUpdate'], + handler: async (ctx: HookContext) => { + if (ctx.event === 'beforeInsert') { + const { input } = ctx; + // Auto-score logic (mock) + let score = 0; + if (input.email && typeof input.email === 'string' && input.email.endsWith('@enterprise.com')) { + score += 50; + } + if (input.phone) { + score += 20; + } + input.score = score; + } + + if (ctx.event === 'afterUpdate') { + const { input, previous } = ctx; + // Detect status change to 'qualified' + if (input.status === 'qualified' && previous && previous.status !== 'qualified') { + console.log('Lead qualified! Ready for conversion.'); + } + } + } +}; + +export default leadHook; diff --git a/examples/app-crm/src/domains/sales/lead.object.ts b/examples/app-crm/src/domains/sales/lead.object.ts new file mode 100644 index 0000000000..e4f180ca3e --- /dev/null +++ b/examples/app-crm/src/domains/sales/lead.object.ts @@ -0,0 +1,236 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const Lead = ObjectSchema.create({ + name: 'lead', + label: 'Lead', + pluralLabel: 'Leads', + icon: 'user-plus', + description: 'Potential customers not yet qualified', + + fields: { + // Personal Information + salutation: Field.select(['Mr.', 'Ms.', 'Mrs.', 'Dr.'], { + label: 'Salutation', + }), + + first_name: Field.text({ + label: 'First Name', + required: true, + searchable: true, + }), + + last_name: Field.text({ + label: 'Last Name', + required: true, + searchable: true, + }), + + full_name: Field.formula({ + label: 'Full Name', + expression: 'CONCAT(salutation, " ", first_name, " ", last_name)', + }), + + // Company Information + company: Field.text({ + label: 'Company', + required: true, + searchable: true, + }), + + title: Field.text({ + label: 'Job Title', + }), + + industry: Field.select(['Technology', 'Finance', 'Healthcare', 'Retail', 'Manufacturing', 'Education'], { + label: 'Industry', + }), + + // Contact Information + email: Field.email({ + label: 'Email', + required: true, + unique: true, + }), + + phone: Field.text({ + label: 'Phone', + format: 'phone', + }), + + mobile: Field.text({ + label: 'Mobile', + format: 'phone', + }), + + website: Field.url({ + label: 'Website', + }), + + // Lead Qualification + status: Field.select({ + label: 'Lead Status', + required: true, + options: [ + { label: 'New', value: 'new', color: '#808080', default: true }, + { label: 'Contacted', value: 'contacted', color: '#FFA500' }, + { label: 'Qualified', value: 'qualified', color: '#4169E1' }, + { label: 'Unqualified', value: 'unqualified', color: '#FF0000' }, + { label: 'Converted', value: 'converted', color: '#00AA00' }, + ] + }), + + rating: Field.rating(5, { + label: 'Lead Score', + description: 'Lead quality score (1-5 stars)', + allowHalf: true, + }), + + lead_source: Field.select(['Web', 'Referral', 'Event', 'Partner', 'Advertisement', 'Cold Call'], { + label: 'Lead Source', + }), + + // Assignment + owner: Field.lookup('user', { + label: 'Lead Owner', + required: true, + }), + + // Conversion tracking + is_converted: Field.boolean({ + label: 'Converted', + defaultValue: false, + readonly: true, + }), + + converted_account: Field.lookup('account', { + label: 'Converted Account', + readonly: true, + }), + + converted_contact: Field.lookup('contact', { + label: 'Converted Contact', + readonly: true, + }), + + converted_opportunity: Field.lookup('opportunity', { + label: 'Converted Opportunity', + readonly: true, + }), + + converted_date: Field.datetime({ + label: 'Converted Date', + readonly: true, + }), + + // Address (using new address field type) + address: Field.address({ + label: 'Address', + addressFormat: 'international', + }), + + // Additional Info + annual_revenue: Field.currency({ + label: 'Annual Revenue', + scale: 2, + }), + + number_of_employees: Field.number({ + label: 'Number of Employees', + }), + + description: Field.markdown({ + label: 'Description', + }), + + // Custom notes with rich text formatting + notes: Field.richtext({ + label: 'Notes', + description: 'Rich text notes with formatting', + }), + + // Flags + do_not_call: Field.boolean({ + label: 'Do Not Call', + defaultValue: false, + }), + + email_opt_out: Field.boolean({ + label: 'Email Opt Out', + defaultValue: false, + }), + }, + + // Database indexes for performance + indexes: [ + { fields: ['email'], unique: true }, + { fields: ['owner'], unique: false }, + { fields: ['status'], unique: false }, + { fields: ['company'], unique: false }, + ], + + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + files: true, + feeds: true, // Enable social feed, comments, and mentions + activities: true, // Enable tasks and events tracking + trash: true, + mru: true, // Track Most Recently Used + }, + + titleFormat: '{full_name} - {company}', + compactLayout: ['full_name', 'company', 'email', 'status', 'owner'], + + // Removed: list_views and form_views belong in UI configuration, not object definition + + validations: [ + { + name: 'email_required', + type: 'script', + severity: 'error', + message: 'Email is required', + condition: 'ISBLANK(email)', + }, + { + name: 'cannot_edit_converted', + type: 'script', + severity: 'error', + message: 'Cannot edit a converted lead', + condition: 'is_converted = true AND ISCHANGED(company, email, first_name, last_name)', + }, + ], + + workflows: [ + { + name: 'auto_qualify_high_score_leads', + objectName: 'lead', + triggerType: 'on_create_or_update', + criteria: 'rating >= 4 AND status = "new"', + active: true, + actions: [ + { + name: 'set_status', + type: 'field_update', + field: 'status', + value: 'contacted', + } + ], + }, + { + name: 'notify_owner_on_high_score_lead', + objectName: 'lead', + triggerType: 'on_create_or_update', + criteria: 'ISCHANGED(rating) AND rating >= 4.5', + active: true, + actions: [ + { + name: 'email_owner', + type: 'email_alert', + template: 'high_score_lead_notification', + recipients: ['{owner.email}'], + } + ], + } + ], +}); diff --git a/examples/app-crm/src/domains/sales/opportunity.object.ts b/examples/app-crm/src/domains/sales/opportunity.object.ts new file mode 100644 index 0000000000..6385c832e0 --- /dev/null +++ b/examples/app-crm/src/domains/sales/opportunity.object.ts @@ -0,0 +1,260 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const Opportunity = ObjectSchema.create({ + name: 'opportunity', + label: 'Opportunity', + pluralLabel: 'Opportunities', + icon: 'dollar-sign', + description: 'Sales opportunities and deals in the pipeline', + titleFormat: '{name} - {stage}', + compactLayout: ['name', 'account', 'amount', 'stage', 'owner'], + + fields: { + // Basic Information + name: Field.text({ + label: 'Opportunity Name', + required: true, + searchable: true, + }), + + // Relationships + account: Field.lookup('account', { + label: 'Account', + required: true, + }), + + primary_contact: Field.lookup('contact', { + label: 'Primary Contact', + referenceFilters: ['account = {opportunity.account}'], // Filter contacts by account + }), + + owner: Field.lookup('user', { + label: 'Opportunity Owner', + required: true, + }), + + // Financial Information + amount: Field.currency({ + label: 'Amount', + required: true, + scale: 2, + min: 0, + }), + + expected_revenue: Field.currency({ + label: 'Expected Revenue', + scale: 2, + readonly: true, // Calculated field + }), + + // Sales Process + stage: Field.select({ + label: 'Stage', + required: true, + options: [ + { label: 'Prospecting', value: 'prospecting', color: '#808080', default: true }, + { label: 'Qualification', value: 'qualification', color: '#FFA500' }, + { label: 'Needs Analysis', value: 'needs_analysis', color: '#FFD700' }, + { label: 'Proposal', value: 'proposal', color: '#4169E1' }, + { label: 'Negotiation', value: 'negotiation', color: '#9370DB' }, + { label: 'Closed Won', value: 'closed_won', color: '#00AA00' }, + { label: 'Closed Lost', value: 'closed_lost', color: '#FF0000' }, + ] + }), + + probability: Field.percent({ + label: 'Probability (%)', + min: 0, + max: 100, + defaultValue: 10, + }), + + // Important Dates + close_date: Field.date({ + label: 'Close Date', + required: true, + }), + + created_date: Field.datetime({ + label: 'Created Date', + readonly: true, + }), + + // Additional Classification + type: Field.select(['New Business', 'Existing Customer - Upgrade', 'Existing Customer - Renewal', 'Existing Customer - Expansion'], { + label: 'Opportunity Type', + }), + + lead_source: Field.select(['Web', 'Referral', 'Event', 'Partner', 'Advertisement', 'Cold Call'], { + label: 'Lead Source', + }), + + // Competitor Analysis + competitors: Field.select(['Competitor A', 'Competitor B', 'Competitor C'], { + label: 'Competitors', + multiple: true, + }), + + // Campaign tracking + campaign: Field.lookup('campaign', { + label: 'Campaign', + description: 'Marketing campaign that generated this opportunity', + }), + + // Sales cycle metrics + days_in_stage: Field.number({ + label: 'Days in Current Stage', + readonly: true, + }), + + // Additional information + description: Field.markdown({ + label: 'Description', + }), + + next_step: Field.textarea({ + label: 'Next Steps', + }), + + // Flags + is_private: Field.boolean({ + label: 'Private', + defaultValue: false, + }), + + forecast_category: Field.select(['Pipeline', 'Best Case', 'Commit', 'Omitted', 'Closed'], { + label: 'Forecast Category', + }), + }, + + // Database indexes for performance + indexes: [ + { fields: ['name'], unique: false }, + { fields: ['account'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['stage'], unique: false }, + { fields: ['close_date'], unique: false }, + ], + + // Enable advanced features + enable: { + trackHistory: true, // Critical for tracking stage changes + searchable: true, + apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'aggregate', 'search'], // Whitelist allowed API operations + files: true, // Attach proposals, contracts + feeds: true, // Team collaboration (Chatter-like) + activities: true, // Enable tasks and events tracking + trash: true, + mru: true, // Track Most Recently Used + }, + + // Removed: list_views and form_views belong in UI configuration, not object definition + + // Validation Rules + validations: [ + { + name: 'close_date_future', + type: 'script', + severity: 'warning', + message: 'Close date should not be in the past unless opportunity is closed', + condition: 'close_date < TODAY() AND stage != "closed_won" AND stage != "closed_lost"', + }, + { + name: 'amount_positive', + type: 'script', + severity: 'error', + message: 'Amount must be greater than zero', + condition: 'amount <= 0', + }, + { + name: 'stage_progression', + type: 'state_machine', + severity: 'error', + message: 'Invalid stage transition', + field: 'stage', + transitions: { + 'prospecting': ['qualification', 'closed_lost'], + 'qualification': ['needs_analysis', 'closed_lost'], + 'needs_analysis': ['proposal', 'closed_lost'], + 'proposal': ['negotiation', 'closed_lost'], + 'negotiation': ['closed_won', 'closed_lost'], + 'closed_won': [], // Terminal state + 'closed_lost': [] // Terminal state + } + }, + ], + + // Workflow Rules + workflows: [ + { + name: 'update_probability_by_stage', + objectName: 'opportunity', + triggerType: 'on_create_or_update', + criteria: 'ISCHANGED(stage)', + active: true, + actions: [ + { + name: 'set_probability', + type: 'field_update', + field: 'probability', + value: `CASE(stage, + "prospecting", 10, + "qualification", 25, + "needs_analysis", 40, + "proposal", 60, + "negotiation", 80, + "closed_won", 100, + "closed_lost", 0, + probability + )`, + }, + { + name: 'set_forecast_category', + type: 'field_update', + field: 'forecast_category', + value: `CASE(stage, + "prospecting", "pipeline", + "qualification", "pipeline", + "needs_analysis", "best_case", + "proposal", "commit", + "negotiation", "commit", + "closed_won", "closed", + "closed_lost", "omitted", + forecast_category + )`, + } + ], + }, + { + name: 'calculate_expected_revenue', + objectName: 'opportunity', + triggerType: 'on_create_or_update', + criteria: 'ISCHANGED(amount) OR ISCHANGED(probability)', + active: true, + actions: [ + { + name: 'update_expected_revenue', + type: 'field_update', + field: 'expected_revenue', + value: 'amount * (probability / 100)', + } + ], + }, + { + name: 'notify_on_large_deal_won', + objectName: 'opportunity', + triggerType: 'on_update', + criteria: 'ISCHANGED(stage) AND stage = "closed_won" AND amount > 100000', + active: true, + actions: [ + { + name: 'notify_management', + type: 'email_alert', + template: 'large_deal_won', + recipients: ['sales_management@example.com'], + } + ], + } + ], +}); \ No newline at end of file diff --git a/examples/app-crm/src/domains/sales/quote.object.ts b/examples/app-crm/src/domains/sales/quote.object.ts new file mode 100644 index 0000000000..3ab54fb95b --- /dev/null +++ b/examples/app-crm/src/domains/sales/quote.object.ts @@ -0,0 +1,215 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +/** + * Quote Object + * Represents price quotes sent to customers + */ +export const Quote = ObjectSchema.create({ + name: 'quote', + label: 'Quote', + pluralLabel: 'Quotes', + icon: 'file-text', + description: 'Price quotes for customers', + titleFormat: '{quote_number} - {name}', + compactLayout: ['quote_number', 'name', 'account', 'status', 'total_price'], + + fields: { + // AutoNumber field + quote_number: Field.autonumber({ + label: 'Quote Number', + format: 'QTE-{0000}', + }), + + // Basic Information + name: Field.text({ + label: 'Quote Name', + required: true, + searchable: true, + maxLength: 255, + }), + + // Relationships + account: Field.lookup('account', { + label: 'Account', + required: true, + }), + + contact: Field.lookup('contact', { + label: 'Contact', + required: true, + referenceFilters: { + account: '{account}', + } + }), + + opportunity: Field.lookup('opportunity', { + label: 'Opportunity', + referenceFilters: { + account: '{account}', + } + }), + + owner: Field.lookup('user', { + label: 'Quote Owner', + required: true, + }), + + // Status + status: Field.select({ + label: 'Status', + options: [ + { label: 'Draft', value: 'draft', color: '#999999', default: true }, + { label: 'In Review', value: 'in_review', color: '#FFA500' }, + { label: 'Presented', value: 'presented', color: '#4169E1' }, + { label: 'Accepted', value: 'accepted', color: '#00AA00' }, + { label: 'Rejected', value: 'rejected', color: '#FF0000' }, + { label: 'Expired', value: 'expired', color: '#666666' }, + ], + required: true, + }), + + // Dates + quote_date: Field.date({ + label: 'Quote Date', + required: true, + defaultValue: 'TODAY()', + }), + + expiration_date: Field.date({ + label: 'Expiration Date', + required: true, + }), + + // Pricing + subtotal: Field.currency({ + label: 'Subtotal', + scale: 2, + readonly: true, + }), + + discount: Field.percent({ + label: 'Discount %', + scale: 2, + min: 0, + max: 100, + }), + + discount_amount: Field.currency({ + label: 'Discount Amount', + scale: 2, + readonly: true, + }), + + tax: Field.currency({ + label: 'Tax', + scale: 2, + }), + + shipping_handling: Field.currency({ + label: 'Shipping & Handling', + scale: 2, + }), + + total_price: Field.currency({ + label: 'Total Price', + scale: 2, + readonly: true, + }), + + // Terms + payment_terms: Field.select({ + label: 'Payment Terms', + options: [ + { label: 'Net 15', value: 'net_15' }, + { label: 'Net 30', value: 'net_30', default: true }, + { label: 'Net 60', value: 'net_60' }, + { label: 'Net 90', value: 'net_90' }, + { label: 'Due on Receipt', value: 'due_on_receipt' }, + ] + }), + + shipping_terms: Field.text({ + label: 'Shipping Terms', + maxLength: 255, + }), + + // Billing & Shipping Address + billing_address: Field.address({ + label: 'Billing Address', + addressFormat: 'international', + }), + + shipping_address: Field.address({ + label: 'Shipping Address', + addressFormat: 'international', + }), + + // Notes + description: Field.markdown({ + label: 'Description', + }), + + internal_notes: Field.textarea({ + label: 'Internal Notes', + }), + }, + + // Database indexes + indexes: [ + { fields: ['account'], unique: false }, + { fields: ['opportunity'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['status'], unique: false }, + { fields: ['quote_date'], unique: false }, + ], + + // Enable advanced features + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'search', 'export'], + files: true, + feeds: true, + activities: true, + trash: true, + mru: true, + }, + + // Validation Rules + validations: [ + { + name: 'expiration_after_quote', + type: 'script', + severity: 'error', + message: 'Expiration Date must be after Quote Date', + condition: 'expiration_date <= quote_date', + }, + { + name: 'valid_discount', + type: 'script', + severity: 'error', + message: 'Discount cannot exceed 100%', + condition: 'discount > 100', + }, + ], + + // Workflow Rules + workflows: [ + { + name: 'quote_expired_check', + objectName: 'quote', + triggerType: 'on_read', + criteria: 'expiration_date < TODAY() AND status NOT IN ("accepted", "rejected", "expired")', + actions: [ + { + name: 'mark_expired', + type: 'field_update', + field: 'status', + value: '"expired"', + } + ], + active: true, + } + ], +}); diff --git a/examples/app-crm/src/domains/service/case.object.ts b/examples/app-crm/src/domains/service/case.object.ts new file mode 100644 index 0000000000..788cfefa0f --- /dev/null +++ b/examples/app-crm/src/domains/service/case.object.ts @@ -0,0 +1,298 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const Case = ObjectSchema.create({ + name: 'case', + label: 'Case', + pluralLabel: 'Cases', + icon: 'life-buoy', + description: 'Customer support cases and service requests', + + fields: { + // Case Information + case_number: Field.autonumber({ + label: 'Case Number', + format: 'CASE-{00000}', + }), + + subject: Field.text({ + label: 'Subject', + required: true, + searchable: true, + maxLength: 255, + }), + + description: Field.markdown({ + label: 'Description', + required: true, + }), + + // Relationships + account: Field.lookup('account', { + label: 'Account', + }), + + contact: Field.lookup('contact', { + label: 'Contact', + required: true, + referenceFilters: ['account = {case.account}'], + }), + + // Case Management + status: Field.select({ + label: 'Status', + required: true, + options: [ + { label: 'New', value: 'new', color: '#808080', default: true }, + { label: 'In Progress', value: 'in_progress', color: '#FFA500' }, + { label: 'Waiting on Customer', value: 'waiting_customer', color: '#FFD700' }, + { label: 'Waiting on Support', value: 'waiting_support', color: '#4169E1' }, + { label: 'Escalated', value: 'escalated', color: '#FF0000' }, + { label: 'Resolved', value: 'resolved', color: '#00AA00' }, + { label: 'Closed', value: 'closed', color: '#006400' }, + ] + }), + + priority: Field.select({ + label: 'Priority', + required: true, + options: [ + { label: 'Low', value: 'low', color: '#4169E1', default: true }, + { label: 'Medium', value: 'medium', color: '#FFA500' }, + { label: 'High', value: 'high', color: '#FF4500' }, + { label: 'Critical', value: 'critical', color: '#FF0000' }, + ] + }), + + type: Field.select(['Question', 'Problem', 'Feature Request', 'Bug'], { + label: 'Case Type', + }), + + origin: Field.select(['Email', 'Phone', 'Web', 'Chat', 'Social Media'], { + label: 'Case Origin', + }), + + // Assignment + owner: Field.lookup('user', { + label: 'Case Owner', + required: true, + }), + + // SLA and Metrics + created_date: Field.datetime({ + label: 'Created Date', + readonly: true, + }), + + closed_date: Field.datetime({ + label: 'Closed Date', + readonly: true, + }), + + first_response_date: Field.datetime({ + label: 'First Response Date', + readonly: true, + }), + + resolution_time_hours: Field.number({ + label: 'Resolution Time (Hours)', + readonly: true, + scale: 2, + }), + + sla_due_date: Field.datetime({ + label: 'SLA Due Date', + }), + + is_sla_violated: Field.boolean({ + label: 'SLA Violated', + defaultValue: false, + readonly: true, + }), + + // Escalation + is_escalated: Field.boolean({ + label: 'Escalated', + defaultValue: false, + }), + + escalation_reason: Field.textarea({ + label: 'Escalation Reason', + }), + + // Related case + parent_case: Field.lookup('case', { + label: 'Parent Case', + description: 'Related parent case', + }), + + // Resolution + resolution: Field.markdown({ + label: 'Resolution', + }), + + // Customer satisfaction + customer_rating: Field.rating(5, { + label: 'Customer Satisfaction', + description: 'Customer satisfaction rating (1-5 stars)', + }), + + customer_feedback: Field.textarea({ + label: 'Customer Feedback', + }), + + // Customer signature (for case resolution acknowledgment) + customer_signature: Field.signature({ + label: 'Customer Signature', + description: 'Digital signature acknowledging case resolution', + }), + + // Internal notes + internal_notes: Field.markdown({ + label: 'Internal Notes', + description: 'Internal notes not visible to customer', + }), + + // Flags + is_closed: Field.boolean({ + label: 'Is Closed', + defaultValue: false, + readonly: true, + }), + }, + + // Database indexes for performance + indexes: [ + { fields: ['case_number'], unique: true }, + { fields: ['account'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['status'], unique: false }, + { fields: ['priority'], unique: false }, + ], + + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + files: true, + feeds: true, // Enable social feed, comments, and mentions + activities: true, // Enable tasks and events tracking + trash: true, + mru: true, // Track Most Recently Used + }, + + titleFormat: '{case_number} - {subject}', + compactLayout: ['case_number', 'subject', 'account', 'status', 'priority'], + + // Removed: list_views and form_views belong in UI configuration, not object definition + + validations: [ + { + name: 'resolution_required_for_closed', + type: 'script', + severity: 'error', + message: 'Resolution is required when closing a case', + condition: 'status = "closed" AND ISBLANK(resolution)', + }, + { + name: 'escalation_reason_required', + type: 'script', + severity: 'error', + message: 'Escalation reason is required when escalating a case', + condition: 'is_escalated = true AND ISBLANK(escalation_reason)', + }, + { + name: 'case_status_progression', + type: 'state_machine', + severity: 'warning', + message: 'Invalid status transition', + field: 'status', + transitions: { + 'new': ['in_progress', 'waiting_customer', 'closed'], + 'in_progress': ['waiting_customer', 'waiting_support', 'escalated', 'resolved'], + 'waiting_customer': ['in_progress', 'closed'], + 'waiting_support': ['in_progress', 'escalated'], + 'escalated': ['in_progress', 'resolved'], + 'resolved': ['closed', 'in_progress'], // Can reopen + 'closed': ['in_progress'], // Can reopen + } + }, + ], + + workflows: [ + { + name: 'set_closed_flag', + objectName: 'case', + triggerType: 'on_create_or_update', + criteria: 'ISCHANGED(status)', + active: true, + actions: [ + { + name: 'update_closed_flag', + type: 'field_update', + field: 'is_closed', + value: 'status = "closed"', + } + ], + }, + { + name: 'set_closed_date', + objectName: 'case', + triggerType: 'on_update', + criteria: 'ISCHANGED(status) AND status = "closed"', + active: true, + actions: [ + { + name: 'set_date', + type: 'field_update', + field: 'closed_date', + value: 'NOW()', + } + ], + }, + { + name: 'calculate_resolution_time', + objectName: 'case', + triggerType: 'on_update', + criteria: 'ISCHANGED(closed_date) AND NOT(ISBLANK(closed_date))', + active: true, + actions: [ + { + name: 'calc_time', + type: 'field_update', + field: 'resolution_time_hours', + value: 'HOURS(created_date, closed_date)', + } + ], + }, + { + name: 'notify_on_critical', + objectName: 'case', + triggerType: 'on_create_or_update', + criteria: 'priority = "critical"', + active: true, + actions: [ + { + name: 'email_support_manager', + type: 'email_alert', + template: 'critical_case_alert', + recipients: ['support_manager@example.com'], + } + ], + }, + { + name: 'notify_on_escalation', + objectName: 'case', + triggerType: 'on_update', + criteria: 'ISCHANGED(is_escalated) AND is_escalated = true', + active: true, + actions: [ + { + name: 'email_escalation_team', + type: 'email_alert', + template: 'case_escalation_alert', + recipients: ['escalation_team@example.com'], + } + ], + }, + ], +}); diff --git a/examples/app-crm/src/domains/service/task.object.ts b/examples/app-crm/src/domains/service/task.object.ts new file mode 100644 index 0000000000..fcf0368ee5 --- /dev/null +++ b/examples/app-crm/src/domains/service/task.object.ts @@ -0,0 +1,261 @@ +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const Task = ObjectSchema.create({ + name: 'task', + label: 'Task', + pluralLabel: 'Tasks', + icon: 'check-square', + description: 'Activities and to-do items', + + fields: { + // Task Information + subject: Field.text({ + label: 'Subject', + required: true, + searchable: true, + maxLength: 255, + }), + + description: Field.markdown({ + label: 'Description', + }), + + // Task Management + status: { + type: 'select', + label: 'Status', + required: true, + options: [ + { label: 'Not Started', value: 'not_started', color: '#808080', default: true }, + { label: 'In Progress', value: 'in_progress', color: '#FFA500' }, + { label: 'Waiting', value: 'waiting', color: '#FFD700' }, + { label: 'Completed', value: 'completed', color: '#00AA00' }, + { label: 'Deferred', value: 'deferred', color: '#999999' }, + ] + }, + + priority: { + type: 'select', + label: 'Priority', + required: true, + options: [ + { label: 'Low', value: 'low', color: '#4169E1', default: true }, + { label: 'Normal', value: 'normal', color: '#00AA00' }, + { label: 'High', value: 'high', color: '#FFA500' }, + { label: 'Urgent', value: 'urgent', color: '#FF0000' }, + ] + }, + + type: Field.select(['Call', 'Email', 'Meeting', 'Follow-up', 'Demo', 'Other'], { + label: 'Task Type', + }), + + // Dates + due_date: Field.date({ + label: 'Due Date', + }), + + reminder_date: Field.datetime({ + label: 'Reminder Date/Time', + }), + + completed_date: Field.datetime({ + label: 'Completed Date', + readonly: true, + }), + + // Assignment + owner: Field.lookup('user', { + label: 'Assigned To', + required: true, + }), + + // Related To (Polymorphic relationship - can link to multiple object types) + related_to_type: Field.select(['Account', 'Contact', 'Opportunity', 'Lead', 'Case'], { + label: 'Related To Type', + }), + + related_to_account: Field.lookup('account', { + label: 'Related Account', + }), + + related_to_contact: Field.lookup('contact', { + label: 'Related Contact', + }), + + related_to_opportunity: Field.lookup('opportunity', { + label: 'Related Opportunity', + }), + + related_to_lead: Field.lookup('lead', { + label: 'Related Lead', + }), + + related_to_case: Field.lookup('case', { + label: 'Related Case', + }), + + // Recurrence (for recurring tasks) + is_recurring: Field.boolean({ + label: 'Recurring Task', + defaultValue: false, + }), + + recurrence_type: Field.select(['Daily', 'Weekly', 'Monthly', 'Yearly'], { + label: 'Recurrence Type', + }), + + recurrence_interval: Field.number({ + label: 'Recurrence Interval', + defaultValue: 1, + min: 1, + }), + + recurrence_end_date: Field.date({ + label: 'Recurrence End Date', + }), + + // Flags + is_completed: Field.boolean({ + label: 'Is Completed', + defaultValue: false, + readonly: true, + }), + + is_overdue: Field.boolean({ + label: 'Is Overdue', + defaultValue: false, + readonly: true, + }), + + // Progress + progress_percent: Field.percent({ + label: 'Progress (%)', + min: 0, + max: 100, + defaultValue: 0, + }), + + // Time tracking + estimated_hours: Field.number({ + label: 'Estimated Hours', + scale: 2, + min: 0, + }), + + actual_hours: Field.number({ + label: 'Actual Hours', + scale: 2, + min: 0, + }), + }, + + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + files: true, + feeds: true, // Enable social feed, comments, and mentions + activities: true, // Enable tasks and events tracking + trash: true, + mru: true, // Track Most Recently Used + }, + + titleFormat: '{subject}', + compactLayout: ['subject', 'status', 'priority', 'due_date', 'owner'], + + // Removed: list_views and form_views belong in UI configuration, not object definition + + validations: [ + { + name: 'completed_date_required', + type: 'script', + severity: 'error', + message: 'Completed date is required when status is Completed', + condition: 'status = "completed" AND ISBLANK(completed_date)', + }, + { + name: 'recurrence_fields_required', + type: 'script', + severity: 'error', + message: 'Recurrence type is required for recurring tasks', + condition: 'is_recurring = true AND ISBLANK(recurrence_type)', + }, + { + name: 'related_to_required', + type: 'script', + severity: 'warning', + message: 'At least one related record should be selected', + condition: 'ISBLANK(related_to_account) AND ISBLANK(related_to_contact) AND ISBLANK(related_to_opportunity) AND ISBLANK(related_to_lead) AND ISBLANK(related_to_case)', + }, + ], + + workflows: [ + { + name: 'set_completed_flag', + objectName: 'task', + triggerType: 'on_create_or_update', + criteria: 'ISCHANGED(status)', + active: true, + actions: [ + { + name: 'update_completed_flag', + type: 'field_update', + field: 'is_completed', + value: 'status = "completed"', + } + ], + }, + { + name: 'set_completed_date', + objectName: 'task', + triggerType: 'on_update', + criteria: 'ISCHANGED(status) AND status = "completed"', + active: true, + actions: [ + { + name: 'set_date', + type: 'field_update', + field: 'completed_date', + value: 'NOW()', + }, + { + name: 'set_progress', + type: 'field_update', + field: 'progress_percent', + value: '100', + } + ], + }, + { + name: 'check_overdue', + objectName: 'task', + triggerType: 'on_create_or_update', + criteria: 'due_date < TODAY() AND is_completed = false', + active: true, + actions: [ + { + name: 'set_overdue_flag', + type: 'field_update', + field: 'is_overdue', + value: 'true', + } + ], + }, + { + name: 'notify_on_urgent', + objectName: 'task', + triggerType: 'on_create_or_update', + criteria: 'priority = "urgent" AND is_completed = false', + active: true, + actions: [ + { + name: 'email_owner', + type: 'email_alert', + template: 'urgent_task_alert', + recipients: ['{owner.email}'], + } + ], + }, + ], +}); diff --git a/examples/app-crm/src/security/profiles.ts b/examples/app-crm/src/security/profiles.ts new file mode 100644 index 0000000000..3f982a7f1d --- /dev/null +++ b/examples/app-crm/src/security/profiles.ts @@ -0,0 +1,441 @@ +import type { Profile } from '@objectstack/spec/security'; + +/** + * CRM Security Profiles + * Define role-based security profiles for the CRM application + */ + +// Sales Representative Profile +export const SalesRepProfile: Profile = { + name: 'sales_rep', + label: 'Sales Representative', + description: 'Standard sales rep with access to sales objects', + + objectPermissions: { + // Sales Objects + lead: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + account: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + contact: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + opportunity: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + quote: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + contract: { + create: false, + read: true, + update: false, + delete: false, + viewAll: false, + modifyAll: false, + }, + product: { + create: false, + read: true, + update: false, + delete: false, + viewAll: true, + modifyAll: false, + }, + campaign: { + create: false, + read: true, + update: false, + delete: false, + viewAll: true, + modifyAll: false, + }, + // Service Objects + case: { + create: false, + read: true, + update: false, + delete: false, + viewAll: false, + modifyAll: false, + }, + task: { + create: true, + read: true, + update: true, + delete: true, + viewAll: false, + modifyAll: false, + }, + }, + + fieldPermissions: { + account: { + annual_revenue: { read: true, update: false }, + description: { read: true, update: true }, + }, + opportunity: { + amount: { read: true, update: true }, + probability: { read: true, update: true }, + }, + }, + + tabVisibility: { + lead: 'default', + account: 'default', + contact: 'default', + opportunity: 'default', + quote: 'default', + product: 'available', + campaign: 'available', + case: 'hidden', + }, + + recordTypeVisibility: {}, + + applicationVisibility: { + crm_example: true, + }, +}; + +// Sales Manager Profile +export const SalesManagerProfile: Profile = { + name: 'sales_manager', + label: 'Sales Manager', + description: 'Sales manager with full access to sales objects', + + objectPermissions: { + lead: { + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + account: { + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + contact: { + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + opportunity: { + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + quote: { + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + contract: { + create: true, + read: true, + update: true, + delete: false, + viewAll: true, + modifyAll: false, + }, + product: { + create: true, + read: true, + update: true, + delete: false, + viewAll: true, + modifyAll: false, + }, + campaign: { + create: true, + read: true, + update: true, + delete: false, + viewAll: true, + modifyAll: false, + }, + case: { + create: false, + read: true, + update: false, + delete: false, + viewAll: true, + modifyAll: false, + }, + task: { + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + }, + + fieldPermissions: {}, + + tabVisibility: { + lead: 'default', + account: 'default', + contact: 'default', + opportunity: 'default', + quote: 'default', + contract: 'default', + product: 'default', + campaign: 'default', + case: 'available', + task: 'default', + }, + + recordTypeVisibility: {}, + + applicationVisibility: { + crm_example: true, + }, +}; + +// Service Agent Profile +export const ServiceAgentProfile: Profile = { + name: 'service_agent', + label: 'Service Agent', + description: 'Customer service agent with access to support objects', + + objectPermissions: { + lead: { + create: false, + read: true, + update: false, + delete: false, + viewAll: false, + modifyAll: false, + }, + account: { + create: false, + read: true, + update: false, + delete: false, + viewAll: false, + modifyAll: false, + }, + contact: { + create: false, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + opportunity: { + create: false, + read: false, + update: false, + delete: false, + viewAll: false, + modifyAll: false, + }, + case: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + task: { + create: true, + read: true, + update: true, + delete: true, + viewAll: false, + modifyAll: false, + }, + product: { + create: false, + read: true, + update: false, + delete: false, + viewAll: true, + modifyAll: false, + }, + }, + + fieldPermissions: { + case: { + is_sla_violated: { read: true, update: false }, + resolution_time_hours: { read: true, update: false }, + }, + }, + + tabVisibility: { + case: 'default', + task: 'default', + account: 'available', + contact: 'available', + product: 'available', + lead: 'hidden', + opportunity: 'hidden', + }, + + recordTypeVisibility: {}, + + applicationVisibility: { + crm_example: true, + }, +}; + +// Marketing User Profile +export const MarketingUserProfile: Profile = { + name: 'marketing_user', + label: 'Marketing User', + description: 'Marketing user with access to campaigns and leads', + + objectPermissions: { + lead: { + create: true, + read: true, + update: true, + delete: false, + viewAll: true, + modifyAll: false, + }, + account: { + create: false, + read: true, + update: false, + delete: false, + viewAll: true, + modifyAll: false, + }, + contact: { + create: true, + read: true, + update: true, + delete: false, + viewAll: true, + modifyAll: false, + }, + campaign: { + create: true, + read: true, + update: true, + delete: false, + viewAll: true, + modifyAll: false, + }, + opportunity: { + create: false, + read: true, + update: false, + delete: false, + viewAll: false, + modifyAll: false, + }, + }, + + fieldPermissions: {}, + + tabVisibility: { + campaign: 'default', + lead: 'default', + contact: 'default', + account: 'available', + opportunity: 'available', + }, + + recordTypeVisibility: {}, + + applicationVisibility: { + crm_example: true, + }, +}; + +// System Administrator Profile +export const SystemAdminProfile: Profile = { + name: 'system_admin', + label: 'System Administrator', + description: 'Full system administrator with all permissions', + + objectPermissions: { + '*': { + create: true, + read: true, + update: true, + delete: true, + viewAll: true, + modifyAll: true, + }, + }, + + fieldPermissions: {}, + + tabVisibility: { + '*': 'default', + }, + + recordTypeVisibility: {}, + + applicationVisibility: { + '*': true, + }, + + systemPermissions: { + viewSetup: true, + manageUsers: true, + customizeApplication: true, + viewAllData: true, + modifyAllData: true, + manageProfiles: true, + manageRoles: true, + manageSharing: true, + }, +}; + +export const CrmProfiles = { + SalesRepProfile, + SalesManagerProfile, + ServiceAgentProfile, + MarketingUserProfile, + SystemAdminProfile, +}; diff --git a/examples/app-crm/src/security/sharing-rules.ts b/examples/app-crm/src/security/sharing-rules.ts new file mode 100644 index 0000000000..92ed4e6067 --- /dev/null +++ b/examples/app-crm/src/security/sharing-rules.ts @@ -0,0 +1,216 @@ +import type { SharingRule } from '@objectstack/spec/security'; + +/** + * CRM Sharing Rules + * Define organization-wide sharing defaults and sharing rules + */ + +// Organization-Wide Defaults (OWD) +export const OrganizationDefaults = { + lead: { + internalAccess: 'private', + externalAccess: 'private', + }, + account: { + internalAccess: 'private', + externalAccess: 'private', + }, + contact: { + internalAccess: 'controlled_by_parent', // Controlled by Account + externalAccess: 'private', + }, + opportunity: { + internalAccess: 'private', + externalAccess: 'private', + }, + case: { + internalAccess: 'private', + externalAccess: 'private', + }, + campaign: { + internalAccess: 'public_read_only', + externalAccess: 'private', + }, + product: { + internalAccess: 'public_read_only', + externalAccess: 'private', + }, + task: { + internalAccess: 'private', + externalAccess: 'private', + }, +}; + +// Account Sharing Rule - Share accounts with team +export const AccountTeamSharingRule: SharingRule = { + name: 'account_team_sharing', + label: 'Account Team Sharing', + objectName: 'account', + type: 'criteria_based', + + criteria: { + type: { $eq: 'customer' }, + is_active: { $eq: true }, + }, + + sharedWith: { + type: 'role', + roles: ['sales_manager', 'sales_director'], + }, + + accessLevel: 'read_write', + + includeRelatedObjects: [ + { objectName: 'contact', accessLevel: 'read_only' }, + { objectName: 'opportunity', accessLevel: 'read_only' }, + ], +}; + +// Opportunity Sharing Rule - Share with sales team +export const OpportunitySalesSharingRule: SharingRule = { + name: 'opportunity_sales_sharing', + label: 'Opportunity Sales Team Sharing', + objectName: 'opportunity', + type: 'criteria_based', + + criteria: { + stage: { $nin: ['closed_won', 'closed_lost'] }, + amount: { $gte: 100000 }, // High-value opportunities + }, + + sharedWith: { + type: 'role', + roles: ['sales_manager', 'sales_director', 'executive'], + }, + + accessLevel: 'read_only', +}; + +// Case Sharing Rule - Share escalated cases +export const CaseEscalationSharingRule: SharingRule = { + name: 'case_escalation_sharing', + label: 'Escalated Cases Sharing', + objectName: 'case', + type: 'criteria_based', + + criteria: { + priority: 'critical', + is_closed: false, + }, + + sharedWith: { + type: 'role_and_subordinates', + roles: ['service_manager'], + }, + + accessLevel: 'read_write', +}; + +// Territory-Based Sharing +export const TerritorySharingRules = [ + { + name: 'north_america_territory', + label: 'North America Territory', + objectName: 'account', + type: 'territory_based', + + criteria: { + billing_address: { + country: { $in: ['US', 'CA', 'MX'] }, + }, + }, + + sharedWith: { + type: 'territory', + territory: 'north_america', + }, + + accessLevel: 'read_write', + }, + { + name: 'europe_territory', + label: 'Europe Territory', + objectName: 'account', + type: 'territory_based', + + criteria: { + billing_address: { + country: { $in: ['UK', 'DE', 'FR', 'IT', 'ES'] }, + }, + }, + + sharedWith: { + type: 'territory', + territory: 'europe', + }, + + accessLevel: 'read_write', + }, +]; + +// Role Hierarchy +export const RoleHierarchy = { + name: 'crm_role_hierarchy', + label: 'CRM Role Hierarchy', + roles: [ + { + name: 'executive', + label: 'Executive', + parentRole: null, + }, + { + name: 'sales_director', + label: 'Sales Director', + parentRole: 'executive', + }, + { + name: 'sales_manager', + label: 'Sales Manager', + parentRole: 'sales_director', + }, + { + name: 'sales_rep', + label: 'Sales Representative', + parentRole: 'sales_manager', + }, + { + name: 'service_director', + label: 'Service Director', + parentRole: 'executive', + }, + { + name: 'service_manager', + label: 'Service Manager', + parentRole: 'service_director', + }, + { + name: 'service_agent', + label: 'Service Agent', + parentRole: 'service_manager', + }, + { + name: 'marketing_director', + label: 'Marketing Director', + parentRole: 'executive', + }, + { + name: 'marketing_manager', + label: 'Marketing Manager', + parentRole: 'marketing_director', + }, + { + name: 'marketing_user', + label: 'Marketing User', + parentRole: 'marketing_manager', + }, + ], +}; + +export const CrmSharingRules = { + OrganizationDefaults, + AccountTeamSharingRule, + OpportunitySalesSharingRule, + CaseEscalationSharingRule, + TerritorySharingRules, + RoleHierarchy, +}; From 165dd2b2e2f34579f139ad526abc20d993de8318 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:28:15 +0000 Subject: [PATCH 3/5] Add comprehensive documentation for data modeling, business logic, security, and AI capabilities Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/app-crm/docs/01-data-modeling.md | 663 ++++++++++++++++ examples/app-crm/docs/02-business-logic.md | 724 +++++++++++++++++ examples/app-crm/docs/05-security.md | 653 ++++++++++++++++ examples/app-crm/docs/08-ai-capabilities.md | 818 ++++++++++++++++++++ examples/app-crm/docs/README.md | 310 ++++++++ 5 files changed, 3168 insertions(+) create mode 100644 examples/app-crm/docs/01-data-modeling.md create mode 100644 examples/app-crm/docs/02-business-logic.md create mode 100644 examples/app-crm/docs/05-security.md create mode 100644 examples/app-crm/docs/08-ai-capabilities.md create mode 100644 examples/app-crm/docs/README.md diff --git a/examples/app-crm/docs/01-data-modeling.md b/examples/app-crm/docs/01-data-modeling.md new file mode 100644 index 0000000000..20cddc0d59 --- /dev/null +++ b/examples/app-crm/docs/01-data-modeling.md @@ -0,0 +1,663 @@ +# Data Modeling Guide + +Complete guide to designing robust data models in ObjectStack following enterprise best practices. + +## Table of Contents + +1. [Object Schema Design](#object-schema-design) +2. [Field Types & Configuration](#field-types--configuration) +3. [Relationships & Lookups](#relationships--lookups) +4. [Validation Rules](#validation-rules) +5. [Formula Fields](#formula-fields) +6. [Database Indexing](#database-indexing) +7. [Best Practices](#best-practices) + +--- + +## Object Schema Design + +### Basic Object Structure + +Every object definition follows this pattern: + +```typescript +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const MyObject = ObjectSchema.create({ + // Metadata + name: 'my_object', // Machine name (snake_case) + label: 'My Object', // Display name + pluralLabel: 'My Objects', // Plural form + icon: 'briefcase', // Icon identifier + description: 'Description...', // Help text + + // Display configuration + titleFormat: '{field1} - {field2}', + compactLayout: ['field1', 'field2', 'field3'], + + // Fields definition + fields: { + // ... field definitions + }, + + // Performance + indexes: [...], + + // Capabilities + enable: {...}, + + // Business rules + validations: [...], + workflows: [...], +}); +``` + +### Object Metadata + +| Property | Type | Description | Example | +|----------|------|-------------|---------| +| `name` | string | Machine name (snake_case) | `'account'` | +| `label` | string | Display name | `'Account'` | +| `pluralLabel` | string | Plural display name | `'Accounts'` | +| `icon` | string | Icon identifier | `'building'` | +| `description` | string | Help text | `'Companies...'` | +| `titleFormat` | string | Record title template | `'{name} - {id}'` | +| `compactLayout` | string[] | Quick view fields | `['name', 'status']` | + +### Enable Features + +Control which features are available for an object: + +```typescript +enable: { + trackHistory: true, // Track field changes over time + searchable: true, // Include in global search + apiEnabled: true, // Expose via REST/GraphQL + apiMethods: [ // Whitelist API operations + 'get', + 'list', + 'create', + 'update', + 'delete', + 'search', + 'export' + ], + files: true, // Allow file attachments + feeds: true, // Enable activity feed (Chatter-like) + activities: true, // Track tasks and events + trash: true, // Soft delete with recycle bin + mru: true, // Track Most Recently Used +} +``` + +--- + +## Field Types & Configuration + +### Text Fields + +```typescript +// Simple text field +Field.text({ + label: 'Account Name', + required: true, + maxLength: 255, + searchable: true, +}) + +// Text area (multi-line) +Field.textarea({ + label: 'Description', + maxLength: 5000, + rows: 5, +}) + +// Rich text / Markdown +Field.markdown({ + label: 'Notes', +}) +``` + +### Numeric Fields + +```typescript +// Number +Field.number({ + label: 'Employees', + min: 0, + max: 1000000, + step: 1, +}) + +// Currency +Field.currency({ + label: 'Annual Revenue', + scale: 2, // Decimal places + min: 0, +}) + +// Percent +Field.percent({ + label: 'Discount', + scale: 2, + min: 0, + max: 100, +}) +``` + +### Date & Time Fields + +```typescript +// Date only +Field.date({ + label: 'Close Date', + required: true, + defaultValue: 'TODAY()', +}) + +// Date and time +Field.datetime({ + label: 'Last Modified', + readonly: true, + defaultValue: 'NOW()', +}) + +// Time only +Field.time({ + label: 'Business Hours Start', +}) +``` + +### Boolean Field + +```typescript +Field.boolean({ + label: 'Active', + defaultValue: true, +}) +``` + +### Select Fields + +```typescript +// Single select (picklist) +Field.select({ + label: 'Status', + options: [ + { label: 'New', value: 'new', color: '#999999', default: true }, + { label: 'In Progress', value: 'in_progress', color: '#FFA500' }, + { label: 'Completed', value: 'completed', color: '#00AA00' }, + ], + required: true, +}) + +// Multi-select +Field.select({ + label: 'Skills', + multiple: true, + options: [ + { label: 'JavaScript', value: 'js' }, + { label: 'Python', value: 'python' }, + { label: 'Go', value: 'go' }, + ], +}) +``` + +### AutoNumber Field + +Generates sequential numbers automatically: + +```typescript +Field.autonumber({ + label: 'Account Number', + format: 'ACC-{0000}', // ACC-0001, ACC-0002, ... +}) + +// Other formats: +// 'INV-{YYYY}-{0000}' // INV-2024-0001 +// '{YYYY}{MM}{DD}-{000}' // 20240115-001 +``` + +### Lookup Fields + +Reference other objects: + +```typescript +// Simple lookup +Field.lookup('account', { + label: 'Account', + required: true, +}) + +// Lookup with filters +Field.lookup('contact', { + label: 'Primary Contact', + referenceFilters: { + account: '{account}', // Same account only + is_active: true, + } +}) +``` + +### Address Field + +Structured address data: + +```typescript +Field.address({ + label: 'Billing Address', + addressFormat: 'international', // or 'us', 'uk', etc. +}) + +// Stores: +// - street +// - city +// - state/province +// - postal_code +// - country +``` + +### Location Field + +Geographic coordinates: + +```typescript +Field.location({ + label: 'Office Location', + displayMap: true, + allowGeocoding: true, +}) + +// Stores: +// - latitude +// - longitude +``` + +### URL Field + +```typescript +Field.url({ + label: 'Website', +}) +``` + +### Email Field + +```typescript +Field.email({ + label: 'Email Address', + unique: true, +}) +``` + +### Phone Field + +```typescript +Field.text({ + label: 'Phone', + format: 'phone', +}) +``` + +### Color Field + +```typescript +Field.color({ + label: 'Brand Color', + colorFormat: 'hex', // 'hex', 'rgb', 'hsl' + presetColors: [ + '#FF0000', + '#00FF00', + '#0000FF', + ], +}) +``` + +--- + +## Relationships & Lookups + +### Lookup (Many-to-One) + +Creates a reference to another object: + +```typescript +fields: { + account: Field.lookup('account', { + label: 'Account', + required: true, + }), +} +``` + +**Naming Convention:** Use the object name directly (e.g., `account`, not `account_id`) + +### Filtered Lookups + +Reference records that meet criteria: + +```typescript +contact: Field.lookup('contact', { + label: 'Contact', + referenceFilters: { + account: '{account}', // Filter by parent account + is_active: true, + } +}) +``` + +### Self-Referencing Lookups + +Create hierarchies: + +```typescript +parent_account: Field.lookup('account', { + label: 'Parent Account', + description: 'Parent company in hierarchy', +}) +``` + +### Related Lists + +Child records automatically appear in related lists when a lookup points to the parent. + +**Example:** +- Account has many Contacts (Contact.account → Account) +- Account detail page shows "Contacts" related list + +--- + +## Validation Rules + +### Script Validations + +Custom JavaScript-like expressions: + +```typescript +validations: [ + { + name: 'revenue_positive', + type: 'script', + severity: 'error', + message: 'Annual Revenue must be positive', + condition: 'annual_revenue < 0', + }, + { + name: 'close_date_future', + type: 'script', + severity: 'warning', + message: 'Close Date should be in the future', + condition: 'close_date < TODAY()', + }, +] +``` + +### Unique Validations + +Ensure field values are unique: + +```typescript +{ + name: 'email_unique', + type: 'unique', + severity: 'error', + message: 'Email must be unique', + fields: ['email'], + caseSensitive: false, +} +``` + +### Required Field Validations + +Mark fields as required: + +```typescript +Field.text({ + label: 'Name', + required: true, +}) +``` + +### Validation Severity + +- **`error`**: Prevents save +- **`warning`**: Shows warning but allows save +- **`info`**: Informational message + +--- + +## Formula Fields + +Calculate values automatically: + +```typescript +// Simple calculation +Field.formula({ + label: 'Total Price', + type: 'currency', + formula: 'subtotal - discount + tax', + scale: 2, +}) + +// Conditional logic +Field.formula({ + label: 'Priority Level', + type: 'text', + formula: 'IF(amount > 100000, "High", IF(amount > 50000, "Medium", "Low"))', +}) + +// Date calculation +Field.formula({ + label: 'Days to Close', + type: 'number', + formula: 'DAYS_DIFF(close_date, TODAY())', +}) + +// Percentage calculation +Field.formula({ + label: 'Response Rate', + type: 'percent', + formula: 'IF(num_sent > 0, (num_responses / num_sent) * 100, 0)', + scale: 2, +}) +``` + +### Common Formula Functions + +| Function | Description | Example | +|----------|-------------|---------| +| `IF(condition, true_value, false_value)` | Conditional | `IF(amount > 1000, "High", "Low")` | +| `AND(expr1, expr2, ...)` | Logical AND | `AND(is_active, amount > 0)` | +| `OR(expr1, expr2, ...)` | Logical OR | `OR(status = "new", status = "pending")` | +| `NOT(expr)` | Logical NOT | `NOT(is_deleted)` | +| `ISBLANK(field)` | Check if blank | `ISBLANK(phone)` | +| `TODAY()` | Current date | `TODAY()` | +| `NOW()` | Current datetime | `NOW()` | +| `DAYS_DIFF(date1, date2)` | Days between dates | `DAYS_DIFF(end_date, start_date)` | + +--- + +## Database Indexing + +Optimize query performance with indexes: + +```typescript +indexes: [ + // Single field index + { fields: ['name'], unique: false }, + + // Unique index + { fields: ['email'], unique: true }, + + // Compound index + { fields: ['type', 'is_active'], unique: false }, + + // Lookup field index + { fields: ['owner'], unique: false }, +] +``` + +### When to Add Indexes + +✅ **Add indexes for:** +- Lookup/reference fields +- Fields used in filters +- Fields used for sorting +- Fields in WHERE clauses +- High-cardinality fields + +❌ **Avoid indexes for:** +- Low-cardinality fields (e.g., boolean) +- Fields rarely queried +- Frequently updated fields + +--- + +## Best Practices + +### 1. Naming Conventions + +✅ **DO:** +- Use `snake_case` for field names: `first_name`, `account_number` +- Use descriptive names: `annual_revenue` not `rev` +- Use boolean prefixes: `is_active`, `has_children` + +❌ **DON'T:** +- Use camelCase for field names +- Use abbreviations: `acc_num` +- Add suffixes to lookups: `account_id` (use `account`) + +### 2. Field Design + +✅ **DO:** +- Use appropriate field types +- Set reasonable max lengths +- Add help text for complex fields +- Use picklists instead of text when values are fixed +- Mark required fields + +❌ **DON'T:** +- Store multiple values in one field +- Use text fields for dates/numbers +- Create too many fields (split into related objects) + +### 3. Relationships + +✅ **DO:** +- Use lookup fields for relationships +- Set up proper cascade delete rules +- Use filtered lookups to improve UX +- Document relationship cardinality + +❌ **DON'T:** +- Store IDs as text fields +- Create circular references +- Over-normalize (balance normalization vs. performance) + +### 4. Performance + +✅ **DO:** +- Add indexes on frequently queried fields +- Use formula fields for calculations +- Limit related list queries +- Use compound indexes for multi-field filters + +❌ **DON'T:** +- Create too many indexes (slows writes) +- Put indexes on low-cardinality fields +- Use SOQL in loops + +### 5. Validation + +✅ **DO:** +- Validate at the field level when possible +- Use validation rules for complex logic +- Provide clear error messages +- Test validation rules thoroughly + +❌ **DON'T:** +- Duplicate validations +- Create overly complex rules +- Block legitimate data entry + +--- + +## Real-World Examples + +### Account Object + +```typescript +export const Account = ObjectSchema.create({ + name: 'account', + label: 'Account', + pluralLabel: 'Accounts', + icon: 'building', + + fields: { + account_number: Field.autonumber({ + label: 'Account Number', + format: 'ACC-{0000}', + }), + + name: Field.text({ + label: 'Account Name', + required: true, + searchable: true, + maxLength: 255, + }), + + type: Field.select({ + label: 'Type', + options: [ + { label: 'Prospect', value: 'prospect', default: true }, + { label: 'Customer', value: 'customer' }, + { label: 'Partner', value: 'partner' }, + ] + }), + + annual_revenue: Field.currency({ + label: 'Annual Revenue', + scale: 2, + min: 0, + }), + + billing_address: Field.address({ + label: 'Billing Address', + addressFormat: 'international', + }), + + owner: Field.lookup('user', { + label: 'Account Owner', + required: true, + }), + }, + + indexes: [ + { fields: ['name'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['type', 'is_active'], unique: false }, + ], + + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + files: true, + feeds: true, + }, + + validations: [ + { + name: 'revenue_positive', + type: 'script', + severity: 'error', + message: 'Revenue must be positive', + condition: 'annual_revenue < 0', + }, + ], +}); +``` + +--- + +**Next:** [Business Logic →](./02-business-logic.md) diff --git a/examples/app-crm/docs/02-business-logic.md b/examples/app-crm/docs/02-business-logic.md new file mode 100644 index 0000000000..a553aa45b5 --- /dev/null +++ b/examples/app-crm/docs/02-business-logic.md @@ -0,0 +1,724 @@ +# Business Logic Guide + +Complete guide to implementing business rules, validations, and automated processes in ObjectStack. + +## Table of Contents + +1. [Validation Rules](#validation-rules) +2. [Workflow Rules](#workflow-rules) +3. [Triggers](#triggers) +4. [Approval Processes](#approval-processes) +5. [Formula Logic](#formula-logic) +6. [Best Practices](#best-practices) + +--- + +## Validation Rules + +Validation rules ensure data quality by preventing invalid data from being saved. + +### Script Validation + +Use JavaScript-like expressions to validate data: + +```typescript +validations: [ + { + name: 'close_date_future', + type: 'script', + severity: 'error', + message: 'Close Date must be in the future', + condition: 'close_date <= TODAY()', + }, + + { + name: 'discount_limit', + type: 'script', + severity: 'warning', + message: 'Discount exceeds 20%', + condition: 'discount > 20', + }, +] +``` + +### Unique Validation + +Ensure field values are unique across records: + +```typescript +{ + name: 'email_unique', + type: 'unique', + severity: 'error', + message: 'Email address must be unique', + fields: ['email'], + caseSensitive: false, +} + +// Compound unique constraint +{ + name: 'account_product_unique', + type: 'unique', + severity: 'error', + message: 'Product already exists for this account', + fields: ['account', 'product'], +} +``` + +### Required Field Validation + +Mark fields as required at the field level: + +```typescript +Field.text({ + label: 'Name', + required: true, +}) + +// Conditional required (use validation rule) +{ + name: 'contact_required_for_customer', + type: 'script', + severity: 'error', + message: 'Contact is required for customer accounts', + condition: 'type = "customer" AND ISBLANK(primary_contact)', +} +``` + +### Validation Functions + +| Function | Description | Example | +|----------|-------------|---------| +| `ISBLANK(field)` | Check if field is empty | `ISBLANK(phone)` | +| `ISCHANGED(field)` | Check if field changed | `ISCHANGED(stage)` | +| `ISNEW()` | Check if record is new | `ISNEW()` | +| `PRIORVALUE(field)` | Get previous value | `PRIORVALUE(status)` | +| `AND(expr1, expr2)` | Logical AND | `AND(is_active, amount > 0)` | +| `OR(expr1, expr2)` | Logical OR | `OR(status="new", status="pending")` | +| `NOT(expr)` | Logical NOT | `NOT(is_deleted)` | +| `TODAY()` | Current date | `TODAY()` | +| `NOW()` | Current datetime | `NOW()` | + +--- + +## Workflow Rules + +Workflow rules automate standard internal procedures and processes to save time. + +### Field Update Workflow + +Automatically update fields when conditions are met: + +```typescript +workflows: [ + { + name: 'update_last_activity', + objectName: 'account', + triggerType: 'on_update', + criteria: 'ISCHANGED(owner) OR ISCHANGED(type)', + actions: [ + { + name: 'set_activity_date', + type: 'field_update', + field: 'last_activity_date', + value: 'TODAY()', + } + ], + active: true, + } +] +``` + +### Email Alert Workflow + +Send emails when conditions are met: + +```typescript +{ + name: 'notify_high_value_opportunity', + objectName: 'opportunity', + triggerType: 'on_create', + criteria: 'amount > 100000', + actions: [ + { + name: 'notify_manager', + type: 'email_alert', + template: 'high_value_opportunity', + recipients: ['{owner.manager}'], + } + ], + active: true, +} +``` + +### Task Creation Workflow + +Automatically create tasks: + +```typescript +{ + name: 'create_follow_up_task', + objectName: 'lead', + triggerType: 'on_create', + criteria: 'rating = "hot"', + actions: [ + { + name: 'create_task', + type: 'task_create', + subject: 'Follow up on hot lead: {name}', + relatedTo: '{id}', + assignedTo: '{owner}', + dueDate: '{TODAY() + 1}', + priority: 'high', + } + ], + active: true, +} +``` + +### Scheduled Workflow + +Run workflows on a schedule: + +```typescript +{ + name: 'contract_expiration_check', + objectName: 'contract', + triggerType: 'scheduled', + schedule: '0 0 * * *', // Daily at midnight + criteria: 'end_date <= TODAY() AND status = "activated"', + actions: [ + { + name: 'mark_expired', + type: 'field_update', + field: 'status', + value: '"expired"', + }, + { + name: 'notify_owner', + type: 'email_alert', + template: 'contract_expired', + recipients: ['{owner}'], + } + ], + active: true, +} +``` + +### Trigger Types + +| Type | When It Fires | Use Case | +|------|---------------|----------| +| `on_create` | Record created | Welcome emails, initial tasks | +| `on_update` | Record updated | Status changes, notifications | +| `on_delete` | Record deleted | Cleanup, archival | +| `on_read` | Record viewed | Lazy updates, calculations | +| `scheduled` | Cron schedule | Batch processing, cleanup | + +--- + +## Triggers + +Advanced event-driven automation with custom logic. + +### Before Trigger + +Modify records before they're saved: + +```typescript +import { Trigger } from '@objectstack/spec/data'; + +export const AccountBeforeTrigger: Trigger = { + name: 'account_before_insert_update', + objectName: 'account', + timing: 'before', + operations: ['insert', 'update'], + + handler: async (context) => { + for (const record of context.records) { + // Normalize phone numbers + if (record.phone) { + record.phone = normalizePhone(record.phone); + } + + // Auto-populate from website + if (!record.industry && record.website) { + record.industry = await lookupIndustry(record.website); + } + } + }, +}; +``` + +### After Trigger + +Perform actions after records are saved: + +```typescript +export const OpportunityAfterTrigger: Trigger = { + name: 'opportunity_after_update', + objectName: 'opportunity', + timing: 'after', + operations: ['update'], + + handler: async (context) => { + const wonOpps = context.records.filter( + r => r.stage === 'closed_won' && + context.oldRecords[r.id].stage !== 'closed_won' + ); + + for (const opp of wonOpps) { + // Create contract + await context.create('contract', { + account: opp.account, + opportunity: opp.id, + contract_value: opp.amount, + start_date: new Date(), + }); + + // Send notification + await context.sendEmail({ + to: opp.owner.email, + template: 'opportunity_won', + data: { opportunity: opp }, + }); + } + }, +}; +``` + +### Trigger Context + +Available in trigger handlers: + +```typescript +context = { + records: Record[], // New records + oldRecords: Map, // Original records (update/delete) + operation: 'insert' | 'update' | 'delete', + timing: 'before' | 'after', + user: User, // Current user + + // Operations + create(objectName, data), + update(objectName, id, data), + delete(objectName, id), + query(objectName, filter), + + // Utilities + sendEmail(options), + callAPI(url, options), + log(message), +} +``` + +--- + +## Approval Processes + +Multi-step approval workflows for business processes. + +### Simple Approval + +Single-step approval: + +```typescript +{ + name: 'opportunity_approval', + objectName: 'opportunity', + triggerType: 'manual', + entryConditions: 'amount > 50000', + + steps: [ + { + name: 'manager_approval', + approver: '{owner.manager}', + approverType: 'user', + emailTemplate: 'approval_request', + requiresComments: true, + + approvalActions: [ + { + type: 'field_update', + field: 'approval_status', + value: '"approved"', + } + ], + + rejectionActions: [ + { + type: 'field_update', + field: 'approval_status', + value: '"rejected"', + }, + { + type: 'email_alert', + template: 'approval_rejected', + recipients: ['{owner}'], + } + ], + } + ], + + finalApprovalActions: [ + { + type: 'field_update', + field: 'stage', + value: '"proposal"', + } + ], + + finalRejectionActions: [ + { + type: 'field_update', + field: 'stage', + value: '"qualification"', + } + ], +} +``` + +### Multi-Step Approval + +Sequential approval with multiple approvers: + +```typescript +{ + name: 'contract_approval', + objectName: 'contract', + triggerType: 'manual', + + steps: [ + // Step 1: Sales Manager + { + name: 'sales_manager', + approver: '{owner.manager}', + emailTemplate: 'contract_approval_manager', + }, + + // Step 2: Legal Review + { + name: 'legal_review', + approver: '{legal_team}', + approverType: 'queue', + emailTemplate: 'contract_approval_legal', + requiresComments: true, + }, + + // Step 3: Finance Approval + { + name: 'finance_approval', + approver: '{finance_director}', + condition: 'contract_value > 100000', + emailTemplate: 'contract_approval_finance', + }, + ], +} +``` + +### Parallel Approval + +Multiple approvers must approve: + +```typescript +{ + name: 'discount_approval', + objectName: 'quote', + + steps: [ + { + name: 'dual_approval', + approverType: 'all', + approvers: [ + '{sales_manager}', + '{pricing_manager}', + ], + emailTemplate: 'discount_approval_request', + } + ], +} +``` + +--- + +## Formula Logic + +Complex calculations and logic in formula fields. + +### Conditional Logic + +```typescript +// Tiered pricing +Field.formula({ + label: 'Discount Tier', + type: 'text', + formula: ` + IF(amount > 1000000, "Platinum", + IF(amount > 500000, "Gold", + IF(amount > 100000, "Silver", "Bronze") + ) + ) + `, +}) + +// Status indicator +Field.formula({ + label: 'Health Score', + type: 'text', + formula: ` + IF(AND(is_active, days_since_contact < 30), "Healthy", + IF(days_since_contact < 90, "At Risk", "Critical") + ) + `, +}) +``` + +### Date Calculations + +```typescript +// Days until close +Field.formula({ + label: 'Days to Close', + type: 'number', + formula: 'DAYS_DIFF(close_date, TODAY())', +}) + +// Contract end in 30 days +Field.formula({ + label: 'Expiring Soon', + type: 'boolean', + formula: 'AND(status = "active", DAYS_DIFF(end_date, TODAY()) <= 30)', +}) + +// Age in months +Field.formula({ + label: 'Age (Months)', + type: 'number', + formula: 'MONTH_DIFF(TODAY(), created_date)', +}) +``` + +### Financial Calculations + +```typescript +// Gross margin +Field.formula({ + label: 'Gross Margin %', + type: 'percent', + formula: 'IF(revenue > 0, ((revenue - cost) / revenue) * 100, 0)', + scale: 2, +}) + +// Weighted pipeline +Field.formula({ + label: 'Weighted Amount', + type: 'currency', + formula: 'amount * (probability / 100)', + scale: 2, +}) + +// Total with tax +Field.formula({ + label: 'Total with Tax', + type: 'currency', + formula: 'subtotal * 1.0825', // 8.25% tax + scale: 2, +}) +``` + +### Text Manipulation + +```typescript +// Full name +Field.formula({ + label: 'Full Name', + type: 'text', + formula: 'CONCAT(first_name, " ", last_name)', +}) + +// Email domain +Field.formula({ + label: 'Email Domain', + type: 'text', + formula: 'SPLIT(email, "@")[1]', +}) + +// Uppercase +Field.formula({ + label: 'Code', + type: 'text', + formula: 'UPPER(sku)', +}) +``` + +--- + +## Best Practices + +### 1. Validation Rules + +✅ **DO:** +- Validate at the lowest level possible (field > validation rule > trigger) +- Provide clear, actionable error messages +- Use appropriate severity levels +- Test with real-world data + +❌ **DON'T:** +- Duplicate validations +- Create overly complex conditions +- Block legitimate edge cases +- Use triggers for simple validations + +### 2. Workflow Rules + +✅ **DO:** +- Keep workflows simple and focused +- Document the business logic +- Test workflow recursion +- Use scheduled workflows for batch updates + +❌ **DON'T:** +- Create workflow loops +- Update too many fields in one workflow +- Use workflows for complex logic (use triggers) +- Mix concerns in one workflow + +### 3. Triggers + +✅ **DO:** +- Bulkify all trigger logic +- Use before triggers for field updates +- Use after triggers for related records +- Handle errors gracefully +- Add logging for debugging + +❌ **DON'T:** +- Query in loops +- Create in loops +- Perform complex calculations in triggers +- Ignore governor limits + +### 4. Approval Processes + +✅ **DO:** +- Define clear entry criteria +- Set reasonable timeout periods +- Allow recall when appropriate +- Notify all stakeholders +- Track approval history + +❌ **DON'T:** +- Create too many approval steps +- Make approvals too complex +- Forget rejection paths +- Hard-code approvers + +### 5. Formula Fields + +✅ **DO:** +- Keep formulas simple and readable +- Add comments for complex logic +- Test edge cases (null, zero, empty) +- Use appropriate data types + +❌ **DON'T:** +- Create circular references +- Use formulas for frequently changing data +- Nest too many IF statements +- Ignore null handling + +--- + +## Real-World Examples + +### Lead Qualification Automation + +```typescript +// Validation: Ensure valid data +validations: [ + { + name: 'valid_email', + type: 'script', + severity: 'error', + message: 'Invalid email address', + condition: 'email != null AND !REGEX(email, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")', + }, +], + +// Workflow: Auto-assign hot leads +workflows: [ + { + name: 'assign_hot_leads', + triggerType: 'on_create', + criteria: 'rating = "hot" AND lead_source = "web"', + actions: [ + { + type: 'field_update', + field: 'owner', + value: 'GET_ROUND_ROBIN_USER("sales_team")', + }, + { + type: 'task_create', + subject: 'Contact hot lead: {name}', + dueDate: '{NOW() + 2 hours}', + }, + ], + }, +], + +// Formula: Lead score +Field.formula({ + label: 'Lead Score', + type: 'number', + formula: ` + (IF(rating = "hot", 30, IF(rating = "warm", 20, 10))) + + (IF(annual_revenue > 1000000, 25, 0)) + + (IF(number_of_employees > 500, 20, 0)) + + (IF(industry IN ("technology", "finance"), 15, 0)) + `, +}) +``` + +### Opportunity Management + +```typescript +// Validation: Stage progression +validations: [ + { + name: 'valid_stage_change', + type: 'script', + severity: 'error', + message: 'Cannot skip stages', + condition: ` + AND( + ISCHANGED(stage), + NOT(ISNEW()), + NOT(VALID_TRANSITION(PRIORVALUE(stage), stage)) + ) + `, + }, +], + +// Workflow: Update probability +workflows: [ + { + name: 'update_probability', + triggerType: 'on_update', + criteria: 'ISCHANGED(stage)', + actions: [ + { + type: 'field_update', + field: 'probability', + value: 'GET_STAGE_PROBABILITY(stage)', + }, + ], + }, +], + +// Approval: Large deals +// (See Approval Processes section above) +``` + +--- + +**Next:** [UI Design →](./03-ui-design.md) diff --git a/examples/app-crm/docs/05-security.md b/examples/app-crm/docs/05-security.md new file mode 100644 index 0000000000..ed6f1b0202 --- /dev/null +++ b/examples/app-crm/docs/05-security.md @@ -0,0 +1,653 @@ +# Security Model Guide + +Complete guide to implementing enterprise-grade security in ObjectStack with fine-grained permissions and data access controls. + +## Table of Contents + +1. [Security Architecture](#security-architecture) +2. [Profiles](#profiles) +3. [Permission Sets](#permission-sets) +4. [Role Hierarchy](#role-hierarchy) +5. [Sharing Rules](#sharing-rules) +6. [Field-Level Security](#field-level-security) +7. [Best Practices](#best-practices) + +--- + +## Security Architecture + +ObjectStack implements a multi-layered security model: + +``` +┌─────────────────────────────────────┐ +│ Application Visibility │ ← Which apps can users access? +├─────────────────────────────────────┤ +│ Object Permissions │ ← CRUD on objects +├─────────────────────────────────────┤ +│ Record-Level Security │ ← Which records can be accessed? +│ - Organization-Wide Defaults │ +│ - Role Hierarchy │ +│ - Sharing Rules │ +│ - Manual Sharing │ +├─────────────────────────────────────┤ +│ Field-Level Security │ ← Read/Write specific fields +└─────────────────────────────────────┘ +``` + +### Security Layers + +1. **Object Permissions**: Control create, read, update, delete on entire objects +2. **Record-Level Security**: Control access to specific records +3. **Field-Level Security**: Control visibility and editability of fields +4. **Row-Level Security**: Control access based on data values + +--- + +## Profiles + +Profiles define a user's baseline permissions. + +### Profile Structure + +```typescript +import type { Profile } from '@objectstack/spec/security'; + +export const SalesRepProfile: Profile = { + name: 'sales_rep', + label: 'Sales Representative', + description: 'Standard sales rep permissions', + + // Object-level permissions + objectPermissions: { + account: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, // See all records regardless of owner + modifyAll: false, // Edit all records regardless of owner + }, + opportunity: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + }, + + // Field-level permissions + fieldPermissions: { + account: { + annual_revenue: { read: true, update: false }, + description: { read: true, update: true }, + }, + }, + + // Tab visibility + tabVisibility: { + account: 'default', // Default tab + lead: 'default', + opportunity: 'default', + case: 'hidden', // Not visible + product: 'available', // Available but not default + }, + + // Application access + applicationVisibility: { + crm_example: true, + }, +}; +``` + +### Object Permission Levels + +| Permission | Description | +|------------|-------------| +| `create` | Create new records | +| `read` | View records they own or have access to | +| `update` | Edit records they own or have access to | +| `delete` | Delete records they own or have access to | +| `viewAll` | View ALL records regardless of ownership | +| `modifyAll` | Edit ALL records regardless of ownership | + +### Standard Profiles + +#### Sales Representative + +```typescript +export const SalesRepProfile: Profile = { + name: 'sales_rep', + objectPermissions: { + lead: { create: true, read: true, update: true, delete: false }, + account: { create: true, read: true, update: true, delete: false }, + contact: { create: true, read: true, update: true, delete: false }, + opportunity: { create: true, read: true, update: true, delete: false }, + quote: { create: true, read: true, update: true, delete: false }, + product: { create: false, read: true, update: false, delete: false }, + }, + fieldPermissions: { + account: { + annual_revenue: { read: true, update: false }, // Read-only + }, + }, +}; +``` + +#### Sales Manager + +```typescript +export const SalesManagerProfile: Profile = { + name: 'sales_manager', + objectPermissions: { + lead: { + create: true, read: true, update: true, delete: true, + viewAll: true, modifyAll: true + }, + account: { + create: true, read: true, update: true, delete: true, + viewAll: true, modifyAll: true + }, + // ... full access to sales objects + }, +}; +``` + +#### Service Agent + +```typescript +export const ServiceAgentProfile: Profile = { + name: 'service_agent', + objectPermissions: { + case: { create: true, read: true, update: true, delete: false }, + task: { create: true, read: true, update: true, delete: true }, + account: { create: false, read: true, update: false, delete: false }, + contact: { create: false, read: true, update: true, delete: false }, + }, + fieldPermissions: { + case: { + is_sla_violated: { read: true, update: false }, + resolution_time_hours: { read: true, update: false }, + }, + }, +}; +``` + +--- + +## Permission Sets + +Permission sets extend profile permissions without changing the profile. + +```typescript +import type { PermissionSet } from '@objectstack/spec/security'; + +export const AdvancedReportingPermissionSet: PermissionSet = { + name: 'advanced_reporting', + label: 'Advanced Reporting', + description: 'Additional permissions for advanced reporting', + + objectPermissions: { + opportunity: { + viewAll: true, // Override profile restriction + }, + account: { + viewAll: true, + }, + }, + + fieldPermissions: { + opportunity: { + amount: { read: true }, + probability: { read: true }, + }, + }, + + systemPermissions: { + runReports: true, + exportReports: true, + createDashboards: true, + }, +}; + +export const BulkDataPermissionSet: PermissionSet = { + name: 'bulk_data_access', + label: 'Bulk Data Access', + description: 'Permissions for bulk data operations', + + systemPermissions: { + bulkApiEnabled: true, + viewAllData: true, + }, +}; +``` + +### Assigning Permission Sets + +```typescript +// Assign to user +await assignPermissionSet({ + userId: 'user123', + permissionSetName: 'advanced_reporting', +}); + +// Assign to multiple users +await assignPermissionSetToGroup({ + permissionSetName: 'bulk_data_access', + userGroup: 'data_analysts', +}); +``` + +--- + +## Role Hierarchy + +Roles control record-level access through a hierarchy. + +```typescript +export const RoleHierarchy = { + name: 'crm_role_hierarchy', + label: 'CRM Role Hierarchy', + + roles: [ + // Top level + { + name: 'executive', + label: 'Executive', + parentRole: null, + }, + + // Sales hierarchy + { + name: 'sales_director', + label: 'Sales Director', + parentRole: 'executive', + }, + { + name: 'sales_manager', + label: 'Sales Manager', + parentRole: 'sales_director', + }, + { + name: 'sales_rep', + label: 'Sales Representative', + parentRole: 'sales_manager', + }, + + // Service hierarchy + { + name: 'service_director', + label: 'Service Director', + parentRole: 'executive', + }, + { + name: 'service_manager', + label: 'Service Manager', + parentRole: 'service_director', + }, + { + name: 'service_agent', + label: 'Service Agent', + parentRole: 'service_manager', + }, + ], +}; +``` + +### How Role Hierarchy Works + +``` + Executive + / \ + Sales Director Service Director + | | + Sales Manager Service Manager + | | + Sales Rep Service Agent +``` + +**Grant Access UP:** Users see records owned by: +- Themselves +- Their subordinates +- Their subordinates' subordinates (all levels down) + +**Example:** Sales Director sees all records owned by Sales Managers and Sales Reps. + +--- + +## Sharing Rules + +Sharing rules extend access beyond the role hierarchy. + +### Organization-Wide Defaults (OWD) + +Set baseline access for all users: + +```typescript +export const OrganizationDefaults = { + lead: { + internalAccess: 'private', // Users see only their own records + externalAccess: 'private', + }, + account: { + internalAccess: 'private', + externalAccess: 'private', + }, + contact: { + internalAccess: 'controlled_by_parent', // Access controlled by Account + externalAccess: 'private', + }, + opportunity: { + internalAccess: 'private', + externalAccess: 'private', + }, + campaign: { + internalAccess: 'public_read_only', // All users can read + externalAccess: 'private', + }, + product: { + internalAccess: 'public_read_only', + externalAccess: 'private', + }, +}; +``` + +### Access Levels + +| Level | Description | +|-------|-------------| +| `private` | Owner only (+ role hierarchy) | +| `public_read_only` | All users can read | +| `public_read_write` | All users can read and edit | +| `controlled_by_parent` | Controlled by parent object | + +### Criteria-Based Sharing Rules + +Share records based on field criteria: + +```typescript +export const AccountTeamSharingRule: SharingRule = { + name: 'account_team_sharing', + label: 'Share Active Customers with Sales Team', + objectName: 'account', + type: 'criteria_based', + + // Criteria: Which records to share + criteria: { + type: { $eq: 'customer' }, + is_active: { $eq: true }, + }, + + // Who to share with + sharedWith: { + type: 'role', + roles: ['sales_manager', 'sales_director'], + }, + + // Access level granted + accessLevel: 'read_write', + + // Also share related records + includeRelatedObjects: [ + { objectName: 'contact', accessLevel: 'read_only' }, + { objectName: 'opportunity', accessLevel: 'read_only' }, + ], +}; +``` + +### Owner-Based Sharing Rules + +Share based on record owner characteristics: + +```typescript +export const OpportunityOwnerSharingRule: SharingRule = { + name: 'opportunity_owner_sharing', + label: 'Share Opportunities within Same Territory', + objectName: 'opportunity', + type: 'owner_based', + + // Share records owned by users in these roles + ownedBy: { + type: 'role', + roles: ['sales_rep'], + }, + + // Share with users in these roles + sharedWith: { + type: 'role', + roles: ['sales_rep'], + sameTerritory: true, // Only same territory + }, + + accessLevel: 'read_only', +}; +``` + +### Territory-Based Sharing + +Share based on geographic territories: + +```typescript +export const TerritorySharingRules = [ + { + name: 'north_america_territory', + label: 'North America Territory', + objectName: 'account', + type: 'territory_based', + + criteria: { + billing_address: { + country: { $in: ['US', 'CA', 'MX'] }, + }, + }, + + sharedWith: { + type: 'territory', + territory: 'north_america', + }, + + accessLevel: 'read_write', + }, +]; +``` + +--- + +## Field-Level Security + +Control visibility and editability of specific fields. + +### Field Permissions in Profiles + +```typescript +fieldPermissions: { + account: { + // Field-level permissions + annual_revenue: { + read: true, // Can view + update: false // Cannot edit + }, + description: { + read: true, + update: true + }, + ssn: { + read: false, // Hidden field + update: false + }, + }, + opportunity: { + amount: { read: true, update: true }, + probability: { read: true, update: false }, + }, +} +``` + +### Hidden vs. Read-Only + +```typescript +// Hidden: Field not visible at all +{ read: false, update: false } + +// Read-Only: Field visible but not editable +{ read: true, update: false } + +// Editable: Field visible and editable +{ read: true, update: true } +``` + +--- + +## Best Practices + +### 1. Profile Design + +✅ **DO:** +- Create minimal profiles (start restrictive, extend with permission sets) +- Use descriptive names +- Document the purpose of each profile +- Assign one profile per user + +❌ **DON'T:** +- Create too many profiles +- Give excessive permissions "just in case" +- Mix unrelated permissions in one profile + +### 2. Permission Sets + +✅ **DO:** +- Use for temporary or special access +- Group related permissions +- Name clearly (e.g., "Advanced Reporting", "Bulk Data Access") +- Remove when no longer needed + +❌ **DON'T:** +- Use as a replacement for profiles +- Grant system-wide permissions unnecessarily + +### 3. Role Hierarchy + +✅ **DO:** +- Mirror your organizational structure +- Keep hierarchies simple +- Document reporting relationships +- Review regularly + +❌ **DON'T:** +- Create overly deep hierarchies +- Mix different org structures +- Grant too much upward access + +### 4. Sharing Rules + +✅ **DO:** +- Start with most restrictive OWD +- Use sharing rules to open up access +- Document business justification +- Test thoroughly + +❌ **DON'T:** +- Set OWD to Public unless necessary +- Create redundant sharing rules +- Grant more access than needed + +### 5. Field-Level Security + +✅ **DO:** +- Protect sensitive data (SSN, salary, etc.) +- Use read-only for calculated fields +- Document security classifications +- Audit regularly + +❌ **DON'T:** +- Hide required fields +- Restrict access unnecessarily +- Forget about API access + +--- + +## Security Checklist + +### Initial Setup + +- [ ] Define user roles and hierarchies +- [ ] Create profiles for each role +- [ ] Set organization-wide defaults +- [ ] Configure field-level security +- [ ] Create sharing rules + +### Ongoing Maintenance + +- [ ] Review user access quarterly +- [ ] Audit permission changes +- [ ] Remove inactive users promptly +- [ ] Update sharing rules as needed +- [ ] Monitor security health checks + +### Compliance + +- [ ] Document security model +- [ ] Maintain access request process +- [ ] Log security changes +- [ ] Conduct security reviews +- [ ] Train users on security policies + +--- + +## Real-World Example + +Complete security setup for a sales team: + +```typescript +// 1. Organization-Wide Defaults +OrganizationDefaults = { + account: { internalAccess: 'private' }, + opportunity: { internalAccess: 'private' }, + contact: { internalAccess: 'controlled_by_parent' }, +}; + +// 2. Role Hierarchy +RoleHierarchy = { + roles: [ + { name: 'sales_vp', parentRole: null }, + { name: 'sales_manager', parentRole: 'sales_vp' }, + { name: 'sales_rep', parentRole: 'sales_manager' }, + ], +}; + +// 3. Profiles +SalesRepProfile = { + objectPermissions: { + account: { create: true, read: true, update: true, delete: false }, + opportunity: { create: true, read: true, update: true, delete: false }, + }, + fieldPermissions: { + account: { + annual_revenue: { read: true, update: false }, // Read-only + }, + }, +}; + +// 4. Sharing Rules +AccountSharingRule = { + // Share high-value accounts with all sales reps + criteria: { annual_revenue: { $gte: 1000000 } }, + sharedWith: { type: 'role', roles: ['sales_rep'] }, + accessLevel: 'read_only', +}; + +// 5. Permission Set +AdvancedReportingPermissionSet = { + // For sales analysts + systemPermissions: { + runReports: true, + exportReports: true, + viewAllData: true, + }, +}; +``` + +--- + +**Next:** [Automation →](./06-automation.md) diff --git a/examples/app-crm/docs/08-ai-capabilities.md b/examples/app-crm/docs/08-ai-capabilities.md new file mode 100644 index 0000000000..b0f4ed6acc --- /dev/null +++ b/examples/app-crm/docs/08-ai-capabilities.md @@ -0,0 +1,818 @@ +# AI Capabilities Guide + +Complete guide to leveraging AI agents, RAG pipelines, and intelligent automation in ObjectStack. + +## Table of Contents + +1. [AI Architecture](#ai-architecture) +2. [AI Agents](#ai-agents) +3. [RAG Pipelines](#rag-pipelines) +4. [Natural Language Queries](#natural-language-queries) +5. [Predictive Analytics](#predictive-analytics) +6. [Best Practices](#best-practices) + +--- + +## AI Architecture + +ObjectStack provides a comprehensive AI platform: + +``` +┌─────────────────────────────────────┐ +│ AI Orchestration │ ← Coordinate AI workflows +├─────────────────────────────────────┤ +│ AI Agents │ ← Autonomous actors +│ - Assistants │ +│ - Workers │ +│ - Analysts │ +├─────────────────────────────────────┤ +│ RAG Pipelines │ ← Knowledge retrieval +│ - Vector Search │ +│ - Semantic Chunking │ +│ - Reranking │ +├─────────────────────────────────────┤ +│ Model Registry │ ← LLM management +│ - OpenAI, Anthropic, etc. │ +│ - Model routing │ +│ - Cost tracking │ +└─────────────────────────────────────┘ +``` + +--- + +## AI Agents + +AI agents are autonomous actors that perform tasks on behalf of users. + +### Agent Types + +| Type | Description | Use Cases | +|------|-------------|-----------| +| **Assistant** | Interactive helpers | Chatbots, Q&A, recommendations | +| **Worker** | Background processors | Data enrichment, automation | +| **Analyst** | Data analysts | Insights, forecasting, reporting | +| **Creator** | Content generators | Emails, documents, designs | + +### Sales Assistant Agent + +```typescript +import type { Agent } from '@objectstack/spec/ai'; + +export const SalesAssistantAgent: Agent = { + name: 'sales_assistant', + label: 'Sales Assistant', + description: 'AI agent to help sales reps with lead qualification', + + role: 'assistant', + + instructions: `You are a sales assistant AI. + +Your responsibilities: +1. Qualify incoming leads (BANT criteria) +2. Suggest next best actions +3. Draft personalized emails +4. Analyze win/loss patterns + +Always be professional and data-driven.`, + + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.7, + maxTokens: 2000, + }, + + // Tools the agent can use + tools: [ + { + name: 'analyze_lead', + description: 'Analyze a lead and provide qualification score', + parameters: { + lead_id: 'string', + }, + }, + { + name: 'suggest_next_action', + description: 'Suggest next best action for an opportunity', + parameters: { + opportunity_id: 'string', + }, + }, + { + name: 'generate_email', + description: 'Generate a personalized email template', + parameters: { + recipient_id: 'string', + context: 'string', + tone: 'string', + }, + }, + ], + + // Knowledge sources + knowledge: { + sources: [ + { + type: 'object', + objectName: 'lead', + fields: ['*'], + }, + { + type: 'object', + objectName: 'opportunity', + fields: ['*'], + }, + { + type: 'document', + path: '/knowledge/sales-playbook.md', + }, + ], + }, + + // When to trigger the agent + triggers: [ + { + type: 'object_create', + objectName: 'lead', + condition: 'rating = "hot"', + }, + { + type: 'object_update', + objectName: 'opportunity', + condition: 'ISCHANGED(stage)', + }, + ], +}; +``` + +### Customer Service Agent + +```typescript +export const ServiceAgent: Agent = { + name: 'service_agent', + label: 'Customer Service Agent', + description: 'AI agent to assist with support cases', + + role: 'assistant', + + instructions: `You are a customer service AI agent. + +Your responsibilities: +1. Triage incoming cases +2. Suggest relevant knowledge articles +3. Draft response templates +4. Escalate critical issues + +Always be empathetic and solution-focused.`, + + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.5, + maxTokens: 1500, + }, + + tools: [ + { + name: 'triage_case', + description: 'Analyze case and assign priority', + parameters: { + case_id: 'string', + }, + }, + { + name: 'search_knowledge', + description: 'Search knowledge base for solutions', + parameters: { + query: 'string', + }, + }, + { + name: 'generate_response', + description: 'Generate customer response', + parameters: { + case_id: 'string', + tone: 'string', + }, + }, + ], + + knowledge: { + sources: [ + { + type: 'object', + objectName: 'case', + fields: ['*'], + }, + { + type: 'document', + path: '/knowledge/support-kb/**/*.md', + }, + ], + }, + + triggers: [ + { + type: 'object_create', + objectName: 'case', + }, + ], +}; +``` + +### Lead Enrichment Agent (Worker) + +```typescript +export const LeadEnrichmentAgent: Agent = { + name: 'lead_enrichment', + label: 'Lead Enrichment Agent', + description: 'Automatically enrich lead data', + + role: 'worker', + + instructions: `You enrich lead records with additional data. + +Tasks: +1. Look up company information +2. Enrich contact details +3. Add firmographic data +4. Research technology stack + +Use reputable data sources.`, + + model: { + provider: 'openai', + model: 'gpt-3.5-turbo', + temperature: 0.3, + maxTokens: 1000, + }, + + tools: [ + { + name: 'lookup_company', + description: 'Look up company information', + parameters: { + company_name: 'string', + domain: 'string', + }, + }, + { + name: 'enrich_contact', + description: 'Enrich contact information', + parameters: { + email: 'string', + }, + }, + ], + + triggers: [ + { + type: 'object_create', + objectName: 'lead', + }, + ], + + schedule: { + type: 'cron', + expression: '0 */4 * * *', // Every 4 hours + timezone: 'UTC', + }, +}; +``` + +### Revenue Intelligence Agent (Analyst) + +```typescript +export const RevenueIntelligenceAgent: Agent = { + name: 'revenue_intelligence', + label: 'Revenue Intelligence Agent', + description: 'Analyze pipeline and provide insights', + + role: 'analyst', + + instructions: `You analyze sales data and provide insights. + +Responsibilities: +1. Analyze pipeline health +2. Identify at-risk deals +3. Forecast revenue +4. Detect anomalies +5. Generate executive summaries + +Use statistical analysis and ML.`, + + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.2, + maxTokens: 3000, + }, + + tools: [ + { + name: 'analyze_pipeline', + description: 'Analyze sales pipeline health', + parameters: { + user_id: 'string', + time_period: 'string', + }, + }, + { + name: 'forecast_revenue', + description: 'Generate revenue forecast', + parameters: { + time_period: 'string', + method: 'string', + }, + }, + ], + + knowledge: { + sources: [ + { + type: 'object', + objectName: 'opportunity', + fields: ['*'], + }, + { + type: 'analytics', + dashboardName: 'sales_dashboard', + }, + ], + }, + + schedule: { + type: 'cron', + expression: '0 8 * * 1', // Monday at 8am + timezone: 'America/Los_Angeles', + }, +}; +``` + +--- + +## RAG Pipelines + +Retrieval-Augmented Generation (RAG) provides AI with access to your knowledge base. + +### Sales Knowledge RAG + +```typescript +import type { RagPipeline } from '@objectstack/spec/ai'; + +export const SalesKnowledgeRAG: RagPipeline = { + name: 'sales_knowledge', + label: 'Sales Knowledge Pipeline', + description: 'Sales playbook and best practices', + + // Define indexes + indexes: [ + { + name: 'sales_playbook_index', + type: 'vector', + + // Data sources + sources: [ + { + type: 'document', + path: '/knowledge/sales/**/*.md', + watch: true, // Auto-update on changes + }, + { + type: 'document', + path: '/knowledge/products/**/*.pdf', + watch: true, + }, + { + type: 'object', + objectName: 'opportunity', + fields: ['name', 'description', 'stage', 'amount'], + filter: { + stage: 'closed_won', + close_date: { $gte: '{last_12_months}' }, + }, + }, + ], + + // Embedding model + embedding: { + provider: 'openai', + model: 'text-embedding-3-large', + dimensions: 1536, + }, + + // Chunking strategy + chunking: { + strategy: 'semantic', + chunkSize: 1000, + chunkOverlap: 200, + }, + + // Metadata extraction + metadata: { + extractors: [ + { + type: 'title', + source: 'filename', + }, + { + type: 'date', + source: 'modified_date', + }, + ], + }, + }, + ], + + // Retrieval strategy + retrieval: { + strategy: 'hybrid', + + vectorSearch: { + topK: 10, + scoreThreshold: 0.7, + algorithm: 'cosine', + }, + + keywordSearch: { + enabled: true, + weight: 0.3, + }, + + reranking: { + enabled: true, + model: 'cohere-rerank', + topK: 5, + }, + }, + + // Generation + generation: { + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.7, + maxTokens: 2000, + }, + + promptTemplate: `You are a sales expert. Use the context to answer. + +Context: +{context} + +Question: {question} + +Answer based on the context. If uncertain, say so.`, + }, + + // Caching + caching: { + enabled: true, + ttl: 3600, // 1 hour + }, +}; +``` + +### Support Knowledge RAG + +```typescript +export const SupportKnowledgeRAG: RagPipeline = { + name: 'support_knowledge', + label: 'Support Knowledge Pipeline', + description: 'Customer support knowledge base', + + indexes: [ + { + name: 'support_kb_index', + type: 'vector', + + sources: [ + { + type: 'document', + path: '/knowledge/support/**/*.md', + watch: true, + }, + { + type: 'object', + objectName: 'case', + fields: ['subject', 'description', 'resolution'], + filter: { + is_closed: true, + resolution: { $ne: null }, + }, + }, + ], + + embedding: { + provider: 'openai', + model: 'text-embedding-3-small', + dimensions: 768, + }, + + chunking: { + strategy: 'fixed', + chunkSize: 512, + chunkOverlap: 100, + }, + }, + ], + + retrieval: { + strategy: 'vector_only', + + vectorSearch: { + topK: 5, + scoreThreshold: 0.75, + algorithm: 'cosine', + }, + }, + + generation: { + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.3, + maxTokens: 1500, + }, + + promptTemplate: `You are a support specialist. Help resolve customer issues. + +Knowledge Base: +{context} + +Issue: {question} + +Provide a clear, step-by-step solution.`, + }, +}; +``` + +--- + +## Natural Language Queries + +Allow users to query data using natural language. + +### NLQ Configuration + +```typescript +import type { NLQConfig } from '@objectstack/spec/ai'; + +export const CrmNLQConfig: NLQConfig = { + name: 'crm_nlq', + label: 'CRM Natural Language Queries', + + // Objects available for querying + objects: [ + 'account', + 'contact', + 'lead', + 'opportunity', + 'case', + ], + + // LLM for query translation + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0, + }, + + // Example queries for training + examples: [ + { + query: 'Show me all high-value opportunities closing this quarter', + soql: 'SELECT Name, Amount, CloseDate FROM Opportunity WHERE Amount > 100000 AND CloseDate >= THIS_QUARTER', + }, + { + query: 'Which accounts have the most open cases?', + soql: 'SELECT Account.Name, COUNT(Id) FROM Case WHERE IsClosed = false GROUP BY Account.Name ORDER BY COUNT(Id) DESC', + }, + ], +}; +``` + +### Using NLQ + +```typescript +// User asks in natural language +const query = "Show me my top 10 opportunities by amount"; + +// AI translates to SOQL +const soql = await nlq.translate(query); +// Result: SELECT Name, Amount FROM Opportunity WHERE OwnerId = '{userId}' ORDER BY Amount DESC LIMIT 10 + +// Execute query +const results = await execute(soql); +``` + +--- + +## Predictive Analytics + +Use machine learning for predictions and recommendations. + +### Lead Scoring + +```typescript +import type { PredictiveModel } from '@objectstack/spec/ai'; + +export const LeadScoringModel: PredictiveModel = { + name: 'lead_scoring', + label: 'Lead Scoring Model', + description: 'Predict lead conversion probability', + + type: 'classification', + + trainingData: { + objectName: 'lead', + features: [ + 'annual_revenue', + 'number_of_employees', + 'industry', + 'lead_source', + 'rating', + ], + label: 'is_converted', + filter: { + created_date: { $gte: '{last_12_months}' }, + }, + }, + + model: { + algorithm: 'gradient_boosting', + hyperparameters: { + n_estimators: 100, + learning_rate: 0.1, + max_depth: 5, + }, + }, + + deployment: { + mode: 'realtime', + trigger: 'on_create', + outputField: 'conversion_score', + }, +}; +``` + +### Revenue Forecasting + +```typescript +export const RevenueForecastModel: PredictiveModel = { + name: 'revenue_forecast', + label: 'Revenue Forecasting Model', + description: 'Forecast monthly revenue', + + type: 'regression', + + trainingData: { + objectName: 'opportunity', + features: [ + 'amount', + 'probability', + 'stage', + 'age_days', + 'account.annual_revenue', + ], + label: 'close_date', + filter: { + stage: { $in: ['closed_won', 'closed_lost'] }, + }, + }, + + model: { + algorithm: 'time_series', + method: 'prophet', + }, + + deployment: { + mode: 'batch', + schedule: '0 0 1 * *', // Monthly + }, +}; +``` + +--- + +## Best Practices + +### 1. AI Agent Design + +✅ **DO:** +- Define clear agent roles and responsibilities +- Provide detailed instructions +- Use appropriate temperature settings +- Test with real-world scenarios +- Monitor agent performance + +❌ **DON'T:** +- Give agents conflicting instructions +- Use high temperatures for factual tasks +- Deploy without testing +- Ignore cost implications + +### 2. RAG Pipeline Design + +✅ **DO:** +- Use semantic chunking for better context +- Implement hybrid search (vector + keyword) +- Enable reranking for accuracy +- Cache frequently accessed content +- Monitor retrieval quality + +❌ **DON'T:** +- Chunk too large or too small +- Rely solely on vector search +- Skip metadata extraction +- Forget to update indexes + +### 3. Prompt Engineering + +✅ **DO:** +- Be specific and clear +- Provide examples +- Use role-playing ("You are a...") +- Include constraints +- Test variations + +❌ **DON'T:** +- Be vague or ambiguous +- Assume context +- Use complex jargon +- Write overly long prompts + +### 4. Cost Management + +✅ **DO:** +- Choose appropriate models (GPT-3.5 vs GPT-4) +- Implement caching +- Set token limits +- Monitor usage +- Use cheaper models for simple tasks + +❌ **DON'T:** +- Always use the most expensive model +- Skip caching +- Allow unlimited tokens +- Ignore cost metrics + +### 5. Security & Privacy + +✅ **DO:** +- Implement access controls +- Mask sensitive data +- Log AI interactions +- Review outputs +- Follow data privacy regulations + +❌ **DON'T:** +- Expose PII to external APIs +- Skip output validation +- Ignore audit trails +- Trust AI blindly + +--- + +## Real-World Integration + +### Complete Sales AI Workflow + +```typescript +// 1. Lead comes in +LeadEnrichmentAgent.enrich(lead); + +// 2. AI scores the lead +score = LeadScoringModel.predict(lead); +lead.conversion_score = score; + +// 3. If hot, sales assistant qualifies +if (score > 80) { + analysis = SalesAssistantAgent.analyze(lead); + task = createTask({ + subject: `Follow up on hot lead: ${lead.name}`, + priority: 'high', + assignedTo: lead.owner, + }); +} + +// 4. Sales rep asks for help +response = SalesKnowledgeRAG.query({ + question: "What's our pitch for fintech companies?", + context: { industry: 'finance' }, +}); + +// 5. Generate personalized email +email = SalesAssistantAgent.generateEmail({ + recipient: lead, + context: analysis, + tone: 'professional', +}); + +// 6. Track in pipeline +RevenueIntelligenceAgent.analyzePipeline(); +``` + +--- + +**Next:** [Integration →](./07-integration.md) diff --git a/examples/app-crm/docs/README.md b/examples/app-crm/docs/README.md new file mode 100644 index 0000000000..64245e1086 --- /dev/null +++ b/examples/app-crm/docs/README.md @@ -0,0 +1,310 @@ +# ObjectStack CRM Application - Complete Guide + +Welcome to the **ObjectStack CRM Application**, a comprehensive, enterprise-grade Customer Relationship Management system demonstrating the full capabilities of the ObjectStack Protocol. + +## 🎯 Overview + +This CRM application showcases: + +- **128+ Protocol Modules** across 15 categories +- **Enterprise-grade architecture** following Salesforce and ServiceNow best practices +- **AI-powered automation** with agents and RAG pipelines +- **Comprehensive security** with profiles, roles, and sharing rules +- **Complete business process automation** with flows and workflows + +## 📚 Documentation Structure + +This guide is organized into specialized sections: + +### 1. [Data Modeling](./01-data-modeling.md) +Learn how to design robust data models with objects, fields, relationships, and validations. + +**Topics:** +- Object schema design patterns +- Field types and configurations +- Relationship modeling (lookup, master-detail) +- Validation rules and formulas +- Database indexing strategies + +### 2. [Business Logic](./02-business-logic.md) +Implement business rules, validations, and automated processes. + +**Topics:** +- Validation rules (script, unique, required) +- Workflow rules and field updates +- Approval processes +- Record triggers +- Business process automation + +### 3. [UI Design](./03-ui-design.md) +Create intuitive user interfaces with apps, views, actions, and layouts. + +**Topics:** +- Application structure and navigation +- List views (grid, kanban, calendar) +- Form views (simple, tabbed, wizard) +- Custom actions and buttons +- Page layouts and compact layouts + +### 4. [Analytics & Reporting](./04-analytics.md) +Build powerful dashboards and reports for data-driven insights. + +**Topics:** +- Dashboard widgets (metrics, charts, tables) +- Report types (tabular, summary, matrix) +- Chart configurations +- Filters and grouping +- Real-time analytics + +### 5. [Security Model](./05-security.md) +Implement enterprise-grade security with fine-grained permissions. + +**Topics:** +- Profiles and permission sets +- Object and field-level security +- Sharing rules and role hierarchy +- Organization-wide defaults +- Territory management + +### 6. [Automation](./06-automation.md) +Automate business processes with flows, workflows, and triggers. + +**Topics:** +- Screen flows (user-interactive) +- Autolaunched flows (background) +- Scheduled flows +- Approval processes +- Trigger-based automation + +### 7. [Integration](./07-integration.md) +Connect with external systems and third-party services. + +**Topics:** +- REST and GraphQL APIs +- Webhook connectors +- Database connectors (SQL, NoSQL) +- SaaS integrations +- ETL processes + +### 8. [AI Capabilities](./08-ai-capabilities.md) +Leverage AI agents and RAG pipelines for intelligent automation. + +**Topics:** +- AI agent types (assistant, worker, analyst) +- RAG pipeline configuration +- Knowledge base integration +- Natural language queries +- Predictive analytics + +## 🏗️ Application Architecture + +### Domain-Driven Design + +The CRM application is organized by business domains: + +``` +src/ +├── domains/ +│ ├── sales/ # Sales objects (Account, Opportunity, Lead, Quote, Contract) +│ ├── service/ # Service objects (Case, Task) +│ ├── marketing/ # Marketing objects (Campaign) +│ ├── products/ # Product catalog +│ └── analytics/ # Analytics and reporting +├── ui/ # User interface definitions +│ ├── dashboards.ts # Dashboard configurations +│ ├── reports.ts # Report definitions +│ └── actions.ts # Custom actions +├── security/ # Security configurations +│ ├── profiles.ts # User profiles +│ └── sharing-rules.ts # Sharing and permissions +├── automation/ # Business process automation +│ └── flows.ts # Flow definitions +├── ai/ # AI and ML configurations +│ ├── agents.ts # AI agents +│ └── rag-pipelines.ts # RAG pipelines +└── integration/ # External integrations + └── connectors.ts # API connectors +``` + +## 🚀 Quick Start + +### 1. Install Dependencies + +```bash +pnpm install +``` + +### 2. Build the Application + +```bash +pnpm --filter @example/app-crm build +``` + +### 3. Run the Application + +```bash +pnpm --filter @example/app-crm dev +``` + +### 4. Deploy to Production + +```bash +pnpm --filter @example/app-crm deploy +``` + +## 📦 Core Objects + +### Sales Domain +- **Account** - Companies and organizations +- **Contact** - People at accounts +- **Lead** - Prospective customers +- **Opportunity** - Sales deals in progress +- **Quote** - Price quotes for customers +- **Contract** - Legal agreements + +### Service Domain +- **Case** - Customer support tickets +- **Task** - To-do items and activities + +### Marketing Domain +- **Campaign** - Marketing campaigns and initiatives + +### Product Domain +- **Product** - Product catalog + +## 🔧 Configuration + +The application is configured in `objectstack.config.ts`: + +```typescript +export default defineStack({ + manifest: { + id: 'com.example.crm', + version: '2.0.0', + type: 'app', + name: 'CRM App', + }, + + objects: [...], + apis: [...], + actions: [...], + dashboards: [...], + reports: [...], + apps: [...], +}); +``` + +## 🎓 Best Practices + +### Naming Conventions + +1. **Configuration Keys (TypeScript Props):** Use `camelCase` + - `maxLength`, `referenceFilters`, `defaultValue` + +2. **Machine Names (Data Values):** Use `snake_case` + - `account_number`, `first_name`, `close_date` + +3. **Object Names:** Use `snake_case` + - `account`, `opportunity`, `case` + +4. **Field Names:** Use `snake_case` + - `account_number`, `annual_revenue`, `is_active` + +### Schema Design + +Always start with Zod schemas: + +```typescript +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const MyObject = ObjectSchema.create({ + name: 'my_object', + label: 'My Object', + + fields: { + my_field: Field.text({ + label: 'My Field', + required: true, + }), + }, +}); +``` + +### Type Safety + +Use type inference from Zod schemas: + +```typescript +import { z } from 'zod'; + +const MySchema = z.object({ + name: z.string(), +}); + +type MyType = z.infer; +``` + +## 🔒 Security + +The application implements enterprise-grade security: + +- **5 User Profiles** (System Admin, Sales Manager, Sales Rep, Service Agent, Marketing User) +- **Role Hierarchy** with 10 roles +- **Sharing Rules** for account, opportunity, and case objects +- **Territory Management** for geographic sales regions +- **Field-Level Security** for sensitive data + +## 🤖 AI Features + +### AI Agents + +- **Sales Assistant** - Lead qualification and opportunity management +- **Service Agent** - Customer support automation +- **Lead Enrichment** - Automatic data enrichment +- **Revenue Intelligence** - Pipeline analysis and forecasting +- **Email Campaign** - Marketing email generation + +### RAG Pipelines + +- **Sales Knowledge** - Sales playbook and best practices +- **Support Knowledge** - Customer support knowledge base +- **Product Information** - Product catalog and specs +- **Competitive Intelligence** - Market research and competitive analysis + +## 📊 Analytics + +### Dashboards + +- **Sales Dashboard** - Pipeline metrics and trends +- **Service Dashboard** - Support case analytics +- **Executive Dashboard** - High-level business metrics + +### Reports + +- Opportunities by Stage +- Won Opportunities by Owner +- Accounts by Industry +- Cases by Status and Priority +- SLA Performance +- Leads by Source +- Contacts by Account +- Tasks by Owner + +## 🔗 Resources + +- [ObjectStack Documentation](https://docs.objectstack.ai) +- [API Reference](https://api.objectstack.ai) +- [Community Forum](https://community.objectstack.ai) +- [GitHub Repository](https://github.com/objectstack-ai/spec) + +## 📝 License + +This example application is part of the ObjectStack specification repository and is licensed under the MIT License. + +## 🤝 Contributing + +Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details. + +--- + +**Next:** Start with [Data Modeling →](./01-data-modeling.md) From 2573f6b601602e9b93af2d69eafd42478d3241d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:30:55 +0000 Subject: [PATCH 4/5] Update CRM configuration with new domain structure and bilingual README Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/app-crm/README.md | 353 ++++++++++--------------- examples/app-crm/objectstack.config.ts | 152 +++++++---- 2 files changed, 253 insertions(+), 252 deletions(-) diff --git a/examples/app-crm/README.md b/examples/app-crm/README.md index 594b583524..04e3dabc4f 100644 --- a/examples/app-crm/README.md +++ b/examples/app-crm/README.md @@ -1,229 +1,170 @@ -# ObjectStack CRM Example - -这是一个全面的 CRM (客户关系管理) 示例,展示了 ObjectStack 协议的所有核心功能。 -This is a comprehensive CRM (Customer Relationship Management) example that demonstrates all core features of the ObjectStack Protocol. - -## 🎯 Features Demonstrated - -### Data Protocol (数据协议) - -#### Objects (对象) -- **Account** - 客户账户 (Companies and organizations) -- **Contact** - 联系人 (People associated with accounts) -- **Opportunity** - 销售机会 (Sales opportunities and deals) -- **Lead** - 潜在客户 (Potential customers) -- **Case** - 客户支持案例 (Customer support cases) -- **Task** - 任务活动 (Activities and to-do items) - -#### Field Types (字段类型) -- ✅ **Text/String**: text, textarea, email, url, phone, password -- ✅ **Rich Content**: markdown, html -- ✅ **Numbers**: number, currency, percent -- ✅ **Date/Time**: date, datetime, time -- ✅ **Logic**: boolean -- ✅ **Selection**: select, multiselect -- ✅ **Relational**: lookup, master_detail -- ✅ **Media**: avatar, image, file -- ✅ **Calculated**: formula, summary, autonumber - -#### Advanced Features (高级功能) -- ✅ **Validation Rules** - Script, uniqueness, state machine, format validation -- ✅ **Workflow Rules** - Field updates, email alerts, automated actions -- ✅ **Permissions** - Object-level and field-level security -- ✅ **History Tracking** - Audit trail for field changes -- ✅ **Relationships** - Lookup and master-detail relationships -- ✅ **Indexes** - Database performance optimization - -### UI Protocol (用户界面协议) - -#### List Views (列表视图) -- ✅ **Grid View** - Traditional table view -- ✅ **Kanban View** - Card-based workflow view -- ✅ **Calendar View** - Date-based visualization -- ✅ **Gantt View** - Timeline/project view - -#### Form Views (表单视图) -- ✅ **Simple Forms** - Single page layout -- ✅ **Tabbed Forms** - Multi-section layout -- ✅ **Dynamic Sections** - Collapsible sections - -#### Actions (操作) -- ✅ **Script Actions** - JavaScript execution -- ✅ **URL Actions** - Navigation -- ✅ **Modal Actions** - Popup forms -- ✅ **Flow Actions** - Visual process automation - -#### Dashboards (仪表盘) -- ✅ **Sales Dashboard** - Pipeline and revenue metrics -- ✅ **Service Dashboard** - Support case analytics -- ✅ **Executive Dashboard** - High-level overview - -#### Reports (报表) -- ✅ **Tabular Reports** - Simple lists -- ✅ **Summary Reports** - Grouped data -- ✅ **Matrix Reports** - Cross-tabulation -- ✅ **Charts** - Bar, line, pie, donut, funnel - -## 📂 Structure +# ObjectStack Enterprise CRM Application + +[English](#english) | [中文](#中文) + +--- + +## English + +### 🎯 Overview + +**Enterprise CRM** is a comprehensive, production-ready Customer Relationship Management system built on the ObjectStack Protocol. It demonstrates all 128+ protocol modules across 15 categories, showcasing enterprise-grade architecture following Salesforce and ServiceNow best practices. + +### ✨ Key Features + +#### 📊 **Complete Data Model** +- **10 Core Objects** organized by domain (Sales, Service, Marketing, Products) +- **50+ Field Types** including advanced types (Location, Color, Address) +- **Comprehensive Relationships** with lookups and master-detail +- **Smart Validations** with script-based rules and formulas + +#### 🔒 **Enterprise Security** +- **5 User Profiles** (Admin, Sales Manager, Sales Rep, Service Agent, Marketing User) +- **Role Hierarchy** with 10 roles +- **Sharing Rules** (criteria-based, owner-based, territory-based) +- **Field-Level Security** for sensitive data +- **Organization-Wide Defaults** for baseline access control + +#### 🤖 **AI-Powered Automation** +- **5 AI Agents** (Sales Assistant, Service Agent, Lead Enrichment, Revenue Intelligence, Email Campaign) +- **4 RAG Pipelines** for knowledge retrieval +- **Natural Language Queries** for intuitive data access +- **Predictive Analytics** (lead scoring, revenue forecasting) + +#### ⚡ **Business Process Automation** +- **5 Automated Flows** (Lead Conversion, Opportunity Approval, Case Escalation, Quote Generation, Campaign Enrollment) +- **Workflow Rules** for field updates and notifications +- **Approval Processes** for large deals and contracts +- **Scheduled Jobs** for batch processing + +#### 📈 **Analytics & Reporting** +- **3 Interactive Dashboards** (Sales, Service, Executive) +- **8 Pre-built Reports** (opportunities, accounts, cases, leads, tasks) +- **Real-time Metrics** with KPIs and trends +- **Custom Charts** (funnel, bar, line, pie, table) + +### 📁 Architecture ``` -examples/crm/ -├── src/ -│ ├── domains/ -│ │ └── crm/ -│ │ ├── account.object.ts # Account object with all field types -│ │ ├── contact.object.ts # Contact with master-detail -│ │ ├── opportunity.object.ts # Opportunity with workflow -│ │ ├── lead.object.ts # Lead with conversion logic -│ │ ├── case.object.ts # Case with SLA tracking -│ │ └── task.object.ts # Task with polymorphic relations -│ └── ui/ -│ ├── actions.ts # Custom actions -│ ├── dashboards.ts # Dashboard definitions -│ └── reports.ts # Report definitions -├── objectstack.config.ts # App configuration -└── README.md # This file +src/ +├── domains/ # Domain-Driven Design +│ ├── sales/ # Account, Contact, Lead, Opportunity, Quote, Contract +│ ├── service/ # Case, Task +│ ├── marketing/ # Campaign +│ └── products/ # Product +├── ui/ # User Interface +│ ├── dashboards.ts # 3 dashboards +│ ├── reports.ts # 8 reports +│ └── actions.ts # Custom actions +├── security/ # Security Model +│ ├── profiles.ts # 5 profiles +│ └── sharing-rules.ts # Sharing and OWD +├── automation/ # Business Logic +│ └── flows.ts # 5 flows +├── ai/ # AI & Machine Learning +│ ├── agents.ts # 5 AI agents +│ └── rag-pipelines.ts # 4 RAG pipelines +└── server/ # Custom APIs + └── apis.ts # REST endpoints ``` -## 🚀 Key Highlights - -### 1. Account Object -Demonstrates: -- Autonumber fields (`account_number`) -- Formula fields (`full_address`) -- Select with custom colors -- Kanban view by type -- Validation rules (positive revenue, unique name) -- Workflow automation (update last activity) - -### 2. Contact Object -Demonstrates: -- Master-detail relationship to Account -- Formula field (`full_name`) -- Email and phone field formats -- Avatar field -- Multiple list views (grid, kanban, calendar) -- Tabbed form layout - -### 3. Opportunity Object -Demonstrates: -- Complex workflow with stage-based automation -- State machine validation for stage progression -- Multiple visualizations (grid, kanban, gantt) -- Probability and forecast calculations -- History tracking for audit trail -- Reference filters (contact filtered by account) - -### 4. Lead Object -Demonstrates: -- Lead conversion process -- Boolean flags (is_converted) -- Readonly fields for conversion tracking -- Kanban view by status -- Lead source tracking - -### 5. Case Object -Demonstrates: -- SLA tracking and violations -- Customer satisfaction ratings -- Escalation workflow -- Priority-based automation -- Resolution time calculation -- Multiple status transitions - -### 6. Task Object -Demonstrates: -- Polymorphic relationships (related_to multiple objects) -- Recurring task support -- Time tracking (estimated vs actual) -- Progress percentage -- Calendar visualization -- Overdue detection - -### 7. UI Components - -**Actions:** -- Convert Lead (Flow action) -- Clone Opportunity (Script action) -- Send Email (Modal action) -- Mass Update (Bulk action with parameters) - -**Dashboards:** -- Metric widgets (KPIs) -- Chart widgets (bar, line, pie, funnel) -- Table widgets (top lists) -- Grid layout system - -**Reports:** -- Summary reports with grouping -- Matrix reports (2D grouping) -- Embedded charts -- Filter criteria -- Aggregations (sum, avg, count) - -## 💡 Usage - -This package is part of the `examples` workspace. To build it and verify types: +### 📚 Documentation + +Comprehensive guides covering all aspects: + +1. **[Data Modeling](./docs/01-data-modeling.md)** - Objects, fields, relationships, validations +2. **[Business Logic](./docs/02-business-logic.md)** - Workflows, triggers, formulas +3. **[Security](./docs/05-security.md)** - Profiles, roles, sharing, permissions +4. **[AI Capabilities](./docs/08-ai-capabilities.md)** - Agents, RAG, NLQ, ML + +### 🚀 Quick Start ```bash -# From monorepo root +# Install dependencies pnpm install -# Build the spec package first -pnpm --filter @objectstack/spec build - -# Build this example +# Build the application pnpm --filter @example/app-crm build -# Run type checking -pnpm --filter @example/app-crm typecheck +# Run development server +pnpm --filter @example/app-crm dev ``` -## 📖 Learning Resources +### 📦 What's Included + +| Category | Count | Examples | +|----------|-------|----------| +| **Objects** | 10 | Account, Opportunity, Case, Product | +| **Fields** | 100+ | AutoNumber, Formula, Lookup, Address | +| **Profiles** | 5 | Admin, Sales Manager, Sales Rep | +| **Sharing Rules** | 5+ | Criteria-based, Territory-based | +| **AI Agents** | 5 | Sales Assistant, Service Agent | +| **RAG Pipelines** | 4 | Sales Knowledge, Support KB | +| **Flows** | 5 | Lead Conversion, Approval | +| **Dashboards** | 3 | Sales, Service, Executive | +| **Reports** | 8 | Opportunities, Cases, Leads | + +--- + +## 中文 + +### 🎯 概述 + +**企业级CRM** 是基于 ObjectStack 协议构建的综合性、生产就绪的客户关系管理系统。它展示了15个类别中的128+协议模块,遵循 Salesforce 和 ServiceNow 的企业级架构最佳实践。 -**For Beginners:** -1. Start with [Todo Example](../todo/) for basics -2. Review [Basic Protocol Examples](../basic/) to understand individual protocols -3. Then explore this CRM example for comprehensive implementation +### ✨ 核心特性 -**Protocol References in Basic Examples:** -- [Stack Definition](../basic/stack-definition-example.ts) - How to use `defineStack()` -- [Capabilities](../basic/capabilities-example.ts) - Runtime capabilities configuration -- [Auth & Permissions](../basic/auth-permission-example.ts) - RBAC and RLS patterns -- [Automation](../basic/automation-example.ts) - Workflows and approvals -- [AI & RAG](../basic/ai-rag-example.ts) - AI integration patterns +#### 📊 **完整数据模型** +- **10个核心对象** 按领域组织(销售、服务、营销、产品) +- **50+字段类型** 包括高级类型(位置、颜色、地址) +- **全面的关系** 查找和主从关系 +- **智能验证** 基于脚本的规则和公式 -**In This Example:** -Each object file contains detailed comments explaining: -- Field configuration options -- View setup patterns -- Validation rule syntax -- Workflow automation examples +#### 🔒 **企业级安全** +- **5种用户配置文件** (管理员、销售经理、销售代表、服务代表、营销用户) +- **角色层次结构** 包含10个角色 +- **共享规则** (基于条件、基于所有者、基于区域) +- **字段级安全** 保护敏感数据 +- **组织范围默认值** 基线访问控制 -Study the code to understand: -1. How to define object schemas with Zod -2. How to create relationships between objects -3. How to set up validation and workflow rules -4. How to configure different view types -5. How to create actions, dashboards, and reports +#### 🤖 **AI驱动自动化** +- **5个AI代理** (销售助手、服务代理、线索丰富、收入智能、邮件营销) +- **4个RAG管道** 用于知识检索 +- **自然语言查询** 直观的数据访问 +- **预测分析** (线索评分、收入预测) -## 🎓 Protocol Coverage +#### ⚡ **业务流程自动化** +- **5个自动化流程** (线索转换、商机审批、案例升级、报价生成、营销注册) +- **工作流规则** 字段更新和通知 +- **审批流程** 大型交易和合同 +- **定时任务** 批处理 -This example demonstrates: +#### 📈 **分析与报表** +- **3个交互式仪表板** (销售、服务、高管) +- **8个预制报表** (商机、客户、案例、线索、任务) +- **实时指标** KPI和趋势 +- **自定义图表** (漏斗、柱状、折线、饼图、表格) -| Protocol Area | Coverage | Examples | -|--------------|----------|----------| -| **Data Protocol** | 100% | All field types, validations, workflows | -| **UI Protocol** | 100% | All view types, actions, dashboards, reports | -| **System Protocol** | 80% | App manifest, menus, settings | +### 📚 文档 -## 🔗 References +1. **[数据建模](./docs/01-data-modeling.md)** - 对象、字段、关系、验证 +2. **[业务逻辑](./docs/02-business-logic.md)** - 工作流、触发器、公式 +3. **[安全模型](./docs/05-security.md)** - 配置文件、角色、共享、权限 +4. **[AI能力](./docs/08-ai-capabilities.md)** - 代理、RAG、NLQ、机器学习 -- [ObjectStack Documentation](https://objectstack.dev) -- [Protocol Specification](../../packages/spec/README.md) -- [Field Types Reference](../../packages/spec/src/data/field.zod.ts) -- [Object Schema Reference](../../packages/spec/src/data/object.zod.ts) +### 🚀 快速开始 + +```bash +# 安装依赖 +pnpm install + +# 构建应用 +pnpm --filter @example/app-crm build + +# 运行开发服务器 +pnpm --filter @example/app-crm dev +``` -## 📝 License +--- -MIT +**构建全球最顶级的企业管理软件平台** 🚀 diff --git a/examples/app-crm/objectstack.config.ts b/examples/app-crm/objectstack.config.ts index f747404f68..127791f0f0 100644 --- a/examples/app-crm/objectstack.config.ts +++ b/examples/app-crm/objectstack.config.ts @@ -1,112 +1,172 @@ import { defineStack } from '@objectstack/spec'; import { App } from '@objectstack/spec/ui'; -import { Account } from './src/domains/crm/account.object'; -import { Contact } from './src/domains/crm/contact.object'; -import { Opportunity } from './src/domains/crm/opportunity.object'; -import { Lead } from './src/domains/crm/lead.object'; -import { Case } from './src/domains/crm/case.object'; -import { Task } from './src/domains/crm/task.object'; + +// Sales Domain Objects +import { Account } from './src/domains/sales/account.object'; +import { Contact } from './src/domains/sales/contact.object'; +import { Opportunity } from './src/domains/sales/opportunity.object'; +import { Lead } from './src/domains/sales/lead.object'; +import { Quote } from './src/domains/sales/quote.object'; +import { Contract } from './src/domains/sales/contract.object'; + +// Service Domain Objects +import { Case } from './src/domains/service/case.object'; +import { Task } from './src/domains/service/task.object'; + +// Marketing Domain Objects +import { Campaign } from './src/domains/marketing/campaign.object'; + +// Product Domain Objects +import { Product } from './src/domains/products/product.object'; + +// APIs import { PipelineStatsApi, LeadConvertApi } from './src/server'; +// UI Configuration import { CrmActions } from './src/ui/actions'; import { CrmDashboards } from './src/ui/dashboards'; import { CrmReports } from './src/ui/reports'; +// Security Configuration +import { CrmProfiles } from './src/security/profiles'; +import { CrmSharingRules } from './src/security/sharing-rules'; + +// AI Configuration +import { CrmAgents } from './src/ai/agents'; +import { CrmRagPipelines } from './src/ai/rag-pipelines'; + +// Automation +import { CrmFlows } from './src/automation/flows'; + export default defineStack({ manifest: { id: 'com.example.crm', - version: '2.0.0', + version: '3.0.0', type: 'app', - name: 'CRM App', - description: 'Comprehensive CRM example demonstrating all ObjectStack Protocol features' + name: 'Enterprise CRM', + description: 'Comprehensive enterprise CRM demonstrating all ObjectStack Protocol features including AI, security, and automation', + author: 'ObjectStack Team', + repository: 'https://github.com/objectstack-ai/spec', + license: 'MIT', }, - // All objects in the app - // - // Method 1: Code-First (Explicit Import) - // Directly import TypeScript object definitions. This provides strict type checking. + // Data Model - Organized by Domain objects: [ + // Sales Domain (6 objects) Account, Contact, - Opportunity, Lead, + Opportunity, + Quote, + Contract, + + // Service Domain (2 objects) Case, - Task + Task, + + // Marketing Domain (1 object) + Campaign, + + // Product Domain (1 object) + Product, ], - - // Method 2: Schema-First (Glob Patterns) - // Alternatively, you can use glob patterns to load objects from the file system. - // This is common for metadata-driven development or when using YAML/JSON files. - // objects: [ - // './src/domains/**/*.object.ts', - // './src/domains/**/*.object.yml' - // ], // Custom APIs apis: [ PipelineStatsApi, - LeadConvertApi + LeadConvertApi, ], - - // Actions available in the app - actions: Object.values(CrmActions), - // Dashboards + // User Interface + actions: Object.values(CrmActions), dashboards: Object.values(CrmDashboards), - - // Reports reports: Object.values(CrmReports), + // Security Configuration + profiles: Object.values(CrmProfiles), + sharingRules: [ + CrmSharingRules.AccountTeamSharingRule, + CrmSharingRules.OpportunitySalesSharingRule, + CrmSharingRules.CaseEscalationSharingRule, + ...CrmSharingRules.TerritorySharingRules, + ], + roleHierarchy: CrmSharingRules.RoleHierarchy, + organizationDefaults: CrmSharingRules.OrganizationDefaults, + + // AI & Automation + agents: Object.values(CrmAgents), + ragPipelines: Object.values(CrmRagPipelines), + flows: Object.values(CrmFlows), + + // Application Definition apps: [ App.create({ - name: 'crm_example', - label: 'CRM App', + name: 'crm_enterprise', + label: 'Enterprise CRM', icon: 'briefcase', branding: { primaryColor: '#4169E1', + secondaryColor: '#00AA00', logo: '/assets/crm-logo.png', favicon: '/assets/crm-favicon.ico', }, - // Navigation menu structure + // Enhanced Navigation Menu Structure navigation: [ { id: 'group_sales', type: 'group', label: 'Sales', + icon: 'chart-line', children: [ - { id: 'nav_lead', type: 'object', objectName: 'lead', label: 'Leads' }, - { id: 'nav_account', type: 'object', objectName: 'account', label: 'Accounts' }, - { id: 'nav_contact', type: 'object', objectName: 'contact', label: 'Contacts' }, - { id: 'nav_opportunity', type: 'object', objectName: 'opportunity', label: 'Opportunities' }, - { id: 'nav_sales_dashboard', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Dashboard' }, + { id: 'nav_lead', type: 'object', objectName: 'lead', label: 'Leads', icon: 'user-plus' }, + { id: 'nav_account', type: 'object', objectName: 'account', label: 'Accounts', icon: 'building' }, + { id: 'nav_contact', type: 'object', objectName: 'contact', label: 'Contacts', icon: 'user' }, + { id: 'nav_opportunity', type: 'object', objectName: 'opportunity', label: 'Opportunities', icon: 'bullseye' }, + { id: 'nav_quote', type: 'object', objectName: 'quote', label: 'Quotes', icon: 'file-invoice' }, + { id: 'nav_contract', type: 'object', objectName: 'contract', label: 'Contracts', icon: 'file-signature' }, + { id: 'nav_sales_dashboard', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Dashboard', icon: 'chart-bar' }, ] }, { id: 'group_service', type: 'group', label: 'Service', + icon: 'headset', + children: [ + { id: 'nav_case', type: 'object', objectName: 'case', label: 'Cases', icon: 'life-ring' }, + { id: 'nav_task', type: 'object', objectName: 'task', label: 'Tasks', icon: 'tasks' }, + { id: 'nav_service_dashboard', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Dashboard', icon: 'chart-pie' }, + ] + }, + { + id: 'group_marketing', + type: 'group', + label: 'Marketing', + icon: 'megaphone', children: [ - { id: 'nav_case', type: 'object', objectName: 'case', label: 'Cases' }, - { id: 'nav_service_dashboard', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Dashboard' }, + { id: 'nav_campaign', type: 'object', objectName: 'campaign', label: 'Campaigns', icon: 'bullhorn' }, + { id: 'nav_lead_marketing', type: 'object', objectName: 'lead', label: 'Leads', icon: 'user-plus' }, ] }, { - id: 'group_activities', + id: 'group_products', type: 'group', - label: 'Activities', + label: 'Products', + icon: 'box', children: [ - { id: 'nav_task', type: 'object', objectName: 'task', label: 'Tasks' }, + { id: 'nav_product', type: 'object', objectName: 'product', label: 'Products', icon: 'box-open' }, ] }, { id: 'group_analytics', type: 'group', label: 'Analytics', + icon: 'chart-area', children: [ - { id: 'nav_exec_dashboard', type: 'dashboard', dashboardName: 'executive_dashboard', label: 'Executive Dashboard' }, - { id: 'nav_analytics_sales_db', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Dashboard' }, - { id: 'nav_analytics_service_db', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Dashboard' }, + { id: 'nav_exec_dashboard', type: 'dashboard', dashboardName: 'executive_dashboard', label: 'Executive Dashboard', icon: 'tachometer-alt' }, + { id: 'nav_analytics_sales_db', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Analytics', icon: 'chart-line' }, + { id: 'nav_analytics_service_db', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Analytics', icon: 'chart-pie' }, ] } ] From 56207bc7c1ce6f6901be49e29ac37f67ea78c28a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 05:34:25 +0000 Subject: [PATCH 5/5] Add comprehensive development standards and specifications document Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/app-crm/docs/STANDARDS.md | 574 +++++++++++++++++++++++++++++ 1 file changed, 574 insertions(+) create mode 100644 examples/app-crm/docs/STANDARDS.md diff --git a/examples/app-crm/docs/STANDARDS.md b/examples/app-crm/docs/STANDARDS.md new file mode 100644 index 0000000000..f7cb5180ad --- /dev/null +++ b/examples/app-crm/docs/STANDARDS.md @@ -0,0 +1,574 @@ +# CRM Application Development Standards & Specifications + +本文档定义了基于ObjectStack协议开发企业管理软件应用市场的最佳方案和编写规范。 +This document defines best practices and coding standards for developing enterprise management software applications using the ObjectStack Protocol. + +--- + +## 📁 1. Folder Structure Standards + +### Recommended Structure + +``` +app-name/ +├── docs/ # 📚 Documentation +│ ├── README.md # Master guide +│ ├── 01-data-modeling.md # Data design standards +│ ├── 02-business-logic.md # Business rules +│ ├── 03-ui-design.md # Interface design +│ ├── 04-analytics.md # Reports & dashboards +│ ├── 05-security.md # Security model +│ ├── 06-automation.md # Workflows & flows +│ ├── 07-integration.md # External systems +│ └── 08-ai-capabilities.md # AI & ML features +├── src/ +│ ├── domains/ # 📊 Domain Objects (DDD) +│ │ ├── sales/ # Sales domain +│ │ │ ├── *.object.ts # Object definitions +│ │ │ └── *.hook.ts # Event hooks +│ │ ├── service/ # Service domain +│ │ ├── marketing/ # Marketing domain +│ │ ├── products/ # Product catalog +│ │ └── analytics/ # Analytics & BI +│ ├── ui/ # 🎨 User Interface +│ │ ├── apps.ts # Application definitions +│ │ ├── views.ts # List & form views +│ │ ├── actions.ts # Custom actions +│ │ ├── dashboards.ts # Dashboard configs +│ │ └── reports.ts # Report definitions +│ ├── security/ # 🔒 Security +│ │ ├── profiles.ts # User profiles +│ │ ├── permission-sets.ts # Permission sets +│ │ └── sharing-rules.ts # Sharing model +│ ├── automation/ # ⚡ Automation +│ │ ├── flows.ts # Visual flows +│ │ ├── workflows.ts # Workflow rules +│ │ └── triggers.ts # Database triggers +│ ├── ai/ # 🤖 AI & Machine Learning +│ │ ├── agents.ts # AI agents +│ │ ├── rag-pipelines.ts # RAG configs +│ │ └── models.ts # ML models +│ ├── integration/ # 🔗 Integration +│ │ ├── connectors.ts # External connectors +│ │ ├── webhooks.ts # Webhook handlers +│ │ └── apis.ts # Custom APIs +│ └── server/ # 🖥️ Backend +│ ├── apis.ts # REST/GraphQL APIs +│ └── index.ts # Server entry +├── tests/ # 🧪 Tests +│ ├── unit/ # Unit tests +│ ├── integration/ # Integration tests +│ └── e2e/ # End-to-end tests +├── objectstack.config.ts # ⚙️ Main configuration +├── package.json +└── README.md # Project overview +``` + +### Key Principles + +1. **Domain-Driven Design**: Organize objects by business domain, not by type +2. **Separation of Concerns**: Keep data, UI, security, and automation separate +3. **Clear Naming**: Use descriptive folder and file names +4. **Documentation-First**: Comprehensive docs for every module + +--- + +## 📊 2. Data Modeling Standards + +### Object Definition Pattern + +```typescript +import { ObjectSchema, Field } from '@objectstack/spec/data'; + +export const MyObject = ObjectSchema.create({ + // ✅ Metadata (Required) + name: 'my_object', // Machine name (snake_case) + label: 'My Object', // Display name + pluralLabel: 'My Objects', // Plural form + icon: 'icon-name', // Icon identifier + description: '...', // Help text + + // ✅ Display Configuration + titleFormat: '{field1} - {field2}', + compactLayout: ['field1', 'field2', 'field3'], + + // ✅ Fields Definition + fields: { + // Use Field.* helpers + field_name: Field.text({ + label: 'Field Label', + required: true, + }), + }, + + // ✅ Performance + indexes: [ + { fields: ['field_name'], unique: false }, + ], + + // ✅ Capabilities + enable: { + trackHistory: true, + searchable: true, + apiEnabled: true, + files: true, + feeds: true, + }, + + // ✅ Business Rules + validations: [...], + workflows: [...], +}); +``` + +### Field Type Selection Guide + +| Data Type | Field Type | Use Case | +|-----------|-----------|----------| +| Short text | `Field.text()` | Names, codes, short strings | +| Long text | `Field.textarea()` | Descriptions, notes | +| Rich content | `Field.markdown()` | Formatted text with links | +| Number | `Field.number()` | Quantities, counts | +| Money | `Field.currency()` | Prices, revenues | +| Percentage | `Field.percent()` | Rates, ratios | +| Date | `Field.date()` | Birthdays, deadlines | +| Date+Time | `Field.datetime()` | Timestamps, appointments | +| Yes/No | `Field.boolean()` | Flags, toggles | +| Choice | `Field.select()` | Status, category, type | +| Multiple choice | `Field.select({ multiple: true })` | Tags, skills | +| Reference | `Field.lookup()` | Relationships | +| Location | `Field.location()` | GPS coordinates | +| Address | `Field.address()` | Mailing addresses | +| Color | `Field.color()` | Brand colors, themes | +| Auto-number | `Field.autonumber()` | Sequential IDs | +| Calculated | `Field.formula()` | Computed values | + +### Relationship Patterns + +```typescript +// ✅ Simple Lookup (Many-to-One) +account: Field.lookup('account', { + label: 'Account', + required: true, +}) + +// ✅ Filtered Lookup +contact: Field.lookup('contact', { + label: 'Contact', + referenceFilters: { + account: '{account}', // Same account + is_active: true, + } +}) + +// ✅ Self-Referencing (Hierarchy) +parent_account: Field.lookup('account', { + label: 'Parent Account', +}) +``` + +--- + +## 🎨 3. UI Design Standards + +### Application Structure + +```typescript +import { App } from '@objectstack/spec/ui'; + +App.create({ + name: 'app_name', + label: 'App Name', + icon: 'icon-name', + branding: { + primaryColor: '#4169E1', + secondaryColor: '#00AA00', + logo: '/assets/logo.png', + }, + + navigation: [ + { + id: 'group_sales', + type: 'group', + label: 'Sales', + children: [ + { id: 'nav_account', type: 'object', objectName: 'account', label: 'Accounts' }, + { id: 'nav_dashboard', type: 'dashboard', dashboardName: 'sales_dashboard' }, + ] + } + ] +}); +``` + +### Dashboard Best Practices + +```typescript +// ✅ Use meaningful names +name: 'sales_dashboard', + +// ✅ Group related metrics +widgets: [ + // Row 1: KPIs + { type: 'metric', ... }, + + // Row 2: Charts + { type: 'bar', ... }, + + // Row 3: Lists + { type: 'table', ... }, +] + +// ✅ Use grid layout +layout: { x: 0, y: 0, w: 3, h: 2 } +``` + +--- + +## 🔒 4. Security Standards + +### Profile Structure + +```typescript +export const MyProfile: Profile = { + name: 'profile_name', + label: 'Profile Label', + + // Object permissions (CRUD) + objectPermissions: { + account: { + create: true, + read: true, + update: true, + delete: false, + viewAll: false, + modifyAll: false, + }, + }, + + // Field permissions + fieldPermissions: { + account: { + sensitive_field: { read: false, update: false }, + }, + }, + + // Tab visibility + tabVisibility: { + account: 'default', // Default tab + lead: 'available', // Available but not default + admin: 'hidden', // Hidden + }, +}; +``` + +### Sharing Rule Pattern + +```typescript +export const MySharingRule: SharingRule = { + name: 'rule_name', + objectName: 'object_name', + type: 'criteria_based', + + criteria: { + field: { $eq: 'value' }, + }, + + sharedWith: { + type: 'role', + roles: ['role_name'], + }, + + accessLevel: 'read_write', +}; +``` + +--- + +## ⚡ 5. Automation Standards + +### Flow Definition + +```typescript +export const MyFlow: Flow = { + name: 'flow_name', + label: 'Flow Label', + description: 'What this flow does', + type: 'autolaunched', // or 'screen', 'scheduled' + + triggerType: 'on_create', + objectName: 'object_name', + criteria: 'condition', + + variables: [...], + + steps: [ + { + id: 'step_1', + type: 'record_lookup', + ... + }, + ], +}; +``` + +### Workflow Rule Pattern + +```typescript +workflows: [ + { + name: 'workflow_name', + objectName: 'object_name', + triggerType: 'on_update', + criteria: 'ISCHANGED(field)', + actions: [ + { + name: 'action_name', + type: 'field_update', + field: 'field_name', + value: 'value', + } + ], + active: true, + } +] +``` + +--- + +## 🤖 6. AI Configuration Standards + +### AI Agent Pattern + +```typescript +export const MyAgent: Agent = { + name: 'agent_name', + label: 'Agent Label', + description: 'What this agent does', + + role: 'assistant', // or 'worker', 'analyst', 'creator' + + instructions: `Clear instructions for the AI...`, + + model: { + provider: 'openai', + model: 'gpt-4', + temperature: 0.7, + maxTokens: 2000, + }, + + tools: [...], + knowledge: {...}, + triggers: [...], +}; +``` + +### RAG Pipeline Pattern + +```typescript +export const MyRAG: RagPipeline = { + name: 'rag_name', + label: 'RAG Label', + + indexes: [ + { + name: 'index_name', + type: 'vector', + sources: [...], + embedding: {...}, + chunking: {...}, + } + ], + + retrieval: { + strategy: 'hybrid', + vectorSearch: {...}, + }, + + generation: { + model: {...}, + promptTemplate: `...`, + }, +}; +``` + +--- + +## 📝 7. Naming Conventions + +### Configuration Keys (TypeScript) +✅ Use **camelCase** for all TypeScript property names: +```typescript +maxLength: 255 +referenceFilters: {...} +defaultValue: true +primaryColor: '#4169E1' +``` + +### Data Values (Machine Names) +✅ Use **snake_case** for all data identifiers: +```typescript +name: 'account_number' +objectName: 'opportunity' +field: 'close_date' +status: 'in_progress' +``` + +### Object Names +✅ Use **snake_case**, singular, descriptive: +```typescript +'account' // ✅ Good +'contact' // ✅ Good +'opportunity' // ✅ Good + +'Account' // ❌ Bad (PascalCase) +'accounts' // ❌ Bad (plural) +'opp' // ❌ Bad (abbreviation) +``` + +### Field Names +✅ Use **snake_case**, descriptive, no suffixes: +```typescript +'first_name' // ✅ Good +'annual_revenue' // ✅ Good +'is_active' // ✅ Good (boolean prefix) +'account' // ✅ Good (lookup without _id) + +'firstName' // ❌ Bad (camelCase) +'revenue' // ❌ Bad (not descriptive) +'active' // ❌ Bad (boolean without prefix) +'account_id' // ❌ Bad (lookup with _id suffix) +``` + +--- + +## 📚 8. Documentation Standards + +### Required Documentation + +Every application must include: + +1. **README.md** - Project overview, quick start, features +2. **docs/01-data-modeling.md** - Object and field design +3. **docs/02-business-logic.md** - Validations, workflows +4. **docs/05-security.md** - Security model +5. **docs/08-ai-capabilities.md** - AI features (if applicable) + +### Documentation Template + +```markdown +# [Module Name] + +Brief description of what this module does. + +## Table of Contents + +1. [Section 1](#section-1) +2. [Section 2](#section-2) + +--- + +## Section 1 + +Content with examples... + +### Subsection + +```typescript +// Code example +``` + +### Best Practices + +✅ **DO:** +- Item 1 +- Item 2 + +❌ **DON'T:** +- Item 1 +- Item 2 +``` + +--- + +## 🧪 9. Testing Standards + +### Test Structure + +``` +tests/ +├── unit/ +│ ├── objects/ +│ │ └── account.test.ts +│ ├── validations/ +│ └── workflows/ +├── integration/ +│ ├── flows/ +│ └── apis/ +└── e2e/ + └── scenarios/ +``` + +### Test Pattern + +```typescript +import { describe, test, expect } from 'vitest'; + +describe('Account Object', () => { + test('should create account with valid data', async () => { + const account = await createRecord('account', { + name: 'Test Company', + type: 'prospect', + }); + + expect(account.name).toBe('Test Company'); + }); + + test('should validate required fields', async () => { + await expect( + createRecord('account', {}) + ).rejects.toThrow('Name is required'); + }); +}); +``` + +--- + +## ✅ 10. Review Checklist + +### Before Committing + +- [ ] All objects use `ObjectSchema.create()` +- [ ] Field names are `snake_case` +- [ ] Config keys are `camelCase` +- [ ] Lookups don't have `_id` suffix +- [ ] All validations have clear messages +- [ ] Documentation is up to date +- [ ] Tests pass +- [ ] No security vulnerabilities +- [ ] Code follows conventions + +### Before Deployment + +- [ ] All objects have proper indexes +- [ ] Security profiles configured +- [ ] Sharing rules tested +- [ ] AI agents tested +- [ ] Flows tested end-to-end +- [ ] Performance benchmarks met +- [ ] Documentation complete +- [ ] User training materials ready + +--- + +## 🎯 Summary + +Follow these standards to build: +- **Maintainable** code that's easy to understand +- **Scalable** applications that grow with your business +- **Secure** systems with enterprise-grade protection +- **Intelligent** solutions powered by AI +- **Well-documented** projects that teams can collaborate on + +--- + +**For more details, refer to individual documentation files in the `docs/` folder.**