{"id":42804818,"url":"https://github.com/edunzer/schedulehoursdistributor","last_synced_at":"2026-02-05T18:02:46.529Z","repository":{"id":335397058,"uuid":"948116355","full_name":"edunzer/ScheduleHoursDistributor","owner":"edunzer","description":"A Salesforce Apex class that generates PSA Schedule Exceptions by distributing planned hours across project phases using a bell-curve–style workload, with built-in handling for workdays, holidays, and onsite gaps.","archived":false,"fork":false,"pushed_at":"2026-01-30T01:42:18.000Z","size":65,"stargazers_count":0,"open_issues_count":15,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-30T16:16:11.930Z","etag":null,"topics":["apex","certinia","psa","salesforce"],"latest_commit_sha":null,"homepage":"","language":"Apex","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edunzer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-13T19:22:27.000Z","updated_at":"2026-01-30T01:42:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/edunzer/ScheduleHoursDistributor","commit_stats":null,"previous_names":["edunzer/schedulehoursdistributor"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/edunzer/ScheduleHoursDistributor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edunzer%2FScheduleHoursDistributor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edunzer%2FScheduleHoursDistributor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edunzer%2FScheduleHoursDistributor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edunzer%2FScheduleHoursDistributor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edunzer","download_url":"https://codeload.github.com/edunzer/ScheduleHoursDistributor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edunzer%2FScheduleHoursDistributor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29128621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T17:12:17.649Z","status":"ssl_error","status_checked_at":"2026-02-05T17:11:23.670Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apex","certinia","psa","salesforce"],"created_at":"2026-01-30T03:52:54.367Z","updated_at":"2026-02-05T18:02:46.524Z","avatar_url":"https://github.com/edunzer.png","language":"Apex","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ScheduleHoursDistributor\n\n## What It Is\n\n`ScheduleHoursDistributor` is a Salesforce Apex class that generates PSA (Certinia / FinancialForce) Schedule Exception records by distributing a total number of planned hours across multiple project phases (Cat1, Cat2, Cat3, and optionally Post) using a bell-curve–style schedule.\n\nIts primary purpose is to create a realistic workload pattern where scheduled hours start low, gradually increase to a peak, and then taper off again, rather than being evenly or manually assigned.\n\nThe class is designed to be invoked from Salesforce Flow and automatically handles:\n- Bell-curve–based hour allocation by category\n- Holiday exclusion\n- Workday-based calculations\n- Optional onsite periods with gap handling\n\nThe overall goal is to replace manual schedule exception entry with consistent, rule-driven automation that produces more natural, balanced schedules.\n\n---\n\n## How It Works\n\n1. **Invoked from Flow**\n   - The class exposes an `@InvocableMethod` that accepts a single `ScheduleInput`.\n\n2. **Input Validation**\n   - Validates required fields such as schedule dates, total hours, workdays, category weeks, and percentages.\n   - Ensures onsite dates (if provided) are valid and complete.\n\n3. **Holiday Processing**\n   - Holiday dates are extracted from existing schedule exceptions.\n   - These dates are excluded from hour calculations and exception creation.\n\n4. **Onsite Logic (Optional)**\n   - If an onsite period is detected (\u003c 30 days and within the schedule range):\n     - A full Monday–Sunday onsite gap week is reserved.\n     - The gap week receives a zero-hour schedule exception.\n     - All category allocations exclude the onsite gap.\n     - The Post category is created only when onsite is valid.\n\n5. **Category Range Calculation**\n   - Category durations are converted from weeks to days (`weeks × 7`, decimals truncated).\n   - Categories are calculated backward from the schedule end date.\n   - Each category is trimmed to the overall schedule window.\n   - Categories that do not overlap the schedule are ignored.\n\n6. **Percentage Redistribution**\n   - Only valid categories are included.\n   - Percentages are normalized so the total always equals 100%.\n   - Any unused or excess percentage is redistributed evenly.\n\n7. **Hour Distribution**\n   - Usable weekdays (Mon–Fri) are counted per category.\n   - Holidays and onsite gap days are excluded.\n   - Daily hours are calculated based on category percentage and usable days.\n\n8. **Schedule Exception Creation**\n   - Category ranges are split at holidays and onsite gap dates.\n   - Each uninterrupted segment becomes its own `pse__Schedule_Exception__c` record.\n   - All generated exceptions are returned to Flow.\n\n---\n\n# Key Elements to Know\n\n## Invocable Method\n\n### `ScheduleHoursDistributor.generateCategoryScheduleExceptions`\n\nThis method is designed to be called from **Salesforce Flow** and serves as the single entry point for schedule exception generation.\n\n---\n\n## Input Wrapper: `ScheduleInput`\n\nThe `ScheduleInput` class provides all parameters required for processing.\n\n### Schedule Details\n- `scheduleId`\n- `startDate`\n- `endDate`\n- `numberOfHours`\n- `numberOfWorkDays`\n\n### Category Configuration\n- `cat1Weeks`, `cat2Weeks`, `cat3Weeks`, `postWeeks`\n- `cat1Percentage`, `cat2Percentage`, `cat3Percentage`, `postPercentage`\n\n### Optional Onsite Period\n- `onsiteStartDate`\n- `onsiteEndDate`\n\n### Existing Exceptions\n- `existingExceptions`  \n  Used to identify holiday dates that must be excluded from calculations.\n\n---\n\n## Output Wrapper: `ScheduleExceptionOutputWrapper`\n\nThe method returns a single wrapper containing:\n\n- `scheduleExceptions`  \n  A list of newly generated `pse__Schedule_Exception__c` records.\n\n- `errorMessage`  \n  A descriptive message populated when validation or execution fails.  \n  If successful, this value is `null`.\n\n---\n\n## Category Behavior\n\n### Supported Categories\n- Cat 1\n- Cat 2\n- Cat 3\n- Post (created only when a valid onsite period exists)\n\n### Calculation Rules\n- Category durations are calculated in **days**, not weeks:\n  - `weeks × 7` (decimals truncated)\n- Categories are calculated **backward** from the schedule end date\n- Categories that do not overlap the schedule range are ignored\n\n---\n\n## Hour Allocation Rules\n\n- Hours are assigned only to **weekdays (Monday–Friday)**\n- Holidays and onsite gap days are **always excluded**\n- Category percentages are **normalized** so valid categories total **100%**\n- Daily hours are calculated using **usable workdays only**\n\n---\n\n## Onsite Handling\n\nOnsite is considered valid only if:\n\n- Duration is **less than 30 days**\n- Fully contained within the schedule range\n\n### Onsite Gap Week\n- A full **Monday–Sunday onsite gap week** is reserved\n- The gap week generates a **zero-hour schedule exception**\n- All category allocations **exclude onsite gap days**\n- The **Post** category begins **after the onsite gap week ends**\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedunzer%2Fschedulehoursdistributor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedunzer%2Fschedulehoursdistributor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedunzer%2Fschedulehoursdistributor/lists"}