Menu
Public repository / main
AkurAI-CRM / crates/crm-core/src/metadata/mod.rs
Latest change f21a8121a42248001d0b1e88d62efe693179ea69 - release: v0.1.1 by Ólafur B. J.
//! Dynamic metadata system — objects and fields defined in data, not code.
//! Dynamic object/field metadata definitions.
use akurai_json::Value;
/// Defines an object type in the CRM (e.g., "Person", "Company", custom objects)
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectMetadata {
pub id: u64,
pub name_singular: String,
pub name_plural: String,
pub label_singular: String,
pub label_plural: String,
pub description: Option<String>,
pub icon: Option<String>,
pub fields: Vec<FieldMetadata>,
pub is_custom: bool,
pub is_searchable: bool,
}
/// Defines a field on an object
#[derive(Debug, Clone, PartialEq)]
pub struct FieldMetadata {
pub id: u64,
pub name: String,
pub label: String,
pub field_type: FieldType,
pub description: Option<String>,
pub is_required: bool,
pub is_unique: bool,
pub is_custom: bool,
pub default_value: Option<Value>,
pub options: Vec<FieldOption>,
}
/// Supported field types
#[derive(Debug, Clone, PartialEq)]
pub enum FieldType {
Text,
Number,
Boolean,
Date,
Currency,
Email,
Phone,
Link,
Address,
FullName,
RichText,
Select,
MultiSelect,
Relation,
Json,
}
/// A select option
#[derive(Debug, Clone, PartialEq)]
pub struct FieldOption {
pub label: String,
pub value: String,
pub color: Option<String>,
}
impl ObjectMetadata {
/// Create the standard objects for the CRM
pub fn standard_objects() -> Vec<Self> {
vec![
Self::person_object(),
Self::company_object(),
Self::opportunity_object(),
Self::task_object(),
Self::note_object(),
]
}
fn person_object() -> Self {
Self {
id: 1,
name_singular: "person".into(),
name_plural: "people".into(),
label_singular: "Person".into(),
label_plural: "People".into(),
description: Some("A contact person".into()),
icon: Some("user".into()),
fields: vec![
FieldMetadata {
id: 1,
name: "name".into(),
label: "Name".into(),
field_type: FieldType::FullName,
description: None,
is_required: true,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 2,
name: "email".into(),
label: "Email".into(),
field_type: FieldType::Email,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 3,
name: "phone".into(),
label: "Phone".into(),
field_type: FieldType::Phone,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 4,
name: "jobTitle".into(),
label: "Job Title".into(),
field_type: FieldType::Text,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 5,
name: "companyId".into(),
label: "Company".into(),
field_type: FieldType::Relation,
description: Some("Related company".into()),
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 6,
name: "linkedinUrl".into(),
label: "LinkedIn".into(),
field_type: FieldType::Link,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
],
is_custom: false,
is_searchable: true,
}
}
fn company_object() -> Self {
Self {
id: 2,
name_singular: "company".into(),
name_plural: "companies".into(),
label_singular: "Company".into(),
label_plural: "Companies".into(),
description: Some("An organization or business".into()),
icon: Some("building".into()),
fields: vec![
FieldMetadata {
id: 7,
name: "name".into(),
label: "Name".into(),
field_type: FieldType::Text,
description: None,
is_required: true,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 8,
name: "domainName".into(),
label: "Domain".into(),
field_type: FieldType::Text,
description: None,
is_required: false,
is_unique: true,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 9,
name: "annualRevenue".into(),
label: "Annual Revenue".into(),
field_type: FieldType::Currency,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 10,
name: "employeeCount".into(),
label: "Employees".into(),
field_type: FieldType::Number,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 11,
name: "websiteUrl".into(),
label: "Website".into(),
field_type: FieldType::Link,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 12,
name: "linkedinUrl".into(),
label: "LinkedIn".into(),
field_type: FieldType::Link,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
],
is_custom: false,
is_searchable: true,
}
}
fn opportunity_object() -> Self {
Self {
id: 3,
name_singular: "opportunity".into(),
name_plural: "opportunities".into(),
label_singular: "Opportunity".into(),
label_plural: "Opportunities".into(),
description: Some("A sales deal or pipeline entry".into()),
icon: Some("trending-up".into()),
fields: vec![
FieldMetadata {
id: 13,
name: "name".into(),
label: "Name".into(),
field_type: FieldType::Text,
description: None,
is_required: true,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 14,
name: "amount".into(),
label: "Amount".into(),
field_type: FieldType::Currency,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 15,
name: "stage".into(),
label: "Stage".into(),
field_type: FieldType::Select,
description: Some("Pipeline stage".into()),
is_required: true,
is_unique: false,
is_custom: false,
default_value: Some(Value::Str("new".into())),
options: vec![
FieldOption {
label: "New".into(),
value: "new".into(),
color: Some("#94a3b8".into()),
},
FieldOption {
label: "Screening".into(),
value: "screening".into(),
color: Some("#60a5fa".into()),
},
FieldOption {
label: "Meeting".into(),
value: "meeting".into(),
color: Some("#818cf8".into()),
},
FieldOption {
label: "Proposal".into(),
value: "proposal".into(),
color: Some("#a78bfa".into()),
},
FieldOption {
label: "Negotiation".into(),
value: "negotiation".into(),
color: Some("#f59e0b".into()),
},
FieldOption {
label: "Won".into(),
value: "won".into(),
color: Some("#22c55e".into()),
},
FieldOption {
label: "Lost".into(),
value: "lost".into(),
color: Some("#ef4444".into()),
},
],
},
FieldMetadata {
id: 16,
name: "closeDate".into(),
label: "Close Date".into(),
field_type: FieldType::Date,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 17,
name: "probability".into(),
label: "Probability".into(),
field_type: FieldType::Number,
description: Some("Win probability (%)".into()),
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 18,
name: "personId".into(),
label: "Contact".into(),
field_type: FieldType::Relation,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 19,
name: "companyId".into(),
label: "Company".into(),
field_type: FieldType::Relation,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
],
is_custom: false,
is_searchable: true,
}
}
fn task_object() -> Self {
Self {
id: 4,
name_singular: "task".into(),
name_plural: "tasks".into(),
label_singular: "Task".into(),
label_plural: "Tasks".into(),
description: Some("A to-do item".into()),
icon: Some("check-square".into()),
fields: vec![
FieldMetadata {
id: 20,
name: "title".into(),
label: "Title".into(),
field_type: FieldType::Text,
description: None,
is_required: true,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 21,
name: "description".into(),
label: "Description".into(),
field_type: FieldType::RichText,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 22,
name: "status".into(),
label: "Status".into(),
field_type: FieldType::Select,
description: None,
is_required: true,
is_unique: false,
is_custom: false,
default_value: Some(Value::Str("todo".into())),
options: vec![
FieldOption {
label: "Todo".into(),
value: "todo".into(),
color: Some("#94a3b8".into()),
},
FieldOption {
label: "In Progress".into(),
value: "in_progress".into(),
color: Some("#60a5fa".into()),
},
FieldOption {
label: "Done".into(),
value: "done".into(),
color: Some("#22c55e".into()),
},
FieldOption {
label: "Cancelled".into(),
value: "cancelled".into(),
color: Some("#ef4444".into()),
},
],
},
FieldMetadata {
id: 23,
name: "dueAt".into(),
label: "Due Date".into(),
field_type: FieldType::Date,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
],
is_custom: false,
is_searchable: true,
}
}
fn note_object() -> Self {
Self {
id: 5,
name_singular: "note".into(),
name_plural: "notes".into(),
label_singular: "Note".into(),
label_plural: "Notes".into(),
description: Some("A rich-text note".into()),
icon: Some("file-text".into()),
fields: vec![
FieldMetadata {
id: 24,
name: "title".into(),
label: "Title".into(),
field_type: FieldType::Text,
description: None,
is_required: false,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
FieldMetadata {
id: 25,
name: "body".into(),
label: "Body".into(),
field_type: FieldType::RichText,
description: None,
is_required: true,
is_unique: false,
is_custom: false,
default_value: None,
options: vec![],
},
],
is_custom: false,
is_searchable: true,
}
}
}