package kr.itn.itnhub.project; import kr.itn.itnhub.dashboard.DashboardOrgRow; import kr.itn.itnhub.dashboard.DashboardService; import kr.itn.itnhub.org.OrgNotFoundException; import kr.itn.itnhub.org.OrganizationMapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 사업관리 화면. 3단계 법률검토로 진행할 기관 현황을 한 곳에 모아 보여주고, 기관마다 * "신청 확인 연락" 이력과 최종 보고서를 관리한다(요청자 요구: 별도 메뉴로 구축 + 연락 부분 이력관리 * + 기관별 최종 보고서 업로드). * *

기관 현황 숫자는 대시보드와 같은 집계를 그대로 쓴다 - 두 화면이 다른 숫자를 보여주면 * 어느 쪽이 맞는지 확인할 방법이 없기 때문이다.

*/ @Service public class ProjectService { private final DashboardService dashboardService; private final ProjectMapper mapper; private final OrganizationMapper orgMapper; public ProjectService(DashboardService dashboardService, ProjectMapper mapper, OrganizationMapper orgMapper) { this.dashboardService = dashboardService; this.mapper = mapper; this.orgMapper = orgMapper; } public List rows() { Map side = new HashMap<>(); for (ProjectMapper.ProjectSideInfo info : mapper.findSideInfo()) { side.put(info.orgId(), info); } List orgs = dashboardService.dashboard().orgs(); List rows = new ArrayList<>(orgs.size()); for (DashboardOrgRow org : orgs) { ProjectMapper.ProjectSideInfo info = side.get(org.id()); rows.add(new ProjectRow(org, info == null ? null : info.lastContactedOn(), info == null ? 0 : info.contactCount(), info == null ? 0 : info.reportCount())); } return rows; } public List logs(Long orgId) { requireOrg(orgId); return mapper.findLogsByOrg(orgId); } @Transactional public List addLog(Long orgId, ContactLogRequest request, String author) { requireOrg(orgId); mapper.insertLog(orgId, request.contactedOn(), request.method(), request.summary(), author); return mapper.findLogsByOrg(orgId); } @Transactional public List updateLog(Long orgId, Long logId, ContactLogRequest request) { requireOrg(orgId); if (mapper.updateLog(orgId, logId, request.contactedOn(), request.method(), request.summary()) == 0) { throw new OrgNotFoundException("연락 이력을 찾을 수 없습니다: " + logId); } return mapper.findLogsByOrg(orgId); } @Transactional public void deleteLog(Long orgId, Long logId) { requireOrg(orgId); if (mapper.deleteLog(orgId, logId) == 0) { throw new OrgNotFoundException("연락 이력을 찾을 수 없습니다: " + logId); } } public List reports(Long orgId) { requireOrg(orgId); return mapper.findReportsByOrg(orgId); } /** 같은 기관에 여러 번 올릴 수 있다(개정판). 최신순으로 목록에 쌓인다. */ @Transactional public List addReport(Long orgId, String fileName, String contentType, byte[] content, String uploadedBy) { requireOrg(orgId); if (content == null || content.length == 0) { throw new IllegalArgumentException("빈 파일은 올릴 수 없습니다."); } mapper.insertReport(orgId, fileName, contentType, content.length, content, uploadedBy); return mapper.findReportsByOrg(orgId); } public ProjectMapper.ReportContent reportContent(Long orgId, Long reportId) { requireOrg(orgId); ProjectMapper.ReportContent content = mapper.findReportContent(orgId, reportId); if (content == null) { throw new OrgNotFoundException("보고서를 찾을 수 없습니다: " + reportId); } return content; } @Transactional public void deleteReport(Long orgId, Long reportId) { requireOrg(orgId); if (mapper.deleteReport(orgId, reportId) == 0) { throw new OrgNotFoundException("보고서를 찾을 수 없습니다: " + reportId); } } private void requireOrg(Long orgId) { if (orgMapper.findById(orgId) == null) { throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId); } } }