0]; } // ────────────────────────────────────────────────────── // 时机 2:plugins_service_goods_save_thing_end(事务内,goods 已落表) // 关键:此时 GoodsSpecificationsInsert + GoodsSaveBaseUpdate // 已经执行完毕(它们处理的是表单原生规格数据)。 // // 对于票务商品,我们需要: // a) 删除原生流程产生的所有 spec 数据(表单垃圾) // b) 用 BatchGenerate 重新生成 VR 座位级 SKU // c) 回写 GoodsSpecType.value(让后台正确展示) // d) 重新计算 goods 表的 price/inventory // ────────────────────────────────────────────────────── if ($hookName === 'plugins_service_goods_save_thing_end') { $data = $params['data'] ?? []; $goodsId = $params['goods_id'] ?? 0; if ($goodsId > 0 && ($data['item_type'] ?? '') === 'ticket') { // 直接从数据库读 vr_goods_config(避免 $params['data'] 值拷贝的引用链问题) $goodsRow = Db::name('Goods')->field('vr_goods_config')->find($goodsId); $rawConfig = $goodsRow['vr_goods_config'] ?? ''; if (!empty($rawConfig)) { $configs = json_decode($rawConfig, true); if (json_last_error() !== JSON_ERROR_NONE) { $configs = null; } if (is_array($configs) && !empty($configs)) { // 0) 填充 template_snapshot(前端没传、或 rooms 为空时兜底从 vr_seat_templates 读) foreach ($configs as $i => &$config) { if (empty($config['template_snapshot']) || empty($config['template_snapshot']['rooms'])) { $templateId = intval($config['template_id'] ?? 0); if ($templateId > 0) { $template = Db::name('vr_seat_templates')->find($templateId); if (empty($template)) { continue; } $seatMap = json_decode($template['seat_map'] ?? '{}', true); $allRooms = $seatMap['rooms'] ?? []; // ── v1→v3 兼容迁移 ── // v1 旧格式没有 rooms 嵌套,只有 sections+map 扁平结构 if (empty($allRooms) && !empty($seatMap['sections'])) { $v1Sections = $seatMap['sections'] ?? []; $v1Map = $seatMap['map'] ?? []; $v1Seats = $seatMap['seats'] ?? []; $v1RoomId = $config['selected_rooms'][0] ?? 'room_1'; $allRooms = [[ 'id' => $v1RoomId, 'name' => $seatMap['venue']['name'] ?? '主馆', 'sections' => $v1Sections, 'map' => $v1Map, 'seats' => $v1Seats, ]]; } // 按 selected_rooms 过滤,只存用户选中的房间 $selectedRoomIds = array_column( array_filter($allRooms, function ($r) use ($config) { return isset($r['id']) && in_array($r['id'], $config['selected_rooms'] ?? []); }), null ); $config['template_snapshot'] = [ 'venue' => $seatMap['venue'] ?? [], 'rooms' => $selectedRoomIds, ]; } } } unset($config); // 解除引用,避免后续误改 // 将填充后的完整 config 写回 goods 表 Db::name('Goods')->where('id', $goodsId)->update([ 'vr_goods_config' => json_encode($configs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); // a) 清空原生规格数据 —— 避免列偏移 Db::name('GoodsSpecType')->where('goods_id', $goodsId)->delete(); Db::name('GoodsSpecBase')->where('goods_id', $goodsId)->delete(); Db::name('GoodsSpecValue')->where('goods_id', $goodsId)->delete(); // b) 逐模板生成 VR SKU(ensureAndFillVrSpecTypes 在内部调用,type.value 同步写入) foreach ($configs as $config) { $templateId = intval($config['template_id'] ?? 0); $selectedRooms = $config['selected_rooms'] ?? []; $selectedSections = $config['selected_sections'] ?? []; $sessions = $config['sessions'] ?? []; if ($templateId > 0) { $res = SeatSkuService::BatchGenerate( $goodsId, $templateId, $selectedRooms, $selectedSections, $sessions ); if ($res['code'] !== 0) { return $res; } } } // c) 重新计算 goods.price / goods.inventory SeatSkuService::refreshGoodsBase($goodsId); } } } return ['code' => 0]; } return ['code' => 0]; } }