Skip to content

Implement metadata-driven enterprise CRM on @objectstack/spec protocol#1

Merged
hotlong merged 4 commits intomainfrom
copilot/develop-crm-system-standardization
Jan 26, 2026
Merged

Implement metadata-driven enterprise CRM on @objectstack/spec protocol#1
hotlong merged 4 commits intomainfrom
copilot/develop-crm-system-standardization

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 26, 2026

Built a complete CRM system using declarative YAML metadata for all business objects, replacing traditional ORM with ObjectQL for type-safe queries, and integrating AI-native features at the architecture level.

Architecture

Metadata-Driven: All objects (Account, Contact, Opportunity, Contract) defined in YAML with fields, relationships, validation, and triggers. No schema hardcoded.

ObjectQL Engine: Type-safe query DSL replacing SQL:

const result = await db.query({
  object: 'Account',
  fields: ['Name', 'Industry', 'AnnualRevenue'],
  filters: { 
    Industry: { $in: ['Technology', 'Finance'] },
    AnnualRevenue: { $gt: 10000000 }
  },
  related: {
    Opportunities: {
      fields: ['Name', 'Amount', 'Stage'],
      filters: { Stage: { $ne: 'Closed Lost' } }
    }
  }
});

Event-Driven Automation: Triggers fire on data changes with full DB access via ObjectQL. Example: Opportunity → "Closed Won" auto-creates Contract, updates Account status, sends notifications.

Implementation

  • Core Objects (4): 23K lines YAML metadata with fields, relationships, list views, validation rules
  • Business Logic: ObjectQL engine + trigger system for workflow automation
  • UI Layer: amis framework + Tailwind for declarative dashboard configs (KPIs, pipeline chart, activity timeline)
  • AI Features: Smart Briefing analyzes activities/emails, generates summaries, sentiment, next-steps using LLM prompts
  • API Server: Express with 15+ RESTful endpoints for CRUD, queries, dashboard data, AI actions

Metadata Example

# src/metadata/Opportunity.object.yml
name: Opportunity
fields:
  - name: Stage
    type: select
    options:
      - label: 🔍 潜在客户
        value: Prospecting
        probability: 10
      - label: ✅ 成交
        value: Closed Won
        probability: 100
        isWon: true

triggers:
  - name: OpportunityStageChange
    event: afterUpdate
    file: triggers/OpportunityTrigger.ts
    conditions:
      - field: Stage
        changed: true

Documentation

Protocol spec, AI prompting guide, architecture diagrams, examples, and quickstart included for extensibility.

Original prompt

开发一套“顶流”的企业级 CRM 系统,核心在于标准化的元数据定义(即 @objectstack/spec 的落地)以及高度现代化的 UI/UX。
为了让 AI 能够准确执行你的意图,我为你设计了一套分阶段、模块化的 Prompt 工程体系。这一套提示词将引导 AI 从“阅读理解协议”开始,逐步深入到架构、逻辑和界面设计。
第一阶段:协议注入与角色设定 (Context Priming)
在使用任何具体指令前,必须先让 AI “学会”你的协议。如果 @objectstack/spec 的文档较长,建议先投喂核心定义。
Prompt 模板:

Role

你是一位精通企业级软件架构的资深 CTO,尤其擅长 Salesforce 架构理念与现代低代码协议的结合。你当前的任务是基于 @objectstack/spec 标准协议,协助我开发一套世界级的 CRM 系统。

Context: The Protocol

请仔细阅读以下 @objectstack/spec 的核心定义(或假设你已经通过知识库掌握):

  1. Metadata Driven: 所有对象(Account, Contact 等)均通过 JSON/YAML 描述。
  2. ObjectQL: 数据查询必须使用 ObjectQL 语法,而非 SQL。
  3. UI Engine: 前端渲染基于 amis 框架,样式使用 Tailwind CSS。
  4. Project Structure: 这里是标准的文件目录结构... [此处简述你的目录结构]

Goal

我们要对标 Salesforce 的功能深度,但拥有 Apple/Linear 级别的用户体验。

第二阶段:核心元数据建模 (Schema Design)
CRM 的地基是数据模型。你需要 AI 生成符合 spec 的 JSON/YAML 文件。
Prompt 模板:

Task: Design CRM Core Objects

请根据 @objectstack/spec 标准,为 CRM 系统设计核心对象模型:Account (客户)。

Requirements

  1. Fields Definition: 包含企业级 CRM 必备字段(如 Industry, AnnualRevenue, Rating, OwnerId 等)。请详细定义字段的 type, label, required, 以及 options (如果是 select 类型)。
  2. Relationships: 定义与 Contact (联系人) 和 Opportunity (商机) 的关系(Lookup 或 Master-Detail)。
  3. Advanced Features:
    • 启用 searchable: true 以支持全局搜索。
    • 定义 list_views (视图),包括 "My Accounts" 和 "New This Week"。
  4. Output Format: 直接输出符合 spec 的 .object.yml 代码块。

