Loading the catalog…
Loading the catalog…
열이 회사마다 다르다 작업일보 메인 화면은 "현장 × 직종" 표다. 각 칸에 그날 단말기로 찍힌 인원과 관리자가 적은 일보 인원을 나란히 보여 준다. 문제는 가로축인 직종 이다. 직종(형틀, 철근 같은 분류)은 건설사가 직접 등록하기 때문에 회사마다 개수도 이름도 다르다. 운영 데이터를 세어 보니 업체 201곳의 직종 수는 2개부터 33개까지였고, 서로 다른 직종 목록이 82가지였다. 그래서 HTML에 <th>형틀</th> 처럼 열을 적어 둘 수 없다. 엑셀도 마찬가지다. 첫 시도: 열은 그렸는데 표가 깨졌다 열 자체는 어렵지 않았다. 그 업체의 직종 목록을 조회해서 th:each 로 돌리면 된다. 어려운 건 칸 안의 값 이었다. 집계 쿼리는 결과를 세로로 준다. 현장 직종 인원 A현장 형틀 5 A현장 철근 3 B현장 형틀 2 이걸 가로 표에 넣으려고, 처음에는 화면에서 행마다·직종마다 결과 리스트를 처음부터 다시 뒤져 맞는 값을 찾았다. <th:block th:each="row : ${countRows}"> <th:block th:each="job : ${row}"> <th:block th:each="entry : ${job}"> <th:block th:each="jobName : ${jobList}"> <!-- 직종마다 리스트를 다시 뒤진다 --> <td th:if="${entry.key == jobName}" th:text="${entry.value}"></td> 값이 없는 칸은 <td> 가 아예 안 그려지니 뒤의 칸이 앞으로 밀리고, 열 위치가 어긋났다. 그날 커밋 메시지가 이랬다. "모든 합계 쿼리를 구현했으나 화면에 뿌려주는 과정에서 화면 깨짐. 타임리프 수정 필요" 문제는 열이 아니라 칸 이었다. (현장, 직종)으로 값을 바로 꺼낼 구조가 없었다. 세로 결과를 2단 Map으로 다시 조립 그래서 서비스에서 세로 결과를 현장 → 직종 → 인원 2단 Map으로 바꿨다. Map<String, Map<String, Object>> grid = new LinkedHashMap<>(); for (Map<String, Object> row : mapper.reportedCountBySiteAndJob(p)) { Map<String, Object> bySite = grid.computeIfAbsent((String) row.get("site_name"), k -> new LinkedHashMap<>()); String job = (String) row.get("job_name"); int cnt = Integer.parseInt(row.get("count").toString()); bySite.merge(job, cnt, (a, b) -> (int) a + (int) b); // 같은 직종이 팀별로 여러 행이면 더한다 } computeIfAbsent : 현장이 처음 나오면 빈 Map을 만들고, 이미 있으면 그걸 쓴다. merge : 같은 현장·직종이 팀별로 여러 행 오면 인원을 더한다. LinkedHashMap : 쿼리에서 정렬한 현장 순서를 그대로 유지한다. HashMap 이면 화면의 행 순서가 뒤섞인다. 이제 화면은 칸마다 좌표로 한 번만 꺼내면 된다. <th th:each="job : ${jobList}" th:text="${job.name}"></th> <!-- 열: 직종 목록 --> <tr th:each="site : ${grid}"> <td th:each="job : ${jobList}" th:text="${grid[site.key][job.name] ?: 0}"></td> <!-- 칸: 좌표 1회 --> </tr> 열은 항상 직종 목록을 기준으로 돌기 때문에, 값이 없는 칸도 ?: 0 으로 0이 그려진다. 칸이 밀리지 않는다. SQL PIVOT으로 가로로 받으면 안 되나? DB에서 가로 결과를 받는 방법(PIVOT, crosstab, 동적 SQL)도 있다. 하지만 열이 회사마다 다르니, 회사마다 다른 쿼리를 만들어야 한다. PostgreSQL에서는 crosstab 확장도 필요하다. 세로로 받아서 애플리케이션에서 펴면 쿼리는 하나로 고정 되고, 열이 몇 개든 같은 코드로 그린다. 엑셀도 같은 Map으로 작업일보는 엑셀 다운로드도 있다(일간·월간 5종). 엑셀도 같은 2단 Map을 받는다. 엑셀은 빈 파일을 코드로 그리지 않고, 서식이 미리 들어 있는 템플릿 xlsx 를 읽어서 값만 채웠다. 고정 열 뒤 10번째 열부터 직종 헤더를 넣고, 합계·병합 범위는 마지막 직종 열 기준으로 계산한다. XSSFWorkbook wb = new XSSFWorkbook(new ClassPathResource("files/report.xlsx").getInputStream()); XSSFRow header = wb.getSheetAt(0).getRow(3); for (int i = 0; i < jobList.size(); i++) { header.getCell(10 + i).setCellValue(jobList.get(i).getName()); // 직종 수만큼 헤더 } int last = 10 + jobList.size() - 1; // 마지막 직종 열 sheet.addMergedRegion(new CellRangeAddress(3, 4, last + 1, last + 7)); // 그 뒤 합계 영역 글꼴이나 색 같은 서식 요청이 오면 코드를 고치지 않고 템플릿 파일만 바꾸면 된다. 정리 동적 컬럼에서 어려운 건 열이 아니라 칸이었다. 열은 직종 목록 하나로 끝나지만, 칸은 (현장, 직종) 좌표로 꺼낼 구조가 있어야 한다. 세로로 받고 2단 Map으로 폈다. 쿼리는 하나로 고정하고, 화면과 엑셀이 같은 Map을 읽는다. 열은 항상 직종 목록 기준으로 돌렸다. 값이 없는 칸도 0으로 그려서 칸이 밀리지 않는다.
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
고객사마다 다른 컬럼을 화면과 엑셀에 그리기: 직종 목록 + 2단 Map. 열이 회사마다 다르다 작업일보 메인 화면은 "현장 × 직종" 표다. 각 칸에 그날 단말기로 찍힌 인원과 관리자가 적은 일보 인원을 나란히 보여 준다. 문제는 가로축인 직종 이다. 직종(형틀, 철근 같은 분류)은 건설사가 직접 등록하기 때문에 회사마다 개수도 이름도 다르다. 운영 데이터를 세어 보니 업체 201곳의 직종 수는 2개부터 33개까지였고, 서로 다른 직종 목록이 82가지였다. 그래서 HTML에 형틀 처럼 열을 적어 둘 수 없다. 엑셀도 마찬가지다. 첫 시도: 열은 그렸는데 표가 깨졌다 열 자체는 어렵지 않았다. 그 업체의 직종 목록을 조회해서 th:each 로 돌리면 된다. 어려운 건 칸 안의 값 이었다. 집계 쿼리는…
Open source