SupplierServiceImpl.java
package com.smartsupplypro.inventory.service.impl;
import java.time.LocalDateTime;
import java.util.List; // <-- adjust package if your entity lives elsewhere
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.UUID;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // static methods used directly
import com.smartsupplypro.inventory.dto.SupplierDTO;
import com.smartsupplypro.inventory.mapper.SupplierMapper;
import com.smartsupplypro.inventory.model.Supplier;
import com.smartsupplypro.inventory.repository.InventoryItemRepository;
import com.smartsupplypro.inventory.repository.SupplierRepository;
import com.smartsupplypro.inventory.service.SupplierService;
import com.smartsupplypro.inventory.validation.SupplierValidator;
import lombok.RequiredArgsConstructor;
/**
* Service implementation for supplier master data management.
*
* <p><strong>Characteristics</strong>:
* <ul>
* <li><strong>Master Data</strong>: Reference catalog with infrequent changes (<1000 records)</li>
* <li><strong>Validation Delegation</strong>: All validation logic in {@link SupplierValidator}</li>
* <li><strong>Referential Integrity</strong>: Prevents deletion if inventory items reference supplier</li>
* <li><strong>Uniqueness Constraint</strong>: Case-insensitive supplier name enforcement</li>
* <li><strong>Static Mapping</strong>: DTO ↔ Entity via {@link SupplierMapper}</li>
* </ul>
*
* <p><strong>Transaction Management</strong>:
* Class-level {@code @Transactional} with method-level {@code readOnly=true} overrides for query operations.
*
* <p><strong>Architecture Documentation</strong>:
* For detailed operation flows, business rules, design patterns, and refactoring notes, see:
* <a href="../../../../../../docs/architecture/services/supplier-service.md">Supplier Service Architecture</a>
*
* @see SupplierService
* @see SupplierValidator
* @see SupplierMapper
* @see com.smartsupplypro.inventory.model.Supplier
*/
@Service
@RequiredArgsConstructor
@Transactional
public class SupplierServiceImpl implements SupplierService {
private final SupplierRepository supplierRepository;
private final InventoryItemRepository inventoryItemRepository;
/**
* Retrieves all suppliers from the database.
*
* <p>Loads ALL suppliers (acceptable for master data <1000 records).
*
* @return list of all suppliers as DTOs
* @see <a href="../../../../../../docs/architecture/services/supplier-service.md#operation-flows">Operation Flows</a>
*/
@Override
@Transactional(readOnly = true)
public List<SupplierDTO> findAll() {
// No filtering here; caller controls access via @PreAuthorize at controller.
return supplierRepository.findAll().stream()
.map(SupplierMapper::toDTO) // static call
.toList();
}
/**
* Finds a supplier by ID.
*
* @param id the supplier ID
* @return Optional containing supplier if found, empty otherwise
* @see <a href="../../../../../../docs/architecture/services/supplier-service.md#operation-flows">Operation Flows</a>
*/
@Override
@Transactional(readOnly = true)
public Optional<SupplierDTO> findById(String id) {
return supplierRepository.findById(id).map(SupplierMapper::toDTO);
}
/**
* Searches suppliers by name (partial match, case-insensitive).
*
* @param name search term (can be partial)
* @return list of matching suppliers
* @see <a href="../../../../../../docs/architecture/services/supplier-service.md#operation-flows">Operation Flows</a>
*/
@Override
@Transactional(readOnly = true)
public List<SupplierDTO> findByName(String name) {
return supplierRepository.findByNameContainingIgnoreCase(name).stream()
.map(SupplierMapper::toDTO)
.toList();
}
/**
* Creates a new supplier with validation and server-authoritative field generation.
*
* <p><strong>Business Rules</strong>:
* <ul>
* <li>Name must be unique (case-insensitive) → HTTP 409 if conflict</li>
* <li>Name and contactName required → HTTP 400 if blank</li>
* <li>UUID and createdAt generated server-side (client values ignored)</li>
* </ul>
*
* @param dto the supplier data (client-provided)
* @return the saved supplier with generated fields
* @throws com.smartsupplypro.inventory.exception.InvalidRequestException if validation fails → HTTP 400
* @throws com.smartsupplypro.inventory.exception.DuplicateResourceException if name exists → HTTP 409
* @see <a href="../../../../../../docs/architecture/services/supplier-service.md#1-create-supplier">Create Supplier Flow</a>
*/
@Override
public SupplierDTO create(SupplierDTO dto) {
SupplierValidator.validateBase(dto);
SupplierValidator.assertUniqueName(supplierRepository, dto.getName(), null);
Supplier entity = SupplierMapper.toEntity(dto);
entity.setId(UUID.randomUUID().toString());
entity.setCreatedAt(LocalDateTime.now());
// TODO: entity.setCreatedBy(SecurityContextUtils.getCurrentUsername());
Supplier saved = supplierRepository.save(entity);
return SupplierMapper.toDTO(saved);
}
/**
* Updates mutable fields of an existing supplier.
*
* <p><strong>Business Rules</strong>:
* <ul>
* <li>Path ID authoritative (DTO ID ignored)</li>
* <li>Name uniqueness check excludes current supplier</li>
* <li>Immutable fields: ID, createdAt</li>
* </ul>
*
* @param id the supplier ID (path parameter, authoritative)
* @param dto the updated supplier data
* @return the updated supplier
* @throws NoSuchElementException if supplier not found → HTTP 404
* @throws com.smartsupplypro.inventory.exception.InvalidRequestException if validation fails → HTTP 400
* @throws com.smartsupplypro.inventory.exception.DuplicateResourceException if name conflicts → HTTP 409
* @see <a href="../../../../../../docs/architecture/services/supplier-service.md#2-update-supplier">Update Supplier Flow</a>
*/
@Override
public SupplierDTO update(String id, SupplierDTO dto) {
Supplier existing = supplierRepository.findById(id)
.orElseThrow(() -> new NoSuchElementException("Supplier not found: " + id));
SupplierValidator.validateBase(dto);
SupplierValidator.assertUniqueName(supplierRepository, dto.getName(), id);
existing.setName(dto.getName());
existing.setContactName(dto.getContactName());
existing.setPhone(dto.getPhone());
existing.setEmail(dto.getEmail());
// TODO: existing.setUpdatedAt(LocalDateTime.now());
// TODO: existing.setUpdatedBy(SecurityContextUtils.getCurrentUsername());
Supplier saved = supplierRepository.save(existing);
return SupplierMapper.toDTO(saved);
}
/**
* Deletes a supplier after ensuring no inventory items reference it.
*
* <p><strong>Referential Integrity</strong>:
* Deletion blocked if ANY inventory items (active or historical) reference this supplier.
*
* @param id the supplier ID to delete
* @throws NoSuchElementException if supplier not found → HTTP 404
* @throws IllegalStateException if inventory items reference supplier → HTTP 409
* @see <a href="../../../../../../docs/architecture/services/supplier-service.md#3-delete-supplier-referential-integrity">Delete Supplier Flow</a>
*/
@Override
public void delete(String id) {
SupplierValidator.assertDeletable(
id,
() -> inventoryItemRepository.existsActiveStockForSupplier(id, 0)
);
if (!supplierRepository.existsById(id)) {
throw new NoSuchElementException("Supplier not found: " + id);
}
supplierRepository.deleteById(id);
}
/**
* Returns the total number of suppliers (KPI metric).
*
* @return supplier count
* @see <a href="../../../../../../docs/architecture/services/supplier-service.md#performance-considerations">Performance Considerations</a>
*/
@Override
@Transactional(readOnly = true)
public long countSuppliers() {
return supplierRepository.count();
}
}