2026-04-18 21:46:37 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
namespace app\plugins\vr_ticket\hook;
|
|
|
|
|
|
|
|
|
|
|
|
use think\facade\Db;
|
|
|
|
|
|
use app\plugins\vr_ticket\service\SeatSkuService;
|
|
|
|
|
|
|
|
|
|
|
|
class AdminGoodsSaveHandle
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 商品保存钩子(同时响应 save_handle 和 save_thing_end 两个时机)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function handle($params = [])
|
|
|
|
|
|
{
|
|
|
|
|
|
$hookName = $params['hook_name'] ?? '';
|
|
|
|
|
|
|
|
|
|
|
|
// ──────────────────────────────────────────────────────
|
|
|
|
|
|
// 时机 1:plugins_service_goods_save_handle(事务前,修改待保存数据)
|
|
|
|
|
|
// - 把 vr_goods_config base64 解码写入 $data
|
|
|
|
|
|
// - 设置 item_type
|
|
|
|
|
|
// - 强制 is_exist_many_spec = 1
|
|
|
|
|
|
// ──────────────────────────────────────────────────────
|
|
|
|
|
|
if ($hookName === 'plugins_service_goods_save_handle') {
|
|
|
|
|
|
$postParams = $params['params'] ?? [];
|
|
|
|
|
|
|
|
|
|
|
|
if (isset($postParams['vr_is_ticket']) && $postParams['vr_is_ticket'] == 1) {
|
|
|
|
|
|
$params['data']['item_type'] = 'ticket';
|
|
|
|
|
|
$params['data']['is_exist_many_spec'] = 1;
|
|
|
|
|
|
|
|
|
|
|
|
$base64Config = $postParams['vr_goods_config_base64'] ?? '';
|
|
|
|
|
|
if (!empty($base64Config)) {
|
|
|
|
|
|
$jsonStr = base64_decode($base64Config);
|
|
|
|
|
|
if ($jsonStr !== false) {
|
|
|
|
|
|
$params['data']['vr_goods_config'] = $jsonStr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$params['data']['item_type'] = 'normal';
|
|
|
|
|
|
$params['data']['vr_goods_config'] = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ['code' => 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') {
|
2026-04-20 04:25:02 +00:00
|
|
|
|
// 直接从数据库读 vr_goods_config(全量查询,不加 field 限制,避免 ThinkPHP 软删除过滤导致查不到)
|
2026-04-20 14:42:41 +00:00
|
|
|
|
// 前端已过滤无效 template_id,优先使用 data。若无前端数据再 fallback 到 DB
|
|
|
|
|
|
if (!empty($data['vr_goods_config'])) {
|
|
|
|
|
|
$rawConfig = $data['vr_goods_config'];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$rawConfig = is_array($goodsRow) ? ($goodsRow['vr_goods_config'] ?? '') : '';
|
2026-04-20 04:25:02 +00:00
|
|
|
|
}
|
2026-04-20 04:23:58 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
if (!empty($rawConfig)) {
|
|
|
|
|
|
$configs = json_decode($rawConfig, true);
|
2026-04-20 03:28:34 +00:00
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
|
|
|
|
$configs = null;
|
|
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
|
2026-04-20 05:31:17 +00:00
|
|
|
|
if (is_array($configs) && !empty($configs)) {
|
2026-04-20 04:13:29 +00:00
|
|
|
|
// 0) 重建 template_snapshot — 前端不发送 template_snapshot,
|
|
|
|
|
|
// 当 template_snapshot 为空、或 selected_rooms 有值时,从 DB 重建
|
2026-04-20 01:36:06 +00:00
|
|
|
|
foreach ($configs as $i => &$config) {
|
2026-04-20 04:13:29 +00:00
|
|
|
|
$templateId = intval($config['template_id'] ?? 0);
|
|
|
|
|
|
$selectedRooms = $config['selected_rooms'] ?? [];
|
2026-04-20 01:36:06 +00:00
|
|
|
|
|
2026-04-20 04:13:29 +00:00
|
|
|
|
// 条件: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);
|
2026-04-20 06:32:38 +00:00
|
|
|
|
|
|
|
|
|
|
// 模板不存在时(硬删除场景):
|
2026-04-20 14:42:41 +00:00
|
|
|
|
// - 移除无效 config 块,避免脏数据写回
|
2026-04-20 06:32:38 +00:00
|
|
|
|
// - 前端下次打开时将看到选单为空,用户可重新选择或清空配置
|
|
|
|
|
|
if (empty($template)) {
|
2026-04-20 14:42:41 +00:00
|
|
|
|
unset($configs[$i]); // 移除无效 config 块
|
2026-04-20 06:32:38 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-20 04:13:29 +00:00
|
|
|
|
$seatMap = json_decode($template['seat_map'] ?? '{}', true);
|
|
|
|
|
|
$allRooms = $seatMap['rooms'] ?? [];
|
2026-04-20 05:31:17 +00:00
|
|
|
|
// 注意:v3 格式 room.id 可能为空(用数组索引代替 id),
|
|
|
|
|
|
// 此时 room_0 对应 rooms[0],room_1 对应 rooms[1],以此类推
|
2026-04-20 04:13:29 +00:00
|
|
|
|
// ── 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,
|
|
|
|
|
|
]];
|
2026-04-20 01:36:06 +00:00
|
|
|
|
}
|
2026-04-20 04:13:29 +00:00
|
|
|
|
|
|
|
|
|
|
// 按 selected_rooms 过滤(支持前端标准化的 "room_0" 格式双向兼容)
|
2026-04-20 04:42:46 +00:00
|
|
|
|
// 注意:v3 格式 room.id 可能为空(用数组索引代替 id),
|
|
|
|
|
|
// 此时 room_0 对应 rooms[0],room_1 对应 rooms[1],以此类推
|
2026-04-20 04:13:29 +00:00
|
|
|
|
$selectedRoomIds = array_column(
|
2026-04-20 05:31:17 +00:00
|
|
|
|
array_filter($allRooms, function ($r) use ($selectedRooms) {
|
2026-04-20 04:13:29 +00:00
|
|
|
|
$rid = $r['id'] ?? '';
|
2026-04-20 04:42:46 +00:00
|
|
|
|
// 直接匹配
|
2026-04-20 04:13:29 +00:00
|
|
|
|
if (in_array($rid, $selectedRooms)) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 尝试加/减 "room_" 前缀匹配(PHP 7.x 兼容)
|
|
|
|
|
|
if (strpos($rid, 'room_') === 0 && in_array(substr($rid, 5), $selectedRooms)) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-04-20 04:42:46 +00:00
|
|
|
|
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)) {
|
2026-04-20 04:13:29 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}), null
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$config['template_snapshot'] = [
|
|
|
|
|
|
'venue' => $seatMap['venue'] ?? [],
|
|
|
|
|
|
'rooms' => $selectedRoomIds,
|
|
|
|
|
|
];
|
2026-04-20 01:36:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
unset($config); // 解除引用,避免后续误改
|
2026-04-20 14:42:41 +00:00
|
|
|
|
$configs = array_values($configs); // 重排数组索引
|
2026-04-20 01:36:06 +00:00
|
|
|
|
|
2026-04-20 14:42:41 +00:00
|
|
|
|
// 将填充后的完整 config 写回 goods 表(仅在有有效配置时)
|
|
|
|
|
|
if (!empty($configs)) {
|
|
|
|
|
|
Db::name('Goods')->where('id', $goodsId)->update([
|
|
|
|
|
|
'vr_goods_config' => json_encode($configs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
2026-04-20 01:36:06 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 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];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|