top of page

كيف تبني تطبيقًا لطلبات الإجازة باستخدام Power Apps وPower Automate وSharePoint (مع موافقة المدير وأرصدة الإجازات)

قبل 32 دقيقة
8 دقيقة قراءة

تنجح طلبات الإجازة عبر البريد الإلكتروني إلى أن تفشل. يبقى طلب أحدهم دون قراءة في صندوق بريد المدير، وتعلم الموارد البشرية بالإجازة بعد أن تُؤخذ، ولا أحد متأكد تمامًا من عدد الأيام المتبقية لكل موظف.

في هذا الدليل سنبني نظامًا بسيطًا لطلبات الإجازة على Microsoft 365: تطبيق Canvas في Power Apps للموظفين، ومسار عمل للموافقات في Power Automate يُرسَل إلى مدير كل موظف، وقوائم SharePoint كخلفية للبيانات. يستطيع المديرون الموافقة أو الرفض أو طلب تواريخ مختلفة مع ذكر السبب، ويتلقى الموظف بريدًا إلكترونيًا في كل الأحوال. وتُخصم الأيام من الرصيد الصحيح (إجازة سنوية أو مرضية أو شخصية) بمجرد تقديم الطلب، وتُعاد إذا لم تتم الموافقة عليه.

والنظام محمي كما ينبغي أيضًا. فالموظفون لا يرون إلا طلباتهم، والمديرون يرون طلبات فرقهم، والمسؤولون يرون كل شيء. ويُفرض ذلك من خلال صلاحيات SharePoint على مستوى العناصر، لا بمجرد عوامل تصفية داخل التطبيق.

الشاشة الرئيسية لتطبيق Canvas لطلبات الإجازة مع أرصدة الإجازات وطلبات بأربع حالات

ما الذي ستبنيه

  • تطبيق Canvas يختار فيه الموظفون نوع الإجازة والتواريخ، ويرون عدد أيام العمل التي يستهلكها الطلب، ثم يقدّمونه.

  • بطاقات رصيد لكل نوع إجازة (Vacation وSick وPersonal). ويمنع التطبيق الطلبات التي تتجاوز الرصيد.

  • مسار عمل للموافقة يوجّه كل طلب إلى مدير الموظف المحدد في Microsoft Entra ID (Azure AD)، مع معتمِد احتياطي عند عدم تحديد مدير.

  • ثلاث نتائج: Approved (موافقة)، أو Rejected (رفض)، أو Change dates (تغيير التواريخ)، ولكل منها تعليقات المدير.

  • بريد إلكتروني بالنتيجة إلى الموظف مع شارة حالة ملوّنة ورصيده المتبقي.

  • خصم واسترداد تلقائيان: تُخصم الأيام عند تقديم الطلب وتُعاد إذا رُفض أو أُعيد لاختيار تواريخ جديدة.

  • أمان على مستوى العناصر بحيث لا يرى كل طلب إلا الموظف ومديره والمسؤولون.

ما الذي تحتاج إليه

  • Microsoft 365 مع SharePoint Online وOutlook وPower Apps / Power Automate (الموصلات القياسية المستخدمة هنا مضمنة في معظم خطط الأعمال، ولا حاجة إلى ترخيص Premium).

  • صلاحية إنشاء موقع SharePoint، أو موقع قائم تملكه.

  • تعبئة بيانات المديرين في ملفات تعريف المستخدمين في Microsoft Entra ID، حتى يتمكن مسار العمل من العثور على المعتمِد لكل موظف.

الخطوة 1: إنشاء موقع SharePoint والقوائم

أنشئ موقع فريق أو موقع اتصالات جديدًا، مثل HR Requests. ثم أضف ثلاث قوائم.

قائمة Vacation Requests

هنا تُحفظ كل الطلبات. أعد تسمية العمود Title إلى Request، ثم أضف الأعمدة التالية:

  • Employee: شخص

  • EmployeeEmail: سطر واحد من النص

  • Manager: شخص

  • ManagerEmail: سطر واحد من النص

  • LeaveType: اختيار (Vacation، Sick، Personal)، والافتراضي Vacation

  • StartDate وEndDate: تاريخ فقط

  • Days: رقم

  • EmployeeNotes: أسطر متعددة من النص

  • Status: اختيار (Pending، Approved، Rejected، Change dates)، والافتراضي Pending

  • ManagerComments: أسطر متعددة من النص