Reference

参考 Salesforce 的 Account 对象模型,但移除过时的字段,保持精简。

第三阶段:业务逻辑与自动化 (Business Logic)
利用 ObjectQL 和触发器实现业务流程。
Prompt 模板:

Task: Implement Business Automation

我需要为 Opportunity (商机) 对象实现一个自动化流程。

Scenario

当商机的 Stage 变更为 "Closed Won" 时,系统需要自动执行以下操作:

  1. 自动创建一个关联的 Contract (合同) 记录。
  2. Account 的状态更新为 "Active Customer"。
  3. 发送一个系统通知给销售总监。

Constraints

  • 使用 Node.js 编写触发器代码。
  • 数据操作必须使用 ObjectQL 语法 (e.g., db.doc.create, db.doc.update)。
  • 代码风格需要防御性编程,处理可能的空值异常。

Output

请提供完整的 Trigger 代码文件内容,包含必要的注释。

第四阶段:现代化 UI/UX 设计 (UI Generation)
这是体现“顶流”体验的关键。结合你喜欢的 Apple 风格、Tailwind 和 amis。
Prompt 模板:

Task: Design the Sales Dashboard UI

请基于 amis 规范和 ObjectUI 组件库,设计 CRM 的销售主管仪表盘 (Dashboard)。

Design Aesthetic

  • Style: 极简主义,参考 Apple macOS 或 Linear 的设计语言。
  • Visuals: 使用大圆角 (rounded-xl),轻微的边框 (border-gray-200),以及磨砂玻璃效果 (backdrop-blur)。
  • Typography: 使用高对比度的深色字体,标题清晰。

Components Needed

  1. KPI Cards: 顶部一排卡片,显示 "本季度营收"、"新增 Leads"、"Win Rate"。需使用 Tailwind 的 Grid 布局。
  2. Pipeline Chart: 一个漏斗图,展示各阶段商机的金额分布。
  3. Recent Activities: 一个列表组件,展示团队最新的跟进记录,带有头像和时间戳。

Output

请生成 amis 的 JSON 配置代码。在 className 中大量使用 Tailwind CSS 类来实现上述设计风格。

第五阶段:AI 原生能力集成 (AI Native Features)
针对你提到的“AI 原生”,需要在开发阶段就植入 Prompt 提示词。
Prompt 模板:

Task: Design AI Feature "Smart Briefing"

我想在 CRM 的客户详情页增加一个 "AI 智能简报" 功能。

Functionality

当用户访问客户页面时,后台读取该客户最近 10 条 Activity (跟进记录) 和关联的 Email,通过 LLM 生成一段 200 字的摘要,建议下一步行动。

Implementation Request

  1. 请设计这个功能的后端 ObjectStack Action 接口定义。
  2. 编写传给 LLM 的 System Prompt。不仅要总结,还要根据客户的行业(从 Account 字段获取)提供个性化的销售话术建议。
  3. 提供前端 amis 配置,使用一个带有 AI 图标的卡片来异步展示这个结果。

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits January 26, 2026 11:07
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Develop enterprise-level CRM system with standardized metadata Implement metadata-driven enterprise CRM on @objectstack/spec protocol Jan 26, 2026
Copilot AI requested a review from hotlong January 26, 2026 11:14
@hotlong hotlong marked this pull request as ready for review January 26, 2026 11:44
Copilot AI review requested due to automatic review settings January 26, 2026 11:44
@hotlong hotlong merged commit 84bd0bb into main Jan 26, 2026
4 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive enterprise CRM system using a metadata-driven architecture based on the @objectstack/spec protocol. The system aims to combine Salesforce-level functionality with Apple/Linear-inspired UI/UX design, featuring AI-native capabilities throughout.

Changes:

  • Complete metadata-driven CRM implementation with 4 core business objects (Account, Contact, Opportunity, Contract) defined in declarative YAML
  • ObjectQL query engine providing type-safe database operations as an alternative to SQL
  • Event-driven business automation through triggers, AI-powered customer insights, and modern dashboard UI built with amis and Tailwind CSS
  • Comprehensive documentation including protocol specifications, architecture guides, examples, and AI prompting templates

Reviewed changes

Copilot reviewed 19 out of 22 changed files in this pull request and generated 22 comments.

