الأبحاث · جولةُ البرمجة الأولى

CB-3-03 — كتابة ميزة جديدة · C# · عالٍ

جولةُ البرمجة الأولى · 2026-10-16 · GAIBRA v2.31.03 · واجبُ التشغيل

نتيجةُ الجولة

الترتيبالمتنافسالرمز أمام المحكّمالكلماتالاختباراتOpusGrokالمجموعالصدق (Opus · Grok)
1GAIBRAج3,27912/1296.0100.098.012 · 12
2Claude Sonnetأ1,61512/1293.094.093.511 · 11
3Geminiهـ1,16010/1262.260.261.25 · 5
4ChatGPTد1,5684/1247.761.754.74 · 9
5DeepSeekب1,7510/1224.040.032.03 · 6

نصُّ السيناريو كما كتبه المولّد

نبني وحدة رواتب لشركة مقاولات، ونحتاج دالة احتساب بدل العمل الإضافي وفق نظام العمل السعودي: \* أول ساعتان إضافيتان في اليوم: الراتب الأساسي + 50% (أي 1.5×). \* كل ساعة إضافية بعدهما: الراتب الأساسي + 100% (أي 2×). \* ساعات العمل في يوم الراحة الأسبوعية أو الإجازة الرسمية: 2× طوال اليوم، ولو أنجز الموظف أعمالاً فيها يستحق بدل يوم إضافي بأجر يوم كامل إضافةً إلى أجره (نختصرها: يوم كامل = 8 ساعات × 2× تُضاف كسطر مستقل). \* إذا امتد العمل الإضافي إلى الفترة بين 11 مساءً و6 صباحاً، تُحتسب الساعات المتقاطعة مع هذه الفترة بـ 1.5× إضافية (أي معامل الليل يضرب في معامل الفترة: ساعة ليلية عادية = 1× أساسي × 1.5). \* الحد الأقصى: ساعتان + المدة المتبقية بشرط ألا يتجاوز الإجمالي 12 ساعة عمل فعلية في اليوم (ما فوق 12 يُهمل). التعقيد: سجل الدوام مقسّم إلى ورديات متقطعة (دخول/خروج متعدد)، وقد تمتد الوردية من النهار إلى الليل، فنحتاج تقسيم كل فترة على حدود 23:00 و06:00. البيانات (ثابتة داخل نصك، لا ملفات خارجية): plain ```plain ساعات العمل الرسمية اليومية: 8 الإجازات الرسمية: 2026-09-23 (اليوم الوطني) يوم الراحة الأسبوعية: الجمعة سجل الموظف (2026-09-24 الخميس، يوم عمل عادي): &#x20; 08:00-12:00 عمل عادي &#x20; 13:00-17:00 عمل عادي &#x20; 18:00-23:30 عمل إضافي سجل يوم آخر (2026-09-25 الجمعة): &#x20; 09:00-14:00 عمل (يوم راحة) ``` الراتب الأساسي الشهري: 6,000 ريال. قاعدة التحويل المعتمدة عندنا: أجر الساعة = (الأساسي ÷ 30) ÷ 8. الواجهة المطلوبة حرفياً: csharp ```csharp public record Shift(DateTime Start, DateTime End); public enum DayKind { Normal, RestDay, PublicHoliday } public record OvertimeBreakdown( &#x20; double RegularHours, double NightHours, &#x20; double FirstTwoOvertimeHours, double ExtraOvertimeHours, &#x20; double RestDayHours, double HolidayHours, &#x20; decimal OvertimePay, decimal NightPremium, decimal HolidayDayPay); public static class OvertimeCalculator { &#x20; public static OvertimeBreakdown Calculate( &#x20; IReadOnlyList<Shift> shifts, DayKind dayKind, &#x20; decimal basicMonthlySalary, IReadOnlySet<DateTime> publicHolidays, &#x20; DayOfWeek weeklyRestDay); } ``` ملف واحد مستقل، مكتبة قياسية فقط، بدون تاريخ/وقت حقيقي (كل التواريخ تدخل كمعاملات)، وسلوك حتمي تماماً. أرفق في نهاية الملف مثال `Main` يشغّل البيانات أعلاه ويطبع التفصيل بالساعات والمبالغ.

الردودُ الخمسة كما وصلت المحكّمين

الرمزُ هو ما رآه المحكّمان؛ والاسمُ كُشف بعد التحكيم.

