From 14a732e4b51ff86080402720899b017edeed4745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=9D=BE?= <8605635+zhang3064194730@user.noreply.gitee.com> Date: Tue, 25 Feb 2025 14:42:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=93=E5=8D=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/czg/mq/PrintMqListener.java | 107 +++++ .../main/java/com/czg/ProductApplication.java | 2 + .../com/czg/config/RestTemplateConfig.java | 18 + .../main/java/com/czg/config/RedisCst.java | 9 +- .../java/com/czg/account/dto/MqLogDTO.java | 72 ++++ .../com/czg/account/dto/OrderDetailDTO.java | 156 +++++++ .../com/czg/account/dto/OrderInfoDTO.java | 262 ++++++++++++ .../czg/account/dto/PrintOrderDetailDTO.java | 14 + .../com/czg/account/dto/pad/PadDetailDTO.java | 2 +- .../java/com/czg/account/entity/MqLog.java | 83 ++++ .../java/com/czg/account/entity/Product.java | 193 --------- .../com/czg/account/service/MqLogService.java | 14 + .../czg/account/service/ProductService.java | 14 - cash-service/account-service/pom.xml | 6 + .../service/account/mapper/MqLogMapper.java | 14 + .../service/account/mapper/ProductMapper.java | 2 +- .../czg/service/account/print/FeiPrinter.java | 268 ++++++++++++ .../service/account/print/PrintConfig.java | 42 ++ .../service/account/print/PrinterHandler.java | 355 ++++++++++++++++ .../service/account/print/PrinterImpl.java | 364 ++++++++++++++++ .../czg/service/account/print/YxyPrinter.java | 399 ++++++++++++++++++ .../service/impl/MqLogServiceImpl.java | 18 + .../service/impl/PadProdServiceImpl.java | 5 +- .../service/impl/ProductServiceImpl.java | 18 - .../service/impl/ShopCouponServiceImpl.java | 4 +- .../src/main/resources/mapper/MqLogMapper.xml | 7 + .../service/impl/OrderDetailServiceImpl.java | 2 + .../service/impl/OrderInfoServiceImpl.java | 2 + .../service/impl/ProdSkuServiceImpl.java | 4 +- .../service/impl/ProductServiceImpl.java | 4 +- 30 files changed, 2227 insertions(+), 233 deletions(-) create mode 100644 cash-api/account-server/src/main/java/com/czg/mq/PrintMqListener.java create mode 100644 cash-common/cash-common-api-config/src/main/java/com/czg/config/RestTemplateConfig.java create mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/dto/MqLogDTO.java create mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderDetailDTO.java create mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderInfoDTO.java create mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/dto/PrintOrderDetailDTO.java create mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/entity/MqLog.java delete mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/entity/Product.java create mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/service/MqLogService.java delete mode 100644 cash-common/cash-common-service/src/main/java/com/czg/account/service/ProductService.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/mapper/MqLogMapper.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/print/FeiPrinter.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/print/PrintConfig.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterHandler.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterImpl.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/print/YxyPrinter.java create mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/service/impl/MqLogServiceImpl.java delete mode 100644 cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ProductServiceImpl.java create mode 100644 cash-service/account-service/src/main/resources/mapper/MqLogMapper.xml diff --git a/cash-api/account-server/src/main/java/com/czg/mq/PrintMqListener.java b/cash-api/account-server/src/main/java/com/czg/mq/PrintMqListener.java new file mode 100644 index 00000000..02e70826 --- /dev/null +++ b/cash-api/account-server/src/main/java/com/czg/mq/PrintMqListener.java @@ -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 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 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; + + } +} diff --git a/cash-api/product-server/src/main/java/com/czg/ProductApplication.java b/cash-api/product-server/src/main/java/com/czg/ProductApplication.java index 649dfdec..1d2ba35f 100644 --- a/cash-api/product-server/src/main/java/com/czg/ProductApplication.java +++ b/cash-api/product-server/src/main/java/com/czg/ProductApplication.java @@ -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 { diff --git a/cash-common/cash-common-api-config/src/main/java/com/czg/config/RestTemplateConfig.java b/cash-common/cash-common-api-config/src/main/java/com/czg/config/RestTemplateConfig.java new file mode 100644 index 00000000..5d64767b --- /dev/null +++ b/cash-common/cash-common-api-config/src/main/java/com/czg/config/RestTemplateConfig.java @@ -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(); + } +} + diff --git a/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java b/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java index c85bdd84..a4853f94 100644 --- a/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java +++ b/cash-common/cash-common-redis/src/main/java/com/czg/config/RedisCst.java @@ -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; + } } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/MqLogDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/MqLogDTO.java new file mode 100644 index 00000000..efe0483e --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/MqLogDTO.java @@ -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; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderDetailDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderDetailDTO.java new file mode 100644 index 00000000..f60e23ff --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderDetailDTO.java @@ -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; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderInfoDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderInfoDTO.java new file mode 100644 index 00000000..3456ed3f --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/OrderInfoDTO.java @@ -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; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/PrintOrderDetailDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/PrintOrderDetailDTO.java new file mode 100644 index 00000000..d6e9052f --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/PrintOrderDetailDTO.java @@ -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; +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/pad/PadDetailDTO.java b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/pad/PadDetailDTO.java index a7d3fb39..f9916d5c 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/dto/pad/PadDetailDTO.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/dto/pad/PadDetailDTO.java @@ -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; diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/MqLog.java b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/MqLog.java new file mode 100644 index 00000000..764fe5ba --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/MqLog.java @@ -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; + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/Product.java b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/Product.java deleted file mode 100644 index 6a7c97e4..00000000 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/Product.java +++ /dev/null @@ -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; - -} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/service/MqLogService.java b/cash-common/cash-common-service/src/main/java/com/czg/account/service/MqLogService.java new file mode 100644 index 00000000..08ccac5d --- /dev/null +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/service/MqLogService.java @@ -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 { + +} diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/service/ProductService.java b/cash-common/cash-common-service/src/main/java/com/czg/account/service/ProductService.java deleted file mode 100644 index 73c755bf..00000000 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/service/ProductService.java +++ /dev/null @@ -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 { - -} diff --git a/cash-service/account-service/pom.xml b/cash-service/account-service/pom.xml index 62cc52e2..3dce1540 100644 --- a/cash-service/account-service/pom.xml +++ b/cash-service/account-service/pom.xml @@ -23,6 +23,12 @@ easy-captcha + + com.czg + cash-common-mq + 1.0.0 + + com.czg cash-common-tools diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/mapper/MqLogMapper.java b/cash-service/account-service/src/main/java/com/czg/service/account/mapper/MqLogMapper.java new file mode 100644 index 00000000..01f5e442 --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/mapper/MqLogMapper.java @@ -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 { + +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/mapper/ProductMapper.java b/cash-service/account-service/src/main/java/com/czg/service/account/mapper/ProductMapper.java index 0dc15e4b..3bd7f1df 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/mapper/ProductMapper.java +++ b/cash-service/account-service/src/main/java/com/czg/service/account/mapper/ProductMapper.java @@ -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; /** * 商品 映射层。 diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/print/FeiPrinter.java b/cash-service/account-service/src/main/java/com/czg/service/account/print/FeiPrinter.java new file mode 100644 index 00000000..8564c31b --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/print/FeiPrinter.java @@ -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 detailList) { + log.warn("退单打印功能未实现"); + } + + @Override + protected void normalOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List 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("
") + .setCut("PCUT") + .setF(new String[]{"", ""}) + .setL(new String[]{"", ""}) + .setS(new String[]{"", ""}) + .setF(new String[]{"", ""}) + .setQr(new String[]{"", ""}) + .setCenter(new String[]{"", ""}) + .setBold(new String[]{"", ""}); + @Override + public PrintSignLabel getSignLabelInfo() { + return printSignLabel; + } + + @Override + public String buildOrderPrintData(PrintInfoDTO printInfoDTO, List detailList) { + StringBuilder data = new StringBuilder(); + data.append(StrUtil.format("{}
", printInfoDTO.getShopName())); + data.append("
"); + data.append(StrUtil.format("{}【{}】
", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum())); + data.append("
"); + data.append(StrUtil.format("订单号:{}
", printInfoDTO.getOrderNo())); + data.append(StrUtil.format("交易时间:{}
", printInfoDTO.getTradeDate())); + data.append(StrUtil.format("收银员:{}
", printInfoDTO.getOperator())); + data.append("
"); + data.append("品名 数量 小计
"); + data.append("--------------------------------
"); + 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("
"); + } + 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("--------------------------------
"); + data.append(StrUtil.format("原价:{}
", toPlainStr(printInfoDTO.getOriginalAmount()))); + data.append(StrUtil.format("折扣:-{}
", toPlainStr(printInfoDTO.getDiscountAmount()))); + } + data.append("--------------------------------
"); + String t = "¥" + printInfoDTO.getPayAmount(); + if (printInfoDTO.isReturn()) { + data.append(StrUtil.format("应退:{}
", t)); + } else { + data.append(StrUtil.format("应收:{}
", t)); + } + data.append("--------------------------------
"); + if (ObjectUtil.isNotEmpty(printInfoDTO.getPayType()) && ObjectUtil.isNotNull(printInfoDTO.getPayType()) && printInfoDTO.getPayType().equals("deposit")) { + data.append(StrUtil.format("储值:{}
", toPlainStr(printInfoDTO.getOriginalAmount()))); + data.append(StrUtil.format("积分:{}
", printInfoDTO.getIntegral())); + } + data.append(StrUtil.format("余额:{}
", toPlainStr(printInfoDTO.getBalance()))); + data.append("--------------------------------
"); + if (StrUtil.isNotBlank(printInfoDTO.getRemark())) { + data.append(StrUtil.format("备注:{}
", printInfoDTO.getRemark())); + } + data.append("打印时间:").append(DateUtil.date().toDateStr()).append("
"); + data.append(""); + return data.toString(); + } + + /** + * 构建打印内容 + */ + @Override + public String buildDishPrintData(String pickupNumber, String date, String productName, + BigDecimal number, String remark, String proGroupInfo) { + StringBuilder builder = new StringBuilder() + .append("").append(pickupNumber).append("
") + .append("时间: ").append(date).append("
") + .append("").append(productName).append(" x ") + .append(number.stripTrailingZeros().toPlainString()).append("
") + .append("").append(StrUtil.emptyToDefault(remark, "")).append("
"); + + if (StrUtil.isNotBlank(proGroupInfo) && JSONUtil.isTypeJSONArray(proGroupInfo)) { + JSONArray subItems = JSONUtil.parseArray(proGroupInfo); + for (int i = 0; i < subItems.size(); i++) { + builder.append("(").append(i + 1).append(")") + .append(subItems.getJSONObject(i).getStr("proName")) + .append(" x ").append(subItems.getJSONObject(i).getInt("number")) + .append("
"); + } + } + return builder.toString(); + } + + + @Override + public R sendPrintRequest(String address, String metaPrintData, String printerNum) { + log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData); + String time = String.valueOf(System.currentTimeMillis() / 1000); + MultiValueMap 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> 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); + } + } + + +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/print/PrintConfig.java b/cash-service/account-service/src/main/java/com/czg/service/account/print/PrintConfig.java new file mode 100644 index 00000000..8d10d068 --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/print/PrintConfig.java @@ -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 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(); + } + +} + diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterHandler.java b/cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterHandler.java new file mode 100644 index 00000000..a664aa6b --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterHandler.java @@ -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 getCanPrintOrderDetails(boolean partPrint, Long orderId, List tbOrderDetailList, List categoryIds) { + List detailList = redisService.lGet(RedisCst.getPrintOrderDetailKey(orderId), 0, -1); + Map 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 productIds = tbOrderDetailList.stream().map(OrderDetail::getProductId).collect(Collectors.toList()); + + Map 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 orderDetails = new ArrayList<>(); + ArrayList 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 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 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 tbOrderDetailList) { + if (tbOrderDetailList.isEmpty()) { + log.info("待打印列表为空"); + return; + } + List 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 detailList); + + protected abstract void normalOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List 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("包含"); + } + } +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterImpl.java new file mode 100644 index 00000000..f7155563 --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/print/PrinterImpl.java @@ -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 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 返回数据类型 + * @return 打印结果 + */ + R sendPrintRequest(String address, String metaPrintData, String printNum); + + PrintSignLabel getSignLabelInfo(); + + /** + * 分割起始标签 如 + * + * @return 标签 + */ + String getStartSplitSign(); + + /** + * 分割结束标签 + * + * @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 格式化后的行数据(带
标签) + */ + 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 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("
"); + + // 剩余的行单独换行输出 + 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("
"); + 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 detailList) { + PrintSignLabel signLabelInfo = getSignLabelInfo(); + StringBuilder data = new StringBuilder(); + data.append(getFormatLabel(printInfoDTO.getShopName(), signLabelInfo.center, signLabelInfo.f)).append(signLabelInfo.br); +// data.append(StrUtil.format("{}
", printInfoDTO.getShopName())); +// data.append("
"); + data.append(signLabelInfo.br); +// data.append(""); + data.append(getFormatLabel(StrUtil.format("{}【{}】", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum()), signLabelInfo.center, signLabelInfo.bold)) + .append(signLabelInfo.br) + .append(signLabelInfo.br); +// data.append(StrUtil.format("{}【{}】
", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum())); + //if (Objects.nonNull(printInfoDTO.getOutNumber())) { + // data.append(StrUtil.format("{}",printInfoDTO.getOutNumber())); + //} +// data.append(""); +// data.append("
"); + data.append(getFormatLabel(StrUtil.format("订单号:{}", printInfoDTO.getOrderNo()), signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("订单号:{}
", printInfoDTO.getOrderNo())); + data.append(getFormatLabel(StrUtil.format("交易时间:{}", printInfoDTO.getTradeDate()), signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("交易时间:{}
", printInfoDTO.getTradeDate())); + data.append(getFormatLabel(StrUtil.format("收银员:{}", printInfoDTO.getOperator()), signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("收银员:{}
", printInfoDTO.getOperator())); + data.append(signLabelInfo.br); + data.append(signLabelInfo.br); +// data.append(""); +// data.append("
"); + data.append(getFormatLabel("品名 数量 小计", signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append("品名 数量 小计
"); + data.append(getFormatLabel("--------------------------------", signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append("--------------------------------
"); + 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("规格:").append(detail.getSkuName()).append("
"); + } + 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("--------------------------------
"); + data.append(getFormatLabel(StrUtil.format("原价:{}", toPlainStr(printInfoDTO.getOriginalAmount())), signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("原价:{}
", toPlainStr(printInfoDTO.getOriginalAmount()))); + data.append(getFormatLabel(StrUtil.format("折扣:-{}", toPlainStr(printInfoDTO.getDiscountAmount())), signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("折扣:-{}
", toPlainStr(printInfoDTO.getDiscountAmount()))); + } + data.append(getFormatLabel("--------------------------------", signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append("--------------------------------
"); + String t = "¥" + printInfoDTO.getPayAmount(); + if (printInfoDTO.isReturn()) { + data.append(getFormatLabel(StrUtil.format("应退:{}", t), signLabelInfo.f)) + .append(signLabelInfo.br); +// data.append("应退").append(t).append("
"); + } else { + data.append(getFormatLabel(StrUtil.format("应收:{}", t) , signLabelInfo.f)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("应收:{}
", t)); + } + data.append(getFormatLabel("--------------------------------", signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append("--------------------------------
"); + 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("储值:{}
", toPlainStr(printInfoDTO.getOriginalAmount()))); + data.append(getFormatLabel(StrUtil.format("积分:{}", printInfoDTO.getIntegral()), signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("积分:{}
", printInfoDTO.getIntegral())); + } + data.append(getFormatLabel(StrUtil.format("余额:{}", toPlainStr(printInfoDTO.getBalance())), signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("余额:{}
", toPlainStr(printInfoDTO.getBalance()))); + data.append(getFormatLabel("--------------------------------", signLabelInfo.s)) + .append(signLabelInfo.br); +// data.append("--------------------------------
"); + if (StrUtil.isNotBlank(printInfoDTO.getRemark())) { + data.append(getFormatLabel(StrUtil.format("备注:{}", printInfoDTO.getRemark()), signLabelInfo.l, signLabelInfo.bold)) + .append(signLabelInfo.br); +// data.append(StrUtil.format("备注:{}
", printInfoDTO.getRemark())); + } + if (Objects.nonNull(printInfoDTO.getOutNumber())) { + data.append(getFormatLabel(printInfoDTO.getOutNumber(), signLabelInfo.qr)) + .append(signLabelInfo.br); +// data.append("".concat(printInfoDTO.getOutNumber()).concat("
")); + } + data.append(getFormatLabel(StrUtil.format("打印时间:{}", DateUtil.date().toString()), signLabelInfo.s)) + .append(signLabelInfo.br) + .append(signLabelInfo.br); +// data.append("打印时间:").append(DateUtil.date().toDateStr()).append("
"); +// data.append(""); + data.append(signLabelInfo.cut); +// data.append(""); + 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 getStrList(String inputString, int length) { + List 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("
"); + } + } + 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(); + } +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/print/YxyPrinter.java b/cash-service/account-service/src/main/java/com/czg/service/account/print/YxyPrinter.java new file mode 100644 index 00000000..b6333317 --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/print/YxyPrinter.java @@ -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("
") + .setCut("PCUT") + .setF(new String[]{"", ""}) + .setL(new String[]{"", ""}) + .setS(new String[]{"", ""}) + .setQr(new String[]{"", ""}) + .setCenter(new String[]{"", ""}) + .setBold(new String[]{"", ""}); + 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 param = getToken(time, uuid); + //参数 + MultiValueMap 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> 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 detailList) { + + } + + @Override + protected void normalOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List 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("").append(pickupNumber).append("【退】

"); + } else { + builder.append("").append(pickupNumber).append("

"); + } + builder.append("时间: ").append(date).append("


"); + skuName = StrUtil.emptyToDefault(skuName, ""); + if (productName.length() > 4 || skuName.length() > 4) { + builder.append("").append(productName).append(" x ").append(number).append("
"); + if (StrUtil.isNotBlank(skuName)) { + builder.append("").append(skuName).append("
"); + } + } else { + builder.append("").append(productName).append(" x ").append(number).append("
"); + if (StrUtil.isNotBlank(skuName)) { + builder.append("").append(skuName).append("
"); + } + } + if (StrUtil.isNotBlank(note)) { + builder.append("备注: ").append(note).append("
"); + } + 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("(").append(i + 1).append(")").append(proName).append(" x ").append(qty).append("
"); + } + } + builder.append(""); + builder.append(""); + return builder.toString(); + } + + /** + * 获取TOKEN值 + * + * @param timestamp 时间戳,13位 + * @param requestId 请求ID,自定义 + * @return token信息 + */ + private static Map getToken(String timestamp, String requestId) { + StringBuilder token = new StringBuilder(); + StringBuilder encode = new StringBuilder(); + SortedMap map = new TreeMap<>(); + map.put("appId", APP_ID); + map.put("timestamp", timestamp); + map.put("requestId", requestId); + map.put("userCode", USER_CODE); + for (Map.Entry next : map.entrySet()) { + String key = next.getKey(); + Object value = next.getValue(); + token.append(key).append(value); + encode.append(key).append("=").append(value).append("&"); + } + Map 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 getStrList(String inputString, int length) { + List 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 字节长度拆分,每段包装在 标签内,并用
换行 + 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("").append(string, 0, pos).append(""); + string = string.substring(pos); + if (!string.isEmpty()) { + result.append("
"); + } + } + 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 格式化后的行数据(带
标签) + */ + 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("").append(title).append(otherStr).append(""); + } else { + // 品名超出一行,进行拆分 + List lines = isEn(title) + // 英文按 b1 个字符拆分 + ? getStrList(title, b1) + // 中文按 b1/2 个字符拆分 + : getStrList(title, b1 / 2); + + // 第一行拼接其它字段 + String firstLine = titleAddSpace(lines.getFirst(), b1); + sb.append("").append(firstLine).append(otherStr).append("
"); + + // 剩余的行单独换行输出 + for (int i = 1; i < lines.size(); i++) { + sb.append("").append(lines.get(i)).append("
"); + } + } + sb.append("
"); + 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 detailList) { + StringBuilder data = new StringBuilder(); + data.append(StrUtil.format("{}
", printInfoDTO.getShopName())); + data.append("
"); + data.append(""); + data.append(StrUtil.format("{}【{}】
", printInfoDTO.getPrintTitle(), printInfoDTO.getPickupNum())); + //if (Objects.nonNull(printInfoDTO.getOutNumber())) { + // data.append(StrUtil.format("{}",printInfoDTO.getOutNumber())); + //} + data.append(""); + data.append("
"); + data.append(StrUtil.format("订单号:{}
", printInfoDTO.getOrderNo())); + data.append(StrUtil.format("交易时间:{}
", printInfoDTO.getTradeDate())); + data.append(StrUtil.format("收银员:{}
", printInfoDTO.getOperator())); + data.append(""); + data.append("
"); + data.append("品名 数量 小计
"); + data.append("--------------------------------
"); + 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("规格:").append(detail.getSkuName()).append("
"); + } + 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("--------------------------------
"); + data.append(StrUtil.format("原价:{}
", toPlainStr(printInfoDTO.getOriginalAmount()))); + data.append(StrUtil.format("折扣:-{}
", toPlainStr(printInfoDTO.getDiscountAmount()))); + } + data.append("--------------------------------
"); + String t = "¥" + printInfoDTO.getPayAmount(); + if (printInfoDTO.isReturn()) { + data.append("应退").append(t).append("
"); + data.append(StrUtil.format("应收:{}
", t)); + } else { + data.append(StrUtil.format("应收:{}
", t)); + } + data.append("--------------------------------
"); + if (ObjectUtil.isNotEmpty(printInfoDTO.getPayType()) && ObjectUtil.isNotNull(printInfoDTO.getPayType()) && "deposit".equals(printInfoDTO.getPayType())) { + data.append(StrUtil.format("储值:{}
", toPlainStr(printInfoDTO.getOriginalAmount()))); + data.append(StrUtil.format("积分:{}
", printInfoDTO.getIntegral())); + } + data.append(StrUtil.format("余额:{}
", toPlainStr(printInfoDTO.getBalance()))); + data.append("--------------------------------
"); + if (StrUtil.isNotBlank(printInfoDTO.getRemark())) { + data.append(StrUtil.format("备注:{}
", printInfoDTO.getRemark())); + } + if (Objects.nonNull(printInfoDTO.getOutNumber())) { + data.append("".concat(printInfoDTO.getOutNumber()).concat("
")); + } + data.append("打印时间:").append(DateUtil.date().toDateStr()).append("
"); + data.append(""); + data.append(""); + return data.toString(); + } +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/MqLogServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/MqLogServiceImpl.java new file mode 100644 index 00000000..af374caa --- /dev/null +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/MqLogServiceImpl.java @@ -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 implements MqLogService{ + +} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/PadProdServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/PadProdServiceImpl.java index 8bce7c73..cd84784b 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/PadProdServiceImpl.java +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/PadProdServiceImpl.java @@ -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; diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ProductServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ProductServiceImpl.java deleted file mode 100644 index faa7f299..00000000 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ProductServiceImpl.java +++ /dev/null @@ -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 implements ProductService{ - -} diff --git a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopCouponServiceImpl.java b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopCouponServiceImpl.java index 87da97b9..2a6b4353 100644 --- a/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopCouponServiceImpl.java +++ b/cash-service/account-service/src/main/java/com/czg/service/account/service/impl/ShopCouponServiceImpl.java @@ -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; diff --git a/cash-service/account-service/src/main/resources/mapper/MqLogMapper.xml b/cash-service/account-service/src/main/resources/mapper/MqLogMapper.xml new file mode 100644 index 00000000..015dcce5 --- /dev/null +++ b/cash-service/account-service/src/main/resources/mapper/MqLogMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderDetailServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderDetailServiceImpl.java index d1a7bdf3..99b0d774 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderDetailServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderDetailServiceImpl.java @@ -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 implements OrderDetailService{ @Override diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoServiceImpl.java index 86e53d7f..649acc33 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoServiceImpl.java @@ -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 implements OrderInfoService { diff --git a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProdSkuServiceImpl.java b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProdSkuServiceImpl.java index 0b093787..ae51e224 100644 --- a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProdSkuServiceImpl.java +++ b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProdSkuServiceImpl.java @@ -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 implements ProdSkuService { -} \ No newline at end of file +} diff --git a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java index dfe88bbb..875fbc1e 100644 --- a/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java +++ b/cash-service/product-service/src/main/java/com/czg/service/product/service/impl/ProductServiceImpl.java @@ -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 implements ProductService { private final ProdSkuMapper prodSkuMapper; @@ -319,4 +321,4 @@ public class ProductServiceImpl extends ServiceImpl impl .eq(Product::getShopId, shopId) .update(); } -} \ No newline at end of file +}