- 引入 alibabacloud/dysmsapi-20170525 依赖,集成阿里云短信服务 - 将短信验证码从6位改为4位,优化用户体验 - 添加聚水潭商品按时间范围同步功能,支持分页拉取 - 增强Cron任务异常处理,添加错误日志记录 - 修复系统附件服务中图片路径处理逻辑 - 更新阿里云SDK相关组件至最新版本 - 添加think-trace调试扩展,便于问题排查 - 优化跨域中间件,使用str_contains替代strpos
944 lines
35 KiB
PHP
944 lines
35 KiB
PHP
<?php
|
|
|
|
namespace app\haichao;
|
|
|
|
use app\services\order\StoreOrderCartInfoServices;
|
|
use app\services\order\StoreOrderRefundServices;
|
|
use app\services\order\StoreOrderServices;
|
|
use app\services\supplier\SystemSupplierServices;
|
|
use crmeb\exceptions\AdminException;
|
|
use think\exception\ValidateException;
|
|
use think\facade\Log;
|
|
|
|
class HaichaoJstServices extends HaichaoBridge
|
|
{
|
|
/**
|
|
* 同步物流公司信息并推送订单发货节点
|
|
* @param string $postId
|
|
* @param string $postName
|
|
* @param int $oid
|
|
* @return array
|
|
*/
|
|
public function updateStoreOrderDelivery(string $postId, string $postName, $oid = 0): array
|
|
{
|
|
$postList = [];
|
|
$postList[] = [
|
|
'l_id' => $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;
|
|
}
|
|
}
|
|
}
|
|
Log::info('jst.orders_upload.request ' . json_encode([
|
|
'api' => 'open/jushuitan/orders/upload',
|
|
'oid' => $oid,
|
|
'shop_status' => $status,
|
|
'biz' => $biz,
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* 按时间范围获取聚水潭商品列表
|
|
* @param string $startTime 开始时间 Y-m-d H:i:s
|
|
* @param string $endTime 结束时间 Y-m-d H:i:s
|
|
* @param int $pageIndex 页码
|
|
* @param int $pageSize 每页数量
|
|
* @return array
|
|
*/
|
|
public function getProductListByTime(string $startTime, string $endTime, int $pageIndex = 1, int $pageSize = 50): array
|
|
{
|
|
$data = $this->post('open/mall/item/query', [
|
|
'modified_begin' => $startTime,
|
|
'modified_end' => $endTime,
|
|
'page_index' => $pageIndex,
|
|
'page_size' => $pageSize,
|
|
'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 uploadItemSku(array $items): array
|
|
{
|
|
return $this->post('open/jushuitan/itemsku/upload', [
|
|
'items' => $items,
|
|
]);
|
|
}
|
|
|
|
public function uploadItemSkuByProductIds(array $productIds): array
|
|
{
|
|
$productIds = array_values(array_unique(array_map('intval', $productIds)));
|
|
if (!$productIds) {
|
|
throw new ValidateException('请选择商品');
|
|
}
|
|
|
|
$products = \app\model\product\product\StoreProduct::whereIn('id', $productIds)
|
|
->where('is_del', 0)
|
|
->with(['attrValue'])
|
|
->select()
|
|
->toArray();
|
|
|
|
$items = [];
|
|
$errors = [];
|
|
foreach ($products as $product) {
|
|
$iId = trim((string)($product['code'] ?? ''));
|
|
$productId = (int)($product['id'] ?? 0);
|
|
if (!$iId) {
|
|
$errors[] = [
|
|
'product_id' => $productId,
|
|
'msg' => '商品编码(code)为空',
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$skuList = $product['attr_value'] ?? ($product['attrValue'] ?? []);
|
|
if (!is_array($skuList) || !$skuList) {
|
|
$errors[] = [
|
|
'product_id' => $productId,
|
|
'i_id' => $iId,
|
|
'msg' => '商品规格为空',
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$productImage = (string)($product['image'] ?? '');
|
|
$productName = (string)($product['store_name'] ?? '');
|
|
$enabled = ((int)($product['is_show'] ?? 0) === 1) ? 1 : -1;
|
|
foreach ($skuList as $sku) {
|
|
$skuId = trim((string)($sku['code'] ?? ''));
|
|
if (!$skuId) {
|
|
$errors[] = [
|
|
'product_id' => $productId,
|
|
'i_id' => $iId,
|
|
'msg' => '规格编码(code)为空',
|
|
];
|
|
continue;
|
|
}
|
|
$skuImage = (string)($sku['image'] ?? $productImage);
|
|
$propertiesValue = (string)($sku['suk'] ?? '');
|
|
$propertiesValue = str_replace(',', ';', $propertiesValue);
|
|
|
|
$items[] = [
|
|
'i_id' => $iId,
|
|
'sku_id' => $skuId,
|
|
'name' => $productName,
|
|
'properties_value' => $propertiesValue,
|
|
's_price' => (float)($sku['price'] ?? ($product['price'] ?? 0)),
|
|
'market_price' => (float)($sku['ot_price'] ?? ($product['ot_price'] ?? 0)),
|
|
'c_price' => (float)($sku['cost'] ?? ($product['cost'] ?? 0)),
|
|
'pic' => $productImage,
|
|
'pic_big' => $productImage,
|
|
'sku_pic' => $skuImage,
|
|
'enabled' => $enabled,
|
|
];
|
|
}
|
|
}
|
|
|
|
if (!$items) {
|
|
throw new ValidateException('没有可同步的商品规格');
|
|
}
|
|
|
|
$rt = $this->uploadItemSku($items);
|
|
return [
|
|
'upload_count' => count($items),
|
|
'errors' => $errors,
|
|
'jst' => $rt,
|
|
];
|
|
}
|
|
|
|
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(array|string $styleCodes, ?array &$stats = null): array
|
|
{
|
|
$rt = [];
|
|
$codeList = is_array($styleCodes) ? $styleCodes : explode(',', trim($styleCodes, ", \t\n\r\0\x0B"));
|
|
$codeList = array_values(array_filter(array_map('trim', $codeList), static fn($v) => $v !== ''));
|
|
|
|
foreach (array_chunk($codeList, 100) as $chunkCodes) {
|
|
if ($stats !== null) {
|
|
$stats['chunk_count'] = (int)($stats['chunk_count'] ?? 0) + 1;
|
|
$stats['request_sku_count'] = (int)($stats['request_sku_count'] ?? 0) + count($chunkCodes);
|
|
}
|
|
$i = 1;
|
|
do {
|
|
if ($stats !== null) {
|
|
$stats['api_call_count'] = (int)($stats['api_call_count'] ?? 0) + 1;
|
|
}
|
|
$result = $this->post('open/inventory/query', [
|
|
'page_index' => $i++,
|
|
'page_size' => 100,
|
|
'sku_ids' => implode(',', $chunkCodes)
|
|
])['data']['inventorys'] ?? [];
|
|
if ($stats !== null) {
|
|
$stats['api_resp_row_count'] = (int)($stats['api_resp_row_count'] ?? 0) + count($result);
|
|
}
|
|
|
|
$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(550000);
|
|
} 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();
|
|
}
|
|
}
|