> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ٢. إصدار الوثائق

> وظيفة منطقية واحدة معرّفة كأداة الذكاء الاصطناعي وعملية سير العمل.

الآن النواة الأساسية: [دالة المنطقية](/l/ar/developers/extend/apps/logic/logic-functions)
التي تحمّل قالب وسجل، تملأ العناصر النائبة، وتحفظ وثيقة
جديدة.

سنقوم بكتابة منطق العمل مرة واحدة ك\*\*معالج \*\*، ثم نكشف عنه من خلال
عدة مشغلات. هذا الفصل يربط اثنين منهم - أداة **AI** و
**عمل سير العمل**.

## مساعد التقديم

حافظ على المنطق الخالص في ملفه الخاص بحيث أنه من السهل إجراء اختبار الوحدة. هذا يربط سجل
في `{{dot.path}}` رموز ويبدلها.

```ts filename="src/logic-functions/utils/render-template.ts" theme={null}
const PLACEHOLDER_PATTERN = /\{\{\s*([\w.]+)\s*\}\}/g;

export const renderTemplate = (body: string, values: Record<string, string>) => {
  const missingTokens = new Set<string>();
  const content = body.replace(PLACEHOLDER_PATTERN, (_m, token: string) => {
    const value = values[token];
    if (value === undefined || value === '') { missingTokens.add(token); return ''; }
    return value;
  });
  return { content, missingTokens: [...missingTokens] };
};
```