Show a summary per file
File Description
package.json Project dependencies and scripts configuration
tsconfig.json TypeScript compiler configuration with strict mode enabled
tailwind.config.js Tailwind CSS customization with extended colors and border radius
src/metadata/Account.object.yml Customer/company object definition with 30+ fields, relationships, views, and validation
src/metadata/Contact.object.yml Contact object with master-detail relationship to Account
src/metadata/Opportunity.object.yml Sales pipeline object with stage management and trigger definitions
src/metadata/Contract.object.yml Contract management object with auto-numbering and signature tracking
src/engine/objectql.ts Type-safe query engine interface (currently mock implementation)
src/triggers/OpportunityTrigger.ts Business automation for opportunity stage changes
src/actions/AISmartBriefing.ts AI-powered customer insights with LLM integration (mock)
src/server.ts Express server with 15+ RESTful API endpoints
src/ui/dashboard/SalesDashboard.ts Sales executive dashboard with KPIs, charts, and activity timeline
src/ui/components/AISmartBriefingCard.ts AI briefing component with sentiment analysis and recommendations
docs/OBJECTSTACK_SPEC.md Complete protocol specification and best practices
docs/ARCHITECTURE.md System architecture documentation with deployment strategies
docs/AI_PROMPT_GUIDE.md Guide for AI prompt engineering with templates
docs/EXAMPLES.md Practical code examples and workflows
README.md Project overview and feature documentation
QUICKSTART.md Quick start guide for development setup
PROJECT_SUMMARY.md Comprehensive project statistics and achievements
LICENSE MIT license
.gitignore Standard Node.js ignore patterns

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +94 to +100
"tpl": `
<div class="flex items-center gap-2">
<span class="\${sentiment === 'positive' ? 'text-green-600' : sentiment === 'negative' ? 'text-red-600' : 'text-yellow-600'} text-sm font-semibold">
\${sentiment === 'positive' ? '😊 积极' : sentiment === 'negative' ? '😟 消极' : '😐 中性'}
</span>
</div>
`
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template syntax mixing amis expressions with escaped template literals may cause parsing issues. The use of '${sentiment}' with backslash escaping inside a tpl field is non-standard. Verify this works with the amis framework or use the standard amis variable interpolation syntax without escaping.

