ProductSyncErp.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. <?php
  2. namespace app\jobs\product;
  3. use app\services\product\branch\StoreBranchProductAttrValueServices;
  4. use app\services\product\branch\StoreBranchProductServices;
  5. use app\services\product\product\StoreProductServices;
  6. use app\services\product\sku\StoreProductAttrServices;
  7. use app\services\product\sku\StoreProductAttrValueServices;
  8. use app\services\store\SystemStoreServices;
  9. use crmeb\basic\BaseJobs;
  10. use crmeb\exceptions\AdminException;
  11. use crmeb\services\erp\Erp as erpServices;
  12. use crmeb\traits\QueueTrait;
  13. use think\facade\Log;
  14. class ProductSyncErp extends BaseJobs
  15. {
  16. use QueueTrait;
  17. /**
  18. * @return mixed
  19. */
  20. public static function queueName()
  21. {
  22. return 'CRMEB_PRO_ERP';
  23. }
  24. /**
  25. * 同步商品到erp
  26. * @param $id
  27. * @return mixed
  28. */
  29. public function upProductToErp($id)
  30. {
  31. try {
  32. /** @var StoreProductServices $productServices */
  33. $productServices = app()->make(StoreProductServices::class);
  34. // 获取商品信息
  35. $productInfo = $productServices->getInfo($id)['productInfo'];
  36. $data = [];
  37. $attrs = $productInfo['attrs'] ?? [];
  38. if (!$attrs && ($productInfo['attr'] ?? [])) {
  39. $attrs = [$productInfo['attr']];
  40. }
  41. foreach ($attrs as $item) {
  42. if ($item['pic'] && strstr($item['pic'], 'http') === false) {
  43. $siteUrl = sys_config('site_url');
  44. $item['pic'] = $siteUrl . $item['pic'];
  45. }
  46. $data[] = [
  47. 'i_id' => $productInfo['code'],
  48. 'sku_id' => $item['code'],
  49. 'name' => $productInfo['store_name'],
  50. 'properties_value' => str_replace(',', ' ', $item['values']),
  51. 's_price' => $item['price'],
  52. 'pic' => $item['pic'],
  53. 'c_price' => $item['cost'],
  54. 'market_price' => $item['ot_price'],
  55. ];
  56. }
  57. (new erpServices())->serviceDriver('product')->updateProduct($data);
  58. } catch (\Exception $e) {
  59. Log::error('商品上传失败, 原因: ' . $e->getMessage());
  60. }
  61. return true;
  62. }
  63. /**
  64. * 上传店铺商品
  65. * @param $id
  66. * @param $shop
  67. * @return bool
  68. */
  69. public function upBranchProductToErp($id, $shop)
  70. {
  71. try {
  72. /** @var StoreProductServices $productServices */
  73. $productServices = app()->make(StoreProductServices::class);
  74. // 获取商品信息
  75. $productInfo = $productServices->getInfo($id)['productInfo'];
  76. $data = [];
  77. $attrs = $productInfo['attrs'] ?? [];
  78. if (!$attrs && ($productInfo['attr'] ?? [])) {
  79. $attrs = [$productInfo['attr']];
  80. }
  81. foreach ($attrs as $item) {
  82. $data[] = [
  83. 'i_id' => $productInfo['code'],
  84. 'sku_id' => $item['code'],
  85. 'shop_i_id' => $shop['erp_shop_id'] . $productInfo['code'],
  86. 'shop_sku_id' => $shop['erp_shop_id'] . $item['code'],
  87. 'name' => $productInfo['store_name'],
  88. 'properties_value' => str_replace(',', ' ', $item['values']),
  89. 'shop_id' => $shop['erp_shop_id'],
  90. ];
  91. }
  92. (new erpServices())->serviceDriver('product')->updateShopProduct($data);
  93. } catch (\Exception $e) {
  94. Log::error('店铺商品上传失败, 原因: ' . $e->getMessage());
  95. }
  96. return true;
  97. }
  98. /**
  99. * 添加商品同步到门店中
  100. * @param $id
  101. * @param $shop
  102. */
  103. public function productToBranch($id, $shop)
  104. {
  105. /** @var StoreProductServices $productServices */
  106. $productServices = app()->make(StoreProductServices::class);
  107. // 获取商品信息
  108. $productInfo = $productServices->getInfo($id)['productInfo'];
  109. /** @var StoreProductAttrValueServices $storeProductAttrValueServices */
  110. $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  111. $skuArray = $storeProductAttrValueServices->getSkuArray(['product_id' => $id, 'type' => 0], 'unique', 'code');
  112. $data = [
  113. 'product_id' => $id,
  114. 'image' => $productInfo['image'],
  115. 'store_name' => $productInfo['store_name'],
  116. 'store_info' => $productInfo['store_info'],
  117. 'keyword' => $productInfo['keyword'],
  118. 'bar_code' => $productInfo['bar_code'],
  119. 'cate_id' => $productInfo['cate_id'],
  120. 'store_id' => $shop['id'],
  121. 'sales' => 0,
  122. 'stock' => 0,
  123. 'sort' => 0,
  124. 'label_id' => $productInfo['label_id'],
  125. 'is_show' => 0,
  126. 'add_time' => time(),
  127. 'is_del' => 0,
  128. 'code' => $productInfo['code'],
  129. ];
  130. $attrs = [];
  131. foreach ($productInfo['attrs'] as $item) {
  132. if (empty($item['code']) || !array_key_exists($item['code'], $skuArray)) {
  133. continue;
  134. }
  135. $attrs[] = [
  136. 'product_id' => $id,
  137. 'store_id' => $shop['id'],
  138. 'unique' => $skuArray[$item['code']],
  139. 'sales' => 0,
  140. 'stock' => 0,
  141. 'type' => 0,
  142. 'bar_code' => $item['bar_code'],
  143. 'code' => $item['code'],
  144. ];
  145. }
  146. /** @var StoreBranchProductAttrValueServices $branchProductAttrServices */
  147. $branchProductAttrServices = app()->make(StoreBranchProductAttrValueServices::class);
  148. $branchProductAttrServices->transaction(function () use ($id, $data, $attrs, $shop, $branchProductAttrServices) {
  149. /** @var StoreBranchProductServices $branchProductServices */
  150. $branchProductServices = app()->make(StoreBranchProductServices::class);
  151. // 判断门店是否有商品
  152. $branchProduct = $branchProductServices->getOne(['product_id' => $id, 'store_id' => $shop['id']]);
  153. if (empty($branchProduct)) {
  154. $branchProductServices->save($data);
  155. $branchProductAttrServices->saveAll($attrs);
  156. } else {
  157. $branchProductAttr = $branchProductAttrServices->getColumn(['product_id' => $id, 'store_id' => $shop['id']], '*', 'code');
  158. if (!empty($branchProductAttr)) {
  159. foreach ($attrs as $key => $attr) {
  160. if (isset($branchProductAttr[$attr['code']])) {
  161. unset($attrs[$key]);
  162. }
  163. }
  164. if (!empty($attrs)) {
  165. $branchProductAttrServices->saveAll($attrs);
  166. }
  167. }
  168. }
  169. });
  170. return true;
  171. }
  172. /**
  173. * 同步商品
  174. * @param $spuArr
  175. * @return bool
  176. * @throws \think\db\exception\DataNotFoundException
  177. * @throws \think\db\exception\DbException
  178. * @throws \think\db\exception\ModelNotFoundException
  179. */
  180. public function productFromErp($spuArr)
  181. {
  182. try {
  183. $result = (new erpServices())->serviceDriver('product')->syncProduct([$spuArr]);;
  184. $productList = $result['datas'];
  185. $productInfo = [];
  186. /** @var StoreProductServices $productServices */
  187. $productServices = app()->make(StoreProductServices::class);
  188. /** @var StoreProductAttrServices $productAttrServices */
  189. $productAttrServices = app()->make(StoreProductAttrServices::class);
  190. /** @var SystemStoreServices $systemStoreServices */
  191. $systemStoreServices = app()->make(SystemStoreServices::class);
  192. $systemStoreList = $systemStoreServices->getErpStore([['erp_shop_id', '>', 0]]);
  193. foreach ($productList as $item) {
  194. $productInfo = [
  195. 'image' => (string)$item['pic'],
  196. 'slider_image' => json_encode([(string)$item['pic']]),
  197. 'store_name' => $item['name'],
  198. 'store_info' => $item['name'],
  199. 'cate_id' => 0,
  200. 'price' => floatval($item['s_price']),
  201. 'ot_price' => floatval($item['market_price']),
  202. 'delivery_type' => '1,2,3',
  203. 'freight' => 1,
  204. 'is_show' => 0,
  205. 'add_time' => time(),
  206. 'cost' => floatval($item['c_price']),
  207. 'ficti' => 0,
  208. 'spec_type' => 1,
  209. 'code' => $item['i_id'],
  210. ];
  211. $detail = $details = $value = [];
  212. foreach ($item['skus'] as $items) {
  213. $detail[] = $items['properties_value'];
  214. $details[] = [
  215. 'name' => $items['properties_value'],
  216. 'select' => false
  217. ];
  218. $value[] = [
  219. 'bar_code' => '',
  220. 'brokerage' => 0,
  221. 'brokerage_two' => 0,
  222. 'code' => $items['sku_id'],
  223. 'cost' => floatval($items['cost_price']),
  224. 'detail' => ['规格' => $items['properties_value']],
  225. 'ot_price' => floatval($items['market_price']),
  226. 'pic' => (string)$items['pic'],
  227. 'price' => floatval($items['sale_price']),
  228. 'select' => true,
  229. 'value1' => $items['properties_value'],
  230. 'values' => $items['properties_value'],
  231. 'vip_price' => 0,
  232. 'volume' => 0,
  233. 'weight' => 0,
  234. 'stock' => 0,
  235. ];
  236. }
  237. $pid = $productServices->value(['code' => $item['i_id']], 'id');
  238. if (!$pid) {
  239. $pid = $productServices->ErpProductSave($productInfo);
  240. }
  241. //检测库存警戒和检测是否售罄
  242. ProductStockTips::dispatch([$pid, 0]);
  243. $attr = [[
  244. 'value' => '规格',
  245. 'detail' => $detail,
  246. 'details' => $details,
  247. ]];
  248. $skuList = $productAttrServices->validateProductAttr($attr, $value, $pid, 0, 0, 0);
  249. $productAttrServices->saveProductAttr($skuList, $pid);
  250. // 同步商品至erp门店
  251. if (!empty($systemStoreList)) {
  252. foreach ($systemStoreList as $store) {
  253. ProductSyncErp::dispatchDo('productToBranch', [$pid, $store]);
  254. }
  255. }
  256. }
  257. //清除数据缓存
  258. $productServices->cacheTag()->clear();
  259. $productAttrServices->cacheTag()->clear();
  260. } catch (\Exception $e) {
  261. Log::error('商品同步失败, 原因: ' . $e->getMessage());
  262. }
  263. return true;
  264. }
  265. /**
  266. * 同步商品库存
  267. * @param array $ids
  268. * @return bool
  269. */
  270. public function stockFromErp(array $ids)
  271. {
  272. try {
  273. /** @var StoreProductServices $storeProductServices */
  274. $storeProductServices = app()->make(StoreProductServices::class);
  275. /** @var StoreProductAttrValueServices $storeProductAttrValueServices */
  276. $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  277. //查询ids下的所有规格对应的sku
  278. $list = $storeProductAttrValueServices->getSkuArray(['product_id' => $ids, 'type' => 0], 'code', 'id');
  279. $values = array_filter(array_values($list));
  280. if (empty($values)) {
  281. throw new AdminException('没有符合同步库存的商品');
  282. }
  283. $skuData = $skuMap = [];
  284. $basic = 20; // 单次查询数量最多20
  285. $num = count($values);
  286. $rate = ceil($num / $basic);
  287. for ($i = 0; $i < $rate; $i++) {
  288. $code = array_slice($values, $i * $basic, $basic);
  289. $codeStr = implode(',', $code);
  290. $result = (new erpServices())->serviceDriver('product')->syncStock($codeStr);
  291. if (!empty($result['inventorys'])) {
  292. foreach ($result['inventorys'] as $inventory) {
  293. $skuMap[$inventory['sku_id']] = $inventory['qty'] - $inventory['order_lock'];
  294. }
  295. }
  296. }
  297. // 拼装规格数据
  298. if (!empty($skuMap)) {
  299. foreach ($skuMap as $key => $item) {
  300. if ($id = array_search($key, $list)) {
  301. $skuData[] = ['id' => $id, 'stock' => $item, 'sum_stock' => $item];
  302. }
  303. }
  304. }
  305. // 同步库存
  306. $storeProductServices->transaction(function () use ($ids, $skuData, $storeProductAttrValueServices, $storeProductServices) {
  307. // 同步规格库存
  308. $storeProductAttrValueServices->saveAll($skuData);
  309. // 同步商品库存
  310. $productData = $storeProductAttrValueServices->getProductStockByValues($ids);
  311. $storeProductServices->saveAll($productData);
  312. });
  313. /** @var SystemStoreServices $systemStoreServices */
  314. $systemStoreServices = app()->make(SystemStoreServices::class);
  315. $systemStoreList = $systemStoreServices->getErpStore([['erp_shop_id', '>', 0]]);
  316. // 同步门店商品库存
  317. if (!empty($systemStoreList)) {
  318. foreach ($systemStoreList as $store) {
  319. ProductSyncErp::dispatchDo('syncBranchProductStock', [$ids[0], $skuMap, $store['id']]);
  320. }
  321. }
  322. //清除缓存
  323. $storeProductServices->cacheTag()->clear();
  324. /** @var StoreProductAttrServices $attrService */
  325. $attrService = app()->make(StoreProductAttrServices::class);
  326. $attrService->cacheTag()->clear();
  327. } catch (\Exception $e) {
  328. Log::error('库存获取失败, 原因: ' . $e->getMessage());
  329. }
  330. return true;
  331. }
  332. /**
  333. * 同步门店商品库存
  334. * @param int $productId
  335. * @param array $data
  336. * @param int $storeId
  337. */
  338. public function syncBranchProductStock(int $productId, array $data, int $storeId)
  339. {
  340. /** @var StoreBranchProductAttrValueServices $branchProductAttrServices */
  341. $branchProductAttrServices = app()->make(StoreBranchProductAttrValueServices::class);
  342. $branchProductAttrServices->transaction(function () use ($productId, $storeId, $data, $branchProductAttrServices) {
  343. $list = $branchProductAttrServices->getColumn(['store_id' => $storeId, 'product_id' => $productId, 'code' => array_keys($data)], '*', 'id');
  344. if (!empty($list)) {
  345. foreach ($list as $item) {
  346. $branchProductAttrServices->update($item['id'], ['stock' => $data[$item['code']]]);
  347. }
  348. }
  349. $stock = (int)$branchProductAttrServices->sum(['product_id' => $productId, 'store_id' => $storeId], 'stock');
  350. /** @var StoreBranchProductServices $branchProductServices */
  351. $branchProductServices = app()->make(StoreBranchProductServices::class);
  352. $branchProductId = $branchProductServices->value(['product_id' => $productId, 'store_id' => $storeId], 'id');
  353. if ($branchProductId > 0) {
  354. $branchProductServices->update($branchProductId, ['stock' => $stock]);
  355. }
  356. });
  357. return true;
  358. }
  359. /**
  360. * 同步商品至新增门店
  361. * @param int $shopId
  362. */
  363. public function syncProductToBranch(int $shopId)
  364. {
  365. /** @var StoreProductServices $productServices */
  366. $productServices = app()->make(StoreProductServices::class);
  367. $list = $productServices->getColumn(['is_del' => 0, 'is_show' => 1], 'id', 'id');
  368. if (!empty($list)) {
  369. foreach ($list as $item) {
  370. ProductSyncErp::dispatchDo('productToBranch', [$item, ['id' => $shopId]]);
  371. }
  372. }
  373. return true;
  374. }
  375. /**
  376. * 更新商品库存
  377. * @param array $list
  378. * @return bool
  379. */
  380. public function updatePlatformStock(array $list): bool
  381. {
  382. try {
  383. /** @var StoreProductAttrValueServices $storeProductAttrValueServices */
  384. $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  385. $data = array_column($list, 'qty', 'sku_id');
  386. $shopData = array_keys(array_column($list, 'shop_id', 'shop_id'));
  387. $erpShopId = $shopData[0] ?? 0;
  388. if ($erpShopId < 1) {
  389. return true;
  390. }
  391. // 更新平台商品库存
  392. $defaultShopId = (int)sys_config('jst_default_shopid');
  393. if ($defaultShopId == $erpShopId) {
  394. $this->updateStoreProductValueStock($data);
  395. }
  396. $attrs = $storeProductAttrValueServices->getColumn(['code' => array_keys($data)], 'id, product_id, suk, stock, sum_stock', 'code');
  397. $productIds = array_unique(array_column($attrs, 'product_id'));
  398. /** @var SystemStoreServices $systemStoreServices */
  399. $systemStoreServices = app()->make(SystemStoreServices::class);
  400. $systemStoreList = $systemStoreServices->getErpStore([['erp_shop_id', '=', $erpShopId]]);
  401. // 同步门店商品规格库存
  402. foreach ($systemStoreList as $store) {
  403. ProductSyncErp::dispatchDo('updateStoreAttrStockByCode', [$data, $store['id']]);
  404. }
  405. // 同步门店商品库存
  406. foreach ($systemStoreList as $store) {
  407. ProductSyncErp::dispatchDo('updateStoreProductStock', [$productIds, $store['id']]);
  408. }
  409. } catch (\Exception $e) {
  410. Log::error('更新门店库存失败, 原因: ' . $e->getMessage());
  411. }
  412. return true;
  413. }
  414. /**
  415. * 更新门店规格库存
  416. * @param array $data
  417. * @param int $storeId
  418. * @return bool
  419. */
  420. public function updateStoreAttrStockByCode(array $data, int $storeId): bool
  421. {
  422. try {
  423. /** @var StoreBranchProductAttrValueServices $branchProductAttrServices */
  424. $branchProductAttrServices = app()->make(StoreBranchProductAttrValueServices::class);
  425. $list = $branchProductAttrServices->getColumn(['store_id' => $storeId, 'code' => array_keys($data)], 'code', 'id');
  426. $skuData = [];
  427. // 同步规格库存
  428. foreach ($data as $key => $item) {
  429. if ($id = array_search($key, $list)) {
  430. $skuData[] = ['id' => $id, 'stock' => $item];
  431. }
  432. }
  433. // 同步规格库存
  434. $branchProductAttrServices->saveAll($skuData);
  435. } catch (\Exception $e) {
  436. Log::error('门店: ' . $storeId . ' 规格库存更新失败, 原因: ' . $e->getMessage());
  437. }
  438. return true;
  439. }
  440. /**
  441. * 更新门店商品库存
  442. * @param array $productIds
  443. * @param int $storeId
  444. * @return bool
  445. */
  446. public function updateStoreProductStock(array $productIds, int $storeId): bool
  447. {
  448. try {
  449. /** @var StoreBranchProductAttrValueServices $branchProductAttrServices */
  450. $branchProductAttrServices = app()->make(StoreBranchProductAttrValueServices::class);
  451. /** @var StoreBranchProductServices $branchProductServices */
  452. $branchProductServices = app()->make(StoreBranchProductServices::class);
  453. $productData = $branchProductAttrServices->getProductStockByValues($productIds, $storeId);
  454. foreach ($productData as $product) {
  455. $branchProductServices->update(['product_id' => $product['product_id'], 'store_id' => $storeId], ['stock' => $product['stock']]);
  456. }
  457. } catch (\Exception $e) {
  458. Log::error('门店: ' . $storeId . ' 商品库存更新失败, 原因: ' . $e->getMessage());
  459. }
  460. return true;
  461. }
  462. /**
  463. * 更新平台商品库存
  464. * @param array $data
  465. * @return bool
  466. */
  467. public function updateStoreProductValueStock(array $data): bool
  468. {
  469. try {
  470. /** @var StoreProductServices $storeProductServices */
  471. $storeProductServices = app()->make(StoreProductServices::class);
  472. /** @var StoreProductAttrValueServices $storeProductAttrValueServices */
  473. $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  474. $skuList = $storeProductAttrValueServices->getSkuArray(['code' => array_keys($data), 'type' => 0], 'id, product_id,code', 'id');
  475. if (empty($skuList)) {
  476. throw new AdminException('没有符合同步库存的商品');
  477. }
  478. $ids = array_unique(array_column($skuList, 'product_id'));
  479. $skuData = [];
  480. foreach ($skuList as $key => $sku) {
  481. if (array_key_exists($sku['code'], $data)) {
  482. $skuData[] = ['id' => $key, 'stock' => $data[$sku['code']]];
  483. }
  484. }
  485. if (empty($skuData)) {
  486. return true;
  487. }
  488. // 同步库存
  489. $storeProductServices->transaction(function () use ($ids, $skuData, $storeProductAttrValueServices, $storeProductServices) {
  490. // 同步规格库存
  491. $storeProductAttrValueServices->saveAll($skuData);
  492. // 同步商品库存
  493. $productData = $storeProductAttrValueServices->getProductStockByValues($ids);
  494. $storeProductServices->saveAll($productData);
  495. });
  496. } catch (\Exception $e) {
  497. Log::error('平台商品库存更新失败, 原因: ' . $e->getMessage());
  498. }
  499. return true;
  500. }
  501. }