<Tip>
  لأن هذا الملف ليس له تأثيرات جانبية، يمكنك تغطيته عن طريق اختبارات الوحدة السريعة
  ('اختبار yarn:unit\`). انظر [Testing](/l/ar/developers/extend/apps/operations/testing).
</Tip>

## المعالج

يستخدم المعالج [`CoreApiClient`](/l/ar/developers/extend/apps/logic/logic-functions)
لقراءة وكتابة بيانات CRM. يحمّل القالب، ويحمّل السجل المستهدف، ويملأ
الجسم، وينشئ `وثيقة`.

```ts filename="src/logic-functions/handlers/generate-document-handler.ts" theme={null}
import { CoreApiClient } from 'twenty-client-sdk/core';
import { loadRecordValues } from 'src/logic-functions/utils/load-record-values';
import { renderTemplate } from 'src/logic-functions/utils/render-template';

export const generateDocumentHandler = async (
  input: { templateId: string; recordId: string },
) => {
  const client = new CoreApiClient();

  // Use a filtered list query, not the singular lookup: the singular query
  // throws when nothing matches, which would become a 500 instead of a 404.
  const { documentTemplates } = await client.query({
    documentTemplates: {
      __args: { filter: { id: { eq: input.templateId } }, first: 1 },
      edges: { node: { id: true, name: true, body: true, target: true } },
    },
  });
  const documentTemplate = documentTemplates?.edges?.[0]?.node;
  if (!documentTemplate?.id) return { success: false, status: 404, message: 'Template not found.' };

  const record = await loadRecordValues(client, documentTemplate.target, input.recordId);
  if (!record.found) return { success: false, status: 404, message: 'Record not found.' };

  const { content, missingTokens } = renderTemplate(documentTemplate.body ?? '', record.values);

  const { createDocument } = await client.mutation({
    createDocument: {
      __args: { data: {
        name: `${documentTemplate.name} — ${record.displayName}`,
        content, status: 'GENERATED', templateId: documentTemplate.id,
      } },
      id: true, name: true,
    },
  });

  return { success: true, documentId: createDocument.id, content, missingTokens };
};
```

'loadRecordValues' يدير استفسارا مختلفا لشخص ضد شركة ومسطحات
النتيجة - انظر
[\`load-record-values.ts'](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/load-record-values.ts).

## الكشف عنه كأداة وإجراءات سير العمل

يمكن لـ `defineLogicFunction' أن يحمل عدة مشغلات. هنا، "إعدادات الأدوات"
تجعلها قابلة للاستدعاء من قبل عملاء AI ، و `workflowtionTriggertings\` تحولها إلى خطوة
في منشئ سير العمل البصري. ويصف كلاهما إسهاماتهما بمخطط "JSON".

```ts filename="src/logic-functions/generate-document.ts" theme={null}
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-sdk/logic-function';
import { GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { generateDocumentHandler } from 'src/logic-functions/handlers/generate-document-handler';
import { generateDocumentInputSchema } from 'src/logic-functions/schemas/generate-document-input.schema';

export default defineLogicFunction({
  universalIdentifier: GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
  name: 'generate-document',
  description: 'Generate a document from a template and a CRM record.',
  timeoutSeconds: 30,
  toolTriggerSettings: {
    inputSchema: generateDocumentInputSchema,
  },
  workflowActionTriggerSettings: {
    label: 'Generate Document',
    icon: 'IconFileText',
    inputSchema: jsonSchemaToInputSchema(generateDocumentInputSchema),
    outputSchema: [{ type: 'object', properties: {
      success: { type: 'boolean' }, documentId: { type: 'string' },
    } }],
  },
  handler: generateDocumentHandler,
});
```

مخطط المدخلات هو مخطط JSON بسيط يصف `templateId` و`recordId` -
أنظر [`generate-document-input.schema.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/schemas/generate-document-input.schema.ts).

## امنح حق الوصول

الوظائف المنطقية تعمل كدور للتطبيق. تحتاج إلى قراءة القوالب والسجلات
وإنشاء المستندات، لذلك اسمح بذلك في `src/roles/default-role.ts`:

```ts theme={null}
export default defineApplicationRole({
  universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
  label: 'Document Generator default role',
  canReadAllObjectRecords: true,
  canUpdateAllObjectRecords: true,
  canAccessAllTools: true,
  canBeAssignedToAgents: true,
  permissionFlagUniversalIdentifiers: [SystemPermissionFlag.UPLOAD_FILE],
});
```

يتيح "UPLOAD\_FILE" للوظيفة تحميل PDF التي تم إنشاؤها في القسم التالي.
انظر \[Roles]\(/l/ar/developers/extend/apps/config/rolesللحصول على أذونات مأخوذة من الحبوب الأكثر.

## إرفاق ملف PDF حقيقي

حقل نص محرر مفيد، لكن المستخدمين يريدون وثيقة حقيقية. دعونا ننشئ
**PDF** ونخزنه في السجل كملف قابل للتنزيل.

أولا، اعطي الكائن 'document' حقل 'FILES' للاحتفاظ بـ PDF. تقوم التطبيقات برفع
في حقول الملفات الخاصة بها **الخاصة**، لذلك هذا الحقل هو مسارات الرفع:

```ts filename="src/objects/document.object.ts" theme={null}
{
  universalIdentifier: DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
  type: FieldType.FILES,
  name: 'file',
  label: 'File',
  icon: 'IconFileTypePdf',
  universalSettings: { maxNumberOfValues: 1 },
}
```

الآن اجعل قوات الدفاع الشعبي تلك. التطبيق هو مشروع عقدة حقيقي، بحيث يمكنك إضافة أي npm
حزمة تحتاج إليها واستيرادها مثل أي مكان آخر. نحن نستخدم **[pdf-lib](https://pdf-lib.js.org/)**
لرسم PDF و **[marked](https://marked.js.org/)** لتحليل جسم Markdown- يقوم CLI بتثبيته في وقت تشغيل الوظيفة لك:

```bash filename="Terminal" theme={null}
yarn add pdf-lib marked
```

المساعد الكامل هو
\[`generate-document-pdf.ts'](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/generate-document-pdf.ts).
إنه يحلل علامة الرموز المميزة مع علامة `علامة'. ممارسة '، ثم تضعها باستخدام
pdf-lib: العناوين الحقيقية، **الجريئة**/*italic*، تدير الرصاصات والقوائم المرقمة،
الكتل والقواعد-عبارة عن مقتطفات متعددة الصفحات A4 لتقديم القالب
نفسه، وليس جدار نص.

<Frame caption="PDF المولدة: الطباعة الحقيقية وتنسيق Markdown، وتقديم نموذج الجسم.">
  <img src="https://mintcdn.com/twenty/sqJBeTZq-W-RDBPU/images/docs/developers/extends/apps/document-generator/07b-generated-pdf.png?fit=max&auto=format&n=sqJBeTZq-W-RDBPU&q=85&s=ab29f3ed244aa424e850abe73f8c082d" alt="تم توليد PDF مصقول قابل للتسويق" width="1286" height="1200" data-path="images/docs/developers/extends/apps/document-generator/07b-generated-pdf.png" />
</Frame>

<Note>
  خطوط pdf-lib المدمجة تستخدم تشفير WinAnsi ، لذا لهجات غرب أوروبا تقدم
  خارج الصندوق؛ خرائط المساعد مقتبسات ذكية والشرطات وتسقط الشخصيات
  لا يمكن ترميزها. إن عرض النصوص غير اللاتينية (الصينية، العربية، السيريلية) سيعني
  تضمين خط Unicode.
</Note>

ثم قم بتحميله وتخزين المرجع في السجل. مسارات 'uploadFile' بايت
إلى حقل الملفات الذي تملكه التطبيق؛ و\`id' المعاد هو ما قمت بحفظه:

```ts filename="src/logic-functions/handlers/generate-document-handler.ts" theme={null}
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { generateDocumentPdf } from 'src/logic-functions/utils/generate-document-pdf';

const documentName = `${documentTemplate.name} — ${record.displayName}`;
const bytes = await generateDocumentPdf(documentName, content);
const fileName = 'proposal.pdf';

const uploaded = await new MetadataApiClient().uploadFile(
  Buffer.from(bytes),
  fileName,
  'application/pdf',
  DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
);

await client.mutation({
  updateDocument: {
    __args: {
      id: documentId,
      data: { file: [{ fileId: uploaded.id, label: fileName }] },
    },
    id: true,
  },
});
```

الوثيقة التي تم إنشاؤها تحمل الآن PDF قابل للتنزيل:

<Frame caption="PDF التي تم إنشاؤها، مخزنة في حقل ملف المستند.">
  <img src="https://mintcdn.com/twenty/sqJBeTZq-W-RDBPU/images/docs/developers/extends/apps/document-generator/08-document-with-pdf.png?fit=max&auto=format&n=sqJBeTZq-W-RDBPU&q=85&s=afbc1091577e93482b71dcc0d73af110" alt="سجل مستند مع ملف PDF الذي تم إنشاؤه" width="2880" height="1800" data-path="images/docs/developers/extends/apps/document-generator/08-document-with-pdf.png" />
</Frame>

<Note>
  'uploadFile' يستهدف فقط حقول الملفات **التي تملكها التطبيقات** (لذلك يتطلب التحميل دائمًا تطبيق
  يمتلك الحقل، بالإضافة إلى علم الدور 'UPLOAD\_FILE\`). لهذا السبب يهبط PDF
  في الحقل 'file' الخاص بالسجل - نفس النمط الذي يستخدمه
  [call-record](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/public/call-recorder)
  للتسجيلات.
</Note>

**بعد هذه الخطوة:** كل مستند تم إنشاؤه لديه PDF حقيقي، قابل للتنزيل. ولكن
لا يوجد ما يمكنه *استدعاء* المولّد من واجهة المستخدم بعد — ولأجل ذلك نحتاج إلى مسار HTTP.

<Card title="التالي: طرق HTTP →" icon="الكرة الأرضية" href="/l/ar/developers/extend/apps/tutorials/document-Generator/http-routes">
  خدمة الوظيفة عبر HTTP وتقديم الوثائق كصفحات على الويب.
</Card>
