InventoryItemController.java
package com.smartsupplypro.inventory.controller;
import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.smartsupplypro.inventory.dto.InventoryItemDTO;
import com.smartsupplypro.inventory.enums.StockChangeReason;
import com.smartsupplypro.inventory.service.InventoryItemService;
/**
* Inventory item REST controller for CRUD operations.
*
* <p>Provides item management with role-based authorization and validation.
* Follows standard HTTP status conventions for REST APIs.</p>
*
* @see InventoryItemService
* @see <a href="file:../../../../../../docs/architecture/patterns/controller-patterns.md">Controller Patterns</a>
*/
@RestController
@RequestMapping("/api/inventory")
@Validated
public class InventoryItemController {
private final InventoryItemService inventoryItemService;
public InventoryItemController(InventoryItemService inventoryItemService) {
this.inventoryItemService = inventoryItemService;
}
/**
* Gets single inventory item by ID.
*
* @param id unique item identifier
* @return inventory item details
* @throws ResponseStatusException 404 if item not found
*/
@PreAuthorize("isAuthenticated()")
@GetMapping("/{id}")
public InventoryItemDTO getById(@PathVariable String id) {
return inventoryItemService.getById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Item not found"));
}
/**
* Gets all inventory items (non-paginated).
*
* @return list of all inventory items
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping
public List<InventoryItemDTO> getAll() {
return inventoryItemService.getAll();
}
/**
* Gets total count of inventory items.
*
* @return total number of items
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/count")
public long countItems() {
return inventoryItemService.countItems();
}
/**
* Searches items by name with pagination and sorting.
*
* @param name case-insensitive name substring
* @param pageable pagination and sorting parameters
* @return page of matching items
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/search")
public Page<InventoryItemDTO> search(
@RequestParam String name,
@org.springframework.data.web.PageableDefault(size = 20, sort = "price") Pageable pageable) {
return inventoryItemService.findByNameSortedByPrice(name, pageable);
}
/**
* Creates new inventory item (ADMIN only).
*
* @param body item data (ID must be absent)
* @return 201 Created with Location header and created item
* @throws ResponseStatusException 400/409 on validation/duplicate errors
*/
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public ResponseEntity<InventoryItemDTO> create(
@Validated(InventoryItemDTO.Create.class) @RequestBody InventoryItemDTO body) {
InventoryItemDTO created = inventoryItemService.save(body);
if (created == null) {
return ResponseEntity.badRequest().build();
}
// Enterprise Comment: REST Location Header Pattern
// Generate Location header pointing to the newly created resource
// Follows RFC 7231 standard for 201 Created responses
URI location = ServletUriComponentsBuilder
.fromCurrentRequest().path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
/**
* Updates existing inventory item completely.
*
* @param id path identifier
* @param body updated item data
* @return updated inventory item
* @throws ResponseStatusException 404 if item not found
*/
@PreAuthorize("isAuthenticated()")
@PutMapping("/{id}")
public InventoryItemDTO update(
@PathVariable String id,
@Validated /* or @Valid */ @RequestBody InventoryItemDTO body) {
// Enterprise Comment: ID Consistency Strategy
// Ignore client-sent body.id to prevent conflicts and match test expectations
// Path parameter takes precedence for resource identification
body.setId(null); // or body.setId(id) if you prefer
return inventoryItemService.update(id, body)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Item not found"));
}
/**
* Deletes inventory item (ADMIN only).
*
* @param id item identifier
* @param reason business reason for deletion (audit trail)
* @throws ResponseStatusException 404 if item not found
*/
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable String id, @RequestParam StockChangeReason reason) {
inventoryItemService.delete(id, reason);
}
/**
* Adjusts item quantity by delta amount.
*
* @param id item identifier
* @param delta quantity change (positive to add, negative to remove)
* @param reason business reason for stock change
* @return updated inventory item
*/
@PreAuthorize("isAuthenticated()")
@PatchMapping("/{id}/quantity")
public InventoryItemDTO adjustQuantity(@PathVariable String id,
@RequestParam int delta,
@RequestParam StockChangeReason reason) {
return inventoryItemService.adjustQuantity(id, delta, reason);
}
/**
* Updates item unit price.
*
* @param id item identifier
* @param price new unit price (must be positive)
* @return updated inventory item
*/
@PreAuthorize("isAuthenticated()")
@PatchMapping("/{id}/price")
public InventoryItemDTO updatePrice(@PathVariable String id,
@RequestParam @jakarta.validation.constraints.Positive BigDecimal price) {
return inventoryItemService.updatePrice(id, price);
}
}