Skip to main content

logistic-au

1. Descripción general

Según el pom.xml, el proyecto se describe como "Logistic from AU". Es un microservicio batch (runner) que toma los pedidos de Australia pendientes de envío logístico, los transforma al formato XML del carrier NPF (National Parcel Federation), los envía a su API de importación, y — solo para pedidos no-marketplace — actualiza el stock en The Iconic tras el envío. Sube también los XML de request/response a Google Cloud Storage como auditoría y notifica errores por Slack.

2. Información técnica

CampoValor
artifactIdlogistic-au
groupIdcom.hawkersco
version1.0.25
Java25 (maven.compiler.release=25)
Spring Boot4.0.6
Tipo de artefactojar (ejecutable, Spring Boot batch/CLI, spring.main.web-application-type=none)
MódulosNo aplica (proyecto de módulo único)

3. Arquitectura y diseño

No es una API REST: es una aplicación Spring Boot CLI con dos CommandLineRunner, de los cuales solo uno está activo.

Paquetes principales:

  • com.hawkersco.logisticau — clase principal (LogisticAuApplication) y los dos runners.
  • .configLogisticAuConfiguration (declaración manual de servicios de logistics-commons), NpfClientProperties (record @ConfigurationProperties, prefijo logisticau.npfclient).
  • .npf — DTOs anotados con JAXB para (de)serializar el XML de NPF (NpfOrder, BillingAddress, ShippingAddress, Products, Totals...) y NpfUtils (construcción del pedido NPF + POST HTTP + parseo de respuesta).
  • .utilsLogisticAuUtils (transiciones de éxito/error compartidas), LogisticAuConst (constantes).
flowchart TD
A["LogisticAuRunner (order=1, ACTIVO)<br/>Pedidos no-marketplace"] -->|construye XML NPF| B[NPF API]
A -->|sube request/response| C[(GCS pi-logistics-segment)]
A -->|si éxito| D[The Iconic: actualiza stock]
A -->|si falla 4 veces| E[Slack]

F["LogisticAuMpRunner (order=2, DESACTIVADO)<br/>Pedidos marketplace"] -.->|mismo flujo NPF| B
F -.->|System.exit al terminar| G[Fin del proceso]

LogisticAuConfiguration declara manualmente los servicios de logistics-commons usados (no son @Component en la librería externa). NpfClientProperties se inyecta como bean @ConfigurationProperties, pero ambos runners acceden a sus campos (username(), password(), clientcode()) en lugar de usar @Value individuales.

4. Dependencias principales

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
com.hawkersco:logistics-commons:1.0.25-SNAPSHOTEntidades JPA y servicios de logística compartidos
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOTDateUtils, StorageUtils, SeveralUtils (serialización JAXB→XML)
com.hawkersco:theiconic-client:1.0.25-SNAPSHOTCliente REST (TheIconicRestClient) para consultar/actualizar stock en The Iconic
com.hawkersco:slack-client:1.0.25-SNAPSHOTNotificaciones de error a Slack
lombokGeneración de código boilerplate
spring-boot-starter-test (test)JUnit 5 + Spring Test

5. API / Endpoints

No aplica a este proyecto. Es un batch/runner sin capa REST propia.

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
API NPF (npfonline.com/NPFM/FMImportAPI_V2.asmx/ImportSingleOrder)HTTP (POST XML)SalienteEnvío del pedido en formato XML; respuesta XML parseada vía JAXB
The Iconic (sellercenter-api.theiconic.com.au)HTTP (TheIconicRestClient)SalientegetProductBySellerSku, getStockProductById, putStockProduct — solo desde LogisticAuRunner
Google Cloud Storage (bucket pi-logistics-segment)API de GCSSalienteAuditoría de requests (logistic-au/request/yyyy/MM/dd/) y responses (response/ok/, response/ko/)
SlackHTTP (SlackClient)SalienteAlertas de fallo de envío y de parseo de respuesta NPF
PostgreSQL (logistics)JDBCEntrante/SalienteLectura de pedidos pendientes y actualización de estado vía logistics-commons

