feat: 添加阿里云短信服务支持并优化验证码逻辑
- 引入 alibabacloud/dysmsapi-20170525 依赖,集成阿里云短信服务 - 将短信验证码从6位改为4位,优化用户体验 - 添加聚水潭商品按时间范围同步功能,支持分页拉取 - 增强Cron任务异常处理,添加错误日志记录 - 修复系统附件服务中图片路径处理逻辑 - 更新阿里云SDK相关组件至最新版本 - 添加think-trace调试扩展,便于问题排查 - 优化跨域中间件,使用str_contains替代strpos
This commit is contained in:
54
app/command/BackfillProductSpu.php
Normal file
54
app/command/BackfillProductSpu.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\services\product\product\StoreProductServices;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Option;
|
||||
use think\facade\Db;
|
||||
|
||||
class BackfillProductSpu extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('product:spu:backfill')
|
||||
->addOption('chunk', null, Option::VALUE_REQUIRED, '单次处理条数', 200)
|
||||
->addOption('limit', null, Option::VALUE_REQUIRED, '最多处理条数(0不限制)', 0)
|
||||
->setDescription('Backfill empty product spu');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$chunk = max(1, (int)$input->getOption('chunk'));
|
||||
$limit = max(0, (int)$input->getOption('limit'));
|
||||
$handled = 0;
|
||||
|
||||
/** @var StoreProductServices $productServices */
|
||||
$productServices = app()->make(StoreProductServices::class);
|
||||
|
||||
while (true) {
|
||||
$query = Db::name('store_product')->where('spu', '')->where('is_del', 0)->order('id', 'asc')->limit($chunk);
|
||||
if ($limit > 0) {
|
||||
$query->limit(min($chunk, $limit - $handled));
|
||||
}
|
||||
$rows = $query->field(['id'])->select()->toArray();
|
||||
if (!$rows) {
|
||||
break;
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
$productServices->ensureSpu((int)$row['id']);
|
||||
$handled++;
|
||||
if ($limit > 0 && $handled >= $limit) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
$output->info('handled: ' . $handled);
|
||||
}
|
||||
|
||||
$output->info('done: ' . $handled);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
93
app/command/TestJstSpu.php
Normal file
93
app/command/TestJstSpu.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\haichao\HaichaoJstServices;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class TestJstSpu extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('test_jst_spu')
|
||||
->addArgument('spu', Argument::REQUIRED, 'SPU编码')
|
||||
->setDescription('测试聚水潭按SPU拉取商品接口');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$spu = $input->getArgument('spu');
|
||||
|
||||
if (empty($spu)) {
|
||||
$output->error('请提供SPU编码');
|
||||
$output->info('用法: php think test_jst_spu SPU编码');
|
||||
$output->info('示例: php think test_jst_spu test123');
|
||||
return;
|
||||
}
|
||||
|
||||
$output->info('正在调用聚水潭API获取商品信息...');
|
||||
$output->info('SPU: ' . $spu);
|
||||
$output->newLine();
|
||||
|
||||
try {
|
||||
$startTime = microtime(true);
|
||||
|
||||
$haichaoJst = HaichaoJstServices::getServices();
|
||||
$result = $haichaoJst->getProductListKuan($spu);
|
||||
|
||||
$endTime = microtime(true);
|
||||
$costTime = round(($endTime - $startTime) * 1000, 2);
|
||||
|
||||
$output->info('✅ API调用成功! 耗时: ' . $costTime . 'ms');
|
||||
$output->newLine();
|
||||
|
||||
if (empty($result)) {
|
||||
$output->warning('⚠️ 未获取到任何数据');
|
||||
return;
|
||||
}
|
||||
|
||||
$output->info('📦 获取到的数据:');
|
||||
$output->write(json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
$output->newLine();
|
||||
|
||||
if (isset($result['code'])) {
|
||||
$output->info('响应码: ' . $result['code']);
|
||||
}
|
||||
if (isset($result['msg'])) {
|
||||
$output->info('响应消息: ' . $result['msg']);
|
||||
}
|
||||
if (isset($result['datas'])) {
|
||||
$output->info('商品数量: ' . count($result['datas']));
|
||||
|
||||
if (!empty($result['datas'])) {
|
||||
$output->newLine();
|
||||
$output->info('📋 第一个商品信息:');
|
||||
$firstProduct = $result['datas'][0];
|
||||
$output->writeln(' - SPU ID: ' . ($firstProduct['i_id'] ?? 'N/A'));
|
||||
$output->writeln(' - 商品名称: ' . ($firstProduct['name'] ?? 'N/A'));
|
||||
$output->writeln(' - 售价: ' . ($firstProduct['s_price'] ?? 'N/A'));
|
||||
$output->writeln(' - 市场价: ' . ($firstProduct['market_price'] ?? 'N/A'));
|
||||
$output->writeln(' - 成本价: ' . ($firstProduct['c_price'] ?? 'N/A'));
|
||||
|
||||
if (isset($firstProduct['skus']) && is_array($firstProduct['skus'])) {
|
||||
$output->writeln(' - SKU数量: ' . count($firstProduct['skus']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$output->newLine();
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('❌ API调用失败!');
|
||||
$output->error('错误信息: ' . $e->getMessage());
|
||||
$output->error('错误文件: ' . $e->getFile() . ':' . $e->getLine());
|
||||
$output->newLine();
|
||||
$output->info('堆栈跟踪:');
|
||||
$output->write($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $code) {
|
||||
CacheService::delete('code_' . $phone);
|
||||
return app('json')->fail('验证码错误');
|
||||
@@ -262,7 +262,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $code) {
|
||||
CacheService::delete('code_' . $phone);
|
||||
return app('json')->fail('验证码错误');
|
||||
|
||||
@@ -996,37 +996,163 @@ class StoreProduct extends AuthController
|
||||
|
||||
public function sync_jst_product()
|
||||
{
|
||||
[$spuStr, $sync] = $this->request->postMore([
|
||||
[$spuStr, $sync, $startTime, $endTime] = $this->request->postMore([
|
||||
['spu_str', ""],
|
||||
['sync', 0]
|
||||
['sync', 0],
|
||||
['start_time', ""],
|
||||
['end_time', ""]
|
||||
], true);
|
||||
|
||||
$traceId = uniqid('jst_sync_', true);
|
||||
Log::info('admin.manual.jst.product.sync.request', [
|
||||
'trace_id' => $traceId,
|
||||
'admin_id' => $this->adminId,
|
||||
'admin_type' => $this->adminType,
|
||||
'supplier_id' => $this->supplierId,
|
||||
'client_ip' => $this->request->ip(),
|
||||
'sync' => (int)$sync,
|
||||
'spu_str' => (string)$spuStr,
|
||||
'start_time' => (string)$startTime,
|
||||
'end_time' => (string)$endTime,
|
||||
'start_time_is_iso' => is_string($startTime) && str_contains($startTime, 'T'),
|
||||
'end_time_is_iso' => is_string($endTime) && str_contains($endTime, 'T'),
|
||||
]);
|
||||
|
||||
if ($startTime && $endTime) {
|
||||
$startTs = strtotime((string)$startTime);
|
||||
$endTs = strtotime((string)$endTime);
|
||||
if (!$startTs || !$endTs) {
|
||||
Log::warning('admin.manual.jst.product.sync_by_time.invalid_time', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
]);
|
||||
return $this->fail('时间格式错误');
|
||||
}
|
||||
if ($endTs < $startTs) {
|
||||
Log::warning('admin.manual.jst.product.sync_by_time.invalid_range', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
]);
|
||||
return $this->fail('时间范围错误');
|
||||
}
|
||||
if (($endTs - $startTs) > 7 * 86400) {
|
||||
Log::info('admin.manual.jst.product.sync_by_time.range_too_large', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'range_seconds' => $endTs - $startTs,
|
||||
]);
|
||||
return $this->fail('时间范围不能超过7天');
|
||||
}
|
||||
$startTime = date('Y-m-d H:i:s', $startTs);
|
||||
$endTime = date('Y-m-d H:i:s', $endTs);
|
||||
|
||||
if ($sync) {
|
||||
Log::info('admin.manual.jst.product.sync_by_time.run', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'supplier_id' => $this->supplierId,
|
||||
]);
|
||||
$result = (new ProductSyncErp())->syncProductByTime($startTime, $endTime, (int)$this->supplierId, $traceId, 'sync');
|
||||
if (!is_array($result) || empty($result['ok'])) {
|
||||
return $this->fail('同步失败,请检查日志');
|
||||
}
|
||||
return $this->success('同步完成', $result);
|
||||
} else {
|
||||
Log::info('admin.manual.jst.product.sync_by_time.dispatch', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'supplier_id' => $this->supplierId,
|
||||
]);
|
||||
ProductSyncErp::dispatchDo('syncProductByTime', [$startTime, $endTime, (int)$this->supplierId, $traceId]);
|
||||
return $this->success('正在按时间范围同步中,请稍后查看', ['trace_id' => $traceId]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$spuStr) {
|
||||
return $this->fail('请输入ERP商品SPU');
|
||||
return $this->fail('请输入ERP商品SPU或选择时间范围');
|
||||
}
|
||||
$spus = array_unique(array_filter(explode(',', $spuStr)));
|
||||
$traceId = uniqid('jst_', true);
|
||||
$spusLog = $spus;
|
||||
$spusTotal = count($spus);
|
||||
if ($spusTotal > 50) {
|
||||
$spusLog = array_slice($spus, 0, 50);
|
||||
}
|
||||
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
|
||||
'count' => $spusTotal,
|
||||
'spus' => $spusLog,
|
||||
'spus_truncated' => $spusTotal > 50
|
||||
]);
|
||||
if ($sync) {
|
||||
$stats = [
|
||||
'ok' => true,
|
||||
'mode' => 'spu',
|
||||
'trace_id' => $traceId,
|
||||
'spu_count' => $spusTotal,
|
||||
'fetched' => 0,
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'failed' => 0,
|
||||
];
|
||||
foreach ($spus as $spu) {
|
||||
(new ProductSyncErp())->productFromErp($spu, (int)$this->supplierId);
|
||||
$rt = (new ProductSyncErp())->productFromErp($spu, (int)$this->supplierId, $traceId);
|
||||
if (is_array($rt)) {
|
||||
foreach (['fetched', 'created', 'updated', 'skipped', 'failed'] as $k) {
|
||||
$stats[$k] += (int)($rt[$k] ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->success('同步完成');
|
||||
return $this->success('同步完成', $stats);
|
||||
} else {
|
||||
foreach ($spus as $spu) {
|
||||
ProductSyncErp::dispatchDo('productFromErp', [$spu, (int)$this->supplierId]);
|
||||
ProductSyncErp::dispatchDo('productFromErp', [$spu, (int)$this->supplierId, $traceId]);
|
||||
}
|
||||
return $this->success('正在同步中,请稍后查看');
|
||||
return $this->success('正在同步中,请稍后查看', ['trace_id' => $traceId, 'task_count' => $spusTotal]);
|
||||
}
|
||||
}
|
||||
|
||||
public function upload_jst_itemsku()
|
||||
{
|
||||
[$ids] = $this->request->postMore([
|
||||
['ids', []],
|
||||
], true);
|
||||
if (is_string($ids)) {
|
||||
$ids = array_filter(explode(',', $ids));
|
||||
}
|
||||
$ids = is_array($ids) ? array_values(array_unique(array_map('intval', $ids))) : [];
|
||||
if (!$ids) {
|
||||
return $this->fail('请选择商品');
|
||||
}
|
||||
|
||||
$traceId = uniqid('jst_itemsku_', true);
|
||||
Log::info('admin.manual.jst.itemsku.upload.start', [
|
||||
'trace_id' => $traceId,
|
||||
'admin_id' => $this->adminId,
|
||||
'admin_type' => $this->adminType,
|
||||
'supplier_id' => $this->supplierId,
|
||||
'client_ip' => $this->request->ip(),
|
||||
'product_count' => count($ids),
|
||||
'product_ids' => $ids,
|
||||
]);
|
||||
|
||||
$rt = app()->make(\app\haichao\HaichaoJstServices::class)->uploadItemSkuByProductIds($ids);
|
||||
Log::info('admin.manual.jst.itemsku.upload.end', [
|
||||
'trace_id' => $traceId,
|
||||
'upload_count' => $rt['upload_count'] ?? null,
|
||||
]);
|
||||
return $this->success($rt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品批量操作
|
||||
* @param StoreProductBatchProcessServices $batchProcessServices
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace app\controller\admin\v1\statistic;
|
||||
use app\controller\admin\AuthController;
|
||||
use app\services\statistic\OrderStatisticServices;
|
||||
use think\annotation\Inject;
|
||||
use think\facade\Log;
|
||||
|
||||
class OrderStatistic extends AuthController
|
||||
{
|
||||
@@ -36,6 +37,10 @@ class OrderStatistic extends AuthController
|
||||
if($this->supplierId > 0){
|
||||
$where['supplier_id'] = $this->supplierId;
|
||||
}
|
||||
$params = $this->request->param();
|
||||
$paramsJson = json_encode($params, JSON_UNESCAPED_UNICODE);
|
||||
$whereJson = json_encode($where, JSON_UNESCAPED_UNICODE);
|
||||
Log::info('adminapi.statistic.order.get_basic supplierId:' . (int)$this->supplierId . ' params:' . ($paramsJson === false ? '' : $paramsJson) . ' where:' . ($whereJson === false ? '' : $whereJson));
|
||||
$data = $this->services->getBasic($where);
|
||||
return app('json')->success($data);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
CacheService::delete('code_' . $phone);
|
||||
return app('json')->fail('验证码错误');
|
||||
@@ -288,7 +288,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $account);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha)
|
||||
return app('json')->fail('验证码错误');
|
||||
if (strlen(trim($password)) < 6 || strlen(trim($password)) > 16)
|
||||
@@ -318,7 +318,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $account);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
return app('json')->fail('验证码错误');
|
||||
}
|
||||
@@ -353,7 +353,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
return app('json')->fail('验证码错误');
|
||||
}
|
||||
@@ -418,7 +418,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
CacheService::delete('code_' . $phone);
|
||||
return app('json')->fail('验证码错误');
|
||||
@@ -458,7 +458,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha)
|
||||
return app('json')->fail('验证码错误');
|
||||
}
|
||||
@@ -488,7 +488,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha)
|
||||
return app('json')->fail('验证码错误');
|
||||
$uid = (int)$request->uid();
|
||||
|
||||
@@ -104,7 +104,7 @@ class UserApply
|
||||
$verifyCode = CacheService::get('code_' . $data['phone']);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
return app('json')->fail('验证码错误');
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class Channel
|
||||
$verifyCode = CacheService::get('code_' . $data['phone']);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $data['code']) {
|
||||
return app('json')->fail('验证码错误');
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class Wechat
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
CacheService::delete('code_' . $phone);
|
||||
return app('json')->fail('验证码错误');
|
||||
|
||||
@@ -72,7 +72,7 @@ class Routine
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
return app('json')->fail('验证码错误');
|
||||
}
|
||||
@@ -170,7 +170,7 @@ class Routine
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
CacheService::delete('code_' . $phone);
|
||||
return app('json')->fail('验证码错误');
|
||||
|
||||
@@ -112,7 +112,7 @@ class Wechat
|
||||
$verifyCode = CacheService::get('code_' . $phone);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
CacheService::delete('code_' . $phone);
|
||||
return app('json')->fail('验证码错误');
|
||||
|
||||
@@ -108,6 +108,9 @@ class Common extends AuthController
|
||||
*/
|
||||
public function homeStatics(SupplierOrderServices $orderServices)
|
||||
{
|
||||
if (!(int)$this->supplierId) {
|
||||
return $this->fail('登录已失效');
|
||||
}
|
||||
[$time] = $this->request->getMore([
|
||||
['data', '', '', 'time']
|
||||
], true);
|
||||
@@ -123,6 +126,9 @@ class Common extends AuthController
|
||||
*/
|
||||
public function orderChart(SupplierOrderServices $orderServices)
|
||||
{
|
||||
if (!(int)$this->supplierId) {
|
||||
return $this->fail('登录已失效');
|
||||
}
|
||||
[$time] = $this->request->getMore([
|
||||
['data', '', '', 'time']
|
||||
], true);
|
||||
@@ -138,6 +144,9 @@ class Common extends AuthController
|
||||
*/
|
||||
public function orderType(SupplierOrderServices $orderServices)
|
||||
{
|
||||
if (!(int)$this->supplierId) {
|
||||
return $this->fail('登录已失效');
|
||||
}
|
||||
[$time] = $this->request->getMore([
|
||||
['data', '', '', 'time']
|
||||
], true);
|
||||
@@ -153,6 +162,9 @@ class Common extends AuthController
|
||||
*/
|
||||
public function orderChannel(SupplierOrderServices $orderServices)
|
||||
{
|
||||
if (!(int)$this->supplierId) {
|
||||
return $this->fail('登录已失效');
|
||||
}
|
||||
[$time] = $this->request->getMore([
|
||||
['data', '', '', 'time']
|
||||
], true);
|
||||
@@ -167,6 +179,9 @@ class Common extends AuthController
|
||||
*/
|
||||
public function jnotice()
|
||||
{
|
||||
if (!(int)$this->supplierId) {
|
||||
return $this->fail('登录已失效');
|
||||
}
|
||||
/** @var StoreOrderServices $orderServices */
|
||||
$orderServices = app()->make(StoreOrderServices::class);
|
||||
$orderNum = $orderServices->storeOrderCount((int)$this->supplierId, -1, 'supplier_id');
|
||||
|
||||
@@ -168,7 +168,7 @@ class Login
|
||||
$verifyCode = CacheService::get('code_' . $account);
|
||||
if (!$verifyCode)
|
||||
return app('json')->fail('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $captcha) {
|
||||
return app('json')->fail('验证码错误');
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ abstract class HaichaoBridge
|
||||
'shopId' => '',
|
||||
];
|
||||
|
||||
private string $tokenFile = 'runtime/jstdata/jst_token_data.json';
|
||||
|
||||
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['access_token'] = $this->getToken();
|
||||
$this->api['shopId'] = (string)env('JST_SHOP_ID', '');
|
||||
}
|
||||
|
||||
@@ -29,18 +31,46 @@ abstract class HaichaoBridge
|
||||
return $this->api['shopId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚水潭开放平台请求封装
|
||||
* @param string $api
|
||||
* @param array $data
|
||||
* @param array $addonBody
|
||||
* @return array
|
||||
*/
|
||||
/**
|
||||
* 聚水潭开放平台请求封装
|
||||
* @param string $api
|
||||
* @param array $data
|
||||
* @param array $addonBody
|
||||
* @return array
|
||||
*/
|
||||
protected function post(string $api, array $data = [], array $addonBody = []): array
|
||||
{
|
||||
return $this->postInternal($api, $data, $addonBody);
|
||||
}
|
||||
|
||||
private function postInternal(string $api, array $data = [], array $addonBody = [], int $attempt = 0, bool $tokenRefreshed = false): array
|
||||
{
|
||||
if (!$this->api['appid'] || !$this->api['appSecret'] || !$this->api['access_token']) {
|
||||
throw new ValidateException('JST config missing');
|
||||
}
|
||||
$apiName = ltrim($api, '/');
|
||||
if ($apiName === 'open/inventory/query') {
|
||||
$skuIdsRaw = $data['sku_ids'] ?? '';
|
||||
$skuIdsList = [];
|
||||
if (is_array($skuIdsRaw)) {
|
||||
$skuIdsList = array_values(array_filter(array_map('trim', $skuIdsRaw), static fn($v) => $v !== ''));
|
||||
$skuIdsRaw = implode(',', $skuIdsList);
|
||||
} else {
|
||||
$skuIdsRaw = (string)$skuIdsRaw;
|
||||
$skuIdsList = array_values(array_filter(array_map('trim', explode(',', trim($skuIdsRaw, ", \t\n\r\0\x0B"))), static fn($v) => $v !== ''));
|
||||
}
|
||||
Log::info('jst.api.inventory.query.request ' . json_encode([
|
||||
'api' => $apiName,
|
||||
'attempt' => $attempt,
|
||||
'page_index' => $data['page_index'] ?? null,
|
||||
'page_num' => $data['page_num'] ?? null,
|
||||
'page_size' => $data['page_size'] ?? null,
|
||||
'sku_ids_count' => count($skuIdsList),
|
||||
'sku_ids' => $skuIdsList,
|
||||
'sku_ids_raw' => $skuIdsRaw,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
$this->throttle();
|
||||
$time = time();
|
||||
$baseBody = [
|
||||
'access_token' => $this->api['access_token'],
|
||||
@@ -58,24 +88,90 @@ abstract class HaichaoBridge
|
||||
'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);
|
||||
$raw = file_get_contents("{$this->api['api']}$api", false, stream_context_create($options));
|
||||
$rt = json_decode((string)$raw, true);
|
||||
$respPreview = null;
|
||||
try {
|
||||
$respPreview = mb_substr((string)$raw, 0, 2000);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
Log::info('jst.api.response ' . json_encode([
|
||||
'api' => $api,
|
||||
'code' => $rt['code'] ?? null,
|
||||
'msg' => $rt['msg'] ?? null,
|
||||
'preview' => $respPreview,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
if (!is_array($rt) || !array_key_exists('code', $rt)) {
|
||||
Log::error('JST response invalid ' . json_encode([
|
||||
'api' => $api,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
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));
|
||||
$code = (int)$rt['code'];
|
||||
if (!$tokenRefreshed && $this->isTokenError($rt)) {
|
||||
$this->api['access_token'] = $this->getToken(true);
|
||||
return $this->postInternal($api, $data, $addonBody, $attempt + 1, true);
|
||||
}
|
||||
if ($this->isRateLimitError($code) && $attempt < 6) {
|
||||
$sleepMs = $this->getRateLimitSleepMs($code, $attempt);
|
||||
usleep($sleepMs * 1000);
|
||||
return $this->postInternal($api, $data, $addonBody, $attempt + 1, $tokenRefreshed);
|
||||
}
|
||||
Log::error('JST request failed ' . json_encode([
|
||||
'api' => $api,
|
||||
'code' => $code,
|
||||
'msg' => (string)($rt['msg'] ?? ''),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
throw new ValidateException($rt['msg'] ?? 'JST error');
|
||||
}
|
||||
return $rt;
|
||||
}
|
||||
|
||||
private function throttle(): void
|
||||
{
|
||||
static $lastAt = 0.0;
|
||||
static $recent = [];
|
||||
|
||||
$minGap = 0.65;
|
||||
while (true) {
|
||||
$now = microtime(true);
|
||||
$gap = $now - $lastAt;
|
||||
if ($gap < $minGap) {
|
||||
usleep((int)(($minGap - $gap) * 1000000));
|
||||
continue;
|
||||
}
|
||||
|
||||
$recent = array_values(array_filter($recent, static fn($t) => ($now - $t) < 60));
|
||||
if (count($recent) >= 95) {
|
||||
$oldest = $recent[0] ?? $now;
|
||||
$wait = 60 - ($now - $oldest) + 0.2;
|
||||
if ($wait > 0) {
|
||||
usleep((int)($wait * 1000000));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$lastAt = microtime(true);
|
||||
$recent[] = $lastAt;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function isRateLimitError(int $code): bool
|
||||
{
|
||||
return $code === 199 || $code === 200;
|
||||
}
|
||||
|
||||
private function getRateLimitSleepMs(int $code, int $attempt): int
|
||||
{
|
||||
$jitter = random_int(0, 300);
|
||||
if ($code === 200) {
|
||||
return min(60000, (int)(6000 * (1 + $attempt)) + $jitter);
|
||||
}
|
||||
return min(10000, (int)(900 * (1 + $attempt)) + $jitter);
|
||||
}
|
||||
|
||||
private function sign(array $data): string
|
||||
{
|
||||
$computerStr = "";
|
||||
@@ -85,4 +181,167 @@ abstract class HaichaoBridge
|
||||
$computerStr = $this->api['appSecret'] . $computerStr;
|
||||
return md5($computerStr);
|
||||
}
|
||||
|
||||
private function isTokenError(array $rt): bool
|
||||
{
|
||||
$msg = (string)($rt['msg'] ?? '');
|
||||
if (!$msg) return false;
|
||||
$msgLower = strtolower($msg);
|
||||
return strpos($msgLower, 'access_token') !== false || strpos($msgLower, 'token') !== false;
|
||||
}
|
||||
|
||||
private function getToken(bool $forceRefresh = false): string
|
||||
{
|
||||
$tokenInfo = $this->readTokenInfo();
|
||||
if ($tokenInfo === null) {
|
||||
$tokenInfo = $this->autoInitTokenFile();
|
||||
if ($tokenInfo === null) {
|
||||
throw new ValidateException('JST token missing, please check .env JST_ACCESS_TOKEN config');
|
||||
}
|
||||
}
|
||||
if (!$forceRefresh && $tokenInfo) {
|
||||
$accessToken = (string)($tokenInfo['access_token'] ?? '');
|
||||
$expiresIn = (int)($tokenInfo['expires_in'] ?? 0);
|
||||
$tokenTime = (int)($tokenInfo['time'] ?? 0);
|
||||
if ($accessToken && $expiresIn > 0 && $tokenTime > 0) {
|
||||
if (time() < ($tokenTime + $expiresIn - 3600)) {
|
||||
return $accessToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$refreshToken = (string)($tokenInfo['refresh_token'] ?? '');
|
||||
if (!$refreshToken) {
|
||||
$refreshToken = trim((string)env('JST_REFRESH_TOKEN', ''));
|
||||
}
|
||||
if ($refreshToken) {
|
||||
$newTokenInfo = $this->refreshAccessToken($refreshToken);
|
||||
$this->writeTokenInfo($newTokenInfo);
|
||||
return (string)$newTokenInfo['access_token'];
|
||||
}
|
||||
|
||||
$code = (string)env('JST_AUTH_CODE', '');
|
||||
if ($code) {
|
||||
$newTokenInfo = $this->code2accessToken($code);
|
||||
$this->writeTokenInfo($newTokenInfo);
|
||||
return (string)$newTokenInfo['access_token'];
|
||||
}
|
||||
|
||||
throw new ValidateException('JST token missing');
|
||||
}
|
||||
|
||||
private function autoInitTokenFile(): ?array
|
||||
{
|
||||
$accessToken = (string)env('JST_ACCESS_TOKEN', '');
|
||||
$refreshToken = (string)env('JST_REFRESH_TOKEN', '');
|
||||
$expiresIn = (int)env('JST_EXPIRES_IN', 2592000);
|
||||
|
||||
if (empty($accessToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tokenInfo = [
|
||||
'access_token' => $accessToken,
|
||||
'expires_in' => $expiresIn,
|
||||
'refresh_token' => $refreshToken,
|
||||
'time' => time(),
|
||||
];
|
||||
|
||||
$this->writeTokenInfo($tokenInfo);
|
||||
return $tokenInfo;
|
||||
}
|
||||
|
||||
private function refreshAccessToken(string $refreshToken): array
|
||||
{
|
||||
$params = [];
|
||||
$params['app_key'] = $this->api['appid'];
|
||||
$params['timestamp'] = time();
|
||||
$params['grant_type'] = 'refresh_token';
|
||||
$params['charset'] = 'utf-8';
|
||||
$params['refresh_token'] = $refreshToken;
|
||||
$params['scope'] = 'all';
|
||||
$params['sign'] = $this->sign($params);
|
||||
|
||||
$url = "{$this->api['api']}openWeb/auth/refreshToken";
|
||||
$raw = file_get_contents($url, false, stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 60,
|
||||
'method' => 'POST',
|
||||
'header' => 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8',
|
||||
'content' => http_build_query($params),
|
||||
],
|
||||
]));
|
||||
$rt = json_decode((string)$raw, true);
|
||||
if (!is_array($rt) || !array_key_exists('code', $rt)) {
|
||||
throw new ValidateException('JST response invalid');
|
||||
}
|
||||
if ((int)$rt['code'] !== 0) {
|
||||
throw new ValidateException((string)($rt['msg'] ?? 'JST error'));
|
||||
}
|
||||
$data = $rt['data'] ?? [];
|
||||
if (!is_array($data) || empty($data['access_token'])) {
|
||||
throw new ValidateException('JST token invalid');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function code2accessToken(string $code): array
|
||||
{
|
||||
$params = [];
|
||||
$params['app_key'] = $this->api['appid'];
|
||||
$params['timestamp'] = time();
|
||||
$params['grant_type'] = 'authorization_code';
|
||||
$params['charset'] = 'utf-8';
|
||||
$params['code'] = $code;
|
||||
$params['sign'] = $this->sign($params);
|
||||
|
||||
$url = "{$this->api['api']}openWeb/auth/accessToken";
|
||||
$raw = file_get_contents($url, false, stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 60,
|
||||
'method' => 'POST',
|
||||
'header' => 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8',
|
||||
'content' => http_build_query($params),
|
||||
],
|
||||
]));
|
||||
$rt = json_decode((string)$raw, true);
|
||||
if (!is_array($rt) || !array_key_exists('code', $rt)) {
|
||||
throw new ValidateException('JST response invalid');
|
||||
}
|
||||
if ((int)$rt['code'] !== 0) {
|
||||
throw new ValidateException((string)($rt['msg'] ?? 'JST error'));
|
||||
}
|
||||
$data = $rt['data'] ?? [];
|
||||
if (!is_array($data) || empty($data['access_token'])) {
|
||||
throw new ValidateException('JST token invalid');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function readTokenInfo(): ?array
|
||||
{
|
||||
$file = root_path() . $this->tokenFile;
|
||||
if (!is_file($file)) {
|
||||
Log::info('JST token file not found, will try to auto init from .env');
|
||||
return null;
|
||||
}
|
||||
$raw = file_get_contents($file);
|
||||
if (!$raw) return null;
|
||||
$arr = json_decode((string)$raw, true);
|
||||
if (!is_array($arr) || empty($arr['access_token'])) {
|
||||
Log::warning('JST token file is invalid, will try to auto init from .env');
|
||||
return null;
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
private function writeTokenInfo(array $tokenInfo): void
|
||||
{
|
||||
$dir = root_path() . 'runtime/jstdata';
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
$tokenInfo['time'] = time();
|
||||
file_put_contents(root_path() . $this->tokenFile, json_encode($tokenInfo, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,12 @@ class HaichaoJstServices extends HaichaoBridge
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -652,6 +658,26 @@ class HaichaoJstServices extends HaichaoBridge
|
||||
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;
|
||||
@@ -704,6 +730,94 @@ class HaichaoJstServices extends HaichaoBridge
|
||||
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', [
|
||||
@@ -758,28 +872,42 @@ class HaichaoJstServices extends HaichaoBridge
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getInneropenStockAndDetailList($styleCodes): array
|
||||
public function getInneropenStockAndDetailList(array|string $styleCodes, ?array &$stats = null): 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)) // 实际可用库存
|
||||
];
|
||||
$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);
|
||||
}
|
||||
$rt = array_merge($formatResult, $rt);
|
||||
usleep(550);
|
||||
} while (count($result) == 100);
|
||||
$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;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,31 +13,82 @@ class JstProductEvent implements JstEvent
|
||||
{
|
||||
try {
|
||||
$param = $request->param();
|
||||
$body = json_decode((string)($param['biz'] ?? '{}'), true);
|
||||
$actionCode = (string)($param['action_code'] ?? '');
|
||||
$parseFrom = 'param.biz';
|
||||
$bizRaw = (string)($param['biz'] ?? '');
|
||||
$body = json_decode($bizRaw !== '' ? $bizRaw : '{}', true);
|
||||
if (!is_array($body)) {
|
||||
Log::warning('jst.cb.product_stock.parse_fail ' . json_encode([
|
||||
'action_code' => $actionCode,
|
||||
'from' => $parseFrom,
|
||||
'biz_raw_len' => strlen($bizRaw),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
$body = [];
|
||||
}
|
||||
$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);
|
||||
$actionCode = (string)($output['action_code'] ?? $actionCode);
|
||||
$parseFrom = 'content.parse_str.biz';
|
||||
$bizRaw = (string)($output['biz'] ?? '');
|
||||
$body = json_decode($bizRaw !== '' ? $bizRaw : '{}', true);
|
||||
if (!is_array($body)) {
|
||||
Log::warning('jst.cb.product_stock.parse_fail ' . json_encode([
|
||||
'action_code' => $actionCode,
|
||||
'from' => $parseFrom,
|
||||
'biz_raw_len' => strlen($bizRaw),
|
||||
'content_len' => strlen((string)$cont),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
$body = [];
|
||||
}
|
||||
$skuId = (string)($body['skuId'] ?? '');
|
||||
$stock = (int)($body['qty'] ?? 0);
|
||||
}
|
||||
if (!$skuId) return [];
|
||||
if (!$skuId) {
|
||||
Log::warning('jst.cb.product_stock.miss ' . json_encode([
|
||||
'action_code' => $actionCode,
|
||||
'from' => $parseFrom,
|
||||
'sku_id' => $skuId,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
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, [
|
||||
if (!$model) {
|
||||
Log::warning('jst.cb.product_stock.not_found ' . json_encode([
|
||||
'action_code' => $actionCode,
|
||||
'from' => $parseFrom,
|
||||
'sku_id' => $skuId,
|
||||
'stock' => $stock,
|
||||
'sum_stock' => $stock
|
||||
]);
|
||||
}
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
return [];
|
||||
}
|
||||
$pdt = $model->toArray();
|
||||
$id = (int)($pdt['id'] ?? 0);
|
||||
if (!$id) {
|
||||
Log::warning('jst.cb.product_stock.not_found ' . json_encode([
|
||||
'action_code' => $actionCode,
|
||||
'from' => $parseFrom,
|
||||
'sku_id' => $skuId,
|
||||
'stock' => $stock,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
return [];
|
||||
}
|
||||
$spas->update($id, [
|
||||
'stock' => $stock,
|
||||
'sum_stock' => $stock
|
||||
]);
|
||||
Log::info('jst.cb.product_stock.updated ' . json_encode([
|
||||
'action_code' => $actionCode,
|
||||
'from' => $parseFrom,
|
||||
'attr_value_id' => $id,
|
||||
'sku_id' => $skuId,
|
||||
'stock' => $stock,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
} catch (Throwable $e) {
|
||||
Log::error('jst.cb.product_stock.error ' . json_encode([
|
||||
'msg' => $e->getMessage(),
|
||||
|
||||
@@ -157,6 +157,7 @@ class JstProductSync extends HaichaoJstServices
|
||||
} else {
|
||||
$productServices->update($pidNow, $productInfo);
|
||||
}
|
||||
$productServices->ensureSpu((int)$pidNow);
|
||||
ProductStockTips::dispatch([$pidNow, 0]);
|
||||
$attr = [[
|
||||
'value' => '规格',
|
||||
@@ -324,6 +325,7 @@ class JstProductSync extends HaichaoJstServices
|
||||
} else {
|
||||
$productServices->update($pid, $productInfo);
|
||||
}
|
||||
$productServices->ensureSpu((int)$pid);
|
||||
ProductStockTips::dispatch([$pid, 0]);
|
||||
$attr = [[
|
||||
'value' => '规格',
|
||||
|
||||
@@ -16,45 +16,76 @@ class JstStorkSync extends HaichaoJstServices
|
||||
{
|
||||
public function syncStorkFromInneropenErp(): void
|
||||
{
|
||||
$startAt = microtime(true);
|
||||
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
|
||||
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
|
||||
Log::info('jst.stork.sync.start');
|
||||
$inventorySkuIds = [];
|
||||
$skuIds = '';
|
||||
$index = 1;
|
||||
$count = 0;
|
||||
$codes = [];
|
||||
$spuIds = [];
|
||||
$codeIdMap = [];
|
||||
$codeProductIdMap = [];
|
||||
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;
|
||||
$code = (string)($item['code'] ?? '');
|
||||
if ($code !== '') {
|
||||
$codes[] = $code;
|
||||
$id = (int)($item['id'] ?? 0);
|
||||
if ($id > 0) {
|
||||
$codeIdMap[$code] = $id;
|
||||
}
|
||||
$productId = (int)($item['product_id'] ?? 0);
|
||||
if ($productId > 0) {
|
||||
$spuIds[$productId] = 1;
|
||||
$codeProductIdMap[$code] = $productId;
|
||||
}
|
||||
}
|
||||
}
|
||||
$productStock = [];
|
||||
foreach ($inventorySkuIds as $inventorySkuId) {
|
||||
foreach ($this->getInneropenStockAndDetailList($inventorySkuId['ids']) as $product) {
|
||||
$productStock[] = [
|
||||
'code' => $product['item_code'],
|
||||
'stock' => $product['stock']
|
||||
];
|
||||
$codes = array_values(array_unique($codes));
|
||||
Log::info('jst.stork.sync.request_stats ' . json_encode([
|
||||
'spu_count' => count($spuIds),
|
||||
'sku_count' => count($codes),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$pullStats = [];
|
||||
$productStockMap = [];
|
||||
foreach ($this->getInneropenStockAndDetailList($codes, $pullStats) as $product) {
|
||||
$productStockMap[(string)$product['item_code']] = (int)$product['stock'];
|
||||
}
|
||||
$respSpuIds = [];
|
||||
foreach (array_keys($productStockMap) as $code) {
|
||||
if (isset($codeProductIdMap[$code])) {
|
||||
$respSpuIds[$codeProductIdMap[$code]] = 1;
|
||||
}
|
||||
}
|
||||
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.response_stats ' . json_encode([
|
||||
'spu_count' => count($respSpuIds),
|
||||
'sku_count' => count($productStockMap),
|
||||
'api_call_count' => (int)($pullStats['api_call_count'] ?? 0),
|
||||
'api_resp_row_count' => (int)($pullStats['api_resp_row_count'] ?? 0),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$updatedSkuCount = 0;
|
||||
$notFoundSkuCount = 0;
|
||||
foreach ($productStockMap as $code => $stock) {
|
||||
$id = (int)($codeIdMap[$code] ?? 0);
|
||||
if ($id < 1) {
|
||||
$notFoundSkuCount++;
|
||||
continue;
|
||||
}
|
||||
$storeProductAttrValueServices->update($id, [
|
||||
'stock' => $stock,
|
||||
'sum_stock' => $stock
|
||||
]);
|
||||
$updatedSkuCount++;
|
||||
}
|
||||
Log::info('jst.stork.sync.end');
|
||||
Log::info('jst.stork.sync.finish ' . json_encode([
|
||||
'requested_spu_count' => count($spuIds),
|
||||
'requested_sku_count' => count($codes),
|
||||
'response_spu_count' => count($respSpuIds),
|
||||
'response_sku_count' => count($productStockMap),
|
||||
'update_sku_count' => $updatedSkuCount,
|
||||
'not_found_sku_count' => $notFoundSkuCount,
|
||||
'duration_ms' => (int)round((microtime(true) - $startAt) * 1000),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
public function syncStockFromErp(array $ids): bool
|
||||
|
||||
@@ -42,8 +42,9 @@ class AllowOriginMiddleware implements MiddlewareInterface
|
||||
$header = Config::get('cookie.header');
|
||||
$origin = $request->header('origin');
|
||||
|
||||
if ($origin && ('' == $this->cookieDomain || strpos($origin, $this->cookieDomain)))
|
||||
if ($origin && ('' === $this->cookieDomain || str_contains($origin, $this->cookieDomain))) {
|
||||
$header['Access-Control-Allow-Origin'] = $origin;
|
||||
}
|
||||
if ($request->method(true) == 'OPTIONS') {
|
||||
$response = Response::create('ok')->code(200)->header($header);
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ 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;
|
||||
|
||||
@@ -42,7 +43,7 @@ class ProductSyncErp extends BaseJobs
|
||||
}
|
||||
foreach ($attrs as $item) {
|
||||
if ($item['pic'] && strstr($item['pic'], 'http') === false) {
|
||||
$siteUrl = sys_config('site_url');
|
||||
$siteUrl = app()->make(\crmeb\services\SystemConfigService::class)->get('site_url');
|
||||
$item['pic'] = $siteUrl . $item['pic'];
|
||||
}
|
||||
|
||||
@@ -119,10 +120,18 @@ class ProductSyncErp extends BaseJobs
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function productFromErp($spuArr, int $supplierId = 0)
|
||||
public function productFromErp($spuArr, int $supplierId = 0, string $traceId = '')
|
||||
{
|
||||
$stats = [
|
||||
'trace_id' => $traceId,
|
||||
'fetched' => 0,
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'failed' => 0,
|
||||
];
|
||||
try {
|
||||
Log::info('admin.manual.jst.product.sync.request', ['spu' => $spuArr]);
|
||||
Log::info('admin.manual.jst.product.sync.request', ['trace_id' => $traceId, 'spu' => $spuArr, 'supplier_id' => $supplierId]);
|
||||
$result = app()->make(HaichaoJstServices::class)->getProductListKuan($spuArr);
|
||||
$resultPreview = null;
|
||||
try {
|
||||
@@ -130,6 +139,7 @@ class ProductSyncErp extends BaseJobs
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
Log::info('admin.manual.jst.product.sync.api_result', [
|
||||
'trace_id' => $traceId,
|
||||
'spu' => $spuArr,
|
||||
'page_index' => $result['page_index'] ?? null,
|
||||
'page_count' => $result['page_count'] ?? null,
|
||||
@@ -137,178 +147,29 @@ class ProductSyncErp extends BaseJobs
|
||||
'result_preview' => $resultPreview,
|
||||
]);
|
||||
$productList = $result['datas'] ?? [];
|
||||
$productInfo = [];
|
||||
$stats['fetched'] = is_array($productList) ? count($productList) : 0;
|
||||
$stats = $this->mergeStats($stats, $this->processProductList($productList, $supplierId, $traceId, [
|
||||
'mode' => 'spu',
|
||||
'spu' => $spuArr,
|
||||
]));
|
||||
|
||||
/** @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' => $pic,
|
||||
'slider_image' => json_encode([$pic]),
|
||||
'store_name' => $name,
|
||||
'store_info' => $name,
|
||||
'cate_id' => 0,
|
||||
'price' => $salePrice,
|
||||
'ot_price' => $marketPrice,
|
||||
'delivery_type' => '1,2,3',
|
||||
'freight' => 1,
|
||||
'is_show' => 0,
|
||||
'add_time' => time(),
|
||||
'cost' => $costPrice,
|
||||
'ficti' => 0,
|
||||
'spec_type' => 1,
|
||||
'code' => $item['i_id'],
|
||||
'type' => $relationSupplierId ? 2 : 0,
|
||||
'relation_id' => $relationSupplierId,
|
||||
];
|
||||
$detail = $details = $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' => $specValue,
|
||||
'select' => false
|
||||
];
|
||||
$skuCost = floatval($items['cost_price'] ?? $costPrice);
|
||||
$skuSettle = $relationSupplierId ? $skuCost : 0.0;
|
||||
$value[] = [
|
||||
'bar_code' => '',
|
||||
'brokerage' => 0,
|
||||
'brokerage_two' => 0,
|
||||
'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'] ?? $salePrice),
|
||||
'select' => true,
|
||||
'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,
|
||||
'stock' => 0,
|
||||
];
|
||||
}
|
||||
$pid = $productServices->value(['code' => $item['i_id']], 'id');
|
||||
$existed = !!$pid;
|
||||
if (!$pid) {
|
||||
$pid = $productServices->ErpProductSave($productInfo);
|
||||
}
|
||||
$attr = [[
|
||||
'value' => '规格',
|
||||
'detail' => $detail,
|
||||
'details' => $details,
|
||||
]];
|
||||
|
||||
$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)]);
|
||||
Log::info('admin.manual.jst.product.sync.finish', [
|
||||
'trace_id' => $traceId,
|
||||
'spu' => $spuArr,
|
||||
'count' => is_array($productList) ? count($productList) : 0,
|
||||
'created' => $stats['created'],
|
||||
'updated' => $stats['updated'],
|
||||
'skipped' => $stats['skipped'],
|
||||
'failed' => $stats['failed'],
|
||||
]);
|
||||
|
||||
return $stats;
|
||||
} catch (\Exception $e) {
|
||||
|
||||
response_log_write([
|
||||
@@ -316,11 +177,433 @@ class ProductSyncErp extends BaseJobs
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
Log::error('admin.manual.jst.product.sync.error', ['spu' => $spuArr, 'message' => $e->getMessage()]);
|
||||
Log::error('admin.manual.jst.product.sync.error', ['trace_id' => $traceId, 'spu' => $spuArr, 'message' => $e->getMessage()]);
|
||||
$stats['failed']++;
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeStats(array $base, array $delta): array
|
||||
{
|
||||
foreach (['fetched', 'created', 'updated', 'skipped', 'failed'] as $key) {
|
||||
$base[$key] = (int)($base[$key] ?? 0) + (int)($delta[$key] ?? 0);
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
return true;
|
||||
private function processProductList($productList, int $supplierId, string $traceId, array $context = []): array
|
||||
{
|
||||
$stats = [
|
||||
'fetched' => 0,
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'failed' => 0,
|
||||
];
|
||||
if (!is_array($productList)) {
|
||||
return $stats;
|
||||
}
|
||||
$stats['fetched'] = count($productList);
|
||||
$seenCodes = [];
|
||||
foreach ($productList as $item) {
|
||||
if (!is_array($item)) {
|
||||
$stats['skipped']++;
|
||||
continue;
|
||||
}
|
||||
$code = (string)($item['i_id'] ?? '');
|
||||
if ($code === '') {
|
||||
$stats['skipped']++;
|
||||
continue;
|
||||
}
|
||||
if (isset($seenCodes[$code])) {
|
||||
$stats['skipped']++;
|
||||
continue;
|
||||
}
|
||||
$seenCodes[$code] = 1;
|
||||
try {
|
||||
$save = $this->saveProductFromErpData($item, $supplierId);
|
||||
if (!empty($save['created'])) {
|
||||
$stats['created']++;
|
||||
} else {
|
||||
$stats['updated']++;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$stats['failed']++;
|
||||
Log::error('admin.manual.jst.product.sync.item.error', [
|
||||
'trace_id' => $traceId,
|
||||
'i_id' => $code,
|
||||
'message' => $e->getMessage(),
|
||||
'context' => $context,
|
||||
]);
|
||||
}
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按时间范围同步商品
|
||||
* @param string $startTime
|
||||
* @param string $endTime
|
||||
* @param int $supplierId
|
||||
* @return bool
|
||||
*/
|
||||
public function syncProductByTime(string $startTime, string $endTime, int $supplierId = 0, string $traceId = '', string $mode = 'dispatch')
|
||||
{
|
||||
$startTs = strtotime($startTime);
|
||||
$endTs = strtotime($endTime);
|
||||
if ($startTs && $endTs) {
|
||||
$maxSeconds = 7 * 86400;
|
||||
if (($endTs - $startTs) > $maxSeconds) {
|
||||
Log::warning('admin.manual.jst.product.sync_by_time.range_too_large', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'range_seconds' => $endTs - $startTs,
|
||||
'max_seconds' => $maxSeconds,
|
||||
'supplier_id' => $supplierId,
|
||||
]);
|
||||
return [
|
||||
'ok' => false,
|
||||
'error' => '时间范围不能超过7天',
|
||||
'trace_id' => $traceId,
|
||||
];
|
||||
}
|
||||
}
|
||||
try {
|
||||
$stats = [
|
||||
'ok' => true,
|
||||
'mode' => $mode,
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'page_count' => 1,
|
||||
'pages_processed' => 0,
|
||||
'fetched' => 0,
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'failed' => 0,
|
||||
];
|
||||
Log::info('admin.manual.jst.product.sync_by_time.start', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'supplier_id' => $supplierId
|
||||
]);
|
||||
$jstService = app()->make(HaichaoJstServices::class);
|
||||
$result = $jstService->getProductListByTime($startTime, $endTime, 1, 50);
|
||||
$pageCount = $result['page_count'] ?? 1;
|
||||
$stats['page_count'] = (int)$pageCount;
|
||||
Log::info('admin.manual.jst.product.sync_by_time.api_result', [
|
||||
'trace_id' => $traceId,
|
||||
'page' => 1,
|
||||
'page_count' => $pageCount,
|
||||
'datas_count' => isset($result['datas']) && is_array($result['datas']) ? count($result['datas']) : 0,
|
||||
]);
|
||||
|
||||
$pageStats = $this->processProductList($result['datas'] ?? [], $supplierId, $traceId, [
|
||||
'mode' => 'time',
|
||||
'page' => 1,
|
||||
]);
|
||||
$stats = $this->mergeStats($stats, $pageStats);
|
||||
$stats['pages_processed']++;
|
||||
|
||||
if ($pageCount > 1) {
|
||||
if ($mode === 'sync') {
|
||||
for ($i = 2; $i <= $pageCount; $i++) {
|
||||
$pageResult = $jstService->getProductListByTime($startTime, $endTime, $i, 50);
|
||||
$productList = $pageResult['datas'] ?? [];
|
||||
$resultPreview = null;
|
||||
try {
|
||||
$resultPreview = mb_substr(json_encode($pageResult, JSON_UNESCAPED_UNICODE), 0, 2000);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
Log::info('admin.manual.jst.product.sync_page.api_result', [
|
||||
'trace_id' => $traceId,
|
||||
'page' => $i,
|
||||
'page_count' => $pageResult['page_count'] ?? null,
|
||||
'datas_count' => is_array($productList) ? count($productList) : 0,
|
||||
'result_preview' => $resultPreview,
|
||||
]);
|
||||
$stats = $this->mergeStats($stats, $this->processProductList($productList, $supplierId, $traceId, [
|
||||
'mode' => 'time',
|
||||
'page' => $i,
|
||||
]));
|
||||
$stats['pages_processed']++;
|
||||
}
|
||||
} else {
|
||||
for ($i = 2; $i <= $pageCount; $i++) {
|
||||
self::dispatchDo('syncProductPage', [$startTime, $endTime, $i, $supplierId, $traceId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var StoreProductServices $productServices */
|
||||
$productServices = app()->make(StoreProductServices::class);
|
||||
/** @var StoreProductAttrServices $productAttrServices */
|
||||
$productAttrServices = app()->make(StoreProductAttrServices::class);
|
||||
$productServices->cacheTag()->clear();
|
||||
$productAttrServices->cacheTag()->clear();
|
||||
Log::info('admin.manual.jst.product.sync_by_time.finish', [
|
||||
'trace_id' => $traceId,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'supplier_id' => $supplierId,
|
||||
'page_count' => $stats['page_count'],
|
||||
'pages_processed' => $stats['pages_processed'],
|
||||
'fetched' => $stats['fetched'],
|
||||
'created' => $stats['created'],
|
||||
'updated' => $stats['updated'],
|
||||
'skipped' => $stats['skipped'],
|
||||
'failed' => $stats['failed'],
|
||||
]);
|
||||
return $stats;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('admin.manual.jst.product.sync_by_time.error', [
|
||||
'trace_id' => $traceId,
|
||||
'message' => $e->getMessage(),
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime
|
||||
]);
|
||||
return [
|
||||
'ok' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'trace_id' => $traceId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步特定页码的商品
|
||||
* @param string $startTime
|
||||
* @param string $endTime
|
||||
* @param int $pageIndex
|
||||
* @param int $supplierId
|
||||
* @return bool
|
||||
*/
|
||||
public function syncProductPage(string $startTime, string $endTime, int $pageIndex, int $supplierId = 0, string $traceId = '')
|
||||
{
|
||||
try {
|
||||
Log::info('admin.manual.jst.product.sync_page.start', [
|
||||
'trace_id' => $traceId,
|
||||
'page' => $pageIndex,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime
|
||||
]);
|
||||
$jstService = app()->make(HaichaoJstServices::class);
|
||||
$result = $jstService->getProductListByTime($startTime, $endTime, $pageIndex, 50);
|
||||
$productList = $result['datas'] ?? [];
|
||||
$resultPreview = null;
|
||||
try {
|
||||
$resultPreview = mb_substr(json_encode($result, JSON_UNESCAPED_UNICODE), 0, 2000);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
Log::info('admin.manual.jst.product.sync_page.api_result', [
|
||||
'trace_id' => $traceId,
|
||||
'page' => $pageIndex,
|
||||
'page_count' => $result['page_count'] ?? null,
|
||||
'datas_count' => is_array($productList) ? count($productList) : 0,
|
||||
'result_preview' => $resultPreview,
|
||||
]);
|
||||
|
||||
$stats = $this->processProductList($productList, $supplierId, $traceId, [
|
||||
'mode' => 'time',
|
||||
'page' => $pageIndex,
|
||||
]);
|
||||
|
||||
/** @var StoreProductServices $productServices */
|
||||
$productServices = app()->make(StoreProductServices::class);
|
||||
/** @var StoreProductAttrServices $productAttrServices */
|
||||
$productAttrServices = app()->make(StoreProductAttrServices::class);
|
||||
$productServices->cacheTag()->clear();
|
||||
$productAttrServices->cacheTag()->clear();
|
||||
|
||||
Log::info('admin.manual.jst.product.sync_page.finish', [
|
||||
'trace_id' => $traceId,
|
||||
'page' => $pageIndex,
|
||||
'count' => is_array($productList) ? count($productList) : 0,
|
||||
'created' => $stats['created'] ?? 0,
|
||||
'updated' => $stats['updated'] ?? 0,
|
||||
'skipped' => $stats['skipped'] ?? 0,
|
||||
'failed' => $stats['failed'] ?? 0,
|
||||
]);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('admin.manual.jst.product.sync_page.error', [
|
||||
'trace_id' => $traceId,
|
||||
'page' => $pageIndex,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存从ERP获取的商品数据
|
||||
* @param array $item
|
||||
* @param int $supplierId
|
||||
*/
|
||||
private function saveProductFromErpData(array $item, int $supplierId = 0): array
|
||||
{
|
||||
/** @var StoreProductServices $productServices */
|
||||
$productServices = app()->make(StoreProductServices::class);
|
||||
/** @var StoreProductAttrServices $productAttrServices */
|
||||
$productAttrServices = app()->make(StoreProductAttrServices::class);
|
||||
|
||||
$relationSupplierId = $supplierId > 0 ? $supplierId : 0;
|
||||
Log::info('admin.manual.jst.product.sync.item.process', [
|
||||
'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' => $pic,
|
||||
'slider_image' => json_encode([$pic]),
|
||||
'store_name' => $name,
|
||||
'store_info' => $name,
|
||||
'cate_id' => 0,
|
||||
'price' => $salePrice,
|
||||
'ot_price' => $marketPrice,
|
||||
'delivery_type' => '1,2,3',
|
||||
'freight' => 1,
|
||||
'is_show' => 0,
|
||||
'add_time' => time(),
|
||||
'cost' => $costPrice,
|
||||
'ficti' => 0,
|
||||
'spec_type' => 1,
|
||||
'code' => $item['i_id'],
|
||||
'type' => $relationSupplierId ? 2 : 0,
|
||||
'relation_id' => $relationSupplierId,
|
||||
];
|
||||
$detail = $details = $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' => $specValue,
|
||||
'select' => false
|
||||
];
|
||||
$skuCost = floatval($items['cost_price'] ?? $costPrice);
|
||||
$skuSettle = $relationSupplierId ? $skuCost : 0.0;
|
||||
$value[] = [
|
||||
'bar_code' => '',
|
||||
'brokerage' => 0,
|
||||
'brokerage_two' => 0,
|
||||
'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'] ?? $salePrice),
|
||||
'select' => true,
|
||||
'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,
|
||||
'stock' => 0,
|
||||
];
|
||||
}
|
||||
$pid = $productServices->value(['code' => $item['i_id']], 'id');
|
||||
$existed = !!$pid;
|
||||
if (!$pid) {
|
||||
$pid = $productServices->ErpProductSave($productInfo);
|
||||
}
|
||||
$productServices->ensureSpu((int)$pid);
|
||||
$attr = [[
|
||||
'value' => '规格',
|
||||
'detail' => $detail,
|
||||
'details' => $details,
|
||||
]];
|
||||
|
||||
$skuList = $productAttrServices->validateProductAttr($attr, $value, $pid, 0, 0, 0);
|
||||
$productAttrServices->saveProductAttr($skuList, $pid);
|
||||
Log::info('admin.manual.jst.product.sync.save', [
|
||||
'product_id' => $pid,
|
||||
'existed' => $existed,
|
||||
'sku_saved_count' => isset($skuList['value']) && is_array($skuList['value']) ? count($skuList['value']) : 0
|
||||
]);
|
||||
return ['created' => !$existed, 'product_id' => $pid, 'existed' => $existed];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,7 +695,7 @@ class ProductSyncErp extends BaseJobs
|
||||
}
|
||||
|
||||
// 更新平台商品库存
|
||||
$defaultShopId = (int)sys_config('jst_default_shopid');
|
||||
$defaultShopId = (int)app()->make(\crmeb\services\SystemConfigService::class)->get('jst_default_shopid');
|
||||
if ($defaultShopId == $erpShopId) {
|
||||
$this->updateStoreProductValueStock($data);
|
||||
}
|
||||
|
||||
@@ -80,6 +80,12 @@ class OrderServices
|
||||
|
||||
$biz = array_merge($biz, $this->getAddress($order['user_address']));
|
||||
|
||||
Log::info('jst.orders_upload.request ' . json_encode([
|
||||
'api' => 'open/jushuitan/orders/upload',
|
||||
'oid' => $oid,
|
||||
'biz' => $biz,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$num = 3;
|
||||
for ($i = $num; $i >= 0; $i--) {
|
||||
if ($i <= 0) {
|
||||
@@ -544,7 +550,7 @@ class OrderServices
|
||||
public function getShopId(int $erpShopId): int
|
||||
{
|
||||
if ($erpShopId < 1) {
|
||||
$erpShopId = sys_config('jst_default_shopid');
|
||||
$erpShopId = (int)app('sysConfig')->get('jst_default_shopid');
|
||||
}
|
||||
|
||||
return (int)$erpShopId;
|
||||
|
||||
@@ -727,6 +727,19 @@ class StoreOrderRefundServices extends BaseServices
|
||||
OrderStatusJob::dispatch([$order['id'], 'refund_price', ['change_message' => '退款给用户:' . bcadd($online_refund_amount, $yue_refund_amount, 2) . '元; ' . $admin, 'change_manager_id' => request()->adminId(), 'change_manager_type' => 'admin']]);
|
||||
return $order;
|
||||
});
|
||||
if ($order['private_bind_id'] && (int)$order['private_phone_status'] === 1 && $order['user_phone']) {
|
||||
try {
|
||||
$privacyPhoneServices = app()->make(\app\services\other\PrivacyPhoneServices::class);
|
||||
if ($privacyPhoneServices->unbindForOrder((string)$order['user_phone'], (string)$order['private_bind_id'])) {
|
||||
$this->storeOrderServices->update($order['id'], ['private_phone_status' => 0]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
\think\facade\Log::error('退款解绑隐私小号失败 order_id={order_id} error={error}', [
|
||||
'order_id' => $order['id'],
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
//订单同意退款事件
|
||||
event('order.refund', [$refundData, $order, 'order_refund', (int)$id]);
|
||||
return true;
|
||||
|
||||
@@ -2027,6 +2027,19 @@ HTML;
|
||||
throw new ValidateException('订单号' . $order['order_id'] . ',取消订单失败');
|
||||
}
|
||||
});
|
||||
if ($order['private_bind_id'] && (int)$order['private_phone_status'] === 1 && $order['user_phone']) {
|
||||
try {
|
||||
$privacyPhoneServices = app()->make(\app\services\other\PrivacyPhoneServices::class);
|
||||
if ($privacyPhoneServices->unbindForOrder((string)$order['user_phone'], (string)$order['private_bind_id'])) {
|
||||
$this->dao->update($order['id'], ['private_phone_status' => 0]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
\think\facade\Log::error('取消订单解绑隐私小号失败 order_id={order_id} error={error}', [
|
||||
'order_id' => $order['id'],
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
//订单取消事件
|
||||
event('order.cancel', [$order]);
|
||||
return true;
|
||||
@@ -2139,7 +2152,12 @@ HTML;
|
||||
public function getExportList(array $where, array $with = [], int $limit = 0)
|
||||
{
|
||||
if ($limit) {
|
||||
[$page] = $this->getPageValue();
|
||||
$page = 1;
|
||||
if (!empty($where['id'])) {
|
||||
$page = 1;
|
||||
} else {
|
||||
[$page] = $this->getPageValue();
|
||||
}
|
||||
} else {
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
}
|
||||
@@ -2151,10 +2169,17 @@ HTML;
|
||||
foreach ($list as &$item) {
|
||||
/** @var StoreOrderCartInfoServices $orderCart */
|
||||
$orderCart = app()->make(StoreOrderCartInfoServices::class);
|
||||
$_info = $orderCart->getCartColunm(['oid' => $item['id']], 'cart_info', 'unique');
|
||||
$_info = $orderCart->getCartColunm(['oid' => $item['id']], 'cart_id,type,relation_id,is_gift,cart_num,refund_num,cart_info', 'unique');
|
||||
foreach ($_info as $k => $v) {
|
||||
$cart_info = is_string($v) ? json_decode($v, true) : $v;
|
||||
$cart_info = is_string($v['cart_info']) ? json_decode($v['cart_info'], true) : $v['cart_info'];
|
||||
if (!isset($cart_info['productInfo'])) $cart_info['productInfo'] = [];
|
||||
$cart_info['cart_num'] = $v['cart_num'] ?? ($cart_info['cart_num'] ?? 0);
|
||||
$cart_info['refund_num'] = $v['refund_num'] ?? 0;
|
||||
$cart_info['cart_id'] = $v['cart_id'] ?? ($cart_info['id'] ?? 0);
|
||||
$cart_info['_relation_type'] = $v['type'] ?? 0;
|
||||
$cart_info['_relation_id'] = $v['relation_id'] ?? 0;
|
||||
$cart_info['is_gift'] = $v['is_gift'] ?? ($cart_info['is_gift'] ?? 0);
|
||||
$cart_info['unique'] = $k;
|
||||
$_info[$k] = $cart_info;
|
||||
unset($cart_info);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,35 @@ use think\facade\Log;
|
||||
*/
|
||||
class PrivacyPhoneServices extends BaseServices
|
||||
{
|
||||
/**
|
||||
* 标准化AX小号:从后往前截取11位手机号
|
||||
* @param string $number
|
||||
* @return string
|
||||
*/
|
||||
private function normalizeAxNumber(string $number): string
|
||||
{
|
||||
if ($number === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return strlen($number) > 11 ? substr($number, -11) : $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志上下文转字符串(ThinkPHP日志上下文仅支持占位符替换)
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
private function toLogString($value): string
|
||||
{
|
||||
if (is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
return $json === false ? var_export($value, true) : $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* API地址
|
||||
*/
|
||||
@@ -122,10 +151,10 @@ class PrivacyPhoneServices extends BaseServices
|
||||
'decoded' => json_decode($response, true) ?? []
|
||||
];
|
||||
|
||||
Log::info('隐私小号API请求', [
|
||||
Log::info('隐私小号API请求 url={url} params={params} result={result}', [
|
||||
'url' => $url,
|
||||
'params' => $params,
|
||||
'result' => $result
|
||||
'params' => $this->toLogString($params),
|
||||
'result' => $this->toLogString($result),
|
||||
]);
|
||||
|
||||
return $result;
|
||||
@@ -267,23 +296,33 @@ class PrivacyPhoneServices extends BaseServices
|
||||
return $result;
|
||||
}
|
||||
|
||||
$bindResult = $this->axBind($number, $realPhone);
|
||||
$bindNumber = $this->normalizeAxNumber($number);
|
||||
if (strlen($bindNumber) !== 11) {
|
||||
Log::error('隐私小号格式异常 raw_number={raw_number} bind_number={bind_number}', [
|
||||
'raw_number' => $number,
|
||||
'bind_number' => $bindNumber,
|
||||
]);
|
||||
return $result;
|
||||
}
|
||||
|
||||
$bindResult = $this->axBind($bindNumber, $realPhone);
|
||||
|
||||
if (!empty($bindResult['success']) && $bindResult['code'] == 200) {
|
||||
$result = [
|
||||
'user_phone' => $number,
|
||||
'user_phone' => $bindNumber,
|
||||
'bind_id' => $realPhone,
|
||||
'status' => 1
|
||||
];
|
||||
Log::info('隐私小号绑定成功', [
|
||||
'privacy_phone' => $number,
|
||||
'real_phone' => $realPhone
|
||||
Log::info('隐私小号绑定成功 privacy_phone={privacy_phone} real_phone={real_phone}', [
|
||||
'privacy_phone' => $bindNumber,
|
||||
'real_phone' => $realPhone,
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('隐私小号绑定异常', [
|
||||
Log::error('隐私小号绑定异常 real_phone={real_phone} ax_number={ax_number} error={error}', [
|
||||
'real_phone' => $realPhone,
|
||||
'error' => $e->getMessage()
|
||||
'ax_number' => $bindNumber ?? ($number ?? ''),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -306,21 +345,30 @@ class PrivacyPhoneServices extends BaseServices
|
||||
return false;
|
||||
}
|
||||
|
||||
$unbindPhone = $this->normalizeAxNumber($privacyPhone);
|
||||
if (strlen($unbindPhone) !== 11) {
|
||||
Log::error('隐私小号格式异常 privacy_phone={privacy_phone} unbind_phone={unbind_phone}', [
|
||||
'privacy_phone' => $privacyPhone,
|
||||
'unbind_phone' => $unbindPhone,
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->unAxBind($privacyPhone, $realPhone);
|
||||
$result = $this->unAxBind($unbindPhone, $realPhone);
|
||||
|
||||
if (!empty($result['success']) && $result['code'] == 200) {
|
||||
Log::info('隐私小号解绑成功', [
|
||||
'privacy_phone' => $privacyPhone,
|
||||
'real_phone' => $realPhone
|
||||
Log::info('隐私小号解绑成功 privacy_phone={privacy_phone} real_phone={real_phone}', [
|
||||
'privacy_phone' => $unbindPhone,
|
||||
'real_phone' => $realPhone,
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('隐私小号解绑异常', [
|
||||
'privacy_phone' => $privacyPhone,
|
||||
Log::error('隐私小号解绑异常 privacy_phone={privacy_phone} real_phone={real_phone} error={error}', [
|
||||
'privacy_phone' => $unbindPhone,
|
||||
'real_phone' => $realPhone,
|
||||
'error' => $e->getMessage()
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,14 @@ namespace app\services\other\export;
|
||||
use app\model\product\stock\StockRecord;
|
||||
use app\services\BaseServices;
|
||||
use app\jobs\system\ExportExcelJob;
|
||||
use app\model\order\StoreOrderRefund;
|
||||
use app\model\order\StoreOrderStatus;
|
||||
use app\services\order\StoreOrderServices;
|
||||
use app\services\pay\PayServices;
|
||||
use app\services\product\label\StoreProductLabelServices;
|
||||
use app\services\product\product\StoreDescriptionServices;
|
||||
use app\services\product\sku\StoreProductAttrResultServices;
|
||||
use app\services\supplier\SystemSupplierServices;
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\services\SpreadsheetExcelService;
|
||||
|
||||
@@ -729,9 +733,11 @@ class ExportServices extends BaseServices
|
||||
$header = ['订单ID', '订单编号', '性别', '电话', '收货人姓名', '收货人电话', '收货地址', '商品信息', '商品总数',
|
||||
'总价格', '实际支付', '邮费', '会员优惠金额', '优惠卷金额', '积分抵扣金额', '支付状态', '支付时间', '订单状态', '下单时间', '用户备注'];
|
||||
$header = [
|
||||
'订单号', '订单类型', '订单状态', '下单时间', '买家姓名(uid)', '买家手机号', '收货地址', '商品名称', '商品数量', '商品金额合计', '邮费',
|
||||
'会员优惠', '优惠券优惠', '积分抵扣', '活动优惠', '首单优惠', '改价优惠', '实付金额', '实付邮费', '支付方式',
|
||||
'订单号', '订单类型', '订单状态', '下单时间', '买家姓名(uid)', '用户名称', '买家手机号', '收货地址',
|
||||
'商品名称', '商品标签', 'SPU', '款式编码', '商品编号', '商品规格', '商品数量', '商品金额合计', '结算价', '商品来源',
|
||||
'邮费', '会员优惠', '优惠券优惠', '积分抵扣', '活动优惠', '首单优惠', '改价优惠', '实付金额', '实付邮费', '支付方式',
|
||||
'支付状态', '支付时间', '订单配送方式', '快递公司(单号)/送货人姓名(手机号)',
|
||||
'退款状态', '退款金额', '分销员', '分销佣金', '订单发货时间',
|
||||
// '售后状态', '售后类型', '售后件数', '退款金额', '退款时间', '退货备注'
|
||||
];
|
||||
$title = ['订单导出', '订单信息' . time(), ' 生成时间:' . date('Y-m-d H:i:s', time())];
|
||||
@@ -744,6 +750,96 @@ class ExportServices extends BaseServices
|
||||
$export = [];
|
||||
$filekey = [];
|
||||
if (!empty($data)) {
|
||||
$userList = [];
|
||||
$spreadList = [];
|
||||
$orderStatusTime = [];
|
||||
$refundCartMap = [];
|
||||
$supplierList = [];
|
||||
$labelNameMap = [];
|
||||
if (!$type) {
|
||||
$userServices = app()->make(UserServices::class);
|
||||
$uids = array_values(array_unique(array_column($data, 'uid')));
|
||||
$userList = $uids ? $userServices->getColumn([['uid', 'IN', $uids]], 'uid,nickname,spread_uid', 'uid') : [];
|
||||
$spreadUids = $userList ? array_values(array_filter(array_unique(array_column($userList, 'spread_uid')))) : [];
|
||||
$spreadList = $spreadUids ? $userServices->getColumn([['uid', 'IN', $spreadUids]], 'uid,nickname', 'uid') : [];
|
||||
|
||||
$orderIds = array_values(array_unique(array_column($data, 'id')));
|
||||
$statusRows = StoreOrderStatus::whereIn('oid', $orderIds)
|
||||
->whereIn('change_type', ['delivery', 'delivery_goods', 'delivery_part_split', 'delivery_split', 'delivery_fictitious'])
|
||||
->field(['oid', 'change_type', 'change_time'])
|
||||
->order('change_time desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($statusRows as $row) {
|
||||
$oid = (int)$row['oid'];
|
||||
$changeType = (string)$row['change_type'];
|
||||
if (!isset($orderStatusTime[$oid][$changeType])) {
|
||||
$orderStatusTime[$oid][$changeType] = (int)$row['change_time'];
|
||||
}
|
||||
}
|
||||
|
||||
$refundRows = StoreOrderRefund::whereIn('store_order_id', $orderIds)
|
||||
->where('is_cancel', 0)
|
||||
->where('is_del', 0)
|
||||
->whereIn('refund_type', [0, 1, 2, 4, 5, 6])
|
||||
->field(['store_order_id', 'refund_type', 'cart_info'])
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($refundRows as $refund) {
|
||||
$oid = (int)$refund['store_order_id'];
|
||||
$refundType = (int)$refund['refund_type'];
|
||||
$cartInfo = $refund['cart_info'] ?? [];
|
||||
foreach ($cartInfo as $cart) {
|
||||
$cartId = (int)($cart['id'] ?? 0);
|
||||
$qty = (int)($cart['cart_num'] ?? 0);
|
||||
if (!$cartId || !$qty) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($refundCartMap[$oid][$cartId])) {
|
||||
$refundCartMap[$oid][$cartId] = ['refunding_qty' => 0, 'refunded_qty' => 0];
|
||||
}
|
||||
if ($refundType === 6) {
|
||||
$refundCartMap[$oid][$cartId]['refunded_qty'] += $qty;
|
||||
} else {
|
||||
$refundCartMap[$oid][$cartId]['refunding_qty'] += $qty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$supplierIds = [];
|
||||
$labelIds = [];
|
||||
foreach ($data as $o) {
|
||||
foreach ($o['_info'] ?? [] as $cart) {
|
||||
$productInfo = $cart['productInfo'] ?? [];
|
||||
$supplierId = (int)($productInfo['supplier_id'] ?? 0);
|
||||
if ($supplierId > 0) {
|
||||
$supplierIds[$supplierId] = $supplierId;
|
||||
} elseif ((int)($cart['_relation_type'] ?? 0) === 2 && (int)($cart['_relation_id'] ?? 0) > 0) {
|
||||
$supplierId = (int)$cart['_relation_id'];
|
||||
$supplierIds[$supplierId] = $supplierId;
|
||||
}
|
||||
$storeLabelId = $productInfo['store_label_id'] ?? [];
|
||||
$storeLabelId = is_string($storeLabelId) ? array_map('intval', array_filter(explode(',', $storeLabelId))) : (is_array($storeLabelId) ? $storeLabelId : []);
|
||||
foreach ($storeLabelId as $lid) {
|
||||
$lid = (int)$lid;
|
||||
if ($lid > 0) {
|
||||
$labelIds[$lid] = $lid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$supplierService = app()->make(SystemSupplierServices::class);
|
||||
$supplierList = $supplierIds ? $supplierService->getColumn([['id', 'IN', array_values($supplierIds)]], 'id,supplier_name', 'id') : [];
|
||||
|
||||
$labelService = app()->make(StoreProductLabelServices::class);
|
||||
$labelList = $labelIds ? $labelService->getLabelCache(array_values($labelIds), ['id', 'label_name'], false) : [];
|
||||
foreach ($labelList as $label) {
|
||||
if (isset($label['id'])) {
|
||||
$labelNameMap[(int)$label['id']] = (string)($label['label_name'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
$i = 0;
|
||||
foreach ($data as $item) {
|
||||
if (!$type) {
|
||||
@@ -805,24 +901,9 @@ class ExportServices extends BaseServices
|
||||
} else if ($item['paid'] == 1 && $item['refund_status'] == 3) {
|
||||
$item['status_name'] = '部分退款';
|
||||
}
|
||||
$goodsName = [];
|
||||
$vip_sum_price = 0;
|
||||
foreach ($item['_info'] as $k => $v) {
|
||||
$suk = '';
|
||||
if (isset($v['productInfo']['attrInfo'])) {
|
||||
if (isset($v['productInfo']['attrInfo']['suk'])) {
|
||||
$suk = '(' . $v['productInfo']['attrInfo']['suk'] . ')';
|
||||
}
|
||||
}
|
||||
if (isset($v['productInfo']['store_name'])) {
|
||||
$goodsName[] = implode(' ',
|
||||
[
|
||||
$v['productInfo']['store_name'],
|
||||
$suk,
|
||||
"[{$v['cart_num']} * {$v['truePrice']}]",
|
||||
]);
|
||||
}
|
||||
$vip_sum_price = bcadd((string)$vip_sum_price, bcmul($v['vip_truePrice'], $v['cart_num'] ? $v['cart_num'] : 1, 4), 2);
|
||||
foreach ($item['_info'] as $v) {
|
||||
$vip_sum_price = bcadd((string)$vip_sum_price, bcmul((string)($v['vip_truePrice'] ?? 0), (string)($v['cart_num'] ? $v['cart_num'] : 1), 4), 2);
|
||||
}
|
||||
// if ($item['sex'] == 1) $sex_name = '男';
|
||||
// else if ($item['sex'] == 2) $sex_name = '女';
|
||||
@@ -856,32 +937,134 @@ class ExportServices extends BaseServices
|
||||
'send' => '配送',
|
||||
default => '暂无',
|
||||
};
|
||||
$one_data = [
|
||||
'order_id' => $item['order_id'],
|
||||
'order_type' => $item['pink_name'] ?? '普通订单',
|
||||
'status_name' => $item['status_name'] ?? '未知状态',
|
||||
'add_time' => empty($item['add_time']) ? '' : date('Y-m-d H:i:s', (int)$item['add_time']),
|
||||
'real_name' => $item['real_name'] . '(' . $item['uid'] . ')',
|
||||
'user_phone' => $item['user_phone'],
|
||||
'user_address' => $item['user_address'],
|
||||
'goods_name' => $goodsName ? implode("\n", $goodsName) : '',
|
||||
'total_num' => $item['total_num'],
|
||||
'total_price' => $item['total_price'],
|
||||
'total_postage' => $item['total_postage'],
|
||||
'vip_sum_price' => $vip_sum_price,
|
||||
'coupon_price' => $item['coupon_price'],
|
||||
'deduction_price' => $item['deduction_price'] ?? 0,
|
||||
'promotions_price' => $item['promotions_price'] ?? 0,
|
||||
'first_order_price' => $item['first_order_price'] ?? 0,
|
||||
'change_price' => $item['change_price'] ?? 0,
|
||||
'pay_price' => $item['pay_price'],
|
||||
'pay_postage' => $item['pay_postage'],
|
||||
'pay_type_name' => $item['pay_type_name'],
|
||||
'pay_status' => $item['paid'] ? '已支付' : '未支付',
|
||||
'pay_time' => empty($item['pay_time']) ? '' : date('Y-m-d H:i:s', (int)$item['pay_time']),
|
||||
'delivery_type' => $delivery_type,
|
||||
'delivery_info' => $item['delivery_name'] . '(' . $item['delivery_id'] . ')',
|
||||
];
|
||||
$orderLevelDeliveryTime = '';
|
||||
$oid = (int)$item['id'];
|
||||
$timeMap = $orderStatusTime[$oid] ?? [];
|
||||
$deliveryTime = $timeMap['delivery'] ?? ($timeMap['delivery_goods'] ?? ($timeMap['delivery_part_split'] ?? ($timeMap['delivery_split'] ?? ($timeMap['delivery_fictitious'] ?? 0))));
|
||||
if ($deliveryTime) {
|
||||
$orderLevelDeliveryTime = date('Y-m-d H:i:s', $deliveryTime);
|
||||
}
|
||||
|
||||
$currentUser = $userList[(int)$item['uid']] ?? [];
|
||||
$userNickname = (string)($currentUser['nickname'] ?? '');
|
||||
$spreadUid = (int)($currentUser['spread_uid'] ?? 0);
|
||||
$spreadNickname = $spreadUid ? (string)($spreadList[$spreadUid]['nickname'] ?? '') : '';
|
||||
if ($spreadUid && $spreadUid === (int)$item['uid'] && $spreadNickname !== '') {
|
||||
$spreadNickname .= '(自购)';
|
||||
}
|
||||
|
||||
foreach ($item['_info'] as $v) {
|
||||
$productInfo = $v['productInfo'] ?? [];
|
||||
$attrInfo = $productInfo['attrInfo'] ?? [];
|
||||
|
||||
$storeName = (string)($productInfo['store_name'] ?? '');
|
||||
$suk = (string)($attrInfo['suk'] ?? '');
|
||||
$productLabelNames = [];
|
||||
if (!empty($productInfo['store_label']) && is_array($productInfo['store_label'])) {
|
||||
foreach ($productInfo['store_label'] as $label) {
|
||||
$name = (string)($label['label_name'] ?? ($label['label'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$productLabelNames[] = $name;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$storeLabelId = $productInfo['store_label_id'] ?? [];
|
||||
$storeLabelId = is_string($storeLabelId) ? array_map('intval', array_filter(explode(',', $storeLabelId))) : (is_array($storeLabelId) ? $storeLabelId : []);
|
||||
foreach ($storeLabelId as $lid) {
|
||||
$name = $labelNameMap[(int)$lid] ?? '';
|
||||
if ($name !== '') {
|
||||
$productLabelNames[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
$productLabelText = $productLabelNames ? implode(',', array_values(array_unique($productLabelNames))) : '';
|
||||
|
||||
$supplierId = (int)($productInfo['supplier_id'] ?? 0);
|
||||
if (!$supplierId && (int)($v['_relation_type'] ?? 0) === 2) {
|
||||
$supplierId = (int)($v['_relation_id'] ?? 0);
|
||||
}
|
||||
$productSource = $supplierId ? (string)($supplierList[$supplierId]['supplier_name'] ?? '') : '平台';
|
||||
|
||||
$cartId = (int)($v['cart_id'] ?? 0);
|
||||
$refundedQty = (int)($refundCartMap[$oid][$cartId]['refunded_qty'] ?? 0);
|
||||
$refundingQty = (int)($refundCartMap[$oid][$cartId]['refunding_qty'] ?? 0);
|
||||
$refundQty = $refundedQty + $refundingQty;
|
||||
|
||||
$cartNum = (int)($v['cart_num'] ?? 0);
|
||||
$truePrice = (string)($v['truePrice'] ?? 0);
|
||||
$productAmount = bcmul($truePrice, (string)$cartNum, 2);
|
||||
|
||||
$refundStatus = '';
|
||||
if ($refundQty > 0) {
|
||||
if ($refundingQty > 0) {
|
||||
$refundStatus = '退款中';
|
||||
} else {
|
||||
$refundStatus = $refundQty >= $cartNum ? '退款成功(全额退款)' : '退款成功(部分退款)';
|
||||
}
|
||||
}
|
||||
$refundAmount = $refundQty > 0 ? bcmul($truePrice, (string)$refundQty, 2) : '';
|
||||
|
||||
$settlePrice = (string)($attrInfo['settle_price'] ?? 0);
|
||||
$oneBrokerage = $v['one_brokerage'] ?? null;
|
||||
if ($oneBrokerage === null) {
|
||||
$oneBrokerage = bcmul((string)($attrInfo['brokerage'] ?? 0), (string)$cartNum, 2);
|
||||
}
|
||||
$twoBrokerage = $v['two_brokerage'] ?? null;
|
||||
if ($twoBrokerage === null) {
|
||||
$twoBrokerage = bcmul((string)($attrInfo['brokerage_two'] ?? 0), (string)$cartNum, 2);
|
||||
}
|
||||
$commission = bcadd((string)$oneBrokerage, (string)$twoBrokerage, 2);
|
||||
|
||||
$one_data = [
|
||||
'order_id' => $item['order_id'],
|
||||
'order_type' => $item['pink_name'] ?? '普通订单',
|
||||
'status_name' => $item['status_name'] ?? '未知状态',
|
||||
'add_time' => empty($item['add_time']) ? '' : date('Y-m-d H:i:s', (int)$item['add_time']),
|
||||
'real_name' => $item['real_name'] . '(' . $item['uid'] . ')',
|
||||
'user_nickname' => $userNickname,
|
||||
'user_phone' => $item['user_phone'],
|
||||
'user_address' => $item['user_address'],
|
||||
'goods_name' => $storeName,
|
||||
'goods_label' => $productLabelText,
|
||||
'spu' => (string)($productInfo['spu'] ?? ''),
|
||||
'style_code' => (string)($productInfo['code'] ?? ''),
|
||||
'sku_code' => (string)($attrInfo['code'] ?? ''),
|
||||
'goods_suk' => $suk,
|
||||
'cart_num' => $cartNum,
|
||||
'product_amount' => $productAmount,
|
||||
'settle_price' => $settlePrice,
|
||||
'product_source' => $productSource,
|
||||
'total_postage' => $item['total_postage'],
|
||||
'vip_sum_price' => $vip_sum_price,
|
||||
'coupon_price' => $item['coupon_price'],
|
||||
'deduction_price' => $item['deduction_price'] ?? 0,
|
||||
'promotions_price' => $item['promotions_price'] ?? 0,
|
||||
'first_order_price' => $item['first_order_price'] ?? 0,
|
||||
'change_price' => $item['change_price'] ?? 0,
|
||||
'pay_price' => $item['pay_price'],
|
||||
'pay_postage' => $item['pay_postage'],
|
||||
'pay_type_name' => $item['pay_type_name'],
|
||||
'pay_status' => $item['paid'] ? '已支付' : '未支付',
|
||||
'pay_time' => empty($item['pay_time']) ? '' : date('Y-m-d H:i:s', (int)$item['pay_time']),
|
||||
'delivery_type' => $delivery_type,
|
||||
'delivery_info' => $item['delivery_name'] . '(' . $item['delivery_id'] . ')',
|
||||
'refund_status' => $refundStatus,
|
||||
'refund_amount' => $refundAmount,
|
||||
'spread_nickname' => $spreadNickname,
|
||||
'spread_commission' => $commission,
|
||||
'delivery_time' => $orderLevelDeliveryTime,
|
||||
];
|
||||
|
||||
if ($export_type == 1) {
|
||||
$export[] = $one_data;
|
||||
if ($i == 0) {
|
||||
$filekey = array_keys($one_data);
|
||||
}
|
||||
} else {
|
||||
$export[] = array_values($one_data);
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
} else {
|
||||
if (isset($item['pinkStatus']) && $item['pinkStatus'] != 2) {
|
||||
continue;
|
||||
@@ -937,15 +1120,17 @@ class ExportServices extends BaseServices
|
||||
'pay_time' => $item['pay_time'] ? date('Y-m-d H:i:s', (int)$item['pay_time']) : '暂无',
|
||||
];
|
||||
}
|
||||
if ($export_type == 1) {
|
||||
$export[] = $one_data;
|
||||
if ($i == 0) {
|
||||
$filekey = array_keys($one_data);
|
||||
if ($type) {
|
||||
if ($export_type == 1) {
|
||||
$export[] = $one_data;
|
||||
if ($i == 0) {
|
||||
$filekey = array_keys($one_data);
|
||||
}
|
||||
} else {
|
||||
$export[] = array_values($one_data);
|
||||
}
|
||||
} else {
|
||||
$export[] = array_values($one_data);
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
if ($export_type == 1) {
|
||||
|
||||
@@ -797,6 +797,35 @@ class StoreProductServices extends BaseServices
|
||||
return substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8) . str_pad((string)mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
protected function createUniqueSpu(): string
|
||||
{
|
||||
$tries = 0;
|
||||
do {
|
||||
$spu = $this->createSpu();
|
||||
$tries++;
|
||||
} while ($tries < 20 && $this->dao->be(['spu' => $spu], 'spu'));
|
||||
return $spu;
|
||||
}
|
||||
|
||||
public function ensureSpu(int $productId): string
|
||||
{
|
||||
$spu = (string)$this->dao->value(['id' => $productId], 'spu');
|
||||
if ($spu !== '') {
|
||||
return $spu;
|
||||
}
|
||||
$spu = $this->createUniqueSpu();
|
||||
$this->update($productId, ['spu' => $spu]);
|
||||
return $spu;
|
||||
}
|
||||
|
||||
public function ErpProductSave(array $data): int
|
||||
{
|
||||
if (!isset($data['spu']) || $data['spu'] === '') {
|
||||
$data['spu'] = $this->createUniqueSpu();
|
||||
}
|
||||
return (int)$this->dao->ErpProductSave($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增编辑商品
|
||||
* @param int $id
|
||||
@@ -1531,7 +1560,7 @@ class StoreProductServices extends BaseServices
|
||||
}
|
||||
}
|
||||
[$page, $limit] = $this->getPageValue();
|
||||
$field = ['id,relation_id,type,pid,delivery_type,product_type,store_name,cate_id,image,IFNULL(sales, 0) + IFNULL(ficti, 0) as sales,price,stock,activity,ot_price,spec_type,recommend_image,unit_name,is_vip,vip_price,is_presale_product,is_vip_product,system_form_id,is_presale_product,presale_start_time,presale_end_time,is_limit,limit_num,video_open,video_link,freight,star,store_label_id,brand_id,min_qty'];
|
||||
$field = ['id,spu,relation_id,type,pid,delivery_type,product_type,store_name,cate_id,image,IFNULL(sales, 0) + IFNULL(ficti, 0) as sales,price,stock,activity,ot_price,spec_type,recommend_image,unit_name,is_vip,vip_price,is_presale_product,is_vip_product,system_form_id,is_presale_product,presale_start_time,presale_end_time,is_limit,limit_num,video_open,video_link,freight,star,store_label_id,brand_id,min_qty'];
|
||||
$list = $this->dao->getSearchList($where, $page, $limit, $field, 'sales desc,id desc', ['couponId']);
|
||||
if ($list) {
|
||||
/** @var MemberCardServices $memberCardService */
|
||||
|
||||
@@ -31,15 +31,13 @@ class OrderStatisticServices extends BaseServices
|
||||
$orderService = app()->make(StoreOrderServices::class);
|
||||
$data['pay_price'] = $orderService->sum(['paid' => 1, 'pid' => 0, 'time' => $where['time'], 'supplier_id' => $supplier_id], 'pay_price', true);
|
||||
$data['pay_count'] = $orderService->count(['paid' => 1, 'pid' => 0, 'time' => $where['time'], 'supplier_id' => $supplier_id]);
|
||||
$times = explode('-', $where['time']);
|
||||
if(count($times) > 1){
|
||||
$times = [
|
||||
'start_time'=>$times[0],
|
||||
'end_time'=>$times[1] . ' 23:59:59'
|
||||
];
|
||||
$refundWhere = ['refund_type' => 6, 'time' => explode('-', $where['time']), 'is_cancel' => 0, 'is_del' => 0];
|
||||
if ($supplier_id !== '') {
|
||||
$refundWhere['supplier_id'] = $supplier_id;
|
||||
}
|
||||
$data['refund_price'] = app()->make(StoreOrderRefundServices::class)->sum(['refund_type' => 6, 'time' => explode('-', $where['time'])],'refund_price', true);
|
||||
$data['refund_count'] = app()->make(StoreOrderRefundServices::class)->count(['refund_type' => 6, 'time' => explode('-', $where['time'])]);
|
||||
$refundService = app()->make(StoreOrderRefundServices::class);
|
||||
$data['refund_price'] = $refundService->sum($refundWhere, 'refund_price', true);
|
||||
$data['refund_count'] = $refundService->count($refundWhere);
|
||||
$data['coupon_price'] = $orderService->sum(['paid' => 1, 'pid' => 0, 'time' => $where['time'], 'supplier_id' => $supplier_id], 'coupon_price', true);
|
||||
$data['coupon_count'] = $orderService->search(['paid' => 1, 'pid' => 0, 'time' => $where['time'], 'is_coupon' => 1, 'supplier_id' => $supplier_id])->count();
|
||||
return $data;
|
||||
|
||||
@@ -394,7 +394,7 @@ class SystemAdminServices extends BaseServices
|
||||
$verifyCode = CacheService::get('code_' . $data['phone']);
|
||||
if (!$verifyCode)
|
||||
throw new AdminException('请先获取验证码');
|
||||
$verifyCode = substr($verifyCode, 0, 6);
|
||||
$verifyCode = substr($verifyCode, 0, 4);
|
||||
if ($verifyCode != $data['code']) {
|
||||
CacheService::delete('code_' . $data['phone']);
|
||||
throw new AdminException('验证码错误');
|
||||
|
||||
@@ -65,7 +65,7 @@ class SystemAttachmentServices extends BaseServices
|
||||
if ($item['file_type'] == 1) {
|
||||
if ($site_url) {
|
||||
$item['satt_dir'] = (strpos($item['satt_dir'], $site_url) !== false || strstr($item['satt_dir'], 'http') !== false) ? $item['satt_dir'] : $site_url . $item['satt_dir'];
|
||||
$item['att_dir'] = (strpos($item['att_dir'], $site_url) !== false || strstr($item['att_dir'], 'http') !== false) ? $item['satt_dir'] : $site_url . $item['att_dir'];
|
||||
$item['att_dir'] = (strpos($item['att_dir'], $site_url) !== false || strstr($item['att_dir'], 'http') !== false) ? $item['att_dir'] : $site_url . $item['att_dir'];
|
||||
}
|
||||
}
|
||||
$item['att_size'] = formatFileSize($item['att_size']);
|
||||
|
||||
@@ -167,7 +167,7 @@ class LoginServices extends BaseServices
|
||||
if (CacheService::get('code_' . $phone))
|
||||
throw new ValidateException($time . '分钟内有效');
|
||||
mt_srand();
|
||||
$code = rand(100000, 999999);
|
||||
$code = rand(1000, 9999);
|
||||
$data['code'] = $code;
|
||||
$data['time'] = $time;
|
||||
$res = SmsJob::dispatch([$phone, $data, 'VERIFICATION_CODE_TIME']);
|
||||
|
||||
Reference in New Issue
Block a user