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( @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 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 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 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 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> stats() { List all = dlRepo.findAll(); java.util.Map 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)); } }