7. Configuración

En producción (application-pro.properties) las credenciales llegan por variables de entorno inyectadas como Secret de Kubernetes; en local (application.properties) el repositorio contiene actualmente valores reales hardcodeados (ver alerta de seguridad).

ClaveDescripciónEjemplo (producción)
spring.datasource.url / .username / .passwordCredenciales de la BD logistics${dbLogisitcsUrl}, etc.
gcs.bucket.nameBucket de GCS para auditoríapi-logistics-segment
theiconic.api.*URL, filtros, versión, API key y credenciales OAuth de The Iconic${theiconicApiUrl}, etc.
logisticau.npfclient.username / .password / .clientcodeCredenciales del cliente NPF (perfil PLY en producción)${npfclientUsername}, etc.
slack.client.url / .auth.token / .channel.id / .channel-atc.idConfiguración del cliente Slack (dos canales: general y ATC)${slackClientUrl}, etc.
hawkers.orders.testCadena que marca un pedido de prueba dentro de rawDatalahermanadeaxel

⚠️ Alerta de seguridad

El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano: contraseña de la base de datos PostgreSQL, API key y clientSecret OAuth de The Iconic, credenciales del cliente NPF de producción (PLY/PLY212) y de un perfil de desarrollo comentado (NPFDRAPI), y token de bot de Slack (xoxb-...). Ninguno de estos valores se ha reproducido en este documento. Se recomienda:

  1. Rotar la contraseña de BD, las credenciales de The Iconic y las credenciales NPF (tanto la de producción como la de desarrollo comentada).
  2. Rotar el token de Slack expuesto.
  3. Sustituir los valores hardcodeados de application.properties por credenciales de un entorno de desarrollo aislado.
  4. Revisar el historial de control de versiones, ya que estas credenciales pueden seguir expuestas en commits anteriores.

8. Persistencia

Base de datos PostgreSQL (logistics), acceso vía JPA a través de la librería logistics-commons (@EnableJpaRepositories("com.hawkersco.logisticscommons.repository")). spring.jpa.hibernate.ddl-auto=none. Entidades relevantes: Order, OrderLine, Customer, OrderError, SfccShippingMethod. No hay Flyway/Liquibase en este repositorio.

9. Procesos programados y mensajería

No hay @Scheduled ni listeners de colas: la periodicidad la impone el CronJob de Kubernetes (k8s/cronjob.yaml), que ejecuta el contenedor cada 5 minutos (schedule: "0/5 * * * *", concurrencyPolicy: Forbid). Solo se ejecuta el runner activo:

  • LogisticAuRunner (order=1, único activo): obtiene los pedidos AU no procesados no-marketplace (orderService.findByOrdersNoProcessedAu); para cada uno: si el rawData contiene la cadena de test, lo marca como TEST y detiene el bucle (break, no continue — ver hallazgo en la sección 13); en caso contrario construye el XML NPF, lo sube a GCS, lo envía al carrier, procesa éxito/error, y si tuvo éxito actualiza el stock en The Iconic restando la cantidad vendida. Tras 4 intentos fallidos (nmSendLogistic >= 4), marca el pedido como error definitivo y notifica por Slack al canal ATC.

Reglas de negocio relevantes (verificadas en el código):

  • Un XML de respuesta NPF con status = "Previously Imported" se trata como éxito (el pedido pasa a INIT y se marca como enviado).
  • El método de envío se resuelve por prioridad: mapeo SFCC (SfccShippingMethodService) → si idOrderServiceType == 27 entonces "Australia Post Express Parcel" → por defecto "Standard [ISO2 país]".
  • Las direcciones que llegan como arrays JSON se limpian con una regex precompilada en NpfUtils.cleanString().
  • La descripción de producto se trunca a 30 caracteres antes de enviarla a NPF.

10. Ejecución en local

