KasarKasar Docs
Objets personnalisés

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_at automatique)
  • 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.

ChampTypeDescription
first_nameTEXTPrénom
last_nameTEXTNom
emailsEMAILSEmails (composé)
phonesPHONESTéléphones (composé)
positionTEXTPoste
companiesMANY_TO_MANYEntreprises liées
sourceSELECTSource d'acquisition
owner_idUSERSPropriétaire

Entreprises (Companies)

Personnes morales.

ChampTypeDescription
nameTEXTRaison sociale
industrySELECTSecteur d'activité
sizeSELECTTaille
websiteURLSite web
addressADDRESSAdresse
contactsMANY_TO_MANYContacts liés
owner_idUSERSPropriétaire

Opportunités (Opportunities)

Affaires commerciales avec pipeline.

ChampTypeDescription
nameTEXTNom de l'opportunité
amountCURRENCYMontant
pipelinePIPELINEPipeline et étape
close_dateDATEDate de clôture prévue
probabilityPERCENTProbabilité
company_idRELATIONEntreprise liée
contactsMANY_TO_MANYContacts liés
owner_idUSERSPropriétaire

Tâches (Tasks)

Gestion des actions à faire.

ChampTypeDescription
titleTEXTTitre
due_dateDATEÉchéance
prioritySELECTPriorité (low, medium, urgent)
is_completedBOOLEANTerminée
assigned_to_idUSERSAssignée à
linked_toMORPH_RELATIONLiée à (contact, entreprise ou opportunité)

Notes

Notes textuelles liées à n'importe quel objet.

ChampTypeDescription
contentRICH_TEXTContenu
visibilitySELECTVisibilité (private, team, public)
linked_toMORPH_RELATIONLié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 → Companies

L'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 ←→ Company

La 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 → Opportunities

Tous 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 PlanInputMigrationAction :

// 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 :

  1. Pipeline — Définit le processus (ex: "Vente B2B")
  2. Steps — Étapes ordonnées (ex: "Qualification" → "Proposition" → "Négociation" → "Gagné/Perdu")
  3. 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 123

Le 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).

On this page