نخزّن عناوين البريد الإلكتروني نصًا عاديًا إلى جانب أعمدة الشخص، لأن أعمدة النص سهلة التصفية في Power Apps دون تحذيرات التفويض.

قائمة Vacation Requests في SharePoint مع طلبات Pending وChange dates وApproved وRejected

قائمة Leave Balances

صف واحد لكل موظف ولكل نوع إجازة:

  • Title (تُعاد تسميته إلى Employee Email): البريد الإلكتروني للموظف بأحرف صغيرة

  • Employee: شخص

  • LeaveType: اختيار (Vacation، Sick، Personal)

  • Allowance: رقم (الاستحقاق السنوي)

  • Booked: رقم، والافتراضي 0

  • Balance: عمود محسوب بالصيغة أدناه، مع ضبط نوع الناتج على سطر واحد من النص

=(Allowance-Booked)&""

لماذا نص؟ لأن العمود المحسوب من نوع رقم يُعاد إلى Power Apps وPower Automate بالشكل «6.00000000000000». أما إعادته نصًا فتُبقيه نظيفًا.

قائمة Leave Balances تعرض الاستحقاق والمحجوز والرصيد للإجازات السنوية والمرضية والشخصية

قائمة App Admins

قائمة بسيطة تحتوي على العمود Title فقط. أضف صفًا لكل مسؤول ببريده الإلكتروني بأحرف صغيرة. يستخدمها التطبيق لتفعيل عرض All requests، ويستخدم مسار العمل أول مسؤول فيها معتمِدًا احتياطيًا.

الخطوة 2: ضبط صلاحيات القوائم

عوامل التصفية داخل التطبيق ليست أمانًا. فأي شخص لديه وصول إلى القائمة يمكنه فتحها في SharePoint ورؤية كل شيء، لذلك نُقفل القوائم أولًا ثم ندع مسار العمل يمنح الوصول لكل طلب على حدة.

  • الموقع: امنح موظفيك (مثل «Everyone except external users» ضمن مجموعة Members في الموقع) صلاحية Read على الموقع.

  • Vacation Requests: أوقف توريث الصلاحيات وامنح Members صلاحية Contribute، حتى يتمكن الموظفون من إنشاء الطلبات من التطبيق. ويحتفظ Owners بصلاحية Full Control.

  • Leave Balances: أوقف توريث الصلاحيات وامنح Members صلاحية Read. ثم افصل التوريث على كل صف رصيد بحيث لا يقرؤه إلا المالكون والموظف المعني. ولا يغيّر العمود Booked إلا مسار العمل (الذي يعمل بصفة مالك).

  • App Admins: صلاحية Read لـ Members، حتى يتمكن التطبيق من التحقق مما إذا كان المستخدم الحالي مسؤولًا.

في الخطوات التالية يزيل مسار العمل مجموعة Members من كل طلب جديد، ويمنح صلاحية Read للموظف ومديره فقط. أما المسؤولون فهم ضمن مجموعة Owners، لذا يظلون يرون كل شيء.

الخطوة 3: إنشاء مسار العمل

في Power Automate، اختر Create > Automated cloud flow. سمِّه Vacation Request Approval واختر مشغّل SharePoint ‏When an item is created.

مربع حوار إنشاء مسار عمل سحابي تلقائي مع تحديد مشغّل SharePoint ‏When an item is created

اضبط Site Address على موقع HR Requests وList Name على Vacation Requests.

المشغّل When an item is created موجّهًا إلى قائمة Vacation Requests

وهذا هو مسار العمل الكامل الذي سنبنيه، من الأعلى إلى الأسفل.

نظرة عامة على مسار العمل، الجزء 1: المشغّل، وتحديد المعتمِد، وخصم الأيام، وبدء إقفال العنصر
نظرة عامة على مسار العمل، الجزء 2: صلاحيات العنصر، والموافقة، والقرار، والاسترداد
نظرة عامة على مسار العمل، الجزء 3: حفظ القرار، والاسترداد، وقراءة الرصيد، ومراسلة الموظف

الخطوة 4: تحديد المعتمِد

