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: Ürün Verilerini Twenty'ye Aktarın
description: Ürün katalog verilerini bir veri ambarından CRM'inize bir programa göre eşitleyin.
---
Veri ambarınızdaki ürün verileriyle Twenty'yi senkron tutmak için bu kalıbı kullanın (örn. Snowflake, BigQuery, PostgreSQL).
## İş Akışı Yapısı
1. **Tetikleyici**: Zamanlanmış
2. **Kod**: Veri ambarınızı sorgulayın
3. **Kod** (isteğe bağlı): Veriyi dizi olarak biçimlendirin
4. **Yineleyici**: Her ürün üzerinde döngü kurun
5. **Kaydı Ekle/Güncelle**: Twenty'de oluşturun veya güncelleyin
<img src="/images/user-guide/workflows/create_users_workflow.png" style={{width:'100%'}} />
## Adım 1: Tetiklemeyi Zamanlayın
İş akışını, veri tazelik gereksinimlerinize uygun bir sıklıkta çalışacak şekilde ayarlayın:
* Neredeyse gerçek zamanlı eşitleme için her 5 dakikada bir
* Daha az kritik veriler için saatte bir
* Toplu güncellemeler için günlük
## Adım 2: Veri Ambarınızı Sorgulayın
En son verileri almak için bir **Kod** eylemi ekleyin:
```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>
`updated_at >= last X minutes` ile filtreleyerek yalnızca yakın zamanda değişen kayıtları alın. Bu, eşitlemeyi verimli tutar.
</Tip>
## Adım 3: Verileri Biçimlendirin (İsteğe Bağlı)
Veri ambarınız dönüştürme gerektiren bir biçimde veri döndürüyorsa, başka bir **Kod** eylemi ekleyin. Yaygın dönüşümler arasında tür dönüştürmeleri, alan yeniden adlandırma ve veri temizleme bulunur.
### Örnek: Boolean ve Durum Alanlarına Sahip Kullanıcı Verileri
```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),
})),
};
};
```
### Örnek: Tür Dönüşümleri Olan Ürün Verileri
```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
}))
};
};
```
### Örnek: Tarih ve Para Birimi Biçimlendirme
```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, " ")
}))
};
};
```
### Yaygın Dönüşümler
| Kaynak Format | Hedef Format | Kod |
| -------------------- | ---------------- | ---------------------------------------- |
| `"true"` / `"false"` | `true` / `false` | `v === true \|\| v === "true"` |
| `"123.45"` | `123.45` | `parseFloat(value)` |
| `"active"` | `"ACTIVE"` | `value.toUpperCase()` |
| `1704067200` (Unix) | ISO tarihi | `new Date(v * 1000).toISOString()` |
| `"$1,234.56"` | `1234.56` | `parseFloat(v.replace(/[^0-9.-]/g, ""))` |
| `null` / `undefined` | `""` | `value \|\| ""` |
## Adım 4: Ürünler Üzerinde Yineleyin
Bir **Iterator** eylemi ekleyin:
* Girdi: `{{code.products}}`
Bu, dizideki her ürün üzerinde döngü kurar.
## Adım 5: Her Kaydı Ekle/Güncelleyin
Yineleyici içinde bir **Kaydı Ekle/Güncelle** eylemi ekleyin:
| Ayar | Değer |
| ---------------------- | ------------------------------------------ |
| **Nesne** | Özel Ürün nesneniz |
| **Şuna göre eşleştir** | Harici ID veya SKU (benzersiz tanımlayıcı) |
| **Ad** | `{{iterator.item.name}}` |
| **SKU** | `{{iterator.item.sku}}` |
| **Fiyat** | `{{iterator.item.price}}` |
<Tip>
Oluşturma ve güncelleme için ayrı dallar oluşturmak yerine **Upsert** (güncelle veya oluştur) kullanın. Oluşturmak daha hızlı ve hata ayıklamak daha kolaydır.
</Tip>
## Örnek Kullanım Durumları
| Kaynak | Veri |
| ----------------------- | ---------------------------------------------- |
| **ERP sistemi** | Ürün kataloğu, fiyatlandırma, envanter |
| **E-ticaret platformu** | Siparişler, müşteriler, ürün güncellemeleri |
| **Veri ambarı** | Toplanmış metrikler, zenginleştirilmiş veriler |
| **Envanter sistemi** | Stok seviyeleri, yeniden sipariş uyarıları |
## İlgili
* [İş Akışı Tetikleyicileri](/l/tr/user-guide/workflows/capabilities/workflow-triggers)
* [İş Akışı Eylemleri](/l/tr/user-guide/workflows/capabilities/workflow-actions)
* [Kod Eylemlerinde Dizileri Ele Alma](/l/tr/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,130 @@
---
title: Typeform Yanıtlarını Twenty'ye Aktarın
description: Form yanıtlarından potansiyel müşteriler oluşturmak için Typeform'un webhook yükünü işleyin.
---
Standart webhook kurulumu için bkz. [Bir Webhook Tetikleyicisi Kurun](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger). Bu makale, Typeform'un özel yük yapısı için gereken özel işlemeyi kapsar.
### Adım 1: Bir Webhook İş Akışı Oluşturun
1. **Ayarlar → İş Akışları** bölümüne gidin
2. **+ Yeni İş Akışı**'na tıklayın
3. Tetikleyici olarak **Webhook**'u seçin
4. Webhook URL'sini kopyalayın
### Adım 2: Typeform'u Yapılandırın
1. Typeform'da formunuzu açın
2. **Connect → Webhooks** bölümüne gidin
3. Twenty webhook URL'nizi yapıştırın
4. Kaydet
### Adım 3: Typeform Yükünü Anlayın
Typeform iç içe geçmiş bir JSON yapısı gönderir. İşte basitleştirilmiş bir örnek:
```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" }
}
]
}
}
```
Dikkat edilmesi gereken başlıca noktalar:
* Form verileri `form_response` altında iç içe yapıdadır
* **Yanıtlar bir dizi olarak döndürülür**, adlandırılmış alanlar olarak değil
* Her yanıt, referans için alan türünü ve başlığını içerir
### Adım 4: Yanıtlar Dizisinden Alanları Çıkarın
`answers` bir dizi olduğundan, sonraki adımlarda yalnızca tüm diziyi seçebilirsiniz — tek tek alanları değil. Gerekli alanları çıkarmak için bir **Kod** eylemi ekleyin:
```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
}
};
};
```
Sonraki adımlarda, değişken seçiciden `contact.firstName` ve `contact.email` gibi tekil alanları artık seçebilirsiniz.
<Tip>
Kod eylemlerinde dizileri ele alma hakkında daha fazla ayrıntı için bkz. [Kod Eylemlerinde Dizileri Ele Alma](/l/tr/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
### Adım 5: Kaydı Oluşturun
**Kayıt Oluştur** eylemini ekleyin:
| Alan | Değer |
| ----------- | ------------------------------------------------------- |
| **Nesne** | İnsanlar |
| **Ad** | `{{code.contact.firstName}}` |
| **Soyadı** | `{{code.contact.lastName}}` |
| **E-posta** | `{{code.contact.email}}` |
| **Şirket** | `{{code.contact.company}}` temelinde ara veya oluşturun |
### Adım 6: Test Edin ve Etkinleştirin
1. Typeform'da bir test yanıtı gönderin
2. Verilerin yakalandığını doğrulamak için iş akışı çalıştırmasını kontrol edin
3. İş akışını etkinleştirin
## İlgili
* [Bir Webhook Tetikleyicisi Kurun](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)
* [Kod Eylemlerinde Dizileri Ele Alma](/l/tr/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,263 @@
---
title: Twenty'den bir PDF oluşturun
description: Bir kayda PDF (ör. bir teklif) oluşturup eklemek için bir iş akışı oluşturun.
---
Twenty'de PDF'yi otomatik olarak oluşturun veya edinin ve bir kayda ekleyin. Bu, Şirketler, Fırsatlar veya diğer nesnelere bağlı teklif, fatura ya da raporlar oluşturmak için yaygın olarak kullanılır.
## Genel Bakış
Bu iş akışı, kullanıcıların seçilen herhangi bir kayıt için isteğe bağlı olarak PDF oluşturabilmesi için **Manuel Tetikleyici** kullanır. Bir **Mantıksal İşlev** şunları gerçekleştirir:
1. PDF'yi bir URL'den indirme (bir PDF oluşturma hizmetinden)
2. Dosyayı Twenty'ye yükleme
3. Kayda bağlı bir ek oluşturma
## Ön Gereksinimler
İş akışını kurmadan önce:
1. **Bir API Anahtarı oluşturun**: **Ayarlar → API'ler**'e gidin ve yeni bir API anahtarı oluşturun. Mantık İşlevi için bu belirtece ihtiyacınız olacak.
2. **Bir PDF oluşturma hizmeti kurun** (isteğe bağlı): PDF'leri dinamik olarak oluşturmak istiyorsanız (ör. teklifler), PDF'yi oluşturmak ve bir indirme URL'si almak için Carbone, PDFMonkey veya DocuSeal gibi bir hizmet kullanın.
## Adım Adım Kurulum
### Adım 1: Tetikleyiciyi Yapılandırın
1. **İş Akışları**'na gidin ve yeni bir iş akışı oluşturun
2. **Manuel Tetikleyici**'yi seçin
3. PDF'leri eklemek istediğiniz nesneyi seçin (ör. **Company** veya **Opportunity**)
<Tip>
Manuel Tetikleyici ile kullanıcılar, bir kayıt seçildiğinde sağ üstte görünen bir düğmeyi kullanarak bu iş akışını çalıştırıp bir PDF oluşturabilir ve ekleyebilir; bu, PDF oluşturmak ve eklemek için iş akışını çalıştırır.
</Tip>
### Adım 2: Bir Mantıksal İşlev ekleyin
1. Bir **Kod** eylemi (Mantık İşlevi) ekleyin
2. Aşağıdaki kodla yeni bir işlev oluşturun
3. Girdi parametrelerini yapılandırın
#### Girdi Parametreleri
| Parametre | Değer |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
Farklı bir nesneye (Person, Opportunity vb.) ekliyorsanız, parametreyi buna göre yeniden adlandırın (ör. `personId`, `opportunityId`) ve mantıksal işlevi güncelleyin.
</Note>
#### Mantıksal İşlev Kodu
```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;
};
```
### Adım 3: Kullanım Senaryonuza Göre Özelleştirin
#### Farklı bir nesneye eklemek için
`targetCompanyId` değerini uygun alanla değiştirin:
| Nesne | Alan Adı |
| ---------- | -------------------------- |
| Şirket | `targetCompanyId` |
| Kişi | `targetPersonId` |
| Fırsat | `targetOpportunityId` |
| Özel Nesne | `targetYourCustomObjectId` |
Ek oluşturma mutasyonunda hem işlev parametresini hem de `variables.data` nesnesini güncelleyin.
#### Dinamik bir PDF URL'si kullanmak için
Bir PDF oluşturma hizmeti kullanıyorsanız şunları yapabilirsiniz:
1. Önce PDF'yi oluşturmak için bir HTTP İsteği eylemi yapın
2. Döndürülen PDF URL'sini mantıksal işleve bir parametre olarak iletin
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Adım 4: Test Edin ve Etkinleştirin
1. İş akışını kaydedin
2. Bir Company kaydına gidin
3. **⋮** menüsüne tıklayın ve iş akışınızı seçin
4. PDF'nin eklendiğini doğrulamak için kayıttaki **Attachments** bölümünü kontrol edin
5. İş akışını etkinleştirin
## PDF Oluşturma Hizmetleriyle Birleştirme
Dinamik teklifler veya faturalar oluşturmak için:
### Örnek: Teklif Oluştur → PDF'yi Ekle
| Adım | Eylem | Amaç |
| ---- | ---------------------------- | ------------------------------------------------ |
| 1 | Manuel Tetikleyici (Company) | Kullanıcı bir kayıtta başlatır |
| 2 | Kayıt Ara | Opportunity veya satır öğesi ayrıntılarını alın |
| 3 | HTTP İsteği | Kayıt verileriyle PDF oluşturma API'sini çağırın |
| 4 | Mantık İşlevi | Oluşturulan PDF'yi indirin ve ekleyin |
### Popüler PDF Oluşturma Hizmetleri
* **Carbone** - Şablon tabanlı belge oluşturma
* **PDFMonkey** - Şablonlardan dinamik PDF oluşturma
* **DocuSeal** - Belge otomasyon platformu
* **Documint** - API-öncelikli belge oluşturma
Her hizmet, bir PDF URL'si döndüren bir API sağlar; ardından bunu Mantık İşlevi'ne iletebilirsiniz.
## Sorun Giderme
| Sorun | Çözüm |
| ------------------------ | -------------------------------------------------------------------------------- |
| "PDF indirilemedi" | PDF URL'sinin erişilebilir olduğunu ve geçerli bir PDF döndürdüğünü kontrol edin |
| "Yükleme başarısız" | API anahtarınızın geçerli olduğunu ve yazma izinlerine sahip olduğunu doğrulayın |
| "Ek oluşturma başarısız" | Nesne kimliği alan adının hedef nesnenizle eşleştiğinden emin olun |
## İlgili
* [İş Akışı Tetikleyicileri](/l/tr/user-guide/workflows/capabilities/workflow-triggers)
* [Mantıksal İşlevler](/l/tr/user-guide/workflows/capabilities/workflow-actions#code)
* [Twenty'den Teklif veya Fatura Oluşturun](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)

View File

@@ -0,0 +1,144 @@
---
title: Twenty'den Teklif veya Fatura Oluşturun
description: Fırsatlar kapandığında harici araçlarda otomatik olarak faturalar oluşturun.
---
Fırsat verilerini faturalama sisteminize (Stripe, QuickBooks, Xero, vb.) otomatik olarak gönderin bir fırsat kazanıldığında.
## İş Akışı Yapısı
1. **Tetikleme**: Kayıt Güncellendi (Fırsat)
2. **Filtre**: Aşama = Closed Won
3. **Kayıt Ara**: Şirket ayrıntılarını alın
4. **Kod** (isteğe bağlı): Yükü biçimlendirin
5. **HTTP İsteği**: Faturalama sistemine gönderin
## Adım 1: Tetiklemeyi Ayarlayın
1. Yeni bir iş akışı oluşturun
2. **Kayıt Güncellendi** tetikleyicisini seçin
3. Nesne olarak **Fırsat**'ı seçin
## Adım 2: Closed Won için filtreleyin
Yalnızca fırsat kazanıldığında devam etmek için bir **Filtre** eylemi ekleyin:
| Ayar | Değer |
| --------- | -------------------------------- |
| **Alan** | Aşama |
| **Koşul** | Eşittir |
| **Değer** | `CLOSED_WON` (veya aşama adınız) |
<Tip>
Tetikleyici, herhangi bir Fırsat güncellemesinde tetiklenir. Filtre, aşama Closed Won olduğunda iş akışının devam etmesini sağlar.
</Tip>
## Adım 3: Şirket Ayrıntılarını Alın
Fırsat kaydı, fatura için ihtiyaç duyduğunuz tüm Şirket alanlarını içermeyebilir. Bir **Kayıt Ara** eylemi ekleyin:
| Ayar | Değer |
| ---------------------- | --------------------------------------------------- |
| **Nesne** | Şirket |
| **Şuna göre eşleştir** | ID, `{{trigger.object.companyId}}` değerine eşittir |
Bu, fatura adresi, vergi kimliği vb. ile birlikte tam Şirket kaydını getirir.
## Adım 4: Yükü Biçimlendirin (İsteğe Bağlı)
Faturalama sisteminiz belirli bir biçim bekliyorsa bir **Kod** eylemi ekleyin:
```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
}
}
};
};
```
## Adım 5: Faturalama Sistemine Gönderin
Bir **HTTP İsteği** eylemi ekleyin:
| Ayar | Değer |
| ------------- | ------------------------------------------------- |
| **Yöntem** | POST |
| **URL** | Faturalama API uç noktanız |
| **Başlıklar** | `Authorization: Bearer YOUR_API_KEY` |
| **Gövde** | `{{code.invoice}}` veya alanları doğrudan eşleyin |
### Örnek: Stripe Faturası
```
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
```
### Örnek: QuickBooks Faturası
```
POST https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Body: {{code.invoice}}
```
## Tam İş Akışı Özeti
| Adım | Eylem | Amaç |
| ---- | ---------------------------- | --------------------------------------------- |
| 1 | Tetikleme: Kayıt Güncellendi | Herhangi bir Fırsat değiştiğinde tetiklenir |
| 2 | Filtre | Yalnızca Aşama = Closed Won ise devam edin |
| 3 | Kayıt Ara | Faturalama için tam Şirket ayrıntılarını alın |
| 4 | Kod | Faturalama API'si için verileri biçimlendirin |
| 5 | HTTP İsteği | Harici sistemde fatura oluşturun |
## İpuçları
* **Harici kimlikleri saklayın**: API tarafından döndürülen fatura kimliğini bir **Kayıt Güncelle** eylemi kullanarak Fırsat'a geri kaydedin
* **Hata işleme**: HTTP isteği başarısız olursa bir bildirim göndermek için bir dal ekleyin
* **Önce test edin**: Canlıya almadan önce faturalama sisteminizin sandbox/test modunu kullanın
## İlgili
* [Twenty'den bir PDF oluşturun](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — oluşturulan PDF'leri kayıtlara ekleyin
* [İş Akışı Tetikleyicileri](/l/tr/user-guide/workflows/capabilities/workflow-triggers)
* [İş Akışı Eylemleri](/l/tr/user-guide/workflows/capabilities/workflow-actions)
* [Kazanıldı Otomasyonları](/l/tr/user-guide/workflows/how-tos/crm-automations/closed-won-automations)

View File

@@ -0,0 +1,170 @@
---
title: Bir Webhook Tetikleyicisi Ayarlayın
description: İş akışlarını tetiklemek için harici hizmetlerden veri alın.
---
Webhook tetikleyicileri, harici hizmetlerin benzersiz bir URL'ye veri göndererek iş akışlarınızı başlatmasına olanak tanır. Formları, üçüncü taraf uygulamaları ve özel entegrasyonları bağlamak için bunları kullanın.
## Webhook'lar Ne Zaman Kullanılır
| Kullanım alanı | Örnek |
| ----------------------------- | -------------------------------------------------------- |
| **Web formları** | İletişim formu gönderimleri potansiyel müşteri oluşturur |
| **Üçüncü taraf uygulamalar** | Stripe ödemesi → müşteri kaydı oluşturur |
| **Özel entegrasyonlar** | Uygulamanız → Twenty otomasyonu |
| **Kod gerektirmeyen araçlar** | Zapier, Make, n8n bağlantıları |
## Adım adım kurulum
### Adım 1: İş akışını oluşturun
1. **Ayarlar → İş Akışları** bölümüne gidin
2. **+ Yeni İş Akışı**'na tıklayın
3. Adlandırın (ör. "Website Form Submission")
### Adım 2: Webhook tetikleyicisini yapılandırın
1. Tetikleyici bloğuna tıklayın
2. **Webhook**'u seçin
3. Şuna benzer benzersiz bir webhook URL'si alacaksınız:
```
https://api.twenty.com/webhooks/workflow/abc123...
```
4. Bu URL'yi kopyalayın—harici hizmetiniz için buna ihtiyacınız olacak
### Adım 3: Beklenen veri yapısını tanımlayın
**POST** istekleri için beklenen gövde yapısını tanımlayın:
1. **Beklenen gövdeyi tanımla**'ya tıklayın
2. Hizmetinizin göndereceğiyle eşleşen örnek bir JSON girin:
```json
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"company": "Acme Inc",
"message": "Interested in your product"
}
```
3. **Kaydet**'e tıklayın—bu, sonraki adımlarda kullanabileceğiniz değişkenler oluşturur
### Adım 4: Eylemler ekleyin
Şimdi webhook verisini kullanan eylemler ekleyin:
**Örnek: Bir Kişi kaydı oluşturun**
1. **Kayıt Oluştur** eylemini ekleyin
2. **Kişiler** nesnesini seçin
3. Alanları eşleyin:
| Alan | Değer |
| ------- | ------------------------------------------------------- |
| İsim | `{{trigger.body.firstName}}` |
| Soyadı | `{{trigger.body.lastName}}` |
| E-posta | `{{trigger.body.email}}` |
| Şirket | `{{trigger.body.company}}` temelinde ara veya oluşturun |
### Adım 5: Webhook'u test edin
Etkinleştirmeden önce, webhook'unuzu test edin:
**cURL kullanarak**:
```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"}'
```
**Postman veya benzerini kullanarak**:
1. Webhook URL'nize bir POST isteği oluşturun
2. Content-Type üst bilgisini `application/json` olarak ayarlayın
3. Test JSON gövdenizi ekleyin
4. Gönderin ve iş akışı çalıştırmalarını kontrol edin
### Adım 6: Etkinleştir
Test edildikten sonra, iş akışını yayına almak için **Etkinleştir**'e tıklayın.
## Farklı veri yapılarıyla çalışma
### İç içe veri
Webhook'unuz iç içe veri gönderiyorsa:
```json
{
"contact": {
"name": "John Doe",
"email": "john@example.com"
},
"source": "website"
}
```
Şu şekilde referans verin: `{{trigger.body.contact.email}}`
### Diziler
Veri diziler içeriyorsa:
```json
{
"items": [
{"name": "Product A", "qty": 2},
{"name": "Product B", "qty": 1}
]
}
```
Dizileri nasıl ele alacağınız kullanım durumunuza bağlıdır:
**Bilinmeyen öğe sayısı → Iterator kullanın**
Dizideki her öğeyi işlemeniz gerekiyorsa (ör. her biri için bir kayıt oluşturmak), diziyi ayrıştırmak için bir **Kod** eylemi ekleyin, ardından **Iterator** kullanın:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return { items };
};
```
Ardından döngü için Iterator kullanın: `{{code.items}}`
**Bilinen/belirli alanlar → Adlandırılmış alanlara ayıklayın**
Dizi, ayrı ayrı erişmek istediğiniz belirli alanlar içeriyorsa (ör. konum 0 her zaman "first name", konum 1 her zaman "last name" ise), bunları ayıklamak için bir **Kod** eylemi ekleyin:
```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
}
};
};
```
Artık sonraki adımlarda `product.name` ve `product.qty` öğelerini ayrı ayrı seçebilirsiniz.
<Tip>
Dizileri ele alma hakkında daha fazla ayrıntı için bkz. [Kod Eylemlerinde Dizileri Ele Alma](/l/tr/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
## İlgili
* [İş Akışı Tetikleyicileri](/l/tr/user-guide/workflows/capabilities/workflow-triggers)
* [İş Akışı Eylemleri](/l/tr/user-guide/workflows/capabilities/workflow-actions)