Objets personnalisés
Système d'objets CRM metadata-driven de Kasar
Kasar utilise une architecture metadata-driven : les objets ne sont pas codés en dur mais définis dynamiquement via des métadonnées stockées dans next_auth.object_metadata et next_auth.field_metadata.
Comment ça fonctionne
Définition d'un objet
Chaque objet CRM est défini par ses métadonnées :
{
id: string,
labelSingular: "Contact", // Nom d'affichage singulier
labelPlural: "Contacts", // Nom d'affichage pluriel
nameSingular: "contact", // Nom technique singulier
namePlural: "contacts", // Nom technique pluriel (= nom de la table)
iconName: "Users", // Icône Lucide
description: "Personnes physiques",
color: "#3b82f6",
standardFieldId: "...", // Champ principal (affiché dans les listes)
standardImageFieldId: "...", // Champ image (avatar)
isSystem: boolean, // Objet système (non supprimable)
isActive: boolean,
isHidden: boolean,
orderIndex: number,
fields: Record<string, CachedField>
}Tables par organisation
Chaque organisation possède son propre schéma PostgreSQL (ex: toto_21a282b6). Quand un objet est créé, une table est automatiquement générée dans le schéma de l'organisation avec :
- Les colonnes correspondant aux champs définis
- Les index nécessaires
- Les triggers (
updated_atautomatique) - Les champs de base injectés automatiquement :
id,name,description,created_at,updated_at,created_by_id
Objets système
Les objets suivants sont créés automatiquement pour chaque nouvelle organisation :
Contacts
Personnes physiques dans le CRM.
| Champ | Type | Description |
|---|---|---|
first_name | TEXT | Prénom |
last_name | TEXT | Nom |
emails | EMAILS | Emails (composé) |
phones | PHONES | Téléphones (composé) |
position | TEXT | Poste |
companies | MANY_TO_MANY | Entreprises liées |
source | SELECT | Source d'acquisition |
owner_id | USERS | Propriétaire |
Entreprises (Companies)
Personnes morales.
| Champ | Type | Description |
|---|---|---|
name | TEXT | Raison sociale |
industry | SELECT | Secteur d'activité |
size | SELECT | Taille |
website | URL | Site web |
address | ADDRESS | Adresse |
contacts | MANY_TO_MANY | Contacts liés |
owner_id | USERS | Propriétaire |
Opportunités (Opportunities)
Affaires commerciales avec pipeline.
| Champ | Type | Description |
|---|---|---|
name | TEXT | Nom de l'opportunité |
amount | CURRENCY | Montant |
pipeline | PIPELINE | Pipeline et étape |
close_date | DATE | Date de clôture prévue |
probability | PERCENT | Probabilité |
company_id | RELATION | Entreprise liée |
contacts | MANY_TO_MANY | Contacts liés |
owner_id | USERS | Propriétaire |
Tâches (Tasks)
Gestion des actions à faire.
| Champ | Type | Description |
|---|---|---|
title | TEXT | Titre |
due_date | DATE | Échéance |
priority | SELECT | Priorité (low, medium, urgent) |
is_completed | BOOLEAN | Terminée |
assigned_to_id | USERS | Assignée à |
linked_to | MORPH_RELATION | Liée à (contact, entreprise ou opportunité) |
Notes
Notes textuelles liées à n'importe quel objet.
| Champ | Type | Description |
|---|---|---|
content | RICH_TEXT | Contenu |
visibility | SELECT | Visibilité (private, team, public) |
linked_to | MORPH_RELATION | Liée à (contact, entreprise ou opportunité) |
Autres objets système
- Pipelines — Définitions de pipelines de vente
- Pipeline Steps — Étapes de pipeline avec ordre et type de résultat
- Enum Definitions — Définitions des listes de valeurs SELECT/MULTI_SELECT
- Workspace Users — Utilisateurs du workspace
- Dashboards — Tableaux de bord personnalisés
Système de relations
BELONGS_TO_ONE
Relation 1:N classique via clé étrangère sur l'objet source.
Contact → company_id → CompaniesL'objet source contient une colonne {target_singular}_id de type UUID.
HAS_MANY
Côté inverse d'un BELONGS_TO_ONE. Virtuel (pas de colonne physique), calculé par requête.
Company → contacts (via contact.company_id)MANY_TO_MANY
Relation N:N via table de jonction auto-générée.
Contact ←→ contacts_companies_target ←→ CompanyLa table de jonction peut contenir des champs additionnels :
junctionFields: [
{ name: "position", type: "TEXT", label: "Poste" },
{ name: "is_primary", type: "BOOLEAN", label: "Principal" }
]MORPH_RELATION
Relation polymorphe : un champ peut pointer vers plusieurs types d'objets différents.
Task → linked_to_contacts_id → Contacts
Task → linked_to_companies_id → Companies
Task → linked_to_opportunities_id → OpportunitiesTous les champs d'un groupe morph partagent le même morphId. Un seul est rempli à la fois.
ROLLUP
Champ calculé qui agrège des données d'objets liés via n'importe quel type de relation.
{
sourceRelationField: "contacts", // Champ relation source
targetField: "amount", // Champ à agréger
rollupFunction: "SUM" // COUNT_ALL, SUM, AVG, MIN, MAX
}Système de migration (PlanInput)
Les modifications de schéma passent par le système PlanInput → MigrationAction :
// Créer un objet
{ kind: 'create_object', namePlural: 'products', labelPlural: 'Produits', ... }
// Ajouter un champ
{ kind: 'add_field', objectName: 'products', fieldName: 'price', fieldType: 'CURRENCY', ... }
// Créer une table de jonction
{ kind: 'create_junction_table', sourceObject: 'contacts', targetObjects: ['products'], ... }Le buildMigrationPlan() valide les inputs, résout les dépendances et génère les actions SQL exécutables.
Pipeline de vente
Les objets peuvent utiliser le type PIPELINE pour gérer un processus en étapes :
- Pipeline — Définit le processus (ex: "Vente B2B")
- Steps — Étapes ordonnées (ex: "Qualification" → "Proposition" → "Négociation" → "Gagné/Perdu")
- Step Field Configs — Chaque étape peut avoir des valeurs par défaut ou forcées pour certains champs
Le Kanban view est optimisé pour afficher les étapes de pipeline comme colonnes.
Créer un objet personnalisé
Via Léo (IA)
Demandez à l'agent Architecte :
"Crée un objet Produits avec les champs : nom, prix, catégorie, description et stock"
L'agent génère les PlanInputs, affiche un aperçu et attend votre confirmation.
Via le système de migration
const inputs: PlanInput[] = [
{
kind: 'create_object',
labelSingular: 'Produit',
labelPlural: 'Produits',
nameSingular: 'product',
namePlural: 'products',
iconName: 'Package',
standardFieldName: 'name',
},
{
kind: 'add_field',
objectName: 'products',
fieldName: 'price',
label: 'Prix',
fieldType: 'CURRENCY',
constraints: { currency: 'EUR' },
},
// ... autres champs
]Pages dynamiques
Les pages objet sont générées dynamiquement via le routing [object] de Next.js :
/platform/contacts → ObjectPageContainer avec config "contacts"
/platform/products → ObjectPageContainer avec config "products"
/platform/contacts/123 → Page détail du contact 123Le ObjectPageContainer charge les métadonnées, applique la configuration de l'objet (vues disponibles, filtres, etc.) et rend la vue appropriée (table, kanban, list ou sheet).