package com.czg.service.order.print;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.CallQueue;
import com.czg.account.entity.CallTable;
import com.czg.order.entity.OrderDetail;
import com.czg.print.*;
import lombok.Data;
import lombok.experimental.Accessors;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 打印机具体打印方法接口，提供打印元数据获取，具体打印机实现对应方法即可
 *
 * @author Administrator
 */
public interface PrinterImpl {
    @Data
    @Accessors(chain = true)
    class PrintLabel {
        /**
         * <BR> ：换行符
         * <CUT> ：切刀指令/自动走纸(主动切纸,仅限切刀打印机使用才有效果/手动撕纸设备使用该指令时为自动走纸）
         * <LOGO> ：打印LOGO指令(有该指令则会打印LOGO，设备首次使用需上传LOGO图片)
         * <PLUGIN> ：预留
         * <S></S>：缩小字体对应24*24字体（注：默认字体大小为32*32）
         * <F></F>：放大字体对应48*48字体
         * <CB></CB>：设置字体64*64且居中
         * <B></B>：放大一倍对应64*64字体
         * <C></C>：居中
         * <L></L>：字体变高一倍
         * <W></W>：字体变宽一倍
         * <QR></QR>：二维码（单个订单，最多只能打印一个二维码）
         * <BC128_A></BC128_A>：数字字母混合条形码
         * <RIGHT></RIGHT>：右对齐
         * <BOLD></BOLD>：字体加粗
         * <UDL></UDL>: 设置下划线打印
         * <HL></HL>: 反白打印
         * <RT:?></RT>: 字符旋转(“?”代表旋转角度)
         * <RS:?>: 此行到下行的行间距“?”该值可自己设置（最小1，最大127，不设置则为默认）
         * <WS:?>: 每行中文字的间距“?”该值可自己设置（最小1，最大48，不设置则为默认）
         * <CS:x>:设置可变字体大小</CS>（x：16,24,32,48,64,72,96都可支持，如x=96则为96*96字体）
         * <OUT:113>:走纸113点
         * <PLUGIN>:切刀全切指令（只适用带切刀型设备）
         * <PCUT>:切刀半切指令（只适用带切刀型设备）
         */
        private String[] center;
        private String[] centerBold;
        private String[] bold;
        private String br;
        //s 的行数字符为 30
        private String[] s;
        //f 的行数字符为 14
        private String[] f;
        private String[] l;
        private String[] qr;
        private String cut;
        private String out;
        private String rs;


        /**
         * 走纸
         */
        public String out(int num) {
            return StrUtil.format("<{}:{}>", out, num);
        }

        public String out() {
            return out(180);
        }

        /**
         * 设置可变字体大小
         * <CS:x>:设置可变字体大小</CS>（x：16,24,32,48,64,72,96都可支持，如x=96则为96*96字体）
         *
         * @param num 字号
         */
        public String[] cs(int num) {
            return new String[]{StrUtil.format("<CS:{}>", num), "</CS>"};
        }
    }


