VACATION SYSTEM IMPLEMENTATION PLAN =================================== Purpose ------- Implement these three supporting tables and their administration views: 1. week_days 2. official_holidays 3. vacation_request_days Then replace the current request/balance behavior with a request lifecycle based on generated request-day records, balance reservation on approval, daily balance consumption, and automatic archiving. The three supporting tables must be completed before enabling the new request lifecycle. The lifecycle should not be deployed partially because the current request creation and approval logic conflicts with the new reservation model. PHASE 1 - DATABASE FOUNDATIONS ============================== 1. WEEK_DAYS TABLE ------------------ Enable these migrations: - 2026_07_09_094221_create_week_days_table.php - 2026_07_09_100006_fill_week_days_table.php The IDs must remain aligned with Carbon dayOfWeek values: - 0: Sunday, الأحد, working day by default - 1: Monday, الاثنين, working day by default - 2: Tuesday, الثلاثاء, working day by default - 3: Wednesday, الأربعاء, working day by default - 4: Thursday, الخميس, working day by default - 5: Friday, الجمعة, weekend by default - 6: Saturday, السبت, weekend by default Rules: - Exactly seven weekday records should exist. - The ID must be an integer between 0 and 6. - sort_order must be unique and between 1 and 7. - A weekday referenced by vacation_request_days cannot be deleted. - Normally weekday records should only be updated, not created or deleted. - Changes to weekend configuration affect newly generated request days only. - Approved or processed requests must not be changed retroactively. 2. OFFICIAL_HOLIDAYS TABLE -------------------------- Enable the official_holidays migration. Existing fields: - id - name - starts_on - ends_on - vacation_year_id, nullable - type: official, religious, national, emergency, administrative - affects_vacation_calculation - affects_attendance_calculation - status: enabled or disabled - notes - created_by - updated_by - timestamps - soft deletes Validation rules: - name is required and must be a valid string. - starts_on is required and must be a valid date. - ends_on is required and must be after or equal to starts_on. - vacation_year_id is nullable but must reference an existing vacation year. - type must be one of the supported enum values. - affects_vacation_calculation must be boolean. - affects_attendance_calculation must be boolean. - status must be enabled or disabled. - Prevent accidental duplicate holidays for the same vacation year and period. Holiday matching rule for a request day: - status is enabled. - starts_on is before or equal to the request-day date. - ends_on is after or equal to the request-day date. - vacation_year_id matches the request vacation year, or vacation_year_id is null for a global holiday. If affects_vacation_calculation is true, the holiday day does not consume vacation balance. 3. VACATION_REQUEST_DAYS TABLE ------------------------------ Enable the vacation_request_days migration, but update its schema before running it. Existing fields: - id - vacation_request_id - date - week_day_id - is_weekend - is_holiday - official_holiday_id - day_value - is_deducted - notes - timestamps - soft deletes Recommended changes before migration: 1. Add a unique index on: vacation_request_id + date 2. Rename is_deducted to: deducts_balance This field means that the day should consume vacation balance. It must not be used as the scheduler processing flag. 3. Add these nullable timestamps: - reserved_at - processed_at - released_at State meanings: - No state timestamp: generated request day, but the request is not approved. - reserved_at set: approved day currently included in reserved balance. - processed_at set: the reserved day was moved into used balance. - released_at set: the reservation was released after cancellation. Recommended indexes: - date - vacation_request_id + date, unique - reserved_at + processed_at + released_at - official_holiday_id - week_day_id PHASE 2 - MODELS AND RELATIONSHIPS ================================== WeekDay model: - Cast id to integer. - Cast sort_order to integer. - Cast is_weekend to boolean. - Keep the vacationRequestDays relationship. - Add an ordered scope using sort_order. OfficialHoliday model: - Keep existing date and boolean casts. - Add scopes such as: - enabled() - forVacationYear() - coveringDate() - affectingVacationCalculation() VacationRequestDay model: - Cast date to date. - Cast week_day_id to integer. - Cast is_weekend and is_holiday to boolean. - Cast deducts_balance to boolean. - Cast day_value to decimal. - Cast reserved_at, processed_at, and released_at to datetime. - Keep relationships to VacationRequest, WeekDay, and OfficialHoliday. Add helper methods: - isPending(): bool - isReserved(): bool - isProcessed(): bool - isReleased(): bool Relationships: - VacationRequest hasMany VacationRequestDay. - VacationRequestDay belongsTo VacationRequest. - VacationRequestDay belongsTo WeekDay. - VacationRequestDay belongsTo OfficialHoliday. PHASE 3 - CRUD VIEWS ==================== All CRUD pages should: - Use Livewire full-page components. - Follow the existing HR module conventions. - Use the general input, select, textarea, switch, table, and card components. - Display Arabic validation errors. - Use permission checks in the UI and server actions. - Use pagination where needed. - Use confirmation dialogs before deletion. - Use three form fields per row on desktop where appropriate. 1. WEEK DAYS CRUD ----------------- Routes: - hr.week-days.index - hr.week-days.create Submenu item: - أيام الأسبوع Index columns: - ID - Day name - Weekend badge - Sort order - Actions Create/update fields: - Day ID - Arabic name - Weekend switch - Sort order Domain restrictions: - Create is available only when one of IDs 0 through 6 is missing. - Delete is blocked if the day is referenced. - Normally deletion should not be used for the fixed seven days. - Changing weekend configuration should show a warning that approved requests are not recalculated. 2. OFFICIAL HOLIDAYS CRUD ------------------------- Routes: - hr.official-holidays.index - hr.official-holidays.create Submenu item: - العطلات الرسمية Index filters: - Vacation-year tabs - Holiday name - Holiday type - Status - Date range Index columns: - Name - Start date - End date - Vacation year or all years - Holiday type - Affects vacation calculation badge - Affects attendance calculation badge - Status badge - Actions Create/update fields: - Name - Start date - End date - Vacation year - Holiday type - Affects vacation calculation - Affects attendance calculation - Status - Notes using the general textarea component Holiday update behavior: - Submitted and under-review requests may be regenerated. - Approved, processed, rejected, cancelled, and archived requests remain frozen. - Optionally provide an explicit action to recalculate affected editable requests. 3. VACATION REQUEST DAYS CRUD ----------------------------- Routes: - hr.vacation-request-days.index - hr.vacation-request-days.create Submenu item: - أيام طلبات الإجازة Also add a "تفاصيل الأيام" action to each vacation request row. Index filters: - Vacation-year tabs - Request number - Employee name, national ID, or phone - Vacation type - Date range - Weekend or holiday - Deducts balance - Pending, reserved, processed, or released state Index columns: - Request number - Employee - Date - Weekday - Weekend badge - Holiday badge and holiday name - Day value - Deducts balance - Processing state - Notes - Actions CRUD restrictions: - Request and date can only be changed before approval. - week_day_id, is_weekend, is_holiday, and official_holiday_id should normally be calculated by the server. - An administrator may correct day_value, deducts_balance, and notes before approval. - Processed days cannot be edited or deleted. - Deleting an editable day must recalculate request working_days. - Add an "إعادة احتساب الأيام" action for submitted and under-review requests. PHASE 4 - REQUEST-DAY GENERATION ================================ Create a dedicated class such as: VacationRequestDayGenerator Responsibilities: 1. Iterate from request start_date through end_date using CarbonPeriod. 2. Resolve week_day_id from Carbon dayOfWeek. 3. Read the current is_weekend setting from week_days. 4. Find an enabled official holiday covering that date. 5. Determine whether the date consumes vacation balance. 6. Create one unique vacation_request_days row for that date. 7. Calculate the request working_days from eligible rows. 8. Update vacation_requests.working_days. Default calculation: Weekend: - day_value = 0 - deducts_balance = false Official holiday affecting vacation calculation: - day_value = 0 - deducts_balance = false Normal working day: - day_value = 1 - deducts_balance = true Half-day support can later use: - day_value = 0.5 - deducts_balance = true The request working_days form input should become readonly or be removed. working_days must be calculated from vacation_request_days and must not be trusted from user input. PHASE 5 - SERVICE ARCHITECTURE ============================== Move workflow logic out of the Livewire pages. 1. VacationRequestDayGenerator - Generate request days. - Regenerate request days while editable. - Calculate working_days. - Apply weekday and holiday rules. 2. VacationBalanceService Suggested methods: - reserve(VacationRequest $request): void - consumeDay(VacationRequestDay $day): void - release(VacationRequest $request): void All balance operations must: - Use database transactions. - Lock the balance row using lockForUpdate(). - Use the exact request-day totals. - Never reset all reserved_days to zero. - Preserve reservations from other requests. - Keep reserved_days out of the remaining_days formula. 3. VacationRequestWorkflowService Suggested methods: - submit() - markUnderReview() - approve() - reject() - cancel() - archive() Livewire actions should call this service instead of changing request or balance columns directly. PHASE 6 - REQUEST LIFECYCLE =========================== Main lifecycle: Create request -> Generate request days -> Submitted -> Under review -> Approved -> Reserve eligible days -> Daily processing -> Archived Alternative endings: - Submitted or under review -> Rejected - Submitted or under review -> Cancelled - Approved -> Cancelled, with only unprocessed reservations released 1. CREATE TO SUBMITTED ---------------------- - Validate the request. - Create the request. - Generate vacation_request_days. - Calculate working_days from eligible request days. - Do not change employee balances. - Set status to submitted. - Set approval_status to pending. - Set submitted_at to the current timestamp. This replaces the current behavior that changes reserved balance during request creation. 2. SUBMITTED TO UNDER REVIEW ---------------------------- - Printing or opening the request for formal review changes the status to under_review. - No balance changes. - The request may still be returned for corrections if that workflow is enabled. 3. SUBMITTED OR UNDER REVIEW TO REJECTED ----------------------------------------- - Set status to rejected. - Set approval_status to rejected. - Set rejected_at. - No balance changes because the request was never approved. 4. SUBMITTED OR UNDER REVIEW TO CANCELLED ------------------------------------------ - Set status to cancelled. - Set cancelled_at. - No balance changes because nothing was reserved. 5. UNDER REVIEW TO APPROVED ---------------------------- Approval must run in one database transaction: 1. Lock the vacation request. 2. Lock its eligible request-day rows. 3. Lock the matching employee balance. 4. Calculate requested days: SUM(day_value) WHERE deducts_balance = true AND processed_at IS NULL AND released_at IS NULL 5. Calculate available balance: available_days = remaining_days - reserved_days 6. Reject approval if requested_days is greater than available_days. 7. Reject approval if the employee has another approved request overlapping the same eligible dates. 8. Update the balance: reserved_days += requested_days 9. Mark eligible request days: reserved_at = now 10. Update the request: status = approved approval_status = approved approved_at = now Do not update used_days or remaining_days at approval. PHASE 7 - DAILY SCHEDULED PROCESSING ==================================== Create an Artisan command: hr:process-vacation-days Suggested location: Modules/Hr/app/Console/Commands/ProcessVacationRequestDaysCommand.php Register it through the HR service provider. Schedule it from routes/console.php: Schedule::command('hr:process-vacation-days') ->dailyAt('00:10') ->withoutOverlapping() ->onOneServer(); The command should process completed days using: - request status is approved - request-day date is before today - deducts_balance is true - reserved_at is not null - processed_at is null - released_at is null Using date before today is safer than date equal to today because the system processes the vacation day after it has finished. For every eligible day, run one transaction: 1. Lock the request-day row. 2. Lock the request. 3. Lock the employee balance. 4. Update the balance: reserved_days -= day_value used_days += day_value remaining_days = opening_balance - (used_days + adjusted_days) 5. Mark the request day: processed_at = now The command must process records in chunks to avoid memory problems. Idempotency requirement: - A request day with processed_at must never be processed again. - Running the command multiple times must not deduct the same day twice. PHASE 8 - AUTOMATIC ARCHIVING ============================= After processing request days, find approved requests where: - end_date is before today. - No eligible request day remains unprocessed or unreleased. Then update: - status = archived Weekend and holiday rows that do not deduct balance must not block archiving. PHASE 9 - CANCELLATION AFTER APPROVAL ===================================== Approved cancellation must distinguish processed and unprocessed days. In one transaction: 1. Lock the request. 2. Lock the employee balance. 3. Lock the request-day rows. 4. Sum only days where: reserved_at IS NOT NULL processed_at IS NULL released_at IS NULL 5. Update: balance.reserved_days -= unprocessed_total request_days.released_at = now request.status = cancelled request.cancelled_at = now Days already processed remain in used_days. They must not be automatically returned. PHASE 10 - EDITING AND DELETION RULES ====================================== Submitted or under-review request: - Allow changing employee, vacation type, dates, and other editable fields. - Regenerate request days after changes. - Recalculate working_days. Approved request: - Block normal editing. - Require cancellation or a dedicated amendment workflow. Archived, rejected, or cancelled request: - Display as readonly. Deletion rules: - Submitted request may be soft-deleted with confirmation. - Under-review deletion requires explicit permission. - Approved request cannot be deleted; it must be cancelled. - Archived request cannot be deleted. - Soft-deleting an editable request must also soft-delete its request-day rows. PHASE 11 - NAVIGATION AND PERMISSIONS ===================================== Add these submenu items under الإجازات: - أيام الأسبوع - العطلات الرسمية - أيام طلبات الإجازة Update the Vacation menu permission list and active-route conditions. Use these permissions: - week_days.view - week_days.create - week_days.edit - week_days.delete - week_days.activate - official_holidays.view - official_holidays.create - official_holidays.edit - official_holidays.delete - official_holidays.activate - vacation_request_days.view - vacation_request_days.create - vacation_request_days.edit - vacation_request_days.delete - vacation_request_days.activate Request-day modification permissions should be limited to authorized HR administrators because these rows directly control employee balances. PHASE 12 - EXISTING DATA RECONCILIATION ======================================= Existing requests and balances must be reconciled because the current logic modifies reserved and used balances differently. Create a one-time reconciliation command with preview and apply modes. The command should: 1. Generate request-day rows for existing requests. 2. Produce a difference report before changing balances. 3. Classify existing requests. Submitted or under-review requests: - Generate days. - No reserved balance. - No used balance change. Approved requests with future days: - Future eligible days become reserved. Approved requests with completed and future days: - Completed eligible days become used. - Future eligible days become reserved. Approved requests whose end date passed: - Eligible days become used. - Request becomes archived after all days are processed. Rejected or cancelled requests: - No active reservation. Do not blindly reset every employee balance. Existing used_days may contain legacy or manually imported values. The reconciliation command must show proposed changes before applying them. PHASE 13 - IMPLEMENTATION ORDER =============================== 1. Modify and enable the week_days migration. 2. Enable the weekday seed migration. 3. Enable the official_holidays migration. 4. Modify and enable the vacation_request_days migration. 5. Migrate the three tables. 6. Update models, casts, relationships, and scopes. 7. Build Week Days CRUD. 8. Build Official Holidays CRUD. 9. Build Vacation Request Days management views. 10. Add submenu items and permissions. 11. Implement VacationRequestDayGenerator. 12. Remove manual working_days input or make it readonly. 13. Remove current balance effects from request creation. 14. Implement request approval reservation. 15. Implement rejection and cancellation rules. 16. Implement the daily processing command. 17. Implement automatic request archiving. 18. Add request-day details and regeneration actions. 19. Build the existing-data reconciliation preview. 20. Review reconciliation differences. 21. Apply reconciliation after approval. 22. Verify the lifecycle manually. MANUAL VERIFICATION CHECKLIST ============================= 1. Create a request containing normal days, a weekend, and an official holiday. 2. Confirm that one request-day row is created per calendar date. 3. Confirm that working_days equals only eligible day values. 4. Confirm that creation does not change balance. 5. Approve the request and confirm only reserved_days changes. 6. Confirm approval is blocked if remaining_days - reserved_days is insufficient. 7. Run the daily command after a completed eligible day. 8. Confirm reserved_days decreases and used_days increases by the same day value. 9. Confirm remaining_days ignores reserved_days. 10. Run the command again and confirm the day is not processed twice. 11. Cancel an approved future request and confirm only its reservation is released. 12. Cancel a partially processed request and confirm used days remain used. 13. Confirm the request becomes archived only after all eligible days are processed. 14. Confirm weekend and holiday rows do not block archiving. 15. Confirm approved, archived, rejected, and cancelled request days cannot be edited improperly. POLICY DECISIONS TO CONFIRM BEFORE IMPLEMENTATION ================================================= 1. Confirm that Friday and Saturday are the default weekend. 2. Confirm that every vacation type excludes weekends and official holidays. 3. If some vacation types use calendar days, add a calculation_basis setting to vacation_types before implementing the generator. 4. Confirm whether half-day requests are required. 5. Confirm that completed days are processed after midnight using date before today. 6. Confirm who may manually correct generated request-day rows.