// +---------------------------------------------------------------------- namespace app\services\product\sku; use app\dao\product\sku\StoreProductAttrDao; use app\services\BaseServices; use app\services\order\StoreCartServices; use app\services\product\product\StoreProductServices; use crmeb\exceptions\AdminException; use crmeb\traits\OptionTrait; /** * Class StoreProductAttrService * @package app\services\product\sku * @mixin StoreProductAttrDao */ class StoreProductAttrServices extends BaseServices { use OptionTrait; /** * StoreProductAttrServices constructor. * @param StoreProductAttrDao $dao */ public function __construct(StoreProductAttrDao $dao) { $this->dao = $dao; } /** * 生成规格唯一值 * @param int $id * @param string $sku * @return false|string */ public function createAttrUnique(int $id, string $sku) { return substr(md5($id . $sku . uniqid(true)), 12, 8); } /** * 根据根据前端规格数据获取sku * @param array $value * @param string */ public function getSku(array $value) { $sku = ''; if ($value) { $detail = []; $count = count($value['detail'] ?? []); for ($i=1; $i<=$count ; $i++) { $detail[] = trim($value['value' . $i]); } $sku = implode(',', $detail); } return $sku; } /** * 添加商品属性数据判断 * @param array $attrList * @param array $valueList * @param int $productId * @param int $type * @param int $is_vip * @param int $validate * @return array */ public function validateProductAttr(array $attrList, array $valueList, int $productId, int $type = 0, int $is_vip = 0, int $validate = 1) { $result = ['attr' => $attrList, 'value' => $valueList]; $attrValueList = []; $attrNameList = []; foreach ($attrList as $index => $attr) { if (!isset($attr['value'])) { throw new AdminException('请输入规则名称!'); } $attr['value'] = trim($attr['value']); if (!isset($attr['value'])) { throw new AdminException('请输入规则名称!!'); } if (!isset($attr['detail']) || !count($attr['detail'])) { throw new AdminException('请输入属性名称!'); } foreach ($attr['detail'] as $k => $attrValue) { $attrValue = trim($attrValue); if (empty($attrValue)) { throw new AdminException('请输入正确的属性'); } $attr['detail'][$k] = $attrValue; $attrValueList[] = $attrValue; $attr['detail'][$k] = $attrValue; } $attrNameList[] = $attr['value']; $attrList[$index] = $attr; } $attrCount = count($attrList); foreach ($valueList as $index => $value) { if (!isset($value['detail']) || count($value['detail']) != $attrCount) { throw new AdminException('请填写正确的商品信息'); } if (!isset($value['price']) || !is_numeric($value['price']) || floatval($value['price']) != $value['price']) { throw new AdminException('请填写正确的商品价格'); } if ($type == 4) { if (!isset($value['integral']) || !is_numeric($value['integral']) || floatval($value['integral']) != $value['integral']) { throw new AdminException('请填写正确的商品兑换积分'); } if (isset($value['price']) && $value['price'] <= 0 && isset($value['integral']) && $value['integral'] <= 0) { throw new AdminException('积分商品兑换积分和价格不能同时为空'); } } if (!isset($value['stock']) || !is_numeric($value['stock']) || intval($value['stock']) != $value['stock']) { throw new AdminException('请填写正确的商品库存'); } //供应商结算价 不用成本价 if (!($value['settle_price'] ?? 0) && (!isset($value['cost']) || !is_numeric($value['cost']) || floatval($value['cost']) != $value['cost'])) { throw new AdminException('请填写正确的商品成本价格'); } if ($validate && (!isset($value['pic']) || empty($value['pic']))) { throw new AdminException('请上传商品规格图片'); } if ($is_vip && (!isset($value['vip_price']) || !$value['vip_price'])) { throw new AdminException('会员价格不能为0'); } foreach ($value['detail'] as $attrName => $attrValue) { //如果attrName 存在空格 则这个规格key 会出现两次 unset($valueList[$index]['detail'][$attrName]); $attrName = trim($attrName); $attrValue = trim($attrValue); if (!in_array($attrName, $attrNameList, true)) { throw new AdminException($attrName . '规则不存在'); } if (!in_array($attrValue, $attrValueList, true)) { throw new AdminException($attrName . '属性不存在'); } if (empty($attrName)) { throw new AdminException('请输入正确的属性'); } $valueList[$index]['detail'][$attrName] = $attrValue; } } $attrGroup = []; $valueGroup = []; foreach ($attrList as $k => $value) { $attrGroup[] = [ 'product_id' => $productId, 'attr_name' => $value['value'], 'attr_values' => $value['detail'], 'type' => $type ]; } /** @var StoreProductAttrValueServices $storeProductAttrValueServices */ $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class); $skuArray = $storeProductAttrValueServices->getSkuArray(['product_id' => $productId, 'type' => $type], 'unique', 'suk'); $store_product_id = $this->getItem('store_product_id', 0); $productSkuArray = []; if ($store_product_id) { $productSkuArray = $storeProductAttrValueServices->getSkuArray(['product_id' => $store_product_id, 'type' => 0], '*', 'suk'); } foreach ($valueList as $k => $value) { // sort($value['detail'], SORT_STRING); $sku = implode(',', $value['detail']); $valueGroup[$sku] = [ 'product_id' => $productId, 'suk' => $this->getSku($value), 'price' => $value['price'], 'integral' => isset($value['integral']) ? $value['integral'] : 0, 'settle_price' => $value['settle_price'] ?? 0.00,//供应商结算价 'cost' => ($value['settle_price'] ?? 0.00) ?: $value['cost'],//供应端:去除成本价 'ot_price' => $value['ot_price'], 'stock' => $value['stock'], 'unique' => $skuArray[$sku] ?? $this->createAttrUnique($productId, $sku), 'image' => $value['pic'], 'bar_code' => $value['bar_code'] ?? '', 'pnum' => $value['pnum'] ?? 0, 'weight' => $value['weight'] ?? 0, 'volume' => $value['volume'] ?? 0, 'brokerage' => $value['brokerage'] ?? 0, 'brokerage_two' => $value['brokerage_two'] ?? 0, 'type' => $type, 'quota' => $value['quota'] ?? 0, 'quota_show' => $value['quota'] ?? 0, 'vip_price' => $is_vip ? ($value['vip_price'] ?? 0) : 0, 'code' => $value['code'] ?? '', 'product_type' => $value['product_type'] ?? 0, 'virtual_list' => $productSkuArray[$sku]['virtual_list'] ?? $value['virtual_list'] ?? [], 'disk_info' => $productSkuArray[$sku]['disk_info'] ?? $value['disk_info'] ?? '', 'write_times' => $productSkuArray[$sku]['write_times'] ?? $value['write_times'] ?? 1, 'write_valid' => $productSkuArray[$sku]['write_valid'] ?? $value['write_valid'] ?? 1, 'write_days' => $productSkuArray[$sku]['write_days'] ?? $value['write_days'] ?? $value['days'] ?? 0, 'write_start' => ($productSkuArray[$sku]['section_time'][0] ?? $value['section_time'][0] ?? '') ? strtotime($productSkuArray[$sku]['section_time'][0] ?? $value['section_time'][0] ?? '') : 0, 'write_end' => ($productSkuArray[$sku]['section_time'][1] ?? $value['section_time'][1] ?? '') ? strtotime($productSkuArray[$sku]['section_time'][1] ?? $value['section_time'][1] ?? '') : 0, ]; } if (!count($attrGroup) || !count($valueGroup)) { throw new AdminException('请设置至少一个属性!'); } return compact('result', 'attrGroup', 'valueGroup'); } /** * 保存商品规格 * @param array $data * @param int $id * @param int $type * @return bool|mixed|\think\Collection * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function saveProductAttr(array $data, int $id, int $type = 0) { $this->setAttr($data['attrGroup'], $id, $type); /** @var StoreProductAttrResultServices $storeProductAttrResultServices */ $storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class); $storeProductAttrResultServices->setResult($data['result'], $id, $type); /** @var StoreProductAttrValueServices $storeProductAttrValueServices */ $storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class); $valueGroup = $data['valueGroup'] ?? []; $updateSuks = array_column($valueGroup, 'suk'); $oldSuks = []; $oldAttrValue = $storeProductAttrValueServices->getSkuArray(['product_id' => $id, 'type' => $type], '*', 'suk'); if ($oldAttrValue) $oldSuks = array_column($oldAttrValue, 'suk'); $delSuks = array_merge(array_diff($oldSuks, $updateSuks)); $dataAll = []; $res1 = $res2 = $res3 = true; foreach ($valueGroup as $item) { if ($oldSuks && in_array($item['suk'], $oldSuks) && isset($oldAttrValue[$item['suk']])) { $attrId = $oldAttrValue[$item['suk']]['id']; unset($item['suk'], $item['unique']); $item['virtual_list'] =json_encode($item['virtual_list']); $res1 = $res1 && $storeProductAttrValueServices->update($attrId, $item); } else { $dataAll[] = $item; } } if ($delSuks) { $res2 = $storeProductAttrValueServices->del($id, $type, $delSuks); } if ($dataAll) { $res3 = $storeProductAttrValueServices->saveAll($dataAll); } if ($res1 && $res2 && $res3) { // $unique = array_column($valueGroup, 'unique'); // $storeProductAttrValueServices->updateSumStock($unique ?? []); return $valueGroup; } else { throw new AdminException('商品规格信息保存失败'); } } /** * 获取商品规格 * @param array $where * @return array */ public function getProductAttr(array $where) { return $this->dao->getProductAttr($where); } /** * 获取商品规格详情 * @param int $id * @param int $uid * @param int $cartNum //是否查询购物车数量 * @param int $type //活动类型 attr_value表 * @param int $productId * @param array $productInfo * @param int $discount //限时折扣 * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getProductAttrDetailCache(int $id, int $uid, int $cartNum = 0, int $type = 0, int $productId = 0, array $productInfo = [], int $discount = -1) { $attrDetail = $this->dao->cacheTag()->remember('attr_' . $id . '_' . $type . '_' . $productId, function () use ($productId, $id, $type) { $attrDetail = $this->dao->getProductAttr(['product_id' => $id, 'type' => $type]); if (!$attrDetail && $type > 0 && $productId) {//活动商品未获取到规格信息 $attrDetail = $this->dao->getProductAttr(['product_id' => $productId, 'type' => 0]); } return $attrDetail; }, 600); /** @var StoreProductAttrValueServices $storeProductAttrValueService */ $storeProductAttrValueService = app()->make(StoreProductAttrValueServices::class); $_values = $this->dao->cacheTag()->remember('attr_value_' . $id . '_' . $type, function () use ($storeProductAttrValueService, $id, $type) { return $storeProductAttrValueService->getProductAttrValue(['product_id' => $id, 'type' => $type]); }, 600); if ($productId == 0) { $productId = $id; } /** @var StoreProductServices $storeProductService */ $storeProductService = app()->make(StoreProductServices::class); $vip_price = true; if (!$storeProductService->vipIsOpen(!!($productInfo['is_vip'] ?? 0))) $vip_price = false; $cartNumList = []; $activityAttr = []; if ($cartNum) { /** @var StoreCartServices $storeCartService */ $storeCartService = app()->make(StoreCartServices::class); $unique = array_column($_values, 'unique'); $cartNumList = $storeCartService->cacheTag('Cart_Nums_' . $uid)->remember(md5(json_encode($unique)), function () use ($storeCartService, $unique, $id, $uid) { return $storeCartService->getUserCartNums($unique, $id, $uid); } , 600); } $values = []; $field = $type ? 'stock,price' : 'stock'; $storeProducts = $this->dao->cacheTag()->remember('attr_sku_' . $productId . '_' . $type, function () use ($storeProductAttrValueService, $productId, $field) { return $storeProductAttrValueService->getSkuArray(['product_id' => $productId, 'type' => 0], $field, 'suk'); }); foreach ($_values as $value) { if ($cartNum) { $value['cart_num'] = $cartNumList[$value['unique']] ?? 0; } if (!$vip_price) $value['vip_price'] = 0; $value['product_stock'] = $storeProducts[$value['suk']]['stock'] ?? 0; if ($discount != -1) $value['price'] = bcmul((string)$value['price'], (string)bcdiv((string)$discount, '100', 2), 2); if ($type) { $value['product_price'] = $storeProducts[$value['suk']]['price'] ?? 0; $attrs = explode(',', $value['suk']); $count = count($attrs); for ($i = 0; $i < $count; $i++) { $activityAttr[$i][] = $attrs[$i]; } } $value['small_image'] = get_thumb_water($value['image']); $values[$value['suk']] = $value; } foreach ($attrDetail as $k => $v) { $attr = $v['attr_values']; //活动商品只展示参与活动sku if ($type && $activityAttr && $a = array_merge(array_intersect($v['attr_values'], $activityAttr[$k]))) { $attrDetail[$k]['attr_values'] = $a; $attr = $a; } foreach ($attr as $kk => $vv) { $attrDetail[$k]['attr_value'][$kk]['attr'] = $vv; $attrDetail[$k]['attr_value'][$kk]['check'] = false; } } return [$attrDetail, $values]; } /** * 获取商品规格详情 * @param int $id * @param int $uid * @param int $cartNum //是否查询购物车数量 * @param int $type //活动类型 attr_value表 * @param int $productId * @param array $productInfo * @param int $discount //限时折扣 * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getProductAttrDetail(int $id, int $uid, int $cartNum = 0, int $type = 0, int $productId = 0, array $productInfo = [], int $discount = -1) { $attrDetail = $this->dao->getProductAttr(['product_id' => $id, 'type' => $type]); if (!$attrDetail && $type > 0 && $productId) {//活动商品未获取到规格信息 $attrDetail = $this->dao->getProductAttr(['product_id' => $productId, 'type' => 0]); } /** @var StoreProductAttrValueServices $storeProductAttrValueService */ $storeProductAttrValueService = app()->make(StoreProductAttrValueServices::class); $_values = $storeProductAttrValueService->getProductAttrValue(['product_id' => $id, 'type' => $type]); if ($productId == 0) $productId = $id; /** @var StoreProductServices $storeProductService */ $storeProductService = app()->make(StoreProductServices::class); if (!$productInfo) { $productInfo = $storeProductService->get($productId, ['is_vip']); } $vip_price = true; if (!$storeProductService->vipIsOpen(!!$productInfo['is_vip'])) $vip_price = false; $cartNumList = []; $activityAttr = []; if ($cartNum) { /** @var StoreCartServices $storeCartService */ $storeCartService = app()->make(StoreCartServices::class); //真实用户 if ($uid) { $cartNumList = $storeCartService->getUserCartNums(array_column($_values, 'unique'), $id, $uid); } else { //虚拟用户 $touristUid = $this->getItem('touristUid'); if ($touristUid) { $cartNumList = $storeCartService->getUserCartNums(array_column($_values, 'unique'), $id, $touristUid, 'tourist_uid'); } } } $values = []; $field = $type ? 'stock,price' : 'stock'; $storeProducts = $storeProductAttrValueService->getSkuArray(['product_id' => $productId, 'type' => 0], $field, 'suk'); foreach ($_values as $value) { if ($cartNum) { $value['cart_num'] = $uid || $touristUid ? ($cartNumList[$value['unique']] ?? 0) : 0; } if (!$vip_price) $value['vip_price'] = 0; $value['product_stock'] = $storeProducts[$value['suk']]['stock'] ?? 0; if ($discount != -1) $value['price'] = bcmul((string)$value['price'], (string)bcdiv((string)$discount, '100', 2), 2); if ($type) { $value['product_price'] = $storeProducts[$value['suk']]['price'] ?? 0; $attrs = explode(',', $value['suk']); $count = count($attrs); for ($i = 0; $i < $count; $i++) { $activityAttr[$i][] = $attrs[$i]; } if ($value['product_stock'] <= 0) {//原商品规格无库存 $value['stock'] = 0; } } $value['small_image'] = get_thumb_water($value['image']); $values[$value['suk']] = $value; } foreach ($attrDetail as $k => $v) { $attr = $v['attr_values']; //活动商品只展示参与活动sku if ($type && $activityAttr && $a = array_merge(array_intersect($v['attr_values'], $activityAttr[$k]))) { $attrDetail[$k]['attr_values'] = $a; $attr = $a; } foreach ($attr as $kk => $vv) { $attrDetail[$k]['attr_value'][$kk]['attr'] = $vv; $attrDetail[$k]['attr_value'][$kk]['check'] = false; } } return [$attrDetail, $values]; } /** * 删除一条数据 * @param int $id * @param int $type */ public function del(int $id, int $type) { $this->dao->del($id, $type); } /** * 设置规格 * @param array $data * @param int $id * @param int $type * @return bool * @throws \Exception */ public function setAttr(array $data, int $id, int $type) { if ($data) { $this->dao->del($id, $type); $res = $this->dao->saveAll($data); if (!$res) throw new AdminException('规格保存失败'); } return true; } }