أولًا، احصل على معتمِد احتياطي في حال لم يكن للموظف مدير في Entra ID.

Get admins: إجراء SharePoint Get items على قائمة App Admins، مع ضبط Top Count على 1.

Fallback approver: إجراء Initialize variable باسم ApproverEmail ومن النوع String، بالقيمة التالية:

first(outputs('Get_admins')?['body/value'])?['Title']

Get manager: إجراء Office 365 Users Get manager (V2)، مع ضبط User على قيمة EmployeeEmail من المشغّل.

Use manager as approver: إجراء Set variable لضبط ApproverEmail على المخرج Mail للمدير.

إذا لم يكن للموظف مدير، يفشل Get manager ويُتخطّى Use manager as approver. لذا افتح في الإجراء التالي Settings > Run after وحدد كلًّا من «is successful» و«is skipped». وعندها يتابع مسار العمل مع المعتمِد الاحتياطي.

Save approver on request: إجراء SharePoint Update item على Vacation Requests، مع Id من المشغّل. اضبط Manager Claims على المتغير ApproverEmail، وManagerEmail على:

toLower(variables('ApproverEmail'))

هذا ما يُشغّل عرض My team في التطبيق.

الخطوة 5: خصم الأيام من الرصيد

Get balance: إجراء SharePoint Get items على Leave Balances، مع Top Count بقيمة 1 واستعلام التصفية (Filter Query) التالي:

Title eq '@{toLower(triggerOutputs()?['body/EmployeeEmail'])}' and LeaveType eq '@{triggerOutputs()?['body/LeaveType/Value']}'

Deduct days: إجراء SharePoint Update item على Leave Balances. استخدم ID أول نتيجة، واضبط Booked على:

add(float(coalesce(first(body('Get_balance')?['value'])?['Booked'],0)), float(triggerOutputs()?['body/Days']))

الخصم عند التقديم يعني أن طلبين قُدّما في وقتين متقاربين لا يمكنهما استهلاك الأيام نفسها.

الخطوة 6: إقفال الطلب بصلاحيات على مستوى العنصر

نستخدم أربعة إجراءات SharePoint من نوع Send an HTTP request to SharePoint. جميعها موجّهة إلى موقع HR Requests، وتستخدم الطريقة POST، وتتضمن الترويسة Accept: application/json;odata=nometadata.

Stop inheriting permissions. قيمة Uri:

_api/web/lists/getbytitle('Vacation Requests')/items(@{triggerOutputs()?['body/ID']})/breakroleinheritance(copyRoleAssignments=true,clearSubscopes=true)

Remove Members group. يزيل هذا الإجراء صلاحية Contribute لمجموعة Members من هذا العنصر وحده. ابحث عن معرّف مجموعة Members ضمن Site settings > People and groups (يظهر في عنوان URL باسم MembershipGroupId)، واستبدل القيمة 5 إن كانت مختلفة لديك. والرقم 1073741827 هو معرّف الدور المضمّن Contribute.

_api/web/lists/getbytitle('Vacation Requests')/items(@{triggerOutputs()?['body/ID']})/roleassignments/removeroleassignment(principalid=5,roledefid=1073741827)

Get employee ID وGet manager ID. يحتاج SharePoint إلى معرّف المستخدم لا إلى بريده الإلكتروني. تُرجع نقطة النهاية ensureuser هذا المعرّف وتضيف المستخدم إلى الموقع عند الحاجة. قيمة Uri:

_api/web/ensureuser

نص الطلب للموظف (استخدم variables('ApproverEmail') للمدير):

{ "logonName": "@{triggerOutputs()?['body/EmployeeEmail']}" }

Employee can read وManager can read. الرقم 1073741826 هو الدور المضمّن Read. وللمدير، استخدم body('Get_manager_ID') بدلًا من ذلك.

_api/web/lists/getbytitle('Vacation Requests')/items(@{triggerOutputs()?['body/ID']})/roleassignments/addroleassignment(principalid=@{body('Get_employee_ID')?['Id']},roledefid=1073741826)

بعد تنفيذ ذلك، لا يرى الطلب إلا الموظف ومديره ومالكو الموقع (المسؤولون لديك). وللموظف صلاحية Read فقط، فلا يمكنه تعديل الطلب بعد تقديمه.

