معادن · الصيانة الميكانيكية PAP · الحضور والقوى العاملة

داشبورد الحضور: صفحة واحدة

صفحة واحدة فقط (6 أرقام و5 رسوم) مبنية بالكامل داخل Power BI بعناصره الأصلية (بدون أي صورة خلفية)، بهوية معادن، تقرأ ثلاثة ملفات: ملف الحضور بالماكرو على OneDrive، وماستر الموظفين على SharePoint، وقائمة المقاول (السويدي). الملف الجاهز على جهازك، وهذا الدليل يعلمك تبنينه بنفسك عنصر عنصر، وكيف تربطين الملفات عشان يتحدث الواحد ويظهر في الثاني مباشرة.

قبل ما تبدين

الصورة الكاملة

الملف الجاهز: 02_Attendance\2_Dashboard\PAP_Workforce_Dashboard.pbip (افتحيه في Power BI Desktop، اضغطي Refresh). المولّد 5_Scripts\att_workforce_pbip.py يعيد بناءه بأمر واحد لو تغير شي.

ملف الحضور (OneDrive)Daily Attendance.xlsm، جدول AttendanceTracker: التاريخ، الرقم، الحالة. يكتبه المسؤول يوميًا.
ماستر الموظفين (SharePoint)PAP_Mech_Employee_Master.xlsx، جدول tblEmployees: سطر لكل موظف، رقم معادن، الشفت الافتراضي.
قائمة المقاولMasterSheet من السويدي، جدول Table7: الحالة، تاريخ التعبئة، الإجازة، انتهاء الهوية.
Power BIيقرأ الثلاثة، يربطهم بالرقم، يحسب، وينشر على SharePoint ويتحدث بجدولة.

وش تجاوب الصفحة (من فوق لتحت)

الجزءالسؤالالعنصر
6 بطاقاتكم موجود، كم نسبة التغطية، كم غائب ومريض، كم بإجازة أو أوف، كم ما انسجل، وكم الهيدكاونت؟On site · Coverage · Absent and sick · On leave or off · Not recorded · Headcount
الصف الأولوصلنا الهدف؟ وش توزيع الحالات؟ والحضور يتحسن أو يسوء يوم بيوم؟Gauge · Donut · Combo (أعمدة داي/نايت + خط التغطية)
الصف الثانيمين وين على أي شفت؟ ومين نشط وما له سجل؟Bar بالمناطق · جدول Active but no record
ليش صفحة واحدةالحضور يُقرأ في دقيقة: رقم، هدف، اتجاه، ومين ناقص. كل شي ثاني (الهويات، الإجازات، قائمة المقاول) موجود في النموذج كمقاييس جاهزة، وتقدرين تضيفين له صفحة لاحقًا لو احتجتيه، لكن الصفحة اليومية تبقى وحدة.
ليش بدون صور خلفيةكل مستطيل ونص وبطاقة هنا عنصر Power BI أصلي. تقدرين تعدلين أي شي بالفورمات، وتنسخين الصفحة بـ Duplicate، والتقرير خفيف وينطبع PDF صح. الشكل يجي من ثلاث حاجات بس: مستطيلات بيضاء على أرضية Bone، خط ذهبي تحت الترويسة، وبطاقات أرقام كبيرة.

الخطوة 2

Power Query: الجداول الأربعة

لكل جدول: Get Data ← Blank Query ← Advanced Editor ← الصقي الكود ← سمّي الاستعلام بنفس الاسم. غيري سطر Source فقط.

Employees · ماستر الموظفين31 عمود
  • LinkID: مفتاح الربط
  • TenureYears: من تاريخ التعبئة
  • IDDaysLeft / IDStatus / IDSort: انتهاء الهوية (سالب = منتهية، ≤60، ≤90، Valid، No date)
  • HasMaadenID، AreaClean، CategoryClean، NationalityClean: الفاضي يصير نصًا واضحًا بدل (Blank)
  • وبعدها 5 أعمدة DAX محسوبة (في القسم 3)
