google-site-verification: google61fe8ba583a51912.html
Warehouse Bin Location System in Java: Complete Guide

A warehouse bin location system manages storage locations, inventory placement, and retrieval operations. This implementation covers bin management, inventory tracking, and optimization algorithms.


1. Project Setup and Dependencies

Maven Dependencies (pom.xml)

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>warehouse-bin-system</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.boot.version>2.7.14</spring.boot.version> <jackson.version>2.15.2</jackson.version> <hibernate.spatial.version>5.6.15.Final</hibernate.spatial.version> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>${spring.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>${spring.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>${spring.boot.version}</version> </dependency> <!-- Spatial Data Support --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-spatial</artifactId> <version>${hibernate.spatial.version}</version> </dependency> <!-- Database --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- Cache --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.6</version> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring.boot.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring.boot.version}</version> </plugin> </plugins> </build> </project>

2. Configuration Classes

Application Properties (application.yml)

# Database Configuration spring: datasource: url: jdbc:postgresql://localhost:5432/warehouse_db username: warehouse_user password: warehouse_pass driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true jdbc: lob: non_contextual_creation: true show-sql: true # Spatial Data Configuration jpa: properties: hibernate: spatial: dialect: org.hibernate.spatial.dialect.postgis.PostgisDialect # Warehouse Configuration warehouse: bin: code: pattern: "[A-Z]{2}-[0-9]{3}-[A-Z]{1}-[0-9]{2}" separator: "-" dimensions: max-weight: 1000.0 # kg max-volume: 10.0 # cubic meters optimization: putaway-strategy: FEFO # FIFO, LIFO, FEFO, Closest picking-strategy: FEFO enable-slotting: true replenishment: min-threshold: 0.2 # 20% max-threshold: 0.8 # 80% # Server Configuration server: port: 8080 # Logging logging: level: com.example.warehouse: DEBUG org.hibernate.SQL: DEBUG

Warehouse Configuration Class

import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "warehouse.bin") public class WarehouseConfig { private String codePattern = "[A-Z]{2}-[0-9]{3}-[A-Z]{1}-[0-9]{2}"; private String separator = "-"; private Dimensions dimensions = new Dimensions(); private Optimization optimization = new Optimization(); private Replenishment replenishment = new Replenishment(); @Data public static class Dimensions { private double maxWeight = 1000.0; private double maxVolume = 10.0; private double maxHeight = 3.0; private double maxWidth = 2.0; private double maxDepth = 2.0; } @Data public static class Optimization { private PutawayStrategy putawayStrategy = PutawayStrategy.FEFO; private PickingStrategy pickingStrategy = PickingStrategy.FEFO; private boolean enableSlotting = true; private boolean enableCrossDocking = false; } @Data public static class Replenishment { private double minThreshold = 0.2; private double maxThreshold = 0.8; private int bufferStock = 10; } public enum PutawayStrategy { FIFO, LIFO, FEFO, CLOSEST, RANDOM, OPTIMAL_SPACE } public enum PickingStrategy { FIFO, LIFO, FEFO, CLOSEST, BATCH } }

3. Database Entities

Warehouse Entity

import lombok.Data; import org.locationtech.jts.geom.Point; import javax.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "warehouses") @Data public class Warehouse { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "code", unique = true, nullable = false) private String code; @Column(name = "name", nullable = false) private String name; @Column(name = "description") private String description; @Column(name = "address_line1") private String addressLine1; @Column(name = "address_line2") private String addressLine2; @Column(name = "city") private String city; @Column(name = "state") private String state; @Column(name = "country") private String country; @Column(name = "postal_code") private String postalCode; @Column(name = "total_capacity") private Integer totalCapacity; @Column(name = "used_capacity") private Integer usedCapacity; @Column(name = "is_active") private Boolean isActive = true; @Column(columnDefinition = "geometry(Point,4326)") private Point location; @Column(name = "operating_hours") private String operatingHours; @Column(name = "contact_phone") private String contactPhone; @Column(name = "contact_email") private String contactEmail; @OneToMany(mappedBy = "warehouse", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Zone> zones = new ArrayList<>(); @OneToMany(mappedBy = "warehouse", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Aisle> aisles = new ArrayList<>(); @Column(name = "created_at") private LocalDateTime createdAt; @Column(name = "updated_at") private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); if (usedCapacity == null) { usedCapacity = 0; } } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } }

Zone Entity

import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "zones") @Data public class Zone { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "code", nullable = false) private String code; @Column(name = "name", nullable = false) private String name; @Column(name = "description") private String description; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "warehouse_id", nullable = false) private Warehouse warehouse; @Column(name = "zone_type") @Enumerated(EnumType.STRING) private ZoneType zoneType; @Column(name = "temperature_range") private String temperatureRange; @Column(name = "security_level") private String securityLevel; @Column(name = "max_weight_capacity") private Double maxWeightCapacity; @Column(name = "max_volume_capacity") private Double maxVolumeCapacity; @Column(name = "current_weight_utilization") private Double currentWeightUtilization = 0.0; @Column(name = "current_volume_utilization") private Double currentVolumeUtilization = 0.0; @Column(name = "is_active") private Boolean isActive = true; @OneToMany(mappedBy = "zone", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Aisle> aisles = new ArrayList<>(); @Column(name = "created_at") private LocalDateTime createdAt; @Column(name = "updated_at") private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } public enum ZoneType { BULK_STORAGE, RACK_STORAGE, PICKING_ZONE, COLD_STORAGE, HAZMAT_STORAGE, QUARANTINE, CROSS_DOCK, STAGING } }

