feat(haichao): 新增聚水潭ERP集成功能

- 新增聚水潭回调接口及事件处理机制
- 添加聚水潭商品、订单、售后同步任务
- 实现聚水潭订单推送及售后状态同步
- 新增手动同步聚水潭商品功能
- 在各路由组添加聚水潭中间件支持
This commit is contained in:
2026-04-01 11:55:18 +08:00
parent 359e9eef48
commit 7e422335e4
30 changed files with 2601 additions and 37 deletions

View File

@@ -0,0 +1,90 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
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;
}
}

View File

@@ -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()]);
}