Låsing av perioder for timeregistrering¶
Låsing av perioder hindrer endringer i timeregistreringer som er datert før en valgt dato. Den brukes typisk etter at en periode er kjørt gjennom lønn — da skal ingen kunne flytte timer tilbakevirkende uten at administrator først slipper opp låsen.
Innstillingen ligger som fanen Periode under Innstillinger → Portalinnstillinger og består av to deler:
- Låsedato for timeregistrering (ny, datobasert lås — anbefales)
- Eldre periodestatus (legacy månedsbasert lås — beholdt for kompatibilitet)
Tilgang¶
| Hvem | Hva trengs |
|---|---|
| Rolle | Bruker med modul-tilgang til Portalinnstillinger (new === true på moduleID = 99 i userAccess) |
| Modul | Portalinnstillinger — moduleId: 99 på /settings/portal-setting-ruten (setting-routing.module.ts:30) |
| Effekt på hvem | Alle ansatte og ledere i samme tenant (klient-database) |
Tilgangssjekken er todelt: ModuleGuard blokkerer hele ruten, og komponentens egen checkAdminAccess() mirrorerer samme regel for å skjule UI når brukeren ikke har tilgang (portal-setting.component.ts:197-208).
Datobasert låsing (anbefalt)¶
Den nye mekanismen består av to felt og en lagre-knapp øverst i fanen.
Aktiver låsing av tidsperiode¶
i18n-nøkkel: PortalSetting.enableTimeLock → «Aktiver låsing av tidsperiode»
Hjelpetekst: PortalSetting.timeLockDescription → «Når aktivert kan ansatte ikke registrere eller endre timer før valgt dato»
På/av-bryter (toggle) som styrer om låsedato-feltet og info-boksen vises. Når bryteren slås av, kaller komponenten disableLockDate() som umiddelbart sender lockDate: null til backend og deaktiverer låsing (portal-setting.component.ts:1249-1284).
Standardverdi: Av ({ lockDate: null, isEnabled: false } i komponent-state, portal-setting.component.ts:84). IsEnabled beregnes på backend som LockDate.HasValue (TimeLockSettingsDto.cs:21).
Låsedato¶
i18n-nøkkel: PortalSetting.lockDate → «Låsedato»
Hjelpetekst: PortalSetting.lockDateHelp → «Alle timeregistreringer før denne datoen kan ikke endres»
HTML5 <input type="date">-felt med dynamisk min/maks:
- Min: Dynamisk fra
TimeService.getEarliestTimeDate()(tidligste eksisterende timeregistrering i tenanten). Fallback til2020-01-01hvis kallet feiler (portal-setting.component.ts:85, 1169-1199). - Maks: Dagens dato (
new Date()) (portal-setting.component.ts:86).
Når en gyldig dato er valgt, vises en blå info-boks med teksten «Timeregistreringer før denne datoen er låst: \<dato>» (PortalSetting.lockDateCurrentInfo + datoen formatert som dd.MM.yyyy, portal-setting.component.html:936-940).
Lagre¶
Lagre-knappen øverst (<app-settings-save-bar labelKey="Common.Save">) er disabled så lenge én av disse er sant:
lockDateSettings.isEnableder false, ellerlockDateSettings.lockDateer tom
(portal-setting.component.html:882-886)
Klikk på Lagre kjører updateLockDate(). Datoen konverteres til UTC-midnatt-ISO-streng for å unngå tidssone-shift (eks. "2026-01-04" → "2026-01-04T00:00:00.000Z", portal-setting.component.ts:1269-1275) og sendt til POST /api/PeriodStatus/UpdateLockDate.
Klient-side-validering: Dersom valgt dato er etter i dag, avvises lagringen lokalt med toast PortalSetting.lockDateFutureError → «Låsedato kan ikke være i fremtiden» (portal-setting.component.ts:1206-1220).
Suksess-toast: PortalSetting.lockDateUpdated → «Låsedato oppdatert».
Datalagring¶
Datobasert låsing lagres direkte i tabellen wv_PeriodStatus (klient-database), ikke i wv_SystemConfiguration. Repositoryt skriver via raw SQL mot kolonnene LockDate, UpdatedDate og UpdatedBy på raden for inneværende år (p_Year = DateTime.Now.Year) — INSERT hvis ikke raden finnes, UPDATE hvis den finnes (PeriodStatusRepository.cs:183-240).
Tabellen wv_PeriodStatus har følgende relevante kolonner (dbscript.sql:7226):
p_Year(int, PK)p_m1Active…p_m12Active(bit) — brukes av legacy månedslåsLockDate(date NULL) — ny datobasert låsUpdatedDate(datetime NULL) — settes alltid tilGETDATE()ved skrivUpdatedBy(int NULL) — bruker-ID fra JWT-claims
Backend leser den nyeste raden (ORDER BY p_Year DESC) når UI henter innstillingen (PeriodStatusRepository.cs:144-176).
Backend-validering og bivirkninger¶
I tillegg til klient-side-sjekken validerer PeriodStatusController.UpdateLockDate(...) at datoen ikke er i fremtiden og at bruker-ID kan leses fra JWT. Begge feilbaner returneres som ApiResponse { Success = false, ErrorMessage }:
- «Lock date cannot be in the future» (engelsk, fra backend)
- «Invalid user ID» (engelsk, fra backend)
(PeriodStatusController.cs:130-152)
Når en gyldig låsedato lagres, og JWT-claimen CCNotifyLockedPeriod er "True", trigges en ISY Jobtech-synkronisering for hele perioden fra 1. januar samme år til låsedatoen (PeriodStatusRepository.cs:233-238). URL-en hentes fra claimen CCLockedPeriodNotificationUrl. Bivirkningen kjører kun når begge claims er satt — for tenants uten ISY Jobtech-integrasjon skjer ingenting.
Hvordan låsen håndheves¶
Backend tilbyr endepunktet GET /api/PeriodStatus/IsDateLocked?date=<dato> som returnerer true hvis date <= LockDate. Frontend-kode må kalle dette eksplisitt — det er ingen global middleware som blokkerer POST/PUT mot timeregistreringer.
Konkrete bruksområder:
- Time-registrering (ansatt-skjerm) kaller
TimeService.isDateLocked(date)når datoen endres. Ved lås settesisDateLocked = trueog en gul «Lock»-alert vises med teksten fraTimeRegistration.dateLocked→ «Perioden er låst for endringer til og med \<dato>. Du kan registrere timer fra dagen etter.» (time-registration.component.ts:622-669, time-registration.component.html:113-117). - KAI-time-operasjoner sjekker
IsDateLockedfør AI-assisterte endringer (KAITimeOperationService.cs:68).
Admin-fane (Kontroll av timer) bruker fortsatt den eldre månedsbaserte sjekken — se neste seksjon.
Eldre periodestatus (legacy månedslås)¶
Eldre månedsbasert mekanikk vises som en sammenklappbar seksjon under datolåsen. Den åpnes med knappen «Eldre periodestatus» (PortalSetting.legacyPeriodStatusToggle) som veksler legacyPeriodCollapsed (portal-setting.component.html:947-956).
Når åpen vises:
- En gul advarsel: «OBS: Gammel periodestatus-funksjon — Denne funksjonen er erstattet av ny låsedato-funksjon ovenfor. Bruk låsedato for enklere administrasjon. Tabellen nedenfor beholdes for kompatibilitet.» (
PortalSetting.legacyPeriodStatusWarning+PortalSetting.legacyPeriodStatusInfo). - En smart-grid med en rad per år og 12 sjekkbokser (én per måned) i kolonnene
m1Active…m12Active. Klikk på blyantknappen aktiverer redigering for raden; lagring sender én oppdatering per år viaPOST /api/PeriodStatus/UpdatePeriodStatus(portal-setting.component.html:967-1002, portal-setting.component.ts:1130-1148).
Den eldre månedslåsen er den eneste som faktisk blokkerer admin-import av timer: CheckHoursController.Save slår opp PeriodStatus.Locked per måned og sender systemmelding «Den valgte perioden er låst: \<år> - \<måned>» til godkjennere, men lar admin lagre uansett (CheckHoursController.cs:298-322).
Både GetPeriodStatusList og UpdatePeriodStatus-endepunktene er markert [Obsolete] i koden (PeriodStatusController.cs:35, 55).
Slik setter du en låsedato (datobasert)¶
- Gå til Innstillinger → Portalinnstillinger og velg fanen Periode (URL:
/settings/portal-setting?tab=period). - Slå på bryteren Aktiver låsing av tidsperiode.
- Velg dato i feltet Låsedato. Min-grense styres av første eksisterende timeregistrering i tenanten, maks er i dag.
- Verifiser at den blå info-boksen viser riktig dato: «Timeregistreringer før denne datoen er låst: \<dato>».
- Klikk Lagre øverst. Toast «Låsedato oppdatert» bekrefter at backend har lagret.
Slik slår du av låsing¶
- Slå av bryteren Aktiver låsing av tidsperiode.
- Komponenten kaller umiddelbart
disableLockDate()som senderlockDate: nulltil backend — ingen separat Lagre-klikk er nødvendig (portal-setting.component.ts:1249-1284).
Vanlige problemer¶
«Låsedato kan ikke være i fremtiden»¶
Toast vist når valgt dato er senere enn i dag. Både klient (updateLockDate()) og backend (UpdateLockDate) sjekker dette — meldingen brukeren ser kommer fra klient-sjekken. Velg dagens dato eller en dato i fortiden.
Lagre-knappen er disabled¶
Knappen aktiveres kun når toggle er på og låsedato er satt. Hvis du nettopp slo på bryteren, må du også velge en dato før Lagre blir klikkbar.
Jeg ser ikke fanen «Periode»¶
Du har ikke tilgang til Portalinnstillinger (moduleID = 99 med new = true på din userLevel). Be administrator om å legge til tilgangen via Datatilgang / Brukerrettigheter.
En tidligere låst registrering er plutselig redigerbar¶
Du eller en annen administrator har sannsynligvis flyttet låsedatoen bakover, eller slått av bryteren. Endringen er reversibel — sett bryteren på igjen eller flytt datoen fremover.
Time-registrerings-skjermen viser «Perioden er låst», men admin-sider gjør ikke¶
Den datobaserte låsen håndheves i time-registration.component via et eksplisitt IsDateLocked-kall per valgt dato. Andre flater (admin-import via Kontroll av timer, eldre rapporter) bruker fortsatt den månedsbaserte legacy-sjekken. Hvis du vil sperre admin-import, må du også sette tilsvarende måneder som inaktive i «Eldre periodestatus»-tabellen.