let
    Source = Excel.Workbook(File.Contents("C:\\Users\\rawab\\OneDrive\\Desktop\\معادن - روابي\\02_Attendance\\3_Output\\PAP_Mech_Employee_Master.xlsx"), null, true),
    T = Source{[Item="tblEmployees", Kind="Table"]}[Data],
    Kept = Table.SelectColumns(T, {"MaadenID", "SIS_ID", "EmployeeName", "Craft", "Area", "Category", "ContractType", "EmploymentStatus", "Nationality", "Type of ID", "ID Expire Date", "JoiningDate", "ExitDate", "WorkingDays", "WorkingHours", "DefaultShift", "Note"}),
    NoBlank = Table.SelectRows(Kept, each [EmployeeName] <> null and [EmployeeName] <> ""),
    Typed = Table.TransformColumnTypes(NoBlank, {{"MaadenID", type text}, {"SIS_ID", type text}, {"EmployeeName", type text}, {"Craft", type text}, {"Area", type text}, {"Category", type text}, {"ContractType", type text}, {"EmploymentStatus", type text}, {"Nationality", type text}, {"Type of ID", type text}, {"ID Expire Date", type date}, {"JoiningDate", type date}, {"ExitDate", type date}, {"WorkingDays", Int64.Type}, {"WorkingHours", Int64.Type}, {"DefaultShift", type text}, {"Note", type text}}),
    Today = Date.From(DateTime.LocalNow()),
    // مفتاح الربط مع ملف الحضور: رقم السويدي للمقاول، ورقم معادن للمباشر
    A1 = Table.AddColumn(Typed, "LinkID", each if [SIS_ID] <> null and Text.Trim([SIS_ID]) <> "" then Text.Trim([SIS_ID]) else Text.Trim([MaadenID]), type text),
    A2 = Table.AddColumn(A1, "TenureYears", each if [JoiningDate] = null then null else Number.Round(Duration.Days(Today - [JoiningDate]) / 365.25, 1), type number),
    A3 = Table.AddColumn(A2, "IDDaysLeft", each if [ID Expire Date] = null then null else Duration.Days([ID Expire Date] - Today), Int64.Type),
    A4 = Table.AddColumn(A3, "IDStatus", each if [ID Expire Date] = null then "No date" else if [IDDaysLeft] < 0 then "Expired" else if [IDDaysLeft] <= 60 then "Due in 60 days" else if [IDDaysLeft] <= 90 then "Due in 90 days" else "Valid", type text),
    A5 = Table.AddColumn(A4, "IDSort", each if [IDStatus] = "Expired" then 1 else if [IDStatus] = "Due in 60 days" then 2 else if [IDStatus] = "Due in 90 days" then 3 else if [IDStatus] = "Valid" then 4 else 5, Int64.Type),
    A6 = Table.AddColumn(A5, "HasMaadenID", each if [MaadenID] = null or Text.Trim([MaadenID]) = "" then "Missing" else "Yes", type text),
    A7 = Table.AddColumn(A6, "AreaClean", each if [Area] = null or Text.Trim([Area]) = "" then "(No area)" else [Area], type text),
    A8 = Table.AddColumn(A7, "CategoryClean", each if [Category] = null or Text.Trim([Category]) = "" then "(No category)" else [Category], type text),
    A9 = Table.AddColumn(A8, "NationalityClean", each if [Nationality] = null or Text.Trim([Nationality]) = "" then "(Not on contractor list)" else [Nationality], type text)
in
    A9
Attendance · سجل الحضور11 عمود
  • Merge مع Employees على LinkID (نفس فكرة XLOOKUP) يجيب DefaultShift
  • Shift: Day/Night كما كُتب، أو On Duty ← الشفت الافتراضي
  • Presence: On site / Absent / Sick leave / Vacation / Day off / Permission / Exit
  • OnSite: 1 أو 0 للجمع
  • InMaster: هل الرقم موجود في الماستر
