Skip to content

ThysToys Logistics

Terug naar ThysToys

Overzicht

Het ThysToys Logistieke portaal is een webapplicatie voor het uitscannen van bestellingen in het magazijn van ThysToys. Het systeem bepaalt automatisch de juiste vervoerder en genereert verzendlabels voor de bestellingen.

Doel van het portaal

Het logistics portaal stroomlijnt het verzendproces door:

  • Automatische selectie van de juiste vervoerder op basis van productkenmerken
  • Genereren van verzendlabels via barcode scanning
  • Real-time koppeling met FileMaker voor productinformatie
  • Ondersteuning voor meerdere vervoerders (PostNL, DPD, Flevobird)
Aspect Details
Type Webapplicatie
Repository thystoys/thystoys-logistics

README


Servers

ThysToys maakt gebruik van 2 fysieke Intel NUC servers die als fail-over configuratie zijn opgezet. Mocht één server uitvallen, dan kan de andere direct worden ingezet.

Status: Actief in productie

Deze server draait momenteel de productieomgeving van ThysToys Logistics.

Status: Passief

Locatie: Fysiek op kantoor bij Flooris

Deze server ligt stand-by als fail-over en kan worden ingezet bij problemen met TY07WEB002.

Server Provisioning

De volledige server provisioning documentatie is beschikbaar in Notion:

KB Hosting - ThysToys NUC webserver provisioning


Vervoerders

Het systeem ondersteunt meerdere vervoerders die automatisch worden geselecteerd op basis van product- en bestelkenmerken.

Vervoerder Gebruik Selectie logica
PostNL Standaard vervoerder Standaard voor alle reguliere bestellingen
DPD Internationale verzending Bestellingen naar Duitsland
Flevobird Speciaal transport Groot speelgoed dat niet met reguliere vervoerders kan

Vervoerder logica

De vervoerder wordt bepaald door logica in FileMaker op productniveau, vanuit het Product Management systeem.


Omgevingen

Local Development

Container platform: OrbStack

URL: http://logistics.thystoys.localhost

# Start local development environment
docker-compose up -d

Productie

Omgevingscode: TTLOG Prod

URL: https://logistics.thystoys.nl

Netwerk configuratie

Deze hostname resolved naar een Local LAN IP address van de actieve NUC server. De applicatie is alleen toegankelijk vanuit het lokale netwerk van ThysToys.


Health monitoring

Health monitoring gaat op een iets minder "standaard" methode, de server is namelijk zelf verantwoordelijk voor het melden van zijn health status bij onze Uptime Kuma instance.

De reden hiervoor is zoals eerder al is aangegeven, de productie server is niet beschikbaar vanaf het publieke internet, waardoor Uptime Kuma dus ook niet bij de applicatie kan komen.

De health check wordt ieder uur uitgevoerd door de Laravel scheduler, hiermee wordt ook direct de health status van de scheduler bepaald.

Tijdens het uitvoeren van ReportHealthStatusJob wordt er bijvoorbeeld een HTTP request uitgevoerd naar de NGINX server op de server zelf, waardoor we toch bepaalde health checks kunnen uitvoeren, ondanks Uptime Kuma er niet zelf bij kan.

Tests in de health check

Test naam Doel
testNginxConnection Testen of de NGINX leeft, of de applicatie bereikbaar is via NGINX en of de front-end assets beschikbaar zijn
testDatabaseConnection Testen of diverse database tabellen leesbaar zijn, waarmee ook direct de MySQL/MariaDB verbinding getest wordt

Wat als een health check faalt?

Als een health check faalt, zal dit direct gemeld worden aan Uptime Kuma.

Omdat deze niet direct aangeeft wat er fout is maar alleen dat er iets fout is, wordt de health status ook weggeschreven naar de Laravel logs. Hierin is het volgende te zien:

  • Simpele health status (Zijn alle checks geslaagd?)
  • Geslaagde checks
  • Gefaalde checks
  • Gefaalde check foutmeldingen + stack trace
