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(全量查询,不加 field 限制,避免 ThinkPHP 软删除过滤导致查不到) $goodsRow = Db::name('Goods')->find($goodsId); $rawConfig = is_array($goodsRow) ? ($goodsRow['vr_goods_config'] ?? '') : ''; // 如果 DB 里没有( goodsRow 为空或 vr_goods_config 字段为空),fallback 到 params[data] if (empty($rawConfig)) { $rawConfig = $data['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 — 前端不发送 template_snapshot, // 当 template_snapshot 为空、或 selected_rooms 有值时,从 DB 重建 foreach ($configs as $i => &$config) { $templateId = intval($config['template_id'] ?? 0); $selectedRooms = $config['selected_rooms'] ?? []; // 条件:snapshot 为空,或者前端有 selected_rooms if ($templateId > 0 && (!empty($selectedRooms) || empty($config['template_snapshot']) || empty($config['template_snapshot']['rooms']))) { $template = Db::name('vr_seat_templates')->find($templateId); $seatMap = json_decode($template['seat_map'] ?? '{}', true); $allRooms = $seatMap['rooms'] ?? []; // 注意:v3 格式 room.id 可能为空(用数组索引代替 id), // 此时 room_0 对应 rooms[0],room_1 对应 rooms[1],以此类推 // ── v1→v3 兼容迁移 ── if (empty($allRooms) && !empty($seatMap['sections'])) { $v1Sections = $seatMap['sections'] ?? []; $v1Map = $seatMap['map'] ?? []; $v1Seats = $seatMap['seats'] ?? []; $v1RoomId = $selectedRooms[0] ?? 'room_1'; $allRooms = [[ 'id' => $v1RoomId, 'name' => $seatMap['venue']['name'] ?? '主馆', 'sections' => $v1Sections, 'map' => $v1Map, 'seats' => $v1Seats, ]]; } // 按 selected_rooms 过滤(支持前端标准化的 "room_0" 格式双向兼容) // 注意:v3 格式 room.id 可能为空(用数组索引代替 id), // 此时 room_0 对应 rooms[0],room_1 对应 rooms[1],以此类推 $selectedRoomIds = array_column( array_filter($allRooms, function ($r) use ($selectedRooms) { $rid = $r['id'] ?? ''; // 直接匹配 if (in_array($rid, $selectedRooms)) { return true; } // 尝试加/减 "room_" 前缀匹配(PHP 7.x 兼容) if (strpos($rid, 'room_') === 0 && in_array(substr($rid, 5), $selectedRooms)) { return true; } if (!empty($rid) && in_array('room_' . $rid, $selectedRooms)) { return true; } // 空 id:用数组索引替代(room_0→rooms[0], room_1→rooms[1]) static $roomIndex = -1; $roomIndex++; if ($rid === '' && in_array('room_' . $roomIndex, $selectedRooms)) { return true; } return false; }), 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]; } }