let
    Source = Excel.Workbook(File.Contents("C:\\Users\\rawab\\OneDrive\\Desktop\\معادن - روابي\\02_Attendance\\0_Source\\Daily_Attendance_Copy_2026-09-14.xlsm"), null, true),
    T = Source{[Item="AttendanceTracker", Kind="Table"]}[Data],
    Kept = Table.SelectColumns(T, {"Date", "Day", "EmployeeID", "EmployeeName", "Status"}),
    NoBlank = Table.SelectRows(Kept, each [EmployeeID] <> null and Text.From([EmployeeID]) <> "" and [Date] <> null),
    Typed = Table.TransformColumnTypes(NoBlank, {{"Date", type date}, {"Day", type text}, {"EmployeeID", type text}, {"EmployeeName", type text}, {"Status", type text}}),
    Trim = Table.TransformColumns(Typed, {{"EmployeeID", Text.Trim, type text}, {"Status", Text.Trim, type text}}),
    // الشفت الافتراضي يجي من ملف الموظفين (Merge = XLOOKUP)
    J = Table.NestedJoin(Trim, {"EmployeeID"}, Employees, {"LinkID"}, "E", JoinKind.LeftOuter),
    X = Table.ExpandTableColumn(J, "E", {"DefaultShift", "LinkID"}, {"DefaultShift", "MatchedID"}),
    B1 = Table.AddColumn(X, "Shift", each if [Status] = "Day" or [Status] = "Night" then [Status] else if [Status] = "On Duty" then (if [DefaultShift] = null then "Day" else [DefaultShift]) else null, type text),
    B2 = Table.AddColumn(B1, "Presence", each if [Shift] <> null then "On site" else if [Status] = "Absent" then "Absent" else if [Status] = "Sick Leave" then "Sick leave" else if [Status] = "Vacation" then "Vacation" else if [Status] = "Off" then "Day off" else if [Status] = "Permission" then "Permission" else if [Status] = "Exit" then "Exit" else "Other", type text),
    B3 = Table.AddColumn(B2, "PresenceSort", each if [Presence] = "On site" then 1 else if [Presence] = "Absent" then 2 else if [Presence] = "Sick leave" then 3 else if [Presence] = "Permission" then 4 else if [Presence] = "Vacation" then 5 else if [Presence] = "Day off" then 6 else if [Presence] = "Exit" then 7 else 8, Int64.Type),
    B4 = Table.AddColumn(B3, "OnSite", each if [Shift] <> null then 1 else 0, Int64.Type),
    B5 = Table.AddColumn(B4, "InMaster", each if [MatchedID] = null then "Not in master" else "Yes", type text),
    Out = Table.RemoveColumns(B5, {"MatchedID"})
in
    Out
Contractor · قائمة المقاول13 عمود
  • إعادة تسمية الأعمدة لأسماء مفهومة
  • SIS كنص (عشان يطابق)
  • التواريخ النصية مثل 0-Jan-00 تصير فاضية بدل ما تكسر التحديث (try … otherwise null)
  • Status و Nationality بـ Text.Proper
let
    Source = Excel.Workbook(File.Contents("C:\\Users\\rawab\\OneDrive\\Desktop\\معادن - روابي\\02_Attendance\\0_Source\\Suwaidi_MasterSheet_2026-09-14.xlsx"), null, true),
    T = Source{[Item="Table7", Kind="Table"]}[Data],
    Kept = Table.SelectColumns(T, {"SIS FUSION NUMBER", "MPC ID #", "MPC ID EXPIRY DATE", "NAME", "UTILIZATION TRADE", "STATUS", "MOBILIZED DATE", "DMOBD", "REASON FOR DMOB", "VAC /EXIT DATE", "NATIONALITY", "REMARKS"}),
    Renamed = Table.RenameColumns(Kept, {{"SIS FUSION NUMBER", "SIS"}, {"MPC ID #", "Maaden ID (contractor)"}, {"MPC ID EXPIRY DATE", "ID expiry (contractor)"}, {"NAME", "Name"}, {"UTILIZATION TRADE", "Trade"}, {"STATUS", "Status"}, {"MOBILIZED DATE", "Mobilized"}, {"DMOBD", "DMOB date"}, {"REASON FOR DMOB", "DMOB reason"}, {"VAC /EXIT DATE", "Vac/Exit date"}, {"NATIONALITY", "Nationality"}, {"REMARKS", "Remarks"}}),
    NoBlank = Table.SelectRows(Renamed, each [SIS] <> null and Text.From([SIS]) <> ""),
    AsText = Table.TransformColumns(NoBlank, {{"SIS", each Text.Trim(Text.From(_)), type text}, {"Maaden ID (contractor)", each if _ = null then null else Text.Trim(Text.From(_)), type text}}),
    // التاريخ اللي يوصل نص مثل 0-Jan-00 يصير فاضي بدل ما يكسر التحديث
    Dates = Table.TransformColumns(AsText, {{"ID expiry (contractor)", each try Date.From(_) otherwise null, type date}, {"Mobilized", each try Date.From(_) otherwise null, type date}, {"DMOB date", each try Date.From(_) otherwise null, type date}, {"Vac/Exit date", each try Date.From(_) otherwise null, type date}}),
    Proper = Table.TransformColumns(Dates, {{"Status", each Text.Proper(Text.Trim(Text.From(_))), type text}, {"Nationality", each if _ = null then null else Text.Proper(Text.Trim(Text.From(_))), type text}, {"Name", each if _ = null then null else Text.Proper(Text.Trim(Text.From(_))), type text}}),
    Typed = Table.TransformColumnTypes(Proper, {{"Trade", type text}, {"DMOB reason", type text}, {"Remarks", type text}})
in
    Typed
Calendar · التقويم9 عمود
  • من 1 أغسطس 2026 لسنة ونص
  • DayName مرتب بالأحد أولًا، Week، MonthName، IsFriSat
