Kompletna specyfikacja techniczno-biznesowa. Dokument opisuje wszystkie przypadki, w których system zatrzymuje, dzieli lub koryguje naliczanie czasu pracy, wraz z przykładami end-to-end.
Dokument opisuje zachowanie mechanizmów ochronnych (time guards), które automatycznie kończą lub dzielą wpis czasu pracy w sytuacjach granicznych: uśpienie/hibernacja komputera, skoki zegara systemowego oraz przekroczenie granicy doby.
| Pojęcie | Znaczenie operacyjne |
|---|---|
LastKnownGoodUtc |
Ostatni bezpieczny punkt czasu (heartbeat), na którym można wiarygodnie zamknąć wpis. |
Resume |
Moment wybudzenia systemu po śnie/hibernacji. |
Dual-entry |
Strategia zapisu dwóch wpisów: (1) czas pracy do snu, (2) czas snu jako nieaktywność. |
Midnight Guard |
Mechanizm zamykania/rozgałęziania wpisów na granicy doby (23:59:59 / 00:00:00). |
FinishStatus używane w tej specyfikacji| ID | Nazwa enum | Znaczenie biznesowe |
|---|---|---|
| 2 | AutoFinishAfterAppRestart |
Automatyczne domknięcie lokalnego aktywnego wpisu po restarcie aplikacji (gdy wpis nie był powiązany z aktywną sesją serwera). |
| 4 | AutoFinishDueToAppUpdate |
Techniczne domknięcie wpisu przy aktualizacji aplikacji lub przy porządkowaniu duplikatów aktywnych wpisów. |
| 3 | AutoFinishAfterSystemRestart |
Fallback/watchdog domykający wpis po granicy doby, gdy standardowe mechanizmy midnight/hibernacji nie zadziałały. |
| 1 | FinishByUser |
Ręczne zakończenie przez użytkownika (stosowane w docelowym wariancie „NIE” w midnight guard). |
| 5 | AutoFinishDueToUserInactivity |
Automatyczne zakończenie jako nieaktywność (m.in. drugi wpis dual-entry). |
| 10 | AutoFinishDueToClocksDifferences |
Auto-zatrzymanie po wykryciu anomalii zegara systemowego. |
| 11 | AutoFinishDueToHibernation |
Auto-zatrzymanie po wykryciu uśpienia/hibernacji. |
| 12 | AutoFinishDueToMidnightCrossing |
Zamknięcie wpisu o 23:59:59 na granicy doby. |
| 69 | FinishedOrBlockedOnServerBackend |
Status techniczny związany z synchronizacją backend/local; wykorzystywany podczas rozstrzygania konfliktów stanu. |
appsettings.json:| Klucz konfiguracji | Wartość domyślna | Rola biznesowa |
|---|---|---|
HibernationHeuristicThresholdSeconds |
120 (sekund, 2 minuty) | Minimalny czas odjazdu zegara, poniżej którego zdarzenie jest traktowane jako szum. |
ClockDiscrepancyThresholdMinutes |
5 (minut) | Maksymalna dopuszczalna rozbieżność zegarów przed uznaniem anomalii. |
HibernationGuardSkipDelegation |
true | Czy zadania oznaczone jako delegacja mają być pomijane przez guard hibernacji. |
MidnightGuardPromptMinutes |
30 (minut) | Aktywny offset pokazywania pytania o kontynuację przed północą. |
MidnightGuardAutoContinueIfStartedWithinMinutes |
15 (minut) | Jeśli zadanie wystartowało w tym progu przed 00:00, dialog jest pomijany i następuje auto-kontynuacja. |
LongWorkingSessionLimitHours |
4 (godziny) | Limit ciągłej sesji dla LongWorkingSessionGuard, liczony również przez północ po potwierdzeniu kontynuacji. |
HibernationDualEntryEnabled (feature flag) |
false (lub true) | Przełącznik aktywujący drugi wpis nieaktywności po krótkiej hibernacji. |
HibernationDualEntryMaxDurationMinutes (feature flag) |
60 (minut) | Maksymalna długość hibernacji kwalifikująca się do zapisu wpisu nr 2. |
Diagnostyka: dla ścieżki dual-entry używany jest znacznik logów HIBERNATION_DUAL_ENTRY.
W trybie bazowym system zawsze zapisuje pojedynczy wpis roboczy zakończony na
ostatnim bezpiecznym momencie sprzed anomalii (heartbeat / LastKnownGoodUtc).
| Krok | Przedział czasu | Status zapisu (FinishStatus) | Zachowanie systemu |
|---|---|---|---|
| Wpis 1 | Start zadania → Ostatni znany czas (LastKnownGoodUtc) |
11: AutoFinishDueToHibernation (lub 10: AutoFinishDueToClocksDifferences) |
Zapis do SQLite i synchronizacja z API. Tylko czas realnie przepracowany. |
| Czas uśpienia | Ostatni znany czas → Czas wybudzenia |
Brak wpisu | Czas ten przepada. Zadanie nie wznawia się automatycznie po wybudzeniu. |
Dla krótkich hibernacji (od progu reakcji HibernationHeuristicThresholdSeconds, domyślnie 120s,
do progu maksymalnego HibernationDualEntryMaxDurationMinutes, domyślnie 60 min) system rejestruje
dwa rozłączne wpisy, aby czas uśpienia pracownika był odnotowany w systemie jako nieaktywność.
Ścieżka jest sterowana przełącznikiem HibernationDualEntryEnabled i może być natychmiast wyłączona.
Poniżej HibernationHeuristicThresholdSeconds. Zdarzenie traktowane jako szum/czas zbyt krótki. Brak dual-entry.
Wpis 1: Start → sen (Status 11)
Wpis 2: sen → wybudzenie (Status 5: Inactivity)
Tylko Wpis 1 (Status 11). Okres snu powyżej HibernationDualEntryMaxDurationMinutes nie jest zapisywany jako osobny wpis nieaktywności.
Uwaga implementacyjna: dual-entry dotyczy wyłącznie ścieżki AutoFinishDueToHibernation.
Dla anomalii zegara (AutoFinishDueToClocksDifferences) system nie tworzy drugiego wpisu.
W logach używany jest znacznik: HIBERNATION_DUAL_ENTRY.
| Wpis | Początek | Koniec | Status (FinishStatus) | Opis biznesowy |
|---|---|---|---|---|
| Wpis 1 | Start zadania | Ostatni czas aktywności (przed uśpieniem) | 11: AutoFinishDueToHibernation | Rzeczywisty czas przepracowany przez pracownika przed odejściem od komputera. |
| Wpis 2 | Koniec Wpisu 1 (moment uśpienia) | Czas wybudzenia (Resume) | 5: AutoFinishDueToUserInactivity | Zarejestrowany czas nieobecności pracownika z automatycznym statusem nieaktywności. |
Jeśli drugi wpis nieaktywności (status 5) obejmuje granicę doby, system dzieli go na dwa rekordy dzienne:
... → 23:59:59 oraz 00:00:00 → ....
Dzięki temu pojedynczy rekord nie przechodzi przez północ.
Poniżej opis zachowania, które obecnie wynika bezpośrednio z implementacji i powinno być traktowane jako „as-is”.
Użytkownik uruchomił zadanie np. o 23:50.
• Brak okna dialogowego (nie przeszkadzamy).
• O 23:59:59 wpis jest zamykany (Status 12).
• O 00:00:00 automatycznie startuje kontynuacja.
O 23:45 pojawia się okno z pytaniem i odliczaniem do północy.
• Odpowiedź „TAK”: o 00:00:00 zadanie biegnie dalej w nowym wpisie.
• Odpowiedź „NIE”: natychmiastowe zakończenie wpisu w chwili kliknięcia.
• Status końca: 1: FinishByUser.
Jeśli użytkownik odszedł od PC i minęła północ (lub komputer zasnął w trakcie okna):
• Zadanie zostaje definitywnie zatrzymane o 23:59:59.
• Nowy wpis po północy NIE powstaje.
• Flaga zakończenia: 5: AutoFinishDueToUserInactivity.
Poniższe reguły opisują stan wdrożony, który jednoznacznie oddziela decyzję użytkownika od automatycznych zakończeń.
• O 23:59:59 zamykany jest wpis dnia poprzedniego (Status 12).
• O 00:00:00 tworzy się nowy wpis kontynuacji.
• Zakończenie następuje natychmiast po kliknięciu.
• Status końca: 1: FinishByUser.
• O 23:59:59 zadanie zamykane automatycznie.
• Status końca: 5: AutoFinishDueToUserInactivity.
• Brak wpisu kontynuacji po północy.
Okno pre-midnight jest sterowane wyłącznie przez MidnightGuardPromptMinutes (aktualnie 30 minut).
| Wpis | Przedział | Status |
|---|---|---|
| Wpis 1 | 10:00 → 12:00 | 11 |
| Wpis 2 | 12:00 → 12:20 | 5 |
| Wpis | Przedział | Status |
|---|---|---|
| Wpis 1 | Start → 23:50 | 11 |
| Wpis 2a | 23:50 → 23:59:59 | 5 |
| Wpis 2b | 00:00:00 → 00:10 | 5 |
| Sytuacja | Moment zamknięcia | Status |
|---|---|---|
| Kliknięcie „Zakończ teraz” | Natychmiast po kliknięciu | 1 |
| Brak reakcji do północy | 23:59:59 | 5 |
Zasada komunikacji: przed północą komunikat ma mówić o zbliżającej się północy i odliczać czas do 23:59:59. Dopiero po faktycznym minięciu północy pokazujemy informację, że północ minęła, wraz z danymi zamkniętego wpisu.
Stan wdrożony: kliknięcie „Zakończ teraz” zamyka wpis natychmiast ze statusem ręcznym, a brak reakcji kończy wpis o 23:59:59 jako nieaktywność.
| Reguła | Stan | Efekt |
|---|---|---|
| Midnight guard aktywny/pending blokuje LongWorkingSessionGuard | Wdrożone | Brak nakładania okien ostrzegawczych i spójny przebieg decyzji użytkownika. |
| Ciągłość sesji przez północ po potwierdzeniu „TAK” | Wdrożone | Long guard liczy limit ciągłej pracy (domyślnie 4h) przez granicę doby. |
Start zadania w progu MidnightGuardAutoContinueIfStartedWithinMinutes |
Wdrożone | Brak dialogu pre-midnight; automatyczny split o 23:59:59/00:00:00 i kontynuacja. |
Guard długiej sesji ostrzega po przekroczeniu limitu LongWorkingSessionLimitHours (domyślnie 4h).
Liczenie jest prowadzone na podstawie ciągłego początku sesji i nie resetuje się na północy,
jeśli użytkownik potwierdził kontynuację w Midnight Guard.
| Scenariusz | Co robi system | Wpływ na WorkTime |
|---|---|---|
| Sesja ciągła bez midnight guard | Po upływie limitu pojawia się okno LongWorkingSessionGuard z akcjami: TAK, Zakończ teraz, Zakończ o HH:mm:ss. |
Decyzja użytkownika steruje dalszym naliczaniem lub zatrzymaniem. |
| Midnight guard aktywny/pending | Long guard jest tymczasowo tłumiony, aby nie nakładać okien. | Brak konfliktu zapisów i jeden spójny przebieg decyzji. |
| Potwierdzona kontynuacja przez północ (np. start 22:00) | Limit długiej sesji liczy się nadal od 22:00. | Alert długiej sesji około 02:00 (dla limitu 4h). |
| Long guard: „Zakończ o HH:mm:ss” | Zatrzymanie wpisu następuje na czasie pojawienia się okna (pełna wielokrotność limitu ciągłej sesji). | Status końca: 1: FinishByUser. |
Anomalie zegara są klasyfikowane przez guard hibernacji/clock-skew i mogą zakończyć aktywne naliczanie
statusem AutoFinishDueToClocksDifferences (10). W ścieżce clock-skew nie powstaje dual-entry.
| Przypadek | Działanie systemu | Status końca |
|---|---|---|
| Wykryto istotną rozbieżność czasu | Zamknięcie aktywnego wpisu na bezpiecznym punkcie i okno informacyjne. | 10: AutoFinishDueToClocksDifferences |
| Brak rozbieżności ponad próg | Brak akcji guarda. | Brak zmian w WorkTime |
Przy odświeżeniu listy zadań API jest źródłem prawdy dla aktywności zadań. Jeśli aktywne lokalnie naliczanie
nie znajduje odpowiednika na liście aktywnych zadań z API, system synchronizuje stan przez zatrzymanie lokalnego
naliczania i pokazuje komunikat MissingTaskMessage.
| Sytuacja | Co robi system | Uwagi |
|---|---|---|
| API zwraca aktywne zadanie zgodne z lokalnym | Kontynuacja naliczania; brak okna MissingTask. | Normalny przebieg. |
| API nie zwraca żadnego aktywnego zadania, a lokalnie trwa naliczanie | Lokalny wpis jest kończony i pojawia się okno MissingTask. | Użytkownik dostaje informację, że zadanie nie jest już dostępne. |
| API zwraca pustą listę zadań (błąd/niejednoznaczna odpowiedź) | Payload jest ignorowany przy aktualizacji cache zadań. | Zgodnie z zasadą: puste odpowiedzi API nie powinny niszczyć lokalnego stanu listy. |
| Obszar | Czy powoduje zmianę WorkTime | Pokrycie w dokumencie |
|---|---|---|
| Hibernacja / sen | Tak (stop 1 wpisu lub dual-entry) | Sekcje 3, 4, 7 |
| Przejście przez północ | Tak (split 23:59:59 / 00:00:00 lub stop) | Sekcje 5, 6, 7, 8, 9 |
| Długa ciągła praca | Pośrednio (okno decyzji użytkownika) | Sekcje 9, 10 |
| Zmiana zegara systemowego | Tak (auto-stop status 10) | Sekcje 3, 11 |
| Zniknięcie zadania z API | Tak (synchronizacyjne zatrzymanie lokalnego naliczania) | Sekcja 12 |
| Zdarzenia techniczne (restart/update/sync-korekta) | Tak (auto-domknięcia i korekty lokalnego stanu) | Sekcja 14 |
| Fallback Quartz przy zmianie doby | Tak (awaryjne domknięcie status 3/5) | Sekcje 14 (przykłady G, H) |
Poniżej lista przypadków, w których czas pracy może zostać zmieniony bez kliknięcia Start/Stop przez użytkownika, a przyczyna nie jest bezpośrednio guardem hibernacji/północy.
| Zdarzenie | Co robi system | Status / efekt |
|---|---|---|
| Restart aplikacji z lokalnym aktywnym wpisem bez powiązania serwerowego | Wpis jest automatycznie domykany przy starcie aplikacji. | 2: AutoFinishAfterAppRestart |
| Aktualizacja aplikacji / wymuszone domknięcie wszystkich aktywnych wpisów | Wszystkie aktywne wpisy są zatrzymywane i synchronizowane. | 4: AutoFinishDueToAppUpdate |
| Wykryte duplikaty aktywnych lokalnych wpisów dla jednego zadania | Jeden wpis pozostaje aktywny, pozostałe są technicznie domykane. | 4: AutoFinishDueToAppUpdate |
| Backend sygnalizuje, że lokalnie aktywny wpis nie jest już aktywny po stronie serwera | Lokalny wpis zostaje zamknięty/oznaczony jako zmieniony przez backend podczas reverse-sync. | Domknięcie lokalne + flaga błędu synchronizacji (HasErrorDuringSync) |
| Zniknięcie aktywnego zadania z listy API podczas refreshu zadań | Synchronizacja zatrzymuje lokalne naliczanie i pokazuje okno MissingTask. | Zamknięcie lokalnego wpisu + komunikat dla użytkownika |
| Watchdog Quartz po północy (>5 minut po 00:00) | Awaryjne zamknięcie wpisu na 23:59:59 poprzedniego dnia. | 3: AutoFinishAfterSystemRestart |
| Quartz wykryje długą przerwę aktywności przy zmianie doby | Zamknięcie wpisu na znaczniku ostatniej aktywności użytkownika. | 5: AutoFinishDueToUserInactivity |
| Krok | Skutek |
|---|---|
| App zamknięta awaryjnie przy aktywnym lokalnym wpisie | Po następnym starcie wpis jest domknięty automatycznie. |
| Domknięty wpis wysyłany do API | Końcowy status: 2. |
| Sytuacja | Skutek |
|---|---|
| W bazie lokalnej istnieją 2+ aktywne wpisy jednocześnie | Najnowszy pozostaje aktywny, starsze są zamykane technicznie ze statusem 4. |
| Sytuacja | Skutek |
|---|---|
| Lokalnie wpis aktywny, ale reverse-sync nie znajduje go jako aktywnego po stronie API | Wpis lokalny zostaje zamknięty i oznaczony jako nadpisany przez backend. |
| Sytuacja | Skutek |
|---|---|
| Po 00:05 nadal istnieje aktywny wpis, który nie został zamknięty przez midnight/hibernation guard | Awaryjne zamknięcie na 23:59:59 poprzedniego dnia ze statusem 3. |
| Sytuacja | Skutek |
|---|---|
| Wykryta przerwa aktywności > 20 min podczas przetwarzania zmiany doby | Zamknięcie wpisu na czasie ostatniej aktywności ze statusem 5. |