239 lines
12 KiB
Java
239 lines
12 KiB
Java
package com.srsystem.controller;
|
|
|
|
import com.srsystem.dto.ApiResponse;
|
|
import com.srsystem.dto.BatchResult;
|
|
import com.srsystem.dto.CreateEquipmentRequest;
|
|
import com.srsystem.dto.CreateMaintenanceRequest;
|
|
import com.srsystem.dto.EquipmentSummary;
|
|
import com.srsystem.dto.UpdateEquipmentRequest;
|
|
import com.srsystem.entity.Equipment;
|
|
import com.srsystem.entity.EquipmentAssignment;
|
|
import com.srsystem.entity.EquipmentMaintenance;
|
|
import com.srsystem.entity.enums.EquipmentStatus;
|
|
import com.srsystem.entity.enums.EquipmentType;
|
|
import com.srsystem.service.EquipmentService;
|
|
import com.srsystem.util.ExcelParser;
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
import jakarta.validation.Valid;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.springframework.data.domain.Page;
|
|
import org.springframework.data.domain.Pageable;
|
|
import org.springframework.data.web.PageableDefault;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.security.access.prepost.PreAuthorize;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.web.bind.annotation.*;
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
@Tag(name = "Equipment", description = "IT 자산 관리 API")
|
|
@RestController
|
|
@RequestMapping("/api/v1/equipment")
|
|
@RequiredArgsConstructor
|
|
@SecurityRequirement(name = "bearerAuth")
|
|
public class EquipmentController {
|
|
|
|
private final EquipmentService equipmentService;
|
|
|
|
@Operation(summary = "장비 목록 조회", description = "필터 조건으로 장비 목록을 조회합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@GetMapping
|
|
public ResponseEntity<ApiResponse<Page<Equipment>>> getEquipment(
|
|
@RequestParam(required = false) String type,
|
|
@RequestParam(required = false) EquipmentStatus status,
|
|
@RequestParam(required = false) String keyword,
|
|
@PageableDefault(size = 20) Pageable pageable) {
|
|
Page<Equipment> equipment = equipmentService.getEquipment(type, status, keyword, pageable);
|
|
return ResponseEntity.ok(ApiResponse.ok(equipment));
|
|
}
|
|
|
|
@Operation(summary = "장비 상태별 카운트", description = "장비 상태별 개수를 조회합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@GetMapping("/summary")
|
|
public ResponseEntity<ApiResponse<EquipmentSummary>> getSummary() {
|
|
Map<String, Long> counts = equipmentService.getSummary();
|
|
EquipmentSummary summary = new EquipmentSummary(counts);
|
|
return ResponseEntity.ok(ApiResponse.ok(summary));
|
|
}
|
|
|
|
@Operation(summary = "장비 단건 조회", description = "장비 ID 로 단일 장비 정보를 조회합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@GetMapping("/{equipId}")
|
|
public ResponseEntity<ApiResponse<Equipment>> getEquipment(@PathVariable Long equipId) {
|
|
Equipment equipment = equipmentService.getEquipment(equipId);
|
|
return ResponseEntity.ok(ApiResponse.ok(equipment));
|
|
}
|
|
|
|
@Operation(summary = "장비 생성", description = "새로운 장비를 생성합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@PostMapping
|
|
public ResponseEntity<ApiResponse<Equipment>> createEquipment(@Valid @RequestBody CreateEquipmentRequest request) {
|
|
Equipment equipment = equipmentService.createEquipment(request);
|
|
return ResponseEntity.ok(ApiResponse.ok("장비가 등록되었습니다", equipment));
|
|
}
|
|
|
|
@Operation(summary = "장비 정보 수정", description = "장비 정보를 수정합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@PutMapping("/{equipId}")
|
|
public ResponseEntity<ApiResponse<Equipment>> updateEquipment(
|
|
@PathVariable Long equipId,
|
|
@RequestBody UpdateEquipmentRequest request) {
|
|
Equipment equipment = equipmentService.updateEquipment(equipId, request);
|
|
return ResponseEntity.ok(ApiResponse.ok("장비 정보가 수정되었습니다", equipment));
|
|
}
|
|
|
|
@Operation(summary = "장비 삭제", description = "장비를 삭제합니다 (Soft Delete)")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@DeleteMapping("/{equipId}")
|
|
public ResponseEntity<ApiResponse<Void>> deleteEquipment(@PathVariable Long equipId) {
|
|
equipmentService.deleteEquipment(equipId);
|
|
return ResponseEntity.ok(ApiResponse.ok("장비가 삭제되었습니다", null));
|
|
}
|
|
|
|
@Operation(summary = "장비 지급", description = "사용자에게 장비를 지급합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@PostMapping("/{equipId}/assign")
|
|
public ResponseEntity<ApiResponse<EquipmentAssignment>> assignEquipment(
|
|
@PathVariable Long equipId,
|
|
@RequestBody Map<String, Object> body) {
|
|
Long userId = ((Number) body.get("userId")).longValue();
|
|
String condition = (String) body.get("condition");
|
|
EquipmentAssignment assignment = equipmentService.assignEquipment(equipId, userId, condition);
|
|
return ResponseEntity.ok(ApiResponse.ok("장비가 지급되었습니다", assignment));
|
|
}
|
|
|
|
@Operation(summary = "장비 반납", description = "장비를 반납합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@PutMapping("/{equipId}/return")
|
|
public ResponseEntity<ApiResponse<EquipmentAssignment>> returnEquipment(
|
|
@PathVariable Long equipId,
|
|
@RequestBody Map<String, String> body) {
|
|
EquipmentAssignment assignment = equipmentService.returnEquipment(equipId, body.get("condition"));
|
|
return ResponseEntity.ok(ApiResponse.ok("장비가 반납되었습니다", assignment));
|
|
}
|
|
|
|
@Operation(summary = "장비 지급 이력 조회", description = "장비의 지급/반납 이력을 조회합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@GetMapping("/{equipId}/assignments")
|
|
public ResponseEntity<ApiResponse<List<EquipmentAssignment>>> getAssignments(@PathVariable Long equipId) {
|
|
List<EquipmentAssignment> assignments = equipmentService.getAssignments(equipId);
|
|
return ResponseEntity.ok(ApiResponse.ok(assignments));
|
|
}
|
|
|
|
@Operation(summary = "장비 유지보수 이력 조회", description = "장비의 유지보수 이력을 조회합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@GetMapping("/{equipId}/maintenances")
|
|
public ResponseEntity<ApiResponse<List<EquipmentMaintenance>>> getMaintenanceHistory(@PathVariable Long equipId) {
|
|
List<EquipmentMaintenance> history = equipmentService.getMaintenanceHistory(equipId);
|
|
return ResponseEntity.ok(ApiResponse.ok(history));
|
|
}
|
|
|
|
@Operation(summary = "장비 유지보수 기록 추가", description = "장비의 유지보수 기록을 추가합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@PostMapping("/{equipId}/maintenance")
|
|
public ResponseEntity<ApiResponse<EquipmentMaintenance>> addMaintenance(
|
|
@PathVariable Long equipId,
|
|
@Valid @RequestBody CreateMaintenanceRequest request) {
|
|
EquipmentMaintenance maintenance = equipmentService.addMaintenance(equipId, request);
|
|
return ResponseEntity.ok(ApiResponse.ok("유지보수 기록이 등록되었습니다", maintenance));
|
|
}
|
|
|
|
@Operation(summary = "장비 이미지 추가", description = "장비에 이미지를 추가합니다 (JSONB 배열)")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@PostMapping("/{equipmentId}/images")
|
|
public ResponseEntity<ApiResponse<Equipment>> addImage(
|
|
@PathVariable Long equipmentId,
|
|
@RequestParam("file") MultipartFile file) {
|
|
Equipment equipment = equipmentService.addImage(equipmentId, file);
|
|
return ResponseEntity.ok(ApiResponse.ok("이미지가 추가되었습니다", equipment));
|
|
}
|
|
|
|
@Operation(summary = "장비 이미지 삭제", description = "장비에서 특정 이미지를 삭제합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@DeleteMapping("/{equipmentId}/images/{imageIndex}")
|
|
public ResponseEntity<ApiResponse<Equipment>> removeImage(
|
|
@PathVariable Long equipmentId,
|
|
@PathVariable int imageIndex) {
|
|
Equipment equipment = equipmentService.removeImage(equipmentId, imageIndex);
|
|
return ResponseEntity.ok(ApiResponse.ok("이미지가 삭제되었습니다", equipment));
|
|
}
|
|
|
|
// ---- Batch ----
|
|
|
|
@Operation(summary = "장비 일괄 등록", description = "엑셀 파일로 장비를 일괄 등록합니다")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@Transactional
|
|
@PostMapping("/batch")
|
|
public ResponseEntity<ApiResponse<BatchResult<Equipment>>> batchCreate(@RequestParam("file") MultipartFile file) {
|
|
try {
|
|
ExcelParser.ParseResult<CreateEquipmentRequest> parsed = ExcelParser.parse(
|
|
file,
|
|
(row) -> new CreateEquipmentRequest(
|
|
EquipmentType.valueOf(row.getOrDefault("장비유형", "LAPTOP").toUpperCase()),
|
|
row.getOrDefault("제조사", ""),
|
|
row.getOrDefault("모델명", ""),
|
|
row.getOrDefault("시리얼번호", ""),
|
|
row.getOrDefault("자산코드", ""),
|
|
row.getOrDefault("제원정보", ""),
|
|
EquipmentStatus.AVAILABLE,
|
|
row.containsKey("구매일") ? java.time.LocalDate.parse(row.get("구매일")) : java.time.LocalDate.now(),
|
|
null
|
|
),
|
|
List.of()
|
|
);
|
|
|
|
List<BatchResult.Failure> failures = new ArrayList<>();
|
|
List<Equipment> success = new ArrayList<>();
|
|
var parseFailures = parsed.failures();
|
|
for (var pf : parseFailures) {
|
|
failures.add(new BatchResult.Failure(pf.row(), pf.reason()));
|
|
}
|
|
|
|
for (int i = 0; i < parsed.data().size(); i++) {
|
|
try {
|
|
Equipment eq = equipmentService.createEquipment(parsed.data().get(i));
|
|
success.add(eq);
|
|
} catch (Exception e) {
|
|
failures.add(new BatchResult.Failure(i + 2, e.getMessage()));
|
|
}
|
|
}
|
|
|
|
BatchResult<Equipment> result = new BatchResult<>(
|
|
parsed.data().size() + parseFailures.size(),
|
|
success.size(),
|
|
failures.size(),
|
|
failures,
|
|
success
|
|
);
|
|
return ResponseEntity.ok(ApiResponse.ok(
|
|
String.format("%d건 중 %d건 성공, %d건 실패", result.totalCount(), result.successCount(), result.failCount()),
|
|
result
|
|
));
|
|
} catch (Exception e) {
|
|
return ResponseEntity.badRequest().body(ApiResponse.error("엑셀 파싱 실패: " + e.getMessage()));
|
|
}
|
|
}
|
|
|
|
@Operation(summary = "장비 일괄 등록 템플릿 다운로드")
|
|
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
|
|
@GetMapping("/batch/template")
|
|
public ResponseEntity<byte[]> downloadTemplate() throws Exception {
|
|
String[] headers = {"장비유형", "제조사", "모델명", "시리얼번호", "자산코드", "제원정보", "구매일"};
|
|
Map<Integer, String[]> dropdowns = Map.of(
|
|
0, new String[]{"LAPTOP", "MONITOR", "TEST_DEVICE", "ACCESSORY"}
|
|
);
|
|
byte[] data = ExcelParser.generateTemplate(headers, "장비 일괄등록", dropdowns);
|
|
return ResponseEntity.ok()
|
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=equipment_batch_template.xlsx")
|
|
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
|
|
.body(data);
|
|
}
|
|
}
|