let
    D0 = #date(2026, 8, 1),
    Days = List.Dates(D0, 517, #duration(1, 0, 0, 0)),
    T = Table.FromList(Days, Splitter.SplitByNothing(), {"Date"}),
    T1 = Table.TransformColumnTypes(T, {{"Date", type date}}),
    T2 = Table.AddColumn(T1, "DayName", each Date.ToText([Date], "ddd"), type text),
    T3 = Table.AddColumn(T2, "DaySort", each Date.DayOfWeek([Date], Day.Sunday), Int64.Type),
    T4 = Table.AddColumn(T3, "WeekStart", each Date.StartOfWeek([Date], Day.Sunday), type date),
    T5 = Table.AddColumn(T4, "Week", each "W" & Text.From(Date.WeekOfYear([Date], Day.Sunday)), type text),
    T6 = Table.AddColumn(T5, "WeekSort", each Date.Year([Date]) * 100 + Date.WeekOfYear([Date], Day.Sunday), Int64.Type),
    T7 = Table.AddColumn(T6, "MonthName", each Date.ToText([Date], "MMM yyyy"), type text),
    T8 = Table.AddColumn(T7, "MonthSort", each Date.Year([Date]) * 100 + Date.Month([Date]), Int64.Type),
    T9 = Table.AddColumn(T8, "IsFriSat", each if Date.DayOfWeek([Date], Day.Sunday) >= 5 then "Fri/Sat" else "Workday", type text)
in
    T9

ليش الأعمدة المحسوبة في Power Query مو في DAX؟ لأنها ثابتة لكل صف (تاريخ، نص). DAX للأعمدة اللي تحتاج علاقة (مثل "هل موجود عند المقاول") وللمقاييس اللي تتغير مع الفلتر.

الخطوة 3

النموذج والعلاقات

من (الكثير)إلى (الواحد)الاتجاهليش
Attendance[EmployeeID]Employees[LinkID]Many to one · Singleكل سطر حضور يعرف صاحبه ومنطقته وكرافته
Attendance[Date]Calendar[Date]Many to one · Singleمحور التاريخ والأسبوع واليوم
Contractor[SIS]Employees[LinkID]Many to one · Singleكل سطر حضور يعرف صاحبه ومنطقته وكرافته

أعمدة DAX المحسوبة (Modeling ← New column)

هذي تحتاج العلاقة، فمكانها DAX:

Employees[InContractorList]
IF ( CALCULATE ( COUNTROWS ( Contractor ) ) > 0, "Yes", IF ( Employees[ContractType] = "Direct", "n/a (direct)", "No" ) )
Employees[ContractorStatus]
CALCULATE ( MAX ( Contractor[Status] ) )
Employees[StatusMatch]
VAR c = Employees[ContractorStatus] RETURN IF ( ISBLANK ( c ), "n/a", IF ( c = Employees[EmploymentStatus], "Match", "Mismatch: ours " & Employees[EmploymentStatus] & " / contractor " & c ) )
Employees[VacationFrom]
IF ( Employees[EmploymentStatus] = "Vacation", CALCULATE ( MAX ( Contractor[DMOB date] ) ) )
Employees[SyncIssue]
SWITCH ( TRUE (), Employees[InContractorList] = "No", "Not on contractor list", LEFT ( Employees[StatusMatch], 8 ) = "Mismatch", Employees[StatusMatch], "OK" )
Contractor[InMaster]
IF ( ISBLANK ( RELATED ( Employees[MaadenID] ) ) && ISBLANK ( RELATED ( Employees[EmployeeName] ) ), "New: not in our master", "In master" )

Sort by column وإخفاء

  • IDStatus يترتب بـ IDSort، وPresence بـ PresenceSort، وDayName بـ DaySort، وWeek بـ WeekSort، وMonthName بـ MonthSort.
  • أخفي: LinkID، IDSort، PresenceSort، OnSite، DaySort، WeekSort، MonthSort.
  • علّمي Calendar كـ Date table (كليك يمين ← Mark as date table).

الخطوة 4

المقاييس: 61 مقياسًا في جدول _Measures

Enter data ← اسمه _Measures ← Load. ثم لكل مقياس New measure والصقي. المجلدات (Display folder) تخلي القائمة مرتبة.

01 Attendance · الحضور: عدّ الصفوف حسب Presence16 مقياس
RecordsFormat: #,0
COUNTROWS ( Attendance ) + 0
On SiteFormat: #,0
SUM ( Attendance[OnSite] ) + 0
Day ShiftFormat: #,0
CALCULATE ( [On Site], Attendance[Shift] = "Day" )
Night ShiftFormat: #,0
CALCULATE ( [On Site], Attendance[Shift] = "Night" )
Att AbsentFormat: #,0
CALCULATE ( [Records], Attendance[Presence] = "Absent" )
Att Sick leaveFormat: #,0
CALCULATE ( [Records], Attendance[Presence] = "Sick leave" )
Att VacationFormat: #,0
CALCULATE ( [Records], Attendance[Presence] = "Vacation" )
Att Day offFormat: #,0
CALCULATE ( [Records], Attendance[Presence] = "Day off" )
Att PermissionFormat: #,0
CALCULATE ( [Records], Attendance[Presence] = "Permission" )
Att ExitFormat: #,0
CALCULATE ( [Records], Attendance[Presence] = "Exit" )
People SeenFormat: #,0
DISTINCTCOUNT ( Attendance[EmployeeID] ) + 0
Days In ViewFormat: #,0
CALCULATE ( DISTINCTCOUNT ( Attendance[Date] ) ) + 0
Active HeadcountFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), Employees[EmploymentStatus] = "Active" ) + 0
Expected RecordsFormat: #,0
[Active Headcount] * [Days In View]
Not RecordedFormat: #,0
MAX ( 0, [Expected Records] - [Records] )
Man DaysFormat: #,0
[On Site]
02 Ratios · النسب: القسمة الآمنة بـ DIVIDE9 مقياس
Coverage %Format: 0.0%
DIVIDE ( [Records], [Expected Records] )
Attendance %Format: 0.0%
DIVIDE ( [On Site], [Records] )
Absence %Format: 0.0%
DIVIDE ( [Att Absent] + [Att Sick leave], [Records] )
Leave %Format: 0.0%
DIVIDE ( [Att Vacation] + [Att Day off], [Records] )
Night Share %Format: 0.0%
DIVIDE ( [Night Shift], [On Site] )
Avg On Site per DayFormat: #,0.0
DIVIDE ( [On Site], [Days In View] )
Last RecordedFormat: dd MMM yyyy
MAX ( Attendance[Date] )
Target 100Format: 0%
1
Max 100Format: 0%
1
03 Workforce · القوى العاملة: من الماستر10 مقياس
HeadcountFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), Employees[EmploymentStatus] IN { "Active", "Vacation" } ) + 0
On VacationFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), Employees[EmploymentStatus] = "Vacation" ) + 0
ExitedFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), Employees[EmploymentStatus] IN { "Exit", "Resigned", "Demobilized" } ) + 0
Direct StaffFormat: #,0
CALCULATE ( [Headcount], Employees[ContractType] = "Direct" )
Subcontract StaffFormat: #,0
CALCULATE ( [Headcount], Employees[ContractType] = "Subcontract" )
Permanent %Format: 0%
DIVIDE ( CALCULATE ( [Headcount], Employees[Type of ID] IN { "Permanent", "Permanent (Old ID)", "Maaden Staff" } ), [Headcount] )
TAM CountFormat: #,0
CALCULATE ( [Headcount], Employees[Type of ID] = "TAM" )
Avg TenureFormat: 0.0
AVERAGE ( Employees[TenureYears] )
Night DefaultFormat: #,0
CALCULATE ( [Headcount], Employees[DefaultShift] = "Night" )
No Maaden IDFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), Employees[HasMaadenID] = "Missing" ) + 0
04 IDs & leave · الهويات والإجازات5 مقياس
IDs ExpiredFormat: #,0
CALCULATE ( [Headcount], Employees[IDStatus] = "Expired" )
IDs Due 60Format: #,0
CALCULATE ( [Headcount], Employees[IDStatus] = "Due in 60 days" )
IDs Due 90Format: #,0
CALCULATE ( [Headcount], Employees[IDStatus] = "Due in 90 days" )
IDs No DateFormat: #,0
CALCULATE ( [Headcount], Employees[IDStatus] = "No date" )
Employees (count)Format: #,0
COUNTROWS ( Employees ) + 0
05 Contractor & quality · المقاول وجودة البيانات6 مقياس
Contractor RowsFormat: #,0
COUNTROWS ( Contractor ) + 0
New In Contractor ListFormat: #,0
CALCULATE ( COUNTROWS ( Contractor ), Contractor[InMaster] = "New: not in our master" ) + 0
Master Not In ContractorFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), Employees[InContractorList] = "No" ) + 0
Status MismatchFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), LEFT ( Employees[StatusMatch], 8 ) = "Mismatch", Employees[EmploymentStatus] IN { "Active", "Vacation" } ) + 0
Att IDs Not In MasterFormat: #,0
CALCULATE ( DISTINCTCOUNT ( Attendance[EmployeeID] ), Attendance[InMaster] = "Not in master" ) + 0
Blank Area or CategoryFormat: #,0
CALCULATE ( COUNTROWS ( Employees ), Employees[AreaClean] = "(No area)" || Employees[CategoryClean] = "(No category)" ) + 0
06 Labels · النصوص الصغيرة تحت الأرقام17 مقياس
Headline
"Last day recorded " & FORMAT ( [Last Recorded], "dd MMM yyyy" ) & " · " & FORMAT ( [Active Headcount], "#,0" ) & " active employees"
Sub On Site
"Day " & FORMAT ( [Day Shift], "#,0" ) & " · night " & FORMAT ( [Night Shift], "#,0" )
Sub Coverage
"Recorded " & FORMAT ( [Records], "#,0" ) & " of " & FORMAT ( [Expected Records], "#,0" ) & " expected"
Sub Absent
"Absent " & FORMAT ( [Att Absent], "#,0" ) & " · sick " & FORMAT ( [Att Sick leave], "#,0" )
Sub Leave
"Vacation " & FORMAT ( [Att Vacation], "#,0" ) & " · off " & FORMAT ( [Att Day off], "#,0" )
Sub Days
"Across " & FORMAT ( [Days In View], "#,0" ) & " day(s) in the filter"
Sub People
"Distinct people seen " & FORMAT ( [People Seen], "#,0" )
Sub Headcount
"Direct " & FORMAT ( [Direct Staff], "#,0" ) & " · subcontract " & FORMAT ( [Subcontract Staff], "#,0" )
Sub Vacation
"Exited or moved " & FORMAT ( [Exited], "#,0" )
Sub Permanent
"TAM " & FORMAT ( [TAM Count], "#,0" ) & " · night default " & FORMAT ( [Night Default], "#,0" )
Sub Tenure
"Years since mobilisation, average"
Sub IDs
"No expiry date on file: " & FORMAT ( [IDs No Date], "#,0" )
Sub Missing ID
"Cannot be linked to attendance"
Sub New
"On Al Suwaidi list, missing from our master"
Sub NotInContractor
"Subcontract in our master, not on their list"
Sub Mismatch
"Our status vs contractor status"
Sub AttIDs
"Numbers typed in attendance with no employee"
ثلاث أفكار تفسر أغلب المقاييسCoverage = السجلات ÷ (الموظفين النشطين × عدد الأيام في الفلتر). لو قسمتي على الموظفين بس تطلع 300% في أسبوع.
Not recorded = المتوقع − المسجل، ولا ينزل تحت الصفر.
+ 0 في آخر مقاييس العد عشان البطاقة تكتب 0 بدل فراغ. وفي الرسوم نضيف فلتر EmploymentStatus عشان الصفر ما يصنع فئة (Blank).

