|
|
@@ -5,6 +5,11 @@ import com.platform.api.request.XpPrintImageReqVo;
|
|
|
import com.platform.api.request.XpPrintReceiptReqVo;
|
|
|
import com.platform.config.XpCloudProperties;
|
|
|
import com.platform.exception.IotException;
|
|
|
+import com.platform.external.client.PrintReceiptContentClient;
|
|
|
+import com.platform.external.request.PrintReceiptContent;
|
|
|
+import com.platform.external.request.WaybillTransportQueryReq;
|
|
|
+import com.platform.external.response.TradeOrderTransportInfoResp;
|
|
|
+import com.platform.result.BaseResult;
|
|
|
import io.github.dv996coding.vo.ObjectRestResponse;
|
|
|
import io.github.dv996coding.vo.PrintOrderRequest;
|
|
|
import jakarta.annotation.Resource;
|
|
|
@@ -12,10 +17,18 @@ import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.security.MessageDigest;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.LocalDateTime;
|
|
|
import java.time.Instant;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.AbstractMap;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
import java.util.UUID;
|
|
|
|
|
|
/**
|
|
|
@@ -30,6 +43,12 @@ public class XpCloudPrintService {
|
|
|
|
|
|
private final XpCloudProperties xpCloudProperties;
|
|
|
|
|
|
+ private final PrintReceiptContentClient printReceiptContentClient;
|
|
|
+
|
|
|
+ private final KwsPrintReceiptRecordRepository kwsPrintReceiptRecordRepository;
|
|
|
+
|
|
|
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
/**
|
|
|
* 直接使用@Resource注入SDK服务
|
|
|
*/
|
|
|
@@ -74,16 +93,24 @@ public class XpCloudPrintService {
|
|
|
* @param reqVo 小票订单打印请求
|
|
|
* @return 云端打印任务号
|
|
|
*/
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public String printReceipt(XpPrintReceiptReqVo reqVo) {
|
|
|
log.info("芯烨云小票打印, 入参:{}", JSON.toJSONString(reqVo));
|
|
|
- if (reqVo == null || reqVo.getContent() == null || StringUtils.isAnyBlank(reqVo.getSn())) {
|
|
|
+ if (reqVo == null || StringUtils.isAnyBlank(reqVo.getSn(), reqVo.getTaskNo())) {
|
|
|
throw new IotException("打印参数不能为空");
|
|
|
}
|
|
|
if (reqVo.getExpiresIn() != null && (reqVo.getMode() == null || reqVo.getMode() != 1)) {
|
|
|
throw new IotException("设置expiresIn时,mode必须为1");
|
|
|
}
|
|
|
try {
|
|
|
- String xmlContent = buildReceiptXml(reqVo.getContent());
|
|
|
+ TradeOrderTransportInfoResp transportInfo = getReceiptContent(reqVo.getTaskNo());
|
|
|
+ PrintReceiptContent content = assembleReceiptContent(transportInfo, reqVo.getTaskNo());
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ int printCount = kwsPrintReceiptRecordRepository.reservePrintCount(reqVo.getTaskNo(), transportInfo.getTradeOrderNo(), reqVo.getSn(),now);
|
|
|
+ content.setPrintTimesDesc(formatPrintTimes(printCount));
|
|
|
+ content.setPrintTime(now.format(DATE_TIME_FORMATTER));
|
|
|
+ validateReceiptContent(content);
|
|
|
+ String xmlContent = buildReceiptXml(content);
|
|
|
PrintOrderRequest request = new PrintOrderRequest(reqVo.getSn(), xmlContent);
|
|
|
fillCommonParams(request);
|
|
|
request.setDirect(reqVo.getDirect());
|
|
|
@@ -108,17 +135,200 @@ public class XpCloudPrintService {
|
|
|
if (response.getCode() == null || response.getCode() != 0) {
|
|
|
throw new IotException("调用芯烨云小票打印失败:" + response.getMsg());
|
|
|
}
|
|
|
+ log.info("芯烨云小票打印次数记录完成, taskNo={}, printCount={}", content.getTaskNo(), printCount);
|
|
|
return response.getData();
|
|
|
+ } catch (IotException e) {
|
|
|
+ throw e;
|
|
|
} catch (Exception e) {
|
|
|
log.error("调用芯烨云小票打印异常, sn={}", reqVo.getSn(), e);
|
|
|
throw new IotException("调用芯烨云小票打印异常:" + e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 通过远程服务获取小票内容。
|
|
|
+ *
|
|
|
+ * @param taskNo 任务单号
|
|
|
+ * @return 小票内容
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp getReceiptContent(String taskNo) {
|
|
|
+ try {
|
|
|
+ WaybillTransportQueryReq query = new WaybillTransportQueryReq();
|
|
|
+ query.setWaybillNo(taskNo);
|
|
|
+ log.info("远程获取小票内容, 请求参数:{}", JSON.toJSONString(query));
|
|
|
+ BaseResult<TradeOrderTransportInfoResp> response = printReceiptContentClient.queryTransportInfoByWaybillNo(query);
|
|
|
+ log.info("远程获取小票内容, 响应参数:{}", JSON.toJSONString(response));
|
|
|
+ if (response == null) {
|
|
|
+ throw new IotException("远程获取小票内容响应为空");
|
|
|
+ }
|
|
|
+ if (!response.isSuccess()) {
|
|
|
+ throw new IotException("远程获取小票内容失败:" + response.getMessage());
|
|
|
+ }
|
|
|
+ if (response.getData() == null) {
|
|
|
+ throw new IotException("远程获取小票内容为空");
|
|
|
+ }
|
|
|
+ return response.getData();
|
|
|
+ } catch (IotException e) {
|
|
|
+ throw e;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("远程获取小票内容异常, taskNo={}", taskNo, e);
|
|
|
+ throw new IotException("远程获取小票内容异常:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验远程返回的小票内容,避免空字段导致打印内容不完整。
|
|
|
+ *
|
|
|
+ * @param content 小票内容
|
|
|
+ */
|
|
|
+ private void validateReceiptContent(PrintReceiptContent content) {
|
|
|
+ if (content == null) {
|
|
|
+ throw new IotException("小票内容不能为空");
|
|
|
+ }
|
|
|
+ List<AbstractMap.SimpleEntry<String, String>> requiredFields = List.of(
|
|
|
+ new AbstractMap.SimpleEntry<>("companyName", content.getCompanyName()),
|
|
|
+ new AbstractMap.SimpleEntry<>("customerName", content.getCustomerName()),
|
|
|
+ new AbstractMap.SimpleEntry<>("taskNo", content.getTaskNo()),
|
|
|
+ new AbstractMap.SimpleEntry<>("acceptTime", content.getAcceptTime()),
|
|
|
+ new AbstractMap.SimpleEntry<>("finishTime", content.getFinishTime()),
|
|
|
+ new AbstractMap.SimpleEntry<>("weigher", content.getWeigher()),
|
|
|
+ new AbstractMap.SimpleEntry<>("driverName", content.getDriverName()),
|
|
|
+ new AbstractMap.SimpleEntry<>("driverMobile", content.getDriverMobile()),
|
|
|
+ new AbstractMap.SimpleEntry<>("driverIdCard", content.getDriverIdCard()),
|
|
|
+ new AbstractMap.SimpleEntry<>("materialName", content.getMaterialName()),
|
|
|
+ new AbstractMap.SimpleEntry<>("materialSpec", content.getMaterialSpec()),
|
|
|
+ new AbstractMap.SimpleEntry<>("taskWeight", content.getTaskWeight()),
|
|
|
+ new AbstractMap.SimpleEntry<>("weightSummary", content.getWeightSummary()),
|
|
|
+ new AbstractMap.SimpleEntry<>("plateNo", content.getPlateNo()),
|
|
|
+ new AbstractMap.SimpleEntry<>("vehicleAxleDesc", content.getVehicleAxleDesc()),
|
|
|
+ new AbstractMap.SimpleEntry<>("destination", content.getDestination()),
|
|
|
+ new AbstractMap.SimpleEntry<>("printTime", content.getPrintTime()),
|
|
|
+ new AbstractMap.SimpleEntry<>("printTimesDesc", content.getPrintTimesDesc()),
|
|
|
+ new AbstractMap.SimpleEntry<>("copyLabel", content.getCopyLabel())
|
|
|
+ );
|
|
|
+ String blankField = requiredFields.stream()
|
|
|
+ .filter(field -> StringUtils.isBlank(field.getValue()))
|
|
|
+ .map(AbstractMap.SimpleEntry::getKey)
|
|
|
+ .findFirst()
|
|
|
+ .orElse(null);
|
|
|
+ if (StringUtils.isNotBlank(blankField)) {
|
|
|
+ throw new IotException("小票内容字段不能为空:" + blankField);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将远程运输信息重新组装为小票打印模板参数。
|
|
|
+ *
|
|
|
+ * @param transportInfo 远程运输信息
|
|
|
+ * @param taskNo 任务单号
|
|
|
+ * @return 小票打印模板参数
|
|
|
+ */
|
|
|
+ private PrintReceiptContent assembleReceiptContent(TradeOrderTransportInfoResp transportInfo, String taskNo) {
|
|
|
+ if (transportInfo == null) {
|
|
|
+ throw new IotException("小票运输信息不能为空");
|
|
|
+ }
|
|
|
+ TradeOrderTransportInfoResp.TaskInfo taskInfo = resolveTaskInfo(transportInfo, taskNo);
|
|
|
+ TradeOrderTransportInfoResp.DriverInfo driverInfo = taskInfo.getDriverInfo();
|
|
|
+ TradeOrderTransportInfoResp.GoodsInfo goodsInfo = taskInfo.getGoodsInfo();
|
|
|
+ TradeOrderTransportInfoResp.TruckInfo truckInfo = taskInfo.getTruckInfo();
|
|
|
+ TradeOrderTransportInfoResp.PrintInfo printInfo = taskInfo.getPrintInfo();
|
|
|
+
|
|
|
+ PrintReceiptContent content = new PrintReceiptContent();
|
|
|
+ content.setCompanyName(transportInfo.getSupplierName());
|
|
|
+ content.setCustomerName(transportInfo.getCustomerName());
|
|
|
+ content.setTaskNo(taskInfo.getTaskNo());
|
|
|
+ content.setAcceptTime(taskInfo.getAcceptTime());
|
|
|
+ content.setFinishTime(taskInfo.getFinishTime());
|
|
|
+ content.setWeigher(taskInfo.getWeigherName());
|
|
|
+ content.setDriverName(driverInfo == null ? null : driverInfo.getName());
|
|
|
+ content.setDriverMobile(driverInfo == null ? null : driverInfo.getPhone());
|
|
|
+ content.setDriverIdCard(driverInfo == null ? null : driverInfo.getIdCard());
|
|
|
+ content.setMaterialName(goodsInfo == null ? null : goodsInfo.getMaterialName());
|
|
|
+ content.setMaterialSpec(goodsInfo == null ? null : goodsInfo.getSpecification());
|
|
|
+ content.setTaskWeight(formatWeight(goodsInfo == null ? null : goodsInfo.getTaskAmount(),
|
|
|
+ goodsInfo == null ? null : goodsInfo.getUnit()));
|
|
|
+ content.setWeightSummary(formatWeightSummary(goodsInfo));
|
|
|
+ content.setPlateNo(truckInfo == null ? null : truckInfo.getTruckNo());
|
|
|
+ content.setVehicleAxleDesc(truckInfo == null ? null : truckInfo.getTruckAxle());
|
|
|
+ content.setDestination(taskInfo.getDestination());
|
|
|
+ content.setCopyLabel("客户联");
|
|
|
+ return content;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按任务单号匹配任务信息,未匹配时取第一条任务。
|
|
|
+ *
|
|
|
+ * @param transportInfo 远程运输信息
|
|
|
+ * @param taskNo 任务单号
|
|
|
+ * @return 任务信息
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp.TaskInfo resolveTaskInfo(TradeOrderTransportInfoResp transportInfo, String taskNo) {
|
|
|
+ List<TradeOrderTransportInfoResp.TaskInfo> tasks = transportInfo.getTasks() == null
|
|
|
+ ? Collections.emptyList()
|
|
|
+ : transportInfo.getTasks();
|
|
|
+ if (tasks.isEmpty()) {
|
|
|
+ throw new IotException("小票任务信息不能为空");
|
|
|
+ }
|
|
|
+ return tasks.stream()
|
|
|
+ .filter(taskInfo -> taskInfo != null && StringUtils.equals(taskNo, taskInfo.getTaskNo()))
|
|
|
+ .findFirst()
|
|
|
+ .orElse(tasks.stream()
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .findFirst()
|
|
|
+ .orElseThrow(() -> new IotException("小票任务信息不能为空")));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化重量与单位。
|
|
|
+ *
|
|
|
+ * @param weight 重量
|
|
|
+ * @param unit 单位
|
|
|
+ * @return 重量展示文案
|
|
|
+ */
|
|
|
+ private String formatWeight(BigDecimal weight, String unit) {
|
|
|
+ if (weight == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String weightText = weight.stripTrailingZeros().toPlainString();
|
|
|
+ return StringUtils.isBlank(unit) ? weightText : weightText + " " + unit;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装皮重、毛重、净重汇总文案。
|
|
|
+ *
|
|
|
+ * @param goodsInfo 货物信息
|
|
|
+ * @return 重量汇总文案
|
|
|
+ */
|
|
|
+ private String formatWeightSummary(TradeOrderTransportInfoResp.GoodsInfo goodsInfo) {
|
|
|
+ if (goodsInfo == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (goodsInfo.getTareWeight() == null || goodsInfo.getGrossWeight() == null || goodsInfo.getNetWeight() == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String unit = goodsInfo.getUnit();
|
|
|
+ return "皮重:" + formatWeight(goodsInfo.getTareWeight(), unit)
|
|
|
+ + " 毛重:" + formatWeight(goodsInfo.getGrossWeight(), unit)
|
|
|
+ + " 净重:" + formatWeight(goodsInfo.getNetWeight(), unit);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装打印次数文案。
|
|
|
+ *
|
|
|
+ * @param printCount 打印次数
|
|
|
+ * @return 打印次数文案
|
|
|
+ */
|
|
|
+ private String formatPrintTimes(Integer printCount) {
|
|
|
+ if (printCount == null || printCount <= 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return "第 " + printCount + " 次打印";
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 按前端对象拼接小票XML内容
|
|
|
*/
|
|
|
- private String buildReceiptXml(XpPrintReceiptReqVo.Content content) {
|
|
|
+ private String buildReceiptXml(PrintReceiptContent content) {
|
|
|
String line = "------------------------";
|
|
|
return "\n<CB>" + content.getCompanyName()
|
|
|
+ "\n<CB>" + line
|