StoreProductServices.php 122 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657
  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\product;
  12. use app\dao\product\product\StoreProductDao;
  13. use app\jobs\product\ProductStockTips;
  14. use app\model\product\product\StoreProduct;
  15. use app\Request;
  16. use app\services\activity\discounts\StoreDiscountsProductsServices;
  17. use app\services\activity\bargain\StoreBargainServices;
  18. use app\services\activity\combination\StoreCombinationServices;
  19. use app\services\activity\promotions\StorePromotionsServices;
  20. use app\services\activity\seckill\StoreSeckillServices;
  21. use app\services\activity\seckill\StoreSeckillTimeServices;
  22. use app\services\BaseServices;
  23. use app\services\activity\coupon\StoreCouponIssueServices;
  24. use app\services\diy\DiyServices;
  25. use app\services\order\StoreCartServices;
  26. use app\services\product\category\StoreProductCategoryServices;
  27. use app\services\product\branch\StoreBranchProductServices;
  28. use app\services\product\brand\StoreBrandServices;
  29. use app\services\other\queue\QueueServices;
  30. use app\services\product\ensure\StoreProductEnsureServices;
  31. use app\services\product\label\StoreProductLabelServices;
  32. use app\services\product\sku\StoreProductAttrResultServices;
  33. use app\services\product\sku\StoreProductAttrServices;
  34. use app\services\product\sku\StoreProductAttrValueServices;
  35. use app\services\product\sku\StoreProductRuleServices;
  36. use app\services\product\shipping\ShippingTemplatesServices;
  37. use app\services\product\sku\StoreProductVirtualServices;
  38. use app\services\product\specs\StoreProductSpecsServices;
  39. use app\services\store\SystemStoreServices;
  40. use app\services\supplier\SystemSupplierServices;
  41. use app\services\system\form\SystemFormServices;
  42. use app\services\user\label\UserLabelServices;
  43. use app\services\user\level\SystemUserLevelServices;
  44. use app\services\user\member\MemberCardServices;
  45. use app\services\activity\collage\UserCollagePartakeServices;
  46. use app\services\user\UserRelationServices;
  47. use app\services\user\UserSearchServices;
  48. use app\services\user\UserServices;
  49. use app\jobs\product\ProductLogJob;
  50. use crmeb\exceptions\AdminException;
  51. use crmeb\services\FormBuilder as Form;
  52. use crmeb\services\SystemConfigService;
  53. use crmeb\traits\ServicesTrait;
  54. use crmeb\traits\OptionTrait;
  55. use think\exception\ValidateException;
  56. use think\facade\Config;
  57. use think\facade\Route as Url;
  58. /**
  59. * Class StoreProductService
  60. * @package app\services\product\product
  61. * @mixin StoreProductDao
  62. */
  63. class StoreProductServices extends BaseServices
  64. {
  65. use OptionTrait, ServicesTrait;
  66. /**
  67. * StoreProductServices constructor.
  68. * @param StoreProductDao $dao
  69. */
  70. public function __construct(StoreProductDao $dao)
  71. {
  72. $this->dao = $dao;
  73. }
  74. /**
  75. * 获取顶部标签
  76. * @param int $store_id
  77. * @param array $where
  78. * @return array[]
  79. */
  80. public function getHeader(int $store_id = 0, array $where = [])
  81. {
  82. if ($store_id || (isset($where['store_id']) && $where['store_id'])) {
  83. $where['type'] = 1;
  84. $where['relation_id'] = $store_id ?: $where['store_id'];
  85. unset($where['store_id']);
  86. } elseif (isset($where['supplier_id']) && $where['supplier_id']) {
  87. $where['type'] = 2;
  88. $where['relation_id'] = $where['supplier_id'];
  89. unset($where['supplier_id']);
  90. } else {
  91. $where['pid'] = 0;
  92. }
  93. //出售中的商品
  94. $onsale = $this->dao->getCount(['status' => 1] + $where);
  95. //已经售馨商品
  96. $outofstock = $this->dao->getCount(['status' => 4] + $where);
  97. //警戒库存商品
  98. $store_stock = sys_config('store_stock', 0);
  99. $policeforce = $this->dao->getCount(['status' => 5, 'store_stock' => $store_stock > 0 ? $store_stock : 2] + $where);
  100. //仓库中的商品
  101. $forsale = $this->dao->getCount(['status' => 2] + $where);
  102. //回收站商品
  103. $delete = $this->dao->getCount(['status' => 6] + $where);
  104. //待审核商品
  105. $unVerify = $this->dao->getCount(['status' => 0] + $where);
  106. //审核未通过商品
  107. $refuseVerify = $this->dao->getCount(['status' => -1] + $where);
  108. //强制下架商品
  109. $removeVerify = $this->dao->getCount(['status' => -2] + $where);
  110. return [
  111. ['type' => 1, 'name' => '销售中', 'count' => $onsale],
  112. ['type' => 2, 'name' => '仓库中', 'count' => $forsale],
  113. ['type' => 4, 'name' => '已售罄', 'count' => $outofstock],
  114. ['type' => 5, 'name' => '库存预警', 'count' => $policeforce],
  115. ['type' => 6, 'name' => '回收站', 'count' => $delete],
  116. ['type' => 0, 'name' => '待审核', 'count' => $unVerify],
  117. ['type' => -1, 'name' => '审核未通过', 'count' => $refuseVerify],
  118. ['type' => -2, 'name' => '强制下架', 'count' => $removeVerify]
  119. ];
  120. }
  121. /**
  122. * 获取列表
  123. * @param $where
  124. * @return array
  125. */
  126. public function getList(array $where)
  127. {
  128. $store_stock = sys_config('store_stock', 0);
  129. $where['store_stock'] = $store_stock > 0 ? $store_stock : 2;
  130. [$page, $limit] = $this->getPageValue();
  131. $order_string = '';
  132. $order_arr = ['asc', 'desc'];
  133. if (isset($where['sales']) && in_array($where['sales'], $order_arr)) {
  134. $order_string = 'sales ' . $where['sales'];
  135. }
  136. //门店不展示卡密商品
  137. $count = $this->dao->getCount($where);
  138. //页面搜索,第二页之后没有结果,强制返回第一页数据
  139. if ($count <= $limit && $page !== 1) {
  140. $page = 1;
  141. }
  142. $list = $this->dao->getList($where, $page, $limit, $order_string);
  143. if ($list) {
  144. $cateIds = implode(',', array_column($list, 'cate_id'));
  145. /** @var StoreProductCategoryServices $categoryService */
  146. $categoryService = app()->make(StoreProductCategoryServices::class);
  147. $cateList = $categoryService->getCateParentAndChildName($cateIds);
  148. $supplierIds = $storeIds = [];
  149. foreach ($list as $value) {
  150. switch ($value['type']) {
  151. case 0:
  152. break;
  153. case 1://门店
  154. $storeIds[] = $value['relation_id'];
  155. break;
  156. case 2://供应商
  157. $supplierIds[] = $value['relation_id'];
  158. break;
  159. }
  160. }
  161. $supplierIds = array_unique($supplierIds);
  162. $storeIds = array_unique($storeIds);
  163. $supplierList = $storeList = [];
  164. if ($supplierIds) {
  165. /** @var SystemSupplierServices $supplierServices */
  166. $supplierServices = app()->make(SystemSupplierServices::class);
  167. $supplierList = $supplierServices->getColumn([['id', 'in', $supplierIds], ['is_del', '=', 0]], 'id,supplier_name', 'id');
  168. }
  169. if ($storeIds) {
  170. /** @var SystemStoreServices $storeServices */
  171. $storeServices = app()->make(SystemStoreServices::class);
  172. $storeList = $storeServices->getColumn([['id', 'in', $storeIds], ['is_del', '=', 0]], 'id,name', 'id');
  173. }
  174. foreach ($list as &$item) {
  175. $item['branch_sales'] = $item['sales'] ?? 0;
  176. $item['branch_stock'] = $item['stock'] ?? 0;
  177. $item['is_show'] = $item['branch_is_show'] ?? $item['is_show'];
  178. $item['cate_name'] = '';
  179. if (isset($item['cate_id']) && $item['cate_id']) {
  180. $cate_name = $categoryService->getCateName(explode(',', $item['cate_id']), $cateList);
  181. if ($cate_name) {
  182. $item['cate_name'] = is_array($cate_name) ? implode(',', $cate_name) : '';
  183. }
  184. }
  185. $item['stock_attr'] = $item['stock'] > 0;//库存
  186. $item['plate_name'] = '平台';
  187. switch ($item['type']) {
  188. case 0:
  189. $item['plate_name'] = '平台';
  190. break;
  191. case 1://门店
  192. $item['plate_name'] = '门店:' . ($storeList[$item['relation_id']]['name'] ?? '');
  193. break;
  194. case 2://供应商
  195. $item['plate_name'] = '供应商:' . ($supplierList[$item['relation_id']]['supplier_name'] ?? '');
  196. break;
  197. }
  198. }
  199. }
  200. return compact('list', 'count');
  201. }
  202. /**
  203. * 设置商品上下架
  204. * @param $ids
  205. * @param $is_show
  206. */
  207. public function setShow(array $ids, int $is_show)
  208. {
  209. if ($is_show == 0) {
  210. //下架检测是否有参与活动商品
  211. $this->checkActivity($ids);
  212. } else {
  213. $count = $this->dao->getCount(['ids' => $ids, 'is_del' => 1]);
  214. if ($count) throw new AdminException('回收站商品无法直接上架,请先恢复商品');
  215. }
  216. /** @var StoreCartServices $cartService */
  217. $cartService = app()->make(StoreCartServices::class);
  218. $cartService->batchUpdate($ids, ['status' => $is_show], 'product_id');
  219. $update = ['is_show' => $is_show];
  220. if ($is_show) {//手动上架 清空定时下架状态
  221. $update['auto_off_time'] = 0;
  222. }
  223. $this->dao->batchUpdate($ids, $update);
  224. /** @var StoreProductRelationServices $storeProductRelationServices */
  225. $storeProductRelationServices = app()->make(StoreProductRelationServices::class);
  226. $storeProductRelationServices->setShow($ids, (int)$is_show);
  227. //门店商品处理
  228. /** @var StoreBranchProductServices $storeBranchProductServices */
  229. $storeBranchProductServices = app()->make(StoreBranchProductServices::class);
  230. $storeBranchProductServices->update(['pid' => $ids, 'type' => 1], ['is_show' => $is_show ? 1 : 0]);
  231. event('product.status', [$ids, $is_show]);
  232. $this->dao->cacheTag()->clear();
  233. return true;
  234. }
  235. /**
  236. * 队列批量上下架
  237. * @param array $ids
  238. * @param int $is_show
  239. * @param $redisKey
  240. * @param $queueId
  241. */
  242. public function setBatchShow(array $ids, int $is_show, $redisKey, $queueId)
  243. {
  244. $res = $this->dao->batchUpdate($ids, ['is_show' => $is_show]);
  245. /** @var StoreProductRelationServices $storeProductRelationServices */
  246. $storeProductRelationServices = app()->make(StoreProductRelationServices::class);
  247. /** @var QueueServices $queueService */
  248. $queueService = app()->make(QueueServices::class);
  249. $re = true;
  250. if ($is_show == 0) {
  251. try {
  252. //下架检测是否有参与活动商品
  253. $re = $this->checkActivity($ids);
  254. //改变购物车中状态
  255. $storeProductRelationServices->setShow($ids, (int)$is_show);
  256. /** @var StoreCartServices $cartService */
  257. $cartService = app()->make(StoreCartServices::class);
  258. $cartService->changeStatus($ids, 1);
  259. } catch (\Throwable $e) {
  260. $re = false;
  261. }
  262. }
  263. if ($re) $storeProductRelationServices->setShow($ids, (int)$is_show);
  264. $queueService->doSuccessSremRedis($ids, $redisKey, $queueId['type']);
  265. if (!$res) {
  266. $queueService->addQueueFail($queueId['id'], $redisKey);
  267. }
  268. return true;
  269. }
  270. /**
  271. * 商品审核表单
  272. * @param int $id
  273. * @param int $is_verify
  274. * @return mixed
  275. */
  276. public function verifyForm(int $id, int $is_verify = 1)
  277. {
  278. $f = [];
  279. if ($is_verify == 1) {
  280. $f[] = Form::radio('is_verify', '审核状态', 1)->options([['value' => 1, 'label' => '通过'], ['value' => -1, 'label' => '拒绝']])->appendControl(-1, [
  281. Form::textarea('refusal', '拒绝原因')->required('请输入拒绝原因')]);
  282. } else {
  283. $f[] = Form::hidden('is_verify', '-2');
  284. $f[] = Form::textarea('refusal', '下架原因')->required('请输入下架原因');
  285. }
  286. return create_form($is_verify == 1 ? '商品审核' : '强制下架', $f, Url::buildUrl('/product/product/set_verify/' . $id), 'post');
  287. }
  288. /**
  289. * 商品审核
  290. * @param int $id
  291. * @param int $is_verify
  292. * @return bool
  293. * @throws \think\db\exception\DataNotFoundException
  294. * @throws \think\db\exception\DbException
  295. * @throws \think\db\exception\ModelNotFoundException
  296. */
  297. public function verify(int $id, array $data)
  298. {
  299. $info = $this->dao->get($id);
  300. if (!$info) {
  301. throw new ValidateException('商品不存在');
  302. }
  303. $is_verify = $data['is_verify'] ?? 1;
  304. /** @var StoreCartServices $cartService */
  305. $cartService = app()->make(StoreCartServices::class);
  306. if (in_array($is_verify, [-1, -2])) {
  307. if ($is_verify == -1) {
  308. $data['is_show'] = 0;
  309. }
  310. $cartService->update(['product_id' => $id, 'status' => 1], ['status' => 0]);
  311. } elseif ($is_verify == 1) {
  312. if ($info['is_show']) $cartService->update(['product_id' => $id, 'status' => 0], ['status' => 1]);
  313. }
  314. $this->dao->update($id, $data);
  315. return true;
  316. }
  317. /**
  318. * 获取规格模板
  319. * @param int $type
  320. * @param int $relation_id
  321. * @return mixed
  322. * @throws \think\db\exception\DataNotFoundException
  323. * @throws \think\db\exception\DbException
  324. * @throws \think\db\exception\ModelNotFoundException
  325. */
  326. public function getRule(int $type = 0, int $relation_id = 0)
  327. {
  328. /** @var StoreProductRuleServices $storeProductRuleServices */
  329. $storeProductRuleServices = app()->make(StoreProductRuleServices::class);
  330. $list = $storeProductRuleServices->getList(['type' => $type, 'relation_id' => $relation_id])['list'] ?? [];
  331. foreach ($list as &$item) {
  332. $item['rule_value'] = json_decode($item['rule_value'], true);
  333. }
  334. return $list;
  335. }
  336. /**
  337. * 获取商品详情
  338. * @param int $id
  339. * @return array|\think\Model|null
  340. */
  341. public function getInfo(int $id)
  342. {
  343. /** @var StoreProductCategoryServices $storeCatecoryService */
  344. $storeCatecoryService = app()->make(StoreProductCategoryServices::class);
  345. /** @var StoreDescriptionServices $storeDescriptionServices */
  346. $storeDescriptionServices = app()->make(StoreDescriptionServices::class);
  347. /** @var StoreProductAttrResultServices $storeProductAttrResultServices */
  348. $storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class);
  349. /** @var StoreProductAttrValueServices $storeProductAttrValueServices */
  350. $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  351. /** @var StoreCouponIssueServices $storeCouponIssueServices */
  352. $storeCouponIssueServices = app()->make(StoreCouponIssueServices::class);
  353. /** @var UserLabelServices $userLabelServices */
  354. $userLabelServices = app()->make(UserLabelServices::class);
  355. $productInfo = $this->dao->getInfo($id);
  356. if ($productInfo) $productInfo = $productInfo->toArray();
  357. else throw new ValidateException('商品不存在');
  358. $data['tempList'] = $this->getTemp((int)$productInfo['type'], (int)$productInfo['relation_id']);
  359. $data['cateList'] = $storeCatecoryService->cascaderList();
  360. $couponIds = $productInfo['coupons'] ? array_column($productInfo['coupons'], 'issue_coupon_id') : [];
  361. $is_sub = $recommend = [];
  362. if ($productInfo['is_sub'] == 1) array_push($is_sub, 1);
  363. if ($productInfo['is_vip'] == 1) array_push($is_sub, 0);
  364. $productInfo['is_sub'] = $is_sub;
  365. $productInfo['recommend'] = $recommend;
  366. $productInfo['price'] = floatval($productInfo['price']);
  367. $productInfo['postage'] = floatval($productInfo['postage']);
  368. $productInfo['ot_price'] = floatval($productInfo['ot_price']);
  369. $productInfo['vip_price'] = floatval($productInfo['vip_price']);
  370. $productInfo['is_limit'] = boolval($productInfo['is_limit'] ?? 0);
  371. $productInfo['cost'] = floatval($productInfo['cost']);
  372. $productInfo['brand_id'] = $productInfo['brand_com'] ? array_map('intval', explode(',', $productInfo['brand_com'])) : [];
  373. $productInfo['video_open'] = (bool)$productInfo['video_open'];
  374. if ($productInfo['video_link'] && strpos($productInfo['video_link'], 'http') === false) {
  375. $productInfo['video_link'] = sys_config('site_url') . $productInfo['video_link'];
  376. }
  377. $productInfo['coupons'] = $storeCouponIssueServices->productCouponList([['id', 'in', $couponIds]], 'title,id');
  378. $productInfo['cate_id'] = is_array($productInfo['cate_id']) ? $productInfo['cate_id'] : explode(',', $productInfo['cate_id']);
  379. if ($productInfo['label_id']) {
  380. $label_id = is_array($productInfo['label_id']) ? $productInfo['label_id'] : explode(',', $productInfo['label_id']);
  381. $productInfo['label_id'] = $userLabelServices->getLabelList(['ids' => $label_id], ['id', 'label_name']);
  382. } else {
  383. $productInfo['label_id'] = [];
  384. }
  385. if ($productInfo['store_label_id']) {
  386. /** @var StoreProductLabelServices $storeProductLabelServices */
  387. $storeProductLabelServices = app()->make(StoreProductLabelServices::class);
  388. $productInfo['store_label_id'] = $storeProductLabelServices->getColumn([['id', 'in', $productInfo['store_label_id']]], 'id,label_name');
  389. } else {
  390. $productInfo['store_label_id'] = [];
  391. }
  392. $productInfo['give_integral'] = floatval($productInfo['give_integral']);
  393. $productInfo['presale_time'] = $productInfo['presale_start_time'] == 0 ? [] : [date('Y-m-d H:i:s', $productInfo['presale_start_time']), date('Y-m-d H:i:s', $productInfo['presale_end_time'])];
  394. $productInfo['auto_on_time'] = $productInfo['is_show'] ? '' : ($productInfo['auto_on_time'] ? date('Y-m-d H:i:s', $productInfo['auto_on_time']) : '');
  395. $productInfo['auto_off_time'] = !$productInfo['is_show'] ? '' : ($productInfo['auto_off_time'] ? date('Y-m-d H:i:s', $productInfo['auto_off_time']) : '');
  396. $productInfo['description'] = $storeDescriptionServices->getDescription(['product_id' => $id, 'type' => 0]);
  397. //系统表单
  398. $productInfo['custom_form'] = $productInfo['custom_form_info'] = [];
  399. if ($productInfo['system_form_id']) {
  400. /** @var SystemFormServices $systemFormServices */
  401. $systemFormServices = app()->make(SystemFormServices::class);
  402. $systemForm = $systemFormServices->value(['id' => $productInfo['system_form_id']], 'value');
  403. if ($systemForm) {
  404. $productInfo['custom_form'] = is_string($systemForm) ? json_decode($systemForm, true) : $systemForm;
  405. }
  406. $productInfo['custom_form_info'] = $systemFormServices->handleForm($productInfo['custom_form']);
  407. }
  408. //无属性添加默认属性
  409. if (!$storeProductAttrResultServices->getResult(['product_id' => $id, 'type' => 0])) {
  410. $attr = [
  411. [
  412. 'value' => '规格',
  413. 'detailValue' => '',
  414. 'attrHidden' => '',
  415. 'detail' => ['默认']
  416. ]
  417. ];
  418. $detail[0] = [
  419. 'value1' => '默认',
  420. 'detail' => ['规格' => '默认'],
  421. 'pic' => $productInfo['image'],
  422. 'price' => $productInfo['price'],
  423. 'settle_price' => $productInfo['settle_price'] ?? 0,
  424. 'award_price' => $productInfo['award_price'] ?? 0,
  425. 'cost' => $productInfo['cost'],
  426. 'ot_price' => $productInfo['ot_price'],
  427. 'stock' => $productInfo['stock'],
  428. 'bar_code' => '',
  429. 'weight' => 0,
  430. 'volume' => 0,
  431. 'brokerage' => 0,
  432. 'brokerage_two' => 0,
  433. 'code' => 0,
  434. ];
  435. /** @var StoreProductAttrServices $storeProductAttrServices */
  436. $storeProductAttrServices = app()->make(StoreProductAttrServices::class);
  437. $skuList = $storeProductAttrServices->validateProductAttr($attr, $detail, $id);
  438. $storeProductAttrServices->saveProductAttr($skuList, $id, 0);
  439. $this->dao->update($id, ['spec_type' => 0]);
  440. }
  441. if ($productInfo['spec_type'] == 1) {
  442. $result = $storeProductAttrResultServices->getResult(['product_id' => $id, 'type' => 0]);
  443. foreach ($result['value'] as $k => $v) {
  444. $num = 1;
  445. foreach ($v['detail'] as $dv) {
  446. $result['value'][$k]['value' . $num] = $dv;
  447. $num++;
  448. }
  449. }
  450. $productInfo['items'] = $result['attr'];
  451. $productInfo['attrs'] = $result['value'];
  452. $productInfo['attr'] = ['pic' => '', 'vip_price' => 0, 'price' => 0, 'settle_price' => 0, 'award_price' => 0, 'cost' => 0, 'ot_price' => 0, 'stock' => 0, 'bar_code' => '', 'weight' => 0, 'volume' => 0, 'brokerage' => 0, 'brokerage_two' => 0, 'code' => ''];
  453. } else {
  454. /** @var StoreProductVirtualServices $virtualService */
  455. $virtualService = app()->make(StoreProductVirtualServices::class);
  456. $result = $storeProductAttrValueServices->getOne(['product_id' => $id, 'type' => 0]);
  457. $productInfo['items'] = [];
  458. $productInfo['attrs'] = [];
  459. $productInfo['attr'] = [
  460. 'pic' => $result['image'] ?? '',
  461. 'vip_price' => isset($result['vip_price']) ? floatval($result['vip_price']) : 0,
  462. 'price' => isset($result['price']) ? floatval($result['price']) : 0,
  463. 'settle_price' => isset($result['settle_price']) ? floatval($result['settle_price']) : 0,
  464. 'award_price' => isset($result['award_price']) ? floatval($result['award_price']) : 0,
  465. 'cost' => isset($result['cost']) ? floatval($result['cost']) : 0,
  466. 'ot_price' => isset($result['ot_price']) ? floatval($result['ot_price']) : 0,
  467. 'stock' => isset($result['stock']) ? floatval($result['stock']) : 0,
  468. 'bar_code' => isset($result['bar_code']) ? $result['bar_code'] : '',
  469. 'code' => isset($result['code']) ? $result['code'] : '',
  470. 'virtual_list' => $virtualService->getArr(isset($result['unique']), $id),
  471. 'weight' => isset($result['weight']) ? floatval($result['weight']) : 0,
  472. 'volume' => isset($result['volume']) ? floatval($result['volume']) : 0,
  473. 'brokerage' => isset($result['brokerage']) ? floatval($result['brokerage']) : 0,
  474. 'brokerage_two' => isset($result['brokerage_two']) ? floatval($result['brokerage_two']) : 0,
  475. 'disk_info' => $result['disk_info'] ?? [],
  476. 'write_times' => intval($result['write_times'] ?? 1),//核销次数
  477. 'write_valid' => intval($result['write_valid'] ?? 1),//核销时效类型
  478. 'days' => intval($result['write_days'] ?? $result['days'] ?? 0),//购买后:N天有效
  479. 'section_time' => [($result['write_start'] ?? '') ? date('Y-m-d', $result['write_start'] ?? '') : '', ($result['write_end'] ?? '') ? date('Y-m-d', $result['write_end'] ?? '') : ''],//[核销开始时间,核销结束时间]
  480. ];
  481. }
  482. if ($productInfo['activity']) {
  483. $activity = explode(',', $productInfo['activity']);
  484. foreach ($activity as $k => $v) {
  485. if ($v == 1) {
  486. $activity[$k] = '秒杀';
  487. } elseif ($v == 2) {
  488. $activity[$k] = '砍价';
  489. } elseif ($v == 3) {
  490. $activity[$k] = '拼团';
  491. } elseif ($v == 0) {
  492. $activity[$k] = '默认';
  493. }
  494. }
  495. $productInfo['activity'] = $activity;
  496. } else {
  497. $productInfo['activity'] = ['默认', '秒杀', '砍价', '拼团'];
  498. }
  499. //推荐产品
  500. $recommend_list = [];
  501. if ($productInfo['recommend_list'] != '') {
  502. $productInfo['recommend_list'] = explode(',', $productInfo['recommend_list']);
  503. if (count($productInfo['recommend_list'])) {
  504. $images = $this->getColumn([['id', 'in', $productInfo['recommend_list']]], 'image', 'id');
  505. foreach ($productInfo['recommend_list'] as $item) {
  506. $recommend_list[] = [
  507. 'product_id' => $item,
  508. 'image' => $images[$item]
  509. ];
  510. }
  511. }
  512. }
  513. $productInfo['recommend_list'] = $recommend_list;
  514. //适用门店
  515. $productInfo['stores'] = [];
  516. if (isset($productInfo['applicable_type']) && ($productInfo['applicable_type'] == 1 || ($productInfo['applicable_type'] == 2 && isset($productInfo['applicable_store_id']) && $productInfo['applicable_store_id']))) {//查询门店信息
  517. $where = ['is_del' => 0];
  518. if ($productInfo['applicable_type'] == 2) {
  519. $store_ids = is_array($productInfo['applicable_store_id']) ? $productInfo['applicable_store_id'] : explode(',', $productInfo['applicable_store_id']);
  520. $where['id'] = $store_ids;
  521. }
  522. $field = ['id', 'cate_id', 'name', 'phone', 'address', 'detailed_address', 'image', 'is_show', 'day_time', 'day_start', 'day_end'];
  523. /** @var SystemStoreServices $storeServices */
  524. $storeServices = app()->make(SystemStoreServices::class);
  525. $storeData = $storeServices->getStoreList($where, $field, '', '', 0, ['categoryName']);
  526. $productInfo['stores'] = $storeData['list'] ?? [];
  527. }
  528. $data['productInfo'] = $productInfo;
  529. return $data;
  530. }
  531. /**
  532. * 获取运费模板列表
  533. * @param int $type
  534. * @param int $relation_id
  535. * @return array
  536. */
  537. public function getTemp(int $type = 0, int $relation_id = 0)
  538. {
  539. /** @var ShippingTemplatesServices $shippingTemplatesServices */
  540. $shippingTemplatesServices = app()->make(ShippingTemplatesServices::class);
  541. return $shippingTemplatesServices->getSelectList(['type' => $type, 'relation_id' => $relation_id]);
  542. }
  543. /**
  544. * 获取商品规格
  545. * @param array $data
  546. * @param int $id
  547. * @param int $type
  548. * @param int $plat_type
  549. * @param int $relation_id
  550. * @return array
  551. * @throws \think\db\exception\DataNotFoundException
  552. * @throws \think\db\exception\DbException
  553. * @throws \think\db\exception\ModelNotFoundException
  554. */
  555. public function getAttr(array $data, int $id, int $type, int $plat_type = 0, int $relation_id = 0)
  556. {
  557. /** @var StoreProductAttrValueServices $storeProductAttrValueServices */
  558. $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  559. $productInfo = [];
  560. if ($id) {
  561. $productInfo = $this->dao->get($id);
  562. if (!$productInfo) {
  563. throw new ValidateException('商品不存在');
  564. }
  565. }
  566. /** @var StoreProductVirtualServices $virtualService */
  567. $virtualService = app()->make(StoreProductVirtualServices::class);
  568. $attr = $data['attrs'];
  569. $product_type = $productInfo['product_type'] ?? $data['product_type'] ?? 0; //商品类型
  570. $value = attr_format($attr)[1];
  571. $valueNew = [];
  572. $count = 0;
  573. foreach ($value as $key => $item) {
  574. $detail = $item['detail'];
  575. foreach ($detail as $v => $d) {
  576. $detail[$v] = trim($d);
  577. }
  578. // sort($item['detail'], SORT_STRING);
  579. $suk = implode(',', $detail);
  580. $types = 1;
  581. if ($id) {
  582. $sukValue = $storeProductAttrValueServices->getSkuArray(['product_id' => $id, 'type' => 0, 'suk' => $suk], 'unique,bar_code,code,cost,award_price,price,settle_price,ot_price,stock,image as pic,weight,volume,brokerage,brokerage_two,vip_price,disk_info', 'suk');
  583. if (!$sukValue) {
  584. if ($type == 0) $types = 0; //编辑商品时,将没有规格的数据不生成默认值
  585. $sukValue[$suk]['pic'] = '';
  586. $sukValue[$suk]['price'] = 0;
  587. $sukValue[$suk]['award_price'] = 0;
  588. $sukValue[$suk]['settle_price'] = 0;
  589. $sukValue[$suk]['cost'] = 0;
  590. $sukValue[$suk]['ot_price'] = 0;
  591. $sukValue[$suk]['stock'] = 0;
  592. $sukValue[$suk]['bar_code'] = '';
  593. $sukValue[$suk]['code'] = '';
  594. $sukValue[$suk]['weight'] = 0;
  595. $sukValue[$suk]['volume'] = 0;
  596. $sukValue[$suk]['brokerage'] = 0;
  597. $sukValue[$suk]['brokerage_two'] = 0;
  598. switch ($product_type) {
  599. case 1://卡密
  600. $sukValue[$suk]['virtual_list'] = [];
  601. break;
  602. case 2://优惠券
  603. $sukValue[$suk]['coupon_id'] = 0;
  604. break;
  605. case 3://虚拟商品
  606. break;
  607. case 4://次卡商品
  608. $sukValue[$suk]['write_times'] = 1;//核销次数
  609. $sukValue[$suk]['write_valid'] = 1;//核销时效类型
  610. $sukValue[$suk]['days'] = 0;//购买后:N天有效
  611. $sukValue[$suk]['section_time'] = [];//[核销开始时间,核销结束时间]
  612. break;
  613. }
  614. }
  615. } else {
  616. $sukValue[$suk]['pic'] = '';
  617. $sukValue[$suk]['price'] = 0;
  618. $sukValue[$suk]['award_price'] = 0;
  619. $sukValue[$suk]['settle_price'] = 0;
  620. $sukValue[$suk]['cost'] = 0;
  621. $sukValue[$suk]['ot_price'] = 0;
  622. $sukValue[$suk]['stock'] = 0;
  623. $sukValue[$suk]['bar_code'] = '';
  624. $sukValue[$suk]['code'] = '';
  625. $sukValue[$suk]['weight'] = 0;
  626. $sukValue[$suk]['volume'] = 0;
  627. $sukValue[$suk]['brokerage'] = 0;
  628. $sukValue[$suk]['brokerage_two'] = 0;
  629. switch ($product_type) {
  630. case 1://卡密
  631. $sukValue[$suk]['virtual_list'] = [];
  632. break;
  633. case 2://优惠券
  634. $sukValue[$suk]['coupon_id'] = 0;
  635. break;
  636. case 3://虚拟商品
  637. break;
  638. case 4://次卡商品
  639. $sukValue[$suk]['write_times'] = 1;//核销次数
  640. $sukValue[$suk]['write_valid'] = 1;//核销时效类型
  641. $sukValue[$suk]['days'] = 0;//购买后:N天有效
  642. $sukValue[$suk]['section_time'] = [];//[核销开始时间,核销结束时间]
  643. break;
  644. }
  645. }
  646. if ($types) { //编辑商品时,将没有规格的数据不生成默认值
  647. foreach (array_keys($detail) as $k => $title) {
  648. $header[$k]['title'] = $title;
  649. $header[$k]['align'] = 'center';
  650. $header[$k]['minWidth'] = 130;
  651. }
  652. $values = '';
  653. foreach (array_values($detail) as $k => $v) {
  654. $valueNew[$count]['value' . ($k + 1)] = $v;
  655. $header[$k]['slot'] = 'value' . ($k + 1);
  656. $values .= $v . ',';
  657. }
  658. $valueNew[$count]['values'] = substr($values, 0, strlen($values) - 1);
  659. $valueNew[$count]['detail'] = $detail;
  660. $valueNew[$count]['pic'] = $sukValue[$suk]['pic'] ?? '';
  661. $valueNew[$count]['price'] = $sukValue[$suk]['price'] ? floatval($sukValue[$suk]['price']) : 0;
  662. $valueNew[$count]['settle_price'] = $sukValue[$suk]['settle_price'] ? floatval($sukValue[$suk]['settle_price']) : 0;
  663. $valueNew[$count]['award_price'] = $sukValue[$suk]['award_price'] ? floatval($sukValue[$suk]['award_price']) : 0;
  664. $valueNew[$count]['cost'] = $sukValue[$suk]['cost'] ? floatval($sukValue[$suk]['cost']) : 0;
  665. $valueNew[$count]['ot_price'] = isset($sukValue[$suk]['ot_price']) ? floatval($sukValue[$suk]['ot_price']) : 0;
  666. $valueNew[$count]['vip_price'] = isset($sukValue[$suk]['vip_price']) ? floatval($sukValue[$suk]['vip_price']) : 0;
  667. $valueNew[$count]['stock'] = $sukValue[$suk]['stock'] ? intval($sukValue[$suk]['stock']) : 0;
  668. $valueNew[$count]['bar_code'] = $sukValue[$suk]['bar_code'] ?? '';
  669. $valueNew[$count]['code'] = $sukValue[$suk]['code'] ?? '';
  670. $valueNew[$count]['weight'] = floatval($sukValue[$suk]['weight']) ?? 0;
  671. $valueNew[$count]['volume'] = floatval($sukValue[$suk]['volume']) ?? 0;
  672. $valueNew[$count]['brokerage'] = floatval($sukValue[$suk]['brokerage']) ?? 0;
  673. $valueNew[$count]['brokerage_two'] = floatval($sukValue[$suk]['brokerage_two']) ?? 0;
  674. switch ($product_type) {
  675. case 1://卡密
  676. if (!$type) {
  677. $valueNew[$count]['virtual_list'] = isset($sukValue[$suk]['unique']) && $sukValue[$suk]['unique'] ? $virtualService->getArr($sukValue[$suk]['unique'], $id) : [];
  678. $valueNew[$count]['disk_info'] = $sukValue[$suk]['disk_info'] ?? '';
  679. }
  680. break;
  681. case 2://优惠券
  682. break;
  683. case 3://虚拟商品
  684. break;
  685. case 4://次卡商品
  686. $valueNew[$count]['write_times'] = intval($sukValue[$suk]['write_times'] ?? 1);//核销次数
  687. $valueNew[$count]['write_valid'] = intval($sukValue[$suk]['write_valid'] ?? 1);//核销时效类型
  688. $valueNew[$count]['days'] = intval($sukValue[$suk]['write_days'] ?? $sukValue[$suk]['days'] ?? 0);//购买后:N天有效
  689. $start = $sukValue[$suk]['write_start'] ?? '';
  690. $end = $sukValue[$suk]['write_end'] ?? '';
  691. $valueNew[$count]['section_time'] = [$start ? date('Y-m-d', $start) : '', $end ? date('Y-m-d', $end) : ''];//[核销开始时间,核销结束时间]
  692. break;
  693. }
  694. $count++;
  695. }
  696. }
  697. $header[] = ['title' => '图片', 'slot' => 'pic', 'align' => 'center', 'minWidth' => 80];
  698. $header[] = ['title' => '售价', 'slot' => 'price', 'align' => 'center', 'minWidth' => 120];
  699. if ($plat_type == 2) {//供应商
  700. $header[] = ['title' => '结算价', 'slot' => 'settle_price', 'align' => 'center', 'minWidth' => 120];
  701. } else {//供应商不展示成本价
  702. $header[] = ['title' => '成本价', 'slot' => 'cost', 'align' => 'center', 'minWidth' => 140];
  703. }
  704. $header[] = ['title' => '奖金结算', 'slot' => 'award_price', 'align' => 'center', 'minWidth' => 140];
  705. $header[] = ['title' => '原价', 'slot' => 'ot_price', 'align' => 'center', 'minWidth' => 140];
  706. // $header[] = ['title' => '会员价', 'slot' => 'vip_price', 'align' => 'center', 'minWidth' => 140];
  707. $header[] = ['title' => '库存', 'slot' => 'stock', 'align' => 'center', 'minWidth' => 140];
  708. $header[] = ['title' => '商品条形码', 'slot' => 'bar_code', 'align' => 'center', 'minWidth' => 140];
  709. $header[] = ['title' => '商品编码', 'slot' => 'code', 'align' => 'center', 'minWidth' => 140];
  710. switch ($product_type) {
  711. case 0:
  712. $header[] = ['title' => '重量(KG)', 'slot' => 'weight', 'align' => 'center', 'minWidth' => 140];
  713. $header[] = ['title' => '体积(m³)', 'slot' => 'volume', 'align' => 'center', 'minWidth' => 140];
  714. break;
  715. case 1://卡密
  716. $header[] = ['title' => '卡密商品', 'slot' => 'fictitious', 'align' => 'center', 'minWidth' => 140];
  717. break;
  718. case 2://优惠券
  719. break;
  720. case 3://虚拟商品
  721. break;
  722. case 4://次卡商品
  723. break;
  724. default:
  725. break;
  726. }
  727. $header[] = ['title' => '操作', 'slot' => 'action', 'align' => 'center', 'minWidth' => 70];
  728. return ['attr' => $attr, 'value' => $valueNew, 'header' => $header, 'product_type' => $product_type];
  729. }
  730. /**
  731. * SPU
  732. * @return string
  733. */
  734. public function createSpu()
  735. {
  736. mt_srand();
  737. return substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8) . str_pad((string)mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
  738. }
  739. /**
  740. * 新增编辑商品
  741. * @param int $id
  742. * @param array $data
  743. * @param int $type
  744. * @param int $relation_id
  745. * @return void
  746. * @throws \think\db\exception\DataNotFoundException
  747. * @throws \think\db\exception\DbException
  748. * @throws \think\db\exception\ModelNotFoundException
  749. */
  750. public function save(int $id, array $data, int $type = 0, int $relation_id = 0)
  751. {
  752. if (count($data['cate_id']) < 1) throw new AdminException('请选择商品分类');
  753. if (!$data['store_name']) throw new AdminException('请输入商品名称');
  754. if (count($data['slider_image']) < 1) throw new AdminException('请上传商品轮播图');
  755. if ($data['product_type'] == 0 && isset($data['delivery_type']) && count($data['delivery_type']) < 1) throw new AdminException('请选择商品配送方式');
  756. if (in_array($data['product_type'], [1, 2, 3, 4])) {
  757. $data['freight'] = 2;
  758. $data['temp_id'] = 0;
  759. $data['postage'] = 0;
  760. } else {
  761. if ($data['freight'] == 1) {
  762. $data['temp_id'] = 0;
  763. $data['postage'] = 0;
  764. } elseif ($data['freight'] == 2) {
  765. $data['temp_id'] = 0;
  766. } elseif ($data['freight'] == 3) {
  767. $data['postage'] = 0;
  768. }
  769. if ($data['freight'] == 2 && !$data['postage']) {
  770. throw new AdminException('请设置运费金额');
  771. }
  772. if ($data['freight'] == 3 && !$data['temp_id']) {
  773. throw new AdminException('请选择运费模版');
  774. }
  775. }
  776. // 开启ERP后商品编码验证
  777. $isOpen = sys_config('erp_open');
  778. if ($isOpen && $data['product_type'] == 0 && empty($data['code'])) {
  779. throw new AdminException('请输入商品编码');
  780. }
  781. $detail = $data['attrs'];
  782. $attr = $data['items'];
  783. $coupon_ids = $data['coupon_ids'];
  784. //关联补充信息
  785. $relationData = [];
  786. $relationData['cate_id'] = $data['cate_id'] ?? [];
  787. $relationData['brand_id'] = $data['brand_id'] ?? [];
  788. $relationData['store_label_id'] = $data['store_label_id'] ?? [];
  789. $relationData['label_id'] = $data['label_id'] ?? [];
  790. $relationData['ensure_id'] = $data['ensure_id'] ?? [];
  791. $relationData['specs_id'] = $data['specs_id'] ?? [];
  792. $relationData['coupon_ids'] = $data['coupon_ids'] ?? [];
  793. $description = $data['description'];
  794. $data['type'] = $type;
  795. $data['relation_id'] = $relation_id;
  796. $supplier_id = $data['supplier_id'] ?? 0;
  797. if ($supplier_id) {
  798. $data['type'] = 2;
  799. $data['relation_id'] = $supplier_id;
  800. }
  801. if ($data['type'] == 0) {//平台商品不需要审核
  802. $data['is_verify'] = 1;
  803. }
  804. $is_copy = $data['is_copy'];
  805. unset($data['supplier_id'], $data['is_copy']);
  806. //视频
  807. if ($data['video_link'] && strpos($data['video_link'], 'http') === false) {
  808. $data['video_link'] = sys_config('site_url') . $data['video_link'];
  809. }
  810. //品牌
  811. $data['brand_com'] = $data['brand_id'] ? implode(',', $data['brand_id']) : '';
  812. $data['brand_id'] = $data['brand_id'] ? end($data['brand_id']) : 0;
  813. $data['is_vip'] = in_array(0, $data['is_sub']) ? 1 : 0;
  814. $data['is_sub'] = in_array(1, $data['is_sub']) ? 1 : 0;
  815. $data['product_type'] = intval($data['product_type']);
  816. $data['is_vip_product'] = intval($data['is_vip_product']);
  817. $data['is_presale_product'] = intval($data['is_presale_product']);
  818. $data['presale_start_time'] = $data['is_presale_product'] ? strtotime($data['presale_time'][0]) : 0;
  819. $data['presale_end_time'] = $data['is_presale_product'] ? strtotime($data['presale_time'][1]) : 0;
  820. if ($data['presale_start_time'] && $data['presale_start_time'] < time()) {
  821. throw new AdminException('预售开始时间不能小于当前时间');
  822. }
  823. if ($data['presale_end_time'] && $data['presale_end_time'] < time()) {
  824. throw new AdminException('预售结束时间不能小于当前时间');
  825. }
  826. $data['auto_on_time'] = $data['auto_on_time'] ? strtotime($data['auto_on_time']) : 0;
  827. $data['auto_off_time'] = $data['auto_off_time'] ? strtotime($data['auto_off_time']) : 0;
  828. if ($data['auto_on_time']) {
  829. $data['is_show'] = 0;
  830. }
  831. $data['is_limit'] = intval($data['is_limit']);
  832. if (!$data['is_limit']) {
  833. $data['limit_type'] = 0;
  834. $data['limit_num'] = 0;
  835. } else {
  836. if (!in_array($data['limit_type'], [1, 2])) throw new AdminException('请选择限购类型');
  837. if ($data['limit_num'] <= 0) throw new AdminException('限购数量不能小于1');
  838. }
  839. $data['custom_form'] = json_encode($data['custom_form']);
  840. $storeLabelId = $data['store_label_id'];
  841. if ($data['store_label_id']) {
  842. $data['store_label_id'] = is_array($data['store_label_id']) ? implode(',', $data['store_label_id']) : $data['store_label_id'];
  843. } else {
  844. $data['store_label_id'] = '';
  845. }
  846. if ($data['ensure_id']) {
  847. $data['ensure_id'] = is_array($data['ensure_id']) ? implode(',', $data['ensure_id']) : $data['ensure_id'];
  848. } else {
  849. $data['ensure_id'] = '';
  850. }
  851. if (!$data['specs_id']) {
  852. $data['specs'] = '';
  853. }
  854. if ($data['specs']) {
  855. $specs = [];
  856. if (is_array($data['specs'])) {
  857. /** @var StoreProductSpecsServices $storeProductSpecsServices */
  858. $storeProductSpecsServices = app()->make(StoreProductSpecsServices::class);
  859. foreach ($data['specs'] as $item) {
  860. $specs[] = $storeProductSpecsServices->checkSpecsData($item);
  861. }
  862. $data['specs'] = json_encode($specs);
  863. }
  864. } else {
  865. $data['specs'] = '';
  866. }
  867. if ($data['spec_type'] == 0) {
  868. $attr = [
  869. [
  870. 'value' => '规格',
  871. 'detailValue' => '',
  872. 'attrHidden' => '',
  873. 'detail' => ['默认']
  874. ]
  875. ];
  876. $detail[0]['value1'] = '默认';
  877. $detail[0]['detail'] = ['规格' => '默认'];
  878. }
  879. foreach ($detail as &$item) {
  880. if ($isOpen && $data['product_type'] == 0 && (!isset($item['code']) || !$item['code'])) {
  881. throw new AdminException('请输入【' . ($item['values'] ?? '默认') . '】商品编码');
  882. }
  883. if ($type == 2 && !$item['settle_price']) {
  884. throw new AdminException('请输入结算价');
  885. }
  886. $item['product_type'] = $data['product_type'];
  887. if ($data['is_sub'] == 0) {
  888. $item['brokerage'] = 0;
  889. $item['brokerage_two'] = 0;
  890. }
  891. if (($item['brokerage'] + $item['brokerage_two']) > $item['price']) {
  892. throw new AdminException('一二级返佣相加不能大于商品售价');
  893. }
  894. //验证次卡商品数据
  895. if ($data['product_type'] == 4) {
  896. if (!isset($item['write_times']) || !$item['write_times']) {
  897. throw new AdminException('请输入核销次数');
  898. }
  899. if (!isset($item['write_valid'])) {
  900. throw new AdminException('请选择核销时效类型');
  901. }
  902. switch ($item['write_valid']) {
  903. case 1://永久
  904. $item['days'] = 0;
  905. $item['section_time'] = [0, 0];
  906. break;
  907. case 2://购买后n天
  908. $item['section_time'] = [0, 0];
  909. if (!isset($item['days']) || !$item['days']) {
  910. throw new AdminException('填写时效天数');
  911. }
  912. break;
  913. case 3://固定时间
  914. $item['days'] = 0;
  915. if (!isset($item['section_time']) || !$item['section_time'] || !is_array($item['section_time']) || count($item['section_time']) != 2) {
  916. throw new AdminException('请选择固定有效时间段或时间格式错误');
  917. }
  918. [$start, $end] = $item['section_time'];
  919. $data['start_time'] = $start ? strtotime($start) : 0;
  920. $data['end_time'] = $end ? strtotime($end) : 0;
  921. if ($data['start_time'] && $data['end_time'] && $data['end_time'] <= $data['start_time']) {
  922. throw new AdminException('请重新选择:结束时间必须大于开始时间');
  923. }
  924. break;
  925. default:
  926. throw new AdminException('请选择核销时效类型');
  927. break;
  928. }
  929. }
  930. }
  931. foreach ($data['activity'] as $k => $v) {
  932. if ($v == '秒杀') {
  933. $data['activity'][$k] = 1;
  934. } elseif ($v == '砍价') {
  935. $data['activity'][$k] = 2;
  936. } elseif ($v == '拼团') {
  937. $data['activity'][$k] = 3;
  938. } else {
  939. $data['activity'][$k] = 0;
  940. }
  941. }
  942. $data['activity'] = implode(',', $data['activity']);
  943. $data['recommend_list'] = count($data['recommend_list']) ? implode(',', $data['recommend_list']) : '';
  944. $data['price'] = min(array_column($detail, 'price'));
  945. $data['award_price'] = max(array_column($detail, 'award_price'));
  946. $data['ot_price'] = min(array_column($detail, 'ot_price'));
  947. $data['cost'] = min(array_column($detail, 'cost'));
  948. if (!$data['cost']) {
  949. $data['cost'] = 0;
  950. }
  951. $data['cate_id'] = implode(',', $data['cate_id']);
  952. $data['label_id'] = implode(',', $data['label_id']);
  953. $data['image'] = $data['slider_image'][0];//封面图
  954. $slider_image = $data['slider_image'];
  955. $data['slider_image'] = json_encode($data['slider_image']);
  956. $data['stock'] = array_sum(array_column($detail, 'stock'));
  957. //是否售罄
  958. $data['is_sold'] = $data['stock'] ? 0 : 1;
  959. unset($data['description'], $data['coupon_ids'], $data['items'], $data['attrs'], $data['recommend']);
  960. /** @var StoreDescriptionServices $storeDescriptionServices */
  961. $storeDescriptionServices = app()->make(StoreDescriptionServices::class);
  962. /** @var StoreProductAttrServices $storeProductAttrServices */
  963. $storeProductAttrServices = app()->make(StoreProductAttrServices::class);
  964. /** @var StoreProductCouponServices $storeProductCouponServices */
  965. $storeProductCouponServices = app()->make(StoreProductCouponServices::class);
  966. /** @var StoreDiscountsProductsServices $storeDiscountProduct */
  967. $storeDiscountProduct = app()->make(StoreDiscountsProductsServices::class);
  968. /** @var StoreProductVirtualServices $productVirtual */
  969. $productVirtual = app()->make(StoreProductVirtualServices::class);
  970. //同一链接不多次保存
  971. if (!$id && $data['soure_link']) {
  972. $productInfo = $this->dao->getOne(['soure_link' => $data['soure_link'], 'is_del' => 0], 'id');
  973. if ($productInfo) $id = (int)$productInfo['id'];
  974. }
  975. [$skuList, $id, $is_new, $data] = $this->transaction(function () use ($id, $data, $description, $storeDescriptionServices, $storeProductAttrServices, $storeProductCouponServices, $detail, $attr, $coupon_ids, $storeDiscountProduct, $productVirtual, $slider_image) {
  976. if ($id) {
  977. //上下架处理
  978. $this->setShow([$id], $data['is_show']);
  979. $oldInfo = $this->get($id)->toArray();
  980. if ($oldInfo['product_type'] != $data['product_type']) {
  981. throw new AdminException('商品类型不能切换!');
  982. }
  983. //修改不改变商品来源
  984. if ($oldInfo['type']) {
  985. $data['type'] = $oldInfo['type'];
  986. $data['relation_id'] = $oldInfo['relation_id'];
  987. }
  988. unset($data['sales']);
  989. $res = $this->dao->update($id, $data);
  990. if (!$res) throw new AdminException('修改失败');
  991. // 修改优惠套餐商品的运费模版id
  992. $storeDiscountProduct->update(['product_id' => $id], ['temp_id' => $data['temp_id']]);
  993. if (!empty($coupon_ids)) {
  994. $storeProductCouponServices->setCoupon($id, $coupon_ids);
  995. } else {
  996. $storeProductCouponServices->delete(['product_id' => $id]);
  997. }
  998. if ($oldInfo['type'] == 1 && !$oldInfo['pid'] && !$data['is_verify']) {
  999. /** @var StoreCartServices $cartService */
  1000. $cartService = app()->make(StoreCartServices::class);
  1001. $cartService->batchUpdate([$id], ['status' => 0], 'product_id');
  1002. }
  1003. $is_new = 1;
  1004. } else {
  1005. $data['add_time'] = time();
  1006. $data['code_path'] = '';
  1007. $data['spu'] = $this->createSpu();
  1008. $res = $this->dao->save($data);
  1009. if (!$res) throw new AdminException('添加失败');
  1010. $id = (int)$res->id;
  1011. if (!empty($coupon_ids)) $storeProductCouponServices->setCoupon($id, $coupon_ids);
  1012. $is_new = 0;
  1013. }
  1014. //商品详情
  1015. $storeDescriptionServices->saveDescription($id, $description);
  1016. $skuList = $storeProductAttrServices->validateProductAttr($attr, $detail, $id, 0, (int)$data['is_vip']);
  1017. foreach ($skuList['valueGroup'] as &$item) {
  1018. if (!isset($item['sum_stock']) || !$item['sum_stock']) $item['sum_stock'] = $item['stock'] ?? 0;
  1019. }
  1020. $proudctVipPrice = 0;
  1021. $detailTemp = array_column($skuList['valueGroup'], 'vip_price');
  1022. if ($detailTemp) {
  1023. $proudctVipPrice = min($detailTemp);
  1024. }
  1025. $this->dao->update($id, ['vip_price' => $proudctVipPrice]);
  1026. $valueGroup = $storeProductAttrServices->saveProductAttr($skuList, $id);
  1027. if (!$valueGroup) throw new AdminException('添加失败!');
  1028. if ($data['product_type'] == 1) {
  1029. $productVirtual->saveProductVirtual($id, $valueGroup);
  1030. }
  1031. return [$skuList, $id, $is_new, $data];
  1032. });
  1033. event('product.create', [$id, $data, $skuList, $is_new, $slider_image, $description, $is_copy, $relationData]);
  1034. }
  1035. /**
  1036. * 放入回收站
  1037. * @param int $id
  1038. * @return string
  1039. */
  1040. public function del(int $id)
  1041. {
  1042. if (!$id) throw new AdminException('参数不正确');
  1043. $productInfo = $this->dao->get($id);
  1044. if (!$productInfo) throw new AdminException('商品数据不存在');
  1045. $msg = '';
  1046. $data = $update = [];
  1047. if ($productInfo['is_del'] == 1) {
  1048. $data['is_del'] = 0;
  1049. $update = ['is_del' => 0];
  1050. $msg = '成功恢复商品';
  1051. } else {
  1052. $data['is_del'] = 1;
  1053. $data['is_show'] = 0;
  1054. $update = ['is_del' => 1];
  1055. $msg = '成功移到回收站';
  1056. }
  1057. $this->transaction(function () use ($id, $data, $productInfo, $update) {
  1058. //门店商品处理
  1059. switch ($productInfo['type']) {
  1060. case 0://平台商品
  1061. $this->dao->update(['pid' => $id], $update);
  1062. break;
  1063. case 1://门店商品
  1064. /** @var SystemStoreServices $storeServices */
  1065. $storeServices = app()->make(SystemStoreServices::class);
  1066. $storeInfo = $storeServices->getStoreInfo((int)$productInfo['relation_id']);
  1067. $data['is_verify'] = 0;
  1068. //门店开启免审
  1069. if (isset($storeInfo['product_verify_status']) && $storeInfo['product_verify_status']) {
  1070. $data['is_verify'] = 1;
  1071. }
  1072. break;
  1073. case 2://供应商
  1074. $data['is_verify'] = 0;
  1075. break;
  1076. }
  1077. $res = $this->dao->update($id, $data);
  1078. if (!$res) throw new AdminException($productInfo['is_del'] == 1 ? '恢复失败,请稍候再试!' : '删除失败,请稍候再试!');
  1079. });
  1080. return $msg;
  1081. }
  1082. /**
  1083. * 获取选择的商品列表
  1084. * @param array $where
  1085. * @param bool $isStock
  1086. * @param int $limit
  1087. * @return array
  1088. */
  1089. public function searchList(array $where, bool $isStock = false, int $limit = 0)
  1090. {
  1091. $store_stock = sys_config('store_stock');
  1092. $where['store_stock'] = $store_stock > 0 ? $store_stock : 2;
  1093. $data = $this->getProductList($where, $isStock, $limit, ['attrValue', 'descriptions']);
  1094. if ($data['list']) {
  1095. $cateIds = implode(',', array_column($data['list'], 'cate_id'));
  1096. /** @var StoreProductCategoryServices $categoryService */
  1097. $categoryService = app()->make(StoreProductCategoryServices::class);
  1098. $cateList = $categoryService->getCateParentAndChildName($cateIds);
  1099. /** @var StoreProductLabelServices $storeProductLabelServices */
  1100. $storeProductLabelServices = app()->make(StoreProductLabelServices::class);
  1101. foreach ($data['list'] as &$item) {
  1102. $item['cate_name'] = '';
  1103. if (isset($item['cate_id']) && $item['cate_id']) {
  1104. $cate_ids = explode(',', $item['cate_id']);
  1105. $cate_name = $categoryService->getCateName($cate_ids, $cateList);
  1106. if ($cate_name) {
  1107. $item['cate_name'] = is_array($cate_name) ? implode(',', $cate_name) : '';
  1108. }
  1109. }
  1110. $item['give_integral'] = floatval($item['give_integral']);
  1111. $item['price'] = floatval($item['price']);
  1112. $item['vip_price'] = floatval($item['vip_price']);
  1113. $item['ot_price'] = floatval($item['ot_price']);
  1114. $item['postage'] = floatval($item['postage']);
  1115. $item['cost'] = floatval($item['cost']);
  1116. $item['delivery_type'] = is_string($item['delivery_type']) ? explode(',', $item['delivery_type']) : $item['delivery_type'];
  1117. $item['store_label'] = '';
  1118. if ($item['store_label_id']) {
  1119. $storeLabelList = $storeProductLabelServices->getColumn([['relation_id', '=', 0], ['type', '=', 0], ['id', 'IN', $item['store_label_id']]], 'id,label_name');
  1120. $item['store_label'] = $storeLabelList ? implode(',', array_column($storeLabelList, 'label_name')) : '';
  1121. }
  1122. }
  1123. }
  1124. return $data;
  1125. }
  1126. /**
  1127. * 后台获取商品列表展示
  1128. * @param array $where
  1129. * @param bool $isStock
  1130. * @param int $limit
  1131. * @param array $with
  1132. * @return array
  1133. * @throws \think\db\exception\DataNotFoundException
  1134. * @throws \think\db\exception\DbException
  1135. * @throws \think\db\exception\ModelNotFoundException
  1136. */
  1137. public function getProductList(array $where, bool $isStock = true, int $limit = 0, array $with = ['attrValue'])
  1138. {
  1139. $field = ['*'];
  1140. if ($isStock) {
  1141. $prefix = Config::get('database.connections.' . Config::get('database.default') . '.prefix');
  1142. $field = [
  1143. '*',
  1144. '(SELECT count(*) FROM `' . $prefix . 'user_relation` WHERE `relation_id` = `' . $prefix . 'store_product`.`id` AND `type` = \'collect\') as collect',
  1145. '(SELECT count(*) FROM `' . $prefix . 'user_relation` WHERE `relation_id` = `' . $prefix . 'store_product`.`id` AND `type` = \'like\') as likes',
  1146. '(SELECT SUM(stock) FROM `' . $prefix . 'store_product_attr_value` WHERE `product_id` = `' . $prefix . 'store_product`.`id` AND `type` = 0) as stock',
  1147. // '(SELECT SUM(sales) FROM `' . $prefix . 'store_product_attr_value` WHERE `product_id` = `' . $prefix . 'store_product`.`id` AND `type` = 0) as sales',
  1148. '(SELECT count(*) FROM `' . $prefix . 'store_visit` WHERE `product_id` = `' . $prefix . 'store_product`.`id` AND `product_type` = \'product\') as visitor',
  1149. ];
  1150. }
  1151. if ($limit) {
  1152. [$page] = $this->getPageValue();
  1153. } else {
  1154. [$page, $limit] = $this->getPageValue();
  1155. }
  1156. $list = $this->dao->getSearchList($where, $page, $limit, $field, '', $with);
  1157. $count = $this->dao->getCount($where);
  1158. return compact('count', 'list');
  1159. }
  1160. /**
  1161. * 获取商品规格
  1162. * @param int $id
  1163. * @param int $type
  1164. * @return array
  1165. */
  1166. public function getProductRules(int $id, int $type = 0)
  1167. {
  1168. $productInfo = $this->dao->get($id);
  1169. if (!$productInfo) {
  1170. throw new ValidateException('商品不存在');
  1171. }
  1172. $product_type = $productInfo['product_type'] ?? $data['product_type'] ?? 0; //商品类型
  1173. /** @var StoreProductAttrServices $storeProductAttrService */
  1174. $storeProductAttrService = app()->make(StoreProductAttrServices::class);
  1175. /** @var StoreProductAttrValueServices $storeProductAttrValueServices */
  1176. $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
  1177. $productAttr = $storeProductAttrService->getProductAttr(['product_id' => $id, 'type' => 0]);
  1178. if (!$productAttr) return [];
  1179. $attr = [];
  1180. foreach ($productAttr as $key => $value) {
  1181. $attr[$key]['value'] = $value['attr_name'];
  1182. $attr[$key]['detailValue'] = '';
  1183. $attr[$key]['attrHidden'] = true;
  1184. $attr[$key]['detail'] = $value['attr_values'];
  1185. }
  1186. $value = attr_format($attr)[1];
  1187. $valueNew = [];
  1188. $count = 0;
  1189. // $sukValue = $storeProductAttrValueServices->getSkuArray(['product_id' => $id, 'type' => $type]);
  1190. $sukValue = $sukDefaultValue = $storeProductAttrValueServices->getSkuArray(['product_id' => $id, 'type' => 0]);
  1191. foreach ($value as $key => $item) {
  1192. $detail = $item['detail'];
  1193. // sort($item['detail'], SORT_STRING);
  1194. $suk = implode(',', $item['detail']);
  1195. if (!isset($sukDefaultValue[$suk])) continue;
  1196. // if (!isset($sukValue[$suk])) {
  1197. // $sukValue[$suk] = $sukDefaultValue[$suk];
  1198. // }
  1199. foreach (array_keys($detail) as $k => $title) {
  1200. $header[$k]['title'] = $title;
  1201. $header[$k]['align'] = 'center';
  1202. $header[$k]['minWidth'] = 80;
  1203. }
  1204. $valueNew[$count]['value'] = '';
  1205. foreach (array_values($detail) as $k => $v) {
  1206. $valueNew[$count]['value' . ($k + 1)] = $v;
  1207. $header[$k]['key'] = 'value' . ($k + 1);
  1208. $valueNew[$count]['value'] .= $valueNew[$count]['value'] == '' ? $v : ',' . $v;
  1209. }
  1210. if ($type == 4) {
  1211. $valueNew[$count]['integral'] = $sukValue[$suk]['integral'] ?? 0;
  1212. $valueNew[$count]['product_id'] = $sukValue[$suk]['product_id'];
  1213. }
  1214. $valueNew[$count]['detail'] = $detail;
  1215. $valueNew[$count]['pic'] = $sukValue[$suk]['pic'];
  1216. $valueNew[$count]['price'] = floatval($sukValue[$suk]['price']);
  1217. if ($type == 2) $valueNew[$count]['min_price'] = 0;
  1218. if ($type == 3) $valueNew[$count]['r_price'] = floatval($sukValue[$suk]['price']);
  1219. if ($type == 0) $valueNew[$count]['p_price'] = floatval($sukValue[$suk]['price']);
  1220. $valueNew[$count]['cost'] = floatval($sukValue[$suk]['cost']);
  1221. $valueNew[$count]['ot_price'] = floatval($sukValue[$suk]['ot_price']);
  1222. $valueNew[$count]['stock'] = intval($sukValue[$suk]['stock']);
  1223. $valueNew[$count]['quota'] = intval($sukValue[$suk]['quota']);
  1224. $valueNew[$count]['bar_code'] = $sukValue[$suk]['bar_code'] ?? '';
  1225. $valueNew[$count]['code'] = $sukValue[$suk]['code'] ?? '';
  1226. $valueNew[$count]['weight'] = $sukValue[$suk]['weight'] ? floatval($sukValue[$suk]['weight']) : 0;
  1227. $valueNew[$count]['volume'] = $sukValue[$suk]['volume'] ? floatval($sukValue[$suk]['volume']) : 0;
  1228. $valueNew[$count]['brokerage'] = $sukValue[$suk]['brokerage'] ? floatval($sukValue[$suk]['brokerage']) : 0;
  1229. $valueNew[$count]['brokerage_two'] = $sukValue[$suk]['brokerage_two'] ? floatval($sukValue[$suk]['brokerage_two']) : 0;
  1230. $count++;
  1231. }
  1232. $header[] = ['title' => '图片', 'slot' => 'pic', 'align' => 'center', 'minWidth' => 120];
  1233. if ($type == 1) {
  1234. $header[] = ['title' => '秒杀价', 'key' => 'price', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
  1235. $header[] = ['title' => '成本价', 'key' => 'cost', 'align' => 'center', 'minWidth' => 80];
  1236. $header[] = ['title' => '原价', 'key' => 'ot_price', 'align' => 'center', 'minWidth' => 80];
  1237. } elseif ($type == 2) {
  1238. $header[] = ['title' => '砍价起始金额', 'slot' => 'price', 'align' => 'center', 'minWidth' => 80];
  1239. $header[] = ['title' => '砍价最低价', 'slot' => 'min_price', 'align' => 'center', 'minWidth' => 80];
  1240. $header[] = ['title' => '成本价', 'key' => 'cost', 'align' => 'center', 'minWidth' => 80];
  1241. $header[] = ['title' => '原价', 'key' => 'ot_price', 'align' => 'center', 'minWidth' => 80];
  1242. } elseif ($type == 3) {
  1243. $header[] = ['title' => '拼团价', 'key' => 'price', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
  1244. $header[] = ['title' => '成本价', 'key' => 'cost', 'align' => 'center', 'minWidth' => 80];
  1245. $header[] = ['title' => '日常售价', 'key' => 'r_price', 'align' => 'center', 'minWidth' => 80];
  1246. } elseif ($type == 4) {
  1247. $header[] = ['title' => '兑换积分', 'key' => 'integral', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
  1248. $header[] = ['title' => '金额', 'key' => 'price', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
  1249. } else {
  1250. $header[] = ['title' => '成本价', 'key' => 'cost', 'align' => 'center', 'minWidth' => 80];
  1251. $header[] = ['title' => '原价', 'key' => 'ot_price', 'align' => 'center', 'minWidth' => 80];
  1252. $header[] = ['title' => '售价', 'key' => 'p_price', 'align' => 'center', 'minWidth' => 80];
  1253. }
  1254. $header[] = ['title' => '库存', 'key' => 'stock', 'align' => 'center', 'minWidth' => 80];
  1255. if ($type == 2) {
  1256. $header[] = ['title' => '限量', 'slot' => 'quota', 'align' => 'center', 'minWidth' => 80];
  1257. } else if ($type == 4) {
  1258. $header[] = ['title' => '兑换次数', 'key' => 'quota', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
  1259. } else {
  1260. $header[] = ['title' => '限量', 'key' => 'quota', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
  1261. }
  1262. $header[] = ['title' => '重量(KG)', 'key' => 'weight', 'align' => 'center', 'minWidth' => 80];
  1263. $header[] = ['title' => '体积(m³)', 'key' => 'volume', 'align' => 'center', 'minWidth' => 80];
  1264. $header[] = ['title' => '商品条形码', 'key' => 'bar_code', 'align' => 'center', 'minWidth' => 80];
  1265. $header[] = ['title' => '商品编码', 'key' => 'code', 'align' => 'center', 'minWidth' => 80];
  1266. return ['items' => $attr, 'attrs' => $valueNew, 'header' => $header, 'product_type' => $product_type];
  1267. }
  1268. /**
  1269. * 检查商品是否有活动
  1270. * @param $id
  1271. * @return bool
  1272. */
  1273. public function checkActivity($id = 0)
  1274. {
  1275. if ($id) {
  1276. /** @var StoreSeckillServices $storeSeckillService */
  1277. $storeSeckillService = app()->make(StoreSeckillServices::class);
  1278. /** @var StoreBargainServices $storeBargainService */
  1279. $storeBargainService = app()->make(StoreBargainServices::class);
  1280. /** @var StoreCombinationServices $storeCombinationService */
  1281. $storeCombinationService = app()->make(StoreCombinationServices::class);
  1282. $res1 = $storeSeckillService->count(['product_id' => $id, 'is_del' => 0, 'status' => 1, 'seckill_time' => 1]);
  1283. $res2 = $storeBargainService->count(['product_id' => $id, 'is_del' => 0, 'status' => 1, 'bargain_time' => 1]);
  1284. $res3 = $storeCombinationService->count(['product_id' => $id, 'is_del' => 0, 'is_show' => 1, 'pinkIngTime' => 1]);
  1285. if ($res1 || $res2 || $res3) throw new AdminException('商品有活动开启,无法进行此操作');
  1286. }
  1287. return true;
  1288. }
  1289. /**
  1290. * 获取临时缓存商品数据
  1291. * @param int $id
  1292. * @return false|mixed|string|null
  1293. * @throws \think\db\exception\DataNotFoundException
  1294. * @throws \think\db\exception\DbException
  1295. * @throws \think\db\exception\ModelNotFoundException
  1296. * @author 等风来
  1297. * @email 136327134@qq.com
  1298. * @date 2022/11/3
  1299. */
  1300. public function getCacheProductInfo(int $id)
  1301. {
  1302. $storeInfo = $this->dao->cacheTag()->remember((string)$id, function () use ($id) {
  1303. $storeInfo = $this->dao->getOne(['id' => $id], '*', ['descriptions']);
  1304. if (!$storeInfo) {
  1305. throw new ValidateException('商品不存在');
  1306. } else {
  1307. $storeInfo = $storeInfo->toArray();
  1308. }
  1309. return $storeInfo;
  1310. }, 600);
  1311. return $storeInfo;
  1312. }
  1313. /**
  1314. * 保存
  1315. * @param array $data
  1316. * @return mixed
  1317. */
  1318. public function create(array $data)
  1319. {
  1320. return $this->dao->save($data);
  1321. }
  1322. /**
  1323. * 前台获取商品列表
  1324. * @param array $where
  1325. * @param int $uid
  1326. * @param int $promotions_type
  1327. * @return array|array[]
  1328. * @throws \think\db\exception\DataNotFoundException
  1329. * @throws \think\db\exception\DbException
  1330. * @throws \think\db\exception\ModelNotFoundException
  1331. */
  1332. public function getGoodsList(array $where, int $uid, int $promotions_type = 0)
  1333. {
  1334. $where['is_verify'] = 1;
  1335. $where['is_show'] = 1;
  1336. $where['is_del'] = 0;
  1337. $collate_code_id = 0;
  1338. if (isset($where['collate_code_id'])) {
  1339. $collate_code_id = $where['collate_code_id'];
  1340. }
  1341. if (isset($where['store_name']) && $where['store_name']) {
  1342. /** @var UserSearchServices $userSearchServices */
  1343. $userSearchServices = app()->make(UserSearchServices::class);
  1344. $searchIds = $userSearchServices->vicSearch($uid, $where['store_name'], $where);
  1345. if ($searchIds) {//之前查询结果记录
  1346. $where['ids'] = $searchIds;
  1347. unset($where['store_name']);
  1348. }
  1349. }
  1350. //优惠活动凑单
  1351. if (isset($where['promotions_id']) && $where['promotions_id']) {
  1352. /** @var StorePromotionsServices $storePromotionsServices */
  1353. $storePromotionsServices = app()->make(StorePromotionsServices::class);
  1354. $promotionsWhere = $storePromotionsServices->collectOrderProduct((int)$where['promotions_id']);
  1355. $where = array_merge($where, $promotionsWhere);
  1356. }
  1357. unset($where['promotions_id']);
  1358. if (isset($where['productId']) && $where['productId'] !== '') {
  1359. $where['ids'] = explode(',', $where['productId']);
  1360. $where['ids'] = array_unique(array_map('intval', $where['ids']));
  1361. unset($where['productId']);
  1362. }
  1363. $where['star'] = 1;
  1364. $where['is_vip_product'] = 0;
  1365. $discount = 100;
  1366. $level_name = '';
  1367. if (!$promotions_type && $uid) {
  1368. /** @var UserServices $user */
  1369. $user = app()->make(UserServices::class);
  1370. $userInfo = $user->getUserCacheInfo($uid);
  1371. $is_vip = $userInfo['is_money_level'] ?? 0;
  1372. $where['is_vip_product'] = $is_vip ? -1 : 0;
  1373. //用户等级是否开启
  1374. /** @var SystemUserLevelServices $systemLevel */
  1375. $systemLevel = app()->make(SystemUserLevelServices::class);
  1376. $levelInfo = $systemLevel->getLevelCache((int)($userInfo['level'] ?? 0));
  1377. if (sys_config('member_func_status', 1) && $levelInfo) {
  1378. $discount = $levelInfo['discount'] ?? 100;
  1379. }
  1380. $level_name = $levelInfo['name'] ?? '';
  1381. }
  1382. [$page, $limit] = $this->getPageValue();
  1383. $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'];
  1384. $list = $this->dao->getSearchList($where, $page, $limit, $field, '', ['couponId']);
  1385. var_dump(StoreProduct::getLastSql());
  1386. if ($list) {
  1387. /** @var MemberCardServices $memberCardService */
  1388. $memberCardService = app()->make(MemberCardServices::class);
  1389. $vipStatus = $memberCardService->isOpenMemberCardCache('vip_price') && sys_config('svip_price_status', 1);
  1390. /** @var SystemFormServices $systemFormServices */
  1391. $systemFormServices = app()->make(SystemFormServices::class);
  1392. $systemForms = $systemFormServices->getColumn([['id', 'in', array_unique(array_column($list, 'system_form_id'))], ['is_del', '=', 0]], 'id,value', 'id');
  1393. foreach ($list as &$item) {
  1394. $minData = $this->getMinPrice($uid, $item, $discount);
  1395. $item['price_type'] = $minData['price_type'] ?? '';
  1396. $item['level_name'] = $level_name;
  1397. $item['vip_price'] = $minData['vip_price'] ?? 0;
  1398. if ($item['price_type'] == 'member' && (!$item['is_vip'] || !$vipStatus)) {
  1399. $item['vip_price'] = 0;
  1400. }
  1401. $custom_form = $systemForms[$item['system_form_id']]['value'] ?? [];
  1402. $item['custom_form'] = is_string($custom_form) ? json_decode($custom_form, true) : $custom_form;
  1403. $item['cart_button'] = $item['product_type'] > 0 || $item['is_presale_product'] || $item['system_form_id'] ? 0 : 1;
  1404. if (isset($item['star']) && count($item['star'])) {
  1405. $item['star'] = bcdiv((string)array_sum(array_column($item['star'], 'product_score')), (string)count($item['star']), 1);
  1406. } else {
  1407. $item['star'] = config('admin.product_default_star');
  1408. }
  1409. $item['presale_pay_status'] = $this->checkPresaleProductPay((int)$item['id'], $item);
  1410. if (!$item['video_open']) {
  1411. $item['video_link'] = '';
  1412. }
  1413. }
  1414. $list = $this->getActivityList($list);
  1415. $list = $this->getProduceOtherList($list, $uid, isset($where['status']) && !!$where['status'], $collate_code_id);
  1416. $list = $this->getProductPromotions($list, $promotions_type ? [$promotions_type] : []);
  1417. }
  1418. return $list;
  1419. }
  1420. /**
  1421. * 搜索获取商品品牌列表
  1422. * @param array $where
  1423. * @return array
  1424. * @throws \think\db\exception\DataNotFoundException
  1425. * @throws \think\db\exception\DbException
  1426. * @throws \think\db\exception\ModelNotFoundException
  1427. */
  1428. public function getBrandList(array $where)
  1429. {
  1430. $where['status'] = 1;
  1431. /** @var StoreProductCategoryBrandServices $productCategoryBrandServices */
  1432. $productCategoryBrandServices = app()->make(StoreProductCategoryBrandServices::class);
  1433. return $productCategoryBrandServices->getList($where, 'distinct(`brand_id`) as id, brand_name');
  1434. }
  1435. /**
  1436. * 获取商品所在优惠活动
  1437. * @param array $list
  1438. * @param array $promotions_type
  1439. * @return array|null
  1440. * @throws \think\db\exception\DataNotFoundException
  1441. * @throws \think\db\exception\DbException
  1442. * @throws \think\db\exception\ModelNotFoundException
  1443. */
  1444. public function getProductPromotions(array $list, array $promotions_type = [])
  1445. {
  1446. if (!$list) {
  1447. return $list;
  1448. }
  1449. $productIds = array_column($list, 'id');
  1450. /** @var StorePromotionsServices $storePromotionsServices */
  1451. $storePromotionsServices = app()->make(StorePromotionsServices::class);
  1452. $with = ['products' => function ($query) {
  1453. $query->field('promotions_id,product_id,is_all,unique');
  1454. }];
  1455. $field = 'id,promotions_type,name,desc,image,promotions_type,title,product_id,product_partake_type,discount,discount_type,start_time,stop_time,applicable_type,applicable_store_id';
  1456. [$promotionsArr, $productDetails, $promotionsDetail] = $storePromotionsServices->getProductsPromotionsDetail($productIds, $field, $with, $promotions_type);
  1457. $promotionsArr = array_combine(array_column($promotionsArr, 'id'), $promotionsArr);
  1458. foreach ($list as &$item) {
  1459. $item['product_id'] = $item['id'];
  1460. $item['promotions'] = $item['activity_frame'] = $item['activity_background'] = [];
  1461. if ($item['type'] == 1) {//门店商品
  1462. if (isset($item['pid']) && $item['pid']) {//平台共享到门店商品
  1463. $id = $item['pid'];
  1464. } else {//门店独立商品
  1465. continue;
  1466. }
  1467. } else {
  1468. $id = $item['id'];
  1469. }
  1470. $item['promotions'] = $item['activity_frame'] = $item['activity_background'] = [];
  1471. $promotionsIds = $productDetails[$id] ?? [];
  1472. if ($promotionsIds) {
  1473. foreach ($promotionsIds as $id) {
  1474. $promotions = $promotionsArr[$id] ?? [];
  1475. switch ($promotions['promotions_type']) {
  1476. case 1:
  1477. case 2:
  1478. case 3:
  1479. case 4:
  1480. if (!$promotions_type) {//无指定优惠类型
  1481. if ($item['promotions']) {
  1482. if (($promotions['promotions_type'] ?? 0) <= ($item['promotions']['promotions_type'] ?? 0)) {
  1483. if (($promotions['promotions_type']['discount'] ?? 0) > ($item['promotions']['discount'] ?? 0)) {
  1484. $item['promotions'] = $promotions;
  1485. }
  1486. } else {
  1487. break;
  1488. }
  1489. } else {
  1490. $item['promotions'] = $promotions;
  1491. }
  1492. } else {
  1493. if (!$item['promotions']) {
  1494. $item['promotions'] = $promotions;
  1495. } else {//同类活动展示最新的一个
  1496. break;
  1497. }
  1498. break;
  1499. }
  1500. break;
  1501. case 5://边框
  1502. if (!$item['activity_frame']) {
  1503. $item['activity_frame'] = [
  1504. 'id' => $promotions['id'],
  1505. 'name' => $promotions['name'],
  1506. 'image' => $promotions['image'],
  1507. ];
  1508. } else {//同类活动展示最新的一个
  1509. break;
  1510. }
  1511. break;
  1512. case 6://背景
  1513. if (!$item['activity_background']) {
  1514. $item['activity_background'] = [
  1515. 'id' => $promotions['id'],
  1516. 'name' => $promotions['name'],
  1517. 'image' => $promotions['image'],
  1518. ];
  1519. } else {//同类活动展示最新的一个
  1520. break;
  1521. }
  1522. break;
  1523. default:
  1524. break;
  1525. }
  1526. }
  1527. }
  1528. }
  1529. return $list;
  1530. }
  1531. /**
  1532. * 获取某些模板所需得购物车数量
  1533. * @param array $list
  1534. * @param int $uid
  1535. * @param bool $type
  1536. * @return array
  1537. */
  1538. public function getProduceOtherList(array $list, int $uid, bool $type = true, int $collate_code_id = 0)
  1539. {
  1540. if (!$type || !$list) {
  1541. return $list;
  1542. }
  1543. $productIds = array_column($list, 'id');
  1544. if ($productIds) {
  1545. /** @var StoreProductAttrValueServices $services */
  1546. $services = app()->make(StoreProductAttrValueServices::class);
  1547. $attList = $services->getSkuArray([
  1548. 'product_id' => $productIds,
  1549. 'type' => 0
  1550. ], 'count(*)', 'product_id');
  1551. $store_id = (int)$this->getItem('store_id', 0);
  1552. $staff_id = (int)$this->getItem('staff_id', 0);
  1553. $tourist_uid = (int)$this->getItem('tourist_uid', 0);
  1554. if ($uid || $tourist_uid) {
  1555. if ($collate_code_id) {
  1556. /** @var UserCollagePartakeServices $cartServices */
  1557. $cartServices = app()->make(UserCollagePartakeServices::class);
  1558. $cartNumList = $cartServices->productIdByCartNum($productIds, $uid, $store_id, $collate_code_id);
  1559. } else {
  1560. /** @var StoreCartServices $cartServices */
  1561. $cartServices = app()->make(StoreCartServices::class);
  1562. $cartNumList = $cartServices->productIdByCartNum($productIds, $uid, $staff_id, $tourist_uid, $store_id);
  1563. }
  1564. $data = [];
  1565. foreach ($cartNumList as $item) {
  1566. $data[$item['product_id']][] = $item['cart_num'];
  1567. }
  1568. $newNumList = [];
  1569. foreach ($data as $key => $item) {
  1570. $newNumList[$key] = array_sum($item);
  1571. }
  1572. $cartNumList = $newNumList;
  1573. } else {
  1574. $cartNumList = [];
  1575. }
  1576. foreach ($list as &$item) {
  1577. if (isset($item['spec_type']) && $item['spec_type']) {
  1578. $item['is_att'] = isset($attList[$item['id']]) && $attList[$item['id']];
  1579. } else {
  1580. $item['is_att'] = false;
  1581. }
  1582. $item['cart_num'] = $cartNumList[$item['id']] ?? 0;
  1583. }
  1584. }
  1585. return $list;
  1586. }
  1587. /**
  1588. * 获取商品活动标签
  1589. * @param array $list
  1590. * @param bool $status
  1591. * @return array|array[]|mixed
  1592. */
  1593. public function getActivityList(array $list, bool $status = true)
  1594. {
  1595. if (!$list) return [];
  1596. if (!$status) {//一维数组
  1597. $list = [$list];
  1598. }
  1599. $productIds = [];
  1600. //处理平台共享到门店、门店独立商品活动
  1601. foreach ($list as $product) {
  1602. if (isset($product['type']) && isset($product['pid'])) {
  1603. if ($product['type'] == 1) {//门店商品
  1604. if ($product['pid']) {//平台共享到门店商品
  1605. $productIds[] = $product['pid'];
  1606. } else {//门店独立商品
  1607. }
  1608. } else {
  1609. $productIds[] = $product['id'];
  1610. }
  1611. }
  1612. }
  1613. if ($productIds) {
  1614. /** @var StoreSeckillServices $storeSeckillService */
  1615. $storeSeckillService = app()->make(StoreSeckillServices::class);
  1616. /** @var StoreBargainServices $storeBargainServices */
  1617. $storeBargainServices = app()->make(StoreBargainServices::class);
  1618. /** @var StoreCombinationServices $storeCombinationServices */
  1619. $storeCombinationServices = app()->make(StoreCombinationServices::class);
  1620. $seckillIdsList = $storeSeckillService->getSeckillIdsArrayCache($productIds);
  1621. $pinkIdsList = $storeCombinationServices->getPinkIdsArrayCache($productIds);
  1622. $bargrainIdsList = $storeBargainServices->getBargainIdsArrayCache($productIds);
  1623. foreach ($list as &$item) {
  1624. if ($item['type'] == 1) {//门店商品
  1625. if ($item['pid']) {//平台共享到门店商品
  1626. $id = $item['pid'];
  1627. } else {//门店独立商品
  1628. continue;
  1629. }
  1630. } else {
  1631. $id = $item['id'];
  1632. }
  1633. $seckillId = $seckillIdsList && is_array($seckillIdsList) ? array_filter($seckillIdsList, function ($val) use ($item, $id) {
  1634. if ($val['product_id'] === $id) {
  1635. return $val;
  1636. }
  1637. }) : [];
  1638. $item['activity'] = $this->activity($item['activity'],
  1639. $item['id'],
  1640. $pinkIdsList[$id] ?? 0,
  1641. $seckillId,
  1642. $bargrainIdsList[$id] ?? 0,
  1643. $status);
  1644. if (isset($item['couponId'])) {
  1645. $item['checkCoupon'] = (bool)count($item['couponId']);
  1646. unset($item['couponId']);
  1647. } else {
  1648. $item['checkCoupon'] = false;
  1649. }
  1650. }
  1651. }
  1652. if ($status) {
  1653. return $list;
  1654. } else {
  1655. return $list[0]['activity'];
  1656. }
  1657. }
  1658. /**
  1659. * 获取商品在此时段活动优先类型
  1660. * @param string $activity
  1661. * @param int $id
  1662. * @param int $combinationId
  1663. * @param array $seckillId
  1664. * @param int $bargainId
  1665. * @param bool $status
  1666. * @return array
  1667. */
  1668. public function activity(string $activity, int $id, int $combinationId, array $seckillId, int $bargainId, bool $status = true)
  1669. {
  1670. if (!$activity) {
  1671. $activity = '0,1,2,3';//如果老商品没有活动顺序,默认活动顺序,秒杀-砍价-拼团
  1672. }
  1673. $activity = explode(',', $activity);
  1674. if ($activity[0] == 0 && $status) return [];
  1675. $activityId = [];
  1676. $time = 0;
  1677. if ($seckillId) {
  1678. /** @var StoreSeckillTimeServices $storeSeckillTimeServices */
  1679. $storeSeckillTimeServices = app()->make(StoreSeckillTimeServices::class);
  1680. $timeList = $storeSeckillTimeServices->time_list();
  1681. if ($timeList) {
  1682. $timeList = array_combine(array_column($timeList, 'id'), $timeList);
  1683. $today = date('Y-m-d');
  1684. $currentHour = date('Hi');
  1685. foreach ($seckillId as $v) {
  1686. $time_ids = is_string($v['time_id']) ? explode(',', $v['time_id']) : $v['time_id'];
  1687. if ($time_ids) {
  1688. foreach ($time_ids as $time_id) {
  1689. $timeInfo = $timeList[$time_id] ?? [];
  1690. if ($timeInfo) {
  1691. $start = str_replace(':', '', $timeInfo['start_time']);
  1692. $end = str_replace(':', '', $timeInfo['end_time']);
  1693. if ($currentHour >= $start && $currentHour < $end) {
  1694. $activityId[1] = $v['id'];
  1695. $time = strtotime($today . ' ' . $timeInfo['end_time']);
  1696. break;
  1697. }
  1698. }
  1699. }
  1700. }
  1701. }
  1702. }
  1703. }
  1704. if ($bargainId) $activityId[2] = $bargainId;
  1705. if ($combinationId) $activityId[3] = $combinationId;
  1706. $data = [];
  1707. foreach ($activity as $k => $v) {
  1708. if (array_key_exists($v, $activityId)) {
  1709. if ($status) {
  1710. $data['type'] = $v;
  1711. $data['id'] = $activityId[$v];
  1712. if ($v == 1) $data['time'] = $time;
  1713. break;
  1714. } else {
  1715. if ($v != 0) {
  1716. $arr['type'] = $v;
  1717. $arr['id'] = $activityId[$v];
  1718. if ($v == 1) $arr['time'] = $time;
  1719. $data[] = $arr;
  1720. }
  1721. }
  1722. }
  1723. }
  1724. return $data;
  1725. }
  1726. /**
  1727. * 获取热门商品
  1728. * @param array $where
  1729. * @param string $order
  1730. * @return array|array[]
  1731. * @throws \think\db\exception\DataNotFoundException
  1732. * @throws \think\db\exception\DbException
  1733. * @throws \think\db\exception\ModelNotFoundException
  1734. */
  1735. public function getProducts(array $where, string $order = '', int $num = 0, array $with = ['couponId', 'descriptions'])
  1736. {
  1737. [$page, $limit] = $this->getPageValue();
  1738. if ($num) {
  1739. $page = 1;
  1740. $limit = $num;
  1741. }
  1742. $list = $this->dao->getSearchList($where, $page, $limit, ['id,pid,type,store_name,cate_id,image,IFNULL(sales, 0) + IFNULL(ficti, 0) as sales,price,stock,activity,unit_name'], $order, $with);
  1743. $list = $this->getActivityList($list);
  1744. $list = $this->getProductPromotions($list);
  1745. return $list;
  1746. }
  1747. /**
  1748. * 检测预售商品是否可以购买
  1749. * @param int $id
  1750. * @param array $productInfo
  1751. * @return int
  1752. */
  1753. public function checkPresaleProductPay(int $id, array $productInfo = [])
  1754. {
  1755. if (!$id) return 0;
  1756. if (!$productInfo) {
  1757. $productInfo = $this->getCacheProductInfo($id);
  1758. if (!$productInfo) {
  1759. return 0;
  1760. }
  1761. }
  1762. if (!isset($productInfo['is_presale_product']) || !isset($productInfo['presale_start_time']) || !isset($productInfo['presale_end_time'])) {
  1763. return 0;
  1764. }
  1765. if ($productInfo['is_presale_product']) {
  1766. if ($productInfo['presale_start_time'] > time()) {
  1767. return 1;
  1768. } elseif ($productInfo['presale_start_time'] <= time() && $productInfo['presale_start_time'] > time()) {
  1769. return 2;
  1770. } elseif ($productInfo['presale_end_time'] < time()) {
  1771. return 3;
  1772. } else {
  1773. return 0;
  1774. }
  1775. } else {
  1776. return 0;
  1777. }
  1778. }
  1779. /**
  1780. * 获取商品详情
  1781. * @param int $uid
  1782. * @param int $id
  1783. * @param int $type
  1784. * @param int $promotions_type
  1785. * @return array
  1786. * @throws \think\db\exception\DataNotFoundException
  1787. * @throws \think\db\exception\DbException
  1788. * @throws \think\db\exception\ModelNotFoundException
  1789. * @throws \throwable
  1790. */
  1791. public function productDetail(int $uid, int $id, int $type = 0, int $promotions_type = 0)
  1792. {
  1793. $data['uid'] = $uid;
  1794. $storeInfo = $this->getCacheProductInfo($id);
  1795. if (!$storeInfo) {
  1796. throw new ValidateException('商品不存在');
  1797. }
  1798. $storeInfo['description'] = $storeInfo['description'] ?: '';
  1799. /** @var DiyServices $diyServices */
  1800. $diyServices = app()->make(DiyServices::class);
  1801. $infoDiy = $diyServices->getProductDetailDiy();
  1802. //diy控制参数
  1803. if (!isset($infoDiy['is_specs']) || !$infoDiy['is_specs']) {
  1804. $storeInfo['specs'] = [];
  1805. }
  1806. $storeInfo['brand_name'] = $this->productIdByBrandName((int)$storeInfo['id'], $storeInfo);
  1807. $storeInfo['store_label'] = $storeInfo['ensure'] = [];
  1808. if ($storeInfo['store_label_id']) {
  1809. /** @var StoreProductLabelServices $storeProductLabelServices */
  1810. $storeProductLabelServices = app()->make(StoreProductLabelServices::class);
  1811. $storeInfo['store_label'] = $storeProductLabelServices->getLabelCache($storeInfo['store_label_id'], ['id', 'label_name']);
  1812. }
  1813. if ($storeInfo['ensure_id'] && isset($infoDiy['is_ensure']) && $infoDiy['is_ensure']) {
  1814. /** @var StoreProductEnsureServices $storeProductEnsureServices */
  1815. $storeProductEnsureServices = app()->make(StoreProductEnsureServices::class);
  1816. $storeInfo['ensure'] = $storeProductEnsureServices->getEnsurCache($storeInfo['ensure_id'], ['id', 'name', 'image', 'desc']);
  1817. }
  1818. $discount = isset($storeInfo['promotions'][0]['promotions_type']) && $storeInfo['promotions'][0]['promotions_type'] == 1 ? $storeInfo['promotions'][0]['discount'] : -1;
  1819. $configData = SystemConfigService::more(['site_url', 'tengxun_map_key', 'store_self_mention', 'routine_contact_type', 'site_name', 'share_qrcode', 'store_func_status', 'product_poster_title']);
  1820. $siteUrl = $configData['site_url'] ?? '';
  1821. if ($storeInfo['video_open']) {
  1822. if ($storeInfo['video_link'] && strpos($storeInfo['video_link'], 'http') === false) {
  1823. $storeInfo['video_link'] = $siteUrl . $storeInfo['video_link'];
  1824. }
  1825. } else {
  1826. $storeInfo['video_link'] = '';
  1827. }
  1828. $storeInfo['image'] = set_file_url($storeInfo['image'], $siteUrl);
  1829. $storeInfo['image_base'] = set_file_url($storeInfo['image'], $siteUrl);
  1830. $storeInfo['fsales'] = $storeInfo['ficti'] + $storeInfo['sales'];
  1831. // /** @var QrcodeServices $qrcodeService */
  1832. // $qrcodeService = app()->make(QrcodeServices::class);
  1833. // $storeInfo['code_base'] = $qrcodeService->getWechatQrcodePath($id . '_product_detail_wap.jpg', '/pages/goods_details/index?id=' . $id);
  1834. /** @var UserRelationServices $userRelationServices */
  1835. $userRelationServices = app()->make(UserRelationServices::class);
  1836. $storeInfo['userCollect'] = $userRelationServices->isProductRelationCache(['uid' => $uid, 'relation_id' => $id, 'type' => 'collect', 'category' => UserRelationServices::CATEGORY_PRODUCT]);
  1837. $storeInfo['userLike'] = 0;
  1838. //预售相关
  1839. $storeInfo['presale_pay_status'] = $this->checkPresaleProductPay($id, $storeInfo);
  1840. $storeInfo['presale_start_time'] = $storeInfo['presale_start_time'] ? date('Y-m-d H:i', $storeInfo['presale_start_time']) : '';
  1841. $storeInfo['presale_end_time'] = $storeInfo['presale_end_time'] ? date('Y-m-d H:i', $storeInfo['presale_end_time']) : '';
  1842. //系统表单
  1843. $storeInfo['custom_form'] = [];
  1844. if ($storeInfo['system_form_id']) {
  1845. /** @var SystemFormServices $systemFormServices */
  1846. $systemFormServices = app()->make(SystemFormServices::class);
  1847. $systemForm = $systemFormServices->value(['id' => $storeInfo['system_form_id']], 'value');
  1848. if ($systemForm) {
  1849. $storeInfo['custom_form'] = is_string($systemForm) ? json_decode($systemForm, true) : $systemForm;
  1850. }
  1851. }
  1852. //有自定义表单或预售或虚拟不展示加入购物车按钮
  1853. $storeInfo['cart_button'] = $storeInfo['custom_form'] || $storeInfo['is_presale_product'] || $storeInfo['product_type'] > 0 ? 0 : 1;
  1854. /** @var StoreProductAttrServices $storeProductAttrServices */
  1855. $storeProductAttrServices = app()->make(StoreProductAttrServices::class);
  1856. [$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetailCache($id, $uid, $type, 0, 0, $storeInfo, $discount);
  1857. //无属性添加默认属性
  1858. if (empty($productValue)) {
  1859. $attr = [
  1860. [
  1861. 'value' => '规格',
  1862. 'detailValue' => '',
  1863. 'attrHidden' => '',
  1864. 'detail' => ['默认']
  1865. ]
  1866. ];
  1867. $detail[0] = [
  1868. 'value1' => '默认',
  1869. 'detail' => ['规格' => '默认'],
  1870. 'pic' => $storeInfo['image'],
  1871. 'price' => $storeInfo['price'],
  1872. 'cost' => $storeInfo['cost'],
  1873. 'ot_price' => $storeInfo['ot_price'],
  1874. 'stock' => $storeInfo['stock'],
  1875. 'bar_code' => '',
  1876. 'code' => '',
  1877. 'weight' => 0,
  1878. 'volume' => 0,
  1879. 'brokerage' => 0,
  1880. 'brokerage_two' => 0,
  1881. ];
  1882. $skuList = $storeProductAttrServices->validateProductAttr($attr, $detail, $id);
  1883. $storeProductAttrServices->saveProductAttr($skuList, $id, 0);
  1884. }
  1885. $attrValue = $productValue;
  1886. if (!$storeInfo['spec_type']) {
  1887. $productAttr = [];
  1888. $productValue = [];
  1889. }
  1890. $data['productAttr'] = $productAttr;
  1891. $data['productValue'] = $productValue;
  1892. $storeInfo['small_image'] = get_thumb_water($storeInfo['image']);
  1893. /**
  1894. * 判断配送方式
  1895. */
  1896. $storeInfo['delivery_type'] = $this->getDeliveryType((int)$storeInfo['id'], (int)$storeInfo['type'], (int)$storeInfo['relation_id'], $storeInfo['delivery_type']);
  1897. $data['storeInfo'] = $storeInfo;
  1898. /** @var MemberCardServices $memberCardService */
  1899. $memberCardService = app()->make(MemberCardServices::class);
  1900. $vipStatus = $memberCardService->isOpenMemberCardCache('vip_price') && sys_config('svip_price_status', 1);
  1901. $price_count = count($infoDiy['price_type']);
  1902. if ($price_count >= 1) {
  1903. //两个都选 取最低的
  1904. $minPrice = $this->getMinPrice($uid, $data['storeInfo'], null, count($infoDiy['price_type']) == 2);
  1905. $price_count = count($infoDiy['price_type']);
  1906. if ($price_count == 1) {//
  1907. if (in_array(1, $infoDiy['price_type'])) {//svip
  1908. $minPrice['price_type'] = 'member';
  1909. } else {//用户等级
  1910. $minPrice['price_type'] = 'level';
  1911. $minPrice['vip_price'] = $minPrice['level_price'];
  1912. }
  1913. }
  1914. } else {//一个都不展示
  1915. $minPrice = ['vip_price' => 0, 'price_type' => '', 'level_name' => ''];
  1916. }
  1917. $data['storeInfo'] = array_merge($data['storeInfo'], $minPrice);
  1918. if ($data['storeInfo']['price_type'] == 'member' && (!$data['storeInfo']['is_vip'] || !$vipStatus)) {
  1919. $data['storeInfo']['vip_price'] = 0;
  1920. }
  1921. $data['priceName'] = 0;
  1922. if ($uid) {
  1923. $data['priceName'] = $this->getPacketPrice($storeInfo, $attrValue, $uid);
  1924. }
  1925. $data['reply'] = [];
  1926. $data['replyChance'] = $data['replyCount'] = 0;
  1927. if (isset($infoDiy['is_reply']) && $infoDiy['is_reply']) {
  1928. /** @var StoreProductReplyServices $storeProductReplyService */
  1929. $storeProductReplyService = app()->make(StoreProductReplyServices::class);
  1930. $reply = $storeProductReplyService->getRecProductReplyCache($id, (int)($infoDiy['reply_num'] ?? 1));
  1931. $data['reply'] = $reply ? get_thumb_water($reply, 'small', ['pics']) : [];
  1932. [$replyCount, $goodReply, $replyChance] = $storeProductReplyService->getProductReplyData($id);
  1933. $data['replyChance'] = $replyChance;
  1934. $data['replyCount'] = $replyCount;
  1935. }
  1936. $data['mer_id'] = 0;
  1937. $data['mapKey'] = $configData['tengxun_map_key'] ?? '';
  1938. $data['store_func_status'] = (int)($configData['store_func_status'] ?? 1);//门店是否开启
  1939. $data['store_self_mention'] = $data['store_func_status'] ? (int)($configData['store_self_mention'] ?? 1) : 0;//门店自提是否开启
  1940. $data['routine_contact_type'] = $configData['routine_contact_type'] ?? 0;
  1941. $data['site_name'] = $configData['site_name'] ?? '';
  1942. $data['share_qrcode'] = $configData['share_qrcode'] ?? 0;
  1943. $data['product_poster_title'] = $configData['product_poster_title'] ?? '';
  1944. $data['is_store_buy'] = 0;
  1945. $count = 0;
  1946. //平台商品 && 支持门店(配送|自提)&& 适用门店(全部|部分)
  1947. if ($storeInfo['type'] == 0 && array_intersect([2, 3], $storeInfo['delivery_type']) && in_array($storeInfo['applicable_type'], [1, 2])) {
  1948. $where = ['is_del' => 0, 'is_show' => 1, 'is_verify' => 1, 'type' => 1];
  1949. if ($storeInfo['applicable_type'] == 2) {
  1950. $applicable_store_id = is_string($storeInfo['applicable_store_id']) ? explode(',', $storeInfo['applicable_store_id']) : $storeInfo['applicable_store_id'];
  1951. if ($applicable_store_id) {//门店商品正常
  1952. $where['relation_id'] = $applicable_store_id;
  1953. $count = $this->dao->count($where);
  1954. }
  1955. } else {
  1956. $count = $this->dao->count($where);
  1957. }
  1958. }
  1959. if (!$storeInfo['stock'] && $count) {//平台无库存,支持门店购买
  1960. $data['is_store_buy'] = 1;
  1961. }
  1962. //浏览记录
  1963. ProductLogJob::dispatch(['visit', ['uid' => $uid, 'id' => $id, 'product_id' => $id], 'product']);
  1964. return $data;
  1965. }
  1966. /**
  1967. * 是否开启vip
  1968. * @param bool $vip
  1969. * @return bool
  1970. */
  1971. public function vipIsOpen(bool $vip = false, $vipStatus = -1)
  1972. {
  1973. if (!$vip) {
  1974. return false;
  1975. }
  1976. $member_status = sys_config('member_card_status', 1);
  1977. if (!$member_status) {
  1978. return false;
  1979. }
  1980. if ($vipStatus == -1) {
  1981. /** @var MemberCardServices $memberCardService */
  1982. $memberCardService = app()->make(MemberCardServices::class);
  1983. $vipStatus = $memberCardService->isOpenMemberCardCache('vip_price', false, $member_status);
  1984. }
  1985. return $vipStatus && $member_status && $vip && sys_config('svip_price_status', 1);
  1986. }
  1987. /**
  1988. * 获取商品分销佣金最低和最高
  1989. * @param $storeInfo
  1990. * @param $productValue
  1991. * @param int $uid
  1992. * @return int|string
  1993. */
  1994. public function getPacketPrice($storeInfo, $productValue, int $uid)
  1995. {
  1996. if (!count($productValue)) {
  1997. return 0;
  1998. }
  1999. /** @var UserServices $userServices */
  2000. $userServices = app()->make(UserServices::class);
  2001. if (!$userServices->checkUserPromoter($uid)) {
  2002. return 0;
  2003. }
  2004. if (isset($storeInfo['is_sub']) && $storeInfo['is_sub'] == 1) {
  2005. $maxPrice = (float)max(array_column($productValue, 'brokerage'));
  2006. $minPrice = (float)min(array_column($productValue, 'brokerage'));
  2007. } else {
  2008. $maxPrice = max(array_column($productValue, 'price'));
  2009. $minPrice = min(array_column($productValue, 'price'));
  2010. $store_brokerage_ratio = sys_config('store_brokerage_ratio');
  2011. $store_brokerage_ratio = (string)bcdiv((string)$store_brokerage_ratio, '100', 2);
  2012. $maxPrice = (float)bcmul($store_brokerage_ratio, (string)$maxPrice, 2);
  2013. $minPrice = (float)bcmul($store_brokerage_ratio, (string)$minPrice, 2);
  2014. //大于1 取整(两位小数前端展示超出)
  2015. $maxPrice = $maxPrice > 1 ? floor($maxPrice) : $maxPrice;
  2016. $minPrice = $minPrice > 1 ? floor($minPrice) : $minPrice;
  2017. }
  2018. if ($minPrice == 0 && $maxPrice == 0) {
  2019. $priceName = 0;
  2020. } else if ($minPrice == 0 && $maxPrice)
  2021. $priceName = $maxPrice;
  2022. else if ($maxPrice == 0 && $minPrice)
  2023. $priceName = $minPrice;
  2024. else if ($maxPrice == $minPrice && $minPrice)
  2025. $priceName = $maxPrice;
  2026. else
  2027. $priceName = $minPrice . '~' . $maxPrice;
  2028. return strlen(trim($priceName)) ? $priceName : 0;
  2029. }
  2030. /**
  2031. * 获取商品用户等级、svip最低价格,优惠类型
  2032. * @param int $uid
  2033. * @param $productInfo
  2034. * @param $discount
  2035. * @param bool $is_min
  2036. * @return array
  2037. */
  2038. public function getMinPrice(int $uid, $productInfo, $discount = null, $is_min = true)
  2039. {
  2040. $level_name = '';
  2041. $vip_price = 0;
  2042. $price_type = '';
  2043. $level_price = 0;
  2044. if ($productInfo && !($productInfo['type'] == 1 && $productInfo['pid'] == 0)) {
  2045. if (is_null($discount)) {
  2046. $discount = 100;
  2047. if ($uid) {
  2048. /** @var UserServices $user */
  2049. $user = app()->make(UserServices::class);
  2050. $userInfo = $user->getUserCacheInfo($uid);
  2051. //用户等级是否开启
  2052. /** @var SystemUserLevelServices $systemLevel */
  2053. $systemLevel = app()->make(SystemUserLevelServices::class);
  2054. $levelInfo = $systemLevel->getLevelCache((int)($userInfo['level'] ?? 0));
  2055. if (sys_config('member_func_status', 1) && $levelInfo) {
  2056. $discount = $levelInfo['discount'] ?? 100;
  2057. }
  2058. $level_name = $levelInfo['name'] ?? '';
  2059. }
  2060. }
  2061. if ($discount >= 0 && $discount < 100) {//等级价格
  2062. $level_price = (float)bcmul((string)bcdiv((string)$discount, '100', 2), (string)$productInfo['price'], 2);
  2063. } else {
  2064. $level_price = $productInfo['price'];
  2065. }
  2066. if ($productInfo['is_vip']) {//svip价格
  2067. $vip_price = $productInfo['vip_price'];
  2068. }
  2069. if (($discount != 100 || $productInfo['is_vip']) && $is_min) {//需要对比价格
  2070. if ($discount != 100 && $productInfo['is_vip']) {
  2071. if ($level_price < $productInfo['vip_price']) {
  2072. $price_type = 'level';
  2073. $vip_price = $level_price;
  2074. } else {
  2075. $price_type = 'member';
  2076. $vip_price = $productInfo['vip_price'];
  2077. }
  2078. } else if ($discount != 100 && !$productInfo['is_vip']) {
  2079. $price_type = 'level';
  2080. $vip_price = $level_price;
  2081. } else if ($discount == 100 && $productInfo['is_vip']) {
  2082. $price_type = 'member';
  2083. $vip_price = $productInfo['vip_price'];
  2084. }
  2085. }
  2086. }
  2087. return compact('level_name', 'vip_price', 'price_type', 'level_price');
  2088. }
  2089. /**
  2090. * 计算商品优惠后金额、优惠价格
  2091. * @param $price
  2092. * @param int $uid
  2093. * @param $userInfo
  2094. * @param $vipStatus
  2095. * @param int $discount
  2096. * @param float $vipPrice
  2097. * @param int $is_vip
  2098. * @param false $is_show
  2099. * @return array [优惠后的总金额,优惠金额]
  2100. */
  2101. public function setLevelPrice($price, int $uid, $userInfo, $vipStatus, $discount = 0, $vipPrice = 0.00, $is_vip = 0, $is_show = false)
  2102. {
  2103. if (!(float)$price) return [(float)$price, (float)$price, ''];
  2104. if (!$vipStatus) $is_vip = 0;
  2105. //已登录
  2106. if ($uid) {
  2107. if (!$userInfo) {
  2108. /** @var UserServices $user */
  2109. $user = app()->make(UserServices::class);
  2110. $userInfo = $user->getUserCacheInfo($uid);
  2111. }
  2112. if ($discount === 0) {
  2113. /** @var SystemUserLevelServices $systemLevel */
  2114. $systemLevel = app()->make(SystemUserLevelServices::class);
  2115. $discount = $systemLevel->getDiscount($uid, (int)$userInfo['level']);
  2116. }
  2117. } else {
  2118. //没登录
  2119. $discount = 100;
  2120. }
  2121. $discount = bcdiv((string)$discount, '100', 2);
  2122. //执行减去会员优惠金额
  2123. [$truePrice, $vip_truePrice, $type] = $this->isPayLevelPrice($uid, $userInfo, $vipStatus, $price, $discount, $vipPrice, $is_vip, $is_show);
  2124. //返回优惠后的总金额
  2125. $truePrice = $truePrice < 0.01 ? 0.01 : $truePrice;
  2126. //优惠的金额
  2127. $vip_truePrice = $vip_truePrice == $price ? bcsub((string)$vip_truePrice, '0.01', 2) : $vip_truePrice;
  2128. return [(float)$truePrice, (float)$vip_truePrice, $type];
  2129. }
  2130. /**
  2131. * 获取会员价格(付费会员价格和购买商品会员价格)
  2132. * @param int $uid
  2133. * @param $userInfo
  2134. * @param $vipStatus
  2135. * @param $price
  2136. * @param string $discount
  2137. * @param float $payVipPrice
  2138. * @param int $is_vip
  2139. * @param false $is_show
  2140. * @return array
  2141. */
  2142. public function isPayLevelPrice(int $uid, $userInfo, $vipStatus, $price, string $discount, $payVipPrice = 0.00, $is_vip = 0, $is_show = false)
  2143. {
  2144. //is_vip == 0表示会员价格不启用,展示为零
  2145. if ($is_vip == 0) $payVipPrice = 0;
  2146. if (!$userInfo && $uid) {
  2147. //检测用户是否是付费会员
  2148. /** @var UserServices $userService */
  2149. $userService = app()->make(UserServices::class);
  2150. $userInfo = $userService->getUserCacheInfo($uid);
  2151. }
  2152. $noPayVipPrice = ($discount && $discount != 0.00) ? bcmul((string)$discount, (string)$price, 2) : $price;
  2153. if ($payVipPrice < $noPayVipPrice && $payVipPrice > 0) {
  2154. $vipPrice = $payVipPrice;
  2155. $type = 'member';
  2156. } else {
  2157. $vipPrice = $noPayVipPrice;
  2158. $type = 'level';
  2159. }
  2160. //如果$isSingle==true 返回优惠后的总金额,否则返回优惠的金额
  2161. if ($vipStatus && $is_vip == 1) {
  2162. //$is_show == false 是计算支付价格,true是展示
  2163. if (!$is_show) {
  2164. return [$vipPrice, bcsub((string)$price, (string)$vipPrice, 2), $type];
  2165. } else {
  2166. if ($userInfo && isset($userInfo['is_money_level']) && $userInfo['is_money_level'] > 0) {
  2167. return [$vipPrice, bcsub((string)$price, (string)$vipPrice, 2), $type];
  2168. } else {
  2169. $type = 'level';
  2170. return [$noPayVipPrice, bcsub((string)$price, (string)$noPayVipPrice, 2), $type];
  2171. }
  2172. }
  2173. } else {
  2174. $type = 'level';
  2175. return [(float)$noPayVipPrice, (float)bcsub((string)$price, (string)$noPayVipPrice, 2), $type];
  2176. }
  2177. }
  2178. /**
  2179. * 商品列表
  2180. * @param array $where
  2181. * @param $limit
  2182. * @param $field
  2183. * @return array
  2184. * @throws \think\db\exception\DataNotFoundException
  2185. * @throws \think\db\exception\DbException
  2186. * @throws \think\db\exception\ModelNotFoundException
  2187. */
  2188. public function getProductLimit(array $where, $limit, $field)
  2189. {
  2190. return $this->dao->getProductLimit($where, $limit, $field);
  2191. }
  2192. /**
  2193. * 通过条件获取商品列表
  2194. * @param $where
  2195. * @param $field
  2196. * @return array
  2197. * @throws \think\db\exception\DataNotFoundException
  2198. * @throws \think\db\exception\DbException
  2199. * @throws \think\db\exception\ModelNotFoundException
  2200. */
  2201. public function getProductListByWhere($where, $field)
  2202. {
  2203. return $this->dao->getProductListByWhere($where, $field);
  2204. }
  2205. /**
  2206. * 根据指定id获取商品列表
  2207. * @param array $ids
  2208. * @param string $field
  2209. * @return array
  2210. * @throws \think\db\exception\DataNotFoundException
  2211. * @throws \think\db\exception\DbException
  2212. * @throws \think\db\exception\ModelNotFoundException
  2213. */
  2214. public function getProductColumn(array $ids, string $field = '')
  2215. {
  2216. $productData = [];
  2217. $productInfoField = 'id,image,price,ot_price,vip_price,postage,give_integral,sales,stock,store_name,unit_name,is_show,is_del,is_postage,cost,is_sub,temp_id';
  2218. if (!empty($ids)) {
  2219. $productAll = $this->dao->idByProductList($ids, $field ?: $productInfoField);
  2220. if (!empty($productAll))
  2221. $productData = array_combine(array_column($productAll, 'id'), $productAll);
  2222. }
  2223. return $productData;
  2224. }
  2225. /**
  2226. * 商品是否存在
  2227. * @param int $productId
  2228. * @return array|\think\Model|null
  2229. * @throws \think\db\exception\DataNotFoundException
  2230. * @throws \think\db\exception\DbException
  2231. * @throws \think\db\exception\ModelNotFoundException
  2232. */
  2233. public function isValidProduct(int $productId)
  2234. {
  2235. return $this->dao->getOne(['id' => $productId, 'is_del' => 0, 'is_show' => 1, 'is_verify' => 1]);
  2236. }
  2237. /**
  2238. * 获取商品库存
  2239. * @param int $productId
  2240. * @param string $uniqueId
  2241. * @return int|mixed
  2242. */
  2243. public function getProductStock(int $productId, string $uniqueId = '')
  2244. {
  2245. /** @var StoreProductAttrValueServices $StoreProductAttrValue */
  2246. $StoreProductAttrValue = app()->make(StoreProductAttrValueServices::class);
  2247. return $uniqueId == '' ?
  2248. $this->dao->value(['id' => $productId], 'stock') ?: 0
  2249. : $StoreProductAttrValue->uniqueByStock($uniqueId);
  2250. }
  2251. /**
  2252. * 下单、退款商品、规格库存变化清空缓存
  2253. * @return void
  2254. */
  2255. public function clearProductCache()
  2256. {
  2257. $this->dao->cacheTag()->clear();
  2258. /** @var StoreProductAttrServices $storeProductAttrServices */
  2259. $storeProductAttrServices = app()->make(StoreProductAttrServices::class);
  2260. $storeProductAttrServices->cacheTag()->clear();
  2261. }
  2262. /**
  2263. * 减库存,加销量
  2264. * @param int $num
  2265. * @param int $productId
  2266. * @param string $unique
  2267. * @param int $store_id
  2268. * @return bool
  2269. */
  2270. public function decProductStock(int $num, int $productId, string $unique = '', int $store_id = 0)
  2271. {
  2272. $res = true;
  2273. /** @var StoreProductAttrValueServices $skuValueServices */
  2274. $skuValueServices = app()->make(StoreProductAttrValueServices::class);
  2275. if ($store_id) {
  2276. /** @var StoreBranchProductServices $branchProductServices */
  2277. $branchProductServices = app()->make(StoreBranchProductServices::class);
  2278. //查询门店商品
  2279. $info = $branchProductServices->isValidStoreProduct($productId, $store_id);
  2280. $storeProductId = $info['id'] ?? 0;
  2281. if ($productId && $storeProductId != $productId) {
  2282. //原商品sku
  2283. $suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $productId, 'type' => 0], 'suk');
  2284. //门店商品ID
  2285. $productId = $storeProductId;
  2286. //门店商品sku unique
  2287. $unique = $skuValueServices->value(['suk' => $suk, 'product_id' => $productId, 'type' => 0], 'unique');
  2288. }
  2289. }
  2290. if ($unique) {
  2291. $res = $res && $skuValueServices->decProductAttrStock($productId, $unique, $num);
  2292. }
  2293. $res = $res && $this->dao->decStockIncSales(['id' => $productId], $num);
  2294. if ($res) {
  2295. $this->workSendStock($productId);
  2296. }
  2297. $this->clearProductCache();
  2298. return $res;
  2299. }
  2300. /**
  2301. * 减销量,加库存
  2302. * @param int $num
  2303. * @param int $productId
  2304. * @param string $unique
  2305. * @param int $store_id
  2306. * @return bool
  2307. * @throws \think\db\exception\DataNotFoundException
  2308. * @throws \think\db\exception\DbException
  2309. * @throws \think\db\exception\ModelNotFoundException
  2310. */
  2311. public function incProductStock(int $num, int $productId, string $unique = '', int $store_id = 0)
  2312. {
  2313. $res = true;
  2314. /** @var StoreProductAttrValueServices $skuValueServices */
  2315. $skuValueServices = app()->make(StoreProductAttrValueServices::class);
  2316. if ($store_id) {
  2317. /** @var StoreBranchProductServices $branchProductServices */
  2318. $branchProductServices = app()->make(StoreBranchProductServices::class);
  2319. //查询门店商品
  2320. $info = $branchProductServices->isValidStoreProduct($productId, $store_id);
  2321. $storeProductId = $info['id'] ?? 0;
  2322. if ($productId && $storeProductId != $productId) {
  2323. //原商品sku
  2324. $suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $productId, 'type' => 0], 'suk');
  2325. //门店商品ID
  2326. $productId = $storeProductId;
  2327. //门店商品sku unique
  2328. $unique = $skuValueServices->value(['suk' => $suk, 'product_id' => $productId, 'type' => 0], 'unique');
  2329. }
  2330. }
  2331. if ($unique) {
  2332. $res = $res && $skuValueServices->incProductAttrStock($productId, $unique, $num);
  2333. }
  2334. $res = $res && $this->dao->incStockDecSales(['id' => $productId], $num);
  2335. $this->clearProductCache();
  2336. return $res;
  2337. }
  2338. /**
  2339. * 库存预警发送消息
  2340. * @param int $productId
  2341. */
  2342. public function workSendStock(int $productId)
  2343. {
  2344. ProductStockTips::dispatch([$productId]);
  2345. }
  2346. /**
  2347. * 获取推荐商品
  2348. * @param int $uid
  2349. * @param array $where
  2350. * @param int $num
  2351. * @param string $type
  2352. * @return array|null
  2353. * @throws \think\db\exception\DataNotFoundException
  2354. * @throws \think\db\exception\DbException
  2355. * @throws \think\db\exception\ModelNotFoundException
  2356. * @throws \throwable
  2357. */
  2358. public function getRecommendProduct(int $uid, array $where = [], int $num = 0, string $type = 'mid')
  2359. {
  2360. [$page, $limit] = $this->getPageValue();
  2361. $where['is_vip_product'] = 0;
  2362. $where['is_verify'] = 1;
  2363. $where['pid'] = 0;
  2364. $where['is_show'] = 1;
  2365. $where['is_del'] = 0;
  2366. if ($uid) {
  2367. /** @var UserServices $userServices */
  2368. $userServices = app()->make(UserServices::class);
  2369. $is_vip = $userServices->value(['uid' => $uid], 'is_money_level');
  2370. $where['is_vip_product'] = $is_vip ? -1 : 0;
  2371. }
  2372. $field = ['id', 'type', 'pid', 'image', 'store_name', 'store_info', 'cate_id', 'price', 'ot_price', 'IFNULL(sales,0) + IFNULL(ficti,0) as sales', 'unit_name', 'sort', 'activity', 'stock', 'vip_price', 'is_vip', 'video_link'];
  2373. $list = $this->dao->getRecommendProduct($where, $field, $num, $page, $limit, ['couponId']);
  2374. if ($list) {
  2375. $list = get_thumb_water($list, $type);
  2376. $list = $this->getActivityList($list);
  2377. $list = $this->getProductPromotions($list);
  2378. /** @var MemberCardServices $memberCardService */
  2379. $memberCardService = app()->make(MemberCardServices::class);
  2380. $vipStatus = $memberCardService->isOpenMemberCardCache('vip_price', false) && sys_config('svip_price_status', 1);;
  2381. foreach ($list as &$item) {
  2382. if (!($vipStatus && $item['is_vip'])) {
  2383. $item['vip_price'] = 0;
  2384. }
  2385. }
  2386. }
  2387. return $list;
  2388. }
  2389. /**
  2390. * 商品名称 图片
  2391. * @param array $productIds
  2392. * @return array
  2393. */
  2394. public function getProductArray(array $where, string $field, string $key)
  2395. {
  2396. return $this->dao->getColumn($where, $field, $key);
  2397. }
  2398. /**
  2399. * 获取商品详情
  2400. * @param int $productId
  2401. * @param string $field
  2402. * @param array $with
  2403. * @return array|\think\Model|null
  2404. * @throws \think\db\exception\DataNotFoundException
  2405. * @throws \think\db\exception\DbException
  2406. * @throws \think\db\exception\ModelNotFoundException
  2407. */
  2408. public function getProductInfo(int $productId, string $field = '*', array $with = [])
  2409. {
  2410. return $this->dao->getOne(['is_del' => 0, 'is_show' => 1, 'id' => $productId], $field, $with);
  2411. }
  2412. /** 生成商品复制口令关键字
  2413. * @param int $productId
  2414. * @return string
  2415. * @throws \think\db\exception\DataNotFoundException
  2416. * @throws \think\db\exception\DbException
  2417. * @throws \think\db\exception\ModelNotFoundException
  2418. */
  2419. public function getProductWords(int $productId)
  2420. {
  2421. $productInfo = $this->dao->getOne(['is_del' => 0, 'is_show' => 1, 'id' => $productId]);
  2422. $keyWords = "";
  2423. if ($productInfo) {
  2424. $oneKey = "crmeb-fu致文本 Http:/ZБ";
  2425. $twoKey = "Б轉移至☞" . sys_config('site_name') . "☜";
  2426. $threeKey = "【" . $productInfo['store_name'] . "】";
  2427. $mainKey = base64_encode($productId);
  2428. $keyWords = $oneKey . $mainKey . $twoKey . $threeKey;
  2429. }
  2430. return $keyWords;
  2431. }
  2432. /**
  2433. * 通过商品id获取商品分类
  2434. * @param array $productId
  2435. * @return array
  2436. * @throws \think\db\exception\DataNotFoundException
  2437. * @throws \think\db\exception\DbException
  2438. * @throws \think\db\exception\ModelNotFoundException
  2439. */
  2440. public function productIdByProductCateName(array $productId)
  2441. {
  2442. $data = $this->dao->productIdByCateId($productId);
  2443. $cateData = [];
  2444. foreach ($data as $item) {
  2445. $cateData[$item['id']] = implode(',', array_map(function ($i) {
  2446. return $i['cate_name'];
  2447. }, $item['cateName']));
  2448. }
  2449. return $cateData;
  2450. }
  2451. /**
  2452. * 根据商品id获取品牌名称
  2453. * @param $productId
  2454. * @return mixed
  2455. */
  2456. public function productIdByBrandName($productId, $productInfo = [])
  2457. {
  2458. if ($productInfo) {
  2459. $brand_id = $productInfo['brand_id'] ?? [];
  2460. } else {
  2461. $storeInfo = $this->getCacheProductInfo($productId);
  2462. $brand_id = $storeInfo['brand_id'] ?? [];
  2463. }
  2464. /** @var StoreBrandServices $storeBrandServices */
  2465. $storeBrandServices = app()->make(StoreBrandServices::class);
  2466. $storeBrandInfo = $storeBrandServices->getCacheBrandInfo($brand_id);
  2467. return $storeBrandInfo['brand_name'] ?? '';
  2468. }
  2469. /**
  2470. * 自动上下架
  2471. * @return bool
  2472. */
  2473. public function autoUpperShelves()
  2474. {
  2475. $this->dao->overUpperShelves(1);
  2476. $this->dao->overUpperShelves(0);
  2477. return true;
  2478. }
  2479. /**
  2480. * 获取预售列表
  2481. * @param int $uid
  2482. * @param array $where
  2483. * @return array
  2484. * @throws \think\db\exception\DataNotFoundException
  2485. * @throws \think\db\exception\DbException
  2486. * @throws \think\db\exception\ModelNotFoundException
  2487. */
  2488. public function getPresaleList(int $uid, array $where)
  2489. {
  2490. [$page, $limit] = $this->getPageValue();
  2491. $data = $this->dao->getPresaleList($where, $page, $limit);
  2492. if ($data['list']) {
  2493. /** @var StoreProductCategoryServices $storeCategoryService */
  2494. $storeCategoryService = app()->make(StoreProductCategoryServices::class);
  2495. /** @var StoreCouponIssueServices $couponServices */
  2496. $couponServices = app()->make(StoreCouponIssueServices::class);
  2497. /** @var StoreBrandServices $storeBrandServices */
  2498. $storeBrandServices = app()->make(StoreBrandServices::class);
  2499. $brands = $storeBrandServices->getColumn([], 'id,pid', 'id');
  2500. /** @var SystemFormServices $systemFormServices */
  2501. $systemFormServices = app()->make(SystemFormServices::class);
  2502. $systemForms = $systemFormServices->getColumn([['id', 'in', array_unique(array_column($data['list'], 'system_form_id'))], ['is_del', '=', 0]], 'id,value', 'id');
  2503. foreach ($data['list'] as &$item) {
  2504. $item['sales'] = $item['sales'] + $item['ficti'];
  2505. $custom_form = $systemForms[$item['system_form_id']]['value'] ?? [];
  2506. $item['custom_form'] = is_string($custom_form) ? json_decode($custom_form, true) : $custom_form;
  2507. $cateId = $item['cate_id'];
  2508. $cateId = explode(',', $cateId);
  2509. $cateId = array_merge($cateId, $storeCategoryService->cateIdByPid($cateId));
  2510. $cateId = array_diff($cateId, [0]);
  2511. $brandId = [];
  2512. if ($item['brand_id']) {
  2513. $brandId = $brands[$item['brand_id']] ?? [];
  2514. }
  2515. $counpons = $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');
  2516. $item['coupon'] = $counpons[0] ?? [];
  2517. }
  2518. }
  2519. return $data;
  2520. }
  2521. /**
  2522. * 判断配送方式
  2523. * @param int $product_id
  2524. * @param int $type 商品类型 0平台 1门店 2供应商
  2525. * @param int $relation_id 门店id
  2526. * @param array $delivery_type 配送方式
  2527. * @return array
  2528. *
  2529. * @date 2022/09/09
  2530. * @author yyw
  2531. */
  2532. public function getDeliveryType(int $product_id, int $type, int $relation_id, array $delivery_type)
  2533. {
  2534. //门店总开关
  2535. if (!sys_config('store_func_status', 1)) {
  2536. if (in_array('2', $delivery_type)) unset($delivery_type[array_search('2', $delivery_type)]);
  2537. if (in_array('3', $delivery_type)) unset($delivery_type[array_search('3', $delivery_type)]);
  2538. } else {
  2539. //获取总平台自提配置设置
  2540. $store_self_mention = (bool)sys_config('store_self_mention');
  2541. $store_mention = true;
  2542. //获取门店自提配置
  2543. if ($type === 1 && $relation_id) {
  2544. /** @var SystemStoreServices $storeServices */
  2545. $storeServices = app()->make(SystemStoreServices::class);
  2546. $storeInfo = $storeServices->cacheRemember($relation_id, function () use ($storeServices, $relation_id) {
  2547. $storeInfo = $storeServices->get(['id' => $relation_id, 'is_show' => 1, 'is_del' => 0]);
  2548. return $storeInfo ? $storeInfo->toArray() : null;
  2549. });
  2550. $store_mention = ($storeInfo['is_store'] ?? 0) === 1;
  2551. if (in_array(1, $delivery_type)) {//门店商品 支持平台配送 验证平台该商品
  2552. $info = $this->getCacheProductInfo($product_id);
  2553. if ($info && $info['type'] == 1) {
  2554. $platInfo = $this->getCacheProductInfo((int)$info['pid']);
  2555. if (!$platInfo || $platInfo['stock'] <= 0) {
  2556. unset($delivery_type[array_search('1', $delivery_type)]);
  2557. }
  2558. }
  2559. }
  2560. }
  2561. //判断当前商品配送方式
  2562. if (!$store_self_mention || !$store_mention || !(in_array('2', $delivery_type))) {
  2563. if (in_array('2', $delivery_type)) unset($delivery_type[array_search('2', $delivery_type)]);
  2564. }
  2565. }
  2566. // Log::error(['$type'=>$type,'$relation_id'=>$relation_id,'$delivery_type'=>$delivery_type,'$store_self_mention'=>$store_self_mention,'$store_mention'=>$store_mention,'$delivery_type'=>$delivery_type]);
  2567. return array_merge($delivery_type);
  2568. }
  2569. }