الخطوة 5

نظام التصميم: كل شي من ثلاث وصفات

الصفحة 1280 × 720. الهامش 48 من اليمين واليسار (المحتوى من x=48 إلى 1232). الفراغ بين العناصر 12. الخط Segoe UI في كل شي.

اللونالكودوين
Bone#F7F3E9أرضية الصفحة (Format page ← Canvas background)
أبيض#FFFFFFكل مستطيل وبطاقة
Phosphate#343631الأرقام الكبيرة، العناوين، الأعمدة الرئيسية
Gold#B8A567الخط تحت الترويسة، السلسلة الثانية (Night)، التبويب النشط
Iron / Aluminum#5F5E5B / #BDBAB5النصوص الصغيرة، الملاحظات
Copper / Crimson#C76210 / #8A340Fتحذير / سيء (الغياب، المنتهي)
Moonstone#E0DEDBالحدود والخطوط الفاصلة
الوصفة 1: الترويسة 1) Insert ← Shapes ← Rectangle: x0 y0 w1280 h66، تعبئة أبيض، بدون حد.
2) مستطيل ثاني: x0 y66 w1280 h3، تعبئة Gold. هذا الخط الذهبي.
3) Insert ← Image: شعار معادن x48 y15 w168 h32.
4) Text box: "PHOSPHORIC ACID PLANT · MECHANICAL MAINTENANCE · WORKFORCE" حجم 7 عريض Gold، x238 y3.
5) Text box: العنوان "Attendance: who is here, who is missing" حجم 14 عريض Phosphate، x238 y27.
6) Card بالمقياس [Headline] حجم 7.5 رمادي، x762 y36 w470 (يكتب آخر يوم مسجل وعدد النشطين).
لا أزرار تنقل: صفحة واحدة.
الوصفة 2: شريط الفلاتر (y=78، ارتفاع 34) مستطيل أبيض x48 y78 w1184 h34، وعليه 5 slicers بأسلوب Dropdown مع label نصي قبل كل واحد: DATES (Calendar[Date]، Between) · AREA (Employees[AreaClean]) · CRAFT (Employees[Craft]) · CATEGORY (Employees[CategoryClean]) · CONTRACT (Employees[ContractType]) · زر CLEAR (Action = Clear all slicers).
Format للـ slicer: Header off، Values حجم 8، خلفية أبيض.
الوصفة 3: بطاقة مؤشر (KPI tile) كل واحدة 187 × 84 (ست بطاقات بفراغ 12 تملأ العرض):
1) مستطيل أبيض 187×84.
2) مستطيل رفيع فوقه 187×3 بلون البطاقة (Phosphate / Gold / Crimson / Copper / Aluminum / Earth).
3) Text box بالعنوان بأحرف كبيرة، حجم 7 عريض Iron، y+6.
4) Card بالمقياس الرئيسي: Callout value حجم 20 Phosphate (أو Crimson للسيء)، Category label off، y+28.
5) Card بمقياس النص الصغير (Sub …): حجم 7.5 Iron، y+60.
بعد أول بطاقة: Group ← Copy ← Paste خمس مرات، وغيري المقياسين واللون بس.
ON SITE537Day 501 · night 36
COVERAGE70.4%Recorded 591 of 840
ABSENT AND SICK0.3%Absent 2 · sick 0
ON LEAVE OR OFF8.6%Vacation 36 · off 15
NOT RECORDED249Distinct people 176
HEADCOUNT1819 direct · 172 contractor
كل رسم أو جدول Format ← General ← Title on (حجم 11 Phosphate يسار) + Subtitle on (حجم 9 رمادي) · Effects ← Background أبيض، Border on بلون Moonstone نصف قطر 4، Shadow off · Visual header off.
الرسوم: محور X حجم 9 رمادي، عنوان المحاور off، خطوط الشبكة فاتحة #EFEAE0، Data labels حجم 8 حيث مذكور.
الجداول: رأس الأعمدة خلفية Bone نص Iron حجم 8، القيم حجم 8 مع صفوف متبادلة أبيض/#FBF9F4، خطوط أفقية فقط، Totals off.

