Goods.php 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229
  1. <?php
  2. namespace common\models\goods;
  3. use addons\Chanjet\common\global_func\Request;
  4. use addons\Chanjet\common\models\AddonsChanjetRequest;
  5. use common\components\export\CsvTool;
  6. use common\enums\AddonsEnum;
  7. use common\enums\DownloadEnum;
  8. use common\enums\GoodsTypeEnum;
  9. use common\enums\import\GoodsImportEnum;
  10. use common\enums\RabbitMqEnum;
  11. use common\enums\StatusEnum;
  12. use common\enums\WhetherEnum;
  13. use common\events\goods\GoodsEditEvent;
  14. use common\globals\GlobalInvoke;
  15. use common\helpers\ArrayHelper;
  16. use common\helpers\csv\CsvImportForm;
  17. use common\models\common\Download;
  18. use common\models\common\ImportFailLog;
  19. use common\models\common\ImportLog;
  20. use common\models\goods\template\GoodsTemplateRelation;
  21. use common\models\mall\MallAddons;
  22. use common\models\user\User;
  23. use common\models\user\UserLevel;
  24. use Exception;
  25. use Yii;
  26. use yii\db\Expression;
  27. use yii\helpers\Json;
  28. /**
  29. * This is the model class for table "{{%goods}}".
  30. *
  31. * @property int $id
  32. * @property int $mall_id 商城id
  33. * @property int $mch_id 商家id
  34. * @property string $goods_name 商品名称
  35. * @property string $subtitle 商品副标题
  36. * @property float $price 售价
  37. * @property float $original_price 原价(划线价仅展示)
  38. * @property float $cost_price 成本价
  39. * @property string $unit 单位
  40. * @property string $cover_pic 商品缩略图
  41. * @property string $bannar_pic 商品展示bannar图
  42. * @property int $is_on_sale 上架状态【1是 0否】
  43. * @property int $is_attr 是否有规格【1是 0否】
  44. * @property int $status
  45. * @property int $attr_groups_format
  46. * @property int $stock 商品库存
  47. * @property int $stock_warning 商品库存预警值
  48. * @property int $virtual_sales 虚拟销量
  49. * @property int $buy_num_limit 购买数量限制
  50. * @property int $freight_id 运费模板ID
  51. * @property int $free_shipping_num 商品满件包邮
  52. * @property float $free_shipping_money 商品满额包邮
  53. * @property int $goods_type 商品类型
  54. * @property string $detail 商品详情,图文
  55. * @property string $video_url 商品视频
  56. * @property string $video_cover_pic 商品视频封面
  57. * @property string $goods_source 商品来源
  58. * @property int $sort 排序
  59. * @property int $created_at 创建时间
  60. * @property int $updated_at 修改时间
  61. * @property int $check_status 审核状态
  62. * @property int $is_show 是否在主商城显示:1:是;0:否
  63. * @property int $comment_score 是否在主商城显示:1:是;0:否
  64. * @property int $can_feedback 是否在主商城显示:1:是;0:否
  65. * @property string $check_remark 审核备注
  66. * @property int $goods_subtype 商品子类型 0普通商品 1药品商品
  67. */
  68. class Goods extends \common\models\base\BaseActiveRecord
  69. {
  70. /**
  71. * {@inheritdoc}
  72. */
  73. public static function tableName()
  74. {
  75. return '{{%goods}}';
  76. }
  77. /**
  78. * {@inheritdoc}
  79. */
  80. public function rules()
  81. {
  82. return [
  83. [[
  84. 'mall_id', 'mch_id', 'is_on_sale', 'is_attr', 'status', 'stock', 'stock_warning', 'is_show_stock',
  85. 'is_show_sales', 'virtual_sales', 'sales_num', 'buy_num_limit', 'freight_rules_type', 'freight_id',
  86. 'free_shipping_num', 'goods_type', 'sort', 'created_at', 'updated_at', 'is_area_limit', 'check_status',
  87. 'is_show','is_pay_limit', 'can_feedback', 'goods_subtype'
  88. ], 'integer'],
  89. [['price', 'original_price', 'cost_price', 'shipping_fee', 'free_shipping_money', 'comment_score'], 'number'],
  90. [['attr_groups', 'attr_groups_format', 'detail', 'area_limit', 'goods_source', 'goods_no','pay_limit'], 'string'],
  91. [['goods_name'], 'string', 'max' => 255],
  92. [['check_remark'], 'safe'],
  93. [['stock'], 'integer', 'max' => 99999999, 'message' => '库存最大数为99999999'],
  94. [['subtitle', 'unit'], 'safe'],
  95. [['cover_pic', 'freight_type', 'video_url', 'services', 'labels', 'goods_no'], 'string', 'max' => 255],
  96. [['video_cover_pic'], 'string', 'max' => 500],
  97. [['bannar_pic'], 'string', 'max' => 1500000],
  98. ];
  99. }
  100. /**
  101. * {@inheritdoc}
  102. */
  103. public function attributeLabels()
  104. {
  105. return [
  106. 'id' => 'ID',
  107. 'mall_id' => '商城id',
  108. 'mch_id' => '商家id',
  109. 'goods_name' => '商品名称',
  110. 'subtitle' => '商品副标题',
  111. 'price' => '售价',
  112. 'original_price' => '原价(划线价仅展示)',
  113. 'cost_price' => '成本价',
  114. 'unit' => '单位',
  115. 'cover_pic' => '商品缩略图',
  116. 'bannar_pic' => '商品展示bannar图',
  117. 'is_on_sale' => '上架状态【1是 0否】',
  118. 'is_attr' => '是否有规格【1是 0否】',
  119. 'attr_groups' => '规格组',
  120. 'attr_groups_format' => '格式化后的规格组',
  121. 'status' => '状态[1正常 -1删除]',
  122. 'stock' => '商品库存',
  123. 'stock_warning' => '商品库存预警值',
  124. 'is_show_stock' => '是否显示库存',
  125. 'is_show_sales' => '是否显示销量【1是 0否】',
  126. 'virtual_sales' => '虚拟销量',
  127. 'sales_num' => '累计销量',
  128. 'buy_num_limit' => '购买数量限制',
  129. 'freight_type' => '物流方式【1快递发货 2上门自提】逗号分隔',
  130. 'freight_rules_type' => '运费规则 【1运费模板 2自定义运费】',
  131. 'freight_id' => '运费模板ID',
  132. 'shipping_fee' => '自定义运费金额',
  133. 'free_shipping_num' => '商品满件包邮',
  134. 'free_shipping_money' => '商品满额包邮',
  135. 'goods_type' => '商品类型',
  136. 'detail' => '商品详情,图文',
  137. 'is_area_limit' => '是否开启区域限制',
  138. 'area_limit' => '允许购买区域',
  139. 'is_pay_limit'=>'是否开启支付限制',
  140. 'pay_limit'=>'限制的支付方式',
  141. 'video_url' => '商品视频',
  142. 'video_cover_pic' => '商品视频封面',
  143. 'services' => '商品服务',
  144. 'labels' => 'Labels',
  145. 'goods_source' => '商品来源',
  146. 'sort' => '排序',
  147. 'created_at' => '创建时间',
  148. 'updated_at' => '修改时间',
  149. 'check_status' => '审核状态',
  150. 'check_remark' => '审核备注',
  151. 'goods_no' => '货号',
  152. 'is_show' => '是否在主商城显示',
  153. 'goods_subtype' => '商品子类型 0普通商品 1药品商品',
  154. ];
  155. }
  156. /**关联模型 */
  157. // 分类关系
  158. public function getGoodsCateRelate()
  159. {
  160. return $this->hasMany(GoodsCateRelate::class, ['goods_id' => 'id']);
  161. }
  162. // 分类
  163. public function getCats()
  164. {
  165. return $this->hasMany(GoodsCate::class, ['id' => 'cate_id'])
  166. // ->where(['mch_id' => 0])
  167. ->via('goodsCateRelate');
  168. }
  169. // 预约商品信息
  170. public function getReserve()
  171. {
  172. return $this->hasOne(GoodsReserve::class, ['goods_id' => 'id']);
  173. }
  174. // sku
  175. public function getAttr()
  176. {
  177. return $this->hasMany(GoodsAttr::class, ['goods_id' => 'id'])->andWhere(['status' => [StatusEnum::ENABLED]]);
  178. }
  179. // 商品额外设置
  180. public function getExtra()
  181. {
  182. return $this->hasOne(GoodsExtra::class, ['goods_id' => 'id'])->andWhere(['status' => [StatusEnum::ENABLED]]);
  183. }
  184. public function getCollect()
  185. {
  186. return $this->hasOne(GoodsCollect::class, ['goods_id' => 'id'])->andWhere(['status' => [StatusEnum::ENABLED]]);
  187. }
  188. public function getSearchAddress()
  189. {
  190. return $this->hasOne(GoodsSearchAddress::class, ['goods_id' => 'id']);
  191. }
  192. public function getMch()
  193. {
  194. return $this->hasOne(\addons\Mch\common\models\Mch::class, ['id' => 'mch_id'])->select('id,store_name,shop_mobile,logo_img,user_id');
  195. }
  196. /**
  197. * 畅捷通请求
  198. *
  199. * @return \yii\db\ActiveQuery
  200. */
  201. public function getAddonsChanjetRequest()
  202. {
  203. return $this
  204. ->hasOne(AddonsChanjetRequest::class, ['table_id' => 'id'])
  205. ->orderBy('updated_at desc')
  206. ->select('id, table_id, request_status, updated_at')
  207. ->where([
  208. 'table' => self::tableName(),
  209. 'path' => [Request::PATHS['INVENTORY_CREATE_BATCH'], Request::PATHS['INVENTORY_UPDATE_BATCH']]
  210. ])
  211. ->andWhere(['>', 'request_status', 0]);
  212. }
  213. /**
  214. * 保存数据
  215. * @Author bing
  216. * @DateTime 2021-08-24 15:34:48
  217. * @param array $params
  218. * @param string $addons 来自插件
  219. * @param callback $call_back 插件回调函数
  220. * @return object|boolean
  221. * @copyright: Copyright (c) 2020 广东七件事集团
  222. */
  223. public static function setData(array $params)
  224. {
  225. try {
  226. $wait_del_attr_id_arr = [];
  227. if (!empty($params['id'])) {
  228. $model = self::findOne(['mall_id' => $params['mall_id'], 'id' => $params['id'], 'status' => [StatusEnum::ENABLED]]);
  229. if (empty($model)) throw new Exception('商品不存在或已删除');
  230. if ($model['is_on_sale'] != $params['is_on_sale']) $data['is_on_sale'] = $params['is_on_sale'];
  231. } else {
  232. $model = new self();
  233. $model->loadDefaultValues();
  234. if (isset($params['is_on_sale']) && $params['is_on_sale'] == 1) $data['is_on_sale'] = $params['is_on_sale'];
  235. }
  236. $params['can_feedback'] = (int) ($params['can_feedback'] ?? StatusEnum::ENABLED);
  237. isset($params['services']) && $params['services'] = implode(',', $params['services']);
  238. isset($params['labels']) && $params['labels'] = implode(',', $params['labels']);
  239. isset($params['freight_type']) && $params['freight_type'] = implode(',', $params['freight_type']);
  240. isset($params['freight_id']) && empty($params['freight_id']) && $params['freight_id'] = 0;
  241. isset($params['virtual_sales']) && empty($params['virtual_sales']) && $params['virtual_sales'] = 0;
  242. if (isset($params['is_attr']) && $params['is_attr'] == 1) {
  243. if (isset($params['attr_groups']) && !empty($params['attr_groups'])) {
  244. $attr_groups = $params['attr_groups'];
  245. $params['attr_groups'] = json_encode($params['attr_groups'], JSON_UNESCAPED_UNICODE);
  246. $params['attr_groups_format'] = json_encode(self::formatGoodsAttrGroup($attr_groups, JSON_UNESCAPED_UNICODE));
  247. }
  248. } else {
  249. $params['attr_groups'] = '';
  250. $params['attr_groups_format'] = '';
  251. }
  252. isset($params['bannar_pic']) && $params['bannar_pic'] = json_encode($params['bannar_pic'], JSON_UNESCAPED_UNICODE);
  253. isset($params['area_limit']) && $params['area_limit'] = json_encode($params['area_limit'], JSON_UNESCAPED_UNICODE);
  254. if (!empty($params['is_area_limit']) && !empty($params['area_limit']) && strlen($params['area_limit']) > 5000) {
  255. // throw new \Exception("地区所有字节数总和超过5000字了");
  256. }
  257. isset($params['pay_limit']) && $params['pay_limit'] = json_encode($params['pay_limit'], JSON_UNESCAPED_UNICODE);
  258. $trans = Yii::$app->db->beginTransaction();
  259. // 分类判断当条件不满足时不导入商品
  260. if (isset($params['cats']) && !empty($params['cats'])) {
  261. $params['cats'] = self::goodsCategorizeValidate($params);
  262. }
  263. if (!$model->load($params, '') || !$model->save()) {
  264. //添加商品统计
  265. if (empty($params['id'])) {
  266. Yii::$app->services->statistics->global->set($params['mall_id'], ['goods_num' => 1]);
  267. }
  268. throw new Exception($model->getErrorMessage());
  269. }
  270. //商品分类处理
  271. if (isset($params['cats']) && !empty($params['cats'])) {
  272. $res = GoodsCateRelate::setData($model->id, $params['cats']);
  273. if ($res === false) throw new Exception('分类处理失败:' . GoodsCateRelate::getStaticError());
  274. }
  275. if (isset($params['goods_template_id']) && !empty($params['goods_template_id'])) {
  276. $res = GoodsTemplateRelation::setData(['mall_id' => $params['mall_id'], 'template_id' => $params['goods_template_id'], 'goods_id' => $model->id, 'status' => StatusEnum::ENABLED]);
  277. if ($res === false) throw new Exception('广告模板处理失败:' . GoodsTemplateRelation::getStaticError());
  278. }
  279. $goods = ArrayHelper::toArray($model);
  280. if ($goods['goods_type'] === GoodsTypeEnum::GoodsTypeReserved) {
  281. // 预约商品
  282. $reserve = $params['reserve'];
  283. $res = GoodsReserve::setData($goods, $reserve);
  284. if ($res === false) throw new Exception('预约商品SKU处理失败:' . GoodsAttr::getStaticError());
  285. } else {
  286. // 商品规格处理
  287. if ($model->is_attr == 1) {
  288. $attrs = $params['attr'] ?? [];
  289. $res = GoodsAttr::setData($model->id, $attrs, $attr_groups, $wait_del_attr_id_arr);
  290. if ($res === false) throw new Exception('规格处理失败:' . GoodsAttr::getStaticError());
  291. } else {
  292. //无规格商品处理
  293. $goods['pic_url'] = $goods['cover_pic'];
  294. $goods['goods_no'] = isset($params['goods_no']) ? $params['goods_no'] : '';
  295. $goods['weight'] = $params['goods_weight'] ?? 0;
  296. $res = GoodsAttr::setAttr($model->id, $goods);
  297. if ($res === false) throw new Exception('设置货品失败:' . GoodsAttr::getStaticError());
  298. }
  299. }
  300. // 商品额外信息处理
  301. if (isset($params['extra']) && !empty($params['extra'])) {
  302. //检测商品限购设置
  303. $member_buy_limit_setting = $params['extra']['member_buy_limit_setting'] ?? [];
  304. if ($member_buy_limit_setting) {
  305. if ($member_buy_limit_setting['nums'] > 0 && $member_buy_limit_setting['every_num'] > 0 && $member_buy_limit_setting['nums'] >= $member_buy_limit_setting['every_num']) {
  306. throw new Exception('商品额外信息每次限购数量需大于起购数量');
  307. }
  308. }
  309. $params['extra']['mall_id'] = $params['mall_id'];
  310. $res = GoodsExtra::setData($model->id, $params['extra']);
  311. if ($res === false) throw new Exception('商品额外信息处理失败:' . GoodsExtra::getStaticError());
  312. }
  313. // 营销插件配置保存
  314. if (!empty($params['addons'])) {
  315. GlobalInvoke::exec(GlobalInvoke::GOODS_DETAIL_EDIT, $params['mall_id'], [
  316. 'type' => 'save',
  317. 'mall_id' => $params['mall_id'],
  318. 'goods_id' => $model->id,
  319. 'setting' => $params['addons'],
  320. ]);
  321. // $res = CouponGiveItemSetting::setGoodsSetting($params['mall_id'], $model->id, $params['coupon_selection']);
  322. // if ($res === false) throw new Exception('商品优惠券处理失败:' . CouponGiveItemSetting::getStaticError());
  323. }
  324. // 商品营销+用户设置处理
  325. // if (isset($params['goods_setting']) && !empty($params['goods_setting'])) {
  326. // $goodsId = $model->id ?? 0;
  327. // $mchId = $model->mch_id ?? 0;
  328. // $res = GoodsExtra::setData($params['goods_setting'], $params['mall_id'], $mchId, $goodsId);
  329. // if ($res === false) throw new Exception('营销/用户处理失败:' . GoodsExtra::getStaticError());
  330. // }
  331. if(!empty($params['searchAddress'])){
  332. $address_search = array_merge($params['searchAddress'], ['goods_id' => $model->id, 'mall_id' => $params['mall_id']]);
  333. if(GoodsSearchAddress::setData($address_search) === false) throw new Exception(GoodsSearchAddress::getStaticError());
  334. }
  335. $res = GoodsAssociation::setData($model->id, $params);
  336. $trans->commit();
  337. //编辑商品触发
  338. $event = new GoodsEditEvent($params['mall_id']);
  339. $data['goods_id'] = $model->id;
  340. $data['mall_id'] = $params['mall_id'];
  341. $data['wait_del_attr_id_arr'] = $wait_del_attr_id_arr;
  342. $data['copy_id'] = $params['copy_id'];
  343. Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  344. return $model;
  345. } catch (Exception $e) {
  346. if (isset($trans)) $trans->rollback();
  347. self::$static_error = $e->getMessage();
  348. return false;
  349. }
  350. }
  351. /**
  352. * 商品分类过滤验证
  353. * @param array $params
  354. * @return array
  355. * @throws Exception
  356. */
  357. public static function goodsCategorizeValidate(array $params): array
  358. {
  359. // 获取商品分类ID合集
  360. $caseIds = GoodsCate::find()->where(['mall_id' => $params['mall_id'], 'status' => StatusEnum::ENABLED])
  361. ->select(['id'])->asArray()->all();
  362. $caseIds = array_column($caseIds, "id");
  363. // 判断输入ID是否是数字
  364. $is_numeric = array_filter($params['cats'], function ($val) {
  365. return !is_numeric($val);
  366. });
  367. if (!empty($is_numeric)) throw new Exception('商品分类必须为数字,不能包含其它字符');
  368. // 判断输入ID 是否在商品分类ID中 获取符合的ID
  369. $caseIds = array_filter($params['cats'], function ($val) use ($caseIds) {
  370. return in_array($val, $caseIds);
  371. });
  372. if (empty($caseIds)) throw new Exception('商品分类不存在或被删除');
  373. return $caseIds;
  374. }
  375. /**
  376. * 格式化商品规格组数据
  377. * @Author bing
  378. * @DateTime 2021-09-03 11:06:31
  379. * @param array $attr_group 规格组
  380. * @return array
  381. * @copyright: Copyright (c) 2020 广东七件事集团
  382. */
  383. public static function formatGoodsAttrGroup(array $attr_group)
  384. {
  385. $format_attr_group = [];
  386. $group_attr_ids = [];
  387. $all_attr_groups = [];
  388. $group_attr_list = array_column($attr_group, 'attr_list', 'attr_id');
  389. //按规格项生成全部规格组
  390. foreach ($group_attr_list as $key => $group_attrs) {
  391. array_push($group_attr_ids, array_column($group_attrs, 'attr_id'));
  392. $attr_group_id = $attr_group[$key]['attr_group_id'];
  393. $attr_group_name = $attr_group[$key]['attr_group_name'];
  394. foreach ($group_attrs as $k => &$group_attr) {
  395. $group_attr['attr_group_id'] = $attr_group_id;
  396. $group_attr['attr_group_name'] = $attr_group_name;
  397. }
  398. $all_attr_groups = array_merge($all_attr_groups, $group_attrs);
  399. }
  400. $all_attr_groups = array_column($all_attr_groups, null, 'attr_id');
  401. // 规格组之间的笛卡尔集组合
  402. $attr_dika = ArrayHelper::combineDika(array_values($group_attr_ids));
  403. // 拼装以规格id列表为key的规格组格式,格式为attr_id1:attr_id2 => [attr_id1的规格组信息, attr_id的规格组信息]
  404. foreach ($attr_dika as $key => $attr_id_arr) {
  405. asort($attr_id_arr);
  406. $sign_id = implode(':', $attr_id_arr);
  407. $format_attr_group[$sign_id] = array_map(function ($id) use ($all_attr_groups) {
  408. return $all_attr_groups[$id];
  409. }, $attr_id_arr);
  410. }
  411. return $format_attr_group;
  412. }
  413. /**
  414. * 商品详情
  415. * @param $params
  416. * @return mixed|void
  417. */
  418. public static function getGoodsDetail($params)
  419. {
  420. $goods = Goods::getOne($params['where'], $params['is_array'], $params['with'], $params['order'], $params['select']);
  421. if (empty($goods)) return $goods;
  422. $goods['is_member_price'] = '0';
  423. $goods['price_type'] = '新品';
  424. $goods['sales_num'] += $goods['virtual_sales'];
  425. $sku_arr = $attr_name_arr = [];
  426. unset($goods['virtual_sales']);
  427. $goods_services_ids = explode(',', $goods['services']);
  428. $service_list = GoodsService::findAll([
  429. 'mall_id' => $params['mall_id'],
  430. 'id' => $goods_services_ids,
  431. 'status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]
  432. ]);
  433. $service_arr = [];
  434. foreach ($service_list as $v) {
  435. $arr = explode('|', $v['name']);
  436. array_push($service_arr, ...$arr);
  437. }
  438. $goods['service_list'] = $service_arr;
  439. return $goods;
  440. }
  441. //商品导出表头标题
  442. public static function fieldsList($field = null)
  443. {
  444. $fields = [
  445. 'id' => '商品ID',
  446. 'goods_name' => '商品名',
  447. 'price' => '售价',
  448. 'cost_price' => '成本价',
  449. 'goods_no' => '商品货号',
  450. 'original_price' => '划线价',
  451. 'sales_num' => '销量',
  452. 'virtual_sales' => '虚拟销量',
  453. 'is_on_sale' => '状态',
  454. 'created_at' => '商品添加时间',
  455. 'unit' => '商品单位',
  456. 'stock' => '库存',
  457. 'cats' => '商品分类',
  458. 'attr_groups' => '规格',
  459. ];
  460. if ($field === null) return $fields;
  461. $temp = [];
  462. foreach ($field as $val) {
  463. if (isset($fields[$val])) $temp[$val] = $fields[$val];
  464. }
  465. return $temp;
  466. }
  467. //商品导出整理数据
  468. public static function exportHandle(array $data)
  469. {
  470. ini_set('memory_limit','512M');
  471. $download_id = $data['download_id'] ?? 0;
  472. $download = Download::findOne(['id' => $download_id]);
  473. if ($download) {
  474. try {
  475. $params = json_decode($download->params, true);
  476. $filename = $download->name;
  477. $type = $download->type;
  478. $goods_detail_model = Goods::find();
  479. $goods_detail_model->where(['mall_id' => $params['mall_id']]);
  480. $goods_detail_model->andWhere(['>', 'status', StatusEnum::DISABLED]);
  481. $goods_detail_model->andWhere(
  482. [
  483. 'or',
  484. ['!=', 'g.goods_source', 'Aiyunuo'],
  485. ['is', 'g.goods_source', new \yii\db\Expression('null')]
  486. ]
  487. );
  488. !empty($params['start']) && $goods_detail_model->andWhere(['>=', 'g.created_at', strtotime($params['start'])]);
  489. !empty($params['end']) && $goods_detail_model->andWhere(['<=', 'g.created_at', strtotime($params['end'])]);
  490. !empty($params['goods_name']) && $goods_detail_model->andWhere(['like', 'g.goods_name', '%' . trim($params['goods_name']) . '%', false]);
  491. // !empty($params['cats']) && $goods_detail_model->andWhere(['in', 'gcr.cate_id', $params['cats']]);
  492. if(!empty($params['cats'])){
  493. $goods_cate_relate_list = GoodsCateRelate::lists(['where'=>[['cate_id'=>$params['cats']]]]);
  494. $goods_ids = array_column($goods_cate_relate_list,'goods_id');
  495. $goods_detail_model->andWhere(['in', 'g.id', $goods_ids]);
  496. }
  497. !empty($params['goods_id']) && $goods_detail_model->andWhere(['=', 'g.id', $params['goods_id']]);
  498. !empty($params['choose_list']) && $goods_detail_model->andWhere(['in', 'g.id', $params['choose_list']]);
  499. !empty($params['goods_type']) && $goods_detail_model->andWhere(['=', 'g.goods_type', $params['goods_type']]);
  500. if (!empty($params['type'])) {
  501. switch ($params['type']) {
  502. case 'onsale':
  503. $goods_detail_model->andWhere(['=', 'g.is_on_sale', WhetherEnum::ENABLED]);
  504. break;
  505. case 'offline':
  506. $goods_detail_model->andWhere(['=', 'g.is_on_sale', WhetherEnum::DISABLED]);
  507. break;
  508. case 'sell-out':
  509. $goods_detail_model->andWhere(['=', 'g.stock', 0]);
  510. break;
  511. case 'stock-lack':
  512. $goods_detail_model->andWhere(new Expression('g.stock <= g.stock_warning'));
  513. break;
  514. }
  515. }
  516. //连表
  517. // $goods_detail_model->join('LEFT JOIN', '{{%goods_cate_relate}} gcr', 'gcr.goods_id=g.id');
  518. $goods_detail_model->alias('g');
  519. $goods_detail_model->with(['cats']);
  520. $goods_detail_model->orderBy('g.id desc');
  521. $fields = $params['fields'];
  522. $fields_arr = self::fieldsList(); //var_dump($fields);
  523. $batchSize = 5000; // 设置订单批量导出数量
  524. $i = 0; //循环次数
  525. $goodsCount = $goods_detail_model->count(); // 查询商品总条数
  526. $is_goods_page = $goodsCount > $batchSize; // 判断是否需要分页
  527. foreach ($goods_detail_model->asArray()->batch($batchSize) as $goods_details) {
  528. $goods_groups_max = 0;
  529. $goods_collective_list = [];
  530. foreach ($goods_details as $goods_detail) {
  531. $processed_goods_detail = self::processGoodsDetail($goods_detail);
  532. $goods_collective_list[] = $processed_goods_detail;
  533. $goods_groups_max = max($goods_groups_max, $processed_goods_detail['goods_group_num']);
  534. }
  535. // 处理商品规格
  536. $goods_export_list = [];
  537. foreach ($goods_collective_list as $goods_detail) {
  538. $goods_item = [];
  539. foreach ($fields as $field) {
  540. if ($field == 'attr_groups') {
  541. // 以最大的规格数量为标准,不足的规格数量用空格补齐
  542. foreach (range(1, $goods_groups_max) as $index) {
  543. $goods_item[] = $goods_detail['attr_groups'][$index - 1] ?? '';
  544. }
  545. } else {
  546. $goods_item[] = $goods_detail[$field] ?? '';
  547. }
  548. }
  549. $goods_export_list[] = $goods_item;
  550. }
  551. if (!empty($goods_export_list)) {
  552. $csv = new CsvTool();
  553. $csv->filename = $is_goods_page ? $filename . "_1" : $filename;;
  554. $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($type);
  555. $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC;
  556. $have_select_field_arr = [];
  557. foreach ($fields as $value) {
  558. if ($value == 'attr_groups' && $goods_groups_max > 0) {
  559. foreach (range(1, $goods_groups_max) as $index) {
  560. $have_select_field_arr[] = '规格' . $index;
  561. }
  562. } else {
  563. $have_select_field_arr[] = $fields_arr[$value];
  564. }
  565. }
  566. $csv->header = $have_select_field_arr;
  567. $csv->data = $goods_export_list;
  568. if ($is_goods_page && $i == 0) { // 因为订单页需要分页,所以需要设置文件名
  569. $download->name = $filename . '_1';
  570. $download->save();
  571. }
  572. if ($i >= 1) {
  573. $newFileName = $filename . '_' . ($i + 1);
  574. $csv->filename = $newFileName;
  575. $downloadData = array(
  576. 'mall_id' => $download->mall_id,
  577. 'mch_id' => $download->mch_id,
  578. 'store_id' => $download->store_id,
  579. 'name' => $newFileName,
  580. 'type' => $download->type,
  581. 'handler_class' => $download->handler_class,
  582. 'method' => $download->method,
  583. 'params' => $download->params,
  584. 'data' => $goods_export_list
  585. );
  586. $downloadModel = Download::setData($downloadData);
  587. if ($downloadModel === false) {
  588. Yii::$app->custom->logs('创建下载任务失败:' . $downloadData['name'], Download::getStaticError());
  589. throw new Exception('创建新下载任务失败');
  590. };
  591. $download = $downloadModel;
  592. }
  593. $csv->export();
  594. $csv->updateDown($download, $params['mall_id']);
  595. $i++;
  596. }
  597. unset($goods_export_list);
  598. }
  599. // 构建字段列表
  600. return true;
  601. } catch (\Exception $e) {
  602. $download->status = 3;
  603. $res = $download->save();
  604. self::setStaticError($e->getMessage());
  605. return $e->getMessage();
  606. }
  607. }
  608. }
  609. public static function processGoodsDetail(array $goods_detail = [])
  610. {
  611. if ($goods_detail['is_attr'] == 1 && !empty($goods_detail['attr_groups'])) {
  612. $attr = json_decode($goods_detail['attr_groups'], true);
  613. if ($attr === null) {
  614. // 如果解码失败,返回默认值
  615. $attr_arr = [];
  616. } else {
  617. $attr_arr = array_map(function ($item) {
  618. $item_attr_names = array_map(function ($item) {
  619. return htmlspecialchars($item['attr_name']); // 防止 XSS 攻击
  620. }, $item['attr_list']);
  621. return htmlspecialchars($item['attr_group_name']) . ':' . implode('、', $item_attr_names);
  622. }, $attr);
  623. }
  624. $goods_detail['goods_group_num'] = count($attr_arr);
  625. $goods_detail['attr_groups'] = $attr_arr;
  626. } else {
  627. $goods_detail['attr_groups'] = '-';
  628. $goods_detail['goods_group_num'] = 0;
  629. }
  630. if (!empty($goods_detail['cats'])) {
  631. $goods_detail['cats'] = implode('|', array_column($goods_detail['cats'], 'name')) ?? '';
  632. }
  633. if (!empty($goods_detail['created_at'])) {
  634. $goods_detail['created_at'] = Yii::$app->formatter->asDatetime($goods_detail['created_at'], 'Y-m-d H:i:s') ?? '';
  635. }
  636. if (in_array($goods_detail['is_on_sale'], [0, 1])) {
  637. $goods_detail['is_on_sale'] = $goods_detail['is_on_sale'] == 1 ? '上架' : '下架';
  638. }
  639. return $goods_detail;
  640. }
  641. /**
  642. * @return array
  643. */
  644. public static function goodsSearchFromAddons($field, $value, $mall_id)
  645. {
  646. $model = self::find()
  647. ->select(['id', 'goods_name', 'cover_pic', 'original_price', 'price'])
  648. ->where(['=', 'status', StatusEnum::ENABLED])
  649. ->andWhere(['=', 'mall_id', $mall_id])
  650. ->andWhere(['>', 'stock', 1]);
  651. if ($field == 'goods_name') $model->andWhere(['like', 'goods_name', $value]);
  652. $goodsLists = [];
  653. foreach ($model->batch(500) as $goodsList) {
  654. $goodsLists = array_merge($goodsLists, $goodsList);
  655. }
  656. return $goodsLists;
  657. }
  658. public static function getGoodsList($params, $is_page = true, $count = false, $is_array = true, $is_get_query = false)
  659. {
  660. if ($is_page) return Goods::listPage($params, $count, $is_array, $is_get_query);
  661. return Goods::lists($params);
  662. }
  663. /**
  664. * 商品处理
  665. * @Author: lun
  666. * @DateTime: 2022/1/19 0019 18:06
  667. * @Copyright: copyright (c) 2021 广东七件事集团
  668. * @param $params
  669. */
  670. public static function goodsHandle($goods_id, $params)
  671. {
  672. try {
  673. $model = self::findOne(['id' => $goods_id, 'mall_id' => $params['mall_id'], 'status' => [StatusEnum::ENABLED]]);
  674. if (empty($model)) throw new Exception('商品不存在或已删除');
  675. if (!$model->load($params, '') || !$model->save()) throw new \yii\db\Exception($model->getErrorMessage());
  676. //判断是否安装云盟店插件
  677. if(MallAddons::isInstall($params['mall_id'],'Yunmeng') && $model->goods_type == 1){
  678. //判断云盟店插件是否已存在该商品数据
  679. $yunmeng_goods = \addons\Yunmeng\common\models\YunmengGoods::findOne(['mall_id' => $params['mall_id'], 'status'=>StatusEnum::ENABLED, 'goods_id'=>$goods_id]);
  680. if (!empty($yunmeng_goods)) {
  681. //修改云盟店商品状态
  682. \addons\Yunmeng\common\models\YunmengGoods::updateAll(['is_on_sale' => 0], ['mall_id' => $params['mall_id'], 'goods_id' => $goods_id]);
  683. \addons\Yunmeng\common\models\YunmengStoreGoods::updateAll(['is_on_sale' => 0], ['mall_id' => $params['mall_id'], 'goods_id' => $goods_id]);
  684. //下架门店商品规格
  685. \addons\Yunmeng\common\models\YunmengStoreGoodsAttr::updateAll(['is_on_sale' => 0], ['mall_id' => $params['mall_id'], 'goods_id' => $yunmeng_goods['goods_id']]);
  686. }
  687. }
  688. return true;
  689. } catch (Exception $e) {
  690. self::setStaticError($e->getMessage());
  691. return false;
  692. }
  693. }
  694. //计算用户价
  695. public static function computeMemberPrice($price, $sign_id, $user_level, $is_attr, $is_member_price, $member_price_setting, $discount)
  696. {
  697. $after_price = $price;
  698. switch ($is_member_price) {
  699. case 0:
  700. $after_price = bcdiv($price * $discount, 10, 2);
  701. break;
  702. case 1:
  703. //单独设置用户价
  704. if (!empty($is_attr)) {
  705. //多规格
  706. if (isset($member_price_setting['level' . $user_level][$sign_id])) $member_price_discount = $member_price_setting['level' . $user_level][$sign_id];
  707. } else {
  708. //默认规格
  709. if (isset($member_price_setting[$user_level])) $member_price_discount = $member_price_setting[$user_level];
  710. }
  711. if (!empty($member_price_discount)) {
  712. // $after_price = 0;
  713. if ($price >= $member_price_discount) $after_price = bcmul($member_price_discount, 1, 2);
  714. }
  715. break;
  716. }
  717. return $after_price;
  718. }
  719. /**
  720. * 批量导入会员
  721. * @param $params
  722. * @return array|bool
  723. */
  724. public static function importGoods($params)
  725. {
  726. $t = Yii::$app->db->beginTransaction();
  727. try {
  728. $csvImport = new CsvImportForm();
  729. $data = $csvImport->setDataForm($params)
  730. ->setMaxCount(GoodsImportEnum::MAX_COUNT)
  731. ->setImportFieldConfig(GoodsImportEnum::HEAD_LIST)
  732. ->setColumnNum(GoodsImportEnum::COLUMN_NUM)
  733. ->readCsvData();
  734. if (!$data) throw new \Exception(CsvImportForm::getStaticError());
  735. $temp_data = array_column($data, null, 'serial_number');
  736. if (count($temp_data) != count($data)) throw new \Exception('存在重复序号');
  737. unset($temp_data);
  738. $result = self::handleImportData($params, $data, 1);
  739. $t->commit();
  740. return ['counts' => $result['counts'], 'success_counts' => $result['success_counts'], 'fail_counts' => $result['fail_counts']];
  741. } catch (\Exception $e) {
  742. self::$static_error = $e->getMessage();
  743. $t->rollBack();
  744. return false;
  745. }
  746. }
  747. public static function importGoodsSync($params)
  748. {
  749. $t = Yii::$app->db->beginTransaction();
  750. try {
  751. $basePath = \Yii::$app->basePath;
  752. if (empty($_FILES) || !isset($_FILES['file'])) throw new \Exception('请上传csv文件');
  753. $fileName = $_FILES['file']['name'];
  754. $tmpName = $_FILES['file']['tmp_name'];
  755. $basePath = $basePath . '/web/uploads/goods/';
  756. if (!is_dir($basePath)) mkdir($basePath, 0775, true);
  757. $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
  758. if ($ext != 'csv') throw new \Exception('请上传csv文件');
  759. $uploadFile = $basePath . 'ADMIN-' . date('Y-m-d His') . '.' . $ext;
  760. //上传
  761. move_uploaded_file($tmpName, $uploadFile);
  762. $success_counts = $fail_counts = 0;
  763. $counts = 0;
  764. $param['counts'] = $counts;
  765. $param['success_counts'] = $success_counts;
  766. $param['fail_counts'] = $fail_counts;
  767. $param['upload_file'] = $uploadFile;
  768. $param['mall_id'] = $params['mall_id'];
  769. $param['source'] = self::class;
  770. $import_log_model = ImportLog::setData($param);
  771. if (!$import_log_model) throw new \Exception(ImportLog::getStaticError());
  772. $t->commit();
  773. $params = ['id' => $import_log_model['id']];
  774. Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_TASK_IMPORT_USER, RabbitMqEnum::IMPORT_USER, $params, self::class, 'syncImportGoods');
  775. return ['counts' => $counts, 'success_counts' => $success_counts, 'fail_counts' => $fail_counts];
  776. } catch (\Exception $e) {
  777. self::$static_error = $e->getMessage();
  778. $t->rollBack();
  779. return false;
  780. }
  781. }
  782. public static function handleImportData($params, $data, $sync = 1)
  783. {
  784. $success_counts = $fail_counts = 0;
  785. $counts = count($data);
  786. $fail_arr = [];
  787. $temp_data = [];
  788. $attr_suffix_arr = ['a', 'b', 'c', 'd', 'e'];
  789. $bannar_pic_suffix_arr = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
  790. $goods_detail_suffix_arr = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i', 'j'];
  791. // 方便记录错误
  792. $data = array_column($data, null, 'serial_number');
  793. foreach ($data as $val) {
  794. $val['data_row_id'] = $val['serial_number'];
  795. $val['mall_id'] = $params['mall_id'];
  796. if (empty($val['serial_number'])) {
  797. $val['reason'] = '序号为空';
  798. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  799. continue;
  800. }
  801. if (empty($val['goods_type'])) {
  802. $val['reason'] = '商品类型为空';
  803. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  804. continue;
  805. }
  806. if (empty($val['goods_no'])) {
  807. $val['reason'] = '商品编号为空';
  808. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  809. continue;
  810. }
  811. if (empty($val['goods_name'])) {
  812. $val['reason'] = '商品名称为空';
  813. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  814. continue;
  815. }
  816. if (empty($val['cate'])) {
  817. $val['reason'] = '商品分类为空';
  818. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  819. continue;
  820. }
  821. if (empty($val['price'])) {
  822. $val['reason'] = '商品售价为空';
  823. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  824. continue;
  825. }
  826. if (empty($val['stock'])) {
  827. $val['reason'] = '商品库存为空';
  828. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  829. continue;
  830. }
  831. if (empty($val['cover_pic'])) {
  832. $val['reason'] = '商品主图为空';
  833. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  834. continue;
  835. }
  836. if (!isset($temp_data[$val['goods_no']])) {
  837. $temp_data[$val['goods_no']] = [
  838. // 存储每一行的数据
  839. 'list' => [],
  840. // 存储规格名称与规格值
  841. 'attr_groups' => [],
  842. // 第一个规格名的规格值图片
  843. 'attr_value_pic' => [],
  844. ];
  845. }
  846. $name = '';
  847. $temp_attr_list = [];
  848. $exist_attr = 0;
  849. // 处理商品规格
  850. foreach ($attr_suffix_arr as $v) {
  851. $attr_column_name = 'attr_name_' . $v;
  852. $attr_column_value = 'attr_value_' . $v;
  853. if (!isset($val[$attr_column_name])) continue;
  854. if (empty($val[$attr_column_name])) continue;
  855. if (!isset($val[$attr_column_value])) continue;
  856. if (empty($val[$attr_column_value])) continue;
  857. if (!isset($attr_column_name, $temp_data[$val['goods_no']]['attr_groups'])) {
  858. $temp_data[$val['goods_no']]['attr_groups'][$val[$attr_column_name]] = [];
  859. }
  860. $temp_data[$val['goods_no']]['attr_groups'][$val[$attr_column_name]][] = $val[$attr_column_value];
  861. $temp_data[$val['goods_no']]['attr_groups'][$val[$attr_column_name]] = array_unique($temp_data[$val['goods_no']]['attr_groups'][$val[$attr_column_name]]);
  862. if ($v == 'a' && !empty($val['pic_url'])) {
  863. $temp_data[$val['goods_no']]['attr_value_pic'][$val[$attr_column_value]] = $val['pic_url'];
  864. }
  865. $name .= $val[$attr_column_name] . ':' . $val[$attr_column_value] . ':';
  866. $temp_attr_list[] = [
  867. 'attr_id' => 0,
  868. 'attr_name' => $val[$attr_column_value],
  869. 'attr_group_id' => 0,
  870. 'attr_group_name' => $val[$attr_column_name],
  871. 'pic_url' => ''
  872. ];
  873. $exist_attr++;
  874. }
  875. if ($exist_attr <= 0) {
  876. $val['reason'] = '商品规格全为空';
  877. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($val, $fail_arr, $fail_counts);
  878. unset($temp_data[$val['goods_no']]);
  879. continue;
  880. }
  881. $val['name'] = trim($name, ':');
  882. $val['temp_attr_list'] = $temp_attr_list;
  883. // 处理商品轮播图
  884. foreach ($bannar_pic_suffix_arr as $k => $bannar_item) {
  885. $bannar_pic_column = 'bannar_pic_' . $bannar_item;
  886. if (!isset($val[$bannar_pic_column])) continue;
  887. if (empty($val[$bannar_pic_column])) continue;
  888. $val['bannar_pic'][] = ['id' => $k, 'pic_url' => $val[$bannar_pic_column]];
  889. }
  890. // 处理商品详情图片
  891. $goods_detail_str = '<p>';
  892. foreach ($goods_detail_suffix_arr as $detail_item) {
  893. $goods_detail_column = 'goods_detail_' . $detail_item;
  894. if (!isset($val[$goods_detail_column])) continue;
  895. if (empty($val[$goods_detail_column])) continue;
  896. $goods_detail_str .= '<img src="' . $val[$goods_detail_column] . '" />';
  897. }
  898. $goods_detail_str .= '</p>';
  899. $val['goods_detail'] = $goods_detail_str;
  900. $val['cats'] = explode(',', $val['cate']);
  901. $temp_data[$val['goods_no']]['list'][] = $val;
  902. }
  903. $extra = [
  904. 'full_reduce_price_limit' => 0,
  905. 'full_reduce_money' => 0,
  906. 'is_member_price' => 0,
  907. 'is_show_price' => 0,
  908. 'member_price_switch' => 0,
  909. 'score_give_setting' => [
  910. 'code' => 'score',
  911. 'name' => '积分',
  912. 'give_num' => 0,
  913. 'give_type' => 1,
  914. ],
  915. 'member_buy_limit_setting' => [
  916. 'member_level' => [],
  917. 'every_num' => 0,
  918. 'every_day_num' => 0,
  919. 'every_week_num' => 0,
  920. 'every_month_num' => 0,
  921. 'nums' => 0,
  922. 'total_num' => 0,
  923. ],
  924. 'currency_deduct_setting' => [],
  925. 'score_deduct_setting' => [
  926. 'code'=>'score',
  927. 'name'=> '积分',
  928. 'is_enable'=> 0,
  929. 'deduct_num'=> 0,
  930. 'deduct_type' => 1,
  931. ],
  932. ];
  933. foreach ($temp_data as $goods_no => $list) {
  934. $sku = $list['list'];
  935. $attr_groups = $list['attr_groups'];
  936. $attr_value_pic = $list['attr_value_pic'];
  937. $is_attr = count($sku) > 1 ? 1 : 0;
  938. $goods_type_res = array_search($sku[0]['goods_type'], GoodsImportEnum::GOODS_TYPE);
  939. $temp = [
  940. 'goods_name' => $sku[0]['goods_name'],
  941. 'subtitle' => $sku[0]['subtitle'] ?? '',
  942. 'cats' => $sku[0]['cats'],
  943. 'is_attr' => $is_attr,
  944. 'price' => !empty($sku[0]['price']) ? $sku[0]['price'] : 0,
  945. 'cost_price' => !empty($sku[0]['cost_price']) ? $sku[0]['cost_price'] : 0,
  946. 'original_price' => !empty($sku[0]['original_price']) ? $sku[0]['original_price'] : 0,
  947. 'sales_num' => !empty($sku['sales_num']) ? $sku['sales_num'] : 0,
  948. 'virtual_sales' => !empty($sku[0]['virtual_sales']) ? $sku[0]['virtual_sales'] : 0,
  949. 'unit' => $sku[0]['unit'] ?? '',
  950. 'stock' => !empty($sku[0]['stock']) ? $sku[0]['stock'] : 0,
  951. 'cover_pic' => $sku[0]['cover_pic'] ?? '',
  952. 'bannar_pic' => $sku[0]['bannar_pic'] ? $sku[0]['bannar_pic'] : [],
  953. 'freight_type' => ['1'],
  954. 'freight_rules_type' => 2,
  955. 'freight_id' => 3,
  956. 'shipping_fee' => 0,
  957. 'free_shipping_num' => 0,
  958. 'free_shipping_money' => 0,
  959. 'is_on_sale' => 1, //上架状态
  960. 'goods_type' => $goods_type_res ? $goods_type_res : 1, //商品类型
  961. 'mall_id' => $params['mall_id'],
  962. 'detail' => $sku[0]['goods_detail'],
  963. 'extra' => $extra,
  964. 'serial_number' => $sku[0]['serial_number']
  965. ];
  966. // 处理多规格
  967. if ($is_attr == 1) {
  968. $attr = [];
  969. $temp_attr_groups = [];
  970. $attr_groups_index = 1;
  971. $attr_list_index = 2;
  972. $temporary_attr_list = [];
  973. $temporary_attr_group_list = [];
  974. foreach ($attr_groups as $group_name => $group) {
  975. $attr_list = [];
  976. foreach ($group as $attr_item) {
  977. $attr_list[] = [
  978. 'attr_id' => $attr_list_index,
  979. 'attr_name' => $attr_item,
  980. 'pic_url' => $attr_value_pic[$attr_item] ?? '',
  981. ];
  982. $temporary_attr_list[$attr_item] = $attr_list_index;
  983. $attr_list_index++;
  984. }
  985. $temp_attr_groups[] = [
  986. 'attr_group_id' => $attr_groups_index,
  987. 'attr_group_name' => $group_name,
  988. 'attr_list' => $attr_list
  989. ];
  990. $temporary_attr_group_list[$group_name] = $attr_groups_index;
  991. $attr_groups_index++;
  992. }
  993. $temp['attr_groups'] = $temp_attr_groups;
  994. foreach ($sku as $ku) {
  995. $temp_sku = [];
  996. $temp_sku['price'] = !empty($ku['price']) ? $ku['price'] : 0;
  997. $temp_sku['stock'] = !empty($ku['stock']) ? $ku['stock'] : 0;
  998. $temp_sku['original_price'] = !empty($ku['original_price']) ? $ku['original_price'] : 0;
  999. $temp_sku['cost_price'] = !empty($ku['cost_price']) ? $ku['cost_price'] : 0;
  1000. $temp_sku['name'] = $ku['name'];
  1001. $temp_sku['weight'] = 0;
  1002. $temp_sku['goods_no'] = $goods_no;
  1003. $temp_sku['attr_list'] = $ku['temp_attr_list'];
  1004. $temp_sku['data_row_id'] = $ku['data_row_id'];
  1005. foreach ($temp_sku['attr_list'] as &$sku_attr) {
  1006. $sku_attr['attr_id'] = $temporary_attr_list[$sku_attr['attr_name']];
  1007. $sku_attr['attr_group_id'] = $temporary_attr_group_list[$sku_attr['attr_group_name']];
  1008. }
  1009. unset($sku_attr);
  1010. $temp_sku['sign_id'] = implode(':', array_column($temp_sku['attr_list'], 'attr_id'));
  1011. $attr[] = $temp_sku;
  1012. }
  1013. $temp['attr'] = $attr;
  1014. }
  1015. $res = self::setData($temp);
  1016. if ($res) {
  1017. $success_counts += $is_attr == 1 ? count($temp['attr']) : 1;
  1018. } else {
  1019. $reason = self::getStaticError();
  1020. if ($is_attr == 1) {
  1021. foreach ($temp['attr'] as $temp_attr) {
  1022. $temp_params = $data[$temp_attr['data_row_id']];
  1023. $temp_params['reason'] = $reason;
  1024. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($temp_params, $fail_arr, $fail_counts);
  1025. }
  1026. } else {
  1027. $temp_params = $data[$temp['serial_number']];
  1028. $temp_params['reason'] = $reason;
  1029. list($fail_arr, $fail_counts) = ImportFailLog::packFailArr($temp_params, $fail_arr, $fail_counts);
  1030. }
  1031. }
  1032. }
  1033. $param['counts'] = $counts;
  1034. $param['success_counts'] = $success_counts;
  1035. $param['fail_counts'] = $fail_counts;
  1036. $param['source'] = self::class;
  1037. $param['mall_id'] = $params['mall_id'];
  1038. if ($sync == 0) {
  1039. $param['fail_arr'] = $fail_arr;
  1040. // 异步返回数据
  1041. return $param;
  1042. } else {
  1043. $import_log_model = ImportLog::setData($param);
  1044. if (!$import_log_model) throw new \Exception(ImportLog::getStaticError());
  1045. if (!empty($fail_arr)) {
  1046. $res = ImportFailLog::setData($fail_arr, $import_log_model->id);
  1047. if (!$res) throw new \Exception(ImportFailLog::getStaticError());
  1048. }
  1049. }
  1050. }
  1051. //异步执行
  1052. public static function syncImportGoods($params)
  1053. {
  1054. try {
  1055. if (empty($params['id'])) throw new Exception('缺少参数id');
  1056. $model = ImportLog::findOne(['id' => $params['id']]);
  1057. if (empty($model['upload_file'])) throw new Exception('缺少参数upload_file');
  1058. $upload_file = $model['upload_file'];
  1059. $mall_id = $model['mall_id'];
  1060. $cvsFile = fopen($upload_file, 'r'); //开始读取csv文件数据
  1061. $i = 0;
  1062. $head_list = GoodsImportEnum::HEAD_LIST;
  1063. $head_list_fields = array_column($head_list, 'field');
  1064. $rows = [];
  1065. while ($fileData = fgetcsv($cvsFile)) {
  1066. $i++;
  1067. if ($i == 1) continue; //过滤表头
  1068. $temp_row = [];
  1069. foreach ($fileData as $k => $val) {
  1070. $val = mb_convert_encoding(trim($val), "UTF-8", "GBK");
  1071. foreach ($head_list_fields as $key => $field) {
  1072. if ($k == $key) {
  1073. $temp_row[$field] = $val;
  1074. continue;
  1075. }
  1076. }
  1077. }
  1078. $rows[] = $temp_row;
  1079. }
  1080. $temp_rows = array_column($rows, null, 'serial_number');
  1081. if (count($temp_rows) == count($rows)) {
  1082. $result = self::handleImportData(['mall_id' => $mall_id], $rows, 0);
  1083. $model->counts = $result['counts'];
  1084. $model->success_counts = $result['success_counts'];
  1085. $model->fail_counts = $result['fail_counts'];
  1086. } else {
  1087. $model->counts = count($rows);
  1088. $model->success_counts = 0;
  1089. $model->fail_counts = $model->counts;
  1090. $result = [];
  1091. }
  1092. if (!$model->save()) throw new \Exception($model->getErrorMessage());
  1093. $fail_arr = $result['fail_arr'] ?? [];
  1094. if (!empty($fail_arr)) {
  1095. $res = ImportFailLog::setData($fail_arr, $model->id);
  1096. if (!$res) throw new \Exception(ImportFailLog::getStaticError());
  1097. }
  1098. } catch (\Exception $e) {
  1099. $msg = __METHOD__ . ' msg: ' . $e->getMessage();
  1100. Yii::error($msg);
  1101. }
  1102. }
  1103. public static function getAddonsGoodsSetting($mall_id, $addons_fields, $data, $source)
  1104. {
  1105. if (empty($addons_fields)) return $data;
  1106. if (in_array('system_score', $addons_fields)) {
  1107. $data = GoodsExtra::getMarketingScore($mall_id, $source, $data);
  1108. }
  1109. $new_data = GlobalInvoke::exec(GlobalInvoke::APPEND_FIELDS_GOODS_DATA, $mall_id, [
  1110. 'source' => $source,
  1111. 'mall_id' => $mall_id,
  1112. 'data' => $data,
  1113. 'addons_fields' => $addons_fields
  1114. ]);
  1115. return $new_data ? $new_data : $data;
  1116. }
  1117. /**
  1118. * 获取默认的属性
  1119. * @Author: lun
  1120. * @DateTime: 2023/4/20 0020 17:04
  1121. * @Copyright: copyright (c) 2021 广东七件事集团
  1122. * @param $mall_id
  1123. * @param array $goods_detail
  1124. * @return array
  1125. */
  1126. public static function getDefaultSetting($mall_id, $goods_detail = [], $mch_id = 0)
  1127. {
  1128. // 获取默认的服务
  1129. $serive_ids = GoodsService::find()->select('id')->where(['mall_id' => $mall_id, 'mch_id' => $mch_id, 'is_default' => StatusEnum::ENABLED, 'status' => StatusEnum::ENABLED])->column();
  1130. if ($serive_ids) $goods_detail['services'] = $serive_ids;
  1131. // 获取默认的标签
  1132. $label_ids = GoodsLabel::find()->select('id')->where(['mall_id' => $mall_id, 'mch_id' => $mch_id, 'is_default' => StatusEnum::ENABLED, 'status' => StatusEnum::ENABLED])->column();
  1133. if ($label_ids) $goods_detail['labels'] = $label_ids;
  1134. // 获取默认的运费模板
  1135. $rule_id = FreightRules::find()->select('id')->where(['mall_id' => $mall_id, 'mch_id' => $mch_id, 'is_default' => StatusEnum::ENABLED, 'status' => StatusEnum::ENABLED])->scalar();
  1136. if ($rule_id) $goods_detail['freight_id'] = $rule_id;
  1137. return $goods_detail;
  1138. }
  1139. public static function getComponentData($params, $mall_id)
  1140. {
  1141. $params['type'] = $params['type'] ?? 'goods';
  1142. switch ($params['type']) {
  1143. case 'goods':
  1144. $goods_ids = Json::decode($params['id'], true);
  1145. break;
  1146. case 'goods_cate':
  1147. $cate_id_arr = [$params['id']];
  1148. $goods_cate_relate_list = GoodsCateRelate::findAll(['cate_id' => $cate_id_arr]);
  1149. $goods_ids = array_column($goods_cate_relate_list, 'goods_id') ?? [];
  1150. break;
  1151. default:
  1152. break;
  1153. }
  1154. $goods_params = ['where' => [['=', 'mall_id', $mall_id], ['in', 'id', $goods_ids], ['=', 'status', StatusEnum::ENABLED], ['=', 'is_on_sale', StatusEnum::ENABLED]], 'select' => 'id,goods_name,subtitle,price,original_price,cover_pic,(sales_num+virtual_sales) as sales_num, is_attr', 'index' => 'id'];
  1155. if (isset($params['limit'])) {
  1156. $goods_params['limit'] = $params['limit'];
  1157. $goods_params['page'] = 1;
  1158. }
  1159. return self::lists($goods_params, true);
  1160. }
  1161. }