Aisle Entity

import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "aisles") @Data public class Aisle { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "code", nullable = false) private String code; @Column(name = "name") private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "warehouse_id", nullable = false) private Warehouse warehouse; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "zone_id") private Zone zone; @Column(name = "aisle_number") private Integer aisleNumber; @Column(name = "side") @Enumerated(EnumType.STRING) private Side side; @Column(name = "width") private Double width; // meters @Column(name = "height") private Double height; // meters @Column(name = "length") private Double length; // meters @Column(name = "access_type") @Enumerated(EnumType.STRING) private AccessType accessType; @Column(name = "is_active") private Boolean isActive = true; @OneToMany(mappedBy = "aisle", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Rack> racks = new ArrayList<>(); @Column(name = "created_at") private LocalDateTime createdAt; @Column(name = "updated_at") private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } public enum Side { LEFT, RIGHT, BOTH } public enum AccessType { SINGLE_ACCESS, DOUBLE_ACCESS, DRIVE_THROUGH } }

Rack Entity

import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "racks") @Data public class Rack { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "code", nullable = false) private String code; @Column(name = "name") private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "aisle_id", nullable = false) private Aisle aisle; @Column(name = "rack_number") private Integer rackNumber; @Column(name = "rack_type") @Enumerated(EnumType.STRING) private RackType rackType; @Column(name = "levels") private Integer levels; @Column(name = "bays") private Integer bays; @Column(name = "max_weight_per_level") private Double maxWeightPerLevel; @Column(name = "max_height") private Double maxHeight; @Column(name = "current_weight_utilization") private Double currentWeightUtilization = 0.0; @Column(name = "is_active") private Boolean isActive = true; @OneToMany(mappedBy = "rack", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<BinLocation> binLocations = new ArrayList<>(); @Column(name = "created_at") private LocalDateTime createdAt; @Column(name = "updated_at") private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); } public enum RackType { SELECTIVE, DRIVE_IN, DRIVE_THROUGH, PUSH_BACK, CANTILEVER, MOBILE, PALLET_FLOW } }

Bin Location Entity (Core Entity)

import lombok.Data; import org.locationtech.jts.geom.Point; import javax.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "bin_locations", indexes = { @Index(name = "idx_bin_code", columnList = "code"), @Index(name = "idx_bin_warehouse", columnList = "warehouse_id"), @Index(name = "idx_bin_status", columnList = "status"), @Index(name = "idx_bin_zone", columnList = "zone_type") }) @Data public class BinLocation { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "code", unique = true, nullable = false) private String code; @Column(name = "name") private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "warehouse_id", nullable = false) private Warehouse warehouse; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "zone_id") private Zone zone; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "aisle_id") private Aisle aisle; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "rack_id") private Rack rack; @Column(name = "level_number") private Integer levelNumber; @Column(name = "bay_number") private Integer bayNumber; @Column(name = "position_number") private Integer positionNumber; @Column(name = "bin_type") @Enumerated(EnumType.STRING) private BinType binType; @Column(name = "zone_type") @Enumerated(EnumType.STRING) private ZoneType zoneType; @Column(name = "status") @Enumerated(EnumType.STRING) private BinStatus status; @Column(name = "max_weight_capacity") private Double maxWeightCapacity; @Column(name = "max_volume_capacity") private Double maxVolumeCapacity; @Column(name = "current_weight") private Double currentWeight = 0.0; @Column(name = "current_volume") private Double currentVolume = 0.0; @Column(name = "utilization_percentage") private Double utilizationPercentage = 0.0; @Column(name = "dimension_width") private Double dimensionWidth; @Column(name = "dimension_height") private Double dimensionHeight; @Column(name = "dimension_depth") private Double dimensionDepth; @Column(name = "pick_sequence") private Integer pickSequence; @Column(name = "putaway_sequence") private Integer putawaySequence; @Column(name = "is_fast_moving") private Boolean isFastMoving = false; @Column(name = "last_activity_date") private LocalDateTime lastActivityDate; @Column(columnDefinition = "geometry(Point,4326)") private Point coordinates; @Column(name = "address_string") private String addressString; @OneToMany(mappedBy = "binLocation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<InventoryItem> inventoryItems = new ArrayList<>(); @Column(name = "created_at") private LocalDateTime createdAt; @Column(name = "updated_at") private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); if (lastActivityDate == null) { lastActivityDate = LocalDateTime.now(); } calculateUtilization(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); calculateUtilization(); } private void calculateUtilization() { if (maxVolumeCapacity != null && maxVolumeCapacity > 0) { this.utilizationPercentage = (currentVolume / maxVolumeCapacity) * 100; } } public boolean canAccommodate(double weight, double volume) { return (currentWeight + weight) <= maxWeightCapacity && (currentVolume + volume) <= maxVolumeCapacity; } public enum BinType { PALLET, SHELF, BIN, CARTON_FLOW, FLOOR_STACK, BULK_STORAGE, MEZZANINE, AUTOMATED } public enum ZoneType { PICKING, STORAGE, OVERFLOW, QUARANTINE, CROSS_DOCK, STAGING, HAZMAT } public enum BinStatus { AVAILABLE, OCCUPIED, RESERVED, BLOCKED, UNDER_MAINTENANCE, QUARANTINED } }

Inventory Item Entity

import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; import java.time.LocalDateTime; @Entity @Table(name = "inventory_items") @Data public class InventoryItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "sku", nullable = false) private String sku; @Column(name = "product_name") private String productName; @Column(name = "description") private String description; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "bin_location_id", nullable = false) private BinLocation binLocation; @Column(name = "quantity") private Integer quantity; @Column(name = "allocated_quantity") private Integer allocatedQuantity = 0; @Column(name = "available_quantity") private Integer availableQuantity; @Column(name = "batch_number") private String batchNumber; @Column(name = "lot_number") private String lotNumber; @Column(name = "serial_number") private String serialNumber; @Column(name = "expiry_date") private LocalDateTime expiryDate; @Column(name = "manufacturing_date") private LocalDateTime manufacturingDate; @Column(name = "received_date") private LocalDateTime receivedDate; @Column(name = "unit_cost", precision = 15, scale = 4) private BigDecimal unitCost; @Column(name = "total_value", precision = 15, scale = 4) private BigDecimal totalValue; @Column(name = "weight") private Double weight; @Column(name = "volume") private Double volume; @Column(name = "dimensions") private String dimensions; @Column(name = "uom") private String uom; // Unit of Measure @Column(name = "status") @Enumerated(EnumType.STRING) private InventoryStatus status; @Column(name = "abc_classification") @Enumerated(EnumType.STRING) private AbcClassification abcClassification; @Column(name = "turnover_rate") private Double turnoverRate; @Column(name = "last_count_date") private LocalDateTime lastCountDate; @Column(name = "next_count_date") private LocalDateTime nextCountDate; @Column(name = "created_at") private LocalDateTime createdAt; @Column(name = "updated_at") private LocalDateTime updatedAt; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); if (receivedDate == null) { receivedDate = LocalDateTime.now(); } calculateAvailableQuantity(); calculateTotalValue(); } @PreUpdate protected void onUpdate() { updatedAt = LocalDateTime.now(); calculateAvailableQuantity(); calculateTotalValue(); } private void calculateAvailableQuantity() { this.availableQuantity = this.quantity - this.allocatedQuantity; } private void calculateTotalValue() { if (unitCost != null && quantity != null) { this.totalValue = unitCost.multiply(BigDecimal.valueOf(quantity)); } } public enum InventoryStatus { AVAILABLE, ALLOCATED, RESERVED, QUARANTINED, DAMAGED, EXPIRED, IN_TRANSIT } public enum AbcClassification { A, B, C, D } }

