SupplierController.java

package com.smartsupplypro.inventory.controller;

import java.net.URI;
import java.util.List;
import java.util.NoSuchElementException;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
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.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.RestController;
import org.springframework.web.server.ResponseStatusException;

import com.smartsupplypro.inventory.dto.SupplierDTO;
import com.smartsupplypro.inventory.service.SupplierService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

/**
 * REST controller for supplier management with full CRUD operations and search.
 * Supports role-based authorization (USER read-only, ADMIN full access).
 * @see SupplierService
 * @see controller-patterns.md for REST API patterns
 */
@RestController
@RequestMapping("/api/suppliers")
@RequiredArgsConstructor
public class SupplierController {

    private final SupplierService supplierService;

    /**
     * Lists all suppliers with optional demo readonly access.
     * @return list of supplier DTOs
     */
    @PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
    @GetMapping
    public ResponseEntity<List<SupplierDTO>> listAll() {
        return ResponseEntity.ok(supplierService.findAll());
    }

    /**
     * Returns total count of suppliers in system.
     * @return supplier count as long value
     */
    @PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
    @GetMapping("/count")
    public long countSuppliers() {
        return supplierService.countSuppliers();
    }

    /**
     * Retrieves supplier by unique identifier.
     * @param id supplier ID
     * @return supplier DTO or 404 if not found
     */
    @PreAuthorize("isAuthenticated()")
    @GetMapping("/{id}")
    public ResponseEntity<SupplierDTO> getById(@PathVariable String id) {
        return supplierService.findById(id)
                .map(ResponseEntity::ok)
                .orElseThrow(() -> new NoSuchElementException("Supplier not found: " + id));
    }

    /**
     * Searches suppliers by partial name match.
     * @param name partial or full supplier name
     * @return list of matching suppliers
     */
    @PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
    @GetMapping("/search")
    public ResponseEntity<List<SupplierDTO>> search(@RequestParam String name) {
        return ResponseEntity.ok(supplierService.findByName(name));
    }

    /**
     * Creates new supplier with admin authorization and returns 201 + Location header.
     * @param dto supplier data (ID must be null)
     * @return created supplier with 201 status
     */
    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping
    public ResponseEntity<SupplierDTO> create(@Valid @RequestBody SupplierDTO dto) {
        if (dto.getId() != null) {
            // Enterprise Comment: ID consistency validation - prevent client-generated IDs on creation
            // to maintain server-side ID generation control and avoid potential ID conflicts
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "ID must be null on create");
        }
        SupplierDTO created = supplierService.create(dto);
        // Enterprise Comment: REST Location header pattern - provide resource URI for immediate access
        // enabling client-side navigation and RESTful resource discovery
        return ResponseEntity.created(URI.create("/api/suppliers/" + created.getId()))
                .header(HttpHeaders.LOCATION, "/api/suppliers/" + created.getId())
                .body(created);
    }

    /**
     * Updates existing supplier with path ID taking precedence over body ID.
     * @param id supplier ID from path
     * @param dto updated supplier data
     * @return updated supplier DTO
     */
    @PreAuthorize("hasRole('ADMIN')")
    @PutMapping("/{id}")
    public ResponseEntity<SupplierDTO> update(@PathVariable String id, @Valid @RequestBody SupplierDTO dto) {
        // Enterprise Comment: Path vs body ID validation - ensure API contract consistency
        // by preventing mismatched identifiers that could lead to unintended updates
        if (dto.getId() != null && !id.equals(dto.getId())) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Path id and body id must match");
        }
        SupplierDTO updated = supplierService.update(id, dto);
        return ResponseEntity.ok(updated);
    }

    /**
     * Deletes supplier with referential integrity checks.
     * @param id supplier ID to delete
     * @return 204 No Content on success
     */
    @PreAuthorize("hasRole('ADMIN')")
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable String id) {
        supplierService.delete(id);
        return ResponseEntity.noContent().build();
    }
}