SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。 == 此快照内容 == - 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091) - 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static) - 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB) - 交接文档 go.md + go-code-reference/endpoints/entities/database.md - 多代理建设脚本 .claude/wf-*.js == 状态 == - 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板) - 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用 - W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成) - 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456 == 排除(gitignore, 可再生) == node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui) 完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules) 时间戳: 20260615-191517 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
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<ContentTask>> 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<ContentTask> 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<ContentTask> 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<ContentTask> 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<Void> 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<ContentTask> 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<ContentTask> 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<ContentTask> 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<ContentTask> 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<ContentTask> 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user