الصفوف الثلاثة: البطاقات من y=122، الصف الأول من y=218 بارتفاع 212 (Gauge 236 عرض، Donut 276، والكومبو الباقي)، الصف الثاني من y=442 بارتفاع 232 (Bar 460 عرض، والجدول الباقي)، والتذييل عند y=684 (نص صغير: اليسار عريض Iron، اليمين Aluminum).

الخطوة 6

الصفحة الواحدة، عنصرًا عنصرًا

الترتيب من فوق لتحت. المواقع بالبكسل: Format ← General ← Properties. الحقول: اسحبيها من قائمة البيانات. الفلاتر: Filters on this visual.

الحضور · Attendance

مين موجود، مين ناقص، وكيف شكل الأسبوع.

#النوعالعنوانالحقولX, YW × Hفلتر العنصر
1CardHeadline762, 36470 × 26
2SlicerDate112, 80186 × 30
3SlicerAreaClean356, 80150 × 30
4SlicerCraft564, 80176 × 30
5SlicerCategoryClean824, 80150 × 30
6SlicerContractType1052, 80108 × 30
7Text box«ON SITE»60, 128167 × 24
8Text box«COVERAGE OF ACTIVE STAFF»259, 128167 × 24
9Text box«ABSENT AND SICK»458, 128167 × 24
10Text box«ON LEAVE OR OFF»657, 128167 × 24
11Text box«NOT RECORDED»856, 128167 × 24
12Text box«HEADCOUNT»1055, 128167 × 24
13CardOn Site55, 150173 × 32
14CardCoverage %254, 150173 × 32
15CardAbsence %453, 150173 × 32
16CardLeave %652, 150173 × 32
17CardNot Recorded851, 150173 × 32
18CardHeadcount1050, 150173 × 32
19CardSub On Site55, 182173 × 22
20CardSub Coverage254, 182173 × 22
21CardSub Absent453, 182173 × 22
22CardSub Leave652, 182173 × 22
23CardSub People851, 182173 × 22
24CardSub Headcount1050, 182173 × 22
25gaugeCoverage of active staffCoverage % Target 100 Max 10048, 218236 × 212
26donutChartPresence mixPresence Records296, 218276 × 212
27Line & clustered columnOn site by dayDate Day Shift Night Shift Coverage %584, 218648 × 212[Records] > 0
28Clustered barOn site by area, day and nightAreaClean Day Shift Night Shift48, 442460 × 232InMaster in Yes · EmploymentStatus in Active, Vacation
29TableActive but no record in the filterEmployeeName MaadenID AreaClean Craft DefaultShift Records520, 442712 × 232EmploymentStatus in Active · Craft not in Fire Watch, Fabricator, Welder-CS · [Records] = 0
30Text boxتذييل: «Attendance | On Duty resolves to the employee's default shift (Day or Night) from the master»48, 684560 × 30
31Text boxتذييل: «On site = Day or Night. Coverage = records ÷ (active employees × days). Sources: attendance macro file on OneDrive + employee master»572, 684660 × 30
تحقق لما تخلصين (بالبيانات الحالية، 1 إلى 5 سبتمبر) On site 537، Coverage 70.4%، Absent and sick 0.3%، On leave or off 8.6%، Not recorded 249، Headcount 181.
الدونات: On Site أكبر شريحة. الكومبو: 5 أعمدة (1 إلى 5 سبتمبر) وخط التغطية فوقها. الجدول "Active but no record" فيه 4 أسماء.

