Files
ERP/oa-backend/src/main/java/com/kaidi/oa/web/CultureMaterialDownloadController.java
T
QiufengandClaude Opus 4.8 5e51dc3f56 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>
2026-06-15 19:19:15 +08:00

174 lines
7.4 KiB
Java

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.CultureMaterial;
import com.kaidi.oa.domain.CultureMaterialDownload;
import com.kaidi.oa.repository.CultureMaterialDownloadRepository;
import com.kaidi.oa.repository.CultureMaterialRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
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;
/**
* 员工宣传素材授权下载追踪(品牌推广部·宣传部·模块7 缺口)。
*
* 补完需求:CultureMaterial.fileUrls 原仅存 URL 字符串,无版本授权下载追踪。
* 本控制器为每次"员工申请下载"建立授权记录,宣传部可审批(已授权/已拒绝),
* 审批通过后员工确认下载时记录 downloadAt 时间戳,形成完整的分发留痕。
*
* 已通过素材(CultureMaterial.status=已通过)可直接申请,自动置为「已授权」;
* 受控素材(如草稿/退回)需人工审批,防止未定稿素材外流。
*/
@RestController
@RequestMapping("/api/oa/culture-material-downloads")
public class CultureMaterialDownloadController {
private final CultureMaterialDownloadRepository dlRepo;
private final CultureMaterialRepository matRepo;
public CultureMaterialDownloadController(CultureMaterialDownloadRepository dlRepo,
CultureMaterialRepository matRepo) {
this.dlRepo = dlRepo;
this.matRepo = matRepo;
}
@GetMapping
public ApiResp<List<CultureMaterialDownload>> list(
@RequestParam(required = false) Long materialId,
@RequestParam(required = false) String downloader,
@RequestParam(required = false) String authStatus) {
if (materialId != null) {
return ApiResp.ok(dlRepo.findByMaterialId(materialId));
}
if (downloader != null && !downloader.isBlank()) {
return ApiResp.ok(dlRepo.findByDownloader(downloader));
}
if (authStatus != null && !authStatus.isBlank()) {
return ApiResp.ok(dlRepo.findByAuthStatus(authStatus));
}
return ApiResp.ok(dlRepo.findAll());
}
@GetMapping("/{id}")
public ApiResp<CultureMaterialDownload> get(@PathVariable Long id) {
return ApiResp.ok(require(id));
}
public record DownloadRequest(
Long materialId, String downloader, String downloaderDept,
String purpose, String version) {
}
/**
* 申请下载素材。
* - 素材为「已通过」状态时,自动置「已授权」,员工直接可下载;
* - 其他状态需宣传部手工授权(置「申请中」等待审批)。
*/
@PostMapping
@Transactional
public ApiResp<CultureMaterialDownload> apply(@RequestBody DownloadRequest req) {
if (req.materialId() == null) {
throw new ApiException(400, "materialId 不能为空");
}
if (req.downloader() == null || req.downloader().isBlank()) {
throw new ApiException(400, "申请人(downloader) 不能为空");
}
CultureMaterial mat = matRepo.findById(req.materialId())
.orElseThrow(() -> new NotFoundException("素材不存在:" + req.materialId()));
CultureMaterialDownload dl = new CultureMaterialDownload();
dl.setMaterialId(req.materialId());
dl.setMaterialTitle(mat.getTitle());
dl.setDownloader(req.downloader());
dl.setDownloaderDept(req.downloaderDept());
dl.setPurpose(req.purpose() == null ? "内部展示" : req.purpose());
dl.setVersion(req.version() == null ? "v1.0" : req.version());
dl.setCreatedAt(Instant.now());
// 已通过素材自动授权;否则人工审批
if ("已通过".equals(mat.getStatus())) {
dl.setAuthStatus("已授权");
dl.setAuthorizedAt(Instant.now());
dl.setAuthorizedBy("系统自动");
} else {
dl.setAuthStatus("申请中");
}
return ApiResp.ok(dlRepo.save(dl));
}
public record AuthRequest(String authorizedBy, String authComment, boolean approved) {
}
/** 宣传部审批:申请中 → 已授权 / 已拒绝。 */
@PostMapping("/{id}/authorize")
@Transactional
public ApiResp<CultureMaterialDownload> authorize(@PathVariable Long id,
@RequestBody AuthRequest req) {
CultureMaterialDownload dl = require(id);
if (!"申请中".equals(dl.getAuthStatus())) {
throw new ApiException(409, "仅「申请中」可审批,当前「" + dl.getAuthStatus() + "」");
}
if (req.authorizedBy() == null || req.authorizedBy().isBlank()) {
throw new ApiException(400, "授权人(authorizedBy) 不能为空");
}
dl.setAuthorizedBy(req.authorizedBy());
dl.setAuthComment(req.authComment());
dl.setAuthStatus(req.approved() ? "已授权" : "已拒绝");
if (req.approved()) {
dl.setAuthorizedAt(Instant.now());
}
return ApiResp.ok(dlRepo.save(dl));
}
/** 确认下载:已授权 → 记录 downloadAt 时间戳(员工实际点击下载时调用)。 */
@PostMapping("/{id}/confirm-download")
@Transactional
public ApiResp<CultureMaterialDownload> confirmDownload(@PathVariable Long id) {
CultureMaterialDownload dl = require(id);
if (!"已授权".equals(dl.getAuthStatus())) {
throw new ApiException(409, "仅已授权记录可确认下载,当前「" + dl.getAuthStatus() + "」");
}
if (dl.getDownloadAt() != null) {
throw new ApiException(409, "该授权记录已确认下载,请勿重复");
}
dl.setDownloadAt(Instant.now());
return ApiResp.ok(dlRepo.save(dl));
}
/** 素材下载统计(按素材汇总申请量/已授权量/实际下载量)。 */
public record DownloadStat(Long materialId, String materialTitle,
long applied, long authorized, long downloaded) {
}
@GetMapping("/stats")
public ApiResp<List<DownloadStat>> stats() {
List<CultureMaterialDownload> all = dlRepo.findAll();
java.util.Map<Long, DownloadStat> m = new java.util.LinkedHashMap<>();
for (CultureMaterialDownload dl : all) {
Long mid = dl.getMaterialId();
DownloadStat prev = m.getOrDefault(mid,
new DownloadStat(mid, dl.getMaterialTitle(), 0, 0, 0));
long app = prev.applied() + 1;
long auth = prev.authorized() + ("已授权".equals(dl.getAuthStatus()) ? 1 : 0);
long done = prev.downloaded() + (dl.getDownloadAt() != null ? 1 : 0);
m.put(mid, new DownloadStat(mid, dl.getMaterialTitle(), app, auth, done));
}
return ApiResp.ok(new java.util.ArrayList<>(m.values()));
}
private CultureMaterialDownload require(Long id) {
return dlRepo.findById(id)
.orElseThrow(() -> new NotFoundException("下载记录不存在:" + id));
}
}