package com.zioinfo.mes.plan; import com.zioinfo.mes.admin.AuditService; import com.zioinfo.mes.ai.AiService; import com.zioinfo.mes.common.ApiResponse; import com.zioinfo.mes.common.AuthSupport; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; /** * 생산계획 API + AI 일정 최적화. 조회 Viewer+, 변경 Manager+. */ @RestController @RequestMapping("/api/mes/plans") @RequiredArgsConstructor public class PlanController { private final PlanMapper mapper; private final AuditService audit; private final AiService ai; @GetMapping public ApiResponse> list(@RequestParam(required = false) String status, @RequestParam(required = false) String itemCode) { return ApiResponse.ok(mapper.findAll(status, itemCode)); } @GetMapping("/{id}") public ApiResponse get(@PathVariable Long id) { return ApiResponse.ok(require(id)); } @PostMapping public ApiResponse create(@RequestBody MesPlan p, Authentication auth) { AuthSupport.requireManager(auth); if (p.getItemCode() == null) throw new IllegalArgumentException("ERR-PLAN-400: itemCode 필수"); if (p.getPlanNo() == null || p.getPlanNo().isBlank()) p.setPlanNo("PLAN-" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + "-" + (System.currentTimeMillis() % 100000)); if (p.getStatus() == null) p.setStatus("PLANNED"); p.setCreatedBy(AuthSupport.actor(auth)); mapper.insert(p); audit.log("PLAN_CREATE", p.getPlanNo(), "item=" + p.getItemCode()); return ApiResponse.ok(mapper.findById(p.getId())); } @PutMapping("/{id}") public ApiResponse update(@PathVariable Long id, @RequestBody MesPlan p, Authentication auth) { AuthSupport.requireManager(auth); require(id); p.setId(id); mapper.update(p); audit.log("PLAN_UPDATE", p.getPlanNo(), "id=" + id); return ApiResponse.ok(mapper.findById(id)); } @PutMapping("/{id}/status") public ApiResponse status(@PathVariable Long id, @RequestBody StatusRequest req, Authentication auth) { AuthSupport.requireManager(auth); MesPlan cur = require(id); mapper.updateStatus(id, req.status()); audit.log("PLAN_STATUS", cur.getPlanNo(), cur.getStatus() + " -> " + req.status()); return ApiResponse.ok(mapper.findById(id)); } /** AI 일정 최적화 — 납기/셋업 기준 순서 제안. */ @PostMapping("/optimize") public ApiResponse>> optimize(@RequestBody List> orders) { return ApiResponse.ok(ai.scheduleOptimize(orders)); } @DeleteMapping("/{id}") public ApiResponse delete(@PathVariable Long id, Authentication auth) { AuthSupport.requireManager(auth); mapper.delete(id); audit.log("PLAN_DELETE", String.valueOf(id), ""); return ApiResponse.ok(null); } private MesPlan require(Long id) { MesPlan p = mapper.findById(id); if (p == null) throw new RuntimeException("ERR-PLAN-404: 생산계획 없음"); return p; } record StatusRequest(String status) {} }