120], [['subtitle'], 'string', 'max' => 150], [['unit', 'cover_pic', 'video_url', 'freight_type', 'goods_no'], 'string', 'max' => 255], [['check_remark'], 'safe'], [['bannar_pic'], 'string', 'max' => 1500], [['video_cover_pic', 'services', 'labels'], 'string', 'max' => 500], [['goods_source'], 'string', 'max' => 32], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'mall_id' => '商城id', 'mall_goods_id' => '主商城goods_id', 'store_id' => '门店id', 'goods_name' => '商品名称', 'subtitle' => '商品副标题', 'price' => '售价', 'original_price' => '原价(划线价仅展示)', 'cost_price' => '成本价', 'unit' => '单位', 'cover_pic' => '商品缩略图', 'bannar_pic' => '商品展示bannar图', 'video_url' => '商品视频', 'video_cover_pic' => '商品视频封面', 'is_on_sale' => '上架状态【1是 0否】', 'is_attr' => '是否有规格【1是 0否】', 'attr_groups' => '规格组', 'attr_groups_format' => '格式化规格组', 'status' => '状态[1正常 -1删除]', 'stock' => '商品库存', 'stock_warning' => '商品库存预警', 'is_show_stock' => '是否显示库存', 'is_show_sales' => '是否显示销量【1是 0否】', 'virtual_sales' => '虚拟销量', 'sales_num' => '累计销量', 'buy_num_limit' => '购买数量限制', 'freight_type' => '物流方式【1快递发货 2上门自提】逗号分隔', 'freight_rules_type' => '运费规则 【1运费模板 2自定义运费】', 'freight_id' => '运费模板ID', 'shipping_fee' => '自定义运费金额', 'free_shipping_num' => '商品满件包邮', 'free_shipping_money' => '商品满额包邮', 'goods_type' => '商品类型', 'detail' => '商品详情,图文', 'is_area_limit' => '是否开启限购区域', 'area_limit' => '限购区域', 'services' => '商品服务', 'labels' => '商品标签', 'goods_source' => '商品来源', 'supplier_type' => '供应者类型:0:自身;1:商城;2:o2o;3:多商户', 'supplier_id' => '供应者id', 'supplier_goods_id' => '供应品id', 'sort' => '排序', 'check_status' => '审核状态 0:待审核;1;:已通过;2:未通过', 'check_remark' => '审核备注', 'created_at' => '创建时间', 'updated_at' => '修改时间', 'show_style' => '商品展示风格', 'is_recommend' => '店铺推荐', 'package_data' => '套餐json数据', 'goods_no' => '商品货号', ]; } // 分类关系 public function getStoreGoodsCateRelate() { return $this->hasMany(StoreGoodsCateRelate::class, ['goods_id' => 'id']); } // 分类 public function getCats() { return $this->hasMany(StoreGoodsCate::class, ['id' => 'cate_id'])->via('storeGoodsCateRelate'); } // 商品额外设置 public function getExtra() { return $this->hasOne(StoreGoodsExtra::class, ['goods_id' => 'id'])->andWhere(['status' => [StatusEnum::ENABLED]]); } // sku public function getAttr() { return $this->hasMany(StoreGoodsAttr::class, ['goods_id' => 'id'])->andWhere(['status' => [StatusEnum::ENABLED]]); } // 预约商品信息 public function getReserve() { return $this->hasOne(StoreGoodsReserve::class, ['goods_id' => 'id']); } public function getStore() { return $this->hasOne(Store::class, ['id' => 'store_id']); } /** * 保存数据 * @Author bing * @DateTime 2021-08-24 15:34:48 * @param array $params * @param string $addons 来自插件 * @param callback $call_back 插件回调函数 * @return object|boolean * @copyright: Copyright (c) 2020 广东七件事集团 */ public static function setData(array $params) { $store_id = $params['store_id']; $mall_id = $params['mall_id']; try { $wait_del_attr_id_arr = []; if (!empty($params['id'])) { $model = self::findOne(['mall_id' => $params['mall_id'], 'id' => $params['id'], 'status' => [StatusEnum::ENABLED]]); if (empty($model)) throw new \Exception('商品不存在或已删除'); } else { $model = new self(); $model->loadDefaultValues(); } // 如果是预约商品 if ($params['goods_type'] == GoodsTypeEnum::GoodsTypeReserved) { // 取出最低价 if (!empty($params['reserve']['skus'])) { $params['stock'] = array_sum(array_column($params['reserve']['skus'], 'reserve_max_buy')); } else { $params['stock'] = $params['reserve']['reserve_max_buy']; } } self::priceLimit($mall_id,$params['price']); isset($params['services']) && $params['services'] = implode(',', $params['services']); isset($params['labels']) && $params['labels'] = implode(',', $params['labels']); isset($params['freight_type']) && $params['freight_type'] = implode(',', $params['freight_type']); isset($params['freight_id']) && empty($params['freight_id']) && $params['freight_id'] = 0; if (isset($params['is_attr']) && $params['is_attr'] == 1) { if (isset($params['attr_groups']) && !empty($params['attr_groups'])) { $attr_groups = $params['attr_groups']; $params['attr_groups'] = json_encode($params['attr_groups'], JSON_UNESCAPED_UNICODE); $params['attr_groups_format'] = json_encode(self::formatGoodsAttrGroup($attr_groups, JSON_UNESCAPED_UNICODE)); } } else { $params['attr_groups'] = ''; $params['attr_groups_format'] = ''; } isset($params['bannar_pic']) && $params['bannar_pic'] = json_encode($params['bannar_pic'], JSON_UNESCAPED_UNICODE); isset($params['area_limit']) && $params['area_limit'] = json_encode($params['area_limit'], JSON_UNESCAPED_UNICODE); isset($params['package_data']) && $params['package_data'] = json_encode($params['package_data'], JSON_UNESCAPED_UNICODE); $trans = Yii::$app->db->beginTransaction(); // 分类判断当条件不满足时不导入商品 if (isset($params['cats']) && !empty($params['cats'])) { $params['cats'] = self::goodsCategorizeValidate($params); } if (!$model->load($params, '') || !$model->save()) { throw new \Exception($model->getErrorMessage()); } //商品分类处理 if (isset($params['cats']) && !empty($params['cats'])) { $res = StoreGoodsCateRelate::setData($model->id, $params['cats']); if ($res === false) throw new \Exception('分类处理失败:' . GoodsCateRelate::getStaticError()); } $goods = ArrayHelper::toArray($model); if ($goods['goods_type'] === GoodsTypeEnum::GoodsTypeReserved) { // 预约商品 $reserve = $params['reserve']; $res = StoreGoodsReserve::setData($goods, $reserve); if ($res === false) throw new Exception('预约商品SKU处理失败:' . StoreGoodsAttr::getStaticError()); } else { // 商品规格处理 if ($model->is_attr == 1) { $attrs = $params['attr'] ?? []; $res = StoreGoodsAttr::setData($model->id, $attrs, $attr_groups, $mall_id, $wait_del_attr_id_arr); if ($res === false) throw new \Exception('规格处理失败:' . GoodsAttr::getStaticError()); } else { //无规格商品处理 $goods = ArrayHelper::toArray($model); $goods['pic_url'] = $goods['cover_pic']; $goods['weight'] = $params['goods_weight'] ?? 0; if ($goods['supplier_type'] == StoreEnum::SELF_MALL_WUZHOU && $goods['mall_goods_id']) { // 五洲供应链商品,记录规格id $mall_goods_attr = GoodsAttr::findOne(['goods_id' => $goods['mall_goods_id'], 'status' => StatusEnum::ENABLED]); $goods['mall_attr_id'] = $mall_goods_attr->id ?? 0; } $res = StoreGoodsAttr::setAttr($model->id, $goods); if ($res === false) throw new \Exception('设置货品失败:' . GoodsAttr::getStaticError()); } } // 商品额外信息处理 if (isset($params['extra']) && !empty($params['extra'])) { if (isset($params['extra']['platform_score_give_setting'])) unset($params['extra']['platform_score_give_setting']); $params['extra']['store_id'] = $store_id; $params['extra']['mall_id'] = $mall_id; $res = StoreGoodsExtra::setData($model->id, $params['extra']); if ($res === false) throw new \Exception('商品额外信息处理失败:' . GoodsExtra::getStaticError()); } if (!empty($params['addons'])) { GlobalInvoke::exec(GlobalInvoke::GOODS_DETAIL_EDIT, $params['mall_id'], [ 'type' => 'save', 'mall_id' => $params['mall_id'], 'goods_id' => $model->id, 'setting' => $params['addons'], ]); // $res = CouponGiveItemSetting::setGoodsSetting($params['mall_id'], $model->id, $params['coupon_selection']); // if ($res === false) throw new Exception('商品优惠券处理失败:' . CouponGiveItemSetting::getStaticError()); } $is_install = MallAddons::isInstall($mall_id, AddonsEnum::ADDONS_NAME_COUPON); if($is_install){ $res = StoreCouponGiveItemSetting::setGoodsSetting($params['mall_id'], $params['store_id'],$model['id'], $params); if ($res === false) throw new \Exception('商品设置赠送优惠券信息处理失败:' . StoreCouponGiveItemSetting::getStaticError()); } $res = GoodsAssociation::setData($model->id, $params); $trans->commit(); $event = new GoodsEditEvent($model['mall_id']); $data['goods_id'] = $model['id']; $data['mall_id'] = $model['mall_id']; Yii::$app->services->events->dispatch($event, $data, $event->listeners); return $model; } catch (\Exception $e) { if (isset($trans)) $trans->rollback(); self::$static_error = $e->getMessage(); return false; } } /** * 商品分类过滤验证 * @param array $params * @return array * @throws Exception */ public static function goodsCategorizeValidate(array $params): array { // 获取商品分类ID合集 $caseIds = StoreGoodsCate::find()->where(['mall_id' => $params['mall_id'], 'status' => StatusEnum::ENABLED]) ->select(['id'])->asArray()->all(); $caseIds = array_column($caseIds, "id"); // 判断输入ID是否是数字 $is_numeric = array_filter($params['cats'], function ($val) { return !is_numeric($val); }); if (!empty($is_numeric)) throw new Exception('商品分类必须为数字,不能包含其它字符'); // 判断输入ID 是否在商品分类ID中 获取符合的ID $caseIds = array_filter($params['cats'], function ($val) use ($caseIds) { return in_array($val, $caseIds); }); if (empty($caseIds)) throw new Exception('商品分类不存在或被删除'); return $caseIds; } public static function priceLimit($mall_id,$price){ $goodsStore = \Yii::$app->services->setting->addons->get($mall_id, StoreEnum::ADDONS_NAME, 'goodsStore'); if(!empty($goodsStore['min_price'])){ if($price<$goodsStore['min_price']){ throw new \Exception('最低价格不能低于'.$goodsStore['min_price']); } } if(isset($goodsStore['is_limit_max_price'])&&$goodsStore['is_limit_max_price']=='false'){ if(!empty($goodsStore['min_price'])) { if ($price > $goodsStore['max_price']) { throw new \Exception('最高价格不能高于' . $goodsStore['max_price']); } } } } /** * 格式化商品规格组数据 * @Author bing * @DateTime 2021-09-03 11:06:31 * @param array $attr_group 规格组 * @return array * @copyright: Copyright (c) 2020 广东七件事集团 */ public static function formatGoodsAttrGroup(array $attr_group) { $format_attr_group = []; $group_attr_ids = []; $all_attr_groups = []; $group_attr_list = array_column($attr_group, 'attr_list', 'attr_id'); //按规格项生成全部规格组 foreach ($group_attr_list as $key => $group_attrs) { array_push($group_attr_ids, array_column($group_attrs, 'attr_id')); $attr_group_id = $attr_group[$key]['attr_group_id']; $attr_group_name = $attr_group[$key]['attr_group_name']; foreach ($group_attrs as $k => &$group_attr) { $group_attr['attr_group_id'] = $attr_group_id; $group_attr['attr_group_name'] = $attr_group_name; } $all_attr_groups = array_merge($all_attr_groups, $group_attrs); } $all_attr_groups = array_column($all_attr_groups, null, 'attr_id'); // 规格组之间的笛卡尔集组合 $attr_dika = ArrayHelper::combineDika(array_values($group_attr_ids)); // 拼装以规格id列表为key的规格组格式,格式为attr_id1:attr_id2 => [attr_id1的规格组信息, attr_id的规格组信息] foreach ($attr_dika as $key => $attr_id_arr) { asort($attr_id_arr); $sign_id = implode(':', $attr_id_arr); $format_attr_group[$sign_id] = array_map(function ($id) use ($all_attr_groups) { return $all_attr_groups[$id]; }, $attr_id_arr); } return $format_attr_group; } /** * 商品处理 * @Author: lun * @DateTime: 2022/1/19 0019 18:06 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $params */ public static function goodsHandle($goods_id, $params) { try { $model = self::findOne(['id' => $goods_id, 'mall_id' => $params['mall_id'], 'status' => [StatusEnum::ENABLED]]); if (empty($model)) throw new \Exception('商品不存在或已删除'); if (!$model->load($params, '') || !$model->save()) throw new \yii\db\Exception($model->getErrorMessage()); return true; } catch (\Exception $e) { self::setStaticError($e->getMessage()); return false; } } /** * 商品详情 * @param $params * @return mixed|void */ public static function getGoodsDetail($params) { $goods = StoreGoods::getOne($params['where'], $params['is_array'], $params['with'], $params['order'], $params['select']); if (empty($goods)) return $goods; $goods['is_member_price'] = '0'; $goods['price_type'] = '新品'; $goods['sales_num'] += $goods['virtual_sales']; unset($goods['virtual_sales']); return $goods; } public static function getGoodListInDiy(int $mall_id, array $params) { $wheres[] = ['sg.mall_id' => $mall_id, 'sg.status' => StatusEnum::ENABLED, 'sg.check_status' => StoreEnum::CHECK_ADOPT, 'sg.is_on_sale' => 1, 's.status' => StatusEnum::ENABLED, 's.check_status' => StoreEnum::CHECK_ADOPT]; $wheres[] = ['or', ['>', 'expire_time', time()], ['expire_time' => 0]]; if (!empty($params['keyword'])) { $wheres[] = ['like', 'sg.goods_name', $params['keyword']]; } if (!empty($params['store_id'])) { $wheres[] = ['sg.store_id' => $params['store_id']]; } $params = [ 'alias' => 'sg', 'select' => 'sg.*,s.store_name', 'join' => [['INNER JOIN', Store::tableName() . ' as s', 'sg.store_id = s.id']], 'where' => $wheres, 'order' => 'sg.sort asc,sg.created_at desc', 'page' => $params['page'] ?? 1, 'limit' => $params['limit'] ?? 15, ]; $list = self::listPage($params); if (false === $list) { return self::$static_error; } foreach ($list['list'] as &$lv) { $lv['title'] = $lv['goods_name']; $lv['img'] = $lv['cover_pic']; } return $list; } public static function getRecommendGoodListInDiy(int $mall_id, array $params) { $wheres[] = [ 'sg.mall_id' => $mall_id, 'sg.is_recommend' => 1, 'sg.status' => StatusEnum::ENABLED, 'sg.check_status' => StoreEnum::CHECK_ADOPT, 'sg.is_on_sale' => 1, 's.status' => StatusEnum::ENABLED, 's.check_status' => StoreEnum::CHECK_ADOPT ]; $wheres[] = ['or', ['>', 'expire_time', time()], ['expire_time' => 0]]; if (!empty($params['keyword'])) { $wheres[] = ['like', 'sg.goods_name', $params['keyword']]; } if (!empty($params['store_id'])) $wheres[] = ['sg.store_id' => $params['store_id']]; $params = [ 'alias' => 'sg', 'select' => 'sg.id,sg.goods_name,sg.cover_pic,sg.price,sg.original_price,sg.show_style,(sg.virtual_sales+sg.sales_num) as sales_num,sg.created_at,s.store_name ', 'join' => [ ['INNER JOIN', Store::tableName() . ' as s', 'sg.store_id = s.id'], ], 'where' => $wheres, 'order' => 'sg.sort asc,sg.created_at desc', 'page' => $params['page'] ?? 1, 'limit' => $params['limit'] ?? 15, ]; $list = StoreGoods::listPage($params); if (false === $list) { return self::$static_error; } foreach ($list['list'] as &$val) { $val['title'] = $val['goods_name']; $val['img'] = $val['cover_pic']; $val['discount'] = bcsub($val['original_price'],$val['price'],2); if($val['discount']<0) $val['discount'] = 0; } return $list; } public static function getRecommendList($mall_id,$store_id){ $goods_list = StoreGoods::lists([ 'where'=>[ ['mall_id'=>$mall_id,'store_id'=>$store_id,'status'=>StatusEnum::ENABLED,'is_recommend'=>1,'check_status'=>1,'is_on_sale'=>1] ], 'select'=>'id,goods_name,price,original_price,cover_pic' ]); return $goods_list; } public static function syncMallGoods($params){ try{ $mall_id = $params['mall_id']; $selected_goods_ids = $params['selected_goods_ids']; $selected_store_ids = $params['selected_store_ids']; $sync_type = $params['sync_type']; // 同步商品到全部门店 if($sync_type == 1){ $store_where[] = ['mall_id' => $mall_id, 'check_status' => StoreEnum::CHECK_ADOPT, 'status' => StatusEnum::ENABLED]; $store_ids = Store::lists([ 'where' => $store_where, 'select' => 'id' ]); $selected_store_ids = array_column($store_ids, 'id') ?? []; } $store_goods_cate = self::getPlatformGoodsCate($mall_id); if(empty($store_goods_cate)){ $cate = ['mall_id' => $mall_id, 'name' => '平台', 'store_id' => 0, 'cate_id' => 0,'parent_id' => 0]; if(!$store_goods_cate = StoreGoodsCate::setData($cate)) throw new \Exception(StoreGoodsCate::getStaticError()); } /** * @var $goods_list \common\models\goods\Goods[] */ $goods_list = \common\models\goods\Goods::findAll(['mall_id' => $mall_id, 'id' => $selected_goods_ids]); foreach ($goods_list as $goods) { foreach ($selected_store_ids as $store_id) { try{ $store_goods = self::findOne(['mall_id' => $mall_id, 'store_id' => $store_id, 'mall_goods_id' => $goods['id'], 'status' => StatusEnum::ENABLED]); $base_params = [ 'mall_id' => $mall_id, 'store_id' => $store_id, 'supplier_id' => $store_id, 'cats' => [$store_goods_cate['id']], 'mall_goods_id' => $goods['id'], 'id' => $store_goods['id'] ?? 0, 'coupon_selection' => [], 'check_status' => StatusEnum::ENABLED ]; $mall_goods = $goods['attributes']; $base_params['extra'] = !empty($goods->extra) ? ArrayHelper::toArray($goods->extra) : []; if(isset($base_params['extra']['goods_id'])) unset($base_params['extra']['goods_id']); if(empty($mall_goods['services'])){ $mall_goods['services'] = []; }else{ $mall_goods['services'] = explode(',', $mall_goods['services']); } if(empty($mall_goods['labels'])){ $mall_goods['labels'] = []; }else{ $mall_goods['labels'] = explode(',', $mall_goods['labels']); } if(empty($mall_goods['freight_type'])){ $mall_goods['freight_type'] = []; }else{ $mall_goods['freight_type'] = explode(',', $mall_goods['freight_type']); } !empty($mall_goods['attr_groups']) && $mall_goods['attr_groups'] = json_decode($mall_goods['attr_groups'], true); !empty($mall_goods['bannar_pic']) && $mall_goods['bannar_pic'] = json_decode($mall_goods['bannar_pic'], true); !empty($mall_goods['area_limit']) && $mall_goods['area_limit'] = json_decode($mall_goods['area_limit'], true); // 预约商品 if ($mall_goods['goods_type'] == GoodsTypeEnum::GoodsTypeReserved) { $reserve = ArrayHelper::toArray($goods->reserve); unset($reserve['id'], $reserve['goods_id']); $skus = GoodsReserveSku::lists([ 'where' => [['goods_id' => $goods['id']]], 'with' => ['times' => function($query) { $query->with(['timeSpec']); }] ]); foreach ($skus as &$sku) { $signId = $sku['sign_id']; $attr_groups_format = json_decode($goods['attr_groups_format'], true) ?? []; $sku['attr_list'] = $attr_groups_format[$signId] ?? []; unset($sku['id'], $sku['goods_id']); foreach ($sku['times'] as &$time) { unset($time['id'], $time['reserve_sku_id']); if (!empty($time['timeSpec'])) { foreach ($time['timeSpec'] as &$time_spec) { unset($time_spec['id'], $time_spec['reserve_time_id'], $time_spec['reserve_sku_id']); } $time['time_spec'] = $time['timeSpec']; unset($time_spec, $time['timeSpec']); } } unset($time); } unset($sku); $reserve['skus'] = $skus; $mall_goods['reserve'] = $reserve; } else { if ($mall_goods['is_attr'] == 1) { $temp_attrs = ArrayHelper::toArray($goods->attr); $attr_groups = $mall_goods['attr_groups']; $attr = []; foreach ($temp_attrs as $val) { unset($val['id']); unset($val['goods_id']); $sku_group_name = explode(',', $val['name']); $attr_list = []; foreach($sku_group_name as $name){ $name_arr = explode(':', $name); foreach($attr_groups as $group){ if($group['attr_group_name'] == $name_arr[0]){ foreach($group['attr_list'] as $v){ if($v['attr_name'] == $name_arr[1]){ $attr_list[] = [ 'attr_id' => $v['attr_id'], 'attr_name' => $v['attr_name'], 'pic_url' => '', 'attr_group_id' => $group['attr_group_id'], 'attr_group_name' => $group['attr_group_name'], ]; break; } } break; } } } $val['attr_list'] = $attr_list; $attr[] = $val; } $base_params['attr'] = $attr; } } unset($mall_goods['id']); if(!self::setData(array_merge($mall_goods, $base_params))) throw new \Exception(self::getStaticError()); }catch(\Exception $e){ \Yii::error(self::class.'->'.__FUNCTION__.'//errMsg2:'.$e->getMessage().'//params:'.json_encode(array_merge($mall_goods, $base_params))); } } } }catch(\Exception $e){ \Yii::error(self::class.'->'.__FUNCTION__.'//errMsg1:'.$e->getMessage()); }finally{ return true; } } public static function getPlatformGoodsCate($mall_id) { return StoreGoodsCate::findOne(['mall_id' => $mall_id, 'store_id' => 0, 'name' => '平台']); } public static function computeMemberPrice($price, $sign_id, $user_level, $is_attr, $is_member_price, $member_price_setting, $discount) { $after_price = $price; switch ($is_member_price) { case 0: $after_price = bcdiv($price * $discount, 10, 2); break; case 1: //单独设置会员价 if (!empty($is_attr)) { //多规格 if (isset($member_price_setting['level' . $user_level][$sign_id])) $member_price_discount = $member_price_setting['level' . $user_level][$sign_id]; } else { //默认规格 if (isset($member_price_setting[$user_level])) $member_price_discount = $member_price_setting[$user_level]; } if (!empty($member_price_discount)) { $after_price = 0; if ($price >= $member_price_discount) $after_price = bcmul($member_price_discount, 1, 2); } break; } return $after_price; } public static function syncMchGoods($params) { try { $selected_goods_ids = $params['selected_goods_ids']; $store_id = $params['store_id']; $mall_id = $params['mall_id']; $goods_list = \common\models\goods\Goods::findAll(['id' => $selected_goods_ids]); /** @var \common\models\goods\Goods $goods */ foreach ($goods_list as $goods) { try { $cats = ArrayHelper::toArray($goods->cats); $goods_cats_name = array_column($cats, 'name') ?? []; if(empty($goods_cats_name)) continue; $store_goods_cates = StoreGoodsCate::lists([ 'where' => [ ['mall_id' => $mall_id], ['name' => $goods_cats_name], ['status' => StatusEnum::ENABLED], ], 'select' => 'id, name, parent_id' ]); $idx_name = array_column($store_goods_cates, 'id', 'name') ?? []; $need_insert = $idx_name ? array_diff($idx_name, $goods_cats_name) : $goods_cats_name; if(!empty($need_insert)){ $need_insert_cate = \common\models\goods\GoodsCate::lists([ 'where' => [ ['mall_id' => $goods->mall_id], ['name' => $need_insert] ], 'select' => 'id,mall_id, name, mch_id, parent_id, pic, big_pic, advert_pic, advert_url, advert_params, is_show', 'with' => ['parent.parent'] ]); self::loopSaveGoodsCate($need_insert_cate, $store_id, $mall_id, $idx_name); } $store_goods = self::findOne(['mall_id' => $mall_id, 'store_id' => $store_id, 'mall_goods_id' => $goods['id'], 'status' => StatusEnum::ENABLED]); $base_params = [ 'mall_id' => $mall_id, 'store_id' => $store_id, 'cats' => array_values($idx_name), 'mall_goods_id' => $goods['id'], 'id' => $store_goods['id'] ?? 0, 'coupon_selection' => [], 'check_status' => StatusEnum::ENABLED, 'is_on_sale' => StatusEnum::DISABLED ]; $mall_goods = $goods['attributes']; $base_params['extra'] = !empty($goods->extra) ? ArrayHelper::toArray($goods->extra) : []; if (isset($base_params['extra']['goods_id'])) unset($base_params['extra']['goods_id']); if (empty($mall_goods['services'])) { $mall_goods['services'] = []; } else { $mall_goods['services'] = explode(',', $mall_goods['services']); } if (empty($mall_goods['labels'])) { $mall_goods['labels'] = []; } else { $mall_goods['labels'] = explode(',', $mall_goods['labels']); } if (empty($mall_goods['freight_type'])) { $mall_goods['freight_type'] = []; } else { $mall_goods['freight_type'] = explode(',', $mall_goods['freight_type']); } !empty($mall_goods['attr_groups']) && $mall_goods['attr_groups'] = json_decode($mall_goods['attr_groups'], true); !empty($mall_goods['bannar_pic']) && $mall_goods['bannar_pic'] = json_decode($mall_goods['bannar_pic'], true); !empty($mall_goods['area_limit']) && $mall_goods['area_limit'] = json_decode($mall_goods['area_limit'], true); if ($mall_goods['is_attr'] == 1) { $temp_attrs = ArrayHelper::toArray($goods->attr); $attr_groups = $mall_goods['attr_groups']; $attr = []; foreach ($temp_attrs as $val) { unset($val['id']); unset($val['goods_id']); $sku_group_name = explode(',', $val['name']); $attr_list = []; foreach ($sku_group_name as $name) { $name_arr = explode(':', $name); foreach ($attr_groups as $group) { if ($group['attr_group_name'] == $name_arr[0]) { foreach ($group['attr_list'] as $v) { if ($v['attr_name'] == $name_arr[1]) { $attr_list[] = [ 'attr_id' => $v['attr_id'], 'attr_name' => $v['attr_name'], 'pic_url' => '', 'attr_group_id' => $group['attr_group_id'], 'attr_group_name' => $group['attr_group_name'], ]; } } break; } } } $val['attr_list'] = $attr_list; $attr[] = $val; } $base_params['attr'] = $attr; } unset($mall_goods['id']); if (!self::setData(array_merge($mall_goods, $base_params))) throw new \Exception(self::getStaticError()); } catch (\Exception $e) { dd($e->getMessage()); \Yii::error(__METHOD__ . '//errMsg2:' . $e->getMessage() . '//params:' . json_encode(array_merge($mall_goods, $base_params))); } } } catch (\Exception $e) { dd($e->getMessage()); \Yii::error(__METHOD__ . '//errMsg1:' . $e->getMessage()); } finally { return true; } } public static function loopSaveGoodsCate($cats, $store_id, $mall_id, &$idx_name) { foreach ($cats as $item) { $item['store_id'] = $store_id; $item['mall_id'] = $mall_id; unset($item['id'], $item['mch_id']); if ($item['parent']) { self::loopSaveGoodsCate([$item['parent']], $store_id, $mall_id, $idx_name); } else { $saveRes = StoreGoodsCate::setData($item); if ($saveRes === false) throw new \Exception(StoreGoodsCate::getStaticError()); $idx_name['name'] = $saveRes->id; } } } //商品导出整理数据 public static function exportHandle(array $data) { ini_set('memory_limit','512M'); $download_id = $data['download_id'] ?? 0; $download = Download::findOne(['id' => $download_id]); if ($download) { try { $params = json_decode($download->params, true); $filename = $download->name; $type = $download->type; $goods_detail_model = StoreGoods::find(); $goods_detail_model->where(['mall_id' => $params['mall_id'],'store_id' => $params['store_id']]); $goods_detail_model->andWhere(['>', 'status', StatusEnum::DISABLED]); !empty($params['start']) && $goods_detail_model->andWhere(['>=', 'g.created_at', strtotime($params['start'])]); !empty($params['end']) && $goods_detail_model->andWhere(['<=', 'g.created_at', strtotime($params['end'])]); !empty($params['goods_name']) && $goods_detail_model->andWhere(['like', 'g.goods_name', trim($params['goods_name']) . '%', false]); // !empty($params['cats']) && $goods_detail_model->andWhere(['in', 'gcr.cate_id', $params['cats']]); if(!empty($params['cats'])){ $goods_cate_relate_list = StoreGoodsCateRelate::lists(['where'=>[['cate_id'=>$params['cats']]]]); $goods_ids = array_column($goods_cate_relate_list,'goods_id'); $goods_detail_model->andWhere(['in', 'g.id', $goods_ids]); } !empty($params['goods_id']) && $goods_detail_model->andWhere(['=', 'g.id', $params['goods_id']]); !empty($params['choose_list']) && $goods_detail_model->andWhere(['in', 'g.id', $params['choose_list']]); !empty($params['goods_type']) && $goods_detail_model->andWhere(['=', 'g.goods_type', $params['goods_type']]); if (!empty($params['type'])) { switch ($params['type']) { case 'checking': $goods_detail_model->andWhere(['=', 'g.check_status', 0]); break; case 'onsale': $goods_detail_model->andWhere(['=', 'g.is_on_sale', WhetherEnum::ENABLED]); $goods_detail_model->andWhere(['=', 'g.check_status', 1]); $goods_detail_model->andWhere(['>', 'g.stock', 0]); break; case 'offline': $goods_detail_model->andWhere(['=', 'g.is_on_sale', WhetherEnum::DISABLED]); break; case 'sell-out': $goods_detail_model->andWhere(['=', 'g.stock', 0]); break; case 'stock-lack': $goods_detail_model->andWhere(new Expression('g.stock <= g.stock_warning')); break; } } //连表 // $goods_detail_model->join('LEFT JOIN', '{{%goods_cate_relate}} gcr', 'gcr.goods_id=g.id'); $goods_detail_model->alias('g'); $goods_detail_model->with(['cats']); $goods_detail_model->orderBy('g.id desc'); $fields = $params['fields']; $fields_arr = self::fieldsList(); //var_dump($fields); $batchSize = 5000; // 设置订单批量导出数量 $i = 0; //循环次数 $goodsCount = $goods_detail_model->count(); // 查询商品总条数 $is_goods_page = $goodsCount > $batchSize; // 判断是否需要分页 foreach ($goods_detail_model->asArray()->batch($batchSize) as $goods_details) { $goods_groups_max = 0; $goods_collective_list = []; foreach ($goods_details as $goods_detail) { $processed_goods_detail = self::processGoodsDetail($goods_detail); $goods_collective_list[] = $processed_goods_detail; $goods_groups_max = max($goods_groups_max, $processed_goods_detail['goods_group_num']); } // 处理商品规格 $goods_export_list = []; foreach ($goods_collective_list as $goods_detail) { $goods_item = []; foreach ($fields as $field) { if ($field == 'attr_groups') { // 以最大的规格数量为标准,不足的规格数量用空格补齐 foreach (range(1, $goods_groups_max) as $index) { $goods_item[] = $goods_detail['attr_groups'][$index - 1] ?? ''; } } else { $goods_item[] = $goods_detail[$field] ?? ''; } } $goods_export_list[] = $goods_item; } if (!empty($goods_export_list)) { $csv = new CsvTool(); $csv->filename = $is_goods_page ? $filename . "_1" : $filename;; $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($type); $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC; $have_select_field_arr = []; foreach ($fields as $value) { if ($value == 'attr_groups' && $goods_groups_max > 0) { foreach (range(1, $goods_groups_max) as $index) { $have_select_field_arr[] = '规格' . $index; } } else { $have_select_field_arr[] = $fields_arr[$value]; } } $csv->header = $have_select_field_arr; $csv->data = $goods_export_list; if ($is_goods_page && $i == 0) { // 因为订单页需要分页,所以需要设置文件名 $download->name = $filename . '_1'; $download->save(); } if ($i >= 1) { $newFileName = $filename . '_' . ($i + 1); $csv->filename = $newFileName; $downloadData = array( 'mall_id' => $download->mall_id, 'mch_id' => $download->mch_id, 'store_id' => $download->store_id, 'name' => $newFileName, 'type' => $download->type, 'handler_class' => $download->handler_class, 'method' => $download->method, 'params' => $download->params, 'data' => $goods_export_list ); $downloadModel = Download::setData($downloadData); if ($downloadModel === false) { Yii::$app->custom->logs('创建下载任务失败:' . $downloadData['name'], Download::getStaticError()); throw new Exception('创建新下载任务失败'); }; $download = $downloadModel; } $csv->export(); $csv->updateDown($download, $params['mall_id']); $i++; } } return true; } catch (\Exception $e) { $download->status = 3; $res = $download->save(); self::setStaticError($e->getMessage()); return $e->getMessage(); } } } public static function processGoodsDetail(array $goods_detail = []) { if ($goods_detail['is_attr'] == 1 && !empty($goods_detail['attr_groups'])) { $attr = json_decode($goods_detail['attr_groups'], true); if ($attr === null) { // 如果解码失败,返回默认值 $attr_arr = []; } else { $attr_arr = array_map(function ($item) { $item_attr_names = array_map(function ($item) { return htmlspecialchars($item['attr_name']); // 防止 XSS 攻击 }, $item['attr_list']); return htmlspecialchars($item['attr_group_name']) . ':' . implode('、', $item_attr_names); }, $attr); } $goods_detail['goods_group_num'] = count($attr_arr); $goods_detail['attr_groups'] = $attr_arr; } else { $goods_detail['attr_groups'] = '-'; $goods_detail['goods_group_num'] = 0; } if (!empty($goods_detail['cats'])) { $goods_detail['cats'] = implode('|', array_column($goods_detail['cats'], 'name')) ?? ''; } if (!empty($goods_detail['created_at'])) { $goods_detail['created_at'] = date("Y-m-d H:i:s", $goods_detail['created_at']) ?? ''; } if (in_array($goods_detail['is_on_sale'], [0, 1])) { $goods_detail['is_on_sale'] = $goods_detail['is_on_sale'] == 1 ? '上架' : '下架'; } return $goods_detail; } public static function fieldsList($field = null) { $fields = [ 'id' => '商品ID', 'goods_name' => '商品名', 'price' => '售价', 'cost_price' => '成本价', 'goods_no' => '商品货号', 'original_price' => '划线价', 'sales_num' => '销量', 'virtual_sales' => '虚拟销量', 'is_on_sale' => '状态', 'created_at' => '商品添加时间', 'unit' => '商品单位', 'stock' => '库存', 'cats' => '商品分类', 'attr_groups' => '规格', ]; if ($field === null) return $fields; $temp = []; foreach ($field as $val) { if (isset($fields[$val])) $temp[$val] = $fields[$val]; } return $temp; } /** * 批量导入商品 * @param $params * @return array|bool */ public static function importGoods($params) { $t = \Yii::$app->db->beginTransaction(); try { $csvImport = new CsvImportForm(); $data = $csvImport->setDataForm($params) ->setMaxCount(GoodsImportEnum::MAX_COUNT) ->setImportFieldConfig(GoodsImportEnum::HEAD_LIST) ->setColumnNum(GoodsImportEnum::COLUMN_NUM) ->readCsvData(); if (!$data) throw new \Exception(CsvImportForm::getStaticError()); $temp_data = array_column($data, null, 'serial_number'); if (count($temp_data) != count($data)) throw new \Exception('存在重复序号'); unset($temp_data); $result = self::handleImportData($params, $data, 1); $t->commit(); return ['counts' => $result['counts'], 'success_counts' => $result['success_counts'], 'fail_counts' => $result['fail_counts']]; } catch (\Exception $e) { self::$static_error = $e->getMessage(); $t->rollBack(); return false; } } /** * 商品数据入库 * @param $params * @param $data * @param $sync * @return array|void * @throws Exception */ public static function handleImportData($params, $data, $sync = 1) { $success_counts = $fail_counts = 0; $counts = count($data); $fail_arr = []; $temp_data = []; $attr_suffix_arr = ['a', 'b', 'c', 'd', 'e']; $bannar_pic_suffix_arr = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i']; $goods_detail_suffix_arr = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i', 'j']; // 方便记录错误 $data = array_column($data, null, 'serial_number'); foreach ($data as $val) { $val['data_row_id'] = $val['serial_number']; $val['mall_id'] = $params['mall_id']; if (empty($val['serial_number'])) { $val['reason'] = '序号为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (empty($val['goods_type'])) { $val['reason'] = '商品类型为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (empty($val['goods_no'])) { $val['reason'] = '商品编号为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (empty($val['goods_name'])) { $val['reason'] = '商品名称为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (empty($val['cate'])) { $val['reason'] = '商品分类为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (empty($val['price'])) { $val['reason'] = '商品售价为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (empty($val['stock'])) { $val['reason'] = '商品库存为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (empty($val['cover_pic'])) { $val['reason'] = '商品主图为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); continue; } if (!isset($temp_data[$val['goods_no'] . $val['goods_name']])) { $temp_data[$val['goods_no'] . $val['goods_name']] = [ // 存储每一行的数据 'list' => [], // 存储规格名称与规格值 'attr_groups' => [], // 第一个规格名的规格值图片 'attr_value_pic' => [], ]; } $name = ''; $temp_attr_list = []; $exist_attr = 0; // 判断门店是否需要审核商品 $isGoodsAuth = Yii::$app->services->setting->addons->get($params['mall_id'], StoreEnum::ADDONS_NAME, 'basic.goods_auth'); // 处理商品规格 foreach ($attr_suffix_arr as $v) { $attr_column_name = 'attr_name_' . $v; $attr_column_value = 'attr_value_' . $v; if (!isset($val[$attr_column_name])) continue; if (empty($val[$attr_column_name])) continue; if (!isset($val[$attr_column_value])) continue; if (empty($val[$attr_column_value])) continue; if (!isset($attr_column_name, $temp_data[$val['goods_no'] . $val['goods_name']]['attr_groups'])) { $temp_data[$val['goods_no'] . $val['goods_name']]['attr_groups'][$val[$attr_column_name]] = []; } $temp_data[$val['goods_no'] . $val['goods_name']]['attr_groups'][$val[$attr_column_name]][] = $val[$attr_column_value]; $temp_data[$val['goods_no'] . $val['goods_name']]['attr_groups'][$val[$attr_column_name]] = array_unique($temp_data[$val['goods_no'] . $val['goods_name']]['attr_groups'][$val[$attr_column_name]]); if ($v == 'a' && !empty($val['pic_url'])) { $temp_data[$val['goods_no'] . $val['goods_name']]['attr_value_pic'][$val[$attr_column_value]] = $val['pic_url']; } $name .= $val[$attr_column_name] . ':' . $val[$attr_column_value] . ':'; $temp_attr_list[] = [ 'attr_id' => 0, 'attr_name' => $val[$attr_column_value], 'attr_group_id' => 0, 'attr_group_name' => $val[$attr_column_name], 'pic_url' => '' ]; $exist_attr++; } if ($exist_attr <= 0) { $val['reason'] = '商品规格全为空'; list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts); unset($temp_data[$val['goods_no'] . $val['goods_name']]); continue; } $val['name'] = trim($name, ':'); $val['temp_attr_list'] = $temp_attr_list; // 处理商品轮播图 foreach ($bannar_pic_suffix_arr as $k => $bannar_item) { $bannar_pic_column = 'bannar_pic_' . $bannar_item; if (!isset($val[$bannar_pic_column])) continue; if (empty($val[$bannar_pic_column])) continue; $val['bannar_pic'][] = ['id' => $k, 'pic_url' => $val[$bannar_pic_column]]; } // 处理商品详情图片 $goods_detail_str = '
';
foreach ($goods_detail_suffix_arr as $detail_item) {
$goods_detail_column = 'goods_detail_' . $detail_item;
if (!isset($val[$goods_detail_column])) continue;
if (empty($val[$goods_detail_column])) continue;
$goods_detail_str .= '';
}
$goods_detail_str .= '