import { DataSource } from 'typeorm'; import { PrintTaskService } from './print-task.service'; import { PrinterService } from './printer.service'; import { DelaySchedulerService } from './delay-scheduler.service'; import { FeieApiConfig, PrintType, PrintStatus, CancelReason } from '../types/feie.types'; import { FeieConfigMt } from '../entities/feie-config.mt.entity'; import { OrderMt } from '@d8d/orders-module-mt'; /** * 打印触发服务 * 负责处理订单支付成功等事件触发的打印任务 */ export class PrintTriggerService { private printTaskService: PrintTaskService; private printerService: PrinterService; private dataSource: DataSource; private feieConfig: FeieApiConfig; private configRepository: any; private orderRepository: any; constructor(dataSource: DataSource, feieConfig: FeieApiConfig) { this.dataSource = dataSource; this.feieConfig = feieConfig; this.printTaskService = new PrintTaskService(dataSource, feieConfig); this.printerService = new PrinterService(dataSource, feieConfig); this.configRepository = dataSource.getRepository(FeieConfigMt); this.orderRepository = dataSource.getRepository(OrderMt); } /** * 处理订单支付成功事件 * @param tenantId 租户ID * @param orderId 订单ID * @param orderInfo 订单信息 */ async handleOrderPaymentSuccess( tenantId: number, orderId: number ): Promise { try { console.debug(`[租户${tenantId}] 处理订单支付成功事件,订单ID: ${orderId}`); // 1. 获取完整的订单信息 const fullOrderInfo = await this.getFullOrderInfo(tenantId, orderId); if (!fullOrderInfo) { console.warn(`[租户${tenantId}] 未找到订单信息,订单ID: ${orderId},跳过打印任务`); return; } // 2. 获取防退款延迟时间 const delaySeconds = await this.getAntiRefundDelaySeconds(tenantId); // 3. 获取默认打印机 const defaultPrinter = await this.printerService.getDefaultPrinter(tenantId); if (!defaultPrinter) { console.warn(`[租户${tenantId}] 未找到默认打印机,跳过打印任务`); return; } // 4. 生成打印内容 const printContent = await this.generateReceiptContent(tenantId, fullOrderInfo); // 5. 创建延迟打印任务 await this.printTaskService.createPrintTask(tenantId, { orderId, printerSn: defaultPrinter.printerSn, content: printContent, printType: PrintType.RECEIPT, delaySeconds }); console.debug(`[租户${tenantId}] 订单支付成功打印任务已创建,订单ID: ${orderId}, 延迟时间: ${delaySeconds}秒`); // 6. 自动启动延迟任务检查 // 如果延迟时间为0或负数,立即触发打印 // 如果延迟时间已过(比如配置错误或系统时间问题),也立即触发 if (delaySeconds <= 0) { console.debug(`[租户${tenantId}] 延迟时间为${delaySeconds}秒,立即触发打印任务检查`); try { // 动态创建DelaySchedulerService实例 const delaySchedulerService = new DelaySchedulerService(this.dataSource, this.feieConfig, tenantId); // 检查并启动调度器 const status = delaySchedulerService.getStatus(); if (!status.isRunning) { await delaySchedulerService.start(); console.debug(`[租户${tenantId}] 延迟调度器已启动`); } const result = await delaySchedulerService.triggerManualProcess(tenantId); if (result.success) { console.debug(`[租户${tenantId}] 立即触发打印成功,处理了${result.processedTasks}个任务`); } else { console.warn(`[租户${tenantId}] 立即触发打印失败: ${result.message}`); } } catch (error) { console.warn(`[租户${tenantId}] 创建延迟调度器失败:`, error); // 不抛出错误,避免影响主流程 } } else { // 对于有延迟的任务,也启动调度器(如果未运行) try { console.debug(`[租户${tenantId}] 检查并启动延迟调度器...`); const delaySchedulerService = new DelaySchedulerService(this.dataSource, this.feieConfig, tenantId); // 检查调度器状态 const status = delaySchedulerService.getStatus(); if (!status.isRunning) { await delaySchedulerService.start(); console.debug(`[租户${tenantId}] 延迟调度器已启动`); } else { console.debug(`[租户${tenantId}] 延迟调度器已在运行中`); } } catch (error) { console.warn(`[租户${tenantId}] 启动调度器失败:`, error); // 不抛出错误,避免影响主流程 } } } catch (error) { console.error(`[租户${tenantId}] 处理订单支付成功事件失败,订单ID: ${orderId}:`, error); // 不抛出错误,避免影响支付流程 } } /** * 处理订单退款事件 * @param tenantId 租户ID * @param orderId 订单ID */ async handleOrderRefund( tenantId: number, orderId: number ): Promise { try { console.debug(`[租户${tenantId}] 处理订单退款事件,订单ID: ${orderId}`); // 1. 查找关联的打印任务 const { tasks: printTasks } = await this.printTaskService.getPrintTasks(tenantId, { orderId, printStatus: PrintStatus.PENDING // 先查询PENDING状态的任务 }); if (printTasks.length === 0) { console.debug(`[租户${tenantId}] 未找到关联的打印任务,订单ID: ${orderId}`); return; } // 2. 取消所有关联的打印任务 for (const task of printTasks) { await this.printTaskService.cancelPrintTask(tenantId, task.taskId, CancelReason.REFUND); console.debug(`[租户${tenantId}] 打印任务已取消,任务ID: ${task.taskId}, 订单ID: ${orderId}`); } console.debug(`[租户${tenantId}] 订单退款事件处理完成,取消 ${printTasks.length} 个打印任务`); } catch (error) { console.error(`[租户${tenantId}] 处理订单退款事件失败,订单ID: ${orderId}:`, error); // 不抛出错误,避免影响退款流程 } } /** * 获取防退款延迟时间(秒) */ private async getAntiRefundDelaySeconds(tenantId: number): Promise { try { const delayValue = await this.getConfigValue(tenantId, 'feie.anti_refund_delay', '120'); const delaySeconds = parseInt(delayValue, 10); // 验证延迟时间范围 if (isNaN(delaySeconds) || delaySeconds < 0) { console.warn(`[租户${tenantId}] 无效的防退款延迟时间配置: ${delayValue},使用默认值120秒`); return 120; } // 限制最大延迟时间(例如24小时) const maxDelay = 24 * 60 * 60; // 24小时 if (delaySeconds > maxDelay) { console.warn(`[租户${tenantId}] 防退款延迟时间超过最大值: ${delaySeconds}秒,限制为${maxDelay}秒`); return maxDelay; } return delaySeconds; } catch (error) { console.warn(`[租户${tenantId}] 获取防退款延迟时间失败,使用默认值120秒:`, error); return 120; } } /** * 获取完整的订单信息 */ private async getFullOrderInfo( tenantId: number, orderId: number ): Promise<{ orderNo: string; amount: number; userId: number; items?: Array<{ name: string | null; quantity: number; price: number; }>; // 新增字段 payAmount: number; freightAmount: number; address: string | null; receiverMobile: string | null; recevierName: string | null; state: number; payState: number; createdAt: Date; } | null> { try { const order = await this.orderRepository.findOne({ where: { tenantId, id: orderId }, relations: ['orderGoods'] // 关联订单商品 }); if (!order) { console.warn(`[租户${tenantId}] 未找到订单,订单ID: ${orderId}`); return null; } // 构建完整的订单信息 return { orderNo: order.orderNo, amount: order.amount, userId: order.userId, items: order.orderGoods?.map((goods: any) => ({ name: goods.goodsName, quantity: goods.num, price: goods.price })) || [], // 新增字段 payAmount: order.payAmount, freightAmount: order.freightAmount, address: order.address, receiverMobile: order.receiverMobile, recevierName: order.recevierName, state: order.state, payState: order.payState, createdAt: order.createdAt }; } catch (error) { console.error(`[租户${tenantId}] 获取完整订单信息失败,订单ID: ${orderId}:`, error); return null; } } /** * 获取配置值 */ private async getConfigValue(tenantId: number, key: string, defaultValue: string): Promise { try { const config = await this.configRepository.findOne({ where: { tenantId, configKey: key } }); return config?.configValue || defaultValue; } catch (error) { console.warn(`[租户${tenantId}] 获取配置失败,key: ${key}:`, error); return defaultValue; } } /** * 获取打印模板 */ private async getPrintTemplate(tenantId: number): Promise { try { const template = await this.getConfigValue(tenantId, 'feie.receipt_template', ''); if (template) { return template; } // 如果没有配置模板,使用默认模板 return ` 订单收据
订单号: {orderNo} 下单时间: {orderTime}
收货信息 收货人: {receiverName} 联系电话: {receiverPhone} 收货地址: {address}
商品信息 {goodsList}
费用明细 商品总额: {totalAmount} 运费: {freightAmount} 实付金额: {payAmount}
订单状态 订单状态: {orderStatus} 支付状态: {payStatus}
感谢您的惠顾!
{orderNo} `; } catch (error) { console.warn(`[租户${tenantId}] 获取打印模板失败,使用默认模板:`, error); return ` 订单收据
订单号: {orderNo} 下单时间: {orderTime}
收货信息 收货人: {receiverName} 联系电话: {receiverPhone} 收货地址: {address}
商品信息 {goodsList}
费用明细 商品总额: {totalAmount} 运费: {freightAmount} 实付金额: {payAmount}
订单状态 订单状态: {orderStatus} 支付状态: {payStatus}
感谢您的惠顾!
{orderNo} `; } } /** * 生成小票打印内容 */ private async generateReceiptContent( tenantId: number, orderInfo: { orderNo: string; amount: number; userId: number; items?: Array<{ name: string | null; quantity: number; price: number; }>; // 新增字段 payAmount: number; freightAmount: number; address: string | null; receiverMobile: string | null; recevierName: string | null; state: number; payState: number; createdAt: Date; } ): Promise { const { orderNo, amount, items = [], payAmount, freightAmount, address, receiverMobile, recevierName, state, payState, createdAt } = orderInfo; try { // 1. 获取打印模板 const template = await this.getPrintTemplate(tenantId); // 2. 准备模板变量 // 状态映射函数 const getOrderStatusLabel = (state: number): string => { const statusMap: Record = { 0: '未发货', 1: '已发货', 2: '收货成功', 3: '已退货' }; return statusMap[state] || '未知'; }; const getPayStatusLabel = (payState: number): string => { const payStatusMap: Record = { 0: '未支付', 1: '支付中', 2: '支付成功', 3: '已退款', 4: '支付失败', 5: '订单关闭' }; return payStatusMap[payState] || '未知'; }; // 确保金额是数字类型 const safeAmount = typeof amount === 'number' ? amount : parseFloat(amount as any) || 0; const safePayAmount = typeof payAmount === 'number' ? payAmount : parseFloat(payAmount as any) || 0; const safeFreightAmount = typeof freightAmount === 'number' ? freightAmount : parseFloat(freightAmount as any) || 0; const variables = { orderNo, orderTime: new Date(createdAt).toLocaleString('zh-CN'), receiverName: recevierName || '客户', receiverPhone: receiverMobile || '未提供', phone: receiverMobile || '未提供', // 兼容变量 address: address || '未提供地址', goodsList: items.map(item => { const itemName = item.name || '未命名商品'; const itemPrice = typeof item.price === 'number' ? item.price : parseFloat(item.price as any) || 0; const itemQuantity = typeof item.quantity === 'number' ? item.quantity : parseInt(item.quantity as any, 10) || 0; const itemTotal = itemPrice * itemQuantity; return `${itemName} × ${itemQuantity} = ¥${itemTotal.toFixed(2)}`; }).join('\n') || '暂无商品信息', totalAmount: `¥${safeAmount.toFixed(2)}`, freightAmount: `¥${safeFreightAmount.toFixed(2)}`, payAmount: `¥${safePayAmount.toFixed(2)}`, orderStatus: getOrderStatusLabel(state), payStatus: getPayStatusLabel(payState) }; // 3. 替换模板变量 let content = template; for (const [key, value] of Object.entries(variables)) { content = content.replace(new RegExp(`{${key}}`, 'g'), value); } return content.trim(); } catch (error) { console.warn(`[租户${tenantId}] 生成打印内容失败,使用简单模板:`, error); // 失败时使用简单的回退模板 const lines = [ '订单小票
', '------------------------
', `订单号:${orderNo}
`, `下单时间:${new Date(createdAt).toLocaleString('zh-CN')}
`, `收货人:${recevierName || '客户'}
`, `联系电话:${receiverMobile || '未提供'}
`, `地址:${address || '未提供地址'}
`, '------------------------
', '商品明细:
' ]; // 添加商品明细 items.forEach(item => { const itemPrice = typeof item.price === 'number' ? item.price : parseFloat(item.price as any) || 0; const itemQuantity = typeof item.quantity === 'number' ? item.quantity : parseInt(item.quantity as any, 10) || 0; const itemTotal = itemPrice * itemQuantity; const itemName = item.name || '未命名商品'; lines.push(`${itemName} x${itemQuantity}
`); lines.push(` ¥${itemPrice.toFixed(2)} x ${itemQuantity} = ¥${itemTotal.toFixed(2)}
`); }); // 添加总计 const safeAmount = typeof amount === 'number' ? amount : parseFloat(amount as any) || 0; const safePayAmount = typeof payAmount === 'number' ? payAmount : parseFloat(payAmount as any) || 0; const safeFreightAmount = typeof freightAmount === 'number' ? freightAmount : parseFloat(freightAmount as any) || 0; lines.push('------------------------
'); lines.push(`商品总额:¥${safeAmount.toFixed(2)}
`); lines.push(`运费:¥${safeFreightAmount.toFixed(2)}
`); lines.push(`实付金额:¥${safePayAmount.toFixed(2)}
`); lines.push('------------------------
'); lines.push('感谢您的惠顾!
'); lines.push('https://example.com/order/' + orderNo + '
'); return lines.join(''); } } }