|
|
@@ -20,6 +20,11 @@ use Yii;
|
|
|
use bizHd\base\classes\BaseClass;
|
|
|
use bizHd\goods\models\GoodsCategory;
|
|
|
|
|
|
+/**
|
|
|
+ * 花店商品业务类
|
|
|
+ * 谁用:hd 后台商品管理、mall 商品详情/下单、首页模块
|
|
|
+ * 解决问题:单规格与多规格存储(多规格主记录承载首规格,goodsStyle=1)
|
|
|
+ */
|
|
|
class GoodsClass extends BaseClass
|
|
|
{
|
|
|
|
|
|
@@ -40,28 +45,66 @@ class GoodsClass extends BaseClass
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 详情附加使用场景与规格列表
|
|
|
+ * specEnabled 按主记录 goodsStyle 判定;多规格时 specList 第一项是主记录自身(可售首规格),其后是子规格
|
|
|
+ * @param array $goods 主商品或规格商品行
|
|
|
+ * @param int $mainId 门店主体 id
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
public static function appendUseCasesAndSpecs($goods, $mainId)
|
|
|
{
|
|
|
$id = $goods['id'];
|
|
|
$relations = GoodsUseCaseClass::getAllByCondition(['goodsId'=>$id], null, 'useCaseId');
|
|
|
$goods['useCaseIdList'] = array_map('intval', array_column($relations, 'useCaseId'));
|
|
|
- $specs = self::getAllByCondition(['masterId'=>$id, 'delStatus'=>0], 'id ASC', '*');
|
|
|
- foreach ($specs as &$spec) {
|
|
|
- $spec = business::formatGoodsImg($spec);
|
|
|
- $shortCover = $spec['cover'] ?? '';
|
|
|
- $spec['shortCover'] = $shortCover;
|
|
|
- $spec['cover'] = $shortCover;
|
|
|
+ $specEnabled = intval($goods['goodsStyle'] ?? 0) === 1 ? 1 : 0;
|
|
|
+ $goods['specEnabled'] = $specEnabled;
|
|
|
+ if ($specEnabled !== 1) {
|
|
|
+ $goods['specList'] = [];
|
|
|
+ return $goods;
|
|
|
+ }
|
|
|
+ // 主记录已由 getGoodsInfo 格式化过图片,这里只抽出规格展示字段,避免二次拼接 OSS 地址
|
|
|
+ $firstSpec = $goods;
|
|
|
+ unset($firstSpec['useCaseIdList'], $firstSpec['specEnabled'], $firstSpec['specList']);
|
|
|
+ $firstSpec['id'] = $id;
|
|
|
+ $firstSpec['masterId'] = 0;
|
|
|
+ $firstSpec['goodsStyle'] = 1;
|
|
|
+ // 与子规格一致:cover 用短路径,避免编辑页把 OSS 完整 URL 当短路径回写
|
|
|
+ $firstSpec['cover'] = $firstSpec['shortCover'] ?? ($firstSpec['cover'] ?? '');
|
|
|
+ $children = self::getAllByCondition(['masterId'=>$id, 'delStatus'=>0], 'id ASC', '*');
|
|
|
+ foreach ($children as &$spec) {
|
|
|
+ $spec = self::formatSpecRow($spec);
|
|
|
}
|
|
|
unset($spec);
|
|
|
- $goods['specEnabled'] = empty($specs) ? 0 : 1;
|
|
|
- $goods['specList'] = $specs;
|
|
|
+ $goods['specList'] = array_merge([$firstSpec], $children);
|
|
|
return $goods;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 规格子记录图片格式化:短路径 cover 保留,供后台编辑回显
|
|
|
+ * @param array $row 规格商品行
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private static function formatSpecRow($row)
|
|
|
+ {
|
|
|
+ $spec = business::formatGoodsImg($row);
|
|
|
+ $shortCover = $spec['cover'] ?? '';
|
|
|
+ $spec['shortCover'] = $shortCover;
|
|
|
+ $spec['cover'] = $shortCover;
|
|
|
+ return $spec;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步多规格子记录:首规格已由 Form 回写到主记录,这里只维护第 2..n 个规格子行
|
|
|
+ * 关闭多规格时软删全部子行;仅一个规格时不建子行
|
|
|
+ * @param array|object $master 主商品
|
|
|
+ * @param array $data 含 specEnabled、specList
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
public static function syncSpecs($master, $data)
|
|
|
{
|
|
|
- $masterId = $master['id'];
|
|
|
- $enabled = (int)($data['specEnabled']);
|
|
|
+ $masterId = (int)(is_array($master) ? $master['id'] : $master->id);
|
|
|
+ $enabled = (int)($data['specEnabled'] ?? 0);
|
|
|
$old = self::getAllByCondition(['masterId'=>$masterId, 'delStatus'=>0], null, '*', 'id');
|
|
|
if (!$enabled) {
|
|
|
if (!empty($old)) {
|
|
|
@@ -69,17 +112,29 @@ class GoodsClass extends BaseClass
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
+ $list = $data['specList'] ?? [];
|
|
|
+ $firstSpec = $list[0] ?? [];
|
|
|
+ $firstId = (int)($firstSpec['id'] ?? 0);
|
|
|
+ // 编辑时把原来的子规格挪到首位:该子行数据已落到主记录,软删旧子行避免双份
|
|
|
+ if ($firstId && isset($old[$firstId])) {
|
|
|
+ self::updateById($firstId, ['delStatus'=>1]);
|
|
|
+ unset($old[$firstId]);
|
|
|
+ }
|
|
|
$base = self::getById($masterId);
|
|
|
if (empty($base)) {
|
|
|
$base = is_array($master) ? $master : $master->attributes;
|
|
|
}
|
|
|
unset($base['id'], $base['sn']);
|
|
|
- $list = $data['specList'] ?? [];
|
|
|
$kept = [];
|
|
|
- foreach ($list as $spec) {
|
|
|
- $id = (int)($spec['id'] ?? 0); $kept[] = $id;
|
|
|
+ $childList = array_slice($list, 1);
|
|
|
+ foreach ($childList as $spec) {
|
|
|
+ $id = (int)($spec['id'] ?? 0);
|
|
|
+ // 原主记录上的首规格被挪到后面:不能拿主 id 去更新主行,改为新建子行
|
|
|
+ if ($id === $masterId) {
|
|
|
+ $id = 0;
|
|
|
+ }
|
|
|
$fields = $base;
|
|
|
- unset($fields['createTime']);
|
|
|
+ unset($fields['createTime'], $fields['goodsStyle']);
|
|
|
$fields = array_merge($fields, [
|
|
|
'specName'=>trim($spec['specName'] ?? ''),
|
|
|
'price'=>(float)($spec['price'] ?? 0),
|
|
|
@@ -90,6 +145,7 @@ class GoodsClass extends BaseClass
|
|
|
'weight'=>(float)($spec['weight'] ?? 1),
|
|
|
'sold'=>(int)($spec['sold'] ?? 0),
|
|
|
'masterId'=>$masterId,
|
|
|
+ 'goodsStyle'=>0,
|
|
|
'status'=>(int)($data['status'] ?? 1),
|
|
|
'delStatus'=>0
|
|
|
]
|
|
|
@@ -100,18 +156,39 @@ class GoodsClass extends BaseClass
|
|
|
$fields['cover'] = !empty($spec['cover']) && in_array($spec['cover'], $specImg, true) ? $spec['cover'] : $specImg[0];
|
|
|
}
|
|
|
if ($id && isset($old[$id])) {
|
|
|
+ $kept[] = $id;
|
|
|
self::updateById($id, $fields);
|
|
|
} else {
|
|
|
- $child = $fields; $child['sn'] = orderSn::getGoodsSn($child); $child['createTime']=date('Y-m-d H:i:s'); self::add($child, true);
|
|
|
+ $child = $fields;
|
|
|
+ $child['sn'] = orderSn::getGoodsSn($child);
|
|
|
+ $child['createTime'] = date('Y-m-d H:i:s');
|
|
|
+ self::add($child, true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- $remove = array_diff(array_keys($old), array_filter($kept));
|
|
|
+ $remove = array_diff(array_keys($old), $kept);
|
|
|
if ($remove) {
|
|
|
\bizHd\goods\models\Goods::updateAll(['delStatus'=>1], ['and', ['masterId'=>$masterId], ['in', 'id', $remove]]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 带规格名的展示名称:specName 非空即拼到商品名后(主记录承载首规格时也要带规格名)
|
|
|
+ * @param array|object $goods 商品行
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ public static function formatSpecDisplayName($goods)
|
|
|
+ {
|
|
|
+ if (is_object($goods)) {
|
|
|
+ $name = strval($goods->name ?? '');
|
|
|
+ $specName = trim(strval($goods->specName ?? ''));
|
|
|
+ } else {
|
|
|
+ $name = strval($goods['name'] ?? '');
|
|
|
+ $specName = trim(strval($goods['specName'] ?? ''));
|
|
|
+ }
|
|
|
+ return $specName !== '' ? $name . '(' . $specName . ')' : $name;
|
|
|
+ }
|
|
|
+
|
|
|
public static function orderCreateGoods($data)
|
|
|
{
|
|
|
$name = $data['name'] ?? '';
|
|
|
@@ -527,7 +604,7 @@ class GoodsClass extends BaseClass
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 批量判断主商品是否启用多规格(masterId 指向该商品且未删除)
|
|
|
+ * 批量判断主商品是否启用多规格(读主记录 goodsStyle,不再靠子记录存在性)
|
|
|
* @param array $goodsIds 主商品 id 列表
|
|
|
* @return array [goodsId => 1|0]
|
|
|
* @throws \Exception
|
|
|
@@ -545,16 +622,16 @@ class GoodsClass extends BaseClass
|
|
|
foreach ($goodsIds as $gid) {
|
|
|
$map[$gid] = 0;
|
|
|
}
|
|
|
- $specs = self::getAllByCondition(
|
|
|
- ['masterId' => ['in', $goodsIds], 'delStatus' => 0],
|
|
|
+ $rows = self::getAllByCondition(
|
|
|
+ ['id' => ['in', $goodsIds]],
|
|
|
null,
|
|
|
- 'masterId'
|
|
|
+ 'id,goodsStyle'
|
|
|
);
|
|
|
- if (!empty($specs)) {
|
|
|
- foreach ($specs as $spec) {
|
|
|
- $masterId = intval($spec['masterId'] ?? 0);
|
|
|
- if ($masterId > 0) {
|
|
|
- $map[$masterId] = 1;
|
|
|
+ if (!empty($rows)) {
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $gid = intval($row['id'] ?? 0);
|
|
|
+ if ($gid > 0) {
|
|
|
+ $map[$gid] = intval($row['goodsStyle'] ?? 0) === 1 ? 1 : 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -570,7 +647,7 @@ class GoodsClass extends BaseClass
|
|
|
$goodsIds = array_column($list, 'id');
|
|
|
// 一次性获取所有商品的分类关系
|
|
|
$allGoodsCategories = GoodsCategoryClass::getGoodsHasCategoryIdsBatch($goodsIds);
|
|
|
- $specEnabledMap = self::getSpecEnabledMap($goodsIds);
|
|
|
+ // 列表行已含 goodsStyle,直接映射为 specEnabled,避免再查子记录
|
|
|
// &$val 这里的指向符号不能去掉
|
|
|
foreach ($list as $key => &$val) {
|
|
|
$id = $val['id'];
|
|
|
@@ -605,13 +682,19 @@ class GoodsClass extends BaseClass
|
|
|
$val['classId'] = 0;
|
|
|
$val['bigPrice'] = $price;
|
|
|
$val['smallPrice'] = $price;
|
|
|
- $val['specEnabled'] = intval($specEnabledMap[$id] ?? 0);
|
|
|
+ $val['specEnabled'] = intval($val['goodsStyle'] ?? 0) === 1 ? 1 : 0;
|
|
|
}
|
|
|
//获取最终需要的价格
|
|
|
return $list;
|
|
|
}
|
|
|
|
|
|
- //新建商品 ssh 20220404
|
|
|
+ /**
|
|
|
+ * 新建商品
|
|
|
+ * @param array $data 商品数据
|
|
|
+ * @param \bizHd\work\models\Work $work 工作单
|
|
|
+ * @return array | \common\base\models\Base goods商品信息
|
|
|
+ * @throws \Exception
|
|
|
+ */
|
|
|
public static function addGoods($data, $work = null)
|
|
|
{
|
|
|
$data['createTime'] = date("Y-m-d H:i:s");
|
|
|
@@ -626,7 +709,7 @@ class GoodsClass extends BaseClass
|
|
|
$data['sn'] = $sn;
|
|
|
$addStock = $data['stock'] ?? 0;
|
|
|
unset($data['stock']);
|
|
|
- $info = self::add($data, true);
|
|
|
+ $info = self::add($data, true); // 添加商品
|
|
|
$id = $info->id;
|
|
|
$name = $info->name;
|
|
|
$py = $data['py'] ?? '';
|
|
|
@@ -719,7 +802,7 @@ class GoodsClass extends BaseClass
|
|
|
} else {
|
|
|
$itemList = $data['itemList'] ?? '';
|
|
|
$itemArr = json_decode($itemList, true);
|
|
|
- //新建花束有子项,采购绿植没有子项
|
|
|
+ //新建花束有子项,采购绿植没有子项
|
|
|
if (!empty($itemArr)) {
|
|
|
foreach ($itemArr as $item) {
|
|
|
$goodsItemData = [
|
|
|
@@ -909,6 +992,8 @@ class GoodsClass extends BaseClass
|
|
|
CategoryClass::counters(['goodsNum' => -1], ['id' => $goodsHasCategoryIds]);
|
|
|
GoodsClass::updateById($id, ['delStatus' => 1]);
|
|
|
GoodsCategoryClass::updateByCondition(['gId' => $id], ['delStatus' => 1]);
|
|
|
+ // 级联软删规格子记录,避免主商品删除后留下可售孤儿规格
|
|
|
+ self::updateByCondition(['masterId' => $id], ['delStatus' => 1]);
|
|
|
}
|
|
|
|
|
|
//产品上下架 ssh 2019.12.8
|
|
|
@@ -916,6 +1001,8 @@ class GoodsClass extends BaseClass
|
|
|
{
|
|
|
self::updateById($id, ['status' => $status]);
|
|
|
GoodsCategoryClass::updateByCondition(['gId' => $id], ['status' => $status]);
|
|
|
+ // 规格子记录与主商品同步上下架
|
|
|
+ self::updateByCondition(['masterId' => $id], ['status' => $status]);
|
|
|
}
|
|
|
|
|
|
//验证商品的有效性 ssh 20201.2.1
|