FolioController.java

package com.licensis.notaire.api;

import com.licensis.notaire.dto.DtoFolio;
import com.licensis.notaire.negocio.Folio;
import com.licensis.notaire.negocio.Persona;
import com.licensis.notaire.negocio.TipoDeFolio;
import com.licensis.notaire.repository.FolioRepository;
import com.licensis.notaire.repository.PersonaRepository;
import com.licensis.notaire.repository.TipoDeFolioRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
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 java.util.List;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/api/v1/folio")
@Tag(name = "Folio", description = "API para gestionar folio")
public class FolioController {

    private static final Logger log = LoggerFactory.getLogger(FolioController.class);

    private static final String ESTADO_UTILIZADO = "Utilizado";

    record FolioRequest(
            int numero,
            int anio,
            String estado,
            String observaciones,
            Integer tipoFolioId,
            Integer escribanoId
    ) {}

    private final FolioRepository folioRepository;
    private final TipoDeFolioRepository tipoDeFolioRepository;
    private final PersonaRepository personaRepository;

    public FolioController(FolioRepository folioRepository,
                           TipoDeFolioRepository tipoDeFolioRepository,
                           PersonaRepository personaRepository) {
        this.folioRepository = folioRepository;
        this.tipoDeFolioRepository = tipoDeFolioRepository;
        this.personaRepository = personaRepository;
    }

    @GetMapping
    @Operation(summary = "Obtener todos los folios")
    public ResponseEntity<List<DtoFolio>> getAll() {
        try {
            List<DtoFolio> result = folioRepository.findAll().stream()
                    .map(Folio::getDto)
                    .toList();
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("Failed to list folios", e);
            return ResponseEntity.internalServerError().build();
        }
    }

    @GetMapping("/search")
    @Operation(summary = "Buscar folios por estado")
    public ResponseEntity<List<DtoFolio>> search(@RequestParam String estado) {
        List<DtoFolio> result = folioRepository.findByEstado(estado).stream()
                .map(Folio::getDto)
                .toList();
        return ResponseEntity.ok(result);
    }

    @GetMapping("/{id}/in-use")
    @Operation(summary = "Verificar si un folio está en uso")
    public ResponseEntity<Map<String, Boolean>> isInUse(@PathVariable Integer id) {
        return folioRepository.findById(id)
                .map(f -> ResponseEntity.ok(Map.of("inUse", ESTADO_UTILIZADO.equals(f.getEstado()))))
                .orElse(ResponseEntity.notFound().build());
    }

    @ApiResponses({
    @ApiResponse(responseCode = "200", description = "OK"),
    @ApiResponse(responseCode = "404", description = "No encontrado")
})
    @GetMapping("/{id}")
    @Operation(summary = "Obtener folio por ID")
    public ResponseEntity<DtoFolio> getById(@PathVariable Integer id) {
        return folioRepository.findById(id)
                .map(f -> ResponseEntity.ok(f.getDto()))
                .orElse(ResponseEntity.notFound().build());
    }

    @ApiResponses({
    @ApiResponse(responseCode = "201", description = "Creado"),
    @ApiResponse(responseCode = "400", description = "Solicitud inválida"),
    @ApiResponse(responseCode = "409", description = "Conflicto")
})
    @PostMapping
    @Operation(summary = "Crear nuevo folio")
    public ResponseEntity<DtoFolio> create(@RequestBody FolioRequest request) {
        if (request.tipoFolioId() == null || request.escribanoId() == null) {
            return ResponseEntity.badRequest().build();
        }
        Optional<TipoDeFolio> tipo = tipoDeFolioRepository.findById(request.tipoFolioId());
        Optional<Persona> escribano = personaRepository.findById(request.escribanoId());
        if (tipo.isEmpty() || escribano.isEmpty()) {
            return ResponseEntity.badRequest().build();
        }
        try {
            Folio folio = new Folio();
            folio.setNumero(request.numero());
            folio.setAnio(request.anio());
            folio.setEstado(request.estado());
            folio.setObservaciones(request.observaciones());
            folio.setFkIdTipoFolio(tipo.get());
            folio.setFkIdPersonaEscribano(escribano.get());
            Folio saved = folioRepository.save(folio);
            return ResponseEntity.status(HttpStatus.CREATED).body(saved.getDto());
        } catch (Exception e) {
            log.error("Failed to create folio", e);
            return ResponseEntity.internalServerError().build();
        }
    }

    @ApiResponses({
    @ApiResponse(responseCode = "200", description = "OK"),
    @ApiResponse(responseCode = "404", description = "No encontrado")
})
    @PutMapping("/{id}")
    @Operation(summary = "Actualizar folio")
    public ResponseEntity<Object> update(@PathVariable Integer id, @RequestBody FolioRequest request) {
        Optional<Folio> existing = folioRepository.findById(id);
        if (existing.isEmpty()) {
            return ResponseEntity.notFound().build();
        }
        if (ESTADO_UTILIZADO.equals(existing.get().getEstado())) {
            return ResponseEntity.status(HttpStatus.CONFLICT)
                    .body(Map.of("error", "Este folio está en uso (Utilizado) y no puede modificarse."));
        }
        Folio folio = existing.get();
        folio.setNumero(request.numero());
        folio.setAnio(request.anio());
        folio.setEstado(request.estado());
        folio.setObservaciones(request.observaciones());
        if (request.tipoFolioId() != null) {
            tipoDeFolioRepository.findById(request.tipoFolioId()).ifPresent(folio::setFkIdTipoFolio);
        }
        if (request.escribanoId() != null) {
            personaRepository.findById(request.escribanoId()).ifPresent(folio::setFkIdPersonaEscribano);
        }
        try {
            Folio saved = folioRepository.save(folio);
            return ResponseEntity.ok(saved.getDto());
        } catch (Exception e) {
            log.error("Failed to update folio {}", id, e);
            return ResponseEntity.internalServerError().build();
        }
    }

    @ApiResponses({
    @ApiResponse(responseCode = "204", description = "Eliminado"),
    @ApiResponse(responseCode = "404", description = "No encontrado")
})
    @DeleteMapping("/{id}")
    @Operation(summary = "Eliminar folio")
    @Transactional
    public ResponseEntity<Object> delete(@PathVariable Integer id) {
        Optional<Folio> opt = folioRepository.findById(id);
        if (opt.isEmpty()) {
            return ResponseEntity.notFound().build();
        }
        if (ESTADO_UTILIZADO.equals(opt.get().getEstado())) {
            return ResponseEntity.status(HttpStatus.CONFLICT)
                    .body(Map.of("error", "No se puede eliminar: el folio está en uso (Utilizado)."));
        }
        try {
            Folio folio = opt.get();
            // TipoDeFolio.folioList is EAGER + CascadeType.ALL: Hibernate 6 would cascade-persist
            // the removed entity back through that collection. Remove it first.
            if (folio.getFkIdTipoFolio() != null && folio.getFkIdTipoFolio().getFolioList() != null) {
                folio.getFkIdTipoFolio().getFolioList().remove(folio);
            }
            folioRepository.delete(folio);
            return ResponseEntity.noContent().build();
        } catch (Exception e) {
            log.error("Failed to delete folio {}", id, e);
            return ResponseEntity.status(HttpStatus.CONFLICT).build();
        }
    }
}