c4a62b7e35
운영 QA 에서 발견된 3가지 결함을 한 번에 수정. 1. SubstituteController.java:56 / SubstituteService.java:242 (requireAdmin) - role 비교에서 "COMPANY_ADMIN" 누락 → 운영 admin 이 대무자 지정 시 항상 403. - 운영 회사 admin 의 user_type 은 COMPANY_ADMIN 이 표준 (AdminAccountCreator 가 그렇게 생성). - "ADMIN" / "SUPER_ADMIN" 외 "COMPANY_ADMIN" 도 허용. 2. mapper/approval.xml (selectMyRequests, selectMyPendingLines) - ORDER BY / SELECT 의 R.CREATED_DATE 가 잘못된 컬럼명 (APPROVAL_REQUESTS 실제: created_at). - 결재함 /api/approval/my-pending, /api/approval/requests 가 항상 500. - 3군데 R.CREATED_DATE → R.CREATED_AT. 3. SubstituteSection.tsx - 대무자 ID 를 직접 타이핑하던 input 을 Select 로 교체. - getUserList 로 같은 회사 활성 사용자 목록 로드, 본인 + SUPER_ADMIN + 비활성 자동 제외. - 다이얼로그 열 때 한 번만 load (openDialog 시 loadCandidates). - 빈 결과/로딩 placeholder 처리.
176 lines
8.7 KiB
Java
176 lines
8.7 KiB
Java
package com.erp.controller;
|
|
|
|
import com.erp.dto.ApiResponse;
|
|
import com.erp.service.SubstituteService;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.security.access.AccessDeniedException;
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* 대무자(代務者) 관리 API.
|
|
*
|
|
* Spec: .omc/specs/deep-dive-user-substitute-management.md
|
|
* Plan: .omc/plans/autopilot-impl.md (T4)
|
|
*
|
|
* 정책:
|
|
* - GET /mine 은 본인 read-only (누구나 가능)
|
|
* - 나머지는 관리자(ADMIN/SUPER_ADMIN) 만 — Service 의 requireAdmin 이 2차 방어
|
|
*/
|
|
@RestController
|
|
@RequestMapping("/api/substitutes")
|
|
@RequiredArgsConstructor
|
|
@Slf4j
|
|
public class SubstituteController {
|
|
|
|
private final SubstituteService substituteService;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 조회 — 관리자
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
@GetMapping
|
|
public ResponseEntity<ApiResponse<Map<String, Object>>> getList(
|
|
@RequestParam Map<String, Object> params,
|
|
@RequestAttribute("company_code") String companyCode,
|
|
@RequestAttribute("role") String role) {
|
|
params.put("company_code", companyCode);
|
|
params.put("role", role);
|
|
try {
|
|
return ResponseEntity.ok(ApiResponse.success(substituteService.getSubstituteList(params)));
|
|
} catch (AccessDeniedException e) {
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.error(e.getMessage()));
|
|
}
|
|
}
|
|
|
|
@GetMapping("/{id}")
|
|
public ResponseEntity<ApiResponse<Map<String, Object>>> getOne(
|
|
@PathVariable("id") Long substituteId,
|
|
@RequestAttribute("company_code") String companyCode,
|
|
@RequestAttribute("role") String role) {
|
|
if (!"ADMIN".equals(role) && !"COMPANY_ADMIN".equals(role) && !"SUPER_ADMIN".equals(role)) {
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
|
.body(ApiResponse.error("관리자만 조회할 수 있습니다."));
|
|
}
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("substitute_id", substituteId);
|
|
params.put("company_code", companyCode);
|
|
try {
|
|
return ResponseEntity.ok(ApiResponse.success(substituteService.getSubstituteInfo(params)));
|
|
} catch (IllegalArgumentException e) {
|
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.error(e.getMessage()));
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 본인 조회 — ProfileModal read-only
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
@GetMapping("/mine")
|
|
public ResponseEntity<ApiResponse<Map<String, Object>>> getMine(
|
|
@RequestAttribute("user_id") String userId,
|
|
@RequestAttribute("company_code") String companyCode) {
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("user_id", userId);
|
|
params.put("company_code", companyCode);
|
|
return ResponseEntity.ok(ApiResponse.success(substituteService.getMySubstitutes(params)));
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 변경 — 관리자
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
@PostMapping
|
|
public ResponseEntity<ApiResponse<Map<String, Object>>> create(
|
|
@RequestBody Map<String, Object> body,
|
|
@RequestAttribute("user_id") String userId,
|
|
@RequestAttribute("company_code") String companyCode,
|
|
@RequestAttribute("role") String role) {
|
|
body.put("company_code", companyCode);
|
|
body.put("role", role);
|
|
body.put("created_by", userId);
|
|
try {
|
|
Map<String, Object> created = substituteService.insertSubstitute(body);
|
|
return ResponseEntity.status(HttpStatus.CREATED)
|
|
.body(ApiResponse.success(created, "대무자가 지정되었습니다."));
|
|
} catch (AccessDeniedException e) {
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.error(e.getMessage()));
|
|
} catch (IllegalArgumentException e) {
|
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.error(e.getMessage()));
|
|
} catch (Exception e) {
|
|
log.error("대무자 등록 오류", e);
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
.body(ApiResponse.error("대무자 등록 중 오류가 발생했습니다."));
|
|
}
|
|
}
|
|
|
|
@PutMapping("/{id}")
|
|
public ResponseEntity<ApiResponse<Map<String, Object>>> update(
|
|
@PathVariable("id") Long substituteId,
|
|
@RequestBody Map<String, Object> body,
|
|
@RequestAttribute("user_id") String userId,
|
|
@RequestAttribute("company_code") String companyCode,
|
|
@RequestAttribute("role") String role) {
|
|
body.put("substitute_id", substituteId);
|
|
body.put("company_code", companyCode);
|
|
body.put("role", role);
|
|
body.put("updated_by", userId);
|
|
try {
|
|
return ResponseEntity.ok(
|
|
ApiResponse.success(substituteService.updateSubstitute(body), "대무 설정이 수정되었습니다."));
|
|
} catch (AccessDeniedException e) {
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.error(e.getMessage()));
|
|
} catch (IllegalArgumentException e) {
|
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.error(e.getMessage()));
|
|
} catch (Exception e) {
|
|
log.error("대무자 수정 오류", e);
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
.body(ApiResponse.error("대무자 수정 중 오류가 발생했습니다."));
|
|
}
|
|
}
|
|
|
|
@DeleteMapping("/{id}")
|
|
public ResponseEntity<ApiResponse<Void>> delete(
|
|
@PathVariable("id") Long substituteId,
|
|
@RequestAttribute("company_code") String companyCode,
|
|
@RequestAttribute("role") String role) {
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("substitute_id", substituteId);
|
|
params.put("company_code", companyCode);
|
|
params.put("role", role);
|
|
try {
|
|
substituteService.deleteSubstitute(params);
|
|
return ResponseEntity.ok(ApiResponse.success(null, "대무 설정이 해지되었습니다."));
|
|
} catch (AccessDeniedException e) {
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.error(e.getMessage()));
|
|
} catch (IllegalArgumentException e) {
|
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.error(e.getMessage()));
|
|
} catch (Exception e) {
|
|
log.error("대무자 해지 오류", e);
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
.body(ApiResponse.error("대무자 해지 중 오류가 발생했습니다."));
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 사전 검증 — UI 가 등록 직전 호출
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
@PostMapping("/check-overlap")
|
|
public ResponseEntity<ApiResponse<Map<String, Object>>> checkOverlap(
|
|
@RequestBody Map<String, Object> body,
|
|
@RequestAttribute("company_code") String companyCode) {
|
|
body.put("company_code", companyCode);
|
|
int cnt = substituteService.checkOverlap(body);
|
|
Map<String, Object> result = new HashMap<>();
|
|
result.put("overlap", cnt > 0);
|
|
result.put("count", cnt);
|
|
return ResponseEntity.ok(ApiResponse.success(result));
|
|
}
|
|
}
|