getOrderInfo($billId); if (!$orderInfo) { return array('success' => false, 'message' => '订单不存在'); } // 检查是否需要配送 if (!$this->needDelivery($orderInfo)) { return array('success' => false, 'message' => '订单不需要配送'); } // 检查是否已同步 if ($this->isOrderSynced($billId)) { return array('success' => false, 'message' => '订单已同步,无需重复同步'); } // 创建骑手订单 $riderOrderId = $this->createRiderOrder($orderInfo); if (!$riderOrderId) { return array('success' => false, 'message' => '创建骑手订单失败'); } // 记录同步日志 $this->logSync($billId, $riderOrderId, 'manual', '手动同步'); return array( 'success' => true, 'message' => '同步成功', 'rider_order_id' => $riderOrderId ); } catch (Exception $e) { return array('success' => false, 'message' => '同步失败:' . $e->getMessage()); } } /** * 测试入口:使用外部传入的订单信息直接创建骑手订单 * 无需订单状态与权限校验,仅用于测试接口 * @param array $orderInfo * @return int|false 骑手订单ID */ public function createRiderOrderFromPayload($orderInfo) { if (!is_array($orderInfo)) { return false; } // 基础字段校验 if (empty($orderInfo['merchant_id']) || empty($orderInfo['store_id'])) { return false; } // 直接调用内部创建逻辑 return $this->createRiderOrder($orderInfo); } /** * 批量同步订单 * @param array $conditions 同步条件 * @return array 同步结果 */ public function batchSyncOrders($conditions = array()) { try { $where = array(); $where['b.pay_state'] = 1; // 已支付 $where['o.pickup_type'] = 2; // 需要配送 $where['o.status'] = 1; // 只同步待发货订单 // 排除退款订单 $where['b.order_type'] = 1; // 只同步购买订单,排除退款订单(2) $where['b.refund_status'] = 0; // 只同步正常订单,排除申请退款/退款成功的订单 // 添加时间条件 if (!empty($conditions['start_date'])) { $where['b.add_time'] = array('egt', $conditions['start_date']); } if (!empty($conditions['end_date'])) { $where['b.add_time'] = array('elt', $conditions['end_date']); } // 添加门店条件 if (!empty($conditions['store_id'])) { $where['b.store_id'] = $conditions['store_id']; } // 获取需要同步的订单 $orders = M('retail_bill_order')->alias('b') ->join('ls_retail_bill_online o ON b.id = o.bill_id') ->where($where) ->field('b.*, b.order_type, b.refund_status, o.user_name, o.user_phone, o.user_address, o.delivery_fee') ->select(); $successCount = 0; $failCount = 0; $skipCount = 0; $errors = array(); foreach ($orders as $order) { // 检查是否已同步 if ($this->isOrderSynced($order['id'])) { $skipCount++; continue; } $result = $this->syncSingleOrder($order['id']); if ($result['success']) { $successCount++; } else { $failCount++; $errors[] = "订单{$order['order_no']}:{$result['message']}"; } } return array( 'success' => true, 'message' => "同步完成:成功{$successCount}个,失败{$failCount}个,跳过{$skipCount}个", 'details' => array( 'success_count' => $successCount, 'fail_count' => $failCount, 'skip_count' => $skipCount, 'errors' => $errors ) ); } catch (Exception $e) { return array('success' => false, 'message' => '批量同步失败:' . $e->getMessage()); } } /** * 获取订单信息 * @param int $billId 订单ID * @return array|false 订单信息 */ private function getOrderInfo($billId) { return M('retail_bill_order')->alias('b') ->join('ls_retail_bill_online o ON b.id = o.bill_id') ->join('ls_retail_store s ON b.store_id = s.id', 'LEFT') ->where(array('b.id' => $billId)) ->field('b.*, b.order_type, b.refund_status, o.user_name, o.user_phone, o.user_address, o.delivery_fee, o.pickup_type, o.status as online_status, s.storetag as store_name, s.address as store_address, s.phone as store_phone, s.longitude as store_lng, s.latitude as store_lat') ->find(); } /** * 检查订单是否需要配送 * @param array $orderInfo 订单信息 * @return bool */ private function needDelivery($orderInfo) { // 检查:需要配送、已支付、非退款订单 $isDeliveryType = $orderInfo['pickup_type'] == 2; $isPaid = $orderInfo['pay_state'] == 1; $isPurchaseOrder = isset($orderInfo['order_type']) && $orderInfo['order_type'] == 1; // 购买订单 $isNotRefunded = !isset($orderInfo['refund_status']) || $orderInfo['refund_status'] == 0; // 未退款 return $isDeliveryType && $isPaid && $isPurchaseOrder && $isNotRefunded; } /** * 检查订单是否已同步 * @param int $billId 订单ID * @return bool */ private function isOrderSynced($billId) { $count = M('rider_order')->where(array('bill_id' => $billId))->count(); return $count > 0; } /** * 创建骑手订单 * @param array $orderInfo 订单信息 * @return int|false 骑手订单ID */ private function createRiderOrder($orderInfo) { // 调试日志:初始化 $debug = array( 'bill_id' => isset($orderInfo['id']) ? $orderInfo['id'] : 0, 'merchant_id' => isset($orderInfo['merchant_id']) ? $orderInfo['merchant_id'] : 0, 'store_id' => isset($orderInfo['store_id']) ? $orderInfo['store_id'] : 0, 'user_address' => isset($orderInfo['user_address']) ? $orderInfo['user_address'] : '' ); // 解析客户地址经纬度 $customerLat = 0; $customerLng = 0; if (!empty($orderInfo['user_address'])) { $coords = \getAddress($orderInfo['user_address'], 1); // 使用系统地图设置进行解析,返回高德坐标 if (is_array($coords) && isset($coords['lat']) && isset($coords['lng'])) { $customerLat = floatval($coords['lat']); $customerLng = floatval($coords['lng']); } } // 调试日志:地址解析结果 $debug['customer_coords'] = array('lat' => $customerLat, 'lng' => $customerLng); // 门店经纬度 $storeLat = isset($orderInfo['store_lat']) ? floatval($orderInfo['store_lat']) : 0; $storeLng = isset($orderInfo['store_lng']) ? floatval($orderInfo['store_lng']) : 0; $debug['store_coords'] = array('lat' => $storeLat, 'lng' => $storeLng); // 计算门店到客户的距离 $distance = 0; $deliveryFee = $orderInfo['delivery_fee'] ? floatval($orderInfo['delivery_fee']) : 0.0; $feeService = null; if ($storeLat && $storeLng && $customerLat && $customerLng) { $feeService = new \Retail\Service\DeliveryFeeService($orderInfo['merchant_id']); // 使用地图API骑行路线距离,失败自动回退到直线距离 // 读取地图类型,便于诊断 $mapConf = M('sms_config')->find(); $mapTypeCode = isset($mapConf['server_map_type']) ? (string)$mapConf['server_map_type'] : ''; $mapType = ($mapTypeCode === '1') ? 'baidu' : (($mapTypeCode === '2') ? 'amap' : (($mapTypeCode === '3') ? 'tencent' : 'unknown')); $debug['map_type'] = $mapType; $distance = $feeService->calculateRouteDistance($storeLat, $storeLng, $customerLat, $customerLng, 'bicycling'); $debug['distance_km'] = $distance; // 根据骑手端配送费配置计算配送费 $orderAmount = isset($orderInfo['all_money']) ? floatval($orderInfo['all_money']) : 0.0; $feeResult = $feeService->calculateDeliveryFee(array( 'distance' => $distance, 'order_amount' => $orderAmount, )); if (is_array($feeResult) && isset($feeResult['success']) && $feeResult['success']) { $deliveryFee = floatval($feeResult['delivery_fee']); } // 记录计费详情(包含失败原因) $debug['order_amount'] = $orderAmount; $debug['fee_result'] = is_array($feeResult) ? $feeResult : array('raw' => $feeResult); $debug['final_delivery_fee'] = $deliveryFee; } else { // 坐标缺失,记录原因 $debug['coords_missing'] = array( 'store_lat' => $storeLat, 'store_lng' => $storeLng, 'customer_lat' => $customerLat, 'customer_lng' => $customerLng ); } // 组装写入数据(字段映射与 ls_rider_order 保持一致) $riderOrderData = array( 'order_no' => $this->generateRiderOrderNumber(), 'bill_id' => $orderInfo['id'], 'merchant_id' => $orderInfo['merchant_id'], 'store_id' => $orderInfo['store_id'], 'rider_id' => 0, 'order_type' => 1, // 即时单 'delivery_fee' => $deliveryFee, 'distance' => $distance, 'estimated_time' => 30, // 预计30分钟 // 商家信息 'store_name' => !empty($orderInfo['store_name']) ? $orderInfo['store_name'] : '', 'store_address' => !empty($orderInfo['store_address']) ? $orderInfo['store_address'] : '', 'store_phone' => !empty($orderInfo['store_phone']) ? $orderInfo['store_phone'] : '', 'store_lat' => $storeLat ? $storeLat : null, 'store_lng' => $storeLng ? $storeLng : null, // 客户信息 'customer_name' => !empty($orderInfo['user_name']) ? $orderInfo['user_name'] : '', 'customer_phone' => !empty($orderInfo['user_phone']) ? $orderInfo['user_phone'] : '', 'customer_address' => !empty($orderInfo['user_address']) ? $orderInfo['user_address'] : '', 'customer_lat' => $customerLat ? $customerLat : null, 'customer_lng' => $customerLng ? $customerLng : null, // 订单状态 'order_status' => 0, // 待接单 'add_time' => date('Y-m-d H:i:s'), 'update_time' => date('Y-m-d H:i:s'), ); $riderOrderId = M('rider_order')->add($riderOrderData); return $riderOrderId; } /** * 生成骑手订单号 * @return string */ private function generateRiderOrderNumber() { return 'RO' . date('YmdHis') . rand(1000, 9999); } /** * 记录同步日志 * @param int $billId 原订单ID * @param int $riderOrderId 骑手订单ID * @param string $syncType 同步类型 * @param string $remark 备注 */ private function logSync( $billId, $riderOrderId, $operationType, $remark = '', $syncResult = 1, $errorMsg = '', $requestData = '', $responseData = '', $oldStatus = null, $newStatus = null, $riderId = null, $retryCount = 0 ) { // 根据 ls_rider_order_sync_log 表结构规范化日志字段 $now = date('Y-m-d H:i:s'); // 规范化 operation_type,确保不超过 varchar(20) $op = (string)$operationType; $opMap = array( 'create_rider_order_geocode' => 'geocode', 'create_rider_order_distance' => 'distance', 'create_rider_order_fee_calc' => 'fee_calc', 'create_rider_order_coords_missing' => 'coords_miss', 'create_rider_order_saved' => 'saved', 'manual' => 'manual', 'auto' => 'auto', ); $opNorm = isset($opMap[$op]) ? $opMap[$op] : $op; if (strlen($opNorm) > 20) { $opNorm = substr($opNorm, 0, 20); } if ($opNorm === '') { $opNorm = 'unknown'; } $logData = array( 'bill_id' => intval($billId), 'rider_order_id' => $riderOrderId ? intval($riderOrderId) : null, 'rider_id' => $riderId ? intval($riderId) : null, 'operation_type' => $opNorm, 'old_status' => is_null($oldStatus) ? null : intval($oldStatus), 'new_status' => is_null($newStatus) ? null : intval($newStatus), 'sync_result' => $syncResult ? 1 : 0, 'error_msg' => $errorMsg !== '' ? (string)$errorMsg : null, // 兼容旧调用:将 remark 优先记录到 request_data,若提供了 requestData 则覆盖 'request_data' => $requestData !== '' ? (string)$requestData : ($remark !== '' ? (string)$remark : null), 'response_data' => $responseData !== '' ? (string)$responseData : null, 'sync_time' => $now, 'retry_count' => intval($retryCount), 'add_time' => $now, 'update_time' => $now, ); M('rider_order_sync_log')->add($logData); } /** * 获取同步统计信息 * @param array $conditions 查询条件 * @return array */ public function getSyncStatistics($conditions = array()) { $where = array(); // 时间条件 if (!empty($conditions['start_date'])) { $where['sync_time'] = array('egt', $conditions['start_date']); } if (!empty($conditions['end_date'])) { $where['sync_time'] = array('elt', $conditions['end_date']); } $syncLogModel = M('rider_order_sync_log'); // 总同步数 $totalSync = $syncLogModel->where($where)->count(); // 成功同步数 $successSync = $syncLogModel->where(array_merge($where, array('sync_status' => 'success')))->count(); // 今日同步数 $todaySync = $syncLogModel->where(array_merge($where, array( 'sync_time' => array('like', date('Y-m-d') . '%') )))->count(); // 手动同步数 $manualSync = $syncLogModel->where(array_merge($where, array('sync_type' => 'manual')))->count(); // 自动同步数 $autoSync = $syncLogModel->where(array_merge($where, array('sync_type' => 'auto')))->count(); return array( 'total_sync' => $totalSync, 'success_sync' => $successSync, 'today_sync' => $todaySync, 'manual_sync' => $manualSync, 'auto_sync' => $autoSync, 'success_rate' => $totalSync > 0 ? round($successSync / $totalSync * 100, 2) : 0 ); } /** * 获取可同步的订单列表 * @param array $conditions 查询条件 * @return array */ public function getSyncableOrders($conditions = array()) { $where = array(); $where['b.pay_state'] = 1; // 已支付 $where['o.pickup_type'] = 2; // 需要配送 $where['o.status'] = array('in', array('1', '2')); // 未发货或已发货 // 排除退款订单 $where['b.order_type'] = 1; // 只同步购买订单,排除退款订单(2) $where['b.refund_status'] = 0; // 只同步正常订单,排除申请退款/退款成功的订单 // 排除已同步的订单 $syncedBillIds = M('rider_order')->getField('bill_id', true); if (!empty($syncedBillIds)) { $where['b.id'] = array('not in', $syncedBillIds); } // 添加查询条件 if (!empty($conditions['start_date'])) { $where['b.add_time'] = array('egt', $conditions['start_date']); } if (!empty($conditions['end_date'])) { $where['b.add_time'] = array('elt', $conditions['end_date']); } if (!empty($conditions['store_id'])) { $where['b.store_id'] = $conditions['store_id']; } $page = isset($conditions['page']) ? $conditions['page'] : 1; $pageSize = isset($conditions['pageSize']) ? $conditions['pageSize'] : 20; $orders = M('retail_bill_order')->alias('b') ->join('ls_retail_bill_online o ON b.id = o.bill_id') ->join('ls_retail_store s ON b.store_id = s.id', 'LEFT') ->where($where) ->field('b.id, b.order_no, b.all_money, b.add_time, b.store_id, b.order_type, b.refund_status, o.user_name, o.user_phone, o.user_address, o.delivery_fee, s.storetag as store_name') ->order('b.add_time DESC') ->page($page, $pageSize) ->select(); $total = M('retail_bill_order')->alias('b') ->join('ls_retail_bill_online o ON b.id = o.bill_id') ->where($where) ->count(); return array( 'list' => $orders, 'total' => $total, 'page' => $page, 'pageSize' => $pageSize ); } }