打印实现

This commit is contained in:
张松 2025-02-25 14:42:19 +08:00
parent 9172e4b9f5
commit 14a732e4b5
30 changed files with 2227 additions and 233 deletions

View File

@ -0,0 +1,107 @@
package com.czg.mq;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.entity.MqLog;
import com.czg.account.entity.PrintMachine;
import com.czg.account.service.MqLogService;
import com.czg.account.service.PrintMachineService;
import com.czg.config.RabbitConstants;
import com.czg.order.entity.OrderInfo;
import com.czg.order.service.OrderInfoService;
import com.czg.service.account.print.PrinterHandler;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 打印mq消息处理器
* @author Administrator
*/
@Component
@Slf4j
public class PrintMqListener {
@DubboReference
private OrderInfoService orderInfoService;
@Resource
private MqLogService mqLogService;
@Resource
private PrintMachineService printMachineService;
@Lazy
@Resource
private PrinterHandler printerHandler;
// @RabbitListener(queues = {RabbitConstants.Queue.ORDER_PRINT_QUEUE})
public void orderPrint(String orderId) {
long startTime = DateUtil.date().getTime();
log.info("接收到订单打印消息:{}", orderId);
MqLog mqLog = new MqLog().setQueue(RabbitConstants.Queue.ORDER_PRINT_QUEUE).setMsg(orderId).setType("orderPrint").setPlat("java.account").setCreateTime(DateUtil.date().toLocalDateTime());
try {
OrderInfo orderInfo = orderInfoService.getById(orderId);
if (orderInfo == null) {
log.error("订单信息不存在, {}", orderId);
throw new RuntimeException("订单信息不存在");
}
getPrintMachine(orderInfo.getShopId(), "cash", "normal", "order").forEach(machine -> {
printerHandler.handleRequest(machine, orderInfo);
// printPlaceTicket(isReturn, machine, orderInfo, shopInfo);
});
}catch (Exception e) {
log.error("订单打印失败", e);
mqLog.setErrInfo(JSONObject.toJSONString(e));
mqLog.setDuration(DateUtil.date().getTime() - startTime);
mqLog.setFailTime(DateUtil.date().toLocalDateTime());
// mqLogService.save(mqLog);
}
}
/**
* 获取可用打印机
* @param shopId 店铺id
* @param subType 打印类型分类label标签 cash小票 kitchen出品
* @param printMethod 打印方式 all-全部打印 normal-仅打印结账单前台one-仅打印制作单厨房
* @param printType 打印类型JSON数组 refund-确认退款单 handover-交班单 queue-排队取号
* @return 打印机列表
*/
private List<PrintMachine> getPrintMachine(Long shopId, String subType, String printMethod, String printType) {
QueryWrapper wrapper = new QueryWrapper()
.eq(PrintMachine::getStatus, 1)
.eq(PrintMachine::getShopId, shopId)
.eq(PrintMachine::getSubType, subType)
.eq(PrintMachine::getConnectionType, "network");
if (StrUtil.isNotEmpty(printMethod)) {
wrapper.in(PrintMachine::getPrintMethod, Arrays.asList(printMethod, "all"));
}
if ("callTicket".equals(printType)) {
printType = "queue";
}
if (StrUtil.isNotEmpty(printType)) {
wrapper.like(PrintMachine::getPrintType, printType);
}
List<PrintMachine> list = printMachineService.list(wrapper);
for (PrintMachine item : list) {
//实际打印以传递的参数为准
item.setPrintMethod(printMethod);
}
if (list.isEmpty()) {
log.error("店铺未配置打印机店铺id: {}", shopId);
return list;
}
log.info("打印机列表: {}", list);
return list;
}
}

View File

