Merge remote-tracking branch 'origin/main' into hjjeong

This commit is contained in:
hjjeong
2026-05-13 17:20:59 +09:00
24 changed files with 441 additions and 241 deletions
@@ -295,7 +295,8 @@ public class AdminController {
@PostMapping("/users/reset-password")
public ResponseEntity<ApiResponse<Void>> resetUserPassword(@RequestBody Map<String, Object> body) {
String userId = (String) body.get("user_id");
adminService.resetUserPassword(userId);
String newPassword = (String) body.get("new_password");
adminService.resetUserPassword(userId, newPassword);
return ResponseEntity.ok(ApiResponse.success(null, "비밀번호 초기화 성공"));
}
@@ -53,7 +53,7 @@ public class SubstituteController {
@PathVariable("id") Long substituteId,
@RequestAttribute("company_code") String companyCode,
@RequestAttribute("role") String role) {
if (!"ADMIN".equals(role) && !"SUPER_ADMIN".equals(role)) {
if (!"ADMIN".equals(role) && !"COMPANY_ADMIN".equals(role) && !"SUPER_ADMIN".equals(role)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("관리자만 조회할 수 있습니다."));
}
@@ -208,10 +208,17 @@ public class AdminService extends BaseService {
}
public void resetUserPassword(String userId) {
String defaultPw = passwordEncoder.encode("Welcome1!");
resetUserPassword(userId, null);
}
public void resetUserPassword(String userId, String newPassword) {
if (userId == null || userId.isBlank()) {
throw new IllegalArgumentException("user_id 는 필수입니다");
}
String rawPw = (newPassword != null && !newPassword.isBlank()) ? newPassword : "Welcome1!";
Map<String, Object> params = new HashMap<>();
params.put("user_id", userId);
params.put("user_password", defaultPw);
params.put("user_password", passwordEncoder.encode(rawPw));
sqlSession.update("admin.updateUserPassword", params);
}
@@ -39,6 +39,12 @@ public class DdlService extends BaseService {
"id", "created_date", "updated_date", "company_code"
);
/** 사용자가 신규 추가하는 컬럼에 허용되는 INPUT_TYPE 8종 (백엔드 백스톱) */
private static final Set<String> USER_SELECTABLE_INPUT_TYPES = Set.of(
"text", "number", "date", "code", "entity",
"numbering", "file", "image"
);
public DdlService(JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager) {
this.jdbcTemplate = jdbcTemplate;
this.transactionTemplate = new TransactionTemplate(transactionManager);
@@ -140,6 +146,12 @@ public class DdlService extends BaseService {
transactionTemplate.execute(status -> {
jdbcTemplate.execute(ddlQuery);
String inputType = convertToInputType(column);
if (!USER_SELECTABLE_INPUT_TYPES.contains(inputType)) {
throw new IllegalArgumentException(
"INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + USER_SELECTABLE_INPUT_TYPES
+ " (받은 값: " + inputType + ")"
);
}
String detailSettings = column.containsKey("detail_settings")
? column.get("detail_settings").toString() : "{}";
Integer maxOrder = jdbcTemplate.queryForObject(
@@ -408,10 +420,17 @@ public class DdlService extends BaseService {
// 사용자 정의 컬럼
for (int i = 0; i < columns.size(); i++) {
Map<String, Object> col = columns.get(i);
String inputType = convertToInputType(col);
if (!USER_SELECTABLE_INPUT_TYPES.contains(inputType)) {
throw new IllegalArgumentException(
"INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + USER_SELECTABLE_INPUT_TYPES
+ " (받은 값: " + inputType + ")"
);
}
String detailSettings = col.containsKey("detail_settings")
? col.get("detail_settings").toString() : "{}";
saveColumnMeta(tableName, (String) col.get("name"), companyCode,
convertToInputType(col), detailSettings, i);
inputType, detailSettings, i);
}
}
@@ -239,7 +239,7 @@ public class SubstituteService extends BaseService {
private void requireAdmin(Map<String, Object> params) {
String role = (String) params.get("role");
if (!"ADMIN".equals(role) && !"SUPER_ADMIN".equals(role)) {
if (!"ADMIN".equals(role) && !"COMPANY_ADMIN".equals(role) && !"SUPER_ADMIN".equals(role)) {
throw new AccessDeniedException("관리자만 대무자를 지정/수정/해지할 수 있습니다.");
}
}
@@ -26,6 +26,12 @@ public class TableManagementService extends BaseService {
private static final String NS = "tableManagement.";
/** 사용자가 직접 선택 가능한 INPUT_TYPE 8종 (INSERT/UPDATE-type 검증용) */
private static final Set<String> USER_SELECTABLE_INPUT_TYPES = Set.of(
"text", "number", "date", "code", "entity",
"numbering", "file", "image"
);
// ──────────────────────────────────────────────────
// 테이블 목록
// ──────────────────────────────────────────────────
@@ -145,7 +151,9 @@ public class TableManagementService extends BaseService {
Map<String, Object> settings, String companyCode) {
ensureTableInLabels(tableName);
String inputType = normalizeInputType((String) settings.get("input_type"));
boolean inputTypeChanged = settings.containsKey("input_type");
String ctx = inputTypeChanged ? "user-update-type" : "user-update-other";
String inputType = normalizeInputType((String) settings.get("input_type"), ctx);
Map<String, Object> params = new HashMap<>();
params.put("table_name", tableName);
params.put("column_name", columnName);
@@ -202,7 +210,7 @@ public class TableManagementService extends BaseService {
public void updateColumnInputType(String tableName, String columnName,
String inputType, String companyCode,
Map<String, Object> detailSettings) {
String finalType = normalizeInputType(inputType);
String finalType = normalizeInputType(inputType, "user-update-type");
Map<String, Object> params = new HashMap<>();
params.put("table_name", tableName);
params.put("column_name", columnName);
@@ -853,7 +861,7 @@ public class TableManagementService extends BaseService {
return name.replaceAll("[^a-zA-Z0-9_]", "");
}
/** "direct" / "auto" → "text" 변환 */
/** "direct" / "auto" → "text" 변환 (legacy 호출처 보호 — system-normalize 동작) */
private String normalizeInputType(String inputType) {
if ("direct".equals(inputType) || "auto".equals(inputType)) {
log.warn("잘못된 inputType 값 감지: {} → 'text'로 변환", inputType);
@@ -862,6 +870,24 @@ public class TableManagementService extends BaseService {
return inputType != null ? inputType : "text";
}
/**
* context 에 따라 INPUT_TYPE 정규화 및 검증.
* @param context "user-insert" | "user-update-type" | "user-update-other" | "system-normalize"
*/
private String normalizeInputType(String value, String context) {
if ("user-insert".equals(context) || "user-update-type".equals(context)) {
if (value == null || !USER_SELECTABLE_INPUT_TYPES.contains(value)) {
throw new IllegalArgumentException(
"INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + USER_SELECTABLE_INPUT_TYPES
+ " (받은 값: " + value + ")"
);
}
return value;
}
// user-update-other / system-normalize: 기존 동작 그대로
return normalizeInputType(value);
}
private String toJsonString(Object obj) {
if (obj == null) return "{}";
if (obj instanceof String s) return s.isBlank() ? "{}" : s;
@@ -222,7 +222,7 @@
AND L.COMPANY_CODE = R.COMPANY_CODE
)
</if>
ORDER BY R.CREATED_DATE DESC
ORDER BY R.CREATED_AT DESC
<if test="page_limit != null">
LIMIT #{page_limit} OFFSET #{page_offset}
</if>
@@ -465,7 +465,7 @@
SELECT L.*,
R.TITLE, R.TARGET_TABLE, R.TARGET_RECORD_ID,
R.REQUESTER_NAME, R.REQUESTER_DEPT,
R.CREATED_DATE AS REQUEST_CREATED_DATE
R.CREATED_AT AS REQUEST_CREATED_DATE
FROM APPROVAL_LINES L
JOIN APPROVAL_REQUESTS R
ON L.REQUEST_ID = R.REQUEST_ID AND L.COMPANY_CODE = R.COMPANY_CODE
@@ -475,7 +475,7 @@
</foreach>
AND L.STATUS = 'pending'
AND (L.COMPANY_CODE = #{company_code} OR L.COMPANY_CODE = '*')
ORDER BY R.CREATED_DATE ASC
ORDER BY R.CREATED_AT ASC
</select>
<!-- ================================================================
@@ -246,8 +246,8 @@
#{company_code}
, #{original_user_id}
, #{proxy_user_id}
, #{start_date}
, #{end_date}
, CAST(#{start_date} AS DATE)
, CAST(#{end_date} AS DATE)
, #{reason}
, COALESCE(#{is_active}, TRUE)
, #{created_by}