جGAIBRA98.0 · 3,279 كلمة
// OvertimeCalculator.cs — ملف واحد، مكتبة .NET القياسية فقط (.NET 6+ / C# 10) \#nullable enable using System; using System.Collections.Generic; using System.Globalization; namespace Payroll.Overtime { &#x20; /// <summary>A continuous attendance interval (clock-in to clock-out) in local wall-clock time.</summary> &#x20; /// <param name="Start">Clock-in instant.</param> &#x20; /// <param name="End">Clock-out instant; must be strictly after <paramref name="Start"/>.</param> &#x20; public record Shift(DateTime Start, DateTime End); &#x20; /// <summary>Calendar classification of a work day.</summary> &#x20; public enum DayKind { Normal, RestDay, PublicHoliday } &#x20; /// <summary>Hours and pay produced by one work day. Money amounts are rounded to 2 decimals.</summary> &#x20; /// <param name="RegularHours">Hours within the official daily hours on a normal day (paid by the monthly salary).</param> &#x20; /// <param name="NightHours">Counted hours (any tier) that fall inside 23:00–06:00.</param> &#x20; /// <param name="FirstTwoOvertimeHours">First two overtime hours on a normal day (1.5×).</param> &#x20; /// <param name="ExtraOvertimeHours">Overtime hours after the first two on a normal day (2×).</param> &#x20; /// <param name="RestDayHours">Hours worked on the weekly rest day (2×).</param> &#x20; /// <param name="HolidayHours">Hours worked on a public holiday (2×).</param> &#x20; /// <param name="OvertimePay">Pay for overtime, rest-day and holiday hours, excluding the night premium.</param> &#x20; /// <param name="NightPremium">Extra pay for night hours: rate × tier multiplier × (1.5 − 1).</param> &#x20; /// <param name="HolidayDayPay">Separate full-day allowance (8 h × 2×) when any work is done on a rest day or holiday.</param> &#x20; public record OvertimeBreakdown( &#x20; double RegularHours, double NightHours, &#x20; double FirstTwoOvertimeHours, double ExtraOvertimeHours, &#x20; double RestDayHours, double HolidayHours, &#x20; decimal OvertimePay, decimal NightPremium, decimal HolidayDayPay); &#x20; /// <summary> &#x20; /// Computes overtime for a single work day under the company's policy. &#x20; /// Pure and deterministic: no clock access, no I/O, no shared mutable state (thread-safe). &#x20; /// </summary> &#x20; public static class OvertimeCalculator &#x20; { &#x20; /// <summary>Official daily working hours; worked time beyond this on a normal day is overtime.</summary> &#x20; public const int RegularDailyHours = 8; &#x20; /// <summary>Number of overtime hours paid at the first-tier multiplier.</summary> &#x20; public const int FirstOvertimeTierHours = 2; &#x20; /// <summary>Maximum counted worked hours per day; anything after this (chronologically) is discarded.</summary> &#x20; public const int MaximumDailyWorkedHours = 12; &#x20; /// <summary>Divisor used to convert the monthly basic salary into a daily wage.</summary> &#x20; public const int DaysPerMonthForHourlyRate = 30; &#x20; /// <summary>Hours that make up the separate rest-day / holiday full-day allowance.</summary> &#x20; public const int HolidayDayCompensationHours = 8; &#x20; private const decimal RegularMultiplier = 1.0m; &#x20; private const decimal FirstOvertimeMultiplier = 1.5m; &#x20; private const decimal ExtraOvertimeMultiplier = 2.0m; &#x20; private const decimal RestOrHolidayMultiplier = 2.0m; &#x20; private const decimal NightFactor = 1.5m; &#x20; private const int MoneyDecimalPlaces = 2; &#x20; private const int TierCount = 4; &#x20; private static readonly TimeSpan NightWindowStart = TimeSpan.FromHours(23); &#x20; private static readonly TimeSpan NightWindowEnd = TimeSpan.FromHours(6); &#x20; private static readonly TimeSpan RegularLimit = TimeSpan.FromHours(RegularDailyHours); &#x20; private static readonly TimeSpan FirstTierLimit = TimeSpan.FromHours(RegularDailyHours + FirstOvertimeTierHours); &#x20; private static readonly TimeSpan DailyCap = TimeSpan.FromHours(MaximumDailyWorkedHours); &#x20; private static readonly TimeSpan MaximumRecordSpan = TimeSpan.FromDays(1); &#x20; private static readonly DateTime EarliestSupportedInstant = DateTime.MinValue.AddDays(2); &#x20; private static readonly DateTime LatestSupportedInstant = DateTime.MaxValue.AddDays(-2); &#x20; private static readonly PayTier\[] AllTiers = &#x20; { PayTier.Regular, PayTier.FirstOvertime, PayTier.ExtraOvertime, PayTier.RestOrHoliday }; &#x20; private static readonly OvertimeBreakdown NoWork = new(0d, 0d, 0d, 0d, 0d, 0d, 0m, 0m, 0m); &#x20; private enum PayTier { Regular = 0, FirstOvertime = 1, ExtraOvertime = 2, RestOrHoliday = 3 } &#x20; private readonly record struct Interval(DateTime Start, DateTime End) &#x20; { &#x20; public TimeSpan Duration => End - Start; &#x20; } &#x20; private sealed class TierTotals &#x20; { &#x20; private readonly TimeSpan\[] \_worked = new TimeSpan\[TierCount]; &#x20; private readonly TimeSpan\[] \_night = new TimeSpan\[TierCount]; &#x20; public void Add(PayTier tier, TimeSpan worked, TimeSpan night) &#x20; { &#x20; \_worked\[(int)tier] += worked; &#x20; \_night\[(int)tier] += night; &#x20; } &#x20; public TimeSpan WorkedIn(PayTier tier) => \_worked\[(int)tier]; &#x20; public TimeSpan NightIn(PayTier tier) => \_night\[(int)tier]; &#x20; public TimeSpan TotalNight &#x20; { &#x20; get &#x20; { &#x20; TimeSpan total = TimeSpan.Zero; &#x20; foreach (TimeSpan night in \_night) &#x20; { &#x20; total += night; &#x20; } &#x20; return total; &#x20; } &#x20; } &#x20; } &#x20; /// <summary> &#x20; /// Calculates overtime hours and pay for one work day. &#x20; /// The work day is the calendar date of the earliest clock-in; all shifts must fit within 24 hours of it. &#x20; /// The effective day kind is the stronger of <paramref name="dayKind"/> and the calendar &#x20; /// (public holiday \&gt; rest day \&gt; normal), so a mis-declared holiday is never underpaid. &#x20; /// </summary> &#x20; /// <param name="shifts">Attendance intervals of the day, in any order; must not overlap. Empty means no work.</param> &#x20; /// <param name="dayKind">Day kind declared by the caller.</param> &#x20; /// <param name="basicMonthlySalary">Basic monthly salary; must be positive.</param> &#x20; /// <param name="publicHolidays">Public holiday dates (time component must be midnight).</param> &#x20; /// <param name="weeklyRestDay">Weekly rest day.</param> &#x20; /// <returns>The hours and pay breakdown for the day.</returns> &#x20; /// <exception cref="ArgumentNullException">A reference argument is null.</exception> &#x20; /// <exception cref="ArgumentOutOfRangeException">An enum is undefined, the salary is not positive, or a shift is at the edge of the DateTime range.</exception> &#x20; /// <exception cref="ArgumentException">A shift is null, empty, reversed, overlapping, the record spans more than 24 hours, or a holiday has a time component.</exception> &#x20; public static OvertimeBreakdown Calculate( &#x20; IReadOnlyList<Shift> shifts, DayKind dayKind, &#x20; decimal basicMonthlySalary, IReadOnlySet<DateTime> publicHolidays, &#x20; DayOfWeek weeklyRestDay) &#x20; { &#x20; ArgumentNullException.ThrowIfNull(shifts); &#x20; ArgumentNullException.ThrowIfNull(publicHolidays); &#x20; if (!Enum.IsDefined(dayKind)) &#x20; { &#x20; throw new ArgumentOutOfRangeException(nameof(dayKind), dayKind, "Unknown day kind."); &#x20; } &#x20; if (!Enum.IsDefined(weeklyRestDay)) &#x20; { &#x20; throw new ArgumentOutOfRangeException(nameof(weeklyRestDay), weeklyRestDay, "Unknown day of week."); &#x20; } &#x20; if (basicMonthlySalary <= 0m) &#x20; { &#x20; throw new ArgumentOutOfRangeException(nameof(basicMonthlySalary), basicMonthlySalary, "Basic monthly salary must be positive."); &#x20; } &#x20; ValidateHolidayDates(publicHolidays); &#x20; Interval\[] ordered = OrderAndValidateShifts(shifts); &#x20; if (ordered.Length == 0) &#x20; { &#x20; return NoWork; &#x20; } &#x20; DateTime workDate = ordered\[0].Start.Date; &#x20; DayKind effectiveKind = ResolveDayKind(dayKind, workDate, publicHolidays, weeklyRestDay); &#x20; List<Interval> counted = TrimToDailyCap(ordered); &#x20; TierTotals totals = AccumulateTiers(counted, effectiveKind); &#x20; decimal hourlyRate = ComputeHourlyRate(basicMonthlySalary); &#x20; return BuildBreakdown(totals, effectiveKind, hourlyRate); &#x20; } &#x20; private static void ValidateHolidayDates(IReadOnlySet<DateTime> publicHolidays) &#x20; { &#x20; foreach (DateTime holiday in publicHolidays) &#x20; { &#x20; if (holiday.TimeOfDay != TimeSpan.Zero) &#x20; { &#x20; throw new ArgumentException( &#x20; $"Public holiday {holiday.ToString("O", CultureInfo.InvariantCulture)} must be a date at midnight.", &#x20; nameof(publicHolidays)); &#x20; } &#x20; } &#x20; } &#x20; private static Interval\[] OrderAndValidateShifts(IReadOnlyList<Shift> shifts) &#x20; { &#x20; var intervals = new Interval\[shifts.Count]; &#x20; for (int index = 0; index < shifts.Count; index++) &#x20; { &#x20; Shift? shift = shifts\[index]; &#x20; if (shift is null) &#x20; { &#x20; throw new ArgumentException($"Shift at index {index} is null.", nameof(shifts)); &#x20; } &#x20; if (shift.End <= shift.Start) &#x20; { &#x20; throw new ArgumentException($"Shift at index {index} must end after it starts.", nameof(shifts)); &#x20; } &#x20; if (shift.Start < EarliestSupportedInstant || shift.End > LatestSupportedInstant) &#x20; { &#x20; throw new ArgumentOutOfRangeException(nameof(shifts), $"Shift at index {index} is outside the supported date range."); &#x20; } &#x20; intervals\[index] = new Interval(shift.Start, shift.End); &#x20; } &#x20; Array.Sort(intervals, (left, right) => left.Start.CompareTo(right.Start)); &#x20; for (int index = 1; index < intervals.Length; index++) &#x20; { &#x20; if (intervals\[index].Start < intervals\[index - 1].End) &#x20; { &#x20; throw new ArgumentException( &#x20; $"Shift starting {intervals\[index].Start.ToString("O", CultureInfo.InvariantCulture)} overlaps the previous shift.", &#x20; nameof(shifts)); &#x20; } &#x20; } &#x20; if (intervals.Length > 0 \&\& intervals\[^1].End - intervals\[0].Start > MaximumRecordSpan) &#x20; { &#x20; throw new ArgumentException("Shifts of one work day must fit within 24 hours of the first clock-in.", nameof(shifts)); &#x20; } &#x20; return intervals; &#x20; } &#x20; private static DayKind ResolveDayKind(DayKind declared, DateTime workDate, IReadOnlySet<DateTime> publicHolidays, DayOfWeek weeklyRestDay) &#x20; { &#x20; DayKind fromCalendar = publicHolidays.Contains(workDate) &#x20; ? DayKind.PublicHoliday &#x20; : workDate.DayOfWeek == weeklyRestDay ? DayKind.RestDay : DayKind.Normal; &#x20; return Precedence(fromCalendar) > Precedence(declared) ? fromCalendar : declared; &#x20; } &#x20; private static int Precedence(DayKind kind) => kind switch &#x20; { &#x20; DayKind.Normal => 0, &#x20; DayKind.RestDay => 1, &#x20; DayKind.PublicHoliday => 2, &#x20; \_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown day kind.") &#x20; }; &#x20; private static List<Interval> TrimToDailyCap(Interval\[] ordered) &#x20; { &#x20; var counted = new List<Interval>(ordered.Length); &#x20; TimeSpan remaining = DailyCap; &#x20; foreach (Interval interval in ordered) &#x20; { &#x20; if (remaining <= TimeSpan.Zero) &#x20; { &#x20; break; &#x20; } &#x20; TimeSpan take = interval.Duration < remaining ? interval.Duration : remaining; &#x20; counted.Add(new Interval(interval.Start, interval.Start + take)); &#x20; remaining -= take; &#x20; } &#x20; return counted; &#x20; } &#x20; private static TierTotals AccumulateTiers(IReadOnlyList<Interval> counted, DayKind effectiveKind) &#x20; { &#x20; var totals = new TierTotals(); &#x20; foreach ((Interval slice, PayTier tier) in SliceByTier(counted, effectiveKind)) &#x20; { &#x20; totals.Add(tier, slice.Duration, NightOverlap(slice)); &#x20; } &#x20; return totals; &#x20; } &#x20; private static IEnumerable<(Interval Slice, PayTier Tier)> SliceByTier(IReadOnlyList<Interval> counted, DayKind effectiveKind) &#x20; { &#x20; if (effectiveKind != DayKind.Normal) &#x20; { &#x20; foreach (Interval interval in counted) &#x20; { &#x20; yield return (interval, PayTier.RestOrHoliday); &#x20; } &#x20; yield break; &#x20; } &#x20; TimeSpan elapsed = TimeSpan.Zero; &#x20; foreach (Interval interval in counted) &#x20; { &#x20; DateTime cursor = interval.Start; &#x20; while (cursor < interval.End) &#x20; { &#x20; PayTier tier = NormalDayTierAt(elapsed); &#x20; TimeSpan tierRemaining = NormalDayTierUpperBound(tier) - elapsed; &#x20; TimeSpan intervalRemaining = interval.End - cursor; &#x20; TimeSpan take = tierRemaining < intervalRemaining ? tierRemaining : intervalRemaining; &#x20; var slice = new Interval(cursor, cursor + take); &#x20; yield return (slice, tier); &#x20; cursor = slice.End; &#x20; elapsed += take; &#x20; } &#x20; } &#x20; } &#x20; private static PayTier NormalDayTierAt(TimeSpan elapsed) &#x20; { &#x20; if (elapsed < RegularLimit) &#x20; { &#x20; return PayTier.Regular; &#x20; } &#x20; if (elapsed < FirstTierLimit) &#x20; { &#x20; return PayTier.FirstOvertime; &#x20; } &#x20; if (elapsed < DailyCap) &#x20; { &#x20; return PayTier.ExtraOvertime; &#x20; } &#x20; throw new InvalidOperationException("Worked time exceeds the daily cap after trimming."); &#x20; } &#x20; private static TimeSpan NormalDayTierUpperBound(PayTier tier) => tier switch &#x20; { &#x20; PayTier.Regular => RegularLimit, &#x20; PayTier.FirstOvertime => FirstTierLimit, &#x20; PayTier.ExtraOvertime => DailyCap, &#x20; \_ => throw new ArgumentOutOfRangeException(nameof(tier), tier, "Tier is not a normal-day tier.") &#x20; }; &#x20; private static TimeSpan NightOverlap(Interval interval) &#x20; { &#x20; TimeSpan total = TimeSpan.Zero; &#x20; for (DateTime day = interval.Start.Date.AddDays(-1); day <= interval.End.Date; day = day.AddDays(1)) &#x20; { &#x20; DateTime nightStart = day + NightWindowStart; &#x20; DateTime nightEnd = day.AddDays(1) + NightWindowEnd; &#x20; DateTime overlapStart = interval.Start > nightStart ? interval.Start : nightStart; &#x20; DateTime overlapEnd = interval.End < nightEnd ? interval.End : nightEnd; &#x20; if (overlapEnd > overlapStart) &#x20; { &#x20; total += overlapEnd - overlapStart; &#x20; } &#x20; } &#x20; return total; &#x20; } &#x20; private static decimal ComputeHourlyRate(decimal basicMonthlySalary) => &#x20; basicMonthlySalary / DaysPerMonthForHourlyRate / RegularDailyHours; &#x20; private static decimal Multiplier(PayTier tier) => tier switch &#x20; { &#x20; PayTier.Regular => RegularMultiplier, &#x20; PayTier.FirstOvertime => FirstOvertimeMultiplier, &#x20; PayTier.ExtraOvertime => ExtraOvertimeMultiplier, &#x20; PayTier.RestOrHoliday => RestOrHolidayMultiplier, &#x20; \_ => throw new ArgumentOutOfRangeException(nameof(tier), tier, "Unknown pay tier.") &#x20; }; &#x20; private static decimal ToDecimalHours(TimeSpan duration) => (decimal)duration.Ticks / TimeSpan.TicksPerHour; &#x20; private static decimal PayFor(TimeSpan duration, decimal hourlyRate, decimal multiplier) => &#x20; ToDecimalHours(duration) \* hourlyRate \* multiplier; &#x20; private static decimal RoundMoney(decimal amount) => &#x20; Math.Round(amount, MoneyDecimalPlaces, MidpointRounding.AwayFromZero); &#x20; private static OvertimeBreakdown BuildBreakdown(TierTotals totals, DayKind effectiveKind, decimal hourlyRate) &#x20; { &#x20; TimeSpan restOrHolidayWorked = totals.WorkedIn(PayTier.RestOrHoliday); &#x20; decimal overtimePay = &#x20; PayFor(totals.WorkedIn(PayTier.FirstOvertime), hourlyRate, Multiplier(PayTier.FirstOvertime)) + &#x20; PayFor(totals.WorkedIn(PayTier.ExtraOvertime), hourlyRate, Multiplier(PayTier.ExtraOvertime)) + &#x20; PayFor(restOrHolidayWorked, hourlyRate, Multiplier(PayTier.RestOrHoliday)); &#x20; decimal nightPremium = 0m; &#x20; foreach (PayTier tier in AllTiers) &#x20; { &#x20; nightPremium += PayFor(totals.NightIn(tier), hourlyRate, Multiplier(tier) \* (NightFactor - 1m)); &#x20; } &#x20; decimal holidayDayPay = restOrHolidayWorked > TimeSpan.Zero &#x20; ? HolidayDayCompensationHours \* hourlyRate \* RestOrHolidayMultiplier &#x20; : 0m; &#x20; return new OvertimeBreakdown( &#x20; RegularHours: totals.WorkedIn(PayTier.Regular).TotalHours, &#x20; NightHours: totals.TotalNight.TotalHours, &#x20; FirstTwoOvertimeHours: totals.WorkedIn(PayTier.FirstOvertime).TotalHours, &#x20; ExtraOvertimeHours: totals.WorkedIn(PayTier.ExtraOvertime).TotalHours, &#x20; RestDayHours: effectiveKind == DayKind.RestDay ? restOrHolidayWorked.TotalHours : 0d, &#x20; HolidayHours: effectiveKind == DayKind.PublicHoliday ? restOrHolidayWorked.TotalHours : 0d, &#x20; OvertimePay: RoundMoney(overtimePay), &#x20; NightPremium: RoundMoney(nightPremium), &#x20; HolidayDayPay: RoundMoney(holidayDayPay)); &#x20; } &#x20; } &#x20; /// <summary>Demo: runs the given data plus one night scenario, prints the breakdown, and self-checks hand-traced values.</summary> &#x20; public static class Program &#x20; { &#x20; private const decimal BasicMonthlySalary = 6000m; &#x20; private const DayOfWeek WeeklyRestDay = DayOfWeek.Friday; &#x20; private const string CurrencyCode = "SAR"; &#x20; private const string HoursFormat = "0.##"; &#x20; private const string MoneyFormat = "0.00"; &#x20; private const double HoursTolerance = 1e-9; &#x20; private const int ExitSuccess = 0; &#x20; private const int ExitSelfCheckFailed = 1; &#x20; private sealed record DemoScenario(string Title, DayKind DeclaredKind, Shift\[] Shifts, OvertimeBreakdown Expected); &#x20; /// <summary>Entry point. Returns 0 when every self-check passes, 1 otherwise.</summary> &#x20; public static int Main() &#x20; { &#x20; var publicHolidays = new HashSet<DateTime> { new DateTime(2026, 9, 23) }; &#x20; DemoScenario\[] scenarios = &#x20; { &#x20; new DemoScenario( &#x20; "2026-09-24 Thursday (normal day)", &#x20; DayKind.Normal, &#x20; new\[] &#x20; { &#x20; new Shift(new DateTime(2026, 9, 24, 8, 0, 0), new DateTime(2026, 9, 24, 12, 0, 0)), &#x20; new Shift(new DateTime(2026, 9, 24, 13, 0, 0), new DateTime(2026, 9, 24, 17, 0, 0)), &#x20; new Shift(new DateTime(2026, 9, 24, 18, 0, 0), new DateTime(2026, 9, 24, 23, 30, 0)), &#x20; }, &#x20; new OvertimeBreakdown(8d, 0d, 2d, 2d, 0d, 0d, 175m, 0m, 0m)), &#x20; new DemoScenario( &#x20; "2026-09-25 Friday (weekly rest day)", &#x20; DayKind.RestDay, &#x20; new\[] &#x20; { &#x20; new Shift(new DateTime(2026, 9, 25, 9, 0, 0), new DateTime(2026, 9, 25, 14, 0, 0)), &#x20; }, &#x20; new OvertimeBreakdown(0d, 0d, 0d, 0d, 5d, 0d, 250m, 0m, 400m)), &#x20; new DemoScenario( &#x20; "2026-09-27 Sunday (extra night scenario, crosses midnight)", &#x20; DayKind.Normal, &#x20; new\[] &#x20; { &#x20; new Shift(new DateTime(2026, 9, 27, 14, 0, 0), new DateTime(2026, 9, 27, 22, 0, 0)), &#x20; new Shift(new DateTime(2026, 9, 27, 22, 0, 0), new DateTime(2026, 9, 28, 1, 0, 0)), &#x20; }, &#x20; new OvertimeBreakdown(8d, 2d, 2d, 1d, 0d, 0d, 125m, 43.75m, 0m)), &#x20; }; &#x20; int failedScenarios = 0; &#x20; foreach (DemoScenario scenario in scenarios) &#x20; { &#x20; OvertimeBreakdown actual = OvertimeCalculator.Calculate( &#x20; scenario.Shifts, scenario.DeclaredKind, BasicMonthlySalary, publicHolidays, WeeklyRestDay); &#x20; PrintBreakdown(scenario.Title, actual); &#x20; List<string> mismatches = FindMismatches(actual, scenario.Expected); &#x20; if (mismatches.Count == 0) &#x20; { &#x20; Console.WriteLine(" Self-check : PASS"); &#x20; } &#x20; else &#x20; { &#x20; failedScenarios++; &#x20; Console.WriteLine(" Self-check : FAIL"); &#x20; foreach (string mismatch in mismatches) &#x20; { &#x20; Console.WriteLine($" - {mismatch}"); &#x20; } &#x20; } &#x20; Console.WriteLine(); &#x20; } &#x20; return failedScenarios == 0 ? ExitSuccess : ExitSelfCheckFailed; &#x20; } &#x20; private static void PrintBreakdown(string title, OvertimeBreakdown breakdown) &#x20; { &#x20; decimal totalAdditional = breakdown.OvertimePay + breakdown.NightPremium + breakdown.HolidayDayPay; &#x20; Console.WriteLine($"== {title} =="); &#x20; Console.WriteLine($" Regular hours : {FormatHours(breakdown.RegularHours)}"); &#x20; Console.WriteLine($" Night hours (23:00-06:00): {FormatHours(breakdown.NightHours)}"); &#x20; Console.WriteLine($" First two OT hours (1.5x): {FormatHours(breakdown.FirstTwoOvertimeHours)}"); &#x20; Console.WriteLine($" Extra OT hours (2x) : {FormatHours(breakdown.ExtraOvertimeHours)}"); &#x20; Console.WriteLine($" Rest-day hours (2x) : {FormatHours(breakdown.RestDayHours)}"); &#x20; Console.WriteLine($" Holiday hours (2x) : {FormatHours(breakdown.HolidayHours)}"); &#x20; Console.WriteLine($" Overtime pay : {FormatMoney(breakdown.OvertimePay)}"); &#x20; Console.WriteLine($" Night premium : {FormatMoney(breakdown.NightPremium)}"); &#x20; Console.WriteLine($" Rest/holiday day pay : {FormatMoney(breakdown.HolidayDayPay)}"); &#x20; Console.WriteLine($" Total additional pay : {FormatMoney(totalAdditional)}"); &#x20; } &#x20; private static string FormatHours(double hours) => hours.ToString(HoursFormat, CultureInfo.InvariantCulture); &#x20; private static string FormatMoney(decimal amount) => &#x20; $"{amount.ToString(MoneyFormat, CultureInfo.InvariantCulture)} {CurrencyCode}"; &#x20; private static List<string> FindMismatches(OvertimeBreakdown actual, OvertimeBreakdown expected) &#x20; { &#x20; var mismatches = new List<string>(); &#x20; CompareHours(mismatches, nameof(OvertimeBreakdown.RegularHours), actual.RegularHours, expected.RegularHours); &#x20; CompareHours(mismatches, nameof(OvertimeBreakdown.NightHours), actual.NightHours, expected.NightHours); &#x20; CompareHours(mismatches, nameof(OvertimeBreakdown.FirstTwoOvertimeHours), actual.FirstTwoOvertimeHours, expected.FirstTwoOvertimeHours); &#x20; CompareHours(mismatches, nameof(OvertimeBreakdown.ExtraOvertimeHours), actual.ExtraOvertimeHours, expected.ExtraOvertimeHours); &#x20; CompareHours(mismatches, nameof(OvertimeBreakdown.RestDayHours), actual.RestDayHours, expected.RestDayHours); &#x20; CompareHours(mismatches, nameof(OvertimeBreakdown.HolidayHours), actual.HolidayHours, expected.HolidayHours); &#x20; CompareMoney(mismatches, nameof(OvertimeBreakdown.OvertimePay), actual.OvertimePay, expected.OvertimePay); &#x20; CompareMoney(mismatches, nameof(OvertimeBreakdown.NightPremium), actual.NightPremium, expected.NightPremium); &#x20; CompareMoney(mismatches, nameof(OvertimeBreakdown.HolidayDayPay), actual.HolidayDayPay, expected.HolidayDayPay); &#x20; return mismatches; &#x20; } &#x20; private static void CompareHours(List<string> mismatches, string field, double actual, double expected) &#x20; { &#x20; if (Math.Abs(actual - expected) > HoursTolerance) &#x20; { &#x20; mismatches.Add($"{field}: expected {FormatHours(expected)}, got {FormatHours(actual)}"); &#x20; } &#x20; } &#x20; private static void CompareMoney(List<string> mismatches, string field, decimal actual, decimal expected) &#x20; { &#x20; if (actual != expected) &#x20; { &#x20; mismatches.Add($"{field}: expected {FormatMoney(expected)}, got {FormatMoney(actual)}"); &#x20; } &#x20; } &#x20; } } الشرح يمرّ الحساب على خطّ واحد. أوّلاً تُرتَّب الورديات ويُرفض المتداخل منها والمعكوس، وكذلك أيّ سجلّ يتجاوز 24 ساعة من أوّل دخول. بعد ذلك يُحدَّد نوع اليوم، ثمّ يُقصّ العمل زمنيّاً عند الساعة الثانية عشرة. يلي ذلك تقطيع الوقت المتبقّي إلى شرائح بحسب الوقت المتراكم: 8 ساعات عاديّة، ثمّ ساعتان بمعامل 1.5×، ثمّ الباقي بمعامل 2×. وفي النهاية تُقاطَع كلّ شريحة مع نوافذ 23:00–06:00. الفصل بين القطعين مقصود. الشريحة تُحدَّد بالوقت المتراكم، أمّا الليل فيُحدَّد بساعة الحائط، والقيدان متعامدان، لذلك تحمل كلّ شريحة معاملها ونصيبها من الليل معاً. علاوة الليل لكلّ ساعة ليليّة هي أجر الساعة × معامل شريحتها × 0.5، وبهذا تكون الساعة الليليّة في الشريحة الثانية 2× × 1.5 كما في صيغتكم الضربيّة. الحساب CPU-bound خالص بلا I/O، فلا معنى فيه لـ async ولا لـ CancellationToken، والواجهة المطلوبة متزامنة أصلاً. الأوقات تُجمع بـ TimeSpan على مستوى ticks لتجنّب انجراف double، والمال يُحسب بـ decimal ويُقرَّب مرّة واحدة في النهاية إلى منزلتين (هللة) بقاعدة AwayFromZero، وهذا افتراض منّي لأنّ نصّكم لم يحدّد قاعدة التقريب. الشرائح أربع وثابتة، لذلك تُجمع في مصفوفة مفهرسة بها بدل Dictionary. مهمّ في بيانات الخميس يلتهم سقفُ الـ12 ساعة العلاوةَ الليليّة كلّها. الموظف عمل 13.5 ساعة، والقصّ الزمنيّ يُسقط الفترة من 22:00 إلى 23:30، ونصف الساعة الليليّ الوحيد (23:00–23:30) يقع داخل الجزء المُهمَل. لذلك تخرج NightHours = 0 مع أنّ الوردية مسجّلة حتّى 23:30. هذا ما يقتضيه نصّ «ما فوق 12 يُهمل» بقراءته الزمنيّة، فإن أردتم أن يُهمَل الأرخص لا الأخير فهذا قرار سياسة يغيّر الرقم. تتبّعتُ القيم التالية يدويّاً ولم أشغّل الشيفرة. أجر الساعة هو 6,000 ÷ 30 ÷ 8 = 25 ريالاً. أضفتُ يوم الأحد مثالاً ثالثاً لأنّ بياناتكم لا تمرّ بمسار الليل، وفيه تُدفع الساعة الليليّة في الشريحة الأولى 1 × 25 × 1.5 × 0.5 = 18.75، وفي الشريحة الثانية 1 × 25 × 2 × 0.5 = 25. اليوم عاديّة أولى (1.5×) لاحقة (2×) راحة ليليّة بدل إضافي علاوة ليل بدل يوم الإجمالي الخميس 24/9 8 2 2 0 0 175 0 0 175 الجمعة 25/9 0 0 0 5 0 250 0 400 650 الأحد 27/9 8 2 1 0 2 125 43.75 0 168.75 يحتوي Main على هذه القيم بأعيانها في صورة فحوص، ويعيد رمز الخروج 1 إن خالفها أيّ حقل، فيكون التشغيل هو الحَكَم لا هذا الجدول. في نصّكم مواضع تحتمل أكثر من قراءة، واخترتُ في كلّ منها ما يوافق صياغة السياسة. التعارض الظاهر بين معامل dayKind وبين مجموعة الإجازات ويوم الراحة حسمتُه بأخذ النوع الأقوى: الإجازة الرسميّة ثمّ الراحة ثمّ العاديّ. بهذا لا يُبخس موظف عمل يوم 23/9 حقّه إن مُرِّر اليوم خطأً بنوع Normal. بدل اليوم الكامل (400 ريال) يُطبَّق على يوم الراحة كذلك، لأنّ عبارة «أنجز أعمالاً فيها» تعود على الاثنين، رغم أنّ اسم الحقل HolidayDayPay. ومعامل الليل يشمل الساعات العاديّة أيضاً، لأنّ مثالكم «ساعة ليلية عادية = 1× أساسي × 1.5» ينصّ عليها. أمّا أجر الساعات العاديّة نفسه فلا يدخل OvertimePay لأنّ الراتب الشهريّ يغطّيه، وتظهر فقط علاوتها الليليّة. ويُنسب اليوم إلى تاريخ أوّل دخول، فالوردية من 22:00 إلى 02:00 تتبع يوم بدايتها. هناك نقطة يجب حسمها قبل الإنتاج. المادّة 107 من نظام العمل تجعل الحدّ الأدنى لساعة العمل الإضافيّ أجرَ الساعة الفعليّ مضافاً إليه 50% من الأساسيّ. معامل 1.5× على الأساسيّ وحده قد يقع دون هذا الحدّ عند موظف له بدلات ثابتة كالسكن. هل لدى موظفيكم بدلات ثابتة؟ إن كانت لديهم فصلتُ لكم أجر الساعة الفعليّ عن الأساسيّ في الحساب، والقرار في ذلك لكم. طريقة الاستخدام الملف يُشغَّل كما هو: أنشئ مشروعاً بالأمر dotnet new console على .NET 6 أو أحدث، واستبدل Program.cs بهذا الملف، ثمّ نفّذ dotnet run. في وحدة الرواتب الفعليّة اجمع بصمات الدخول والخروج حسب تاريخ أوّل دخول، واستدعِ Calculate مرّة لكلّ يوم، ثمّ اجمع الحقول المالية الثلاثة على مستوى الشهر. الدالة بلا حالة مشتركة، فيمكن توزيع أيّام آلاف الموظفين على Parallel.ForEach دون أقفال.وثمّة أمرٌ في الإدخال لا تفحصه الدالة. DateTime لا يقارن قيمة Kind، فإن جاءت بعض البصمات بتوقيت UTC وبعضها بالتوقيت المحلّيّ فسيُحسب التداخل وحدود 23:00 و06:00 على ساعاتٍ مختلطة دون أيّ استثناء. لذلك حوِّل كلّ البصمات إلى التوقيت المحلّيّ للموقع قبل بناء Shift، فنافذة الليل في السياسة مربوطة بساعة الحائط هناك. ابدأ غداً بخطوتين. الأولى أن تشغّل الملف وتتأكّد أنّ الأيّام الثلاثة تطبع PASS وأنّ رمز الخروج 0. والثانية أن تحسم سؤال البدلات الثابتة، فهو الوحيد الذي قد يغيّر أجر الساعة نفسه، وما سواه من قرارات السياسة مثبّت في الشيفرة وفي فحوصها.
أClaude Sonnet93.5 · 1,615 كلمة
سأكتب الملف المستقل بالقواعد التي حددتها، ثم أشغّله على بياناتك للتحقق من الأرقام. الملف OvertimeCalculator.cs جاهز، لكنني لم أستطع تشغيله لأن بيئتي لا تحتوي .NET ولا يمكن تثبيته. الأرقام أدناه محسوبة يدوياً من الكود، فشغّل dotnet run عندك للتأكد منها. أجر الساعة = 6000 ÷ 30 ÷ 8 = 25 ريال. اليوم التفصيل بدل العمل الإضافي علاوة الليل بدل اليوم الكامل الإجمالي الخميس 24/9 8 ساعات عادية، ثم ساعتان بـ 1.5× وساعتان بـ 2× 175.00 0.00 0.00 175.00 الجمعة 25/9 5 ساعات بـ 2× 250.00 0.00 400.00 650.00 سقف الـ 12 ساعة يقطع وردية الخميس. سجل الخميس يبلغ 13.5 ساعة فعلية، فتُهمل آخر ساعة ونصف حسب القاعدة. تنتهي الوردية المحتسبة عند 22:00، فلا تصل إلى فترة الليل (23:00–06:00) ولا تظهر علاوة ليلية. لذلك أضفت في Main مثالاً ثالثاً (الأحد 27/9، وردية مسائية 20:00–24:00) تظهر فيه علاوة الليل، وتبلغ 25 ريالاً لساعة 23:00–24:00. قرارات تصميمية يجب مراجعتها: علاوة الليل: تُحسب كزيادة بنسبة 50% فوق معامل الفترة (ساعة بـ 2× في الليل = 3×)، وهذا يطابق نصك "معامل الليل يضرب في معامل الفترة". وهي مفصولة في NightPremium حتى لا تختلط ببدل العمل الإضافي. بدل اليوم الكامل: في يوم الراحة أو الإجازة يُضاف سطر مستقل بقيمة 8 × 2× (400 ريال)، كما اختصرتَه في وصفك. هذا يعني أن ساعات الجمعة تُدفع بـ 2× ثم يُضاف بدل اليوم الكامل فوقها، فتأكد أن هذا هو المقصود. نوع اليوم: إذا مُرّر DayKind.Normal ووافق التاريخ إجازة رسمية أو يوم الراحة، تستنتج الدالة النوع الصحيح. وإن اجتمعا تتقدم الإجازة الرسمية. الورديات عبر منتصف الليل: تتبع يوم بدايتها. التحقق من المدخلات: الورديات المتداخلة أو التي يسبق خروجها دخولها ترمي ArgumentException. التقريب: الحسابات بـ decimal ثم تقريب المبالغ النهائية إلى خانتين فقط. الساعات تُحسب بالـ ticks فلا أخطاء عائمة. Overtimecalculator CS  // OvertimeCalculator.cs // حاسبة بدل العمل الإضافي وفق نظام العمل السعودي (وحدة الرواتب - شركة المقاولات) // ملف واحد مستقل، مكتبة قياسية فقط (.NET 6+ / C# 10+)، بلا أي قراءة لتاريخ/وقت النظام، وسلوك حتمي. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; public record Shift(DateTime Start, DateTime End); public enum DayKind { Normal, RestDay, PublicHoliday } public record OvertimeBreakdown( &#x20; double RegularHours, double NightHours, &#x20; double FirstTwoOvertimeHours, double ExtraOvertimeHours, &#x20; double RestDayHours, double HolidayHours, &#x20; decimal OvertimePay, decimal NightPremium, decimal HolidayDayPay); public static class OvertimeCalculator { &#x20; // ثوابت القواعد &#x20; private const int OfficialDailyHours = 8; // ساعات العمل الرسمية اليومية &#x20; private const int FirstOvertimeBandHours = 2; // أول ساعتين إضافيتين &#x20; private const int MaxActualDailyHours = 12; // سقف الساعات الفعلية، وما فوقه يُهمل &#x20; private const int DaysInMonth = 30; // قاعدة التحويل: الأساسي ÷ 30 ÷ 8 &#x20; private const decimal FirstBandFactor = 1.5m; // أول ساعتين إضافيتين &#x20; private const decimal ExtraBandFactor = 2.0m; // ما بعدهما &#x20; private const decimal RestOrHolidayFactor = 2.0m; // يوم الراحة / الإجازة الرسمية &#x20; private const decimal NightFactor = 1.5m; // معامل الليل (يضرب في معامل الفترة) &#x20; private static readonly TimeSpan NightStart = TimeSpan.FromHours(23); &#x20; private static readonly TimeSpan NightEnd = TimeSpan.FromHours(6); &#x20; private const long Hour = TimeSpan.TicksPerHour; &#x20; public static OvertimeBreakdown Calculate( &#x20; IReadOnlyList<Shift> shifts, DayKind dayKind, &#x20; decimal basicMonthlySalary, IReadOnlySet<DateTime> publicHolidays, &#x20; DayOfWeek weeklyRestDay) &#x20; { &#x20; ArgumentNullException.ThrowIfNull(shifts); &#x20; ArgumentNullException.ThrowIfNull(publicHolidays); &#x20; if (basicMonthlySalary < 0) &#x20; throw new ArgumentOutOfRangeException(nameof(basicMonthlySalary)); &#x20; if (shifts.Count == 0) &#x20; return new OvertimeBreakdown(0, 0, 0, 0, 0, 0, 0m, 0m, 0m); &#x20; // 1) تحقق وترتيب: كل وردية صالحة وبلا تداخل &#x20; var ordered = shifts.OrderBy(s => s.Start).ThenBy(s => s.End).ToList(); &#x20; for (int i = 0; i < ordered.Count; i++) &#x20; { &#x20; if (ordered\[i].End <= ordered\[i].Start) &#x20; throw new ArgumentException($"الوردية رقم {i + 1}: وقت الخروج يجب أن يكون بعد وقت الدخول.", nameof(shifts)); &#x20; if (i > 0 \&\& ordered\[i].Start < ordered\[i - 1].End) &#x20; throw new ArgumentException($"الورديتان {i} و{i + 1} متداخلتان.", nameof(shifts)); &#x20; } &#x20; // 2) يوم العمل = تاريخ بداية أول وردية (الوردية الممتدة بعد منتصف الليل تتبع يوم بدايتها) &#x20; DateTime workDate = ordered\[0].Start.Date; &#x20; // 3) نوع اليوم الفعلي: القيمة الصريحة تتقدم، وإن كانت Normal نستنتج من التاريخ. &#x20; // الإجازة الرسمية تتقدم على يوم الراحة إن اجتمعا. &#x20; DayKind kind = dayKind; &#x20; if (kind == DayKind.Normal) &#x20; { &#x20; if (publicHolidays.Contains(workDate)) kind = DayKind.PublicHoliday; &#x20; else if (workDate.DayOfWeek == weeklyRestDay) kind = DayKind.RestDay; &#x20; } &#x20; decimal hourly = basicMonthlySalary / DaysInMonth / OfficialDailyHours; &#x20; // 4) تجميع (بالـ ticks لتفادي أخطاء الفاصلة العائمة) &#x20; long regularT = 0, firstT = 0, extraT = 0, restT = 0, holidayT = 0, nightT = 0; &#x20; decimal otPay = 0m, nightPrem = 0m; &#x20; long worked = 0; // الزمن الفعلي المحتسب حتى الآن (بعد السقف) &#x20; long cap = MaxActualDailyHours \* Hour; &#x20; foreach (var shift in ordered) &#x20; { &#x20; long remainingCap = cap - worked; &#x20; if (remainingCap <= 0) break; // ما فوق 12 ساعة يُهمل &#x20; DateTime start = shift.Start; &#x20; DateTime end = shift.End; &#x20; if ((end - start).Ticks > remainingCap) &#x20; end = start.AddTicks(remainingCap); &#x20; // تقسيم الفترة على حدود 23:00 و06:00 &#x20; DateTime cur = start; &#x20; while (cur < end) &#x20; { &#x20; bool isNight; &#x20; DateTime boundary; &#x20; TimeSpan t = cur.TimeOfDay; &#x20; if (t < NightEnd) { isNight = true; boundary = cur.Date + NightEnd; } &#x20; else if (t < NightStart) { isNight = false; boundary = cur.Date + NightStart; } &#x20; else { isNight = true; boundary = cur.Date.AddDays(1) + NightEnd; } &#x20; DateTime next = boundary < end ? boundary : end; &#x20; long len = (next - cur).Ticks; &#x20; // توزيع القطعة على فئات المعامل حسب الزمن المتراكم &#x20; while (len > 0) &#x20; { &#x20; long take; &#x20; decimal factor; &#x20; int band; // 0 عادي، 1 أول ساعتين، 2 إضافي، 3 راحة، 4 إجازة &#x20; if (kind == DayKind.Normal) &#x20; { &#x20; long regLimit = OfficialDailyHours \* Hour; &#x20; long firstLimit = (OfficialDailyHours + FirstOvertimeBandHours) \* Hour; &#x20; if (worked < regLimit) { take = Math.Min(len, regLimit - worked); factor = 1m; band = 0; } &#x20; else if (worked < firstLimit) { take = Math.Min(len, firstLimit - worked); factor = FirstBandFactor; band = 1; } &#x20; else { take = len; factor = ExtraBandFactor; band = 2; } &#x20; } &#x20; else &#x20; { &#x20; take = len; factor = RestOrHolidayFactor; &#x20; band = kind == DayKind.RestDay ? 3 : 4; &#x20; } &#x20; decimal hours = (decimal)take / Hour; &#x20; switch (band) &#x20; { &#x20; case 0: regularT += take; break; // مشمول بالراتب الأساسي &#x20; case 1: firstT += take; otPay += hourly \* factor \* hours; break; &#x20; case 2: extraT += take; otPay += hourly \* factor \* hours; break; &#x20; case 3: restT += take; otPay += hourly \* factor \* hours; break; &#x20; case 4: holidayT += take; otPay += hourly \* factor \* hours; break; &#x20; } &#x20; if (isNight) &#x20; { &#x20; nightT += take; &#x20; // علاوة الليل = الزيادة فوق المعامل الأساسي: معامل × (1.5 - 1) &#x20; nightPrem += hourly \* factor \* (NightFactor - 1m) \* hours; &#x20; } &#x20; worked += take; &#x20; len -= take; &#x20; } &#x20; cur = next; &#x20; } &#x20; } &#x20; // 5) بدل اليوم الكامل لمن عمل في يوم الراحة أو الإجازة: 8 ساعات × 2×، كسطر مستقل &#x20; decimal holidayDayPay = (kind != DayKind.Normal \&\& worked > 0) &#x20; ? hourly \* OfficialDailyHours \* RestOrHolidayFactor &#x20; : 0m; &#x20; return new OvertimeBreakdown( &#x20; RegularHours: (double)regularT / Hour, &#x20; NightHours: (double)nightT / Hour, &#x20; FirstTwoOvertimeHours: (double)firstT / Hour, &#x20; ExtraOvertimeHours: (double)extraT / Hour, &#x20; RestDayHours: (double)restT / Hour, &#x20; HolidayHours: (double)holidayT / Hour, &#x20; OvertimePay: Round(otPay), &#x20; NightPremium: Round(nightPrem), &#x20; HolidayDayPay: Round(holidayDayPay)); &#x20; } &#x20; private static decimal Round(decimal v) => Math.Round(v, 2, MidpointRounding.AwayFromZero); } public static class Program { &#x20; public static void Main() &#x20; { &#x20; Console.OutputEncoding = System.Text.Encoding.UTF8; &#x20; var holidays = new HashSet<DateTime> { new DateTime(2026, 9, 23) }; // اليوم الوطني &#x20; const DayOfWeek restDay = DayOfWeek.Friday; &#x20; const decimal salary = 6000m; &#x20; // 2026-09-24 الخميس، يوم عمل عادي &#x20; var thursday = new List<Shift> &#x20; { &#x20; new(new DateTime(2026, 9, 24, 8, 0, 0), new DateTime(2026, 9, 24, 12, 0, 0)), &#x20; new(new DateTime(2026, 9, 24, 13, 0, 0), new DateTime(2026, 9, 24, 17, 0, 0)), &#x20; new(new DateTime(2026, 9, 24, 18, 0, 0), new DateTime(2026, 9, 24, 23, 30, 0)), &#x20; }; &#x20; Print("2026-09-24 الخميس (يوم عادي)", OvertimeCalculator.Calculate(thursday, DayKind.Normal, salary, holidays, restDay)); &#x20; // 2026-09-25 الجمعة، يوم الراحة &#x20; var friday = new List<Shift> &#x20; { &#x20; new(new DateTime(2026, 9, 25, 9, 0, 0), new DateTime(2026, 9, 25, 14, 0, 0)), &#x20; }; &#x20; Print("2026-09-25 الجمعة (يوم راحة)", OvertimeCalculator.Calculate(friday, DayKind.RestDay, salary, holidays, restDay)); &#x20; // مثال إضافي للتوضيح فقط: وردية تمتد عبر منتصف الليل لتظهر علاوة الليل &#x20; var sunday = new List<Shift> &#x20; { &#x20; new(new DateTime(2026, 9, 27, 8, 0, 0), new DateTime(2026, 9, 27, 12, 0, 0)), &#x20; new(new DateTime(2026, 9, 27, 13, 0, 0), new DateTime(2026, 9, 27, 17, 0, 0)), &#x20; new(new DateTime(2026, 9, 27, 20, 0, 0), new DateTime(2026, 9, 28, 0, 0, 0)), &#x20; }; &#x20; Print("2026-09-27 الأحد (مثال إضافي، عبور 23:00)", OvertimeCalculator.Calculate(sunday, DayKind.Normal, salary, holidays, restDay)); &#x20; } &#x20; private static void Print(string title, OvertimeBreakdown b) &#x20; { &#x20; var ci = CultureInfo.InvariantCulture; &#x20; Console.WriteLine($"=== {title} ==="); &#x20; Console.WriteLine($" ساعات عادية: {b.RegularHours.ToString("0.##", ci)}"); &#x20; Console.WriteLine($" ساعات ليلية (23-06): {b.NightHours.ToString("0.##", ci)}"); &#x20; Console.WriteLine($" إضافي أول ساعتين (1.5x): {b.FirstTwoOvertimeHours.ToString("0.##", ci)}"); &#x20; Console.WriteLine($" إضافي إضافي (2x): {b.ExtraOvertimeHours.ToString("0.##", ci)}"); &#x20; Console.WriteLine($" ساعات يوم الراحة (2x): {b.RestDayHours.ToString("0.##", ci)}"); &#x20; Console.WriteLine($" ساعات الإجازة الرسمية (2x): {b.HolidayHours.ToString("0.##", ci)}"); &#x20; Console.WriteLine($" بدل العمل الإضافي: {b.OvertimePay.ToString("N2", ci)} ريال"); &#x20; Console.WriteLine($" علاوة الليل: {b.NightPremium.ToString("N2", ci)} ريال"); &#x20; Console.WriteLine($" بدل اليوم الكامل: {b.HolidayDayPay.ToString("N2", ci)} ريال"); &#x20; Console.WriteLine($" الإجمالي: {(b.OvertimePay + b.NightPremium + b.HolidayDayPay).ToString("N2", ci)} ريال"); &#x20; Console.WriteLine(); &#x20; } }
دChatGPT54.7 · 1,568 كلمة
سأبني الحل كملف C# مستقل باستخدام المكتبة القياسية فقط، مع الحفاظ على الواجهة المطلوبة، ودعم الورديات المتقطعة وتقسيم الساعات عند حدود 23:00 و06:00، وتحديد نوع اليوم من التاريخ الفعلي. سأجعل الحساب حتميًا دون الاعتماد على الساعة الحالية أو المنطقة الزمنية للجهاز. ملاحظة مهمة قبل التنفيذ: سأتعامل مع قواعد المضاعفات التي حددتها باعتبارها سياسة الحساب المطلوبة في المسألة، لا باعتبارها تأكيدًا مستقلًا للتفسير القانوني لنظام العمل السعودي. كذلك سأفصل بدل اليوم الإضافي للإجازة عن أجر ساعات العمل، حتى لا يُحسب مرتين ضمن السطر نفسه. هناك نقطة مؤثرة في بيانات الاختبار: يوم الخميس يحتوي على 13.5 ساعة عمل فعلية، لكن الحد الأقصى المطلوب هو 12 ساعة. لذلك سيُعتمد العمل من 18:00 إلى 22:00 فقط من الوردية الإضافية، وتُهمل الساعات اللاحقة؛ ستكون ساعات الليل المعتمدة صفرًا في هذا المثال تحديدًا، رغم أن الخوارزمية تدعم احتسابها عند وجود وردية تصل إلى الليل ضمن الحد المسموح. الملف الكامل: Program.cs الكود التالي يحافظ على التواقيع المطلوبة، ويستخدم decimal للمبالغ المالية، ويتعامل مع تعدد الورديات وتداخلها مع فترات الليل وتغير نوع اليوم عند منتصف الليل. using System; using System.Collections.Generic; using System.Linq; public record Shift(DateTime Start, DateTime End); public enum DayKind { &#x20; Normal, &#x20; RestDay, &#x20; PublicHoliday } public record OvertimeBreakdown( &#x20; double RegularHours, &#x20; double NightHours, &#x20; double FirstTwoOvertimeHours, &#x20; double ExtraOvertimeHours, &#x20; double RestDayHours, &#x20; double HolidayHours, &#x20; decimal OvertimePay, &#x20; decimal NightPremium, &#x20; decimal HolidayDayPay); public static class OvertimeCalculator { &#x20; private const double DailyRegularHours = 8.0; &#x20; private const double FirstOvertimeLimit = 2.0; &#x20; private const double DailyMaximumHours = 12.0; &#x20; private static readonly TimeSpan NightStart = &#x20; new(23, 0, 0); &#x20; private static readonly TimeSpan NightEnd = &#x20; new(6, 0, 0); &#x20; public static OvertimeBreakdown Calculate( &#x20; IReadOnlyList<Shift> shifts, &#x20; DayKind dayKind, &#x20; decimal basicMonthlySalary, &#x20; IReadOnlySet<DateTime> publicHolidays, &#x20; DayOfWeek weeklyRestDay) &#x20; { &#x20; ArgumentNullException.ThrowIfNull(shifts); &#x20; ArgumentNullException.ThrowIfNull(publicHolidays); &#x20; if (basicMonthlySalary < 0) &#x20; throw new ArgumentOutOfRangeException( &#x20; nameof(basicMonthlySalary)); &#x20; var ordered = shifts.OrderBy(s => s.Start).ToList(); &#x20; foreach (var shift in ordered) &#x20; { &#x20; if (shift.End <= shift.Start) &#x20; throw new ArgumentException( &#x20; "Shift.End must be after Shift.Start."); &#x20; if (shift.Start.Kind != shift.End.Kind) &#x20; throw new ArgumentException( &#x20; "Shift endpoints must use the same DateTimeKind."); &#x20; } &#x20; for (int i = 1; i < ordered.Count; i++) &#x20; { &#x20; if (ordered\[i].Start < ordered\[i - 1].End) &#x20; throw new ArgumentException( &#x20; "Overlapping shifts are not allowed."); &#x20; } &#x20; decimal hourlyRate = basicMonthlySalary / 30m / 8m; &#x20; double regular = 0; &#x20; double night = 0; &#x20; double firstTwo = 0; &#x20; double extra = 0; &#x20; double restHours = 0; &#x20; double holidayHours = 0; &#x20; decimal overtimePay = 0; &#x20; decimal nightPremium = 0; &#x20; decimal holidayDayPay = 0; &#x20; // Accepted work hours per calendar day. &#x20; var dailyHours = new Dictionary<DateTime, double>(); &#x20; // Ensure the additional full-day amount is applied &#x20; // only once per qualifying calendar day. &#x20; var paidAdditionalDays = new HashSet<DateTime>(); &#x20; foreach (var shift in ordered) &#x20; { &#x20; DateTime cursor = shift.Start; &#x20; while (cursor < shift.End) &#x20; { &#x20; DateTime date = cursor.Date; &#x20; dailyHours.TryGetValue(date, out double worked); &#x20; // The daily cap applies to actual accepted work. &#x20; double remainingDaily = &#x20; DailyMaximumHours - worked; &#x20; if (remainingDaily <= 0) &#x20; break; &#x20; DayKind effectiveKind = &#x20; GetDayKind(date, dayKind, &#x20; publicHolidays, weeklyRestDay); &#x20; DateTime next = shift.End; &#x20; // Split at midnight. &#x20; DateTime midnight = date.AddDays(1); &#x20; if (midnight < next) &#x20; next = midnight; &#x20; // Split at the night-window boundaries. &#x20; DateTime sixAm = date.Add(NightEnd); &#x20; DateTime elevenPm = date.Add(NightStart); &#x20; if (sixAm > cursor \&\& sixAm < next) &#x20; next = sixAm; &#x20; if (elevenPm > cursor \&\& elevenPm < next) &#x20; next = elevenPm; &#x20; // On a normal day, split at 8 and 10 &#x20; // cumulative accepted work hours. &#x20; if (effectiveKind == DayKind.Normal) &#x20; { &#x20; double nextThreshold = &#x20; worked < DailyRegularHours &#x20; ? DailyRegularHours &#x20; : worked < DailyRegularHours &#x20; + FirstOvertimeLimit &#x20; ? DailyRegularHours &#x20; + FirstOvertimeLimit &#x20; : double.PositiveInfinity; &#x20; if (!double.IsPositiveInfinity(nextThreshold)) &#x20; { &#x20; double untilThreshold = &#x20; nextThreshold - worked; &#x20; DateTime thresholdTime = &#x20; cursor.AddHours(untilThreshold); &#x20; if (thresholdTime > cursor \&\& &#x20; thresholdTime < next) &#x20; { &#x20; next = thresholdTime; &#x20; } &#x20; } &#x20; } &#x20; double segmentHours = &#x20; (next - cursor).TotalHours; &#x20; // Never accept more than 12 hours per day. &#x20; segmentHours = Math.Min( &#x20; segmentHours, remainingDaily); &#x20; if (segmentHours <= 0) &#x20; break; &#x20; bool isNight = &#x20; cursor.TimeOfDay >= NightStart || &#x20; cursor.TimeOfDay < NightEnd; &#x20; decimal coefficient; &#x20; if (effectiveKind == DayKind.RestDay) &#x20; { &#x20; restHours += segmentHours; &#x20; coefficient = 2m; &#x20; } &#x20; else if (effectiveKind == DayKind.PublicHoliday) &#x20; { &#x20; holidayHours += segmentHours; &#x20; coefficient = 2m; &#x20; } &#x20; else if (worked < DailyRegularHours) &#x20; { &#x20; regular += segmentHours; &#x20; coefficient = 1m; &#x20; } &#x20; else if (worked < &#x20; DailyRegularHours + FirstOvertimeLimit) &#x20; { &#x20; firstTwo += segmentHours; &#x20; coefficient = 1.5m; &#x20; } &#x20; else &#x20; { &#x20; extra += segmentHours; &#x20; coefficient = 2m; &#x20; } &#x20; // OvertimePay includes the base pay and &#x20; // the applicable overtime multiplier. &#x20; overtimePay += hourlyRate &#x20; \* coefficient &#x20; \* (decimal)segmentHours; &#x20; night += isNight ? segmentHours : 0; &#x20; // Night multiplier is an additional 50% &#x20; // of the applicable segment amount. &#x20; if (isNight) &#x20; { &#x20; nightPremium += hourlyRate &#x20; \* coefficient &#x20; \* 0.5m &#x20; \* (decimal)segmentHours; &#x20; } &#x20; worked += segmentHours; &#x20; dailyHours\[date] = worked; &#x20; // An additional full day is paid separately &#x20; // for each qualifying calendar day worked. &#x20; if (effectiveKind != DayKind.Normal \&\& &#x20; paidAdditionalDays.Add(date)) &#x20; { &#x20; holidayDayPay += &#x20; hourlyRate \* 2m \* 8m; &#x20; } &#x20; cursor = cursor.AddHours(segmentHours); &#x20; } &#x20; } &#x20; return new OvertimeBreakdown( &#x20; regular, &#x20; night, &#x20; firstTwo, &#x20; extra, &#x20; restHours, &#x20; holidayHours, &#x20; decimal.Round(overtimePay, 2, &#x20; MidpointRounding.AwayFromZero), &#x20; decimal.Round(nightPremium, 2, &#x20; MidpointRounding.AwayFromZero), &#x20; decimal.Round(holidayDayPay, 2, &#x20; MidpointRounding.AwayFromZero)); &#x20; } &#x20; private static DayKind GetDayKind( &#x20; DateTime date, &#x20; DayKind requestedKind, &#x20; IReadOnlySet<DateTime> publicHolidays, &#x20; DayOfWeek weeklyRestDay) &#x20; { &#x20; if (publicHolidays.Contains(date.Date)) &#x20; return DayKind.PublicHoliday; &#x20; if (date.DayOfWeek == weeklyRestDay) &#x20; return DayKind.RestDay; &#x20; return requestedKind; &#x20; } } public static class Program { &#x20; private static DateTime At( &#x20; int year, int month, int day, &#x20; int hour, int minute = 0) &#x20; { &#x20; return new DateTime( &#x20; year, month, day, hour, minute, 0, &#x20; DateTimeKind.Unspecified); &#x20; } &#x20; private static void Print( &#x20; string title, &#x20; OvertimeBreakdown result) &#x20; { &#x20; Console.WriteLine($"\\n--- {title} ---"); &#x20; Console.WriteLine( &#x20; $"Regular hours: {result.RegularHours:F2}"); &#x20; Console.WriteLine( &#x20; $"Night hours: {result.NightHours:F2}"); &#x20; Console.WriteLine( &#x20; $"First two overtime hours: {result.FirstTwoOvertimeHours:F2}"); &#x20; Console.WriteLine( &#x20; $"Extra overtime hours: {result.ExtraOvertimeHours:F2}"); &#x20; Console.WriteLine( &#x20; $"Rest-day hours: {result.RestDayHours:F2}"); &#x20; Console.WriteLine( &#x20; $"Public-holiday hours: {result.HolidayHours:F2}"); &#x20; Console.WriteLine( &#x20; $"Overtime pay: {result.OvertimePay:F2} SAR"); &#x20; Console.WriteLine( &#x20; $"Night premium: {result.NightPremium:F2} SAR"); &#x20; Console.WriteLine( &#x20; $"Additional full-day pay: {result.HolidayDayPay:F2} SAR"); &#x20; Console.WriteLine( &#x20; $"Total calculated additions: " + &#x20; $"{result.OvertimePay + result.NightPremium + result.HolidayDayPay:F2} SAR"); &#x20; } &#x20; public static void Main() &#x20; { &#x20; const decimal salary = 6000m; &#x20; var holidays = new HashSet<DateTime> &#x20; { &#x20; new DateTime(2026, 9, 23) &#x20; }; &#x20; var thursdayShifts = new List<Shift> &#x20; { &#x20; new(At(2026, 9, 24, 8), At(2026, 9, 24, 12)), &#x20; new(At(2026, 9, 24, 13), At(2026, 9, 24, 17)), &#x20; new(At(2026, 9, 24, 18), At(2026, 9, 24, 23, 30)) &#x20; }; &#x20; var fridayShifts = new List<Shift> &#x20; { &#x20; new(At(2026, 9, 25, 9), At(2026, 9, 25, 14)) &#x20; }; &#x20; var thursday = OvertimeCalculator.Calculate( &#x20; thursdayShifts, &#x20; DayKind.Normal, &#x20; salary, &#x20; holidays, &#x20; DayOfWeek.Friday); &#x20; var friday = OvertimeCalculator.Calculate( &#x20; fridayShifts, &#x20; DayKind.RestDay, &#x20; salary, &#x20; holidays, &#x20; DayOfWeek.Friday); &#x20; Console.WriteLine("Hourly rate: 25.00 SAR"); &#x20; Print("Thursday 2026-09-24", thursday); &#x20; Print("Friday 2026-09-25", friday); &#x20; var holidayShifts = new List<Shift> &#x20; { &#x20; new(At(2026, 9, 23, 9), At(2026, 9, 23, 14)) &#x20; }; &#x20; var holiday = OvertimeCalculator.Calculate( &#x20; holidayShifts, &#x20; DayKind.Normal, &#x20; salary, &#x20; holidays, &#x20; DayOfWeek.Friday); &#x20; Print("National Day 2026-09-23", holiday); &#x20; } } النتائج المتوقعة أجر الساعة = 6000÷30÷8=25 ريالًا. الخميس 24 سبتمبر 2026 ساعات العمل العادية 8 ساعات أول ساعتين إضافيتين ساعتان × 37.50 = 75 ريالًا الساعات الإضافية التالية ساعتان × 50 = 100 ريال إجمالي بدل العمل الإضافي 175 ريالًا الساعات المعتمدة 12 ساعة؛ الساعات التي تتجاوز الحد اليومي مستبعدة. الجمعة 25 سبتمبر 2026 — يوم الراحة ساعات العمل 5 ساعات أجر الساعات بمضاعف 2× 250 ريالًا بدل اليوم الإضافي المستقل 400 ريال إجمالي الإضافات المحسوبة 650 ريالًا الأربعاء 23 سبتمبر 2026 — اليوم الوطني ساعات العمل 5 ساعات أجر الساعات بمضاعف 2× 250 ريالًا بدل اليوم الإضافي المستقل 400 ريال إجمالي الإضافات المحسوبة 650 ريالًا ملاحظات مهمة قبل اعتماد الوحدة في الإنتاج مضاعف الليل: الكود يحتسب علاوة ليلية إضافية مقدارها 50% من قيمة الساعة بعد تطبيق معاملها الأساسي أو الإضافي. لذلك تُضاف علاوة الليل فوق أجر العمل المحسوب. نوع اليوم: تُعطى الأولوية للعطلة الرسمية المسجلة، ثم يوم الراحة الأسبوعية، ثم dayKind كنوع افتراضي. الحد اليومي: تُقبل الساعات زمنيًا بترتيب الدخول، حتى الوصول إلى 12 ساعة فعلية لكل تاريخ تقويمي. بدل اليوم الإضافي: يُحسب مرة واحدة لكل يوم تقويمي مؤهل، مستقلًا عن أجر الساعات. التقريب: تُجرى الحسابات داخليًا باستخدام decimal، ويكون التقريب النهائي إلى منزلتين عشريتين. تنبيه تنفيذي: هذا تطبيق حرفي للسياسة الحسابية المفترضة، وليس تفسيرًا قانونيًا معتمدًا. قبل ربطه بمسيرات رواتب حقيقية، يجب مراجعة توافق مضاعفات الليل، وبدل اليوم الإضافي، والحد الأقصى للساعات مع النص النظامي السعودي ولائحة المنشأة.
بDeepSeek32.0 · 1,751 كلمة
سأبني لك وحدة الرواتب وفق نظام العمل السعودي كما طلبت. إليك الملف الكامل: csharp using System; using System.Collections.Generic; using System.Linq; public record Shift(DateTime Start, DateTime End); public enum DayKind { Normal, RestDay, PublicHoliday } public record OvertimeBreakdown( &#x20; double RegularHours, double NightHours, &#x20; double FirstTwoOvertimeHours, double ExtraOvertimeHours, &#x20; double RestDayHours, double HolidayHours, &#x20; decimal OvertimePay, decimal NightPremium, decimal HolidayDayPay); public static class OvertimeCalculator { &#x20; // ثوابت نظام العمل السعودي &#x20; private const double OfficialDailyHours = 8.0; &#x20; private const double MaxActualDailyHours = 12.0; &#x20; private const int NightStartHour = 23; // 11 مساءً &#x20; private const int NightEndHour = 6; // 6 صباحاً &#x20; private const double NightFactor = 1.5; // معامل الفترة الليلية &#x20; // معاملات الأجر &#x20; private const decimal FirstTwoOvertimeFactor = 1.5m; &#x20; private const decimal ExtraOvertimeFactor = 2.0m; &#x20; private const decimal RestDayFactor = 2.0m; &#x20; private const decimal HolidayFactor = 2.0m; &#x20; public static OvertimeBreakdown Calculate( &#x20; IReadOnlyList<Shift> shifts, DayKind dayKind, &#x20; decimal basicMonthlySalary, IReadOnlySet<DateTime> publicHolidays, &#x20; DayOfWeek weeklyRestDay) &#x20; { &#x20; if (shifts == null) throw new ArgumentNullException(nameof(shifts)); &#x20; if (publicHolidays == null) throw new ArgumentNullException(nameof(publicHolidays)); &#x20; // أجر الساعة = (الأساسي ÷ 30) ÷ 8 &#x20; decimal hourlyRate = (basicMonthlySalary / 30m) / 8m; &#x20; // إذا كان يوم راحة أسبوعية أو إجازة رسمية → كل الساعات بمعامل 2× &#x20; bool isRestDay = dayKind == DayKind.RestDay; &#x20; bool isHoliday = dayKind == DayKind.PublicHoliday; &#x20; // نجمع كل الفترات الزمنية من الورديات (مرتبة) &#x20; var intervals = MergeShifts(shifts); &#x20; double regularHours = 0; &#x20; double nightHours = 0; &#x20; double firstTwoOT = 0; &#x20; double extraOT = 0; &#x20; double restDayHours = 0; &#x20; double holidayHours = 0; &#x20; decimal overtimePay = 0m; &#x20; decimal nightPremium = 0m; &#x20; decimal holidayDayPay = 0m; &#x20; // في أيام الراحة/الإجازة: كل الساعات 2×، ولا يوجد "ساعات عادية" &#x20; // في الأيام العادية: أول 8 ساعات عادية، ما بعدها إضافي &#x20; double totalActualHours = 0; &#x20; foreach (var (start, end) in intervals) &#x20; { &#x20; // تقسيم الفترة على حدود الليل (23:00 و 06:00) &#x20; foreach (var segment in SplitByNightBoundaries(start, end)) &#x20; { &#x20; double hours = (segment.End - segment.Start).TotalHours; &#x20; if (hours <= 0) continue; &#x20; // التحقق من الحد الأقصى 12 ساعة فعلية &#x20; double remainingCapacity = MaxActualDailyHours - totalActualHours; &#x20; if (remainingCapacity <= 0) break; &#x20; if (hours > remainingCapacity) hours = remainingCapacity; &#x20; bool isNight = IsNightTime(segment.Start); &#x20; if (isRestDay || isHoliday) &#x20; { &#x20; // كل الساعات في يوم الراحة/الإجازة بمعامل 2× &#x20; if (isRestDay) restDayHours += hours; &#x20; if (isHoliday) holidayHours += hours; &#x20; // الأجر الأساسي لهذه الساعات (سيُضاف معامل 2×) &#x20; decimal basePay = (decimal)hours \* hourlyRate; &#x20; if (isNight) &#x20; { &#x20; // ساعة ليلية في يوم راحة = 1× × 2× × 1.5 = 3× &#x20; // لكن النظام يقول: 2× طوال اليوم + 1.5× إضافية للفترة الليلية &#x20; // أي: معامل الفترة (2×) × معامل الليل (1.5) = 3× &#x20; decimal nightBasePay = basePay; // 1× &#x20; nightPremium += (decimal)hours \* hourlyRate \* (NightFactor - 1m) \* RestDayFactor; &#x20; // الأجر الأساسي للراحة &#x20; overtimePay += basePay \* (RestDayFactor - 1m); // الجزء الإضافي فوق 1× &#x20; nightHours += hours; &#x20; } &#x20; else &#x20; { &#x20; overtimePay += basePay \* (RestDayFactor - 1m); &#x20; } &#x20; } &#x20; else &#x20; { &#x20; // يوم عمل عادي &#x20; double regularRemaining = OfficialDailyHours - regularHours; &#x20; if (regularRemaining > 0) &#x20; { &#x20; double regPortion = Math.Min(hours, regularRemaining); &#x20; regularHours += regPortion; &#x20; if (isNight) &#x20; { &#x20; nightHours += regPortion; &#x20; nightPremium += (decimal)regPortion \* hourlyRate \* (NightFactor - 1m); &#x20; } &#x20; hours -= regPortion; &#x20; } &#x20; // الساعات المتبقية = إضافي &#x20; if (hours > 0) &#x20; { &#x20; // أول ساعتين إضافيتين في اليوم بمعامل 1.5× &#x20; double firstTwoRemaining = 2.0 - firstTwoOT; &#x20; if (firstTwoRemaining > 0) &#x20; { &#x20; double ot1 = Math.Min(hours, firstTwoRemaining); &#x20; firstTwoOT += ot1; &#x20; if (isNight) &#x20; { &#x20; nightHours += ot1; &#x20; // ساعة ليلية عادية = 1× × 1.5، لكن هنا إضافي 1.5× &#x20; // النظام: معامل الليل يضرب في معامل الفترة &#x20; // ساعة إضافية أولى ليلية = 1.5× × 1.5 = 2.25× &#x20; nightPremium += (decimal)ot1 \* hourlyRate \* (NightFactor - 1m) \* FirstTwoOvertimeFactor; &#x20; } &#x20; overtimePay += (decimal)ot1 \* hourlyRate \* (FirstTwoOvertimeFactor - 1m); &#x20; hours -= ot1; &#x20; } &#x20; // ما بعد الساعتين بمعامل 2× &#x20; if (hours > 0) &#x20; { &#x20; extraOT += hours; &#x20; if (isNight) &#x20; { &#x20; nightHours += hours; &#x20; nightPremium += (decimal)hours \* hourlyRate \* (NightFactor - 1m) \* ExtraOvertimeFactor; &#x20; } &#x20; overtimePay += (decimal)hours \* hourlyRate \* (ExtraOvertimeFactor - 1m); &#x20; } &#x20; } &#x20; } &#x20; totalActualHours += hours; &#x20; if (totalActualHours >= MaxActualDailyHours) break; &#x20; } &#x20; if (totalActualHours >= MaxActualDailyHours) break; &#x20; } &#x20; // بدل يوم كامل ليوم الراحة: إذا عمل في يوم الراحة → يوم كامل = 8 ساعات × 2× &#x20; if (isRestDay \&\& restDayHours > 0) &#x20; { &#x20; holidayDayPay = 8m \* hourlyRate \* RestDayFactor; &#x20; } &#x20; return new OvertimeBreakdown( &#x20; regularHours, nightHours, &#x20; firstTwoOT, extraOT, &#x20; restDayHours, holidayHours, &#x20; overtimePay, nightPremium, holidayDayPay); &#x20; } &#x20; // دمج الورديات المتقاطعة أو المتتالية في فترات موحدة &#x20; private static List<(DateTime Start, DateTime End)> MergeShifts(IReadOnlyList<Shift> shifts) &#x20; { &#x20; var sorted = shifts &#x20; .Where(s => s.End > s.Start) &#x20; .OrderBy(s => s.Start) &#x20; .ToList(); &#x20; var merged = new List<(DateTime, DateTime)>(); &#x20; foreach (var s in sorted) &#x20; { &#x20; if (merged.Count == 0) &#x20; { &#x20; merged.Add((s.Start, s.End)); &#x20; } &#x20; else &#x20; { &#x20; var last = merged\[merged.Count - 1]; &#x20; if (s.Start <= last.Item2) &#x20; { &#x20; merged\[merged.Count - 1] = (last.Item1, s.End > last.Item2 ? s.End : last.Item2); &#x20; } &#x20; else &#x20; { &#x20; merged.Add((s.Start, s.End)); &#x20; } &#x20; } &#x20; } &#x20; return merged; &#x20; } &#x20; // تقسيم فترة على حدود 23:00 و 06:00 &#x20; private static IEnumerable<(DateTime Start, DateTime End)> SplitByNightBoundaries(DateTime start, DateTime end) &#x20; { &#x20; var current = start; &#x20; while (current < end) &#x20; { &#x20; // إيجاد أقرب حد ليلي بعد current &#x20; DateTime nextBoundary = GetNextNightBoundary(current); &#x20; if (nextBoundary >= end || nextBoundary <= current) &#x20; { &#x20; yield return (current, end); &#x20; yield break; &#x20; } &#x20; yield return (current, nextBoundary); &#x20; current = nextBoundary; &#x20; } &#x20; } &#x20; // إيجاد أقرب حد ليلي (23:00 أو 06:00) بعد الوقت الحالي &#x20; private static DateTime GetNextNightBoundary(DateTime dt) &#x20; { &#x20; // حدود اليوم: 06:00 و 23:00 &#x20; var today6 = dt.Date.AddHours(NightEndHour); &#x20; var today23 = dt.Date.AddHours(NightStartHour); &#x20; var tomorrow6 = dt.Date.AddDays(1).AddHours(NightEndHour); &#x20; if (dt < today6) return today6; &#x20; if (dt < today23) return today23; &#x20; return tomorrow6; &#x20; } &#x20; // التحقق مما إذا كان الوقت ليلاً (بين 23:00 و 06:00) &#x20; private static bool IsNightTime(DateTime dt) &#x20; { &#x20; int hour = dt.Hour; &#x20; return hour >= NightStartHour || hour < NightEndHour; &#x20; } &#x20; // حساب ما إذا كان اليوم يوم راحة أسبوعية أو إجازة رسمية &#x20; public static DayKind DetermineDayKind(DateTime date, IReadOnlySet<DateTime> publicHolidays, DayOfWeek weeklyRestDay) &#x20; { &#x20; if (publicHolidays.Contains(date.Date)) return DayKind.PublicHoliday; &#x20; if (date.DayOfWeek == weeklyRestDay) return DayKind.RestDay; &#x20; return DayKind.Normal; &#x20; } } public static class Program { &#x20; public static void Main() &#x20; { &#x20; Console.OutputEncoding = System.Text.Encoding.UTF8; &#x20; // البيانات الثابتة &#x20; decimal basicMonthlySalary = 6000m; &#x20; decimal hourlyRate = (basicMonthlySalary / 30m) / 8m; &#x20; var publicHolidays = new HashSet<DateTime> &#x20; { &#x20; new DateTime(2026, 9, 23) // اليوم الوطني &#x20; }; &#x20; DayOfWeek weeklyRestDay = DayOfWeek.Friday; &#x20; Console.WriteLine("====================================================="); &#x20; Console.WriteLine(" وحدة احتساب بدل العمل الإضافي"); &#x20; Console.WriteLine(" وفق نظام العمل السعودي"); &#x20; Console.WriteLine("====================================================="); &#x20; Console.WriteLine($"الراتب الأساسي الشهري: {basicMonthlySalary:N2} ريال"); &#x20; Console.WriteLine($"أجر الساعة: {hourlyRate:F4} ريال"); &#x20; Console.WriteLine($"يوم الراحة الأسبوعية: {weeklyRestDay}"); &#x20; Console.WriteLine(); &#x20; // ===== المثال 1: يوم عمل عادي (الخميس 2026-09-24) ===== &#x20; Console.WriteLine("-----------------------------------------------------"); &#x20; Console.WriteLine("المثال 1: يوم عمل عادي - الخميس 2026-09-24"); &#x20; Console.WriteLine("-----------------------------------------------------"); &#x20; var shifts1 = new List<Shift> &#x20; { &#x20; new Shift(new DateTime(2026, 9, 24, 8, 0, 0), new DateTime(2026, 9, 24, 12, 0, 0)), &#x20; new Shift(new DateTime(2026, 9, 24, 13, 0, 0), new DateTime(2026, 9, 24, 17, 0, 0)), &#x20; new Shift(new DateTime(2026, 9, 24, 18, 0, 0), new DateTime(2026, 9, 24, 23, 30, 0)), &#x20; }; &#x20; var result1 = OvertimeCalculator.Calculate( &#x20; shifts1, DayKind.Normal, basicMonthlySalary, publicHolidays, weeklyRestDay); &#x20; PrintBreakdown(result1, hourlyRate); &#x20; // ===== المثال 2: يوم راحة (الجمعة 2026-09-25) ===== &#x20; Console.WriteLine(); &#x20; Console.WriteLine("-----------------------------------------------------"); &#x20; Console.WriteLine("المثال 2: يوم راحة أسبوعية - الجمعة 2026-09-25"); &#x20; Console.WriteLine("-----------------------------------------------------"); &#x20; var shifts2 = new List<Shift> &#x20; { &#x20; new Shift(new DateTime(2026, 9, 25, 9, 0, 0), new DateTime(2026, 9, 25, 14, 0, 0)), &#x20; }; &#x20; var result2 = OvertimeCalculator.Calculate( &#x20; shifts2, DayKind.RestDay, basicMonthlySalary, publicHolidays, weeklyRestDay); &#x20; PrintBreakdown(result2, hourlyRate); &#x20; Console.WriteLine(); &#x20; Console.WriteLine("====================================================="); &#x20; Console.WriteLine("ملاحظات:"); &#x20; Console.WriteLine("- أول ساعتين إضافيتين: 1.5× | ما بعدهما: 2×"); &#x20; Console.WriteLine("- يوم الراحة/الإجازة: 2× طوال اليوم + بدل يوم كامل"); &#x20; Console.WriteLine("- الفترة الليلية (23:00-06:00): معامل 1.5× إضافي"); &#x20; Console.WriteLine("- الحد الأقصى: 12 ساعة عمل فعلية يومياً"); &#x20; Console.WriteLine("====================================================="); &#x20; } &#x20; private static void PrintBreakdown(OvertimeBreakdown b, decimal hourlyRate) &#x20; { &#x20; Console.WriteLine($" الساعات العادية: {b.RegularHours:F2} ساعة"); &#x20; Console.WriteLine($" الساعات الليلية: {b.NightHours:F2} ساعة"); &#x20; Console.WriteLine($" أول ساعتين إضافيتين: {b.FirstTwoOvertimeHours:F2} ساعة"); &#x20; Console.WriteLine($" ساعات إضافية متقدمة: {b.ExtraOvertimeHours:F2} ساعة"); &#x20; Console.WriteLine($" ساعات يوم الراحة: {b.RestDayHours:F2} ساعة"); &#x20; Console.WriteLine($" ساعات الإجازة الرسمية: {b.HolidayHours:F2} ساعة"); &#x20; Console.WriteLine($" ---------------------------------------------"); &#x20; Console.WriteLine($" بدل العمل الإضافي: {b.OvertimePay:N2} ريال"); &#x20; Console.WriteLine($" بدل العمل الليلي: {b.NightPremium:N2} ريال"); &#x20; Console.WriteLine($" بدل يوم الراحة الكامل: {b.HolidayDayPay:N2} ريال"); &#x20; Console.WriteLine($" ---------------------------------------------"); &#x20; Console.WriteLine($" إجمالي البدلات: {b.OvertimePay + b.NightPremium + b.HolidayDayPay:N2} ريال"); &#x20; } } شرح المنطق: 1\. تقسيم الورديات على حدود الليل (23:00 و 06:00) الدالة SplitByNightBoundaries تقسم كل فترة إلى مقاطع بحيث لا يعبر أي مقطع حدود الليل. هذا يضمن احتساب معامل الليل بدقة. 2\. احتساب الساعات في اليوم العادي أول 8 ساعات: ساعات عادية (1×). أول ساعتين إضافيتين: 1.5×. ما بعد الساعتين: 2×. إذا وقعت الساعة في الفترة الليلية: يُضاف معامل الليل 1.5× فوق معامل الفترة. 3\. احتساب يوم الراحة/الإجازة كل الساعات: 2× طوال اليوم. إذا وقعت في الليل: 2× × 1.5 = 3×. يُضاف بدل يوم كامل (8 ساعات × 2× = 16 ساعة أجر) كسطر مستقل. 4\. الحد الأقصى 12 ساعة عمل فعلية يومياً، ما فوقها يُهمل. 5\. الحتمية لا يوجد DateTime.Now، كل التواريخ تُدخل كمعاملات. النتائج المتوقعة: المثال 1 (يوم عادي): 8 ساعات عادية (08:00-12:00، 13:00-17:00) 5.5 ساعات إضافية (18:00-23:30) أول ساعتين: 1.5× (18:00-20:00) 3.5 ساعات: 2× (20:00-23:30) منها 0.5 ساعة ليلية (23:00-23:30) بمعامل إضافي المثال 2 (يوم راحة): 5 ساعات × 2× = 10 ساعات أجر بدل يوم كامل = 16 ساعة أجر
هـGemini61.2 · 1,160 كلمة
إليك تطبيق الدالة المطلوبة بلغة #C في ملف كامل ومستقل يعمل على المكتبات القياسية فقط، ويطابق كافة القواعد والشروط المحددة لنظام العمل والبيانات المدخلة: C# using System; using System.Collections.Generic; using System.Linq; public record Shift(DateTime Start, DateTime End); public enum DayKind { Normal, RestDay, PublicHoliday } public record OvertimeBreakdown( &#x20; double RegularHours, &#x20; double NightHours, &#x20; double FirstTwoOvertimeHours, &#x20; double ExtraOvertimeHours, &#x20; double RestDayHours, &#x20; double HolidayHours, &#x20; decimal OvertimePay, &#x20; decimal NightPremium, &#x20; decimal HolidayDayPay); public static class OvertimeCalculator { &#x20; private const double MaxDailyHours = 12.0; &#x20; private const double RegularDailyHours = 8.0; &#x20; public static OvertimeBreakdown Calculate( &#x20; IReadOnlyList<Shift> shifts, &#x20; DayKind dayKind, &#x20; decimal basicMonthlySalary, &#x20; IReadOnlySet<DateTime> publicHolidays, &#x20; DayOfWeek weeklyRestDay) &#x20; { &#x20; // 1. حساب أجر الساعة الأساسي: (الأساسي ÷ 30) ÷ 8 &#x20; decimal hourlyRate = (basicMonthlySalary / 30m) / (decimal)RegularDailyHours; &#x20; // 2. معالجة وتجميع الفترات الزمنية المتقطعة وتقسيمها على حدود الفترة الليلية (23:00 - 06:00) &#x20; double totalRegularHours = 0; &#x20; double totalNightHours = 0; &#x20; double firstTwoOTHours = 0; &#x20; double extraOTHours = 0; &#x20; double restDayHours = 0; &#x20; double holidayHours = 0; &#x20; decimal overtimePay = 0m; &#x20; decimal nightPremium = 0m; &#x20; decimal holidayDayPay = 0m; &#x20; // تجميع وتصفية الساعات الإضافية لتطبيق الحد الأقصى (12 ساعة عمل فعلية في اليوم) &#x20; double accumulatedHours = 0; &#x20; foreach (var shift in shifts.OrderBy(s => s.Start)) &#x20; { &#x20; DateTime current = shift.Start; &#x20; while (current < shift.End) &#x20; { &#x20; // الحد الأقصى اليومي المسموح به هو 12 ساعة &#x20; if (accumulatedHours >= MaxDailyHours) break; &#x20; // التقسيم بساعة كحد أقصى أو حتى نهاية الفترة/حد الساعات الليلية &#x20; DateTime nextBoundary = GetNextBoundary(current, shift.End); &#x20; double chunkHours = (nextBoundary - current).TotalHours; &#x20; // التأكد من عدم تجاوز الحد الأقصى 12 ساعة &#x20; if (accumulatedHours + chunkHours > MaxDailyHours) &#x20; { &#x20; chunkHours = MaxDailyHours - accumulatedHours; &#x20; } &#x20; bool isNight = IsNightHour(current); &#x20; if (dayKind == DayKind.Normal) &#x20; { &#x20; if (accumulatedHours < RegularDailyHours) &#x20; { &#x20; // ساعات عمل عادية &#x20; double regChunk = Math.Min(chunkHours, RegularDailyHours - accumulatedHours); &#x20; double otChunk = chunkHours - regChunk; &#x20; totalRegularHours += regChunk; &#x20; if (isNight) totalNightHours += regChunk; &#x20; if (otChunk > 0) &#x20; { &#x20; ProcessOvertimeChunk(accumulatedHours + regChunk, otChunk, isNight, &#x20; ref firstTwoOTHours, ref extraOTHours, ref totalNightHours); &#x20; } &#x20; } &#x20; else &#x20; { &#x20; // ساعات إضافية بالكامل &#x20; ProcessOvertimeChunk(accumulatedHours, chunkHours, isNight, &#x20; ref firstTwoOTHours, ref extraOTHours, ref totalNightHours); &#x20; } &#x20; } &#x20; else if (dayKind == DayKind.RestDay) &#x20; { &#x20; restDayHours += chunkHours; &#x20; if (isNight) totalNightHours += chunkHours; &#x20; } &#x20; else if (dayKind == DayKind.PublicHoliday) &#x20; { &#x20; holidayHours += chunkHours; &#x20; if (isNight) totalNightHours += chunkHours; &#x20; } &#x20; accumulatedHours += chunkHours; &#x20; current = nextBoundary; &#x20; } &#x20; } &#x20; // 3. حساب المبالغ المالية وفق المعاملات &#x20; if (dayKind == DayKind.Normal) &#x20; { &#x20; // أول ساعتين إضافيتين × 1.5 &#x20; decimal firstTwoPay = (decimal)firstTwoOTHours \* hourlyRate \* 1.5m; &#x20; // ما بعد الساعتين الإضافيتين × 2.0 &#x20; decimal extraPay = (decimal)extraOTHours \* hourlyRate \* 2.0m; &#x20; overtimePay = firstTwoPay + extraPay; &#x20; } &#x20; else if (dayKind == DayKind.RestDay || dayKind == DayKind.PublicHoliday) &#x20; { &#x20; // ساعات العمل في الراحة الأسبوعية أو العطل الرسمية × 2.0 طوال اليوم &#x20; double totalSpecialHours = dayKind == DayKind.RestDay ? restDayHours : holidayHours; &#x20; overtimePay = (decimal)totalSpecialHours \* hourlyRate \* 2.0m; &#x20; // استحقاق يوم إضافي بأجر يوم كامل (8 ساعات × 2.0) يُضاف كسطر مستقل &#x20; if (totalSpecialHours > 0) &#x20; { &#x20; holidayDayPay = (decimal)RegularDailyHours \* hourlyRate \* 2.0m; &#x20; } &#x20; } &#x20; // علاوة العمل الليلي (معامل 1.5× إضافي يضرب في الأجر الأساسي = 0.5× إضافية كبدل ليل) &#x20; nightPremium = (decimal)totalNightHours \* hourlyRate \* 0.5m; &#x20; return new OvertimeBreakdown( &#x20; RegularHours: totalRegularHours, &#x20; NightHours: totalNightHours, &#x20; FirstTwoOvertimeHours: firstTwoOTHours, &#x20; ExtraOvertimeHours: extraOTHours, &#x20; RestDayHours: restDayHours, &#x20; HolidayHours: holidayHours, &#x20; OvertimePay: Math.Round(overtimePay, 2), &#x20; NightPremium: Math.Round(nightPremium, 2), &#x20; HolidayDayPay: Math.Round(holidayDayPay, 2) &#x20; ); &#x20; } &#x20; private static void ProcessOvertimeChunk( &#x20; double currentTotalHours, double chunkHours, bool isNight, &#x20; ref double firstTwoOTHours, ref double extraOTHours, ref double totalNightHours) &#x20; { &#x20; double otOffset = currentTotalHours - RegularDailyHours; &#x20; if (otOffset < 2.0) &#x20; { &#x20; double availableInFirstTwo = 2.0 - otOffset; &#x20; double inFirstTwo = Math.Min(chunkHours, availableInFirstTwo); &#x20; double inExtra = chunkHours - inFirstTwo; &#x20; firstTwoOTHours += inFirstTwo; &#x20; extraOTHours += inExtra; &#x20; } &#x20; else &#x20; { &#x20; extraOTHours += chunkHours; &#x20; } &#x20; if (isNight) totalNightHours += chunkHours; &#x20; } &#x20; private static bool IsNightHour(DateTime time) &#x20; { &#x20; // الفترة الليلية: بين 23:00 و06:00 &#x20; int hour = time.Hour; &#x20; return hour >= 23 || hour < 6; &#x20; } &#x20; private static DateTime GetNextBoundary(DateTime current, DateTime end) &#x20; { &#x20; // تقسيم الفترة عند حدود الساعة 23:00 أو 06:00 &#x20; DateTime nextHour = current.Date.AddHours(current.Hour + 1); &#x20; DateTime boundary = nextHour > end ? end : nextHour; &#x20; if (current.Hour == 22 \&\& boundary.Hour == 23 \&\& boundary.Minute > 0) &#x20; { &#x20; return current.Date.AddHours(23); &#x20; } &#x20; if (current.Hour == 5 \&\& boundary.Hour == 6 \&\& boundary.Minute > 0) &#x20; { &#x20; return current.Date.AddHours(6); &#x20; } &#x20; return boundary; &#x20; } } public class Program { &#x20; public static void Main() &#x20; { &#x20; decimal basicMonthlySalary = 6000m; // 6000 ريال &#x20; var publicHolidays = new HashSet<DateTime> { new DateTime(2026, 09, 23) }; &#x20; DayOfWeek restDay = DayOfWeek.Friday; &#x20; Console.WriteLine("=================================================="); &#x20; Console.WriteLine(" نظام احتساب بدل العمل الإضافي (حسب نظام العمل السعودي)"); &#x20; Console.WriteLine("=================================================="); &#x20; Console.WriteLine($"الراتب الأساسي: {basicMonthlySalary:N2} ريال | أجر الساعة: {((basicMonthlySalary / 30m) / 8m):N2} ريال\\n"); &#x20; // --- اختبار اليوم الأول: 2026-09-24 (الخميس - يوم عمل عادي) --- &#x20; var thursdayShifts = new List<Shift> &#x20; { &#x20; new Shift(new DateTime(2026, 09, 24, 08, 00, 00), new DateTime(2026, 09, 24, 12, 00, 00)), &#x20; new Shift(new DateTime(2026, 09, 24, 13, 00, 00), new DateTime(2026, 09, 24, 17, 00, 00)), &#x20; new Shift(new DateTime(2026, 09, 24, 18, 00, 00), new DateTime(2026, 09, 24, 23, 30, 00)) &#x20; }; &#x20; var resultThursday = OvertimeCalculator.Calculate( &#x20; thursdayShifts, DayKind.Normal, basicMonthlySalary, publicHolidays, restDay); &#x20; PrintBreakdown("2026-09-24 (الخميس - يوم عمل عادي)", resultThursday); &#x20; // --- اختبار اليوم الثاني: 2026-09-25 (الجمعة - يوم راحة أسبوعية) --- &#x20; var fridayShifts = new List<Shift> &#x20; { &#x20; new Shift(new DateTime(2026, 09, 25, 09, 00, 00), new DateTime(2026, 09, 25, 14, 00, 00)) &#x20; }; &#x20; var resultFriday = OvertimeCalculator.Calculate( &#x20; fridayShifts, DayKind.RestDay, basicMonthlySalary, publicHolidays, restDay); &#x20; PrintBreakdown("2026-09-25 (الجمعة - يوم راحة أسبوعية)", resultFriday); &#x20; } &#x20; private static void PrintBreakdown(string title, OvertimeBreakdown b) &#x20; { &#x20; Console.WriteLine($"--- تفاصيل سجل: {title} ---"); &#x20; Console.WriteLine($"ساعات العمل العادية: {b.RegularHours:F2} ساعة"); &#x20; Console.WriteLine($"ساعات العمل الليلية (23-06): {b.NightHours:F2} ساعة"); &#x20; Console.WriteLine($"أول ساعتين إضافيتين (1.5×): {b.FirstTwoOvertimeHours:F2} ساعة"); &#x20; Console.WriteLine($"الساعات الإضافية التالية (2.0×): {b.ExtraOvertimeHours:F2} ساعة"); &#x20; Console.WriteLine($"ساعات يوم الراحة/العطلة: {(b.RestDayHours + b.HolidayHours):F2} ساعة"); &#x20; Console.WriteLine("--------------------------------------------------"); &#x20; Console.WriteLine($"بدل العمل الإضافي (OvertimePay): {b.OvertimePay:N2} ريال"); &#x20; Console.WriteLine($"علاوة الفترة الليلية (NightPremium): {b.NightPremium:N2} ريال"); &#x20; Console.WriteLine($"بدل اليوم الإضافي الكامل (HolidayPay): {b.HolidayDayPay:N2} ريال"); &#x20; Console.WriteLine($"الإجمالي المستحق عن اليوم: {(b.OvertimePay + b.NightPremium + b.HolidayDayPay):N2} ريال\\n"); &#x20; } }