snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
---
title: Extinderea obiectelor
description: Adăugați câmpuri la obiectele standard Twenty (Person, Company, …) sau la obiecte din alte aplicații folosind defineField.
icon: wand-magic-sparkles
---
Folosiți `defineField()` pentru a adăuga un câmp la un obiect pe care nu îl dețineți — un obiect standard Twenty precum Person sau Company, sau un obiect livrat de o altă aplicație instalată. Spre deosebire de câmpurile inline declarate în interiorul [`defineObject`](/l/ro/developers/extend/apps/data/objects), câmpurile independente necesită un `objectUniversalIdentifier` pentru a specifica obiectul pe care îl extind.
```ts src/fields/company-loyalty-tier.field.ts
import { defineField, FieldType } from 'twenty-sdk/define';
export default defineField({
universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890',
objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object
name: 'loyaltyTier',
type: FieldType.SELECT,
label: 'Loyalty Tier',
icon: 'IconStar',
options: [
{ value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' },
{ value: 'SILVER', label: 'Silver', position: 1, color: 'gray' },
{ value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' },
],
});
```
## Puncte cheie
* `objectUniversalIdentifier` identifică obiectul țintă. Pentru obiectele standard Twenty, importați constanta din `twenty-sdk`:
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier
// …
```
* Atunci când definiți câmpuri **inline în `defineObject()`**, **nu** aveți nevoie de `objectUniversalIdentifier` — acesta este moștenit de la obiectul părinte.
* `defineField()` este singura modalitate de a adăuga câmpuri la obiecte pe care nu le-ați creat cu `defineObject()`.
* Locația fișierului depinde de dumneavoastră. Convenția este `src/fields/\<name>.field.ts`, dar SDK-ul detectează câmpuri oriunde în `src/`.
* `writability` poate fi de asemenea setată per câmp: `MetadataWritability.APPLICATION` restricționează scrierile la propriile funcții logice ale aplicației tale, `MetadataWritability.SYSTEM` este rezervat pentru date administrate de platformă. Capacitatea de scriere a unui câmp poate fi doar mai strictă decât nivelul obiectului său, iar citirile nu sunt afectate. Vezi [Objects](/l/ro/developers/extend/apps/data/objects) pentru semantica completă.
* Pentru a adăuga o filă într-un layout standard de pagină (de ex. pagina de detalii pentru Task sau Company), folosește [`definePageLayoutTab`](/l/ro/developers/extend/apps/layout/page-layouts#definepagelayouttab) cu `STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS` din `twenty-sdk/define`.
## Adăugarea unei relații la un obiect existent
Pentru a adăuga un câmp de tip relație (de ex. pentru a lega obiectul personalizat de un `Person` standard), folosiți `defineField()` cu `FieldType.RELATION`. Modelul este același ca pentru relațiile inline, dar cu `objectUniversalIdentifier` setat explicit. Consultați [Relații](/l/ro/developers/extend/apps/data/relations) pentru modelul bidirecțional.

View File

@@ -0,0 +1,143 @@
---
title: Obiecte
description: Declarați noi tipuri de înregistrări — tabele personalizate cu propriile câmpuri — folosind defineObject.
icon: table
---
**Obiectele** personalizate sunt tipuri noi de înregistrări pe care aplicația dvs. le adaugă într-un spațiu de lucru — carte poștală, factură, abonament, orice este specific domeniului dvs. Fiecare obiect își declară propria schemă (câmpuri, relații, valori implicite) și un identificator universal stabil, care este păstrat între sincronizări și implementări.
```ts src/objects/post-card.object.ts
import { defineObject, FieldType } from 'twenty-sdk/define';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
}
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
## Puncte cheie
* `universalIdentifier` trebuie să fie unic și stabil între implementări.
* Fiecare câmp necesită un `name`, un `type`, un `label` și propriul `universalIdentifier` stabil.
* Tabloul `fields` este opțional — puteți defini obiecte fără câmpuri personalizate.
* `openRecordIn` stabilește unde se deschid înregistrările acestui obiect când se face clic pe ele: `ObjectOpenRecordIn.USER_CHOICE` (valoarea implicită, care respectă preferința fiecărui membru al spațiului de lucru din Settings → Experience), `ObjectOpenRecordIn.SIDE_PANEL` sau `ObjectOpenRecordIn.RECORD_PAGE`. Fixează-l la `RECORD_PAGE` pentru înregistrările care au nevoie de o pagină completă pentru a fi utilizabile, așa cum este cazul fluxurilor de lucru și al dashboard-urilor, sau la `SIDE_PANEL` pentru înregistrările care au sens doar ca un panou rapid, așa cum este cazul evenimentelor din calendar.
* `writability` controlează cine poate scrie înregistrări ale obiectului, înainte ca permisiunile pe roluri să se aplice: `MetadataWritability.OPEN` (valoarea implicită — rolurile din spațiul de lucru decid), `MetadataWritability.APPLICATION` (numai funcțiile de logică ale aplicației tale pot crea, actualiza sau șterge înregistrări; folosește acest lucru pentru obiecte de tip configurație ale căror înregistrări determină comportamentul, astfel încât membrii spațiului de lucru cu acces extins la înregistrări să nu le poată edita prin API) sau `MetadataWritability.SYSTEM` (rezervat pentru date administrate de platformă). Operațiunile de citire nu sunt afectate — acest lucru este impus la nivel de server, spre deosebire de `isUIEditable`, care doar ascunde elementele din interfața cu utilizatorul. Se aplică și la nivel de câmp, unde poate fi doar mai restrictivă decât la nivelul obiectului.
* Câmpurile inline definite aici **nu** au nevoie de `objectUniversalIdentifier` — este moștenit de la obiectul părinte. Folosiți [`defineField()`](/l/ro/developers/extend/apps/data/extending-objects) pentru a adăuga câmpuri la obiecte care nu vă aparțin.
* Puteți genera obiecte noi cu `yarn twenty dev:add object`, care vă ghidează prin denumire, câmpuri și relații. Consultați [Arhitectură → Generarea entităților](/l/ro/developers/extend/apps/getting-started/scaffolding).
<Note>
**Câmpurile de bază sunt adăugate automat.** Când definiți un obiect personalizat, Twenty creează pentru dvs. câmpuri standard precum `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` și `deletedAt`. Nu trebuie să le declarați în tabloul `fields` — doar câmpurile dvs. personalizate. Puteți suprascrie un câmp implicit declarând unul cu același nume, dar acest lucru este rareori o idee bună.
</Note>
## Tipuri de câmpuri
Setul complet de valori `FieldType`, exportate din `twenty-sdk/define`:
| Categorie | Tipuri |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Text | `TEXT`, `RICH_TEXT`, `ARRAY` (de șiruri), `RAW_JSON` |
| Numerice | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (precizie arbitrară), `RATING`, `POSITION` |
| Date calendaristice | `DATE`, `DATE_TIME` |
| Alegere | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Compuse | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identificatori și relații | `UUID`, `RELATION`, `MORPH_RELATION` (vezi [Relații](/l/ro/developers/extend/apps/data/relations)) |
| Sistem | `TS_VECTOR` (vector de căutare full-text, gestionat de server) |
Tipurile compuse stochează mai multe sub‑câmpuri (de ex. `FULL_NAME` = prenume + nume de familie; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` și `MULTI_SELECT` necesită un tablou `options`, ca în exemplul de mai sus.
## Valori implicite
Valorile implicite de tip șir literal trebuie să fie încadrate în ghilimele simple **în interiorul** șirului — `defaultValue: "'Draft'"`, nu `defaultValue: "Draft"`. De aceea câmpul `status` de mai sus folosește `` `'${PostCardStatus.DRAFT}'` ``.
Șirurile fără ghilimele sunt rezervate pentru valorile implicite calculate, evaluate atunci când este creată o înregistrare:
* `'uuid'` — generează un UUID (pentru câmpurile `UUID`)
* `'now'` — marcajul temporal curent (pentru câmpurile `DATE_TIME`)
Aceeași convenție se aplică și subcâmpurilor de tip șir de caractere ale valorilor implicite compuse (de ex. `{ source: "'MANUAL'" }` pe un câmp `ACTOR`) și valorilor `SELECT`/`MULTI_SELECT`. O valoare implicită literală de tip șir de caractere lăsată neîncadrată în ghilimele generează un avertisment atunci când aplicația este construită.
## Acceptarea valorii NULL
`isNullable` controlează dacă un câmp acceptă `NULL`. Valoarea sa implicită este `true` — omiteți-l pentru câmpurile opționale. Setați `isNullable: false` pentru a face un câmp obligatoriu la nivel de bază de date.
Modificările aduse proprietății `isNullable` sunt aplicate la fiecare sincronizare, inclusiv la sincronizările care actualizează un câmp existent — astfel încât puteți schimba nullabilitatea unui câmp editând manifestul și resincronizând.
<Note>
**Transformarea unui câmp existent în non-nullable necesită o valoare implicită.** Atunci când modificați un câmp la `isNullable: false`, trebuie să furnizați și un `defaultValue` nenul. Valoarea implicită completează retroactiv orice rânduri `NULL` existente înainte ca restricția `NOT NULL` să fie aplicată; fără aceasta, sincronizarea eșuează cu mesajul `Default value cannot be null for non-nullable fields`. Câmpurile de relație și câmpurile `TS_VECTOR` sunt întotdeauna nullable, astfel încât `isNullable` nu are niciun efect asupra lor.
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## Ce urmează
* **Conectați acest obiect la altele** — consultați [Relații](/l/ro/developers/extend/apps/data/relations) pentru modelul de relație bidirecțională.
* **Adăugați câmpuri la obiecte din alte aplicații** — consultați [Extinderea obiectelor](/l/ro/developers/extend/apps/data/extending-objects) pentru `defineField()`.
* **Afișează acest obiect în interfața de utilizator** — consultă [Elemente de meniu de navigare](/l/ro/developers/extend/apps/layout/navigation-menu-items) pentru a adăuga un element în bara laterală; consultă [Vizualizări](/l/ro/developers/extend/apps/layout/views) pentru a adăuga configurații personalizate de listă.

View File

@@ -0,0 +1,101 @@
---
title: Prezentare generală
description: Modelați datele pe care aplicația le adaugă într-un spațiu de lucru — obiecte, câmpuri și relații.
icon: database
---
Stratul de **date** al unei aplicații Twenty reprezintă datele pe care aplicația le *adaugă* într-un spațiu de lucru — noile tipuri de înregistrări pe care le declară, coloanele pe care le adaugă la obiectele existente și modul în care aceste înregistrări se conectează între ele.
```text
┌──────────────────────────────────────────────────┐
│ Object — a record type, e.g. PostCard │
│ ├─ Field (name, type, label) │
│ ├─ Field │
│ └─ Relation (link to another object) │
└──────────────────────────────────────────────────┘
│
├── lives in your app, OR
│
▼
┌──────────────────────────────────────────────────┐
│ Standard / other apps' objects │
│ └─ Field added by your app via defineField │
└──────────────────────────────────────────────────┘
```
## În această secțiune
<CardGroup cols={2}>
<Card title="Obiecte" icon="table" href="/l/ro/developers/extend/apps/data/objects">
`defineObject` — declarați noi tipuri de înregistrări cu propriile lor câmpuri.
</Card>
<Card title="Extinderea obiectelor" icon="wand-magic-sparkles" href="/l/ro/developers/extend/apps/data/extending-objects">
`defineField` — adăugați câmpuri la obiecte standard sau la obiectele altor aplicații.
</Card>
<Card title="Relații" icon="diagram-project" href="/l/ro/developers/extend/apps/data/relations">
Conexiuni bidirecționale `MANY_TO_ONE` / `ONE_TO_MANY` între obiecte.
</Card>
<Card title="Tipuri de activități ale cronologiei" icon="clock-rotate-left" href="/l/ro/developers/extend/apps/data/timeline-activity-types">
Definiți evenimente de audit automate și evenimente explicite ale aplicației pentru cronologiile înregistrărilor.
</Card>
</CardGroup>
## Entități dintr-o privire
| Entitate | Scop | Definit cu |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| **Obiect** | Un nou tip de înregistrare personalizat (de ex. PostCard, Invoice) cu propriile sale câmpuri | `defineObject()` |
| **Câmp** | O coloană pe un obiect. Câmpurile independente pot extinde obiecte pe care nu le-ați creat (de ex. adăugați `loyaltyTier` la Company) | `defineField()` |
| **Relație** | O legătură bidirecțională între două obiecte — ambele părți declarate ca câmpuri | `defineField()` cu `FieldType.RELATION` |
| **Indice** | Un indice de bază de date pentru a accelera o interogare recurentă asupra unuia dintre obiectele tale | `defineIndex()` |
| **Tip de activitate a cronologiei** | Un eveniment automat de audit sau un eveniment explicit de domeniu afișat în cronologiile înregistrărilor. | `defineTimelineActivityType()` |
SDK-ul detectează acestea prin analiza AST la momentul build-ului, astfel încât organizarea fișierelor ține de tine — convenția este `src/objects/`, `src/fields/`, `src/indexes/` și `src/timeline-activity-types/`. UUID-urile stabile `universalIdentifier` leagă totul în toate implementările.
## Indici (opțional)
Aplicațiile pot livra indici împreună cu obiectele lor pentru a menține rapide interogările recurente. Cel mai comun caz este o coloană de status sau o coloană cu cheie străină pe care o citești frecvent.
```ts src/indexes/post-card-status.index.ts
import { defineIndex } from 'twenty-sdk/define';
import {
POST_CARD_UNIVERSAL_IDENTIFIER,
STATUS_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card.object';
export default defineIndex({
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff0',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff1',
fieldUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
},
],
});
```
### Indici unici
`defineIndex` acceptă `isUnique: true` atât pentru unicitatea pe o singură coloană, cât și pe mai multe coloane. Aceasta este primitiva recomandată — `defineField({ isUnique: true })` este învechită (deprecated) și va fi eliminată într-o versiune viitoare.
```ts
defineIndex({
universalIdentifier: '…',
objectUniversalIdentifier: PERSON_UNIVERSAL_IDENTIFIER,
isUnique: true,
fields: [{ universalIdentifier: '…', fieldUniversalIdentifier: EMAIL_FIELD_UNIVERSAL_IDENTIFIER }],
});
```
### Alte constrângeri
* Clauzele `WHERE` parțiale rămân sub controlul administratorului — aplicațiile nu le pot declara.
* Fiecare obiect este limitat la 10 indici personalizați (indicii proprii ai framework-ului nu se pun la socoteală).
Ordonează array-ul `fields` în modul în care Postgres ar trebui să îl folosească — coloana din stânga prima, ca într-o agendă telefonică. Indicii nu sunt gratuiți: fiecare scriere în tabel îi actualizează. Adaugă unul doar atunci când ai o interogare care are nevoie de el.
<Note>
Căutați **Application Config** sau **Roles & Permissions**? Acestea descriu aplicația în sine, mai degrabă decât datele pe care le adaugă — se află la [Config](/l/ro/developers/extend/apps/config/overview). Căutați **Connections** (Linear, GitHub, Slack OAuth)? Acestea există pentru a fi apelate *din* funcții de logică și se află la [Logic](/l/ro/developers/extend/apps/logic/connections).
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Relații
description: Conectați obiectele între ele cu relații bidirecționale MANY_TO_ONE / ONE_TO_MANY.
icon: diagram-project
---
Relațiile conectează două obiecte între ele. În Twenty, relațiile sunt întotdeauna **bidirecționale** — fiecare relație are două părți, iar fiecare parte este declarată ca un câmp ce o referențiază pe cealaltă.
| Tip relație | Descriere | Are cheie străină? |
| ------------- | ---------------------------------------------------------------------------------- | --------------------- |
| `MANY_TO_ONE` | Multe înregistrări ale acestui obiect indică către o singură înregistrare a țintei | Da (`joinColumnName`) |
| `ONE_TO_MANY` | O înregistrare a acestui obiect are multe înregistrări ale țintei | Nu (partea inversă) |
## Cum funcționează relațiile
Fiecare relație necesită **două câmpuri** care se referențiază reciproc:
1. Partea **MANY_TO_ONE** — se află pe obiectul care deține cheia străină.
2. Partea **ONE_TO_MANY** — se află pe obiectul care deține colecția.
Ambele câmpuri folosesc `FieldType.RELATION` și se referențiază încrucișat prin `relationTargetFieldMetadataUniversalIdentifier`.
## Exemplu: Post Card are mulți destinatari
Un `PostCard` poate fi trimis către multe înregistrări `PostCardRecipient`. Fiecare destinatar aparține exact unui Post Card.
**Pasul 1: Definiți partea ONE_TO_MANY pe PostCard** (partea "one"):
```ts src/fields/post-card-recipients-on-post-card.field.ts
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111';
// Import from the other side
import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field';
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
icon: 'IconUsers',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
```
**Pasul 2: Definiți partea MANY_TO_ONE pe PostCardRecipient** (partea "many" — deține cheia străină):
```ts src/fields/post-card-on-post-card-recipient.field.ts
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222';
// Import from the other side
import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field';
export default defineField({
universalIdentifier: POST_CARD_FIELD_ID,
objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
icon: 'IconMail',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
});
```
<Note>
**Importuri circulare:** ambele câmpuri de relație se referă unul la celălalt prin `universalIdentifier`. Pentru a evita problemele de import circular, exportați ID-urile câmpurilor ca constante denumite din fiecare fișier și importați-le în celălalt. Sistemul de build le rezolvă în timpul compilării.
</Note>
## Relații de joncțiune
Un obiect de joncțiune conectează o înregistrare sursă la una sau mai multe înregistrări-țintă. De exemplu, `PostCardRecipient` poate conține atât o relație `postCard`, cât și o relație `recipient`. În câmpul `ONE_TO_MANY` al obiectului sursă, setează `junctionTargetFieldUniversalIdentifier` la câmpul de relație de pe obiectul de joncțiune care indică spre țintă:
```ts
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
relationTargetObjectMetadataUniversalIdentifier:
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
junctionTargetFieldUniversalIdentifier: RECIPIENT_FIELD_ID,
},
});
```
`RECIPIENT_FIELD_ID` trebuie să identifice o relație `MANY_TO_ONE` sau morfă pe obiectul de joncțiune. Acesta indică lui Twenty care înregistrare asociată este ținta atunci când o funcționalitate precum [distribuirea activității din cronologie](/l/ro/developers/extend/apps/data/timeline-activity-types#automatic-and-explicit-events) parcurge joncțiunea.
## Relaționarea cu obiectele standard
Pentru a crea o relație cu un obiect Twenty încorporat (Person, Company etc.), utilizați `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
```ts src/fields/person-on-self-hosting-user.field.ts
import {
defineField,
FieldType,
RelationType,
OnDeleteAction,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object';
export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333';
export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444';
export default defineField({
universalIdentifier: PERSON_FIELD_ID,
objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'person',
label: 'Person',
description: 'Person matching with the self hosting user',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'personId',
},
});
```
## Proprietăți ale câmpului de relație
| Proprietate | Obligatoriu | Descriere |
| ---------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `type` | Da | Trebuie să fie `FieldType.RELATION` |
| `relationTargetObjectMetadataUniversalIdentifier` | Da | `universalIdentifier` al obiectului țintă |
| `relationTargetFieldMetadataUniversalIdentifier` | Da | `universalIdentifier` al câmpului corespunzător de pe obiectul țintă |
| `universalSettings.relationType` | Da | `RelationType.MANY_TO_ONE` sau `RelationType.ONE_TO_MANY` |
| `universalSettings.onDelete` | Doar MANY_TO_ONE | Ce se întâmplă atunci când înregistrarea referențiată este ștearsă: `CASCADE`, `SET_NULL`, `RESTRICT` sau `NO_ACTION` |
| `universalSettings.joinColumnName` | Doar MANY_TO_ONE | Numele coloanei din baza de date pentru cheia străină (de ex., `postCardId`) |
| `universalSettings.junctionTargetFieldUniversalIdentifier` | Doar ONE_TO_MANY de joncțiune | Câmp de relație pe obiectul de joncțiune care indică spre ținta distribuției |
## Câmpuri de relație inline
Puteți, de asemenea, să declarați o relație direct în [`defineObject`](/l/ro/developers/extend/apps/data/objects). Când este inline, omiteți `objectUniversalIdentifier` — este moștenit de la obiectul părinte:
```ts
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCardRecipient',
// ...
fields: [
{
universalIdentifier: POST_CARD_FIELD_ID,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
},
// … other fields
],
});
```

