| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- 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<void> {
- 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<void> {
- 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<number> {
- 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<string> {
- 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<string> {
- try {
- const template = await this.getConfigValue(tenantId, 'feie.receipt_template', '');
- if (template) {
- return template;
- }
- // 如果没有配置模板,使用默认模板
- return `
- <CB>订单收据</CB>
- <BR>
- 订单号: {orderNo}
- 下单时间: {orderTime}
- <BR>
- <B>收货信息</B>
- 收货人: {receiverName}
- 联系电话: {receiverPhone}
- 收货地址: {address}
- <BR>
- <B>商品信息</B>
- {goodsList}
- <BR>
- <B>费用明细</B>
- 商品总额: {totalAmount}
- 运费: {freightAmount}
- 实付金额: {payAmount}
- <BR>
- <B>订单状态</B>
- 订单状态: {orderStatus}
- 支付状态: {payStatus}
- <BR>
- <C>感谢您的惠顾!</C>
- <BR>
- <QR>{orderNo}</QR>
- `;
- } catch (error) {
- console.warn(`[租户${tenantId}] 获取打印模板失败,使用默认模板:`, error);
- return `
- <CB>订单收据</CB>
- <BR>
- 订单号: {orderNo}
- 下单时间: {orderTime}
- <BR>
- <B>收货信息</B>
- 收货人: {receiverName}
- 联系电话: {receiverPhone}
- 收货地址: {address}
- <BR>
- <B>商品信息</B>
- {goodsList}
- <BR>
- <B>费用明细</B>
- 商品总额: {totalAmount}
- 运费: {freightAmount}
- 实付金额: {payAmount}
- <BR>
- <B>订单状态</B>
- 订单状态: {orderStatus}
- 支付状态: {payStatus}
- <BR>
- <C>感谢您的惠顾!</C>
- <BR>
- <QR>{orderNo}</QR>
- `;
- }
- }
- /**
- * 生成小票打印内容
- */
- 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<string> {
- 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<number, string> = {
- 0: '未发货',
- 1: '已发货',
- 2: '收货成功',
- 3: '已退货'
- };
- return statusMap[state] || '未知';
- };
- const getPayStatusLabel = (payState: number): string => {
- const payStatusMap: Record<number, string> = {
- 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 = [
- '<CB>订单小票</CB><BR>',
- '------------------------<BR>',
- `<B>订单号:</B>${orderNo}<BR>`,
- `<B>下单时间:</B>${new Date(createdAt).toLocaleString('zh-CN')}<BR>`,
- `<B>收货人:</B>${recevierName || '客户'}<BR>`,
- `<B>联系电话:</B>${receiverMobile || '未提供'}<BR>`,
- `<B>地址:</B>${address || '未提供地址'}<BR>`,
- '------------------------<BR>',
- '<B>商品明细:</B><BR>'
- ];
- // 添加商品明细
- 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}<BR>`);
- lines.push(` ¥${itemPrice.toFixed(2)} x ${itemQuantity} = ¥${itemTotal.toFixed(2)}<BR>`);
- });
- // 添加总计
- 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('------------------------<BR>');
- lines.push(`<B>商品总额:</B>¥${safeAmount.toFixed(2)}<BR>`);
- lines.push(`<B>运费:</B>¥${safeFreightAmount.toFixed(2)}<BR>`);
- lines.push(`<B>实付金额:</B>¥${safePayAmount.toFixed(2)}<BR>`);
- lines.push('------------------------<BR>');
- lines.push('<B>感谢您的惠顾!</B><BR>');
- lines.push('<QR>https://example.com/order/' + orderNo + '</QR><BR>');
- return lines.join('');
- }
- }
- }
|