package com.kaidi.oa.web; import com.kaidi.oa.common.ApiException; import com.kaidi.oa.common.ApiResp; import com.kaidi.oa.common.NotFoundException; import com.kaidi.oa.domain.ContentTask; import com.kaidi.oa.repository.ContentTaskRepository; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.time.Instant; import java.util.List; /** * 品牌推广部·内容创作任务分发(内容创作与审核发布·任务分发子功能)。 * * 补全缺口:ContentPiece 仅有 author 字段,无独立 ContentTask 实体/控制器, * 无任务指派给供应商的关联机制。 * * 功能: * 1. 内容创作任务 CRUD(文案/设计/视频,指派内部员工或外部供应商) * 2. 状态机:待接受 → 进行中 → 待审核 → 已发布 / 已驳回 * 3. 任务列表支持按状态/类型/指派类型过滤 * 4. 完成后可关联 ContentPiece 稿件(contentCode/contentId)形成追溯链 */ @RestController @RequestMapping("/api/oa/content-tasks") public class ContentTaskController { private final ContentTaskRepository taskRepo; public ContentTaskController(ContentTaskRepository taskRepo) { this.taskRepo = taskRepo; } // ---------- 任务台账 ---------- @GetMapping public ApiResp> list( @RequestParam(required = false) String status, @RequestParam(required = false) String taskType, @RequestParam(required = false) String assigneeType) { if (status != null && !status.isBlank()) { return ApiResp.ok(taskRepo.findByStatus(status)); } if (taskType != null && !taskType.isBlank()) { return ApiResp.ok(taskRepo.findByTaskType(taskType)); } if (assigneeType != null && !assigneeType.isBlank()) { return ApiResp.ok(taskRepo.findByAssigneeType(assigneeType)); } return ApiResp.ok(taskRepo.findAll()); } @GetMapping("/{id}") public ApiResp get(@PathVariable Long id) { return ApiResp.ok(find(id)); } public record TaskRequest( String code, String title, String contentCode, Long contentId, String taskType, String description, String assigneeType, String assignee, String supplierCode, String dueDate, String issuedBy) { } @PostMapping public ApiResp create(@RequestBody TaskRequest req) { if (req.title() == null || req.title().isBlank()) { throw new ApiException(400, "任务标题(title) 不能为空"); } if (req.assignee() == null || req.assignee().isBlank()) { throw new ApiException(400, "指派对象(assignee) 不能为空"); } ContentTask t = new ContentTask(); t.setCode(req.code() == null || req.code().isBlank() ? "CT-" + (taskRepo.count() + 1) : req.code()); t.setTitle(req.title()); t.setContentCode(req.contentCode()); t.setContentId(req.contentId()); t.setTaskType(req.taskType() == null || req.taskType().isBlank() ? "文案" : req.taskType()); t.setDescription(req.description()); t.setAssigneeType(req.assigneeType() == null || req.assigneeType().isBlank() ? "内部员工" : req.assigneeType()); t.setAssignee(req.assignee()); t.setSupplierCode(req.supplierCode()); t.setDueDate(req.dueDate()); t.setIssuedBy(req.issuedBy()); t.setStatus("待接受"); t.setCreatedAt(Instant.now()); return ApiResp.ok(taskRepo.save(t)); } @PatchMapping("/{id}") public ApiResp update(@PathVariable Long id, @RequestBody TaskRequest req) { ContentTask t = find(id); if ("已发布".equals(t.getStatus())) { throw new ApiException(409, "已发布的任务不可编辑"); } if (req.title() != null && !req.title().isBlank()) t.setTitle(req.title()); if (req.contentCode() != null) t.setContentCode(req.contentCode()); if (req.contentId() != null) t.setContentId(req.contentId()); if (req.taskType() != null && !req.taskType().isBlank()) t.setTaskType(req.taskType()); if (req.description() != null) t.setDescription(req.description()); if (req.assigneeType() != null && !req.assigneeType().isBlank()) t.setAssigneeType(req.assigneeType()); if (req.assignee() != null && !req.assignee().isBlank()) t.setAssignee(req.assignee()); if (req.supplierCode() != null) t.setSupplierCode(req.supplierCode()); if (req.dueDate() != null) t.setDueDate(req.dueDate()); return ApiResp.ok(taskRepo.save(t)); } @DeleteMapping("/{id}") public ApiResp delete(@PathVariable Long id) { ContentTask t = find(id); if (!"待接受".equals(t.getStatus()) && !"已驳回".equals(t.getStatus())) { throw new ApiException(409, "仅待接受/已驳回状态可删除"); } taskRepo.deleteById(id); return ApiResp.ok(null); } // ---------- 状态机 ---------- /** 接受任务:待接受 → 进行中。 */ @PostMapping("/{id}/accept") public ApiResp accept(@PathVariable Long id) { ContentTask t = find(id); if (!"待接受".equals(t.getStatus())) { throw new ApiException(409, "仅待接受状态可接受,当前「" + t.getStatus() + "」"); } t.setStatus("进行中"); return ApiResp.ok(taskRepo.save(t)); } public record SubmitDeliverableRequest(String deliverableUrl, String contentCode, Long contentId) { } /** 提交交付物:进行中 → 待审核,关联稿件。 */ @PostMapping("/{id}/submit") @Transactional public ApiResp submit(@PathVariable Long id, @RequestBody(required = false) SubmitDeliverableRequest req) { ContentTask t = find(id); if (!"进行中".equals(t.getStatus())) { throw new ApiException(409, "仅进行中可提交交付物,当前「" + t.getStatus() + "」"); } if (req != null) { if (req.deliverableUrl() != null) t.setDeliverableUrl(req.deliverableUrl()); if (req.contentCode() != null) t.setContentCode(req.contentCode()); if (req.contentId() != null) t.setContentId(req.contentId()); } t.setStatus("待审核"); return ApiResp.ok(taskRepo.save(t)); } public record ReviewRequest(String reviewNote) { } /** 审核通过:待审核 → 已发布。 */ @PostMapping("/{id}/approve") public ApiResp approve(@PathVariable Long id, @RequestBody(required = false) ReviewRequest req) { ContentTask t = find(id); if (!"待审核".equals(t.getStatus())) { throw new ApiException(409, "仅待审核可通过,当前「" + t.getStatus() + "」"); } if (req != null && req.reviewNote() != null) t.setReviewNote(req.reviewNote()); t.setStatus("已发布"); return ApiResp.ok(taskRepo.save(t)); } /** 驳回:待审核 → 已驳回(驳回后可重新提交,状态回进行中需手动再接受)。 */ @PostMapping("/{id}/reject") public ApiResp reject(@PathVariable Long id, @RequestBody(required = false) ReviewRequest req) { ContentTask t = find(id); if (!"待审核".equals(t.getStatus())) { throw new ApiException(409, "仅待审核可驳回,当前「" + t.getStatus() + "」"); } if (req != null && req.reviewNote() != null) t.setReviewNote(req.reviewNote()); t.setStatus("已驳回"); return ApiResp.ok(taskRepo.save(t)); } /** 驳回后重新接受:已驳回 → 进行中。 */ @PostMapping("/{id}/restart") public ApiResp restart(@PathVariable Long id) { ContentTask t = find(id); if (!"已驳回".equals(t.getStatus())) { throw new ApiException(409, "仅已驳回任务可重启,当前「" + t.getStatus() + "」"); } t.setStatus("进行中"); return ApiResp.ok(taskRepo.save(t)); } // ---------- 内部工具 ---------- private ContentTask find(Long id) { return taskRepo.findById(id) .orElseThrow(() -> new NotFoundException("content task not found: " + id)); } }