diff --git a/client/nexacro-deploy/assets/app.js b/client/nexacro-deploy/assets/app.js
index ada360e..fe05cfb 100644
--- a/client/nexacro-deploy/assets/app.js
+++ b/client/nexacro-deploy/assets/app.js
@@ -125,22 +125,36 @@ function table(columns, rows, options = {}) {
const value = options.render ? options.render(column, row[column.id], row) : row[column.id];
return `
${formatValue(value)} | `;
})
- .join("")}`
+ .join("")}${options.actions ? `${options.actions(row) || ""} | ` : ""}`
)
.join("")
- : `| 데이터가 없습니다. |
`;
+ : `| 데이터가 없습니다. |
`;
return `${columns
.map((column) => `| ${column.text} | `)
- .join("")}
${body}
`;
+ .join("")}${options.actions ? "작업 | " : ""}${body}`;
}
function pill(value) {
return `${value}`;
}
+function hasRole(requiredRole) {
+ const roleOrder = {
+ PUBLIC: 0,
+ VIEWER: 10,
+ OPERATOR: 20,
+ ADMIN: 30
+ };
+ return (roleOrder[state.session?.roleCode] || 0) >= (roleOrder[requiredRole] || 0);
+}
+
function isAdmin() {
- return state.session?.roleCode === "ADMIN";
+ return hasRole("ADMIN");
+}
+
+function isOperator() {
+ return hasRole("OPERATOR");
}
function cloneRows(rows) {
@@ -418,7 +432,7 @@ function renderUploads() {
-
+
오류 샘플
정상 TB
@@ -445,6 +459,9 @@ function renderUploads() {
return pill(value);
}
return value;
+ },
+ actions(row) {
+ return `
`;
}
}
)}
@@ -791,6 +808,9 @@ function bindEvents() {
const uploadButton = document.querySelector("[data-action='upload']");
if (uploadButton) {
uploadButton.addEventListener("click", async () => {
+ if (!isOperator()) {
+ return;
+ }
const templateCode = document.getElementById("upload-template").value;
const fiscalPeriod = document.getElementById("upload-period").value;
const file = document.getElementById("upload-file").files[0];
@@ -816,6 +836,22 @@ function bindEvents() {
});
}
+ document.querySelectorAll("[data-action='delete-upload']").forEach((button) => {
+ button.addEventListener("click", async () => {
+ if (!isOperator()) {
+ return;
+ }
+ const batchId = button.dataset.batchId;
+ const confirmed = window.confirm(`업로드 이력 ${batchId}번을 삭제하시겠습니까? 관련 오류내역과 업로드 행도 함께 삭제됩니다.`);
+ if (!confirmed) {
+ return;
+ }
+ await api(`/api/uploads/${batchId}`, { method: "DELETE" });
+ await loadUploads();
+ render();
+ });
+ });
+
const runButton = document.querySelector("[data-action='request-run']");
if (runButton) {
runButton.addEventListener("click", async () => {
diff --git a/client/nexacro-deploy/assets/styles.css b/client/nexacro-deploy/assets/styles.css
index d1f4b24..5088ee5 100644
--- a/client/nexacro-deploy/assets/styles.css
+++ b/client/nexacro-deploy/assets/styles.css
@@ -270,13 +270,17 @@ th {
font-weight: 700;
}
+.actions-cell {
+ white-space: nowrap;
+ width: 88px;
+}
+
.master-table input {
min-width: 110px;
padding: 10px 12px;
}
.master-table td.actions-cell {
- white-space: nowrap;
width: 88px;
}
diff --git a/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadController.java b/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadController.java
index bf1a12a..bcf117d 100644
--- a/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadController.java
+++ b/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadController.java
@@ -9,6 +9,7 @@ import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
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;
@@ -46,6 +47,12 @@ public class UploadController {
return uploadService.loadIssues(batchId);
}
+ @DeleteMapping("/{batchId}")
+ public Map
delete(@PathVariable Long batchId, HttpSession session) {
+ authService.requireRole(session, "OPERATOR");
+ return uploadService.deleteUploadBatch(batchId);
+ }
+
@GetMapping("/templates/{templateCode}/download")
public ResponseEntity templateDownload(@PathVariable String templateCode, HttpSession session) {
authService.requireRole(session, "VIEWER");
@@ -55,5 +62,18 @@ public class UploadController {
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + templateCode + "-template.xlsx\"")
.body(resource);
}
-}
+ @GetMapping("/samples/{sampleCode}/download")
+ public ResponseEntity sampleDownload(
+ @PathVariable String sampleCode,
+ @RequestParam String fiscalPeriod,
+ HttpSession session
+ ) {
+ authService.requireRole(session, "VIEWER");
+ ByteArrayResource resource = uploadService.downloadSample(sampleCode, fiscalPeriod);
+ return ResponseEntity.ok()
+ .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + sampleCode + "-" + fiscalPeriod + ".xlsx\"")
+ .body(resource);
+ }
+}
diff --git a/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadMapper.java b/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadMapper.java
index 3140630..9d6db65 100644
--- a/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadMapper.java
+++ b/server/api/src/main/java/com/hanwha/nexacrodemo/upload/UploadMapper.java
@@ -2,6 +2,7 @@ package com.hanwha.nexacrodemo.upload;
import java.util.List;
import java.util.Map;
+import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
@@ -154,5 +155,22 @@ public interface UploadMapper {
order by ur.id
""")
List `;
}
function pill(value) {
return `${value}`;
}
+function hasRole(requiredRole) {
+ const roleOrder = {
+ PUBLIC: 0,
+ VIEWER: 10,
+ OPERATOR: 20,
+ ADMIN: 30
+ };
+ return (roleOrder[state.session?.roleCode] || 0) >= (roleOrder[requiredRole] || 0);
+}
+
function isAdmin() {
- return state.session?.roleCode === "ADMIN";
+ return hasRole("ADMIN");
+}
+
+function isOperator() {
+ return hasRole("OPERATOR");
}
function cloneRows(rows) {
@@ -350,11 +364,11 @@ function renderUploads() {
@@ -377,6 +391,9 @@ function renderUploads() {
return pill(value);
}
return value;
+ },
+ actions(row) {
+ return ``;
}
}
)}
@@ -519,14 +536,6 @@ function shellContent(content) {
-
${content}
@@ -723,6 +732,9 @@ function bindEvents() {
const uploadButton = document.querySelector("[data-action='upload']");
if (uploadButton) {
uploadButton.addEventListener("click", async () => {
+ if (!isOperator()) {
+ return;
+ }
const templateCode = document.getElementById("upload-template").value;
const fiscalPeriod = document.getElementById("upload-period").value;
const file = document.getElementById("upload-file").files[0];
@@ -748,6 +760,22 @@ function bindEvents() {
});
}
+ document.querySelectorAll("[data-action='delete-upload']").forEach((button) => {
+ button.addEventListener("click", async () => {
+ if (!isOperator()) {
+ return;
+ }
+ const batchId = button.dataset.batchId;
+ const confirmed = window.confirm(`업로드 이력 ${batchId}번을 삭제하시겠습니까? 관련 오류내역과 업로드 행도 함께 삭제됩니다.`);
+ if (!confirmed) {
+ return;
+ }
+ await api(`/api/uploads/${batchId}`, { method: "DELETE" });
+ await loadUploads();
+ render();
+ });
+ });
+
const runButton = document.querySelector("[data-action='request-run']");
if (runButton) {
runButton.addEventListener("click", async () => {
diff --git a/templates/nexacro/preview.css.tpl b/templates/nexacro/preview.css.tpl
index d1f4b24..5088ee5 100644
--- a/templates/nexacro/preview.css.tpl
+++ b/templates/nexacro/preview.css.tpl
@@ -270,13 +270,17 @@ th {
font-weight: 700;
}
+.actions-cell {
+ white-space: nowrap;
+ width: 88px;
+}
+
.master-table input {
min-width: 110px;
padding: 10px 12px;
}
.master-table td.actions-cell {
- white-space: nowrap;
width: 88px;
}