$mall_id], ['id' => $params['goods_id']], ['status' => StatusEnum::ENABLED] ], true, ['attr', 'extra', 'collect']); if (empty($goods)) throw new \Exception('商品不存在'); $goodsModel = new GoodsModel(); $goodsModel->setAfterSaleAddressId(1); $goodsModel->after_sale_info['after_sale_address_id'] = 1; $goodsModel->title = mb_substr($goods['goods_name'], 0, 60); $goodsModel->sub_title = !empty($goods['subtitle']) ? mb_substr($goods['subtitle'], 0, 18) : ''; $banner_images = Json::decode($goods['bannar_pic']); $banner_images = array_column($banner_images, 'pic_url'); array_unshift($banner_images, $goods['cover_pic']); // 发货方式,若为无需快递(仅对部分类目开放),则无需填写运费模版id。0:快递发货(默认),1:无需快递 // 需要将图片传至微信 $req_banner_images = []; foreach ($banner_images as $banner_image) { $img_url = $this->uploadImage($banner_image, $config); if (!empty($img_url)) $req_banner_images[] = $img_url; } $goodsModel->head_imgs = $req_banner_images; // 0快递发货, 1无需物流 $goodsModel->deliver_method = $goods['freight_type'] == StatusEnum::ENABLED || !empty($goods['shipping_fee']) ? StatusEnum::DISABLED : StatusEnum::ENABLED; // 详情 - 需要将图片拿出来 // 文本也需要取出来 if (empty($goods['detail'])) { throw new \Exception("商品详情不能为空"); } $goodsModel->desc_info = $this->getDescInfo($goods['detail'], $config); // 类目 $goodsModel->cats = $this->getCats($params['cat_id']); // 属性 // https://developers.weixin.qq.com/doc/channels/API/product/add.html // 商品参数,部分类目有必填的参数,具体参考文档获取类目信息中的字段attr.product_attr_list[].is_required if (!empty($params['attrs'])) { $goodsModel->attrs = $this->getAttrs($params['attrs']); } // 品牌id,无品牌为“2100000000” if (!empty($params['brand_id'])) { $goodsModel->brand_id = $params['brand_id']; } // 产品ID if (!empty($params['product_id'])) { $goodsModel->product_id = $params['product_id']; } // 运费模板 if ($goodsModel->deliver_method == StatusEnum::DISABLED) { $goodsModel->express_info = $this->getExpressTemplate($goods, $config, $params['shop_id']); } // 商品资质图片(最多5张) if (!empty($params['qualifications'])) { $goodsModel->qualifications = $params['qualifications']; } // 限购 if ($goods['buy_num_limit'] > 0) { $limitedInfo = new LimitedInfoModel(); $limitedInfo->limited_buy_num = $goods['buy_num_limit']; // 限购周期类型,0:无限购(默认),1:按自然日限购,2:按自然周限购,3:按自然月限购,4:按自然年限购 $limitedInfo->period_type = 3; // 按年限购 $goodsModel->limited_info = $limitedInfo; } // 售后 $extraServiceModel = new GoodsExtraServiceModel(); // 是否支持七天无理由退货,0-不支持七天无理由, 1-支持七天无理由, 2-支持七天无理由(定制商品除外)。 //管理规则请参见七天无理由退货管理规则。类目是否必须支持七天无理由退货,可参考文档获取类目信息中的字段attr.seven_day_return $extraServiceModel->seven_day_return = $params['seven_day_return'] == StatusEnum::ENABLED ? StatusEnum::ENABLED : StatusEnum::DISABLED; $goodsModel->extra_service = $extraServiceModel; // 最少要一个 if (!empty($goods['attr'])) { $goodsModel->skus = $this->getSkus($goods, $config); } // 判断是否有产品ID,有的话则为更新商品 if (!empty($params['product_id'])) { $req = new GoodsUpdateRequest($config); } else { $req = new GoodsAddRequest($config); } $resp = WechatRequestHelper::accessTokenAndToArray($req, $goodsModel); if (!empty($resp['data']['product_id'])) { // 判断是否为编辑 $model = WechatVideoShopGoods::getOne([ ['product_id' => $params['product_id']] ]); if (empty($model)) { $model = new WechatVideoShopGoods(); } $model->mall_id = $goods['mall_id']; $model->goods_id = $goods['id']; $model->shop_id = $params['shop_id']; $model->product_id = $resp['data']['product_id']; $model->audit_status = StatusEnum::DISABLED; // $model->audit_reason = ''; $model->cate_ids = [ $goodsModel->cats[0]->cat_id, $goodsModel->cats[1]->cat_id, $goodsModel->cats[2]->cat_id, ]; $model->content = $goodsModel->toArray(); $model->attrs = $params['attrs'] ?? []; if (!$model->save()) throw new \Exception($model->getErrorMessage()); } return true; } catch (\Exception $e) { return $e->getMessage(); } } /** * @param int $cat_id * @return array * @throws \Exception */ protected function getCats(int $cat_id) { $cat = WechatVideoShopCate::getOne([ ['id' => $cat_id] ], true, ['parent' => function ($query) { $query->with('parent'); }]); if (empty($cat)) throw new \Exception("类目不存在"); if (empty($cat['parent'])) throw new \Exception("上级类目不存在"); if (empty($cat['parent']['parent'])) throw new \Exception("顶级类目不存在"); $cats = []; $cat1Model = new CatModel(); $cat1Model->cat_id = $cat['parent']['parent']['id']; $cats[] = $cat1Model; $cat2Model = new CatModel(); $cat2Model->cat_id = $cat['parent']['id']; $cats[] = $cat2Model; $cat3Model = new CatModel(); $cat3Model->cat_id = $cat['id']; $cats[] = $cat3Model; return $cats; } /** * @param string $content * @param array $config * @return DescInfoModel * @throws \Exception */ protected function getDescInfo(string $content, array $config) { $text = strip_tags($content); $text = html_entity_decode($text, ENT_HTML5, "UTF-8"); $text = mb_substr($text, 0, strlen($text), "UTF-8"); $descInfoModel = new DescInfoModel(); $descInfoModel->desc = $text; preg_match_all('//i', $content, $images); $pics = $images[1] ?? []; $req_banner_images = []; if (!empty($pics)) { foreach ($pics as $banner_image) { $img_url = $this->uploadImage($banner_image, $config); if (!empty($img_url)) $req_banner_images[] = $img_url; } } $descInfoModel->imgs = $req_banner_images; return $descInfoModel; } /** * @param array $goods * @param array $config * @param int $shop_id * @return ExpressInfoModel * @throws \Exception */ protected function getExpressTemplate(array $goods, array $config, int $shop_id) { $model = new ExpressInfoModel(); // 运费规则 【1运费模板 2自定义运费】 $is_free = StatusEnum::DISABLED; // 如果运费模板ID为0的时候 那么证明就是自定义运费了 if ($goods['freight_rules_type'] == StatusEnum::ENABLED && $goods['freight_id'] > 0) { // 运费模板方式 // 运费模板ID // 取出运费模板ID $model->template_id = WechatVideoShopExpressTemplate::find() ->where(['mall_id' => $goods['mall_id']]) ->andWhere(['shop_id' => $shop_id]) ->andWhere(['template_id' => $goods['freight_id']]) ->andWhere(['status' => StatusEnum::ENABLED]) ->select('third_template_id')->scalar(); } else { // 自定义运费 if ($goods['shipping_fee'] == 0) { // 包邮 // 取出包邮ID $model->template_id = WechatVideoShopExpressTemplate::find() ->where(['mall_id' => $goods['mall_id']]) ->andWhere(['shop_id' => $shop_id]) ->andWhere(['is_free' => StatusEnum::ENABLED]) ->andWhere(['status' => StatusEnum::ENABLED]) ->select('third_template_id')->scalar(); if (empty($model->template_id)) { // 需要创建一个包邮模板 $is_free = StatusEnum::ENABLED; } } if (empty($model->template_id)) { // 通过商品ID能否找出来 $model->template_id = WechatVideoShopExpressTemplate::find() ->where(['mall_id' => $goods['mall_id']]) ->andWhere(['shop_id' => $shop_id]) ->andWhere(['goods_id' => $goods['id']]) ->andWhere(['status' => StatusEnum::ENABLED]) ->select('third_template_id')->scalar(); } } if (empty($model->template_id)) { if ($is_free == StatusEnum::DISABLED && $goods['price'] < 30) { throw new \Exception("售价小于30的商品必须包邮"); } // 需要创建 $expressService = new ExpressTemplateService(); $ret = $expressService->create($goods['mall_id'], $is_free, $goods, $config, $shop_id); if (is_string($ret)) throw new \Exception($ret); $model->template_id = $ret->third_template_id; } // 重量? $model->weight = GoodsAttr::find() ->andWhere(['goods_id' => $goods['id']]) ->andWhere(['status' => StatusEnum::ENABLED]) ->orderBy('weight desc') ->select('weight')->scalar(); if (empty($model->weight)) $model->weight = 10; // 默认10克 $model->weight = (int) $model->weight; return $model; } /** * @param array $data * @return array */ protected function getAttrs(array $data) { $attrs = []; foreach ($data as $key => $value) { $attrItemModel = new SkuAttrItemModel(); $attrItemModel->attr_key = $key; $attrItemModel->attr_value = $value; $attrs[] = $attrItemModel; } return $attrs; } /** * @param array $goods * @param array $config * @return array * @throws \Exception */ protected function getSkus(array $goods, array $config) { $skus = []; foreach ($goods['attr'] as $sku) { $skuModel = new SkuItemModel(); $skuModel->out_sku_id = $sku['id']; if (!empty($sku['pic_url'])) { $sku['pic_url'] = $this->uploadImage($sku['pic_url'], $config); } $skuModel->thumb_img = $sku['pic_url']; $skuModel->sku_code = $sku['goods_no']; $skuModel->sale_price = (float)bcmul($sku['price'], 100); // 售卖价格,以分为单位,不超过1000000000(1000万元) $skuModel->stock_num = (int)$sku['stock']; $skuDeliverInfoModel = new SkuDeliverInfoModel(); $skuDeliverInfoModel->stock_type = StatusEnum::DISABLED; // 0现货,1预售 $skuModel->sku_deliver_info = $skuDeliverInfoModel; // 颜色:绿色,尺码:小 $sku_attrs = []; if (empty($sku['name'])) { $skuAttrItemModel = new SkuAttrItemModel(); $skuAttrItemModel->attr_key = '选择规格'; $skuAttrItemModel->attr_value = '默认规格'; $sku_attrs[] = $skuAttrItemModel; } else { $names = explode(",", $sku['name']); foreach ($names as $name) { $name_arr = explode(":", $name); $skuAttrItemModel = new SkuAttrItemModel(); $skuAttrItemModel->attr_key = "{$name_arr[0]}"; $skuAttrItemModel->attr_value = $name_arr[1]; $sku_attrs[] = $skuAttrItemModel; } } $skuModel->sku_attrs = $sku_attrs; $skus[] = $skuModel; } return $skus; } protected function parseCateAttrs(int $cat_id) { } public function getGoodsList($mall_id, $param) { try { $params['where'] = [ ['mall_id' => $mall_id], ['status' => [StatusEnum::DISABLED, StatusEnum::ENABLED]] ]; if(!empty($param['audit_status'])){ $params['where'][] = ['audit_status' => $param['audit_status']]; } if(!empty($param['shop_id'])){ $params['where'][] = ['shop_id' => $param['shop_id']]; } if (!empty($param['keyword'])) { $gids = Goods::find()->where(['mall_id' => $mall_id]) ->andWhere(['status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]) ->andWhere(['like', 'goods_name', $param['keyword']]) ->select('id')->column(); if (empty($gids)) return []; $params['where'][] = ['goods_id' => $gids]; } $params['limit'] = $param['limit'] ?? 5; $params['page'] = $param['page'] ?? 1; $params['with'] = ['shop', 'goods']; $params['order'] = 'id desc'; $list = WechatVideoShopGoods::listPage($params); if ($list === false) throw new \Exception(WechatVideoShopGoods::getStaticError()); $audit_status = WechatVideoShopGoodsAuditStatusEnums::AUDIT_STATUS_REMARKS; $listing_status = WechatVideoShopGoodsListingStatusEnums::AUDIT_STATUS_REMARKS; $shop_list = WechatVideoShop::lists([ 'where' => [ ['mall_id' => $mall_id], ], 'index' => 'id' ]); foreach ($list['list'] as &$val) { $val['audit_text'] = $audit_status[$val['audit_status']] ?? '未知状态'; $val['listing_text'] = $listing_status[$val['listing_status']] ?? '未知状态'; $itemModel = new GoodsItem(); $itemModel->product_id = $val['product_id']; $itemModel->data_type = 3; $val['third_data'] = WechatRequestHelper::accessTokenAndToArray(new GoodsItemRequest($shop_list[$val['shop_id']]), $itemModel); $val['stock'] = !empty($val['third_data']['edit_product']['skus']) ? array_sum(array_column($val['third_data']['edit_product']['skus'], 'stock_num')) : 0; if (!empty($val['third_data']['product']['skus'])) { $val['stock'] = array_sum(array_column($val['third_data']['product']['skus'], 'stock_num')); } } unset($val); return $list; } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } } /** * 创建商品 * @param int $mall_id * @param array $data * @return string|true * @throws \Exception */ public function create(int $mall_id, array $data) { $qualifications = []; $config = (new ShopService())->getShop($data['shop_id']); if (!empty($data['qualifications'])) { foreach ($data['qualifications'] as $param) { if (strpos($param, 'https://mmecimage.cn/') === 0) { $qualifications[] = $param; } else { // 如果没有文件格式,那么这里会报错,所以必须要一个文件格式? $pic_url = $this->uploadImage($param, $config); if (!empty($pic_url)) $qualifications[] = $pic_url; } } } $attr = !empty($data['attr']) ? $data['attr'] : []; $sale_attr = !empty($data['sale_attr']) ? $data['sale_attr'] : []; $tmp_attrs = array_merge($attr, $sale_attr); // 这里之前需要处理一下数据 foreach ($tmp_attrs as &$t_attr) { switch ($t_attr['ext']['type_v2']) { case 'integer': $t_attr['value'] = intval(!empty($t_attr['value']) ? $t_attr['value'] : 0); break; case 'decimal4': $t_attr['value'] = bcmul(!empty($t_attr['value']) ? $t_attr['value'] : 0, 1, 4); break; case 'integer_unit': $t_attr['value'] = intval(!empty($t_attr['value']) ? $t_attr['value'] : 0) . " {$t_attr['ext']['value']}"; break; case 'decimal4_unit': $t_attr['value'] = bcmul(!empty($t_attr['value']) ? $t_attr['value'] : 0, 1, 4) . " {$t_attr['ext']['value']}"; break; case 'select_many': $t_attr['value'] = !empty($t_attr['value']) && is_array($t_attr['value']) ? implode(";", $t_attr['value']) : ''; break; } } $attrs = array_combine(array_column($tmp_attrs, "name"), array_column($tmp_attrs, "value")); $attrs = array_filter($attrs, function ($val) { return !empty($val); }); // 创建商品 return $this->push($mall_id, [ 'goods_id' => $data['goods_id'], 'attrs' => $attrs, 'cat_id' => $data['cat_id'], 'shop_id' => $data['shop_id'], 'seven_day_return' => $data['seven_day_return'], 'qualifications' => $qualifications, // 资质上传 // 'brand_id' => '' 'product_id'=> $data['product_id'] ?? '' ], $config); } /** * 上传图片 * @param string $url * @param array $config * @return mixed|string * @throws \Exception */ protected function uploadImage(string $url, array $config) { if (empty($url)) return ''; $notPregUrl = '/(?= 2) { $url = preg_replace($notPregUrl, '/', $url); } $uploadModel = new UploadImageModel(); // 如果图片地址没有图片类型的话那么是传不成功的,需要给其设置一个格式,比如 .jpg、.png? if (empty(pathinfo($url, PATHINFO_EXTENSION))) { $url .= '.jpg'; } $uploadModel->img_url = $url; $uploadRequest = new UploadImageRequest($config); $resp = WechatRequestHelper::accessTokenAndToArray($uploadRequest->withQuery([ "upload_type" => StatusEnum::ENABLED, "resp_type" => StatusEnum::ENABLED, ]), $uploadModel); return $resp['pic_file']['img_url'] ?? ''; } /** * @param int $mall_id * @param int $id * @return string|true */ public function delete(int $mall_id, int $id) { $goods = WechatVideoShopGoods::getOne([ ['mall_id' => $mall_id], ['id' => $id], ]); if (empty($goods)) return '商品不存在'; $config = (new ShopService())->getShop($goods->shop_id); $goodsModel = new GoodsDeleteModel(); $goodsModel->product_id = $goods->product_id; try { WechatRequestHelper::accessTokenAndToArray(new GoodsDeleteRequest($config), $goodsModel); $goods->status = StatusEnum::DELETE; if (!$goods->save()) throw new \Exception($goods->getErrorMessage()); } catch (\Exception $e) { return $e->getMessage(); } return true; } /** * @param int $mall_id * @param int $id * @param int $type * @return string|true */ public function onSale(int $mall_id, int $id, int $type) { $goods = WechatVideoShopGoods::getOne([ ['mall_id' => $mall_id], ['id' => $id], ]); if (empty($goods)) return '商品不存在'; $config = (new ShopService())->getShop($goods->shop_id); $goodsModel = new GoodsDeleteModel(); $goodsModel->product_id = $goods->product_id; try { if ($type == StatusEnum::DISABLED) { // 下架 $req = new GoodsDelistingRequest($config); } else { $req = new GoodsListingRequest($config); } WechatRequestHelper::accessTokenAndToArray($req, $goodsModel); } catch (\Exception $e) { return $e->getMessage(); } return true; } /** * 取消审核 * @param int $mall_id * @param int $id * @return string|true */ public function cancelAudit(int $mall_id, int $id) { $goods = WechatVideoShopGoods::getOne([ ['mall_id' => $mall_id], ['id' => $id], ]); if (empty($goods)) return '商品不存在'; $config = (new ShopService())->getShop($goods->shop_id); $goodsModel = new GoodsDeleteModel(); $goodsModel->product_id = $goods->product_id; try { WechatRequestHelper::accessTokenAndToArray(new GoodsAuditCancelRequest($config), $goodsModel); $goods->audit_status = StatusEnum::DISABLED; if (!$goods->save()) throw new \Exception($goods->getErrorMessage()); } catch (\Exception $e) { return $e->getMessage(); } return true; } /** * 更新库存 * @param int $mall_id * @param int $id * @param array $skus * @return string|true */ public function setStock(int $mall_id, int $id, array $skus) { $goods = WechatVideoShopGoods::getOne([ ['mall_id' => $mall_id], ['id' => $id], ]); if (empty($goods)) return '商品不存在'; $config = (new ShopService())->getShop($goods->shop_id); foreach ($skus as $sku) { if ($sku['num'] != $sku['stock_num']) { $stockModel = new UpdateStockModel(); $stockModel->product_id = $goods->product_id; $stockModel->sku_id = $sku['sku_id']; $stockModel->diff_type = 3; // 修改类型。1: 增加;2:减少;3:设置。 $stockModel->num = intval($sku['num']); // 增加、减少或者设置的库存值 try { $resp = WechatRequestHelper::accessTokenAndToArray(new GoodsUpdateStockRequest($config), $stockModel); } catch (\Exception $e) { return $e->getMessage(); } } } return true; } }