Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -370,22 +370,21 @@ test.describe('Dashboard — multi-shift (3-5) round-trip regression guard', ()
});

/**
* Phase 4 — second-precision DISPLAY: when a row's site has
* `useOneMinuteIntervals = true` AND the planning has a precise
* `start1StartedAt` stamp (e.g. 07:03:53), the plannings-table cell must
* render the stamp at HH:mm:ss instead of the legacy HH:mm.
* Plannings-table display contract: the cell always renders the actual
* shift stamp as `HH:mm`, even when `AssignedSite.UseOneMinuteIntervals`
* is on. Seconds are never shown regardless of the flag.
*
* Server-side seeding `AssignedSite.UseOneMinuteIntervals = true` plus
* a planning with `Start1StartedAt = 2026-05-15 07:03:53` requires DB
* fixture work the CI playwright shard doesn't yet wire up (the tests
* here log in as admin and rely on the default seed). Captured here as
* a TODO so the assertion shape survives any future fixture work; the
* Phase 4 jest unit test on `formatStamp(...)` covers the contract for
* the merge-blocking path.
* Server-side seeding of `AssignedSite.UseOneMinuteIntervals = true`
* plus a planning row with `Start1StartedAt = 2026-05-15 07:03:53`
* requires DB fixture work the CI playwright shard doesn't yet wire up
* (the tests here log in as admin and rely on the default seed).
* Captured here as a TODO so the assertion shape survives any future
* fixture work; the jest unit test on `formatStamp(...)` covers the
* format-helper contract for the merge-blocking path.
*/
test.skip('plannings-table renders HH:mm:ss for actual stamp when site flag is on', async ({ page }) => {
// TODO(phase 4 fixture): seed AssignedSite.UseOneMinuteIntervals = true
// for the worker referenced by #cell3_0 AND a PlanRegistration row with
test.skip('plannings-table renders HH:mm for actual stamp when site flag is on', async ({ page }) => {
// TODO(fixture): seed AssignedSite.UseOneMinuteIntervals = true for the
// worker referenced by #cell3_0 AND a PlanRegistration row with
// Start1StartedAt = '2026-05-15T07:03:53Z' on a date that lands inside
// the dashboard's default visible range.
//
Expand All @@ -400,12 +399,12 @@ test.describe('Dashboard — multi-shift (3-5) round-trip regression guard', ()
//
// // The first-shift actual line is rendered with id firstShiftActual{rowIdx}_{colField}.
// const firstShiftActual = page.locator('[id^="firstShiftActual"]').first();
// await expect(firstShiftActual).toContainText('07:03:53');
// // Negative guard — the legacy 5-min path would render '07:00' / '07:05' instead.
// await expect(firstShiftActual).not.toContainText(/07:0[05]\s/);
// await expect(firstShiftActual).toContainText('07:03');
// // Negative guard — seconds are never displayed even when the flag is on.
// await expect(firstShiftActual).not.toContainText('07:03:53');
//
// Until the fixture lands the unit test
// `formatStamp (Phase 4) — uses HH:mm:ss format when row.useOneMinuteIntervals is true`
// `formatStamp — uses HH:mm format when row.useOneMinuteIntervals is true`
// covers the format-helper contract (eform-client/src/app/plugins/modules/time-planning-pn/
// components/plannings/time-plannings-table/time-plannings-table.component.spec.ts).
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,10 @@ describe('TimePlanningsTableComponent', () => {
});

// ------------------------------------------------------------------
// Phase 4 — second-precision display when UseOneMinuteIntervals is on
// formatStamp / getStopTimeDisplayWithSeconds — always HH:mm
// (`useOneMinuteIntervals` retained on the row but no longer affects display)
// ------------------------------------------------------------------
describe('formatStamp (Phase 4)', () => {
describe('formatStamp', () => {
it('returns empty string when value is falsy', () => {
expect(component.formatStamp({ useOneMinuteIntervals: true }, null)).toBe('');
expect(component.formatStamp({ useOneMinuteIntervals: true }, undefined as any)).toBe('');
Expand All @@ -397,16 +398,16 @@ describe('TimePlanningsTableComponent', () => {
expect(result).toBe('07:03');
});

it("uses HH:mm:ss format when row.useOneMinuteIntervals is true", () => {
it("uses HH:mm format when row.useOneMinuteIntervals is true", () => {
const transformSpy = jest
.spyOn(component['datePipe'], 'transform')
.mockReturnValue('07:03:53');
.mockReturnValue('07:03');
const result = component.formatStamp(
{ useOneMinuteIntervals: true },
'2026-05-15T07:03:53Z',
);
expect(transformSpy).toHaveBeenCalledWith('2026-05-15T07:03:53Z', 'HH:mm:ss', 'UTC');
expect(result).toBe('07:03:53');
expect(transformSpy).toHaveBeenCalledWith('2026-05-15T07:03:53Z', 'HH:mm', 'UTC');
expect(result).toBe('07:03');
});

it("falls back to HH:mm when row is null/undefined (defensive)", () => {
Expand All @@ -419,7 +420,7 @@ describe('TimePlanningsTableComponent', () => {
});
});

describe('getStopTimeDisplayWithSeconds (Phase 4)', () => {
describe('getStopTimeDisplayWithSeconds', () => {
it('returns empty string when either timestamp is falsy', () => {
expect(
component.getStopTimeDisplayWithSeconds({ useOneMinuteIntervals: true }, null, '2026-05-15T10:00:00Z'),
Expand Down Expand Up @@ -452,21 +453,22 @@ describe('TimePlanningsTableComponent', () => {
expect(result).toBe('15:30');
});

it("uses HH:mm:ss format when flag on", () => {
it("uses HH:mm format when flag on", () => {
const transformSpy = jest
.spyOn(component['datePipe'], 'transform')
.mockReturnValue('15:30:11');
.mockReturnValue('15:30');
const result = component.getStopTimeDisplayWithSeconds(
{ useOneMinuteIntervals: true },
'2026-05-15T07:00:00Z',
'2026-05-15T15:30:11Z',
);
expect(transformSpy).toHaveBeenCalledWith('2026-05-15T15:30:11Z', 'HH:mm:ss', 'UTC');
expect(result).toBe('15:30:11');
expect(transformSpy).toHaveBeenCalledWith('2026-05-15T15:30:11Z', 'HH:mm', 'UTC');
expect(result).toBe('15:30');
});
});

describe('convertHoursToTimeWithSeconds (Phase 4)', () => {
// Dormant helper — production display no longer uses seconds.
describe('convertHoursToTimeWithSeconds', () => {
it('formats whole-hour values with seconds suffix', () => {
expect(component.convertHoursToTimeWithSeconds(8)).toBe('08:00:00');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,24 +291,22 @@ export class TimePlanningsTableComponent implements OnInit, OnChanges, AfterView
}

/**
* Phase 4 second-precision sibling of the inline `datePipe.transform(..., 'HH:mm', 'UTC')`
* calls used in the plannings table. When the row's site has
* <c>useOneMinuteIntervals</c> on, the actual stamp cells render at
* <c>HH:mm:ss</c>; otherwise falls through to the legacy <c>HH:mm</c>
* format so flag-off rows are byte-identical to before.
* Formats an actual shift timestamp for display in the plannings table.
* Always renders as `HH:mm`; seconds are never displayed regardless of
* the row's `useOneMinuteIntervals` flag. The `row` parameter is kept
* for call-site stability.
*/
formatStamp(row: any, value: string | null | undefined): string {
if (!value) {
return '';
}
const fmt = row?.useOneMinuteIntervals ? 'HH:mm:ss' : 'HH:mm';
return this.datePipe.transform(value, fmt, 'UTC') ?? '';
return this.datePipe.transform(value, 'HH:mm', 'UTC') ?? '';
}

/**
* Phase 4 second-precision sibling of {@link getStopTimeDisplay}. Same
* cross-day "24:00" guard as the legacy method, but emits seconds in the
* normal case when the row's site has the flag on.
* Stop-time display sibling of {@link getStopTimeDisplay} with the same
* cross-day "24:00" guard. Always renders as `HH:mm`; the `WithSeconds`
* suffix is a historical name — seconds are not emitted.
*/
getStopTimeDisplayWithSeconds(row: any, startedAt: string | null, stoppedAt: string | null): string {
if (!startedAt || !stoppedAt) {
Expand All @@ -323,8 +321,7 @@ export class TimePlanningsTableComponent implements OnInit, OnChanges, AfterView
) {
return '24:00';
}
const fmt = row?.useOneMinuteIntervals ? 'HH:mm:ss' : 'HH:mm';
return this.datePipe.transform(stoppedAt, fmt, 'UTC') ?? '';
return this.datePipe.transform(stoppedAt, 'HH:mm', 'UTC') ?? '';
}

onFirstColumnClick(row: any): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export class TimePlanningModel {
deviceManufacturer: string;
softwareVersionIsValid: boolean;
/**
* Phase 4: mirror of the row's assigned-site UseOneMinuteIntervals flag.
* When true, the plannings table renders actual stamps at HH:mm:ss instead
* of HH:mm. Default false preserves byte-identical legacy behavior for
* rows whose site has the flag off.
* Mirror of the row's assigned-site UseOneMinuteIntervals flag. The flag now
* controls only (a) the wire-precision of stored start/stop timestamps and
* (b) the minutesGap input granularity in the workday-entity dialog (1-min
* vs 5-min). Displayed timestamps in the plannings table always show
* HH:mm regardless of the flag.
*/
useOneMinuteIntervals: boolean;
}
Loading