Logotipo Hedhog

Get Hedhog updates in your inbox

New releases, fresh recipes, and breaking changes — no spam.

Back to CRM modulesCRM Submodule

CRM Submodule

persons

Manages people and companies in the CRM — contacts, accounts, interactions, follow-ups, activities, lifecycle stages and CSV import.

Source path: libraries/crm/src/person

Module file: person.module.ts

Introduction

The Persons submodule is the core of the CRM: a single `person` table represents both individuals and companies (discriminated by the `type` field), with company records additionally exposed through a dedicated Accounts surface (`/person/accounts`) that filters to `type: company` and adds account-specific fields such as industry, website, annual revenue and employee count. The base `person` table holds only structural fields (name, type, status, avatar). Everything else that PersonController accepts on create/update — notes, employer_company_id, owner_user_id, source, lifecycle_stage, next_action_at, score, deal_value, tags — is persisted as key/value rows in a companion person_metadata table and reassembled into the response shape by the service. This keeps the core schema stable while letting CRM-specific attributes evolve without a migration for every new field.

Ownership and lifecycle tracking are first-class concerns. Every person can be assigned an owner_user_id (gated by the admin, admin-crm, or owner-crm role) and a lifecycle_stage drawn from a sales funnel (new, contacted, qualified, proposal, negotiation, customer, lost). POST /person/:id/lifecycle-stage records the transition explicitly rather than silently overwriting the value on a generic update, which keeps a clean history for reporting. GET /person/dashboard and GET /person/reports aggregate these stage transitions and interaction logs into funnel metrics and time-bucketed reports (day/week/month/year), giving the admin panel its pipeline visualizations without separate data warehousing.

Interactions (call, email, whatsapp, meeting, note) and follow-ups (a scheduled next_action_at plus notes) are logged per person and also surfaced as cross-person worklist endpoints — GET /person/followups and GET /person/activities return paginated, filterable views across the whole CRM so a rep can work a queue without opening every person record individually. Activities are a separate, more structured concept than interactions: they carry status and can be explicitly completed via POST /person/activities/:id/complete.

Duplicate prevention and consolidation are handled by two complementary endpoints. GET /person/duplicates performs a fuzzy check by name, email or phone before a new record is created. POST /person/merge consolidates an existing duplicate pair: contacts, documents, addresses and metadata are merged into the target record inside a single transaction, the source person is marked inactive, and a merged_into_person_id metadata pointer is left behind for traceability. Only persons of the same type can be merged, and only the contact_only strategy is currently implemented — the DTO's strategy field is typed for future strategies but the service only accepts this one value today.

Bulk onboarding is supported via a two-step CSV flow: POST /person/import/preview parses the uploaded file and returns headers and sample rows so the caller can build a column mapping, then POST /person/import applies that mapping (passed as a JSON string in a multipart field) to create person records, optionally associating them all with a company via company_id. Both endpoints reject non-CSV uploads and cap file size at 10 MB.

GET /person/avatar/:id is the one public, unauthenticated endpoint in the submodule — it serves a person's avatar image by file ID with ETag-based caching (If-None-Match) so the admin panel and any embedded widgets can render avatars without needing a session token. Linking a person to a platform user account (user_id) is restricted to a dedicated person-user-linker role in addition to admin/admin-crm, since it grants that user visibility tied to the CRM record.

HTTP Endpoints

31 endpoints

Get global CRM person statistics (totals by type and status). When company registration is disabled, company counts are reported as zero.

Get CRM dashboard data (funnel/lifecycle metrics) for a given period, optionally scoped to an owner.

Query
owner_user_id?, period? (7d|30d|90d|custom), date_from?, date_to?

Get CRM reports grouped by time period.

Query
date_from (required), date_to (required), group_by (day|week|month|year, required)

List user options to assign as person owners (autocomplete; restricted to admin, admin-crm, or owner-crm role holders).

Query
search?

List platform user options that can be linked to a person record.

Query
search?

Check for potential duplicate persons based on name, email, or phone before creating a new record.

Query
name?, email?, phone?

Paginated list of persons with optional filters.

Query
page, pageSize, search, type?, status?, lifecycle_stage?, owner_user_id?, source?, tags?

Serve a person's public avatar image (no auth required), with ETag-based caching via If-None-Match.

Params
id (int) — file ID of the avatar

Get full details for a single person including contacts, addresses, documents, and relationships.

Params
id (int)

Create a new person (individual or company).

Body

FieldTypeRequiredNotes
namestringyes
typePersonType (individual | company)yes
statusPersonStatus (active | inactive)yesDefaults to active
avatar_idintno
birth_datedate stringno
genderPersonGender (male | female | other)no
job_titlestringno
trade_namestringnoCompany trade/fantasy name
foundation_datedate stringno
legal_naturestringno
notesstringnoStored as person_metadata
employer_company_idintnoStored as person_metadata
owner_user_idintnoStored as person_metadata
user_idintnoLink to a platform user
sourcePersonSource (referral | website | social | inbound | outbound | other)noStored as person_metadata
lifecycle_stagePersonLifecycleStage (new | contacted | qualified | proposal | negotiation | customer | lost)noStored as person_metadata
next_action_atdate stringnoStored as person_metadata
scorenumbernoStored as person_metadata
deal_valuenumbernoStored as person_metadata
tagsstring[]no

