diff --git a/app/controller/admin/v1/product/StoreProduct.php b/app/controller/admin/v1/product/StoreProduct.php index 1a55533..f2cedd3 100644 --- a/app/controller/admin/v1/product/StoreProduct.php +++ b/app/controller/admin/v1/product/StoreProduct.php @@ -22,10 +22,12 @@ use app\services\product\shipping\ShippingTemplatesServices; use app\services\product\sku\StoreProductAttrServices; use app\services\product\sku\StoreProductAttrValueServices; use app\services\product\sku\StoreProductRuleServices; +use app\jobs\product\ProductSyncErp; use crmeb\services\FileService; use crmeb\services\SpreadsheetExcelService; use crmeb\services\UploadService; use think\annotation\Inject; +use think\facade\Log; /** * Class StoreProduct @@ -992,6 +994,39 @@ class StoreProduct extends AuthController return $this->success('已加入消息队列进行同步'); } + public function sync_jst_product() + { + [$spuStr, $sync] = $this->request->postMore([ + ['spu_str', ""], + ['sync', 0] + ], true); + if (!$spuStr) { + return $this->fail('请输入ERP商品SPU'); + } + $spus = array_unique(array_filter(explode(',', $spuStr))); + $traceId = uniqid('jst_', true); + Log::info('admin.manual.jst.product.sync.dispatch', [ + 'trace_id' => $traceId, + 'admin_id' => $this->adminId, + 'admin_type' => $this->adminType, + 'supplier_id' => $this->supplierId, + 'client_ip' => $this->request->ip(), + 'count' => count($spus), + 'spus' => $spus + ]); + if ($sync) { + foreach ($spus as $spu) { + (new ProductSyncErp())->productFromErp($spu, (int)$this->supplierId); + } + return $this->success('同步完成'); + } else { + foreach ($spus as $spu) { + ProductSyncErp::dispatchDo('productFromErp', [$spu, (int)$this->supplierId]); + } + return $this->success('正在同步中,请稍后查看'); + } + } + /** * 商品批量操作 * @param StoreProductBatchProcessServices $batchProcessServices diff --git a/app/controller/api/v1/PublicController.php b/app/controller/api/v1/PublicController.php index a781df0..7c31afd 100644 --- a/app/controller/api/v1/PublicController.php +++ b/app/controller/api/v1/PublicController.php @@ -39,6 +39,8 @@ use Joypack\Tencent\Map\Bundle\LocationOption; use app\Request; use crmeb\services\CacheService; use crmeb\services\UploadService; +use think\facade\Log; +use think\Response; /** * 公共类 @@ -131,6 +133,22 @@ class PublicController return app('json')->success($data); } + public function jstCallback(Request $request) + { + $data = json_decode((string)$request->getContent(), true); + if (!is_array($data)) { + $data = $request->param(); + } + $actionCode = $data['action_code'] ?? ($request->param('action_code') ?: ''); + Log::info('jst.api.callback ' . $actionCode); + if ($actionCode === 'business_sku_syn') (new \app\haichao\callback\event\JstProductEvent())->event($request); + if ($actionCode === 'sync_shipped') (new \app\haichao\callback\event\JstOrderPostEvent())->event($request); + if ($actionCode === 'orderPost') (new \app\haichao\callback\event\JstOrderPostEvent())->event($request); + if ($actionCode === 'orderRefund') (new \app\haichao\callback\event\JstOrderRefundEvent())->event($request); + if ($actionCode === 'trade_after_sale') (new \app\haichao\callback\event\JstTradeEvent())->event($request); + return Response::create(['code' => 0, 'msg' => 'ok'], 'json'); + } + /** * 获取个人中心菜单 * @param Request $request diff --git a/app/event.php b/app/event.php index 88379aa..2366efb 100644 --- a/app/event.php +++ b/app/event.php @@ -38,7 +38,9 @@ return [ //定时执行 'crontab' => (file_exists(root_path() . "public/install/") && !file_exists(root_path() . "public/install/install.lock")) || !env('TIMER_ENABLE', false) ? [] : [ - \app\listener\system\timer\SystemTimer::class,//定时任务 + \app\listener\system\timer\SystemTimer::class, + \app\haichao\task\JstSyncTask::class, + \app\haichao\task\JstSyncStorkTask::class, ], 'user.register' => [\app\listener\user\Register::class], //用户注册事件 diff --git a/app/haichao/HaichaoAop.php b/app/haichao/HaichaoAop.php new file mode 100644 index 0000000..4c788e9 --- /dev/null +++ b/app/haichao/HaichaoAop.php @@ -0,0 +1,86 @@ +rule(); + Log::info('jst.aop.route ' . $rule->getName()); + switch ($rule->getName()) { + case 'StoreOrderSplitDelivery': + case 'StoreOrderUpdateDelivery': + { + $postInfo = $request->getMore(['delivery_name', 'delivery_id']); + $param = $request->param(); + Log::info('jst.aop.delivery ' . json_encode([$postInfo, $param], JSON_UNESCAPED_UNICODE)); + $haichaoJstServices->updateStoreOrderDelivery($postInfo['delivery_id'] ?? '', $postInfo['delivery_name'] ?? '', $param['id'] ?? 0); + $rt = $next($request); + break; + } + case 'StoreOrderApplRefund': + { + $orderId = $request->param(['id'])['id'] ?? 0; + Log::info('jst.aop.apply_refund ' . $orderId); + $rt = $next($request); + break; + } + case 'StoreOrderUpdateRefund': + { + $param = $request->param(); + $orderId = $param['id'] ?? 0; + Log::info('jst.aop.update_refund ' . $orderId); + $rt = $next($request); + break; + } + case 'StoreOrderRefundRemark': + { + $orderId = $request->param(['id'])['id'] ?? 0; + Log::info('jst.aop.refund_remark ' . $orderId); + $rt = $next($request); + break; + } + case 'StoreOrderorRemark': + { + $orderId = $request->param(['id'])['id'] ?? 0; + Log::info('jst.aop.remark ' . $orderId); + $rt = $next($request); + break; + } + case 'orderPay': + { + $rt = $next($request); + break; + } + default: + { + $rt = $next($request); + } + } + + // $actionCode = $request->getMore(['action_code'])['action_code'] ?? ''; + // if ($actionCode) { + // try { + // Log::info('jst.aop.callback ' . $actionCode); + // return JstCallbackEventManager::callback($request); + // } catch (\Throwable $e) { + // Log::error('jst.aop.callback.err ' . $e->getMessage()); + // return Response::create(['msg' => 'callback error ' . $e->getMessage()], 'json'); + // } + // } + + return $rt; + } +} diff --git a/app/haichao/HaichaoBridge.php b/app/haichao/HaichaoBridge.php new file mode 100644 index 0000000..d5c1615 --- /dev/null +++ b/app/haichao/HaichaoBridge.php @@ -0,0 +1,88 @@ + '', + 'appSecret' => '', + 'api' => 'https://openapi.jushuitan.com/', + 'access_token' => '', + 'shopId' => '', + ]; + + public function __construct() + { + $this->api['appid'] = env('JST_APP_KEY', ''); + $this->api['appSecret'] = env('JST_APP_SECRET', ''); + $this->api['api'] = env('JST_API', 'https://openapi.jushuitan.com/'); + $this->api['access_token'] = env('JST_ACCESS_TOKEN', ''); + $this->api['shopId'] = (string)env('JST_SHOP_ID', ''); + } + + public function getShopId(): string + { + return $this->api['shopId']; + } + + /** + * 聚水潭开放平台请求封装 + * @param string $api + * @param array $data + * @param array $addonBody + * @return array + */ + protected function post(string $api, array $data = [], array $addonBody = []): array + { + if (!$this->api['appid'] || !$this->api['appSecret'] || !$this->api['access_token']) { + throw new ValidateException('JST config missing'); + } + $time = time(); + $baseBody = [ + 'access_token' => $this->api['access_token'], + 'app_key' => $this->api['appid'], + ]; + $baseBody = array_merge($baseBody, $addonBody); + $baseBody['biz'] = json_encode($data, JSON_UNESCAPED_UNICODE); + $baseBody['charset'] = 'utf-8'; + $baseBody['timestamp'] = $time; + $baseBody['version'] = '2'; + $baseBody['sign'] = $this->sign($baseBody); + $options['http'] = [ + 'timeout' => 60, + 'method' => 'POST', + 'header' => 'Content-Type: application/json;charset=utf-8', + 'content' => http_build_query($baseBody) + ]; + $rt = json_decode(file_get_contents("{$this->api['api']}$api", false, stream_context_create($options)), true); + if (!is_array($rt) || !array_key_exists('code', $rt)) { + Log::error('JST response invalid ' . json_encode([ + 'api' => $api, + ], JSON_UNESCAPED_UNICODE)); + throw new ValidateException('JST response invalid'); + } + if ($rt['code'] != 0) { + Log::error('JST request failed ' . json_encode([ + 'api' => $api, + 'code' => $rt['code'], + 'msg' => (string)($rt['msg'] ?? ''), + ], JSON_UNESCAPED_UNICODE)); + throw new ValidateException($rt['msg'] ?? 'JST error'); + } + return $rt; + } + + private function sign(array $data): string + { + $computerStr = ""; + foreach ($data as $k => $v) { + $computerStr .= $k . $v; + } + $computerStr = $this->api['appSecret'] . $computerStr; + return md5($computerStr); + } +} diff --git a/app/haichao/HaichaoJstServices.php b/app/haichao/HaichaoJstServices.php new file mode 100644 index 0000000..6d01277 --- /dev/null +++ b/app/haichao/HaichaoJstServices.php @@ -0,0 +1,815 @@ + $postId, + 'l_name' => $postName, + 'warehouseRemark' => '' + ]; + $rt = $this->post('open/express/register/upload', $postList); + $this->orderPush($oid, 'WAIT_BUYER_CONFIRM_GOODS', '', [], [ + 'l_id' => $postId, + 'l_name' => $postName, + 'warehouseRemark' => '' + ]); + return $rt; + } + + /** + * 推送订单到聚水潭(含支付/发货/退款等状态同步) + * @param int $oid + * @param string $status + * @param string $rftype + * @param array $rfinfo + * @param array $extpm + * @return bool + */ + public function orderPush(int $oid, string $status = 'WAIT_SELLER_SEND_GOODS', $rftype = '', $rfinfo = [], $extpm = []): bool + { + Log::info('jst.order_push.start ' . $oid . ' ' . $status); + /** @var StoreOrderServices $orderServices */ + $orderServices = app()->make(StoreOrderServices::class); + $order = $orderServices->get($oid, ['*'], ['store']); + if (!$order || $order['id'] < 1) { + throw new AdminException('订单信息不能为空'); + } + if ($order['product_type'] != 4) { + if ($order['product_type'] > 0 || !$order['paid']) { + return true; + } + } + $rid = $order['supplier_id'] ?? 0; + $shopid = (int)$this->getShopId(); + if ($rid) { + /** @var SystemSupplierServices $systemSupplierServices */ + $systemSupplierServices = app()->make(SystemSupplierServices::class); + $info = $systemSupplierServices->getSupplierInfo((int)$rid, 'erp_shopid'); + if (!$info || !$info['erp_shopid']) return true; + $shopid = (int)$info['erp_shopid']; + if (!$shopid) return true; + } + $items = $this->getItems($oid, (int)$order['uid'], $order['unique'], $rftype); + if (isset($extpm['l_id'])) { + $order['delivery_id'] = $extpm['l_id']; + $order['delivery_name'] = $extpm['l_name']; + } + $biz = [ + 'shop_id' => $shopid, + 'so_id' => $order['order_id'], + 'shop_status' => $status, + 'buyer_message' => $order['mark'], + 'shop_buyer_id' => (string)$order['uid'], + 'receiver_name' => $order['real_name'], + 'receiver_mobile' => $order['user_phone'], + 'pay_amount' => (float)$order['pay_price'], + 'freight' => (float)$order['freight_price'], + 'order_date' => date('Y-m-d H:i:s', $order['add_time']), + 'shop_modified' => date('Y-m-d H:i:s', $order['add_time']), + 'items' => $items, + 'l_id' => $order['delivery_id'] ?? '', + 'logistics_company' => $order['delivery_name'] ?? '', + 'pay' => [], + ]; + if ($rfinfo) { + $biz['pay'] = [ + 'outer_pay_id' => $rfinfo['order_id'] ?? $order['order_id'], + 'pay_date' => date('Y-m-d H:i:s'), + 'payment' => $order['pay_type'], + 'seller_account' => 'seller', + 'buyer_account' => 'buyer' . $order['uid'], + 'amount' => 0 - (float)($rfinfo['refunded_price'] ?? 0) + ]; + $q = [ + 'shop_id' => $shopid, + 'so_ids' => [$order['order_id']] + ]; + $resultb = $this->post('open/orders/single/query', $q)['data']; + $biz['shop_status'] = $resultb['orders'][0]['shop_status'] ?? $status; + } else { + $biz['pay'] = [ + 'outer_pay_id' => $order['order_id'], + 'pay_date' => date('Y-m-d H:i:s', $order['pay_time']), + 'payment' => $order['pay_type'], + 'seller_account' => 'seller', + 'buyer_account' => 'buyer' . $order['uid'], + 'amount' => (float)$order['pay_price'] + ]; + } + $addr = $this->getAddress($order['user_address']); + $biz = array_merge($biz, $addr); + if (isset($biz['l_id']) && $biz['l_id'] === '') { + unset($biz['l_id']); + } + if (isset($biz['logistics_company']) && $biz['logistics_company'] === '') { + unset($biz['logistics_company']); + } + if (isset($biz['receiver_zip']) && $biz['receiver_zip'] === '') { + unset($biz['receiver_zip']); + } + if (isset($biz['items']) && is_array($biz['items'])) { + foreach ($biz['items'] as $k => $item) { + if (is_array($item) && isset($item['pic']) && $item['pic'] === '') { + unset($item['pic']); + $biz['items'][$k] = $item; + } + } + } + $num = 3; + for ($i = $num; $i >= 0; $i--) { + if ($i <= 0) { + Log::error('ERP订单上传失败 ' . json_encode([ + 'api' => 'open/jushuitan/orders/upload', + 'shop_id' => $shopid, + 'so_id' => (string)($biz['so_id'] ?? ''), + ], JSON_UNESCAPED_UNICODE)); + return false; + } + try { + $resp = $this->post('open/jushuitan/orders/upload', [$biz]); + $result = $resp['data'] ?? []; + if (!empty($result['datas'][0]['issuccess']) && $order['erp_order_id'] != $biz['so_id']) { + $order->save(['erp_id' => $result['datas'][0]['o_id'], 'erp_order_id' => $biz['so_id']]); + } + break; + } catch (\Throwable $e) { + usleep(1000 * 50); + Log::error('ERP订单上传失败:' . $e->getMessage() . ' ' . json_encode([ + 'try' => ($num - $i + 1), + 'api' => 'open/jushuitan/orders/upload', + 'shop_id' => $shopid, + 'so_id' => (string)($biz['so_id'] ?? ''), + ], JSON_UNESCAPED_UNICODE)); + } + } + return true; + } + + /** + * 售后单创建推送(本地 -> 聚水潭) + * @param int $refundId + * @return bool + */ + public function afterSaleCreatePush(int $refundId): bool + { + $api = 'open/aftersale/upload'; + Log::info('jst.after_sale.create.start ' . json_encode(['refund_id' => $refundId], JSON_UNESCAPED_UNICODE)); + + /** @var StoreOrderRefundServices $storeOrderRefundServices */ + $storeOrderRefundServices = app()->make(StoreOrderRefundServices::class); + $refund = $storeOrderRefundServices->get($refundId); + if (!$refund) { + Log::warning('jst.after_sale.create.skip ' . json_encode(['refund_id' => $refundId, 'reason' => 'refund_missing'], JSON_UNESCAPED_UNICODE)); + return true; + } + if (method_exists($refund, 'toArray')) { + $refund = $refund->toArray(); + } + + /** @var StoreOrderServices $storeOrderServices */ + $storeOrderServices = app()->make(StoreOrderServices::class); + $order = $storeOrderServices->get((int)($refund['store_order_id'] ?? 0), ['*'], ['store']); + if (!$order) { + Log::warning('jst.after_sale.create.skip ' . json_encode(['refund_id' => $refundId, 'reason' => 'order_missing'], JSON_UNESCAPED_UNICODE)); + return true; + } + + if (($order['product_type'] ?? 0) != 4) { + if (($order['product_type'] ?? 0) > 0 || !($order['paid'] ?? 0)) { + Log::info('jst.after_sale.create.skip ' . json_encode([ + 'refund_id' => $refundId, + 'reason' => 'product_filter', + 'product_type' => $order['product_type'] ?? null, + 'paid' => $order['paid'] ?? null, + ], JSON_UNESCAPED_UNICODE)); + return true; + } + } + + $shopid = $this->resolveShopIdByOrder($order); + if (!$shopid) { + Log::info('jst.after_sale.create.skip ' . json_encode([ + 'refund_id' => $refundId, + 'reason' => 'missing_shopid', + 'supplier_id' => $order['supplier_id'] ?? 0 + ], JSON_UNESCAPED_UNICODE)); + return true; + } + + $outerAsId = (string)($refund['order_id'] ?? ''); + if ($outerAsId === '') { + $outerAsId = (string)$refundId; + } + + $items = $this->buildAfterSaleItemsFromRefund($refund); + + $applyType = (int)($refund['apply_type'] ?? $refund['refund_type'] ?? 0); + if ($applyType == 1) { + $typeStr = '仅退款'; + $goodStatus = 'BUYER_NOT_RECEIVED'; + } else { + $typeStr = '普通退货'; + $goodStatus = 'BUYER_RETURNED_GOODS'; + } + + if ($applyType == 1) { + $items = []; + } elseif (is_array($items)) { + foreach ($items as $idx => $it) { + if (is_array($it)) { + $it['type'] = '退货'; + $items[$idx] = $it; + } + } + } + + $biz = [ + 'shop_id' => $shopid, + 'so_id' => (string)($order['order_id'] ?? ''), + 'outer_as_id' => $outerAsId, + 'type' => $typeStr, + 'good_status' => $goodStatus, + 'shop_status' => 'wait', + 'total_amount' => (float)($order['pay_price'] ?? 0), + 'payment' => 0.00, + 'question_type' => (string)($refund['refund_reason'] ?? $refund['refund_explain'] ?? ''), + 'refund' => (float)($refund['refund_price'] ?? $refund['apply_price'] ?? 0), + 'remark' => (string)($refund['remark'] ?? ''), + 'items' => $items, + ]; + + $this->retryPost($api, [$biz], 'jst.after_sale.create'); + return true; + } + + /** + * 售后单撤销推送(本地 -> 聚水潭) + * @param int $refundId + * @return bool + */ + public function afterSaleCancelPush(int $refundId): bool + { + $api = 'open/webapi/aftersaleapi/open/cancel'; + Log::info('jst.after_sale.cancel.start ' . json_encode(['refund_id' => $refundId], JSON_UNESCAPED_UNICODE)); + + /** @var StoreOrderRefundServices $storeOrderRefundServices */ + $storeOrderRefundServices = app()->make(StoreOrderRefundServices::class); + $refund = $storeOrderRefundServices->get($refundId); + if (!$refund) { + Log::warning('jst.after_sale.cancel.skip ' . json_encode(['refund_id' => $refundId, 'reason' => 'refund_missing'], JSON_UNESCAPED_UNICODE)); + return true; + } + if (method_exists($refund, 'toArray')) { + $refund = $refund->toArray(); + } + + /** @var StoreOrderServices $storeOrderServices */ + $storeOrderServices = app()->make(StoreOrderServices::class); + $order = $storeOrderServices->get((int)($refund['store_order_id'] ?? 0), ['*'], ['store']); + if (!$order) { + Log::warning('jst.after_sale.cancel.skip ' . json_encode(['refund_id' => $refundId, 'reason' => 'order_missing'], JSON_UNESCAPED_UNICODE)); + return true; + } + + if (($order['product_type'] ?? 0) != 4) { + if (($order['product_type'] ?? 0) > 0 || !($order['paid'] ?? 0)) { + Log::info('jst.after_sale.cancel.skip ' . json_encode([ + 'refund_id' => $refundId, + 'reason' => 'product_filter', + 'product_type' => $order['product_type'] ?? null, + 'paid' => $order['paid'] ?? null, + ], JSON_UNESCAPED_UNICODE)); + return true; + } + } + + $shopid = $this->resolveShopIdByOrder($order); + if (!$shopid) { + Log::info('jst.after_sale.cancel.skip ' . json_encode([ + 'refund_id' => $refundId, + 'reason' => 'missing_shopid', + 'supplier_id' => $order['supplier_id'] ?? 0 + ], JSON_UNESCAPED_UNICODE)); + return true; + } + + $outerAsId = (string)($refund['order_id'] ?? ''); + if ($outerAsId === '') { + $outerAsId = (string)$refundId; + } + + $erpInfo = []; + if (!empty($refund['erp'])) { + $erpInfo = json_decode($refund['erp'], true) ?: []; + } + $asId = $erpInfo['as_id'] ?? 0; + + if ($asId) { + $biz = [ + 'as_ids' => [(int)$asId], + ]; + } else { + $biz = [ + 'shop_id' => $shopid, + 'so_id' => (string)($order['order_id'] ?? ''), + 'outer_as_id' => $outerAsId, + 'reason' => (string)($refund['refuse_reason'] ?? $refund['refund_reason'] ?? ''), + ]; + } + + $this->retryPost($api, $biz, 'jst.after_sale.cancel'); + return true; + } + + /** + * 售后单确认退款推送(本地 -> 聚水潭) + * @param int $refundId + * @return bool + */ + public function afterSaleRefundedPush(int $refundId): bool + { + $api = 'open/webapi/aftersaleapi/open/confirm'; + Log::info('jst.after_sale.refunded.start ' . json_encode(['refund_id' => $refundId], JSON_UNESCAPED_UNICODE)); + + /** @var StoreOrderRefundServices $storeOrderRefundServices */ + $storeOrderRefundServices = app()->make(StoreOrderRefundServices::class); + $refund = $storeOrderRefundServices->get($refundId); + if (!$refund) { + Log::warning('jst.after_sale.refunded.skip ' . json_encode(['refund_id' => $refundId, 'reason' => 'refund_missing'], JSON_UNESCAPED_UNICODE)); + return true; + } + if (method_exists($refund, 'toArray')) { + $refund = $refund->toArray(); + } + + /** @var StoreOrderServices $storeOrderServices */ + $storeOrderServices = app()->make(StoreOrderServices::class); + $order = $storeOrderServices->get((int)($refund['store_order_id'] ?? 0), ['*'], ['store']); + if (!$order) { + Log::warning('jst.after_sale.refunded.skip ' . json_encode(['refund_id' => $refundId, 'reason' => 'order_missing'], JSON_UNESCAPED_UNICODE)); + return true; + } + + if (($order['product_type'] ?? 0) != 4) { + if (($order['product_type'] ?? 0) > 0 || !($order['paid'] ?? 0)) { + Log::info('jst.after_sale.refunded.skip ' . json_encode([ + 'refund_id' => $refundId, + 'reason' => 'product_filter', + 'product_type' => $order['product_type'] ?? null, + 'paid' => $order['paid'] ?? null, + ], JSON_UNESCAPED_UNICODE)); + return true; + } + } + + $shopid = $this->resolveShopIdByOrder($order); + if (!$shopid) { + Log::info('jst.after_sale.refunded.skip ' . json_encode([ + 'refund_id' => $refundId, + 'reason' => 'missing_shopid', + 'supplier_id' => $order['supplier_id'] ?? 0 + ], JSON_UNESCAPED_UNICODE)); + return true; + } + + $outerAsId = (string)($refund['order_id'] ?? ''); + if ($outerAsId === '') { + $outerAsId = (string)$refundId; + } + + $erpInfo = []; + if (!empty($refund['erp'])) { + $erpInfo = json_decode($refund['erp'], true) ?: []; + } + $asId = $erpInfo['as_id'] ?? 0; + + if ($asId) { + $biz = [ + 'as_ids' => [(int)$asId], + 'exchange_force' => true, + 'confirm_refund' => true, + ]; + } else { + $biz = [ + 'shop_id' => $shopid, + 'so_id' => (string)($order['order_id'] ?? ''), + 'outer_as_id' => $outerAsId, + 'amount' => sprintf('%.2f', (float)($refund['refund_price'] ?? $refund['apply_price'] ?? 0)), + ]; + } + + $this->retryPost($api, $biz, 'jst.after_sale.refunded'); + return true; + } + + public function getItems(int $oid, int $uid, string $unique, $rftype = ''): array + { + /** @var StoreOrderCartInfoServices $storeOrderCartInfoServices */ + $storeOrderCartInfoServices = app()->make(StoreOrderCartInfoServices::class); + $cartInfo = $storeOrderCartInfoServices->getOrderCartInfo($oid); + if (empty($cartInfo)) { + /** @var StoreOrderServices $orderServices */ + $orderServices = app()->make(StoreOrderServices::class); + $orderInfo = $orderServices->getCacheOrderInfo($uid, $unique); + $cartInfo = $orderInfo['cartInfo'] ?? []; + } + $items = []; + foreach ($cartInfo as $cart) { + $cart = $cart['cart_info'] ?? $cart; + $attrInfo = $cart['productInfo']['attrInfo']; + $truePrice = (string)($cart['sum_true_price'] ?? $cart['truePrice']); + $itemdet = [ + 'sku_id' => $attrInfo['code'] ?: ($attrInfo['unique'] . 'ok'), + 'shop_sku_id' => $attrInfo['code'] ?: ($attrInfo['unique'] . 'ok'), + 'base_price' => (float)($attrInfo['price'] ?? 0), + 'price' => (float)bcdiv($truePrice, (string)$cart['cart_num'], 2), + 'amount' => (float)$truePrice, + 'qty' => $cart['cart_num'], + 'pic' => $cart['productInfo']['image'], + 'name' => $cart['productInfo']['store_name'], + 'properties_value' => $attrInfo['suk'], + 'outer_oi_id' => $cart['product_attr_unique'], + 'refund_qty' => $rftype == 'refund_nosend' ? $cart['cart_num'] : ($cart['refund_num'] ?? 0), + ]; + if ($itemdet['qty'] == $itemdet['refund_qty']) { + $itemdet['refund_status'] = 'success'; + } + $items[] = $itemdet; + } + return $items; + } + + private function resolveShopIdByOrder($order): int + { + $rid = $order['supplier_id'] ?? 0; + $shopid = (int)$this->getShopId(); + if ($rid) { + /** @var SystemSupplierServices $systemSupplierServices */ + $systemSupplierServices = app()->make(SystemSupplierServices::class); + $info = $systemSupplierServices->getSupplierInfo((int)$rid, 'erp_shopid'); + if (!$info || !$info['erp_shopid']) { + return 0; + } + $shopid = (int)$info['erp_shopid']; + } + return (int)$shopid; + } + + private function buildAfterSaleItemsFromRefund(array $refund): array + { + $cartInfo = $refund['cart_info'] ?? []; + if (is_string($cartInfo)) { + $decoded = json_decode($cartInfo, true); + if (is_array($decoded)) { + $cartInfo = $decoded; + } + } + if (!is_array($cartInfo)) { + $cartInfo = []; + } + + $items = []; + foreach ($cartInfo as $cart) { + $cart = $cart['cart_info'] ?? $cart; + $productInfo = $cart['productInfo'] ?? []; + $attrInfo = $productInfo['attrInfo'] ?? []; + $skuId = (string)($attrInfo['code'] ?? ''); + if ($skuId === '') { + $skuId = (string)($attrInfo['unique'] ?? ''); + if ($skuId !== '') { + $skuId .= 'ok'; + } + } + $items[] = [ + 'sku_id' => $skuId, + 'qty' => (int)($cart['cart_num'] ?? 0), + 'price' => sprintf('%.2f', (float)($attrInfo['price'] ?? 0)), + 'amount' => sprintf('%.2f', (float)($cart['sum_true_price'] ?? 0)), + 'outer_oi_id' => (string)($cart['product_attr_unique'] ?? ''), + 'name' => (string)($productInfo['store_name'] ?? ''), + ]; + } + return $items; + } + + private function retryPost(string $api, array $biz, string $logPrefix): void + { + $meta = $this->extractBizMeta($biz); + $num = 3; + for ($i = $num; $i >= 0; $i--) { + if ($i <= 0) { + Log::error($logPrefix . '.failed ' . json_encode(array_merge([ + 'api' => $api, + ], $meta), JSON_UNESCAPED_UNICODE)); + return; + } + try { + $rt = $this->post($api, $biz); + return; + } catch (\Throwable $e) { + usleep(1000 * 50); + Log::error($logPrefix . '.error ' . $e->getMessage() . ' ' . json_encode(array_merge([ + 'try' => ($num - $i + 1), + 'api' => $api, + ], $meta), JSON_UNESCAPED_UNICODE)); + } + } + } + + private function extractBizMeta(array $biz): array + { + $first = $biz; + if ($this->isListArray($biz) && isset($biz[0]) && is_array($biz[0])) { + $first = $biz[0]; + } + if (!is_array($first)) { + return []; + } + $meta = []; + foreach (['shop_id', 'so_id', 'outer_as_id', 'as_ids'] as $k) { + if (array_key_exists($k, $first)) { + $meta[$k] = $first[$k]; + } + } + return $meta; + } + + private function isListArray(array $arr): bool + { + if ($arr === []) { + return true; + } + return array_keys($arr) === range(0, count($arr) - 1); + } + + private function getAddress($address): array + { + if (is_string($address)) { + $decoded = json_decode($address, true); + if (is_array($decoded)) { + $address = $decoded; + } else { + $parts = preg_split('/\s+/u', trim($address)); + if (is_array($parts) && count($parts) >= 3) { + $address = [ + 'province' => $parts[0] ?? '', + 'city' => $parts[1] ?? '', + 'area' => $parts[2] ?? '', + 'detail' => trim(implode(' ', array_slice($parts, 3))), + ]; + if (($address['detail'] ?? '') === '') { + $address['detail'] = $parts[2] ?? ''; + } + } else { + $address = ['detail' => $address]; + } + } + } + if (!is_array($address)) { + $address = []; + } + return [ + 'receiver_address' => $address['detail'] ?? '', + 'receiver_state' => $address['province'] ?? '', + 'receiver_city' => $address['city'] ?? '', + 'receiver_district' => $address['area'] ?? '', + 'receiver_zip' => $address['post_code'] ?? '', + ]; + } + + public static function getServices(): HaichaoJstServices + { + return new HaichaoJstServices(); + } + + public function getProductListShop($key = ''): array + { + $time = 1; + $data = $this->post('open/skumap/query', [ + 'modified_begin' => date('Y-m-d H:i', (time() - (86400 * $time))), + 'modified_end' => date('Y-m-d H:i', time() - (86400 * $time) + 7 * 86400), + 'page_index' => 1, + 'page_size' => 50, + 'shop_id' => $key + ])['data']; + return $data; + } + + public function getProductListName($key = ''): array + { + $data = $this->post('open/sku/query', [ + 'exactly_name' => $key, + 'page_index' => 1, + 'page_size' => 10, + ])['data']; + return $data; + } + + public function syncProductSingle($data, $pid = 0) + { + $jstProductSync = app()->make(\app\haichao\sync\JstProductSync::class); + return $jstProductSync->syncProductSingle($data, $pid); + } + + public function getProductListSku($key = ''): array + { + $data = $this->post('open/sku/query', [ + 'sku_ids' => $key, + 'page_index' => 1, + 'page_size' => 10, + ])['data']; + return $data; + } + + public function getProductListKuan($key = ''): array + { + $data = $this->post('open/mall/item/query', [ + 'i_ids' => [$key], + 'page_index' => 1, + 'page_size' => 10, + 'date_field' => '', + ])['data']; + return $data; + } + + public function getProductListStand(): array + { + $time = 10; + $data = $this->post('open/mall/item/query', [ + 'modified_begin' => date('Y-m-d H:i', (time() - (86400 * $time))), + 'modified_end' => date('Y-m-d H:i', time() - (86400 * $time) + 7 * 86400), + 'i_ids' => [], + 'page_index' => 1, + 'page_size' => 50, + 'date_field' => '', + ])['data']; + return $data; + } + + public function getProductList(): array + { + $time = 1; + if (file_exists(root_path() . 'runtime/jstdata/synctime.txt')) { + $time = file_get_contents(root_path() . 'runtime/jstdata/synctime.txt'); + } + + $syncint = 0; + if (file_exists(root_path() . 'runtime/jstdata/syncint.txt')) { + $syncint = file_get_contents(root_path() . 'runtime/jstdata/syncint.txt'); + } + $syncint++; // 这里的 $syncint 实际上代表要拉取的页码(page_index) + + $data = $this->post('open/mall/item/query', [ + 'modified_begin' => date('Y-m-d H:i', (time() - (86400 * $time))), + 'modified_end' => date('Y-m-d H:i', time() - (86400 * $time) + 7 * 86400), + 'i_ids' => [], + 'page_index' => $syncint, // 将写死的 1 改为动态的页码变量 + 'page_size' => 10, + 'date_field' => '', + ])['data']; + + if ($syncint > $data['page_count']) { + $syncint = 1; + $time += 7; + if ($time > 500) { + $time = 1; + } + } + + if (!is_dir(root_path() . 'runtime/jstdata')) { + mkdir(root_path() . 'runtime/jstdata', 0777, true); + } + file_put_contents(root_path() . 'runtime/jstdata/syncint.txt', $syncint); + file_put_contents(root_path() . 'runtime/jstdata/synctime.txt', $time); + return $data; + } + + public function getStork(string $ids): array + { + return $this->post('open/inventory/query', [ + 'page_index' => 1, + 'page_size' => 100, + 'sku_ids' => $ids, + ]); + } + + public function getInneropenItemList(): array + { + $return = []; + $n = 1; + if (file_exists(root_path() . 'runtime/jstdata/syncintgood.txt')) { + $n = file_get_contents(root_path() . 'runtime/jstdata/syncintgood.txt'); + } + $data = $this->post('open/api/goods/inneropen/goods/querygoodslist', [ + 'page_num' => $n, + 'page_size' => 10, + 'sort' => 'ASC' + ])['data']; + $result = $data['list']; + $return = array_merge($result, $return); + if (file_exists(root_path() . 'runtime/jstdata/syncintgood.txt')) { + $n = file_get_contents(root_path() . 'runtime/jstdata/syncintgood.txt'); + $n++; + if ($n > ceil($data['total'] / 10)) { + $n = 1; + } + } + if (!is_dir(root_path() . 'runtime/jstdata')) { + mkdir(root_path() . 'runtime/jstdata', 0777, true); + } + file_put_contents(root_path() . 'runtime/jstdata/syncintgood.txt', $n); + return $return; + } + + public function getInneropenItemDetail($skuId): array + { + return $this->post('open/api/goods/inneropen/goods/querygoodsdetail', [ + 'item_spuId' => $skuId + ])['data']; + } + + public function findStockBySkuId($skuid = ''): array + { + $result = $this->post('open/inventory/query', [ + 'page_num' => 1, + 'page_size' => 500, + 'sku_ids' => $skuid, + ])['data']['inventorys']; + return $result; + } + + public function getInneropenStockAndDetailList($styleCodes): array + { + $rt = []; + $i = 1; + do { + $result = $this->post('open/inventory/query', [ + 'page_index' => $i++, + 'page_size' => 100, + 'sku_ids' => $styleCodes + ])['data']['inventorys'] ?? []; + + // 适配原来的返回格式结构 + $formatResult = []; + foreach ($result as $item) { + $formatResult[] = [ + 'item_code' => $item['sku_id'], + 'stock' => max(0, ($item['qty'] ?? 0) - ($item['order_lock'] ?? 0)) // 实际可用库存 + ]; + } + $rt = array_merge($formatResult, $rt); + usleep(550); + } while (count($result) == 100); + return $rt; + } + + public function findAllInneropenItemBySkuId(string $skuIds): array + { + return $this->getInneropenStockAndDetailList($skuIds); + } + + public function getInneropenList(int $status = 2): array + { + $rt = $this->post('open/api/company/inneropen/partner/channel/querymysupplier', [ + 'page_num' => 1, + 'page_size' => 100, + ])['data']['supplier_vos']; + $rtr = []; + foreach ($rt as $inneropen) { + if ($inneropen['status'] == $status) { + $rtr[] = $inneropen; + } + } + return $rtr; + } + + public function getSyncProduct(): \app\haichao\sync\JstProductSync + { + return new \app\haichao\sync\JstProductSync(); + } + + public function getSyncStork(): \app\haichao\sync\JstStorkSync + { + return new \app\haichao\sync\JstStorkSync(); + } +} diff --git a/app/haichao/callback/JstCallbackEventManager.php b/app/haichao/callback/JstCallbackEventManager.php new file mode 100644 index 0000000..e00e776 --- /dev/null +++ b/app/haichao/callback/JstCallbackEventManager.php @@ -0,0 +1,40 @@ + JstProductEvent::class, + 'trade_after_sale' => JstTradeEvent::class, + 'orderCancel' => JstOrderCancelEvent::class, + 'orderPost' => JstOrderPostEvent::class, + 'orderRefund' => JstOrderRefundEvent::class, + ]; + + public static function callback(Request $request): Json + { + $actionCode = $request->getMore([['action_code', false]])['action_code'] ?? ''; + Log::info('JST callback ' . $actionCode); + if ($actionCode && isset(self::$eventList[$actionCode])) { + $reflectionClass = new ReflectionClass(self::$eventList[$actionCode]); + /** @var JstEvent $eventObj */ + $eventObj = $reflectionClass->newInstance(); + $eventObj->event($request); + return json(['code' => 0, 'msg' => 'ok']); + } + throw new ValidateException('unknown action_code'); + } +} diff --git a/app/haichao/callback/event/JstEvent.php b/app/haichao/callback/event/JstEvent.php new file mode 100644 index 0000000..43b211c --- /dev/null +++ b/app/haichao/callback/event/JstEvent.php @@ -0,0 +1,10 @@ +getMore([['so_id', false]])['so_id']; + if ($soId) { + /** @var OrderServices $orderServices */ + $orderServices = app()->make(OrderServices::class); + $orderServices->cancelCallback($request->postMore([ + ['so_id', 0], + ['remark', ''], + ])); + } + return []; + } +} diff --git a/app/haichao/callback/event/JstOrderPostEvent.php b/app/haichao/callback/event/JstOrderPostEvent.php new file mode 100644 index 0000000..434c28b --- /dev/null +++ b/app/haichao/callback/event/JstOrderPostEvent.php @@ -0,0 +1,296 @@ +getContent(); + if ($content !== '') { + $decoded = json_decode($content, true); + if (is_array($decoded)) { + $payload = $decoded; + } else { + $output = []; + parse_str($content, $output); + if (is_array($output)) { + $payload = $output; + } + } + } + if (!$payload) { + $payload = $request->param(); + } + + $biz = []; + if (isset($payload['biz']) && $payload['biz'] !== '') { + if (is_array($payload['biz'])) { + $biz = $payload['biz']; + } else { + $bizDecoded = json_decode((string)$payload['biz'], true); + if (is_array($bizDecoded)) { + $biz = $bizDecoded; + } + } + } + + $oId = $request->getMore([['o_id', false]])['o_id'] ?: (string)($payload['o_id'] ?? $biz['o_id'] ?? ($payload['oId'] ?? $biz['oId'] ?? '')); + $logisticsCompany = $request->getMore([['logistics_company', false]])['logistics_company'] ?: (string)($payload['logistics_company'] ?? $biz['logistics_company'] ?? ($payload['logisticsCompany'] ?? $biz['logisticsCompany'] ?? '')); + $lId = $request->getMore([['l_id', false]])['l_id'] ?: (string)($payload['l_id'] ?? $biz['l_id'] ?? ($payload['lId'] ?? $biz['lId'] ?? '')); + $lcId = $request->getMore([['lc_id', false]])['lc_id'] ?: (string)($payload['lc_id'] ?? $biz['lc_id'] ?? ($payload['lcId'] ?? $biz['lcId'] ?? '')); + $soId = $request->getMore([['so_id', false]])['so_id'] ?: (string)($payload['so_id'] ?? $biz['so_id'] ?? ($payload['soId'] ?? $biz['soId'] ?? '')); + $items = $request->getMore([['items', false]])['items'] ?: ($payload['items'] ?? $biz['items'] ?? ($payload['item_list'] ?? $biz['item_list'] ?? [])); + if (!is_array($items)) { + $items = []; + } + + $skus = []; + if (is_array($items)) { + foreach ($items as $it) { + $skus[] = ['sku' => $it['sku_id'] ?? '', 'qty' => (int)($it['qty'] ?? 0)]; + } + } + Log::info('jst.cb.orderPost.start ' . json_encode([ + 'so_id' => $soId, + 'o_id' => $oId, + 'logistics_company' => $logisticsCompany, + 'l_id' => $lId, + 'lc_id' => $lcId, + 'item_count' => is_array($items) ? count($items) : 0, + 'sku_list' => $skus, + ], JSON_UNESCAPED_UNICODE)); + + /** @var StoreOrderServices $storeOrderServices */ + $storeOrderServices = app()->make(StoreOrderServices::class); + /** @var StoreOrderDeliveryServices $orderDeliverServices */ + $orderDeliverServices = app()->make(StoreOrderDeliveryServices::class); + /** @var StoreOrderRefundServices $storeOrderRefundServices */ + $storeOrderRefundServices = app()->make(StoreOrderRefundServices::class); + /** @var StoreOrderCartInfoServices $storeOrderCartInfoServices */ + $storeOrderCartInfoServices = app()->make(StoreOrderCartInfoServices::class); + + if ($soId === '') { + Log::info('jst.cb.orderPost.skip ' . json_encode(['reason' => 'missing so_id'], JSON_UNESCAPED_UNICODE)); + return 'missing so_id'; + } + + $orderInfo = $storeOrderServices->getOne(['order_id' => $soId]); + if (!$orderInfo) throw new ValidateException('order not found ' . $soId); + $oid = $orderInfo['id']; + $splitOrderList = $storeOrderServices->getSplitOrderList(['pid' => $oid]); + $nol = []; + foreach ($splitOrderList as $splitOrder) { + if ($splitOrder['status'] === 0) $nol[] = $splitOrder; + } + $splitOrderList = $nol; + Log::info('jst.cb.orderPost.split.count ' . json_encode(['so_id' => $soId, 'count' => count($splitOrderList)], JSON_UNESCAPED_UNICODE)); + + $cardIds = $orderInfo['cart_id']; + $num = 0; + foreach ($cardIds as $cardId) { + $cardInfo = $storeOrderCartInfoServices->getOne(['cart_id' => $cardId])->toArray(); + $num += $cardInfo['cart_num']; + } + $num2 = 0; + foreach ($items as $item) { + $num2 += $item['qty']; + } + if ($num !== $num2) { + Log::warning('jst.cb.orderPost.qty_mismatch ' . json_encode(['so_id' => $soId, 'order_cart_num' => $num, 'ship_qty_sum' => $num2], JSON_UNESCAPED_UNICODE)); + } + + $existSo = false; + if ($splitOrderList) { + foreach ($splitOrderList as $splitOrder) { + if ($splitOrder['order_id'] == $soId) { + $existSo = true; + } + } + } else { + $existSo = true; + } + if (!$existSo) { + Log::warning('jst.cb.orderPost.no_exist_so ' . json_encode(['so_id' => $soId], JSON_UNESCAPED_UNICODE)); + foreach ($splitOrderList as $item) { + foreach ($item['cart_id'] as $cartId) { + $cartInfo = $item['_info'][$cartId]['cart_info']; + $productInfo = $cartInfo['productInfo']; + $cartfa = []; + foreach ($items as $sentProduct) { + if ($productInfo['attrInfo']['suk'] == $sentProduct['sku_id'] || + $productInfo['attrInfo']['code'] == $sentProduct['sku_id'] || + ($productInfo['attrInfo']['unique'] . 'ok') == $sentProduct['sku_id']) { + $cartfa[] = [ + 'cart_id' => $cartId, + 'cart_num' => $sentProduct['qty'], + ]; + } + } + try { + $deliverArgs = [ + 'type' => 1, + 'delivery_name' => $logisticsCompany, + 'delivery_id' => $lId, + 'delivery_code' => $lcId, + 'express_record_type' => "1", + 'express_temp_id' => md5(time()), + 'cart_ids' => $cartfa + ]; + $orderDeliverServices->splitDelivery($item['id'], $deliverArgs, 0, true); + Log::info('jst.cb.orderPost.success ' . json_encode(['so_id' => $soId, 'oid' => (int)$item['id'], 'mode' => 'split'], JSON_UNESCAPED_UNICODE)); + return 'ok'; + } catch (\Throwable $exception) { + Log::error('jst.cb.orderPost.split.error ' . json_encode(['oid' => $item['id'], 'msg' => $exception->getMessage()], JSON_UNESCAPED_UNICODE)); + throw new ValidateException('split delivery error ' . $exception->getMessage()); + } + } + } + Log::warning('jst.cb.orderPost.no_match.split ' . json_encode(['so_id' => $soId], JSON_UNESCAPED_UNICODE)); + return 'no match'; + } + try { + if (count($splitOrderList) > 1 || count($orderInfo['cart_id']) > 1) { + if (count($splitOrderList) > 0) { + foreach ($splitOrderList as $item) { + foreach ($item['cart_id'] as $cartId) { + $cartInfo = $item['_info'][$cartId]['cart_info']; + $productInfo = $cartInfo['productInfo']; + foreach ($items as $postItem) { + $postItemSku = $postItem['sku_id']; + if ($productInfo['attrInfo']['suk'] == $postItemSku || $productInfo['attrInfo']['code'] == $postItemSku) { + try { + $deliverArgs = [ + 'type' => 1, + 'delivery_name' => $logisticsCompany, + 'delivery_id' => $lId, + 'delivery_code' => $lcId, + 'express_record_type' => "1", + 'express_temp_id' => md5(time()), + 'cart_ids' => [ + [ + 'cart_id' => $cartId, + 'cart_num' => $cartInfo['cart_num'], + ] + ] + ]; + $orderDeliverServices->splitDelivery($oid, $deliverArgs, 0, true); + Log::info('jst.cb.orderPost.success ' . json_encode(['so_id' => $soId, 'oid' => (int)$oid, 'mode' => 'split.pid', 'cart_id' => (int)$cartId], JSON_UNESCAPED_UNICODE)); + return 'ok'; + } catch (\Throwable $exception) { + Log::error('jst.cb.orderPost.split.pid.error ' . json_encode(['pid' => $oid, 'cart_id' => $cartId, 'msg' => $exception->getMessage()], JSON_UNESCAPED_UNICODE)); + throw new ValidateException('split delivery error ' . $exception->getMessage()); + } + } + } + } + } + Log::warning('jst.cb.orderPost.no_match.pid ' . json_encode(['so_id' => $soId, 'pid' => (int)$oid], JSON_UNESCAPED_UNICODE)); + return 'no match'; + } + if (count($orderInfo['cart_id']) > 1) { + $cartfa = []; + foreach ($items as $sentProduct) { + foreach ($orderInfo['cart_id'] as $cartId) { + $cartInfo = $storeOrderCartInfoServices->getOne(['cart_id' => $cartId]); + $productInfo = $cartInfo['cart_info']['productInfo'] ?? []; + if (!$productInfo) continue; + if ($productInfo['attrInfo']['suk'] == $sentProduct['sku_id'] || + $productInfo['attrInfo']['code'] == $sentProduct['sku_id'] || + ($productInfo['attrInfo']['unique'] . 'ok') == $sentProduct['sku_id']) { + $cartfa[] = [ + 'cart_id' => $cartId, + 'cart_num' => $cartInfo['cart_num'], + ]; + } + } + } + Log::info('jst.cb.orderPost.multi.mapping ' . json_encode(['pid' => $oid, 'match' => $cartfa], JSON_UNESCAPED_UNICODE)); + try { + $deliverArgs = [ + 'type' => 1, + 'delivery_id' => $lId, + 'delivery_code' => $lcId, + 'express_record_type' => "1", + 'express_temp_id' => md5(time()), + 'cart_ids' => $cartfa + ]; + $orderDeliverServices->splitDelivery($oid, $deliverArgs, 0, true); + Log::info('jst.cb.orderPost.success ' . json_encode(['so_id' => $soId, 'oid' => (int)$oid, 'mode' => 'multi'], JSON_UNESCAPED_UNICODE)); + return 'ok'; + } catch (\Throwable $exception) { + Log::error('jst.cb.orderPost.multi.error ' . json_encode(['pid' => $oid, 'msg' => $exception->getMessage()], JSON_UNESCAPED_UNICODE)); + throw new ValidateException('split delivery error ' . $exception->getMessage()); + } + } + } else { + if ($num == $num2) { + $deliverArgs = [ + 'type' => 1, + 'delivery_name' => $logisticsCompany, + 'delivery_id' => $lId, + 'delivery_code' => $lcId, + 'express_record_type' => "1", + 'express_temp_id' => md5(time()), + ]; + $orderDeliverServices->delivery($oid, $deliverArgs); + Log::info('jst.cb.orderPost.success ' . json_encode(['so_id' => $soId, 'oid' => (int)$oid, 'mode' => 'delivery'], JSON_UNESCAPED_UNICODE)); + return 'ok'; + } + } + } catch (\Throwable $e) { + if ($e->getMessage() != '订单已发货请勿重复操作!') { + Log::error('jst.cb.orderPost.error ' . json_encode(['oid' => $oid, 'msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE)); + throw new ValidateException($e->getMessage()); + } + Log::info('jst.cb.orderPost.repeat ' . json_encode(['so_id' => $soId, 'oid' => (int)$oid], JSON_UNESCAPED_UNICODE)); + return 'ok'; + } + $cardIds = $orderInfo['cart_id']; + if (count($cardIds) >= 1) { + $cartfa = []; + foreach ($cardIds as $cartId) { + $cardInfo = $storeOrderCartInfoServices->getOne(['cart_id' => $cartId])->toArray(); + $productInfo = $cardInfo['cart_info']['productInfo'] ?? []; + foreach ($items as $sentProduct) { + if ($productInfo['attrInfo']['suk'] == $sentProduct['sku_id'] || + $productInfo['attrInfo']['code'] == $sentProduct['sku_id'] || + ($productInfo['attrInfo']['unique'] . 'ok') == $sentProduct['sku_id']) { + $cartfa[] = [ + 'cart_id' => $cartId, + 'cart_num' => $sentProduct['qty'], + ]; + } + } + } + try { + $deliverArgs = [ + 'type' => 1, + 'delivery_name' => $logisticsCompany, + 'delivery_id' => $lId, + 'delivery_code' => $lcId, + 'express_record_type' => "1", + 'express_temp_id' => md5(time()), + 'cart_ids' => $cartfa + ]; + $orderDeliverServices->splitDelivery($oid, $deliverArgs); + Log::info('jst.cb.orderPost.success ' . json_encode(['so_id' => $soId, 'oid' => (int)$oid, 'mode' => 'cartfa'], JSON_UNESCAPED_UNICODE)); + return 'ok'; + } catch (\Throwable $exception) { + Log::error('jst.cb.orderPost.cartfa.error ' . json_encode(['pid' => $oid, 'msg' => $exception->getMessage()], JSON_UNESCAPED_UNICODE)); + throw new ValidateException('split delivery error ' . $exception->getMessage()); + } + } + Log::warning('jst.cb.orderPost.unknown ' . json_encode(['so_id' => $soId], JSON_UNESCAPED_UNICODE)); + throw new ValidateException('unknown delivery ' . $soId); + } +} diff --git a/app/haichao/callback/event/JstOrderRefundEvent.php b/app/haichao/callback/event/JstOrderRefundEvent.php new file mode 100644 index 0000000..70aacd6 --- /dev/null +++ b/app/haichao/callback/event/JstOrderRefundEvent.php @@ -0,0 +1,27 @@ +getMore(['action_name'])['action_name']; + switch ($actionName) { + case '确认收货': + { + /** @var StoreOrderServices $storeOrderServices */ + $storeOrderServices = app()->make(StoreOrderServices::class); + $orderInfo = $storeOrderServices->getOne(['erp_id' => $request->getMore([['o_id', -1]])['o_id']]); + if ($orderInfo) { + $storeOrderServices->update(['id' => $orderInfo['id']], ['status' => 3]); + } + break; + } + } + return []; + } +} diff --git a/app/haichao/callback/event/JstProductEvent.php b/app/haichao/callback/event/JstProductEvent.php new file mode 100644 index 0000000..3b50d55 --- /dev/null +++ b/app/haichao/callback/event/JstProductEvent.php @@ -0,0 +1,48 @@ +param(); + $body = json_decode((string)($param['biz'] ?? '{}'), true); + $skuId = (string)($body['skuId'] ?? ''); + $stock = (int)($body['qty'] ?? 0); + if (!$skuId) { + $cont = $request->getContent(); + $output = []; + parse_str($cont, $output); + $body = json_decode((string)($output['biz'] ?? '{}'), true); + $skuId = (string)($body['skuId'] ?? ''); + $stock = (int)($body['qty'] ?? 0); + } + if (!$skuId) return []; + /** @var StoreProductAttrValueServices $spas */ + $spas = app()->make(StoreProductAttrValueServices::class); + $model = $spas->getOne(['code' => $skuId]); + if ($model) { + $pdt = $model->toArray(); + $id = $pdt['id'] ?? 0; + if ($id) { + $spas->update($id, [ + 'stock' => $stock, + 'sum_stock' => $stock + ]); + } + } + } catch (Throwable $e) { + Log::error('jst.cb.product_stock.error ' . json_encode([ + 'msg' => $e->getMessage(), + ], JSON_UNESCAPED_UNICODE)); + } + return []; + } +} diff --git a/app/haichao/callback/event/JstTradeEvent.php b/app/haichao/callback/event/JstTradeEvent.php new file mode 100644 index 0000000..520dbff --- /dev/null +++ b/app/haichao/callback/event/JstTradeEvent.php @@ -0,0 +1,298 @@ +getContent(); + if ($content !== '') { + $decoded = json_decode($content, true); + if (is_array($decoded)) { + $payload = $decoded; + } else { + $output = []; + parse_str($content, $output); + if (is_array($output)) { + $payload = $output; + } + } + } + if (!$payload) { + $payload = $request->param(); + } + + $biz = []; + if (isset($payload['biz']) && $payload['biz'] !== '') { + if (is_array($payload['biz'])) { + $biz = $payload['biz']; + } else { + $bizDecoded = json_decode((string)$payload['biz'], true); + if (is_array($bizDecoded)) { + $biz = $bizDecoded; + } + } + } + + $actionName = (string)($payload['action_name'] ?? $biz['action_name'] ?? ($payload['action'] ?? $biz['action'] ?? '')); + $shopStatus = strtoupper((string)($payload['shop_status'] ?? $biz['shop_status'] ?? ($payload['shopStatus'] ?? $biz['shopStatus'] ?? ''))); + $outerAsId = (string)($payload['outer_as_id'] ?? $biz['outer_as_id'] ?? ($payload['outerAsId'] ?? $biz['outerAsId'] ?? '')); + $soId = (string)($payload['so_id'] ?? $biz['so_id'] ?? ($payload['soId'] ?? $biz['soId'] ?? '')); + $bizStatus = strtoupper((string)($biz['status'] ?? $payload['status'] ?? '')); + $bizType = (string)($biz['type'] ?? ''); + $bizRemark = (string)($biz['remark'] ?? ''); + + Log::info('jst.cb.trade_after_sale.start ' . json_encode([ + 'action_name' => $actionName, + 'shop_status' => $shopStatus, + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'biz_status' => $bizStatus, + 'biz_type' => $bizType, + 'content_len' => strlen($content), + ], JSON_UNESCAPED_UNICODE)); + + if ($outerAsId === '') { + Log::info('jst.cb.trade_after_sale.skip ' . json_encode([ + 'reason' => 'missing outer_as_id', + 'shop_status' => $shopStatus, + 'action_name' => $actionName, + ], JSON_UNESCAPED_UNICODE)); + return []; + } + + try { + $applyType = 1; + if ($bizType !== '') { + if (mb_strpos($bizType, '仅退款') !== false) { + $applyType = 1; + } elseif (mb_strpos($bizType, '退货') !== false) { + $applyType = 2; + } + } + + /** @var StoreOrderServices $storeOrderServices */ + $storeOrderServices = app()->make(StoreOrderServices::class); + $soIdsToTry = array_values(array_filter(array_unique([ + $soId, + (string)(explode('_', $soId)[0] ?? ''), + ]))); + $order = null; + foreach ($soIdsToTry as $trySoId) { + $order = $storeOrderServices->getOne(['erp_order_id' => $trySoId], 'id,uid,store_id,supplier_id,order_id,erp_order_id,total_num,pay_price,pay_postage,channel,pid'); + if ($order) break; + $order = $storeOrderServices->getOne(['order_id' => $trySoId], 'id,uid,store_id,supplier_id,order_id,erp_order_id,total_num,pay_price,pay_postage,channel,pid'); + if ($order) break; + } + if (!$order) { + Log::error('jst.cb.trade_after_sale.order_missing ' . json_encode([ + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'biz_status' => $bizStatus, + 'biz_type' => $bizType, + ], JSON_UNESCAPED_UNICODE)); + return []; + } + + /** @var StoreOrderRefundServices $storeOrderRefundServices */ + $storeOrderRefundServices = app()->make(StoreOrderRefundServices::class); + $orderRefund = $storeOrderRefundServices->getOne(['order_id' => $outerAsId]); + if (!$orderRefund) { + /** @var StoreOrderCartInfoServices $storeOrderCartInfoServices */ + $storeOrderCartInfoServices = app()->make(StoreOrderCartInfoServices::class); + $items = $biz['items'] ?? []; + $skuQtyMap = []; + if (is_array($items)) { + foreach ($items as $item) { + if (!is_array($item)) continue; + $sku = (string)($item['sku_id'] ?? $item['skuId'] ?? ''); + $qty = (int)($item['qty'] ?? 0); + if ($sku !== '' && $qty > 0) { + $skuQtyMap[$sku] = ($skuQtyMap[$sku] ?? 0) + $qty; + } + } + } + + $targetOrder = $order; + $leftSkuQtyMap = $skuQtyMap; + $cartInfos = $storeOrderCartInfoServices->getCartColunm(['oid' => (int)$order['id']], 'id,cart_id,cart_num,cart_info'); + foreach ($cartInfos as &$cartInfo) { + $cartInfo['cart_info'] = is_string($cartInfo['cart_info']) ? json_decode($cartInfo['cart_info'], true) : $cartInfo['cart_info']; + } + unset($cartInfo); + + if ($skuQtyMap && (int)($order['pid'] ?? 0) === -1) { + $candidateOrders = [$order]; + $subOrders = $storeOrderServices->getColumn(['pid' => (int)$order['id'], 'is_system_del' => 0], 'id,uid,store_id,supplier_id,order_id,erp_order_id,total_num,pay_price,pay_postage,channel,pid'); + if ($subOrders && is_array($subOrders)) { + $candidateOrders = array_merge($candidateOrders, $subOrders); + } + + $bestScore = -1; + $bestCartInfos = $cartInfos; + $bestLeftSkuQtyMap = $skuQtyMap; + foreach ($candidateOrders as $cand) { + if (!is_array($cand) || !isset($cand['id'])) continue; + $candCartInfos = $storeOrderCartInfoServices->getCartColunm(['oid' => (int)$cand['id']], 'id,cart_id,cart_num,cart_info'); + foreach ($candCartInfos as &$candCartInfo) { + $candCartInfo['cart_info'] = is_string($candCartInfo['cart_info']) ? json_decode($candCartInfo['cart_info'], true) : $candCartInfo['cart_info']; + } + unset($candCartInfo); + + $need = $skuQtyMap; + $score = 0; + foreach ($candCartInfos as $candCart) { + $ci = $candCart['cart_info']; + if (!is_array($ci)) continue; + $sku = (string)($ci['productInfo']['attrInfo']['code'] ?? $ci['productInfo']['attrInfo']['sku'] ?? $ci['productInfo']['sku'] ?? ''); + if ($sku === '' || !isset($need[$sku]) || (int)$need[$sku] <= 0) continue; + $cartNum = (int)($candCart['cart_num'] ?? 0); + $useNum = min($cartNum, (int)$need[$sku]); + if ($useNum <= 0) continue; + $score += $useNum; + $need[$sku] -= $useNum; + } + + if ($score > $bestScore) { + $bestScore = $score; + $targetOrder = $cand; + $bestCartInfos = $candCartInfos; + $bestLeftSkuQtyMap = $need; + } + } + $cartInfos = $bestCartInfos; + $leftSkuQtyMap = $bestLeftSkuQtyMap; + Log::info('jst.cb.trade_after_sale.split_match ' . json_encode([ + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'best_score' => $bestScore, + 'target_store_order_id' => (int)($targetOrder['id'] ?? 0), + 'target_order_id' => (string)($targetOrder['order_id'] ?? ''), + 'target_erp_order_id' => (string)($targetOrder['erp_order_id'] ?? ''), + ], JSON_UNESCAPED_UNICODE)); + } + + $selectedCartInfo = []; + if ($skuQtyMap) { + foreach ($cartInfos as $cart) { + $ci = $cart['cart_info']; + if (!is_array($ci)) continue; + $sku = (string)($ci['productInfo']['attrInfo']['code'] ?? $ci['productInfo']['attrInfo']['sku'] ?? $ci['productInfo']['sku'] ?? ''); + if ($sku === '' || !isset($skuQtyMap[$sku])) continue; + $cartNum = (int)($cart['cart_num'] ?? 0); + $applyNum = min($cartNum, (int)$skuQtyMap[$sku]); + if ($applyNum <= 0) continue; + $ci['id'] = $ci['id'] ?? (int)($cart['id'] ?? 0); + $ci['cart_id'] = $ci['cart_id'] ?? (int)($cart['cart_id'] ?? 0); + $ci['cart_num'] = $applyNum; + $selectedCartInfo[] = $ci; + $skuQtyMap[$sku] -= $applyNum; + } + } + if (!$selectedCartInfo) { + $selectedCartInfo = array_values(array_filter(array_column($cartInfos, 'cart_info'), 'is_array')); + } + + $refundNum = 0; + $refundPrice = 0.00; + foreach ($selectedCartInfo as $ci) { + $refundNum += (int)($ci['cart_num'] ?? 0); + if (isset($ci['truePrice'])) { + $refundPrice = bcadd((string)$refundPrice, bcmul((string)$ci['truePrice'], (string)($ci['cart_num'] ?? 0), 4), 2); + } + } + if ($refundPrice <= 0 && (int)($targetOrder['total_num'] ?? 0) > 0 && $refundNum > 0) { + $refundPrice = (float)bcmul((string)$targetOrder['pay_price'], bcdiv((string)$refundNum, (string)$targetOrder['total_num'], 6), 2); + } + + $refundData = [ + 'uid' => (int)$targetOrder['uid'], + 'store_id' => (int)$targetOrder['store_id'], + 'supplier_id' => (int)($targetOrder['supplier_id'] ?? 0), + 'store_order_id' => (int)$targetOrder['id'], + 'refund_num' => $refundNum > 0 ? $refundNum : (int)($targetOrder['total_num'] ?? 0), + 'apply_type' => $applyType, + 'refund_type' => $applyType, + 'refund_goods_type' => in_array($applyType, [2, 3]) ? $applyType : 1, + 'apply_price' => $refundPrice, + 'refund_price' => $refundPrice, + 'refund_reason' => $bizRemark !== '' ? $bizRemark : '聚水潭发起售后', + 'order_id' => $outerAsId, + 'add_time' => time(), + 'cart_info' => json_encode($selectedCartInfo, JSON_UNESCAPED_UNICODE), + 'channel' => (int)($targetOrder['channel'] ?? 0), + ]; + $res = $storeOrderRefundServices->save($refundData); + $orderRefund = $storeOrderRefundServices->get((int)$res->id); + Log::info('jst.cb.trade_after_sale.refund_created ' . json_encode([ + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'store_order_id' => (int)$targetOrder['id'], + 'refund_id' => (int)$res->id, + 'apply_type' => $applyType, + 'refund_num' => $refundData['refund_num'], + 'refund_price' => $refundData['refund_price'], + ], JSON_UNESCAPED_UNICODE)); + } + + if ($shopStatus === 'CLOSED' || $bizStatus === 'CLOSED') { + $storeOrderRefundServices->update((int)$orderRefund['id'], [ + 'refund_type' => 3, + 'refuse_reason' => $bizRemark !== '' ? $bizRemark : '聚水潭关闭售后', + 'refunded_time' => time(), + ]); + Log::info('jst.cb.trade_after_sale.refund_closed ' . json_encode([ + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'refund_id' => (int)$orderRefund['id'], + ], JSON_UNESCAPED_UNICODE)); + return []; + } + + if (in_array($bizStatus, ['REFUNDED', 'REFUND_SUCCESS', 'FINISHED', 'COMPLETED'], true)) { + /** @var OrderServices $orderServices */ + $orderServices = app()->make(OrderServices::class); + $ok = (bool)$orderServices->receivedCallback(['outer_as_id' => $outerAsId, 'so_id' => $soId], 1, $soId !== ''); + Log::info('jst.cb.trade_after_sale.refund_finalize ' . json_encode([ + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'refund_id' => (int)$orderRefund['id'], + 'success' => $ok ? 1 : 0, + ], JSON_UNESCAPED_UNICODE)); + } else { + Log::info('jst.cb.trade_after_sale.synced ' . json_encode([ + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'refund_id' => (int)$orderRefund['id'], + 'apply_type' => (int)($orderRefund['apply_type'] ?? 0), + 'refund_type' => (int)($orderRefund['refund_type'] ?? 0), + ], JSON_UNESCAPED_UNICODE)); + } + } catch (\Throwable $e) { + Log::error('jst.cb.trade_after_sale.error ' . json_encode([ + 'outer_as_id' => $outerAsId, + 'so_id' => $soId, + 'action_name' => $actionName, + 'shop_status' => $shopStatus, + 'biz_status' => $bizStatus, + 'biz_type' => $bizType, + 'error' => [ + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], + ], JSON_UNESCAPED_UNICODE)); + } + return []; + } +} diff --git a/app/haichao/sync/JstProductSync.php b/app/haichao/sync/JstProductSync.php new file mode 100644 index 0000000..859fb6f --- /dev/null +++ b/app/haichao/sync/JstProductSync.php @@ -0,0 +1,344 @@ +make(StoreProductServices::class); + $ps = $make->count(['is_del' => 0]); + $iil = $this->getInneropenItemList(); + /** @var StoreDescriptionServices $proDesServices */ + $proDesServices = app()->make(StoreDescriptionServices::class); + /** @var StoreProductServices $productServices */ + $productServices = app()->make(StoreProductServices::class); + foreach ($iil as $item) { + $itemSpuId = $item['item_spu_id']; + try { + $inneropenProduct = $this->getInneropenItemDetail($itemSpuId); + } catch (Throwable $e) { + $inneropenProduct = $this->getInneropenItemDetail($itemSpuId); + } + $images = ""; + foreach ($inneropenProduct['item_photo']['item_detail_images'] as $oImage) { + $images .= "

"; + } + $pid = $productServices->value(['code' => $item['style_code']], 'id'); + if ($pid) { + $proDesServices->saveDescription($pid, $images); + } + } + } + + public function syncProductSingle($data, $pid = 0) + { + $productList = $data['datas']; + /** @var StoreProductServices $productServices */ + $productServices = app()->make(StoreProductServices::class); + /** @var StoreProductAttrServices $productAttrServices */ + $productAttrServices = app()->make(StoreProductAttrServices::class); + try { + $skuParList = []; + $index = 1; + $count = 0; + $skuIds = ''; + foreach ($productList as $item) { + $count++; + $skuIds .= ($skuIds ? "," . $item['sku_id'] : $item['sku_id']); + $skuParList[$index] = [ + 'length' => $count, + 'code' => $skuIds + ]; + } + $stockMap = []; + foreach ($skuParList as $k => $skus) { + $r = $this->findStockBySkuId($skus['code']); + foreach ($r as $v) { + if (!isset($stockMap[$v['sku_id']])) $stockMap[$v['sku_id']] = 0; + $stockMap[$v['sku_id']] += $v['qty']; + } + } + foreach ($productList as $item) { + $pic = ($item['pic'] ? $item['pic'] : ''); + if (!$item['sale_price']) { + $item['sale_price'] = 0; + $item['s_price'] = 0; + } + if (!$item['market_price']) { + $item['market_price'] = 0; + } + $item['c_price'] = $item['cost_price']; + $item['i_id'] = $item['sku_id']; + $supplierId = 0; + try { + if (isset($item['shop_id']) && $item['shop_id']) { + /** @var \app\services\supplier\SystemSupplierServices $supplierServices */ + $supplierServices = app()->make(\app\services\supplier\SystemSupplierServices::class); + $supplier = $supplierServices->getOne(['erp_shopid' => (int)$item['shop_id'], 'is_del' => 0], 'id'); + if ($supplier && isset($supplier['id'])) { + $supplierId = (int)$supplier['id']; + } + } + } catch (\Throwable $e) { + Log::error('jst.product.sync.supplier_map_err ' . $e->getMessage()); + } + $relationSupplierId = $pid ?: $supplierId; + $productInfo = [ + 'image' => $pic, + 'slider_image' => json_encode([$pic]), + 'store_name' => $item['name'], + 'store_info' => $item['name'], + 'cate_id' => 0, + 'price' => floatval($item['sale_price'] ?? 0), + 'ot_price' => floatval($item['market_price']), + 'delivery_type' => '1', + 'freight' => 1, + 'add_time' => time(), + 'cost' => floatval($item['c_price']), + 'ficti' => 0, + 'spec_type' => 1, + 'code' => $item['i_id'], + 'type' => $relationSupplierId ? 2 : 0, + 'relation_id' => $relationSupplierId, + ]; + $stockSum = 0; + $detail = $details = $value = []; + $items = $item; + if ($items['properties_value']) { + $detail[] = $items['properties_value']; + $details[] = [ + 'name' => $items['properties_value'], + 'select' => false + ]; + $stock = $stockMap[$items['sku_id']] ?? 0; + $skuCost = floatval($items['cost_price']); + $skuSettle = $relationSupplierId ? $skuCost : 0.0; + $value[] = [ + 'bar_code' => '', + 'brokerage' => 0, + 'brokerage_two' => 0, + 'code' => $items['sku_id'], + 'settle_price' => $skuSettle, + 'cost' => $skuCost, + 'detail' => ['规格' => $items['properties_value']], + 'ot_price' => floatval($items['market_price']), + 'pic' => ImageUtil::urlToOssUrl($items['pic']), + 'price' => floatval($items['sale_price']), + 'select' => true, + 'value1' => $items['properties_value'], + 'values' => $items['properties_value'], + 'vip_price' => 0, + 'volume' => 0, + 'weight' => 0, + 'stock' => $stock, + 'sum_stock' => $stock, + ]; + $stockSum = bcadd($stockSum, $stock, 0); + } else { + return; + } + $productInfo['stock'] = (int)$stockSum; + $pidNow = $productServices->value(['code' => $item['i_id']], 'id'); + if (!isset($pidNow)) { + $pidNow = $productServices->ErpProductSave($productInfo); + } else { + $productServices->update($pidNow, $productInfo); + } + ProductStockTips::dispatch([$pidNow, 0]); + $attr = [[ + 'value' => '规格', + 'detail' => $detail, + 'details' => $details, + ]]; + $skuList = $productAttrServices->validateProductAttr($attr, $value, $pidNow, 0, 0, 0); + $productAttrServices->saveProductAttr($skuList, $pidNow); + } + $productServices->cacheTag()->clear(); + $productAttrServices->cacheTag()->clear(); + } catch (Exception $e) { + Log::error('jst.product.sync.error ' . $e->getMessage()); + } + } + + public function syncProduct(): void + { + /** @var StoreProductServices $make */ + $make = app()->make(StoreProductServices::class); + $ps = $make->count(['is_del' => 0]); + /** @var StoreProductServices $productServices */ + $productServices = app()->make(StoreProductServices::class); + /** @var StoreProductAttrServices $productAttrServices */ + $productAttrServices = app()->make(StoreProductAttrServices::class); + try { + $pls = $this->getProductList(); + $productList = $pls['datas']; + Log::info('jst.product.sync.start ' . count($productList)); + $skuParList = []; + $index = 1; + $count = 0; + $skuIds = ''; + foreach ($productList as $item) { + if (CacheProduct::isCache(md5($item['i_id']))) continue; + foreach ($item['skus'] as $skus) { + $count++; + $skuIds .= $skus['sku_id'] . ","; + $skuParList[$index] = [ + 'length' => $count, + 'code' => $skuIds + ]; + if ($count >= 500) { + $index++; + $count = 0; + $skuIds = ''; + } + } + } + $stockMap = []; + foreach ($skuParList as $k => $skus) { + $r = $this->findAllInneropenItemBySkuId($skus['code']); + foreach ($r as $v) { + $stockMap[$v['item_code']] = $v['stock']; + } + } + foreach ($productList as $item) { + if (!$item['skus']) continue; + if (CacheProduct::isCache(md5($item['i_id']))) continue; + $pic = ImageUtil::urlToOssUrl($item['pic']); + $supplierId = 0; + try { + if (isset($item['shop_id']) && $item['shop_id']) { + /** @var \app\services\supplier\SystemSupplierServices $supplierServices */ + $supplierServices = app()->make(\app\services\supplier\SystemSupplierServices::class); + $supplier = $supplierServices->getOne(['erp_shopid' => (int)$item['shop_id'], 'is_del' => 0], 'id'); + if ($supplier && isset($supplier['id'])) { + $supplierId = (int)$supplier['id']; + } + } + } catch (\Throwable $e) { + } + $salePrice = floatval($item['s_price'] ?? 0); + if ($salePrice <= 0 && isset($item['skus']) && is_array($item['skus'])) { + $skuSalePrices = []; + foreach ($item['skus'] as $skuRow) { + if (isset($skuRow['sale_price']) && is_numeric($skuRow['sale_price'])) { + $skuSalePrices[] = floatval($skuRow['sale_price']); + } + } + if ($skuSalePrices) { + $salePrice = min($skuSalePrices); + } + } + $marketPrice = floatval($item['market_price'] ?? 0); + if ($marketPrice <= 0 && isset($item['skus']) && is_array($item['skus'])) { + $skuMarketPrices = []; + foreach ($item['skus'] as $skuRow) { + if (isset($skuRow['market_price']) && is_numeric($skuRow['market_price'])) { + $skuMarketPrices[] = floatval($skuRow['market_price']); + } + } + if ($skuMarketPrices) { + $marketPrice = max($skuMarketPrices); + } + } + $costPrice = floatval($item['c_price'] ?? ($item['cost_price'] ?? 0)); + if ($costPrice <= 0 && isset($item['skus']) && is_array($item['skus'])) { + $skuCostPrices = []; + foreach ($item['skus'] as $skuRow) { + if (isset($skuRow['cost_price']) && is_numeric($skuRow['cost_price'])) { + $skuCostPrices[] = floatval($skuRow['cost_price']); + } + } + if ($skuCostPrices) { + $costPrice = max($skuCostPrices); + } + } + $productInfo = [ + 'image' => $pic, + 'slider_image' => json_encode([$pic]), + 'store_name' => $item['name'], + 'store_info' => $item['name'], + 'cate_id' => 0, + 'price' => $salePrice, + 'ot_price' => $marketPrice, + 'delivery_type' => '1', + 'freight' => 1, + 'add_time' => time(), + 'cost' => $costPrice, + 'ficti' => 0, + 'spec_type' => 1, + 'code' => $item['i_id'], + 'type' => $supplierId ? 2 : 0, + 'relation_id' => $supplierId, + ]; + $stockSum = 0; + $detail = $details = $value = []; + foreach ($item['skus'] as $items) { + if (!$items['properties_value']) continue 2; + $detail[] = $items['properties_value']; + $details[] = [ + 'name' => $items['properties_value'], + 'select' => false + ]; + $stock = $stockMap[$items['sku_id']] ?? 0; + $skuCost = floatval($items['cost_price']); + $skuSettle = $supplierId ? $skuCost : 0.0; + $value[] = [ + 'bar_code' => '', + 'brokerage' => 0, + 'brokerage_two' => 0, + 'code' => $items['sku_id'], + 'settle_price' => $skuSettle, + 'cost' => $skuCost, + 'detail' => ['规格' => $items['properties_value']], + 'ot_price' => floatval($items['market_price']), + 'pic' => ImageUtil::urlToOssUrl($items['pic']), + 'price' => floatval($items['sale_price']), + 'select' => true, + 'value1' => $items['properties_value'], + 'values' => $items['properties_value'], + 'vip_price' => 0, + 'volume' => 0, + 'weight' => 0, + 'stock' => $stock, + 'sum_stock' => $stock, + ]; + $stockSum = bcadd($stockSum, $stock, 0); + } + $productInfo['stock'] = (int)$stockSum; + $pid = $productServices->value(['code' => $item['i_id']], 'id'); + if (!isset($pid)) { + $pid = $productServices->ErpProductSave($productInfo); + } else { + $productServices->update($pid, $productInfo); + } + ProductStockTips::dispatch([$pid, 0]); + $attr = [[ + 'value' => '规格', + 'detail' => $detail, + 'details' => $details, + ]]; + $skuList = $productAttrServices->validateProductAttr($attr, $value, $pid, 0, 0, 0); + $productAttrServices->saveProductAttr($skuList, $pid); + CacheProduct::setCache(md5($item['i_id']), $item['i_id']); + } + $productServices->cacheTag()->clear(); + $productAttrServices->cacheTag()->clear(); + Log::info('jst.product.sync.end'); + } catch (Exception $e) { + Log::error('jst.product.sync.error ' . $e->getMessage()); + } + } +} diff --git a/app/haichao/sync/JstStorkSync.php b/app/haichao/sync/JstStorkSync.php new file mode 100644 index 0000000..65d61ca --- /dev/null +++ b/app/haichao/sync/JstStorkSync.php @@ -0,0 +1,115 @@ +make(StoreProductAttrValueServices::class); + Log::info('jst.stork.sync.start'); + $inventorySkuIds = []; + $skuIds = ''; + $index = 1; + $count = 0; + foreach ($storeProductAttrValueServices->getList(['is_del' => 0]) as $item) { + if ($item['code']) { + $skuIds .= $item['code'] . ','; + $count++; + $inventorySkuIds[$index] = [ + 'length' => $count, + 'ids' => $skuIds, + ]; + if ($count >= 500) { + $skuIds = ''; + $index++; + $count = 0; + } + } + } + $productStock = []; + foreach ($inventorySkuIds as $inventorySkuId) { + foreach ($this->getInneropenStockAndDetailList($inventorySkuId['ids']) as $product) { + $productStock[] = [ + 'code' => $product['item_code'], + 'stock' => $product['stock'] + ]; + } + } + Log::info('jst.stork.sync.merge ' . count($productStock)); + foreach ($productStock as $item) { + $storeProductAttrValueServices->update(['code' => $item['code']], [ + 'stock' => $item['stock'], + 'sum_stock' => $item['stock'] + ]); + } + Log::info('jst.stork.sync.end'); + } + + public function syncStockFromErp(array $ids): bool + { + try { + /** @var StoreProductServices $storeProductServices */ + $storeProductServices = app()->make(StoreProductServices::class); + /** @var StoreProductAttrValueServices $storeProductAttrValueServices */ + $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class); + $list = $storeProductAttrValueServices->getSkuArray(['product_id' => $ids, 'type' => 0], 'code', 'id'); + $values = array_filter(array_values($list)); + if (empty($values)) { + throw new AdminException('没有符合同步库存的商品'); + } + $skuData = $skuMap = []; + $basic = 20; + $num = count($values); + $rate = ceil($num / $basic); + for ($i = 0; $i < $rate; $i++) { + $code = array_slice($values, $i * $basic, $basic); + $codeStr = implode(',', $code); + $result = $this->getStork($codeStr)['data']; + if (!empty($result['inventorys'])) { + foreach ($result['inventorys'] as $inventory) { + $skuMap[$inventory['sku_id']] = $inventory['qty'] - $inventory['order_lock']; + } + } + } + if (!empty($skuMap)) { + foreach ($skuMap as $key => $item) { + if ($id = array_search($key, $list)) { + $skuData[] = ['id' => $id, 'stock' => $item, 'sum_stock' => $item]; + } + } + } + $storeProductServices->transaction(function () use ($ids, $skuData, $storeProductAttrValueServices, $storeProductServices) { + $storeProductAttrValueServices->saveAll($skuData); + $productData = $storeProductAttrValueServices->getProductStockByValues($ids); + $storeProductServices->saveAll($productData); + }); + /** @var SystemStoreServices $systemStoreServices */ + $systemStoreServices = app()->make(SystemStoreServices::class); + $systemStoreList = $systemStoreServices->getErpStore([['erp_shop_id', '>', 0]]); + if (!empty($systemStoreList)) { + foreach ($systemStoreList as $store) { + ProductSyncErp::dispatchDo('syncBranchProductStock', [$ids[0], $skuMap, $store['id']]); + } + } + $storeProductServices->cacheTag()->clear(); + /** @var StoreProductAttrServices $attrService */ + $attrService = app()->make(StoreProductAttrServices::class); + $attrService->cacheTag()->clear(); + } catch (Exception $e) { + Log::error('jst.stork.sync.error ' . $e->getMessage()); + } + return true; + } +} diff --git a/app/haichao/task/JstSyncStorkTask.php b/app/haichao/task/JstSyncStorkTask.php new file mode 100644 index 0000000..d857323 --- /dev/null +++ b/app/haichao/task/JstSyncStorkTask.php @@ -0,0 +1,23 @@ +tick(150000, function () { + Log::info('jst.task.stork.tick'); + HaichaoJstServices::getServices()->getSyncStork()->syncStorkFromInneropenErp(); + Log::info('jst.task.stork.tick.done'); + }); + } +} diff --git a/app/haichao/task/JstSyncTask.php b/app/haichao/task/JstSyncTask.php new file mode 100644 index 0000000..f1a4098 --- /dev/null +++ b/app/haichao/task/JstSyncTask.php @@ -0,0 +1,21 @@ +tick(20000, function () { + Log::info('jst.task.product.tick'); + HaichaoJstServices::getServices()->getSyncProduct()->syncProduct(); + Log::info('jst.task.product.tick.done'); + }); + } +} diff --git a/app/jobs/haichao/JstAfterSalePushJob.php b/app/jobs/haichao/JstAfterSalePushJob.php new file mode 100644 index 0000000..4f6c512 --- /dev/null +++ b/app/jobs/haichao/JstAfterSalePushJob.php @@ -0,0 +1,90 @@ + +// +---------------------------------------------------------------------- + +namespace app\jobs\haichao; + +use app\haichao\HaichaoJstServices; +use crmeb\basic\BaseJobs; +use crmeb\traits\QueueTrait; +use think\facade\Log; + +class JstAfterSalePushJob extends BaseJobs +{ + use QueueTrait; + + public static function queueName() + { + return (string)env('QUEUE_LISTEN_NAME', 'CRMEB_PRO'); + } + + public function create(int $refundId, int $times = 0): bool + { + Log::info('jst.after_sale.create.job_start ' . json_encode([ + 'refund_id' => $refundId, + 'times' => $times + ], JSON_UNESCAPED_UNICODE)); + try { + HaichaoJstServices::getServices()->afterSaleCreatePush($refundId); + } catch (\Throwable $e) { + Log::error('jst.after_sale.create.job_error ' . json_encode([ + 'refund_id' => $refundId, + 'times' => $times, + 'msg' => $e->getMessage(), + ], JSON_UNESCAPED_UNICODE)); + if ($times < 3) { + self::dispatchSece(10 * ($times + 1), 'create', [$refundId, $times + 1]); + } + } + return true; + } + + public function cancel(int $refundId, int $times = 0): bool + { + Log::info('jst.after_sale.cancel.job_start ' . json_encode([ + 'refund_id' => $refundId, + 'times' => $times + ], JSON_UNESCAPED_UNICODE)); + try { + HaichaoJstServices::getServices()->afterSaleCancelPush($refundId); + } catch (\Throwable $e) { + Log::error('jst.after_sale.cancel.job_error ' . json_encode([ + 'refund_id' => $refundId, + 'times' => $times, + 'msg' => $e->getMessage(), + ], JSON_UNESCAPED_UNICODE)); + if ($times < 3) { + self::dispatchSece(10 * ($times + 1), 'cancel', [$refundId, $times + 1]); + } + } + return true; + } + + public function refunded(int $refundId, int $times = 0): bool + { + Log::info('jst.after_sale.refunded.job_start ' . json_encode([ + 'refund_id' => $refundId, + 'times' => $times + ], JSON_UNESCAPED_UNICODE)); + try { + HaichaoJstServices::getServices()->afterSaleRefundedPush($refundId); + } catch (\Throwable $e) { + Log::error('jst.after_sale.refunded.job_error ' . json_encode([ + 'refund_id' => $refundId, + 'times' => $times, + 'msg' => $e->getMessage(), + ], JSON_UNESCAPED_UNICODE)); + if ($times < 3) { + self::dispatchSece(10 * ($times + 1), 'refunded', [$refundId, $times + 1]); + } + } + return true; + } +} diff --git a/app/jobs/product/ProductSyncErp.php b/app/jobs/product/ProductSyncErp.php index 6007107..3f71160 100644 --- a/app/jobs/product/ProductSyncErp.php +++ b/app/jobs/product/ProductSyncErp.php @@ -5,10 +5,11 @@ namespace app\jobs\product; use app\services\product\product\StoreProductServices; use app\services\product\sku\StoreProductAttrServices; use app\services\product\sku\StoreProductAttrValueServices; +use app\haichao\HaichaoJstServices; use crmeb\basic\BaseJobs; use crmeb\exceptions\AdminException; -use crmeb\services\erp\Erp as erpServices; use crmeb\traits\QueueTrait; +use think\facade\Log; class ProductSyncErp extends BaseJobs { @@ -118,55 +119,164 @@ class ProductSyncErp extends BaseJobs * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ - public function productFromErp($spuArr) + public function productFromErp($spuArr, int $supplierId = 0) { try { - $result = (new erpServices())->serviceDriver('product')->syncProduct([$spuArr]); - $productList = $result['datas']; + Log::info('admin.manual.jst.product.sync.request', ['spu' => $spuArr]); + $result = app()->make(HaichaoJstServices::class)->getProductListKuan($spuArr); + $resultPreview = null; + try { + $resultPreview = mb_substr(json_encode($result, JSON_UNESCAPED_UNICODE), 0, 2000); + } catch (\Throwable $e) { + } + Log::info('admin.manual.jst.product.sync.api_result', [ + 'spu' => $spuArr, + 'page_index' => $result['page_index'] ?? null, + 'page_count' => $result['page_count'] ?? null, + 'datas_count' => isset($result['datas']) && is_array($result['datas']) ? count($result['datas']) : 0, + 'result_preview' => $resultPreview, + ]); + $productList = $result['datas'] ?? []; $productInfo = []; /** @var StoreProductServices $productServices */ $productServices = app()->make(StoreProductServices::class); /** @var StoreProductAttrServices $productAttrServices */ $productAttrServices = app()->make(StoreProductAttrServices::class); + Log::info('admin.manual.jst.product.sync.response', [ + 'spu' => $spuArr, + 'datas_count' => is_array($productList) ? count($productList) : 0 + ]); + $relationSupplierId = $supplierId > 0 ? $supplierId : 0; foreach ($productList as $item) { + Log::info('admin.manual.jst.product.sync.item', [ + 'spu' => $spuArr, + 'i_id' => $item['i_id'] ?? '', + 'name' => $item['name'] ?? '', + 'sku_count' => isset($item['skus']) && is_array($item['skus']) ? count($item['skus']) : 0 + ]); + $skuRows = isset($item['skus']) && is_array($item['skus']) ? $item['skus'] : []; + $salePrice = floatval($item['s_price'] ?? 0); + if ($salePrice <= 0 && $skuRows) { + $skuSalePrices = []; + foreach ($skuRows as $skuRow) { + if (isset($skuRow['sale_price']) && is_numeric($skuRow['sale_price'])) { + $skuSalePrices[] = floatval($skuRow['sale_price']); + } + } + if ($skuSalePrices) { + $salePrice = min($skuSalePrices); + } + } + $marketPrice = floatval($item['market_price'] ?? 0); + if ($marketPrice <= 0 && $skuRows) { + $skuMarketPrices = []; + foreach ($skuRows as $skuRow) { + if (isset($skuRow['market_price']) && is_numeric($skuRow['market_price'])) { + $skuMarketPrices[] = floatval($skuRow['market_price']); + } + } + if ($skuMarketPrices) { + $marketPrice = max($skuMarketPrices); + } + } + $costPrice = floatval($item['c_price'] ?? ($item['cost_price'] ?? 0)); + if ($costPrice <= 0 && $skuRows) { + $skuCostPrices = []; + foreach ($skuRows as $skuRow) { + if (isset($skuRow['cost_price']) && is_numeric($skuRow['cost_price'])) { + $skuCostPrices[] = floatval($skuRow['cost_price']); + } + } + if ($skuCostPrices) { + $costPrice = max($skuCostPrices); + } + } + $pic = (string)($item['pic'] ?? ($skuRows[0]['pic'] ?? '')); + $name = (string)($item['name'] ?? ($skuRows[0]['name'] ?? '')); $productInfo = [ - 'image' => (string)$item['pic'], - 'slider_image' => json_encode([(string)$item['pic']]), - 'store_name' => $item['name'], - 'store_info' => $item['name'], + 'image' => $pic, + 'slider_image' => json_encode([$pic]), + 'store_name' => $name, + 'store_info' => $name, 'cate_id' => 0, - 'price' => floatval($item['s_price']), - 'ot_price' => floatval($item['market_price']), + 'price' => $salePrice, + 'ot_price' => $marketPrice, 'delivery_type' => '1,2,3', 'freight' => 1, 'is_show' => 0, 'add_time' => time(), - 'cost' => floatval($item['c_price']), + 'cost' => $costPrice, 'ficti' => 0, 'spec_type' => 1, 'code' => $item['i_id'], + 'type' => $relationSupplierId ? 2 : 0, + 'relation_id' => $relationSupplierId, ]; $detail = $details = $value = []; - foreach ($item['skus'] as $items) { - $detail[] = $items['properties_value']; + $usedSpecValues = []; + foreach ($skuRows as $skuIndex => $items) { + $specValue = trim((string)($items['properties_value'] ?? '')); + if ($specValue === '') { + $specValue = trim((string)($items['name'] ?? '')); + } + if ($specValue === '') { + $specValue = '规格' . ($skuIndex + 1); + } + if (isset($usedSpecValues[$specValue])) { + $specValue = $specValue . '-' . ($skuIndex + 1); + } + $usedSpecValues[$specValue] = 1; + $detail[] = $specValue; $details[] = [ - 'name' => $items['properties_value'], + 'name' => $specValue, 'select' => false ]; + $skuCost = floatval($items['cost_price'] ?? $costPrice); + $skuSettle = $relationSupplierId ? $skuCost : 0.0; $value[] = [ 'bar_code' => '', 'brokerage' => 0, 'brokerage_two' => 0, - 'code' => $items['sku_id'], - 'cost' => floatval($items['cost_price']), - 'detail' => ['规格' => $items['properties_value']], - 'ot_price' => floatval($items['market_price']), + 'code' => (string)($items['sku_id'] ?? ''), + 'settle_price' => $skuSettle, + 'cost' => $skuCost, + 'detail' => ['规格' => $specValue], + 'ot_price' => floatval($items['market_price'] ?? $marketPrice), 'pic' => (string)$items['pic'], - 'price' => floatval($items['sale_price']), + 'price' => floatval($items['sale_price'] ?? $salePrice), 'select' => true, - 'value1' => $items['properties_value'], - 'values' => $items['properties_value'], + 'value1' => $specValue, + 'values' => $specValue, + 'vip_price' => 0, + 'volume' => 0, + 'weight' => 0, + 'stock' => 0, + ]; + } + if (!$value) { + $specValue = '默认'; + $detail[] = $specValue; + $details[] = [ + 'name' => $specValue, + 'select' => false + ]; + $defCost = floatval($item['c_price'] ?? 0); + $defSettle = $relationSupplierId ? $defCost : 0.0; + $value[] = [ + 'bar_code' => '', + 'brokerage' => 0, + 'brokerage_two' => 0, + 'code' => (string)($item['i_id'] ?? ''), + 'settle_price' => $defSettle, + 'cost' => $defCost, + 'detail' => ['规格' => $specValue], + 'ot_price' => floatval($item['market_price'] ?? 0), + 'pic' => (string)($item['pic'] ?? ''), + 'price' => floatval($item['s_price'] ?? 0), + 'select' => true, + 'value1' => $specValue, + 'values' => $specValue, 'vip_price' => 0, 'volume' => 0, 'weight' => 0, @@ -174,6 +284,7 @@ class ProductSyncErp extends BaseJobs ]; } $pid = $productServices->value(['code' => $item['i_id']], 'id'); + $existed = !!$pid; if (!$pid) { $pid = $productServices->ErpProductSave($productInfo); } @@ -185,11 +296,18 @@ class ProductSyncErp extends BaseJobs $skuList = $productAttrServices->validateProductAttr($attr, $value, $pid, 0, 0, 0); $productAttrServices->saveProductAttr($skuList, $pid); + Log::info('admin.manual.jst.product.sync.save', [ + 'spu' => $spuArr, + 'product_id' => $pid, + 'existed' => $existed, + 'sku_saved_count' => isset($skuList['value']) && is_array($skuList['value']) ? count($skuList['value']) : 0 + ]); } //清除数据缓存 $productServices->cacheTag()->clear(); $productAttrServices->cacheTag()->clear(); + Log::info('admin.manual.jst.product.sync.finish', ['spu' => $spuArr, 'count' => count($productList)]); } catch (\Exception $e) { @@ -198,6 +316,7 @@ class ProductSyncErp extends BaseJobs 'file' => $e->getFile(), 'line' => $e->getLine() ]); + Log::error('admin.manual.jst.product.sync.error', ['spu' => $spuArr, 'message' => $e->getMessage()]); } diff --git a/app/listener/order/ApplyRefund.php b/app/listener/order/ApplyRefund.php index e812b26..bb07048 100644 --- a/app/listener/order/ApplyRefund.php +++ b/app/listener/order/ApplyRefund.php @@ -10,8 +10,10 @@ // +---------------------------------------------------------------------- namespace app\listener\order; +use app\jobs\haichao\JstAfterSalePushJob; use app\jobs\notice\SocketPushJob; use crmeb\interfaces\ListenerInterface; +use think\facade\Log; /** * 订单申请退款事件 @@ -29,6 +31,16 @@ class ApplyRefund implements ListenerInterface SocketPushJob::dispatchDo('sendApplyRefund', [$order]); + try { + $jobId = JstAfterSalePushJob::dispatchDo('create', [(int)$refundId]); + Log::info('jst.after_sale.create.dispatch_ok ' . json_encode([ + 'refund_id' => (int)$refundId, + 'job_id' => $jobId, + ], JSON_UNESCAPED_UNICODE)); + } catch (\Throwable $e) { + Log::error('jst.after_sale.create.dispatch_error ' . $e->getMessage()); + } + //退款消息推送 event('notice.notice', [['order' => $order], 'send_order_apply_refund']); diff --git a/app/listener/order/Pay.php b/app/listener/order/Pay.php index 96b3eb8..42f509a 100644 --- a/app/listener/order/Pay.php +++ b/app/listener/order/Pay.php @@ -12,6 +12,7 @@ namespace app\listener\order; +use app\haichao\HaichaoJstServices; use app\jobs\agent\AgentJob; use app\jobs\order\OrderCreateAfterJob; use app\jobs\order\OrderDeliveryJob; @@ -134,10 +135,16 @@ class Pay implements ListenerInterface if ($orderInfo['shipping_type'] == 2 || $orderInfo['product_type'] == 1) { event('order.routine.shipping', ['product', $orderInfo, 4, '', '']); } - // 同步订单 -// if (sys_config('erp_open')) { -// OrderSyncJob::dispatchDo('syncOrder', [(int)$orderInfo['id']]); -// } + try { + $pushRet = HaichaoJstServices::getServices()->orderPush((int)$orderInfo['id'], 'WAIT_SELLER_SEND_GOODS'); + Log::info('jst.event.order_pay.push', [ + 'order_id' => $orderInfo['order_id'] ?? '', + 'oid' => (int)$orderInfo['id'], + 'ret' => $pushRet + ]); + } catch (\Throwable $e) { + Log::error('jst.event.order_pay.push.err ' . $e->getMessage()); + } if ($orderInfo['channel'] == 1) { $ChannelMerchantServices = app()->make(ChannelMerchantServices::class); $channelInfo = $ChannelMerchantServices->get(['uid' => $orderInfo['uid'], 'is_del' => 0]); diff --git a/app/listener/order/Refund.php b/app/listener/order/Refund.php index 2480162..f47cc79 100644 --- a/app/listener/order/Refund.php +++ b/app/listener/order/Refund.php @@ -10,6 +10,7 @@ // +---------------------------------------------------------------------- namespace app\listener\order; +use app\jobs\haichao\JstAfterSalePushJob; use app\jobs\order\OrderStatusJob; use app\jobs\product\ProductLogJob; use app\jobs\supplier\SupplierFinanceJob;use app\jobs\system\CapitalFlowJob; @@ -18,6 +19,7 @@ use app\services\order\StoreOrderServices; use app\services\order\StoreOrderStatusServices; use app\services\user\UserServices; use crmeb\interfaces\ListenerInterface; +use think\facade\Log; /** * 订单退款事件 @@ -31,7 +33,9 @@ class Refund implements ListenerInterface */ public function handle($event): void { - [$data, $order] = $event; + $data = $event[0] ?? []; + $order = $event[1] ?? []; + $refundId = (int)($event[3] ?? 0); $order = is_array($order) ? $order : $order->toArray(); //修改开票数据退款状态 $orderInvoiceServices = app()->make(StoreOrderInvoiceServices::class); @@ -76,5 +80,18 @@ class Refund implements ListenerInterface } //对外接口推送订单 event('out.outPush', ['refund_create_push', ['order_id' => (int)$order['id']]]); + + if ($refundId > 0) { + try { + $jobId = JstAfterSalePushJob::dispatchDo('refunded', [$refundId]); + Log::info('jst.after_sale.refunded.dispatch_ok ' . json_encode([ + 'refund_id' => $refundId, + 'order_id' => (int)$order['id'], + 'job_id' => $jobId, + ], JSON_UNESCAPED_UNICODE)); + } catch (\Throwable $e) { + Log::error('jst.after_sale.refunded.dispatch_error ' . $e->getMessage()); + } + } } } diff --git a/app/services/order/StoreOrderRefundServices.php b/app/services/order/StoreOrderRefundServices.php index f5102a3..64ec307 100644 --- a/app/services/order/StoreOrderRefundServices.php +++ b/app/services/order/StoreOrderRefundServices.php @@ -473,6 +473,15 @@ class StoreOrderRefundServices extends BaseServices $orderInfo = $this->storeOrderServices->get((int)$orderRefundInfo['store_order_id']); //订单拒绝退款事件 event('order.refuseRefund', [$orderInfo]); + try { + $jobId = \app\jobs\haichao\JstAfterSalePushJob::dispatchDo('cancel', [(int)$orderRefundInfo['id']]); + \think\facade\Log::info('jst.after_sale.cancel.dispatch_ok ' . json_encode([ + 'refund_id' => (int)$orderRefundInfo['id'], + 'job_id' => $jobId, + ], JSON_UNESCAPED_UNICODE)); + } catch (\Throwable $e) { + \think\facade\Log::error('jst.after_sale.cancel.dispatch_error ' . $e->getMessage()); + } return true; } @@ -538,6 +547,15 @@ class StoreOrderRefundServices extends BaseServices $storeOrderCartInfoServices->clearOrderCartInfo($oid); // 推送订单 event('out.outPush', ['refund_cancel_push', ['order_id' => (int)$orderRefundInfo['id']]]); + try { + $jobId = \app\jobs\haichao\JstAfterSalePushJob::dispatchDo('cancel', [(int)$orderRefundInfo['id']]); + \think\facade\Log::info('jst.after_sale.cancel.dispatch_ok ' . json_encode([ + 'refund_id' => (int)$orderRefundInfo['id'], + 'job_id' => $jobId, + ], JSON_UNESCAPED_UNICODE)); + } catch (\Throwable $e) { + \think\facade\Log::error('jst.after_sale.cancel.dispatch_error ' . $e->getMessage()); + } return true; } @@ -710,7 +728,7 @@ class StoreOrderRefundServices extends BaseServices return $order; }); //订单同意退款事件 - event('order.refund', [$refundData, $order, 'order_refund']); + event('order.refund', [$refundData, $order, 'order_refund', (int)$id]); return true; } diff --git a/crmeb/services/erp/storage/jushuitan/Product.php b/crmeb/services/erp/storage/jushuitan/Product.php index 378aaf7..a70a9b1 100644 --- a/crmeb/services/erp/storage/jushuitan/Product.php +++ b/crmeb/services/erp/storage/jushuitan/Product.php @@ -4,6 +4,7 @@ namespace crmeb\services\erp\storage\jushuitan; use crmeb\services\erp\AccessToken; use crmeb\services\erp\storage\Jushuitan; +use think\facade\Log; class Product extends Jushuitan { @@ -96,7 +97,9 @@ class Product extends Jushuitan $params = $this->getParams($biz); //请求平台接口 + Log::info('jst.api.product.syncProduct.request', ['url' => $url, 'biz' => $biz]); $request = $this->postRequest($url, $params); + Log::info('jst.api.product.syncProduct.response', ['code' => $request['code'] ?? null, 'msg' => $request['msg'] ?? null]); //获取ERP商品信息 return $request["data"]; @@ -121,7 +124,9 @@ class Product extends Jushuitan $params = $this->getParams($biz); //请求平台接口 + Log::info('jst.api.product.syncStock.request', ['url' => $url, 'biz' => $biz]); $request = $this->postRequest($url, $params); + Log::info('jst.api.product.syncStock.response', ['code' => $request['code'] ?? null, 'msg' => $request['msg'] ?? null]); return $request["data"]; } -} \ No newline at end of file +} diff --git a/route/admin.php b/route/admin.php index 0278ad0..524ecde 100644 --- a/route/admin.php +++ b/route/admin.php @@ -12,6 +12,7 @@ use app\http\middleware\InstallMiddleware; use think\facade\Route; use think\facade\Config; use think\Response; +use app\haichao\HaichaoAop; /** * 后台管理路由配置 @@ -1622,6 +1623,8 @@ Route::group('adminapi', function () { Route::get('cache', 'v1.product.StoreProduct/getCacheData')->option(['real_name' => '获取退出未保存的数据']); //导入ERP商品到平台 Route::post('import_erp_product', 'v1.product.StoreProduct/import_erp_product')->option(['real_name' => '导入ERP商品到平台']); + //手动同步聚水潭商品 + Route::post('sync_jst_product', 'v1.product.StoreProduct/sync_jst_product')->option(['real_name' => '手动同步聚水潭商品']); //保存还未提交数据 Route::post('cache', 'v1.product.StoreProduct/saveCacheData')->option(['real_name' => '保存还未提交数据']); //删除退出未保存的数据 @@ -2865,4 +2868,4 @@ Route::group('adminapi', function () { } else return Response::create()->code(404); }); -})->prefix('admin.')->middleware(InstallMiddleware::class); +})->prefix('admin.')->middleware(InstallMiddleware::class)->middleware(HaichaoAop::class); diff --git a/route/api.php b/route/api.php index b1a596c..2d9681d 100644 --- a/route/api.php +++ b/route/api.php @@ -6,6 +6,7 @@ use app\http\middleware\api\BlockerMiddleware; use app\http\middleware\api\ClientMiddleware; use app\http\middleware\InstallMiddleware; use app\http\middleware\StationOpenMiddleware; +use app\haichao\HaichaoAop; use app\http\middleware\api\CommunityOpenMiddleware; use think\facade\Config; use think\facade\Route; @@ -17,6 +18,7 @@ use think\Response; Route::group('api', function () { Route::any('order_call_back', 'v1.order.StoreOrder/callBack');//商家寄件回调 + Route::any('jst/callback', 'v1.PublicController/jstCallback')->name('jstCallback'); Route::any('wechat/serve', 'v1.wechat.Wechat/serve');//公众号服务 Route::any('wechat/miniServe', 'v1.wechat.Wechat/miniServe');//小程序服务 Route::any('work/serve', 'v1.wechat.Wechat/work');//企业微信服务 @@ -899,4 +901,4 @@ Route::group('api', function () { return Response::create()->code(404); }); -})->prefix('api.')->middleware(InstallMiddleware::class)->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class); +})->prefix('api.')->middleware(InstallMiddleware::class)->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class)->middleware(HaichaoAop::class); diff --git a/route/erp.php b/route/erp.php index 1bbbeb3..1f5ebfa 100644 --- a/route/erp.php +++ b/route/erp.php @@ -2,6 +2,7 @@ use app\http\middleware\AllowOriginMiddleware; use app\http\middleware\StationOpenMiddleware; +use app\haichao\HaichaoAop; use think\facade\Route; /** @@ -25,4 +26,4 @@ Route::group('erpapi', function () { Route::post('order/cancel_callback', 'Order/cancelCallback')->name('cancelCallback'); //订单取消回调 Route::post('refund/receive_callback', 'Order/receiveCallback')->name('receiveCallback'); //售后收货回调 -})->prefix('erp.')->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class); +})->prefix('erp.')->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class)->middleware(HaichaoAop::class); diff --git a/route/kefu.php b/route/kefu.php index a5d6bbf..dcda84b 100644 --- a/route/kefu.php +++ b/route/kefu.php @@ -16,6 +16,7 @@ use think\facade\Route; use think\facade\Config; use app\http\middleware\AllowOriginMiddleware; use app\http\middleware\kefu\KefuAuthTokenMiddleware; +use app\haichao\HaichaoAop; /** * 客服端路由配置 @@ -143,4 +144,4 @@ Route::group('kefuapi', function () { } else return Response::create()->code(404); }); -})->prefix('kefu.')->middleware(InstallMiddleware::class); +})->prefix('kefu.')->middleware(InstallMiddleware::class)->middleware(HaichaoAop::class); diff --git a/route/out.php b/route/out.php index 8f22d2d..9ba8af3 100644 --- a/route/out.php +++ b/route/out.php @@ -5,6 +5,7 @@ use app\http\middleware\AllowOriginMiddleware; use app\http\middleware\InstallMiddleware; use app\http\middleware\out\AuthTokenMiddleware; use app\http\middleware\StationOpenMiddleware; +use app\haichao\HaichaoAop; use think\facade\Route; /** @@ -76,5 +77,4 @@ Route::group('outapi', function () { })->middleware(AuthTokenMiddleware::class, true); -})->prefix('out.')->middleware(InstallMiddleware::class)->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class); - +})->prefix('out.')->middleware(InstallMiddleware::class)->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class)->middleware(HaichaoAop::class); diff --git a/route/supplier.php b/route/supplier.php index 7c3dc00..95083fb 100644 --- a/route/supplier.php +++ b/route/supplier.php @@ -5,6 +5,7 @@ use app\http\middleware\AllowOriginMiddleware; use app\http\middleware\InstallMiddleware; use app\http\middleware\supplier\AuthTokenMiddleware; use app\http\middleware\StationOpenMiddleware; +use app\haichao\HaichaoAop; use think\facade\Config; use think\facade\Route; use think\Response; @@ -516,6 +517,5 @@ Route::group('supplierapi', function () { return Response::create()->code(404); }); -})->prefix('supplier.')->middleware(InstallMiddleware::class)->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class); - +})->prefix('supplier.')->middleware(InstallMiddleware::class)->middleware(AllowOriginMiddleware::class)->middleware(StationOpenMiddleware::class)->middleware(HaichaoAop::class);