TailorFitWMS — Lagerhanteringssystem

Ett skräddarsytt lagersystem för Cookinglife: från ordermottagning och plockning till lagersaldon, fraktetiketter och NetSuite-ERP-synk — snart två år i löpande drift och underhåll.

Lagerhantering
PHP-→-Laravel-migrering
NetSuite ERP
Frakt med flera transportörer

Roll & omfattning

Cookinglife (webbshop för köksartiklar, med fysiska butiker vid sidan om) kör hela sin lagerprocess på ett system som jag byggt och sedan dess vidareutvecklar: ordermottagning, plockning (styck, batch, vagn och flernivå), lager- och platshantering, packning, fraktetiketter och synkronisering med NetSuite som ERP. Vid sidan av det löpande underhållet migreras den befintliga PHP-plattformen till Laravel, modul för modul, utan driftstopp.

  • Från ordermottagning till frakt: ett enda system för hela fulfilmentprocessen, kopplat till NetSuite som sanningskälla för ordrar och lagersaldon.
  • Flera plockmetoder (styck, batch, vagn, flernivå) och streckkodsläsning på golvet via OnScan.js.
  • Fraktetiketter för flera transportörer via Sendcloud, med ett eget sweep-jobb som hämtar etiketter så snart NetSuite skapat dem efter statusen ”packed”.
  • Integrationer mot Shopify och Magento, plus normalisering av adresser och telefonnummer via Alumio.
  • Migrering till Laravel utan driftstopp: modul för modul (ordrar, plockning, lager, frakt, NetSuite, admin) via nginx-routing, med omedelbar rollback per modul.
Se plattformen live

Effekt

  • Ett system för hela fulfilmentprocessen i stället för separata verktyg för plockning, lager och frakt.
  • Ett strukturellt NetSuite-fel på kit-ordrar (produkter som består av flera delar) löstes genom att allokera lager automatiskt över flera lagerplatser i stället för att kräva att allt ligger på en enda.
  • Ett race condition mellan att skapa och hämta fraktetiketter togs bort med ett återkommande synkjobb, så att etiketter inte längre försvann då och då.
  • En reservväg för frakt när WMS:et ligger nere utanför kontorstid, med avstämning så att ordrar inte dyker upp dubbelt i plockkön.
  • Snart två år i löpande produktion (848 commits sedan augusti 2024) — inget greenfield-projekt utan ett system som fortsätter växa med företaget.

Arkitektur

Tre lager: en delad Core, själva TailorFitWMS-modulen och Overwrites per kund — så att företagsspecifika anpassningar aldrig hamnar i kärnan. Funktionalitet är byggd som pluginmoduler som kan slås på eller av per företag, med händelsestyrda utbyggnadspunkter. Data ligger i MySQL (transaktionsdata), MongoDB (dokument) och Redis (cache och sessioner); bakgrundsarbete (etikettsynk, NetSuite-uppgifter) körs via Laravel Horizon, med 100 till 500 jobb i timmen. Den pågående migreringen av den befintliga PHP-plattformen till Laravel sker modul för modul via nginx location-block, så att varje del kan rullas ut och rullas tillbaka separat utan att röra resten.

Modellval

Ingen AI/LLM i kärnan av den här plattformen — fulfilmentlogik (lager, plockning, frakt, ERP-synk) kräver förutsägbarhet och spårbarhet, inte ett språkmodells flexibilitet. Det viktigaste ”modellvalet” här är arkitektoniskt, inte AI: att lämna det befintliga egenbyggda PHP-ramverket för Laravel, för ett större verktygsekosystem och bättre underhållbarhet, men gradvis och modul för modul i stället för en big bang-omskrivning, för att begränsa risken i ett system som styr ett lager dygnet runt.

Kostnad & latens

  • Produktionsmiljön kör på 5 Docker-tjänster med 8 CPU / 8 GB var — en dimensionering som passar ett system som löpande behandlar order- och NetSuite-trafik, inte bara topplast.
  • Fraktetiketter är medvetet asynkrona: NetSuite skapar etiketten först efter statusen ”packed”, så systemet behöver inte vänta — ett sweep-jobb hämtar etiketter var tredje minut i stället för att polla per order.
  • NetSuite-integrationen (OAuth1 TBA, SOAP + RESTlet + REST) är den långsammare länken i kedjan; lokala lageroperationer som skanning och plockning fortsätter oberoende av den.

Lärdomar

Kit-ordrar (produkter som består av flera delar) fastnade gång på gång på NetSuites fel VALID_LINE_ITEM_REQD, eftersom komponenternas lager låg utspritt över flera lagerplatser medan NetSuite förväntade sig en plats per rad. Löst genom att skriva om RESTlet-integrationen så att lager allokeras automatiskt över flera platser. Fraktetiketter visade sig försvinna då och då på grund av ett race condition mellan när NetSuite skapar en etikett och när systemet hämtar den; ett återkommande sweep-jobb var tredje minut löste det strukturellt i stället för som en engångspatch. Och för kvällar och helger när WMS:et självt ligger nere finns en extern nödväg så att frakt kan fortsätta — med avstämningslogik som hindrar att samma ordrar dyker upp dubbelt i plockkön när systemet kommer tillbaka.