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,82 @@
---
title: التعامل مع المصفوفات في إجراءات Code
description: تعلّم كيفية التعامل بشكل صحيح مع مُدخلات المصفوفات في إجراءات Code في سير العمل.
---
عند العمل مع المصفوفات في إجراءات Code، قد تواجه تحديين شائعين:
1. **مصفوفات مُمرَّرة كسلاسل نصية** — تصل البيانات من الأنظمة الخارجية أو من الخطوات السابقة كسلسلة نصية بدلاً من أن تكون مصفوفة فعلية
2. **لا يمكن تحديد عناصر فردية** — يمكنك تحديد المصفوفة كاملة فقط، وليس الحقول المحددة داخلها
يمكن حل كلا الأمرين باستخدام عقدة Code.
## تحليل المصفوفات من السلاسل النصية
غالباً ما تُمرَّر المصفوفات بين خطوات سير العمل كسلاسل نصية أو JSON بدلاً من مصفوفات أصلية. يحدث هذا عندما:
* استقبال البيانات من واجهات برمجة التطبيقات الخارجية عبر HTTP Request
* معالجة حمولات خطافات الويب
* تمرير البيانات بين خطوات سير العمل
**الحل**: أضف هذا النمط في بداية إجراء Code الخاص بك:
```javascript
export const main = async (params: {
users: any;
}): Promise<object> => {
const { users } = params;
// Handle input that may come as a string or an array
const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;
// Now you can safely work with usersFormatted as an array
return {
users: usersFormatted.map((user) => ({
...user,
activityStatus: String(user.activityStatus).toUpperCase(),
})),
};
};
```
السطر الأساسي `typeof users === "string" ? JSON.parse(users) : users` يتحقق مما إذا كان الإدخال سلسلة نصية، ويُجري تحليله إذا لزم الأمر، أو يستخدمه مباشرة إذا كان مصفوفة بالفعل.
## استخراج الحقول الفردية من المصفوفات
قد يُرجِع خطّاف ويب مصفوفةً مثل `answers: [...]`، ولكن في خطوات سير العمل اللاحقة يمكنك تحديد **المصفوفة بأكملها** فقط — وليس العناصر الفردية داخلها.
**الحل**: أضف عقدة Code لاستخراج حقول محددة وإرجاعها ككائن منظَّم:
```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 specific fields from the array
const firstname = answersFormatted[0]?.text || "";
const name = answersFormatted[1]?.text || "";
return {
answer: {
firstname,
name
}
};
};
```
تعيد عقدة Code كائناً منظَّماً بدلاً من مصفوفة. في الخطوات اللاحقة، يمكنك الآن تحديد حقول فردية مثل `answer.firstname` و`answer.name` من منتقي المتغيرات.
<Tip>
**تريد التكرار على المصفوفة بدلًا من استخراج الحقول؟** عندما تُرجِع خطوة Code أو Logic Function مصفوفة على المستوى الأعلى، يمكنك تمريرها مباشرة إلى [Iterator](/l/ar/user-guide/workflows/capabilities/use-iterator): اختر خيار **Whole list** للخطوة كمُدخل لـ Iterator، ثم ارجع إلى كل عنصر باستخدام `{{iterator.currentItem}}` داخل الحلقة. في هذه الحالة، لا تحتاج إلى إعادة هيكلة المصفوفة إلى كائن.
</Tip>
<Tip>
انقر على أيقونة المربع في أعلى يمين محرر الكود لعرضه بملء الشاشة — فهذا مفيد لأن عرض المحرر الافتراضي محدود.
</Tip>

View File

