bmc_hub/docs/UNIFIED_PLANNING.md

108 lines
17 KiB
Markdown

# Samlet planlægning
Lokal implementering, 17. oktober 2026. Produktionsudrulning og endelig brugeraccept er ikke gennemført.
## Arbejdsflade og datakilder
`/planning` har tre hovedfaner: **Mit arbejde** (kø og kalender med dag/uge/måned), **Opgaver** (sager uden medarbejder, medarbejderkø, tildeling og datoer) og **Team og kapacitet** (medarbejderoversigt og kapacitet). De tidligere view-parametre fungerer fortsat som indgange til de samlede faner. `/planner`, eksisterende kalenderadresser og kalenderabonnementer bevares. Medarbejderoversigten genbruger den eksisterende planlægger; kalenderen bruger FullCalendar. Bundmenuen åbner næste opgave, personlig kø og kalender. Næste opgave starter aldrig timeren.
- `sag_case_assignees` er den entydige allokering pr. sag/medarbejder. Individuelt estimat, manuel rest, status, udsættelse, fastlåsning og position ligger på relationen. Ansvarlig er fortsat en selvstændig rolle.
- Køen sorteres efter fastlåsning og gemt position. Første overgang bruger prioritet, deadline og sags-ID; senere ændringer af deadline/SLA flytter ikke køen. Positioner har numeriske mellemrum og omnummereres kun ved behov. Køversion beskytter også imod samtidige medlemsændringer fra gamle tildelingshandlinger.
- `planner_events` holder fælles tidspunkt/titel; `planner_allocations` bevarer booking-ID og medarbejderreservation. Gamle bookinger får hver sin post. Der foretages ingen sammenlægning efter tidslighed. Den gamle API afviser individuel flytning af en fælles aftale; fællesændringen foretages gennem event-API'en.
- `quick_time_items` læses direkte. Planlagt arbejde kopieres ikke. Konverteret arbejde vises gennem sin kanoniske tidsregistrering.
- Planlagt fravær ligger i `planner_absences`. Kun den udtrykkelige handling "Registrer som afholdt" opretter faktisk tid i `tmodule_times`. Forbindelsen er entydig pr. fravær og arbejdsdato. Gentagelse opretter ikke mere tid. Fravær er ikke fakturerbart og kan ikke annulleres som planlagt efter konvertering.
- Kapacitet bruger medarbejderens arbejdstider og unionen af bookinger, fravær, eksternt optaget og selvstændige planer. Faktisk tid er information, ikke endnu en reservation. Overlap markeres samtidig som konflikt. Køprognosen trækker fremtidigt booket personarbejde fra individuel rest; ukendte estimater forbliver ukendte.
- Hoved-/undersagsoversigten bruger unikke sags-ID'er, også ved flere relationer eller cykler, og summerer egne registreringer pr. sag. Egne, undersagers og samlede værdier kan vælges separat.
I Opgaver kan sager trækkes til en medarbejder, mellem køplaceringer og til en kalenderdato. Fra en eksisterende medarbejder overdrages allokeringen; fra indbakken oprettes den. Et særskilt dropfelt fjerner den valgte medarbejders allokering og bevarer øvrige medarbejdere og ansvarlig. Ved overdragelse med kommende reservationer spørges der, om tiderne skal overdrages til den nye medarbejder eller fjernes. Alle tidsrum håndteres samlet med versionskontrol, konfliktbekræftelse og audit i samme transaktion. Andre mødedeltagere og registreret tid bevares. Fjernelse af en allokering uden overdragelse kræver fortsat, at kommende reservationer først håndteres. Start-/deadlinevalg styrer, hvilken dato en opgave ændrer. Eksisterende start- og deadlinemarkeringer kan også flyttes. Knapper tilbyder samme handlinger på mobil. Yderligere køhandlinger er samlet under "Flere handlinger".
`deadline_date` på sagsplan-API'en flytter alene datoen og bevarer et eksisterende dansk klokkeslæt, også hen over urskift. Ny deadline får kl. 17. Datoændringer kræver sagsredigering og sagens aktuelle version. Sagsstart er fortsat en ren dato med valgfrit separat klokkeslæt. Ændringer opretter hverken bookinger eller tidsregistreringer.
Nye API'er ligger under `/api/v1/planner`: `queue`, `next-task`, `cases/{id}/assignees`, `cases/{id}/transfer`, `cases/{id}/planning`, `cases/{id}/summary`, `events`, `schedule`, `absences`, `capacity`, `unassigned`, `configuration`, `work-schedules`, `activity-types` og `team-scope`. Eksisterende endpoints og svarfelter er bevaret. Uaktuelle versioner giver HTTP 409. Audit og flertrinsændringer deler transaktion.
## Adgang og fortrolighed
Planlægningsadgang, medarbejderens tilladte teamomfang og sagsadgang kontrolleres på serveren. Særskilte rettigheder: `planner.view_team`, `planner.allocate`, `planner.absence_own`, `planner.absence_team`, `planner.absence_reason` og `cases.manage_confidential`.
Fortrolighedslåsen administreres fra sagsvisningen og `/api/v1/sag/{id}/visibility`. Adgang kan gives til udvalgte medarbejdere og grupper; øvrige brugere får 404 ved direkte adgang. Superadministrator har adgang. Ulåste sager beholder den hidtidige synlighed. En ulåsning bevarer versionshistorikken, så en gammel version ikke kan genbruges.
Beskyttelsen omfatter kendte sagslister, relationer, søgning, planlægning, JSON-API'er, kalenderfeeds og påmindelsesmodtagere. Skjulte reservationer bliver "Optaget" uden sag, kunde eller beskrivelse. Fraværsårsager maskeres uden særskilt adgang. Fortrolige sager udsendes ikke til vilkårlige eksterne mailadresser eller Mattermost-kanaler.
Deadlinekontrol genbruger det eksisterende reminderjob og opretter kun interne påmindelser. Nøglen deduplikerer pr. sag, deadline, allokeringstilstand og lokal dato. Ændringspåmindelser har særskilte hændelsesnøgler. Standardgrænsen før fast aftale er 30 minutter og kan administreres.
## Migration og drift
Anvend `1070_unified_planning.sql` efter 1069, derefter `1071_case_confidentiality.sql`. Kør med stop ved fejl og i én transaktion. Migrationerne er additive og kan genkøres; den første køinitialisering gentages ikke. PostgreSQL anvendes både lokalt og i produktion.
Før produktion:
1. Tag og verificér en databasebackup; afprøv migrationskørslen på en isoleret databasekopi.
2. Registrér kontroltal og ID'er for sager, tidsregistreringer og bookinger, og kontrollér dem efter migrationen. Kontrollér også én eventforbindelse pr. historisk booking og fravær uden fakturerbar tid.
3. Stands API og baggrundsskrivere under skemaændringen. En lokal prøve med samtidig API-læsning ramte en deadlock og blev rullet helt tilbage; den efterfølgende kørsel med standset API lykkedes.
4. Start API'en igen, så rettigheder og indstillinger indlæses på ny. Kontrollér de konkrete arbejdsgange med de relevante medarbejderroller.
5. Ved UI-tilbageførsel sættes `PLANNING_UI_ENABLED=false` i API-miljøet, og API'en genstartes. `/planning` viderestiller derefter til `/planner`; nye data og tabeller bevares. Undlad destruktiv down-migration. Fortrolighedskontrollen skal fortsat være aktiv.
Lokal backup: `/tmp/bmc_hub_before_planning_20261017.dump` (rettigheder 0600). Opbevaring uden for `/tmp` og produktionsbackup håndteres særskilt ved udrulning. Lokale kontroltal før/efter den afsluttende migration: 185 sager, 1.385 tidsregistreringer, 15 bookinger, alle 15 med eventforbindelse. Dette er ikke dokumentation for produktionsdrift.
## Validering
Kør databaseprøverne alene mod en dedikeret `bmc_planning_test_*`-database. Testfixturen afviser andre databasenavne og bruger syntetiske data. Den ændrer ikke udviklings- eller produktionsdata.
```sh
BMC_PLANNING_DB_TESTS=1 BMC_PLANNING_TEST_DATABASE=bmc_planning_test_20261017 \
PYTHONPYCACHEPREFIX=/tmp/bmc_planning_pycache ./.venv/bin/python -m pytest \
tests/test_unified_planning.py tests/test_planner.py tests/test_quick_time.py \
tests/test_quick_time_calendar.py tests/test_bottom_bar_timer_pause.py \
tests/test_case_commands.py tests/test_sag_v5.py -q --tb=short
```
Automatiseret dækning: tre medarbejderes uafhængige køer, stale versioner, reelle samtidige kø-/bookingændringer, delvis fordeling med atomisk rollback, omfordeling med auditfejl og bevaret ansvarlig, fælles mødeflytning med bevarede ID'er, fraværskonvertering, skjulte fraværsårsager, fortrolighed via bruger/gruppe, API-afvisning før skrivning, kapacitet/overlap, danske urskift, rene startdatoer, næste-opgave-advarsel uden tidsoprettelse, hoved-/undersager, reminder-deduplikering og pagineret søgning blandt 2.000 syntetiske sager. Relevante eksisterende timer-, sags-, kommandoflow- og selvstændig-tid-tests indgår.
Safari er kontrolleret lokalt efter genindlæsning: kø, dagkalender og kapacitetsvisning indlæses; den oprindelige "The string did not match the expected pattern" vises ikke længere. Den konkrete årsag var manglende migreret skema og et ikke-JSON-fejlsvar. Frontenden viser nu en forståelig fejl ved sådanne svar.
Endelig accept kræver fortsat fuld praktisk gennemgang af kravspecifikationens 20 scenarier og afsnit 31 med rigtige roller, mobilbetjening og det eksisterende faktureringsflow samt måling under produktionslignende belastning. Automatiserede testresultater og lokal Safari-kontrol erstatter ikke denne accept eller verificeret produktionsdrift. Afhængigheder mellem undersager og AI-forslag er bevidst udskudt.
Drag-handlerprøver: `node tests/test_planning_drag_ui.js` tester produktionens eventhandlers med isolerede DOM/API-adaptere: tildeling, overdragelse, start, deadline, uvedkommende drag og forældet ændring. Dette er ikke en erstatning for manuel drag-and-drop på alle browser-/mobilkombinationer.
## Lokal fejlretning: kalendertræk, 18. oktober 2026
Den lokale API-proces kørte ældre Python-kode, mens frontendfilerne allerede var opdateret. Den aktive kø og indbakke manglede `case_updated_at`, og datoflytningens backend var derfor ikke tilgængelig. Genstart af den lokale API indlæste ændringerne. Kontrol efter genstart viste versionsfeltet i begge læsemodeller samt fungerende gem/læs-tilbage af startdato og deadlinedato på en særskilt midlertidig testsag. Testsagen blev efterfølgende blødt slettet. En manglende version i browserens opgaveliste giver nu en konkret besked om at genindlæse siden; der sendes intet datokald uden version. Dette var en lokal runtime-fejl og dokumenterer ikke produktionsudrulning.
## Direkte kalenderhandlinger på kort, 18. oktober 2026
Hvert opgavekort har nu tre selvstændige knapper/trækhåndtag: Start, Deadline og Allokeret. De to første viser den gemte dato og eventuelt klokkeslæt. Allokeret viser antal kommende tidsrum for den valgte medarbejder; hvert tidsrum vises med fra/til på kortet. Den fælles dropvælger er fjernet. Et klik på knappen er alternativet til at trække.
Start og Deadline ændrer alene det tilsvarende sagsfelt. Allokeret åbner en enkel medarbejder/fra/til-dialog på den valgte kalenderdato; i timegitteret medtages det ramte klokkeslæt. Gem opretter en ny event/reservation via eksisterende API og konfliktbekræftelse. Gentagne handlinger tilføjer flere perioder og erstatter ikke tidligere. Bookingflytning i kalenderen påvirker fortsat kun den valgte event og dens deltagere.
Validering: 74 fokuserede Python-tests samt Node-handlerprøver består. Databasetesten opretter to perioder på samme sag, flytter den første og kontrollerer at den anden er uændret. UI-handlerprøver kontrollerer særskilt valg af Start/Deadline/Allokeret og de viste kortværdier. Lokal API genstartes efter Python-ændringer, og frontend-cacheversionen er opdateret.
Kalenderdrop er efterfølgende rettet til at finde dagen ud fra de renderede dagkolonners skærmposition, når tidsfeltet ikke selv har en dateret DOM-forælder. Samme hit bruges til markering under træk og ved drop. Regressionstesten rammer et tidsfelt uden `data-date`, vælger anden dagkolonne og kl. 09.30 samt kontrollerer, at et drop uden for kolonnerne ikke vælger en forkert dato.
Allokeret arbejdsreservation er nu en svag baggrundsfarve i kalenderens dag-/ugegitter. En diskret tekst kan åbne den eksisterende reservationsdialog. Kalenderens måneds-/listevisning beholder læsbare, nedtonede reservationsposter; møder forbliver almindelige aftaler. Registreret tid vises fortsat særskilt i lilla. Signatur og reservationsdialog forklarer, at allokering alene reserverer kapacitet, mens faktisk tid skal registreres med eksisterende timer/manuelle registrering. Ingen ændring i lagring, tidsregistrering eller fakturering. Renderprøver kontrollerer adskillelsen mellem baggrundsreservationer, møder og listevisning.
### Fraværsstatus og rapport (lokalt, 18. oktober 2026)
- Planlægning har knappen **Fraværsstatus og rapport**, der åbner `/planning/absence`. Det er en separat side uden yderligere planlægningsfaner.
- `planner.absence_report` giver rapport- og CSV-adgang; `planner.admin` og superadministrator har også adgang. Migration `1072_absence_reports.sql` tilføjer rettigheden og giver den til den eksisterende Administrators-gruppe. Rapportadgang udvider ikke medarbejderscope. `planner.absence_reason` styrer fortsat adgang til årsager; rapporten indeholder aldrig private noter.
- Rapportens periode er inklusive begge datoer, højst 367 dage. Medarbejderfilter og paginering genbruges i eksporten. CSV beskytter mod regnearksformler i tekstfelter.
- Planlagte timer og arbejdsdage afgrænses til perioden og arbejdstiden. Overlappende fravær tælles én gang i overblik og totaler, mens de oprindelige perioder fortsat kan ses separat. Faktisk fravær summeres fra ikke-afviste tidsregistreringer i den valgte periode og vises separat. Manglende arbejdstid markeres som ukendt. Der beregnes ikke feriesaldo.
- Flerdagesfravær vises tydeligt i kalenderens heldagsfelt og i en særskilt fraværsoversigt for den viste periode. Kalenderen gælder den valgte medarbejder. Nye hele fraværsdage angives med første og sidste dato; serveren omsætter datoerne til Europe/Copenhagen, inklusive sommer-/vintertid. Eksisterende tidsrum ændres ikke ved læsning.
- Lokal kontrol: 80 relevante Python-tests består mod isoleret PostgreSQL, samt `node tests/test_planning_drag_ui.js` og `node tests/test_absence_report_ui.js`. Safari-kontrol viser eksisterende ferieperioder i ugekalenderen og på rapporten. Migration 1072 er anvendt lokalt, og den lokale app er genstartet. Dette dokumenterer ikke produktionsudrulning.
Rapporten grupperer nu overblik og perioder efter fraværstype med medarbejdersubtotaler under hver type. CSV-eksporten følger samme rækkefølge. API'et har additivt `type_summary` og valgfrit `group_by=type`; eksisterende standardrækkefølge bevares for øvrige klienter. Skjulte årsager samles før gruppering under Fravær. Overlap fjernes inden for hver type og i det samlede overblik; overlap mellem forskellige typer kan derfor give højere sum af typer end samlet fravær. Lokal kontrol: 31 planlægningstests og rapportens JavaScript-test består; kørende lokalt API og opdateret HTML er kontrolleret. Browserens session var udløbet ved genindlæsning, så ny visuel kontrol kræver login.
Teamkalenderens fraværsvisning: `/api/v1/planner/period` returnerer nu additivt fraværsperioder med rettighedsfiltrerede labels og uden private noter. Den eksisterende teamkalender viser perioderne som diskrete baggrundsblokke i dag/uge og som markeringer pr. medarbejder i måned. Flerdagesperioder fordeles over hver berørt dag; slutdato ved midnat er eksklusiv. Kapacitetsberegningen ændres ikke. Validering: 41 relevante Python-tests samt `node tests/test_planner_absence_ui.js` består, inklusive adgangskontrol og dansk vintertid. Den lokale app blev genstartet efter ændringen. Ved den afsluttende genoptagelse var Docker ikke længere startet (docker.sock mangler); den sidste kontrol af den kørende visning kunne derfor ikke gennemføres. Ingen produktionsudrulning.
Supplerende lokal rettelse: Fraværsrapportens perioder og CSV har nu kolonnen Arbejdsdage. Sidetælleren viser desuden periodens samlede arbejdsdage, hvor overlap fortsat tælles én gang. Teamkalenderen har en synlig fraværsoversigt før medarbejderrækkerne med navn, type og datoer; klik ruller kun kalenderpanelet til medarbejderen. Kalender og sagsliste har på desktop afgrænset højde og ruller hver for sig, så lange sagslister ikke strækker kalenderen ud. Kalender-API'et er kontrolleret i den kørende lokale app og returnerer de eksisterende fraværsperioder; Safari viste ferie/sygdom og oversigtens knapper. Alle tre relevante JavaScript-prøver består. Sidste forsøg på screenshot efter scrollrettelsen fik timeout; appens health svarer HTTP 200. Ingen produktionsudrulning.
Dobbelt fravær af samme type: oprettelse og redigering af overlappende perioder for samme medarbejder afvises med HTTP 409. Eksisterende medarbejderlås serialiserer kontrollen, så samtidige gemninger ikke kan oprette dubletter. Perioder, der kun rører ved hinanden, andre medarbejderes ferie og forskellige typer bevarer den eksisterende adfærd. Tidligere dubletter slettes ikke automatisk; de kan annulleres gennem den eksisterende fraværsdialog.
Datovælgere i planlægningsdialoger, teamkalender og rapportfilter bruger Flatpickr 4.6.13 (allerede anvendt i tidsregistreringen). Bibliotek, dansk locale og MIT-licens ligger lokalt under `static/vendor/flatpickr/`. UI viser dansk datoformat, mandag først og 24-timers tid, men API-værdier bevarer deres ISO-format. Adapteren håndterer hele/delvise dage og krævede felter; tomme obligatoriske datoer kan ikke sendes. Der er ikke ændret globale datovælgere på andre sider.
Lokal validering: 43 relevante Python-tests består, inklusive samtidige ferieoprettelser, og fire JavaScript-prøver består (datovælger, rapport, drag og teamfravær). Appen er genstartet med overlapkontrollen. Safari-kontrol af rapportfilteret viser den danske månedskalender og I dag/Færdig. Ingen produktionsudrulning.