View File

@@ -0,0 +1,213 @@
---
title: Țintirea metadatelor de sistem
description: Rezolvă identificatorii universali determinați ai metadatelor pe care Twenty le provizionează automat pe fiecare obiect, astfel încât aplicația ta să le poată referenția fără hardcodare.
icon: gears
---
Fiecare obiect din Twenty vine cu **metadate de sistem** pe care nu le declari niciodată tu însuți, cum ar fi un set de câmpuri și o vizualizare principală de listă cu coloanele sale. Serverul le creează pe toate atunci când obiectul este provizionat, iar setul crește pe măsură ce Twenty evoluează.
Deoarece nu îl declari, nu există nicio constantă `universalIdentifier` pe care să o imporți. În schimb, serverul **derivează** fiecare identificator într-un mod determinist, iar `twenty-sdk` expune aceeași derivare astfel încât manifestul tău să poată rezolva exact valoarea pe care o folosește serverul.
## Câmpuri de sistem
Câmpurile scalare prezente pe fiecare obiect, niciunul dintre ele nu este declarat de tine cu [`defineField()`](/l/ro/developers/extend/apps/data/extending-objects):
`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
Deci cum faci referire la `createdAt` ca o coloană într-o [vizualizare](/l/ro/developers/extend/apps/layout/views)?
### Problema
Începând cu Twenty 2.19, identificatorul universal al unui câmp de sistem este **derivat deterministic** de server din trei intrări: identificatorul universal al aplicației, identificatorul universal al obiectului și numele câmpului. Inventarea unui id și hardcodarea lui nu va funcționa: acesta nu se potrivește cu nimic de pe server, iar sincronizarea respinge referința rămasă în aer:
```
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
```
### Soluția
<Note>
`getFieldUniversalIdentifier` este disponibil începând cu `twenty-sdk` 2.21.
</Note>
Folosește `getFieldUniversalIdentifier` pentru a obține exact aceeași valoare pe care o folosește serverul. Aceasta primește cele trei intrări și returnează identificatorul universal al câmpului:
```ts
import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
const createdAtFieldId = getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
});
```
* `applicationUniversalIdentifier` este identificatorul aplicației tale, cel pe care îl transmiți către [`defineApplication()`](/l/ro/developers/extend/apps/config/application).
* `objectUniversalIdentifier` este identificatorul obiectului căruia îi aparține câmpul.
* `name` este numele câmpului de sistem, una dintre valorile listate mai sus.
### Exemplu: o coloană createdAt într-o vizualizare
Cazul tipic este adăugarea unei coloane `createdAt` la o vizualizare a unuia dintre obiectele tale personalizate. Calculează id-ul câmpului și fă referire la el ca la orice alt `fieldMetadataUniversalIdentifier`:
```ts src/views/example-view.ts
import {
defineView,
getFieldUniversalIdentifier,
} from 'twenty-sdk/define';
const APPLICATION_UNIVERSAL_IDENTIFIER =
'0b04e15c-27b2-4741-9046-b32e07469072';
const MY_OBJECT_UNIVERSAL_IDENTIFIER =
'c782b61c-70fd-4c88-9cd6-4e61ab8d7591';
export default defineView({
universalIdentifier: '70f10d44-144a-4da8-8c6f-3ec2422138c0',
name: 'All records',
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
fields: [
{
universalIdentifier: '75a90bc4-d901-4df4-85e0-af29db5e0104',
fieldMetadataUniversalIdentifier: getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
}),
position: 0,
isVisible: true,
size: 200,
},
],
});
```
Același id calculat funcționează oriunde este așteptat un `fieldMetadataUniversalIdentifier`: câmpuri de vizualizare, filtre, sortări, grupări și widget-uri de tip page-layout.
<Note>
Calculează id-ul, nu îl hardcoda. Deoarece serverul derivă valoarea din
id-ul aplicației, id-ul obiectului și numele câmpului, apelarea
`getFieldUniversalIdentifier` menține referința corectă chiar dacă aceste
intrări se schimbă și evită divergența dacă metoda de derivare evoluează vreodată.
</Note>
### Câmpuri de relație de sistem
<Note>
`getSystemRelationFieldUniversalIdentifier` este disponibil începând cu versiunea 2.23 a `twenty-sdk` și necesită un server Twenty pe versiunea 2.23 sau o versiune ulterioară.
</Note>
Pe lângă câmpurile scalare de sistem de mai sus, serverul mai pune la dispoziție patru **câmpuri de relație de sistem** pentru fiecare obiect: `timelineActivities`, `attachments`, `noteTargets` și `taskTargets`, fiecare indicând către obiectul de relație standard corespunzător.
Aceste câmpuri nu sunt rezolvate cu `getFieldUniversalIdentifier`: identificatorul lor este derivat **independent de nume**, din obiectul care găzduiește câmpul și obiectul către care indică acest câmp. Astfel, redenumirea unui obiect nu modifică niciodată identificatorii câmpurilor sale de relație.
Folosește `getSystemRelationFieldUniversalIdentifier` pentru a le rezolva:
```ts
import {
getSystemRelationFieldUniversalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
// rocket.attachments — the relation field hosted on your custom object
const rocketAttachmentsFieldId = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
});
```
* `objectUniversalIdentifier` este obiectul care **găzduiește** câmpul.
* `relationTargetObjectUniversalIdentifier` este obiectul către care **indică** câmpul.
Direcția este codificată prin ordinea argumentelor. Pentru a rezolva partea inversă (de ex. `attachment.targetRocket`, câmpul morfic pe care serverul îl creează pe obiectul de relație standard), inversează-le pe cele două:
```ts
// attachment.targetRocket — the reverse morph field on Attachment
const attachmentTargetRocketFieldId =
getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
relationTargetObjectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
});
```
Ca și în cazul câmpurilor de sistem scalare, id-ul rezolvat funcționează oriunde este așteptat un `fieldMetadataUniversalIdentifier`.
## Vizualizări de sistem
<Note>
`getSystemViewUniversalIdentifier` și `getSystemViewFieldUniversalIdentifier`
sunt disponibile începând cu versiunea 2.26 a `twenty-sdk` și necesită un server Twenty pe
versiunea 2.26 sau o versiune ulterioară.
</Note>
Serverul provizionează, de asemenea, o **vizualizare de sistem** pentru fiecare obiect: vizualizarea principală de listă (`All {objectLabelPlural}`, indexată prin `ViewKey.INDEX` (derivată cu `SYSTEM_VIEW_KEYS.INDEX`)), cu câte o coloană pentru fiecare câmp afișabil. La fel ca în cazul câmpurilor de relație de sistem, identificatorii lor sunt derivați **fără nume**, astfel încât redenumirea unui obiect sau a unui câmp nu îi schimbă niciodată.
Folosește `getSystemViewUniversalIdentifier` pentru a rezolva vizualizarea:
```ts
import {
getSystemViewUniversalIdentifier,
SYSTEM_VIEW_KEYS,
} from 'twenty-sdk/define';
const rocketIndexViewId = getSystemViewUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
viewKey: SYSTEM_VIEW_KEYS.INDEX,
});
```
* `objectMetadataApplicationUniversalIdentifier` este aplicația care deține **obiectul**, ceea ce reprezintă spațiul de nume pentru vizualizare.
* `objectUniversalIdentifier` este obiectul pe care îl listează vizualizarea.
* `viewKey` este discriminatorul de vizualizare de sistem: `SYSTEM_VIEW_KEYS.INDEX` pentru vizualizarea principală de listă, `SYSTEM_VIEW_KEYS.FIELDS_WIDGET` pentru vizualizarea widgetului de câmpuri de pe pagina de înregistrare. Acesta stabilește derivarea; numai `INDEX` este, de asemenea, stocat pe rândul vizualizării.
ID-ul rezolvat funcționează oriunde este așteptat un `viewUniversalIdentifier`, cum ar fi o intrare în bara laterală [`NavigationMenuItemType.VIEW`](/l/ro/developers/extend/apps/layout/navigation-menu-items). Pentru a deschide pur și simplu lista principală a unui obiect, preferă `NavigationMenuItemType.OBJECT` cu `targetObjectUniversalIdentifier`: nu are nevoie de derivare.
`getSystemViewFieldUniversalIdentifier` rezolvă o singură **coloană** dintr-o vizualizare de sistem, pornind de la vizualizare și câmpul pe care îl afișează:
```ts
import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define';
const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
viewUniversalIdentifier: rocketIndexViewId,
fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER,
});
```
Observă primul argument: o coloană este plasată în spațiul de nume al aplicației care deține **câmpul pe care îl afișează**, nu al celei care deține vizualizarea. Un câmp pe care aplicația ta îl adaugă la un obiect standard își obține coloana derivată sub aplicația ta, pe o vizualizare deținută de Twenty.
<Warning>
Vizualizările de sistem și coloanele lor sunt **deținute de server**: rezolvă-le identificatorii
pentru a le referenția, niciodată pentru a le declara. `key` pe
[`defineView()`](/l/ro/developers/extend/apps/layout/views) este depreciat și
ignorat, astfel încât o vizualizare din manifest nu poate revendica niciodată cheia `INDEX`, iar serverul
provizionează deja o coloană pentru fiecare câmp pe care îl adaugi, deci declararea propriului tău
`defineViewField()` pentru același câmp într-o vizualizare de sistem intră în conflict cu aceasta.
</Warning>
## Obiecte standard Twenty
Pentru un obiect Twenty **standard** (Person, Company, Opportunity, …), nu trebuie să derivezi nimic: identificatorii sunt constante pre-calculate pe care le poți importa direct, atât pentru câmpuri, cât și pentru vizualizări.
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople.universalIdentifier
```
Apelează la utilitarele de mai sus atunci când obiectul este unul pe care aplicația ta îl definește cu [`defineObject()`](/l/ro/developers/extend/apps/data/objects), unde nu există o astfel de constantă.
<Note>
`name` este un câmp **implicit**, nu un câmp de sistem. Acesta își păstrează propriul identificator universal
hardcodat și nu este obținut prin
`getFieldUniversalIdentifier`. În obiectele pe care le definești, fă referire la câmpul
`name` prin identificatorul pe care i l-ai dat în `defineObject()`.
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Tipuri de activitate cronologică
description: Definiți evenimentele de audit automat și evenimentele de aplicație explicite care se execută în calendarele înregistrării.
icon: clock-rotate-left
---
Un tip de activitate cronologică definește vocabularul stabil pentru un eveniment afișat într-un calendar al înregistrării. Obiectele standard și obiectele din aplicație utilizează același contract: un tip are o etichetă și o pictogramă, poate declara opțional când este emis, și pot reda opțional prin una dintre [componentele frontale](/l/ro/developers/extend/apps/layout/front-components).
<Note>
Tipurile de activitate sunt în beta şi vor apărea în curând în 20 2,34. API-ul
poate evolua în timp ce învățăm din cazurile de utilizare a dezvoltatorilor de aplicații.
</Note>
Creați unul cu caseta de teme:
```bash filename="Terminal"
yarn twenty dev:add timelineActivityType
```
Sau definește direct:
```ts filename="src/timeline-activity-types/post-card-created.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7500',
name: 'postCardCreated',
label: 'created a post card',
icon: 'IconMail',
emit: {
on: 'created',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
},
});
```
## Evenimente automate și explicite
Adăugați `emit` când douăzeci de persoane ar trebui să creeze acest tip automat. `emit.on` suportă `created`, `updated`, `deleted`, `restored`, `linked` şi `unlinked`, în timp ce `emit.objectalIdentifier` identifică obiectul sursă. Doar un singur tip eficient poate gestiona aceeași tastă emit — `on`, obiect și opțional prin intermediul relației — într-un spațiu de lucru.
Fără `emit.through`, evenimentul este scris pe calendarul înregistrărilor sursă. Pentru a o acoperi la înregistrările conexe, setați \`emit.through. elationFieldUniversalIdentifier" fie la o relaţie directă de la un individ la altul, fie la o [relaţie de unu-la-multe joncţiuni ](/l/ro/developers/extend/apps/data/relations#junction-relations) la obiectul sursă. Relaţiile dintre Morph şi fiecare membru al grupului lor, astfel încât o singură declaraţie poate viza mai multe tipuri de obiecte.
Pentru o legătură directă, crearea sau restaurarea înregistrării sursă produce `linked`, ștergându-l produce `unlinked`, și redirecționarea relației produce `unlinked` pe ținta anterioară plus `linked` pe noul țintă. Alte actualizări sursă nu produc evenimente legate de legătură. Acesta este contractul utilizat de ataşamente, a cărui ţintă este o relaţie directă de morfologie.
Pentru o legătură de joncțiune, `universalSettings.junctionTargetFieldUniversalIdentifier` identifică relația de la obiectul de joncțiune la țintă. Crearea sau ştergerea unui rând de joncţiune produce `linked` sau `unlinked`. De asemenea, redirecționarea oricărei părți a unui rând de joncțiune produce un eveniment de legătură; actualizări ale câmpurilor de joncțiune care nu au legătură cu acestea.
Evenimentele `linked` şi `unlinked` necesită `emit.through`, deoarece declanşatorul lor este o schimbare la relaţia directă sau de joncţiune configurată.
De exemplu, acesta este același contract generic utilizat de note, sarcini, mesaje și evenimente calendaristice:
```ts filename="src/timeline-activity-types/post-card-linked.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/post-card-recipients-on-post-card.field';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7502',
name: 'postCardLinked',
label: 'received a post card',
icon: 'IconMail',
emit: {
on: 'linked',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
through: {
relationFieldUniversalIdentifier:
POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER,
},
},
});
```
Un `updated` prin eveniment este emis implicit pentru fiecare actualizare sursă. Setați `emit.through.triggerFieldUniversalIdentifiers` când numai modificările câmpurilor sursă selectate ar trebui să apară în termenele țintă.
Omiteți `emit` pentru un eveniment de domeniu explicit pe care funcția logică îl creează. Astfel se evită realizarea atât a unui rând automat de audit, cât și a unui rând explicit pentru aceeași operațiune.
```ts filename="src/timeline-activity-types/post-card-sent.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
});
```
Creați evenimente explicite cu [`createTimelineActivity()`](/l/ro/developers/extend/apps/logic/logic-functions#create-a-timeline-activity). Codul aplicației folosește identificatori universali stabili; Douăzeci rezolvă ID-urile de metadate specifice instalației.
## Redare personalizată
Fără o componentă din față, douăzeci face un rând nativ generic de pe eticheta tipului, pictograma și metadatele obiectelor legate. Aceasta funcționează pentru obiecte standard și personalizate fără un dispozitiv de redare specific obiectului.
Pentru detalii personalizate, setați `frontComponentUniversalIdentifier` la o componentă din față deținută de aceeași aplicație:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Rândul nativ rămâne prezentarea prăbuşită. Douăzeci de montați componenta frontală numai după ce utilizatorul se extinde acea linie, evitând un sandbox și un lucrător pentru fiecare eveniment vizibil. În interiorul componentei, apelați `useTimelineActivityId()` din `douăzeci de elemente-sdk/front-component` pentru a citi ID-ul rândului și a prelua orice date de care aveți nevoie prezentarea. Returnează `null` când componenta este randată în afara liniei cronologice.
## Înlocuirea prezentării altei aplicații
O aplicație deține contracte de cronologie automată pentru propriile obiecte. Pentru a personaliza un eveniment pe un obiect deținut de o altă aplicație, declarați tipul existent în mod explicit cu `replacesTimelineActivityTypeUniversalIdentifier`:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7503',
name: 'companyCreatedWithDeploymentContext',
label: 'was created from a deployment',
emit: {
on: 'created',
objectUniversalIdentifier: COMPANY_UNIVERSAL_IDENTIFIER,
},
replacesTimelineActivityTypeUniversalIdentifier:
RECORD_CREATED_TIMELINE_ACTIVITY_TYPE_UNIVERSAL_IDENTIFIER,
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
O suprascriere trebuie să includă `emit`, deoarece înlocuiește un slot automat existent emit mai degrabă decât un tip doar explicit. Tipul de referință trebuie să aparțină aplicației obiectului țintă și să descrie aceeași acțiune și aceeași rută. Eliminarea aplicației suprascriere restaurează tipul de bază; eliminarea tipului de bază dezactivează suprascrierea.
## Suprascrie spațiul de lucru și amuțiri
Administratorii spațiului de lucru pot schimba prezentarea unui tip sau pot bloca evenimentele sale automate și explicite fără a forja aplicația. Folosiți mutația `updateTimelineActivityType` a metadata API cu ID-ul specific instalației:
```graphql
mutation CustomizeTimelineActivityType(
$id: UUID!
$label: String
$icon: String
$isActive: Boolean
) {
updateTimelineActivityType(
input: { id: $id, label: $label, icon: $icon, isActive: $isActive }
) {
id
label
icon
isActive
}
}
```
`label` şi `icon` sunt stocate ca suprascrieri de spaţiu de lucru, astfel încât actualizările ulterioare ale aplicaţiei nu suprascriu opţiunile administratorului. Setați `isActive` la `false` pentru a opri emisia automată și pentru a respinge noi evenimente explicite de acest tip. Rândurile existente rămân vizibile.
Restabiliți setările implicite, inclusiv starea activă, cu:
```graphql
mutation ResetTimelineActivityType($id: UUID!) {
resetTimelineActivityType(id: $id) {
id
label
icon
isActive
}
}
```
## Câmpuri de configurare
| Câmp | Obligatoriu | Descriere |
| ------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------- |
| `universalIdentifier` | Da | UUID stabil pentru tipul de instalații și modernizări |
| `name` | Da | Nume programatic local aplicației; trebuie să fie unic în cadrul aplicației |
| `label` | Da | Text orientat către utilizator utilizat de către dispozitivul de redare nativă |
| `icon` | Nu | Douăzeci de nume afișate alături de eveniment |
| `emit` | Nu | Declarație de emisii automată; omit pentru tipuri numai explicite |
| `emit.on` | Cu emit | Acțiuni de audit care fac ca acest tip să fie redactat |
| `emit.objectUniversalIdentifier` | Cu emit | Obiect ale cărui înregistrări emit de acest tip |
| `emit.through.relationFieldUniversalIdentifier` | Nu | Relaţia directă sau de joncţiune utilizată pentru ventilator la termenele aferente |
| `emit.through.triggerFieldUniversalIdentifiers` | Nu | Câmpurile sursă care pot declanșa un `updated` prin eveniment |
| `frontComponentUniversalIdentifier` | Nu | Componentă frontală deținută de aplicație montată când rândul este extins |
| `replacesTimelineActivityTypeUniversalIdentifier` | Nu | Tipul de emit existent înlocuit; necesită `emit` |
Douăzeci de instantanee transformă identitatea semantică și prezentarea durabilă atunci când un eveniment este creat. Rândurile istorice păstrează acțiunea originală și semnificația obiectului. În timp ce tipul este instalat, eticheta curentă tradusă, pictograma și componenta frontală redare live; după dezinstalare, instantaneul face rândul să fie lizibil. Rezoluția folosește identificatorul universal, astfel încât prezentarea supraviețuiește și reinstalării aplicației.