StoreBranchProductServices.php 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2020 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\services\product\branch;
  12. use app\dao\product\product\StoreProductDao;
  13. use app\jobs\product\ProductSyncStoreJob;
  14. use app\services\activity\seckill\StoreSeckillServices;
  15. use app\services\BaseServices;
  16. use app\services\order\StoreCartServices;
  17. use app\services\product\brand\StoreBrandServices;
  18. use app\services\product\product\StoreDescriptionServices;
  19. use app\services\product\product\StoreProductRelationServices;
  20. use app\services\product\product\StoreProductReplyCommentServices;
  21. use app\services\product\product\StoreProductReplyServices;
  22. use app\services\product\product\StoreProductServices;
  23. use app\services\product\sku\StoreProductAttrResultServices;
  24. use app\services\product\sku\StoreProductAttrServices;
  25. use app\services\product\sku\StoreProductAttrValueServices;
  26. use app\services\activity\coupon\StoreCouponIssueServices;
  27. use app\services\product\category\StoreProductCategoryServices;
  28. use app\services\store\SystemStoreServices;
  29. use app\services\system\attachment\SystemAttachmentServices;
  30. use app\services\user\UserServices;
  31. use app\webscoket\SocketPush;
  32. use crmeb\exceptions\AdminException;
  33. use crmeb\traits\ServicesTrait;
  34. use think\exception\ValidateException;
  35. /**
  36. * Class StoreBranchProductServices
  37. * @package app\services\product\branch
  38. * @mixin StoreProductDao
  39. */
  40. class StoreBranchProductServices extends BaseServices
  41. {
  42. use ServicesTrait;
  43. /**
  44. * StoreBranchProductServices constructor.
  45. * @param StoreProductDao $dao
  46. */
  47. public function __construct(StoreProductDao $dao)
  48. {
  49. $this->dao = $dao;
  50. }
  51. /**
  52. * 获取平台商品ID
  53. * @param int $product_id
  54. * @param $productInfo
  55. * @return int
  56. */
  57. public function getStoreProductId(int $product_id, $productInfo = [])
  58. {
  59. $id = 0;
  60. if (!$product_id) {
  61. return $id;
  62. }
  63. if (!$productInfo) {
  64. $productInfo = $this->dao->get($product_id, ['id', 'pid']);
  65. }
  66. if ($productInfo) {
  67. //门店平台共享商品
  68. if ($productInfo['pid']) {
  69. $id = (int)$productInfo['pid'];
  70. } else {
  71. $id = $productInfo['id'];
  72. }
  73. }
  74. return $id;
  75. }
  76. /**
  77. * 平台商品ID:获取在门店该商品详情
  78. * @param int $uid
  79. * @param int $id
  80. * @param int $store_id
  81. * @return array|mixed
  82. * @throws \think\db\exception\DataNotFoundException
  83. * @throws \think\db\exception\DbException
  84. * @throws \think\db\exception\ModelNotFoundException
  85. */
  86. public function getStoreProductInfo(int $uid, int $id, int $store_id)
  87. {
  88. /** @var StoreProductServices $productServices */
  89. $productServices = app()->make(StoreProductServices::class);
  90. try {
  91. $productInfo = $productServices->getCacheProductInfo($id);
  92. } catch (\Throwable $e) {
  93. $productInfo = [];
  94. }
  95. if (!$productInfo) {
  96. return [];
  97. }
  98. if ($productInfo['type'] != 1 && $store_id) {//查询该门店商品
  99. $info = $this->dao->get(['is_del' => 0, 'is_show' => 1, 'is_verify' => 1, 'pid' => $id, 'type' => 1, 'relation_id' => $store_id], ['id']);
  100. if ($info) {
  101. $id = (int)$info['id'];
  102. }
  103. }
  104. return $productServices->productDetail($uid, $id);
  105. }
  106. /**
  107. * 批量获取平台商品ID
  108. * @param array $product_ids
  109. * @return array
  110. */
  111. public function getStoreProductIds(array $product_ids)
  112. {
  113. $result = [];
  114. if (!$product_ids) {
  115. return $result;
  116. }
  117. $productInfos = $this->dao->getColumn([['id', 'IN', $product_ids]], 'id,pid', 'id');
  118. if ($productInfos) {
  119. foreach ($productInfos as $key => $productInfo) {
  120. //门店平台共享商品
  121. if ($productInfo['pid']) {
  122. $id = (int)$productInfo['pid'];
  123. } else {
  124. $id = $productInfo['id'];
  125. }
  126. $result[$key] = $id;
  127. }
  128. }
  129. return $result;
  130. }
  131. /**
  132. * 根据商品ID,获取适用门店ids
  133. * @param int $product_id
  134. * @param int $type
  135. * @return array
  136. * @throws \think\db\exception\DataNotFoundException
  137. * @throws \think\db\exception\DbException
  138. * @throws \think\db\exception\ModelNotFoundException
  139. */
  140. public function getApplicableStoreIds(int $product_id, int $type = 0)
  141. {
  142. $ids = [];
  143. $productInfo = [];
  144. switch ($type) {
  145. case 0://商品
  146. $productInfo = $this->dao->getOne(['id' => $product_id], 'id,type,relation_id,applicable_type,applicable_store_id');
  147. break;
  148. case 1://秒杀商品
  149. /** @var StoreSeckillServices $seckillServices */
  150. $seckillServices = app()->make(StoreSeckillServices::class);
  151. $productInfo = $seckillServices->getOne(['id' => $product_id], 'id,applicable_type,applicable_store_id');
  152. break;
  153. }
  154. $productInfo['applicable_type'] = 1;
  155. if ($productInfo) {
  156. switch ($productInfo['applicable_type']) {
  157. case 0://仅平台
  158. break;
  159. case 1://所有门店 查询有商品的门店
  160. if ($type == 0) {
  161. if (!$productInfo['type']) {//平台商品
  162. $ids = $this->dao->getColumn(['pid' => $product_id, 'is_show' => 1, 'is_del' => 0, 'type' => 1], 'relation_id');
  163. } else if ($productInfo['type'] == 1) {//门店商品
  164. $ids = [$productInfo['relation_id']];
  165. }
  166. }
  167. break;
  168. case 2://部分门店
  169. $ids = is_array($productInfo['applicable_store_id']) ? $productInfo['applicable_store_id'] : explode(',', $productInfo['applicable_store_id']);
  170. break;
  171. default:
  172. break;
  173. }
  174. }
  175. return [$ids, $productInfo['applicable_type']];
  176. }
  177. /**
  178. * 收银台获取门店商品
  179. * @param array $where
  180. * @param int $uid
  181. * @param int $staff_id
  182. * @param int $tourist_uid
  183. * @return array
  184. * @throws \think\db\exception\DataNotFoundException
  185. * @throws \think\db\exception\DbException
  186. * @throws \think\db\exception\ModelNotFoundException
  187. */
  188. public function getCashierProductListV2(array $where, int $store_id, int $uid = 0, int $staff_id = 0, int $tourist_uid = 0)
  189. {
  190. $where['is_del'] = 0;
  191. $where['is_show'] = 1;
  192. $where['is_verify'] = 1;
  193. $where['type'] = 1;
  194. $where['relation_id'] = $store_id;
  195. [$page, $limit] = $this->getPageValue();
  196. $where['is_vip_product'] = 0;
  197. if ($uid) {
  198. /** @var UserServices $user */
  199. $user = app()->make(UserServices::class);
  200. $userInfo = $user->getUserCacheInfo($uid);
  201. $is_vip = $userInfo['is_money_level'] ?? 0;
  202. $where['is_vip_product'] = $is_vip ? -1 : 0;
  203. }
  204. //门店不展示卡密商品
  205. $where['product_type'] = [0, 2, 4];
  206. $list = $this->dao->getSearchList($where, $page, $limit, ['*'], 'sort desc,sales desc,id desc', []);
  207. $count = 0;
  208. if ($list) {
  209. $productIds = array_column($list, 'id');
  210. if ($uid || $tourist_uid) {
  211. if ($uid) {
  212. $tourist_uid = 0;
  213. }
  214. /** @var StoreCartServices $cartServices */
  215. $cartServices = app()->make(StoreCartServices::class);
  216. $cartNumList = $cartServices->productIdByCartNum($productIds, $uid, $staff_id, $tourist_uid, $store_id);
  217. $data = [];
  218. foreach ($cartNumList as $item) {
  219. $data[$item['product_id']][] = $item['cart_num'];
  220. }
  221. $newNumList = [];
  222. foreach ($data as $key => $item) {
  223. $newNumList[$key] = array_sum($item);
  224. }
  225. $cartNumList = $newNumList;
  226. } else {
  227. $cartNumList = [];
  228. }
  229. $product = ['image' => '', 'id' => 0, 'store_name' => '', 'spec_type' => 0, 'store_info' => '', 'keyword' => '', 'price' => 0, 'stock' => 0, 'sales' => 0];
  230. /** @var StoreProductServices $storeProductServices */
  231. $storeProductServices = app()->make(StoreProductServices::class);
  232. $list = $storeProductServices->getProduceOtherList($list, $uid, true);
  233. $list = $storeProductServices->getProductPromotions($list);
  234. /** @var StoreCouponIssueServices $couponServices */
  235. $couponServices = app()->make(StoreCouponIssueServices::class);
  236. /** @var StoreProductCategoryServices $storeCategoryService */
  237. $storeCategoryService = app()->make(StoreProductCategoryServices::class);
  238. /** @var StoreBrandServices $storeBrandServices */
  239. $storeBrandServices = app()->make(StoreBrandServices::class);
  240. $brands = $storeBrandServices->getColumn([], 'id,pid', 'id');
  241. foreach ($list as &$item) {
  242. $product = array_merge($product, array_intersect_key($item, $product));
  243. $item['product'] = $product;
  244. $item['product_id'] = $item['id'];
  245. $item['cart_num'] = $cartNumList[$item['id']] ?? 0;
  246. $item['branch_stock'] = $item['stock'];
  247. $cateId = $item['cate_id'];
  248. $cateId = explode(',', $cateId);
  249. $cateId = array_merge($cateId, $storeCategoryService->cateIdByPid($cateId));
  250. $cateId = array_diff($cateId, [0]);
  251. $brandId = [];
  252. if ($item['brand_id']) {
  253. $brandId = $brands[$item['brand_id']] ?? [];
  254. }
  255. //平台商品
  256. $coupons = [];
  257. if ($item['pid'] > 0) $coupons = $couponServices->getIssueCouponListNew($uid, ['product_id' => $item['id'], 'cate_id' => $cateId, 'brand_id' => $brandId], 'id,coupon_title,coupon_price,use_min_price', 0, 1, 'coupon_price desc,sort desc,id desc');
  258. $item['coupon'] = $coupons[0] ?? [];
  259. }
  260. }
  261. $count = $this->dao->getCount($where);
  262. $code = $where['store_name'] ?? '';
  263. $attrValue = $userInfo = null;
  264. if ($code) {
  265. /** @var StoreProductAttrValueServices $attrService */
  266. $attrService = app()->make(StoreProductAttrValueServices::class);
  267. $attrValueArr = $attrService->getColumn(['bar_code' => $code], '*', 'product_id');
  268. if ($attrValueArr) {
  269. $product_ids = array_unique(array_column($attrValueArr, 'product_id'));
  270. $product = $this->dao->get(['id' => $product_ids, 'type' => 1, 'relation_id' => $store_id, 'is_del' => 0, 'is_show' => 1, 'is_verify' => 1], ['id', 'type', 'relation_id']);
  271. if ($product) {
  272. $attrValue = $attrValueArr[$product['id']] ?? [];
  273. }
  274. }
  275. if (!$attrValue) {
  276. /** @var UserServices $userService */
  277. $userService = app()->make(UserServices::class);
  278. $userInfo = $userService->get(['bar_code' => $code]);
  279. if ($userInfo) {
  280. $userInfo = $userInfo->toArray();
  281. $list = [];
  282. $count = 0;
  283. }
  284. }
  285. }
  286. return compact('list', 'count', 'attrValue', 'userInfo');
  287. }
  288. /**
  289. * 获取商品详情
  290. * @param int $storeId
  291. * @param int $id
  292. * @param int $uid
  293. * @param int $touristUid
  294. * @return mixed
  295. * @throws \think\db\exception\DataNotFoundException
  296. * @throws \think\db\exception\DbException
  297. * @throws \think\db\exception\ModelNotFoundException
  298. */
  299. public function getProductDetail(int $storeId, int $id, int $uid, int $touristUid)
  300. {
  301. /** @var StoreProductServices $productService */
  302. $productService = app()->make(StoreProductServices::class);
  303. $storeInfo = $productService->getOne(['id' => $id, 'is_show' => 1, 'is_del' => 0], '*');
  304. if (!$storeInfo) {
  305. throw new ValidateException('商品不存在');
  306. } else {
  307. $storeInfo = $storeInfo->toArray();
  308. }
  309. $siteUrl = sys_config('site_url');
  310. $storeInfo['image'] = set_file_url($storeInfo['image'], $siteUrl);
  311. $storeInfo['image_base'] = set_file_url($storeInfo['image'], $siteUrl);
  312. $storeInfo['fsales'] = $storeInfo['ficti'] + $storeInfo['sales'];
  313. /** @var StoreProductAttrServices $storeProductAttrServices */
  314. $storeProductAttrServices = app()->make(StoreProductAttrServices::class);
  315. $storeProductAttrServices->setItem('touristUid', $touristUid);
  316. [$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetail($id, $uid, 1, 0, 0, $storeInfo);
  317. $storeProductAttrServices->reset();
  318. if (!$storeInfo['spec_type']) {
  319. $productAttr = [];
  320. $productValue = [];
  321. }
  322. $data['productAttr'] = $productAttr;
  323. $data['productValue'] = $productValue;
  324. $data['storeInfo'] = $storeInfo;
  325. return $data;
  326. }
  327. /**
  328. * 保存或者修改门店数据
  329. * @param int $id
  330. * @param int $storeId
  331. * @param int $stock
  332. * @param array $data
  333. * @return bool
  334. * @throws \think\db\exception\DataNotFoundException
  335. * @throws \think\db\exception\DbException
  336. * @throws \think\db\exception\ModelNotFoundException
  337. */
  338. public function saveStoreProduct(int $id, int $storeId, int $stock, array $data = [])
  339. {
  340. /** @var StoreProductServices $service */
  341. $service = app()->make(StoreProductServices::class);
  342. $productData = $service->get($id, ['store_name', 'image', 'sort', 'store_info', 'keyword', 'bar_code', 'cate_id', 'is_show']);
  343. if (!$productData) {
  344. throw new ValidateException('商品不穿在');
  345. }
  346. $productData = $productData->toArray();
  347. $productInfo = $this->dao->get(['product_id' => $id, 'store_id' => $storeId]);
  348. if ($productInfo) {
  349. $productInfo->label_id = isset($data['label_id']) ? implode(',', $data['label_id']) : '';
  350. $productInfo->is_show = $data['is_show'] ?? 1;
  351. $productInfo->stock = $stock;
  352. $productInfo->image = $productData['image'];
  353. $productInfo->sort = $productData['sort'];
  354. $productInfo->store_name = $productData['store_name'];
  355. $productInfo->store_info = $productData['store_info'];
  356. $productInfo->keyword = $productData['keyword'];
  357. $productInfo->bar_code = $productData['bar_code'];
  358. $productInfo->cate_id = $productData['cate_id'];
  359. $productInfo->save();
  360. } else {
  361. $product = [];
  362. $product['product_id'] = $id;
  363. $product['label_id'] = isset($data['label_id']) ? implode(',', $data['label_id']) : '';
  364. $product['is_show'] = $data['is_show'] ?? 1;
  365. $product['store_id'] = $storeId;
  366. $product['stock'] = $stock;
  367. $product['image'] = $productData['image'];
  368. $product['sort'] = $productData['sort'];
  369. $product['store_name'] = $productData['store_name'];
  370. $product['store_info'] = $productData['store_info'];
  371. $product['keyword'] = $productData['keyword'];
  372. $product['bar_code'] = $productData['bar_code'];
  373. $product['cate_id'] = $productData['cate_id'];
  374. $product['add_time'] = time();
  375. $this->dao->save($product);
  376. }
  377. return true;
  378. }
  379. /**
  380. * 平台商品在门店是否存在
  381. * @param int $productId
  382. * @param int $storeId
  383. * @return array|\think\Model|null
  384. * @throws \think\db\exception\DataNotFoundException
  385. * @throws \think\db\exception\DbException
  386. * @throws \think\db\exception\ModelNotFoundException
  387. */
  388. public function isValidStoreProduct(int $productId, int $storeId)
  389. {
  390. $info = $this->dao->getOne(['id' => $productId, 'type' => 1, 'relation_id' => $storeId, 'is_del' => 0, 'is_show' => 1]);
  391. if ($info) {
  392. return $info;
  393. }
  394. return $this->dao->getOne(['pid' => $productId, 'type' => 1, 'relation_id' => $storeId, 'is_del' => 0, 'is_show' => 1]);
  395. }
  396. /**
  397. * 获取商品库存
  398. * @param int $productId
  399. * @param string $uniqueId
  400. * @return int|mixed
  401. */
  402. public function getProductStock(int $productId, int $storeId, string $uniqueId = '')
  403. {
  404. /** @var StoreProductAttrValueServices $StoreProductAttrValue */
  405. $StoreProductAttrValue = app()->make(StoreProductAttrValueServices::class);
  406. return $uniqueId == '' ?
  407. $this->dao->value(['product_id' => $productId], 'stock') ?: 0
  408. : $StoreProductAttrValue->uniqueByStock($uniqueId);
  409. }
  410. /**
  411. * 回退|扣除,门店、平台原商品库存
  412. * @param $order
  413. * @param array $cartInfo
  414. * @param int $platDec
  415. * @param int $storeDec
  416. * @return bool
  417. */
  418. public function regressionBranchProductStock($order, $cartInfo = [], int $platDec = 0, int $storeDec = 0, int $store_id = 0)
  419. {
  420. if (!$order || !$cartInfo) return true;
  421. /** @var StoreProductServices $services */
  422. $services = app()->make(StoreProductServices::class);
  423. /** @var StoreProductAttrValueServices $skuValueServices */
  424. $skuValueServices = app()->make(StoreProductAttrValueServices::class);
  425. $activity_id = (int)$order['activity_id'];
  426. $store_id = $store_id ? $store_id : ((int)$order['store_id'] ?? 0);
  427. $res = true;
  428. /** @var StoreBranchProductServices $branchServices */
  429. $branchServices = app()->make(StoreBranchProductServices::class);
  430. try {
  431. foreach ($cartInfo as $cart) {
  432. $productInfo = $cart['productInfo'] ?? [];
  433. if (!$productInfo) {
  434. continue;
  435. }
  436. $type = $productInfo['type'] ?? 0;
  437. //增库存减销量
  438. $unique = isset($cart['productInfo']['attrInfo']) ? $cart['productInfo']['attrInfo']['unique'] : '';
  439. $cart_num = (int)$cart['cart_num'];
  440. $product_id = (int)$cart['product_id'];
  441. //原商品sku
  442. $suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $product_id, 'type' => 0], 'suk');
  443. if ($type == 1) {//门店
  444. $product_id = $productInfo['pid'] ?? $productInfo['id'];
  445. }
  446. //查出门店该商品ID,unique
  447. if ($store_id) {
  448. $storeProduct = $branchServices->isValidStoreProduct((int)$product_id, $store_id);
  449. if (!$storeProduct) {
  450. return false;
  451. }
  452. $product_id = $storeProduct['id'];
  453. }
  454. switch ($order['type']) {
  455. case 0://普通
  456. case 6://预售
  457. $productUnique = $unique;
  458. if ($store_id) {
  459. $productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
  460. }
  461. break;
  462. case 1://秒杀
  463. case 2://砍价
  464. case 3://拼团
  465. case 5://套餐
  466. $suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $activity_id, 'type' => $order['type']], 'suk');
  467. $productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
  468. break;
  469. default:
  470. $productUnique = $unique;
  471. break;
  472. }
  473. switch ($platDec) {
  474. case -1://不执行
  475. break;
  476. case 0://减销量、加库存
  477. $res = $res && $services->incProductStock($cart_num, $product_id, $productUnique);
  478. break;
  479. case 1://增加销量、减库存
  480. $res = $res && $services->decProductStock($cart_num, $product_id, $productUnique);
  481. break;
  482. }
  483. switch ($storeDec) {
  484. case -1://不执行
  485. break;
  486. case 0://减销量、加库存
  487. $res = $res && $this->updataDecStock($cart_num, $product_id, $store_id, $productUnique, false);
  488. break;
  489. case 1://增加销量、减库存
  490. $res = $res && $this->updataDecStock($cart_num, $product_id, $store_id, $productUnique);
  491. break;
  492. }
  493. }
  494. } catch (\Throwable $e) {
  495. throw new ValidateException('库存不足!');
  496. }
  497. return $res;
  498. }
  499. /**
  500. * 加库存,减销量
  501. * @param $num
  502. * @param $productId
  503. * @param string $unique
  504. * @return bool
  505. */
  506. public function incProductStock(array $cartInfo, int $storeId)
  507. {
  508. $res = true;
  509. foreach ($cartInfo as $cart) {
  510. $unique = isset($cart['productInfo']['attrInfo']) ? $cart['productInfo']['attrInfo']['unique'] : '';
  511. $res = $res && $this->updataDecStock((int)$cart['cart_num'], (int)$cart['productInfo']['id'], $storeId, $unique, false);
  512. }
  513. return $res;
  514. }
  515. /**
  516. * 修改库存
  517. * @param array $cartInfo
  518. * @param int $storeId
  519. * @param bool $dec
  520. * @return bool
  521. */
  522. public function decProductStock(array $cartInfo, int $storeId, bool $dec = true)
  523. {
  524. $res = true;
  525. foreach ($cartInfo as $cart) {
  526. $unique = isset($cart['productInfo']['attrInfo']) ? $cart['productInfo']['attrInfo']['unique'] : '';
  527. $res = $res && $this->updataDecStock((int)$cart['cart_num'], (int)$cart['productInfo']['id'], $storeId, $unique, $dec);
  528. }
  529. return $res;
  530. }
  531. /**
  532. * 修改库存
  533. * @param int $num
  534. * @param int $productId
  535. * @param int $storeId
  536. * @param $unique
  537. * @param bool $dec
  538. * @return bool
  539. */
  540. public function updataDecStock(int $num, int $productId, int $storeId, $unique, bool $dec = true)
  541. {
  542. /** @var StoreProductAttrValueServices $skuValueServices */
  543. $skuValueServices = app()->make(StoreProductAttrValueServices::class);
  544. //原商品sku
  545. $suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $productId, 'type' => 0], 'suk');
  546. //查询门店商品
  547. $info = $this->isValidStoreProduct($productId, $storeId);
  548. $res = true;
  549. $storeProductId = $info['id'] ?? 0;
  550. if ($productId && $storeProductId != $productId) {
  551. $productId = $storeProductId;
  552. //门店商品sku
  553. $unique = $skuValueServices->value(['suk' => $suk, 'product_id' => $productId, 'type' => 0], 'unique');
  554. }
  555. if ($dec) {
  556. if ($unique) {
  557. $res = $res && $skuValueServices->decProductAttrStock($productId, $unique, $num, 0);
  558. }
  559. $res = $res && $this->dao->decStockIncSales(['id' => $productId], $num);
  560. } else {
  561. if ($unique) {
  562. $res = $res && $skuValueServices->incProductAttrStock($productId, $unique, $num, 0);
  563. }
  564. $res = $res && $this->dao->incStockDecSales(['id' => $productId], $num);
  565. }
  566. return $res;
  567. }
  568. /**
  569. * 上下架
  570. * @param int $store_id
  571. * @param int $id
  572. * @param int $is_show
  573. * @throws \think\db\exception\DataNotFoundException
  574. * @throws \think\db\exception\DbException
  575. * @throws \think\db\exception\ModelNotFoundException
  576. */
  577. public function setShow(int $store_id, int $id, int $is_show)
  578. {
  579. $info = $this->dao->get($id);
  580. if (!$info) {
  581. throw new AdminException('操作失败!');
  582. }
  583. //平台统一商品
  584. if ($info['pid']) {
  585. $productInfo = $this->dao->get($info['pid']);
  586. if ($is_show && !$productInfo['is_show']) {
  587. throw new AdminException('平台该商品暂未上架!');
  588. }
  589. }
  590. /** @var StoreCartServices $cartService */
  591. $cartService = app()->make(StoreCartServices::class);
  592. $cartService->batchUpdate([$id], ['status' => $is_show], 'product_id');
  593. $update = ['is_show' => $is_show];
  594. if ($is_show) {//手动上架 清空定时下架状态
  595. if ($info['is_verify'] != 1) {//验证商品是否审核通过
  596. throw new AdminException('该商品暂未审核通过');
  597. }
  598. $update['auto_off_time'] = 0;
  599. }
  600. $res = $this->update($info['id'], $update);
  601. /** @var StoreProductRelationServices $storeProductRelationServices */
  602. $storeProductRelationServices = app()->make(StoreProductRelationServices::class);
  603. $storeProductRelationServices->setShow([$id], (int)$is_show);
  604. if (!$res) throw new AdminException('操作失败!');
  605. }
  606. /**
  607. * 门店同步库存
  608. * @param $ids
  609. * @param $storeId
  610. * @return mixed
  611. */
  612. public function synchStocks($ids, $storeId)
  613. {
  614. /** @var StoreProductServices $productServices */
  615. $productServices = app()->make(StoreProductServices::class);
  616. /** @var StoreProductAttrValueServices $attrValueServices */
  617. $attrValueServices = app()->make(StoreProductAttrValueServices::class);
  618. /** @var StoreBranchProductAttrValueServices $services */
  619. $branchAttrValueServices = app()->make(StoreBranchProductAttrValueServices::class);
  620. $productAllData = $productServices->getColumn([['id', 'in', $ids]], 'id,image,store_name,store_info,keyword,bar_code,cate_id,stock,label_id', 'id');
  621. $productBranchData = $this->dao->getColumn([['product_id', 'in', $ids], ['store_id', '=', $storeId]], 'product_id');
  622. $allData = $attrValueServices->getColumn([['product_id', 'in', $ids], ['type', '=', 0]], 'product_id,unique,stock,bar_code', 'unique');
  623. $branchData = $branchAttrValueServices->getColumn([['product_id', 'in', $ids], ['store_id', '=', $storeId]], 'unique');
  624. return $this->transaction(function () use ($allData, $branchData, $productAllData, $productBranchData, $storeId, $branchAttrValueServices) {
  625. $data = [];
  626. $res = true;
  627. $datas = [];
  628. foreach ($productAllData as $keys => $items) {
  629. if (in_array($keys, $productBranchData)) {
  630. $res = $res && $this->dao->update(['product_id' => $keys, 'store_id' => $storeId], [
  631. 'stock' => $items['stock'],
  632. 'image' => $items['image'],
  633. 'store_name' => $items['store_name'],
  634. 'store_info' => $items['store_info'],
  635. 'keyword' => $items['keyword'],
  636. 'bar_code' => $items['bar_code'],
  637. 'cate_id' => $items['cate_id'],
  638. 'label_id' => $items['label_id'],
  639. ]);
  640. } else {
  641. $datas[] = [
  642. 'product_id' => $items['id'],
  643. 'image' => $items['image'],
  644. 'store_name' => $items['store_name'],
  645. 'store_info' => $items['store_info'],
  646. 'keyword' => $items['keyword'],
  647. 'bar_code' => $items['bar_code'],
  648. 'cate_id' => $items['cate_id'],
  649. 'label_id' => $items['label_id'],
  650. 'store_id' => $storeId,
  651. 'stock' => $items['stock'],
  652. 'add_time' => time()
  653. ];
  654. }
  655. }
  656. if ($datas) {
  657. $res = $res && $this->dao->saveAll($datas);
  658. }
  659. foreach ($allData as $key => $item) {
  660. if (in_array($key, $branchData)) {
  661. $res = $res && $branchAttrValueServices->update(['unique' => $key, 'store_id' => $storeId], ['stock' => $item['stock']]);
  662. } else {
  663. $data[] = [
  664. 'product_id' => $item['product_id'],
  665. 'store_id' => $storeId,
  666. 'unique' => $item['unique'],
  667. 'stock' => $item['stock'],
  668. 'bar_code' => $item['bar_code']
  669. ];
  670. }
  671. }
  672. if ($data) {
  673. $res = $res && $branchAttrValueServices->saveAll($data);
  674. }
  675. if (!$res) throw new ValidateException('同步库存失败!');
  676. return $res;
  677. });
  678. }
  679. /**
  680. * 同步一个商品到多个门店
  681. * @param int $product_id
  682. * @param array $store_ids
  683. * @return bool
  684. */
  685. public function syncProductToStores(int $product_id, int $applicable_type, array $store_ids = [])
  686. {
  687. if (!$product_id) {
  688. return true;
  689. }
  690. $where = ['is_del' => 0];
  691. //不传门店ID,默认同步至所有未删除门店
  692. if ($store_ids) {
  693. $where['id'] = $store_ids;
  694. }
  695. /** @var SystemStoreServices $storeServices */
  696. $storeServices = app()->make(SystemStoreServices::class);
  697. $stores = $storeServices->getList($where, ['id']);
  698. if (!$stores) {
  699. return true;
  700. }
  701. $ids = array_column($stores, 'id');
  702. //查询目前商品已经同步的门店
  703. $alreadyIds = $this->dao->getColumn(['type' => 1, 'pid' => $product_id], 'relation_id');
  704. switch ($applicable_type) {
  705. case 0://仅平台
  706. $ids = [];
  707. $this->dao->update(['type' => 1, 'pid' => $product_id], ['is_verify' => -1]);
  708. break;
  709. case 1://全部门店
  710. break;
  711. case 2://部分门店
  712. $delIds = array_merge(array_diff($alreadyIds, $ids));
  713. if ($delIds) $this->dao->update(['type' => 1, 'pid' => $product_id, 'relation_id' => $delIds], ['is_verify' => -1]);
  714. break;
  715. }
  716. foreach ($ids as $store_id) {
  717. ProductSyncStoreJob::dispatchDo('syncProduct', [$product_id, $store_id]);
  718. }
  719. return true;
  720. }
  721. /**
  722. * 同步门店商品
  723. * @param int $product_id
  724. * @param int $store_id
  725. * @return bool
  726. * @throws \think\db\exception\DataNotFoundException
  727. * @throws \think\db\exception\DbException
  728. * @throws \think\db\exception\ModelNotFoundException
  729. */
  730. public function syncProduct(int $product_id, int $store_id)
  731. {
  732. if (!$product_id || !$store_id) {
  733. return true;
  734. }
  735. /** @var StoreProductServices $productServices */
  736. $productServices = app()->make(StoreProductServices::class);
  737. //同步正常普通商品、次卡商品
  738. $productInfo = $productServices->get(['type' => 0, 'product_type' => [0, 4], 'id' => $product_id]);
  739. if (!$productInfo) {
  740. return true;
  741. }
  742. $productInfo = $productInfo->toArray();
  743. $productInfo['pid'] = $productInfo['id'];
  744. $productInfo['slider_image'] = json_encode($productInfo['slider_image']);
  745. $productInfo['custom_form'] = json_encode($productInfo['custom_form']);
  746. $productInfo['specs'] = is_array($productInfo['specs']) ? json_encode($productInfo['specs']) : $productInfo['specs'];
  747. unset($productInfo['id'], $productInfo['sales']);
  748. //关联补充信息
  749. $relationData = [];
  750. $relationData['cate_id'] = ($productInfo['cate_id'] ?? []) && is_string($productInfo['cate_id']) ? explode(',', $productInfo['cate_id']) : ($productInfo['cate_id'] ?? []);
  751. $relationData['brand_id'] = ($productInfo['brand_id'] ?? []) && is_string($productInfo['brand_id']) ? explode(',', $productInfo['brand_id']) : ($productInfo['brand_id'] ?? []);
  752. $relationData['store_label_id'] = ($productInfo['store_label_id'] ?? []) && is_string($productInfo['store_label_id']) ? explode(',', $productInfo['store_label_id']) : ($productInfo['store_label_id'] ?? []);
  753. $relationData['label_id'] = ($productInfo['label_id'] ?? []) && is_string($productInfo['label_id']) ? explode(',', $productInfo['label_id']) : ($productInfo['label_id'] ?? []);
  754. $relationData['ensure_id'] = ($productInfo['ensure_id'] ?? []) && is_string($productInfo['ensure_id']) ? explode(',', $productInfo['ensure_id']) : ($productInfo['ensure_id'] ?? []);
  755. $relationData['specs_id'] = ($productInfo['specs_id'] ?? []) && is_string($productInfo['specs_id']) ? explode(',', $productInfo['specs_id']) : ($productInfo['specs_id'] ?? []);
  756. $relationData['coupon_ids'] = ($productInfo['coupon_ids'] ?? []) && is_string($productInfo['coupon_ids']) ? explode(',', $productInfo['coupon_ids']) : ($productInfo['coupon_ids'] ?? []);
  757. $where = ['product_id' => $product_id, 'type' => 0];
  758. /** @var StoreProductAttrServices $productAttrServices */
  759. $productAttrServices = app()->make(StoreProductAttrServices::class);
  760. $attrInfo = $productAttrServices->getProductAttr($where);
  761. /** @var StoreProductAttrResultServices $productAttrResultServices */
  762. $productAttrResultServices = app()->make(StoreProductAttrResultServices::class);
  763. $attrResult = $productAttrResultServices->getResult($where);
  764. /** @var StoreProductAttrValueServices $productAttrValueServices */
  765. $productAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  766. $attrValue = $productAttrValueServices->getList($where);
  767. /** @var StoreDescriptionServices $productDescriptionServices */
  768. $productDescriptionServices = app()->make(StoreDescriptionServices::class);
  769. $description = $productDescriptionServices->getDescription($where);
  770. $description = $description ?: '';
  771. $branchProductInfo = $this->dao->get(['pid' => $product_id, 'type' => 1, 'relation_id' => $store_id]);
  772. //存在修改
  773. [$id, $is_new] = $productServices->transaction(function () use (
  774. $branchProductInfo, $productInfo, $store_id, $attrInfo, $attrResult, $attrValue, $description,
  775. $productServices, $productAttrServices, $productAttrResultServices, $productAttrValueServices, $productDescriptionServices
  776. ) {
  777. $productInfo['type'] = 1;
  778. $productInfo['is_verify'] = 1;
  779. $productInfo['relation_id'] = $store_id;
  780. if ($branchProductInfo) {
  781. $id = $branchProductInfo['id'];
  782. $productInfo['is_verify'] = 1;
  783. unset($productInfo['stock'], $productInfo['is_show']);
  784. $res = $this->dao->update($id, $productInfo);
  785. if (!$res) throw new ValidateException('商品添加失败');
  786. $updateSuks = array_column($attrValue, 'suk');
  787. $oldSuks = [];
  788. $oldAttrValue = $productAttrValueServices->getSkuArray(['product_id' => $id, 'type' => 0], '*', 'suk');
  789. if ($oldAttrValue) $oldSuks = array_column($oldAttrValue, 'suk');
  790. $delSuks = array_merge(array_diff($oldSuks, $updateSuks));
  791. $dataAll = [];
  792. $res1 = $res2 = $res3 = true;
  793. foreach ($attrValue as $item) {
  794. unset($item['id'], $item['stock'], $item['sales']);
  795. $item['product_id'] = $id;
  796. if ($oldSuks && in_array($item['suk'], $oldSuks) && isset($oldAttrValue[$item['suk']])) {
  797. $attrId = $oldAttrValue[$item['suk']]['id'];
  798. unset($item['suk'], $item['unique']);
  799. $res1 = $res1 && $productAttrValueServices->update($attrId, $item);
  800. } else {
  801. $item['unique'] = $productAttrServices->createAttrUnique($id, $item['suk']);
  802. $dataAll[] = $item;
  803. }
  804. }
  805. if ($delSuks) {
  806. $res2 = $productAttrValueServices->del($id, 0, $delSuks);
  807. }
  808. if ($dataAll) {
  809. $res3 = $productAttrValueServices->saveAll($dataAll);
  810. }
  811. if (!$res1 || !$res2 || !$res3) {
  812. throw new AdminException('商品规格信息保存失败');
  813. }
  814. $is_new = 0;
  815. } else {// 新增 保留平台库存到门店
  816. $res = $this->dao->save($productInfo);
  817. if (!$res) throw new ValidateException('商品添加失败');
  818. $id = (int)$res->id;
  819. if ($attrValue) {
  820. foreach ($attrValue as &$value) {
  821. unset($value['id'], $value['sales']);
  822. $value['product_id'] = $id;
  823. $value['unique'] = $productAttrServices->createAttrUnique($id, $value['suk']);
  824. }
  825. $productAttrValueServices->saveAll($attrValue);
  826. }
  827. $is_new = 1;
  828. }
  829. if ($attrInfo) {
  830. foreach ($attrInfo as &$attr) {
  831. unset($attr['id']);
  832. $attr['product_id'] = $id;
  833. }
  834. $productAttrServices->setAttr($attrInfo, $id, 0);
  835. }
  836. if ($attrResult) $productAttrResultServices->setResult($attrResult, $id, 0);
  837. $productDescriptionServices->saveDescription($id, $description, 0);
  838. return [$id, $is_new];
  839. });
  840. //商品创建事件
  841. event('product.create', [$id, $productInfo, [], $is_new, [], $description, 1, $relationData]);
  842. $this->dao->cacheTag()->clear();
  843. $productAttrServices->cacheTag()->clear();
  844. return true;
  845. }
  846. /**
  847. * 删除门店、供应商同步删除商品
  848. * @param array $where
  849. * @param int $type
  850. * @param int $relation_id
  851. * @return bool
  852. */
  853. public function deleteProducts(array $where = [], int $type = 0, int $relation_id = 0)
  854. {
  855. $where['type'] = $type;
  856. $where['relation_id'] = $relation_id;
  857. $productIds = $this->dao->getColumn($where, 'id');
  858. if ($productIds) {
  859. /** @var StoreProductAttrServices $productAttrServices */
  860. $productAttrServices = app()->make(StoreProductAttrServices::class);
  861. /** @var StoreProductAttrResultServices $productAttrResultServices */
  862. $productAttrResultServices = app()->make(StoreProductAttrResultServices::class);
  863. /** @var StoreProductAttrValueServices $productAttrValueServices */
  864. $productAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  865. /** @var StoreDescriptionServices $productDescriptionServices */
  866. $productDescriptionServices = app()->make(StoreDescriptionServices::class);
  867. /** @var StoreProductRelationServices $productRelationServices */
  868. $productRelationServices = app()->make(StoreProductRelationServices::class);
  869. /** @var StoreProductReplyServices $productReplyServices */
  870. $productReplyServices = app()->make(StoreProductReplyServices::class);
  871. /** @var StoreProductReplyCommentServices $productReplyCommentServices */
  872. $productReplyCommentServices = app()->make(StoreProductReplyCommentServices::class);
  873. $idsArr = array_chunk($productIds, 100);
  874. foreach ($idsArr as $ids) {
  875. $productAttrServices->delete(['product_id' => $ids, 'type' => 0]);
  876. $productAttrResultServices->delete(['product_id' => $ids, 'type' => 0]);
  877. $productAttrValueServices->delete(['product_id' => $ids, 'type' => 0]);
  878. $productDescriptionServices->delete(['product_id' => $ids, 'type' => 0]);
  879. $productRelationServices->delete(['product_id' => $ids]);
  880. $this->dao->delete(['id' => $ids]);
  881. $replyIds = $productReplyServices->getColumn([['product_id', 'IN', $ids]], 'id');
  882. $replyIdsArr = array_chunk($replyIds, 100);
  883. foreach ($replyIdsArr as $rids) {
  884. $productReplyCommentServices->delete(['reply_id' => $rids]);
  885. $productReplyServices->delete(['id' => $rids]);
  886. }
  887. event('product.delete', [$ids]);
  888. }
  889. $this->dao->cacheTag()->clear();
  890. $productAttrServices->cacheTag()->clear();
  891. }
  892. return true;
  893. }
  894. }