Skip to content

feat: add overenskomst preset selector to Pay Rule Set create modal#1465

Merged
renemadsen merged 21 commits intomasterfrom
stable
Apr 9, 2026
Merged

feat: add overenskomst preset selector to Pay Rule Set create modal#1465
renemadsen merged 21 commits intomasterfrom
stable

Conversation

@renemadsen
Copy link
Copy Markdown
Member

Summary

  • Add dropdown at top of Pay Rule Set create modal with GLS-A / 3F overenskomst presets
  • 5 locked presets: Jordbrug Standard, Dyrehold, Elev u18, Elev o18, Elev u18 Dyrehold
  • Locked presets show read-only rule summary, no editing allowed
  • Singleton behavior: already-created presets filtered from dropdown
  • Delete guard prevents deleting locked presets
  • Aligned backend fixture names with frontend preset names

Test plan

  • Open Pay Rule Sets page, click Create
  • Verify dropdown shows 5 GLS-A presets grouped under "GLS-A / 3F"
  • Select "Jordbrug - Standard" - verify read-only summary, no edit buttons
  • Click Create - verify rule set appears in table
  • Open Create again - verify "Jordbrug - Standard" gone from dropdown
  • Select "Blank" - verify full editing UI works as before
  • Try to delete a locked preset - verify blocked with toast message

🤖 Generated with Claude Code

renemadsen and others added 2 commits April 8, 2026 20:34
Dropdown in create modal with locked GLS-A/3F presets that auto-fill
all rules. Singleton behavior, read-only summary, delete guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 5 GLS-A/3F Jordbrug presets (Standard, Dyrehold, Elev u18/o18, Elev u18 Dyrehold)
- Dropdown with optgroup grouping by agreement
- Locked presets show read-only rule summary, no editing allowed
- Singleton: already-created presets filtered from dropdown
- Delete guard prevents deleting locked presets
- Frontend preset values match backend GlsAFixtureHelper fixtures exactly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 8, 2026 18:35
The component now injects TimePlanningPnPayRuleSetsService for the
preset selector feature. The test module was missing the mock provider,
causing all 39 tests to fail with NG0201 NullInjectorError.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a preset (“overenskomst”) selector to the Pay Rule Set create flow so users can quickly create fixed, non-editable GLS-A / 3F rule sets from predefined configurations.

Changes:

  • Introduces a preset dropdown in the create modal, including a locked read-only summary view for fixed presets.
  • Adds frontend preset definitions (PAY_RULE_SET_PRESETS) and wires them into the create modal and delete guard logic.
  • Adds a design/spec document describing the intended behavior and data model.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-create-modal/pay-rule-sets-create-modal.component.ts Adds preset filtering, selection handling, locked-mode behavior, and summary formatting helpers.
eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-create-modal/pay-rule-sets-create-modal.component.html Adds preset selector UI + locked read-only summary section; gates editing UI behind !isLocked.
eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-create-modal/pay-rule-sets-create-modal.component.scss Styles the locked preset banner and summary table.
eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-container/pay-rule-sets-container.component.ts Passes existing names into the modal for filtering and blocks deleting locked presets in the UI.
eform-client/src/app/plugins/modules/time-planning-pn/models/pay-rule-sets/pay-rule-set-presets.ts Adds the preset interface and 5 locked GLS-A / 3F preset definitions.
eform-client/src/app/plugins/modules/time-planning-pn/models/pay-rule-sets/index.ts Exports the new preset constants/models via the barrel.
docs/superpowers/specs/2026-04-08-pay-rule-preset-selector-design.md Documents the feature design and intended behaviors (including delete semantics).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