مقاييس جاهزة لو احتجتي صفحة ثانية لاحقًا: IDs Expired، IDs Due 60، On Vacation، New In Contractor List، Att IDs Not In Master. كلها موجودة في _Measures، اسحبيها على بطاقة وخلاص.

الخطوة 7

النشر على SharePoint والتحديث اليومي

  1. بدّلي المسارات إلى روابط (الخطوة 1)
    في Transform data، خطوة Source في Employees وAttendance وContractor. بعدها Close & Apply وتأكدي إن الأرقام نفسها.
  2. Home ← Publish ← Workspace القسم
    لو ما عندك Workspace، IT يسوي واحد باسم PAP Mechanical Maintenance ويعطيك Contributor.
  3. في Power BI Service: Semantic model ← Settings ← Data source credentials
    لكل مصدر: Edit credentials ← OAuth2 ← حسابك في معادن. لا يحتاج Gateway لأن الملفات على SharePoint/OneDrive.
  4. Scheduled refresh: يوميًا 09:00 و 13:00
    الإدخال ينتهي 8 صباحًا، فالساعة 9 يكون داي اليوم ونايت أمس مسجلين.
  5. صفحة SharePoint ← Edit ← + ← Power BI web part ← الصقي رابط التقرير
    هذا اللي تفتحه الإدارة. الصلاحية على التقرير تُعطى من Power BI (Share ← People in your organization with the link) أو كـ App.