الخطوة 7: إرسال طلب الموافقة

أضف الإجراء Approvals Start and wait for an approval، مع نوع الموافقة Custom Responses - Wait for one response. وأضف ثلاثة خيارات للرد: Approve وReject وChange dates.

العنوان:

@{triggerOutputs()?['body/LeaveType/Value']} request: @{triggerOutputs()?['body/Employee/DisplayName']}, @{formatDateTime(triggerOutputs()?['body/StartDate'],'MMM d')} to @{formatDateTime(triggerOutputs()?['body/EndDate'],'MMM d, yyyy')}

Assigned to: المتغير ApproverEmail. ضع في Details نوع الإجازة والتواريخ وأيام العمل والملاحظات والرصيد المتبقي في حال الموافقة. يدعم مربع Details تنسيق Markdown، لذا تعمل التسميات الغامقة. ولسطر الرصيد استخدم:

sub(float(first(body('Get_balance')?['value'])?['Balance']), float(triggerOutputs()?['body/Days']))

أضف سطرًا يطلب من المدير اختيار Change dates إذا كان التوقيت غير مناسب واقتراح تواريخ جديدة في التعليقات. واضبط Item link على Link to item من المشغّل حتى يتمكن من فتح الطلب في SharePoint.

يتلقى المدير طلب الموافقة في Outlook وفي Teams وفي مركز الموافقات في Power Automate.

طلب موافقة يعرض نوع الإجازة والتواريخ وأيام العمل والرصيد المتبقي في حال الموافقة

الخطوة 8: تسجيل القرار واسترداد الأيام

Decision status: إجراء Compose يحوّل رد المدير إلى قيم الحالة لدينا:

if(equals(outputs('Start_and_wait_for_an_approval')?['body/outcome'],'Approve'),'Approved',if(equals(outputs('Start_and_wait_for_an_approval')?['body/outcome'],'Reject'),'Rejected','Change dates'))

Save decision: إجراء Update item على Vacation Requests. اضبط Status على مخرجات Decision status، وManagerComments على:

first(outputs('Start_and_wait_for_an_approval')?['body/responses'])?['comments']

Refund if not approved: شرط (Condition) يكون فيه مخرج Decision status غير مساوٍ لـ Approved. في الفرع True، اجلب صف الرصيد مرة أخرى (إجراء Get items نفسه في الخطوة 5، باسم Get balance again) وحدّثه بما يلي:

max(0, sub(float(coalesce(first(body('Get_balance_again')?['value'])?['Booked'],0)), float(triggerOutputs()?['body/Days'])))

نقرأ الصف مجددًا بدلًا من إعادة استخدام نتيجة الخطوة 5 لأن الموافقة قد تستغرق أيامًا، وربما غيّرت طلبات أخرى الرصيد في هذه الأثناء.

الخطوة 9: إرسال بريد إلكتروني إلى الموظف

Read balance: إجراء Get items آخر على Leave Balances بعامل التصفية نفسه، حتى يعرض البريد الرصيد بعد أي استرداد.

Email the employee: إجراء Office 365 Outlook Send an email (V2) إلى قيمة EmployeeEmail. الموضوع:

Your @{toLower(triggerOutputs()?['body/LeaveType/Value'])} request: @{outputs('Decision_status')}

في نص الرسالة، انتقل إلى عرض التعليمات البرمجية (</>) وأنشئ جدول HTML صغيرًا: تحية، ورسالة من سطر واحد حسب الحالة، وشارة حالة ملوّنة، والتواريخ، وأيام العمل، وتعليقات المدير، و«الأيام المتبقية». ولون الشارة تعبير واحد:

if(equals(outputs('Decision_status'),'Approved'),'#107c10',if(equals(outputs('Decision_status'),'Rejected'),'#a4262c','#8a5a00'))
بريد نتيجة الموافقة مع شارة حالة خضراء وأيام الإجازة المتبقية
بريد نتيجة الرفض مع سبب المدير واسترداد أيام الإجازة المرضية
بريد نتيجة تغيير التواريخ مع التواريخ الجديدة التي اقترحها المدير

احفظ مسار العمل وفعّله.

الخطوة 10: بناء تطبيق Canvas