@@ -0,0 +1,182 @@
---
title: جلب بيانات المنتج إلى Twenty
description: زامِن بيانات كتالوج المنتجات من مستودع بيانات إلى نظام إدارة علاقات العملاء (CRM) لديك وفق جدول زمني.
---
استخدم هذا النمط للحفاظ على مزامنة Twenty مع بيانات المنتجات من مستودع البيانات لديك (مثل Snowflake وBigQuery وPostgreSQL).
## بنية سير العمل
1. **المحفّز**: وفق جدول زمني
2. **Code**: الاستعلام عن مستودع البيانات لديك
3. **Code** (اختياري): تنسيق البيانات كمصفوفة
4. **Iterator**: التكرار عبر كل منتج
5. **Upsert Record**: أنشئ أو حدّث في Twenty
<img src="/images/user-guide/workflows/create_users_workflow.png" style={{width:'100%'}} />
## الخطوة 1: جدولة المُحفّز
اضبط سير العمل ليعمل بتواتر يتوافق مع احتياجاتك من حداثة البيانات:
* كل 5 دقائق لمزامنة شبه فورية
* كل ساعة للبيانات الأقل أهمية
* يوميًا للتحديثات الدفعية
## الخطوة 2: الاستعلام عن مستودع البيانات لديك
أضِف إجراء **Code** لجلب البيانات الحديثة:
```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` لاسترجاع السجلات التي تغيّرت حديثًا فقط. هذا يحافظ على كفاءة المزامنة.
</Tip>
## الخطوة 3: تنسيق البيانات (اختياري)
إذا كان مستودعك يُرجع البيانات بتنسيق يحتاج إلى تحويل، فأضف إجراء **Code** آخر. تشمل التحويلات الشائعة تحويل الأنواع، وإعادة تسمية الحقول، وتنظيف البيانات.
### مثال: بيانات المستخدم مع حقول منطقية وحقول الحالة
```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),
})),
};
};
```
### مثال: بيانات المنتج مع تحويلات الأنواع
```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
}))
};
};
```
### مثال: تنسيق التاريخ والعملة
```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, " ")
}))
};
};
```
### التحويلات الشائعة
| تنسيق المصدر | تنسيق الهدف | كود |
| -------------------- | ---------------- | ---------------------------------------- |
| `"true"` / `"false"` | `true` / `false` | `v === true \|\| v === "true"` |
| `"123.45"` | `123.45` | `parseFloat(value)` |
| `"active"` | `"ACTIVE"` | `value.toUpperCase()` |
| `1704067200` (Unix) | تاريخ ISO | `new Date(v * 1000).toISOString()` |
| `"$1,234.56"` | `1234.56` | `parseFloat(v.replace(/[^0-9.-]/g, ""))` |
| `null` / `undefined` | `""` | `value \|\| ""` |
## الخطوة 4: التكرار عبر المنتجات
أضِف إجراء **Iterator**:
* المدخل: `{{code.products}}`
يقوم هذا بالتكرار عبر كل منتج في المصفوفة.
## الخطوة 5: إجراء Upsert لكل سجل
داخل المُكرِّر، أضف إجراء **Upsert Record**:
| الإعداد | القيمة |
| ---------------- | ------------------------------------ |
| **الكائن** | كائن المنتج المخصص لديك |
| **المطابقة حسب** | المعرّف الخارجي أو SKU (مُعرّف فريد) |
| **الاسم** | `{{iterator.item.name}}` |
| **SKU** | `{{iterator.item.sku}}` |
| **السعر** | `{{iterator.item.price}}` |
<Tip>
استخدم **Upsert** (تحديث أو إنشاء) بدلًا من إنشاء فروع منفصلة للإنشاء مقابل التحديث. إنه أسرع في الإنشاء وأسهل في استكشاف الأخطاء وإصلاحها.
</Tip>
## أمثلة لحالات الاستخدام
| المصدر | بيانات |
| ---------------------------------- | ------------------------------------ |
| **نظام تخطيط موارد المؤسسة (ERP)** | كتالوج المنتجات، التسعير، المخزون |
| **منصة التجارة الإلكترونية** | الطلبات، العملاء، تحديثات المنتجات |
| **مستودع البيانات** | مقاييس مجمّعة، بيانات مُثرَاة |
| **نظام إدارة المخزون** | مستويات المخزون، تنبيهات إعادة الطلب |
## ذات صلة
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [التعامل مع المصفوفات في إجراءات Code](/l/ar/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,130 @@
---
title: جلب عمليات إرسال Typeform إلى Twenty
description: التعامل مع حمولة webhook الخاصة بـ Typeform لإنشاء عملاء محتملين من عمليات إرسال النماذج.
---
لإعداد webhook القياسي، راجع [إعداد مشغّل Webhook](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger). يتناول هذا المقال المعالجة المحددة المطلوبة لبنية الحمولة المخصصة الخاصة بـ Typeform.
### الخطوة 1: إنشاء سير عمل Webhook
1. انتقل إلى **الإعدادات → سير العمل**
2. انقر **+ سير عمل جديد**
3. حدد **Webhook** كمشغّل
4. انسخ عنوان URL الخاص بالـ webhook
### الخطوة 2: تهيئة Typeform
1. في Typeform، افتح نموذجك
2. انتقل إلى **Connect → Webhooks**
3. الصق عنوان URL للـ webhook الخاص بـ Twenty
4. حفظ
### الخطوة 3: فهم حمولة Typeform
يرسل Typeform بنية JSON متداخلة. إليك مثال مبسط:
```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" }
}
]
}
}
```
أمور رئيسية يجب ملاحظتها:
* بيانات النموذج متداخلة تحت `form_response`
* **تُعاد الإجابات كمصفوفة**، وليست كحقول مسماة
* تتضمن كل إجابة نوع الحقل وعنوانه للرجوع إليه
### الخطوة 4: استخراج الحقول من مصفوفة الإجابات
بما أن `answers` مصفوفة، يمكنك اختيار المصفوفة كاملة فقط في الخطوات اللاحقة — وليس حقول فردية. أضف إجراء **Code** لاستخراج الحقول التي تحتاجها:
```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
}
};
};
```
الآن في الخطوات اللاحقة، يمكنك تحديد `contact.firstName` و`contact.email` وغيرها من منتقي المتغيرات.
<Tip>
لمزيد من التفاصيل حول التعامل مع المصفوفات في إجراءات Code، راجع [التعامل مع المصفوفات في إجراءات Code](/l/ar/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
### الخطوة 5: إنشاء السجل
أضف إجراء **Create Record**:
| الحقل | القيمة |
| --------------------- | ------------------------------------------------ |
| **الكائن** | الأشخاص |
| **الاسم الأول** | `{{code.contact.firstName}}` |
| **الاسم الأخير** | `{{code.contact.lastName}}` |
| **البريد الإلكتروني** | `{{code.contact.email}}` |
| **الشركة** | ابحث أو أنشئ بناء على `{{code.contact.company}}` |
### الخطوة 6: الاختبار والتفعيل
1. أرسل ردا تجريبيا في Typeform
2. تحقق من تشغيل سير العمل للتأكد من التقاط البيانات
3. فعّل سير العمل
## ذات صلة
* [إعداد مشغّل Webhook](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)
* [التعامل مع المصفوفات في إجراءات Code](/l/ar/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,263 @@
---
title: إنشاء ملف PDF من Twenty
description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سعر) وإرفاقه بسجل.
---
أنشئ أو اجلب ملف PDF تلقائيًا وأرفقه بسجل في Twenty. يُستخدم هذا عادةً لإنشاء عروض أسعار أو فواتير أو تقارير مرتبطة بالشركات أو الفرص أو كائنات أخرى.
## نظرة عامة
يستخدم سير العمل هذا **المحفز اليدوي** بحيث يتمكن المستخدمون من إنشاء ملف PDF عند الطلب لأي سجل محدد. تتولى **الوظيفة المنطقية** ما يلي:
1. تنزيل ملف PDF من عنوان URL (من خدمة إنشاء PDF)
2. رفع الملف إلى Twenty
3. إنشاء مرفق مرتبط بالسجل
## المتطلبات الأساسية
قبل إعداد سير العمل:
1. **أنشئ مفتاح API**: انتقل إلى **الإعدادات → واجهات برمجة التطبيقات** ثم أنشئ مفتاح API جديدًا. ستحتاج إلى هذا الرمز لوظيفة المنطق.
2. **قم بإعداد خدمة إنشاء PDF** (اختياري): إذا كنت تريد إنشاء ملفات PDF ديناميكيًا (مثل عروض الأسعار)، فاستخدم خدمة مثل Carbone أو PDFMonkey أو DocuSeal لإنشاء ملف PDF والحصول على رابط تنزيل.
## إعداد خطوة بخطوة
### الخطوة 1: تهيئة المشغّل
1. انتقل إلى **سير العمل** وأنشئ سير عمل جديدًا
2. اختر **المحفز اليدوي**
3. اختر الكائن الذي تريد إرفاق ملفات PDF به (مثل **شركة** أو **فرصة**)
<Tip>
مع محفز يدوي، يمكن للمستخدمين تشغيل سير العمل هذا باستخدام زر يظهر في الجزء العلوي الأيمن بمجرد تحديد سجل - وهذا يعمل على تشغيل سير العمل لإنشاء وإرفاق PDF.
</Tip>
### الخطوة 2: إضافة وظيفة منطقية
1. إضافة إجراء **الكود** (الدالة المنطقية)
2. أنشئ وظيفة جديدة باستخدام الكود أدناه
3. قم بتهيئة معلمات الإدخال
#### معلمات الإدخال
| المعلمة | القيمة |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
إذا كنت تُرفِقه بكائن مختلف (شخص، فرصة، إلخ)، فأعد تسمية المعلمة وفقًا لذلك (مثلًا، `personId`، `opportunityId`) وحدّث الوظيفة المنطقية.
</Note>
#### كود الوظيفة المنطقية
```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;
};
```
### الخطوة 3: خصّص وفق حالة الاستخدام الخاصة بك
#### لإرفاقه بكائن مختلف
استبدل `targetCompanyId` بالحقل المناسب:
| كائن | اسم الحقل |
| ---------- | -------------------------- |
| الشركة | `targetCompanyId` |
| شخص | `targetPersonId` |
| الفرصة | `targetOpportunityId` |
| كائن مخصّص | `targetYourCustomObjectId` |
حدّث كل من معلمة الوظيفة وكائن `variables.data` في عملية الـ mutation الخاصة بالمرفق.
#### لاستخدام رابط PDF ديناميكي
إذا كنت تستخدم خدمة إنشاء PDF، يمكنك:
1. أولًا، أنشئ إجراء طلب HTTP لإنشاء ملف PDF
2. مرّر رابط ملف PDF المُعاد إلى الوظيفة المنطقية كمعلمة
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### الخطوة 4: الاختبار والتفعيل
1. احفظ سير العمل
2. انتقل إلى سجل شركة
3. انقر على قائمة **⋮** واختر سير العمل الخاص بك
4. تحقّق من قسم **المرفقات** في السجل للتأكد من أنه تم إرفاق ملف PDF
5. فعّل سير العمل
## الدمج مع خدمات إنشاء ملفات PDF
لإنشاء عروض أسعار أو فواتير ديناميكية:
### مثال: إنشاء عرض سعر → إرفاق ملف PDF
| الخطوة | الإجراء | الغرض |
| ------ | ---------------------- | ---------------------------------------------------------- |
| 1 | المحفز اليدوي (الشركة) | يبدأ المستخدم التنفيذ على سجل |
| 2 | البحث عن سجل | الحصول على تفاصيل الفرصة أو بنود السطر |
| 3 | طلب HTTP | استدعِ واجهة برمجة تطبيقات إنشاء PDF باستخدام بيانات السجل |
| 4 | دالة منطقية | تنزيل وإرفاق ملف PDF المُنشأ |
### خدمات شائعة لإنشاء ملفات PDF
* **Carbone** - إنشاء مستندات يعتمد على القوالب
* **PDFMonkey** - إنشاء PDF ديناميكي من القوالب
* **DocuSeal** - منصة أتمتة المستندات
* **Documint** - إنشاء مستندات يعتمد على واجهة برمجة التطبيقات أولًا
توفر كل خدمة API الذي يعيد رابط PDF الذي يمكنك انتقاله إلى الدالة المنطقية.
## استكشاف الأخطاء وإصلاحها
| المشكلة | الحل |
| ------------------- | -------------------------------------------------------- |
| "فشل تنزيل ملف PDF" | تحقق من أن رابط ملف PDF قابل للوصول ويعيد ملف PDF صالحًا |
| "فشل الرفع" | تحقق من أن مفتاح API صالح ولديه أذونات كتابة |
| "فشل إنشاء المرفق" | تأكد من أن اسم حقل معرّف الكائن يطابق الكائن المستهدف |
## ذات صلة
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [الوظائف المنطقية](/l/ar/user-guide/workflows/capabilities/workflow-actions#code)
* [إنشاء عرض سعر أو فاتورة من Twenty](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)

View File

@@ -0,0 +1,144 @@
---
title: إنشاء عرض سعر أو فاتورة من Twenty
description: إنشاء فواتير تلقائياً في الأدوات الخارجية عند إغلاق الصفقات.
---
إرسال بيانات الصفقة تلقائياً إلى نظام الفوترة لديك (Stripe وQuickBooks وXero، إلخ) عند ربح فرصة.
## بنية سير العمل
1. **المحفّز**: يتم تحديث السجل (الفرصة)
2. **التصفية**: المرحلة = Closed Won
3. **Search Record**: الحصول على تفاصيل الشركة
4. **Code** (اختياري): تنسيق الحمولة
5. **HTTP Request**: الإرسال إلى نظام الفوترة
## الخطوة 1: إعداد المُحفّز
1. إنشاء سير عمل جديد
2. اختر المُحفّز **Record is Updated**
3. اختر **Opportunity** بوصفه الكائن
## الخطوة 2: التصفية من أجل Closed Won
أضف إجراء **Filter** للمتابعة فقط عند ربح الصفقة:
| الإعداد | القيمة |
| ---------- | ---------------------------------- |
| **الحقل** | المرحلة |
| **الشرط** | يساوي |
| **القيمة** | `CLOSED_WON` (أو اسم المرحلة لديك) |
<Tip>
يتم تشغيل المُحفّز عند أي تحديث للفرصة. تضمن التصفية أن يستمر سير العمل فقط عندما تتغير المرحلة إلى Closed Won.
</Tip>
## الخطوة 3: الحصول على تفاصيل الشركة
قد لا يتضمن سجل الفرصة جميع حقول الشركة التي تحتاجها للفاتورة. أضف إجراء **Search Record**:
| الإعداد | القيمة |
| ---------------- | -------------------------------------------- |
| **الكائن** | الشركة |
| **المطابقة حسب** | المعرّف يساوي `{{trigger.object.companyId}}` |
يؤدي ذلك إلى استرجاع سجل الشركة الكامل متضمناً عنوان الفوترة، ومعرّف الضريبة، وما إلى ذلك.
## الخطوة 4: تنسيق الحمولة (اختياري)
إذا كان نظام الفوترة لديك يتطلب تنسيقاً محدداً، فأضف إجراء **Code**:
```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
}
}
};
};
```
## الخطوة 5: الإرسال إلى نظام الفوترة
أضف إجراء **HTTP Request**:
| الإعداد | القيمة |
| ------------- | ------------------------------------------------ |
| **الطريقة** | POST |
| **عنوان URL** | نقطة نهاية واجهة برمجة تطبيقات الفوترة الخاصة بك |
| **الرؤوس** | `Authorization: Bearer YOUR_API_KEY` |
| **المحتوى** | `{{code.invoice}}` أو قم بربط الحقول مباشرةً |
### مثال: فاتورة 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
```
### مثال: فاتورة QuickBooks
```
POST https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Body: {{code.invoice}}
```
## الملخص الكامل لسير العمل
| الخطوة | الإجراء | الغرض |
| ------ | -------------------- | ----------------------------------------------- |
| 1 | المحفّز: تحديث السجل | يتم تشغيله عند حدوث أي تغيير في أي فرصة |
| ٢ | تصفية | المتابعة فقط إذا كانت المرحلة = Closed Won |
| 3 | البحث عن سجل | الحصول على تفاصيل الشركة الكاملة لأغراض الفوترة |
| 4 | كود | تنسيق البيانات لواجهة برمجة تطبيقات الفوترة |
| 5 | طلب HTTP | إنشاء فاتورة في نظام خارجي |
## نصائح
* **تخزين المعرّفات الخارجية**: احفظ معرّف الفاتورة الذي تُرجعه واجهة برمجة التطبيقات إلى الفرصة باستخدام إجراء **Update Record**
* **التعامل مع الأخطاء**: أضف فرعاً لإرسال إشعار إذا فشل طلب HTTP
* **اختبر أولاً**: استخدم وضع sandbox/test في نظام الفوترة قبل الإطلاق
## ذات صلة
* [إنشاء PDF من عشرين سنتي](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) - إرفاق ملفات PDFs التي تم إنشاؤها بالسجلات
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [أتمتة Closed Won](/l/ar/user-guide/workflows/how-tos/crm-automations/closed-won-automations)

View File

@@ -0,0 +1,170 @@
---
title: إعداد مشغّل ربط الويب
description: استقبل بيانات من خدمات خارجية لتشغيل عمليات سير العمل.
---
تتيح مشغّلات ربط الويب للخدمات الخارجية بدء تشغيل عمليات سير العمل لديك عبر إرسال بيانات إلى عنوان URL فريد. استخدمها لربط النماذج والتطبيقات من جهات خارجية والتكاملات المخصّصة.
## متى تستخدم ربط الويب
| حالة الاستخدام | مثال |
| ----------------------- | ---------------------------------------------- |
| **نماذج الويب** | تُنشئ عمليات إرسال نموذج الاتصال عملاء محتملين |
| **تطبيقات جهات خارجية** | دفعة Stripe → إنشاء سجل عميل |
| **تكاملات مخصّصة** | تطبيقك → أتمتة Twenty |
| **أدوات بدون كود** | اتصالات Zapier وMake وn8n |
## إعداد خطوة بخطوة
### الخطوة 1: إنشاء سير العمل
1. اذهب إلى **الإعدادات → سير العمل**
2. انقر **+ سير عمل جديد**
3. قم بتسميته (مثلًا، "إرسال نموذج الموقع")
### الخطوة 2: تهيئة مشغّل ربط الويب
1. انقر على كتلة المشغّل
2. اختر **ربط الويب**
3. ستتلقى عنوان URL فريدًا لربط الويب مثل:
```
https://api.twenty.com/webhooks/workflow/abc123...
```
4. انسخ هذا العنوان URL - ستحتاج إليه لخدمتك الخارجية
### الخطوة 3: تحديد بنية البيانات المتوقعة
بالنسبة لطلبات **POST**، حدِّد بنية جسم الطلب المتوقعة:
1. انقر **تحديد الجسم المتوقع**
2. أدخل JSON نموذجيًا يطابق ما سترسله خدمتك:
```json
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"company": "Acme Inc",
"message": "Interested in your product"
}
```
3. انقر **حفظ** - سيؤدي هذا إلى إنشاء متغيّرات يمكنك استخدامها في الخطوات اللاحقة
### الخطوة 4: إضافة إجراءات
أضف الآن إجراءات تستخدم بيانات ربط الويب:
**مثال: إنشاء سجل شخص**
1. أضف إجراء **إنشاء سجل**
2. حدِّد كائن **الأشخاص**
3. عيّن الحقول:
| الحقل | القيمة |
| ----------------- | ------------------------------------------------- |
| الاسم الأول | `{{trigger.body.firstName}}` |
| الاسم الأخير | `{{trigger.body.lastName}}` |
| البريد الإلكتروني | `{{trigger.body.email}}` |
| الشركة | ابحث أو أنشئ بناءً على `{{trigger.body.company}}` |
### الخطوة 5: اختبار ربط الويب
قبل التفعيل، اختبر ربط الويب لديك:
**باستخدام 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"}'
```
**باستخدام Postman أو ما شابهه**:
1. أنشئ طلب POST إلى عنوان ربط الويب الخاص بك
2. عيّن ترويسة Content-Type إلى `application/json`
3. أضف جسم JSON الاختباري الخاص بك
4. أرسل وتحقّق من تشغيلات سير العمل
### الخطوة 6: التفعيل
بعد الاختبار، انقر **تفعيل** لجعل سير العمل فعّالًا.
## التعامل مع بُنى بيانات مختلفة
### بيانات متداخلة
إذا كان ربط الويب يرسل بيانات متداخلة:
```json
{
"contact": {
"name": "John Doe",
"email": "john@example.com"
},
"source": "website"
}
```
أشر إليها باستخدام: `{{trigger.body.contact.email}}`
### المصفوفات
إذا كانت البيانات تتضمن مصفوفات:
```json
{
"items": [
{"name": "Product A", "qty": 2},
{"name": "Product B", "qty": 1}
]
}
```
تعتمد طريقة تعاملك مع المصفوفات على حالة الاستخدام لديك:
**عدد العناصر غير معروف → استخدم Iterator**
إذا كنت بحاجة إلى معالجة كل عنصر في المصفوفة (مثلًا، إنشاء سجل لكل عنصر)، أضف إجراء **Code** لتحليل المصفوفة، ثم استخدم **Iterator**:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return { items };
};
```
ثم استخدم Iterator للتكرار عبر: `{{code.items}}`
**حقول معروفة/محددة → استخرجها إلى حقول مسماة**
إذا كانت المصفوفة تحتوي على حقول محددة تريد الوصول إليها بشكل فردي (مثلًا، إجابات نموذج حيث الموضع 0 دائمًا هو "الاسم الأول"، والموضع 1 دائمًا هو "اسم العائلة")، فأضف إجراء **Code** لاستخراجها:
```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
}
};
};
```
يمكنك الآن اختيار `product.name` و`product.qty` بشكل فردي في الخطوات اللاحقة.
<Tip>
لمزيد من التفاصيل حول التعامل مع المصفوفات، راجع [التعامل مع المصفوفات في إجراءات Code](/l/ar/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
## ذات صلة
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,121 @@
---
title: الرد التلقائي على رسائل البريد الإلكتروني الواردة
description: أنشئ سير عمل يستخدم الذكاء الاصطناعي لفرز رسائل البريد الإلكتروني الواردة وإرسال ردود ضمن سلسلة المحادثة تلقائيًا.
---
استجب لرسائل البريد الإلكتروني الواردة خلال ثوانٍ — وليس ساعات. يستخدم سير العمل هذا وكيل AI لتصفية الضوضاء (الرسائل الإخبارية، الرسائل غير المرغوب فيها، الردود التلقائية) وصياغة استجابة مخصصة للرسائل الحقيقية. ثم تقوم منظمة العفو الدولية بإرسالها كرد متسلل داخل المحادثة الأصلية.
## كيف يعمل تسلسل رسائل البريد الإلكتروني
تحمل كل رسالة بريد إلكتروني ترويسة `Message-ID` مخفية — بصمة فريدة يعيّنها خادم بريد المرسل. عند الرد على بريد إلكتروني، يقوم عميل البريد لديك بتعيين ترويسة `In-Reply-To` التي تُشير إلى تلك البصمة. بهذه الطريقة يقوم Gmail وOutlook وكل عميل آخر بتجميع الرسائل في سلاسل المحادثات.
في Twenty، تُخزَّن تلك البصمة كـ `headerMessageId` على كائن Message. يلتقطه سير العمل لديك ويمرّره إلى حقل In-Reply-To لإجراء Send Email.
## بناء سير العمل
### الخطوة 1: إنشاء سير عمل جديد
توجّه إلى **Settings -> Workflows** وانقر **+ New Workflow**.
### الخطوة 2: التشغيل عند الرسائل الواردة
اختر **When a Record is Created** ثم **Messages**.
في كل مرة يصل فيها بريد إلكتروني إلى Twenty، يُشغَّل هذا.
<img src="/images/user-guide/workflows/auto-reply/workflow-overview.png" style={{width:'100%'}} />
### الخطوة 3: البحث عن المُرسِل
أضِف إجراء **Search Records**.
عنوان المُرسِل ليس في الرسالة نفسها — بل في سجل Message Participant المرتبط.
| الحقل | القيمة |
| ---------- | ---------------------------------- |
| **الكائن** | المشاركون في الرسالة |
| **تصفية** | Message **يساوي** `{{trigger.id}}` |
| **تصفية** | Role **يساوي** From |
| **الحد** | 1 |
سيمنحك هذا بريد المُرسِل الإلكتروني في `handle` واسمه في `displayName`.
<img src="/images/user-guide/workflows/auto-reply/find-sender.png" style={{width:'100%'}} />
### الخطوة 4: التكرار وصياغة رد مع الذكاء الاصطناعي
أضِف إجراء **AI Agent**. تقوم هذه الخطوة الواحدة بأمرين: تقرر ما إذا كان البريد الإلكتروني يستحق الرد، وإن كان كذلك، تصوغ ردًا.
استخدم موجهًا مثل:
```
You are an email triage assistant for a sales team. Read the following
inbound email and decide if it deserves a reply.
Subject: {{trigger.subject}}
Body: {{trigger.text}}
From: {{Find Sender.first.displayName}} ({{Find Sender.first.handle}})
If this email is spam, a newsletter, an automated notification, or
otherwise does not need a human reply, respond with exactly: SKIP
Otherwise, write a short, professional reply (3-4 sentences max) that:
- Acknowledges their specific message
- Lets them know someone from the team will follow up shortly
- Is warm but not overly casual
Respond with only the reply text, no subject line or greeting prefix.
```
يُنتِج AI Agent استجابته في الحقل `response` الذي يمكن للخطوات التالية الرجوع إليه.
<img src="/images/user-guide/workflows/auto-reply/ai-triage.png" style={{width:'100%'}} />
### الخطوة 5: التفريع بناءً على قرار الذكاء الاصطناعي
أضِف إجراء **If/Else** للتحقق مما إذا كان الذكاء الاصطناعي قرر الرد أم التخطي.
| الحقل | القيمة |
| ------------------ | ------------------------------------------- |
| **الشرط** | AI Agent `response` **لا يحتوي على** `SKIP` |
| **إذا كان صحيحًا** | المتابعة إلى Send Email |
| **وإلا** | لا تفعل شيئًا (ينتهي سير العمل) |
يتم تجاهل الرسائل غير المرغوب فيها والنشرات البريدية والرسائل المُنشأة تلقائيًا. كل ما عدا ذلك ينتقل إلى الخطوة التالية.
<img src="/images/user-guide/workflows/auto-reply/should-reply.png" style={{width:'100%'}} />
### الخطوة 6: إرسال رد ضمن سلسلة المحادثة
أضِف إجراء **Send Email** على فرع "if true". انقر **Advanced options** ثم **Add In-Reply-To**.
| الحقل | القيمة |
| --------------- | -------------------------------------- |
| **إلى** | `{{Find Sender.first.handle}}` |
| **الموضوع** | `Re: {{trigger.subject}}` |
| **المحتوى** | `{{AI Triage & Draft Reply.response}}` |
| **In-Reply-To** | `{{trigger.headerMessageId}}` |
حقل In-Reply-To هو ما يجعل هذا ردًا بدلًا من محادثة جديدة. سيراه المستلِم ضمن سلسلة تحت البريد الأصلي في Gmail أو Outlook أو أي عميل آخر.
<img src="/images/user-guide/workflows/auto-reply/send-email.png" style={{width:'100%'}} />
<Tip>
**In-Reply-To** يتوقّع `message.headerMessageId` من المشغّل — إنها البصمة الفريدة للبريد الإلكتروني، وليست عنوان المستلم. إذا تركته فارغًا، فسيُرسَل البريد الإلكتروني على أي حال، ولكن كرسالة مستقلة.
</Tip>
<Warning>
يستخدم Gmail سطر الموضوع لتجميع الرسائل في سلاسل المحادثات. يجب أن يبدأ الموضوع بـ `Re:` (بما في ذلك النقطتان والمسافة) ليعرض Gmail الرد داخل سلسلة المحادثة الأصلية. بدون ذلك، سيظهر الرد كمحادثة منفصلة — حتى إذا تم تعيين ترويسة In-Reply-To بشكل صحيح.
</Warning>
### الخطوة 7: الاختبار والتفعيل
اضغط **Test**، ثم تحقّق من عميل البريد لديك. يجب أن يظهر الرد متداخلًا تحت الرسالة الأصلية.
فعِّل عندما تكون راضيًا عنه.
## أفكار للبناء عليها
* **الرد على كبار الشخصيات فقط** — أضِف فرعًا يتحقق من مجال المُرسِل أو مما إذا كان موجودًا كجهة اتصال في Twenty
* **التوجيه حسب النية** — استخدم موجهات AI Agent منفصلة للتعامل مع استفسارات المبيعات بشكل مختلف عن طلبات الدعم
* **الإثراء قبل الرد** — أضِف خطوة Search Records لجلب شركة المُرسِل أو سجل الصفقات إلى الموجه الخاص بالذكاء الاصطناعي للحصول على ردود أكثر تخصيصًا

View File

@@ -0,0 +1,179 @@
---
title: أتمتة الصفقات المغلقة الرابحة
description: أتمتة الأنشطة بعد الفوز عند إغلاق الفرص.
---
عند إغلاق صفقة، يجب أن تحدث أمور عدة: تحديث حالة الشركة، إشعار أعضاء الفريق، إنشاء مهام الإعداد. قم بأتمتة كل ذلك عبر سير عمل واحد.
## المشكلة
عند انتقال فرصة إلى "Closed Won":
* يجب تغيير نوع الشركة من "Prospect" إلى "Customer"
* يجب إنشاء مهام الإعداد
* يجب إشعار فريق نجاح العملاء
* يحتاج مندوب المبيعات إلى تأكيد
القيام بذلك يدويًا يستغرق وقتًا ومعرّض للأخطاء.
## الحل
أنشئ سير عمل يتولى تلقائيًا جميع الأنشطة بعد الفوز.
## إعداد سير العمل الكامل
### الخطوة 1: إنشاء سير العمل
1. اذهب إلى **الإعدادات → سير العمل**
2. انقر **+ سير عمل جديد**
3. قم بتسميته "تم ربح الصفقة - أتمتة ما بعد الفوز"
### الخطوة 2: تهيئة المشغّل
1. اختر **Record is Updated**
2. اختر **Opportunities**
3. ضمن "Fields to monitor"، اختر **Stage**
### الخطوة 3: إضافة عامل تصفية للمرحلة
1. أضف إجراء **Filter**
2. الشرط: `{{trigger.object.stage}}` يساوي "Closed Won"
### الخطوة 4: تحديث نوع الشركة
1. أضف إجراء **Update Record**
2. التكوين:
| الحقل | القيمة |
| ------------------ | ------------------------------- |
| **الكائن** | الشركات |
| **السجل** | `{{trigger.object.company.id}}` |
| **نوع** | العميل |
| **تاريخ أول صفقة** | `{{trigger.object.closedAt}}` |
| **مالك الحساب** | `{{trigger.object.owner.id}}` |
### الخطوة 5: إنشاء مهمة إعداد
1. أضف إجراء **Create Record**
2. التكوين:
| الحقل | القيمة |
| -------------------- | ----------------------------------------------------------------------------------------------- |
| **الكائن** | المهام |
| **العنوان** | `Onboarding: {{trigger.object.name}}` |
| **المسند إليه** | عضو فريق نجاح العملاء |
| **تاريخ الاستحقاق** | بعد 3 أيام |
| **الأولوية** | عالية |
| **الشركة ذات الصلة** | `{{trigger.object.company.id}}` |
| **الفرصة ذات الصلة** | `{{trigger.object.id}}` |
| **الوصف** | `إعداد عميل جديد لشركة {{trigger.object.company.name}}. قيمة الصفقة: {{trigger.object.amount}}` |
### الخطوة 6: إشعار فريق نجاح العملاء
1. أضف إجراء **Send Email**
2. التكوين:
| الحقل | القيمة |
| ----------- | ----------------------------------------------- |
| **إلى** | customer-success@yourcompany.com |
| **الموضوع** | `🎉 عميل جديد: {{trigger.object.company.name}}` |
| **المحتوى** | انظر المثال أدناه |
**مثال على محتوى البريد الإلكتروني**:
```
Hi CS Team,
We have a new customer!
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
An onboarding task has been created automatically.
Let's give them a great start!
```
### الخطوة 7: تأكيد لمندوب المبيعات
1. أضف إجراء **Send Email** آخر
2. التكوين:
| الحقل | القيمة |
| ----------- | -------------------------------------------------------------------------- |
| **إلى** | `{{trigger.object.owner.email}}` |
| **الموضوع** | `✅ تم إغلاق الصفقة: {{trigger.object.name}}` |
| **المحتوى** | تهانينا! تمت معالجة صفقتك. تم إشعار فريق نجاح العملاء وبدأت عملية الإعداد. |
### الخطوة 8: الاختبار والتفعيل
1. اختبر بنقل فرصة اختبار إلى "Closed Won"
2. التحقق:
* تم تغيير نوع الشركة إلى "Customer"
* تم إنشاء مهمة الإعداد
* تلقى فريق نجاح العملاء بريدًا إلكترونيًا
* تلقى مندوب المبيعات تأكيدًا
3. فعّل عند الجاهزية
## التعامل مع "Closed Lost"
أنشئ سير عمل مشابهًا للصفقات الخاسرة:
### المشغّل
* تم تحديث السجل (Opportunities، Stage = "Closed Lost")
### الإجراءات
1. **Create Record**: مهمة "تحليل الصفقة الخاسرة"
2. **Update Record**: أضِف سبب الخسارة إلى سجل الشركة
3. **Send Email**: إخطار المدير بالصفقة الخاسرة
## متقدم: إعداد متعدد الخطوات
لعمليات الإعداد المعقدة، أنشئ مهام متعددة:
```javascript
export const main = async (params) => {
const tasks = [
{ title: "Welcome call", daysFromNow: 1, assignee: "CS" },
{ title: "Send onboarding materials", daysFromNow: 2, assignee: "CS" },
{ title: "Technical setup", daysFromNow: 5, assignee: "Support" },
{ title: "30-day check-in", daysFromNow: 30, assignee: "CS" }
];
return { tasks };
};
```
استخدم **Iterator** لإنشاء كل مهمة من المصفوفة.
## أفكار للتخصيص
### أبقِ أدواتك الأخرى محدثة
* أنشئ عميلًا في نظام الفوترة بواسطة **HTTP Request**
### الإجراءات المشروطة
استخدم إجراءات **Filter** لـ:
* إعداد مختلف للمؤسسات مقابل الشركات الصغيرة والمتوسطة
* مُسندون مختلفون حسب المنطقة
* تخطي الإشعارات للصفقات الصغيرة
### تضمين تفاصيل الصفقة
استخدم إجراء **Code** للتنسيق:
* مستندات ملخص الصفقة
* ملاحظات التسليم لفريق نجاح العملاء
* قوائم تدقيق إعداد مخصصة
## ذات صلة
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [إرسال رسائل البريد الإلكتروني من سير العمل](/l/ar/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,136 @@
---
title: اكتشاف الفرص الراكدة
description: إخطار المديرين تلقائيًا عند عدم تحديث الفرص.
---
حافظ على صحة خط أنابيب المبيعات من خلال تنبيه المديرين عندما تصبح الفرص راكدة. يتحقق سير العمل هذا من الفرص التي لم يتم تحديثها خلال عدد محدد من الأيام.
## المشكلة
الفرص التي تبقى دون تحديث تؤدي إلى:
* فتور الصفقات
* توقعات غير موثوقة
* إيرادات مفقودة
## الحل
أنشئ سير عملًا مُجدولًا يعثر على الفرص الراكدة ويرسل بريدًا إلكترونيًا إلى مديريها.
## إعداد خطوة بخطوة
### الخطوة 1: إنشاء سير العمل
1. اذهب إلى **الإعدادات → سير العمل**
2. انقر **+ سير عمل جديد**
3. قم بتسميته "تنبيه الفرص الراكدة"
### الخطوة 2: تهيئة المشغّل
1. حدد **وفق جدول**
2. عيّن التشغيل يوميًا (مثلاً، كل يوم الساعة 8 صباحًا)
### الخطوة 3: البحث عن الفرص الراكدة
1. أضف إجراء **Search Records**
2. التكوين:
| الحقل | القيمة |
| ---------- | ------------------------------------------- |
| **الكائن** | الفرص |
| **تصفية** | Updated At قبل (اليوم - 7 أيام) |
| **تصفية** | المرحلة ليست "Closed Won" ولا "Closed Lost" |
| **الحد** | 100 |
### الخطوة 4: التحقق مما إذا تم العثور على أي عناصر
1. أضف إجراء **تصفية**
2. الشرط: `{{searchRecords.length}}` أكبر من 0
3. إذا لم توجد فرص راكدة، يتوقف سير العمل هنا
### الخطوة 5: تنسيق التنبيه (إجراء Code)
أضف إجراء **Code** لتنسيق البريد الإلكتروني:
```javascript
export const main = async (params) => {
const opportunities = params.opportunities;
// Group opportunities by owner
const byOwner = {};
opportunities.forEach(opp => {
const ownerEmail = opp.owner?.email || 'unassigned';
if (!byOwner[ownerEmail]) {
byOwner[ownerEmail] = [];
}
byOwner[ownerEmail].push({
name: opp.name,
amount: opp.amount,
lastUpdated: opp.updatedAt,
stage: opp.stage
});
});
// Format summary for manager
let summary = "Stale Opportunities Report\n\n";
Object.entries(byOwner).forEach(([owner, opps]) => {
summary += `${owner}: ${opps.length} stale opportunities\n`;
opps.forEach(opp => {
summary += ` - ${opp.name} (${opp.stage})\n`;
});
summary += "\n";
});
return {
summary,
totalCount: opportunities.length
};
};
```
### الخطوة 6: إرسال بريد التنبيه
أضف إجراء **Send Email**:
| الحقل | القيمة |
| ----------- | --------------------------------------------------- |
| **إلى** | sales-manager@yourcompany.com |
| **الموضوع** | `🚨 {{code.totalCount}} فرص راكدة تحتاج إلى اهتمام` |
| **المحتوى** | `{{code.summary}}` |
### الخطوة 7: الاختبار والتفعيل
1. انقر **Test** لتشغيل سير العمل
2. تحقق من أن البريد الإلكتروني يحتوي على البيانات الصحيحة
3. فعّل عند الجاهزية
## خيارات التخصيص
### تغيير عتبة الركود
عدّل عامل تصفية Search Records لتغيير 7 أيام إلى الفترة التي تفضّلها:
* 3 أيام للمبيعات سريعة الوتيرة
* 14 يومًا لصفقات المؤسسات
* 30 يومًا لدورات المبيعات الطويلة
### تنبيه المندوبين بشكل فردي
بدلًا من بريد مدير واحد، استخدم **Iterator** لإرسال رسائل مخصصة إلى كل مندوب حول صفقاته الراكدة.
### إضافة تصعيد
أنشئ عدة سير عمل بتدرّج في الشدة:
1. اليوم 7: بريد إلى المندوب
2. اليوم 14: بريد إلى المندوب + المدير
3. اليوم 21: إنشاء مهمة للمدير للتدخل
### التضمين في Slack
استخدم **HTTP Request** للنشر إلى Slack webhook بدلًا من البريد الإلكتروني أو بالإضافة إليه.
## ذات صلة
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [إرسال رسائل البريد الإلكتروني من سير العمل](/l/ar/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,69 @@
---
title: عرض عدد رسائل البريد الإلكتروني المستلمة
description: أنشئ سير عمل لحساب عدد رسائل البريد الإلكتروني المستلمة من كل جهة اتصال وعرضه تلقائيًا.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/workflow-number-of-emails-received.png" style={{width:'100%'}} />
## نظرة عامة
يعمل سير العمل هذا في كل مرة يتم فيها استلام بريد إلكتروني جديد، ويحدّث حقلًا مخصصًا في سجل Person بالقيمة الإجمالية لعدد الرسائل من ذلك المرسِل.
## المتطلبات الأساسية
قبل إعداد سير العمل هذا، أنشئ حقلًا مخصصًا على كائن **People**:
1. اذهب إلى **الإعدادات → نموذج البيانات → People**
2. أضف حقل **Number** جديدًا
3. قم بتسميته بشيء مثل "عدد رسائل البريد الإلكتروني المستلمة من هذا الشخص"
## إعداد خطوة بخطوة
<VimeoEmbed videoId="1148700336" title="عرض توضيحي بالفيديو" />
### الخطوة 1: تهيئة المشغّل
1. انتقل إلى **سير العمل** وأنشئ سير عمل جديدًا
2. اختر **Record is Created** كالمشغّل
3. اختر **Message Participants** (متوفر ضمن الكائنات المتقدمة)
<Note>
يمثّل Message Participant مزيجًا بين معرّف الرسالة ومعرّف الشخص، ما ينشئ سجلًا فريدًا واحدًا لكل رسالة. يكون هذا أسهل في التتبع من Messages مباشرةً لأننا نستطيع الوصول إلى الحقل `handle` الذي يحتوي على عنوان البريد الإلكتروني للمرسل (أو المستلم).
</Note>
### الخطوة 2: التصفية حسب Role
1. أضف إجراء **Filter**
2. عيّن الشرط: **Role** يساوي **FROM**
يضمن هذا أن تحسب الرسائل المُرسلة من هذا الشخص فقط، وليس الرسائل المُرسلة إليه.
### الخطوة 3: البحث عن جميع Message Participants الذين لديهم نفس الـ Handle
1. أضف إجراء **Search Records**
2. حدّد **Message Participants** كالكائن
3. أضف عوامل تصفية: **Handle** يساوي الـ handle من المشغّل (عنوان البريد الإلكتروني للمرسل) و**Role** يساوي **FROM**
4. قم بزيادة **Limit** من 1 إلى **200** (الحد الأقصى)
هذا يعثر على جميع الرسائل من عنوان البريد الإلكتروني هذا للحصول على العدد الإجمالي.
<Note>
إجراء Search Records محدود بإرجاع 200 سجل كحد أقصى. ومع ذلك، بما أنك تستخدم قيمة `totalCount` فقط (وليس السجلات الفردية)، فستُرجِع هذه الخطوة إجمالي عدد رسائل البريد الإلكتروني المُرسلة من هذا الشخص.
</Note>
### الخطوة 4: تحديث سجل Person باستخدام إجراء Create or Update Record
1. أضف إجراء **Create or Update Record**
2. حدّد **People** كالكائن
3. اعثر على الشخص بمطابقة بريده الإلكتروني مع قيمة `handle` من Message Participant
4. عيّن الحقل المخصص "عدد رسائل البريد الإلكتروني المستلمة" إلى `{{searchRecords.totalCount}}`
تمثّل قيمة `totalCount` من إجراء Search Records إجمالي عدد رسائل البريد الإلكتروني المستلمة من هذا الشخص.
## ذات صلة
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [إنشاء حقول مخصصة](/l/ar/user-guide/data-model/how-tos/customize-your-data-model)
* [إجراء Search Records](/l/ar/user-guide/workflows/capabilities/workflow-actions#search-records)

View File

@@ -0,0 +1,170 @@
---
title: عرض بيانات السجل المرتبط
description: اعرض بيانات من سجلات مرتبطة (مثل معلومات الشركة على الفرص) باستخدام سير العمل.
---
اعرض بيانات من سجلات مرتبطة مباشرةً على سجلاتك — على سبيل المثال، اعرض عدد الموظفين من شركة على فرصها. هذا الحل عبر سير العمل مفيد حتى تصبح الحقول المتداخلة مدعومة أصلاً.
## الاستخدامات الشائعة
| المصدر | الوجهة | الحقول المطلوب نسخها |
| ------ | ------ | ------------------------------------------------- |
| الشركة | الفرصة | الصناعة، حجم الشركة، الإيراد السنوي المتكرر (ARR) |
| شخص | الفرصة | البريد الإلكتروني، الهاتف، المسمّى الوظيفي |
| الفرصة | الشركة | قيمة آخر صفقة، تاريخ آخر صفقة رابحة |
## نسخ أساسي للحقل
### مثال: نسخ البريد الإلكتروني لجهة الاتصال إلى الفرصة
**الهدف**: عند تعيين نقطة اتصال على فرصة، انسخ البريد الإلكتروني لجهة الاتصال إلى الفرصة لسهولة الوصول.
### المتطلبات الأساسية
أنشئ حقول الوجهة في **الإعدادات → نموذج البيانات → الفرص** قبل إنشاء سير العمل:
* البريد الإلكتروني لجهة الاتصال (النوع: Email)
* هاتف جهة الاتصال (النوع: Phone)
### إعداد
1. **المحفّز**: يتم تحديث السجل (الفرص، حقل نقطة الاتصال)
2. **الفلتر**: تحقّق من أن نقطة الاتصال غير فارغة
3. **البحث في السجلات**: اعثر على الشخص المرتبط
* الكائن: الأشخاص
* الفلتر: المعرّف يساوي `{{trigger.object.pointOfContact.id}}`
4. **تحديث السجل**:
* الكائن: الفرص
* السجل: `{{trigger.object.id}}`
* البريد الإلكتروني لجهة الاتصال: `{{searchRecords[0].email}}`
* هاتف جهة الاتصال: `{{searchRecords[0].phone}}`
## نسخ عدة حقول
### مثال: مزامنة معلومات الشركة مع جميع الفرص المرتبطة
**الهدف**: عند تغيّر تفاصيل الشركة، حدّث جميع الفرص المرتبطة.
### إعداد
1. **المحفّز**: يتم تحديث السجل (الشركات)
* الحقول: الصناعة، حجم الشركة، الإيراد السنوي
2. **البحث في السجلات**: اعثر على جميع الفرص الخاصة بهذه الشركة
* الكائن: الفرص
* الفلتر: معرّف الشركة يساوي `{{trigger.object.id}}`
3. **Iterator**: التكرار عبر كل فرصة
4. **تحديث السجل** (داخل الـ Iterator):
* الكائن: الفرص
* السجل: `{{iterator.currentItem.id}}`
* صناعة الشركة: `{{trigger.object.industry}}`
* حجم الشركة: `{{trigger.object.companySize}}`
* الإيراد السنوي المتكرر للشركة: `{{trigger.object.annualRevenue}}`
## النسخ عند إنشاء السجل
### مثال: تعبئة الفرصة مسبقًا ببيانات الشركة
**الهدف**: عند إنشاء فرصة مرتبطة بشركة، انسخ تلقائيًا معلومات الشركة الأساسية.
### المتطلبات الأساسية
أنشئ حقول الوجهة في **الإعدادات → نموذج البيانات → الفرص**:
* صناعة الشركة (النوع: Text)
* حجم الشركة (النوع: Number)
### إعداد
1. **المحفّز**: يتم إنشاء السجل (الفرص)
* الفلتر: الشركة ليست فارغة
2. **البحث في السجلات**: احصل على تفاصيل الشركة المرتبطة
* الكائن: الشركات
* الفلتر: المعرّف يساوي `{{trigger.object.company.id}}`
3. **تحديث السجل**:
* الكائن: الفرص
* السجل: `{{trigger.object.id}}`
* صناعة الشركة: `{{searchRecords[0].industry}}`
* حجم الشركة: `{{searchRecords[0].employees}}`
<Note>
**قيود المهام والملاحظات**: العلاقات في المهام والملاحظات مُحدّدة في الشفرة كعلاقات متعدّدة-لمتعدّدة وليست متاحة بعد في محفّزات أو إجراءات سير العمل. للوصول إلى هذه العلاقات، استخدم بدلًا من ذلك [API](/l/ar/developers/extend/api).
</Note>
## مزامنة ثنائية الاتجاه
### مثال: الحفاظ على تزامن جهة الاتصال الأساسية
**الهدف**: عند تغيّر جهة الاتصال الأساسية للشركة، حدّث جهة الاتصال. وعندما يصبح شخص ما جهة الاتصال الأساسية، حدّث الشركة.
### سير العمل 1: الشركة → الشخص
1. **المحفّز**: يتم تحديث السجل (الشركات، حقل جهة الاتصال الأساسية)
2. **تحديث السجل**: عيّن قيمة "Is Primary Contact" للشخص إلى true
3. **البحث في السجلات**: اعثر على جهة الاتصال الأساسية السابقة
4. **تحديث السجل**: عيّن قيمة "Is Primary Contact" لجهة الاتصال السابقة إلى false
### سير العمل 2: الشخص → الشركة
1. **المحفّز**: يتم تحديث السجل (الأشخاص، Is Primary Contact = true)
2. **تحديث السجل**: عيّن جهة الاتصال الأساسية للشركة إلى هذا الشخص
<Note>
كن حذرًا مع المزامنات ثنائية الاتجاه لتجنّب الحلقات اللانهائية. استخدم عوامل تصفية للتحقّق مما إذا كانت القيمة قد تغيّرت فعلاً قبل التحديث.
</Note>
## استخدام الشيفرة للتعيين المعقّد
### مثال: تحويل البيانات أثناء النسخ
**الهدف**: نسخ رقم الهاتف من الشخص إلى الفرصة وتنسيقه.
```javascript
export const main = async (params) => {
const { phone } = params;
if (!phone) return { formattedPhone: null };
// Remove non-numeric characters
const digits = phone.replace(/\D/g, '');
// Format as (XXX) XXX-XXXX
const formatted = digits.length === 10
? `(${digits.slice(0,3)}) ${digits.slice(3,6)}-${digits.slice(6)}`
: phone;
return { formattedPhone: formatted };
};
```
## أفضل الممارسات
### تجنّب الحلقات
* لا تنشئ سير عمل تُحفّز بعضها بعضًا بلا نهاية
* استخدم شروطًا خاصة بالحقول
* أضف تحقّقات لمعرفة ما إذا كانت القيمة قد تغيّرت فعلاً
### التعامل مع البيانات المفقودة
* تحقّق دائمًا من وجود سجل المصدر قبل النسخ
* وفّر قيمًا افتراضية للحقول الاختيارية
* استخدم عوامل تصفية للتخطّي عندما يكون حقل المصدر فارغًا
### الأداء
* نفّذ تحديثات دفعية عند النسخ إلى العديد من السجلات
* استخدم سير عمل مجدولة لعمليات المزامنة بالجملة
* فكّر في استخدام Iterator لتحديثات متعددة للسجلات
## ذات صلة
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [محفّزات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)

View File

@@ -0,0 +1,202 @@
---
title: حقول الصيغ
description: أنشئ حقول الصيغ باستخدام سير العمل إلى حين توفّر الدعم الأصلي.
---
20 لا تدعم بعد حقول الصيغة الأصلية (القادمة في عام 2026)، ولكن يمكنك تحقيق نفس النتيجة باستخدام سير العمل. يتيح لك هذا الحل البديل حساب قيم الحقول وملؤها تلقائيًا — من عمليات الربط البسيطة إلى منطق الأعمال المعقّد.
## الاستخدامات الشائعة
| حالة الاستخدام | مثال على الصيغة |
| ------------------------ | ------------------------------- |
| **الاسم الكامل** | الاسم الأول + " " + اسم العائلة |
| **المبلغ المتوقع** | المبلغ × الاحتمال |
| **الأيام حتى الاستحقاق** | تاريخ الاستحقاق - اليوم |
| **الأيام في المرحلة** | اليوم - تاريخ دخول المرحلة |
| **درجة العميل المحتمل** | نقاط استنادًا إلى معايير متعددة |
<Tip>
للاطلاع على مثال كامل لتتبّع الوقت في مراحل خط الأنابيب، راجع [تتبّع مدة بقاء الفرص في كل مرحلة](/l/ar/user-guide/views-pipelines/how-tos/track-time-in-stage).
</Tip>
## صيغة أساسية: الربط
### مثال: تعبئة الاسم الكامل تلقائيًا
**الهدف**: دمج الاسم الأول واسم العائلة تلقائيًا في حقل الاسم الكامل.
### إعداد
1. **المشغّل**: يتم تحديث السجل أو إنشاؤه (الأشخاص)
2. **التصفية**: التحقق من تغيّر الاسم الأول أو اسم العائلة
3. **إجراء الكود**:
```javascript
export const main = async (params) => {
const { firstName, lastName } = params;
const fullName = [firstName, lastName]
.filter(Boolean)
.join(' ');
return { fullName };
};
```
4. **تحديث السجل**: عيّن الاسم الكامل إلى `{{code.fullName}}`
## صيغة رقمية: المبلغ المتوقع
### مثال: حساب الإيراد المتوقع
**الهدف**: ضرب مبلغ الفرصة في الاحتمال للحصول على المبلغ المتوقع.
اطلع على [كيفية إظهار المبلغ المتوقع في خط الأنابيب](/l/ar/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline) للاطلاع على سير العمل الكامل.
### إعداد
1. **المشغّل**: يتم تحديث السجل (الفرص، حقل المبلغ أو الاحتمال)
2. **إجراء الكود**:
```javascript
export const main = async (params) => {
const { amount, probability } = params;
const expectedAmount = (amount || 0) * (probability || 0) / 100;
return { expectedAmount };
};
```
3. **تحديث السجل**: عيّن المبلغ المتوقع إلى `{{code.expectedAmount}}`
## صيغة التاريخ: حساب الأيام
### مثال: الأيام المتبقية حتى استحقاق المهمة
**الهدف**: حساب عدد الأيام المتبقية حتى تاريخ استحقاق المهمة.
### إعداد
1. **المشغّل**: يتم تحديث السجل أو إنشاؤه (المهام، حقل تاريخ الاستحقاق)
2. **إجراء الكود**:
```javascript
export const main = async (params) => {
const { dueDate } = params;
if (!dueDate) {
return { daysUntilDue: null };
}
const due = new Date(dueDate);
const today = new Date();
const diffTime = due - today;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return { daysUntilDue: diffDays };
};
```
3. **تحديث السجل**: عيّن الأيام حتى الاستحقاق إلى `{{code.daysUntilDue}}`
<Note>
تشير القيم السالبة إلى مهام متأخرة عن موعدها. يمكنك استخدام هذا الحقل لتصفية المهام أو فرزها حسب الإلحاح.
</Note>
## صيغة شرطية: درجة العميل المحتمل
### مثال: حساب درجة العميل المحتمل بناءً على معايير
**الهدف**: منح نقاط للعملاء المحتملين استنادًا إلى حجم الشركة والصناعة والتفاعل.
### إعداد
1. **المشغّل**: يتم تحديث السجل (الأشخاص أو الشركات)
2. **إجراء الكود**:
```javascript
export const main = async (params) => {
const { companySize, industry, hasEmail, hasPhone, source } = params;
let score = 0;
// Company size scoring
if (companySize === 'Enterprise') score += 30;
else if (companySize === 'Mid-Market') score += 20;
else if (companySize === 'SMB') score += 10;
// Industry scoring
const targetIndustries = ['Technology', 'Finance', 'Healthcare'];
if (targetIndustries.includes(industry)) score += 25;
// Contact info scoring
if (hasEmail) score += 10;
if (hasPhone) score += 15;
// Source scoring
if (source === 'Referral') score += 20;
else if (source === 'Website') score += 10;
return { leadScore: score };
};
```
3. **تحديث السجل**: عيّن درجة العميل المحتمل إلى `{{code.leadScore}}`
## صيغة نصية: استخراج النطاق
### مثال: استخراج النطاق من البريد الإلكتروني
**الهدف**: استخراج نطاق البريد الإلكتروني وتخزينه تلقائيًا.
### إعداد
1. **المشغّل**: يتم تحديث السجل (الأشخاص، حقل البريد الإلكتروني)
2. **إجراء الكود**:
```javascript
export const main = async (params) => {
const { email } = params;
if (!email) return { domain: null };
const domain = email.split('@')[1]?.toLowerCase();
return { domain };
};
```
3. **تحديث السجل**: عيّن حقل النطاق إلى `{{code.domain}}`
## أفضل الممارسات
### الأداء
* فعّل المشغّل فقط عند تغيّر الحقول ذات الصلة
* استخدم عوامل التصفية لتجاوز السجلات التي لا تحتاج إلى حساب
* تجنّب الحسابات المعقّدة في عمليات سير عمل ذات حجم كبير
### التعامل مع الأخطاء
* تحقق من القيم null/undefined قبل إجراء الحسابات
* استخدم قيمًا افتراضية عند فقدان البيانات
* أعد رسائل خطأ واضحة عند فشل الحسابات
### "الاختبار"
* اختبر بحالات الحافة (حقول فارغة، قيم صفرية)
* تحقّق من الحسابات يدويًا قبل التفعيل
* راقب تشغيلات سير العمل لرصد النتائج غير المتوقعة
## ذات صلة
* [كيفية إظهار المبلغ المتوقع في خط الأنابيب](/l/ar/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline)
* [كيفية تتبّع الوقت في المرحلة](/l/ar/user-guide/views-pipelines/how-tos/track-time-in-stage)
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,127 @@
---
title: إشعار الزملاء بملاحظة للمراجعة
description: أرسل إشعارات بريد إلكتروني تلقائية عند إسناد زميل لمراجعة ملاحظة.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>
**الإشارة إلى الزملاء مباشرةً داخل الملاحظات غير مدعومة بعد.** من المخطط طرح هذه الميزة في النصف الثاني من عام 2026. في هذه الأثناء، يوفّر سير العمل هذا حلاً عمليًا بديلاً.
</Note>
## التحدّي
تريد الإشارة إلى زميل في ملاحظة ليصله إشعار لمراجعتها. حاليًا، Twenty لا يدعم @mentions في الملاحظات أو المهام.
## الحل البديل
بدلًا من الإشارة في نص الملاحظة، يمكنك:
1. أنشئ حقل علاقة **Reviewer** على كائن Notes
2. عيّن المراجع عند إنشاء الملاحظة أو تحريرها
3. الحصول على سير العمل إرسال بريد إلكتروني تلقائياً إلى المستعرض مع رابط إلى الملاحظة
<Warning>
**قيد**: يتيح هذا الأسلوب تعيين مراجع واحد لكل حقل. إذا كنت بحاجة إلى إشعار عدة أشخاص، فأنشئ حقول علاقة إضافية (مثلًا: Reviewer 1، Reviewer 2). لن يكون ذلك قابلًا للتوسّع بشكلٍ جيد إذا احتجت إلى الإشارة إلى العديد من المتعاونين دفعةً واحدة.
</Warning>
## إعداد خطوة بخطوة
### الخطوة 1: إنشاء حقل المراجع
<img src="/images/user-guide/workflows/reviewer-field-for-note.png" style={{width:'100%'}} />
1. انتقل إلى **الإعدادات → نموذج البيانات → Notes**
2. انقر **إضافة حقل**
3. التكوين:
| الإعداد | القيمة |
| ------------------ | --------------------------------------------- |
| **Name** | Reviewer |
| **النوع** | علاقة |
| **الكائن المرتبط** | عضو في مساحة العمل |
| **نوع العلاقة** | العديد من الملاحظات → عضو واحد في مساحة العمل |
4. احفظ الحقل
### الخطوة 2: إنشاء سير العمل
<VimeoEmbed videoId="1151897168" title="عرض توضيحي بالفيديو" />
#### إنشاء مشغّل سير العمل
1. انتقل إلى العرض الرئيسي وأنشئ سير عمل جديدًا
2. اختر **Record Updated** كمشغِّل
3. اختر **Note** كالكائن وحدِّد حقل Reviewer.
4. سيؤدي ذلك إلى التشغيل كلما تم تحديث مُراجع الملاحظة.
#### أضِف عامل تصفية للتحقق من أنه تم تعيين المراجع للتو
أضِف إجراء **Filter** للتأكد من أن سير العمل يستمر فقط عند تعيين مراجع:
| الشرط | القيمة |
| ------------ | -------- |
| **Reviewer** | غير فارغ |
#### ابحث عن عضو مساحة العمل
1. أضف إجراء **Search Records**
2. حدِّد **Workspace Member** (ضمن الكائنات المتقدمة)
3. أضف عامل تصفية:
| الشرط | القيمة |
| ------ | --------------------------- |
| **ID** | يساوي `{{note.reviewerId}}` |
هذا يجلب سجل عضو مساحة العمل الكامل، بما في ذلك عنوان بريده الإلكتروني واسمه.
#### إرسال بريد الإشعار الإلكتروني
1. أضف إجراء **Send Email**
2. التكوين:
| الحقل | القيمة |
| ----------- | --------------------------------------------------- |
| **إلى** | Search Record → First Workspace Member → User Email |
| **الموضوع** | هناك ملاحظة تحتاج إلى مراجعتك |
| **المحتوى** | انظر المثال أدناه |
**مثال على محتوى البريد الإلكتروني:**
```text
Hi {{searchRecord.firstWorkspaceMember.name.firstName}},
You've been assigned to review a note.
View the note here:
https://yourSubDomain.twenty.com/object/note/{{recordIsUpdated.id}}
Best,
Twenty
```
<Note>
استبدل `yourSubDomain` بنطاقك الفرعي الفعلي لمساحة عمل Twenty.
</Note>
#### الاختبار والتفعيل
1. انقر **Test** لتشغيل سير العمل يدويًا
2. أنشئ ملاحظة (أو حرّر ملاحظة موجودة) وعيّن مراجعًا
3. تحقّق من بريد المراجع الوارد بحثًا عن رسالة الإشعار
4. فعّل سير العمل
## استخدام
بعد الإعداد، يمكن لفريقك استخدام سير العمل هذا عبر:
1. إنشاء ملاحظة جديدة (أو تحرير ملاحظة موجودة)
2. اختيار زميل في حقل **Reviewer**
3. سيتلقى الزميل تلقائيًا بريدًا إلكترونيًا يحتوي على رابط مباشر إلى الملاحظة
## ذات صلة
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [إرسال رسائل البريد الإلكتروني من سير العمل](/l/ar/user-guide/workflows/capabilities/send-emails-from-workflows)
* [إنشاء حقول مخصصة](/l/ar/user-guide/data-model/how-tos/create-custom-fields)
* [إنشاء حقول العلاقات](/l/ar/user-guide/data-model/how-tos/create-relation-fields)

View File

@@ -0,0 +1,106 @@
---
title: إرسال تنبيهات عبر البريد الإلكتروني بشأن المهام المستحقة
description: إخطار أعضاء الفريق تلقائيًا بمهامهم القادمة أو المتأخرة.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/email_tasks_due.png" style={{width:'100%'}} />
إرسال تذكيرات يومية عبر البريد الإلكتروني لكل عضو في الفريق بشأن المهام المستحقة اليوم.
## نظرة عامة
يعمل سير العمل هذا وفق جدول زمني ويقوم بما يلي:
1. يجلب جميع أعضاء مساحة العمل
2. يُنفِّذ حلقة على كل عضو
3. يعثر على مهامهم المستحقة اليوم
4. ينسّق ويرسل بريدًا إلكترونيًا مخصصًا
## إعداد خطوة بخطوة
<VimeoEmbed videoId="1147463514" title="عرض توضيحي بالفيديو" />
### الخطوة 1: تهيئة المشغّل
1. انتقل إلى **الإعدادات → سير العمل** وأنشئ سير عمل جديد
2. حدّد **وفق جدول** كمشغّل
3. استخدم تعبير cron للتشغيل يوميًا عند الساعة 8:00 صباحًا: `0 8 * * *`
### الخطوة 2: البحث عن جميع أعضاء مساحة العمل
1. أضف إجراء **Search Records**
2. حدّد **Workspace Members** (ضمن الكائنات المتقدمة)
3. لا حاجة إلى فلاتر — فهذا يعيد جميع الأعضاء
### الخطوة 3: إضافة Iterator
1. أضف إجراء **Iterator**
2. اضبط مصفوفة الإدخال على أعضاء مساحة العمل من الخطوة السابقة
3. ستُنفَّذ جميع الإجراءات داخل المكرّر مرة واحدة لكل عضو
### الخطوة 4: البحث عن المهام المستحقة اليوم (داخل المكرّر)
1. داخل المكرّر، أضف إجراء **Search Records**
2. حدّد **Tasks** كالكائن
3. أضف فلاتر:
* **Assignee** = عضو مساحة العمل الحالي (من المكرّر)
* **Due Date** = اليوم
### الخطوة 5: تنسيق المهام ضمن محتوى البريد الإلكتروني (داخل المكرّر)
أضف إجراء **Code** لتنسيق المهام في قائمة قابلة للقراءة مع روابط:
```javascript
export const main = async (params: {
tasksDue?: Array<{ id: string; title: string }> | null | string;
}) => {
const tasksDue =
typeof params.tasksDue === "string"
? JSON.parse(params.tasksDue)
: params.tasksDue;
if (!Array.isArray(tasksDue) || tasksDue.length === 0) {
return {
formattedTasks: "No tasks due today."
};
}
const formattedTasks = tasksDue
.map(
t =>
`${t.title}\nhttps://yourSubDomain.twenty.com/object/task/${t.id}`
)
.join("\n\n");
return { formattedTasks };
};
```
<Note>
استبدل `yourSubDomain` بنطاقك الفرعي الفعلي لمساحة عمل Twenty.
</Note>
### الخطوة 6: إرسال بريد إلكتروني (داخل المكرّر)
1. أضف إجراء **Send Email** (ما زال داخل المكرّر)
2. التكوين:
| الحقل | القيمة |
| ----------- | ------------------------------------------------------------------------- |
| **إلى** | `{{iterator.currentItem.userEmail}}` (البريد الإلكتروني لعضو مساحة العمل) |
| **الموضوع** | مهامك المستحقة اليوم |
| **المحتوى** | `{{code.formattedTasks}}` |
### الخطوة 7: الاختبار والتفعيل
1. انقر **Test** لتشغيل سير العمل يدويًا
2. تحقّق من صناديق الوارد للرسائل الإلكترونية
3. فعّل سير العمل
## ذات صلة
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [إرسال رسائل البريد الإلكتروني من سير العمل](/l/ar/user-guide/workflows/capabilities/send-emails-from-workflows)
* [التعامل مع المصفوفات في إجراءات Code](/l/ar/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,21 @@
---
title: خدمات احترافية
description: احصل على مساعدة احترافية لبناء مسارات عمل وأتمتة معقدة من فريق Twenty وشركائه المعتمدين.
---
## متى تحتاج إلى مساعدة احترافية؟
فكّر في الاستفادة من الخدمات الاحترافية من أجل:
* تكامل أنظمة متعددة ومعقدة
* منطق الأعمال المتقدم وقواعد الأتمتة
* مسارات عمل لمعالجة البيانات على نطاق واسع
* تطوير واجهة برمجة التطبيقات المخصصة
* تدريب الفريق وتحسين سير العمل
* عندما تكون الموارد الداخلية غير متوفرة
## خيارات الخدمة
### شركاء التنفيذ
اعمل مع شركاء معتمدين من أجل تخصيصات متقدمة. تواصل معنا عبر [contact@twenty.com](mailto:contact@twenty.com) للتواصل مع [شركائنا في التنفيذ](https://twenty.com/partners).

View File

@@ -0,0 +1,170 @@
---
title: استكشاف مشكلات سير العمل وإصلاحها
description: المشكلات الشائعة في سير العمل وكيفية حلّها.
---
## المشاكل الشائعة والحلول
### لم يتم تشغيل سير العمل
**الأعراض**: لا يعمل سير العمل كما تتوقع.
**الأسباب المحتملة**:
1. **لم يتم تفعيل سير العمل**: تأكّد من تعيين سير العمل إلى "Active" وليس "Draft"
2. **شروط المُحفِّز غير مستوفاة**: تحقّق من أن المُحفِّز يطابق الحدث الذي تتوقعه
3. **حقل غير مُراقَب**: بالنسبة لمُحفِّزات "Record is Updated"، تأكّد من مراقبة الحقل المحدد
4. **الأذونات**: تحقّق من أن لديك الإذن لتشغيل سير العمل
**حلول**:
* تحقّق من حالة سير العمل في قائمة سير العمل
* اختبر بالإجراء المحدد الذي تتوقع أن يُشغِّله
* راجِع إعداد المُحفِّز
* تواصَل مع المسؤول لديك بشأن الأذونات
### مُحفِّزات سير العمل تعمل مبكراً جداً (حقول فارغة)
**الأعراض**: عند إنشاء سجل يدوياً في واجهة المستخدم، يتم تشغيل سير العمل قبل أن تتمكّن من ملء جميع الحقول. يعمل سير العمل مع قيم حقول فارغة في الغالب.
**سبب حدوث هذا**: تقوم Twenty بحفظ كل شيء في الزمن الحقيقي — لا يوجد وضع منفصل "edit" مقابل "read". عند إنشاء سجل، يتم حفظه فوراً، مما يؤدي إلى تشغيل حدث "Record is created" قبل أن تتمكّن من ملء حقول إضافية.
**متى يعمل "Record is created" بشكل جيد**:
* السجلات التي تُنشأ عبر استدعاءات API (تُملأ الحقول في طلب واحد)
* السجلات التي تُنشأ عبر الاستيراد
* إنشاء السجلات تلقائياً من سير عمل أخرى
**الحل**: بالنسبة للسجلات التي تُنشأ يدوياً في واجهة المستخدم، استخدم **"Record is created or updated"** كمحفّز بدلاً من ذلك. بهذه الطريقة:
* سيتم تشغيل سير العمل بعد أن يُنهي المستخدم ملء الحقول وحفظها
* ستحصل على البيانات الكاملة بدلاً من القيم الفارغة
<Tip>
إذا كنت تريد تشغيل سير العمل مرة واحدة فقط لكل سجل، أضِف إجراء تصفية لفحص حقل مثل `createdAt equals updatedAt` (الحفظ الأول) أو استخدم حقلاً مخصصاً من نوع خانة اختيار لتتبع ما إذا كان سير العمل قد تم تشغيله بالفعل.
</Tip>
### فشل الإجراءات
**الأعراض**: يعمل سير العمل لكن بعض الإجراءات تفشل.
**الأسباب المحتملة**:
1. **بيانات مفقودة**: الحقول المطلوبة فارغة
2. **مراجع غير صالحة**: المتغيّرات من الخطوات السابقة غير موجودة
3. **أخطاء API**: تعيد الخدمات الخارجية أخطاء
4. **مشكلات الأذونات**: يتطلب الإجراء أذونات لا تملكها
**حلول**:
* راجِع تفاصيل تشغيل سير العمل بحثاً عن رسائل الخطأ
* تحقّق من أن جميع الحقول المطلوبة تحتوي على قيم
* اختبر اتصالات API بشكل مستقل
* راجِع أذونات الدور
### أخطاء طلبات HTTP
**الأعراض**: تفشل إجراءات طلب HTTP أو تُرجِع نتائج غير متوقعة.
**رموز الأخطاء الشائعة**:
* **400**: طلب غير صالح - تحقّق من تنسيق جسم الطلب
* **401**: غير مُخوَّل - تحقّق من مفتاح API
* **403**: محظور - تحقّق من أذونات API
* **404**: غير موجود - تحقّق من عنوان URL لنقطة النهاية
* **429**: عدد كبير جداً من الطلبات - نفّذ تحديداً لمعدل الطلبات
* **500**: خطأ في الخادم - مشكلة في خدمة خارجية
**حلول**:
* تحقّق من عنوان URL لنقطة نهاية API
* تحقّق من ترويسات المصادقة
* اختبر استدعاء API خارج Twenty أولاً
* أضِف معالجة للأخطاء في إجراءات الكود
### أخطاء إجراءات الكود
**الأعراض**: يفشل كود JavaScript في التنفيذ.
**المشكلات الشائعة**:
1. **أخطاء الصياغة**: أخطاء إملائية أو JavaScript غير صالح
2. **متغيّرات غير معرّفة**: الإشارة إلى متغيّرات غير موجودة
3. **أخطاء النوع**: إجراء عمليات على أنواع بيانات خاطئة
4. **انتهاء المهلة**: يستغرق الكود وقتاً طويلاً للتنفيذ
**حلول**:
* استخدم التحقق المدمج في محرر الكود
* اختبر منطق الكود في وحدة تحكم JavaScript أولاً
* أضِف عبارات console.log لأغراض تصحيح الأخطاء
* بسّط العمليات المعقّدة
### البريد الإلكتروني لا يُرسَل
**الأعراض**: لا يقوم إجراء إرسال البريد الإلكتروني بتسليم الرسائل.
**الأسباب المحتملة**:
1. **لا يوجد حساب بريد إلكتروني متصل**: تحقّق من الإعدادات → الحسابات
2. **عنوان بريد إلكتروني غير صالح**: عنوان بريد المستلم غير صحيح التنسيق
3. **قيود الإرسال**: تم الوصول إلى حدود معدل مزوّد البريد الإلكتروني
4. **مرشّحات البريد العشوائي**: يتم حظر الرسائل
**حلول**:
* تحقّق من اتصال حساب البريد الإلكتروني
* تحقّق من صحة عناوين بريد المستلمين
* تحقّق من حدود مزوّد البريد الإلكتروني
* راجِع محتوى البريد بحثاً عن عناصر قد تُحفِّز مرشّحات البريد العشوائي
## تصحيح أخطاء سير العمل
### استخدام عمليات تشغيل سير العمل
1. انتقل إلى محرر سير العمل
2. افتح لوحة **Runs**
3. اعثر على عملية التشغيل التي فشلت
4. انقر لعرض التفاصيل خطوة بخطوة
5. راجِع رسائل الخطأ وبيانات المخرجات
### اختبار الخطوات بشكل فردي
1. بالنسبة لإجراءات الكود، استخدم زر **Test**
2. بالنسبة لطلبات HTTP، اختبر نقطة النهاية بشكل منفصل
3. أنشئ سجلات اختبار لتشغيل سير العمل
4. استخدم المُحفِّزات اليدوية للاختبار المُتحكَّم فيه
### أنماط شائعة لتصحيح الأخطاء
**أضِف التسجيل**:
استخدم إجراءات الكود لتسجيل القيم الوسيطة لأغراض تصحيح الأخطاء.
**اعزل الخطوات**:
اختبر كل خطوة بشكل مستقل لتحديد مواضع الفشل.
**تحقّق من تدفق البيانات**:
تأكّد من أن كل خطوة تتلقى بيانات الإدخال المتوقعة.
## أفضل الممارسات لتجنّب المشكلات
### قبل التفعيل
* اختبر بدقة في وضع المسودة
* تحقّق من صحة جميع اتصالات API
* راجِع شروط المُحفِّز بعناية
* وثّق السلوك المتوقع
### أثناء التطوير
* استخدم أسماء خطوات وصفية
* أضِف تعليقات في إجراءات الكود
* اختبر ببيانات واقعية
* خطّط للحالات الحدّية
### بعد التفعيل
* راقِب عمليات التشغيل الأولى عن كثب
* قم بإعداد تنبيهات لحالات الفشل
* راجِع سجلّ التشغيل بانتظام
* اجعل سير العمل بسيطًا قدر الإمكان

View File

@@ -0,0 +1,236 @@
---
title: الأسئلة الشائعة حول سير العمل
description: الأسئلة الشائعة حول سير العمل في Twenty.
---
<AccordionGroup>
<Accordion title="لماذا لا أستطيع تفعيل سير عمل؟">
من المحتمل أن تكون مشكلة أذونات. تحتاج إلى صلاحية الوصول إلى سير العمل لإنشائها وتفعيلها.
**الحل**: تواصل مع مسؤول مساحة العمل لمنحك صلاحية الوصول إلى سير العمل ضمن **الإعدادات → الأعضاء → الأدوار**.
إذا لم ترَ قسم سير العمل إطلاقاً في الشريط الجانبي لديك، فهذا يؤكد أنها مشكلة أذونات.
</Accordion>
<Accordion title="لماذا لا أرى زر سير العمل اليدوي في شريط التنقل العلوي؟">
تظهر عمليات سير العمل اليدوية في شريط التنقل فقط إذا تم إعدادها بشكل صحيح:
1. يجب أن يكون سير العمل **مفعَّلاً** (ليس في وضع المسودة)
2. يجب تعيين موضع شريط التنقل إلى **Pinned**
3. بالنسبة لمشغلات Single/Bulk، يجب أن تكون في صفحة الكائن الصحيحة
**للتحقق**: افتح سير العمل → انقر المشغل → تأكد من أن "Navbar placement" معيّن إلى "Pinned".
يمكنك دائماً الوصول إلى عمليات سير العمل اليدوية عبر **Cmd + K** (أو **Ctrl + K**) بغض النظر عن إعدادات شريط التنقل.
</Accordion>
<Accordion title="ما الفرق بين المشغلات اليدوية Single وGlobal وBulk؟">
| النوع | السجلات المطلوبة | عمليات تشغيل سير العمل |
| ----- | ---------------- | ---------------------- |
\| **Global** | لا شيء | مرة واحدة، دون إدخال سجل |
\| **Single** | واحد أو أكثر محدد | مرة لكل سجل محدد |
\| **Bulk** | واحد أو أكثر محدد | مرة واحدة، مع جميع السجلات كمصفوفة |
* **Global**: استخدمه عندما لا يحتاج سير العمل إلى سياق أي سجل (مثال: إنشاء تقرير)
* **Single**: استخدمه عندما تريد معالجة كل سجل محدد بشكل مستقل (مثال: إرسال رسائل بريد إلكتروني فردية)
* **Bulk**: استخدمه عندما تحتاج إلى معالجة السجلات معاً أو تحسين استخدام الرصيد (يتطلب إجراء Iterator)
راجع [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers) للحصول على التفاصيل.
</Accordion>
<Accordion title="لا أرى عقدة If/Else. ماذا يجب أن أفعل؟">
عقدة If/Else صريحة غير متاحة بعد، لكنها ضمن خارطة الطريق لدينا.
**الحل المؤقت الحالي**: أنشئ عدة فروع من خطوتك، يبدأ كل منها بإجراء **Filter**:
```
Step 1
│
├── Branch A: Filter (condition = true) → Actions...
│
└── Branch B: Filter (condition = false) → Actions...
```
فقط الفرع الذي يمر فيه شرط التصفية سيُنفِّذ إجراءاته اللاحقة.
راجع [كيفية استخدام الفروع](/l/ar/user-guide/workflows/capabilities/workflow-branches) للحصول على دليل خطوة بخطوة.
</Accordion>
<Accordion title="هل تعمل فروع سير العمل بالتوازي؟">
**نعم**، تعمل الفروع بالتوازي افتراضياً.
إذا كنت تريد تنفيذ فرع واحد فقط:
* أضف إجراء **Filter** في بداية كل فرع
* عيّن شروطاً متعاكسة (مثال: Branch A: status = "Open", Branch B: status ≠ "Open")
الفروع التي لا تستوفي شرط التصفية تتوقف عن التنفيذ، بينما تستمر الفروع الأخرى.
</Accordion>
<Accordion title="هل يمكنني دمج الفروع معاً مرة أخرى؟">
**نعم**. بعد اكتمال فروعك المتوازية، يمكنك إضافة خطوة يتصل بها الفرعان.
في محرر سير العمل:
1. أكمل إجراءات الفروع
2. أضف خطوة جديدة بعد الفروع
3. اسحب الاتصالات من نهاية كل فرع إلى هذه الخطوة الجديدة
ستُنفَّذ الخطوة المدمجة بعد اكتمال جميع الفروع المتصلة.
</Accordion>
<Accordion title="ما الحد الأقصى لنتائج Search Records؟">
**تعيد Search Records بحد أقصى 200 سجل.**
إذا كنت بحاجة إلى معالجة المزيد:
* أضف عوامل تصفية أكثر تحديداً لتقليل النتائج
* استخدم سير عمل مجدولاً للمعالجة على دفعات
* فكّر في استخدام الـAPI للعمليات المجمّعة
بالنسبة لمعظم سير العمل، يكفي 200 سجل. إذا بلغت هذا الحد بشكل متكرر، فكر في إعادة هيكلة الأتمتة لديك.
</Accordion>
<Accordion title="هل يمكنني إضافة CC إلى الأشخاص في رسائل البريد الخاصة بسير العمل؟">
**ليس بعد.** حقولا CC وBCC لإجراء Send Email على خارطة الطريق لدينا.
**الحل المؤقت الحالي**: أضف عدة إجراءات Send Email للإرسال إلى مستلمين إضافيين، أو استخدم HTTP Request للإرسال عبر خدمة بريد إلكتروني خارجية تدعم CC.
</Accordion>
<Accordion title="كيف أستخدم مخرجات الخطوات السابقة؟">
كل إجراء ينتج بيانات مخرجات يمكن استخدامها في الخطوات اللاحقة.
**للإشارة إلى بيانات الخطوات السابقة**:
* استخدم أداة اختيار المتغير عند تكوين حقل
* أو اكتب `{{stepName.fieldName}}` مباشرةً
**أمثلة**:
* بيانات المشغل: `{{trigger.object.email}}`
* نتائج البحث: `{{searchRecords[0].name}}`
* مخرجات الكود: `{{code.calculatedValue}}`
مرّر المؤشر فوق أي حقل في تكوين الإجراء لرؤية المتغيرات المتاحة من الخطوات السابقة.
</Accordion>
<Accordion title="لماذا يفشل Iterator لدي؟">
**Iterator يتطلب مُدخلًا من نوع مصفوفة.** مشكلات شائعة:
1. **المدخل ليس مصفوفة**: تأكد من أنك تمرر نتائج من Search Records أو إجراء آخر يُرجع مصفوفة
2. **المصفوفة فارغة**: أضف عامل تصفية قبل Iterator للتحقق من `{{searchRecords.length}} > 0`
3. **تم اختيار متغير خاطئ**: تأكد من اختيارك للمصفوفة نفسها، وليس سجلاً واحداً
**الإعداد الصحيح**:
1. Search Records (تُرجع مصفوفة)
2. Filter: length > 0
3. Iterator: اختر `{{searchRecords}}`
4. الإجراءات داخل Iterator تستخدم `{{iterator.currentItem.fieldName}}`
</Accordion>
<Accordion title="ما أقصى وقت تنفيذ لإجراءات Code؟">
إجراءات Code (دوال منطقية) لديها **مهلة افتراضية قدرها 5 دقائق** (300 ثانية).
أقصى مهلة يمكن ضبطها هي **15 دقيقة** (900 ثانية).
إذا تجاوز الكود هذا الحد، سيفشل الإجراء بخطأ انتهاء المهلة.
**نصائح لتجنب انتهاء المهلة**:
* قسّم العمليات الكبيرة إلى أجزاء أصغر باستخدام Iterator
* تجنب العمليات الحسابية الثقيلة؛ استخدم خدمات خارجية عبر HTTP Request للمعالجة المكثفة
* حسّن الكود لديك لتقليل وقت التنفيذ
* إذا كنت بحاجة إلى معالجة أطول، ففكّر في استخدام سير عمل مجدول يعالج البيانات على دفعات
</Accordion>
<Accordion title="كيف أقرأ تشغيلات سير العمل؟">
تعرض تشغيلات سير العمل سجل التنفيذ وتساعدك على تصحيح المشكلات.
**الوصول إلى التشغيلات**:
* في محرر سير العمل → لوحة **التشغيلات** على اليمين
* أو انتقل إلى **تشغيلات سير العمل** في الشريط الجانبي
**فهم التشغيل**:
* **الحالة**: قيد التشغيل، مكتمل، فشل، في الانتظار
* **الخطوات**: اطّلِع على الخطوات التي تم تنفيذها ومخرجاتها
* **الأخطاء**: انقر الخطوات الفاشلة لرؤية رسائل الخطأ
* **البيانات**: اعرض بيانات الإدخال/الإخراج في كل خطوة
راجع [تشغيلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-runs) للحصول على التفاصيل.
</Accordion>
<Accordion title="لماذا لا أرى تشغيلات سير العمل الخاصة بي؟">
قد تفشل تشغيلات سير العمل فوراً بسبب حدود المعدل. تفرض Twenty حداً صارماً يبلغ 5,000 عملية تشغيل في الساعة لكل مساحة عمل.
إذا تجاوزت هذا الحد، ستُعلَّم عمليات سير العمل فوراً كفاشلة ولن تظهر في قائمة عمليات التشغيل لديك.
**سيناريوهات شائعة تصل إلى هذا الحد**:
* اختيار أكثر من 5,000 سجل باستخدام مشغل يدوي Single
* تشغيل عدة سير عمل بالتزامن عبر مساحة العمل
* مشغلات آلية عالية التكرار (مثال: Record Updated على كائن مزدحم)
**حلول**:
* استخدم مشغلات **Bulk** بدلاً من Single لمعالجة العديد من السجلات في تشغيل واحد
* وزّع العمليات الدفعية الكبيرة زمنياً
* استخدم عوامل تصفية لتقليل تكرار المشغل
* قم بجدولة سير العمل الثقيلة خلال ساعات خارج أوقات الذروة
</Accordion>
<Accordion title="ما هي حدود معدل سير العمل؟">
لدى Twenty حدّان للمعدل لضمان استقرار النظام:
| الحد | القيمة | السلوك |
| ---- | ------ | ------ |
\| **حد مرن** | 100 تشغيل/دقيقة | تُصفّ في قائمة الانتظار بحالة "Not Started" وتُعالَج تدريجياً |
\| **حد صارم** | 5,000 تشغيل/ساعة | تفشل التشغيلات فوراً |
**الحد المرن (100/دقيقة)**: لن تفشل سير العمل لديك—ستنتظر فقط في قائمة الانتظار وتُعالَج بمرور الوقت. يمكنك تشغيل أكثر من 100 سجل؛ سيكون التنفيذ أبطأ.
**الحد الصارم (5,000/ساعة)**: ينطبق هذا على مساحة العمل بالكامل. إذا تجاوز مجموع تشغيلات جميع سير العمل لديك 5,000 تشغيل في ساعة واحدة، فسوف تفشل التشغيلات الإضافية فوراً.
**نصائح للبقاء ضمن الحدود**:
* استخدم مشغلات Bulk مع Iterator بدلاً من مشغلات Single للدفعات الكبيرة
* ادمج الأتمتة ذات الصلة في عدد أقل من سير العمل
* استخدم سير عمل مجدولة لتوزيع الحمل بمرور الوقت
</Accordion>
<Accordion title="هل يمكنني إعادة محاولة تشغيل سير عمل فاشل؟">
**لا، لا توجد حالياً وظيفة إعادة المحاولة التلقائية.**
إذا فشل تشغيل سير عمل، فستحتاج إلى:
1. راجع الخطأ في **الإعدادات → سير العمل → [سير العمل الخاص بك] → التشغيلات**
2. أصلِح المشكلة (البيانات، الإعداد، أو الخدمة الخارجية)
3. قم بتشغيل سير العمل يدوياً مرة أخرى على السجل/السجلات المتأثرة
**نصائح لتقليل حالات الفشل**:
* أضف عقد **Filter** للتحقق من البيانات قبل الإجراءات
* استخدم **Search Records** للتحقق مما إذا كانت السجلات ذات الصلة موجودة
* اختبر بدقة باستخدام عدد قليل من السجلات قبل العمليات المجمّعة
وظيفة إعادة المحاولة التلقائية ضمن خارطة الطريق لإصدار مستقبلي.
</Accordion>
<Accordion title="هل يجب إلغاء تفعيل سير العمل قبل استيراد CSV؟">
**نعم، إذا كانت سير العمل لديك تُشغَّل بإنشاء السجلات أو تحديثها.**
عند استيراد البيانات عبر CSV، يمكن لكل سجل يتم إنشاؤه أو تحديثه تشغيل سير العمل. قد يؤدي الاستيراد الكبير (آلاف السجلات) إلى:
* بلوغ حد 5,000 تشغيل/ساعة
* استهلاك قدر كبير من اعتمادات سير العمل
* إرسال رسائل بريد إلكتروني أو إشعارات غير متوقعة
* إنشاء مهام أو سجلات مكررة
**قبل الاستيراد الكمي**:
1. اذهب إلى **الإعدادات → سير العمل**
2. حدّد سير العمل المُشغَّلة بواسطة الكائن الذي تقوم باستيراده
3. **عطِّلها** مؤقتاً
4. شغّل استيراد CSV الخاص بك
5. **أعد تفعيل** سير العمل عند الانتهاء
**بديل**: إذا كنت تحتاج إلى أن تعمل سير العمل على البيانات المستوردة، فقم بالاستيراد على دفعات أصغر للبقاء ضمن حدود المعدل.
</Accordion>
<Accordion title="كيف أرتّب مخطط سير العمل؟">
إذا بدا مخطط سير العمل لديك فوضوياً مع تشتت العقد، فيمكنك تنظيمه تلقائياً:
1. انقر بزر الفأرة الأيمن في أي مكان على مخطط سير العمل
2. انقر **Tidy up workflow**
سيؤدي ذلك إلى إعادة ترتيب جميع العقد تلقائياً في مخطط نظيف ومنظم.
</Accordion>
</AccordionGroup>