909 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			909 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			PHP
		
	
	
<?php
 | 
						||
 | 
						||
namespace app\repository;
 | 
						||
 | 
						||
use app\exception\RepositoryException;
 | 
						||
use app\model\AccountRecord;
 | 
						||
use app\model\mall\Category;
 | 
						||
use app\model\mall\SpuCategoryPivot;
 | 
						||
use app\model\mall\SpuLimitTime;
 | 
						||
use app\model\Order;
 | 
						||
use app\model\OrderActivity;
 | 
						||
use app\model\OrderGroupList;
 | 
						||
use app\model\Sku;
 | 
						||
use app\model\sku\SpecParam;
 | 
						||
use app\model\sku\SpecValue;
 | 
						||
use app\model\Spu;
 | 
						||
use app\model\SpuActivity;
 | 
						||
use app\service\Repository;
 | 
						||
use app\traits\spu\LimitTimeTrait;
 | 
						||
use app\traits\spu\SkuTrait;
 | 
						||
use Exception;
 | 
						||
use think\Collection;
 | 
						||
use think\db\exception\DataNotFoundException;
 | 
						||
use think\db\exception\DbException;
 | 
						||
use think\db\exception\ModelNotFoundException;
 | 
						||
use think\facade\Db;
 | 
						||
use think\facade\Log;
 | 
						||
use think\Model;
 | 
						||
 | 
						||
/**
 | 
						||
 * Class SpuRepository
 | 
						||
 * @package app\repository
 | 
						||
 * @method self getInstance(Model $model = null) static
 | 
						||
 */
 | 
						||
class SpuRepository extends Repository
 | 
						||
{
 | 
						||
    use SkuTrait;
 | 
						||
    use LimitTimeTrait;
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取商品列表【后台使用】
 | 
						||
     *
 | 
						||
     * @param  array  $params
 | 
						||
     * @return array
 | 
						||
     * @throws Exception
 | 
						||
     */
 | 
						||
    public function list(array $params = []): array
 | 
						||
    {
 | 
						||
        event('SpuCheck');
 | 
						||
        $whereMap = [];
 | 
						||
        $order    = $params['order'] ?? ['sort' => 'desc', 'id' => 'desc'];
 | 
						||
        $type     = $params['type'] ?? '';//type为空时 默认为商品管理 包含除积分商品外所有类型
 | 
						||
 | 
						||
        if (!empty($type)) {
 | 
						||
            $whereMap[] = ['activity_type', '=', $type];
 | 
						||
        }
 | 
						||
        switch ($type) {
 | 
						||
            case Spu::TYPE_NORMAL:
 | 
						||
                $whereMap[] = ['is_activity', '=', Spu::COMMON_OFF];
 | 
						||
                $whereMap[] = ['is_score', '=', Spu::COMMON_OFF];
 | 
						||
                break;
 | 
						||
            case Spu::TYPE_SCORE:
 | 
						||
                $whereMap[] = ['is_activity', '=', Spu::COMMON_OFF];
 | 
						||
                $whereMap[] = ['is_score', '=', Spu::COMMON_ON];
 | 
						||
                break;
 | 
						||
            case Spu::TYPE_GROUP_MAKE:
 | 
						||
            case Spu::TYPE_GROUP_BUY:
 | 
						||
            case Spu::TYPE_LIMIT_TIME:
 | 
						||
                $whereMap[] = ['is_activity', '=', Spu::COMMON_ON];
 | 
						||
                $whereMap[] = ['is_score', '=', Spu::COMMON_OFF];
 | 
						||
                break;
 | 
						||
            default:
 | 
						||
                $whereMap[] = ['is_score', '=', Spu::COMMON_OFF];
 | 
						||
            //                $whereMap[] = ['is_activity', '=', Spu::COMMON_OFF];
 | 
						||
        }
 | 
						||
 | 
						||
        if (isset($params['saleable'])) {
 | 
						||
            $whereMap[] = ['saleable', '=', $params['saleable']];
 | 
						||
        }
 | 
						||
 | 
						||
        if (isset($params['keyword']) && !empty($params['keyword'])) {
 | 
						||
            $whereMap[] = ['name|subtitle', 'like', '%'.$params['keyword'].'%'];
 | 
						||
        }
 | 
						||
 | 
						||
        if (isset($params['spu_type']) && !empty($params['spu_type'])) {
 | 
						||
            $whereMap[] = ['spu_type', '=', $params['spu_type']];
 | 
						||
        }
 | 
						||
 | 
						||
        $whereMap[] = ['deleted_at', '=', null];
 | 
						||
 | 
						||
        $params['page'] = $params['page'] ?? 1;
 | 
						||
        $params['size'] = $params['size'] ?? 20;
 | 
						||
 | 
						||
        $fields = $params['fields'] ?? [];
 | 
						||
 | 
						||
        return Spu::findList($whereMap, $fields, $params['page'], $params['size'], null, $order);
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取活动商品列表【后台使用】
 | 
						||
     *
 | 
						||
     * @param  array  $params
 | 
						||
     * @return array
 | 
						||
     * @throws Exception
 | 
						||
     */
 | 
						||
    public function activityList(array $params = []): array
 | 
						||
    {
 | 
						||
        $whereMap = [];
 | 
						||
        $order    = $params['order'] ?? ['sort' => 'desc', 'id' => 'desc'];
 | 
						||
        $type     = $params['type'] ?? '';//type为空时 默认为商品管理 包含除积分商品外所有类型
 | 
						||
 | 
						||
        if (!empty($type)) {
 | 
						||
            $whereMap[] = ['activity_type', '=', $type];
 | 
						||
        }
 | 
						||
 | 
						||
        if (isset($params['saleable'])) {
 | 
						||
            $whereMap[] = ['saleable', '=', $params['saleable']];
 | 
						||
        }
 | 
						||
 | 
						||
        if (isset($params['keyword']) && !empty($params['keyword'])) {
 | 
						||
            $whereMap[] = ['name|subtitle', 'like', '%'.$params['keyword'].'%'];
 | 
						||
        }
 | 
						||
 | 
						||
        if (isset($params['spu_type']) && !empty($params['spu_type'])) {
 | 
						||
            $whereMap[] = ['spu_type', '=', $params['spu_type']];
 | 
						||
        }
 | 
						||
 | 
						||
        $whereMap[] = ['deleted_at', '=', null];
 | 
						||
 | 
						||
        $params['page'] = $params['page'] ?? 1;
 | 
						||
        $params['size'] = $params['size'] ?? 20;
 | 
						||
 | 
						||
        $fields = $params['fields'] ?? [];
 | 
						||
 | 
						||
        return SpuActivity::findList($whereMap, $fields, $params['page'], $params['size'], null, $order);
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 前端商品列表
 | 
						||
     *
 | 
						||
     * @throws Exception
 | 
						||
     */
 | 
						||
    public function listForFront(array $params = [], callable $callback = null, array $order = [], array $where = []): array
 | 
						||
    {
 | 
						||
        $page = $params['page'] ?? 1;
 | 
						||
        $size = $params['size'] ?? 10;
 | 
						||
 | 
						||
        $page = (!is_numeric($page) || $page <= 0) ? 1 : $page;
 | 
						||
        $size = (!is_numeric($size) || $size <= 0) ? 10 : $size;
 | 
						||
 | 
						||
        event('SpuCheck');
 | 
						||
 | 
						||
        $searchMap = $where;
 | 
						||
 | 
						||
        // 商品名称模糊搜索
 | 
						||
        if (isset($params['keyword']) && !empty($params['keyword'])) {
 | 
						||
            $searchMap[] = ['name', 'like', '%'.$params['keyword'].'%'];
 | 
						||
        }
 | 
						||
 | 
						||
        // 活动类型搜索
 | 
						||
        if (isset($params['activity']) && !empty($params['activity'])) {
 | 
						||
            $searchMap[] = ['activity_type', 'in', explode(',', $params['activity'])];
 | 
						||
            if (!in_array($params['activity'], [Spu::TYPE_NORMAL, Spu::TYPE_SCORE])) {
 | 
						||
                $searchMap[] = ['is_activity', '=', Spu::COMMON_ON];
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        // 价格区间开始
 | 
						||
        if (isset($params['price_from']) && is_numeric($params['price_from'])) {
 | 
						||
            $searchMap[] = ['price', '>=', $params['price_from']];
 | 
						||
        }
 | 
						||
 | 
						||
        // 价格区间结束
 | 
						||
        if (isset($params['price_to']) && is_numeric($params['price_to'])) {
 | 
						||
            $searchMap[] = ['price', '<=', $params['price_to']];
 | 
						||
        }
 | 
						||
 | 
						||
        $searchMap[] = ['saleable', '=', Spu::COMMON_ON];
 | 
						||
 | 
						||
        // 是否积分
 | 
						||
        if (isset($params['is_score'])) {
 | 
						||
            $searchMap[] = ['is_score', '=', $params['is_score']];
 | 
						||
        }
 | 
						||
 | 
						||
        // 是否热门
 | 
						||
        if (isset($params['is_hot'])) {
 | 
						||
            $searchMap[] = ['is_hot', '=', $params['is_hot']];
 | 
						||
        }
 | 
						||
 | 
						||
        // 是否推荐到首页
 | 
						||
        if (isset($params['is_home']) && in_array($params['is_home'], [0, 1])) {
 | 
						||
            $searchMap[] = ['is_home', '=', $params['is_home']];
 | 
						||
        }
 | 
						||
 | 
						||
        // 指定分类
 | 
						||
        if (isset($params['category_id']) && !empty($params['category_id'])) {
 | 
						||
            // 多个分类逗号分割
 | 
						||
            $spuIds = SpuCategoryPivot::whereIn('category_id', explode(',', $params['category_id']))->column('spu_id');
 | 
						||
            if ($spuIds) {
 | 
						||
                $searchMap[] = ['id', 'in', array_unique($spuIds)];
 | 
						||
            }else{
 | 
						||
                $searchMap[] = ['id', '=', 0];
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        $fields = $params['fields'] ?? ['id', 'name', 'price', 'original_price', 'cover', 'activity_type'];
 | 
						||
 | 
						||
        if (empty($order)) {
 | 
						||
            $order = ['published_at' => 'desc'];
 | 
						||
        }
 | 
						||
 | 
						||
        $list = Spu::findList($searchMap, $fields, $page, $size, $callback, $order);
 | 
						||
 | 
						||
        $activityText = Spu::activityTextList();
 | 
						||
 | 
						||
        $list['list'] = $list['list']->each(function ($item) use ($activityText) {
 | 
						||
            $spuSkuList = $this->spuSkuList($item->id);
 | 
						||
            $item->skuId = $spuSkuList[0]["id"] ?? 0;
 | 
						||
            $item->tag = $activityText[$item->activity_type] ?? '';
 | 
						||
        });
 | 
						||
 | 
						||
        return $list;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 内容详情
 | 
						||
     *
 | 
						||
     * @param  int  $id
 | 
						||
     * @param  int  $accountId
 | 
						||
     * @return array
 | 
						||
     * @throws DataNotFoundException
 | 
						||
     * @throws DbException
 | 
						||
     * @throws ModelNotFoundException
 | 
						||
     * @throws RepositoryException
 | 
						||
     * @throws Exception
 | 
						||
     */
 | 
						||
    public function detail(int $id, int $accountId = 0): array
 | 
						||
    {
 | 
						||
        $field = [
 | 
						||
            'id', 'activity_id', 'activity_type', 'name', 'subtitle', 'price', 'original_price', 'unit','limit_num', 'cover', 'images',
 | 
						||
            'share_img', 'is_activity', 'stock', 'amount', 'multi_spec', 'content'
 | 
						||
        ];
 | 
						||
 | 
						||
        if (!$spu = $this->findById($id, $field)) {
 | 
						||
            throw new RepositoryException('数据不存在');
 | 
						||
        }
 | 
						||
 | 
						||
        $spu = $spu->toArray();
 | 
						||
 | 
						||
        if ($spu['is_activity'] == Spu::COMMON_ON) {
 | 
						||
            switch ($spu['activity_type']) {
 | 
						||
                case SpuLimitTime::TYPE:
 | 
						||
                    $activityInfo = SpuLimitTime::findById($spu['activity_id']);
 | 
						||
                    break;
 | 
						||
                default:
 | 
						||
                    $activityInfo = null;
 | 
						||
            }
 | 
						||
 | 
						||
            if (!$activityInfo) {
 | 
						||
                throw new RepositoryException('活动不存在');
 | 
						||
            }
 | 
						||
 | 
						||
            if ($activityInfo['status'] != Spu::COMMON_ON) {
 | 
						||
                throw new RepositoryException('活动已下架');
 | 
						||
            }
 | 
						||
 | 
						||
            $now = date('Y-m-d H:i:s');
 | 
						||
            if ($activityInfo['end_at'] < $now || $activityInfo['begin_at'] > $now) {
 | 
						||
                throw new RepositoryException('不在活动时间内');
 | 
						||
            }
 | 
						||
 | 
						||
            $spu['name']     = $activityInfo['name'];
 | 
						||
            $spu['subtitle'] = $activityInfo['subtitle'];
 | 
						||
            $spu['begin_at'] = $activityInfo['begin_at'];
 | 
						||
            $spu['end_at']   = $activityInfo['end_at'];
 | 
						||
        }
 | 
						||
 | 
						||
        if (isset($spu['is_score']) && $spu['is_score']) {
 | 
						||
            $spu['activity_type'] = Spu::TYPE_NORMAL;
 | 
						||
        }
 | 
						||
 | 
						||
        $spu['activity_text'] = '';
 | 
						||
        $spu['tag']           = '';
 | 
						||
        // 活动标签
 | 
						||
        if ($spu['is_activity'] && isset($spu['activity_type'])) {
 | 
						||
            $spu['activity_text'] = Spu::activityTextList()[$spu['activity_type']] ?? '';
 | 
						||
            $spu['tag']           = Spu::activityTextList()[$spu['activity_type']] ?? '';
 | 
						||
        }
 | 
						||
 | 
						||
        // 规格列表
 | 
						||
        $skuList = $spu['is_activity'] == Spu::COMMON_ON ? $this->spuSkuList($spu['activity_id'], $spu['activity_type'], true) : $this->spuSkuList($id);
 | 
						||
        if ($spu['is_activity'] == Spu::COMMON_ON) {
 | 
						||
            $default               = $skuList->where('is_default', Spu::COMMON_ON)->first();
 | 
						||
            $spu['original_price'] = $spu['price'];
 | 
						||
            $spu['price']          = $default['price'];
 | 
						||
        }
 | 
						||
        $spu["skuId"] = $skuList[0]["id"] ?? 0;
 | 
						||
 | 
						||
        $res['detail'] = $spu;
 | 
						||
        //        $res['sku']    = $skuList;
 | 
						||
 | 
						||
        return $res;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取商品规格列表
 | 
						||
     *
 | 
						||
     * @param  int  $id  ID
 | 
						||
     * @param  string  $activityType  活动类型 normal=普通 limit_time=限时折扣
 | 
						||
     * @param  bool  $isActivity  是否活动
 | 
						||
     * @return Sku[]|array|Collection
 | 
						||
     * @throws DataNotFoundException
 | 
						||
     * @throws DbException
 | 
						||
     * @throws ModelNotFoundException
 | 
						||
     */
 | 
						||
    public function spuSkuList(int $id, string $activityType = Spu::TYPE_NORMAL, bool $isActivity = false)
 | 
						||
    {
 | 
						||
        $skuFields = ['id', 'coding', 'title', 'stock', 'price', 'original_price', 'score', 'is_default'];
 | 
						||
        $where     = [];
 | 
						||
        $where[]   = ['type', '=', $activityType];
 | 
						||
        if ($isActivity) {
 | 
						||
            $where[] = ['spu_activity_id', '=', $id];
 | 
						||
        } else {
 | 
						||
            $where[] = ['spu_id', '=', $id];
 | 
						||
        }
 | 
						||
 | 
						||
        return Sku::where($where)->field($skuFields)->select();
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取商品 相关产品推荐
 | 
						||
     *
 | 
						||
     * @param  int  $spuId
 | 
						||
     * @param  bool  $isScore  是否积分商品
 | 
						||
     * @param  int  $page
 | 
						||
     * @param  int  $size
 | 
						||
     * @return mixed
 | 
						||
     * @throws Exception
 | 
						||
     */
 | 
						||
    public function spuProduct(int $spuId, bool $isScore = false, int $page = 1, int $size = 10)
 | 
						||
    {
 | 
						||
        $params             = [];
 | 
						||
        $params['is_score'] = $isScore;
 | 
						||
        $params['page']     = $page;
 | 
						||
        $params['size']     = $size;
 | 
						||
        $params['fields']   = $isScore ? Spu::scoreListFields() : Spu::spuListFields();
 | 
						||
 | 
						||
        return $this->listForFront($params, function ($q) use ($spuId) {
 | 
						||
            return $q->where('id', '<>', $spuId)->order('published_at', 'desc');
 | 
						||
        })['list'];
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 添加SPU商品信息
 | 
						||
     *
 | 
						||
     * @param  array  $data  基础信息
 | 
						||
     * @param  array  $sku  规格信息
 | 
						||
     * @return Model
 | 
						||
     * @throws RepositoryException
 | 
						||
     */
 | 
						||
    public function addSpu(array $data, array $sku): Model
 | 
						||
    {
 | 
						||
        Db::startTrans();
 | 
						||
        try {
 | 
						||
            $now = date('Y-m-d H:i:s');
 | 
						||
 | 
						||
            $data['saleable']     = $data['saleable'] ?? Spu::COMMON_ON;
 | 
						||
            $data['published_at'] = $data['published_at'] ?? $now;
 | 
						||
            $data['created_at']   = $now;
 | 
						||
 | 
						||
            if (isset($data['init_amount']) && $data['init_amount'] > 0) {
 | 
						||
                $data['amount'] += $data['init_amount'];
 | 
						||
            }
 | 
						||
 | 
						||
            $categoryIds = $data['category_id'] ?? '';
 | 
						||
            $categoryIds = explode(',', $categoryIds);
 | 
						||
 | 
						||
            $multiSpec = (bool) $data['multi_spec'];
 | 
						||
 | 
						||
            $spu = $this->create($data);
 | 
						||
 | 
						||
            $skuData = $this->handleSku($spu['id'], $sku, [], 'normal', $multiSpec);
 | 
						||
            (new Sku())->saveAll($skuData['data']);
 | 
						||
            $spu->save([
 | 
						||
                'stock'          => $skuData['stock'],
 | 
						||
                'price'          => $skuData['price'],
 | 
						||
                'original_price' => $skuData['original_price'],
 | 
						||
                'score'          => $skuData['score'],
 | 
						||
                'spec'           => json_encode($skuData['spec'], JSON_UNESCAPED_UNICODE)
 | 
						||
            ]);
 | 
						||
 | 
						||
            // 中间表
 | 
						||
            $this->setCategory($spu->id, $categoryIds);
 | 
						||
 | 
						||
            Db::commit();
 | 
						||
            return $spu;
 | 
						||
        } catch (RepositoryException $e) {
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw $e;
 | 
						||
        } catch (Exception $e) {
 | 
						||
            SpuRepository::log($e->getMessage(), $e);
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw new RepositoryException('商品创建失败');
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 编辑商品信息
 | 
						||
     *
 | 
						||
     * @param  int  $id
 | 
						||
     * @param  array  $data  基础信息
 | 
						||
     * @param  array  $sku  规格信息
 | 
						||
     * @param  array  $originalSkuList  原始规格列表
 | 
						||
     * @throws RepositoryException
 | 
						||
     */
 | 
						||
    public function editSpu(int $id, array $data, array $sku, array $originalSkuList)
 | 
						||
    {
 | 
						||
        Db::startTrans();
 | 
						||
        try {
 | 
						||
            $spu = $this->findById($id);
 | 
						||
            if (empty($spu)) {
 | 
						||
                throw new RepositoryException('没有相关的商品记录');
 | 
						||
            }
 | 
						||
 | 
						||
            if (isset($data['init_amount']) && $data['init_amount'] > 0) {
 | 
						||
                $data['amount'] = $spu['amount'] - $spu['init_amount'];
 | 
						||
                $data['amount'] = $data['amount'] > 0 ? $data['amount'] : 0;
 | 
						||
                $data['amount'] += $data['init_amount'];
 | 
						||
            }
 | 
						||
 | 
						||
            $now = date('Y-m-d H:i:s');
 | 
						||
 | 
						||
            $data['saleable']     = $data['saleable'] ?? Spu::COMMON_ON;
 | 
						||
            $data['published_at'] = $data['published_at'] ?? $now;
 | 
						||
            $data['updated_at']   = $now;
 | 
						||
            $categoryIds          = $data['category_id'] ?? '';
 | 
						||
            $categoryIds          = explode(',', $categoryIds);
 | 
						||
 | 
						||
            $multiSpec = (bool) $data['multi_spec'];
 | 
						||
            $skuData   = $this->handleSku($id, $sku, array_column($originalSkuList, 'id'), 'normal', $multiSpec);
 | 
						||
            (new Sku())->saveAll($skuData['data']);
 | 
						||
            (new Sku())->where('id', 'in', $skuData['delete'])->delete();
 | 
						||
 | 
						||
            $data['stock']          = $skuData['stock'];
 | 
						||
            $data['price']          = $skuData['price'];
 | 
						||
            $data['original_price'] = $skuData['original_price'];
 | 
						||
            $data['score']          = $skuData['score'];
 | 
						||
            $data['spec']           = json_encode($skuData['spec'], JSON_UNESCAPED_UNICODE);
 | 
						||
 | 
						||
            $spu->save($data);
 | 
						||
 | 
						||
            $this->setCategory($id, $categoryIds);
 | 
						||
            Db::commit();
 | 
						||
        } catch (RepositoryException $e) {
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw $e;
 | 
						||
        } catch (Exception $e) {
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw new RepositoryException('商品信息修改失败');
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 删除SPU商品信息 软删除
 | 
						||
     *
 | 
						||
     * @param  array  $ids
 | 
						||
     * @return bool
 | 
						||
     * @throws RepositoryException
 | 
						||
     */
 | 
						||
    public function deleteSpu(array $ids): bool
 | 
						||
    {
 | 
						||
        try {
 | 
						||
            return $this->model->whereIn('id', $ids)->save(['deleted_at' => date('Y-m-d H:i:s')]);
 | 
						||
        } catch (Exception $e) {
 | 
						||
            throw new RepositoryException('删除失败'.$e->getMessage());
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 处理SKU数据
 | 
						||
     *
 | 
						||
     * @param  int  $id  spu_id或spu_activity_id
 | 
						||
     * @param  array  $data
 | 
						||
     * @param  array  $originalSkuIds  原始skuID列表
 | 
						||
     * @param  string  $type  normal=普通商品 limit_time=限时折扣
 | 
						||
     * @param  bool  $isMulti  是否多规格 默认是·
 | 
						||
     * @return array
 | 
						||
     * @throws RepositoryException
 | 
						||
     */
 | 
						||
    public function handleSku(int $id, array $data, array $originalSkuIds = [], string $type = 'normal', bool $isMulti = true): array
 | 
						||
    {
 | 
						||
        if (empty($data)) {
 | 
						||
            throw new RepositoryException('规格不能为空');
 | 
						||
        }
 | 
						||
        $stock                = 0;//总库存
 | 
						||
        $defaultNum           = 0;//默认规格数量
 | 
						||
        $defaultPrice         = 0;//默认规格价格
 | 
						||
        $defaultOriginalPrice = 0;//默认规格原价
 | 
						||
        $defaultScore         = 0;//默认规格积分
 | 
						||
        $deleteIds            = array_diff($originalSkuIds, array_column($data, 'id'));;//待删除待skuID列表
 | 
						||
 | 
						||
        // 获取所有规格值及其相关规格
 | 
						||
        $specValueList   = SpecValue::alias('sv')->leftJoin('spec_param sp', 'sv.spec_id = sp.id')
 | 
						||
            ->where('sv.status', SpecValue::COMMON_ON)
 | 
						||
            ->where('sp.status', SpecParam::COMMON_ON)
 | 
						||
            ->field('sv.id as value_id, sv.title as value_title, sv.spec_id,sp.title as spec_title')
 | 
						||
            ->order('sv.sort', 'desc')
 | 
						||
            ->order('sv.id', 'asc')
 | 
						||
            ->select();
 | 
						||
        $valueKvList     = $isMulti ? $specValueList->column('value_title', 'value_id') : [];
 | 
						||
        $value2Spec      = $isMulti ? $specValueList->column('spec_id', 'value_id') : [];
 | 
						||
        $value2SpecTitle = $isMulti ? $specValueList->column('spec_title', 'value_id') : [];
 | 
						||
        $specValueIds    = [];// 该商品启用的规格值
 | 
						||
        // 验证规格
 | 
						||
        foreach ($data as $key => &$spec) {
 | 
						||
            // ID为空的表示新增  去掉id字段 saveAll时 自动识别更新或新增
 | 
						||
            if (empty($spec['id'])) {
 | 
						||
                unset($spec['id']);
 | 
						||
            }
 | 
						||
            if ($isMulti) {
 | 
						||
                $keys            = explode('-', $key);
 | 
						||
                $spec['indexes'] = $key;
 | 
						||
                $spec['title']   = '';
 | 
						||
                $specText        = [];
 | 
						||
 | 
						||
                $specValueIds = array_merge($specValueIds, $keys);
 | 
						||
                foreach ($keys as $k) {
 | 
						||
                    $spec['title'] .= ' '.($valueKvList[$k] ?? '');
 | 
						||
                    $specText[]    = [$value2SpecTitle[$k] => $valueKvList[$k]];
 | 
						||
                }
 | 
						||
                $spec['spec_text'] = json_encode($specText, JSON_UNESCAPED_UNICODE);
 | 
						||
            }
 | 
						||
            if (!isset($spec['stock']) || !isset($spec['is_default'])) {
 | 
						||
                throw new RepositoryException('规格信息不完整');
 | 
						||
            }
 | 
						||
            if (empty($spec['stock'])) {
 | 
						||
                throw new RepositoryException('请填写库存');
 | 
						||
            }
 | 
						||
            $spec['price'] = $spec['original_price'] ?? 0;
 | 
						||
            if (isset($spec['price']) && $spec['price'] <= 0) {
 | 
						||
                throw new RepositoryException('规格价格必须大于0');
 | 
						||
            }
 | 
						||
 | 
						||
            if (!isset($spec['price']) && !isset($spec['score'])) {
 | 
						||
                throw new RepositoryException('规格价格|积分不能为空');
 | 
						||
            }
 | 
						||
 | 
						||
            $spec['original_price'] = $spec['original_price'] ?: $spec['price'];
 | 
						||
 | 
						||
            if ($spec['is_default'] == 1) {
 | 
						||
                $defaultNum           += 1;
 | 
						||
                $defaultPrice         = $spec['price'];
 | 
						||
                $defaultOriginalPrice = $spec['original_price'];
 | 
						||
                $defaultScore         = $spec['score'] ?? 0;
 | 
						||
            }
 | 
						||
 | 
						||
            if ($type == 'normal') {
 | 
						||
                if (!isset($spec['spu_id'])) {
 | 
						||
                    $spec['spu_id'] = $id;
 | 
						||
                }
 | 
						||
            } else {
 | 
						||
                if (!isset($spec['spu_activity_id'])) {
 | 
						||
                    $spec['spu_activity_id'] = $id;
 | 
						||
                }
 | 
						||
                $spec['type'] = $type;
 | 
						||
            }
 | 
						||
 | 
						||
            if (!isset($spec['coding']) || empty($spec['coding'])) {
 | 
						||
                $spec['coding'] = generateCode();
 | 
						||
            }
 | 
						||
 | 
						||
            $stock += $spec['stock'];
 | 
						||
        }
 | 
						||
 | 
						||
        $specInfo     = [];// 该商品的启用的规格及其规格值
 | 
						||
        $specValueIds = array_unique($specValueIds);
 | 
						||
        foreach ($specValueIds as $valueId) {
 | 
						||
            $specId = $value2Spec[$valueId] ?? 0;
 | 
						||
            if ($specId > 0 && !isset($specInfo[$specId])) {
 | 
						||
                $specInfo[$specId] = [];
 | 
						||
            }
 | 
						||
            $specInfo[$specId][] = $valueId;
 | 
						||
        }
 | 
						||
 | 
						||
        if ($defaultNum !== 1) {
 | 
						||
            throw new RepositoryException('默认规格有且仅有一个');
 | 
						||
        }
 | 
						||
 | 
						||
        return [
 | 
						||
            'data'           => $data,
 | 
						||
            'stock'          => $stock,
 | 
						||
            'price'          => $defaultPrice,
 | 
						||
            'original_price' => $defaultOriginalPrice,
 | 
						||
            'score'          => $defaultScore,
 | 
						||
            'delete'         => $deleteIds,
 | 
						||
            'spec'           => $specInfo
 | 
						||
        ];
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 添加SPU活动商品信息
 | 
						||
     *
 | 
						||
     * @param  string  $type
 | 
						||
     * @param  array  $data  活动基础信息
 | 
						||
     * @param  int  $spuId
 | 
						||
     * @param  array  $sku  规格信息
 | 
						||
     * @return Model
 | 
						||
     * @throws RepositoryException
 | 
						||
     */
 | 
						||
    public function addActivity(string $type, array $data, int $spuId, array $sku): Model
 | 
						||
    {
 | 
						||
        Db::startTrans();
 | 
						||
        try {
 | 
						||
            if (!$spu = Spu::findById($spuId)) {
 | 
						||
                throw new RepositoryException('商品不存在');
 | 
						||
            }
 | 
						||
 | 
						||
            $insert = $spu->toArray();
 | 
						||
            unset($insert['id']);
 | 
						||
            $allowFields                 = [
 | 
						||
                'name', 'subtitle', 'saleable', 'price', 'original_price', 'score', 'level_id',
 | 
						||
                'is_hot', 'has_postage', 'cover', 'images', 'share_img', 'video', 'spu_type', 'content', 'views',
 | 
						||
            ];
 | 
						||
            $now                         = date('Y-m-d H:i:s');
 | 
						||
            $insert                      = arrayKeysFilter($insert, $allowFields);
 | 
						||
            $insert['activity_type']     = $type;
 | 
						||
            $insert['cover']             = $data['cover'] ?: $insert['cover'];
 | 
						||
            $insert['activity_begin_at'] = $data['activity_begin_at'];
 | 
						||
            $insert['activity_end_at']   = $data['activity_end_at'];
 | 
						||
            $insert['limit_time']        = $data['limit_time'] ?? 0;
 | 
						||
            $insert['limit_num']         = $data['limit_num'] ?? 0;
 | 
						||
            $insert['group_base_num']    = $data['group_base_num'] ?? 0;
 | 
						||
            $insert['group_num']         = $data['group_num'] ?? 0;
 | 
						||
            $insert['group_time']        = $data['group_time'] ?? 0;
 | 
						||
            $insert['virtual_group']     = $data['virtual_group'] ?? 0;
 | 
						||
            $insert['open_one']          = $data['open_one'] ?? 0;
 | 
						||
            $insert['amount']            = 0;//销量设为0
 | 
						||
            $insert['spu_id']            = $spuId;
 | 
						||
            $insert['created_at']        = $now;
 | 
						||
            $insert['updated_at']        = $now;
 | 
						||
            $insert['published_at']      = $now;
 | 
						||
            $spuActivity                 = SpuActivity::create($insert);
 | 
						||
 | 
						||
            $spu->save([
 | 
						||
                'activity_id'   => $spuActivity['id'],
 | 
						||
                'is_activity'   => Spu::COMMON_ON,
 | 
						||
                'activity_type' => $type,
 | 
						||
            ]);
 | 
						||
 | 
						||
            $skuData = $this->handleSku($spuActivity['id'], $sku, [], 'activity');
 | 
						||
            (new Sku())->saveAll($skuData['data']);
 | 
						||
 | 
						||
            $spuActivity->save([
 | 
						||
                'stock'          => $skuData['stock'],
 | 
						||
                'price'          => $skuData['price'],
 | 
						||
                'original_price' => $skuData['original_price'],
 | 
						||
                'score'          => $skuData['score'],
 | 
						||
            ]);
 | 
						||
 | 
						||
            Db::commit();
 | 
						||
            return $spu;
 | 
						||
        } catch (RepositoryException $e) {
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw $e;
 | 
						||
        } catch (Exception $e) {
 | 
						||
            SpuRepository::log($e->getMessage(), $e);
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw new RepositoryException('活动商品创建失败');
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 编辑活动商品信息
 | 
						||
     *
 | 
						||
     * @param  int  $id
 | 
						||
     * @param  array  $data  基础信息
 | 
						||
     * @param  array  $sku  规格信息
 | 
						||
     * @param  array  $originalSkuList  原始规格列表
 | 
						||
     * @throws RepositoryException
 | 
						||
     */
 | 
						||
    public function editActivity(int $id, array $data, array $sku, array $originalSkuList)
 | 
						||
    {
 | 
						||
        Db::startTrans();
 | 
						||
        try {
 | 
						||
            if (!$spuActivity = SpuActivity::findById($id)) {
 | 
						||
                throw new RepositoryException('没有相关的活动商品记录');
 | 
						||
            }
 | 
						||
 | 
						||
            $now = date('Y-m-d H:i:s');
 | 
						||
 | 
						||
            $data['updated_at'] = $now;
 | 
						||
 | 
						||
            $skuData = $this->handleSku($id, $sku, array_column($originalSkuList, 'id'), 'activity');
 | 
						||
            (new Sku())->saveAll($skuData['data']);
 | 
						||
            (new Sku())->where('id', 'in', $skuData['delete'])->delete();
 | 
						||
 | 
						||
            $data['stock']          = $skuData['stock'];
 | 
						||
            $data['price']          = $skuData['price'];
 | 
						||
            $data['original_price'] = $skuData['original_price'];
 | 
						||
            $data['score']          = $skuData['score'];
 | 
						||
 | 
						||
            $spuActivity->save($data);
 | 
						||
            Db::commit();
 | 
						||
        } catch (RepositoryException $e) {
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw $e;
 | 
						||
        } catch (Exception $e) {
 | 
						||
            // 回滚事务
 | 
						||
            Db::rollback();
 | 
						||
            throw new RepositoryException('活动商品信息修改失败'.$e->getLine());
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取活动商品的订单列表
 | 
						||
     *
 | 
						||
     * @throws Exception
 | 
						||
     */
 | 
						||
    public function getActivityOrderList(int $activityId, string $type = Spu::TYPE_LIMIT_TIME, int $page = 1, int $size = 0): array
 | 
						||
    {
 | 
						||
        $where   = [];
 | 
						||
        $where[] = ['spu_activity_id', '=', $activityId];
 | 
						||
        $where[] = ['deleted_at', '=', null];
 | 
						||
        $where[] = ['type', '=', $type];
 | 
						||
 | 
						||
        $res = OrderActivity::findList($where, [], $page, $size, function ($q) {
 | 
						||
            return $q->with(['orderInfo', 'account']);
 | 
						||
        }, ['id' => 'desc']);
 | 
						||
 | 
						||
        $res['list'] = $res['list']->each(function ($item) {
 | 
						||
            $item->activity_text = Spu::activityTextList()[$item->activity_type] ?? '';
 | 
						||
            $item->created_at    = $item->orderInfo->created_at ?? '';
 | 
						||
            $item->phone         = $item->orderInfo->phone ?? '';
 | 
						||
            $item->status        = $item->orderInfo->status ?? '';
 | 
						||
            $item->status_text   = Order::statusTextList()[$item->status] ?? '';
 | 
						||
            $item->nickname      = $item->account->nickname ?? '';
 | 
						||
            $item->real_name     = $item->account->real_name ?? '';
 | 
						||
        });
 | 
						||
 | 
						||
        return $res;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 商品分类 构造xmSelect 需要的数据[xmSelect使用时需要json_encode处理]
 | 
						||
     *
 | 
						||
     * @param  array  $selected
 | 
						||
     * @param  array  $disabled
 | 
						||
     * @param  bool  $excludeParent  拥有下级的节点是否不可勾选 默认false
 | 
						||
     * @return array
 | 
						||
     * @throws DataNotFoundException
 | 
						||
     * @throws DbException
 | 
						||
     * @throws ModelNotFoundException
 | 
						||
     */
 | 
						||
    public function categoryXmSelect(array $selected = [], array $disabled = [], bool $excludeParent = false): array
 | 
						||
    {
 | 
						||
        $category = Category::order('sort', 'desc')
 | 
						||
            ->field('id,pid,title')
 | 
						||
            ->select()->toArray();
 | 
						||
        foreach ($category as $k => $m) {
 | 
						||
            $category[$k]['selected'] = in_array($m['id'], $selected);
 | 
						||
            $category[$k]['disabled'] = in_array($m['id'], $disabled);
 | 
						||
        }
 | 
						||
 | 
						||
        $category = CmsRepository::getInstance()->buildMenuChild(0, $category, 'children', $excludeParent);
 | 
						||
        return CmsRepository::getInstance()->handleSelectedList($category);
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 商品分类
 | 
						||
     *
 | 
						||
     * @param  int  $pid
 | 
						||
     * @param  array  $fields
 | 
						||
     * @return Category[]|array|Collection
 | 
						||
     * @throws DataNotFoundException
 | 
						||
     * @throws DbException
 | 
						||
     * @throws ModelNotFoundException
 | 
						||
     */
 | 
						||
    public function category(int $pid = 0, array $fields = ['id', 'title'])
 | 
						||
    {
 | 
						||
        return Category::where('pid', $pid)
 | 
						||
            ->field($fields)
 | 
						||
            ->order('sort', 'desc')
 | 
						||
            ->order('id', 'asc')
 | 
						||
            ->select();
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 设置商品分类
 | 
						||
     *
 | 
						||
     * @param  int  $spuId
 | 
						||
     * @param  array  $categoryIds
 | 
						||
     */
 | 
						||
    public function setCategory(int $spuId, array $categoryIds)
 | 
						||
    {
 | 
						||
        SpuCategoryPivot::where('spu_id', $spuId)->delete();
 | 
						||
 | 
						||
        if ($categoryIds) {
 | 
						||
            $insert = [];
 | 
						||
            foreach ($categoryIds as $categoryId) {
 | 
						||
                $insert[] = [
 | 
						||
                    'spu_id'      => $spuId,
 | 
						||
                    'category_id' => $categoryId
 | 
						||
                ];
 | 
						||
            }
 | 
						||
 | 
						||
            (new SpuCategoryPivot())->insertAll($insert);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取商品分类ID列表
 | 
						||
     *
 | 
						||
     * @param  int  $spuId
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    public function getCategoryIdList(int $spuId): array
 | 
						||
    {
 | 
						||
        return SpuCategoryPivot::where('spu_id', $spuId)->column('category_id');
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取商品规格信息
 | 
						||
     *
 | 
						||
     * @param  int  $spuId
 | 
						||
     * @return array
 | 
						||
     * @throws DataNotFoundException
 | 
						||
     * @throws DbException
 | 
						||
     * @throws ModelNotFoundException
 | 
						||
     * @throws RepositoryException
 | 
						||
     */
 | 
						||
    public function getSpec(int $spuId): array
 | 
						||
    {
 | 
						||
        if (!$spu = Spu::findById($spuId)) {
 | 
						||
            throw new RepositoryException('商品不存在');
 | 
						||
        }
 | 
						||
 | 
						||
        $res = [];
 | 
						||
 | 
						||
        if ($spu['is_activity'] == Spu::COMMON_ON) {
 | 
						||
            $where[] = ['spu_activity_id', '=', $spu['activity_id']];
 | 
						||
            $where[] = ['type', '=', $spu['activity_type']];
 | 
						||
        } else {
 | 
						||
            $where[] = ['spu_id', '=', $spuId];
 | 
						||
        }
 | 
						||
        $fields          = ['id', 'spu_id', 'indexes', 'title', 'coding', 'stock', 'price', 'original_price', 'is_default'];
 | 
						||
        $skuList         = Sku::where($where)->field($fields)->select();
 | 
						||
        $res['sku_list'] = $skuList;//SKU列表
 | 
						||
        $res['sku']      = $skuList->where('is_default', Sku::COMMON_ON)->first();//默认SKU
 | 
						||
        $res['spec']     = [];//规格
 | 
						||
        if ($spu['multi_spec'] > 0) {
 | 
						||
            $spec     = json_decode($spu['spec'], true);
 | 
						||
            $specIds  = array_keys($spec);
 | 
						||
            $valueIds = [];
 | 
						||
            foreach (array_values($spec) as $arr) {
 | 
						||
                $valueIds = array_merge($valueIds, $arr);
 | 
						||
            }
 | 
						||
 | 
						||
            $specList      = SpecParam::whereIn('id', $specIds)->order('sort', 'desc')->order('id', 'asc')->select()->toArray();
 | 
						||
            $specValueList = SpecValue::whereIn('id', $valueIds)->order('sort', 'desc')->order('id', 'asc')->select()->toArray();
 | 
						||
 | 
						||
            foreach ($specList as $sl) {
 | 
						||
                $arr          = [];
 | 
						||
                $arr['id']    = $sl['id'];
 | 
						||
                $arr['title'] = $sl['title'];
 | 
						||
                $children     = [];
 | 
						||
                foreach ($specValueList as $vl) {
 | 
						||
                    if ($vl['spec_id'] == $sl['id']) {
 | 
						||
                        $children[] = [
 | 
						||
                            'id'    => $vl['id'],
 | 
						||
                            'title' => $vl['title'],
 | 
						||
                        ];
 | 
						||
                    }
 | 
						||
                }
 | 
						||
                $arr['children'] = $children;
 | 
						||
 | 
						||
                $res['spec'][] = $arr;
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        return $res;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * [{"颜色": "红色"},{"尺寸": "L"}] 转成 ["颜色:红色","尺寸:L"]
 | 
						||
     *
 | 
						||
     * @param $specArr
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    public function convertSpecInfo($specArr): array
 | 
						||
    {
 | 
						||
        if (!$specArr) {
 | 
						||
            return [];
 | 
						||
        }
 | 
						||
        $arr = [];
 | 
						||
        foreach ($specArr as $s) {
 | 
						||
            $k     = key($s);
 | 
						||
            $arr[] = sprintf("%s:%s", $k, $s[$k] ?? '');
 | 
						||
        }
 | 
						||
        return $arr;
 | 
						||
    }
 | 
						||
} |