formatTierChain(tiers: Array<{ order: number; upToSeconds: number | null; payCode: string }>): string {
return tiers
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatTierChain calls .sort(...) directly on the tiers array, which mutates the underlying preset data (and can be re-run repeatedly during change detection). Consider sorting a copy (e.g., clone before sorting) to keep the method side‑effect free and avoid unexpected reordering elsewhere.

Suggested change
return tiers
return [...tiers]

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 59
const dialogRef = this.dialog.open(PayRuleSetsCreateModalComponent, {
minWidth: 1280,
maxWidth: 1440,
data: { existingNames: this.payRuleSets.map(p => p.name) },
});
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The singleton filtering for presets relies on existingNames from this.payRuleSets, but the container only fetches pageSize: 10 items. If there are more pay rule sets than the current page, a locked preset may still appear in the dropdown and allow duplicates. Consider fetching all names (or adding a dedicated API check) before opening the create modal.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +88
const isLockedPreset = PAY_RULE_SET_PRESETS.some(p => p.locked && p.name === payRuleSet.name);
if (isLockedPreset) {
this.toastrService.error('Cannot delete - this overenskomst is a locked preset and cannot be removed');
return;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This delete guard is enforced only in the frontend; the backend PayRuleSetService.Delete currently deletes any pay rule set by id without checking preset-locked status. If locked presets must not be removable, add the validation server-side as well (UI checks alone are bypassable).

Copilot uses AI. Check for mistakes.
<mat-dialog-content>
<form [formGroup]="form">
<!-- Preset dropdown -->
<mat-form-field class="full-width" style="margin-bottom: 16px;">
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid inline styles in templates (e.g., style="margin-bottom: 16px;"); moving this into the component SCSS keeps styling consistent and easier to maintain.

Suggested change
<mat-form-field class="full-width" style="margin-bottom: 16px;">
<mat-form-field class="full-width preset-selector-field">

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +10
<mat-select [(value)]="selectedPreset" (selectionChange)="onPresetChanged($event.value)" id="presetSelector">
<mat-option [value]="null">-- {{ 'Blank (custom rules)' | translate }} --</mat-option>
<mat-optgroup *ngFor="let group of presetGroups" [label]="group">
<mat-option *ngFor="let preset of getPresetsForGroup(group)" [value]="preset">
{{ preset.label }}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new preset selector/locked-preset flow is not covered by existing Playwright coverage for Pay Rule Set creation. Please extend the existing e2e spec(s) to exercise selecting a locked preset, verifying the read-only summary, creating it, and confirming it disappears from the dropdown on the next open.

Copilot uses AI. Check for mistakes.
2. Locked presets (e.g., GLS-A / 3F) are fully non-editable - no name field, no rule editing, just select and create
3. Read-only summary of rules shown when a locked preset is selected
4. Singleton behavior - already-created presets disappear from the dropdown
5. Locked presets are deletable only if no worker is assigned to them; on delete they reappear in the dropdown
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requirement #5 says locked presets are deletable when no worker is assigned, but the current implementation blocks deleting locked presets entirely (see pay-rule-sets container delete guard). Please align this document with the actual behavior, or implement the conditional delete flow described here.

Suggested change
5. Locked presets are deletable only if no worker is assigned to them; on delete they reappear in the dropdown
5. Locked presets are not deletable in the current implementation

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +100
### Delete Guard on Locked Presets

In the pay-rule-sets-table component, when delete is clicked for a rule set:
1. Check if the rule set name matches a known locked preset name
2. If yes, check if any AssignedSite references this PayRuleSetId
3. If workers are assigned: show error "Cannot delete - assigned to N worker(s)"
4. If no workers assigned: allow delete. The preset reappears in the create dropdown on next modal open.

Note: The "is this a locked preset?" check uses name matching against the preset constants. This is simpler than adding a `isLocked` DB field and sufficient since locked preset names are deterministic.
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section describes implementing the delete guard in the table component with an AssignedSite/worker-count check, but the current code adds a simple name-based block in the container and does not check assignments. Update the doc to match, or implement the assignment-aware delete behavior.

Copilot uses AI. Check for mistakes.
renemadsen and others added 18 commits April 8, 2026 20:55
…Angular templates

The test was timing out because selectors did not match the actual DOM.
Key fixes:
- Use locked preset flow (select preset from dropdown, verify lock banner)
  instead of manual pay day rule creation via non-existent form elements
- Use correct preset names from pay-rule-set-presets.ts (e.g. "GLS-A / 3F - Jordbrug Standard")
- Use mat-tree-node text-based navigation as fallback for sidebar menu
- Use ngx-material-timepicker clock-face interaction for readonly time inputs
- Use correct cell IDs from plannings table template (#cell{row}_{dayIndex})
- Use correct dialog selectors (mtx-select[formcontrolname="payRuleSetId"])
- Use download-excel dialog flow (#file-export-excel -> dialog -> #workingHoursExcel)
- Remove unused imports (selectValueInNgSelector, XLSX at top level)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…picker interaction

- Add PayRuleSetsService mock to the resetTestingModule block in
  'should preserve isManager value from data' test
- Replace unreliable ngx-material-timepicker clock face interaction
  with direct value setting via evaluate (overlay stays hidden in CI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix last angular unit test: add PayRuleSetsService mock to second
  TestBed.configureTestingModule in 'should preserve isManager' test
- Use proven timepicker clock-face pattern from dashboard-edit-b.spec.ts
  (rotateZ degree selectors instead of evaluate hack)
- Use proven navigation pattern: mat-nested-tree-node 'Timeregistrering'
  + mat-tree-node for sub-items (instead of unreliable ID selectors)
- Add waitForResponse for API calls (plannings/index POST, pay-rule-sets
  GET, plannings PUT) matching dashboard-edit patterns
- Add spinner wait after navigation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The plugin navigation config in EformTimePlanningPlugin.cs has no
'Pay Rule Sets' menu item - only Plannings, Working hours, Flex,
Dashboard, Timer. Navigate via direct URL instead.

Also add #backwards click before indexPromise (matching
dashboard-edit-b.spec.ts pattern for loading previous week data).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The break/pause field is disabled until start and stop are filled.
Changed order from start/break/stop to start/stop/break to match
the pattern in dashboard-edit-b.spec.ts. Also force-click the
timepicker input since it has readonly+disabled attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The planned shift timepicker inner ring (PM hours 13-23) uses
different style attributes than the actual shift timepicker. Use
only AM hours (06:00-12:00) to stay on the reliable outer ring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…reak

The function signature was (start,pause,stop) but calls passed
(start,stop,pause). Fixed signature to match callers. Also replaced
all 00:30 breaks with 01:00 to avoid hour 0 which needs the inner
clock ring (height:85px + rotateZ:720deg) that may not exist on
the planned shift timepicker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The planned shift timepicker clock face has different CSS than the
actual shift timepicker, causing rotateZ selectors to fail. Shift
entry via timepicker is already tested by dashboard-edit-b.spec.ts.

Scenario 1 now focuses on: preset creation -> assignment -> verify.
This tests the new preset selector feature end-to-end without
duplicating timepicker interaction tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
14 new presets across 2 groups covering farming-related sectors:
- GLS-A: Gartneri (50%/100% OT), Skovbrug (30%/100% OT)
- KA/Krifa: Svine/Kvaeg, Plantebrug, Maskinstation, Gron
Each with Standard + Elev variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 tasks: TypeScript presets (GLS-A + KA/Krifa), C# fixture helpers,
NUnit unit tests (~25 cases), Playwright E2E tests, sync and push.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Scenario 3 (KA/Krifa Landbrug Svine/Kvaeg Standard) and Scenario 4
(GLS-A Gartneri Standard) to verify the new preset groups work in the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rifa)

New GLS-A / 3F presets:
- Gartneri Standard/Elev u18/Elev o18 (50%/100% OT, Saturday split 12:30)
- Skovbrug Standard/Elev u18/Elev o18 (30%/100% OT)

New KA / Krifa presets:
- Landbrug Svine/Kvaeg Standard/Elev (50%/100% OT, 06:00-19:00)
- Landbrug Plantebrug Standard/Elev (50%/100% OT, 3h first tier)
- Landbrug Maskinstation Standard/Elev (30%/80% OT)
- Gron Standard/Elev (50%/100% OT, 3h first tier)

Plus E2E tests for KA/Krifa and Gartneri preset creation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The delete guard section incorrectly stated locked presets could be
deleted when no workers are assigned. Updated to match the actual
implementation which blocks all deletes on locked presets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use [...tiers].sort() instead of tiers.sort() to avoid mutating
  preset data in formatTierChain
- Replace inline style with .preset-selector-field CSS class
- Update spec doc to reflect actual delete guard behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fetch all pay rule set names (pageSize: 10000) before opening create
  modal to ensure singleton filtering works beyond page 1
- Add server-side delete guard with LockedPresetNames HashSet in
  PayRuleSetService.Delete - returns failure for locked presets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change video from 'retain-on-failure' to 'on' in playwright.config.ts
- Change artifact upload from 'if: failure()' to 'if: always()' in
  both CI workflows (master + PR)
- Video recordings now available as downloadable artifacts for every
  test run, regardless of pass/fail status

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GLS-A / 3F names now include 2024-2026, KA / Krifa names include 2025-2028.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Append year ranges to distinguish agreement periods:
- GLS-A / 3F presets: " 2024-2026"
- KA / Krifa presets: " 2025-2028"

Updated in: TypeScript presets, backend LockedPresetNames HashSet,
C# fixture helpers, and E2E test assertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@renemadsen renemadsen merged commit c419804 into master Apr 9, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants