feat(会员系统): 添加从预约记录转换为VIP客户功能
- 在bookings.php中添加转换为VIP客户的按钮 - 在vip.php中实现从预约记录转换VIP客户的逻辑 - 添加VIP客户转换界面和批量转换功能 - 新增get_package.php用于获取套餐信息 - 优化会员类型自动转换逻辑
This commit is contained in:
@@ -263,6 +263,24 @@ try {
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openEditModal(<?php echo $booking['id']; ?>, '<?php echo date('Y-m-d\TH:i', strtotime($booking['start_time'])); ?>', '<?php echo date('Y-m-d\TH:i', strtotime($booking['end_time'])); ?>')">修改预约时间</button>
|
||||
<br>
|
||||
<?php endif; ?>
|
||||
<!-- 转换为VIP客户按钮 -->
|
||||
<?php
|
||||
// 检查该客户是否已经是VIP
|
||||
$stmt_check_vip = $pdo->prepare("SELECT id FROM vip_customers WHERE phone = ? AND car_number = ? AND is_active = 1");
|
||||
$stmt_check_vip->execute([$booking['phone'], $booking['car_number']]);
|
||||
$is_vip = $stmt_check_vip->fetch() !== false;
|
||||
?>
|
||||
<?php if (!$is_vip && $booking['member_type'] === '普通客户'): ?>
|
||||
<form method="POST" action="vip.php" style="display: inline; margin-top: 10px;">
|
||||
<input type="hidden" name="action" value="convert_from_booking">
|
||||
<input type="hidden" name="booking_id" value="<?php echo $booking['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm" style="background-color: #ffd700; color: #333; font-weight: bold;"
|
||||
onclick="return confirm('确定要将该客户转换为VIP客户吗?\n\n客户:<?php echo htmlspecialchars($booking['customer_name']); ?>\n手机号:<?php echo htmlspecialchars($booking['phone']); ?>\n车牌号:<?php echo htmlspecialchars($booking['car_number']); ?>')">
|
||||
👑 转换为VIP客户
|
||||
</button>
|
||||
</form>
|
||||
<br>
|
||||
<?php endif; ?>
|
||||
<!-- 复制预约信息按钮 -->
|
||||
<?php
|
||||
// 计算服务时长(分钟)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
require_once 'db_connect.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(['error' => '无效的套餐ID'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT * FROM packages WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$package = $stmt->fetch();
|
||||
|
||||
if (!$package) {
|
||||
echo json_encode(['error' => '套餐不存在'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode($package, JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => '获取套餐信息失败:' . $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -32,11 +32,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$phone = $vip_customer['phone'];
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'index.php:28', 'message' => 'VIP customer data', 'data' => ['vip_id' => $vip_id, 'has_car_model' => isset($vip_customer['car_model']), 'has_car_number' => isset($vip_customer['car_number'])], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'A']);
|
||||
// 确保日志目录存在
|
||||
$log_dir = '.cursor';
|
||||
if (!file_exists($log_dir)) {
|
||||
mkdir($log_dir, 0777, true);
|
||||
}
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
// VIP客户信息优先,但允许通过POST覆盖(如果用户想修改)
|
||||
@@ -121,11 +116,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// 两个时间段重叠的条件:现有预约的开始时间 < 新预约的结束时间 AND 现有预约的结束时间 > 新预约的开始时间
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'index.php:98', 'message' => 'Checking time conflict', 'data' => ['start_time' => $start_time, 'end_time' => $end_time, 'duration' => $duration], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'F']);
|
||||
// 确保日志目录存在
|
||||
$log_dir = '.cursor';
|
||||
if (!file_exists($log_dir)) {
|
||||
mkdir($log_dir, 0777, true);
|
||||
}
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings
|
||||
@@ -136,11 +126,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$conflict_count = $stmt->fetchColumn();
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'index.php:107', 'message' => 'Time conflict check result', 'data' => ['conflict_count' => $conflict_count], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'F']);
|
||||
// 确保日志目录存在
|
||||
$log_dir = '.cursor';
|
||||
if (!file_exists($log_dir)) {
|
||||
mkdir($log_dir, 0777, true);
|
||||
}
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
|
||||
@@ -148,6 +133,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
throw new Exception('该时间段已被预约,请选择其他时间');
|
||||
}
|
||||
|
||||
// 检查客户是否有历史预约记录,如果有则自动转为普通客户
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'index.php:136', 'message' => 'Checking customer booking history', 'data' => ['phone' => $phone, 'car_number' => $car_number, 'current_member_type' => $member_type], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'I']);
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
// 检查该手机号和车牌号组合是否有历史预约记录
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings
|
||||
WHERE phone = ? AND car_number = ?");
|
||||
$stmt->execute([$phone, $car_number]);
|
||||
$has_booking_history = $stmt->fetchColumn() > 0;
|
||||
|
||||
// 如果客户有历史预约记录,自动转为普通客户(除非是VIP客户模式)
|
||||
if ($has_booking_history && $member_type === 'VIP会员' && $customer_type !== 'vip') {
|
||||
$member_type = '普通客户';
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'index.php:144', 'message' => 'Auto converted to regular customer', 'data' => ['reason' => 'has_booking_history', 'phone' => $phone, 'car_number' => $car_number], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'I']);
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
}
|
||||
|
||||
// 插入预约记录
|
||||
// 对于0元订单,自动标记为已付款
|
||||
$payment_status = ($total_price <= 0) ? '已付款' : '未付款';
|
||||
@@ -231,11 +236,6 @@ foreach ($all_bookings as $booking) {
|
||||
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'index.php:196', 'message' => 'Checking cross-day booking', 'data' => ['booking_date' => $booking_date, 'start_time' => $booking_start_time, 'end_time' => $booking_end_time, 'start_ts' => $start_timestamp, 'end_ts' => $end_timestamp], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'G']);
|
||||
// 确保日志目录存在
|
||||
$log_dir = '.cursor';
|
||||
if (!file_exists($log_dir)) {
|
||||
mkdir($log_dir, 0777, true);
|
||||
}
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
|
||||
|
||||
+1117
-234
File diff suppressed because it is too large
Load Diff
@@ -94,6 +94,56 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$stmt = $pdo->prepare("DELETE FROM vip_customers WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$success_message = "VIP客户删除成功!";
|
||||
|
||||
} elseif ($action === 'convert_from_booking') {
|
||||
// 从预约记录转换为VIP客户
|
||||
$booking_id = isset($_POST['booking_id']) ? (int)$_POST['booking_id'] : 0;
|
||||
|
||||
if ($booking_id <= 0) {
|
||||
throw new Exception('无效的预约ID');
|
||||
}
|
||||
|
||||
// 获取预约信息
|
||||
$stmt = $pdo->prepare("SELECT customer_name, phone, car_model, car_number FROM bookings WHERE id = ?");
|
||||
$stmt->execute([$booking_id]);
|
||||
$booking = $stmt->fetch();
|
||||
|
||||
if (!$booking) {
|
||||
throw new Exception('预约记录不存在');
|
||||
}
|
||||
|
||||
// 检查是否已经是VIP客户
|
||||
$stmt = $pdo->prepare("SELECT id FROM vip_customers WHERE phone = ? AND car_number = ?");
|
||||
$stmt->execute([$booking['phone'], $booking['car_number']]);
|
||||
if ($stmt->fetch()) {
|
||||
throw new Exception('该客户已经是VIP客户');
|
||||
}
|
||||
|
||||
// 插入VIP客户
|
||||
$stmt = $pdo->prepare("INSERT INTO vip_customers
|
||||
(customer_name, phone, car_model, car_number, notes)
|
||||
VALUES (?, ?, ?, ?, ?)");
|
||||
try {
|
||||
$notes = '从预约记录 #' . $booking_id . ' 自动转换';
|
||||
$stmt->execute([
|
||||
$booking['customer_name'],
|
||||
$booking['phone'],
|
||||
$booking['car_model'] ?? '',
|
||||
$booking['car_number'] ?? '',
|
||||
$notes
|
||||
]);
|
||||
|
||||
// 更新该客户的所有预约记录的member_type为VIP会员
|
||||
$stmt = $pdo->prepare("UPDATE bookings SET member_type = 'VIP会员' WHERE phone = ? AND car_number = ?");
|
||||
$stmt->execute([$booking['phone'], $booking['car_number']]);
|
||||
|
||||
$success_message = "客户已成功转换为VIP客户!";
|
||||
} catch (PDOException $e) {
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
throw new Exception('该客户已经是VIP客户');
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
@@ -248,6 +298,64 @@ try {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 从预约转换为VIP -->
|
||||
<div class="card">
|
||||
<h2>📋 从预约记录转换为VIP客户</h2>
|
||||
<p style="color: #666; margin-bottom: 15px;">选择预约记录,快速将普通客户转换为VIP客户</p>
|
||||
<?php
|
||||
// 获取可以转换为VIP的普通客户预约记录(最近30天,且不是VIP客户)
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT DISTINCT b.id, b.customer_name, b.phone, b.car_model, b.car_number,
|
||||
COUNT(*) as booking_count,
|
||||
MAX(b.start_time) as last_booking_time
|
||||
FROM bookings b
|
||||
LEFT JOIN vip_customers v ON b.phone = v.phone AND b.car_number = v.car_number AND v.is_active = 1
|
||||
WHERE b.member_type = '普通客户'
|
||||
AND v.id IS NULL
|
||||
AND b.start_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
GROUP BY b.phone, b.car_number, b.customer_name, b.car_model
|
||||
ORDER BY last_booking_time DESC
|
||||
LIMIT 20");
|
||||
$stmt->execute();
|
||||
$convertible_bookings = $stmt->fetchAll();
|
||||
} catch (Exception $e) {
|
||||
$convertible_bookings = [];
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (empty($convertible_bookings)): ?>
|
||||
<div class="empty-message">暂无可转换的普通客户</div>
|
||||
<?php else: ?>
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
<?php foreach ($convertible_bookings as $booking): ?>
|
||||
<div style="padding: 12px; border: 1px solid #e0e0e0; border-radius: 6px; margin-bottom: 10px; background: #f9f9f9;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="flex: 1;">
|
||||
<strong><?php echo htmlspecialchars($booking['customer_name']); ?></strong><br>
|
||||
<span style="color: #666; font-size: 0.9em;">
|
||||
手机号:<?php echo htmlspecialchars($booking['phone']); ?> |
|
||||
车牌号:<?php echo htmlspecialchars($booking['car_number']); ?><br>
|
||||
<?php if ($booking['car_model']): ?>
|
||||
车型:<?php echo htmlspecialchars($booking['car_model']); ?> |
|
||||
<?php endif; ?>
|
||||
预约次数:<?php echo $booking['booking_count']; ?>次
|
||||
</span>
|
||||
</div>
|
||||
<form method="POST" style="margin-left: 15px;">
|
||||
<input type="hidden" name="action" value="convert_from_booking">
|
||||
<input type="hidden" name="booking_id" value="<?php echo $booking['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm" style="background-color: #ffd700; color: #333; font-weight: bold;"
|
||||
onclick="return confirm('确定要将 <?php echo htmlspecialchars($booking['customer_name']); ?> 转换为VIP客户吗?')">
|
||||
👑 转为VIP
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- VIP客户列表 -->
|
||||
<div class="card">
|
||||
<h2>👑 VIP客户列表 (共 <?php echo count($vip_customers); ?> 位)</h2>
|
||||
@@ -416,11 +524,6 @@ try {
|
||||
// 查询所有可用的洗车套餐
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'vip.php:417', 'message' => 'Loading packages', 'data' => ['using_pdo' => isset($pdo), 'using_conn' => isset($conn)], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'B']);
|
||||
// 确保日志目录存在
|
||||
$log_dir = '.cursor';
|
||||
if (!file_exists($log_dir)) {
|
||||
mkdir($log_dir, 0777, true);
|
||||
}
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user