Função & âmbito
A Cookinglife (loja online de artigos de cozinha, com lojas físicas a acompanhar) corre todo o processo de armazém sobre um sistema que construí e que continuo a desenvolver desde então: receção de encomendas, picking (individual, em lote, com carrinho e multinível), gestão de stock e de localizações, embalamento, etiquetas de envio e sincronização com o NetSuite como ERP. A par da manutenção contínua, a plataforma PHP existente está a ser migrada para Laravel módulo a módulo, sem interrupção de serviço.
- Da receção da encomenda ao envio: um único sistema para todo o processo de fulfilment, ligado ao NetSuite como fonte de verdade para encomendas e stock.
- Vários métodos de picking (individual, em lote, com carrinho, multinível) e leitura de códigos de barras no chão de armazém via OnScan.js.
- Etiquetas de envio multitransportadora via Sendcloud, com uma tarefa de varrimento própria que recolhe as etiquetas assim que o NetSuite as cria após o estado «packed».
- Integrações com Shopify e Magento, além da normalização de moradas e números de telefone através do Alumio.
- Migração para Laravel sem downtime: módulo a módulo (encomendas, picking, stock, envios, NetSuite, admin) através de routing nginx, com rollback imediato por módulo.
Impacto
- Um único sistema para todo o processo de fulfilment em vez de ferramentas separadas para picking, stock e envios.
- Resolvido um erro estrutural do NetSuite em encomendas de kits (produtos compostos por várias peças), alocando o stock automaticamente por várias localizações em vez de exigir que tudo estivesse numa só.
- Eliminada uma race condition entre a criação e a recolha de etiquetas de envio com uma tarefa de sincronização recorrente, pelo que as etiquetas deixaram de se perder de vez em quando.
- Um caminho de envio de recurso para quando o WMS cai fora do horário de expediente, com reconciliação para que as encomendas não reapareçam duplicadas na fila de picking.
- Quase dois anos em produção contínua (848 commits desde agosto de 2024) — não um projeto greenfield, mas um sistema que continua a crescer com a empresa.
Arquitetura
Três camadas: um Core partilhado, o próprio módulo TailorFitWMS e Overwrites por cliente — para que as adaptações específicas de uma empresa não acabem no núcleo. As funcionalidades são construídas como módulos plugin, ligáveis ou desligáveis por empresa, com pontos de extensão orientados a eventos. Os dados vivem em MySQL (dados transacionais), MongoDB (documentos) e Redis (cache e sessões); o trabalho em segundo plano (sync de etiquetas, tarefas NetSuite) corre via Laravel Horizon, com 100 a 500 jobs por hora. A migração em curso da plataforma PHP existente para Laravel é feita módulo a módulo através de blocos location do nginx, permitindo lançar e reverter cada parte em separado sem tocar no resto.
Escolha do modelo
Sem IA/LLM no núcleo desta plataforma — a lógica de fulfilment (stock, picking, envios, sincronização com o ERP) exige previsibilidade e rastreabilidade, não a flexibilidade de um modelo de linguagem. A principal «escolha de modelo» aqui é arquitetural e não de IA: sair do framework PHP próprio existente para Laravel, por um ecossistema de ferramentas maior e melhor manutenção, mas de forma gradual e módulo a módulo em vez de uma reescrita big-bang, para limitar o risco num sistema que comanda um armazém 24 horas por dia.
Custos & latência
- O ambiente de produção corre em 5 serviços Docker, cada um com 8 CPU / 8 GB atribuídos — um dimensionamento adequado a um sistema que processa continuamente tráfego de encomendas e NetSuite, não apenas picos.
- As etiquetas de envio são deliberadamente assíncronas: o NetSuite só cria a etiqueta após o estado «packed», pelo que o sistema não tem de esperar — uma tarefa de varrimento recolhe etiquetas a cada 3 minutos em vez de consultar encomenda a encomenda.
- A integração com o NetSuite (OAuth1 TBA, SOAP + RESTlet + REST) é o elo mais lento da cadeia; as ações locais de armazém, como scan e picking, continuam a correr independentemente.
Lições aprendidas
As encomendas de kits (produtos compostos por várias peças) falhavam no erro VALID_LINE_ITEM_REQD do NetSuite, porque o stock dos componentes estava espalhado por várias localizações enquanto o NetSuite esperava uma localização por linha. Resolvido reescrevendo a integração RESTlet para que o stock seja alocado automaticamente por várias localizações. As etiquetas de envio perdiam-se de vez em quando devido a uma race condition entre o momento em que o NetSuite cria a etiqueta e o momento em que o sistema a recolhe; uma tarefa de varrimento recorrente a cada 3 minutos resolveu isso estruturalmente em vez de com um patch pontual. E para as noites e fins de semana em que o próprio WMS cai, existe um caminho de emergência externo para continuar a expedir — com lógica de reconciliação que impede que essas mesmas encomendas reapareçam duplicadas na fila de picking quando o sistema volta.