feat(cross-tenant): 부서 endpoint + UserFormModal 회사-우선 reorder
직전 Phase 1 의 후속 폴리시. 신규 백엔드 - crosstenant/CrossTenantDeptController.java GET /api/admin/cross-tenant/departments?company_code=TEST02 단일 모드 GET /admin/departments 와 응답 형태 동일. company_code query param 으로 명시된 회사 DB 컨텍스트로 임시 전환해서 부서 트리 반환. 버그 수정: 메타 DB DEPT_INFO 시드 (qnc/COMPANY_7 등 다른 회사 부서) 가 TEST02 선택 시에도 dropdown 에 섞여 보이던 문제 해결. 프론트 - lib/api/user.ts — getDepartmentList(companyCode) 가 isCrossTenantMode() 면 /admin/cross-tenant/departments?company_code= 호출. cross-tenant 모드 + companyCode 미지정 → 빈 배열 반환 (회사 안 골랐는데 메타 부서 보여주는 것 방지). UserFormModal - 회사 dropdown 을 폼 가장 위로 이동 — 사용자 ID 중복확인·부서 선택이 모두 회사에 의존하므로 자연스러운 입력 순서 - SUPER_ADMIN 인데 회사 미선택 상태에선 사용자 ID input + 중복확인 버튼 disable + placeholder "회사 먼저 선택" - checkUserIdDuplicate 가드: 회사 미선택이면 "회사를 먼저 선택해주세요" (백엔드의 400 "company_code 가 비어있음" 보다 친절) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package com.erp.crosstenant;
|
||||
|
||||
import com.erp.service.AdminService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SUPER_ADMIN 의 cross-tenant 부서 조회 — 사용자 등록/수정 폼의 "부서" dropdown 을
|
||||
* 선택된 회사 DB 기준으로 채우기 위한 보조 endpoint.
|
||||
*
|
||||
* 단일 회사 모드의 {@code GET /api/admin/departments} 와 응답 형태 동일.
|
||||
* 차이점: company_code 가 query param 으로 명시되고, 그 회사 DB 컨텍스트로 임시 전환.
|
||||
*
|
||||
* @see CrossTenantUserController
|
||||
* @see com.erp.controller.AdminController#getDepartmentList // 단일 모드 원본
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/cross-tenant/departments")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CrossTenantDeptController {
|
||||
|
||||
private final CrossTenantExecutor executor;
|
||||
private final AdminService adminService;
|
||||
|
||||
/**
|
||||
* GET /api/admin/cross-tenant/departments?company_code=TEST02
|
||||
* 응답 구조는 단일 모드와 동일: { success, data: { departments, flat_list }, total, total_count }
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<Map<String, Object>> listDepartments(
|
||||
HttpServletRequest request,
|
||||
@RequestParam("company_code") String companyCode) {
|
||||
if (!CrossTenantContext.isSuperAdmin(request)) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(errorBody("super_admin_required", request.getRequestURI()));
|
||||
}
|
||||
if (!CrossTenantContext.isMetaContext()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(errorBody("cross_tenant_requires_meta_context", request.getRequestURI()));
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> serviceResult = executor.runInCompany(companyCode, () -> {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("company_code", companyCode);
|
||||
return adminService.getDepartmentList(params);
|
||||
});
|
||||
|
||||
int total = ((Number) serviceResult.get("total")).intValue();
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("departments", serviceResult.get("departments"));
|
||||
data.put("flat_list", serviceResult.get("flat_list"));
|
||||
|
||||
Map<String, Object> response = new LinkedHashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("data", data);
|
||||
response.put("message", "부서 목록 조회 성공");
|
||||
response.put("total", total);
|
||||
response.put("total_count", total);
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(errorBody(e.getMessage(), request.getRequestURI()));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> errorBody(String message, String path) {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("success", false);
|
||||
body.put("message", message);
|
||||
body.put("path", path);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user