Bliv en del af mit community / gratis nyhedsbrev — tilmeld dig her
- Client
- Dansk kontorfællesskab — kundebrief, bygget som fungerende demo
- Sector
- Kontorfællesskaber og delekontorer
- Periode
- juli 2026
At a glance
Et dansk kontorfællesskab ville lade medlemmerne booke et bord pr. time og betale for det — for ved 20 kr. i timen pr. bord er en booking, der ikke er betalt, ikke en booking. tableflow.grn.dk er det system, bygget efter kundens brief og kørende live: et timegitter, der viser, hvad der faktisk er ledigt, en betalingsreservation på femten minutter, der frigiver sig selv, og en admin-skærm til dem, der driver stedet. Ren PHP og MariaDB — intet framework, ingen dependencies, 853 linjer i syv filer.
The challenge
Briefen var kort og usædvanligt klar: PHP og en MySQL-database; vælg en dato, et bord og et fra/til-tidspunkt; kun hele timer og kun inden for åbningstiden; registrér navn, e-mail og telefon; 20 kr. i timen pr. bord — og bordet er først reserveret, når der er betalt. Responsivt, fungerer godt på en telefon. Funktionelt.
Netop den sætning om betaling er det, der forvandler en bookingformular til et lille concurrency-problem. Tiden skal holdes, mens den besøgende betaler, men ikke holdes for evigt, hvis vedkommende forsvinder. To personer, der klikker på den samme time i det samme sekund, må ikke begge gå derfra i den tro, at de har den. Og en udløbet, ubetalt reservation skal frigive sig selv — ingen i et kontorfællesskab kommer til at huske at rydde bordet manuelt.
The solution
TableFlow er en bevidst lille PHP 8.3-applikation på MariaDB, der taler med databasen gennem PDO med prepared statements. Intet framework, ingen Composer, intet build-step: 853 linjer fordelt på syv filer — fire sider, et fælles bibliotek, ét stylesheet og en konfigurationsfil, der ligger uden for web-roden.
Booking. Når man vælger en dato og et bord, gentegnes et timegitter, hvor hver time enten er ledig eller streget over som optaget. Et klik på en ledig time sætter starten, et klik på en senere time udvider intervallet — kun hen over sammenhængende ledige timer — og prisen genberegnes undervejs; og så genberegnes den igen på serveren, for browseren er en bekvemmelighed og aldrig autoriteten. Åbningstiderne er en konfigurationstabel (man-fre 08-20, lør 09-16, søndag lukket), lukkedage siger det i stedet for at tilbyde tider, og timer, der allerede er passeret i dag, kan ikke bookes.
Reservationen — uden cronjob. »Denne tid er blokeret« er defineret ét sted, som én SQL-betingelse: reservationen er betalt, eller den er oprettet inden for de seneste femten minutter. Gitteret, bookingtjekket og betalingstrinnet genbruger alle den ene definition, så en ubetalt reservation simpelthen holder op med at blokere, når den bliver for gammel. Intet skal køre efter en tidsplan, og der er intet oprydningsjob, der kan fejle i stilhed.
Ingen dobbeltbooking. Indsættelsen kører i en transaktion, der først selecter de overlappende reservationer FOR UPDATE. To samtidige bookinger af samme time serialiseres mod hinanden, og den, der taber, får at vide, at tiden lige er blevet taget — i stedet for at få en fantomreservation i hånden. Betalingstrinnet gentager tjekket, i tilfælde af at reservationen udløb, mens den besøgende betalte, og en anden tog tiden i mellemtiden.
Betaling og kvittering. Betalingssiden nås via et tilfældigt token på 32 tegn, så en reservation kun er synlig for den, der lavede den. Selve knappen er en ærlig simulation, markeret i brugerfladen som det sted, hvor MobilePay eller en kortgateway kobles på; en betaling skifter reservationen til Betalt, stempler tidspunktet og forvandler siden til en kvittering.
Admin. En adgangskodebeskyttet skærm viser kommende reservationer (eller dem alle) og lader den, der driver stedet, markere en booking som betalt — for eksempel ved kontant betaling — markere den som ubetalt igen eller slette den. Ubetalte reservationer ud over fristen markeres Udløbet: tiden er ledig igen, men den kan stadig afregnes manuelt, hvis ingen andre har taget den.
Alt det andet. Hver POST bærer et CSRF-token, bookingformularen har en honeypot til bots, og dato, tidsinterval, navn, e-mail og telefon valideres alt sammen på serveren. Brugerfladen er dansk, mobile-first og følger den besøgendes lyse eller mørke systemindstilling.
Leverancen omfattede hostingen: sin egen virtual host under en dedikeret tableflow-systembruger, sit eget git-repository, en databasebruger afgrænset til netop denne applikation, credentials i en fil, web-roden ikke kan servere, et Let's Encrypt-certifikat og en HTTP-til-HTTPS-redirect, bag Cloudflare.
The results
Systemet har været live på tableflow.grn.dk siden 23. juli 2026 og blev verificeret hele vejen igennem før overdragelsen: en booking helt til betaling, en afvist dobbeltbooking, admin-handlingerne og værnet mod lukkedage.
At det fylder 853 linjer uden dependencies er pointen, ikke praleri. Der er intet framework at opgradere, intet plugin-træ at auditere og intet build, der kan knække en tilfældig tirsdag — koden, der kører, er koden, der blev skrevet, og en ændring af åbningstider eller timepris er én linje i en konfigurationsfil.
Det er en demo, og det siger den selv: betalingsknappen simulerer en transaktion, og admin-adgangskoden står trykt på dens egen login-skærm. At gøre det til et produktionssystem for et konkret kontorfællesskab er en afgrænset opgave — en rigtig betalingsgateway, rigtige admin-konti, prøvedata ryddet — ikke en omskrivning.
»Man har først reserveret bordet, når man har betalt.«