Merge two duplicate persons of the same type, consolidating contacts/documents/addresses/metadata into the target inside a single transaction.

Body

FieldTypeRequiredNotes
source_person_idintyesPerson to be merged and marked inactive
target_person_idintyesPerson to keep
strategy"contact_only"yesOnly contact_only is currently implemented

Preview CSV import — returns column headers and sample rows for field mapping.

Body

FieldTypeRequiredNotes
filemultipart/CSV (max 10 MB)yesCSV file to preview before import

Import persons from a CSV file with custom field mapping.

Body

FieldTypeRequiredNotes
filemultipart/CSV (max 10 MB)yes
mappingJSON string (Record<string, string>)yesCSV column → person field mapping
company_idint (form field)noAssociate imported persons with a company

Update person data (partial update, same fields as create — contacts/addresses/documents arrays are synchronized: created, updated or removed as needed).

Params
id (int)

Delete one or more persons (cascades to contacts, documents, addresses, relations and metadata).

Body

FieldTypeRequiredNotes
idsnumber[]yesIDs to delete

List all interactions logged for a person.

Params
id (int)

Log a new interaction for the person.

Params
id (int)

Body

FieldTypeRequiredNotes
typePersonInteractionType (call | email | whatsapp | meeting | note)yes
notesstringno

Paginated list of follow-ups across all persons, filterable by status or date range.

Query
page, pageSize, search, status? (all | today | upcoming | overdue), date_from?, date_to?

Summary statistics for follow-ups (counts by status).

Query
search?

Schedule a follow-up action for a person.

Params
id (int)

Body

FieldTypeRequiredNotes
next_action_atdate stringyes
notesstringno

Update the CRM lifecycle stage for a person, explicitly recording the stage transition for funnel reporting.

Params
id (int)

Body

FieldTypeRequiredNotes
lifecycle_stagePersonLifecycleStage (new | contacted | qualified | proposal | negotiation | customer | lost)yes

Paginated list of activities across all persons.

Query
page, pageSize, search, status?, type?, person_id?

Summary statistics for all activities (counts by status/type).

Get full detail for a single activity.

Params
id (int)

Mark an activity as completed.

Params
id (int)

Paginated list of company accounts (persons of type company) with CRM lifecycle filtering.

Query
page, pageSize, search, status? (all | active | inactive), lifecycle_stage?, sortField? (name | created_at), sortOrder? (asc | desc)

Summary statistics for company accounts (counts by lifecycle stage).

Create a new company account.

Body

FieldTypeRequiredNotes
namestringyes
statusPersonStatus (active | inactive)yes
trade_namestringno
foundation_datedate stringno
legal_naturestringno
industrystringno
websitestringno
emailemail stringno
phonestringno
owner_user_idintno
annual_revenuenumberno
employee_countintno
lifecycle_stageAccountLifecycleStage (prospect | customer | churned | inactive)no
citystringno
statestring (max 2)no
collaborator_person_idsint[]no
contactsUpdateAllContactDTO[]no
addressesUpdateAllAddressDTO[]no
documentsUpdateAllDocumentDTO[]no
avatar_idintno

Update a company account (same fields as create).

Params
id (int)

Delete one or more company accounts.

Body

FieldTypeRequiredNotes
idsnumber[]yesIDs to delete

MCP Tools

Named tools callable by AI models via the Model Context Protocol (MCP).

  • crm.persons.list

    Lists persons (individuals or companies) with pagination and filters

  • crm.persons.stats

    Returns person statistics: total, individual, company, active, inactive counts

  • crm.persons.dashboard

    Returns CRM dashboard data with funnel metrics, grouped by period

  • crm.persons.reports

    Returns CRM reports data

  • crm.persons.owner-options

    Returns users available as owners for a person record

  • crm.persons.linked-user-options

    Returns users that can be linked to a person record

  • crm.persons.duplicates

    Checks for duplicate person records by name, email, or phone

  • crm.persons.get

    Returns a single person record by ID with full details

  • crm.persons.create

    Creates a new person record (individual or company)

  • crm.persons.update

    Updates an existing person record by ID

  • crm.persons.delete

    Deletes one or more person records by their IDs

  • crm.persons.merge

    Merges two duplicate person records into one

  • crm.persons.interactions.list

    Lists all interactions for a person (calls, emails, meetings, notes)

  • crm.persons.interactions.create

    Records a new interaction with a person

  • crm.persons.followups.list

    Lists all follow-ups across all persons with pagination and filters

  • crm.persons.followups.stats

    Returns follow-up statistics grouped by status and period

  • crm.persons.followups.create

    Schedules a follow-up for a person

  • crm.persons.activities.list

    Lists CRM activities with pagination and filters

  • crm.persons.activities.stats

    Returns activity statistics

  • crm.persons.activities.get

    Returns a single CRM activity by ID

  • crm.persons.activities.complete

    Marks a CRM activity as completed

  • crm.persons.lifecycle-stage.update

    Updates the lifecycle stage of a person record

  • crm.persons.accounts.list

    Lists company accounts with pagination and filters

  • crm.persons.accounts.stats

    Returns account statistics

  • crm.persons.accounts.create

    Creates a new company account

  • crm.persons.accounts.update

    Updates an existing company account by ID

  • crm.persons.accounts.delete

    Deletes one or more company accounts by their IDs