Requisitos previos: JDK 25, Maven, acceso a la BD logistics, credenciales de aplicación por defecto de Google (GCS) y credenciales válidas de NPF/The Iconic en un application.properties local.

# Compilar sin tests (patrón de CI)
mvn clean install -DskipTests

# Ejecutar tests
mvn test

# Ejecutar un test concreto
mvn test -Dtest=LogisticAuApplicationTests

# Ejecutar la aplicación localmente
mvn spring-boot:run

Al ser un CommandLineRunner sin servidor web, no expone Actuator/health: la forma de verificar la ejecución es revisar el log de consola o los ficheros subidos a GCS bajo logistic-au/.

11. Despliegue

  • Imagen: construida con jib-maven-plugin (base eclipse-temurin:25-jre, containerizingMode=packaged), publicada en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/logistic-au:<tag>. El CLAUDE.md menciona una imagen base eclipse-temurin:17-jdk-alpine, que no coincide con la configuración actual del pom.xml.
  • Orquestación: Kubernetes CronJob (k8s/cronjob.yaml) en el clúster GKE pi-cluster-hw (zona europe-west3-a, proyecto pi-saldum), namespace pi, ejecutándose cada 5 minutos.
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & Push (sustituye application-pro.properties por application.properties) → Deploy to GKE (borra el CronJob existente y aplica el manifiesto templado). El CLAUDE.md describe un pipeline más extenso (Build → KICS scan → SonarQube → Test → Push → Deploy → Clean) que no coincide con el Jenkinsfile real.
  • Las variables sensibles se inyectan en el pod mediante un Secret de Kubernetes llamado igual que la app (logistic-au).

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/logistic-au/

12. Manejo de errores y logging

No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). sendOrderRequestLogisticNPF captura excepciones de conexión y de parseo JAXB, notificando a Slack en ambos casos y devolviendo un resultado de fallo sin interrumpir el resto del pedido. LogisticAuUtils.processOrderError incrementa el contador de reintentos (nmSendLogistic) y registra la respuesta de error con Level.SEVERE. Tras 4 reintentos fallidos, se marca el pedido como error definitivo (OrderError) y se envía una alerta específica al canal Slack "ATC" con enlace directo al pedido en el panel interno (infranete.hawkersco.net/order-edit/<pedido>). Logging mediante java.util.logging.Logger estándar (consola).

13. Notas y consideraciones

  • LogisticAuMpRunner está desactivado y los pedidos de marketplace AU no se procesan: la clase LogisticAuMpRunner (order=2) tiene el @Component comentado. Según el CLAUDE.md, este runner es responsable de procesar los "pedidos de marketplace" (orderService.findByOrdersNoProcessedMpAu) y de cerrar la aplicación (SpringApplication.exit). Con el runner desactivado, ningún pedido de marketplace AU se envía actualmente a NPF por este servicio, ya que solo LogisticAuRunner (no-marketplace) está activo. El CLAUDE.md describe ambos runners como si estuvieran operativos y no menciona en absoluto que uno de los dos está apagado; es la discrepancia más relevante encontrada en este proyecto y probablemente el motivo de que ningún proceso llame ya a System.exit (solo lo hacía el runner desactivado).
  • Pedido de test detiene el resto del lote: en LogisticAuRunner.run(), al detectar un pedido de test se ejecuta break en lugar de continue, por lo que el bucle se interrumpe por completo y ningún pedido posterior de esa ejecución se procesa hasta la siguiente invocación del CronJob (5 minutos después). Si esto no es intencional, es un defecto que retrasa el procesamiento de pedidos reales cuando aparecen pedidos de prueba en medio del lote.
  • NpfClientProperties coexiste con @Value puntuales: aunque las credenciales NPF se centralizan en un record @ConfigurationProperties, ambos runners siguen usando @Value individuales para el resto de la configuración (gcs.bucket.name, slack.channel.id, etc.), un patrón mixto que el propio CLAUDE.md señala como una transición incompleta.
  • Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en application.properties.