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 3s

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,182 @@
---
title: Přeneste produktová data do Twenty
description: Synchronizujte data produktového katalogu z datového skladu do svého CRM podle plánu.
---
Použijte tento vzor k udržení synchronizace Twenty s produktovými daty z vašeho datového skladu (např. Snowflake, BigQuery, PostgreSQL).
## Struktura pracovního postupu
1. **Spouštěč**: Podle plánu
2. **Code**: Spusťte dotaz na svůj datový sklad
3. **Code** (volitelné): Naformátujte data jako pole
4. **Iterator**: Procházejte jednotlivé produkty
5. **Upsert Record**: Vytvořte nebo aktualizujte záznam v Twenty
<img src="/images/user-guide/workflows/create_users_workflow.png" style={{width:'100%'}} />
## Krok 1: Naplánujte spouštěč
Nastavte pracovní postup tak, aby běžel s frekvencí odpovídající vašim požadavkům na aktuálnost dat:
* Každých 5 minut pro synchronizaci téměř v reálném čase
* Každou hodinu pro méně kritická data
* Denně pro dávkové aktualizace
## Krok 2: Proveďte dotaz na svůj datový sklad
Přidejte akci **Code** pro načtení nejnovějších dat:
```javascript
export const main = async () => {
const intervalMinutes = 10; // Match your schedule frequency
const cutoffTime = new Date(Date.now() - intervalMinutes * 60 * 1000).toISOString();
// Replace with your actual data warehouse connection
const response = await fetch("https://your-warehouse-api.com/query", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
query: `
SELECT id, name, sku, price, stock_quantity, updated_at
FROM products
WHERE updated_at >= '${cutoffTime}'
`
})
});
const data = await response.json();
return { products: data.results };
};
```
<Tip>
Filtrováním podle `updated_at >= last X minutes` získáte pouze nedávno změněné záznamy. Díky tomu zůstane synchronizace efektivní.
</Tip>
## Krok 3: Formátování dat (volitelné)
Pokud váš datový sklad vrací data ve formátu, který vyžaduje transformaci, přidejte další akci **Code**. Mezi běžné transformace patří převody typů, přejmenování polí a čištění dat.
### Příklad: Uživatelská data s logickými a stavovými poli
```javascript
export const main = async (params: {
users: any;
}): Promise<object> => {
const { users } = params;
const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;
// Convert string "true"/"false" to actual booleans
const toBool = (v: any) => v === true || v === "true";
return {
users: usersFormatted.map((user) => ({
...user,
activityStatus: String(user.activityStatus).toUpperCase(),
isActiveLast30d: toBool(user.isActiveLast30d),
isActiveLast7d: toBool(user.isActiveLast7d),
isActiveLast24h: toBool(user.isActiveLast24h),
isTwenty: toBool(user.isTwenty),
})),
};
};
```
### Příklad: Produktová data s převody typů
```javascript
export const main = async (params: { products: any }) => {
const products = typeof params.products === "string"
? JSON.parse(params.products)
: params.products;
return {
products: products.map(product => ({
externalId: product.id,
name: product.name,
sku: product.sku,
price: parseFloat(product.price), // String → Number
stockQuantity: parseInt(product.stock_quantity),
isActive: product.status === "active" // String → Boolean
}))
};
};
```
### Příklad: Formátování dat a měn
```javascript
export const main = async (params: { deals: any }) => {
const deals = typeof params.deals === "string"
? JSON.parse(params.deals)
: params.deals;
return {
deals: deals.map(deal => ({
...deal,
// Convert Unix timestamp to ISO date
closedAt: deal.closed_timestamp
? new Date(deal.closed_timestamp * 1000).toISOString()
: null,
// Ensure amount is a number (remove currency symbols)
amount: parseFloat(String(deal.amount).replace(/[^0-9.-]/g, "")),
// Normalize stage names
stage: deal.stage?.toLowerCase().replace(/_/g, " ")
}))
};
};
```
### Běžné transformace
| Zdrojový formát | Cílový formát | Kód |
| -------------------- | -------------------- | ---------------------------------------- |
| `"true"` / `"false"` | `true` / `false` | `v === true \|\| v === "true"` |
| `"123.45"` | `123.45` | `parseFloat(value)` |
| `"active"` | `"ACTIVE"` | `value.toUpperCase()` |
| `1704067200` (Unix) | Datum ve formátu ISO | `new Date(v * 1000).toISOString()` |
| `"$1,234.56"` | `1234.56` | `parseFloat(v.replace(/[^0-9.-]/g, ""))` |
| `null` / `undefined` | `""` | `value \|\| ""` |
## Krok 4: Projděte produkty
Přidejte akci **Iterator**:
* Vstup: `{{code.products}}`
Tímto projdete každý produkt v poli.
## Krok 5: Proveďte upsert pro každý záznam
Uvnitř iterátoru přidejte akci **Upsert Record**:
| Nastavení | Hodnota |
| --------------- | --------------------------------------------- |
| **Objekt** | Váš vlastní objekt Product |
| **Shoda podle** | Externí ID nebo SKU (jedinečný identifikátor) |
| **Název** | `{{iterator.item.name}}` |
| **SKU** | `{{iterator.item.sku}}` |
| **Cena** | `{{iterator.item.price}}` |
<Tip>
Použijte **Upsert** (aktualizace nebo vytvoření) místo vytváření samostatných větví pro vytvoření vs. aktualizaci. Je to rychlejší na vytvoření a snazší na ladění.
</Tip>
## Příklady použití
| Zdroj | Data |
| ------------------------ | ------------------------------------------- |
| **ERP systém** | Katalog produktů, ceny, skladové zásoby |
| **E-commerce platforma** | Objednávky, zákazníci, aktualizace produktů |
| **Datový sklad** | Agregované metriky, obohacená data |
| **Skladový systém** | Stavy zásob, upozornění na doobjednání |
## Související
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
* [Akce pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-actions)
* [Práce s poli v akcích Code](/l/cs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,130 @@
---
title: Přeneste odeslané odpovědi z Typeformu do Twenty
description: Zpracujte payload webhooku Typeformu a vytvářejte leady z odeslaných formulářů.
---
Pro standardní nastavení webhooku viz [Nastavení spouštěče webhooku](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger). Tento článek se věnuje specifickému zpracování požadovanému pro vlastní strukturu payloadu Typeformu.
### Krok 1: Vytvořte pracovní postup pro webhook
1. Přejděte na **Nastavení → Pracovní postupy**
2. Klikněte na **+ Nový pracovní postup**
3. Vyberte **Webhook** jako spouštěč
4. Zkopírujte URL webhooku
### Krok 2: Nakonfigurujte Typeform
1. V Typeformu otevřete svůj formulář
2. Přejděte na **Connect → Webhooks**
3. Vložte svou URL webhooku Twenty
4. Uložit
### Krok 3: Porozumějte payloadu Typeformu
Typeform posílá vnořenou strukturu JSON. Zde je zjednodušený příklad:
```json
{
"event_type": "form_response",
"form_response": {
"form_id": "abc123",
"submitted_at": "2025-01-15T10:30:00Z",
"answers": [
{
"text": "Jane",
"type": "text",
"field": { "id": "field1", "type": "short_text", "title": "First Name" }
},
{
"text": "Smith",
"type": "text",
"field": { "id": "field2", "type": "short_text", "title": "Last Name" }
},
{
"text": "Acme Corp",
"type": "text",
"field": { "id": "field3", "type": "short_text", "title": "Company" }
},
{
"email": "jane@acme.com",
"type": "email",
"field": { "id": "field4", "type": "email", "title": "Email" }
},
{
"type": "choice",
"field": { "id": "field5", "type": "dropdown", "title": "Team Size" },
"choice": { "label": "10-50" }
}
]
}
}
```
Důležité body:
* Data formuláře jsou vnořena pod `form_response`
* **Odpovědi jsou vráceny jako pole**, nikoli jako pojmenovaná pole
* Každá odpověď obsahuje pro referenci typ a název pole
### Krok 4: Extrahujte pole z pole odpovědí
Protože `answers` je pole, v následujících krocích můžete vybrat pouze celé pole — nikoli jednotlivá pole. Přidejte akci **Code** k extrakci potřebných polí:
```javascript
export const main = async (params: {
answers: any;
}): Promise<object> => {
const { answers } = params;
// Handle input that may come as a string or an array
const answersFormatted = typeof answers === "string"
? JSON.parse(answers)
: answers;
// Extract fields by position or by finding the field type
const firstName = answersFormatted[0]?.text || "";
const lastName = answersFormatted[1]?.text || "";
const company = answersFormatted[2]?.text || "";
const email = answersFormatted.find(a => a.type === "email")?.email || "";
const teamSize = answersFormatted.find(a => a.type === "choice")?.choice?.label || "";
return {
contact: {
firstName,
lastName,
company,
email,
teamSize
}
};
};
```
Nyní v následujících krocích můžete z výběru proměnných vybrat `contact.firstName`, `contact.email` apod.
<Tip>
Další podrobnosti o práci s poli v akcích Code viz [Práce s poli v akcích Code](/l/cs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
### Krok 5: Vytvořte záznam
Přidejte akci **Vytvořit záznam**:
| Pole | Hodnota |
| -------------- | -------------------------------------------------------------- |
| **Objekt** | Osoby |
| **Jméno** | `{{code.contact.firstName}}` |
| **Příjmení** | `{{code.contact.lastName}}` |
| **Email** | `{{code.contact.email}}` |
| **Společnost** | Vyhledejte nebo vytvořte na základě `{{code.contact.company}}` |
### Krok 6: Otestujte a aktivujte
1. Odešlete testovou odpověď v Typeformu
2. Zkontrolujte běh pracovního postupu, abyste ověřili, že data byla zachycena
3. Aktivujte pracovní postup
## Související
* [Nastavení spouštěče webhooku](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)
* [Práce s poli v akcích Code](/l/cs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,263 @@
---
title: Vygenerovat PDF z Twenty
description: Vytvořte pracovní postup pro generování a připojení PDF (například nabídky) k záznamu.
---
Automaticky vygenerujte nebo získejte PDF a připojte ho k záznamu v Twenty. To se běžně používá k vytváření nabídek, faktur nebo reportů, které jsou propojené se společnostmi, příležitostmi nebo jinými objekty.
## Přehled
Tento pracovní postup používá **Ruční spouštěč**, takže uživatelé mohou na požádání vygenerovat PDF pro libovolný vybraný záznam. O zpracování se stará **logická funkce**:
1. Stažení PDF z adresy URL (ze služby pro generování PDF)
2. Nahrání souboru do Twenty
3. Vytvoření přílohy propojené se záznamem
## Předpoklady
Než nastavíte pracovní postup:
1. **Vytvořte klíč API**: Přejděte do **Nastavení → API** a vytvořte nový klíč API. Tento token budeš potřebovat pro logickou funkci.
2. **Nastavte službu pro generování PDF** (volitelné): Pokud chcete dynamicky generovat PDF (např. nabídky), použijte službu jako Carbone, PDFMonkey nebo DocuSeal k vytvoření PDF a získání adresy URL pro stažení.
## Nastavení krok za krokem
### Krok 1: Nakonfigurujte spouštěč
1. Přejděte na **Pracovní postupy** a vytvořte nový pracovní postup
2. Vyberte **Ruční spouštěč**
3. Zvolte objekt, ke kterému chcete připojovat PDF (např. **Společnost** nebo **Příležitost**)
<Tip>
s manuálním spouštěčem, uživatelé mohou spustit tento pracovní postup pomocí tlačítka, které se zobrazí vpravo nahoře, jakmile je vybrán záznam - toto běží pracovní postup pro generování a připojení PDF.
</Tip>
### Krok 2: Přidejte logickou funkci
1. Přidat akci **Code** (funkce Logike)
2. Vytvořte novou funkci pomocí kódu níže
3. Nakonfigurujte vstupní parametry
#### Vstupní parametry
| Parametr | Hodnota |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
Pokud připojujete k jinému objektu (Osoba, Příležitost apod.), přejmenujte parametr podle toho (např. `personId`, `opportunityId`) a aktualizujte logickou funkci.
</Note>
#### Kód logické funkce
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Krok 3: Přizpůsobte pro svůj případ použití
#### Chcete-li připojit k jinému objektu
Nahraďte `targetCompanyId` příslušným polem:
| Objekt | Název pole |
| -------------- | -------------------------- |
| Společnost | `targetCompanyId` |
| Osoba | `targetPersonId` |
| Příležitost | `targetOpportunityId` |
| Vlastní objekt | `targetYourCustomObjectId` |
Aktualizujte jak parametr funkce, tak objekt `variables.data` v mutaci přílohy.
#### Chcete-li použít dynamickou adresu URL PDF
Pokud používáte službu pro generování PDF, můžete:
1. Nejprve proveďte akci HTTP Request pro vygenerování PDF
2. Předejte vrácenou adresu URL PDF logické funkci jako parametr
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Krok 4: Otestujte a aktivujte
1. Uložte pracovní postup
2. Přejděte na záznam společnosti
3. Klikněte na nabídku **⋮** a vyberte svůj pracovní postup
4. Zkontrolujte v části **Přílohy** u záznamu, že bylo PDF připojeno
5. Aktivujte pracovní postup
## Kombinace se službami pro generování PDF
Pro vytváření dynamických nabídek nebo faktur:
### Příklad: Vygenerovat nabídku → Připojit PDF
| Krok | Akce | Účel |
| ---- | --------------------------- | ------------------------------------------------------ |
| 1 | Ruční spouštěč (Společnost) | Uživatel spustí na záznamu |
| 2 | Vyhledat záznam | Získat podrobnosti o příležitosti nebo řádkové položce |
| 3 | HTTP požadavek | Zavolat API pro generování PDF s daty záznamu |
| 4 | Logická funkce | Stáhnout a připojit vygenerované PDF |
### Oblíbené služby pro generování PDF
* **Carbone** - Generování dokumentů založené na šablonách
* **PDFMonkey** - Dynamické vytváření PDF ze šablon
* **DocuSeal** - Platforma pro automatizaci dokumentů
* **Documint** - Generování dokumentů primárně přes API
Každá služba poskytuje API, které vrátí PDF adresu URL, kterou můžete předat funkci Logika.
## Řešení potíží
| Problém | Řešení |
| -------------------------------- | --------------------------------------------------------------- |
| "Nepodařilo se stáhnout PDF" | Zkontrolujte, že adresa URL PDF je dostupná a vrací platné PDF |
| "Nepodařilo se nahrát" | Ověřte, že váš klíč API je platný a má oprávnění k zápisu |
| "Vytvoření přílohy se nezdařilo" | Ujistěte se, že název pole ID objektu odpovídá cílovému objektu |
## Související
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
* [Logické funkce](/l/cs/user-guide/workflows/capabilities/workflow-actions#code)
* [Vygenerujte nabídku nebo fakturu z Twenty](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)

View File

@@ -0,0 +1,144 @@
---
title: Vytvořte nabídku nebo fakturu z Twenty
description: Automaticky vytvářejte faktury v externích nástrojích, když se obchodní případy uzavřou.
---
Automaticky odesílejte data o obchodních případech do vašeho fakturačního systému (Stripe, QuickBooks, Xero atd.). když je příležitost vyhrána.
## Struktura pracovního postupu
1. **Spouštěč**: Záznam je aktualizován (Příležitost)
2. **Filtr**: Stav = Uzavřeno – vyhráno
3. **Vyhledat záznam**: Získat podrobnosti o společnosti
4. **Kód** (volitelné): Formátovat payload
5. **Požadavek HTTP**: Odeslat do fakturačního systému
## Krok 1: Nastavení spouštěče
1. Vytvořte nový pracovní postup
2. Vyberte spouštěč **Záznam je aktualizován**
3. Vyberte jako objekt **Příležitost**
## Krok 2: Filtrování na Uzavřeno – vyhráno
Přidejte akci **Filtr**, aby se pokračovalo pouze tehdy, když je obchodní případ vyhraný:
| Nastavení | Hodnota |
| ------------ | -------------------------------------- |
| **Pole** | Fáze |
| **Podmínka** | Rovná se |
| **Hodnota** | `CLOSED_WON` (nebo název vašeho stavu) |
<Tip>
Spouštěč se vyvolá při jakékoli aktualizaci Příležitosti. Filtr zajistí, že pracovní postup bude pokračovat pouze tehdy, když se stav změní na Uzavřeno – vyhráno.
</Tip>
## Krok 3: Získat podrobnosti o společnosti
Záznam Příležitosti nemusí obsahovat všechna pole Společnosti, která potřebujete pro fakturu. Přidejte akci **Vyhledat záznam**:
| Nastavení | Hodnota |
| --------------- | ------------------------------------------ |
| **Objekt** | Společnost |
| **Shoda podle** | ID se rovná `{{trigger.object.companyId}}` |
Tímto získáte úplný záznam Společnosti s fakturační adresou, daňovým ID atd.
## Krok 4: Formátování payloadu (volitelné)
Pokud váš fakturační systém očekává specifický formát, přidejte akci **Kód**:
```javascript
export const main = async (params: {
opportunity: any;
company: any;
}): Promise<object> => {
const { opportunity, company } = params;
return {
invoice: {
// Customer info from Company
customer_name: company.name,
customer_email: company.email || "",
billing_address: {
line1: company.address?.street || "",
city: company.address?.city || "",
postal_code: company.address?.postalCode || "",
country: company.address?.country || ""
},
tax_id: company.taxId || null,
// Invoice details from Opportunity
amount: opportunity.amount,
currency: opportunity.currency || "USD",
description: `Invoice for ${opportunity.name}`,
due_days: 30,
// Reference back to Twenty
metadata: {
opportunity_id: opportunity.id,
company_id: company.id
}
}
};
};
```
## Krok 5: Odeslání do fakturačního systému
Přidejte akci **Požadavek HTTP**:
| Nastavení | Hodnota |
| ----------- | -------------------------------------------- |
| **Metoda** | POST |
| **URL** | Koncový bod API vašeho fakturačního systému |
| **Záhlaví** | `Authorization: Bearer YOUR_API_KEY` |
| **Tělo** | `{{code.invoice}}` nebo namapujte pole přímo |
### Příklad: Faktura ve Stripe
```
POST https://api.stripe.com/v1/invoices
Headers:
Authorization: Bearer sk_live_xxx
Content-Type: application/x-www-form-urlencoded
Body:
customer: {{company.stripeCustomerId}}
collection_method: send_invoice
days_until_due: 30
```
### Příklad: Faktura v QuickBooks
```
POST https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Body: {{code.invoice}}
```
## Shrnutí kompletního pracovního postupu
| Krok | Akce | Účel |
| ---- | ----------------------------- | ------------------------------------------------------ |
| 1 | Spouštěč: Záznam aktualizován | Spustí se při jakékoli změně Příležitosti |
| 2 | Filtr | Pokračujte pouze pokud Stav = Uzavřeno – vyhráno |
| 3 | Vyhledat záznam | Získejte úplné podrobnosti o Společnosti pro fakturaci |
| 4 | Kód | Naformátujte data pro fakturační API |
| 5 | HTTP požadavek | Vytvořte fakturu v externím systému |
## Tipy
* **Ukládejte externí ID**: Uložte ID faktury vrácené API zpět do Příležitosti pomocí akce **Aktualizovat záznam**
* **Zpracování chyb**: Přidejte větev pro odeslání upozornění, pokud požadavek HTTP selže
* **Nejprve testujte**: Použijte sandbox/test režim vašeho fakturačního systému před ostrým spuštěním
## Související
* [Vygenerovat PDF z Twenty](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — připojte vygenerované soubory PDF k záznamům
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
* [Akce pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-actions)
* [Automatizace pro Uzavřeno – vyhráno](/l/cs/user-guide/workflows/how-tos/crm-automations/closed-won-automations)

View File

@@ -0,0 +1,170 @@
---
title: Nastavení spouštěče webhooku
description: Přijímejte data z externích služeb pro spouštění pracovních postupů.
---
Spouštěče webhooků umožňují externím službám spustit vaše pracovní postupy zasláním dat na jedinečnou adresu URL. Použijte je k propojení formulářů, aplikací třetích stran a vlastních integrací.
## Kdy použít webhooky
| Případ použití | Příklad |
| -------------------------- | -------------------------------------------- |
| **Webové formuláře** | Odeslání kontaktního formuláře vytváří leady |
| **Aplikace třetích stran** | Platba ve Stripe → vytvořit záznam zákazníka |
| **Vlastní integrace** | Vaše aplikace → automatizace v Twenty |
| **Nástroje bez kódu** | Připojení Zapier, Make, n8n |
## Nastavení krok za krokem
### Krok 1: Vytvořte pracovní postup
1. Přejděte na **Nastavení → Pracovní postupy**
2. Klikněte na **+ Nový pracovní postup**
3. Pojmenujte jej (např. "Odeslání webového formuláře")
### Krok 2: Nakonfigurujte spouštěč webhooku
1. Klikněte na blok spouštěče
2. Vyberte **Webhook**
3. Obdržíte jedinečnou adresu URL webhooku, například:
```
https://api.twenty.com/webhooks/workflow/abc123...
```
4. Zkopírujte tuto adresu URL — budete ji potřebovat pro svou externí službu
### Krok 3: Definujte očekávanou datovou strukturu
Pro požadavky **POST** definujte očekávanou strukturu těla:
1. Klikněte na **Definovat očekávané tělo**
2. Zadejte ukázkový JSON, který odpovídá tomu, co vaše služba pošle:
```json
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"company": "Acme Inc",
"message": "Interested in your product"
}
```
3. Klikněte na **Uložit** — tím se vytvoří proměnné, které můžete použít v dalších krocích
### Krok 4: Přidejte akce
Nyní přidejte akce, které využívají data z webhooku:
**Příklad: Vytvořit záznam osoby**
1. Přidejte akci **Vytvořit záznam**
2. Vyberte objekt **People**
3. Namapujte pole:
| Pole | Hodnota |
| ---------- | -------------------------------------------------------------- |
| Jméno | `{{trigger.body.firstName}}` |
| Příjmení | `{{trigger.body.lastName}}` |
| Email | `{{trigger.body.email}}` |
| Společnost | Vyhledejte nebo vytvořte na základě `{{trigger.body.company}}` |
### Krok 5: Otestujte webhook
Před aktivací otestujte svůj webhook:
**Pomocí cURL**:
```bash
curl -X POST https://api.twenty.com/webhooks/workflow/abc123... \
-H "Content-Type: application/json" \
-d '{"firstName":"Test","lastName":"User","email":"test@example.com"}'
```
**Pomocí Postmanu nebo podobného nástroje**:
1. Vytvořte požadavek POST na adresu URL vašeho webhooku
2. Nastavte hlavičku Content-Type na `application/json`
3. Přidejte své testovací tělo JSON
4. Odešlete a zkontrolujte spuštění pracovního postupu
### Krok 6: Aktivujte
Po otestování klikněte na **Aktivovat**, aby byl pracovní postup aktivní.
## Zpracování různých datových struktur
### Vnořená data
Pokud váš webhook posílá vnořená data:
```json
{
"contact": {
"name": "John Doe",
"email": "john@example.com"
},
"source": "website"
}
```
Odkazujte pomocí: `{{trigger.body.contact.email}}`
### Pole
Pokud data obsahují pole:
```json
{
"items": [
{"name": "Product A", "qty": 2},
{"name": "Product B", "qty": 1}
]
}
```
Jak pole zpracujete, závisí na vašem případě použití:
**Neznámý počet položek → Použijte Iterator**
Pokud potřebujete zpracovat každou položku v poli (např. pro každou vytvořit záznam), přidejte akci **Code** pro rozparsování pole a poté použijte **Iterator**:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return { items };
};
```
Poté použijte Iterator k procházení: `{{code.items}}`
**Známá/konkrétní pole → Extrahujte do pojmenovaných polí**
Pokud pole obsahuje konkrétní položky, ke kterým chcete přistupovat jednotlivě (např. odpovědi formuláře, kde pozice 0 je vždy "křestní jméno", pozice 1 je vždy "příjmení"), přidejte akci **Code** k jejich extrakci:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return {
product: {
name: items[0]?.name || "",
qty: items[0]?.qty || 0
}
};
};
```
Nyní můžete v následujících krocích vybírat `product.name` a `product.qty` jednotlivě.
<Tip>
Další podrobnosti o práci s poli viz [Zpracování polí v akcích Code](/l/cs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
## Související
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
* [Akce pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-actions)