package kr.itn.itnhub.process; import kr.itn.itnhub.org.OrgNotFoundException; import kr.itn.itnhub.org.Organization; import kr.itn.itnhub.org.OrganizationMapper; import kr.itn.itnhub.seed.SeedParseException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; /** 기관관리 상세의 권리처리 탭이 부르는 엔드포인트. */ @RestController public class ProcessController { private final ProcessService processService; private final ProcessImportService importService; private final OrganizationMapper orgMapper; public ProcessController(ProcessService processService, ProcessImportService importService, OrganizationMapper orgMapper) { this.processService = processService; this.importService = importService; this.orgMapper = orgMapper; } @PostMapping("/api/orgs/{id}/process/import") public ProcessImportReport upload(@PathVariable("id") Long orgId, @RequestParam("file") MultipartFile file) { if (file.isEmpty()) { throw new SeedParseException("업로드된 파일이 비어 있습니다."); } String filename = file.getOriginalFilename(); if (filename == null || !hasAllowedExtension(filename)) { throw new SeedParseException("엑셀 파일(.xlsx 또는 .xlsm)만 업로드할 수 있습니다."); } try (InputStream in = file.getInputStream()) { return importService.importFile(orgId, in); } catch (IOException e) { throw new SeedParseException("엑셀 파일을 읽지 못했습니다.", e); } } /** keyword는 통합검색, site/board/title은 열별 조건(서로 AND). status는 처리상태 필터다. */ @GetMapping("/api/orgs/{id}/process") public ProcessPage list(@PathVariable("id") Long orgId, @RequestParam(required = false) String keyword, @RequestParam(required = false) String site, @RequestParam(required = false) String board, @RequestParam(required = false) String title, @RequestParam(required = false) String status, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "30") int size) { return processService.list(orgId, keyword, site, board, title, status, page, size); } /** 목록에서 체크한 건들에 같은 값을 한 번에 반영한다. */ @PutMapping("/api/orgs/{id}/process/bulk") public java.util.Map updateBulk(@PathVariable("id") Long orgId, @RequestBody BulkProcessingRequest request) { return java.util.Map.of("updated", processService.updateBulk(orgId, request)); } /** 목록에서 체크한 건들을 지운다. */ @PostMapping("/api/orgs/{id}/process/bulk-delete") public java.util.Map deleteBulk(@PathVariable("id") Long orgId, @RequestBody java.util.List ids) { return java.util.Map.of("deleted", processService.deleteBulk(orgId, ids)); } @GetMapping("/api/orgs/{id}/process/{itemId}") public ProcessItem get(@PathVariable("id") Long orgId, @PathVariable Long itemId) { return processService.get(orgId, itemId); } @PutMapping("/api/orgs/{id}/process/{itemId}") public ProcessItem update(@PathVariable("id") Long orgId, @PathVariable Long itemId, @RequestBody ProcessingRequest request) { return processService.updateProcessing(orgId, itemId, request); } @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/api/orgs/{id}/process/{itemId}") public void delete(@PathVariable("id") Long orgId, @PathVariable Long itemId) { processService.delete(orgId, itemId); } /** 한글 파일명이라 ReviewController.download와 동일하게 RFC 5987 인코딩을 쓴다. */ @GetMapping("/api/orgs/{id}/process/download") public ResponseEntity download(@PathVariable("id") Long orgId) { Organization org = orgMapper.findById(orgId); if (org == null) { throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId); } byte[] content = processService.downloadWorkbook(orgId); String filename = "권리처리_" + org.getOrgName() + ".xlsx"; String encodedName = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20"); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedName) .contentType(MediaType.parseMediaType( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) .body(content); } private static boolean hasAllowedExtension(String filename) { String lower = filename.toLowerCase(); return lower.endsWith(".xlsx") || lower.endsWith(".xlsm"); } }