feat:打印发票修改发票样式
feat:把hard的定时任务迁移到定时任务服务
This commit is contained in:
1 parent
dac1538022
commit
8b6f20f6bc
25 files changed
+1082
-409
No files matched your search
+90
@@ -0,0 +1,90 @@
|
||||
package com.bsd.pjcloud.hard.api.feign;
|
||||
|
||||
import com.bsd.pjcloud.common.core.constant.ServiceNameConstants;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.feign.annotation.NoToken;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
/**
|
||||
* daemon-quartz 定时触发 Hardware 各补偿任务的内部 Feign 契约。
|
||||
*
|
||||
* <p>Hardware 原本地 {@code @Scheduled} 任务已全部迁移到 daemon-quartz 统一调度,本契约对应
|
||||
* HardScheduleInnerController 暴露的内部端点;各任务内部由分布式订单锁、任务行锁、
|
||||
* Outbox 条件更新或轮询协调器退避门控保证重复触发幂等安全。</p>
|
||||
*/
|
||||
@FeignClient(contextId = "remoteHardScheduleService", value = ServiceNameConstants.HARDWARE_SERVICE)
|
||||
public interface RemoteHardScheduleService {
|
||||
|
||||
/**
|
||||
* 高频补偿最近十五分钟内仍处于创建状态的聚合支付订单;空闲时由轮询协调器退避门控直接空转返回。
|
||||
*
|
||||
* @return 固定返回成功标记,本轮是否实际扫描由 Hardware 实例内协调器决定
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/aggregate-pay/settle-pending")
|
||||
R<Void> settlePendingAggregateOrders();
|
||||
|
||||
/**
|
||||
* 低频核对十五分钟至一小时内的遗留聚合支付订单,并原子取消超过一小时的订单。
|
||||
*
|
||||
* @return 固定返回成功标记,逐单结果由订单锁与状态条件更新保证
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/aggregate-pay/settle-stale")
|
||||
R<Void> settleStaleAggregateOrders();
|
||||
|
||||
/**
|
||||
* 扫描到期的自动退款任务并按任务租户恢复上下文后执行。
|
||||
*
|
||||
* @return 本轮扫描到并尝试执行的任务数量
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/auto-refund/retry-due")
|
||||
R<Integer> retryDueAutoRefundTasks();
|
||||
|
||||
/**
|
||||
* 重试通知失败的咖啡机支付回调与退款同步缺口。
|
||||
*
|
||||
* @return 固定返回成功标记,失败订单按通知次数上限熔断
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/coffee-notify/retry-failures")
|
||||
R<Void> retryCoffeeNotifyFailures();
|
||||
|
||||
/**
|
||||
* 扫描到期的核销消息 Outbox 事件并投递,条件更新领取保证重复触发幂等。
|
||||
*
|
||||
* @return 本轮处理的事件数量
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/outbox/process-due")
|
||||
R<Integer> processOutboxDueEvents();
|
||||
|
||||
/**
|
||||
* 分批清理超过保留期的终态核销消息 Outbox。
|
||||
*
|
||||
* @return 本轮清理的事件数量
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/outbox/cleanup")
|
||||
R<Integer> cleanupOutboxExpiredEvents();
|
||||
|
||||
/**
|
||||
* 扫描待生成的持久化打印任务并补偿下发待打印任务。
|
||||
*
|
||||
* @return 本轮处理的打印任务数量
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/print/process-due")
|
||||
R<Integer> processPrintDueTasks();
|
||||
|
||||
/**
|
||||
* 清理创建时间超过三十天的消费机日志,低于阈值时不执行删除。
|
||||
*
|
||||
* @return 本轮删除的日志行数,未达阈值时为零
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/hard-schedule/inner/xfj-log/cleanup")
|
||||
R<Integer> cleanupXfjLog();
|
||||
}
|
||||
+15
-6
@@ -175,9 +175,9 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
* 渲染兑换券商品卡片,数量为整数份数。
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand voucherProduct(String name, int quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems) {
|
||||
return voucherProduct(name, "x" + Math.max(0, quantity), unitPrice, subtotal, combo, componentItems);
|
||||
public EscPosReceiptCommand voucherProduct(String name, String subName, int quantity, String unitPrice,
|
||||
String subtotal, boolean combo, List<String> componentItems) {
|
||||
return voucherProduct(name, subName, "x" + Math.max(0, quantity), unitPrice, subtotal, combo, componentItems);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,12 +185,15 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
*
|
||||
* <p>与图片版卡片逐项对应:框线对应描边矩形;组合商品标签为 GS B 反色黑底(对应图片
|
||||
* fillRect 黑底白字),单品标签为制表符描边小框(对应图片 drawRect 描边框);卡片内部
|
||||
* 压缩行距让左右竖线行间衔接不断线,各区块间距按图片版像素值(1px≈1 点)走纸。</p>
|
||||
* 压缩行距让左右竖线行间衔接不断线,各区块间距按图片版像素值(1px≈1 点)走纸。
|
||||
* subName 非空时在倍宽主名后输出单倍宽副行,对应图片版主名下方的小字号商品名。</p>
|
||||
*/
|
||||
@Override
|
||||
public EscPosReceiptCommand voucherProduct(String name, String quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems) {
|
||||
public EscPosReceiptCommand voucherProduct(String name, String subName, String quantity, String unitPrice,
|
||||
String subtotal, boolean combo, List<String> componentItems) {
|
||||
String safeName = name == null || name.isBlank() ? "未命名商品" : name;
|
||||
String safeSubName = subName == null ? null : subName.trim();
|
||||
if (safeSubName != null && safeSubName.isBlank()) safeSubName = null;
|
||||
String priceText = "单价 ¥" + (unitPrice == null || unitPrice.isBlank() ? "--" : unitPrice)
|
||||
+ " " + (quantity == null || quantity.isBlank() ? "x0" : quantity);
|
||||
String subtotalText = "小计 ¥" + (subtotal == null || subtotal.isBlank() ? "--" : subtotal);
|
||||
@@ -208,6 +211,12 @@ public class EscPosReceiptCommand implements ReceiptRenderer {
|
||||
for (String nameLine : wrapByWidth(safeName, scaledInner)) {
|
||||
printScaledNameInBox(nameLine, scaledInner - displayWidth(nameLine));
|
||||
}
|
||||
// 副行商品名:单倍宽不加粗,对应图片版小字号,ESC/POS 无缩小字号指令故与价格行同档。
|
||||
if (safeSubName != null) {
|
||||
for (String subLine : wrapByWidth(" " + safeSubName, BOX_INNER_WIDTH)) {
|
||||
boxLine(subLine, false);
|
||||
}
|
||||
}
|
||||
boxLineTwoColumns(priceText, subtotalText, false);
|
||||
|
||||
List<String> safeComponents = componentItems == null ? List.of() : componentItems.stream()
|
||||
|
||||
+6
-4
@@ -92,7 +92,8 @@ public interface ReceiptRenderer {
|
||||
/**
|
||||
* 渲染兑换券商品卡片,数量为整数份数。
|
||||
*
|
||||
* @param name 商品快照名称
|
||||
* @param name 主商品名(套餐名优先);空值由实现兜底
|
||||
* @param subName 主名下方的副行名称(商品名);null 或空白时不渲染副行
|
||||
* @param quantity 剩余可履约数量
|
||||
* @param unitPrice 商品快照单价,单位元且不含货币符号
|
||||
* @param subtotal 剩余可履约数量对应的小计,单位元且不含货币符号
|
||||
@@ -100,13 +101,14 @@ public interface ReceiptRenderer {
|
||||
* @param componentItems 组合商品组成说明,每项应已包含对应数量;可为空
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer voucherProduct(String name, int quantity, String unitPrice, String subtotal,
|
||||
ReceiptRenderer voucherProduct(String name, String subName, int quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems);
|
||||
|
||||
/**
|
||||
* 渲染兑换券商品卡片,数量为已格式化文本(份数“x2”或称重“0.5/kg”)。
|
||||
*
|
||||
* @param name 商品快照名称
|
||||
* @param name 主商品名(套餐名优先);空值由实现兜底
|
||||
* @param subName 主名下方的副行名称(商品名);null 或空白时不渲染副行
|
||||
* @param quantity 数量展示文本
|
||||
* @param unitPrice 商品快照单价,单位元且不含货币符号
|
||||
* @param subtotal 商品小计,单位元且不含货币符号
|
||||
@@ -114,7 +116,7 @@ public interface ReceiptRenderer {
|
||||
* @param componentItems 组合商品组成说明,每项应已包含对应数量;可为空
|
||||
* @return 当前渲染器
|
||||
*/
|
||||
ReceiptRenderer voucherProduct(String name, String quantity, String unitPrice, String subtotal,
|
||||
ReceiptRenderer voucherProduct(String name, String subName, String quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems);
|
||||
|
||||
/**
|
||||
|
||||
+24
-8
@@ -292,17 +292,19 @@ public class ThermalReceiptImage implements ReceiptRenderer {
|
||||
* @param componentItems 组合商品组成说明,每项应已包含对应数量;单品或无组成时可为空
|
||||
* @return 当前绘制器
|
||||
*/
|
||||
public ThermalReceiptImage voucherProduct(String name, int quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems) {
|
||||
return voucherProduct(name, "x" + Math.max(0, quantity), unitPrice, subtotal, combo, componentItems);
|
||||
public ThermalReceiptImage voucherProduct(String name, String subName, int quantity, String unitPrice,
|
||||
String subtotal, boolean combo, List<String> componentItems) {
|
||||
return voucherProduct(name, subName, "x" + Math.max(0, quantity), unitPrice, subtotal, combo, componentItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制兑换券商品卡片,数量为已格式化文本,供兑换券与发票共用同一卡片版式。
|
||||
*
|
||||
* <p>数量文本由调用方按业务格式化,例如按份数的“x2”或称重的“0.5/kg”,绘制时原样展示。</p>
|
||||
* <p>数量文本由调用方按业务格式化,例如按份数的“x2”或称重的“0.5/kg”,绘制时原样展示。
|
||||
* 主名保持大号粗体;subName 非空时在主名与价格行之间以正文字号渲染副行小字。</p>
|
||||
*
|
||||
* @param name 商品快照名称;空值显示“未命名商品”
|
||||
* @param name 主商品名(套餐名优先);空值显示“未命名商品”
|
||||
* @param subName 主名下方的副行名称(商品名);null 或空白时不渲染副行
|
||||
* @param quantity 数量展示文本,例如“x2”或“0.5/kg”
|
||||
* @param unitPrice 商品快照单价,单位元且不含货币符号
|
||||
* @param subtotal 商品小计,单位元且不含货币符号
|
||||
@@ -310,10 +312,12 @@ public class ThermalReceiptImage implements ReceiptRenderer {
|
||||
* @param componentItems 组合商品组成说明,每项应已包含对应数量;单品或无组成时可为空
|
||||
* @return 当前绘制器
|
||||
*/
|
||||
public ThermalReceiptImage voucherProduct(String name, String quantity, String unitPrice, String subtotal,
|
||||
boolean combo, List<String> componentItems) {
|
||||
public ThermalReceiptImage voucherProduct(String name, String subName, String quantity, String unitPrice,
|
||||
String subtotal, boolean combo, List<String> componentItems) {
|
||||
String typeLabel = combo ? "组合商品" : "单品";
|
||||
String safeName = name == null || name.isBlank() ? "未命名商品" : name;
|
||||
String safeSubName = subName == null ? null : subName.trim();
|
||||
if (safeSubName != null && safeSubName.isBlank()) safeSubName = null;
|
||||
String safeQuantity = quantity == null || quantity.isBlank() ? "x0" : quantity;
|
||||
String safeUnitPrice = unitPrice == null || unitPrice.isBlank() ? "--" : unitPrice;
|
||||
String safeSubtotal = subtotal == null || subtotal.isBlank() ? "--" : subtotal;
|
||||
@@ -335,6 +339,10 @@ public class ThermalReceiptImage implements ReceiptRenderer {
|
||||
int nameLineHeight = nameMetrics.getHeight() + 4;
|
||||
|
||||
setFont(Font.PLAIN, priceFontSize);
|
||||
FontMetrics subNameMetrics = graphics.getFontMetrics();
|
||||
List<String> subNameLines = safeSubName == null ? List.of() : wrap(safeSubName, innerWidth, subNameMetrics);
|
||||
int subNameLineHeight = subNameMetrics.getHeight() + 3;
|
||||
|
||||
FontMetrics priceMetrics = graphics.getFontMetrics();
|
||||
String priceAndQuantity = "单价 ¥" + safeUnitPrice + " " + safeQuantity;
|
||||
String subtotalText = "小计 ¥" + safeSubtotal;
|
||||
@@ -352,7 +360,8 @@ public class ThermalReceiptImage implements ReceiptRenderer {
|
||||
int componentHeight = componentLines.stream().mapToInt(List::size).sum() * componentLineHeight;
|
||||
int comboSectionHeight = combo && !componentLines.isEmpty()
|
||||
? 18 + componentLineHeight + componentHeight : 0;
|
||||
int cardHeight = PRODUCT_CARD_PADDING + tagHeight + 10 + nameLines.size() * nameLineHeight + 8
|
||||
int cardHeight = PRODUCT_CARD_PADDING + tagHeight + 10 + nameLines.size() * nameLineHeight
|
||||
+ subNameLines.size() * subNameLineHeight + 8
|
||||
+ (splitPriceLine ? priceLineHeight * 2 : priceLineHeight) + comboSectionHeight
|
||||
+ PRODUCT_CARD_PADDING;
|
||||
ensureCapacity(cardHeight + PRODUCT_CARD_GAP);
|
||||
@@ -382,6 +391,13 @@ public class ThermalReceiptImage implements ReceiptRenderer {
|
||||
graphics.drawString(line, contentX, contentY + nameMetrics.getAscent());
|
||||
contentY += nameLineHeight;
|
||||
}
|
||||
if (!subNameLines.isEmpty()) {
|
||||
setFont(Font.PLAIN, priceFontSize);
|
||||
for (String line : subNameLines) {
|
||||
graphics.drawString(line, contentX, contentY + subNameMetrics.getAscent());
|
||||
contentY += subNameLineHeight;
|
||||
}
|
||||
}
|
||||
contentY += 8;
|
||||
|
||||
setFont(Font.PLAIN, priceFontSize);
|
||||
|
||||
Reference in new issue
Block a user