diff --git a/.github/workflows/dotnet-core-master.yml b/.github/workflows/dotnet-core-master.yml index 9f6d8bcd2..375e8528e 100644 --- a/.github/workflows/dotnet-core-master.yml +++ b/.github/workflows/dotnet-core-master.yml @@ -88,7 +88,7 @@ jobs: strategy: fail-fast: false matrix: - test: [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,b1m,c1m,d1m,e1m,f1m,h1m,i1m,j1m,l1m] + test: [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,b1m,c1m,d1m,e1m,f1m,h1m,i1m,j1m,l1m] steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/dotnet-core-pr.yml b/.github/workflows/dotnet-core-pr.yml index ed34a7e28..917064803 100644 --- a/.github/workflows/dotnet-core-pr.yml +++ b/.github/workflows/dotnet-core-pr.yml @@ -81,7 +81,7 @@ jobs: strategy: fail-fast: false matrix: - test: [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,b1m,c1m,d1m,e1m,f1m,h1m,i1m,j1m,l1m] + test: [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,b1m,c1m,d1m,e1m,f1m,h1m,i1m,j1m,l1m] steps: - uses: actions/checkout@v3 with: diff --git a/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/PlanRegistrationVersionHistoryTests.cs b/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/PlanRegistrationVersionHistoryTests.cs index 7e3dcf51a..c4a1de0d1 100644 --- a/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/PlanRegistrationVersionHistoryTests.cs +++ b/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/PlanRegistrationVersionHistoryTests.cs @@ -290,4 +290,105 @@ public async Task GetVersionHistory_ExcludesGpsCoordinates_WhenGpsDisabled() .Any(v => v.Changes.Any(c => c.FieldType == "gps")); Assert.That(hasGpsChange, Is.False); } + + [Test] + public async Task GetVersionHistory_ReturnsPauseSubSlotChanges_WhenPauseSubSlotFieldsAreUpdated() + { + // Arrange + var assignedSite = new AssignedSiteEntity + { + SiteId = 1, + GpsEnabled = false, + SnapshotEnabled = false, + CreatedByUserId = 1, + UpdatedByUserId = 1 + }; + await assignedSite.Create(TimePlanningPnDbContext); + + var initialPause10StartedAt = new DateTime(2026, 7, 1, 9, 0, 0); + var planRegistration = new PlanRegistration + { + Date = DateTime.Now, + SdkSitId = 1, + Pause10StartedAt = initialPause10StartedAt, + CreatedByUserId = 1, + UpdatedByUserId = 1 + }; + await planRegistration.Create(TimePlanningPnDbContext); + + // Update the pause sub-slot fields + var updatedPause10StartedAt = new DateTime(2026, 7, 1, 9, 15, 0); + var updatedPause11StoppedAt = new DateTime(2026, 7, 1, 12, 30, 0); + planRegistration.Pause10StartedAt = updatedPause10StartedAt; + planRegistration.Pause11StoppedAt = updatedPause11StoppedAt; + planRegistration.UpdatedByUserId = 2; + await planRegistration.Update(TimePlanningPnDbContext); + + // Act + var result = await _planningService.GetVersionHistory(planRegistration.Id); + + // Assert + Assert.That(result.Success, Is.True); + Assert.That(result.Model, Is.Not.Null); + Assert.That(result.Model.Versions.Count, Is.GreaterThan(0)); + + var latestVersion = result.Model.Versions.FirstOrDefault(); + Assert.That(latestVersion, Is.Not.Null); + + var pause10Change = latestVersion.Changes.FirstOrDefault(c => c.FieldName == "Pause10StartedAt"); + Assert.That(pause10Change, Is.Not.Null); + Assert.That(pause10Change.FromValue, Is.EqualTo(initialPause10StartedAt.ToString("yyyy-MM-dd HH:mm:ss.ffffff"))); + Assert.That(pause10Change.ToValue, Is.EqualTo(updatedPause10StartedAt.ToString("yyyy-MM-dd HH:mm:ss.ffffff"))); + + var pause11Change = latestVersion.Changes.FirstOrDefault(c => c.FieldName == "Pause11StoppedAt"); + Assert.That(pause11Change, Is.Not.Null); + Assert.That(pause11Change.FromValue, Is.EqualTo("")); + Assert.That(pause11Change.ToValue, Is.EqualTo(updatedPause11StoppedAt.ToString("yyyy-MM-dd HH:mm:ss.ffffff"))); + } + + [Test] + public async Task GetVersionHistory_ReturnsPauseOverrideMinutesChange_WhenPauseOverrideMinutesIsUpdated() + { + // Arrange + var assignedSite = new AssignedSiteEntity + { + SiteId = 1, + GpsEnabled = false, + SnapshotEnabled = false, + CreatedByUserId = 1, + UpdatedByUserId = 1 + }; + await assignedSite.Create(TimePlanningPnDbContext); + + var planRegistration = new PlanRegistration + { + Date = DateTime.Now, + SdkSitId = 1, + Pause2OverrideMinutes = 15, + CreatedByUserId = 1, + UpdatedByUserId = 1 + }; + await planRegistration.Create(TimePlanningPnDbContext); + + // Update the pause override minutes + planRegistration.Pause2OverrideMinutes = 45; + planRegistration.UpdatedByUserId = 2; + await planRegistration.Update(TimePlanningPnDbContext); + + // Act + var result = await _planningService.GetVersionHistory(planRegistration.Id); + + // Assert + Assert.That(result.Success, Is.True); + Assert.That(result.Model, Is.Not.Null); + Assert.That(result.Model.Versions.Count, Is.GreaterThan(0)); + + var latestVersion = result.Model.Versions.FirstOrDefault(); + Assert.That(latestVersion, Is.Not.Null); + + var overrideChange = latestVersion.Changes.FirstOrDefault(c => c.FieldName == "Pause2OverrideMinutes"); + Assert.That(overrideChange, Is.Not.Null); + Assert.That(overrideChange.FromValue, Is.EqualTo("15")); + Assert.That(overrideChange.ToValue, Is.EqualTo("45")); + } } diff --git a/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/TimePlanningPlanningService/TimePlanningPlanningService.cs b/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/TimePlanningPlanningService/TimePlanningPlanningService.cs index db768ccdd..d5ce2e36b 100644 --- a/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/TimePlanningPlanningService/TimePlanningPlanningService.cs +++ b/eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/TimePlanningPlanningService/TimePlanningPlanningService.cs @@ -2154,12 +2154,68 @@ private List CompareVersions(PlanRegistrationVersion current, PlanR CompareDateTimeField(changes, "Pause1StartedAt", previous?.Pause1StartedAt, current.Pause1StartedAt); CompareDateTimeField(changes, "Pause1StoppedAt", previous?.Pause1StoppedAt, current.Pause1StoppedAt); + // Shift 1 pause sub-slots + CompareDateTimeField(changes, "Pause10StartedAt", previous?.Pause10StartedAt, current.Pause10StartedAt); + CompareDateTimeField(changes, "Pause10StoppedAt", previous?.Pause10StoppedAt, current.Pause10StoppedAt); + CompareDateTimeField(changes, "Pause11StartedAt", previous?.Pause11StartedAt, current.Pause11StartedAt); + CompareDateTimeField(changes, "Pause11StoppedAt", previous?.Pause11StoppedAt, current.Pause11StoppedAt); + CompareDateTimeField(changes, "Pause12StartedAt", previous?.Pause12StartedAt, current.Pause12StartedAt); + CompareDateTimeField(changes, "Pause12StoppedAt", previous?.Pause12StoppedAt, current.Pause12StoppedAt); + CompareDateTimeField(changes, "Pause13StartedAt", previous?.Pause13StartedAt, current.Pause13StartedAt); + CompareDateTimeField(changes, "Pause13StoppedAt", previous?.Pause13StoppedAt, current.Pause13StoppedAt); + CompareDateTimeField(changes, "Pause14StartedAt", previous?.Pause14StartedAt, current.Pause14StartedAt); + CompareDateTimeField(changes, "Pause14StoppedAt", previous?.Pause14StoppedAt, current.Pause14StoppedAt); + CompareDateTimeField(changes, "Pause15StartedAt", previous?.Pause15StartedAt, current.Pause15StartedAt); + CompareDateTimeField(changes, "Pause15StoppedAt", previous?.Pause15StoppedAt, current.Pause15StoppedAt); + CompareDateTimeField(changes, "Pause16StartedAt", previous?.Pause16StartedAt, current.Pause16StartedAt); + CompareDateTimeField(changes, "Pause16StoppedAt", previous?.Pause16StoppedAt, current.Pause16StoppedAt); + CompareDateTimeField(changes, "Pause17StartedAt", previous?.Pause17StartedAt, current.Pause17StartedAt); + CompareDateTimeField(changes, "Pause17StoppedAt", previous?.Pause17StoppedAt, current.Pause17StoppedAt); + CompareDateTimeField(changes, "Pause18StartedAt", previous?.Pause18StartedAt, current.Pause18StartedAt); + CompareDateTimeField(changes, "Pause18StoppedAt", previous?.Pause18StoppedAt, current.Pause18StoppedAt); + CompareDateTimeField(changes, "Pause19StartedAt", previous?.Pause19StartedAt, current.Pause19StartedAt); + CompareDateTimeField(changes, "Pause19StoppedAt", previous?.Pause19StoppedAt, current.Pause19StoppedAt); + CompareDateTimeField(changes, "Pause100StartedAt", previous?.Pause100StartedAt, current.Pause100StartedAt); + CompareDateTimeField(changes, "Pause100StoppedAt", previous?.Pause100StoppedAt, current.Pause100StoppedAt); + CompareDateTimeField(changes, "Pause101StartedAt", previous?.Pause101StartedAt, current.Pause101StartedAt); + CompareDateTimeField(changes, "Pause101StoppedAt", previous?.Pause101StoppedAt, current.Pause101StoppedAt); + CompareDateTimeField(changes, "Pause102StartedAt", previous?.Pause102StartedAt, current.Pause102StartedAt); + CompareDateTimeField(changes, "Pause102StoppedAt", previous?.Pause102StoppedAt, current.Pause102StoppedAt); + // Shift 2 CompareDateTimeField(changes, "Start2StartedAt", previous?.Start2StartedAt, current.Start2StartedAt); CompareDateTimeField(changes, "Stop2StoppedAt", previous?.Stop2StoppedAt, current.Stop2StoppedAt); CompareDateTimeField(changes, "Pause2StartedAt", previous?.Pause2StartedAt, current.Pause2StartedAt); CompareDateTimeField(changes, "Pause2StoppedAt", previous?.Pause2StoppedAt, current.Pause2StoppedAt); + // Shift 2 pause sub-slots + CompareDateTimeField(changes, "Pause20StartedAt", previous?.Pause20StartedAt, current.Pause20StartedAt); + CompareDateTimeField(changes, "Pause20StoppedAt", previous?.Pause20StoppedAt, current.Pause20StoppedAt); + CompareDateTimeField(changes, "Pause21StartedAt", previous?.Pause21StartedAt, current.Pause21StartedAt); + CompareDateTimeField(changes, "Pause21StoppedAt", previous?.Pause21StoppedAt, current.Pause21StoppedAt); + CompareDateTimeField(changes, "Pause22StartedAt", previous?.Pause22StartedAt, current.Pause22StartedAt); + CompareDateTimeField(changes, "Pause22StoppedAt", previous?.Pause22StoppedAt, current.Pause22StoppedAt); + CompareDateTimeField(changes, "Pause23StartedAt", previous?.Pause23StartedAt, current.Pause23StartedAt); + CompareDateTimeField(changes, "Pause23StoppedAt", previous?.Pause23StoppedAt, current.Pause23StoppedAt); + CompareDateTimeField(changes, "Pause24StartedAt", previous?.Pause24StartedAt, current.Pause24StartedAt); + CompareDateTimeField(changes, "Pause24StoppedAt", previous?.Pause24StoppedAt, current.Pause24StoppedAt); + CompareDateTimeField(changes, "Pause25StartedAt", previous?.Pause25StartedAt, current.Pause25StartedAt); + CompareDateTimeField(changes, "Pause25StoppedAt", previous?.Pause25StoppedAt, current.Pause25StoppedAt); + CompareDateTimeField(changes, "Pause26StartedAt", previous?.Pause26StartedAt, current.Pause26StartedAt); + CompareDateTimeField(changes, "Pause26StoppedAt", previous?.Pause26StoppedAt, current.Pause26StoppedAt); + CompareDateTimeField(changes, "Pause27StartedAt", previous?.Pause27StartedAt, current.Pause27StartedAt); + CompareDateTimeField(changes, "Pause27StoppedAt", previous?.Pause27StoppedAt, current.Pause27StoppedAt); + CompareDateTimeField(changes, "Pause28StartedAt", previous?.Pause28StartedAt, current.Pause28StartedAt); + CompareDateTimeField(changes, "Pause28StoppedAt", previous?.Pause28StoppedAt, current.Pause28StoppedAt); + CompareDateTimeField(changes, "Pause29StartedAt", previous?.Pause29StartedAt, current.Pause29StartedAt); + CompareDateTimeField(changes, "Pause29StoppedAt", previous?.Pause29StoppedAt, current.Pause29StoppedAt); + CompareDateTimeField(changes, "Pause200StartedAt", previous?.Pause200StartedAt, current.Pause200StartedAt); + CompareDateTimeField(changes, "Pause200StoppedAt", previous?.Pause200StoppedAt, current.Pause200StoppedAt); + CompareDateTimeField(changes, "Pause201StartedAt", previous?.Pause201StartedAt, current.Pause201StartedAt); + CompareDateTimeField(changes, "Pause201StoppedAt", previous?.Pause201StoppedAt, current.Pause201StoppedAt); + CompareDateTimeField(changes, "Pause202StartedAt", previous?.Pause202StartedAt, current.Pause202StartedAt); + CompareDateTimeField(changes, "Pause202StoppedAt", previous?.Pause202StoppedAt, current.Pause202StoppedAt); + // Shift 3 CompareDateTimeField(changes, "Start3StartedAt", previous?.Start3StartedAt, current.Start3StartedAt); CompareDateTimeField(changes, "Stop3StoppedAt", previous?.Stop3StoppedAt, current.Stop3StoppedAt); @@ -2178,6 +2234,13 @@ private List CompareVersions(PlanRegistrationVersion current, PlanR CompareDateTimeField(changes, "Pause5StartedAt", previous?.Pause5StartedAt, current.Pause5StartedAt); CompareDateTimeField(changes, "Pause5StoppedAt", previous?.Pause5StoppedAt, current.Pause5StoppedAt); + // Pause override minutes (admin override of a shift's pause total) + CompareField(changes, "Pause1OverrideMinutes", previous?.Pause1OverrideMinutes.ToString(), current.Pause1OverrideMinutes.ToString()); + CompareField(changes, "Pause2OverrideMinutes", previous?.Pause2OverrideMinutes.ToString(), current.Pause2OverrideMinutes.ToString()); + CompareField(changes, "Pause3OverrideMinutes", previous?.Pause3OverrideMinutes.ToString(), current.Pause3OverrideMinutes.ToString()); + CompareField(changes, "Pause4OverrideMinutes", previous?.Pause4OverrideMinutes.ToString(), current.Pause4OverrideMinutes.ToString()); + CompareField(changes, "Pause5OverrideMinutes", previous?.Pause5OverrideMinutes.ToString(), current.Pause5OverrideMinutes.ToString()); + return changes; } diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/r/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/r/activate-plugin.spec.ts new file mode 100644 index 000000000..05e9ede65 --- /dev/null +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/r/activate-plugin.spec.ts @@ -0,0 +1,47 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../../../Page objects/Login.page'; +import { PluginPage } from '../../../Page objects/Plugin.page'; + +test.describe('Enable Backend Config plugin', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:4200'); + await new LoginPage(page).login(); + await new PluginPage(page).Navbar.goToPluginsPage(); + }); + + test('should enabled Time registration plugin', async ({ page }) => { + test.setTimeout(180000); + const pluginName = 'Microting Time Planning Plugin'; + + // Open action menu for the plugin + await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() + .locator('#actionMenu').click(); + await page.waitForTimeout(500); + + // Click the status button inside the menu to enable the plugin + await page.locator('#plugin-status-button0').scrollIntoViewIfNeeded(); + await expect(page.locator('#plugin-status-button0')).toBeVisible(); + await page.locator('#plugin-status-button0').click(); + await page.waitForTimeout(500); + + // Confirm activation in the modal if present + if (await page.locator('#pluginOKBtn').count() > 0) { + await page.locator('#pluginOKBtn').click(); + await page.waitForTimeout(100000); // Wait for plugin activation + } + + await page.goto('http://localhost:4200'); + await new LoginPage(page).login(); + await new PluginPage(page).Navbar.goToPluginsPage(); + + // Verify the plugin is enabled by checking the status + // Re-open the action menu to check the status + await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() + .locator('#actionMenu').click(); + await page.waitForTimeout(500); + + await expect( + page.locator('#plugin-status-button0').locator('mat-icon') + ).toContainText('toggle_on'); // plugin is enabled + }); +}); diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/r/assert-true.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/r/assert-true.spec.ts new file mode 100644 index 000000000..a14cb84a5 --- /dev/null +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/r/assert-true.spec.ts @@ -0,0 +1,6 @@ +import { test, expect } from '@playwright/test'; + +// create canary in a coal mine test asserting true +test('asserts true', () => { + expect(true).toBe(true); // this will pass +}); diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/r/dashboard-version-history.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/r/dashboard-version-history.spec.ts new file mode 100644 index 000000000..ef83c61f9 --- /dev/null +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/r/dashboard-version-history.spec.ts @@ -0,0 +1,194 @@ +import { test, expect, Page } from '@playwright/test'; +import { LoginPage } from '../../../Page objects/Login.page'; + +/** + * r shard: version-history ("Aktivitetslog") modal + human-readable value + * formatting. This lane exists so the spec never shares dashboard state with + * other lanes' cumulative edit chains; like lane q it runs only the + * activate-plugin + assert-true bootstraps before this file (workers=1, + * alphabetical file order), on the shared 'a' seed (the shard has no seed of + * its own, so CI falls back to a/'s SQL). + * + * The workday edit dialog exposes an admin-only history icon-button which + * opens VersionHistoryModalComponent — a day-grouped timeline where the + * backend diffs PlanRegistrationVersion rows into FieldChange lists and the + * frontend renders values human-readably: + * - Planned(Start|End|Break)OfShift1-5 / Pause1-5OverrideMinutes are stored + * as integer minutes and must render HH:mm (420 -> "07:00", 900 -> "15:00", + * 60 -> "01:00") — never the raw integer. + * - Each event line reads "