StorePromotionsServices.php 119 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296
  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. declare (strict_types=1);
  12. namespace app\services\activity\promotions;
  13. use app\dao\activity\promotions\StorePromotionsDao;
  14. use app\services\activity\coupon\StoreCouponIssueServices;
  15. use app\services\activity\coupon\StoreCouponUserServices;
  16. use app\services\order\StoreOrderCreateServices;
  17. use app\services\order\StoreOrderComputedServices;
  18. use app\services\BaseServices;
  19. use app\services\product\brand\StoreBrandServices;
  20. use app\services\product\label\StoreProductLabelServices;
  21. use app\services\product\product\StoreProductRelationServices;
  22. use app\services\product\product\StoreProductServices;
  23. use app\services\product\branch\StoreBranchProductServices;
  24. use app\services\product\sku\StoreProductAttrValueServices;
  25. use app\services\store\SystemStoreServices;
  26. use app\services\user\label\UserLabelServices;
  27. use app\services\order\StoreOrderServices;
  28. use app\services\order\StoreOrderCartInfoServices;
  29. use app\services\order\StoreCartServices;
  30. use app\services\product\category\StoreProductCategoryServices;
  31. use crmeb\exceptions\AdminException;
  32. use think\exception\ValidateException;
  33. use \crmeb\traits\OptionTrait;
  34. /**
  35. * 促销活动
  36. * Class StorePromotionsServices
  37. * @package app\services\activity\promotions
  38. * @mixin StorePromotionsDao
  39. */
  40. class StorePromotionsServices extends BaseServices
  41. {
  42. use OptionTrait;
  43. /**
  44. * 活动类型
  45. * @var string[]
  46. */
  47. protected $promotionsType = [
  48. 1 => '限时折扣',
  49. 2 => '第N件N折',
  50. 3 => '满减满折',
  51. 4 => '满送',
  52. ];
  53. /**
  54. * 优惠内容数据
  55. * @var array
  56. */
  57. protected $promotionsData = [
  58. 'threshold_type' => 1,//门槛类型1:满N元2:满N件
  59. 'threshold' => 0,//优惠门槛
  60. 'discount_type' => 1,//优惠类型1:满减2:满折
  61. 'n_piece_n_discount' => 3,//n件n折类型:1:第二件半件2:买1送1 3:自定义
  62. 'discount' => 0,//优惠
  63. 'give_integral' => 0,//赠送积分
  64. 'give_coupon_id' => [],//赠送优惠券ID
  65. 'give_product_id' => [],//赠送商品ID
  66. ];
  67. /**
  68. * StorePromotionsServices constructor.
  69. * @param StorePromotionsDao $dao
  70. */
  71. public function __construct(StorePromotionsDao $dao)
  72. {
  73. $this->dao = $dao;
  74. }
  75. /**
  76. * 获取打折折扣 || 优惠折扣
  77. * @param $num
  78. * @param int $unit
  79. * @param int $type 1:打折折扣 2:优惠折扣
  80. * @return float
  81. */
  82. public function computedDiscount($num, int $unit = 100, int $type = 1)
  83. {
  84. if ((float)$num < 0) {
  85. $num = 0;
  86. } elseif ((float)$num > 100) {
  87. $num = 100;
  88. }
  89. $discount = bcdiv((string)$num, (string)$unit, 2);
  90. if ($type == 2) {//优惠折扣 打9折扣优惠就是1折
  91. $discount = bcsub('1', (string)$discount, 2);
  92. }
  93. return (float)$discount;
  94. }
  95. /**
  96. * 获取优惠活动标题,内容详情
  97. * @param int $type
  98. * @param int $promotions_cate
  99. * @param array $promotions
  100. * @return array
  101. */
  102. public function getPromotionsDesc(int $type, int $promotions_cate, array $promotions)
  103. {
  104. $title = '';
  105. $desc = [];
  106. if ($promotions) {
  107. switch ($type) {
  108. case 1:
  109. $title = '限时折扣';
  110. $base = '限时打' . $this->computedDiscount($promotions[0]['discount'] ?? 0, 10) . '折';
  111. if (isset($promotions['is_limit']) && isset($promotions['limit_num']) && (int)$promotions['is_limit'] && (int)$promotions['limit_num']) {
  112. $base .= ',每人限购' . (int)$promotions['limit_num'] . '件';
  113. }
  114. $desc[] = $base;
  115. break;
  116. case 2:
  117. switch ($promotions[0]['n_piece_n_discount'] ?? 3) {
  118. case 1:
  119. $title = '第二件半价';
  120. break;
  121. case 2:
  122. $title = '买1送1';
  123. break;
  124. case 3:
  125. $title = '第' . floatval($promotions[0]['threshold'] ?? 0) . '件' . $this->computedDiscount($promotions[0]['discount'] ?? 0, 10) . '折';
  126. break;
  127. }
  128. $desc[] = '买' . floatval($promotions[0]['threshold'] ?? 0) . '件商品,其中一件享' . $this->computedDiscount($promotions[0]['discount'] ?? 0, 10) . '折优惠';
  129. break;
  130. case 3:
  131. $title = '满减满折';
  132. foreach ($promotions as $p) {
  133. if ($promotions_cate == 2) {
  134. $give = '每满' . floatval($p['threshold'] ?? 0);
  135. } else {
  136. $give = '满' . floatval($p['threshold'] ?? 0);
  137. }
  138. $give .= $p['threshold_type'] == 1 ? '元' : '件';
  139. $give .= $p['discount_type'] == 1 ? ('减' . floatval($p['discount'] ?? 0) . '元') : '打' . $this->computedDiscount($p['discount'] ?? 0, 10) . '折';
  140. $desc[] = $give;
  141. }
  142. break;
  143. case 4:
  144. $title = '满送活动';
  145. foreach ($promotions as $p) {
  146. if ($promotions_cate == 2) {
  147. $base = '每满' . floatval($p['threshold'] ?? 0);
  148. } else {
  149. $base = '满' . floatval($p['threshold'] ?? 0);
  150. }
  151. $base .= $p['threshold_type'] == 1 ? '元送' : '件送';
  152. if ($p['give_integral']) {
  153. $desc[] = $base . floatval($p['give_integral'] ?? 0) . '积分';
  154. }
  155. if ($p['give_coupon_id']) {
  156. $desc[] = $base . '优惠券';
  157. }
  158. if ($p['give_product_id']) {
  159. $desc[] = $base . '赠品';
  160. }
  161. }
  162. break;
  163. default:
  164. break;
  165. }
  166. }
  167. return [$title, $desc];
  168. }
  169. /**
  170. * 返回目前进行中的活动ids
  171. * @param array $promotions_type
  172. * @param string $field
  173. * @return array
  174. * @throws \think\db\exception\DataNotFoundException
  175. * @throws \think\db\exception\DbException
  176. * @throws \think\db\exception\ModelNotFoundException
  177. */
  178. public function getAllShowActivityIds(array $promotions_type = [], string $field = 'id')
  179. {
  180. $where = ['type' => 1, 'store_id' => 0, 'pid' => 0, 'is_del' => 0, 'status' => 1, 'promotionsTime' => true];
  181. if ($promotions_type) $where['promotions_type'] = $promotions_type;
  182. $promotions = $this->dao->getList($where, $field);
  183. $ids = [];
  184. if ($promotions) {
  185. if ($field == 'id') {
  186. $ids = array_column($promotions, 'id');
  187. } else {
  188. $ids = $promotions;
  189. }
  190. }
  191. return $ids;
  192. }
  193. /**
  194. * 获取商品所属活动
  195. * @param array $productIds
  196. * @param array $promotions_type
  197. * @param string $field
  198. * @param array $with
  199. * @param string $group
  200. * @return array
  201. * @throws \think\db\exception\DataNotFoundException
  202. * @throws \think\db\exception\DbException
  203. * @throws \think\db\exception\ModelNotFoundException
  204. */
  205. public function getProductsPromotions(array $productIds, array $promotions_type = [], string $field = '*', array $with = [], string $group = '', int $store_id = 0)
  206. {
  207. if (!$productIds) {
  208. return [[], []];
  209. }
  210. $promotionsIds = $this->getAllShowActivityIds($promotions_type, 'id,promotions_type,product_partake_type,applicable_type,applicable_store_id');
  211. if (!$promotionsIds) {
  212. return [[], []];
  213. }
  214. $ids = [];
  215. $productArr = [];
  216. /** @var StoreProductRelationServices $productRelationServices */
  217. $productRelationServices = app()->make(StoreProductRelationServices::class);
  218. foreach ($productIds as $productId) {
  219. $productArr[$productId] = $productRelationServices->getProductRelationCache((int)$productId, [2, 3]);
  220. }
  221. /** @var StorePromotionsAuxiliaryServices $promotionsAuxiliaryServices */
  222. $promotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
  223. $preType = [];
  224. foreach ($promotionsIds as $info) {
  225. $pid = (int)$info['id'];
  226. $applicable_store_id = is_array($info['applicable_store_id']) ? $info['applicable_store_id'] : explode(',', $info['applicable_store_id']);
  227. //活动不适用该门店
  228. if ($store_id && ($info['applicable_type'] == 0 || ($info['applicable_type'] == 2 && !in_array($store_id, $applicable_store_id)))) {
  229. continue;
  230. }
  231. if ($group && in_array($info['promotions_type'], $preType)) {
  232. continue;
  233. }
  234. if ($info['product_partake_type'] == 1) {//所有商品
  235. $ids[] = $pid;
  236. $preType[] = $info['promotions_type'];
  237. } else {
  238. $promotionsAuxiliaryData = $promotionsAuxiliaryServices->getPromotionsAuxiliaryCache($pid);
  239. if ($info['product_partake_type'] == 2) {
  240. if (array_intersect($promotionsAuxiliaryData, $productIds)) {
  241. $ids[] = $pid;
  242. $preType[] = $info['promotions_type'];
  243. }
  244. } else {
  245. foreach ($productArr as $productInfo) {
  246. $data = [];
  247. switch ($info['product_partake_type']) {
  248. case 4://品牌
  249. $data = $productInfo[2] ?? [];
  250. break;
  251. case 5://商品标签
  252. $data = $productInfo[3] ?? [];
  253. break;
  254. }
  255. if (array_intersect($promotionsAuxiliaryData, $data)) {//一个商品满足活动
  256. $ids[] = $pid;
  257. $preType[] = $info['promotions_type'];
  258. break;
  259. }
  260. }
  261. }
  262. }
  263. }
  264. $ids = array_unique($ids);
  265. $result = [];
  266. if ($ids) {
  267. $order = 'promotions_type asc,update_time desc';
  268. $promotions = $this->dao->getList(['ids' => $ids], $field, 0, 0, $with, $order);
  269. if ($promotions) {
  270. $data = $this->promotionsData;
  271. $data['giveCoupon'] = [];
  272. $data['giveProducts'] = [];
  273. foreach ($promotions as &$item) {
  274. if (!isset($item['promotions'])) {
  275. $item['promotions'] = [];
  276. }
  277. $first = array_merge($data, array_intersect_key($item, $data));
  278. array_unshift($item['promotions'], $first);
  279. $item['promotions'] = $this->handelPromotions($item['promotions']);
  280. $result[] = $item;
  281. }
  282. }
  283. }
  284. return [$result, $productArr];
  285. }
  286. /**
  287. * 获取商品所有优惠活动 所属活动详情
  288. * @param array $productIds
  289. * @param string $field
  290. * @param array $with
  291. * @param array $promotions_type
  292. * @param string $group
  293. * @param int $store_id
  294. * @return array
  295. * @throws \think\db\exception\DataNotFoundException
  296. * @throws \think\db\exception\DbException
  297. * @throws \think\db\exception\ModelNotFoundException
  298. */
  299. public function getProductsPromotionsDetail(array $productIds, string $field = '*', array $with = [], array $promotions_type = [], string $group = '', int $store_id = 0)
  300. {
  301. $productDetails = [];
  302. $promotionsDetails = [];
  303. $promotions = [];
  304. if ($productIds) {
  305. /** @var StoreProductServices $productServices */
  306. $productServices = app()->make(StoreProductServices::class);
  307. $newProductIds = $productServices->getColumn([['id', 'in', $productIds]], 'id,pid,type,relation_id', 'id');
  308. //处理门店商品活动
  309. $newIds = [];
  310. if ($newProductIds) {
  311. foreach ($newProductIds as $item) {
  312. //门店自营商品 不参与活动
  313. if ($item['type'] == 1) {
  314. if ($item['pid']) {//平台共享到门店商品
  315. $newIds[] = $item['pid'];
  316. }
  317. } else {
  318. $newIds[] = $item['id'];
  319. }
  320. }
  321. }
  322. [$promotions, $productRelation] = $this->getProductsPromotions($newIds, $promotions_type, $field, $with, $group);
  323. if ($promotions) {
  324. /** @var StorePromotionsAuxiliaryServices $promotionsAuxiliaryServices */
  325. $promotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
  326. foreach ($promotions as $info) {
  327. $id = (int)$info['id'];
  328. $promotionsAuxiliaryData = $promotionsAuxiliaryServices->getPromotionsAuxiliaryCache($id);
  329. $applicable_store_id = $info['applicable_store_id'] ? (is_string($info['applicable_store_id']) ? explode(',', $info['applicable_store_id']) : $info['applicable_store_id']) : [];
  330. foreach ($productIds as $productId) {
  331. $newProductId = $productId;
  332. $detail = $newProductIds[$productId] ?? [];
  333. if (!$detail) continue;
  334. if ($detail['type'] == 1) {//门店商品
  335. if ($detail['pid']) {//平台共享到门店商品
  336. $newProductId = $detail['pid'];
  337. } else {//门店自营商品 不参与活动
  338. continue;
  339. }
  340. $product_store_id = $detail['relation_id'];
  341. } else {//平台、供应商商品 存在在门店购买情况 需要验证
  342. $product_store_id = $store_id;
  343. }
  344. //活动不适用该门店
  345. if ($product_store_id && ($info['applicable_type'] == 0 || ($info['applicable_type'] == 2 && !in_array($product_store_id, $applicable_store_id)))) {
  346. continue;
  347. }
  348. $products = $info['products'] ?? [];
  349. $pIds = $products ? array_unique(array_column($products, 'product_id')) : [];
  350. switch ($info['product_partake_type']) {
  351. case 1://全部商品
  352. $productDetails[$productId][] = $id;
  353. $promotionsDetails[$id][] = $productId;
  354. break;
  355. case 2://选中商品参与
  356. if (in_array($newProductId, $pIds)) {
  357. $productDetails[$productId][] = $id;
  358. $promotionsDetails[$id][] = $productId;
  359. }
  360. break;
  361. case 3://选中商品不参与
  362. $products = $info['products'] ?? [];
  363. if ($products) $products = array_combine(array_column($products, 'product_id'), $products);
  364. if (!in_array($newProductId, $pIds) || (isset($products[$newProductId]['is_all']) && $products[$newProductId]['is_all'] == 0)) {
  365. $productDetails[$productId][] = $id;
  366. $promotionsDetails[$id][] = $productId;
  367. }
  368. break;
  369. case 4://品牌
  370. $data = $productRelation[$newProductId][2] ?? [];
  371. if (array_intersect($promotionsAuxiliaryData, $data)) {//一个商品满足活动
  372. $productDetails[$productId][] = $id;
  373. $promotionsDetails[$id][] = $productId;
  374. }
  375. break;
  376. case 5://商品标签
  377. $data = $productRelation[$newProductId][3] ?? [];
  378. if (array_intersect($promotionsAuxiliaryData, $data)) {//一个商品满足活动
  379. $productDetails[$productId][] = $id;
  380. $promotionsDetails[$id][] = $productId;
  381. }
  382. break;
  383. }
  384. }
  385. }
  386. }
  387. }
  388. return [$promotions, $productDetails, $promotionsDetails];
  389. }
  390. /**
  391. * 检测活动内容,格式数据
  392. * @param int $type
  393. * @param array $data
  394. * @return array
  395. */
  396. public function checkPromotions(int $type, array $data)
  397. {
  398. if (!$data) {
  399. throw new AdminException('请添加活动优惠内容');
  400. }
  401. $data = array_merge($this->promotionsData, array_intersect_key($data, $this->promotionsData));
  402. switch ($type) {
  403. case 1:
  404. case 2:
  405. $data['promotions_cate'] = 1;
  406. $data['discount_type'] = 2;
  407. $data['give_coupon_id'] = $data['give_product_id'] = [];
  408. if ($type == 2) {
  409. $data['threshold_type'] = 2;
  410. if (!$data['threshold']) {
  411. throw new AdminException('请输入打折门槛');
  412. }
  413. }
  414. if ($data['discount'] === '') {
  415. throw new AdminException('请添加折扣');
  416. }
  417. if ($data['discount'] < 0 || $data['discount'] > 100) {
  418. throw new AdminException('折扣必须为0~99数字');
  419. }
  420. break;
  421. case 3:
  422. $data['give_coupon_id'] = $data['give_product_id'] = [];
  423. if (!$data['threshold']) {
  424. throw new AdminException('请输入优惠门槛');
  425. }
  426. if ($data['discount'] === '') {
  427. throw new AdminException($data['discount_type'] == 1 ? '请输入优惠金额' : '请输入打折折扣');
  428. }
  429. if ($data['discount_type'] == 2 && ($data['discount'] < 0 || $data['discount'] > 100)) {
  430. throw new AdminException('折扣必须为0~99数字');
  431. }
  432. break;
  433. case 4:
  434. if (!$data['threshold']) {
  435. throw new AdminException('请输入优惠门槛');
  436. }
  437. if (!$data['give_integral'] && !$data['give_coupon_id'] && !$data['give_product_id']) {
  438. throw new AdminException('请至少选择一项赠送内容');
  439. }
  440. if ($data['give_coupon_id']) {
  441. $couponsIds = array_column($data['give_coupon_id'], 'give_coupon_id');
  442. $giveCoupon = array_combine($couponsIds, $data['give_coupon_id']);
  443. /** @var StoreCouponIssueServices $storeCouponServices */
  444. $storeCouponServices = app()->make(StoreCouponIssueServices::class);
  445. $coupons = $storeCouponServices->getValidGiveCoupons($couponsIds, 'id,is_permanent,remain_count');
  446. if (!$coupons || count($coupons) != count($couponsIds)) {
  447. throw new AdminException('优惠券已失效请重新选择');
  448. }
  449. foreach ($coupons as $coupon) {
  450. if (!isset($giveCoupon[$coupon['id']]['give_coupon_num']) || !$giveCoupon[$coupon['id']]['give_coupon_num']) {
  451. throw new AdminException('请输入赠送优惠券数量');
  452. }
  453. if ($coupon['is_permanent'] == 0 && $coupon['remain_count'] < $giveCoupon[$coupon['id']]['give_coupon_num']) {
  454. throw new AdminException('赠送优惠券数量不能超出优惠券限量');
  455. }
  456. }
  457. }
  458. if ($data['give_product_id']) {
  459. $productIds = array_column($data['give_product_id'], 'give_product_id');
  460. $giveProduct = array_combine($productIds, $data['give_product_id']);
  461. /** @var StoreProductServices $storeProductServices */
  462. $storeProductServices = app()->make(StoreProductServices::class);
  463. $products = $storeProductServices->getSearchList(['ids' => $productIds], 0, 0, ['id,stock']);
  464. if (!$products || count($products) != count(array_unique($productIds))) {
  465. throw new AdminException('商品已失效请重新选择');
  466. }
  467. foreach ($products as $product) {
  468. if (!isset($giveProduct[$product['id']]['give_product_num']) || !$giveProduct[$product['id']]['give_product_num']) {
  469. throw new AdminException('请输入赠送商品数量');
  470. }
  471. if ($product['stock'] < $giveProduct[$product['id']]['give_product_num']) {
  472. throw new AdminException('赠送商品数量不能超出商品库存');
  473. }
  474. }
  475. }
  476. break;
  477. default:
  478. throw new AdminException('暂不支持该类型优惠活动');
  479. }
  480. return $data;
  481. }
  482. /**获取门店适用的活动
  483. * @param $where
  484. * @param $storeId
  485. * @return array
  486. * @throws \think\db\exception\DataNotFoundException
  487. * @throws \think\db\exception\DbException
  488. * @throws \think\db\exception\ModelNotFoundException
  489. */
  490. public function getgetPromotionListInfo($where, $storeId)
  491. {
  492. $list = $this->dao->getList($where);
  493. $listInfo = [];
  494. foreach ($list as $key => &$info) {
  495. if ($info['applicable_type'] == 2) {
  496. $store_ids = is_array($info['applicable_store_id']) ? $info['applicable_store_id'] : explode(',', $info['applicable_store_id']);
  497. if (!in_array($storeId, $store_ids)) continue;
  498. }
  499. if ($info['start_time'])
  500. $start_time = $info['promotions_type'] == 1 ? date('Y-m-d H:i', (int)$info['start_time']) : date('Y-m-d', (int)$info['start_time']);
  501. if ($info['stop_time'])
  502. $stop_time = $info['promotions_type'] == 1 ? date('Y-m-d H:i', (int)$info['stop_time']) : date('Y-m-d', (int)$info['stop_time']);
  503. if (isset($start_time) && isset($stop_time))
  504. $info['section_time'] = [$start_time, $stop_time];
  505. else
  506. $info['section_time'] = [];
  507. unset($info['start_time'], $info['stop_time']);
  508. $info['is_label'] = $info['label_id'] ? 1 : 0;
  509. $info['threshold'] = floatval($info['threshold']);
  510. $info['discount'] = floatval($info['discount']);
  511. $info['give_integral'] = intval($info['give_integral']);
  512. $info['is_overlay'] = $info['overlay'] ? 1 : 0;
  513. $info['promotions'] = $info['promotions'] ?? [];
  514. $data = $this->promotionsData;
  515. $first = array_merge($data, array_intersect_key($info, $data));
  516. array_unshift($info['promotions'], $first);
  517. $info['promotions'] = $this->handelPromotions($info['promotions']);
  518. $listInfo[] = $info;
  519. }
  520. return $listInfo;
  521. }
  522. /**
  523. * 获取列表
  524. * @param array $where
  525. * @return array
  526. * @throws \think\db\exception\DataNotFoundException
  527. * @throws \think\db\exception\DbException
  528. * @throws \think\db\exception\ModelNotFoundException
  529. */
  530. public function systemPage(array $where)
  531. {
  532. [$page, $limit] = $this->getPageValue();
  533. $list = $this->dao->getList($where, '*', $page, $limit, ['giveProducts' => function ($query) {
  534. $query->field('promotions_id,product_id,limit_num,surplus_num')->with(['productInfo' => function ($query) {
  535. $query->field('id,store_name');
  536. }]);
  537. }, 'giveCoupon' => function ($query) {
  538. $query->field('promotions_id,coupon_id,limit_num,surplus_num')->with(['coupon' => function ($query) {
  539. $query->field('id,type,coupon_type,coupon_title,coupon_price,use_min_price');
  540. }]);
  541. }, 'promotions' => function ($query) {
  542. $query->field('id,pid,promotions_type,promotions_cate,threshold_type,threshold,discount_type,n_piece_n_discount,discount,give_integral,give_coupon_id,give_product_id,give_product_unique')->with(['giveProducts' => function ($query) {
  543. $query->field('promotions_id, product_id,limit_num,surplus_num')->with(['productInfo' => function ($query) {
  544. $query->field('id,store_name');
  545. }]);
  546. }, 'giveCoupon' => function ($query) {
  547. $query->field('promotions_id, coupon_id,limit_num,surplus_num')->with(['coupon' => function ($query) {
  548. $query->field('id,type,coupon_type,coupon_title,coupon_price,use_min_price');
  549. }]);
  550. }]);
  551. }]);
  552. $count = 0;
  553. if ($list) {
  554. $count = $this->dao->count($where);
  555. $data = $this->promotionsData;
  556. $data['giveCoupon'] = [];
  557. $data['giveProducts'] = [];
  558. /** @var StoreOrderCartInfoServices $storeOrderCartInfoServices */
  559. $storeOrderCartInfoServices = app()->make(StoreOrderCartInfoServices::class);
  560. /** @var StoreOrderServices $storeOrderServices */
  561. $storeOrderServices = app()->make(StoreOrderServices::class);
  562. /** @var StoreProductServices $storeProductServices */
  563. $storeProductServices = app()->make(StoreProductServices::class);
  564. /** @var StoreProductRelationServices $storeProductRelationServices */
  565. $storeProductRelationServices = app()->make(StoreProductRelationServices::class);
  566. /** @var StorePromotionsAuxiliaryServices $promotionsAuxiliaryServices */
  567. $promotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
  568. $getPromotions = function ($cartList, $oids) {
  569. $promotionsPrice = 0;
  570. $uids = [];
  571. $oldUids = [];
  572. $ids = [];
  573. foreach ($cartList as $key => $cart) {
  574. if (!in_array($cart['oid'], $oids)) continue;
  575. $info = is_string($cart['cart_info']) ? json_decode($cart['cart_info'], true) : $cart['cart_info'];
  576. $promotionsPrice = bcadd((string)$promotionsPrice, (string)bcmul((string)($info['promotions_true_price'] ?? 0), (string)$info['cart_num'], 2), 2);
  577. if (!in_array($cart['oid'], $ids)) {
  578. $ids[] = $cart['oid'];
  579. if (!in_array($cart['uid'], $uids)) {
  580. $uids[] = $cart['uid'];
  581. } else {
  582. $oldUids[] = $cart['uid'];
  583. }
  584. }
  585. }
  586. return [$promotionsPrice, $uids, $oldUids];
  587. };
  588. foreach ($list as &$item) {
  589. if ($item['status']) {
  590. if ($item['start_time'] > time())
  591. $item['start_name'] = '未开始';
  592. else if (bcadd((string)$item['stop_time'], '86400') < time())
  593. $item['start_name'] = '已结束';
  594. else if (bcadd((string)$item['stop_time'], '86400') > time() && $item['start_time'] < time()) {
  595. $item['start_name'] = '进行中';
  596. }
  597. } else $item['start_name'] = '已结束';
  598. $end_time = $item['stop_time'] ? date('Y/m/d', (int)$item['stop_time']) : '';
  599. $item['_stop_time'] = $end_time;
  600. $item['stop_status'] = $item['stop_time'] + 86400 < time() ? 1 : 0;
  601. $item['sum_pay_price'] = 0.00;
  602. $item['sum_promotions_price'] = 0.00;
  603. $item['sum_order'] = 0;
  604. $item['sum_user'] = 0;
  605. $item['old_user'] = 0;
  606. $item['new_user'] = 0;
  607. $pids = array_merge([$item['id']], array_column($item['promotions'], 'id'));
  608. $cartInfos = $storeOrderCartInfoServices->getColumn(['promotions_id' => $pids], 'oid,uid,cart_info', 'id', true);
  609. if ($cartInfos) {
  610. $oids = $storeOrderServices->getColumn(['id' => array_unique(array_column($cartInfos, 'oid')), 'is_del' => 0, 'pid' => 0, 'is_system_del' => 0, 'refund_status' => [0, 3]], 'id', '');
  611. $item['sum_pay_price'] = $storeOrderServices->sum(['id' => $oids, 'is_del' => 0, 'pid' => 0, 'is_system_del' => 0, 'refund_status' => [0, 3]], 'pay_price', true);
  612. [$promotionsPrice, $uids, $oldUids] = $getPromotions($cartInfos, $oids);
  613. $item['sum_promotions_price'] = $promotionsPrice;
  614. $item['sum_order'] = $storeOrderServices->count(['id' => $oids, 'is_del' => 0, 'pid' => 0, 'is_system_del' => 0, 'refund_status' => [0, 3]]);
  615. $item['sum_user'] = count($uids);
  616. $item['old_user'] = count(array_unique($oldUids));
  617. $item['new_user'] = bcsub((string)$item['sum_user'], (string)$item['old_user'], 0);
  618. }
  619. $first = array_merge($data, array_intersect_key($item, $data));
  620. array_unshift($item['promotions'], $first);
  621. $item['promotions'] = $this->handelPromotions($item['promotions']);
  622. $promotionsAuxiliaryData = $promotionsAuxiliaryServices->getPromotionsAuxiliaryCache($item['id']);
  623. switch ($item['product_partake_type']) {
  624. case 1://所有商品
  625. $item['product_count'] = $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'type' => [0, 2], 'is_verify' => 1]);
  626. break;
  627. case 2://选中商品参与
  628. $product_ids = $promotionsAuxiliaryData;
  629. $item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'is_verify' => 1]) : 0;
  630. break;
  631. case 3:
  632. $item['product_count'] = 0;
  633. break;
  634. case 4://品牌
  635. $product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 2, 'relation_id' => $promotionsAuxiliaryData]) : [];
  636. $item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'type' => [0, 2], 'is_verify' => 1]) : 0;
  637. break;
  638. case 5://商品标签
  639. $product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 3, 'relation_id' => $promotionsAuxiliaryData]) : [];
  640. $item['product_count'] = $product_ids ? $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $product_ids, 'type' => [0, 2], 'is_verify' => 1]) : 0;
  641. break;
  642. }
  643. }
  644. }
  645. return compact('list', 'count');
  646. }
  647. /**
  648. * 获取信息
  649. * @param int $id
  650. * @return array|\think\Model|null
  651. * @throws \think\db\exception\DataNotFoundException
  652. * @throws \think\db\exception\DbException
  653. * @throws \think\db\exception\ModelNotFoundException
  654. */
  655. public function getInfo(int $id)
  656. {
  657. $info = $this->dao->get($id, ['*'], ['products' => function ($query) {
  658. $query->field('promotions_id,product_id,unique')->with(['productInfo', 'attrValue']);
  659. }, 'brands' => function ($query) {
  660. $query->field('promotions_id,brand_id')->with(['brandInfo' => function ($q) {
  661. $q->field('id,brand_name');
  662. }]);
  663. }, 'productLabels' => function ($query) {
  664. $query->field('promotions_id,store_label_id')->with(['productLabelInfo' => function ($q) {
  665. $q->field('id,label_name');
  666. }]);
  667. }, 'giveProducts' => function ($query) {
  668. $query->field('type,promotions_id,product_id,limit_num,unique')->with(['productInfo' => function ($q) {
  669. $q->field('id,store_name');
  670. }, 'giveAttrValue']);
  671. }, 'giveCoupon' => function ($query) {
  672. $query->field('type,promotions_id,coupon_id,limit_num')->with(['coupon']);
  673. }, 'promotions' => function ($query) {
  674. $query->field('id,pid,promotions_type,promotions_cate,threshold_type,threshold,discount_type,n_piece_n_discount,discount,give_integral,give_coupon_id,give_product_id,give_product_unique')->with(['giveProducts' => function ($query) {
  675. $query->field('type,promotions_id, product_id,limit_num,unique')->with(['productInfo' => function ($q) {
  676. $q->field('id,store_name');
  677. }, 'giveAttrValue']);
  678. }, 'giveCoupon' => function ($query) {
  679. $query->field('type,promotions_id, coupon_id,limit_num')->with(['coupon']);
  680. }]);
  681. }]);
  682. if (!$info) {
  683. throw new AdminException('数据不存在');
  684. }
  685. $info = $info->toArray();
  686. if ($info['start_time'])
  687. $start_time = $info['promotions_type'] == 1 ? date('Y-m-d H:i', (int)$info['start_time']) : date('Y-m-d', (int)$info['start_time']);
  688. if ($info['stop_time'])
  689. $stop_time = $info['promotions_type'] == 1 ? date('Y-m-d H:i', (int)$info['stop_time']) : date('Y-m-d', (int)$info['stop_time']);
  690. if (isset($start_time) && isset($stop_time))
  691. $info['section_time'] = [$start_time, $stop_time];
  692. else
  693. $info['section_time'] = [];
  694. unset($info['start_time'], $info['stop_time']);
  695. $info['is_label'] = $info['label_id'] ? 1 : 0;
  696. if ($info['is_label']) {
  697. $label_id = is_array($info['label_id']) ? $info['label_id'] : explode(',', $info['label_id']);
  698. /** @var UserLabelServices $userLabelServices */
  699. $userLabelServices = app()->make(UserLabelServices::class);
  700. $info['label_id'] = $userLabelServices->getLabelList(['ids' => $label_id], ['id', 'label_name']);
  701. } else {
  702. $info['label_id'] = [];
  703. }
  704. $info['threshold'] = floatval($info['threshold']);
  705. $info['discount'] = floatval($info['discount']);
  706. $info['give_integral'] = intval($info['give_integral']);
  707. $info['is_overlay'] = $info['overlay'] ? 1 : 0;
  708. $info['promotions'] = $info['promotions'] ?? [];
  709. $info['products'] = $info['products'] ?? [];
  710. $info['giveCoupon'] = $info['giveCoupon'] ?? [];
  711. $info['giveProducts'] = $info['giveProducts'] ?? [];
  712. if ($info['products']) {
  713. /** @var StoreProductLabelServices $storeProductLabelServices */
  714. $storeProductLabelServices = app()->make(StoreProductLabelServices::class);
  715. $products = [];
  716. foreach ($info['products'] as &$item) {
  717. $product = is_object($item) ? $item->toArray() : $item;
  718. $product = array_merge($product, $product['productInfo'] ?? []);
  719. $product['store_label'] = '';
  720. if (isset($product['store_label_id']) && $product['store_label_id']) {
  721. $storeLabelList = $storeProductLabelServices->getColumn([['relation_id', '=', 0], ['type', '=', 0], ['id', 'IN', $product['store_label_id']]], 'id,label_name');
  722. $product['store_label'] = $storeLabelList ? implode(',', array_column($storeLabelList, 'label_name')) : '';
  723. }
  724. $unique = is_string($product['unique']) ? explode(',', $product['unique']) : $product['unique'];
  725. foreach ($product['attrValue'] as $key => $value) {
  726. if (!in_array($value['unique'], $unique)) {
  727. unset($product['attrValue'][$key]);
  728. }
  729. }
  730. $product['attrValue'] = array_merge($product['attrValue']);
  731. unset($product['productInfo']);
  732. $products[] = $product;
  733. }
  734. if ($products) {
  735. $cateIds = implode(',', array_column($products, 'cate_id'));
  736. /** @var StoreProductCategoryServices $categoryService */
  737. $categoryService = app()->make(StoreProductCategoryServices::class);
  738. $cateList = $categoryService->getCateParentAndChildName($cateIds);
  739. foreach ($products as $key => &$item) {
  740. $item['cate_name'] = '';
  741. if (isset($item['cate_id']) && $item['cate_id']) {
  742. $cate_ids = explode(',', $item['cate_id']);
  743. $cate_name = $categoryService->getCateName($cate_ids, $cateList);
  744. if ($cate_name) {
  745. $item['cate_name'] = is_array($cate_name) ? implode(',', $cate_name) : '';
  746. }
  747. }
  748. foreach ($item['attrValue'] as $key => &$value) {
  749. $value['store_label'] = $item['store_label'] ?? '';
  750. $value['cate_name'] = $item['cate_name'] ?? '';
  751. }
  752. }
  753. }
  754. unset($info['products']);
  755. $info['products'] = $products;
  756. }
  757. $data = $this->promotionsData;
  758. $data['giveCoupon'] = [];
  759. $data['giveProducts'] = [];
  760. $first = array_merge($data, array_intersect_key($info, $data));
  761. array_unshift($info['promotions'], $first);
  762. $info['promotions'] = $this->handelPromotions($info['promotions']);
  763. $info['brand_id'] = isset($info['brands']) && $info['brands'] ? array_column($info['brands'], 'brand_id') : [];
  764. $info['store_label_id'] = [];
  765. if (isset($info['productLabels']) && $info['productLabels']) {
  766. foreach ($info['productLabels'] as $label) {
  767. if (isset($label['productLabelInfo']) && $label['productLabelInfo']) {
  768. $info['store_label_id'][] = $label['productLabelInfo'];
  769. }
  770. }
  771. }
  772. //适用门店
  773. $info['stores'] = [];
  774. if (isset($info['applicable_type']) && ($info['applicable_type'] == 1 || ($info['applicable_type'] == 2 && isset($info['applicable_store_id']) && $info['applicable_store_id']))) {//查询门店信息
  775. $where = ['is_del' => 0];
  776. if ($info['applicable_type'] == 2) {
  777. $store_ids = is_array($info['applicable_store_id']) ? $info['applicable_store_id'] : explode(',', $info['applicable_store_id']);
  778. $where['id'] = $store_ids;
  779. }
  780. $field = ['id', 'cate_id', 'name', 'phone', 'address', 'detailed_address', 'image', 'is_show', 'day_time', 'day_start', 'day_end'];
  781. /** @var SystemStoreServices $storeServices */
  782. $storeServices = app()->make(SystemStoreServices::class);
  783. $storeData = $storeServices->getStoreList($where, $field, '', '', 0, ['categoryName']);
  784. $info['stores'] = $storeData['list'] ?? [];
  785. }
  786. unset($info['brands'], $info['productLabels']);
  787. return $info;
  788. }
  789. /**
  790. * 处理阶梯优惠赠送商品、优惠券
  791. * @param array $promotions
  792. * @return array
  793. */
  794. public function handelPromotions(array $promotions)
  795. {
  796. if ($promotions) {
  797. foreach ($promotions as &$p) {
  798. $p['threshold'] = (float)$p['threshold'];
  799. $p['discount'] = (float)$p['discount'];
  800. if (isset($p['giveCoupon']) && $p['giveCoupon']) {
  801. $coupons = [];
  802. foreach ($p['giveCoupon'] as &$coupon) {
  803. $coupon = is_object($coupon) ? $coupon->toArray() : $coupon;
  804. $coupon = array_merge($coupon, $coupon['coupon'] ?? []);
  805. unset($coupon['coupon']);
  806. $coupons[] = $coupon;
  807. }
  808. unset($p['giveCoupon']);
  809. $p['giveCoupon'] = $coupons;
  810. }
  811. if (isset($p['giveProducts']) && $p['giveProducts']) {
  812. $products = [];
  813. foreach ($p['giveProducts'] as &$product) {
  814. $product = is_object($product) ? $product->toArray() : $product;
  815. $product = array_merge($product, $product['productInfo'] ?? []);
  816. $product = array_merge($product, $product['giveAttrValue'] ?? []);
  817. unset($product['productInfo'], $product['giveAttrValue']);
  818. $products[] = $product;
  819. }
  820. unset($p['giveProducts']);
  821. $p['giveProducts'] = $products;
  822. }
  823. }
  824. }
  825. return $promotions;
  826. }
  827. /**
  828. * 保存促销活动
  829. * @param int $id
  830. * @param array $data
  831. * @return bool
  832. */
  833. public function saveData(int $id, array $data)
  834. {
  835. if (!$data['section_time'] || count($data['section_time']) != 2) {
  836. throw new AdminException('请选择活动时间');
  837. }
  838. [$start_time, $end_time] = $data['section_time'];
  839. if (strtotime($end_time) < time()) {
  840. throw new AdminException('活动结束时间不能小于当前时间');
  841. }
  842. if ($id) {
  843. $info = $this->dao->get((int)$id);
  844. if (!$info) {
  845. throw new AdminException('数据不存在');
  846. }
  847. }
  848. $data['start_time'] = strtotime($start_time);
  849. $data['stop_time'] = $data['promotions_type'] == 1 ? strtotime($end_time) : strtotime($end_time) + 86399;
  850. $data['label_id'] = $data['label_id'] ? implode(',', $data['label_id']) : '';
  851. $data['overlay'] = $data['overlay'] ? implode(',', $data['overlay']) : '';
  852. $promotionsAuxiliaryData = [];
  853. switch ($data['product_partake_type']) {
  854. case 1://全部
  855. $data['product_id'] = [];
  856. break;
  857. case 2://指定ID参与
  858. case 3://指定ID不参与
  859. $promotionsAuxiliaryData = $productData = $data['product_id'];
  860. $productIds = $productData ? array_column($productData, 'product_id') : [];
  861. $data['product_id'] = $productIds ? implode(',', $productIds) : '';
  862. /** @var StoreProductServices $storeProductServices */
  863. $storeProductServices = app()->make(StoreProductServices::class);
  864. $count = $storeProductServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $productIds]);
  865. $productCount = count(array_unique($productIds));
  866. if ($count != $productCount) {
  867. throw new AdminException('选择商品中有已下架或移入回收站');
  868. }
  869. break;
  870. case 4://指定品牌
  871. $data['brand_id'] = array_unique($data['brand_id']);
  872. /** @var StoreBrandServices $storeBrandServices */
  873. $storeBrandServices = app()->make(StoreBrandServices::class);
  874. $brandCount = $storeBrandServices->count(['is_show' => 1, 'is_del' => 0, 'id' => $data['brand_id']]);
  875. if (count(array_unique($data['brand_id'])) != $brandCount) {
  876. throw new AdminException('选择商品品牌中有已下架或删除的');
  877. }
  878. $promotionsAuxiliaryData['brand_id'] = $data['brand_id'];
  879. break;
  880. case 5://指定商品标签
  881. $data['store_label_id'] = array_unique($data['store_label_id']);
  882. /** @var StoreProductLabelServices $storeProductLabelServices */
  883. $storeProductLabelServices = app()->make(StoreProductLabelServices::class);
  884. $labelCount = $storeProductLabelServices->count(['id' => $data['store_label_id']]);
  885. if (count(array_unique($data['store_label_id'])) != $labelCount) {
  886. throw new ValidateException('选择商品标签中有已下架或删除的');
  887. }
  888. $promotionsAuxiliaryData['store_label_id'] = $data['store_label_id'];
  889. break;
  890. default:
  891. throw new ValidateException('暂不支持该类型商品');
  892. break;
  893. }
  894. $promotions = $data['promotions'];
  895. $threshold = -1;
  896. foreach ($promotions as &$value) {
  897. if ($threshold != -1 && $value['threshold'] <= $threshold) {
  898. throw new AdminException('优惠门槛只能递增(例如二级必须大于一级优惠)');
  899. }
  900. $threshold = $value['threshold'];
  901. $value = $this->checkPromotions((int)$data['promotions_type'], $value);
  902. $value['threshold_type'] = $data['threshold_type'];
  903. }
  904. [$title, $desc] = $this->getPromotionsDesc((int)$data['promotions_type'], (int)$data['promotions_cate'], $data['promotions_type'] == 1 ? array_merge($promotions, ['is_limit' => $data['is_limit'], 'limit_num' => $data['limit_num']]) : $promotions);
  905. $data['title'] = $title;
  906. $data['desc'] = implode(',', $desc);
  907. $first = array_shift($promotions);
  908. $giveCoupon = $first['give_coupon_id'] ?? [];
  909. $giveProduct = $first['give_product_id'] ?? [];
  910. unset($first['give_coupon_id'], $first['give_product_id']);
  911. $first['give_coupon_id'] = $giveCoupon ? implode(',', array_unique(array_column($giveCoupon, 'give_coupon_id'))) : '';
  912. $first['give_product_id'] = $giveProduct ? implode(',', array_unique(array_column($giveProduct, 'give_product_id'))) : '';
  913. $first['give_product_unique'] = $giveProduct ? implode(',', array_unique(array_column($giveProduct, 'unique'))) : '';
  914. $data = array_merge($data, $first);
  915. unset($data['section_time'], $data['promotions']);
  916. $this->transaction(function () use ($id, $data, $promotions, $promotionsAuxiliaryData, $giveCoupon, $giveProduct) {
  917. $time = time();
  918. $data['update_time'] = $time;
  919. if ($id) {
  920. $this->dao->update($id, $data);
  921. //删除之前阶梯数据
  922. $this->dao->delete(['pid' => $id]);
  923. } else {
  924. $data['add_time'] = $time;
  925. $res = $this->dao->save($data);
  926. $id = $res->id;
  927. }
  928. /** @var StorePromotionsAuxiliaryServices $storePromotionsAuxiliaryServices */
  929. $storePromotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
  930. $storePromotionsAuxiliaryServices->savePromotionsRelation((int)$id, (int)$data['product_partake_type'], $promotionsAuxiliaryData, $giveCoupon, $giveProduct);
  931. if ($promotions) {
  932. foreach ($promotions as $item) {
  933. $giveCoupon = $item['give_coupon_id'] ?? [];
  934. $giveProduct = $item['give_product_id'] ?? [];
  935. unset($item['give_coupon_id'], $item['give_product_id']);
  936. $item['give_coupon_id'] = $giveCoupon ? implode(',', array_unique(array_column($giveCoupon, 'give_coupon_id'))) : '';
  937. $item['give_product_id'] = $giveProduct ? implode(',', array_unique(array_column($giveProduct, 'give_product_id'))) : '';
  938. $item['give_product_unique'] = $giveProduct ? implode(',', array_unique(array_column($giveProduct, 'unique'))) : '';
  939. $item['pid'] = $id;
  940. $item['promotions_type'] = (int)$data['promotions_type'];
  941. $item['promotions_cate'] = (int)$data['promotions_cate'];
  942. $item['add_time'] = $time;
  943. $res = $this->dao->save($item);
  944. $storePromotionsAuxiliaryServices->savePromotionsRelation((int)$res->id, (int)$data['product_partake_type'], $promotionsAuxiliaryData, $giveCoupon, $giveProduct);
  945. }
  946. }
  947. });
  948. return true;
  949. }
  950. /**
  951. * 验证购买商品规格是否在优惠活动适用商品选择规格中
  952. * @param array $productIds
  953. * @param array $cartList
  954. * @param array $promotions
  955. * @return array
  956. */
  957. public function checkProductCanUsePromotions(array $productIds, array $cartList, array $promotions)
  958. {
  959. $ids = $uniques = [];
  960. if (!$productIds || !$cartList || !$promotions) return [$ids, $uniques];
  961. $useProducts = $promotions['products'] ?? [];
  962. if ($useProducts) {
  963. $useProducts = array_combine(array_column($useProducts, 'product_id'), $useProducts);
  964. }
  965. foreach ($cartList as $cart) {
  966. if (!in_array($cart['product_id'], $productIds)) continue;
  967. $productUnique = $cart['product_attr_unique'] ?? '';
  968. $productInfo = $cart['productInfo'] ?? [];
  969. $product_id = $cart['product_id'] ?? 0;
  970. $useUniques = $useProducts[$product_id]['unique'] ?? [];
  971. if (!$productUnique || !$productInfo) continue;
  972. if ($productInfo['type'] == 1 && $productInfo['pid'] > 0) {//平台共享到门店商品 查询平台商品unique
  973. $unique = $this->getProductUnique($productUnique, (int)$productInfo['id'], (int)$productInfo['pid']);
  974. $productUnique = $unique ?: $productUnique;
  975. $useUniques = $useProducts[$productInfo['pid']]['unique'] ?? [];
  976. }
  977. if (in_array($promotions['product_partake_type'], [2, 3])) {
  978. if ($promotions['product_partake_type'] == 2) {
  979. $uniques[$product_id] = $useUniques;
  980. if (!in_array($productUnique, $useUniques)) {
  981. continue;
  982. }
  983. } else {
  984. $uniques[$product_id] = $useUniques;
  985. if (in_array($productUnique, $useUniques)) {
  986. continue;
  987. }
  988. }
  989. }
  990. $ids[] = $product_id;
  991. }
  992. return [$ids, $uniques];
  993. }
  994. /**
  995. * 检查优惠活动限量
  996. * @param StoreOrderCartInfoServices $storeOrderCartInfoServices
  997. * @param int $uid
  998. * @param int $id
  999. * @param array $productIds
  1000. * @param array $promotions
  1001. * @return array
  1002. */
  1003. public function checkPromotionsLimit(StoreOrderCartInfoServices $storeOrderCartInfoServices, int $uid, int $id, array $productIds, array $promotions)
  1004. {
  1005. if (!$productIds) {
  1006. return [];
  1007. }
  1008. if ($uid && $promotions['promotions_type'] == 1 && $promotions['is_limit']) {//限时折扣 存在限量
  1009. //获取包含子级获取ids
  1010. $ids = array_unique(array_merge([$id], array_column($promotions['promotions'] ?? [], 'id')));
  1011. $data = [];
  1012. foreach ($productIds as $key => $product_id) {
  1013. if ($storeOrderCartInfoServices->count(['uid' => $uid, 'product_id' => $product_id, 'promotions_id' => $ids]) < $promotions['limit_num']) {
  1014. $data[] = $product_id;
  1015. }
  1016. }
  1017. return $data;
  1018. }
  1019. return $productIds;
  1020. }
  1021. /**
  1022. * 计算几个商品总金额 并返回商品信息
  1023. * @param int $promotions_type
  1024. * @param array $productIds
  1025. * @param array $cartList
  1026. * @param array $uniquesArr
  1027. * @param int $product_partake_type
  1028. * @param bool $isGive
  1029. * @return array
  1030. */
  1031. protected function getPromotionsProductInfo(int $promotions_type, array $productIds, array $cartList, array $uniquesArr = [], int $product_partake_type = 1, bool $isGive = false)
  1032. {
  1033. $sumPrice = $sumCount = 0;
  1034. $p = [];
  1035. if (!$cartList || !$productIds) {
  1036. return [$sumPrice, $sumCount, $p];
  1037. }
  1038. $productComputedArr = $this->getItem('productComputedArr', []);
  1039. $computedArr = $this->getItem('computedArr', []);
  1040. foreach ($cartList as $product) {
  1041. if (!in_array($product['product_id'], $productIds)) continue;
  1042. $cart_num = $product['cart_num'] ?? 1;
  1043. $unique = $product['product_attr_unique'] ?? '';
  1044. $product_id = $product['product_id'];
  1045. $productInfo = $product['productInfo'] ?? [];
  1046. if (!$unique || !$product_id || !$productInfo) continue;
  1047. if (!$this->checkProductUnque($unique, $productInfo, $uniquesArr[$product_id] ?? [], $product_partake_type)) {
  1048. continue;
  1049. }
  1050. if ($isGive) {
  1051. $key = 'true_price';
  1052. } else {
  1053. if ($promotions_type == 1) {
  1054. $key = 'price';
  1055. } else {
  1056. if (isset($productComputedArr[$product_id]['typeArr']) && in_array(1, $productComputedArr[$product_id]['typeArr'])) {
  1057. $key = 'price';
  1058. } else {
  1059. $key = 'true_price';
  1060. }
  1061. }
  1062. }
  1063. if ($key == 'price') {
  1064. $price = isset($product['productInfo']['attrInfo']['price']) ? $product['productInfo']['attrInfo']['price'] : ($product['productInfo']['price'] ?? 0);
  1065. } else {
  1066. $price = $product['truePrice'];
  1067. }
  1068. $isOverlay = $productComputedArr[$product_id]['is_overlay'] ?? false;
  1069. if ($isOverlay && $computedArr) {
  1070. foreach ($computedArr as $key => $computedDetail) {
  1071. if (isset($computedDetail[$unique]['promotions_true_price'])) {
  1072. $price = bcsub((string)$price, (string)$computedDetail[$unique]['promotions_true_price'], 2);
  1073. }
  1074. }
  1075. }
  1076. $product['price'] = floatval($price) > 0 ? $price : 0;
  1077. $sumPrice = bcadd((string)$sumPrice, (string)bcmul((string)$price, (string)$cart_num, 2), 2);
  1078. if ($isGive && isset($product['coupon_price'])) {
  1079. $sumPrice = bcsub((string)$sumPrice, (string)$product['coupon_price'], 2);
  1080. }
  1081. $sumCount = bcadd((string)$sumCount, (string)$cart_num, 0);
  1082. $p[] = $product;
  1083. }
  1084. return [$sumPrice, $sumCount, $p];
  1085. }
  1086. /**
  1087. * 验证是否叠加其他活动
  1088. * @param int $promotions_type
  1089. * @param array $overlay
  1090. * @return bool
  1091. */
  1092. public function checkOverlay(int $promotions_type, array $overlay)
  1093. {
  1094. $data = [1, 2, 3];
  1095. return boolval(array_intersect($overlay, array_diff($data, [$promotions_type])));
  1096. }
  1097. /**
  1098. * 获取商品活动是否叠加计算
  1099. * @param array $promotionsList
  1100. * @param array $productDetails
  1101. * @param array $promotionsDetail
  1102. * @return array[]
  1103. */
  1104. public function getProductComputedPromotions(array $promotionsList, array $productDetails, array $promotionsDetail)
  1105. {
  1106. $productArr = [];
  1107. $promotionsArr = [];
  1108. $productComputedArr = [];
  1109. if (!$promotionsList) {
  1110. return [$productArr, $promotionsArr, $productComputedArr];
  1111. }
  1112. //验证是否能叠加使用
  1113. foreach ($productDetails as $product_id => $promotionsIds) {
  1114. $pIds = [];
  1115. $overlayPIds = [];
  1116. $unOverlayPIds = [];
  1117. $prevType = [];
  1118. $preOverlay = [];
  1119. $isOverlay = true;
  1120. foreach ($promotionsIds as $id) {
  1121. $promotions = $promotionsList[$id] ?? [];
  1122. if (!$promotions) continue;
  1123. $overlay = is_string($promotions['overlay']) ? explode(',', $promotions['overlay']) : $promotions['overlay'];
  1124. //同一个商品 同一类型活动取最新一个
  1125. if (!in_array($promotions['promotions_type'], $prevType)) {
  1126. if (!$prevType) {
  1127. $overlayPIds[] = $unOverlayPIds[] = $id;
  1128. $prevType[] = $promotions['promotions_type'];
  1129. $preOverlay[$promotions['promotions_type']] = $overlay;
  1130. } else {
  1131. //有限时折扣
  1132. if (isset($preOverlay[1]) && !$this->checkOverlay(1, $preOverlay[1])) {
  1133. if ($promotions['promotions_type'] == 4) {
  1134. $prevType[] = $promotions['promotions_type'];
  1135. $preOverlay[$promotions['promotions_type']] = $overlay;
  1136. $unOverlayPIds[] = $id;
  1137. }
  1138. } else {
  1139. if ($promotions['promotions_type'] == 4) {
  1140. $prevType[] = $promotions['promotions_type'];
  1141. $preOverlay[$promotions['promotions_type']] = $overlay;
  1142. $overlayPIds[] = $id;
  1143. $unOverlayPIds[] = $id;
  1144. } else {
  1145. foreach ($preOverlay as $key => $value) {
  1146. if (in_array($key, $overlay) && in_array($promotions['promotions_type'], $value)) {
  1147. $overlayPIds[] = $id;
  1148. } else {
  1149. $unOverlayPIds[] = $id;
  1150. }
  1151. }
  1152. }
  1153. $prevType[] = $promotions['promotions_type'];
  1154. $preOverlay[$promotions['promotions_type']] = $overlay;
  1155. }
  1156. }
  1157. }
  1158. }
  1159. $overlayPIds = array_unique($overlayPIds);
  1160. $unOverlayPIds = array_unique($unOverlayPIds);
  1161. if (count($overlayPIds) > 1) {
  1162. $isOverlay = true;
  1163. $pIds = $overlayPIds;
  1164. } else if (count($overlayPIds) == 1 && count($unOverlayPIds) == 1) {
  1165. $isOverlay = false;
  1166. $pIds = $overlayPIds;
  1167. } else {
  1168. $isOverlay = false;
  1169. $pIds = $unOverlayPIds;
  1170. }
  1171. $productComputedArr[$product_id]['is_overlay'] = $pIds && $isOverlay;
  1172. $typeArr = [];
  1173. //重新整理商品关联ids数据
  1174. foreach ($pIds as $id) {
  1175. $promotions = $promotionsList[$id] ?? [];
  1176. if ($promotions) {
  1177. $productArr[$product_id][] = $id;
  1178. $typeArr[] = $promotions['promotions_type'];
  1179. }
  1180. }
  1181. //存在限时折扣 不叠加
  1182. if (count($typeArr) > 1 && in_array(1, $typeArr) && !$productComputedArr[$product_id]['is_overlay']) {
  1183. $typeArr = [];
  1184. $productArr[$product_id] = [];
  1185. foreach ($pIds as $id) {
  1186. $promotions = $promotionsList[$id] ?? [];
  1187. if ($promotions) {
  1188. if (in_array($promotions['promotions_type'], [1, 4])) {
  1189. $productArr[$product_id][] = $id;
  1190. $typeArr[] = $promotions['promotions_type'];
  1191. }
  1192. }
  1193. }
  1194. }
  1195. $productComputedArr[$product_id]['typeArr'] = $typeArr;
  1196. }
  1197. //重新整理活动关联商品ids数据
  1198. foreach ($promotionsDetail as $promotions_id => $productIds) {
  1199. foreach ($productIds as $pid) {
  1200. $pIds = $productArr[$pid] ?? [];
  1201. if ($pIds && in_array($promotions_id, $pIds)) {
  1202. $promotionsArr[$promotions_id][] = $pid;
  1203. }
  1204. }
  1205. }
  1206. return [$productArr, $promotionsArr, $productComputedArr];
  1207. }
  1208. /**
  1209. * 组合使用的优惠活动详情
  1210. * @param int $uid
  1211. * @param int $store_id
  1212. * @param array $usePromotionsIds
  1213. * @param array $promotionsArr
  1214. * @param array $computedArr
  1215. * @return array
  1216. */
  1217. public function getUsePromotiosnInfo(int $uid, int $store_id, array $usePromotionsIds, array $promotionsArr, array $computedArr)
  1218. {
  1219. $usePromotions = [];
  1220. if (!$usePromotionsIds || !$promotionsArr || !$computedArr) {
  1221. return $usePromotions;
  1222. }
  1223. $giveCoupon = $giveProduct = $giveCartList = [];
  1224. $giveIntegral = 0;
  1225. foreach ($usePromotionsIds as $id) {
  1226. $promotionsInfo = $promotionsArr[$id] ?? [];
  1227. if (!$promotionsInfo) continue;
  1228. $details = $computedArr[$id] ?? [];
  1229. $promotionsInfo['details'] = $details;
  1230. $promotionsInfo = array_merge($promotionsInfo, $details['give'] ?? []);
  1231. $promotionsInfo['is_valid'] = $details['is_valid'] ?? 0;
  1232. $promotionsInfo['reach_threshold'] = $details['reach_threshold'] ?? 0;
  1233. $promotionsInfo['sum_promotions_price'] = $details['sum_promotions_price'] ?? 0;
  1234. $promotionsInfo['differ_threshold'] = $details['differ_threshold'] ?? 0;//下一级优惠差多少元|件
  1235. $promotionsInfo['differ_price'] = $details['differ_price'] ?? 0;//下一级优惠金额
  1236. $promotionsInfo['differ_discount'] = $details['differ_discount'] ?? 0;//下一级享受折扣
  1237. $giveProductIds = $giveCart = [];
  1238. if (isset($promotionsInfo['give_product']) && $promotionsInfo['give_product']) {
  1239. $giveCart = $this->createGiveProductCart($uid, (int)$id, $promotionsInfo['give_product'], $promotionsInfo, $store_id);
  1240. if ($giveCart) {
  1241. $giveProductIds = array_column($giveProductIds, 'product_id');
  1242. $giveCartList = array_merge($giveCartList, $giveCart);
  1243. }
  1244. }
  1245. $promotionsInfo['product_ids'] = $promotionsDetail[$id] ?? [];
  1246. $promotionsInfo['product_ids'] = array_merge($promotionsInfo['product_ids'], $giveProductIds);
  1247. $promotionsInfo['give_product'] = $giveProductIds;
  1248. $giveCoupon = array_merge($giveCoupon, $promotionsInfo['give_coupon'] ?? []);
  1249. $giveProduct = array_merge($giveProduct, $promotionsInfo['give_product'] ?? []);
  1250. $giveIntegral = bcadd((string)$giveIntegral, (string)($promotionsInfo['give_integral'] ?? 0), 0);
  1251. $usePromotions[] = $promotionsInfo;
  1252. }
  1253. return [$usePromotions, $giveIntegral, $giveCoupon, $giveCartList];
  1254. }
  1255. /**
  1256. * 根据门店|平台商品 unique 获取另一端商品unique
  1257. * @param string $unique
  1258. * @param int $product_id
  1259. * @param int $type
  1260. * @return false|string
  1261. * @throws \think\db\exception\DataNotFoundException
  1262. * @throws \think\db\exception\DbException
  1263. * @throws \think\db\exception\ModelNotFoundException
  1264. */
  1265. public function getProductUnique(string $unique, int $product_id, int $new_product_id, int $type = 0)
  1266. {
  1267. /** @var StoreProductAttrValueServices $skuValueServices */
  1268. $skuValueServices = app()->make(StoreProductAttrValueServices::class);
  1269. //商品sku
  1270. $suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $product_id, 'type' => $type], 'suk');
  1271. $productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $new_product_id, 'type' => $type], 'unique');
  1272. return $productUnique ?: '';
  1273. }
  1274. /**
  1275. * 验证购物车商品规格是否在活动中
  1276. * @param string $unique
  1277. * @param array $productInfo
  1278. * @param array $uniques
  1279. * @param int $product_partake_type
  1280. * @return bool
  1281. */
  1282. public function checkProductUnque(string $unique, array $productInfo, array $uniques, int $product_partake_type = 1)
  1283. {
  1284. if (!$unique) {
  1285. return false;
  1286. }
  1287. if ((in_array($product_partake_type, [2, 3]) && !$uniques) || !is_array($uniques)) {
  1288. return false;
  1289. }
  1290. if ($productInfo['type'] == 1 && $productInfo['pid'] > 0) {//平台共享到门店商品 查询平台商品unique
  1291. $productUnique = $this->getProductUnique($unique, (int)$productInfo['id'], (int)($productInfo['pid'] ?? 0));
  1292. $unique = $productUnique ?: $unique;
  1293. }
  1294. switch ($product_partake_type) {
  1295. case 1:
  1296. case 4:
  1297. case 5:
  1298. break;
  1299. case 2:
  1300. if (!$uniques) {
  1301. return true;
  1302. }
  1303. if (!in_array($unique, $uniques)) {
  1304. return false;
  1305. }
  1306. break;
  1307. case 3:
  1308. if (!$uniques) {
  1309. return true;
  1310. }
  1311. if (in_array($unique, $uniques)) {
  1312. return false;
  1313. }
  1314. break;
  1315. default:
  1316. return false;
  1317. break;
  1318. }
  1319. return true;
  1320. }
  1321. /**
  1322. * 计算商品优惠价格
  1323. * @param int $uid
  1324. * @param array $cartList
  1325. * @param int $store_id
  1326. * @param int $couponId
  1327. * @param bool $isCart
  1328. * @return array
  1329. * @throws \think\db\exception\DataNotFoundException
  1330. * @throws \think\db\exception\DbException
  1331. * @throws \think\db\exception\ModelNotFoundException
  1332. */
  1333. public function computedPromotions(int $uid, array $cartList, int $store_id = 0, int $couponId = 0, bool $isCart = false)
  1334. {
  1335. $giveIntegral = $couponPrice = 0;
  1336. $giveCoupon = $giveCartList = $usePromotions = $useCounpon = [];
  1337. if ($cartList) {
  1338. $productIds = array_column($cartList, 'product_id');
  1339. // $productArr = array_combine($productIds, $cartList);
  1340. $with = ['products' => function ($query) {
  1341. $query->field('promotions_id,product_id,is_all,unique');
  1342. }, 'giveProducts' => function ($query) {
  1343. $query->field('type,promotions_id,product_id,limit_num,surplus_num,unique')->with(['productInfo' => function ($query) {
  1344. $query->field('id,store_name');
  1345. }]);
  1346. }, 'giveCoupon' => function ($query) {
  1347. $query->field('type,promotions_id,coupon_id,limit_num,surplus_num')->with(['coupon' => function ($query) {
  1348. $query->field('id,type,coupon_type,coupon_title,coupon_price,use_min_price,remain_count,is_permanent');
  1349. }]);
  1350. }, 'promotions' => function ($query) {
  1351. $query->field('id,pid,promotions_type,promotions_cate,threshold_type,threshold,discount_type,n_piece_n_discount,discount,give_integral,give_coupon_id,give_product_id,give_product_unique')->with(['giveProducts' => function ($query) {
  1352. $query->field('type,promotions_id,product_id,limit_num,surplus_num,unique')->with(['productInfo' => function ($query) {
  1353. $query->field('id,store_name');
  1354. }]);
  1355. }, 'giveCoupon' => function ($query) {
  1356. $query->field('type,promotions_id, coupon_id,limit_num,surplus_num')->with(['coupon' => function ($query) {
  1357. $query->field('id,type,coupon_type,coupon_title,coupon_price,use_min_price,remain_count,is_permanent');
  1358. }]);
  1359. }]);
  1360. }];
  1361. //获取购物车商品所有活动
  1362. [$promotionsArr, $productDetails, $promotionsDetail] = $this->getProductsPromotionsDetail($productIds, '*', $with, [1, 2, 3, 4], '', $store_id);
  1363. $computedArr = [];
  1364. $usePromotionsIds = [];
  1365. if ($promotionsArr) {
  1366. $promotionsArr = array_combine(array_column($promotionsArr, 'id'), $promotionsArr);
  1367. //获取商品活动是否叠加计算
  1368. [$productDetails, $promotionsDetail, $productComputedArr] = $this->getProductComputedPromotions($promotionsArr, $productDetails, $promotionsDetail);
  1369. //计算优惠金额
  1370. $computedArr = $this->doComputeV1($uid, $cartList, $promotionsDetail, $promotionsArr, $productComputedArr);
  1371. foreach ($cartList as &$cart) {
  1372. $sum_promotions_true_price = 0;
  1373. $product_id = (int)($cart['product_id'] ?? 0);
  1374. $unique = $cart['product_attr_unique'] ?? '';
  1375. $productInfo = $cart['productInfo'] ?? [];
  1376. $promotionsIds = $productDetails[$product_id] ?? [];
  1377. $cart['promotions_id'] = [];
  1378. if (!$promotionsIds || !$unique || !$productInfo) {
  1379. continue;
  1380. }
  1381. $price = isset($cart['productInfo']['attrInfo']['price']) ? $cart['productInfo']['attrInfo']['price'] : ($cart['productInfo']['price'] ?? 0);
  1382. //叠加
  1383. $typeArr = $productComputedArr[$product_id]['typeArr'] ?? [];
  1384. $isOverly = isset($productComputedArr[$product_id]['is_overlay']) && $productComputedArr[$product_id]['is_overlay'];
  1385. foreach ($promotionsIds as $promotions_id) {
  1386. $promotionsInfo = $promotionsArr[$promotions_id] ?? [];
  1387. $trueDetail = $computedArr[$promotions_id] ?? [];
  1388. if (!$promotionsInfo || !$trueDetail) continue;
  1389. if (!$this->checkProductUnque($unique, $productInfo, $trueDetail['uniques'][$product_id] ?? [], (int)$promotionsInfo['product_partake_type'])) {
  1390. continue;
  1391. }
  1392. $trueArr = $trueDetail[$unique] ?? [];
  1393. if (!isset($trueDetail['is_valid']) || $trueDetail['is_valid'] == 0) {
  1394. if ($isCart) {//购物车不满足也展示
  1395. $cart['promotions_id'][] = $promotions_id;
  1396. }
  1397. } else {
  1398. //活动叠加商品 单件总计优惠金额
  1399. if ($isOverly) {
  1400. $cart['promotions_id'][] = $promotions_id;
  1401. $sum_promotions_true_price = bcadd((string)$sum_promotions_true_price, (string)($trueArr['promotions_true_price'] ?? 0), 2);
  1402. } else { //不叠加取最优惠
  1403. if ($isCart) {
  1404. $cart['promotions_id'][] = $promotions_id;
  1405. }
  1406. if ($sum_promotions_true_price < ($trueArr['promotions_true_price'] ?? 0)) {
  1407. $sum_promotions_true_price = $trueArr['promotions_true_price'] ?? 0;
  1408. if (!$isCart) {
  1409. $cart['promotions_id'] = [$promotions_id];
  1410. }
  1411. }
  1412. }
  1413. }
  1414. }
  1415. if ($sum_promotions_true_price) {
  1416. //是否有限时折扣
  1417. if (in_array(1, $typeArr)) {
  1418. $true_price = (float)bcsub((string)$price, (string)$sum_promotions_true_price, 2);
  1419. if ($true_price < 0) {
  1420. $true_price = 0;
  1421. }
  1422. //比较与用户等级、svip优惠后金额
  1423. if ($true_price && $cart['truePrice'] > $true_price) {
  1424. $cart['truePrice'] = $true_price;
  1425. $cart['promotions_true_price'] = $sum_promotions_true_price;
  1426. $cart['price_type'] = 'promotions';
  1427. $cart['vip_truePrice'] = 0;
  1428. } else { //使用了用户等级、svip价格 去掉优惠活动关联
  1429. $cart['promotions_id'] = [];
  1430. }
  1431. } else {//svip 用户等级价格上继续优惠
  1432. $true_price = (float)bcsub((string)$cart['truePrice'], (string)$sum_promotions_true_price, 2);
  1433. $cart['truePrice'] = $true_price > 0 ? $true_price : 0;
  1434. $cart['promotions_true_price'] = $sum_promotions_true_price;
  1435. $cart['price_type'] = 'promotions';
  1436. }
  1437. }
  1438. $usePromotionsIds = array_unique(array_merge($usePromotionsIds, $cart['promotions_id']));
  1439. //排出不叠加 去最优的其他优惠活动金额
  1440. foreach ($promotionsIds as $promotions_id) {
  1441. //使用了优惠会跳过
  1442. if (in_array($promotions_id, $cart['promotions_id'])) continue;
  1443. $trueDetail = $computedArr[$promotions_id] ?? [];
  1444. $trueArr = $trueDetail[$unique] ?? [];
  1445. if (!$trueDetail || !$trueArr) {
  1446. continue;
  1447. }
  1448. $true_price = $trueArr['promotions_true_price'] ?? 0;
  1449. $sum_promotions_price = bcsub((string)($trueDetail['sum_promotions_price'] ?? '0'), (string)bcmul((string)$true_price, (string)$cart['cart_num'], 2), 2);
  1450. $computedArr[$promotions_id]['sum_promotions_price'] = $sum_promotions_price >= 0 ? $sum_promotions_price : 0;
  1451. //这个商品的没有使用优惠活动
  1452. unset($computedArr[$promotions_id][$unique]);
  1453. }
  1454. }
  1455. }
  1456. //使用优惠券
  1457. $coupon_id = 0;
  1458. if ($uid) {
  1459. if ($usePromotionsIds) [$usePromotions, $giveIntegral, $giveCoupon, $giveCartList] = $this->getUsePromotiosnInfo($uid, $store_id, $usePromotionsIds, $promotionsArr, $computedArr);
  1460. if ($couponId) {
  1461. [$useCounpon, $couponPrice] = $this->useCoupon($couponId, $uid, $cartList, $usePromotions, $store_id);
  1462. $coupon_id = $couponId;
  1463. } else {
  1464. //获取最优优惠券
  1465. if ($isCart) {
  1466. /** @var StoreCouponIssueServices $couponServices */
  1467. $couponServices = app()->make(StoreCouponIssueServices::class);
  1468. $useCounpon = $couponServices->getCanUseCoupon($uid, $cartList, $usePromotions, $store_id);
  1469. $couponPrice = $useCounpon['true_coupon_price'] ?? 0;
  1470. $coupon_id = $useCounpon['used']['id'] ?? 0;
  1471. }
  1472. }
  1473. //计算每一件商品优惠券优惠金额
  1474. if ($coupon_id && $useCounpon && $couponPrice) {
  1475. /** @var StoreOrderComputedServices $computedServices */
  1476. $computedServices = app()->make(StoreOrderComputedServices::class);
  1477. $payPrice = $computedServices->getOrderSumPrice($cartList);
  1478. if ($couponPrice > $payPrice) {
  1479. $couponPrice = $payPrice;
  1480. }
  1481. /** @var StoreOrderCreateServices $createServices */
  1482. $createServices = app()->make(StoreOrderCreateServices::class);
  1483. $priceData = ['coupon_id' => $coupon_id, 'coupon_price' => $couponPrice];
  1484. $cartList = $createServices->computeOrderProductCoupon($cartList, $priceData, $usePromotions, $store_id);
  1485. }
  1486. }
  1487. //获取赠送积分、优惠券、商品
  1488. [$cartList, $computedArr, $useGivePromotionsIds] = $this->getPromotionsGive($uid, $cartList, $computedArr, $promotionsDetail, $productDetails, $promotionsArr, $isCart);
  1489. $usePromotionsIds = array_unique(array_merge($usePromotionsIds, $useGivePromotionsIds));
  1490. //整合返回数据
  1491. if ($usePromotionsIds) [$usePromotions, $giveIntegral, $giveCoupon, $giveCartList] = $this->getUsePromotiosnInfo($uid, $store_id, $usePromotionsIds, $promotionsArr, $computedArr);
  1492. }
  1493. return [$cartList, $couponPrice, $useCounpon, $usePromotions, $giveIntegral, $giveCoupon, $giveCartList];
  1494. }
  1495. /**
  1496. * 实际计算商品优惠金额
  1497. * @param int $uid
  1498. * @param array $cartList
  1499. * @param array $promotionsDetail
  1500. * @param array $promotionsArr
  1501. * @param array $productComputedArr
  1502. * @return array
  1503. */
  1504. public function doComputeV1(int $uid, array $cartList, array $promotionsDetail, array $promotionsArr, array $productComputedArr)
  1505. {
  1506. $computedArr = [];
  1507. if (!$cartList || !$promotionsDetail) {
  1508. return $computedArr;
  1509. }
  1510. /** @var StoreOrderCartInfoServices $storeOrderCartInfoServices */
  1511. $storeOrderCartInfoServices = app()->make(StoreOrderCartInfoServices::class);
  1512. foreach ($promotionsDetail as $promotions_id => $productIds) {
  1513. $promotions = $promotionsArr[$promotions_id] ?? [];
  1514. $productCount = count($productIds);
  1515. if (!$promotions || !$productCount || $promotions['promotions_type'] == 4) continue;
  1516. //验证商品规格是否满足活动
  1517. [$productIds, $uniques] = $this->checkProductCanUsePromotions($productIds, $cartList, $promotions);
  1518. if (!$productIds) {
  1519. continue;
  1520. }
  1521. //验证限量
  1522. if ($uid) {
  1523. $productIds = $this->checkPromotionsLimit($storeOrderCartInfoServices, $uid, (int)$promotions_id, $productIds, $promotions);
  1524. if (!$productIds) {
  1525. continue;
  1526. }
  1527. }
  1528. $promotions_type = (int)$promotions['promotions_type'] ?? 1;
  1529. $this->setItem('productComputedArr', $productComputedArr)->setItem('computedArr', $computedArr);
  1530. [$sumPrice, $sumCount, $promotionsProductArr] = $this->getPromotionsProductInfo($promotions_type, $productIds, $cartList, $uniques, (int)$promotions['product_partake_type']);
  1531. $this->reset();
  1532. $compute_price = $sum_promotions_true_price = $sum_promotions_price = 0;
  1533. $data = ['is_valid' => 0, 'reach_threshold' => 0, 'differ_threshold' => 0, 'promotions_type' => $promotions['promotions_type'], 'sum_promotions_price' => 0, 'product_id' => [], 'uniques' => $uniques];
  1534. switch ($promotions['promotions_type']) {
  1535. case 1://限制折扣
  1536. $p = $promotions['promotions'][0] ?? [];
  1537. if ($p) {
  1538. $promotionsDiscount = $this->computedDiscount($p['discount'], 100, 2);
  1539. $sum_promotions_price = 0;
  1540. if ($promotions['is_limit']) {//是否限量
  1541. $limit_num = $promotions['limit_num'];
  1542. if ($limit_num <= 0) break;
  1543. $sumCount = 0;
  1544. foreach ($promotionsProductArr as $product) {
  1545. $product_id = $product['product_id'];
  1546. $unique = $product['product_attr_unique'];
  1547. $price = $product['price'];
  1548. $cartNum = $newCartNum = $product['cart_num'] ?? 1;
  1549. if ($sumCount >= $limit_num) {
  1550. break;
  1551. }
  1552. if (bcadd((string)$sumCount, (string)$cartNum) > $limit_num) {//加上下一个商品数量 大于限购数量
  1553. $newCartNum = bcsub((string)$limit_num, (string)$sumCount);
  1554. $sumCount = $limit_num;
  1555. }
  1556. $promotions_true_price = (float)bcmul((string)$price, (string)$promotionsDiscount, 2);
  1557. if ($promotions_true_price < 0.01) {//实际优惠小于0.01 就不计算优惠
  1558. $promotions_true_price = 0;
  1559. }
  1560. //部分享受折扣
  1561. if ($cartNum != $newCartNum) {
  1562. $promotions_true_price = bcdiv((string)bcmul((string)$promotions_true_price, (string)$newCartNum, 4), (string)$cartNum, 2);
  1563. }
  1564. $data[$unique] = ['product_id' => $product_id, 'uniqid' => $unique, 'price' => $price, 'promotions_true_price' => $promotions_true_price];
  1565. $sum_promotions_price = bcadd((string)$sum_promotions_price, (string)bcmul((string)$promotions_true_price, (string)$cartNum, 2), 2);
  1566. }
  1567. } else {
  1568. foreach ($promotionsProductArr as $product) {
  1569. $product_id = $product['product_id'];
  1570. $unique = $product['product_attr_unique'];
  1571. $price = $product['price'];
  1572. $promotions_true_price = (float)bcmul((string)$price, (string)$promotionsDiscount, 2);
  1573. if ($promotions_true_price < 0.01) {//实际优惠小于0.01 就不计算优惠
  1574. $promotions_true_price = 0;
  1575. }
  1576. $data[$unique] = ['product_id' => $product_id, 'uniqid' => $unique, 'price' => $price, 'promotions_true_price' => $promotions_true_price];
  1577. $sum_promotions_price = bcadd((string)$sum_promotions_price, (string)bcmul((string)$promotions_true_price, (string)($product['cart_num'] ?? 1), 2), 2);
  1578. }
  1579. }
  1580. $data['is_valid'] = 1;
  1581. $data['sum_promotions_price'] = $sum_promotions_price;
  1582. }
  1583. break;
  1584. case 2://n件n折
  1585. $p = $promotions['promotions'][0] ?? [];
  1586. if ($p) {
  1587. $promotionsDiscount = $this->computedDiscount($p['discount'], 100, 2);
  1588. if ($sumCount >= $p['threshold']) {//满足
  1589. $useProductArr = $promotionsProductArr;
  1590. //商品价格升序
  1591. array_multisort(array_column($promotionsProductArr, 'price'), SORT_ASC, $promotionsProductArr);
  1592. $minPrice = $promotionsProductArr[0]['price'] ?? 0;
  1593. //算出n件实际优惠金额
  1594. $sum_promotions_price = (float)bcmul((string)$minPrice, (string)$promotionsDiscount, 2);
  1595. if ($sum_promotions_price < 0.01) {
  1596. $sum_promotions_price = 0;
  1597. }
  1598. if ($sum_promotions_price) {
  1599. $useCount = $productCount;
  1600. $useSumPrice = $sumPrice;
  1601. $count = count($useProductArr);
  1602. $compute_price = 0;
  1603. array_multisort(array_column($useProductArr, 'cart_num'), SORT_DESC, $useProductArr);
  1604. foreach ($useProductArr as $value) {
  1605. $product_id = $value['product_id'];
  1606. $unique = $value['product_attr_unique'];
  1607. $price = $value['price'];
  1608. if ($count > 1) {
  1609. $promotions_true_price = bcmul((string)bcdiv((string)$price, (string)$useSumPrice, 4), (string)$sum_promotions_price, 2);
  1610. $compute_price = bcadd((string)$compute_price, (string)bcmul((string)$promotions_true_price, (string)$value['cart_num'], 2), 2);
  1611. } else {
  1612. $one_promotions_sum_price = bcsub((string)$sum_promotions_price, (string)$compute_price, 2);
  1613. $promotions_true_price = bcdiv((string)$one_promotions_sum_price, (string)$value['cart_num'], 2);
  1614. }
  1615. $data[$unique] = ['product_id' => $product_id, 'uniqid' => $unique, 'price' => $price, 'promotions_true_price' => $promotions_true_price];
  1616. $count--;
  1617. }
  1618. }
  1619. $data['is_valid'] = 1;
  1620. $data['reach_threshold'] = $p['threshold'];
  1621. $data['sum_promotions_price'] = $sum_promotions_price;
  1622. } else {
  1623. $data['differ_discount'] = $p['discount'];
  1624. $data['differ_threshold'] = bcsub((string)$p['threshold'], (string)$sumCount, 0);
  1625. }
  1626. }
  1627. break;
  1628. case 3://满减折
  1629. if ($promotions['promotions_cate'] == 1) {//阶梯 享受最高
  1630. $valid = $invalid = [];
  1631. foreach ($promotions['promotions'] as $key => $p) {
  1632. if (($p['threshold_type'] == 1 ? $sumPrice : $sumCount) >= $p['threshold']) {
  1633. $valid = $p;
  1634. } else {
  1635. $invalid = $p;
  1636. break;
  1637. }
  1638. }
  1639. if ($valid) {
  1640. if ($valid['discount_type'] == 1) {//免减
  1641. $sum_promotions_price = (string)$valid['discount'];
  1642. } else {//折扣
  1643. $promotionsDiscount = $this->computedDiscount($valid['discount'], 100, 2);
  1644. $sum_promotions_price = 0;
  1645. }
  1646. array_multisort(array_column($promotionsProductArr, 'cart_num'), SORT_DESC, $promotionsProductArr);
  1647. foreach ($promotionsProductArr as $product) {
  1648. $product_id = $product['product_id'];
  1649. $unique = $product['product_attr_unique'];
  1650. $price = $product['price'];
  1651. if ($valid['discount_type'] == 1) {
  1652. if ($productCount > 1) {
  1653. $promotions_true_price = bcmul((string)bcdiv((string)$price, (string)$sumPrice, 4), (string)$sum_promotions_price, 2);
  1654. $compute_price = bcadd((string)$compute_price, (string)bcmul((string)$promotions_true_price, (string)$product['cart_num'], 2), 2);
  1655. } else {
  1656. $one_promotions_sum_price = bcsub((string)$sum_promotions_price, (string)$compute_price, 2);
  1657. $promotions_true_price = bcdiv((string)$one_promotions_sum_price, (string)$product['cart_num'], 2);
  1658. }
  1659. } else {
  1660. $promotions_true_price = (float)bcmul((string)$price, (string)$promotionsDiscount, 2);
  1661. if ($promotions_true_price < 0.01) {//实际优惠小于0.01 就不计算优惠
  1662. $promotions_true_price = 0;
  1663. }
  1664. $sum_promotions_price = bcadd((string)$sum_promotions_price, (string)bcmul((string)$promotions_true_price, (string)($product['cart_num'] ?? 1), 2), 2);
  1665. }
  1666. $data[$unique] = ['product_id' => $product_id, 'uniqid' => $unique, 'price' => $price, 'promotions_true_price' => $promotions_true_price];
  1667. $productCount--;
  1668. }
  1669. $data['is_valid'] = 1;
  1670. $data['reach_threshold'] = $valid['threshold'];
  1671. $data['sum_promotions_price'] = $sum_promotions_price;
  1672. }
  1673. if ($invalid) {
  1674. if ($valid) $data['is_valid'] = 2;
  1675. if ($invalid['discount_type'] == 1) {//免减
  1676. $data['differ_price'] = $invalid['discount'];
  1677. } else {
  1678. $data['differ_discount'] = $invalid['discount'];
  1679. }
  1680. $data['differ_threshold'] = bcsub((string)$invalid['threshold'], (string)($invalid['threshold_type'] == 1 ? $sumPrice : $sumCount), 0);
  1681. }
  1682. } else {//循环
  1683. $p = $promotions['promotions'][0] ?? [];
  1684. $validCount = floor(bcdiv((string)($p['threshold_type'] == 1 ? $sumPrice : $sumCount), (string)$p['threshold'], 2));
  1685. if ($validCount) {//满足次数
  1686. $promotionsDiscount = $this->computedDiscount($p['discount'], 100, 2);
  1687. $sum_promotions_price = 0;
  1688. $useProductArr = $promotionsProductArr;
  1689. if ($p['discount_type'] == 2 && $p['threshold_type'] == 2) {//满n件 打n折
  1690. // $suprplusDiscount = bcsub((string)$productCount, bcmul((string)$validCount, (string)$p['threshold'], 0), 0);
  1691. //商品价格升序
  1692. array_multisort(array_column($promotionsProductArr, 'price'), SORT_ASC, $promotionsProductArr);
  1693. $minPrice = $promotionsProductArr[0]['price'] ?? 0;
  1694. //算出n件实际优惠金额
  1695. $sum_promotions_price = (float)bcmul((string)$minPrice, (string)$promotionsDiscount, 2);
  1696. if ($sum_promotions_price < 0.01) {
  1697. $sum_promotions_price = 0;
  1698. }
  1699. $sum_promotions_price = bcmul((string)$validCount, (string)$sum_promotions_price, 2);
  1700. } else {
  1701. if ($p['discount_type'] == 1) {//免减(n元、n件)
  1702. $sum_promotions_price = bcmul((string)$validCount, (string)$p['discount'], 2);
  1703. } else {//打折
  1704. if ($p['threshold_type'] == 1) {//满n元
  1705. $sum_promotions_price = bcmul((string)bcmul((string)$validCount, (string)$p['threshold'], 2), (string)$promotionsDiscount, 2);
  1706. }
  1707. }
  1708. }
  1709. $useCount = 0;
  1710. array_multisort(array_column($useProductArr, 'cart_num'), SORT_DESC, $useProductArr);
  1711. foreach ($useProductArr as $product) {
  1712. $price = $product['price'];
  1713. $product_id = $product['product_id'];
  1714. $unique = $product['product_attr_unique'];
  1715. // if ($p['discount_type'] == 2 && $p['threshold_type'] == 2) { //满n件打n折
  1716. // if ($p['threshold'] > $useCount) {
  1717. // $promotions_true_price = (float)bcmul((string)$price, (string)$promotionsDiscount, 2);
  1718. // if ($promotions_true_price < 0.01) {//实际优惠小于0.01 就不计算优惠
  1719. // $promotions_true_price = 0;
  1720. // }
  1721. // } else {
  1722. // $promotions_true_price = 0;
  1723. // }
  1724. // $sum_promotions_price = bcadd((string)$sum_promotions_price, (string)$promotions_true_price, 2);
  1725. // $useCount = (int)bcadd((string)$useCount, (string)$product['cart_num']);
  1726. // } else {
  1727. if ($productCount > 1) {
  1728. $promotions_true_price = bcmul((string)bcdiv((string)$price, (string)$sumPrice, 4), (string)$sum_promotions_price, 2);
  1729. $compute_price = bcadd((string)$compute_price, (string)bcmul((string)$promotions_true_price, (string)$product['cart_num'], 2), 2);
  1730. } else {
  1731. $one_promotions_sum_price = bcsub((string)$sum_promotions_price, (string)$compute_price, 2);
  1732. $promotions_true_price = bcdiv((string)$one_promotions_sum_price, (string)$product['cart_num'], 2);
  1733. }
  1734. // }
  1735. $data[$unique] = ['product_id' => $product_id, 'uniqid' => $unique, 'price' => $price, 'promotions_true_price' => $promotions_true_price];
  1736. $productCount--;
  1737. }
  1738. $data['is_valid'] = 1;
  1739. $data['reach_threshold'] = $p['threshold'];
  1740. $data['sum_promotions_price'] = $sum_promotions_price;
  1741. } else {
  1742. if ($p['discount_type'] == 1) {//免减
  1743. $data['differ_price'] = $p['discount'];
  1744. } else {
  1745. $data['differ_discount'] = $p['discount'];
  1746. }
  1747. $data['differ_threshold'] = bcsub((string)$p['threshold'], (string)($p['threshold_type'] == 1 ? $sumPrice : $sumCount), 0);
  1748. }
  1749. }
  1750. break;
  1751. case 4://满送
  1752. break;
  1753. default:
  1754. break;
  1755. }
  1756. $data['give'] = ['give_integral' => 0, 'give_coupon' => [], 'give_product' => []];
  1757. $computedArr[$promotions_id] = $data;
  1758. }
  1759. return $computedArr;
  1760. }
  1761. /**
  1762. * 使用优惠卷
  1763. * @param int $couponId
  1764. * @param int $uid
  1765. * @param array $cartInfo
  1766. * @param array $promotions
  1767. * @param int $store_id
  1768. * @return array
  1769. * @throws \think\db\exception\DataNotFoundException
  1770. * @throws \think\db\exception\DbException
  1771. * @throws \think\db\exception\ModelNotFoundException
  1772. */
  1773. public function useCoupon(int $couponId, int $uid, array $cartInfo, array $promotions, int $store_id = 0)
  1774. {
  1775. //使用优惠劵
  1776. $couponPrice = 0;
  1777. $couponInfo = [];
  1778. if ($couponId && $cartInfo) {
  1779. /** @var StoreCouponUserServices $couponServices */
  1780. $couponServices = app()->make(StoreCouponUserServices::class);
  1781. $couponInfo = $couponServices->getOne([['id', '=', $couponId], ['uid', '=', $uid], ['is_fail', '=', 0], ['status', '=', 0], ['start_time', '<=', time()], ['end_time', '>=', time()]], '*', ['issue']);
  1782. if (!$couponInfo) {
  1783. throw new ValidateException('选择的优惠劵无效!');
  1784. }
  1785. $type = $couponInfo['coupon_applicable_type'] ?? 0;
  1786. $flag = false;
  1787. $price = 0;
  1788. $count = 0;
  1789. $promotionsList = [];
  1790. if ($promotions) {
  1791. $promotionsList = array_combine(array_column($promotions, 'id'), $promotions);
  1792. }
  1793. //验证是否适用门店
  1794. if ($store_id && isset($couponInfo['applicable_type']) && isset($couponInfo['applicable_store_id'])) {
  1795. $applicable_store_id = is_array($couponInfo['applicable_store_id']) ? $couponInfo['applicable_store_id'] : explode(',', $couponInfo['applicable_store_id']);
  1796. //活动不适用该门店
  1797. if ($couponInfo['applicable_type'] == 0 || ($couponInfo['applicable_type'] == 2 && !in_array($store_id, $applicable_store_id))) {
  1798. throw new ValidateException('优惠劵暂不支持该门店使用!');
  1799. }
  1800. }
  1801. $isOverlay = function ($cart) use ($promotionsList) {
  1802. $productInfo = $cart['productInfo'] ?? [];
  1803. if (!$productInfo) {
  1804. return false;
  1805. }
  1806. //门店独立商品 不使用优惠券
  1807. $isBranchProduct = isset($productInfo['type']) && isset($productInfo['pid']) && $productInfo['type'] == 1 && !$productInfo['pid'];
  1808. if ($isBranchProduct) {
  1809. return false;
  1810. }
  1811. if (isset($cart['promotions_id']) && $cart['promotions_id']) {
  1812. foreach ($cart['promotions_id'] as $key => $promotions_id) {
  1813. $promotions = $promotionsList[$promotions_id] ?? [];
  1814. if ($promotions && $promotions['promotions_type'] != 4) {
  1815. $overlay = is_string($promotions['overlay']) ? explode(',', $promotions['overlay']) : $promotions['overlay'];
  1816. if (!in_array(5, $overlay)) {
  1817. return false;
  1818. }
  1819. }
  1820. }
  1821. }
  1822. return true;
  1823. };
  1824. switch ($type) {
  1825. case 0:
  1826. foreach ($cartInfo as $cart) {
  1827. if (!$isOverlay($cart)) continue;
  1828. $price = bcadd((string)$price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  1829. $count++;
  1830. }
  1831. break;
  1832. case 1://品类券
  1833. /** @var StoreProductCategoryServices $storeCategoryServices */
  1834. $storeCategoryServices = app()->make(StoreProductCategoryServices::class);
  1835. $cateGorys = $storeCategoryServices->getAllById((int)$couponInfo['category_id']);
  1836. if ($cateGorys) {
  1837. $cateIds = array_column($cateGorys, 'id');
  1838. foreach ($cartInfo as $cart) {
  1839. if (!$isOverlay($cart)) continue;
  1840. if (isset($cart['productInfo']['cate_id']) && array_intersect(explode(',', $cart['productInfo']['cate_id']), $cateIds)) {
  1841. $price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  1842. $count++;
  1843. }
  1844. }
  1845. }
  1846. break;
  1847. case 2:
  1848. foreach ($cartInfo as $cart) {
  1849. if (!$isOverlay($cart)) continue;
  1850. $product_id = isset($cart['productInfo']['pid']) && $cart['productInfo']['pid'] ? $cart['productInfo']['pid'] : ($cart['product_id'] ?? 0);
  1851. if ($product_id && in_array($product_id, explode(',', $couponInfo['product_id']))) {
  1852. $price = bcadd((string)$price, bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  1853. $count++;
  1854. }
  1855. }
  1856. case 3:
  1857. /** @var StoreBrandServices $storeBrandServices */
  1858. $storeBrandServices = app()->make(StoreBrandServices::class);
  1859. $brands = $storeBrandServices->getAllById((int)$couponInfo['brand_id']);
  1860. if ($brands) {
  1861. $brandIds = array_column($brands, 'id');
  1862. foreach ($cartInfo as $cart) {
  1863. if (!$isOverlay($cart)) continue;
  1864. if (isset($cart['productInfo']['brand_id']) && in_array($cart['productInfo']['brand_id'], $brandIds)) {
  1865. $price = bcadd((string)$price, (string)bcmul((string)$cart['truePrice'], (string)$cart['cart_num'], 2), 2);
  1866. $count++;
  1867. }
  1868. }
  1869. }
  1870. break;
  1871. }
  1872. if ($count && $couponInfo['use_min_price'] <= $price) {
  1873. $flag = true;
  1874. }
  1875. if (!$flag) {
  1876. return [[], 0];
  1877. // throw new ValidateException('不满足优惠劵的使用条件!');
  1878. }
  1879. //满减券
  1880. if ($couponInfo['coupon_type'] == 1) {
  1881. $couponPrice = $couponInfo['coupon_price'] > $price ? $price : $couponInfo['coupon_price'];
  1882. } else {
  1883. if ($couponInfo['coupon_price'] <= 0) {//0折
  1884. $couponPrice = $price;
  1885. } else if ($couponInfo['coupon_price'] >= 100) {
  1886. $couponPrice = 0;
  1887. } else {
  1888. $truePrice = (float)bcmul((string)$price, bcdiv((string)$couponInfo['coupon_price'], '100', 2), 2);
  1889. $couponPrice = (float)bcsub((string)$price, (string)$truePrice, 2);
  1890. }
  1891. }
  1892. }
  1893. return [$couponInfo, $couponPrice];
  1894. }
  1895. /**
  1896. * 实际获取优惠活动赠送赠品
  1897. * @param int $uid
  1898. * @param array $cartList
  1899. * @param array $computedArr
  1900. * @param array $promotionsDetail
  1901. * @param array $productDetails
  1902. * @param array $promotionsArr
  1903. * @param bool $isCart
  1904. * @return array
  1905. */
  1906. public function getPromotionsGive(int $uid, array $cartList, array $computedArr, array $promotionsDetail, array $productDetails, array $promotionsArr, bool $isCart = false)
  1907. {
  1908. $usePromotionsIds = [];
  1909. if (!$cartList || !$promotionsDetail) {
  1910. return [$cartList, $computedArr, $usePromotionsIds];
  1911. }
  1912. foreach ($promotionsDetail as $promotions_id => $productIds) {
  1913. $promotions = $promotionsArr[$promotions_id] ?? [];
  1914. $productCount = count($productIds);
  1915. if (!$promotions || !$productCount || $promotions['promotions_type'] != 4) continue;
  1916. //验证商品规格是否满足活动
  1917. [$productIds, $uniques] = $this->checkProductCanUsePromotions($productIds, $cartList, $promotions);
  1918. if (!$productIds) {
  1919. continue;
  1920. }
  1921. $promotions_type = (int)$promotions['promotions_type'] ?? 1;
  1922. [$sumPrice, $sumCount, $promotionsProductArr] = $this->getPromotionsProductInfo($promotions_type, $productIds, $cartList, $uniques, (int)$promotions['product_partake_type'], true);
  1923. $give_product_id = [];
  1924. $give_coupon_ids = [];
  1925. $give_integral = 0;
  1926. $data = ['is_valid' => 0, 'reach_threshold' => 0, 'differ_threshold' => 0, 'promotions_type' => $promotions['promotions_type'], 'sum_promotions_price' => 0, 'product_id' => []];
  1927. if ($promotions['promotions_cate'] == 1) {//阶梯
  1928. $valid = $invalid = [];
  1929. foreach ($promotions['promotions'] as $key => $p) {
  1930. if (($p['threshold_type'] == 1 ? $sumPrice : $sumCount) >= $p['threshold']) {
  1931. $valid = $p;
  1932. } else {
  1933. $invalid = $p;
  1934. break;
  1935. }
  1936. }
  1937. if ($valid) {
  1938. if ($valid['give_integral']) {
  1939. $give_integral = bcadd((string)$give_integral, (string)$valid['give_integral'], 0);
  1940. }
  1941. if ($valid['give_coupon_id']) {
  1942. $coupon_ids = is_string($valid['give_coupon_id']) ? explode(',', $valid['give_coupon_id']) : $valid['give_coupon_id'];
  1943. $give_coupon_ids = array_merge($give_coupon_ids, $coupon_ids);
  1944. }
  1945. if ($valid['giveProducts']) {
  1946. foreach ($valid['giveProducts'] as $value) {
  1947. if (isset($give_product_id[$value['unique']])) {
  1948. $give_product_id[$value['unique']]['cart_num'] = $give_product_id[$value['unique']]['cart_num'] + 1;
  1949. } else {
  1950. $give_product_id[$value['unique']] = ['promotions_id' => $value['promotions_id'], 'unique' => $value['unique'], 'product_id' => $value['product_id'], 'cart_num' => 1];
  1951. }
  1952. }
  1953. }
  1954. $data['is_valid'] = 1;
  1955. $data['reach_threshold'] = $valid['threshold'];
  1956. }
  1957. if ($invalid) {
  1958. if ($valid) $data['is_valid'] = 2;
  1959. $data['differ_threshold'] = bcsub((string)$invalid['threshold'], (string)($invalid['threshold_type'] == 1 ? $sumPrice : $sumCount), 0);
  1960. }
  1961. } else {//循环
  1962. $p = $promotions['promotions'][0] ?? [];
  1963. $validCount = floor(bcdiv((string)($p['threshold_type'] == 1 ? $sumPrice : $sumCount), (string)$p['threshold'], 2));
  1964. if ($validCount) {//满足次数
  1965. if ($p['give_integral']) {
  1966. $give_integral = bcadd((string)$give_integral, (string)bcmul((string)$validCount, (string)$p['give_integral'], 0), 0);
  1967. }
  1968. if ($p['give_coupon_id']) {
  1969. $coupon_ids = is_string($p['give_coupon_id']) ? explode(',', $p['give_coupon_id']) : $p['give_coupon_id'];
  1970. $give_coupon_ids = array_merge($give_coupon_ids, $coupon_ids);
  1971. }
  1972. if ($p['giveProducts']) {
  1973. foreach ($p['giveProducts'] as $value) {
  1974. if (isset($give_product_id[$value['unique']])) {
  1975. $give_product_id[$value['unique']]['cart_num'] = bcadd((string)$give_product_id[$value['unique']]['cart_num'], (string)$validCount);
  1976. } else {
  1977. $give_product_id[$value['unique']] = ['promotions_id' => $value['promotions_id'], 'unique' => $value['unique'], 'product_id' => $value['product_id'], 'cart_num' => $validCount];
  1978. }
  1979. }
  1980. }
  1981. $data['is_valid'] = 1;
  1982. $data['reach_threshold'] = $p['threshold'];
  1983. } else {
  1984. $data['differ_threshold'] = bcsub((string)$p['threshold'], (string)($p['threshold_type'] == 1 ? $sumPrice : $sumCount), 0);
  1985. }
  1986. }
  1987. $ids = [];
  1988. //验证优惠券限量
  1989. if ($give_coupon_ids) {
  1990. foreach ($give_coupon_ids as $give_coupon_id) {
  1991. foreach ($promotions['promotions'] as $value) {
  1992. $giveCoupon = $value['giveCoupon'] ?? [];
  1993. if ($giveCoupon) $giveCoupon = array_combine(array_column($giveCoupon, 'coupon_id'), $giveCoupon);
  1994. if ($giveCoupon && ($giveCoupon[$give_coupon_id]['surplus_num'] ?? 0) >= 1) {
  1995. $ids[] = $give_coupon_id;
  1996. break;
  1997. }
  1998. }
  1999. }
  2000. }
  2001. $data['give'] = ['give_integral' => $give_integral, 'give_coupon' => $ids, 'give_product' => $give_product_id];
  2002. $computedArr[$promotions_id] = $data;
  2003. if (!$isCart && $data['is_valid'] > 0) {
  2004. $usePromotionsIds[] = $promotions_id;
  2005. } elseif ($isCart) {
  2006. $usePromotionsIds[] = $promotions_id;
  2007. }
  2008. }
  2009. foreach ($cartList as &$cart) {
  2010. $product_id = $cart['product_id'] ?? 0;
  2011. $promotionsIds = $productDetails[$product_id] ?? [];
  2012. if (!$promotionsIds) {
  2013. continue;
  2014. }
  2015. $useIds = array_intersect($promotionsIds, $usePromotionsIds);
  2016. $cart['promotions_id'] = array_unique(array_merge($cart['promotions_id'] ?? [], $useIds));
  2017. }
  2018. return [$cartList, $computedArr, $usePromotionsIds];
  2019. }
  2020. /**
  2021. * 生成赠送商品购物车数据
  2022. * @param int $uid
  2023. * @param int $promotions_id
  2024. * @param array $giveProduct
  2025. * @param array promotions
  2026. * @param int $store_id
  2027. * @return array
  2028. */
  2029. public function createGiveProductCart(int $uid, int $promotions_id, array $giveProduct, array $promotions, int $store_id = 0)
  2030. {
  2031. $cart = [];
  2032. if ($giveProduct) {
  2033. /** @var StoreOrderCreateServices $storeOrderCreateService */
  2034. $storeOrderCreateService = app()->make(StoreOrderCreateServices::class);
  2035. /** @var StoreCartServices $storeCartServices */
  2036. $storeCartServices = app()->make(StoreCartServices::class);
  2037. $promotionsArr = $promotions['promotions'] ?? [];
  2038. foreach ($giveProduct as $unique => $give) {
  2039. $product_id = $give['product_id'] ?? 0;
  2040. $cart_num = $give['cart_num'] ?? 1;
  2041. if (!$product_id) {
  2042. continue;
  2043. }
  2044. try {
  2045. [$attrInfo, $product_attr_unique, $bargainPriceMin, $cart_num, $productInfo] = $storeCartServices->checkProductStock($uid, (int)$product_id, (int)$cart_num, $store_id, $unique, true);
  2046. } catch (\Throwable $e) {
  2047. continue;
  2048. }
  2049. $is_limit = false;
  2050. foreach ($promotionsArr as $key => $value) {
  2051. $giveProducts = $value['giveProducts'] ?? [];
  2052. if ($giveProducts) $giveProducts = array_combine(array_column($giveProducts, 'product_id'), $giveProducts);
  2053. if ($giveProducts && $cart_num <= ($giveProducts[$product_id]['surplus_num'] ?? 0)) {
  2054. $is_limit = true;
  2055. break;
  2056. }
  2057. }
  2058. if (!$is_limit) {
  2059. continue;
  2060. }
  2061. $key = $storeOrderCreateService->getNewOrderId((string)$uid);
  2062. $info['id'] = $key;
  2063. $info['type'] = 0;
  2064. $info['product_type'] = $productInfo['product_type'];
  2065. $info['promotions_id'] = [$give['promotions_id'] ?? $promotions_id];
  2066. $info['activity_id'] = 0;
  2067. $info['discount_product_id'] = 0;
  2068. $info['product_id'] = $product_id;
  2069. $info['is_gift'] = 1;
  2070. $info['is_valid'] = 1;
  2071. $info['product_attr_unique'] = $product_attr_unique;
  2072. $info['cart_num'] = $cart_num;
  2073. $info['productInfo'] = $productInfo ? $productInfo->toArray() : [];
  2074. $info['productInfo']['express_delivery'] = false;
  2075. $info['productInfo']['store_mention'] = false;
  2076. $info['productInfo']['store_delivery'] = false;
  2077. if (isset($info['productInfo']['delivery_type'])) {
  2078. $info['productInfo']['delivery_type'] = is_string($info['productInfo']['delivery_type']) ? explode(',', $info['productInfo']['delivery_type']) : $info['productInfo']['delivery_type'];
  2079. if (in_array(1, $info['productInfo']['delivery_type'])) {
  2080. $info['productInfo']['express_delivery'] = true;
  2081. }
  2082. if (in_array(2, $info['productInfo']['delivery_type'])) {
  2083. $info['productInfo']['store_mention'] = true;
  2084. }
  2085. if (in_array(3, $info['productInfo']['delivery_type'])) {
  2086. $info['productInfo']['store_delivery'] = true;
  2087. }
  2088. }
  2089. $info['productInfo']['attrInfo'] = $attrInfo->toArray();
  2090. $info['attrStatus'] = (bool)$info['productInfo']['attrInfo'];
  2091. $info['sum_price'] = $info['productInfo']['attrInfo']['price'] ?? $info['productInfo']['price'] ?? 0;
  2092. $info['truePrice'] = 0;
  2093. $info['vip_truePrice'] = 0;
  2094. $info['trueStock'] = $info['productInfo']['attrInfo']['stock'] ?? 0;
  2095. $info['costPrice'] = $info['productInfo']['attrInfo']['cost'] ?? 0;
  2096. $info['limit_num'] = $giveProducts[$product_id]['limit_num'] ?? 0;
  2097. $cart[] = $info;
  2098. }
  2099. }
  2100. return $cart;
  2101. }
  2102. /**
  2103. * 根据类型获取商品列表
  2104. * @param int $type
  2105. * @param int $uid
  2106. * @param int $promotions_id
  2107. * @param int $store_id
  2108. * @param int $staff_id
  2109. * @param int $tourist_uid
  2110. * @return array|array[]|null
  2111. * @throws \think\db\exception\DataNotFoundException
  2112. * @throws \think\db\exception\DbException
  2113. * @throws \think\db\exception\ModelNotFoundException
  2114. */
  2115. public function getTypeList(int $type, int $uid, int $promotions_id = 0)
  2116. {
  2117. $store_id = (int)$this->getItem('store_id', 0);
  2118. $staff_id = (int)$this->getItem('staff_id', 0);
  2119. $tourist_uid = (int)$this->getItem('tourist_uid', 0);
  2120. $store_name = $this->getItem('store_name', '');
  2121. $ids = $this->getItem('ids', []);
  2122. $not_ids = $this->getItem('not_ids', []);
  2123. $where = [];
  2124. $where['type'] = 1;
  2125. $where['store_id'] = 0;
  2126. $where['pid'] = 0;
  2127. $where['is_del'] = 0;
  2128. $where['status'] = 1;
  2129. $where['promotionsTime'] = true;
  2130. $where['promotions_type'] = $type;
  2131. if ($promotions_id) {
  2132. $where['id'] = $promotions_id;
  2133. }
  2134. $product_where = [];
  2135. $product_where['store_name'] = $store_name;
  2136. $product_where['pids'] = $ids;
  2137. $product_where['not_pids'] = $not_ids;
  2138. //门店不展示卡密商品
  2139. $product_where['product_type'] = [0, 2, 4];
  2140. //存在一个全部商品折扣优惠活动 直接返回商品
  2141. if ($ids && !$this->dao->count($where + ['product_partake_type' => 1])) {
  2142. //正选并集
  2143. $mergeIds = function ($promotions) {
  2144. $data = [];
  2145. foreach ($promotions as $item) {
  2146. $productIds = is_string($item['product_id']) ? explode(',', $item['product_id']) : $item['product_id'];
  2147. $data = array_merge($data, $productIds);
  2148. }
  2149. return $data;
  2150. };
  2151. $promotions = $this->getList($where + ['product_partake_type' => 2], 'id,product_id');
  2152. $product_where['pids'] = $promotions ? $mergeIds($promotions) : [];
  2153. $notPromotions = $this->getList($where + ['product_partake_type' => 3], 'id,product_id');
  2154. //反选交集
  2155. /** @var StorePromotionsAuxiliaryServices $auxiliaryService */
  2156. $auxiliaryService = app()->make(StorePromotionsAuxiliaryServices::class);
  2157. $intersectIds = function ($promotions) use ($auxiliaryService) {
  2158. $data = [];
  2159. foreach ($promotions as $item) {
  2160. $productIds = is_string($item['product_id']) ? explode(',', $item['product_id']) : $item['product_id'];
  2161. if (!$productIds) {
  2162. continue;
  2163. }
  2164. $productIds = $auxiliaryService->getColumn(['promotions_id' => $item['id'], 'type' => 1, 'is_all' => 1, 'product_id' => $productIds], 'product_id', '', true);
  2165. if (!$productIds) {
  2166. continue;
  2167. }
  2168. if ($data) {
  2169. $data = array_intersect($data, $productIds);
  2170. } else {
  2171. $data = $productIds;
  2172. }
  2173. }
  2174. return $data;
  2175. };
  2176. $product_where['not_pids'] = array_merge($product_where['not_pids'], $notPromotions ? $intersectIds($notPromotions) : []);
  2177. }
  2178. $list = [];
  2179. $count = 0;
  2180. if ($product_where['pids'] && $this->dao->count($where)) {
  2181. $product_where['pids'] = array_merge(array_diff($product_where['pids'], $product_where['not_pids']));
  2182. unset($product_where['not_pids']);
  2183. /** @var StoreBranchProductServices $branchProductServices */
  2184. $branchProductServices = app()->make(StoreBranchProductServices::class);
  2185. return $branchProductServices->getCashierProductListV2($product_where, $store_id, $uid, $staff_id, $tourist_uid);
  2186. }
  2187. return compact('list', 'count');
  2188. }
  2189. /**
  2190. * 获取凑单商品ids
  2191. * @param int $promotions_id
  2192. * @return array
  2193. */
  2194. public function collectOrderProduct(int $promotions_id)
  2195. {
  2196. $product_where = [];
  2197. $promotions = $this->dao->get($promotions_id, ['*']);
  2198. if ($promotions) {
  2199. $promotions = $promotions->toArray();
  2200. /** @var StoreProductRelationServices $storeProductRelationServices */
  2201. $storeProductRelationServices = app()->make(StoreProductRelationServices::class);
  2202. /** @var StorePromotionsAuxiliaryServices $promotionsAuxiliaryServices */
  2203. $promotionsAuxiliaryServices = app()->make(StorePromotionsAuxiliaryServices::class);
  2204. $promotionsAuxiliaryData = $promotionsAuxiliaryServices->getPromotionsAuxiliaryCache($promotions_id);
  2205. switch ($promotions['product_partake_type']) {
  2206. case 1://所有商品
  2207. break;
  2208. case 2://选中商品参与
  2209. $product_ids = $promotionsAuxiliaryData;
  2210. $product_where['ids'] = $product_ids;
  2211. break;
  2212. case 3:
  2213. $ids = is_string($promotions['product_id']) ? explode(',', $promotions['product_id']) : $promotions['product_id'];
  2214. if ($ids) {//商品全部规格 不参与 才不显示该商品
  2215. /** @var StorePromotionsAuxiliaryServices $auxiliaryService */
  2216. $auxiliaryService = app()->make(StorePromotionsAuxiliaryServices::class);
  2217. $ids = $auxiliaryService->getColumn(['promotions_id' => $promotions['id'], 'type' => 1, 'is_all' => 1, 'product_id' => $ids], 'product_id', '', true);
  2218. }
  2219. $product_where['not_ids'] = $ids;
  2220. break;
  2221. case 4://品牌
  2222. $product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 2, 'relation_id' => $promotionsAuxiliaryData]) : [];
  2223. $product_where['ids'] = $product_ids;
  2224. break;
  2225. case 5://商品标签
  2226. $product_ids = $promotionsAuxiliaryData ? $storeProductRelationServices->getIdsByWhere(['type' => 3, 'relation_id' => $promotionsAuxiliaryData]) : [];
  2227. $product_where['ids'] = $product_ids;
  2228. break;
  2229. }
  2230. }
  2231. return $product_where;
  2232. }
  2233. }