@ -1,5 +1,6 @@
package com.czg;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -10,6 +11,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableDubbo
@MapperScan("com.czg.service.product.mapper")
public class ProductApplication {

View File

@ -0,0 +1,18 @@
package com.czg.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author Administrator
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@ -22,7 +22,9 @@ public interface RedisCst {
// 排队取号全局号码
String TABLE_CALL_NUMBER = "TABLE_CALL_NUMBER:";
String TABLE_CALL_NUMBER = "table:call:number:";
String PRINT_ORDER_DETAIL = "print:order:detail:";
static String getLockKey(String sign, Object... args) {
StringBuilder key = new StringBuilder(LOCK_KEY + ":" + sign + ":");
@ -37,4 +39,9 @@ public interface RedisCst {
static String getTableCallNumKey(Long shopId, Long callTableId) {
return TABLE_CALL_NUMBER + shopId + ":" + callTableId;
}
static String getPrintOrderDetailKey(Long orderId) {
return PRINT_ORDER_DETAIL + orderId;
}
}

View File

@ -0,0 +1,72 @@
package com.czg.account.dto;
import java.io.Serializable;
import java.time.LocalDateTime;
import com.alibaba.fastjson2.annotation.JSONField;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 实体类
*
* @author zs
* @since 2025-02-21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MqLogDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Integer id;
/**
* 队列名称
*/
private String queue;
/**
* 消息内容
*/
private String msg;
/**
* 消息类型 orderPrint订单打印
*/
private String type;
/**
* 消费状态 0代消费 1已消费 -1已失败
*/
private Integer state;
/**
* 打印平台 java php 自行定义
*/
private String plat;
/**
* 失败信息
*/
private String errInfo;
/**
* 接收时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 失败时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime failTime;
}

View File

@ -0,0 +1,156 @@
package com.czg.account.dto;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.alibaba.fastjson2.annotation.JSONField;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 订单详情 实体类
*
* @author zs
* @since 2025-02-24
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderDetailDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
private Long orderId;
private Long shopId;
private Long productId;
private String productImg;
private String productName;
/**
* 商品类型单规格商品 single 多规格商品 sku 套餐商品 package 称重商品 weigh 团购券 coupon
*/
private String productType;
private Long skuId;
private String skuName;
/**
* 单价原价/会员价/临时改价
*/
private BigDecimal price;
/**
* 折扣金额
*/
private BigDecimal discountAmount;
/**
* 打包费(单价)
*/
private BigDecimal packAmount;
/**
* 支付金额去除优惠券优惠金额的商品金额 不包含打包费
*/
private BigDecimal payAmount;
/**
* 已退款金额
*/
private BigDecimal returnAmount;
/**
* 数量
*/
private BigDecimal num;
/**
* 打包数量
*/
private BigDecimal packNumber;
/**
* 优惠券抵扣数量
*/
private BigDecimal couponNum;
/**
* 退菜数量不管价格
*/
private BigDecimal returnNum;
/**
* 退单数量
*/
private BigDecimal refundNum;
/**
* 退款单号
*/
private String refundNo;
/**
* 临时改价备注
*/
private String discountSaleNote;
/**
* 状态: wait-pay 待支付;in-production 制作中;wait-out 待取餐;refunding 退款中; part-refund 部分退单; refund-退单; done 完成;
*/
private String status;
/**
* 当前下单次数
*/
private Integer placeNum;
/**
* 是否是临时菜品
*/
private Integer isTemporary;
/**
* 是否打票
*/
private Integer isPrint;
/**
* 是否等叫
*/
private Integer isWaitCall;
/**
* 套餐商品选择信息
*/
private String proGroupInfo;
/**
* 备注
*/
private String remark;
/**
* 退款备注
*/
private String refundRemark;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,262 @@
package com.czg.account.dto;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import com.alibaba.fastjson2.annotation.JSONField;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 订单表 实体类
*
* @author zs
* @since 2025-02-24
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderInfoDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private BigInteger id;
/**
* 订单编号
pc 收银机客户端 PC+雪花ID
wechat 微信小程序 WX+雪花ID
alipay 支付宝小程序 ALI+雪花ID
admin-pc PC管理端 WEB+雪花ID
admin-app APP管理端 APP+雪花ID
*/
private String orderNo;
/**
* 店铺Id
*/
private Long shopId;
/**
* 用户Id user_info表的id
*/
private Long userId;
/**
* 退单金额
*/
private BigDecimal refundAmount;
/**
* 订单原金额 不含折扣价格
*/
private BigDecimal originAmount;
/**
* 抹零金额
*/
private BigDecimal roundAmount;
/**
* 订单金额 (扣除各类折扣)
*/
private BigDecimal orderAmount;
/**
* 实际支付金额
*/
private BigDecimal payAmount;
/**
* 积分抵扣金额
*/
private BigDecimal pointsDiscountAmount;
/**
* 使用的积分数量
*/
private Integer pointsNum;
/**
* 商品优惠券抵扣金额
*/
private BigDecimal productCouponDiscountAmount;
/**
* 用户使用的卡券 券id集合
*/
private String couponInfoList;
/**
* 满减优惠券抵扣金额
*/
private BigDecimal fullCouponDiscountAmount;
/**
* 手动优惠金额
*/
private BigDecimal discountAmount;
/**
* 折扣比例 多次下单的用,分割
*/
private String discountRatio;
/**
* 打包费
*/
private BigDecimal packFee;
/**
* 台桌编号
*/
private String tableCode;
/**
* 台桌名称
*/
private String tableName;
/**
* 订单类型-
cash收银(除小程序以外 都属于收银)
miniapp小程序
*/
private String orderType;
/**
* 平台类型
微信小程序 WX
支付宝小程序 ALI
收银机客户端 PC
PC管理端 APC
APP管理端 APP
*/
private String platformType;
/**
* 用餐模式 堂食 dine-in 外带 take-out 外卖 take-away
*/
private String dineMode;
/**
* 支付模式:
后付费 after-pay
先付费 before-pay
无桌码 no-table
*/
private String payMode;
/**
* 支付类型
主扫 main-scan
被扫 back-scan
微信小程序 wechat-mini
支付宝小程序 alipay-mini
会员支付 vip-pay
现金支付 cash-pay
*/
private String payType;
/**
* 状态: unpaid-待支付;in-production 制作中;wait-out 待取餐;;done-订单完成;refunding-申请退单;refund-退单;part-refund 部分退单;cancelled-取消订单
*/
private String status;
/**
* 折扣信息 json
*/
private String discountInfo;
/**
* 是否支持退款1支持退单 0不支持退单
*/
private Integer refundAble;
/**
* 支付时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime paidTime;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 支付订单号
tb_order_payment.id tb_shop_user_flow.id
*/
private Long payOrderId;
/**
* 交易日期
*/
private String tradeDay;
/**
* 备注
*/
private String remark;
/**
* 取餐码
*/
private String takeCode;
/**
* 员工id
*/
private Long staffId;
/**
* 当前订单下单次数
*/
private Integer placeNum;
/**
* 用餐人数
*/
private Integer seatNum;
/**
* 餐位费
*/
private BigDecimal seatAmount;
/**
* 退款备注
*/
private String refundRemark;
/**
* 是否使用了霸王餐
*/
private Integer isFreeDine;
/**
* 是否等叫 0 1 等叫
*/
private Integer isWaitCall;
/**
* 挂账人id
*/
private Long creditBuyerId;
/**
* 是否回收站 0-1回收站
*/
private Integer isDel;
}

View File

@ -0,0 +1,14 @@
package com.czg.account.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class PrintOrderDetailDTO {
private String productName;
private String num;
private String price;
private String remark;
private String proGroupInfo;
}

View File

@ -1,6 +1,6 @@
package com.czg.account.dto.pad;
import com.czg.account.entity.Product;
import com.czg.product.entity.Product;
import lombok.Data;
import java.util.List;

View File

@ -0,0 +1,83 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 实体类
*
* @author zs
* @since 2025-02-21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("mq_log")
@Accessors(chain = true)
public class MqLog implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id(keyType = KeyType.Auto)
private Integer id;
/**
* 队列名称
*/
private String queue;
/**
* 消息内容
*/
private String msg;
/**
* 消息类型 orderPrint订单打印
*/
private String type;
/**
* 消费状态 0代消费 1已消费 -1已失败
*/
private Integer state;
/**
* 打印平台 java php 自行定义
*/
private String plat;
/**
* 失败信息
*/
private String errInfo;
/**
* 接收时间
*/
private LocalDateTime createTime;
/**
* 失败时间
*/
private LocalDateTime failTime;
/**
* 执行时间
*/
private Long duration;
}

View File

@ -1,193 +0,0 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Time;
import java.time.LocalDateTime;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 商品 实体类
*
* @author zs
* @since 2025-02-20
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("tb_product")
public class Product implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@Id(keyType = KeyType.Auto)
private Long id;
/**
* 商品分类
*/
private Long categoryId;
/**
* 商品规格
*/
private Long specId;
/**
* 单位id
*/
private Long unitId;
/**
* 店铺id
*/
private Long shopId;
/**
* 商品名称
*/
private String name;
/**
* 短标题--促销语
*/
private String shortTitle;
/**
* 商品类型 single-单规格商品 sku-多规格商品 package-套餐商品 weight-称重商品 coupon-团购券
*/
private String type;
/**
* 0 固定套餐 1可选套餐
*/
private Integer groupType;
/**
* 包装费
*/
private BigDecimal packFee;
/**
* 商品封面图
*/
private String coverImg;
/**
* 商品图片第一张为缩略图其他为详情
*/
private String images;
/**
* 套餐内容
*/
private String groupSnap;
/**
* 库存警戒线
*/
private Integer warnLine;
/**
* 称重 价格/千克
*/
private BigDecimal weight;
/**
* 是否允许临时改价
*/
private Integer isAllowTempModifyPrice;
/**
* 数组 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'
*/
private String days;
/**
* 可用开始时间
*/
private Time startTime;
/**
* 可用结束时间
*/
private Time endTime;
/**
* 规格选详情
*/
private String selectSpecInfo;
/**
* 排序
*/
private Integer sort;
/**
* 是否热销
*/
private Integer isHot;
/**
* 是否开启库存
*/
private Integer isStock;
/**
* 是否售罄
*/
private Integer isSoldStock;
/**
* 团购卷分类可有多个分类
*/
private String groupCategoryId;
/**
* 商品级库存数量
*/
private Integer stockNumber;
/**
* 是否上架
*/
private Boolean isSale;
/**
* 退款是否退回库存
*/
private Boolean isRefundStock;
/**
* 创建时间
*/
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
/**
* 更新时间
*/
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
/**
* 逻辑删除
*/
private Integer isDel;
}

View File

@ -0,0 +1,14 @@
package com.czg.account.service;
import com.mybatisflex.core.service.IService;
import com.czg.account.entity.MqLog;
/**
* 服务层
*
* @author zs
* @since 2025-02-21
*/
public interface MqLogService extends IService<MqLog> {
}

View File

@ -1,14 +0,0 @@
package com.czg.account.service;
import com.mybatisflex.core.service.IService;
import com.czg.account.entity.Product;
/**
* 商品 服务层
*
* @author zs
* @since 2025-02-20
*/
public interface ProductService extends IService<Product> {
}

View File

@ -23,6 +23,12 @@
<artifactId>easy-captcha</artifactId>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>cash-common-mq</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>cash-common-tools</artifactId>

View File

@ -0,0 +1,14 @@
package com.czg.service.account.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.MqLog;
/**
* 映射层
*
* @author zs
* @since 2025-02-21
*/
public interface MqLogMapper extends BaseMapper<MqLog> {
}

View File

@ -1,7 +1,7 @@
package com.czg.service.account.mapper;
import com.czg.product.entity.Product;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.Product;
/**
* 商品 映射层

View File

@ -0,0 +1,268 @@
package com.czg.service.account.print;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author Administrator
*/
@Component
@Slf4j
public class FeiPrinter extends PrinterHandler implements PrinterImpl{
@Resource
private RestTemplate restTemplate;
@Resource
private ShopInfoService shopInfoService;
// API 地址
private static final String URL = "http://api.feieyun.cn/Api/Open/";
// 飞鹅云 API 账号
private static final String USER = "chaozhanggui2022@163.com";
private static final String UKEY = "UfWkhXxSkeSSscsU";
public FeiPrinter() {
super("fePrinter");
}
@Override
protected void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
String remark = orderDetail.getRemark();
String content = buildDishPrintData(getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
orderDetail.getProductName(), orderDetail.getNum(), remark, orderDetail.getProGroupInfo());
sendPrintRequest(machine.getAddress(), content, "1");
// shopPrintLogService.save(machine, "新订单", resp[0], resp[1]); // 可以解开注释用于日志存储
}
@Override
protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
log.warn("退菜打印功能未实现");
}
@Override
protected void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList) {
log.warn("退单打印功能未实现");
}
@Override
protected void normalOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList) {
ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId());
String printerNum = "1";
if (StrUtil.isNotBlank(machine.getPrintQty())) {
printerNum = machine.getPrintQty().split("\\^")[1];
}
PrintInfoDTO printInfoDTO = new PrintInfoDTO().setShopName(shopInfo.getShopName()).setPrintType("普通打印").setPickupNum(getPickupNum(orderInfo))
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.date().toDateStr()).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
.setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo))
.setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0")
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle("结算单")
.setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
String string = buildOrderPrintData(printInfoDTO, detailList);
sendPrintRequest(machine.getAddress(), string, printerNum);
// shopPrintLogService.save(machine, "结算单", resp[0], resp[1]);
}
@Override
protected void callNumPrint(PrintMachine machine) {
log.warn("叫号打印功能未实现");
}
/**
* 生成签名
*/
private static String signature(String timeStamp) {
return DigestUtils.sha1Hex(FeiPrinter.USER + FeiPrinter.UKEY + timeStamp);
}
@Override
public String getStartSplitSign() {
return "";
}
@Override
public String getEndSplitSign() {
return "";
}
private PrintSignLabel printSignLabel = new PrintSignLabel()
.setBr("<BR>")
.setCut("PCUT")
.setF(new String[]{"<F>", "</F>"})
.setL(new String[]{"<L>", "</L>"})
.setS(new String[]{"", ""})
.setF(new String[]{"<B>", "</B>"})
.setQr(new String[]{"<QR>", "</QR>"})
.setCenter(new String[]{"<CB>", "</CB>"})
.setBold(new String[]{"<BOLD>", "</BOLD>"});
@Override
public PrintSignLabel getSignLabelInfo() {
return printSignLabel;
}
@Override
public String buildOrderPrintData(PrintInfoDTO printInfoDTO, List<OrderDetail> detailList) {
StringBuilder data = new StringBuilder();
data.append(StrUtil.format("<CB>{}</CB><BR>", printInfoDTO.getShopName()));
data.append("<BR>");
data.append(StrUtil.format("<C><BOLD>{}【{}】</C></BOLD><BR>", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum()));
data.append("<BR>");
data.append(StrUtil.format("订单号:{}<BR>", printInfoDTO.getOrderNo()));
data.append(StrUtil.format("交易时间:{}<BR>", printInfoDTO.getTradeDate()));
data.append(StrUtil.format("收银员:{}<BR>", printInfoDTO.getOperator()));
data.append("<BR>");
data.append("品名 数量 小计<BR>");
data.append("--------------------------------<BR>");
for (OrderDetail detail : detailList) {
String productName = detail.getProductName();
String number = detail.getNum().stripTrailingZeros().toPlainString();
String amount = toPlainStr(detail.getPayAmount().toPlainString());
//58mm的机器,一行打印16个汉字,32个字母; 80mm的机器,一行打印24个汉字,48个字母
//展示4列 b1代表名称列占用14个字节 b2单价列6个字节 b3数量列3个字节 b4金额列6个字节-->这里的字节数可按自己需求自由改写14+6+3+6再加上代码写的3个空格就是32了58mm打印机一行总占32字节
//String row = FeieYunUtil.getRow(productName, "",number, amount, 14, 6,3, 6)
//展示3列 b1代表名称列占用20个字节 b2单价列0个字节 b3数量列3个字节 b4金额列6个字节-->这里的字节数可按自己需求自由改写20+0+3+6再加上代码写的3个空格就是32了58mm打印机一行总占32字节
String row = getRow(productName, "", number, amount, 20, 0, 3, 6);
data.append(row);
if (StrUtil.isNotBlank(detail.getSkuName())) {
data.append("规格:").append(detail.getSkuName()).append("<BR>");
}
String proGroupInfo = detail.getProGroupInfo();
if (StrUtil.isBlank(proGroupInfo)) {
continue;
}
if (!JSONUtil.isTypeJSONArray(proGroupInfo)) {
continue;
}
JSONArray subItems = JSONUtil.parseArray(proGroupInfo);
for (int i = 0; i < subItems.size(); i++) {
String proName = subItems.getJSONObject(i).getStr("proName");
int qty = subItems.getJSONObject(i).getInt("number");
String subRow = getRow(" - %s".formatted(proName), "", "%d.00".formatted(qty), "0.00", 20, 0, 3, 6);
data.append(subRow);
}
}
if (ObjectUtil.isNotNull(printInfoDTO.getDiscountAmount())) {
data.append("--------------------------------<BR>");
data.append(StrUtil.format("原价:{}<BR>", toPlainStr(printInfoDTO.getOriginalAmount())));
data.append(StrUtil.format("折扣:-{}<BR>", toPlainStr(printInfoDTO.getDiscountAmount())));
}
data.append("--------------------------------<BR>");
String t = "" + printInfoDTO.getPayAmount();
if (printInfoDTO.isReturn()) {
data.append(StrUtil.format("<B>应退:{}</B><BR>", t));
} else {
data.append(StrUtil.format("<B>应收:{}</B><BR>", t));
}
data.append("--------------------------------<BR>");
if (ObjectUtil.isNotEmpty(printInfoDTO.getPayType()) && ObjectUtil.isNotNull(printInfoDTO.getPayType()) && printInfoDTO.getPayType().equals("deposit")) {
data.append(StrUtil.format("储值:{}<BR>", toPlainStr(printInfoDTO.getOriginalAmount())));
data.append(StrUtil.format("积分:{}<BR>", printInfoDTO.getIntegral()));
}
data.append(StrUtil.format("余额:{}<BR>", toPlainStr(printInfoDTO.getBalance())));
data.append("--------------------------------<BR>");
if (StrUtil.isNotBlank(printInfoDTO.getRemark())) {
data.append(StrUtil.format("<L><BOLD>备注:{}</BOLD></L><BR>", printInfoDTO.getRemark()));
}
data.append("打印时间:").append(DateUtil.date().toDateStr()).append("<BR>");
data.append("<CUT>");
return data.toString();
}
/**
* 构建打印内容
*/
@Override
public String buildDishPrintData(String pickupNumber, String date, String productName,
BigDecimal number, String remark, String proGroupInfo) {
StringBuilder builder = new StringBuilder()
.append("<CB>").append(pickupNumber).append("</CB><BR>")
.append("<L>时间: ").append(date).append(" </L><BR>")
.append("<B><BOLD>").append(productName).append(" x ")
.append(number.stripTrailingZeros().toPlainString()).append("</BOLD></B><BR>")
.append("<B><BOLD>").append(StrUtil.emptyToDefault(remark, "")).append("</BOLD></B><BR>");
if (StrUtil.isNotBlank(proGroupInfo) && JSONUtil.isTypeJSONArray(proGroupInfo)) {
JSONArray subItems = JSONUtil.parseArray(proGroupInfo);
for (int i = 0; i < subItems.size(); i++) {
builder.append("<B>(").append(i + 1).append(")")
.append(subItems.getJSONObject(i).getStr("proName"))
.append(" x ").append(subItems.getJSONObject(i).getInt("number"))
.append("</B><BR>");
}
}
return builder.toString();
}
@Override
public <R> R sendPrintRequest(String address, String metaPrintData, String printerNum) {
log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData);
String time = String.valueOf(System.currentTimeMillis() / 1000);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("user", USER);
formData.add("stime", time);
formData.add("sig", signature(time));
formData.add("apiname", "Open_printMsg");
formData.add("sn", address);
formData.add("content", metaPrintData);
formData.add("times", printerNum == null ? "1" : printerNum);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
String result = restTemplate.postForObject(URL, requestEntity, String.class);
log.info("打印结果: {}", result);
return (R) result;
} catch (Exception e) {
log.error("打印请求失败: {}", e.getMessage());
throw new RuntimeException("飞蛾打印请求失败", e);
}
}
}

View File

@ -0,0 +1,42 @@
package com.czg.service.account.print;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
/**
* 打印机责任链初始化
* @author Administrator
*/
@Configuration
public class PrintConfig {
@Resource
private List<PrinterHandler> printers;
// 初始化责任链
@PostConstruct
public void initChain() {
// 检查打印处理器列表是否为空
if (printers != null && !printers.isEmpty()) {
for (int i = 0; i < printers.size() - 1; i++) {
// 设置当前处理器的下一个处理器
printers.get(i).setNextPrinter(printers.get(i + 1));
}
}
}
@Bean
@Primary
public PrinterHandler printerHandler() {
// 返回责任链的起始处理器
return printers.getFirst();
}
}

View File