4. Data Transfer Objects (DTOs)

Bin Location DTOs

import lombok.Data; import javax.validation.constraints.*; import java.time.LocalDateTime; @Data public class BinLocationDTO { private Long id; @NotBlank(message = "Bin code is required") @Pattern(regexp = "[A-Z]{2}-[0-9]{3}-[A-Z]{1}-[0-9]{2}", message = "Invalid bin code format") private String code; private String name; @NotNull(message = "Warehouse ID is required") private Long warehouseId; private Long zoneId; private Long aisleId; private Long rackId; @Min(1) private Integer levelNumber; @Min(1) private Integer bayNumber; @Min(1) private Integer positionNumber; @NotNull private BinLocation.BinType binType; @NotNull private BinLocation.ZoneType zoneType; @NotNull private BinLocation.BinStatus status; @Positive private Double maxWeightCapacity; @Positive private Double maxVolumeCapacity; @Positive private Double dimensionWidth; @Positive private Double dimensionHeight; @Positive private Double dimensionDepth; private Integer pickSequence; private Integer putawaySequence; private Boolean isFastMoving = false; // Coordinates private Double latitude; private Double longitude; } @Data public class BinLocationSearchDTO { private Long warehouseId; private Long zoneId; private BinLocation.BinType binType; private BinLocation.ZoneType zoneType; private BinLocation.BinStatus status; private Double minUtilization; private Double maxUtilization; private Boolean isFastMoving; private String sku; // Search bins containing specific SKU } @Data public class BinAllocationRequestDTO { @NotBlank private String sku; @NotNull @Positive private Integer quantity; private String batchNumber; private String lotNumber; @NotNull @Positive private Double weight; @NotNull @Positive private Double volume; private LocalDateTime expiryDate; @NotNull private AllocationStrategy strategy = AllocationStrategy.OPTIMAL; public enum AllocationStrategy { OPTIMAL, CLOSEST, FEFO, FIFO, RANDOM } }

Inventory Movement DTOs

import lombok.Data; import javax.validation.constraints.*; import java.time.LocalDateTime; import java.util.List; @Data public class PutawayRequestDTO { @NotBlank private String referenceNumber; @NotNull private LocalDateTime putawayDate; @NotBlank private String sku; @NotNull @Positive private Integer quantity; private String batchNumber; private String lotNumber; @NotNull @Positive private Double weight; @NotNull @Positive private Double volume; private LocalDateTime expiryDate; private LocalDateTime manufacturingDate; @NotNull private PutawayStrategy strategy = PutawayStrategy.OPTIMAL; private Long suggestedBinId; // Optional pre-selected bin public enum PutawayStrategy { OPTIMAL, CLOSEST, FEFO, SAME_SKU, RANDOM } } @Data public class PickingRequestDTO { @NotBlank private String orderNumber; @NotNull private LocalDateTime pickingDate; @NotNull @Size(min = 1) private List<PickingLineDTO> lines; @NotNull private PickingStrategy strategy = PickingStrategy.FEFO; private Boolean wavePicking = false; @Data public static class PickingLineDTO { @NotBlank private String sku; @NotNull @Positive private Integer quantity; private String batchNumber; // Optional specific batch private String lotNumber; // Optional specific lot } public enum PickingStrategy { FIFO, LIFO, FEFO, BATCH, ZONE } } @Data public class MoveRequestDTO { @NotNull private Long sourceBinId; @NotNull private Long destinationBinId; @NotBlank private String sku; @NotNull @Positive private Integer quantity; private String batchNumber; private String lotNumber; @NotBlank private String reason; }

5. Service Layer

Bin Location Service

import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @Slf4j @Service @Transactional public class BinLocationService { @Autowired private BinLocationRepository binLocationRepository; @Autowired private WarehouseRepository warehouseRepository; @Autowired private ZoneRepository zoneRepository; @Autowired private AisleRepository aisleRepository; @Autowired private RackRepository rackRepository; @Autowired private WarehouseConfig warehouseConfig; /** * Create new bin location */ public BinLocation createBinLocation(BinLocationDTO binLocationDTO) { try { BinLocation binLocation = new BinLocation(); mapBinLocationDTOToEntity(binLocationDTO, binLocation); // Generate bin code if not provided if (binLocation.getCode() == null) { binLocation.setCode(generateBinCode(binLocation)); } // Validate capacity validateBinCapacity(binLocation); BinLocation savedBin = binLocationRepository.save(binLocation); log.info("Created bin location: {}", savedBin.getCode()); return savedBin; } catch (Exception e) { log.error("Error creating bin location: {}", e.getMessage(), e); throw new WarehouseException("Failed to create bin location: " + e.getMessage()); } } /** * Find optimal bin for putaway */ @Transactional(readOnly = true) public Optional<BinLocation> findOptimalBin(PutawayRequestDTO putawayRequest) { try { List<BinLocation> candidateBins = binLocationRepository .findAvailableBinsByCapacity( putawayRequest.getWeight(), putawayRequest.getVolume(), BinLocation.BinStatus.AVAILABLE ); if (candidateBins.isEmpty()) { return Optional.empty(); } // Apply putaway strategy return applyPutawayStrategy(candidateBins, putawayRequest); } catch (Exception e) { log.error("Error finding optimal bin: {}", e.getMessage(), e); throw new WarehouseException("Failed to find optimal bin: " + e.getMessage()); } } /** * Get bin utilization report */ @Cacheable(value = "binUtilization", key = "#warehouseId") public BinUtilizationReport getBinUtilizationReport(Long warehouseId) { try { List<Object[]> utilizationData = binLocationRepository .getBinUtilizationByWarehouse(warehouseId); BinUtilizationReport report = new BinUtilizationReport(); report.setWarehouseId(warehouseId); report.setGeneratedAt(LocalDateTime.now()); for (Object[] data : utilizationData) { String zoneType = (String) data[0]; Double avgUtilization = (Double) data[1]; Long totalBins = (Long) data[2]; Long availableBins = (Long) data[3]; report.addZoneUtilization(zoneType, avgUtilization, totalBins, availableBins); } return report; } catch (Exception e) { log.error("Error generating utilization report: {}", e.getMessage(), e); throw new WarehouseException("Failed to generate utilization report: " + e.getMessage()); } } /** * Search bins with filters */ @Transactional(readOnly = true) public Page<BinLocation> searchBins(BinLocationSearchDTO searchDTO, Pageable pageable) { try { return binLocationRepository.findBySearchCriteria(searchDTO, pageable); } catch (Exception e) { log.error("Error searching bins: {}", e.getMessage(), e); throw new WarehouseException("Failed to search bins: " + e.getMessage()); } } /** * Update bin status */ public BinLocation updateBinStatus(Long binId, BinLocation.BinStatus newStatus, String reason) { try { BinLocation binLocation = binLocationRepository.findById(binId) .orElseThrow(() -> new WarehouseException("Bin not found with id: " + binId)); BinLocation.BinStatus oldStatus = binLocation.getStatus(); binLocation.setStatus(newStatus); binLocation.setLastActivityDate(LocalDateTime.now()); BinLocation updatedBin = binLocationRepository.save(binLocation); log.info("Updated bin {} status from {} to {} - Reason: {}", binLocation.getCode(), oldStatus, newStatus, reason); return updatedBin; } catch (Exception e) { log.error("Error updating bin status: {}", e.getMessage(), e); throw new WarehouseException("Failed to update bin status: " + e.getMessage()); } } private void mapBinLocationDTOToEntity(BinLocationDTO dto, BinLocation entity) { entity.setCode(dto.getCode()); entity.setName(dto.getName()); entity.setLevelNumber(dto.getLevelNumber()); entity.setBayNumber(dto.getBayNumber()); entity.setPositionNumber(dto.getPositionNumber()); entity.setBinType(dto.getBinType()); entity.setZoneType(dto.getZoneType()); entity.setStatus(dto.getStatus()); entity.setMaxWeightCapacity(dto.getMaxWeightCapacity()); entity.setMaxVolumeCapacity(dto.getMaxVolumeCapacity()); entity.setDimensionWidth(dto.getDimensionWidth()); entity.setDimensionHeight(dto.getDimensionHeight()); entity.setDimensionDepth(dto.getDimensionDepth()); entity.setPickSequence(dto.getPickSequence()); entity.setPutawaySequence(dto.getPutawaySequence()); entity.setIsFastMoving(dto.getIsFastMoving()); // Set relationships if (dto.getWarehouseId() != null) { Warehouse warehouse = warehouseRepository.findById(dto.getWarehouseId()) .orElseThrow(() -> new WarehouseException("Warehouse not found")); entity.setWarehouse(warehouse); } if (dto.getZoneId() != null) { Zone zone = zoneRepository.findById(dto.getZoneId()) .orElseThrow(() -> new WarehouseException("Zone not found")); entity.setZone(zone); } if (dto.getAisleId() != null) { Aisle aisle = aisleRepository.findById(dto.getAisleId()) .orElseThrow(() -> new WarehouseException("Aisle not found")); entity.setAisle(aisle); } if (dto.getRackId() != null) { Rack rack = rackRepository.findById(dto.getRackId()) .orElseThrow(() -> new WarehouseException("Rack not found")); entity.setRack(rack); } } private String generateBinCode(BinLocation binLocation) { // Format: ZONE-AISLE-LEVEL-BAY-POSITION // Example: PK-A01-L02-B03-P01 return String.format("%s-%s-L%02d-B%02d-P%02d", binLocation.getZoneType().toString().substring(0, 2), binLocation.getAisle() != null ? binLocation.getAisle().getCode() : "XX", binLocation.getLevelNumber(), binLocation.getBayNumber(), binLocation.getPositionNumber() ); } private void validateBinCapacity(BinLocation binLocation) { if (binLocation.getMaxWeightCapacity() > warehouseConfig.getDimensions().getMaxWeight()) { throw new WarehouseException("Bin weight capacity exceeds warehouse maximum"); } if (binLocation.getMaxVolumeCapacity() > warehouseConfig.getDimensions().getMaxVolume()) { throw new WarehouseException("Bin volume capacity exceeds warehouse maximum"); } } private Optional<BinLocation> applyPutawayStrategy(List<BinLocation> bins, PutawayRequestDTO request) { switch (request.getStrategy()) { case OPTIMAL: return bins.stream() .filter(bin -> bin.getZoneType() == BinLocation.ZoneType.PICKING) .filter(bin -> bin.getUtilizationPercentage() < 70.0) .filter(BinLocation::getIsFastMoving) .findFirst(); case CLOSEST: // Implement distance calculation based on coordinates return bins.stream() .sorted((b1, b2) -> Integer.compare(b1.getPickSequence(), b2.getPickSequence())) .findFirst(); case FEFO: // First Expiry First Out - prioritize bins with earliest expiry return bins.stream() .min((b1, b2) -> compareByExpiry(b1, b2, request.getSku())); case SAME_SKU: // Try to put with same SKU first return bins.stream() .filter(bin -> hasSameSku(bin, request.getSku())) .findFirst() .or(() -> bins.stream().findFirst()); default: return bins.stream().findFirst(); } } private boolean hasSameSku(BinLocation bin, String sku) { return bin.getInventoryItems().stream() .anyMatch(item -> item.getSku().equals(sku)); } private int compareByExpiry(BinLocation b1, BinLocation b2, String sku) { // Implementation for FEFO comparison return 0; // Simplified } }

Inventory Service

import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @Slf4j @Service @Transactional public class InventoryService { @Autowired private InventoryItemRepository inventoryItemRepository; @Autowired private BinLocationRepository binLocationRepository; @Autowired private BinLocationService binLocationService; @Autowired private InventoryMovementRepository movementRepository; /** * Execute putaway operation */ public InventoryItem executePutaway(PutawayRequestDTO putawayRequest) { try { // Find optimal bin BinLocation binLocation = binLocationService.findOptimalBin(putawayRequest) .orElseThrow(() -> new WarehouseException("No available bin found for putaway")); // Create inventory item InventoryItem inventoryItem = new InventoryItem(); inventoryItem.setSku(putawayRequest.getSku()); inventoryItem.setQuantity(putawayRequest.getQuantity()); inventoryItem.setBatchNumber(putawayRequest.getBatchNumber()); inventoryItem.setLotNumber(putawayRequest.getLotNumber()); inventoryItem.setWeight(putawayRequest.getWeight()); inventoryItem.setVolume(putawayRequest.getVolume()); inventoryItem.setExpiryDate(putawayRequest.getExpiryDate()); inventoryItem.setManufacturingDate(putawayRequest.getManufacturingDate()); inventoryItem.setReceivedDate(LocalDateTime.now()); inventoryItem.setStatus(InventoryItem.InventoryStatus.AVAILABLE); inventoryItem.setBinLocation(binLocation); // Update bin utilization updateBinUtilization(binLocation, putawayRequest.getWeight(), putawayRequest.getVolume()); InventoryItem savedItem = inventoryItemRepository.save(inventoryItem); // Record movement recordInventoryMovement(savedItem, "PUTAWAY", putawayRequest.getReferenceNumber()); log.info("Putaway completed for SKU: {} in bin: {}", putawayRequest.getSku(), binLocation.getCode()); return savedItem; } catch (Exception e) { log.error("Error executing putaway: {}", e.getMessage(), e); throw new WarehouseException("Putaway failed: " + e.getMessage()); } } /** * Execute picking operation */ public List<InventoryItem> executePicking(PickingRequestDTO pickingRequest) { try { // This is a simplified implementation // In real scenario, you would implement wave picking, batch picking, etc. List<InventoryItem> pickedItems = pickingRequest.getLines().stream() .map(line -> pickLineItem(line, pickingRequest.getOrderNumber())) .toList(); log.info("Picking completed for order: {}", pickingRequest.getOrderNumber()); return pickedItems; } catch (Exception e) { log.error("Error executing picking: {}", e.getMessage(), e); throw new WarehouseException("Picking failed: " + e.getMessage()); } } /** * Move inventory between bins */ public InventoryItem moveInventory(MoveRequestDTO moveRequest) { try { // Find source inventory InventoryItem sourceItem = inventoryItemRepository .findBySkuAndBinLocationIdAndBatchNumberAndLotNumber( moveRequest.getSku(), moveRequest.getSourceBinId(), moveRequest.getBatchNumber(), moveRequest.getLotNumber() ) .orElseThrow(() -> new WarehouseException("Source inventory not found")); if (sourceItem.getAvailableQuantity() < moveRequest.getQuantity()) { throw new WarehouseException("Insufficient quantity available"); } // Find destination bin BinLocation destinationBin = binLocationRepository.findById(moveRequest.getDestinationBinId()) .orElseThrow(() -> new WarehouseException("Destination bin not found")); if (!destinationBin.canAccommodate( sourceItem.getWeight() * moveRequest.getQuantity(), sourceItem.getVolume() * moveRequest.getQuantity())) { throw new WarehouseException("Destination bin cannot accommodate the inventory"); } // Create new inventory item in destination InventoryItem destinationItem = new InventoryItem(); destinationItem.setSku(sourceItem.getSku()); destinationItem.setQuantity(moveRequest.getQuantity()); destinationItem.setBatchNumber(sourceItem.getBatchNumber()); destinationItem.setLotNumber(sourceItem.getLotNumber()); destinationItem.setWeight(sourceItem.getWeight()); destinationItem.setVolume(sourceItem.getVolume()); destinationItem.setExpiryDate(sourceItem.getExpiryDate()); destinationItem.setManufacturingDate(sourceItem.getManufacturingDate()); destinationItem.setReceivedDate(LocalDateTime.now()); destinationItem.setStatus(InventoryItem.InventoryStatus.AVAILABLE); destinationItem.setBinLocation(destinationBin); // Update source quantity sourceItem.setQuantity(sourceItem.getQuantity() - moveRequest.getQuantity()); // Update bin utilizations updateBinUtilization(sourceItem.getBinLocation(), -sourceItem.getWeight() * moveRequest.getQuantity(), -sourceItem.getVolume() * moveRequest.getQuantity()); updateBinUtilization(destinationBin, destinationItem.getWeight() * moveRequest.getQuantity(), destinationItem.getVolume() * moveRequest.getQuantity()); InventoryItem savedItem = inventoryItemRepository.save(destinationItem); inventoryItemRepository.save(sourceItem); // Record movement recordInventoryMovement(savedItem, "MOVE", moveRequest.getReason()); log.info("Inventory moved from bin {} to bin {}", sourceItem.getBinLocation().getCode(), destinationBin.getCode()); return savedItem; } catch (Exception e) { log.error("Error moving inventory: {}", e.getMessage(), e); throw new WarehouseException("Inventory move failed: " + e.getMessage()); } } /** * Get inventory by location */ @Transactional(readOnly = true) public List<InventoryItem> getInventoryByBin(Long binId) { try { return inventoryItemRepository.findByBinLocationIdAndStatus( binId, InventoryItem.InventoryStatus.AVAILABLE); } catch (Exception e) { log.error("Error getting inventory by bin: {}", e.getMessage(), e); throw new WarehouseException("Failed to get inventory: " + e.getMessage()); } } private InventoryItem pickLineItem(PickingRequestDTO.PickingLineDTO line, String orderNumber) { // Find available inventory for this SKU List<InventoryItem> availableItems = inventoryItemRepository .findAvailableBySku(line.getSku(), line.getBatchNumber(), line.getLotNumber()); if (availableItems.isEmpty()) { throw new WarehouseException("No available inventory for SKU: " + line.getSku()); } int remainingQuantity = line.getQuantity(); for (InventoryItem item : availableItems) { if (remainingQuantity <= 0) break; int pickQuantity = Math.min(remainingQuantity, item.getAvailableQuantity()); // Allocate quantity item.setAllocatedQuantity(item.getAllocatedQuantity() + pickQuantity); remainingQuantity -= pickQuantity; // Update bin utilization updateBinUtilization(item.getBinLocation(), -item.getWeight() * pickQuantity, -item.getVolume() * pickQuantity); inventoryItemRepository.save(item); // Record movement recordInventoryMovement(item, "PICK", orderNumber); } if (remainingQuantity > 0) { throw new WarehouseException("Insufficient inventory for SKU: " + line.getSku()); } return availableItems.get(0); // Return first item for simplicity } private void updateBinUtilization(BinLocation binLocation, Double weightDelta, Double volumeDelta) { binLocation.setCurrentWeight(binLocation.getCurrentWeight() + weightDelta); binLocation.setCurrentVolume(binLocation.getCurrentVolume() + volumeDelta); binLocation.setLastActivityDate(LocalDateTime.now()); binLocationRepository.save(binLocation); } private void recordInventoryMovement(InventoryItem item, String movementType, String reference) { InventoryMovement movement = new InventoryMovement(); movement.setInventoryItem(item); movement.setMovementType(movementType); movement.setQuantity(item.getQuantity()); movement.setReferenceNumber(reference); movement.setMovementDate(LocalDateTime.now()); movementRepository.save(movement); } }

6. Repository Interfaces with Custom Queries

Bin Location Repository

import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; @Repository public interface BinLocationRepository extends JpaRepository<BinLocation, Long> { Optional<BinLocation> findByCode(String code); List<BinLocation> findByWarehouseId(Long warehouseId); List<BinLocation> findByZoneId(Long zoneId); List<BinLocation> findByStatus(BinLocation.BinStatus status); List<BinLocation> findByZoneType(BinLocation.ZoneType zoneType); @Query("SELECT b FROM BinLocation b WHERE " + "(:warehouseId IS NULL OR b.warehouse.id = :warehouseId) AND " + "(:zoneId IS NULL OR b.zone.id = :zoneId) AND " + "(:binType IS NULL OR b.binType = :binType) AND " + "(:zoneType IS NULL OR b.zoneType = :zoneType) AND " + "(:status IS NULL OR b.status = :status) AND " + "(:minUtilization IS NULL OR b.utilizationPercentage >= :minUtilization) AND " + "(:maxUtilization IS NULL OR b.utilizationPercentage <= :maxUtilization) AND " + "(:isFastMoving IS NULL OR b.isFastMoving = :isFastMoving)") Page<BinLocation> findBySearchCriteria(@Param("warehouseId") Long warehouseId, @Param("zoneId") Long zoneId, @Param("binType") BinLocation.BinType binType, @Param("zoneType") BinLocation.ZoneType zoneType, @Param("status") BinLocation.BinStatus status, @Param("minUtilization") Double minUtilization, @Param("maxUtilization") Double maxUtilization, @Param("isFastMoving") Boolean isFastMoving, Pageable pageable); default Page<BinLocation> findBySearchCriteria(BinLocationSearchDTO searchDTO, Pageable pageable) { return findBySearchCriteria( searchDTO.getWarehouseId(), searchDTO.getZoneId(), searchDTO.getBinType(), searchDTO.getZoneType(), searchDTO.getStatus(), searchDTO.getMinUtilization(), searchDTO.getMaxUtilization(), searchDTO.getIsFastMoving(), pageable ); } @Query("SELECT b FROM BinLocation b WHERE " + "b.status = 'AVAILABLE' AND " + "(b.currentWeight + :weight) <= b.maxWeightCapacity AND " + "(b.currentVolume + :volume) <= b.maxVolumeCapacity " + "ORDER BY b.utilizationPercentage ASC, b.putawaySequence ASC") List<BinLocation> findAvailableBinsByCapacity(@Param("weight") Double weight, @Param("volume") Double volume, @Param("status") BinLocation.BinStatus status); @Query("SELECT b.zoneType, AVG(b.utilizationPercentage), COUNT(b), " + "SUM(CASE WHEN b.status = 'AVAILABLE' THEN 1 ELSE 0 END) " + "FROM BinLocation b WHERE b.warehouse.id = :warehouseId " + "GROUP BY b.zoneType") List<Object[]> getBinUtilizationByWarehouse(@Param("warehouseId") Long warehouseId); @Query("SELECT b FROM BinLocation b JOIN b.inventoryItems i WHERE " + "i.sku = :sku AND i.status = 'AVAILABLE' AND i.availableQuantity > 0 " + "ORDER BY " + "CASE WHEN :strategy = 'FEFO' THEN i.expiryDate END ASC, " + "CASE WHEN :strategy = 'FIFO' THEN i.receivedDate END ASC, " + "b.pickSequence ASC") List<BinLocation> findBinsForPicking(@Param("sku") String sku, @Param("strategy") String strategy); }

Inventory Item Repository

import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @Repository public interface InventoryItemRepository extends JpaRepository<InventoryItem, Long> { List<InventoryItem> findBySku(String sku); List<InventoryItem> findByBinLocationId(Long binLocationId); List<InventoryItem> findByBinLocationIdAndStatus(Long binLocationId, InventoryItem.InventoryStatus status); Optional<InventoryItem> findBySkuAndBatchNumberAndLotNumber(String sku, String batchNumber, String lotNumber); Optional<InventoryItem> findBySkuAndBinLocationIdAndBatchNumberAndLotNumber( @Param("sku") String sku, @Param("binLocationId") Long binLocationId, @Param("batchNumber") String batchNumber, @Param("lotNumber") String lotNumber); @Query("SELECT i FROM InventoryItem i WHERE " + "i.sku = :sku AND " + "i.status = 'AVAILABLE' AND " + "i.availableQuantity > 0 AND " + "(:batchNumber IS NULL OR i.batchNumber = :batchNumber) AND " + "(:lotNumber IS NULL OR i.lotNumber = :lotNumber) " + "ORDER BY i.expiryDate ASC, i.receivedDate ASC") List<InventoryItem> findAvailableBySku(@Param("sku") String sku, @Param("batchNumber") String batchNumber, @Param("lotNumber") String lotNumber); @Query("SELECT i.sku, SUM(i.availableQuantity), b.zoneType " + "FROM InventoryItem i JOIN i.binLocation b " + "WHERE i.status = 'AVAILABLE' " + "GROUP BY i.sku, b.zoneType " + "ORDER BY SUM(i.availableQuantity) DESC") List<Object[]> getInventorySummaryBySkuAndZone(); @Query("SELECT i FROM InventoryItem i WHERE i.expiryDate < :date AND i.status = 'AVAILABLE'") List<InventoryItem> findExpiringItems(@Param("date") LocalDateTime date); @Query(value = """ SELECT sku, SUM(available_quantity) as total_quantity, COUNT(DISTINCT bin_location_id) as location_count FROM inventory_items WHERE status = 'AVAILABLE' GROUP BY sku HAVING SUM(available_quantity) > 0 ORDER BY total_quantity DESC """, nativeQuery = true) List<Object[]> getSkuDistribution(); }

7. REST Controllers

Bin Location Controller

import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.HashMap; import java.util.Map; @Slf4j @RestController @RequestMapping("/api/warehouse/bins") public class BinLocationController { @Autowired private BinLocationService binLocationService; @Autowired private InventoryService inventoryService; /** * Create new bin location */ @PostMapping public ResponseEntity<?> createBinLocation(@Valid @RequestBody BinLocationDTO binLocationDTO) { try { BinLocation binLocation = binLocationService.createBinLocation(binLocationDTO); return ResponseEntity.ok(binLocation); } catch (WarehouseException e) { log.error("Error creating bin location: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } /** * Search bins with filters */ @GetMapping("/search") public ResponseEntity<?> searchBins( @ModelAttribute BinLocationSearchDTO searchDTO, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "50") int size) { try { Pageable pageable = PageRequest.of(page, size); Page<BinLocation> bins = binLocationService.searchBins(searchDTO, pageable); return ResponseEntity.ok(bins); } catch (WarehouseException e) { log.error("Error searching bins: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } /** * Get bin utilization report */ @GetMapping("/utilization/{warehouseId}") public ResponseEntity<?> getBinUtilization(@PathVariable Long warehouseId) { try { BinUtilizationReport report = binLocationService.getBinUtilizationReport(warehouseId); return ResponseEntity.ok(report); } catch (WarehouseException e) { log.error("Error getting utilization report: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } /** * Update bin status */ @PutMapping("/{binId}/status") public ResponseEntity<?> updateBinStatus( @PathVariable Long binId, @RequestParam BinLocation.BinStatus status, @RequestParam String reason) { try { BinLocation binLocation = binLocationService.updateBinStatus(binId, status, reason); return ResponseEntity.ok(binLocation); } catch (WarehouseException e) { log.error("Error updating bin status: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } /** * Get inventory in a bin */ @GetMapping("/{binId}/inventory") public ResponseEntity<?> getBinInventory(@PathVariable Long binId) { try { List<InventoryItem> inventory = inventoryService.getInventoryByBin(binId); return ResponseEntity.ok(inventory); } catch (WarehouseException e) { log.error("Error getting bin inventory: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } private Map<String, Object> createErrorResponse(String message) { Map<String, Object> errorResponse = new HashMap<>(); errorResponse.put("error", true); errorResponse.put("message", message); return errorResponse; } }

Inventory Controller

import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.HashMap; import java.util.Map; @Slf4j @RestController @RequestMapping("/api/warehouse/inventory") public class InventoryController { @Autowired private InventoryService inventoryService; /** * Execute putaway operation */ @PostMapping("/putaway") public ResponseEntity<?> executePutaway(@Valid @RequestBody PutawayRequestDTO putawayRequest) { try { InventoryItem inventoryItem = inventoryService.executePutaway(putawayRequest); return ResponseEntity.ok(inventoryItem); } catch (WarehouseException e) { log.error("Error executing putaway: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } /** * Execute picking operation */ @PostMapping("/picking") public ResponseEntity<?> executePicking(@Valid @RequestBody PickingRequestDTO pickingRequest) { try { var pickedItems = inventoryService.executePicking(pickingRequest); return ResponseEntity.ok(pickedItems); } catch (WarehouseException e) { log.error("Error executing picking: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } /** * Move inventory between bins */ @PostMapping("/move") public ResponseEntity<?> moveInventory(@Valid @RequestBody MoveRequestDTO moveRequest) { try { InventoryItem movedItem = inventoryService.moveInventory(moveRequest); return ResponseEntity.ok(movedItem); } catch (WarehouseException e) { log.error("Error moving inventory: {}", e.getMessage()); return ResponseEntity.badRequest().body(createErrorResponse(e.getMessage())); } } private Map<String, Object> createErrorResponse(String message) { Map<String, Object> errorResponse = new HashMap<>(); errorResponse.put("error", true); errorResponse.put("message", message); return errorResponse; } }

8. Custom Exception

public class WarehouseException extends RuntimeException { public WarehouseException(String message) { super(message); } public WarehouseException(String message, Throwable cause) { super(message, cause); } }

This comprehensive warehouse bin location system provides complete functionality for managing storage locations, inventory placement, putaway and picking operations, and optimization algorithms. The system supports multiple warehouse layouts, zone management, and sophisticated inventory tracking with various strategies for space utilization and movement optimization.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper