• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

miaoxing / product / 10654305759

01 Sep 2024 01:03PM UTC coverage: 78.63% (-2.9%) from 81.523%
10654305759

push

github

semantic-release-bot
chore(release): publish

See CHANGELOG.md for more details.

149 of 173 branches covered (86.13%)

574 of 730 relevant lines covered (78.63%)

38.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.79
/src/Service/ProductModel.php
1
<?php
2

3
namespace Miaoxing\Product\Service;
4

5
use Miaoxing\Category\Service\CategoryModel;
6
use Miaoxing\Logistics\Service\ShippingTplModel;
7
use Miaoxing\Plugin\BaseModel;
8
use Miaoxing\Plugin\Model\HasAppIdTrait;
9
use Miaoxing\Plugin\Model\ModelTrait;
10
use Miaoxing\Plugin\Model\ReqQueryTrait;
11
use Miaoxing\Plugin\Model\SnowflakeTrait;
12
use Miaoxing\Product\Metadata\ProductTrait;
13
use Wei\Event;
14
use Wei\Model\SoftDeleteTrait;
15
use Wei\Ret;
16
use Wei\Time;
17

18
/**
19
 * 商品模型
20
 *
21
 * @property ProductImageModel|ProductImageModel[] $images 商品图片
22
 * @property ProductDetailModel $detail 商品详情
23
 * @property ProductSpecModel $spec 商品规格
24
 * @property SkuModel|SkuModel[] $skus 商品 SKU
25
 * @property CategoryModel|CategoryModel[] $categories 商品分类
26
 * @property CategoriesProductModel|CategoriesProductModel[] $categoriesProducts 商品分类关联
27
 * @property ShippingTplModel $shippingTpl 运费模板
28
 */
29
class ProductModel extends BaseModel
30
{
31
    use HasAppIdTrait;
32
    use ModelTrait;
33
    use ProductTrait;
34
    use ReqQueryTrait;
35
    use SnowflakeTrait;
36
    use SoftDeleteTrait;
37

38
    public const STATUS_NOT_STARTED = 1;
39

40
    public const STATUS_ON_SALE = 2;
41

42
    public const STATUS_ENDED = 3;
43

44
    public const STATUS_SOLD_OUT = 4;
45

46
    public const STATUS_DELISTED = 5;
47

48
    public const STATUS_DELETED = 6;
49

50
    /**
51
     * 下单减库存
52
     */
53
    public const DEC_STOCK_MODE_BUY = 1;
54

55
    /**
56
     * 脏腑减库存
57
     */
58
    public const DEC_STOCK_MODE_PAY = 2;
59

60
    protected $deleteStatusColumn = 'status';
61

62
    protected $columns = [
63
        'configs' => [
64
            'cast' => 'array',
65
            'default' => [],
66
        ],
67
    ];
68

69
    /**
70
     * 状态值的详细配置
71
     *
72
     * @var array
73
     */
74
    protected $statusConfigs = [
75
        self::STATUS_NOT_STARTED => [
76
            'name' => '抢购即将开始',
77
            'shortName' => '即将开始',
78
        ],
79
        self::STATUS_ON_SALE => [
80
        ],
81
        self::STATUS_ENDED => [
82
            'name' => '抢购结束',
83
            'shortName' => '已结束',
84
        ],
85
        self::STATUS_SOLD_OUT => [
86
            'name' => '商品已卖光了',
87
            'shortName' => '已售罄',
88
        ],
89
        self::STATUS_DELISTED => [
90
            'name' => '商品已下架',
91
            'shortName' => '已下架',
92
        ],
93
        self::STATUS_DELETED => [
94
            'name' => '商品已删除',
95
            'shortName' => '已删除',
96
        ],
97
    ];
98

99
    public function getGuarded(): array
100
    {
101
        return array_merge($this->guarded, [
13✔
102
            'price',
13✔
103
            'stockNum',
13✔
104
            'soldNum',
13✔
105
        ]);
13✔
106
    }
107

108
    /**
109
     * 商品图片
110
     *
111
     * @return ProductImageModel|ProductImageModel[]
112
     */
113
    public function images(): ProductImageModel
114
    {
115
        return $this->hasMany(ProductImageModel::class);
2✔
116
    }
117

118
    /**
119
     * 商品详情
120
     *
121
     * @return ProductDetailModel
122
     */
123
    public function detail(): ProductDetailModel
124
    {
125
        return $this->hasOne(ProductDetailModel::class);
1✔
126
    }
127

128
    /**
129
     * 商品规格
130
     *
131
     * @return ProductSpecModel
132
     */
133
    public function spec(): ProductSpecModel
134
    {
135
        return $this->hasOne(ProductSpecModel::class);
11✔
136
    }
137

138
    /**
139
     * 商品 SKU
140
     *
141
     * @return SkuModel|SkuModel[]
142
     */
143
    public function skus(): SkuModel
144
    {
145
        return $this->hasMany(SkuModel::class);
11✔
146
    }
147

148
    /**
149
     * 商品分类
150
     *
151
     * @return CategoryModel|CategoryModel[]
152
     */
153
    public function categories(): CategoryModel
154
    {
155
        return $this->belongsToMany(CategoryModel::class);
×
156
    }
157

158
    /**
159
     * 商品分类关联
160
     *
161
     * @return CategoriesProductModel|CategoriesProductModel[]
162
     */
163
    public function categoriesProducts(): CategoriesProductModel
164
    {
165
        return $this->hasMany(CategoriesProductModel::class);
1✔
166
    }
167

168
    /**
169
     * 运费模板
170
     *
171
     * @return ShippingTplModel
172
     */
173
    public function shippingTpl(): ShippingTplModel
174
    {
175
        return $this->belongsTo(ShippingTplModel::class);
×
176
    }
177

178
    /**
179
     * 获取状态配置
180
     *
181
     * @return array
182
     */
183
    public function getStatusConfigs(): array
184
    {
185
        foreach ($this->statusConfigs as $status => &$config) {
5✔
186
            $config['status'] = $status;
5✔
187
        }
188

189
        return $this->statusConfigs;
5✔
190
    }
191

192
    /**
193
     * {@inheritDoc}
194
     */
195
    public function beforeSave()
196
    {
197
        $this->isInList = $this->calIsInList();
19✔
198
    }
199

200
    /**
201
     * {@inheritDoc}
202
     */
203
    public function afterSave()
204
    {
205
        $this->clearTagCache();
19✔
206
//        $this->clearRecordCache();
207
    }
208

209
    /**
210
     * {@inheritDoc}
211
     */
212
    public function afterDestroy()
213
    {
214
        $this->clearTagCache();
2✔
215
//        $this->clearRecordCache();
216
    }
217

218
    /**
219
     * 计算出商品状态并保存
220
     *
221
     * @return $this
222
     */
223
    public function updateStatus(): self
224
    {
225
        $this->status = $this->calStatus();
4✔
226
        $this->save();
4✔
227
        return $this;
4✔
228
    }
229

230
    /**
231
     * 返回默认的规格配置
232
     *
233
     * @return array[][]
234
     * @svc
235
     */
236
    protected function getDefaultSpecs(): array
237
    {
238
        $spec = SpecModel::findByOrCreate(['name' => '默认']);
×
239
        $specValue = SpecValueModel::findByOrCreate(['specId' => $spec->id, 'name' => '默认']);
×
240
        return [
×
241
            $spec->toArray(['id', 'name']) + [
×
242
                'values' => [
×
243
                    $specValue->toArray(['id', 'name']),
×
244
                ],
×
245
            ],
×
246
        ];
×
247
    }
248

249
    /**
250
     * 计算出商品状态
251
     *
252
     * @return int
253
     * @experimental 可能改为保存前自动计算
254
     */
255
    public function calStatus(): int
256
    {
257
        if (!$this->isListing) {
21✔
258
            return static::STATUS_DELISTED;
2✔
259
        }
260

261
        if ($this->stockNum <= 0) {
20✔
262
            return static::STATUS_SOLD_OUT;
4✔
263
        }
264

265
        $now = Time::now();
16✔
266
        if ($this->startAt && $this->startAt > $now) {
16✔
267
            return static::STATUS_NOT_STARTED;
1✔
268
        }
269

270
        if ($this->endAt && $this->endAt < $now) {
15✔
271
            return static::STATUS_ENDED;
3✔
272
        }
273

274
        return static::STATUS_ON_SALE;
12✔
275
    }
276

277
    /**
278
     * 根据状态等计算是否显示在前台列表
279
     *
280
     * @return bool
281
     */
282
    protected function calIsInList(): bool
283
    {
284
        if (
285
            !$this->isListing
19✔
286
            || !$this->isHidden
19✔
287
            || $this->isDeleted()
19✔
288
        ) {
289
            return false;
19✔
290
        }
291
        return true;
×
292
    }
293

294
    /**
295
     * {@inheritDoc}
296
     */
297
    protected function getDeleteStatusValue(): int
298
    {
299
        return static::STATUS_DELETED;
12✔
300
    }
301

302
    /**
303
     * {@inheritDoc}
304
     */
305
    protected function getRestoreStatusValue(): int
306
    {
307
        return $this->calStatus();
×
308
    }
309

310
    /**
311
     * @param string|int|array $categoryId
312
     * @return $this
313
     */
314
    public function withCategoryId($categoryId): self
315
    {
316
        $categoryIds = (array) $categoryId;
×
317
        $subCategories = CategoryModel::select('id')->where('parentId', $categoryId)->fetchAll();
×
318
        $categoryIds = array_merge($categoryIds, array_column($subCategories, 'id'));
×
319

320
        $this->selectMain()
×
321
            ->leftJoinRelation('categoriesProducts')
×
322
            ->where('categoriesProducts.categoryId', $categoryIds);
×
323

324
        return $this;
×
325
    }
326

327
    /**
328
     * 检查商品是否可以加入购物车和下单的相同操作
329
     *
330
     * @return Ret
331
     */
332
    public function checkBeforeCreateCartAndOrder(): Ret
333
    {
334
        if (!in_array($this->status, [static::STATUS_ON_SALE, static::STATUS_NOT_STARTED], true)) {
8✔
335
            $statusConfig = $this->getStatusConfigs()[$this->status];
4✔
336
            return err([
4✔
337
                'message' => $statusConfig['name'],
4✔
338
                'shortMessage' => $statusConfig['shortName'],
4✔
339
            ]);
4✔
340
        }
341

342
        $ret = Event::until('productCheckBeforeCreateCartAndOrder', [$this]);
4✔
343
        if ($ret) {
4✔
344
            return $ret;
×
345
        }
346

347
        return suc();
4✔
348
    }
349

350
    /**
351
     * 检查商品是否可以加入购物车
352
     *
353
     * @param Ret|null $createRet
354
     * @return Ret
355
     */
356
    public function checkCreateCart(?Ret $createRet = null): Ret
357
    {
358
        $createRet || $createRet = $this->checkBeforeCreateCartAndOrder();
8✔
359
        if ($createRet->isErr()) {
8✔
360
            return $createRet;
4✔
361
        }
362

363
        if (!$this->isAllowAddCart) {
4✔
364
            return err('该商品不可加入购物车');
1✔
365
        }
366

367
        $ret = Event::until('productCheckCreateCart', [$this]);
3✔
368
        if ($ret) {
3✔
369
            return $ret;
×
370
        }
371

372
        return suc('可以加入购物车');
3✔
373
    }
374

375
    /**
376
     * 检查商品是否可以下单
377
     *
378
     * @param Ret|null $createRet
379
     * @return Ret
380
     */
381
    public function checkCreateOrder(?Ret $createRet = null): Ret
382
    {
383
        $createRet || $createRet = $this->checkBeforeCreateCartAndOrder();
8✔
384
        if ($createRet->isErr()) {
8✔
385
            return $createRet;
4✔
386
        }
387

388
        if ($this->status === static::STATUS_NOT_STARTED) {
4✔
389
            $statusConfig = $this->getStatusConfigs()[$this->status];
1✔
390
            return err([
1✔
391
                'message' => $statusConfig['name'],
1✔
392
                'shortMessage' => $statusConfig['shortName'],
1✔
393
            ]);
1✔
394
        }
395

396
        $ret = Event::until('productCheckCreateOrder', [$this]);
3✔
397
        if ($ret) {
3✔
398
            return $ret;
×
399
        }
400

401
        return suc('可以购买');
3✔
402
    }
403

404
    /**
405
     * 检查商品是否可以加入购物车或下单
406
     *
407
     * 可用场景
408
     * 1. 商品详情,按需显示加入购物车或立即下单按钮
409
     * 2. SKU 选择器,按需显示加入购物车或立即下单按钮
410
     * 3. 商品列表,按需显示下单按钮,点击后再弹出选择窗口
411
     *
412
     * @return Ret
413
     */
414
    public function checkCreateCartOrOrder(): Ret
415
    {
416
        $create = $this->checkBeforeCreateCartAndOrder();
8✔
417
        $createCart = $this->checkCreateCart($create);
8✔
418
        $createOrder = $this->checkCreateOrder($create);
8✔
419

420
        if ($createCart->isErr() && $createOrder->isErr()) {
8✔
421
            // 如果两者都失败,暂时使用购物车的返回值
422
            $ret = err($createOrder['message'], $createCart['code']);
4✔
423
        } else {
424
            $ret = suc();
4✔
425
        }
426
        $ret['createCart'] = $createCart;
8✔
427
        $ret['createOrder'] = $createOrder;
8✔
428

429
        return $ret;
8✔
430
    }
431
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc