gaoluyang
5 天以前 3fd8d1f2e73a843acd330e8e8b84f5800f858407
feat(seal): 印章详情视图与编辑删除接口

- 印章详情返回视图数据供前端展示
- 补充印章详情视图 SQL

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加1个文件
已修改2个文件
83 ■■■■■ 文件已修改
sql/mysql/20261109_pd_seal_detail_view.sql 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/seal/PdSealController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/seal/PdSealService.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/20261109_pd_seal_detail_view.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
-- ç”¨å°å®¡æ‰¹è¯¦æƒ…:展示使用的印章信息,并支持预览印章图片
--
-- æœºåˆ¶ï¼šbpm_business_attach æŠŠã€Œæµç¨‹ Key」映射到业务详情组件,
--       å®¡æ‰¹è¯¦æƒ…页在 processDefinition ä¸ºæ™®é€šè¡¨å•且带 businessKey æ—¶è‡ªåŠ¨æ”¹ä¸º CUSTOM,
--       æ¸²æŸ“ component_path æŒ‡å‘的组件,并把 businessKey(用印申请 id)作为 id ä¼ å…¥ã€‚
-- æœ¬è„šæœ¬å¹‚ç­‰ï¼Œå¯é‡å¤æ‰§è¡Œã€‚
INSERT INTO bpm_business_attach (process_definition_key, business_type, component_path, status, remark, creator, create_time, updater, update_time, deleted)
SELECT 'hrm-seal-use-approval', 'pd_seal_application', '/mes/pd/seal/detail/approve/index.vue', 0,
       '用印审批展示用印申请信息与印章图片', '1', NOW(), '1', NOW(), b'0'
WHERE NOT EXISTS (
    SELECT 1 FROM bpm_business_attach WHERE process_definition_key = 'hrm-seal-use-approval' AND deleted = b'0'
);
UPDATE bpm_business_attach
SET component_path = '/mes/pd/seal/detail/approve/index.vue',
    business_type = 'pd_seal_application',
    status = 0,
    updater = '1',
    update_time = NOW()
WHERE process_definition_key = 'hrm-seal-use-approval'
  AND deleted = b'0';
-- æ ¡éªŒï¼šåº”返回 1 è¡Œï¼Œstatus=0(启用)
SELECT id, process_definition_key, component_path, status
FROM bpm_business_attach
WHERE process_definition_key = 'hrm-seal-use-approval' AND deleted = b'0';
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/seal/PdSealController.java
@@ -48,6 +48,17 @@
    }
    @GetMapping("/applications") @PreAuthorize("@ss.hasPermission('mes:pd-project:export')")
    public CommonResult<List<Map<String,Object>>> applications(@RequestParam(required=false) Long projectId){ return success(service.applications(getLoginUserId(),projectId)); }
    /** å®¡æ‰¹è¯¦æƒ…使用:登录即可访问,可见范围在 Service å†…按申请人/审批人/管理权限校验 */
    @GetMapping("/application")
    public CommonResult<Map<String,Object>> application(@RequestParam long id){ return success(service.applicationDetail(id,getLoginUserId())); }
    @GetMapping("/application/image")
    public void applicationImage(@RequestParam long id,HttpServletResponse response) throws java.io.IOException {
        byte[] image=service.applicationImage(id,getLoginUserId());
        response.setContentType("image/png");
        response.setHeader("Cache-Control","no-store, private");
        response.setHeader("X-Content-Type-Options","nosniff");
        response.getOutputStream().write(image);
    }
    @GetMapping("/download") @PreAuthorize("@ss.hasPermission('mes:pd-project:export')")
    public void download(@RequestParam long id,HttpServletResponse response) throws Exception {byte[] bytes=service.download(id,getLoginUserId()); response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); response.setHeader("Content-Disposition","attachment; filename=tracking.docx"); response.getOutputStream().write(bytes);}
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/seal/PdSealService.java
@@ -4,6 +4,7 @@
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
@@ -24,6 +25,7 @@
    @Resource private BpmProcessInstanceApi bpm;
    @Resource private cn.iocoder.yudao.module.bpm.service.definition.BpmProcessAutoApproveService autoApprove;
    @Resource private cn.iocoder.yudao.module.bpm.service.task.BpmTaskService taskService;
    @Resource private SecurityFrameworkService security;
    private long tenant() { Long id = cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder.getTenantId(); return id == null ? 0L : id; }
    private ServiceException fail(String text) { return new ServiceException(1_040_990_001, text); }