    /**
     * 发送打印请求
     *
     * @param address       打印地址
     * @param metaPrintData 打印元数据
     * @param voiceData     语音信息
     * @param printNum      打印联数
     * @return 打印结果
     */
    String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);

    /**
     * 获取当前打印机标签信息
     *
     * @return 标签信息
     */
    PrintLabel getSignLabelInfo();

    /**
     * 获取分割线
     */
    default String getDividingLine() {
        String line = StrUtil.repeat("-", 32);
        return formatLabel(line, getSignLabelInfo().s);
    }

    default String getDividingLine(Integer num) {
        int length = num == null ? 32 : num;
        String line = StrUtil.repeat("-", length);
        return formatLabel(line, getSignLabelInfo().s);
    }

    default String formatLabel(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 buildHandoverData(HandoverRecordDTO handoverRecord) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder()
                .append(formatLabel(handoverRecord.getShopName(), signLabelInfo.center)).append(signLabelInfo.br)
                .append(formatLabel("交班小票", signLabelInfo.center)).append(signLabelInfo.br)
                .append(formatLabel("交班时间: " + DateUtil.format(handoverRecord.getHandoverTime(), "yyyy/MM/dd HH:mm:ss"), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel("收银员: " + handoverRecord.getStaffName(), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel("交班周期: " + DateUtil.format(handoverRecord.getLoginTime(), "dd日HH:mm") + "--" +
                        DateUtil.format(handoverRecord.getHandoverTime(), "dd日HH:mm"), signLabelInfo.s)).append(signLabelInfo.br)

                .append(signLabelInfo.br)
                .append(signLabelInfo.br)
                .append(formatLabel("当班营业总额: " + handoverRecord.getTurnover(), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel("实际收款的支付方式", signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 现金:", bdToStr(handoverRecord.getCash()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 微信:", bdToStr(handoverRecord.getWechat()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 支付宝:", bdToStr(handoverRecord.getAlipay()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 二维码收款:", bdToStr(handoverRecord.getSelfScan()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 扫码收款:", bdToStr(handoverRecord.getBarScan()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 充值:", bdToStr(handoverRecord.getRecharge()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel("非实际收款的支付方式", signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 挂账:", bdToStr(handoverRecord.getOwed()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 余额:", bdToStr(handoverRecord.getBalance()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(getDividingLine()).append(signLabelInfo.br)
                .append(formatLabel("退菜/退款", signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 退款金额:", bdToStr(handoverRecord.getRefundAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(formatLabel(LRAlign(" 退菜数量:", bdToStr(handoverRecord.getReturnDishCount()), 32), signLabelInfo.s)).append(signLabelInfo.br)
                .append(getDividingLine()).append(signLabelInfo.br)
                .append(formatLabel(LRAlign("订单数量/订单总额", handoverRecord.getOrderCount() + "/" + bdToStr(handoverRecord.getOrderTurnover()), 32), signLabelInfo.s))
                .append(signLabelInfo.br);

        if (StrUtil.isNotBlank(handoverRecord.getCategoryData())) {
            builder.append(formatLabel("----------- 销售数据 -----------", signLabelInfo.s)).append(signLabelInfo.br)
                    .append(formatLabel(key3("商品分类", "数量", "总计", 14, 8, 32), signLabelInfo.s))
                    .append(signLabelInfo.br);
            JSONArray.parseArray(handoverRecord.getCategoryData()).forEach(item -> {
                JSONObject info = (JSONObject) item;
                String categoryName = info.getString("categoryName");
                Integer quantity = info.getInteger("quantity");
                BigDecimal amount = info.getBigDecimal("amount");
                builder.append(formatLabel(key3(categoryName, quantity.toString(), bdToStr(amount), 14, 8, 32), signLabelInfo.s)).append(signLabelInfo.br);
            });
        }
        builder.append(getDividingLine()).append(signLabelInfo.br)
                .append(formatLabel("打印时间: " + DateUtil.format(DateUtil.date(), "yyyy/MM/dd HH:mm:ss"), signLabelInfo.s)).append(signLabelInfo.br)
                .append(signLabelInfo.out())
                .append(signLabelInfo.cut);
        return builder.toString();
    }

    /**
     * 构建结算单 预结算单订单打印元数据
     *
     * @param orderPrintDTO 打印信息
     * @param detailList    订单详情
     * @return 订单打印元数据
     */
    default String buildOrderPrintData(OrderPrintDTO orderPrintDTO, List<OrderDetail> detailList) {
        PrintLabel signLabel = getSignLabelInfo();
        StringBuilder data = new StringBuilder();
        data.append(formatLabel(orderPrintDTO.getShopName(), signLabel.center)).append(signLabel.br);
        data.append(formatLabel(orderPrintDTO.getPrintTitle(), signLabel.center)).append(signLabel.br);
        data.append(formatLabel(orderPrintDTO.getPickupNum(), signLabel.f)).append(signLabel.br);
        data.append(formatLabel(formatLabel(LRAlign(orderPrintDTO.getPrintType(), StrUtil.isNotBlank(orderPrintDTO.getSeatNum()) ? orderPrintDTO.getSeatNum() + "人" : "", 32))
                , signLabel.s)).append(signLabel.br);
        if (StrUtil.isNotBlank(orderPrintDTO.getTradeDate())) {
            data.append(formatLabel(StrUtil.format("结账时间：{}", orderPrintDTO.getTradeDate()), signLabel.s)).append(signLabel.br);
        }
        data.append(getDividingLine()).append(signLabel.br);
        data.append(formatLabel(key3("品名", "单价", "数量", 18, 9, 32), signLabel.s)).append(signLabel.br);

        for (OrderDetail detail : detailList) {
            BigDecimal subtract = detail.getNum().subtract(detail.getReturnNum());
            if (subtract.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            String number = subtract.stripTrailingZeros().toPlainString();
            String unitPrice = toPlainStr(detail.getUnitPrice().stripTrailingZeros().toPlainString());
            data.append(formatLabel(key3(detail.getProductName(), unitPrice, number, 18, 7, 32), signLabel.s)).append(signLabel.br);
//            data.append(formatLabel(key3(detail.getProductName(), unitPrice, number, 13, 4,24), signLabel.cs(32))).append(signLabel.br);

            if (StrUtil.isNotBlank(detail.getSkuName())) {
                data.append(formatLabel(StrUtil.format("规格：{}", detail.getSkuName()), signLabel.s))
                        .append(signLabel.br);
            }
            String proGroupInfo = detail.getProGroupInfo();
            if (StrUtil.isBlank(proGroupInfo)) {
                continue;
            }
            if (!JSONUtil.isTypeJSONArray(proGroupInfo)) {
                continue;
            }
            JSONArray subItems = com.alibaba.fastjson2.JSONArray.parseArray(proGroupInfo);
            for (Object subItem : subItems) {
                JSONObject jsonObject = (JSONObject) subItem;
                jsonObject.getJSONArray("goods").forEach(item -> {
                    String proName = ((JSONObject) item).getString("proName");
                    String qty = ((JSONObject) item).getString("number");
                    data.append(formatLabel(key3(" - " + proName, "", qty, 18, 9, 32), signLabel.s)).append(signLabel.br);
                });
            }
        }
        if (StrUtil.isNotBlank(orderPrintDTO.getSeatAmount())) {
            data.append(formatLabel(key3("餐位费", orderPrintDTO.getSeatAmount(), orderPrintDTO.getSeatNum(), 18, 9, 32), signLabel.s)).append(signLabel.br);
        }
        if (StrUtil.isNotBlank(orderPrintDTO.getPackFee())) {
            data.append(formatLabel(key3("打包费", toPlainStr(orderPrintDTO.getPackFee()), "", 18, 9, 32), signLabel.s)).append(signLabel.br);

        }
        data.append(getDividingLine())
                .append(signLabel.br);
        data.append(formatLabel(LRAlign("原价:", orderPrintDTO.getOriginalAmount(), 32), signLabel.s)).append(signLabel.br);
        if (ObjectUtil.isNotNull(orderPrintDTO.getDiscountAmount())) {
            data.append(formatLabel(LRAlign("优惠:", "-" + orderPrintDTO.getDiscountAmount(), 32), signLabel.s)).append(signLabel.br);
        }
        if (StrUtil.isNotBlank(orderPrintDTO.getRemark())) {
            data.append(formatLabel(StrUtil.format("备注：{}", orderPrintDTO.getRemark()), signLabel.s)).append(signLabel.br);
        }
        data.append(getDividingLine()).append(signLabel.br);
        data.append(formatLabel(LRAlign("应付:", orderPrintDTO.getPayAmount(), 16), signLabel.f)).append(signLabel.br);

        if ("结算单".equals(orderPrintDTO.getPrintTitle())) {
            data.append(getDividingLine()).append(signLabel.br);
            data.append(formatLabel(LRAlign("已付:", orderPrintDTO.getPayAmount(), 16), signLabel.f)).append(signLabel.br);
            data.append(formatLabel(LRAlign("支付方式:", orderPrintDTO.getPayType(), 32), signLabel.s)).append(signLabel.br);
        }

        data.append(getDividingLine()).append(signLabel.br);
        data.append(formatLabel(StrUtil.format("操作员：{}", orderPrintDTO.getOperator()), signLabel.s)).append(signLabel.br);
        data.append(formatLabel(StrUtil.format("打印时间：{}", DateUtil.date().toString()), signLabel.s)).append(signLabel.br);
        data.append(formatLabel(StrUtil.format("订单号：{}", orderPrintDTO.getOrderNo()), signLabel.s)).append(signLabel.br);
        data.append(signLabel.out());
        data.append(signLabel.cut);
        return data.toString();
    }

    /**
     * 构建客看单订单打印元数据
     *
     * @param orderPrintDTO 打印信息
     * @param detailList    订单详情
     * @return 订单打印元数据
     */
    default String buildGuestOrderPrintData(OrderPrintDTO orderPrintDTO, List<OrderDetail> detailList) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder data = new StringBuilder();
        data.append(formatLabel(orderPrintDTO.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        data.append(formatLabel("客看单", signLabelInfo.center)).append(signLabelInfo.br);
        if (StrUtil.isNotBlank(orderPrintDTO.getPickupNum())) {
            data.append(formatLabel(orderPrintDTO.getPickupNum(), signLabelInfo.f)).append(signLabelInfo.br);
        }
        data.append(formatLabel(formatLabel(LRAlign(orderPrintDTO.getPrintType(), StrUtil.isNotBlank(orderPrintDTO.getSeatNum()) ? orderPrintDTO.getSeatNum() + "人" : "", 32))
                , signLabelInfo.s)).append(signLabelInfo.br);
        data.append(getDividingLine()).append(signLabelInfo.br);
        data.append(formatLabel(key3("品名", "单价", "数量", 18, 9, 32), signLabelInfo.s)).append(signLabelInfo.br);

        for (OrderDetail detail : detailList) {
            BigDecimal subtract = detail.getNum().subtract(detail.getReturnNum());
            if (subtract.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            String number = subtract.stripTrailingZeros().toPlainString();
            String unitPrice = toPlainStr(detail.getUnitPrice().stripTrailingZeros().toPlainString());
            data.append(formatLabel(key3(detail.getProductName(), unitPrice, number, 18, 7, 32), signLabelInfo.s)).append(signLabelInfo.br);

            if (StrUtil.isNotBlank(detail.getSkuName())) {
                data.append(formatLabel(StrUtil.format("规格：{}", detail.getSkuName()), signLabelInfo.s))
                        .append(signLabelInfo.br);
            }
            String proGroupInfo = detail.getProGroupInfo();
            if (StrUtil.isBlank(proGroupInfo)) {
                continue;
            }
            if (!JSONUtil.isTypeJSONArray(proGroupInfo)) {
                continue;
            }
            JSONArray subItems = com.alibaba.fastjson2.JSONArray.parseArray(proGroupInfo);
            for (Object subItem : subItems) {
                JSONObject jsonObject = (JSONObject) subItem;
                jsonObject.getJSONArray("goods").forEach(item -> {
                    String proName = ((JSONObject) item).getString("proName");
                    String qty = ((JSONObject) item).getString("number");
                    data.append(formatLabel(key3(" - " + proName, "", qty, 18, 9, 32), signLabelInfo.s)).append(signLabelInfo.br);
                });
            }
        }
        if (StrUtil.isNotBlank(orderPrintDTO.getSeatAmount())) {
            data.append(formatLabel(key3("餐位费", orderPrintDTO.getSeatAmount(), orderPrintDTO.getSeatNum(), 18, 9, 32), signLabelInfo.s)).append(signLabelInfo.br);
        }
        if (StrUtil.isNotBlank(orderPrintDTO.getPackFee())) {
            data.append(formatLabel(key3("打包费", toPlainStr(orderPrintDTO.getPackFee()), "", 18, 9, 32), signLabelInfo.s)).append(signLabelInfo.br);
        }
        data.append(getDividingLine()).append(signLabelInfo.br);
        data.append(formatLabel(LRAlign("原价:", orderPrintDTO.getOriginalAmount(), 32), signLabelInfo.s)).append(signLabelInfo.br);
        if (ObjectUtil.isNotNull(orderPrintDTO.getDiscountAmount())) {
            data.append(formatLabel(LRAlign("优惠:", "-" + orderPrintDTO.getDiscountAmount(), 32), signLabelInfo.s)).append(signLabelInfo.br);
        }
        if (StrUtil.isNotBlank(orderPrintDTO.getRemark())) {
            data.append(formatLabel(StrUtil.format("备注：{}", orderPrintDTO.getRemark()), signLabelInfo.s)).append(signLabelInfo.br);
        }
        data.append(getDividingLine()).append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("操作员：{}", orderPrintDTO.getOperator()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("打印时间：{}", DateUtil.date().toString()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("订单号：{}", orderPrintDTO.getOrderNo()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(signLabelInfo.out());
        data.append(signLabelInfo.cut);
        return data.toString();
    }

    /**
     * 构建退款订单打印元数据
     *
     * @param orderPrintDTO 打印信息
     * @param detailList    订单详情
     * @return 订单打印元数据
     */
    default String buildRefundOrderPrintData(OrderPrintDTO orderPrintDTO, List<OrderDetail> detailList) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder data = new StringBuilder();
        data.append(formatLabel(orderPrintDTO.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        data.append(formatLabel(orderPrintDTO.getPrintTitle(), signLabelInfo.center)).append(signLabelInfo.br);
        data.append(formatLabel(orderPrintDTO.getPickupNum(), signLabelInfo.f)).append(signLabelInfo.br);
        data.append(formatLabel(formatLabel(LRAlign(orderPrintDTO.getPrintType(), StrUtil.isNotBlank(orderPrintDTO.getSeatNum()) ? orderPrintDTO.getSeatNum() + "人" : "", 32))
                , signLabelInfo.s)).append(signLabelInfo.br);
        data.append(getDividingLine()).append(signLabelInfo.br);
        if (CollUtil.isNotEmpty(detailList)) {
            data.append(formatLabel(key3("退款明细", "数量", "小计", 18, 9, 32), signLabelInfo.s)).append(signLabelInfo.br);

            for (OrderDetail detail : detailList) {
                String number = detail.getNum().stripTrailingZeros().toPlainString();
                String amount = "";
                if ("退款单".equals(orderPrintDTO.getPrintTitle())) {
                    amount = detail.getReturnAmount().stripTrailingZeros().toPlainString();
                }
                data.append(formatLabel(key3(detail.getProductName(), number, amount, 18, 7, 32), signLabelInfo.s)).append(signLabelInfo.br);
                if (StrUtil.isNotBlank(detail.getSkuName())) {
                    data.append(formatLabel(StrUtil.format("规格：{}", detail.getSkuName()), signLabelInfo.s))
                            .append(signLabelInfo.br);
                }
                String proGroupInfo = detail.getProGroupInfo();
                if (StrUtil.isBlank(proGroupInfo)) {
                    continue;
                }
                if (!JSONUtil.isTypeJSONArray(proGroupInfo)) {
                    continue;
                }
                JSONArray subItems = com.alibaba.fastjson2.JSONArray.parseArray(proGroupInfo);
                for (Object subItem : subItems) {
                    JSONObject jsonObject = (JSONObject) subItem;
                    jsonObject.getJSONArray("goods").forEach(item -> {
                        String proName = ((JSONObject) item).getString("proName");
                        String qty = ((JSONObject) item).getString("number");
                        data.append(formatLabel(key3(" - " + proName, "", qty, 18, 9, 32), signLabelInfo.s)).append(signLabelInfo.br);
                    });
                }
            }
            data.append(getDividingLine())
                    .append(signLabelInfo.br);
        }
        if ("退款单".equals(orderPrintDTO.getPrintTitle())) {
            data.append(formatLabel(LRAlign("退款总计", orderPrintDTO.getRefundAmount(), 16), signLabelInfo.f)).append(signLabelInfo.br);
            data.append(formatLabel(StrUtil.format("退款方式: {}", orderPrintDTO.getRefundType()), signLabelInfo.s))
                    .append(signLabelInfo.br);
            data.append(formatLabel(StrUtil.format("退款原因: {}", orderPrintDTO.getRefundReason()), signLabelInfo.s))
                    .append(signLabelInfo.br);
        } else {
            data.append(formatLabel(StrUtil.format("退菜原因: {}", orderPrintDTO.getRefundReason()), signLabelInfo.s))
                    .append(signLabelInfo.br);
        }
        data.append(getDividingLine())
                .append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("操作员：{}", orderPrintDTO.getOperator()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("打印时间：{}", DateUtil.date().toString()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("订单号：{}", orderPrintDTO.getOrderNo()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(signLabelInfo.out());
        data.append(signLabelInfo.cut);
        return data.toString();
    }

    /**
     * 后厨-总单
     * 后厨-退菜单
     *
     * @return 元数据
     */
    default String buildAllKitchenPrintData(OrderPrintDTO orderPrintDTO, List<OrderDetail> detailList) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder data = new StringBuilder();
        data.append(formatLabel(orderPrintDTO.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        data.append(formatLabel(orderPrintDTO.getPrintTitle(), signLabelInfo.center)).append(signLabelInfo.br);
        data.append(formatLabel(orderPrintDTO.getPickupNum(), signLabelInfo.f)).append(signLabelInfo.br);
        data.append(formatLabel(formatLabel(orderPrintDTO.getPrintType()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(getDividingLine()).append(signLabelInfo.br);
        if (CollUtil.isNotEmpty(detailList)) {
            data.append(formatLabel(LRAlign("品名", "数量", 32), signLabelInfo.s)).append(signLabelInfo.br);
            for (OrderDetail detail : detailList) {
                BigDecimal num = detail.getNum().subtract(detail.getReturnNum());
                data.append(formatLabel(LRAlign(detail.getProductName(), num.stripTrailingZeros().toPlainString(), 32), signLabelInfo.s)).append(signLabelInfo.br);

                if (StrUtil.isNotBlank(detail.getSkuName())) {
                    data.append(formatLabel(StrUtil.format("规格：{}", detail.getSkuName()), signLabelInfo.s))
                            .append(signLabelInfo.br);
                }
                String proGroupInfo = detail.getProGroupInfo();
                if (StrUtil.isNotBlank(proGroupInfo) && JSONUtil.isTypeJSONArray(proGroupInfo)) {
                    JSONArray subItems = com.alibaba.fastjson2.JSONArray.parseArray(proGroupInfo);
                    for (Object subItem : subItems) {
                        JSONObject jsonObject = (JSONObject) subItem;
                        jsonObject.getJSONArray("goods").forEach(item -> {
                            String proName = ((JSONObject) item).getString("proName");
                            String qty = ((JSONObject) item).getString("number");
                            data.append(formatLabel(key3(" - " + proName, "", qty, 18, 9, 32), signLabelInfo.s)).append(signLabelInfo.br);
                        });
                    }
                }
                if (StrUtil.isNotBlank(detail.getRemark()) && !"null".equals(detail.getRemark())) {
                    data.append(formatLabel(StrUtil.format(" 备注：{}", detail.getRemark()), signLabelInfo.s)).append(signLabelInfo.br);
                }
            }
            data.append(getDividingLine())
                    .append(signLabelInfo.br);
        }
        if (StrUtil.isNotBlank(orderPrintDTO.getRemark()) && !"null".equals(orderPrintDTO.getRemark())) {
            data.append(formatLabel(StrUtil.format("备注: {}", orderPrintDTO.getRemark()), signLabelInfo.s)).append(signLabelInfo.br);
            data.append(getDividingLine()).append(signLabelInfo.br);
        }

        data.append(formatLabel(StrUtil.format("操作员：{}", orderPrintDTO.getOperator()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("打印时间：{}", DateUtil.date().toString()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(formatLabel(StrUtil.format("订单号：{}", orderPrintDTO.getOrderNo()), signLabelInfo.s)).append(signLabelInfo.br);
        data.append(signLabelInfo.out());
        data.append(signLabelInfo.cut);
        return data.toString();
    }

    /**
     * 后厨-单商品单
     *
     * @param pickupNumber 取餐号
     * @return 元数据
     */
    default String buildOnlyKitchenPrintData(Integer isCode, String pickupNumber, OrderDetail orderDetail) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder();
        builder.append(formatLabel(pickupNumber, signLabelInfo.centerBold))
                .append(signLabelInfo.br);
        builder.append(signLabelInfo.br);
        builder.append(signLabelInfo.br);
        builder.append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("时间:{}", DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss")), signLabelInfo.s, signLabelInfo.center))
                .append(signLabelInfo.br);
        builder.append(signLabelInfo.out(70));

        Integer isWaitCall = ObjectUtil.defaultIfNull(orderDetail.getIsWaitCall(), 0);
        if (isWaitCall == 1) {
            if (!orderDetail.getProductName().contains("【等叫】")) {
                orderDetail.setProductName("【等叫】" + orderDetail.getProductName());
            }
        }
        Integer isTemporary = ObjectUtil.defaultIfNull(orderDetail.getIsTemporary(), 0);
        if (isTemporary == 1) {
            orderDetail.setProductId(0L);
            orderDetail.setSkuId(0L);
            if (!orderDetail.getProductName().contains("【临】")) {
                orderDetail.setProductName("【临】" + orderDetail.getProductName());
            }
        }
        boolean isGift = orderDetail.getIsGift() != null && orderDetail.getIsGift() == 1;
        if (isGift && !orderDetail.getProductName().contains("【赠】")) {
            orderDetail.setProductName("【赠】" + orderDetail.getProductName());
        }
        if (orderDetail.getProductName().length() > 4) {
            builder.append(formatLabel(StrUtil.format("{} x {}", orderDetail.getProductName(), orderDetail.getNum().stripTrailingZeros().toPlainString()), signLabelInfo.f, signLabelInfo.center))
                    .append(signLabelInfo.br);
            if (StrUtil.isNotBlank(orderDetail.getSkuName())) {
                builder.append(formatLabel("(" + orderDetail.getSkuName() + ")", signLabelInfo.f, signLabelInfo.center)).append(signLabelInfo.br);
            }
        } else {
            builder.append(formatLabel(StrUtil.format("{} x {}", orderDetail.getProductName(), orderDetail.getNum().stripTrailingZeros().toPlainString()), signLabelInfo.f, signLabelInfo.center))
                    .append(signLabelInfo.br);
            if (StrUtil.isNotBlank(orderDetail.getSkuName())) {
                builder.append(formatLabel("(" + orderDetail.getSkuName() + ")", signLabelInfo.f, signLabelInfo.center))
                        .append(signLabelInfo.br);
            }
        }
        if (StrUtil.isNotBlank(orderDetail.getRemark()) && !"null".equals(orderDetail.getRemark())) {
            builder.append(formatLabel(StrUtil.format("备注：{}", orderDetail.getRemark()), signLabelInfo.f))
                    .append(signLabelInfo.br);
        }
        if (!StrUtil.isBlank(orderDetail.getProGroupInfo()) && JSONUtil.isTypeJSONArray(orderDetail.getProGroupInfo())) {
            JSONArray subItems = JSONArray.parseArray(orderDetail.getProGroupInfo());
            for (Object subItem : subItems) {
                JSONObject jsonObject = (JSONObject) subItem;
                jsonObject.getJSONArray("goods").forEach(item -> {
                    String proName = ((JSONObject) item).getString("proName");
                    String qty = ((JSONObject) item).getString("number");
                    builder.append(StrUtil.format("({}) x {}", proName, qty))
                            .append(signLabelInfo.br);
                });
            }
        }
        if (orderDetail.isUrgent()) {
            builder.append(formatLabel("【加急】", signLabelInfo.center, signLabelInfo.bold))
                    .append(signLabelInfo.br);
        }
        if (1 == isCode) {
            builder.append(formatLabel("UP_ORDER_DETAIL:" + orderDetail.getId(), signLabelInfo.center, signLabelInfo.qr))
                    .append(signLabelInfo.br);
        }
        builder.append(signLabelInfo.br)
                .append(signLabelInfo.out())
                .append(signLabelInfo.cut);
        return builder.toString();
    }

    /**
     * 出入库打印单
     */
    default String buildStockData(StockPrintDTO record) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder();

        // 标题：入库 + 店铺名称 + 入库单
        builder.append(formatLabel(record.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        builder.append(formatLabel("IN".equals(record.getType()) ? "入库单" : "出库单", signLabelInfo.center)).append(signLabelInfo.br);

        // 入库时间
        builder.append(formatLabel(StrUtil.format("IN".equals(record.getType()) ? "入库时间: {}" : "出库时间: {}", DateUtil.format(record.getInStockTime(), "yyyy/MM/dd HH:mm:ss")), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);
        builder.append(formatLabel(key4("耗材名称", "库存单位", "IN".equals(record.getType()) ? "入库数量" : "出库数量", "金额",
                10, 9, 9), signLabelInfo.s)).append(signLabelInfo.br);

        // 遍历耗材明细，逐行打印
        List<StockPrintDTO.InStockItem> items = record.getItems();
        if (CollUtil.isNotEmpty(items)) {
            for (StockPrintDTO.InStockItem item : items) {
                String stockNumber = bdToStr(item.getStockNumber());
                String amount = bdToStr(item.getAmount());

                // 左对齐排版，保证列对齐
                String itemLine = key4(item.getConsName(), StrUtil.isBlank(item.getUnit()) ? "" : item.getUnit(), stockNumber, amount, 11, 9, 7);
                builder.append(formatLabel(itemLine, signLabelInfo.s)).append(signLabelInfo.br);
            }
        }
        builder.append(signLabelInfo.br);
        // 总计行
        String consCountStr = "耗材" + (record.getConsCount() == null ? "0" : record.getConsCount().toString()) + "种";
        String stockNumberCountStr = "数量" + record.getStockNumberCount();
        String amountCountStr = "金额" + record.getAmountCount();

        String totalLine = key4("总计", consCountStr, stockNumberCountStr, amountCountStr, 6, 9, 7);
        builder.append(formatLabel(totalLine, signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);

        // 操作员、打印时间
        builder.append(formatLabel(StrUtil.format("操作员: {}", StrUtil.isNotBlank(record.getOperator()) ? record.getOperator() : ""), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("打印时间: {}", DateUtil.format(record.getPrintTime(), "yyyy/MM/dd HH:mm:ss")), signLabelInfo.s)).append(signLabelInfo.br);

        // 结尾空行 + 切纸（和充值单保持一致）
        builder.append(signLabelInfo.out()).append(signLabelInfo.cut);
        return builder.toString();
    }

    /**
     * 经营日报打印单
     */
    default String buildDayReportData(DayReportPrintDTO record) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder();

        // 标题：店铺名称 + 经营日报（居中，和充值/入库单风格统一）
        builder.append(formatLabel(record.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        builder.append(formatLabel("经营日报", signLabelInfo.center)).append(signLabelInfo.br).append(signLabelInfo.br);

        // 基础信息：打印时间、操作人、统计时间
        builder.append(formatLabel(StrUtil.format("打印时间: {}", DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd HH:mm:ss")), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("操作人: {}", StrUtil.isNotBlank(record.getOperator()) ? record.getOperator() : ""), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("统计时间: {}", record.getStatisticsTime()), signLabelInfo.s)).append(signLabelInfo.br).append(signLabelInfo.br);

        // 营业总额
        DayReportPrintDTO.BusinessSts turnover = record.getTurnover();
        builder.append(formatLabel("营业指标:", signLabelInfo.bold)).append(signLabelInfo.br).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  订单原价总额", bdToStr(turnover.getOriginAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  营业额", bdToStr(turnover.getTurnover()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  优惠金额", bdToStr(turnover.getDiscountAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  订单数(含退款)", String.valueOf(turnover.getOrderCount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  折前单均价", bdToStr(turnover.getAverageOrderAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  折后单均价", bdToStr(turnover.getAverageTurnover()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  退款金额", bdToStr(turnover.getRefundAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  退款订单数", String.valueOf(turnover.getRefundOrderCount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);

        // 收入来源
        DayReportPrintDTO.Income soIncome = record.getSourceIncome();
        builder.append(formatLabel("收入来源", signLabelInfo.bold)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  现金", bdToStr(soIncome.getCash()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  微信", bdToStr(soIncome.getWechat()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  支付宝", bdToStr(soIncome.getAlipay()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  美团团购", bdToStr(soIncome.getMeituan()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  抖音团购", bdToStr(soIncome.getDouyin()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  其它", bdToStr(soIncome.getOther()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);

        // 实收统计
        DayReportPrintDTO.Income actualIncome = record.getActualIncome();
        builder.append(formatLabel("实收统计", signLabelInfo.bold)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  现金", bdToStr(actualIncome.getCash()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  微信", bdToStr(actualIncome.getWechat()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  支付宝", bdToStr(actualIncome.getAlipay()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  美团团购", bdToStr(actualIncome.getMeituan()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  抖音团购", bdToStr(actualIncome.getDouyin()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  其它", bdToStr(actualIncome.getOther()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);
        // 实收统计
        DayReportPrintDTO.DiscountSts disSta = record.getDiscountSta();
        builder.append(formatLabel("优惠统计", signLabelInfo.bold)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  新客立减", bdToStr(disSta.getNewConsumerDiscount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  霸王餐", bdToStr(disSta.getFreeCashAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  满减活动", bdToStr(disSta.getFullMinusAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  优惠券", bdToStr(disSta.getCouponAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  会员折扣", bdToStr(disSta.getMemberDiscount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  积分抵扣金额", bdToStr(disSta.getPointsDiscountAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  订单改价", bdToStr(disSta.getOrderDiscount()), 32), signLabelInfo.s)).append(signLabelInfo.br);

        // 结尾空行 + 切纸（和充值/入库单完全一致）
        builder.append(signLabelInfo.out()).append(signLabelInfo.cut);
        return builder.toString();
    }

    /**
     * 日结单
     */
    default String buildDaySettleData(DaySettlePrintDTO record) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder();

        builder.append(formatLabel(record.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        builder.append(formatLabel("日结单", signLabelInfo.center)).append(signLabelInfo.br).append(signLabelInfo.br);

        // 基础信息：打印时间、操作人、统计时间
        builder.append(formatLabel(StrUtil.format("打印时间: {}", DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd HH:mm:ss")), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("操作人: {}", StrUtil.isNotBlank(record.getOperator()) ? record.getOperator() : ""), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("统计时间: {}", record.getStatisticsTime()), signLabelInfo.s)).append(signLabelInfo.br).append(signLabelInfo.br);

        // 营业统计
        DaySettlePrintDTO.BusinessSts turnover = record.getTurnover();
        builder.append(formatLabel("营业统计:", signLabelInfo.bold)).append(signLabelInfo.br).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  订单原价总额", bdToStr(turnover.getOriginAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  营业额", bdToStr(turnover.getTurnover()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  优惠金额", bdToStr(turnover.getDiscountAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  订单数(含退款)", turnover.getOrderCount() == null ? "0" : String.valueOf(turnover.getOrderCount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  退款订单数", turnover.getRefundOrderCount() == null ? "0" : String.valueOf(turnover.getRefundOrderCount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  退款金额", bdToStr(turnover.getRefundAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  现金收款", bdToStr(turnover.getCash()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel("  备用金", signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel("  钱箱剩余", signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);

        // 收入来源
        DaySettlePrintDTO.Income income = record.getIncome();
        builder.append(formatLabel("收款构成", signLabelInfo.bold)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  现金", bdToStr(income.getCash()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  微信", bdToStr(income.getWechat()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  支付宝", bdToStr(income.getAlipay()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  团购", bdToStr(income.getGroup()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);

        // 实收统计
        DaySettlePrintDTO.DiscountSts disSta = record.getDiscountSta();
        builder.append(formatLabel("优惠统计", signLabelInfo.bold)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  新客立减", bdToStr(disSta.getNewConsumerDiscount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  霸王餐", bdToStr(disSta.getFreeCashAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  满减活动", bdToStr(disSta.getFullMinusAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  优惠券", bdToStr(disSta.getCouponAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  会员折扣", bdToStr(disSta.getMemberDiscount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  积分抵扣金额", bdToStr(disSta.getPointsDiscountAmount()), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("  订单改价", bdToStr(disSta.getOrderDiscount()), 32), signLabelInfo.s)).append(signLabelInfo.br);

        List<DaySettlePrintDTO.OperationRecord> operationRecords = record.getOperationRecords();
        if (CollUtil.isNotEmpty(operationRecords)) {
            builder.append(formatLabel("敏感操作记录", signLabelInfo.bold)).append(signLabelInfo.br);
            for (DaySettlePrintDTO.OperationRecord operation : operationRecords) {
                builder.append(formatLabel(
                        key3(operation.getOperation(), "数量:" + operation.getCount(), "金额" + bdToStr(operation.getAmount()), 8, 14, 32)
                        , signLabelInfo.s)).append(signLabelInfo.br);
            }
        }
        builder.append(signLabelInfo.out()).append(signLabelInfo.cut);
        return builder.toString();
    }

    /**
     * 商品报表打印单
     */
    default String buildProductReportData(ProductReportPrintDTO record) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder();

        // 标题：店铺名称 + 商品报表（居中，和之前的报表风格统一）
        builder.append(formatLabel(record.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        builder.append(formatLabel("商品报表", signLabelInfo.center)).append(signLabelInfo.br).append(signLabelInfo.br);

        // 基础信息：打印时间、操作人、统计时间
        builder.append(formatLabel(StrUtil.format("打印时间: {}", DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd HH:mm:ss")), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("操作人: {}", StrUtil.isNotBlank(record.getOperator()) ? record.getOperator() : ""), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("时间: {}", record.getStatisticsTime()), signLabelInfo.s)).append(signLabelInfo.br).append(signLabelInfo.br);

        // 总计行：总计商品X件，实收金额X元
        String totalCountStr = record.getTotalProductCount().stripTrailingZeros().toPlainString();
        String totalActualStr = record.getTotalActualAmount().stripTrailingZeros().toPlainString();
        builder.append(formatLabel(StrUtil.format("总计商品{}件，实收金额{}元", totalCountStr, totalActualStr), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br).append(signLabelInfo.br);

        // 表头：商品、数量、实收、销售额（四列对齐，和示例图完全匹配）
        String header = key4("商品", "数量", "实收", "销售额", 12, 6, 6);
        builder.append(formatLabel(header, signLabelInfo.s)).append(signLabelInfo.br);

        // 遍历分类+商品明细
        List<ProductReportPrintDTO.CategoryItem> items = record.getItems();
        if (CollUtil.isNotEmpty(items)) {
            for (ProductReportPrintDTO.CategoryItem item : items) {
                List<ProductReportPrintDTO.ProductItem> productList = item.getProductItems();
                // 打印分类行（缩进0，和表头对齐）
                String categoryLine = key4(item.getCategoryName(),
                        item.getNumber().stripTrailingZeros().toPlainString(),
                        item.getActualAmount().stripTrailingZeros().toPlainString(),
                        item.getSalesAmount().stripTrailingZeros().toPlainString(),
                        12, 8, 8);
                builder.append(formatLabel(categoryLine, signLabelInfo.s)).append(signLabelInfo.br);

                // 再打印该分类下的商品明细（缩进2个空格，和示例图一致）
                for (ProductReportPrintDTO.ProductItem pro : productList) {
                    String numStr = pro.getNumber().stripTrailingZeros().toPlainString();
                    String actualStr = pro.getActualAmount().stripTrailingZeros().toPlainString();
                    String salesStr = pro.getSalesAmount().stripTrailingZeros().toPlainString();
                    // 商品名称前加2个空格，实现缩进效果
                    String productLine = key4(" " + pro.getProductName(), numStr, actualStr, salesStr, 12, 8, 8);
                    builder.append(formatLabel(productLine, signLabelInfo.s)).append(signLabelInfo.br);
                }
            }
        }
        // 结尾空行 + 切纸（和之前所有打印单完全一致）
        builder.append(signLabelInfo.out()).append(signLabelInfo.cut);
        return builder.toString();
    }


    /**
     * 充值打印单
     */
    default String buildRechargeData(RechargePrintDTO record) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder();
        // 标题：店铺名称 + 充值凭证
        builder.append(formatLabel(record.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        builder.append(formatLabel("充值凭证", signLabelInfo.center)).append(signLabelInfo.br);

        // 用户信息
        builder.append(formatLabel("充值用户:" + record.getUserName(), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel("手机号:" + record.getUserPhone(), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel("支付时间:" + DateUtil.format(record.getPayTime(), "yyyy/MM/dd HH:mm:ss"), signLabelInfo.s)).append(signLabelInfo.br);
        // 充值明细
        builder.append(getDividingLine()).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("充值金额:", record.getRechargeAmount().stripTrailingZeros().toPlainString(), 24), signLabelInfo.bold)).append(signLabelInfo.br);
        if (record.getGiftAmount() != null && record.getGiftAmount().compareTo(BigDecimal.ZERO) > 0) {
            builder.append(formatLabel(LRAlign("赠送金额:", record.getGiftAmount().stripTrailingZeros().toPlainString(), 32), signLabelInfo.s)).append(signLabelInfo.br);
        }
        if (record.getGiftPoints() != null && record.getGiftPoints().compareTo(BigDecimal.ZERO) > 0) {
            builder.append(formatLabel(LRAlign("赠送积分:", record.getGiftPoints().stripTrailingZeros().toPlainString(), 32), signLabelInfo.s)).append(signLabelInfo.br);
        }
        if (record.getGiftCoupon() != null && record.getGiftCoupon() > 0) {
            builder.append(formatLabel(LRAlign("赠送优惠券:", record.getGiftCoupon() + "", 32), signLabelInfo.s)).append(signLabelInfo.br);
        }
        builder.append(formatLabel(LRAlign("充值后余额:", record.getBalance().stripTrailingZeros().toPlainString(), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);
        // 支付信息
        builder.append(formatLabel(LRAlign("已付金额:", record.getRechargeAmount().stripTrailingZeros().toPlainString(), 24), signLabelInfo.bold)).append(signLabelInfo.br);
        builder.append(formatLabel(LRAlign("支付方式:", record.getPayType(), 32), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(getDividingLine()).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("操作员: {}", StrUtil.isNotBlank(record.getOperator()) ? record.getOperator() : ""), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("打印时间: {}", DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd HH:mm:ss")), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("充值编号: {}", record.getRechargeId()), signLabelInfo.s)).append(signLabelInfo.br);

        // 结尾空行 + 切纸
        builder.append(signLabelInfo.out()).append(signLabelInfo.cut);
        return builder.toString();
    }

    /**
     * 库存盘点打印单
     */
    default String buildStockCheckData(StockCheckPrintDTO record) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        StringBuilder builder = new StringBuilder();

        // 标题：店铺名称 + 盘点单（居中，和之前所有打印单风格统一）
        builder.append(formatLabel(record.getShopName(), signLabelInfo.center)).append(signLabelInfo.br);
        builder.append(formatLabel("盘点单", signLabelInfo.center)).append(signLabelInfo.br).append(signLabelInfo.br);

        // 表头：耗材名称、单价、实际数、盈亏数（四列对齐，复用key4工具方法）
        String header = key4("耗材名称", "单价", "实际数", "盈亏数", 10, 7, 7);
        builder.append(formatLabel(header, signLabelInfo.s)).append(signLabelInfo.br);

        // 遍历耗材明细，逐行打印
        List<StockCheckPrintDTO.StockCheckItem> items = record.getItems();
        if (CollUtil.isNotEmpty(items)) {
            for (StockCheckPrintDTO.StockCheckItem item : items) {
                String priceStr = item.getPrice().stripTrailingZeros().toPlainString() + "/" + item.getUnit();
                String actualNumberStr = item.getActualNumber().toString();
                String winLossNumberStr = item.getWinLossNumber().toString();
                // 四列对齐打印
                String itemLine = key4(item.getConsName(), priceStr, actualNumberStr, winLossNumberStr, 10, 8, 8);
                builder.append(formatLabel(itemLine, signLabelInfo.s)).append(signLabelInfo.br);
            }
        }

        builder.append(getDividingLine()).append(signLabelInfo.br).append(signLabelInfo.br);

        // 账存数量、盈亏金额、备注
        builder.append(formatLabel(StrUtil.format("账存数量: {}", record.getWinLossNumberCount()), signLabelInfo.s)).append(signLabelInfo.br);
        String winLossAmountStr = record.getWinLossAmount().stripTrailingZeros().toPlainString();
        builder.append(formatLabel(StrUtil.format("盈亏金额: {}", winLossAmountStr), signLabelInfo.s)).append(signLabelInfo.br);
        builder.append(formatLabel(StrUtil.format("备注: {}", StrUtil.isBlank(record.getRemark()) ? "" : record.getRemark()), signLabelInfo.s)).append(signLabelInfo.br);

        builder.append(getDividingLine()).append(signLabelInfo.br).append(signLabelInfo.br);
        // 操作员、打印时间
        builder.append(formatLabel(StrUtil.format("操作员: {}", StrUtil.isNotBlank(record.getOperator()) ? record.getOperator() : ""), signLabelInfo.s)).append(signLabelInfo.br);
        // 打印时间：复用之前的DateUtil格式化，若DTO需补充printTime字段，可按需求添加
        builder.append(formatLabel(StrUtil.format("打印时间: {}", DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd HH:mm:ss")), signLabelInfo.s)).append(signLabelInfo.br);

        // 结尾空行 + 切纸（和之前所有打印单完全一致）
        builder.append(signLabelInfo.out()).append(signLabelInfo.cut);
        return builder.toString();
    }


    /**
     * 构建叫号元数据
     *
     * @param shopName 店铺名称
     * @return 元数据
     */
    default String buildCallTicketData(String shopName, CallTablePrintDTO printDTO) {
        PrintLabel signLabelInfo = getSignLabelInfo();
        CallTable callTable = printDTO.getCallTable();
        CallQueue callQueue = printDTO.getCallQueue();
        String str = formatLabel(shopName, signLabelInfo.center) +
                signLabelInfo.br +
                signLabelInfo.br +
                formatLabel(StrUtil.format("{} {}", callTable.getName(), callQueue.getCallNum()), signLabelInfo.center, signLabelInfo.bold) +
                signLabelInfo.br +
                formatLabel(StrUtil.format("前面有{}桌", printDTO.getPreNum()), signLabelInfo.center) +
                signLabelInfo.br +
                formatLabel("怕过号,扫一扫", signLabelInfo.center, signLabelInfo.f) +
                signLabelInfo.br +
                formatLabel(printDTO.getCallUrl(), signLabelInfo.center, signLabelInfo.qr) +
                signLabelInfo.br + getDividingLine() + signLabelInfo.br +
                formatLabel("描述：" + callTable.getNote(), signLabelInfo.s) + signLabelInfo.br;
        if (callTable.getIsPostpone() != null && callTable.getIsPostpone() == 1) {
            str = str + formatLabel(StrUtil.format("过号顺延{}桌 {}桌后需重新排号", callTable.getPostponeNum(), callTable.getPostponeNum()), signLabelInfo.s) + signLabelInfo.br;
        }
        str = str + formatLabel(StrUtil.format("取号时间: {}", DateUtil.format(callQueue.getCreateTime(), "yyyy-MM-dd HH:mm:ss")), signLabelInfo.s) +
                signLabelInfo.br +
                signLabelInfo.out() + signLabelInfo.cut;
        return str;
    }


    /**
     * 获取规格化的字符串
     *
     * @param str 字符串
     * @return 返回规格化字符串
     */
    default String toPlainStr(String str) {
        if (StrUtil.isBlank(str)) {
            return "0";
        }
        return NumberUtil.round(new BigDecimal(str), 2).toPlainString();
    }

    /**
     * BigDecimal 转 String
     * 保留两位小数
     */
    default String bdToStr(BigDecimal bigDecimal) {
        if (bigDecimal == null) {
            return "0";
        }
        //四舍五入
        return bigDecimal.setScale(2, RoundingMode.HALF_UP).toPlainString();
    }

    // 两个内容 左 右对齐
    default String LRAlign(String leftText, String rightText, int totalWidth) {
        if (leftText == null) leftText = "";
        if (rightText == null) rightText = "";

        // 1. 计算真实打印宽度（汉字=2，英文=1）
        int leftWidth = getStringWidth(leftText);
        int rightWidth = getStringWidth(rightText);
        // 总长度 - 右边长度 = 左边可用长度
        // 2. 计算中间需要填充的空格数
        int spaceNum = totalWidth - leftWidth - rightWidth;
        if (spaceNum < 0) spaceNum = 0; // 防止负数

        // 3. 生成空格
        String spaces = " ".repeat(spaceNum);
        // 4. 三段式拼接：左边 + 空格 + 右边
        return leftText + spaces + rightText;
    }

    /**
     * 通用三列对齐方法（严格总长度）
     * 规则：key1=左对齐，key2=左对齐，key3=右对齐
     * 24的字体 最大宽度32
     * 32的字体 最大宽度24
     */
    default String key3(String key1, String key2, String key3,
                        int key1Width, int key2Width, int allWidth) {
        // 1. 空值安全处理
        key1 = Objects.toString(key1, "");
        key2 = Objects.toString(key2, "");
        key3 = Objects.toString(key3, "");

        // 👉 核心：key1 超长截取 + 加 ...
        key1 = truncateWithEllipsis(key1, key1Width);

        int w1 = getStringWidth(key1);
        int w2 = getStringWidth(key2);
        int w3 = getStringWidth(key3);

        int space1 = Math.max(key1Width - w1, 0);
        int space2 = Math.max(key2Width - w2, 0);

        // 6. 最终按总显示宽度截断（100%不超界）
        int usedWidth = key1Width + key2Width + w3;
        int space3 = Math.max(allWidth - usedWidth, 0);

        // 5. 拼接：左1 + 空格 + 左2 + 空格 + （靠右空格） + 右3
        return key1 + " ".repeat(space1)
                + key2 + " ".repeat(space2)
                + " ".repeat(space3)
                + key3;
    }

    /**
     * 通用四列对齐方法（严格总长度）
     * 规则：key1=左对齐，key2=左对齐，key3=左对齐，key4=右对齐
     * 24的字体 最大宽度32
     * 32的字体 最大宽度24
     * 目前写死了最大宽度32
     */
    default String key4(String key1, String key2, String key3, String key4,
                        int key1Width, int key2Width, int key3Width) {
        // 1. 空值安全处理
        key1 = Objects.toString(key1, "");
        key2 = Objects.toString(key2, "");
        key3 = Objects.toString(key3, "");
        key4 = Objects.toString(key4, "");

        // 👉 核心：key1 超长截取 + 加 ...
        key1 = truncateWithEllipsis(key1, key1Width);

        int w1 = getStringWidth(key1);
        int w2 = getStringWidth(key2);
        int w3 = getStringWidth(key3);
        int w4 = getStringWidth(key4);

        int space1 = Math.max(key1Width - w1, 0);
        int space2 = Math.max(key2Width - w2, 0);
        int space3 = Math.max(key3Width - w3, 0);

        // 6. 最终按总显示宽度截断（100%不超界）
        int usedWidth = key1Width + key2Width + key3Width + w4;
        int space4 = Math.max(32 - usedWidth, 0);

        // 5. 拼接：左1 + 空格 + 左2 + 空格 + （靠右空格） + 右3
        return key1 + " ".repeat(space1)
                + key2 + " ".repeat(space2)
                + key3 + " ".repeat(space3)
                + " ".repeat(space4)
                + key4;
    }

    /**
     * 获取字符串实际打印宽度（汉字=2，字母/数字=1）
     */
    static int getStringWidth(String str) {
        if (str == null || str.isEmpty()) return 0;
        int width = 0;
        for (char c : str.toCharArray()) {
            if (String.valueOf(c).getBytes().length > 1) {
                width += 2; // 中文
            } else {
                width += 1; // 英文/数字
            }
        }
        return width;
    }

    private String truncateWithEllipsis(String text, int maxWidth) {
        if (maxWidth <= 0) return "";
        if (getStringWidth(text) <= maxWidth) {
            return text;
        }
        StringBuilder sb = new StringBuilder();
        int currentWidth = 0;
        int ellipsisWidth = getStringWidth("...");

        for (char c : text.toCharArray()) {
            int charWidth = (c > 127) ? 2 : 1;
            // 剩下的宽度必须能放下 当前字符 + ...
            if (currentWidth + charWidth + ellipsisWidth > maxWidth) {
                break;
            }
            sb.append(c);
            currentWidth += charWidth;
        }
        return sb + "..";
    }
}