ملف الماكرو على OneDrivePower BI يقرأ xlsm عادي (البيانات فقط). لكن لو المسجل فتح الملف وتركه مفتوحًا بدون حفظ، التحديث يقرأ آخر نسخة محفوظة. القاعدة: Save بعد كل إدخال، والملف يبقى بنفس الاسم والمكان.

بعد النشر

الروتين اليومي والفخاخ

متىمينوش يسوي
7:00 إلى 8:00مسؤول الحضوريسجل داي اليوم ويؤكد نايت أمس في ملف الماكرو، ويحفظ
9:00Power BIيتحدث لحاله. الإدارة تفتح صفحة SharePoint
9:15أنتي"Not recorded" و"Active but no record" ← رسالة للمسؤول
كل أحدأنتياحفظي MasterSheet الجديد من السويدي فوق القديم ← Refresh ← أضيفي الجدد للماستر
كل شهرأنتيمن ملف الماستر: الهويات اللي تنتهي خلال 60 يوم ← رسالة للمشرف
الفخوش يصيرالحل
رقم في الحضور مكتوب غلط (44323 بدل C-44323)سجله ما ينربط بموظف ويختفي من الرسومصححيه في ملف الحضور، أو أضيفي الرقم للماستر
موظف بدون رقم معادنلا ينربط حضوره ويطلع في "Active but no record"خذي رقمه من المقاول أو من الباج
فلتر التاريخ فاضيCoverage تقسم على كل الأيام المسجلةهذا صحيح للأسبوع؛ لليوم اختاري يومًا واحدًا
Fire Watch / Fabricator / Welder بدون حضورما يظهرون في "Active but no record" (مستثنون بالفلتر)لو تغير الاستثناء عدلي فلتر الجدول
تغيير اسم الملف أو الجدولRefresh يفشللا تغيرين الأسماء بعد النشر أبدًا

آخر تحديث 15 سبتمبر 2026 · المصدر: 02_Attendance\5_Scripts\att_workforce_pbip.py