InternalInventoryController.java

package com.ecommerce.inventory.interfaces.rest;

import com.ecommerce.inventory.application.model.InventoryModels.ReservationLineCommand;
import com.ecommerce.inventory.application.model.InventoryModels.ReservationView;
import com.ecommerce.inventory.application.model.InventoryModels.ReserveInventoryCommand;
import com.ecommerce.inventory.application.model.InventoryModels.WarehouseView;
import com.ecommerce.inventory.application.service.InventoryService;
import com.ecommerce.platform.common.api.ApiResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Instant;
import java.util.List;

@RestController
@RequestMapping("/api/v1/inventory/internal")
public class InternalInventoryController {

    private static final String BUSINESS_NO_PATTERN = "[A-Za-z0-9._:-]+";

    private final InventoryService inventoryService;

    public InternalInventoryController(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    @PostMapping("/reservations")
    public ApiResponse<ReservationView> reserve(@Valid @RequestBody ReserveRequest request) {
        List<ReservationLineCommand> items = request.items().stream()
                .map(item -> new ReservationLineCommand(item.skuId(), item.quantity()))
                .toList();
        return ApiResponse.success(inventoryService.reserve(new ReserveInventoryCommand(
                request.reservationNo(), request.orderNo(), request.warehouseId(), request.expiresAt(), items)));
    }

    @GetMapping("/warehouses/{code}")
    public ApiResponse<WarehouseView> warehouse(
            @PathVariable @Pattern(regexp = "[A-Z0-9_-]{2,32}") String code) {
        return ApiResponse.success(inventoryService.getActiveWarehouseByCode(code));
    }

    @GetMapping("/reservations/{reservationNo}")
    public ApiResponse<ReservationView> reservation(
            @PathVariable @Pattern(regexp = BUSINESS_NO_PATTERN) String reservationNo) {
        return ApiResponse.success(inventoryService.getReservation(reservationNo));
    }

    @PostMapping("/reservations/{reservationNo}/confirm")
    public ApiResponse<ReservationView> confirm(
            @PathVariable @Pattern(regexp = BUSINESS_NO_PATTERN) String reservationNo) {
        return ApiResponse.success(inventoryService.confirmReservation(reservationNo));
    }

    @PostMapping("/reservations/{reservationNo}/release")
    public ApiResponse<ReservationView> release(
            @PathVariable @Pattern(regexp = BUSINESS_NO_PATTERN) String reservationNo) {
        return ApiResponse.success(inventoryService.releaseReservation(reservationNo));
    }

    @PostMapping("/reservations/{reservationNo}/expire")
    public ApiResponse<ReservationView> expire(
            @PathVariable @Pattern(regexp = BUSINESS_NO_PATTERN) String reservationNo) {
        return ApiResponse.success(inventoryService.expireReservation(reservationNo));
    }

    public record ReserveRequest(
            @NotBlank @Size(max = 64) @Pattern(regexp = BUSINESS_NO_PATTERN) String reservationNo,
            @NotBlank @Size(max = 64) @Pattern(regexp = BUSINESS_NO_PATTERN) String orderNo,
            @NotNull @Positive Long warehouseId,
            @NotNull @Future Instant expiresAt,
            @NotEmpty @Size(max = 100) List<@Valid ReserveItemRequest> items
    ) {
    }

    public record ReserveItemRequest(
            @NotNull @Positive Long skuId,
            @Positive @Max(1000000000L) long quantity
    ) {
    }
}