@ -0,0 +1,355 @@
package com.czg.service.account.print;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.dto.PrintOrderDetailDTO;
import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopUser;
import com.czg.account.service.ShopUserService;
import com.czg.config.RedisCst;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import com.czg.order.service.OrderDetailService;
import com.czg.order.service.OrderInfoService;
import com.czg.product.entity.ProdSku;
import com.czg.product.entity.Product;
import com.czg.product.service.ProdSkuService;
import com.czg.product.service.ProductService;
import com.czg.service.RedisService;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author Administrator
*/
@Slf4j
@ToString
public abstract class PrinterHandler {
@Setter
protected PrinterHandler nextPrinter;
protected String printerBrand;
@Resource
protected RedisService redisService;
@Resource
protected ShopUserService shopUserService;
@DubboReference
protected OrderDetailService orderDetailService;
@DubboReference
protected OrderInfoService orderInfoService;
@DubboReference
protected ProductService productService;
@DubboReference
protected ProdSkuService prodSkuService;
@Data
public static class PrintDetailInfo {
private boolean isPrint;
private boolean isReturn;
private long orderId;
private long detailId;
private BigDecimal printNum;
}
@Data
@Accessors(chain = true)
public static class PrintInfoDTO {
// 打印标题 退款单/结算单
private String printTitle;
private String shopName;
private String printType;
private String pickupNum;
private String orderNo;
private String tradeDate;
private String operator;
private String payAmount;
private String originalAmount;
private String balance;
private String payType;
private String integral;
private String remark;
private String outNumber;
private String discountAmount;
private String discountRadio;
// 是否退款单
private boolean isReturn;
@Data
@Accessors(chain = true)
public static class DetailInfo {
private String num;
private String productName;
private String amount;
private String skuName;
private String prodGroupInfo;
}
}
public PrinterHandler(String printerBrand) {
this.printerBrand = printerBrand;
}
public void handleRequest(PrintMachine machine, OrderInfo orderInfo) {
if (canHandleRequest(machine.getContentType(), machine.getConnectionType())) {
log.info("打印机: {}, 订单信息: {}", machine.getName(), orderInfo);
print(machine, orderInfo);
} else if (nextPrinter != null) {
log.info("当前打印机无法处理: {},将请求传递给下一个打印机:{}...", this.printerBrand, nextPrinter.printerBrand);
nextPrinter.handleRequest(machine, orderInfo);
} else {
log.warn("未找到匹配打印机");
}
}
boolean canHandleRequest(String currentBrand, String connectType) {
log.info("handle判断是否可处理: {}, 连接类型: {}, handler类型: {}", currentBrand, connectType, printerBrand);
if (StrUtil.isBlank(printerBrand)) {
throw new RuntimeException("打印机品牌未赋值");
}
return printerBrand.equals(currentBrand) && "network".equals(connectType);
}
protected List<OrderDetail> getCanPrintOrderDetails(boolean partPrint, Long orderId, List<OrderDetail> tbOrderDetailList, List<?> categoryIds) {
List<Object> detailList = redisService.lGet(RedisCst.getPrintOrderDetailKey(orderId), 0, -1);
Map<String, PrintDetailInfo> detailMap = detailList.stream().collect(Collectors.toMap(i -> {
if (i instanceof PrintDetailInfo i2) {
return i2.getDetailId() + "_" + i2.isReturn;
}
throw new RuntimeException("转换orderDetail失败");
}, i -> (PrintDetailInfo) i));
List<Long> productIds = tbOrderDetailList.stream().map(OrderDetail::getProductId).collect(Collectors.toList());
Map<Long, Boolean> canPrintProMap = partPrint || categoryIds.isEmpty() ? new HashMap<>() :
productService.list(new QueryWrapper().in(Product::getCategoryId, categoryIds).in(Product::getId, productIds))
.stream().collect(Collectors.toMap(com.czg.product.entity.Product::getId, i -> true));
ArrayList<OrderDetail> orderDetails = new ArrayList<>();
ArrayList<String> status = CollectionUtil.newArrayList("refunding", "part-refund", "refund");
tbOrderDetailList.forEach(item -> {
boolean isReturn = status.contains(item.getStatus());
PrintDetailInfo printDetailInfo = detailMap.get(item.getId() + "_" + isReturn);
if (item.getIsPrint() != null && item.getIsPrint() == 1 && (canPrintProMap.get(item.getProductId()) != null || !partPrint) &&
(printDetailInfo == null || item.getNum().subtract(printDetailInfo.getPrintNum()).compareTo(BigDecimal.ZERO) > 0)) {
if (printDetailInfo != null) {
item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum()));
}
orderDetails.add(item);
}
});
return orderDetails;
}
protected void print(PrintMachine machine, OrderInfo orderInfo) {
String printMethod = machine.getPrintMethod();
if (StrUtil.isBlank(printMethod) && StrUtil.isBlank(machine.getPrintType())) {
throw new RuntimeException("打印机配置为空");
}
// 订单打印
if (StrUtil.isNotBlank(printMethod)) {
// 查询订单详情
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
//仅打印后厨-一菜一品
switch (printMethod) {
case "one" -> onlyKitchen(machine, orderInfo, orderDetailList);
case "normal" ->
//仅打印前台
onlyFrontDesk(machine, orderInfo, orderDetailList);
case "all" -> {
//全部打印 前台+后厨
onlyFrontDesk(machine, orderInfo, orderDetailList);
onlyKitchen(machine, orderInfo, orderDetailList);
}
default -> log.warn("未知打印类型: {}", printMethod);
}
}
if (StrUtil.isBlank(machine.getPrintType())) {
return;
}
JSONArray options = JSONArray.parseArray(machine.getPrintType());
if (options == null || options.isEmpty()) {
log.warn("打印机: {}, 未配置: print_type", machine.getId());
return;
}
//是否包含排队取号
if (!options.contains("queue")) {
log.warn("打印机: {}, 此打印机不包含排队叫号打印", machine.getId());
return;
}
// if (printDTO == null) {
// return;
// }
// callNumPrint(machine, printDTO);
}
/**
* 仅打印制作单厨房
*/
private void onlyKitchen(PrintMachine machine, OrderInfo orderInfo, List<OrderDetail> tbOrderDetailList) {
List<?> categoryIds = JSONObject.parseObject(StrUtil.emptyToDefault(machine.getCategoryIds(), "[]"), List.class);
if (StrUtil.isEmpty(machine.getClassifyPrint())) {
log.error("分类打印是空, classifyPrint: {}", machine.getClassifyPrint());
return;
}
tbOrderDetailList = getCanPrintOrderDetails("1".equals(machine.getClassifyPrint()), orderInfo.getId(), tbOrderDetailList, categoryIds);
tbOrderDetailList.parallelStream().filter(o -> ObjectUtil.defaultIfNull(o.getIsPrint(), 0) == 1).forEach(item -> {
log.info("开始打印菜品,商品名:{}", item.getProductName());
Integer isWaitCall = ObjectUtil.defaultIfNull(item.getIsWaitCall(), 0);
if (isWaitCall == 1) {
if (!item.getProductName().contains("【等叫】")) {
item.setProductName("【等叫】" + item.getProductName());
}
}
Integer isTemporary = ObjectUtil.defaultIfNull(item.getIsTemporary(), 0);
if (isTemporary == 1) {
item.setProductId(0L);
item.setSkuId(0L);
if (!item.getProductName().contains("【临】")) {
item.setProductName("【临】" + item.getProductName());
}
}
boolean isGift = item.getPackAmount().compareTo(BigDecimal.ZERO) == 0;
if (isGift && !item.getProductName().contains("【赠】")) {
item.setProductName("【赠】" + item.getProductName());
}
// 台位费不打印
if (item.getProductId().equals(-999L)) {
log.info("台位费商品,不打印");
return;
}
// ProdSku sku = prodSkuService.getById(item.getSkuId());
// if (isTemporary == 0 && sku == null) {
// log.error("商品不存在, id: {}", item.getSkuId());
// return;
// } else if (isTemporary == 1) {
// sku = new ProdSku();
// }
// String remark = StrUtil.isNotBlank(sku.getSpecInfo()) ? sku.getSpecInfo() : "";
// item.setRemark(remark);
if (ArrayUtil.contains(new String[]{"refunding", "part-refund", "refund"}, item.getStatus())) {
returnDishesPrint(orderInfo, item, machine);
} else {
normalDishesPrint(orderInfo, item, machine);
}
});
}
/**
* 仅打印结算单前台
*/
private void onlyFrontDesk(PrintMachine machine, OrderInfo orderInfo, List<OrderDetail> tbOrderDetailList) {
if (tbOrderDetailList.isEmpty()) {
log.info("待打印列表为空");
return;
}
List<PrintOrderDetailDTO> detailList = new ArrayList<>();
tbOrderDetailList.forEach(it -> {
Integer isTemporary = ObjectUtil.defaultIfNull(it.getIsTemporary(), 0);
String remark = "";
if (isTemporary == 0) {
ProdSku prodSku = prodSkuService.getById(it.getSkuId());
if (ObjectUtil.isNotEmpty(prodSku) && ObjectUtil.isNotEmpty(prodSku.getSpecInfo())) {
remark = prodSku.getSpecInfo();
}
}
Integer isWaitCall = ObjectUtil.defaultIfNull(it.getIsWaitCall(), 0);
if (isWaitCall == 1) {
it.setProductName("【等叫】%s".formatted(it.getProductName()));
}
if (isTemporary == 1) {
it.setProductName("【临】%s".formatted(it.getProductName()));
}
boolean isGift = it.getPackAmount().compareTo(BigDecimal.ZERO) == 0;
if (isGift) {
it.setProductName("【赠】%s".formatted(it.getProductName()));
}
BigDecimal unitPrice = it.getPrice();
if (isGift) {
unitPrice = BigDecimal.ZERO;
}
PrintOrderDetailDTO detail = new PrintOrderDetailDTO(it.getProductName(), it.getNum().toString(), unitPrice.stripTrailingZeros().toPlainString(), remark, it.getProGroupInfo());
detailList.add(detail);
});
String balance = "0";
if ("deposit".equals(orderInfo.getPayType())) {
ShopUser user = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getShopId, orderInfo.getShopId()).eq(ShopUser::getUserId, orderInfo.getUserId()));
if (ObjectUtil.isNotEmpty(user) && ObjectUtil.isNotEmpty(user.getAmount())) {
balance = user.getAmount().toPlainString();
}
}
if (!detailList.isEmpty()) {
if (isReturn(orderInfo)) {
returnOrderPrint(orderInfo, machine, balance, tbOrderDetailList);
} else {
normalOrderPrint(orderInfo, machine, balance, tbOrderDetailList);
}
}
}
/**
* 获取取餐号
*/
protected static String getPickupNum(OrderInfo orderInfo) {
return StrUtil.isBlank(orderInfo.getTableName()) ? orderInfo.getTakeCode() : orderInfo.getTableName();
}
protected static boolean isReturn(OrderInfo orderInfo) {
return ArrayUtil.contains(new String[]{"refunding", "part-refund", "refund"}, orderInfo.getStatus());
}
protected abstract void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine);
protected abstract void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine);
protected abstract void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList);
protected abstract void normalOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList);
protected abstract void callNumPrint(PrintMachine machine);
public static void main(String[] args) {
String a = "1【赠1】1【临1】1【等叫1】1";
if (!a.contains("【赠】")) {
System.out.println("不包含");
}
if (a.indexOf("【临】") != -1) {
System.out.println("包含");
}
if (a.indexOf("【等叫】") != -1) {
System.out.println("包含");
}
}
}

