CartGoods.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Administrator
  5. * Date: 2021/4/6
  6. * Time: 17:12
  7. */
  8. namespace app\frontend\modules\cart\models;
  9. use app\backend\modules\goods\models\GoodsTradeSet;
  10. use app\common\exceptions\AppException;
  11. use app\common\models\BaseModel;
  12. use app\common\models\GoodsOption;
  13. use app\frontend\models\Goods;
  14. use app\frontend\modules\cart\deduction\BaseCartDeduction;
  15. use app\frontend\modules\cart\deduction\models\PreCartGoodsDeduction;
  16. use app\frontend\modules\cart\discount\BaseCartDiscount;
  17. use app\frontend\modules\cart\discount\EnoughReduceDiscount;
  18. use app\frontend\modules\cart\discount\MemberLevelDiscount;
  19. use app\frontend\modules\cart\discount\models\PreCartGoodsDiscount;
  20. use app\frontend\modules\cart\discount\SingleEnoughReduceDiscount;
  21. use app\frontend\modules\cart\extra\BaseCartExtraCharges;
  22. use app\frontend\modules\cart\manager\GoodsAdapter;
  23. use app\frontend\modules\cart\manager\GoodsOptionAdapter;
  24. use app\frontend\modules\cart\node\CartGoodsBaseCartExtraChargesPriceNode;
  25. use app\frontend\modules\cart\node\CartGoodsDeductionsPriceNode;
  26. use app\frontend\modules\cart\node\CartGoodsDiscountPriceNode;
  27. use app\frontend\modules\cart\node\CartGoodsPriceNodeBase;
  28. use app\frontend\modules\cart\services\CartGoodsInterface;
  29. use app\frontend\modules\order\PriceNode;
  30. use app\frontend\modules\order\PriceNodeTrait;
  31. use Illuminate\Support\Carbon;
  32. /**
  33. * Class CartGoods
  34. * @package app\frontend\modules\cart\models
  35. * @property memberCart memberCart
  36. * @property Goods goods
  37. * @property GoodsOption goodsOption
  38. *
  39. */
  40. class CartGoods extends BaseModel implements CartGoodsInterface
  41. {
  42. use PriceNodeTrait;
  43. protected $appends = ['checked', 'disable'];
  44. protected $hidden = ['memberCart', 'goods','goodsOption'];
  45. public $shop;
  46. protected $goodsAdapter;
  47. protected $isChecked; //是否勾选
  48. protected $isInvalid; //是否失效
  49. protected $disable; //是否禁止选中
  50. protected $estimated_price;
  51. protected $price;
  52. //节点法
  53. public function _getPriceNodes()
  54. {
  55. // 商品价格节点
  56. $nodes = collect([
  57. new CartGoodsPriceNodeBase($this, 1000)
  58. ]);
  59. //优惠的节点
  60. $discountNodes = $this->getDiscounts()->map(function (BaseCartDiscount $discount) {
  61. return new CartGoodsDiscountPriceNode($this, $discount, 2000);
  62. });
  63. //抵扣的节点
  64. $deductionsNodes = $this->getDeductions()->map(function (BaseCartDeduction $deduction) {
  65. return new CartGoodsDeductionsPriceNode($this, $deduction, 3000);
  66. });
  67. //附加费用节点
  68. $extraChargesNodes = $this->getExtraCharges()->map(function (BaseCartExtraCharges $extraCharges) {
  69. return new CartGoodsBaseCartExtraChargesPriceNode($this, $extraCharges, 4000);
  70. });
  71. // 按照weight排序
  72. return $nodes->merge($discountNodes)
  73. ->merge($deductionsNodes)
  74. ->merge($extraChargesNodes)
  75. ->sortBy(function (PriceNode $priceNode) {
  76. return $priceNode->getWeight();
  77. })->values();
  78. }
  79. /**
  80. * 优惠集合
  81. * @return \Illuminate\Support\Collection
  82. */
  83. public function getDiscounts()
  84. {
  85. $default = collect([
  86. new MemberLevelDiscount($this), new SingleEnoughReduceDiscount($this), new EnoughReduceDiscount($this),
  87. ]);
  88. // $default = collect([
  89. // new SingleEnoughReduceDiscount($this), new EnoughReduceDiscount($this),
  90. // ]);
  91. $aggregate = $default->merge($this->setDiscounts());
  92. return $aggregate;
  93. }
  94. public function setDiscounts()
  95. {
  96. return collect([]);
  97. }
  98. /**
  99. * 抵扣集合
  100. * @return \Illuminate\Support\Collection
  101. */
  102. public function getDeductions()
  103. {
  104. $default = collect([]);
  105. $aggregate = $default->merge($this->setDeductions());
  106. return $aggregate;
  107. }
  108. public function setDeductions()
  109. {
  110. return collect([]);
  111. }
  112. /**
  113. * 附加费用
  114. * @return \Illuminate\Support\Collection
  115. */
  116. public function getExtraCharges()
  117. {
  118. $default = collect([]);
  119. $aggregate = $default->merge($this->setExtraCharges());
  120. return $aggregate;
  121. }
  122. public function setExtraCharges()
  123. {
  124. return collect([]);
  125. }
  126. /*
  127. * 加载购物车参数
  128. */
  129. public function initialAttributes($data)
  130. {
  131. $this->setRawAttributes($data);
  132. $this->beforeCreating();
  133. }
  134. public function beforeCreating()
  135. {
  136. }
  137. public function invalidGoods()
  138. {
  139. $stock = $this->isOption() ? $this->goodsOption->stock : $this->goods->stock;
  140. //商品下架 || 已删除 || 商品库存不足
  141. $invalid = (empty($this->goods()->status) || $this->goods()->deleted_at || ($stock <= 0) || ($this->isOption() && !$this->goods()->has_option) || (!$this->isOption() && $this->goods()->has_option));
  142. return $invalid;
  143. }
  144. /**
  145. * 购物车商品是否失效
  146. * @return bool
  147. */
  148. public function isInvalid()
  149. {
  150. if (!isset($this->isInvalid)) {
  151. $this->isInvalid = $this->invalidGoods();
  152. }
  153. return $this->isInvalid;
  154. }
  155. /**
  156. * 验证商品
  157. * @throws AppException
  158. */
  159. public function goodsValidate()
  160. {
  161. //未勾选不验证
  162. if (!$this->isChecked()) {
  163. return true;
  164. }
  165. if (!isset($this->goods)) {
  166. throw new AppException('(ID:' . $this->goods_id . ')未找到商品或已经删除');
  167. }
  168. //todo 验证商品是否启用规格
  169. $this->goods->verifyOption($this->goods_option_id);
  170. if (empty($this->goods->status)) {
  171. throw new AppException($this->goods->title.'(ID:' . $this->goods->id . ')商品已下架');
  172. }
  173. if ($this->isOption()) {
  174. $this->goodsOptionValidate();
  175. }
  176. }
  177. /**
  178. * 规格验证
  179. * @throws AppException
  180. */
  181. public function goodsOptionValidate()
  182. {
  183. if (!$this->goods->has_option) {
  184. throw new AppException($this->goods->title.'(ID:' . $this->goods_id . ')商品未启用规格');
  185. }
  186. if (!isset($this->goodsOption)) {
  187. throw new AppException($this->goods->title.'(ID:' . $this->goods_id . ')未找到规格或已经删除');
  188. }
  189. if ($this->goods_id != $this->goodsOption->goods_id) {
  190. throw new AppException('规格('.$this->option_id.')'.$this->goodsOption->title.'不属于商品('.$this->goods_id.')'.$this->goods->title);
  191. }
  192. }
  193. /**
  194. * 注入分组类
  195. * @param $shop
  196. */
  197. public function setShop($shop)
  198. {
  199. $this->shop = $shop;
  200. }
  201. /**
  202. * @return ShopCart
  203. * @throws AppException
  204. */
  205. final public function getShop()
  206. {
  207. if (!isset($this->shop)) {
  208. throw new AppException('调用顺序错误,店铺对象还没有载入');
  209. }
  210. return $this->shop;
  211. }
  212. public function setDisable($bool)
  213. {
  214. $this->disable = $bool;
  215. }
  216. /**
  217. * @return bool
  218. */
  219. public function getDisableAttribute()
  220. {
  221. return $this->disable;
  222. }
  223. /**
  224. * @return bool
  225. * @throws AppException
  226. */
  227. public function getCheckedAttribute()
  228. {
  229. return $this->isChecked();
  230. }
  231. /**
  232. * 选择了此购物车
  233. * @return bool
  234. * @throws AppException
  235. */
  236. public function isChecked()
  237. {
  238. if (!isset($this->isChecked)) {
  239. if ($this->noBeChecked()) {
  240. // 不能选中
  241. $this->isChecked = false;
  242. } else {
  243. // 用户选中
  244. $cart_ids = $this->getShop()->getRequest()->input('cart_ids');
  245. if (!is_array($cart_ids)) {
  246. //strpos($cart_ids, ',') !== false
  247. //$cart_ids = json_decode($cart_ids, true);
  248. $cart_ids = explode(',', $cart_ids);
  249. }
  250. $this->isChecked = in_array($this->cart_id, $cart_ids);
  251. }
  252. }
  253. return $this->isChecked;
  254. }
  255. /**
  256. * todo 这里暂时没用,因 isChecked 在 disable 设置之前调用
  257. * 不能选中
  258. * @return bool
  259. */
  260. protected function noBeChecked()
  261. {
  262. return false;
  263. }
  264. /**
  265. * 重构购物车参数
  266. * 获取生成前的模型属性
  267. * @return array
  268. * @throws AppException
  269. */
  270. public function getExtraField()
  271. {
  272. $attributes = array(
  273. // 'cart_id' => $this->cart_id,
  274. // 'goods_id' => $this->goods_id,
  275. // 'total' => $this->total,
  276. // 'goods_option_id' => $this->goods_option_id,
  277. 'is_alone' => $this->getShop()->isAlone(),
  278. 'shop_id' => $this->getShop()->getShopId(), //分组标识
  279. 'unit' => $this->getUnit(), //单位
  280. 'style_type' => $this->getStyleType(), //样式
  281. 'goods_title' => $this->goods()->title,
  282. 'vip_price' => $this->getVipPrice(),
  283. 'goods_thumb' => yz_tomedia($this->goods()->thumb),
  284. 'discount_activity' => $this->getDiscountActivity(),
  285. 'goods_price' => sprintf('%.2f', $this->getGoodsPrice()),
  286. 'price' => sprintf('%.2f', $this->getPrice()),
  287. 'estimated_price' => sprintf('%.2f', $this->getEstimatedPrice()), //预估价格
  288. 'month_buy_limit' => $this->getMonthBuyLimit(), //分类限购
  289. 'show_time_word' => $this->getArrivedTime(),
  290. );
  291. if ($this->goodsOption) {
  292. $attributes += [
  293. 'goods_option_title' => $this->goodsOption->title,
  294. ];
  295. if ($this->goodsOption['thumb']) {
  296. $attributes['goods_thumb'] = yz_tomedia($this->goodsOption['thumb']);
  297. }
  298. }
  299. $attributes = array_merge($this->getAttributes(), $attributes);
  300. return $attributes;
  301. }
  302. private function getArrivedTime()
  303. {
  304. $goods_trade_set = GoodsTradeSet::where('goods_id', $this->goods_id)->first();
  305. if (!$goods_trade_set || !$goods_trade_set->arrived_day || !app('plugins')->isEnabled('address-code')) {
  306. return '';
  307. }
  308. $arrived_day = $goods_trade_set->arrived_day;
  309. $arrived_word = $goods_trade_set->arrived_word;
  310. if ($arrived_day > 1) {
  311. $arrived_day -= 1;
  312. $time_format = Carbon::createFromTimestamp(time())->addDays($arrived_day)->format('Y-m-d');
  313. } else {
  314. $time_format = Carbon::createFromTimestamp(time())->format('Y-m-d');
  315. }
  316. $time_format .= " {$goods_trade_set->arrived_time}:00";
  317. $timestamp = strtotime($time_format);
  318. if ($timestamp < time()) {
  319. $timestamp += 86400;
  320. }
  321. $show_time = ltrim(date('m', $timestamp), '0').'月';
  322. $show_time .= ltrim(date('d', $timestamp), '0').'日';
  323. $show_time .= $goods_trade_set->arrived_time;
  324. return str_replace('[送达时间]', $show_time, $arrived_word);
  325. }
  326. public function getUnit()
  327. {
  328. return '元';
  329. }
  330. public function getStyleType()
  331. {
  332. return 'shop';
  333. }
  334. /**
  335. * 商品金额
  336. * @return int
  337. */
  338. public function getGoodsPrice()
  339. {
  340. return $this->getAdapter()->getPrice();
  341. }
  342. /**
  343. * 商品支付金额
  344. * @return mixed
  345. */
  346. public function getPrice()
  347. {
  348. if (isset($this->price)) {
  349. return $this->price;
  350. }
  351. //未选中不计算金额
  352. if (!$this->isChecked()) {
  353. return 0;
  354. }
  355. //商品单价 * 库存
  356. $this->price = $this->getGoodsPrice() * $this->total;
  357. return $this->price;
  358. }
  359. /**
  360. * 商品预估金额
  361. * @return mixed
  362. * @throws AppException
  363. */
  364. public function getEstimatedPrice()
  365. {
  366. if (isset($this->estimated_price)) {
  367. return $this->estimated_price;
  368. }
  369. // //未选中不计算金额
  370. if (!$this->isChecked()) {
  371. return 0;
  372. }
  373. ///商品支付金额 - 等级优惠金额 - 单品满减 - 全场满减
  374. $this->estimated_price = $this->getPriceAfter($this->getPriceNodes()->last()->getKey());
  375. //优惠劵
  376. return $value = sprintf('%.2f', $this->estimated_price);
  377. }
  378. /**
  379. * 商品的会员等级折扣金额
  380. * @return float
  381. * @throws AppException
  382. */
  383. public function getVipDiscountAmount()
  384. {
  385. // $amount = $this->getAdapter()->_getVipDiscountAmount();
  386. // $preCartGoodsDiscount = new PreCartGoodsDiscount([
  387. // 'code' => 'memberLevel',
  388. // 'amount' => $amount ?: 0,
  389. // 'name' => '会员等级优惠',
  390. // ]);
  391. // $preCartGoodsDiscount->setCartGoods($this);
  392. //
  393. // return $amount;
  394. return $this->getAdapter()->_getVipDiscountAmount() * $this->total;
  395. }
  396. /**
  397. * 关联优惠抵扣
  398. * @return mixed
  399. */
  400. public function getCartGoodsDiscounts()
  401. {
  402. if (!$this->getRelation('cartGoodsDiscounts')) {
  403. $this->setRelation('cartGoodsDiscounts', $this->newCollection());
  404. }
  405. return $this->cartGoodsDiscounts;
  406. }
  407. /**
  408. * 抵扣
  409. * @return mixed
  410. */
  411. public function getCartGoodsDeductions()
  412. {
  413. if (!$this->getRelation('cartGoodsDeductions')) {
  414. $this->setRelation('cartGoodsDeductions', $this->newCollection());
  415. }
  416. return $this->cartGoodsDeductions;
  417. }
  418. /**
  419. * 额外费用
  420. * @return mixed
  421. */
  422. public function getCartGoodsExtraCharges()
  423. {
  424. if (!$this->getRelation('cartGoodsExtraCharges')) {
  425. $this->setRelation('cartGoodsExtraCharges', $this->newCollection());
  426. }
  427. return $this->cartGoodsExtraCharges;
  428. }
  429. protected $isCoinExchange;
  430. /**
  431. * 是否满足全额抵扣判断
  432. * @return bool
  433. */
  434. protected function isCoinExchange()
  435. {
  436. //获取商城设置: 判断 积分、余额 是否有自定义名称
  437. $shopSet = \Setting::get('shop.shop');
  438. if (!isset($this->isCoinExchange)) {
  439. if (!$this->goods()->hasOneSale->has_all_point_deduct) {
  440. $this->isCoinExchange = false;
  441. } else {
  442. $this->isCoinExchange = true;
  443. // 全额抵扣记录
  444. $preModel = new PreCartGoodsDeduction([
  445. 'code' => 'pointAll',
  446. 'amount' => ($this->getGoodsPrice() * $this->total) ?: 0,
  447. 'name' => $shopSet['credit1'] ? $shopSet['credit1'] . '全额抵扣' : '积分全额抵扣',
  448. ]);
  449. $preModel->setCartGoods($this);
  450. }
  451. }
  452. return $this->isCoinExchange;
  453. }
  454. /**
  455. * @return array
  456. * @throws AppException
  457. */
  458. public function toArray()
  459. {
  460. $this->setRawAttributes($this->getExtraField());
  461. return parent::toArray();
  462. }
  463. //店铺商品活动优惠满减
  464. public function getDiscountActivity()
  465. {
  466. //todo 这样可能要做成动态的
  467. $data = [];
  468. $sale = $this->goods()->hasOneSale;
  469. if ($sale->ed_num || $sale->ed_money) {
  470. $str = '';
  471. if ($sale->ed_money) {
  472. $str .= '满' . $sale->ed_money . '包邮';
  473. }
  474. if ($sale->ed_num) {
  475. if (!empty($str)) {
  476. $str .= ',';
  477. }
  478. $str .= '满' . $sale->ed_num . '件包邮';
  479. }
  480. $data[] = [
  481. 'name' => '包邮',
  482. 'code'=> 'freeSend',
  483. 'type'=> 'string',
  484. 'desc'=> $str,
  485. ];
  486. }
  487. if ((bccomp($sale->ed_full, 0.00, 2) == 1) && (bccomp($sale->ed_reduction, 0.00, 2) == 1)) {
  488. $data[] = [
  489. 'name'=> '满减',
  490. 'code'=> 'goodsReduce',
  491. 'type'=> 'string',
  492. 'desc'=> '满'.$sale->ed_full.'减'.$sale->ed_reduction,
  493. ];
  494. }
  495. return $data;
  496. }
  497. /**
  498. * 设置价格计算者
  499. */
  500. public function _getAdapter()
  501. {
  502. if ($this->isOption()) {
  503. $adapter = new GoodsOptionAdapter($this);
  504. } else {
  505. $adapter = new GoodsAdapter($this);
  506. }
  507. return $adapter;
  508. }
  509. /**
  510. * 获取价格计算者
  511. */
  512. public function getAdapter()
  513. {
  514. if (!isset($this->goodsAdapter)) {
  515. $this->goodsAdapter = $this->_getAdapter();
  516. }
  517. return $this->goodsAdapter;
  518. }
  519. /**
  520. * 是否为规格商品
  521. * @return bool
  522. */
  523. public function isOption()
  524. {
  525. return !empty($this->goods_option_id);
  526. }
  527. /**
  528. * 商品模型
  529. * @return Goods
  530. */
  531. public function goods()
  532. {
  533. return $this->goods;
  534. }
  535. /**
  536. * 插件类判断
  537. * @param \app\common\models\Goods $goods
  538. * @return bool
  539. */
  540. public function verify()
  541. {
  542. return false;
  543. }
  544. public function getVipPrice()
  545. {
  546. if($this->isOption()){
  547. return $this->goodsOption->vip_price;
  548. }else{
  549. return $this->goods()->vip_price;
  550. }
  551. }
  552. private function getMonthBuyLimit()
  553. {
  554. if (!app('plugins')->isEnabled('month-buy-limit')) {
  555. return [];
  556. }
  557. return \Yunshop\MonthBuyLimit\models\MonthLimitMember::getMemberLimit($this->goods()->id, \YunShop::app()->getMemberId());
  558. }
  559. }