From 4ea3ff8ac4feac9a20ccb490d75c5f373ae5d0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E8=AF=9A?= <1009578407@qq.com> Date: Fri, 16 Oct 2026 09:00:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=85=B0=E9=97=AE=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E4=BA=8C=E7=BB=B4=E7=A0=81=EF=BC=8C=E5=8A=A0?= =?UTF-8?q?=E4=B8=8A=E6=B5=81=E6=B0=B4=E5=8F=B7=EF=BC=88=E6=8C=89=E5=B9=B4?= =?UTF-8?q?=E6=9C=88=E6=97=A5=E7=BC=96=E5=8F=B7=E8=87=AA=E5=8A=A8=E7=BC=96?= =?UTF-8?q?=E5=8F=B7=EF=BC=8C=E4=BE=8B=E5=A6=8220261009001=EF=BC=8C?= =?UTF-8?q?=E5=90=8E=E4=B8=89=E4=BD=8D=E2=80=9C001=E2=80=9D=E6=8C=89?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E6=97=B6=E9=97=B4=E8=87=AA=E5=8A=A8=E9=80=92?= =?UTF-8?q?=E5=A2=9E=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/UnionReimburseExportServiceImpl.java | 113 +++++------------- 1 file changed, 33 insertions(+), 80 deletions(-) diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseExportServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseExportServiceImpl.java index 65440ee1..67028ca8 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseExportServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseExportServiceImpl.java @@ -7,6 +7,8 @@ import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.web.commons.auth.utils.AuthUtil; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.utils.MoneyUtil; import com.budwk.app.base.utils.OfficePlusUtil; @@ -20,20 +22,6 @@ import com.budwk.app.zhgh.staffbenefit.condolence.model.CondolenceType; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; -import com.deepoove.poi.data.PictureType; -import com.deepoove.poi.data.Pictures; -import com.deepoove.poi.template.ElementTemplate; -import com.google.zxing.*; -import com.google.zxing.client.j2se.BufferedImageLuminanceSource; -import com.google.zxing.client.j2se.MatrixToImageWriter; -import com.google.zxing.common.HybridBinarizer; -import com.google.zxing.qrcode.QRCodeWriter; -import com.google.zxing.qrcode.QRCodeReader; -import org.apache.poi.xwpf.usermodel.*; -import org.apache.poi.util.Units; -import javax.imageio.ImageIO; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import lombok.extern.slf4j.Slf4j; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -139,7 +127,7 @@ public class UnionReimburseExportServiceImpl implements UnionReimburseExportServ return CONDOLENCE_TEMPLATE; } - /** 两个入口按慰问类型选模板并共用字段和循环行配置;汇总打印额外补充编号二维码。 */ + /** 两个入口按慰问类型选模板并共用流水号字段和循环行配置,不再生成二维码。 */ private byte[] renderDocument(UnionReimburse form, boolean asPdf, boolean collectPrint) { String templateCode = resolveTemplateCode(form, collectPrint); Path docx = null; @@ -157,9 +145,6 @@ public class UnionReimburseExportServiceImpl implements UnionReimburseExportServ XWPFTemplate template = XWPFTemplate.compile(input, config); OutputStream output = Files.newOutputStream(docx)) { Map data = buildExportData(form); - if (collectPrint) { - prepareCollectQrCode(template, data, form.getDocumentNo()); - } template.render(data).write(output); } if (!asPdf) { @@ -193,71 +178,38 @@ public class UnionReimburseExportServiceImpl implements UnionReimburseExportServ } /** - * 汇总打印沿用老系统的单据编号二维码,仅修改本次生成的文档,不覆盖模板。 - * @param template 当前已编译模板,优先使用 qrcode 图片标记,其次替换标题区的固定二维码 - * @param data 本次渲染数据,qrcode 字段为 80×80 的 PNG 图片对象 - * @param documentNo 原始单据编号,缺失时拒绝打印,避免用业务 ID 冒充编号 + * 按申请日期和当日申请时间顺序生成打印流水号,六类模板使用同一编号口径。 + * 历史单据也按申请时间参与排序,同一秒内用创建时间及主键确定顺序; + * 不按打印顺序编号,也不按当前用户或报销状态过滤申请。 + * + * @param form 已通过导出权限校验的报销申请 + * @return 年月日加至少三位当日序号,例如 20261009001 */ - private void prepareCollectQrCode(XWPFTemplate template, Map data, - String documentNo) throws Exception { - if (StrUtil.isBlank(documentNo)) { - throw new BaseException("报销单编号为空,无法生成打印二维码,请先核对单据编号"); + private String buildPrintSerialNo(UnionReimburse form) { + if (form.getCreateTime() == null || StrUtil.isBlank(form.getId())) { + throw new BaseException("报销申请时间或单据ID为空,无法生成打印流水号"); } - var matrix = new QRCodeWriter().encode(documentNo, BarcodeFormat.QR_CODE, 80, 80); - var image = MatrixToImageWriter.toBufferedImage(matrix); - data.put("qrcode", Pictures.ofBufferedImage(image, PictureType.PNG).size(80, 80).create()); - boolean hasTag = template.getElementTemplates().stream() - .anyMatch(item -> item instanceof ElementTemplate - && "qrcode".equals(((ElementTemplate) item).getTagName())); - if (hasTag) { - return; + Date startTime = DateUtil.beginOfDay(form.getCreateTime()); + // 先对当日全部申请排序,再读取目标申请,避免过滤ID后序号始终为1。 + Sql sql = Sqls.create(""" + SELECT numbered.seq + FROM ( + SELECT id, ROW_NUMBER() OVER (ORDER BY createTime, createdAt, id) AS seq + FROM union_reimburse + WHERE createTime >= @startTime AND createTime < @endTime + ) numbered + WHERE numbered.id = @id + """); + sql.setParam("startTime", startTime); + sql.setParam("endTime", DateUtil.offsetDay(startTime, 1)); + sql.setParam("id", form.getId()); + sql.setCallback(Sqls.callback.integer()); + dao.execute(sql); + int sequence = sql.getInt(); + if (sequence < 1) { + throw new BaseException("未找到报销申请,无法生成打印流水号"); } - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - ImageIO.write(image, "png", bytes); - XWPFDocument document = template.getXWPFDocument(); - // 部分现用模板内嵌了固定二维码。仅识别第一张业务表格之前的二维码,保留徽标等普通图片。 - boolean replaced = false; - for (IBodyElement element : document.getBodyElements()) { - if (element instanceof XWPFTable) break; - if (!(element instanceof XWPFParagraph)) continue; - for (XWPFRun run : ((XWPFParagraph) element).getRuns()) { - for (XWPFPicture picture : run.getEmbeddedPictures()) { - if (picture.getPictureData() == null) continue; - var existing = ImageIO.read(new ByteArrayInputStream(picture.getPictureData().getData())); - if (existing == null) continue; - try { - new QRCodeReader().decode(new BinaryBitmap(new HybridBinarizer( - new BufferedImageLuminanceSource(existing)))); - } catch (NotFoundException | ChecksumException | FormatException e) { - continue; - } - String relationship = document.addPictureData(bytes.toByteArray(), Document.PICTURE_TYPE_PNG); - picture.getCTPicture().getBlipFill().getBlip().setEmbed(relationship); - replaced = true; - } - } - } - if (replaced) return; - // 无二维码标记或固定二维码的模板,在正文开头添加右对齐图片,不依赖模板重新上传。 - XWPFParagraph paragraph; - if (document.getBodyElements().isEmpty()) { - paragraph = document.createParagraph(); - } else { - IBodyElement first = document.getBodyElements().get(0); - var cursor = first instanceof XWPFParagraph - ? ((XWPFParagraph) first).getCTP().newCursor() - : ((XWPFTable) first).getCTTbl().newCursor(); - try { - paragraph = document.insertNewParagraph(cursor); - } finally { - cursor.dispose(); - } - } - paragraph.setAlignment(ParagraphAlignment.RIGHT); - paragraph.setSpacingBefore(0); - paragraph.setSpacingAfter(0); - paragraph.createRun().addPicture(new ByteArrayInputStream(bytes.toByteArray()), - Document.PICTURE_TYPE_PNG, "qrcode.png", Units.pixelToEMU(80), Units.pixelToEMU(80)); + return DateUtil.format(form.getCreateTime(), "yyyyMMdd") + String.format(Locale.ROOT, "%03d", sequence); } /** 平铺字段供两类模板复用,支付总额与发票总额分别计算,不使用第一条支付代替全部明细。 */ @@ -266,6 +218,7 @@ public class UnionReimburseExportServiceImpl implements UnionReimburseExportServ Map data = new LinkedHashMap<>(); data.put("date", date(form.getCreateTime())); data.put("no", text(form.getDocumentNo())); + data.put("serialNo", buildPrintSerialNo(form)); String unit = text(form.getUnitName()); data.put("union", text(form.getUnionName()) + (unit.isEmpty() ? "" : "(" + unit + ")")); data.put("handler", text(form.getUserName()));