View File

@ -0,0 +1,364 @@
package com.czg.service.account.print;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.czg.order.entity.OrderDetail;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 打印机具体打印方法接口提供打印元数据获取具体打印机实现对应方法即可
*
* @author Administrator
*/
public interface PrinterImpl {
@Data
@Accessors(chain = true)
class PrintSignLabel {
private String[] center;
private String[] centerBold;
private String[] bold;
private String br;
private String[] s;
private String[] f;
private String[] l;
private String[] qr;
private String cut;
}
/**
* 构建订单打印元数据
* @param printInfoDTO 打印信息
* @param detailList 订单详情内容
* @return 元数据
*/
// String buildOrderPrintData(PrinterHandler.PrintInfoDTO printInfoDTO, List<OrderDetail> detailList);
/**
* 构建菜品单打印数据
*
* @param pickupNumber 取餐号
* @param date 时间
* @param productName 商品名
* @param number 数量
* @param remark 备注
* @param proGroupInfo 套餐信息
* @return 元数据
*/
String buildDishPrintData(String pickupNumber, String date, String productName, BigDecimal number, String remark, String proGroupInfo);
/**
* 发送打印请求
*
* @param address 打印地址
* @param metaPrintData 打印元数据
* @param printNum 打印联数
* @param <R> 返回数据类型
* @return 打印结果
*/
<R> R sendPrintRequest(String address, String metaPrintData, String printNum);
PrintSignLabel getSignLabelInfo();
/**
* 分割起始标签 <S>
*
* @return 标签
*/
String getStartSplitSign();
/**
* 分割结束标签 </S>
*
* @return 标签
*/
String getEndSplitSign();
/**
* 获取对齐后的小票明细行数据
* 例如58mm 机器一行打印16个汉字/32个字母80mm机器打印24个汉字/48个字母
*
* @param title 品名
* @param price 单价
* @param num 数量
* @param total 小计
* @param b1 品名占用字节数
* @param b2 单价占用字节数
* @param b3 数量占用字节数
* @param b4 小计占用字节数
* @return 格式化后的行数据<S><BR>标签
*/
default String getRow(String title, String price, String num, String total,
int b1, int b2, int b3, int b4) {
PrintSignLabel signLabelInfo = getSignLabelInfo();
price = addSpace(price, b2);
num = addSpace(num, b3);
total = addSpace(total, b4);
String otherStr = " %s%s %s".formatted(price, num, total);
int titleByteLen = StrUtil.bytes(title, CharsetUtil.CHARSET_GBK).length;
StringBuilder sb = new StringBuilder();
if (titleByteLen <= b1) {
// 品名可以在一行显示直接补足空格
title = titleAddSpace(title, b1);
sb.append(getFormatLabel(title + otherStr, signLabelInfo.s));
// sb.append(startSplitSign).append(title).append(otherStr).append(endSplitSign);
} else {
// 品名超出一行进行拆分
List<String> lines = isEn(title)
// 英文按 b1 个字符拆分
? getStrList(title, b1)
// 中文按 b1/2 个字符拆分
: getStrList(title, b1 / 2);
// 第一行拼接其它字段
String firstLine = titleAddSpace(lines.getFirst(), b1);
sb.append(getFormatLabel(firstLine + otherStr, signLabelInfo.s)).append(signLabelInfo.br);
// sb.append(startSplitSign).append(firstLine).append(otherStr).append(endSplitSign).append("<BR>");
// 剩余的行单独换行输出
for (int i = 1; i < lines.size(); i++) {
sb.append(getFormatLabel(lines.get(i), signLabelInfo.s)).append(signLabelInfo.br);
// sb.append(startSplitSign).append(lines.get(i)).append(endSplitSign).append("BR>");
}
}
sb.append("<BR>");
return sb.toString();
}
default String getFormatLabel(String text, String[]... labels) {
StringBuilder str = new StringBuilder();
for (String[] label : labels) {
str.append(label[0]);
}
str.append(text);
for (int i = labels.length - 1; i >= 0; i--) {
str.append(labels[i][1]);
}
return str.toString();
}
default String buildOrderPrintData(PrinterHandler.PrintInfoDTO printInfoDTO, List<OrderDetail> detailList) {
PrintSignLabel signLabelInfo = getSignLabelInfo();
StringBuilder data = new StringBuilder();
data.append(getFormatLabel(printInfoDTO.getShopName(), signLabelInfo.center, signLabelInfo.f)).append(signLabelInfo.br);
// data.append(StrUtil.format("<C><F>{}</F></C><BR>", printInfoDTO.getShopName()));
// data.append("<BR>");
data.append(signLabelInfo.br);
// data.append("<OUT:30>");
data.append(getFormatLabel(StrUtil.format("{}【{}】", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum()), signLabelInfo.center, signLabelInfo.bold))
.append(signLabelInfo.br)
.append(signLabelInfo.br);
// data.append(StrUtil.format("<C><BOLD>{}【{}】</BOLD></C><BR>", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum()));
//if (Objects.nonNull(printInfoDTO.getOutNumber())) {
// data.append(StrUtil.format("<CB><BOLD>{}</BOLD></CB>",printInfoDTO.getOutNumber()));
//}
// data.append("<OUT:30>");
// data.append("<BR>");
data.append(getFormatLabel(StrUtil.format("订单号:{}", printInfoDTO.getOrderNo()), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>订单号:{}</S><BR>", printInfoDTO.getOrderNo()));
data.append(getFormatLabel(StrUtil.format("交易时间:{}", printInfoDTO.getTradeDate()), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>交易时间:{}</S><BR>", printInfoDTO.getTradeDate()));
data.append(getFormatLabel(StrUtil.format("收银员:{}", printInfoDTO.getOperator()), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>收银员:{}</S><BR>", printInfoDTO.getOperator()));
data.append(signLabelInfo.br);
data.append(signLabelInfo.br);
// data.append("<OUT:15>");
// data.append("<BR>");
data.append(getFormatLabel("品名 数量 小计", signLabelInfo.s))
.append(signLabelInfo.br);
// data.append("<S>品名 数量 小计</S><BR>");
data.append(getFormatLabel("--------------------------------", signLabelInfo.s))
.append(signLabelInfo.br);
// data.append("<S>--------------------------------</S><BR>");
for (OrderDetail detail : detailList) {
String number = detail.getNum().stripTrailingZeros().toPlainString();
String row = getRow(detail.getProductName(), "", number, toPlainStr(detail.getPayAmount().stripTrailingZeros().toPlainString()), 20, 0, 3, 6);
data.append(row);
if (StrUtil.isNotBlank(detail.getSkuName())) {
data.append(getFormatLabel(detail.getSkuName(), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append("<S>规格:").append(detail.getSkuName()).append("</S><BR>");
}
String proGroupInfo = detail.getProGroupInfo();
if (StrUtil.isBlank(proGroupInfo)) {
continue;
}
if (!JSONUtil.isTypeJSONArray(proGroupInfo)) {
continue;
}
JSONArray subItems = cn.hutool.json.JSONUtil.parseArray(proGroupInfo);
for (int i = 0; i < subItems.size(); i++) {
String proName = subItems.getJSONObject(i).getStr("proName");
int qty = subItems.getJSONObject(i).getInt("number");
String subRow = getRow(" - " + proName, "", qty + ".00", "0.00", 20, 0, 3, 6);
data.append(subRow);
}
}
if (ObjectUtil.isNotNull(printInfoDTO.getDiscountAmount())) {
data.append(getFormatLabel("--------------------------------", signLabelInfo.s))
.append(signLabelInfo.br);
// data.append("<S>--------------------------------</S><BR>");
data.append(getFormatLabel(StrUtil.format("原价:{}", toPlainStr(printInfoDTO.getOriginalAmount())), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>原价:{}</S><BR>", toPlainStr(printInfoDTO.getOriginalAmount())));
data.append(getFormatLabel(StrUtil.format("折扣:-{}", toPlainStr(printInfoDTO.getDiscountAmount())), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>折扣:-{}</S><BR>", toPlainStr(printInfoDTO.getDiscountAmount())));
}
data.append(getFormatLabel("--------------------------------", signLabelInfo.s))
.append(signLabelInfo.br);
// data.append("<S>--------------------------------</S><BR>");
String t = "" + printInfoDTO.getPayAmount();
if (printInfoDTO.isReturn()) {
data.append(getFormatLabel(StrUtil.format("应退:{}", t), signLabelInfo.f))
.append(signLabelInfo.br);
// data.append("<F>应退").append(t).append("</F><BR>");
} else {
data.append(getFormatLabel(StrUtil.format("应收:{}", t) , signLabelInfo.f))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<F>应收:{}</F><BR>", t));
}
data.append(getFormatLabel("--------------------------------", signLabelInfo.s))
.append(signLabelInfo.br);
// data.append("<S>--------------------------------</S><BR>");
if (ObjectUtil.isNotEmpty(printInfoDTO.getPayType()) && ObjectUtil.isNotNull(printInfoDTO.getPayType()) && "deposit".equals(printInfoDTO.getPayType())) {
data.append(getFormatLabel(StrUtil.format("储值:{}", toPlainStr(printInfoDTO.getOriginalAmount())), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>储值:{}</S><BR>", toPlainStr(printInfoDTO.getOriginalAmount())));
data.append(getFormatLabel(StrUtil.format("积分:{}", printInfoDTO.getIntegral()), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>积分:{}</S><BR>", printInfoDTO.getIntegral()));
}
data.append(getFormatLabel(StrUtil.format("余额:{}", toPlainStr(printInfoDTO.getBalance())), signLabelInfo.s))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<S>余额:{}</S><BR>", toPlainStr(printInfoDTO.getBalance())));
data.append(getFormatLabel("--------------------------------", signLabelInfo.s))
.append(signLabelInfo.br);
// data.append("<S>--------------------------------</S><BR>");
if (StrUtil.isNotBlank(printInfoDTO.getRemark())) {
data.append(getFormatLabel(StrUtil.format("备注:{}", printInfoDTO.getRemark()), signLabelInfo.l, signLabelInfo.bold))
.append(signLabelInfo.br);
// data.append(StrUtil.format("<L><BOLD>备注:{}</BOLD></L><BR>", printInfoDTO.getRemark()));
}
if (Objects.nonNull(printInfoDTO.getOutNumber())) {
data.append(getFormatLabel(printInfoDTO.getOutNumber(), signLabelInfo.qr))
.append(signLabelInfo.br);
// data.append("<QR>".concat(printInfoDTO.getOutNumber()).concat("</QR><BR>"));
}
data.append(getFormatLabel(StrUtil.format("打印时间:{}", DateUtil.date().toString()), signLabelInfo.s))
.append(signLabelInfo.br)
.append(signLabelInfo.br);
// data.append("<S>打印时间:").append(DateUtil.date().toDateStr()).append("</S><BR>");
// data.append("<OUT:180>");
data.append(signLabelInfo.cut);
// data.append("<PCUT>");
return data.toString();
}
/**
* 按字符数补足空格英文数字时使用
*
* @param str 对应字符串
* @param size 总长度
* @return 填充之后的字符串
*/
default String addSpace(String str, int size) {
return StrUtil.fillAfter(str, ' ', size);
}
/**
* 按固定长度拆分字符串按字符拆分
*
* @param inputString 字符串
* @param length 每个长度
* @return 分割后的集合
*/
default List<String> getStrList(String inputString, int length) {
List<String> list = new ArrayList<>();
for (int i = 0; i < inputString.length(); i += length) {
list.add(StrUtil.sub(inputString, i, Math.min(i + length, inputString.length())));
}
return list;
}
/**
* 判断字符串是否全为英文GBK编码下每个字符均为 1 字节
*
* @param str 字符
* @return 是否英文
*/
default boolean isEn(String str) {
return StrUtil.bytes(str, CharsetUtil.CHARSET_GBK).length == str.length();
}
/**
* 根据 GBK 字节数补空格用于中文排版
*
* @param str 字符串
* @param b1 总长度
* @return 添加间距的字符串
*/
default String titleAddSpace(String str, int b1) {
int byteLen = StrUtil.bytes(str, CharsetUtil.CHARSET_GBK).length;
return StrUtil.fillAfter(str, ' ', b1 - byteLen);
}
/**
* 获取填充字符串, 并且换行
*
* @param length 长度
* @param string 字符串
* @return
*/
default String getStringByEnter(String string, int length) {
StringBuilder result = new StringBuilder();
while (!string.isEmpty()) {
int pos = 0, byteCount = 0;
for (int i = 0; i < string.length(); i++) {
int charByteLen = StrUtil.bytes(string.substring(i, i + 1), CharsetUtil.CHARSET_GBK).length;
if (byteCount + charByteLen > length) break;
byteCount += charByteLen;
pos = i + 1;
}
result.append(getStartSplitSign()).append(string, 0, pos).append(getEndSplitSign());
string = string.substring(pos);
if (!string.isEmpty()) {
result.append("<BR>");
}
}
return result.toString();
}
/**
* 获取规格化的字符串
*
* @param str 字符串
* @return 返回规格化字符串
*/
default String toPlainStr(String str) {
if (StrUtil.isBlank(str)) {
return "0";
}
return NumberUtil.roundDown(new BigDecimal(str), 2).toPlainString();
}
}

View File

@ -0,0 +1,399 @@
package com.czg.service.account.print;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
@Slf4j
@Component
public class YxyPrinter extends PrinterHandler {
//请求地址
private static final String URL_STR = "https://ioe.car900.com/v1/openApi/dev/customPrint.json";
//APPID
private static final String APP_ID = "ZF544";
//USERCODE
private static final String USER_CODE = "ZF544";
//APPSECRET
private static final String APP_SECRET = "2022bsjZF544GAH";
@Resource
private ShopInfoService shopInfoService;
public YxyPrinter() {
super("yxyPrinter");
}
private PrinterImpl.PrintSignLabel printSignLabel = new PrinterImpl.PrintSignLabel()
.setBr("<BR>")
.setCut("PCUT")
.setF(new String[]{"<F>", "</F>"})
.setL(new String[]{"<L>", "</L>"})
.setS(new String[]{"<S>", "</S>"})
.setQr(new String[]{"<QR>", "</QR>"})
.setCenter(new String[]{"<CB>", "</CB>"})
.setBold(new String[]{"<BOLD>", "</BOLD>"});
public PrinterImpl.PrintSignLabel getSignLabelInfo() {
return printSignLabel;
}
/**
* 打印票据
*/
public static String printTickets(String voiceJson, Integer actWay, Integer cn, String devName, String data) {
log.info("开始请求云享印,请求数据:{}, {}", voiceJson, data);
//设备名称
//行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息
// actWay = 3;
// //打印联数
// int cn = 1;
//打印内容
//播报语音数据体,字段参考文档IOT_XY_API11001
String time = String.valueOf(System.currentTimeMillis());
String uuid = UUID.randomUUID().toString();
Map<String, String> param = getToken(time, uuid);
//参数
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("token", param.get("TOKEN"));
multiValueMap.add("devName", devName);
multiValueMap.add("actWay", actWay);
multiValueMap.add("cn", cn);
multiValueMap.add("data", data);
multiValueMap.add("voiceJson", voiceJson);
multiValueMap.add("appId", APP_ID);
multiValueMap.add("timestamp", time);
multiValueMap.add("requestId", uuid);
multiValueMap.add("userCode", USER_CODE);
RestTemplate restTemplate = new RestTemplate();
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(multiValueMap, header);
String httpResponse = restTemplate.postForObject(URL_STR,
httpEntity, String.class);
log.info("请求云享印成功,响应数据: {}", httpResponse);
return httpResponse;
}
@Override
protected void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
// todo 修改为bigdecimal
String data = getPrintData("", getPickupNum(orderInfo),
DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(),
orderDetail.getNum().intValue(), orderDetail.getSkuName(), orderDetail.getRemark(), orderDetail.getProGroupInfo());
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
printTickets(voiceJson, 3, 1, machine.getAddress(), data);
}
@Override
protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
}
@Override
protected void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList) {
}
@Override
protected void normalOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList) {
ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId());
PrintInfoDTO printInfoDTO = new PrintInfoDTO().setShopName(shopInfo.getShopName()).setPrintType("普通打印").setPickupNum(getPickupNum(orderInfo))
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.date().toDateStr()).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
.setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo))
.setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0")
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle("结算单")
.setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
String data = getCashPrintData(printInfoDTO, detailList);
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
String printerNum = "1";
if (StrUtil.isNotBlank(machine.getPrintQty())) {
printerNum = machine.getPrintQty().split("\\^")[1];
}
String resp = printTickets(voiceJson, 3, Integer.valueOf(printerNum), machine.getAddress(), data);
// shopPrintLogService.save(machine, printType, data, resp);
}
@Override
protected void callNumPrint(PrintMachine machine) {
}
/**
* 菜品票打印
*
* @param type 是否退菜单
* @param pickupNumber 取餐号
* @param date 时间
* @param productName 商品名
* @param number 数量
* @param skuName sku规格名
* @param note 备注
*/
public static String getPrintData(String type, String pickupNumber, String date, String productName, Integer number, String skuName, String note, String proGroupInfo) {
StringBuilder builder = new StringBuilder();
if ("return".equals(type)) {
builder.append("<C><B>").append(pickupNumber).append("【退】</B></C><BR><BR>");
} else {
builder.append("<C><B>").append(pickupNumber).append("</B></C><BR><BR>");
}
builder.append("<S><L>时间: ").append(date).append(" </L></S><BR><BR><BR>");
skuName = StrUtil.emptyToDefault(skuName, "");
if (productName.length() > 4 || skuName.length() > 4) {
builder.append("<CS:32>").append(productName).append(" x ").append(number).append("</CS><BR>");
if (StrUtil.isNotBlank(skuName)) {
builder.append("<CS:32>").append(skuName).append(" </CS><BR>");
}
} else {
builder.append("<B>").append(productName).append(" x ").append(number).append("</B><BR>");
if (StrUtil.isNotBlank(skuName)) {
builder.append("<B>").append(skuName).append(" </B><BR>");
}
}
if (StrUtil.isNotBlank(note)) {
builder.append("<S><L>备注: ").append(note).append(" </L></S><BR>");
}
if (!StrUtil.isBlank(proGroupInfo) && JSONUtil.isTypeJSONArray(proGroupInfo)) {
JSONArray subItems = cn.hutool.json.JSONUtil.parseArray(proGroupInfo);
for (int i = 0; i < subItems.size(); i++) {
String proName = subItems.getJSONObject(i).getStr("proName");
int qty = subItems.getJSONObject(i).getInt("number");
builder.append("<CS:32>(").append(i + 1).append(")").append(proName).append(" x ").append(qty).append("</CS><BR>");
}
}
builder.append("<OUT:150>");
builder.append("<PCUT>");
return builder.toString();
}
/**
* 获取TOKEN值
*
* @param timestamp 时间戳13位
* @param requestId 请求ID自定义
* @return token信息
*/
private static Map<String, String> getToken(String timestamp, String requestId) {
StringBuilder token = new StringBuilder();
StringBuilder encode = new StringBuilder();
SortedMap<String, Object> map = new TreeMap<>();
map.put("appId", APP_ID);
map.put("timestamp", timestamp);
map.put("requestId", requestId);
map.put("userCode", USER_CODE);
for (Map.Entry<String, Object> next : map.entrySet()) {
String key = next.getKey();
Object value = next.getValue();
token.append(key).append(value);
encode.append(key).append("=").append(value).append("&");
}
Map<String, String> finalMap = new HashMap<>();
finalMap.put("ENCODE", encode.toString());
finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
return finalMap;
}
// 按字符数补足空格英文数字时使用
public static String addSpace(String str, int size) {
return StrUtil.fillAfter(str, ' ', size);
}
// 按固定长度拆分字符串按字符拆分
public static List<String> getStrList(String inputString, int length) {
List<String> list = new ArrayList<>();
for (int i = 0; i < inputString.length(); i += length) {
list.add(StrUtil.sub(inputString, i, Math.min(i + length, inputString.length())));
}
return list;
}
// 根据 GBK 字节数补空格用于中文排版
public static String titleAddSpace(String str, int b1) {
int byteLen = StrUtil.bytes(str, CharsetUtil.CHARSET_GBK).length;
return StrUtil.fillAfter(str, ' ', b1 - byteLen);
}
// 判断字符串是否全为英文GBK编码下每个字符均为 1 字节
public static boolean isEn(String str) {
return StrUtil.bytes(str, CharsetUtil.CHARSET_GBK).length == str.length();
}
// 将字符串按 GBK 字节长度拆分每段包装在 <S> 标签内并用 <BR> 换行
public static String getStringByEnter(int length, String string) {
StringBuilder result = new StringBuilder();
while (!string.isEmpty()) {
int pos = 0, byteCount = 0;
for (int i = 0; i < string.length(); i++) {
int charByteLen = StrUtil.bytes(string.substring(i, i + 1), CharsetUtil.CHARSET_GBK).length;
if (byteCount + charByteLen > length) break;
byteCount += charByteLen;
pos = i + 1;
}
result.append("<S>").append(string, 0, pos).append("</S>");
string = string.substring(pos);
if (!string.isEmpty()) {
result.append("<BR>");
}
}
return result.toString();
}
/**
* 获取对齐后的小票明细行数据
* 例如58mm 机器一行打印16个汉字/32个字母80mm机器打印24个汉字/48个字母
*
* @param title 品名
* @param price 单价
* @param num 数量
* @param total 小计
* @param b1 品名占用字节数
* @param b2 单价占用字节数
* @param b3 数量占用字节数
* @param b4 小计占用字节数
* @return 格式化后的行数据<S><BR>标签
*/
public static String getRow(String title, String price, String num, String total,
int b1, int b2, int b3, int b4) {
price = addSpace(price, b2);
num = addSpace(num, b3);
total = addSpace(total, b4);
String otherStr = " %s%s %s".formatted(price, num, total);
int titleByteLen = StrUtil.bytes(title, CharsetUtil.CHARSET_GBK).length;
StringBuilder sb = new StringBuilder();
if (titleByteLen <= b1) {
// 品名可以在一行显示直接补足空格
title = titleAddSpace(title, b1);
sb.append("<S>").append(title).append(otherStr).append("</S>");
} else {
// 品名超出一行进行拆分
List<String> lines = isEn(title)
// 英文按 b1 个字符拆分
? getStrList(title, b1)
// 中文按 b1/2 个字符拆分
: getStrList(title, b1 / 2);
// 第一行拼接其它字段
String firstLine = titleAddSpace(lines.getFirst(), b1);
sb.append("<S>").append(firstLine).append(otherStr).append("</S><BR>");
// 剩余的行单独换行输出
for (int i = 1; i < lines.size(); i++) {
sb.append("<S>").append(lines.get(i)).append("</S><BR>");
}
}
sb.append("<BR>");
return sb.toString();
}
private static String toPlainStr(String str) {
if (StrUtil.isBlank(str)) {
return "0";
}
return NumberUtil.roundDown(new BigDecimal(str), 2).toPlainString();
}
public static String getCashPrintData(PrintInfoDTO printInfoDTO, List<OrderDetail> detailList) {
StringBuilder data = new StringBuilder();
data.append(StrUtil.format("<C><F>{}</F></C><BR>", printInfoDTO.getShopName()));
data.append("<BR>");
data.append("<OUT:30>");
data.append(StrUtil.format("<C><BOLD>{}【{}】</BOLD></C><BR>", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum()));
//if (Objects.nonNull(printInfoDTO.getOutNumber())) {
// data.append(StrUtil.format("<CB><BOLD>{}</BOLD></CB>",printInfoDTO.getOutNumber()));
//}
data.append("<OUT:30>");
data.append("<BR>");
data.append(StrUtil.format("<S>订单号:{}</S><BR>", printInfoDTO.getOrderNo()));
data.append(StrUtil.format("<S>交易时间:{}</S><BR>", printInfoDTO.getTradeDate()));
data.append(StrUtil.format("<S>收银员:{}</S><BR>", printInfoDTO.getOperator()));
data.append("<OUT:15>");
data.append("<BR>");
data.append("<S>品名 数量 小计</S><BR>");
data.append("<S>--------------------------------</S><BR>");
for (OrderDetail detail : detailList) {
String number = detail.getNum().stripTrailingZeros().toPlainString();
String row = getRow(detail.getProductName(), "", number, toPlainStr(detail.getPayAmount().stripTrailingZeros().toPlainString()), 20, 0, 3, 6);
data.append(row);
if (StrUtil.isNotBlank(detail.getSkuName())) {
data.append("<S>规格:").append(detail.getSkuName()).append("</S><BR>");
}
String proGroupInfo = detail.getProGroupInfo();
if (StrUtil.isBlank(proGroupInfo)) {
continue;
}
if (!JSONUtil.isTypeJSONArray(proGroupInfo)) {
continue;
}
JSONArray subItems = cn.hutool.json.JSONUtil.parseArray(proGroupInfo);
for (int i = 0; i < subItems.size(); i++) {
String proName = subItems.getJSONObject(i).getStr("proName");
int qty = subItems.getJSONObject(i).getInt("number");
String subRow = getRow(" - " + proName, "", qty + ".00", "0.00", 20, 0, 3, 6);
data.append(subRow);
}
}
if (ObjectUtil.isNotNull(printInfoDTO.getDiscountAmount())) {
data.append("<S>--------------------------------</S><BR>");
data.append(StrUtil.format("<S>原价:{}</S><BR>", toPlainStr(printInfoDTO.getOriginalAmount())));
data.append(StrUtil.format("<S>折扣:-{}</S><BR>", toPlainStr(printInfoDTO.getDiscountAmount())));
}
data.append("<S>--------------------------------</S><BR>");
String t = "" + printInfoDTO.getPayAmount();
if (printInfoDTO.isReturn()) {
data.append("<F>应退").append(t).append("</F><BR>");
data.append(StrUtil.format("<F>应收:{}</F><BR>", t));
} else {
data.append(StrUtil.format("<F>应收:{}</F><BR>", t));
}
data.append("<S>--------------------------------</S><BR>");
if (ObjectUtil.isNotEmpty(printInfoDTO.getPayType()) && ObjectUtil.isNotNull(printInfoDTO.getPayType()) && "deposit".equals(printInfoDTO.getPayType())) {
data.append(StrUtil.format("<S>储值:{}</S><BR>", toPlainStr(printInfoDTO.getOriginalAmount())));
data.append(StrUtil.format("<S>积分:{}</S><BR>", printInfoDTO.getIntegral()));
}
data.append(StrUtil.format("<S>余额:{}</S><BR>", toPlainStr(printInfoDTO.getBalance())));
data.append("<S>--------------------------------</S><BR>");
if (StrUtil.isNotBlank(printInfoDTO.getRemark())) {
data.append(StrUtil.format("<L><BOLD>备注:{}</BOLD></L><BR>", printInfoDTO.getRemark()));
}
if (Objects.nonNull(printInfoDTO.getOutNumber())) {
data.append("<QR>".concat(printInfoDTO.getOutNumber()).concat("</QR><BR>"));
}
data.append("<S>打印时间:").append(DateUtil.date().toDateStr()).append("</S><BR>");
data.append("<OUT:180>");
data.append("<PCUT>");
return data.toString();
}
}

View File

@ -0,0 +1,18 @@
package com.czg.service.account.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.account.entity.MqLog;
import com.czg.account.service.MqLogService;
import com.czg.service.account.mapper.MqLogMapper;
import org.springframework.stereotype.Service;
/**
* 服务层实现
*
* @author zs
* @since 2025-02-21
*/
@Service
public class MqLogServiceImpl extends ServiceImpl<MqLogMapper, MqLog> implements MqLogService{
}

View File

@ -4,11 +4,14 @@ import com.czg.account.dto.pad.*;
import com.czg.account.entity.*;
import com.czg.account.service.*;
import com.czg.exception.ApiNotPrintException;
import com.czg.product.entity.Product;
import com.czg.product.service.ProductService;
import com.czg.service.account.mapper.PadProductCategoryDetailMapper;
import com.czg.utils.JoinQueryWrapper;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@ -23,7 +26,7 @@ public class PadProdServiceImpl implements PadProdService {
private PadProductCategoryDetailMapper padProductCategoryDetailMapper;
@Resource
private PadProductCategoryService padProductCategoryService;
@Resource
@DubboReference
private ProductService productService;
@Resource
private ShopProdCategoryService shopProdCategoryService;

View File

@ -1,18 +0,0 @@
package com.czg.service.account.service.impl;
import com.czg.account.entity.Product;
import com.czg.account.service.ProductService;
import com.czg.service.account.mapper.ProductMapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.apache.dubbo.config.annotation.DubboService;
/**
* 商品 服务层实现
*
* @author zs
* @since 2025-02-20
*/
@DubboService
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService{
}

View File

@ -2,12 +2,12 @@ package com.czg.service.account.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.czg.account.dto.ShopCouponDTO;
import com.czg.account.entity.Product;
import com.czg.account.entity.ShopActivateCouponRecord;
import com.czg.account.entity.ShopCoupon;
import com.czg.account.service.ProductService;
import com.czg.account.service.ShopActivateCouponRecordService;
import com.czg.account.service.ShopCouponService;
import com.czg.product.entity.Product;
import com.czg.product.service.ProductService;
import com.czg.sa.StpKit;
import com.czg.service.account.mapper.ShopCouponMapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.account.mapper.MqLogMapper">
</mapper>

View File

@ -4,6 +4,7 @@ import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.order.entity.OrderDetail;
import com.czg.order.service.OrderDetailService;
import com.czg.service.order.mapper.OrderDetailMapper;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
import java.util.List;
@ -15,6 +16,7 @@ import java.util.List;
* @since 2025-02-13
*/
@Service
@DubboService
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService{
@Override

View File

@ -44,6 +44,7 @@ import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -60,6 +61,7 @@ import java.util.*;
* @since 2025-02-13
*/
@Slf4j
@DubboService
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

View File

@ -4,6 +4,7 @@ import com.czg.product.entity.ProdSku;
import com.czg.product.service.ProdSkuService;
import com.czg.service.product.mapper.ProdSkuMapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
/**
@ -13,6 +14,7 @@ import org.springframework.stereotype.Service;
* @since 1.0 2025-02-16
*/
@Service
@DubboService
public class ProdSkuServiceImpl extends ServiceImpl<ProdSkuMapper, ProdSku> implements ProdSkuService {
}
}

View File

@ -35,6 +35,7 @@ import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -58,6 +59,7 @@ import static com.czg.product.entity.table.ShopProdUnitTableDef.SHOP_PROD_UNIT;
@Slf4j
@AllArgsConstructor
@Service
@DubboService
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
private final ProdSkuMapper prodSkuMapper;
@ -319,4 +321,4 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
.eq(Product::getShopId, shopId)
.update();
}
}
}