| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- <?php
- /**
- * 零售店开店样例模板复制
- * 谁用:SjService::initHdBaseInfo 在申请勾选「生成样例模板」后调用
- * 解决问题:把指定模板店的首页配置、分类、使用场景、商品、秒杀数据复制给新开零售店,并重写关联 ID
- */
- namespace bizHd\shop\classes;
- use bizHd\base\classes\BaseClass;
- use bizHd\goods\classes\CategoryClass;
- use bizHd\goods\classes\GoodsCategoryClass;
- use bizHd\goods\classes\GoodsClass;
- use bizHd\goods\classes\GoodsUseCaseClass;
- use bizHd\goods\classes\KindClass;
- use bizHd\goods\classes\UseCaseClass;
- use bizHd\homePageConfig\classes\HomePageConfigClass;
- use bizHd\homePageConfig\classes\HomePageModuleConfigClass;
- use bizHd\seckill\classes\SeckillActivityClass;
- use bizHd\seckill\classes\SeckillGoodsClass;
- use Yii;
- class ShopTemplateClass extends BaseClass
- {
- /**
- * 从模板店复制零售店样例数据到新店
- * @param int $tplMainId 模板店 mainId(按环境写死)
- * @param int $tplShopId 模板店 shopId(按环境写死)
- * @param object $shopInfo 新创建的门店对象,需含 id/mainId/sjId
- * @return bool
- */
- public static function copyTemplateData($tplMainId, $tplShopId, $shopInfo)
- {
- $tplMainId = intval($tplMainId);
- $tplShopId = intval($tplShopId);
- $newMainId = intval($shopInfo->mainId ?? 0);
- $newShopId = intval($shopInfo->id ?? 0);
- $newSjId = intval($shopInfo->sjId ?? 0);
- if ($tplMainId <= 0 || $tplShopId <= 0 || $newMainId <= 0 || $newShopId <= 0) {
- Yii::error('复制零售店样例模板参数无效');
- return false;
- }
- $newShop = [
- 'mainId' => $newMainId,
- 'shopId' => $newShopId,
- 'sjId' => $newSjId,
- ];
- // 已有开店事务时 Yii2 会创建保存点;独立调用时则开启新事务。
- // 任一步骤失败都只回滚模板数据,避免外层继续提交残缺的分类、商品或首页配置。
- $transaction = Yii::$app->db->beginTransaction();
- try {
- // 分类必须先复制:商品分类关联、首页金刚区/轮播都依赖分类 ID 映射
- $categoryMap = self::copyCategories($tplMainId, $newShop);
- $useCaseMap = self::copyUseCases($tplMainId, $tplShopId, $newShop);
- $kindMap = self::buildKindMap($tplMainId, $newMainId);
- $goodsMap = self::copyGoods($tplMainId, $tplShopId, $newShop, $kindMap);
- self::copyGoodsCategories($tplMainId, $newShop, $goodsMap, $categoryMap);
- self::copyUseCaseRelations($tplMainId, $newShop, $goodsMap, $useCaseMap);
- $activityMap = self::copySeckillActivities($tplMainId, $tplShopId, $newShop);
- self::copySeckillGoods($tplMainId, $tplShopId, $newShop, $activityMap, $goodsMap);
- self::copyHomePageConfig($tplShopId, $newShop, $goodsMap, $categoryMap, $useCaseMap);
- $transaction->commit();
- return true;
- } catch (\Throwable $e) {
- $transaction->rollBack();
- throw $e;
- }
- }
- /**
- * 删掉开店时初始化的默认分类,再整份复制模板店分类
- * 为什么先删:ShopClass::addShop 已写入花束/花篮/永生花/绿植,勾选模板后以模板店分类为准
- * @param int $tplMainId
- * @param array $newShop
- * @return array 旧分类id => 新分类id
- */
- protected static function copyCategories($tplMainId, $newShop)
- {
- $map = [];
- CategoryClass::deleteByCondition(['mainId' => $newShop['mainId']]);
- $list = CategoryClass::getAllByCondition(['mainId' => $tplMainId, 'delStatus' => 0], 'id ASC');
- if (empty($list)) {
- return $map;
- }
- foreach ($list as $row) {
- $oldId = intval($row['id'] ?? 0);
- $payload = self::prepareCopyRow($row, $newShop, ['goodsNum']);
- $payload['goodsNum'] = 0;
- $created = CategoryClass::add($payload, true);
- $newId = intval($created->id ?? 0);
- if ($oldId > 0 && $newId > 0) {
- $map[$oldId] = $newId;
- }
- }
- return $map;
- }
- /**
- * 复制使用场景,建立旧 useCaseId => 新 useCaseId 映射
- * @param int $tplMainId
- * @param int $tplShopId
- * @param array $newShop
- * @return array
- */
- protected static function copyUseCases($tplMainId, $tplShopId, $newShop)
- {
- $map = [];
- $list = UseCaseClass::getAllByCondition([
- 'mainId' => $tplMainId,
- 'shopId' => $tplShopId,
- 'delStatus' => 0,
- ], 'inTurn DESC, id ASC');
- if (empty($list)) {
- return $map;
- }
- foreach ($list as $row) {
- $oldId = intval($row['id'] ?? 0);
- $payload = self::prepareCopyRow($row, $newShop);
- $created = UseCaseClass::add($payload, true);
- $newId = intval($created->id ?? 0);
- if ($oldId > 0 && $newId > 0) {
- $map[$oldId] = $newId;
- }
- }
- return $map;
- }
- /**
- * 按种类名称把模板店 kindId 映射到新店已初始化的种类
- * 为什么按名称:开店时 KindClass::initKind 已给新店写过一套种类,不能直接复用模板店 kindId
- * @param int $tplMainId
- * @param int $newMainId
- * @return array 旧kindId => 新kindId
- */
- protected static function buildKindMap($tplMainId, $newMainId)
- {
- $map = [];
- $oldList = KindClass::getAllByCondition(['mainId' => $tplMainId, 'delStatus' => 0], null, 'id,name');
- $newList = KindClass::getAllByCondition(['mainId' => $newMainId, 'delStatus' => 0], null, 'id,name');
- $newByName = [];
- foreach ($newList as $item) {
- $name = strval($item['name'] ?? '');
- if ($name !== '') {
- $newByName[$name] = intval($item['id']);
- }
- }
- foreach ($oldList as $item) {
- $name = strval($item['name'] ?? '');
- $oldId = intval($item['id'] ?? 0);
- if ($oldId > 0 && isset($newByName[$name])) {
- $map[$oldId] = $newByName[$name];
- }
- }
- return $map;
- }
- /**
- * 复制商品:先主记录再子规格,销量清零,masterId/kindId 按映射重写
- * 商品按 mainId 归属,xhGoods 没有 shopId,不能按门店过滤
- * @param int $tplMainId
- * @param int $tplShopId
- * @param array $newShop
- * @param array $kindMap
- * @return array 旧goodsId => 新goodsId
- */
- protected static function copyGoods($tplMainId, $tplShopId, $newShop, $kindMap)
- {
- $map = [];
- $list = GoodsClass::getAllByCondition([
- 'mainId' => $tplMainId,
- 'delStatus' => 0,
- ], 'id ASC');
- if (empty($list)) {
- return $map;
- }
- $masters = [];
- $children = [];
- foreach ($list as $row) {
- if (intval($row['masterId'] ?? 0) === 0) {
- $masters[] = $row;
- } else {
- $children[] = $row;
- }
- }
- foreach ($masters as $row) {
- $oldId = intval($row['id'] ?? 0);
- $payload = self::prepareGoodsRow($row, $newShop, $kindMap, 0);
- $created = GoodsClass::add($payload, true);
- $newId = intval($created->id ?? 0);
- if ($oldId > 0 && $newId > 0) {
- $map[$oldId] = $newId;
- }
- }
- foreach ($children as $row) {
- $oldId = intval($row['id'] ?? 0);
- $oldMasterId = intval($row['masterId'] ?? 0);
- if (!isset($map[$oldMasterId])) {
- continue;
- }
- $payload = self::prepareGoodsRow($row, $newShop, $kindMap, $map[$oldMasterId]);
- $created = GoodsClass::add($payload, true);
- $newId = intval($created->id ?? 0);
- if ($oldId > 0 && $newId > 0) {
- $map[$oldId] = $newId;
- }
- }
- return $map;
- }
- /**
- * 整理商品复制字段:归属改到新店,销量清零,种类指向新店 kind
- * @param array $row
- * @param array $newShop
- * @param array $kindMap
- * @param int $newMasterId
- * @return array
- */
- protected static function prepareGoodsRow($row, $newShop, $kindMap, $newMasterId)
- {
- $payload = self::prepareCopyRow($row, $newShop);
- $payload['masterId'] = intval($newMasterId);
- $payload['sold'] = 0;
- $payload['actualSold'] = 0;
- $payload['createTime'] = date('Y-m-d H:i:s');
- $oldKindId = intval($row['kindId'] ?? 0);
- if ($oldKindId > 0) {
- $payload['kindId'] = $kindMap[$oldKindId] ?? 0;
- }
- return $payload;
- }
- /**
- * 按商品/分类映射复制 xhGoodsCategory,并回写分类商品数量
- * @param int $tplMainId
- * @param array $newShop
- * @param array $goodsMap
- * @param array $categoryMap
- * @return void
- */
- protected static function copyGoodsCategories($tplMainId, $newShop, $goodsMap, $categoryMap)
- {
- if (empty($goodsMap) || empty($categoryMap)) {
- return;
- }
- $oldGoodsIds = array_keys($goodsMap);
- $list = GoodsCategoryClass::getAllByCondition([
- 'mainId' => $tplMainId,
- 'gId' => ['in', $oldGoodsIds],
- ], 'id ASC');
- if (empty($list)) {
- return;
- }
- $goodsNum = [];
- foreach ($list as $row) {
- $oldGoodsId = intval($row['gId'] ?? 0);
- $oldCatId = intval($row['cId'] ?? 0);
- if (!isset($goodsMap[$oldGoodsId]) || !isset($categoryMap[$oldCatId])) {
- continue;
- }
- $newCatId = $categoryMap[$oldCatId];
- $payload = self::prepareCopyRow($row, $newShop);
- $payload['gId'] = $goodsMap[$oldGoodsId];
- $payload['cId'] = $newCatId;
- GoodsCategoryClass::add($payload);
- $goodsNum[$newCatId] = ($goodsNum[$newCatId] ?? 0) + 1;
- }
- foreach ($goodsNum as $catId => $num) {
- CategoryClass::updateById($catId, ['goodsNum' => $num]);
- }
- }
- /**
- * 按商品/场景映射复制 xhGoodsUseCaseRelation
- * @param int $tplMainId
- * @param array $newShop
- * @param array $goodsMap
- * @param array $useCaseMap
- * @return void
- */
- protected static function copyUseCaseRelations($tplMainId, $newShop, $goodsMap, $useCaseMap)
- {
- if (empty($goodsMap) || empty($useCaseMap)) {
- return;
- }
- $oldGoodsIds = array_keys($goodsMap);
- $list = GoodsUseCaseClass::getAllByCondition([
- 'mainId' => $tplMainId,
- 'goodsId' => ['in', $oldGoodsIds],
- ], 'id ASC');
- if (empty($list)) {
- return;
- }
- foreach ($list as $row) {
- $oldGoodsId = intval($row['goodsId'] ?? 0);
- $oldUseCaseId = intval($row['useCaseId'] ?? 0);
- if (!isset($goodsMap[$oldGoodsId]) || !isset($useCaseMap[$oldUseCaseId])) {
- continue;
- }
- GoodsUseCaseClass::add([
- 'mainId' => $newShop['mainId'],
- 'goodsId' => $goodsMap[$oldGoodsId],
- 'useCaseId' => $useCaseMap[$oldUseCaseId],
- ]);
- }
- }
- /**
- * 复制模板店未删除的秒杀活动批次(秒杀按 mainId 共享,不按 shopId 过滤)
- * @param int $tplMainId
- * @param int $tplShopId
- * @param array $newShop
- * @return array 旧activityId => 新activityId
- */
- protected static function copySeckillActivities($tplMainId, $tplShopId, $newShop)
- {
- $map = [];
- $list = SeckillActivityClass::getAllByCondition([
- 'mainId' => $tplMainId,
- 'delStatus' => 0,
- ], 'id ASC');
- if (empty($list)) {
- return $map;
- }
- foreach ($list as $row) {
- $oldId = intval($row['id'] ?? 0);
- $payload = self::prepareCopyRow($row, $newShop);
- $created = SeckillActivityClass::add($payload, true);
- $newId = intval($created->id ?? 0);
- if ($oldId > 0 && $newId > 0) {
- $map[$oldId] = $newId;
- }
- }
- return $map;
- }
- /**
- * 复制秒杀商品版本,重写 activityId 与 goodsId
- * @param int $tplMainId
- * @param int $tplShopId
- * @param array $newShop
- * @param array $activityMap
- * @param array $goodsMap
- * @return void
- */
- protected static function copySeckillGoods($tplMainId, $tplShopId, $newShop, $activityMap, $goodsMap)
- {
- if (empty($activityMap) || empty($goodsMap)) {
- return;
- }
- $list = SeckillGoodsClass::getAllByCondition([
- 'mainId' => $tplMainId,
- 'delStatus' => 0,
- ], 'id ASC');
- if (empty($list)) {
- return;
- }
- foreach ($list as $row) {
- $oldActivityId = intval($row['activityId'] ?? 0);
- $oldGoodsId = intval($row['goodsId'] ?? 0);
- if (!isset($activityMap[$oldActivityId]) || !isset($goodsMap[$oldGoodsId])) {
- continue;
- }
- $payload = self::prepareCopyRow($row, $newShop);
- $payload['activityId'] = $activityMap[$oldActivityId];
- $payload['goodsId'] = $goodsMap[$oldGoodsId];
- SeckillGoodsClass::add($payload);
- }
- }
- /**
- * 复制首页模块配置,并把 JSON 里引用的商品/分类/场景 ID 换成新店 ID
- * @param int $tplShopId
- * @param array $newShop
- * @param array $goodsMap
- * @param array $categoryMap
- * @param array $useCaseMap
- * @return void
- */
- protected static function copyHomePageConfig($tplShopId, $newShop, $goodsMap, $categoryMap, $useCaseMap)
- {
- $list = HomePageModuleConfigClass::getAllByCondition(['shopId' => $tplShopId], 'id ASC');
- if (empty($list)) {
- return;
- }
- foreach ($list as $row) {
- $moduleKey = strval($row['moduleKey'] ?? '');
- if ($moduleKey === '') {
- continue;
- }
- $payload = json_decode($row['configValue'] ?? '', true);
- if (!is_array($payload)) {
- $payload = [];
- }
- $payload = self::remapHomePagePayload($moduleKey, $payload, $goodsMap, $categoryMap, $useCaseMap);
- HomePageModuleConfigClass::saveConfigValue($newShop['mainId'], $newShop['shopId'], $moduleKey, $payload);
- }
- }
- /**
- * 按 moduleKey 把首页配置里的关联 ID 换成新店数据
- * banner/navGrid 的 list[].type+value、hot/new/pullGoods 的 type+value 都可能指向商品/分类/场景
- * @param string $moduleKey
- * @param array $payload
- * @param array $goodsMap
- * @param array $categoryMap
- * @param array $useCaseMap
- * @return array
- */
- protected static function remapHomePagePayload($moduleKey, $payload, $goodsMap, $categoryMap, $useCaseMap)
- {
- if ($moduleKey === 'banner' || $moduleKey === 'navGrid') {
- if (empty($payload['list']) || !is_array($payload['list'])) {
- return $payload;
- }
- foreach ($payload['list'] as $index => $item) {
- if (!is_array($item)) {
- continue;
- }
- $type = intval($item['type'] ?? 0);
- $value = strval($item['value'] ?? '');
- $payload['list'][$index]['value'] = self::remapLinkedValue($type, $value, $goodsMap, $categoryMap, $useCaseMap);
- }
- return $payload;
- }
- if (in_array($moduleKey, ['hot', 'new', 'pullGoods'], true)) {
- $type = intval($payload['type'] ?? 0);
- $value = strval($payload['value'] ?? '');
- $payload['value'] = self::remapLinkedValue($type, $value, $goodsMap, $categoryMap, $useCaseMap);
- return $payload;
- }
- return $payload;
- }
- /**
- * 把关联配置的 value(单个 ID 或逗号拼接)按 type 映射到新店
- * type:2 商品、3 分类、4 使用场景;未映射到的 ID 丢弃,避免新店引用模板店数据
- * @param int $type
- * @param string $value
- * @param array $goodsMap
- * @param array $categoryMap
- * @param array $useCaseMap
- * @return string
- */
- protected static function remapLinkedValue($type, $value, $goodsMap, $categoryMap, $useCaseMap)
- {
- $map = [];
- if ($type === HomePageConfigClass::BANNER_TYPE_GOODS) {
- $map = $goodsMap;
- } elseif ($type === HomePageConfigClass::BANNER_TYPE_CATEGORY) {
- $map = $categoryMap;
- } elseif ($type === HomePageConfigClass::BANNER_TYPE_USE_CASE) {
- $map = $useCaseMap;
- } else {
- return $value;
- }
- $ids = array_filter(array_map('intval', explode(',', strval($value))));
- $mapped = [];
- foreach ($ids as $id) {
- if (isset($map[$id])) {
- $mapped[] = $map[$id];
- }
- }
- return implode(',', $mapped);
- }
- /**
- * 去掉主键与时间戳,并把归属字段改成新店
- * @param array $row
- * @param array $newShop
- * @param array $extraUnset 额外要丢掉的字段
- * @return array
- */
- protected static function prepareCopyRow($row, $newShop, $extraUnset = [])
- {
- unset($row['id'], $row['addTime'], $row['updateTime']);
- foreach ($extraUnset as $field) {
- unset($row[$field]);
- }
- $row['mainId'] = $newShop['mainId'];
- if (array_key_exists('shopId', $row)) {
- $row['shopId'] = $newShop['shopId'];
- }
- if (array_key_exists('sjId', $row)) {
- $row['sjId'] = $newShop['sjId'];
- }
- return $row;
- }
- }
|