Suggested change
"tpl": `
<div class="flex items-center gap-2">
<span class="\${sentiment === 'positive' ? 'text-green-600' : sentiment === 'negative' ? 'text-red-600' : 'text-yellow-600'} text-sm font-semibold">
\${sentiment === 'positive' ? '😊 积极' : sentiment === 'negative' ? '😟 消极' : '😐 中性'}
</span>
</div>
`
"tpl": "<div class=\"flex items-center gap-2\"><span class=\"${sentiment === 'positive' ? 'text-green-600' : sentiment === 'negative' ? 'text-red-600' : 'text-yellow-600'} text-sm font-semibold\">${sentiment === 'positive' ? '😊 积极' : sentiment === 'negative' ? '😟 消极' : '😐 中性'}</span></div>"

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +119
"progressClassName": "bg-gradient-to-r from-purple-500 to-blue-500",
"map": {
"*": "bg-gray-200"
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The progress bar component uses a 'map' property with a wildcard selector, but the syntax appears incorrect for amis. The map configuration should define how values map to CSS classes. Verify the correct amis syntax for progress bar styling or use the 'stripe' or 'showLabel' properties instead.

Suggested change
"progressClassName": "bg-gradient-to-r from-purple-500 to-blue-500",
"map": {
"*": "bg-gray-200"
}
"progressClassName": "bg-gradient-to-r from-purple-500 to-blue-500"

Copilot uses AI. Check for mistakes.
Comment thread src/server.ts
Comment on lines +321 to +334
console.log(`
╔═══════════════════════════════════════════════════════════╗
║ ║
║ 🔥 HotCRM - Enterprise CRM System ║
║ ║
║ Server running on http://localhost:${PORT} ║
║ Health check: http://localhost:${PORT}/health ║
║ ║
║ 📊 Dashboard: /api/ui/dashboard/sales ║
║ 🤖 AI Briefing: /api/ai/smart-briefing ║
║ 🔍 ObjectQL: /api/query ║
║ ║
╚═══════════════════════════════════════════════════════════╝
`);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console banner in the server startup uses template literals with ${PORT} which will work, but the ASCII art box has inconsistent spacing. The right side of the box doesn't align properly because PORT could be of variable length (1-5 digits). Consider using fixed-width formatting or calculating the box width dynamically.

Copilot uses AI. Check for mistakes.
Comment thread package.json
"amis": "^6.0.0",
"express": "^4.18.2",
"tailwindcss": "^3.4.0",
"typescript": "^5.3.0",
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript version ^5.3.0 should be moved to devDependencies since it's only needed during development and build time, not at runtime. Having it in dependencies increases the production bundle size unnecessarily.

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +153
await sendNotification({
to: ['sales-director@company.com'], // In production, this would be dynamic
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email address 'sales-director@company.com' is hardcoded. This should be retrieved dynamically from a configuration file, environment variable, or user/role settings. Hardcoded values make the system inflexible and difficult to configure for different deployments.

Suggested change
await sendNotification({
to: ['sales-director@company.com'], // In production, this would be dynamic
const salesDirectorEmail = process.env.SALES_DIRECTOR_EMAIL || ctx.user.email;
await sendNotification({
to: [salesDirectorEmail],

Copilot uses AI. Check for mistakes.
Comment thread src/server.ts
Comment on lines +259 to +280
app.post('/api/ai/smart-briefing', async (req: Request, res: Response) => {
try {
const { accountId, activityLimit } = req.body;

if (!accountId) {
return res.status(400).json({
error: 'Bad Request',
message: 'accountId is required'
});
}

const briefing = await executeSmartBriefing({ accountId, activityLimit });
res.json({ briefing });

} catch (error: any) {
console.error('AI Smart Briefing error:', error);
res.status(500).json({
error: 'Smart Briefing failed',
message: error.message
});
}
});
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing rate limiting on AI endpoints. The AI smart briefing endpoint can be called repeatedly without limits, leading to excessive API costs and potential denial of service. Implement rate limiting (e.g., using express-rate-limit) to restrict the number of AI requests per user/IP within a time window.

Copilot uses AI. Check for mistakes.
Comment thread package.json
"typescript": "^5.3.0",
"yaml": "^2.3.4",
"dotenv": "^16.3.1",
"axios": "^1.6.0"
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing important dependencies for a production application: no database driver (pg, mongodb, mysql2), no environment variable validation library (joi, zod), no logging framework (winston, pino), and no rate limiting library (express-rate-limit). These are essential for production deployment.

Suggested change
"axios": "^1.6.0"
"axios": "^1.6.0",
"pg": "^8.11.5",
"zod": "^3.23.8",
"winston": "^3.13.0",
"express-rate-limit": "^7.2.0"

Copilot uses AI. Check for mistakes.
Comment thread src/server.ts
Comment on lines +16 to +29
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// CORS middleware
app.use((req: Request, res: Response, next: NextFunction) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CORS middleware allows all origins with '*' which is a security risk in production. This should be restricted to specific allowed origins based on environment configuration. Consider using environment variables to specify allowed origins and implementing proper CORS validation.

Suggested change
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// CORS middleware
app.use((req: Request, res: Response, next: NextFunction) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
// CORS configuration
const allowedOriginsEnv = process.env.ALLOWED_ORIGINS;
const allowedOrigins = allowedOriginsEnv
? allowedOriginsEnv.split(',').map(origin => origin.trim()).filter(origin => origin.length > 0)
: [];
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// CORS middleware
app.use((req: Request, res: Response, next: NextFunction) => {
const origin = req.headers.origin as string | undefined;
// Determine if the request origin is allowed.
// If allowedOrigins is empty, allow any origin that is explicitly sent.
const isOriginAllowed =
!!origin && (allowedOrigins.length === 0 || allowedOrigins.includes(origin));
if (isOriginAllowed && origin) {
res.header('Access-Control-Allow-Origin', origin);
// Ensure caches differentiate responses by Origin
res.header('Vary', 'Origin');
}
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
if (!isOriginAllowed) {
return res.sendStatus(403);
}
return res.sendStatus(200);
}

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +92
// In production, this would be logged to error tracking system
throw error;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is caught and logged but then re-thrown, which could cause the entire operation to fail. Consider whether partial failures should prevent the entire trigger from completing. For example, if contract creation succeeds but notification fails, should the transaction be rolled back? Implement a more nuanced error handling strategy with transaction management.

Suggested change
// In production, this would be logged to error tracking system
throw error;
// In production, this would be logged to an error tracking system.
// Do not rethrow here to avoid failing the entire operation for partial failures.

Copilot uses AI. Check for mistakes.
Comment thread src/server.ts
Comment on lines +14 to +15
const PORT = process.env.PORT || 3000;

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PORT environment variable defaults to 3000 without validation. If PORT is set to an invalid value (non-numeric, negative, or out of valid port range), the server will fail to start with unclear errors. Add validation to ensure PORT is a valid number between 1 and 65535.

Suggested change
const PORT = process.env.PORT || 3000;
const rawPort = process.env.PORT;
function getValidatedPort(raw: string | undefined): number {
const defaultPort = 3000;
if (!raw) {
return defaultPort;
}
const parsed = Number(raw);
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
console.error(
`Invalid PORT environment variable "${raw}". ` +
'Expected an integer between 1 and 65535.'
);
process.exit(1);
}
return parsed;
}
const PORT = getValidatedPort(rawPort);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants