package com.klp.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.io.IoUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.klp.common.core.domain.PageQuery; import com.klp.common.core.domain.entity.SysUser; import com.klp.common.core.page.TableDataInfo; import com.klp.common.exception.ServiceException; import com.klp.config.StampProperties; import com.klp.domain.WmsApproval; import com.klp.domain.WmsApprovalTask; import com.klp.domain.WmsDept; import com.klp.domain.WmsSealReq; import com.klp.domain.bo.WmsSealReqBo; import com.klp.domain.bo.WmsSealStampBo; import com.klp.domain.vo.WmsSealReqVo; import com.klp.mapper.WmsApprovalMapper; import com.klp.mapper.WmsApprovalTaskMapper; import com.klp.mapper.WmsDeptMapper; import com.klp.mapper.WmsSealReqMapper; import com.klp.oss.core.OssClient; import com.klp.oss.entity.UploadResult; import com.klp.oss.factory.OssFactory; import com.klp.service.IWmsSealReqService; import com.klp.system.mapper.SysUserMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.Collection; import java.util.Date; import java.util.List; /** * 用印申请 服务实现 */ @Slf4j @RequiredArgsConstructor @Service public class WmsSealReqServiceImpl implements IWmsSealReqService { private final WmsSealReqMapper baseMapper; private final WmsDeptMapper wmsDeptMapper; private final SysUserMapper sysUserMapper; private final WmsApprovalMapper approvalMapper; private final WmsApprovalTaskMapper approvalTaskMapper; private final StampProperties stampProperties; @Override public WmsSealReqVo queryById(Long bizId) { return baseMapper.selectVoById(bizId); } @Override public TableDataInfo queryPageList(WmsSealReqBo bo, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); return TableDataInfo.build(result); } @Override public List queryList(WmsSealReqBo bo) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); return baseMapper.selectVoList(lqw); } @Override @Transactional(rollbackFor = Exception.class) public Boolean insertByBo(WmsSealReqBo bo) { WmsSealReq add = BeanUtil.toBean(bo, WmsSealReq.class); add.setStatus(defaultStatus(add.getStatus())); validEntityBeforeSave(add); boolean ok = baseMapper.insert(add) > 0; if (ok) { createDeptLeaderApproval(add, bo.getDeptId()); } return ok; } @Override @Transactional(rollbackFor = Exception.class) public Boolean updateByBo(WmsSealReqBo bo) { if (bo.getBizId() == null) { throw new ServiceException("bizId不能为空"); } WmsSealReq update = BeanUtil.toBean(bo, WmsSealReq.class); validEntityBeforeSave(update); return baseMapper.updateById(update) > 0; } @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if (isValid) { // 可添加业务校验 } return baseMapper.deleteBatchIds(ids) > 0; } @Override @Transactional(rollbackFor = Exception.class) public Boolean approveByDeptLeader(Long bizId, String approvalOpinion) { WmsSealReq req = baseMapper.selectById(bizId); if (req == null) { throw new ServiceException("用印申请不存在"); } WmsApproval approval = getApprovalByApplyId(bizId); if (approval == null) { throw new ServiceException("审批记录不存在"); } WmsApprovalTask task = getPendingTaskForCurrentLeader(approval.getApprovalId(), req.getDeptId()); if (task == null) { throw new ServiceException("当前用户不是该部门负责人或审批任务不存在"); } task.setTaskStatus("approved"); task.setApprovalOpinion(approvalOpinion); task.setApprovalTime(new Date()); approvalTaskMapper.updateById(task); approval.setApprovalStatus("已同意"); approval.setFinalStatus("all_approved"); approval.setApprovalOpinion(approvalOpinion); approval.setApprovalTime(new Date()); approvalMapper.updateById(approval); updateStatus(bizId, "approved"); return true; } @Override @Transactional(rollbackFor = Exception.class) public Boolean rejectByDeptLeader(Long bizId, String approvalOpinion) { WmsSealReq req = baseMapper.selectById(bizId); if (req == null) { throw new ServiceException("用印申请不存在"); } WmsApproval approval = getApprovalByApplyId(bizId); if (approval == null) { throw new ServiceException("审批记录不存在"); } WmsApprovalTask task = getPendingTaskForCurrentLeader(approval.getApprovalId(), req.getDeptId()); if (task == null) { throw new ServiceException("当前用户不是该部门负责人或审批任务不存在"); } task.setTaskStatus("rejected"); task.setApprovalOpinion(approvalOpinion); task.setApprovalTime(new Date()); approvalTaskMapper.updateById(task); approval.setApprovalStatus("已驳回"); approval.setFinalStatus("rejected"); approval.setApprovalOpinion(approvalOpinion); approval.setApprovalTime(new Date()); approvalMapper.updateById(approval); updateStatus(bizId, "rejected"); return true; } @Override @Transactional(rollbackFor = Exception.class) public Boolean updateStatus(Long bizId, String status) { WmsSealReq req = new WmsSealReq(); req.setBizId(bizId); req.setStatus(status); return baseMapper.updateById(req) > 0; } @Override @Transactional(rollbackFor = Exception.class) public String stampWithJava(Long bizId, WmsSealStampBo cmd) { if (!Boolean.TRUE.equals(stampProperties.getJavaService().isEnabled())) { throw new ServiceException("Java盖章未启用"); } String resultUrl = doPdfStamp(cmd); WmsSealReq update = new WmsSealReq(); update.setBizId(bizId); update.setReceiptStatus("done"); update.setReceiptFileIds(resultUrl); baseMapper.updateById(update); return resultUrl; } @Override public String stampWithPython(Long bizId, WmsSealStampBo cmd) { if (!Boolean.TRUE.equals(stampProperties.getPythonService().isEnabled())) { throw new ServiceException("Python盖章未启用"); } throw new ServiceException("Python盖章接口未实现"); } private LambdaQueryWrapper buildQueryWrapper(WmsSealReqBo bo) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(bo.getBizId() != null, WmsSealReq::getBizId, bo.getBizId()); lqw.eq(bo.getEmpId() != null, WmsSealReq::getEmpId, bo.getEmpId()); lqw.eq(bo.getSealType() != null, WmsSealReq::getSealType, bo.getSealType()); lqw.eq(bo.getStatus() != null, WmsSealReq::getStatus, bo.getStatus()); lqw.orderByDesc(WmsSealReq::getCreateTime); return lqw; } private void validEntityBeforeSave(WmsSealReq entity) { // 预留数据校验,如唯一约束 } private String defaultStatus(String status) { return status == null ? "draft" : status; } private void createDeptLeaderApproval(WmsSealReq req, Long deptId) { if (deptId == null) { return; } WmsDept dept = wmsDeptMapper.selectById(deptId); if (dept == null || dept.getLeader() == null) { return; } SysUser leader = sysUserMapper.selectById(dept.getLeader()); if (leader == null) { return; } WmsApproval approval = new WmsApproval(); approval.setApplyType("seal"); approval.setApplyId(req.getBizId()); approval.setApproverName(leader.getNickName()); approval.setApprovalStatus("待审批"); approval.setApprovalType("single"); approval.setRequiredApprovers(1); approval.setCurrentApprovers(0); approval.setFinalStatus("pending"); approval.setCreateBy(req.getCreateBy()); approval.setCreateTime(req.getCreateTime()); approvalMapper.insert(approval); WmsApprovalTask task = new WmsApprovalTask(); task.setApprovalId(approval.getApprovalId()); task.setApproverId(leader.getUserId()); task.setApproverName(leader.getNickName()); task.setTaskStatus("pending"); task.setCreateBy(req.getCreateBy()); task.setCreateTime(req.getCreateTime()); approvalTaskMapper.insert(task); updateStatus(req.getBizId(), "running"); } private WmsApproval getApprovalByApplyId(Long applyId) { return approvalMapper.selectOne(Wrappers.lambdaQuery() .eq(WmsApproval::getApplyType, "seal") .eq(WmsApproval::getApplyId, applyId) .eq(WmsApproval::getDelFlag, 0)); } private WmsApprovalTask getPendingTaskForCurrentLeader(Long approvalId, Long deptId) { if (approvalId == null || deptId == null) { return null; } WmsDept dept = wmsDeptMapper.selectById(deptId); if (dept == null || dept.getLeader() == null) { return null; } Long leaderId = dept.getLeader(); return approvalTaskMapper.selectOne(Wrappers.lambdaQuery() .eq(WmsApprovalTask::getApprovalId, approvalId) .eq(WmsApprovalTask::getApproverId, leaderId) .eq(WmsApprovalTask::getTaskStatus, "pending") .eq(WmsApprovalTask::getDelFlag, 0)); } private String doPdfStamp(WmsSealStampBo cmd) { try (InputStream pdfIn = getObject(cmd.getTargetFileUrl()); InputStream imgIn = getObject(cmd.getStampImageUrl()); PDDocument document = PDDocument.load(pdfIn); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { int pageIndex = cmd.getPageNo() - 1; if (pageIndex < 0 || pageIndex >= document.getNumberOfPages()) { throw new ServiceException("页码超出范围"); } PDPage page = document.getPage(pageIndex); PDRectangle mediaBox = page.getMediaBox(); byte[] imgBytes = IoUtil.readBytes(imgIn); PDImageXObject image = PDImageXObject.createFromByteArray(document, imgBytes, "stamp"); float stampW = image.getWidth(); float stampH = image.getHeight(); float pdfW = mediaBox.getWidth(); float pdfH = mediaBox.getHeight(); float x; float y; if (cmd.getViewportWidth() != null && cmd.getViewportHeight() != null && cmd.getViewportWidth() > 0 && cmd.getViewportHeight() > 0) { float ratioX = pdfW / cmd.getViewportWidth(); float ratioY = pdfH / cmd.getViewportHeight(); x = cmd.getXPx() * ratioX; y = cmd.getYPx() * ratioY; } else { x = cmd.getXPx(); y = cmd.getYPx(); } final float maxCm = 4.0f; final float maxPt = maxCm * 72.0f / 2.54f; float scale = Math.min(maxPt / stampW, maxPt / stampH); scale = Math.min(scale, 1.0f); float width = stampW * scale; float height = stampH * scale; log.info("[stamp] pdfW={},pdfH={}, viewportW={},viewportH={}, ratioX={},ratioY={}, xPx={},yPx={}, x={},y={}, stampW={},stampH={}, drawW={},drawH={}", pdfW, pdfH, cmd.getViewportWidth(), cmd.getViewportHeight(), (cmd.getViewportWidth() != null && cmd.getViewportWidth() > 0) ? (pdfW / cmd.getViewportWidth()) : null, (cmd.getViewportHeight() != null && cmd.getViewportHeight() > 0) ? (pdfH / cmd.getViewportHeight()) : null, cmd.getXPx(), cmd.getYPx(), x, y, stampW, stampH, width, height); if (x + width > mediaBox.getWidth()) { x = Math.max(0, mediaBox.getWidth() - width); } if (y + height > mediaBox.getHeight()) { y = Math.max(0, mediaBox.getHeight() - height); } try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) { contentStream.drawImage(image, x, y, width, height); } document.save(bos); OssClient storage = OssFactory.instance(); UploadResult uploadResult = storage.uploadSuffix(bos.toByteArray(), ".pdf", "application/pdf"); return uploadResult.getUrl(); } catch (Exception e) { log.error("PDF盖章失败", e); throw new ServiceException("PDF盖章失败: " + e.getMessage()); } } private InputStream getObject(String url) { OssClient storage = OssFactory.instance(); return storage.getObjectContent(url); } }