Class WarehouseDispatcherService

java.lang.Object
com.v1rex.liftnexus.planning.service.WarehouseDispatcherService

@Service public class WarehouseDispatcherService extends Object
Service responsible for orchestrating warehouse optimisation using Timefold Solver.

Manages the full lifecycle of a dispatch-optimisation job: submitting new jobs, monitoring their status, terminating running jobs, and persisting the final forklift-to-transport-order assignments. Acts as the bridge between the warehouse domain (bins, forklifts, transport orders) and the constraint-solving engine.

Job lifecycle: QUEUED → SOLVING → COMPLETED / FAILED / ABORTED.

  • Constructor Details

    • WarehouseDispatcherService

      public WarehouseDispatcherService()
  • Method Details

    • getJobStatusAndReconcile

      public DispatchJobResponse getJobStatusAndReconcile(UUID jobId)
      Returns the current status of a dispatch job.

      Intended to be extended with reconciliation logic that detects when Timefold has silently stopped solving (e.g., due to an internal error) while the job is still marked as SOLVING in the database, and marks it as FAILED accordingly.

      Parameters:
      jobId - The unique identifier of the dispatch job.
      Returns:
      A response DTO with the current job state.
      Throws:
      DispatchJobNotFoundException - if no job exists for the given ID.
    • buildCurrentState

      public WarehouseSchedule buildCurrentState()
      Assembles the current warehouse snapshot that the solver will optimise.

      Loads all storage bins, forklifts, and pending transport orders from the database into an in-memory WarehouseSchedule object. This serves as the problem fact set for the Timefold constraint solver.

      Known issue: Loading all entities without pagination is a performance bottleneck for large datasets. A more selective fetching strategy (cursor-based pagination, lazy fields, caching) should be implemented.

      Returns:
      A fully populated WarehouseSchedule representing the current state of the warehouse.
    • submitOptimizationJob

      public UUID submitOptimizationJob()
      Submits a new asynchronous optimisation job to Timefold.

      Creates a DispatchJob record in state QUEUED, then delegates to SolverManager.solveAndListen(java.lang.Object, Solution_, java.util.function.Consumer<? super Solution_>) with:

      1. A problem-fetcher that transitions the job to SOLVING and builds the current warehouse state.
      2. A best-solution consumer that persists the final assignments and marks the job as COMPLETED (or FAILED).
      Returns:
      The UUID assigned to the newly created optimisation job. The caller can use this ID to poll status (getJobStatusAndReconcile(java.util.UUID)) or to terminate the job (terminateOptimizationJob(java.util.UUID)).
    • terminateOptimizationJob

      @Transactional public void terminateOptimizationJob(UUID jobId)
      Attempts to gracefully terminate a running or queued optimisation job.

      Only jobs in state QUEUED or SOLVING can be terminated. Once terminated, the job is marked as ABORTED and any partial results are discarded (the best-solution consumer checks for this flag).

      Parameters:
      jobId - The unique identifier of the job to terminate.
      Throws:
      DispatchJobInvalidStateException - if the job is already in a terminal state ( COMPLETED, FAILED, or ABORTED).
    • buildCurrentProblemAndSetSolvingStatus

      @Transactional public WarehouseSchedule buildCurrentProblemAndSetSolvingStatus(UUID jobId)
      Fetcher callback used by Timefold at the start of solving.

      Transitions the job from QUEUED to SOLVING in the database and then builds the current warehouse snapshot. If building the snapshot fails, the job is marked as FAILED and the exception is rethrown to Timefold.

      Note: This method is annotated @Transactional so that the status update and snapshot build happen within the same persistence context.

      Parameters:
      jobId - The unique identifier of the job being started.
      Returns:
      A fully populated WarehouseSchedule for the solver.
    • saveFinalSolution

      @Transactional public void saveFinalSolution(WarehouseSchedule solution, UUID jobId)
      Best-solution consumer callback invoked by Timefold when a solution is available (either the final optimal solution or an intermediate best-effort one).

      Persists the solver's assignments back to the database:

      • Updates forklift assignments on transport orders.
      • Records assigned orders on forklifts.

      If the job was ABORTED during solving, the solution is silently discarded. Otherwise the job is marked as COMPLETED (or FAILED if persistence fails). Even infeasible solutions are saved as a best-effort fallback.

      Parameters:
      solution - The WarehouseSchedule produced by Timefold, which contains the optimised assignments.
      jobId - The unique identifier of the job that produced this solution.
    • findJobEntityById

      public DispatchJob findJobEntityById(UUID jobId)
      Retrieves the raw DispatchJob entity by its ID.
      Parameters:
      jobId - The unique identifier of the dispatch job.
      Returns:
      The managed DispatchJob entity.
      Throws:
      DispatchJobNotFoundException - if no job exists for the given ID.