Klasyczny CRM dla warsztatu ma tabelę vehicles. Pole make, model, vin, plate, year. Działa. Aż klient przyjeżdża z motocyklem — nie ma plate, ma frame number i engine_cc. Albo z łodzią — ma HIN zamiast VIN. Albo z maszyną rolną — ma serial number. Twój CRM trzeba forkać per typ obiektu. Albo wszystko upchnąć w vehicles z null-able polami i if (type === 'motorcycle') w kodzie.

Tak nie skaluje się.

Universal Object Model

W MoreCRM mamy polimorficzną tabelę objects z object_types discriminator + JSONB specs:

CREATE TABLE objects (
  id UUID PRIMARY KEY,
  tenant_id UUID NOT NULL,
  customer_id UUID NOT NULL,
  object_type_id UUID NOT NULL,
  identifier TEXT,           -- VIN, IMEI, S/N, HIN, chip
  display_name TEXT,
  primary_image TEXT,
  specs JSONB NOT NULL,      -- vertical-specific JSON
  metadata JSONB,
  public_card_token TEXT,
  -- ...
);

CREATE TABLE object_types (
  id UUID PRIMARY KEY,
  tenant_id UUID NULL,       -- NULL = system-wide
  key TEXT NOT NULL,         -- 'vehicle', 'motorcycle', 'boat', 'pet', 'device'
  label TEXT NOT NULL,
  identifier_label TEXT,     -- 'VIN', 'IMEI', 'Chip', 'Hull Number'
  spec_schema JSONB NOT NULL -- JSON Schema for specs validation
);

Day-one seed: vehicle, motorcycle, boat, truck, farm_equipment, bicycle. Otwarte na adjacent: pet (weterynaria), device (IT serwis), client_only (salon).

Cross-vertical timeline jako killer feature

Tu leży prawdziwa wartość. Klient ma jedno auto. To auto trafia do Twojego studio detailingu na PPF w marcu. Wraca do Twojej mechaniki na pasek rozrządu w wrześniu. Idzie do Twojej wulkanizacji na opony zima w październiku. Wraca do lakierni na odprysk w lutym. Idzie do auto-szkła w kwietniu.

W single-vertical narzędziu masz 5 osobnych baz, każda widzi tylko swoją zaszłość. W MoreCRM: jedna karta pojazdu, timeline cross-vertical, pełna historia.

To daje Ci moc której nie ma żaden konkurent w Polsce. Klient wraca na przegląd techniczny — Twoja mechanika widzi że ostatni PPF był 14 miesięcy temu, upsell na polerkę przed sezonem. Klient wraca na opony letnie — Twoja wulkanizacja widzi że szyby ma odprysk, upsell na naprawę resin.

Otwarte na adjacent

Universal Object Model otwiera Ci drogę do ekspansji bez refaktoru kodu. Otwierasz salon fryzjerski — object_type: client_only. Otwierasz weterynarię — object_type: pet z polami chip, breed, weight. Otwierasz IT serwis — object_type: device z polami serial, manufacturer, warranty_until.

Architektura jest gotowa już dzisiaj. Marketing nie day-one — robimy go gdy widzimy że biznes się broni w adjacent.

Performance + indexing

JSONB specs jest indexable z GIN:

CREATE INDEX idx_objects_specs ON objects USING GIN (specs);
CREATE INDEX idx_objects_year ON objects (((specs->>'year')::int));

Query “wszystkie auta po 2018 roku” jest szybkie:

SELECT * FROM objects
WHERE object_type_id = (SELECT id FROM object_types WHERE key = 'vehicle')
  AND (specs->>'year')::int >= 2018;

Public client portal

Każdy objects ma public_card_token UUID. Klient dostaje link morecrm.pl/portal/<token> i widzi swoją kartę pojazdu 24/7 — bez logowania. Timeline, gwarancje, faktury, kontakt do Twojego warsztatu.

Public portal w Premium tier ma custom domain — Twój klient widzi klient.twojaservice.pl/avensis zamiast generic morecrm.pl URL. Whitelabel multi-brand: 10 brands w Premium, kolejne sloty jako add-on.


Kiedy to ma sens?

Universal Object Model jest overkill jeśli serwisujesz tylko jeden typ obiektu (np. tylko opony). Ale jest must-have jeśli planujesz:

  • multi-vertical (detailing + mechanika + wulkanizacja)
  • multi-object (auta + motocykle + łódź)
  • adjacent ekspansję (weterynaria, IT, salon)

Wypróbuj 14 dni bez karty i zobacz jak Universal Object Card wygląda w produkcji.