Voorbeeld log
[2026-03-25 14:45:11] local.INFO: [ReportHealthStatusJob] Determined isHealthy status: NO (Passed checks: [testDatabaseConnection], Failed checks: [testNginxConnection]) {"failedCheckExceptions":{"testNginxConnection":{"message":"cURL error 7: Failed to connect to thystoys-logistics.localhost port 80 after 0 ms: Could not connect to server (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://thystoys-logistics.localhost/login","trace":[{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Support/helpers.php","line":338,"function":"Illuminate\\Http\\Client\\{closure}","class":"Illuminate\\Http\\Client\\PendingRequest","type":"->","args":[1]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php","line":905,"function":"retry","args":[0,{"Closure":[]},100,{"Closure":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php","line":772,"function":"send","class":"Illuminate\\Http\\Client\\PendingRequest","type":"->","args":["GET","http://ttlog-nginx/home",{"json":null}]},{"file":"/var/www/html/app/Jobs/ReportHealthStatusJob.php","line":68,"function":"get","class":"Illuminate\\Http\\Client\\PendingRequest","type":"->","args":["/home"]},{"file":"/var/www/html/app/Jobs/ReportHealthStatusJob.php","line":32,"function":"testNginxConnection","class":"App\\Jobs\\ReportHealthStatusJob","type":"->","args":[]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php","line":36,"function":"handle","class":"App\\Jobs\\ReportHealthStatusJob","type":"->","args":[]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Util.php","line":43,"function":"Illuminate\\Container\\{closure}","class":"Illuminate\\Container\\BoundMethod","type":"::","args":[]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php","line":95,"function":"unwrapIfClosure","class":"Illuminate\\Container\\Util","type":"::","args":[{"Closure":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php","line":35,"function":"callBoundMethod","class":"Illuminate\\Container\\BoundMethod","type":"::","args":[{"Illuminate\\Foundation\\Application":{"contextual":[],"contextualAttributes":[]}},[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}},"handle"],{"Closure":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php","line":696,"function":"call","class":"Illuminate\\Container\\BoundMethod","type":"::","args":[{"Illuminate\\Foundation\\Application":{"contextual":[],"contextualAttributes":[]}},[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}},"handle"],[],null]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php","line":126,"function":"call","class":"Illuminate\\Container\\Container","type":"->","args":[[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}},"handle"]]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php","line":170,"function":"Illuminate\\Bus\\{closure}","class":"Illuminate\\Bus\\Dispatcher","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php","line":127,"function":"Illuminate\\Pipeline\\{closure}","class":"Illuminate\\Pipeline\\Pipeline","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php","line":130,"function":"then","class":"Illuminate\\Pipeline\\Pipeline","type":"->","args":[{"Closure":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php","line":126,"function":"dispatchNow","class":"Illuminate\\Bus\\Dispatcher","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}},false]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php","line":170,"function":"Illuminate\\Queue\\{closure}","class":"Illuminate\\Queue\\CallQueuedHandler","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php","line":127,"function":"Illuminate\\Pipeline\\{closure}","class":"Illuminate\\Pipeline\\Pipeline","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php","line":121,"function":"then","class":"Illuminate\\Pipeline\\Pipeline","type":"->","args":[{"Closure":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php","line":69,"function":"dispatchThroughMiddleware","class":"Illuminate\\Queue\\CallQueuedHandler","type":"->","args":[{"Illuminate\\Queue\\Jobs\\SyncJob":[]},{"App\\Jobs\\ReportHealthStatusJob":{"job":[],"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php","line":102,"function":"call","class":"Illuminate\\Queue\\CallQueuedHandler","type":"->","args":[{"Illuminate\\Queue\\Jobs\\SyncJob":[]},{"commandName":"App\\Jobs\\ReportHealthStatusJob","command":"O:30:\"App\\Jobs\\ReportHealthStatusJob\":1:{s:10:\"connection\";s:4:\"sync\";}"}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php","line":76,"function":"fire","class":"Illuminate\\Queue\\Jobs\\Job","type":"->","args":[]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php","line":56,"function":"executeJob","class":"Illuminate\\Queue\\SyncQueue","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":null,"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}},"",null]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php","line":244,"function":"push","class":"Illuminate\\Queue\\SyncQueue","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":null,"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}},"",null]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php","line":228,"function":"pushCommandToQueue","class":"Illuminate\\Bus\\Dispatcher","type":"->","args":[{"Illuminate\\Queue\\SyncQueue":[]},{"App\\Jobs\\ReportHealthStatusJob":{"job":null,"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php","line":95,"function":"dispatchToQueue","class":"Illuminate\\Bus\\Dispatcher","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":null,"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Bus/Dispatchable.php","line":76,"function":"dispatchSync","class":"Illuminate\\Bus\\Dispatcher","type":"->","args":[{"App\\Jobs\\ReportHealthStatusJob":{"job":null,"connection":"sync","queue":null,"delay":null,"afterCommit":null,"middleware":[],"chained":[],"chainConnection":null,"chainQueue":null,"chainCatchCallbacks":null}}]},{"file":"/var/www/html/app/Console/Commands/HealthReportHealthStatusCommand.php","line":29,"function":"dispatchSync","class":"App\\Jobs\\ReportHealthStatusJob","type":"::","args":[]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php","line":36,"function":"handle","class":"App\\Console\\Commands\\HealthReportHealthStatusCommand","type":"->","args":[]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Util.php","line":43,"function":"Illuminate\\Container\\{closure}","class":"Illuminate\\Container\\BoundMethod","type":"::","args":[]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php","line":95,"function":"unwrapIfClosure","class":"Illuminate\\Container\\Util","type":"::","args":[{"Closure":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php","line":35,"function":"callBoundMethod","class":"Illuminate\\Container\\BoundMethod","type":"::","args":[{"Illuminate\\Foundation\\Application":{"contextual":[],"contextualAttributes":[]}},[{"App\\Console\\Commands\\HealthReportHealthStatusCommand":[]},"handle"],{"Closure":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php","line":696,"function":"call","class":"Illuminate\\Container\\BoundMethod","type":"::","args":[{"Illuminate\\Foundation\\Application":{"contextual":[],"contextualAttributes":[]}},[{"App\\Console\\Commands\\HealthReportHealthStatusCommand":[]},"handle"],[],null]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Console/Command.php","line":213,"function":"call","class":"Illuminate\\Container\\Container","type":"->","args":[[{"App\\Console\\Commands\\HealthReportHealthStatusCommand":[]},"handle"]]},{"file":"/var/www/html/vendor/symfony/console/Command/Command.php","line":279,"function":"execute","class":"Illuminate\\Console\\Command","type":"->","args":[{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"},{"Illuminate\\Console\\OutputStyle":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Console/Command.php","line":182,"function":"run","class":"Symfony\\Component\\Console\\Command\\Command","type":"->","args":[{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"},{"Illuminate\\Console\\OutputStyle":[]}]},{"file":"/var/www/html/vendor/symfony/console/Application.php","line":1094,"function":"run","class":"Illuminate\\Console\\Command","type":"->","args":[{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"},{"Symfony\\Component\\Console\\Output\\ConsoleOutput":[]}]},{"file":"/var/www/html/vendor/symfony/console/Application.php","line":342,"function":"doRunCommand","class":"Symfony\\Component\\Console\\Application","type":"->","args":[{"App\\Console\\Commands\\HealthReportHealthStatusCommand":[]},{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"},{"Symfony\\Component\\Console\\Output\\ConsoleOutput":[]}]},{"file":"/var/www/html/vendor/symfony/console/Application.php","line":193,"function":"doRun","class":"Symfony\\Component\\Console\\Application","type":"->","args":[{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"},{"Symfony\\Component\\Console\\Output\\ConsoleOutput":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php","line":198,"function":"run","class":"Symfony\\Component\\Console\\Application","type":"->","args":[{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"},{"Symfony\\Component\\Console\\Output\\ConsoleOutput":[]}]},{"file":"/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Application.php","line":1235,"function":"handle","class":"Illuminate\\Foundation\\Console\\Kernel","type":"->","args":[{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"},{"Symfony\\Component\\Console\\Output\\ConsoleOutput":[]}]},{"file":"/var/www/html/artisan","line":13,"function":"handleCommand","class":"Illuminate\\Foundation\\Application","type":"->","args":[{"Symfony\\Component\\Console\\Input\\ArgvInput":"'health:report-health-status'"}]}]}}} 
[2026-03-25 15:06:32] local.INFO: [ReportHealthStatusJob] Determined isHealthy status: YES (Passed checks: [testNginxConnection,testDatabaseConnection], Failed checks: []) {"failedCheckExceptions":[]} 

Als de hele scheduler or server down gaat, zal Uptime Kuma ook geen pings meer ontvangen en zal hij na een aantal gemiste pings ook een melding versturen naar de monitoring channels dat de health check/server niet meer lijkt te reageren.


Bekende Issues

Bekende issues bij ThysToys Logistics, met een verwijzing naar het bijbehorende KB-artikel voor de volledige oorzaak en oplossing.

Issue ClickUp ticket Datum KB-artikel
SSL-certificaat foutmelding ("Niet beveiligd") op uitscan PC's TT-1394 27 november 2025 KB: "Niet beveiligd" SSL foutmelding

Barcode Cheatsheet

Debug Mode in Development

Bij een debug build van de front-end verschijnt een invoerveld waarmee je handmatig barcodes kunt invoeren. Zo heb je tijdens ontwikkeling/testen geen fysieke scanner nodig.

Debug Mode in Productie

Ook in een productie build kun je handmatig barcodes invoeren door de geheime debug code in te voeren:

Code: AABBCC + Enter

Dit activeert het handmatige invoerveld.

Barcode Acties

Actie Barcode Formaat Toelichting
Printer selecteren PRINTER-{printer_id} Selecteer een specifieke printer voor het afdrukken van labels
Inloggen LT-{login_token} Login token moet 15 karakters lang zijn
Uitloggen LOGOUT Gebruiker uitloggen uit het systeem
Label selecteren LABEL Lijkt momenteel niet in gebruik te zijn
Order selecteren {order_token} Order token moet 10 karakters lang zijn, geen barcode prefix vereist

Voorbeeld barcodes

PRINTER-12          # Selecteer printer met ID 12
LT-ABC123456789012  # Login met token (15 chars)
LOGOUT              # Uitloggen
AB12345678          # Order selecteren (10 chars)

Veelgestelde Vragen

Hoe weet ik welke NUC momenteel actief is?

Je kunt dit controleren door te kijken welke server toegankelijk is via https://logistics.thystoys.nl. Momenteel is TY07WEB002 de actieve server.

Wat te doen bij uitval van de actieve NUC?
  1. Controleer of TY07WEB001 beschikbaar is
  2. Update de DNS/network configuratie om naar TY07WEB001 te wijzen
  3. Raadpleeg de Notion KB voor volledige fail-over procedure
Kan ik de applicatie van buitenaf benaderen?

Nee, de productie URL resolved naar een Local LAN IP address. De applicatie is alleen toegankelijk vanuit het lokale netwerk van ThysToys.


Gerelateerde Documentatie