@@ -103,6 +105,49 @@
    public List<Map<String,Object>> applications(long userId, Long projectId) {
        return jdbc.queryForList("SELECT id,project_id,project_code,seal_name,reason,status,process_instance_id,create_time,finish_time FROM pd_seal_application WHERE tenant_id=? AND applicant_id=?"+(projectId == null ? "" : " AND project_id="+projectId)+" ORDER BY id DESC LIMIT 200",tenant(),userId);
    }
    /**
     * å®¡æ‰¹è¯¦æƒ…:查询用印申请的业务信息(含使用的印章名称与申请时间等)
     *
     * å¯è§èŒƒå›´ï¼šç”³è¯·äººæœ¬äººã€è¯¥æµç¨‹å®žä¾‹çš„审批人(含已办)、以及具备用印/项目/流程查询权限的管理人员。
     */
    public Map<String,Object> applicationDetail(long id, long userId) {
        var rows = jdbc.queryForList("SELECT a.id,a.project_id,COALESCE(a.project_code,p.task_code) project_code,p.task_name project_name,"
                + "a.seal_id,a.seal_name,a.reason,a.status,a.process_instance_id,a.applicant_id,"
                + "u.nickname applicant_name,a.create_time,a.finish_time "
                + "FROM pd_seal_application a "
                + "LEFT JOIN mes_pd_project p ON p.id=a.project_id AND p.tenant_id=a.tenant_id "
                + "LEFT JOIN system_users u ON u.id=a.applicant_id "
                + "WHERE a.id=? AND a.tenant_id=?", id, tenant());
        if (rows.isEmpty()) throw fail("用印申请不存在");
        var row = rows.get(0);
        if (!canViewApplication(row, userId)) throw fail("无权查看该用印申请,仅申请人、本流程审批人和用印管理人员可查看");
        return row;
    }
    /**
     * å®¡æ‰¹è¯¦æƒ…:获取申请时使用的印章图片(申请提交时已快照,后续更换印章图片不影响历史记录)
     */
    public byte[] applicationImage(long id, long userId) {
        var rows = jdbc.queryForList("SELECT seal_image,applicant_id,process_instance_id FROM pd_seal_application WHERE id=? AND tenant_id=?", id, tenant());
        if (rows.isEmpty()) throw fail("用印申请不存在");
        var row = rows.get(0);
        if (!canViewApplication(row, userId)) throw fail("无权查看该用印申请,仅申请人、本流程审批人和用印管理人员可查看");
        return (byte[]) row.get("seal_image");
    }
    private boolean canViewApplication(Map<String,Object> row, long userId) {
        Object applicant = row.get("applicant_id");
        if (applicant instanceof Number number && number.longValue() == userId) return true;
        // ç”¨å°ç”³è¯·äºº/审批人之外的可见范围:项目、印章、流程/待办的管理与审批权限
        if (security.hasAnyPermissions("mes:pd-project:export", "mes:pd-seal:manage",
                "bpm:process-instance:query", "bpm:process-instance:manager-query",
                "bpm:task:update", "bpm:task:manager-query")) return true;
        Object instance = row.get("process_instance_id");
        if (instance == null || instance.toString().isBlank()) return false;
        return taskService.getTaskListByProcessInstanceId(instance.toString(), true).stream()
                .anyMatch(task -> task.getAssignee() != null && String.valueOf(userId).equals(task.getAssignee()));
    }
    @Transactional(rollbackFor=Exception.class)
    public byte[] download(long id,long userId) throws IOException {
        var rows=jdbc.queryForList("SELECT document_snapshot,seal_image,process_instance_id FROM pd_seal_application WHERE id=? AND tenant_id=? AND applicant_id=? AND status=2",id,tenant(),userId);