في Power Apps، أنشئ تطبيق Canvas فارغًا بتنسيق الأجهزة اللوحية وأضف القوائم الثلاث كمصادر بيانات: Vacation Requests وLeave Balances وApp Admins.

من هو المستخدم؟

حدد App وأضف هذه الصيغ المسماة في الخاصية Formulas:

MyEmail = Lower(User().Email);
IsAdmin = !IsBlank(LookUp('App Admins', Title = MyEmail));
IsManager = !IsBlank(LookUp('Vacation Requests', ManagerEmail = MyEmail));

ولأن القوائم نفسها محمية، فإن المستخدم الذي ليس مديرًا لن يحصل ببساطة على أي صفوف من عامل تصفية الفريق. فالأزرار وسيلة للراحة، وليست هي الأمان.

الشاشة الرئيسية

  • ثلاثة أزرار تبديل: My requests (ظاهر دائمًا)، وMy team (Visible: IsManager || IsAdmin)، وAll requests (Visible: IsAdmin). يضبط كل منها المتغير varView، مثل Set(varView, "Team").

  • معرض أفقي لبطاقات الرصيد. Items: Filter('Leave Balances', Title = MyEmail). وداخله اعرض ThisItem.LeaveType.Value وRound(Value(ThisItem.Balance), 1) & " days left".

  • معرض عمودي للطلبات، مع تسمية حالة ملوّنة وتعليقات المدير. خاصية Items الخاصة به:

SortByColumns(If(varView = "All", 'Vacation Requests', varView = "Team", Filter('Vacation Requests', ManagerEmail = MyEmail), Filter('Vacation Requests', EmployeeEmail = MyEmail)), "Created", SortOrder.Descending)
  • لون الحالة في الخاصية Fill للتسمية:

Switch(ThisItem.Status.Value, "Approved", RGBA(16,124,16,1), "Rejected", RGBA(164,38,44,1), "Change dates", RGBA(160,100,0,1), RGBA(96,110,125,1))
  • OnVisible للشاشة: Refresh('Vacation Requests'); Refresh('Leave Balances') حتى تكون الحالات والأرصدة محدّثة دائمًا.

وهذا عرض All requests الخاص بالمسؤول، مع اسم كل موظف على صفه.

عرض All requests للمسؤولين يعرض طلبات جميع الموظفين

شاشة الطلب الجديد

  • قائمة منسدلة لنوع الإجازة مع Items ["Vacation", "Sick", "Personal"].

  • تسمية مخفية تحتوي على الرصيد المتاح للنوع المحدد:

Coalesce(Value(LookUp('Leave Balances', Title = MyEmail && LeaveType.Value = ddType.Selected.Value).Balance), 0)
  • منتقيا تاريخ لأول يوم وآخر يوم في الإجازة.

  • تسمية مخفية تحسب أيام العمل (من الاثنين إلى الجمعة) بين التاريخين:

With({s: dpStart.SelectedDate, e: dpEnd.SelectedDate}, If(e < s, 0, CountIf(Sequence(DateDiff(s, e, TimeUnit.Days) + 1, 0), Weekday(DateAdd(s, Value, TimeUnit.Days), StartOfWeek.Monday) < 6)))
  • رسالة تعرض الأيام المستخدمة والمتبقية، أو تحذيرًا باللون الأحمر عندما يتجاوز الطلب الرصيد.

  • زر Submit يكون معطّلًا عندما تكون التواريخ خاطئة أو الرصيد غير كافٍ.

شاشة طلب إجازة جديد تعرض 4 أيام عمل و6 أيام إجازة متبقية بعد الطلب
زر Submit معطّل لأن الطلب يحتاج إلى 4 أيام إجازة شخصية ولم يتبقَّ سوى 3

تنشئ الخاصية OnSelect للزر Submit العنصر باستخدام Patch، ثم يتولى مسار العمل الباقي:

Patch('Vacation Requests', Defaults('Vacation Requests'), { Title: ddType.Selected.Value & " " & Text(dpStart.SelectedDate, "yyyy-mm-dd"), LeaveType: {Value: ddType.Selected.Value}, Employee: { '@odata.type': "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser", Claims: "i:0#.f|membership|" & MyEmail, DisplayName: User().FullName, Email: MyEmail, Department: "", JobTitle: "", Picture: "" }, EmployeeEmail: MyEmail, StartDate: dpStart.SelectedDate, EndDate: dpEnd.SelectedDate, Days: Value(lblDaysCalc.Text), EmployeeNotes: txtNotes.Text, Status: {Value: "Pending"} });
Notify("Request sent. Your manager will get an approval request.", NotificationType.Success); Navigate(Screen1, ScreenTransition.UnCover)

