feat:慰问打印去掉二维码,加上流水号(按年月日编号自动编号,例如20261009001,后三位“001”按申请时间自动递增)
This commit is contained in:
+33
-80
@@ -7,6 +7,8 @@ import com.budwk.app.base.constant.RoleConstant;
|
|||||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
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.exception.BaseException;
|
||||||
import com.budwk.app.base.utils.MoneyUtil;
|
import com.budwk.app.base.utils.MoneyUtil;
|
||||||
import com.budwk.app.base.utils.OfficePlusUtil;
|
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.XWPFTemplate;
|
||||||
import com.deepoove.poi.config.Configure;
|
import com.deepoove.poi.config.Configure;
|
||||||
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
@@ -139,7 +127,7 @@ public class UnionReimburseExportServiceImpl implements UnionReimburseExportServ
|
|||||||
return CONDOLENCE_TEMPLATE;
|
return CONDOLENCE_TEMPLATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 两个入口按慰问类型选模板并共用字段和循环行配置;汇总打印额外补充编号二维码。 */
|
/** 两个入口按慰问类型选模板并共用流水号字段和循环行配置,不再生成二维码。 */
|
||||||
private byte[] renderDocument(UnionReimburse form, boolean asPdf, boolean collectPrint) {
|
private byte[] renderDocument(UnionReimburse form, boolean asPdf, boolean collectPrint) {
|
||||||
String templateCode = resolveTemplateCode(form, collectPrint);
|
String templateCode = resolveTemplateCode(form, collectPrint);
|
||||||
Path docx = null;
|
Path docx = null;
|
||||||
@@ -157,9 +145,6 @@ public class UnionReimburseExportServiceImpl implements UnionReimburseExportServ
|
|||||||
XWPFTemplate template = XWPFTemplate.compile(input, config);
|
XWPFTemplate template = XWPFTemplate.compile(input, config);
|
||||||
OutputStream output = Files.newOutputStream(docx)) {
|
OutputStream output = Files.newOutputStream(docx)) {
|
||||||
Map<String, Object> data = buildExportData(form);
|
Map<String, Object> data = buildExportData(form);
|
||||||
if (collectPrint) {
|
|
||||||
prepareCollectQrCode(template, data, form.getDocumentNo());
|
|
||||||
}
|
|
||||||
template.render(data).write(output);
|
template.render(data).write(output);
|
||||||
}
|
}
|
||||||
if (!asPdf) {
|
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<String, Object> data,
|
private String buildPrintSerialNo(UnionReimburse form) {
|
||||||
String documentNo) throws Exception {
|
if (form.getCreateTime() == null || StrUtil.isBlank(form.getId())) {
|
||||||
if (StrUtil.isBlank(documentNo)) {
|
throw new BaseException("报销申请时间或单据ID为空,无法生成打印流水号");
|
||||||
throw new BaseException("报销单编号为空,无法生成打印二维码,请先核对单据编号");
|
|
||||||
}
|
}
|
||||||
var matrix = new QRCodeWriter().encode(documentNo, BarcodeFormat.QR_CODE, 80, 80);
|
Date startTime = DateUtil.beginOfDay(form.getCreateTime());
|
||||||
var image = MatrixToImageWriter.toBufferedImage(matrix);
|
// 先对当日全部申请排序,再读取目标申请,避免过滤ID后序号始终为1。
|
||||||
data.put("qrcode", Pictures.ofBufferedImage(image, PictureType.PNG).size(80, 80).create());
|
Sql sql = Sqls.create("""
|
||||||
boolean hasTag = template.getElementTemplates().stream()
|
SELECT numbered.seq
|
||||||
.anyMatch(item -> item instanceof ElementTemplate
|
FROM (
|
||||||
&& "qrcode".equals(((ElementTemplate) item).getTagName()));
|
SELECT id, ROW_NUMBER() OVER (ORDER BY createTime, createdAt, id) AS seq
|
||||||
if (hasTag) {
|
FROM union_reimburse
|
||||||
return;
|
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();
|
return DateUtil.format(form.getCreateTime(), "yyyyMMdd") + String.format(Locale.ROOT, "%03d", sequence);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 平铺字段供两类模板复用,支付总额与发票总额分别计算,不使用第一条支付代替全部明细。 */
|
/** 平铺字段供两类模板复用,支付总额与发票总额分别计算,不使用第一条支付代替全部明细。 */
|
||||||
@@ -266,6 +218,7 @@ public class UnionReimburseExportServiceImpl implements UnionReimburseExportServ
|
|||||||
Map<String, Object> data = new LinkedHashMap<>();
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
data.put("date", date(form.getCreateTime()));
|
data.put("date", date(form.getCreateTime()));
|
||||||
data.put("no", text(form.getDocumentNo()));
|
data.put("no", text(form.getDocumentNo()));
|
||||||
|
data.put("serialNo", buildPrintSerialNo(form));
|
||||||
String unit = text(form.getUnitName());
|
String unit = text(form.getUnitName());
|
||||||
data.put("union", text(form.getUnionName()) + (unit.isEmpty() ? "" : "(" + unit + ")"));
|
data.put("union", text(form.getUnionName()) + (unit.isEmpty() ? "" : "(" + unit + ")"));
|
||||||
data.put("handler", text(form.getUserName()));
|
data.put("handler", text(form.getUserName()));
|
||||||
|
|||||||
Reference in New Issue
Block a user