احفظ التطبيق وانشره، ثم شاركه مع موظفيك (مثل مجموعة Members نفسها).

الخطوة 11: اختبار النتائج الثلاث

قدّم بعض الطلبات من التطبيق وأجب عن كل طلب موافقة بطريقة مختلفة:

  • Change dates، مع تعليق يقترح تواريخ جديدة. تتغير الحالة إلى Change dates، وتعود الأيام إلى الرصيد، ويتلقى الموظف البريد ذا اللون الكهرماني.

  • Approve. تبقى الأيام محجوزة ويعرض البريد الرصيد الجديد.

  • ارفض طلب إجازة مرضية. تُسترد الأيام، فيعود رصيد Sick إلى 10.

ثم سجّل الدخول بصفة موظف آخر. ينبغي ألا يرى إلا طلباته في التطبيق، وألا يعرض له SharePoint أي شيء آخر في القائمة أيضًا.

من يرى ماذا

  • الموظف: طلباته (للقراءة فقط بعد التقديم) وأرصدته.

  • المدير: طلباته إضافةً إلى كل طلب يُوجَّه إليه، من خلال عرض My team.

  • المسؤول: كل شيء، من خلال عرض All requests ومجموعة Owners في الموقع.

  • مسار العمل: يعمل باتصال مالك مسار العمل، ولهذا يحتاج حساب المالك إلى صلاحية Full Control على الموقع.

تجهيزه للتشغيل الفعلي

  • العطلات الرسمية: احتفظ بقائمة Holidays واستبعد تلك التواريخ في صيغة أيام العمل وفي مسار العمل.

  • أنصاف الأيام: غيّر Days للسماح بالكسور العشرية وأضف مفتاح تبديل لنصف اليوم في التطبيق.

  • إعادة الضبط السنوية: مسار عمل مجدول في 1 يناير (أو في تاريخ ذكرى التعيين) يعيد Booked إلى 0 ويطبق قواعد الترحيل.

  • الإلغاء: أضف زر Cancel للطلبات Pending أو Approved يعيد الأيام إلى الرصيد.

  • إلحاق الموظفين الجدد: مسار عمل ينشئ صفوف الرصيد الثلاثة (مع صلاحيات العناصر) عند انضمام موظف جديد.

  • حساب الخدمة: شغّل مسار العمل بحساب مخصص بدلًا من حساب شخص بعينه، حتى لا يتعطل عندما يغادر أحدهم.

الجزء الممتع: لعبة «بينغو» الغياب عن المكتب

ضع علامة على كل ما رأيته:

  • «سأكون في إجازة يوم الجمعة» في محادثة Teams، مرسلة صباح الجمعة.

  • إجازة تمت الموافقة عليها بالرد على الجميع.

  • شخصان من الفريق نفسه في إجازة في الأسبوع نفسه، وكلاهما «موافق عليه».

  • رد غياب تلقائي يقول «سأعود يوم الاثنين» منذ ثلاثة أيام اثنين مضت.

  • شخص يسأل الموارد البشرية عن عدد أيامه المتبقية. في ديسمبر.

ثلاث أو أكثر؟ سيعوّض هذا التطبيق تكلفته قبل العطلات.

هل تحتاج إلى مساعدة؟

تبني Smart Solutions حلول Power Apps وPower Automate للشركات الكندية، من موافقات الإجازات والمصروفات إلى تطبيقات متكاملة للموارد البشرية والعمليات. تواصل معنا وسنساعدك على تطبيق هذا الحل، أو على بناء حل مصمم وفق سياساتك.

هل تدير طلبات الشراء والموردين والموافقات عبر البريد الإلكتروني أيضًا؟ اطّلع على ProcuraCloud، منصتنا الكندية للمشتريات وإدارة الأعمال المخصصة للشركات الصغيرة والمتوسطة.


تعليقات


bottom of page