From dbcaaa33fdd45c704fd40926df0a542556e8e4f2 Mon Sep 17 00:00:00 2001
From: 刘帅 <rr.iii@qq.com>
Date: 星期五, 18 九月 2026 17:15:09 +0800
Subject: [PATCH] feat:门店店长手机号列表展示问题

---
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/BallController.java |  356 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 288 insertions(+), 68 deletions(-)

diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/BallController.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/BallController.java
index 479dd69..e4b3514 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/BallController.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/BallController.java
@@ -2,6 +2,7 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.dsh.course.dto.JsDto;
 import com.dsh.course.dto.StudentQeryDto;
 import com.dsh.course.entity.SiteLock;
@@ -23,12 +24,17 @@
 import com.dsh.guns.config.UserExt;
 import com.dsh.guns.core.base.controller.BaseController;
 import com.dsh.guns.core.util.ToolUtil;
+import com.dsh.guns.modular.system.audit.ManagementAuditWriter;
 import com.dsh.guns.modular.system.controller.util.MD5;
 import com.dsh.guns.modular.system.model.*;
 import com.dsh.guns.modular.system.model.dto.*;
 import com.dsh.guns.modular.system.service.*;
 import com.dsh.guns.modular.system.util.*;
 import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,8 +47,12 @@
 
 import javax.annotation.Resource;
 import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletResponse;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.math.BigDecimal;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -50,6 +60,8 @@
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 @Controller
@@ -90,6 +102,9 @@
     @Autowired
     private AppUserClient appUserClient;
 
+    @Resource
+    private ManagementAuditWriter auditWriter;
+
     @RequestMapping("/tolist")
     public String tolist(Model model) {
         List<TCity> list = cityService.list(new LambdaQueryWrapper<TCity>().eq(TCity::getParentId, 0));
@@ -114,15 +129,93 @@
     @RequestMapping("/selectJoin")
     @ResponseBody
     public ResultUtil join(@RequestBody MethodDto method) {
+        if (method == null || method.getId() == null) {
+            return ResultUtil.error("门票记录不能为空");
+        }
+        SiteBooking before = iSiteBookingService.getById(method.getId());
+        if (before == null) {
+            return ResultUtil.error("门票记录不存在");
+        }
+        Integer previousStatus = before.getStatus();
+        if (!Objects.equals(previousStatus, 1) && !Objects.equals(previousStatus, 4)) {
+            return ResultUtil.error("当前门票状态不能确认到店");
+        }
 
-        System.out.println("============" + method);
+        // 待核销正常确认到店;已过期但运营确认用户实际入场时,直接人工完成。
+        Integer targetStatus = Objects.equals(previousStatus, 4) ? 3 : 2;
         SiteBooking siteBooking = new SiteBooking();
-        siteBooking.setStatus(2);
+        siteBooking.setStatus(targetStatus);
         siteBooking.setId(method.getId());
         siteBooking.setGoType(method.getMethod());
-        iSiteBookingService.updateById(siteBooking);
+        try {
+            // 以修改前状态作为条件,避免并发操作覆盖其他状态变更。
+            boolean updated = iSiteBookingService.update(siteBooking,
+                    new LambdaUpdateWrapper<SiteBooking>()
+                            .eq(SiteBooking::getId, before.getId())
+                            .eq(SiteBooking::getStatus, previousStatus));
+            if (!updated) {
+                appendTicketStatusAudit(before, targetStatus, method.getMethod(), "FAIL", "门票状态更新失败");
+                return ResultUtil.error("确认到店失败");
+            }
+            appendTicketStatusAudit(before, targetStatus, method.getMethod(), "SUCCESS", null);
+        } catch (RuntimeException ex) {
+            appendTicketStatusAudit(before, targetStatus, method.getMethod(), "FAIL", ex.getMessage());
+            throw ex;
+        }
 
         return new ResultUtil<>(0, 0, "到店成功", null, null);
+    }
+
+    /** 记录运营管理员人工确认到店导致的门票状态变更。 */
+    private void appendTicketStatusAudit(SiteBooking before, Integer targetStatus, Integer goType,
+                                         String result, String errorMessage) {
+        try {
+            Map<String, Object> beforeData = new HashMap<>();
+            beforeData.put("id", before.getId());
+            beforeData.put("orderNo", before.getOrderNo());
+            beforeData.put("status", before.getStatus());
+            beforeData.put("statusName", ticketStatusName(before.getStatus()));
+
+            Map<String, Object> afterData = new HashMap<>(beforeData);
+            afterData.put("status", targetStatus);
+            afterData.put("statusName", ticketStatusName(targetStatus));
+            afterData.put("goType", goType);
+
+            Map<String, Object> audit = new HashMap<>();
+            audit.put("traceId", UUID.randomUUID().toString().replace("-", ""));
+            audit.put("sourceService", "mb-cloud-management");
+            audit.put("sourceType", "HTTP");
+            audit.put("operationType", "STATUS_CHANGE");
+            audit.put("riskLevel", "CRITICAL");
+            audit.put("operationName", "运营管理员人工修改门票状态");
+            audit.put("requestMethod", "POST");
+            audit.put("requestUri", "/ball/selectJoin");
+            audit.put("requestParams", "id=" + before.getId() + ", goType=" + goType + ", targetStatus=" + targetStatus);
+            audit.put("beforeData", com.alibaba.fastjson.JSONObject.toJSONString(beforeData));
+            audit.put("afterData", com.alibaba.fastjson.JSONObject.toJSONString(afterData));
+            audit.put("userId", UserExt.getUser() == null ? null : UserExt.getUser().getId());
+            audit.put("userAccount", UserExt.getUser() == null ? null : UserExt.getUser().getAccount());
+            audit.put("result", result);
+            audit.put("errorMessage", errorMessage);
+            // 审计表的 createdAt 即本次管理员操作时间。
+            audit.put("createdAt", new Date());
+            auditWriter.append(audit);
+        } catch (Exception ignored) {
+            // 审计失败不能阻断实际到店确认,网关日志可作为兜底。
+        }
+    }
+
+    private String ticketStatusName(Integer status) {
+        if (status == null) return "未知";
+        switch (status) {
+            case 0: return "待支付";
+            case 1: return "待核销";
+            case 2: return "已到店";
+            case 3: return "已完成";
+            case 4: return "已过期";
+            case 5: return "已取消";
+            default: return "未知";
+        }
     }
 
 
@@ -898,73 +991,81 @@
         }
         System.out.println("========strings1=====" + strings1);
 
-//时间段
-        List<String> strings = new ArrayList<>();
-//        List<SiteBooking> siteBookings = siteClient.listBooks(book.getSiteId());
-        List<SiteBooking> siteBookings = iSiteBookingService.list(new QueryWrapper<SiteBooking>().eq("siteId", book.getSiteId()).like("times", split1[0].substring(0, 10)));
-        System.out.println("=======siteBookings======" + siteBookings);
-        for (SiteBooking siteBooking : siteBookings) {
-            String[] split = siteBooking.getTimes().split(";");
-            for (String s : split) {
-
-                String convertedValue = s.substring(11);
-                strings.add(convertedValue);
-            }
-
-        }
-        System.out.println("======strings=======" + strings);
+////时间段
+//        List<String> strings = new ArrayList<>();
+////        List<SiteBooking> siteBookings = siteClient.listBooks(book.getSiteId());
+//        List<SiteBooking> siteBookings = iSiteBookingService.list(new QueryWrapper<SiteBooking>().eq("siteId", book.getSiteId()).like("times", split1[0].substring(0, 10)));
+//        System.out.println("=======siteBookings======" + siteBookings);
+//        for (SiteBooking siteBooking : siteBookings) {
+//            String[] split = siteBooking.getTimes().split(";");
+//            for (String s : split) {
+//
+//                String convertedValue = s.substring(11);
+//                strings.add(convertedValue);
+//            }
+//
+//        }
+//        System.out.println("======strings=======" + strings);
 
 
         List<OrderDto> timeRanges = new ArrayList<>();
 
         TSite site = siteService.getById(book.getSiteId());
         System.out.println("================" + site);
-        String appointmentStartTime = site.getAppointmentStartTime();
-        String appointmentEndTime = site.getAppointmentEndTime();
-        //生成用于比较日期是否超过的当前日期
-        LocalDate currentDate = LocalDate.now();
-        LocalDate parsedDate = LocalDate.parse(split1[0].substring(0, 10), DateTimeFormatter.ISO_DATE);
-        boolean isAfterCurrentDate = parsedDate.isBefore(currentDate);
-        System.out.println("是否超过当前日期" + isAfterCurrentDate);
-        String currentTime = appointmentStartTime;
-        while (currentTime.compareTo(appointmentEndTime) < 0) {
-            String nextTime = getNextTime(currentTime);
-
-
-            String timeRange = currentTime + "-" + nextTime;
-            OrderDto orderDto = new OrderDto();
-            orderDto.setTime(timeRange);
-
-            LocalTime currentTime1 = LocalTime.now();
-            LocalTime targetTime = LocalTime.parse(currentTime);
-
-            boolean hasExceeded = currentTime1.isAfter(targetTime);
-            //是否超过当前时间
-            if (hasExceeded) {
-                orderDto.setState(0);
-            }
-            //是否已经预定
-            if (strings.contains(timeRange)) {
-                orderDto.setState(0);
-            }
-            //日期是否超过
-            if (isAfterCurrentDate) {
-                orderDto.setState(0);
-            }
-            //是否是当前场地当前日期预定的
-            if (strings1.contains(timeRange)) {
-                orderDto.setState(1);
-            }
-
-
-            timeRanges.add(orderDto);
-            currentTime = nextTime;
-        }
+//        String appointmentStartTime = site.getAppointmentStartTime();
+//        String appointmentEndTime = site.getAppointmentEndTime();
+//        //生成用于比较日期是否超过的当前日期
+//        LocalDate currentDate = LocalDate.now();
+//        LocalDate parsedDate = LocalDate.parse(split1[0].substring(0, 10), DateTimeFormatter.ISO_DATE);
+//        boolean isAfterCurrentDate = parsedDate.isBefore(currentDate);
+//        System.out.println("是否超过当前日期" + isAfterCurrentDate);
+//        String currentTime = appointmentStartTime;
+//        while (currentTime.compareTo(appointmentEndTime) < 0) {
+//            String nextTime = getNextTime(currentTime);
+//
+//
+//            String timeRange = currentTime + "-" + nextTime;
+//            OrderDto orderDto = new OrderDto();
+//            orderDto.setTime(timeRange);
+//
+//            LocalTime currentTime1 = LocalTime.now();
+//            LocalTime targetTime = LocalTime.parse(currentTime);
+//
+//            boolean hasExceeded = currentTime1.isAfter(targetTime);
+//            //是否超过当前时间
+//            if (hasExceeded) {
+//                orderDto.setState(0);
+//            }
+//            //是否已经预定
+//            if (strings.contains(timeRange)) {
+//                orderDto.setState(0);
+//            }
+//            //日期是否超过
+//            if (isAfterCurrentDate) {
+//                orderDto.setState(0);
+//            }
+//            //是否是当前场地当前日期预定的
+//            if (strings1.contains(timeRange)) {
+//                orderDto.setState(1);
+//            }
+//            timeRanges.add(orderDto);
+//            currentTime = nextTime;
+//        }
         System.out.println("-------------" + timeRanges);
         model.addAttribute("timeRanges", timeRanges);
         System.out.println("======timeRanges========" + timeRanges);
         Integer objectType = UserExt.getUser().getObjectType();
         model.addAttribute("province", book.getProvince());
+        String yuyueTime = "";
+        if (strings1.size()>1){
+            String start = strings1.get(0).split("-")[0];
+            String end = strings1.get(strings1.size()-1).split("-")[1];
+            yuyueTime = start +"-"+end;
+        }else{
+            yuyueTime = strings1.get(0);
+        }
+        model.addAttribute("yuyueTime", yuyueTime);
+        model.addAttribute("targetAudience", book.getTargetAudience());
         model.addAttribute("city", book.getCity());
         model.addAttribute("store", storeService.getById(book.getStoreId()).getName());
         model.addAttribute("siteName", siteService.getById(book.getSiteId()).getName());
@@ -1097,12 +1198,76 @@
     @RequestMapping("/yuyuelist")
     @ResponseBody
     public List<Map<String, Object>> yuyuelist(BookingQuery bookingQuery) {
+        return queryBookingOrders(bookingQuery);
+    }
+
+    /**
+     * 导出当前筛选条件下的全部预约记录,不受列表分页影响。
+     */
+    @RequestMapping("/yuyueexport")
+    public void exportYuyue(BookingQuery bookingQuery, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> orders = queryBookingOrders(bookingQuery);
+        String[] headers = {"序号", "所在省", "所在市", "预约门店", "入场方式", "预约场地", "姓名", "联系电话",
+                "预约时间段", "支付流水号", "支付方式", "金额", "状态"};
+
+        // 线上运行环境使用的是旧版 POI 核心包,使用 HSSF 保持依赖兼容;
+        // 超过单 Sheet 上限的数据拆分到多个工作表,确保导出全部命中记录。
+        HSSFWorkbook workbook = new HSSFWorkbook();
+        try {
+            final int maxDataRowsPerSheet = 60000;
+            int sheetCount = Math.max(1, (orders.size() + maxDataRowsPerSheet - 1) / maxDataRowsPerSheet);
+            for (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++) {
+                Sheet sheet = workbook.createSheet(sheetCount == 1 ? "预约管理" : "预约管理" + (sheetIndex + 1));
+                Row header = sheet.createRow(0);
+                for (int i = 0; i < headers.length; i++) {
+                    header.createCell(i).setCellValue(headers[i]);
+                    sheet.setColumnWidth(i, i == 8 ? 28 * 256 : 18 * 256);
+                }
+                int start = sheetIndex * maxDataRowsPerSheet;
+                int end = Math.min(start + maxDataRowsPerSheet, orders.size());
+                for (int rowIndex = start; rowIndex < end; rowIndex++) {
+                    Map<String, Object> order = orders.get(rowIndex);
+                    Row row = sheet.createRow(rowIndex - start + 1);
+                    String[] values = {
+                            valueOf(order.get("id")), valueOf(order.get("province")), valueOf(order.get("city")),
+                            valueOf(order.get("name")), targetAudienceName(order.get("targetAudience")),
+                            valueOf(order.get("siteName")), valueOf(order.get("booker")), valueOf(order.get("phone")),
+                            valueOf(order.get("times")), valueOf(order.get("payOrderNo")), valueOf(order.get("payType")),
+                            moneyText(order.get("payType"), order.get("payMoney")), ticketStatusName(toInteger(order.get("status")))
+                    };
+                    for (int columnIndex = 0; columnIndex < values.length; columnIndex++) {
+                        Cell cell = row.createCell(columnIndex);
+                        cell.setCellValue(values[columnIndex]);
+                    }
+                }
+            }
+
+            String fileName = "预约管理_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xls";
+            response.reset();
+            response.setContentType("application/vnd.ms-excel");
+            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"
+                    + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20"));
+            try (OutputStream output = response.getOutputStream()) {
+                workbook.write(output);
+                output.flush();
+            }
+        } finally {
+            // HSSFWorkbook 由旧版 POI 提供,不调用新版 Workbook 的 close API。
+        }
+    }
+
+    /** 列表和导出共用同一查询与数据展示转换,保证权限范围和筛选条件一致。 */
+    private List<Map<String, Object>> queryBookingOrders(BookingQuery bookingQuery) {
         Integer objectType = UserExt.getUser().getObjectType();
 
         if (objectType == 2) {
             List<Integer> operatorId = storeService.list(new QueryWrapper<TStore>()
                             .eq("operatorId", UserExt.getUser().getObjectId()))
                     .stream().map(TStore::getId).collect(Collectors.toList());
+            if (operatorId.isEmpty()){
+                operatorId.add(0);
+            }
             bookingQuery.setStoreIds(operatorId);
         }
         if (objectType == 3) {
@@ -1146,6 +1311,31 @@
 //            }
         }
         return orders;
+    }
+
+    private String targetAudienceName(Object targetAudience) {
+        Integer value = toInteger(targetAudience);
+        if (Objects.equals(value, 1)) return "订场";
+        if (Objects.equals(value, 2)) return "散客";
+        return "";
+    }
+
+    private String moneyText(Object payType, Object payMoney) {
+        if (payMoney == null) return "";
+        return valueOf(payMoney) + ("玩湃币".equals(valueOf(payType)) ? "币" : "元");
+    }
+
+    private Integer toInteger(Object value) {
+        if (value == null || value.toString().trim().isEmpty()) return null;
+        try {
+            return Integer.valueOf(value.toString());
+        } catch (NumberFormatException ignored) {
+            return null;
+        }
+    }
+
+    private String valueOf(Object value) {
+        return value == null ? "" : String.valueOf(value);
     }
 
 
@@ -1489,29 +1679,56 @@
         siteBooking.setAddType(1);
 
         //存开始和结束时间
+        // times格式示例: 2025-10-14 19:00-19:30;2025-10-14 19:30-20:00;2025-10-14 20:00-20:30;2025-10-14 20:30-21:00
+        // targetAudience字段会由Spring MVC自动绑定(1=订场,2=散客)
         String times = siteBooking.getTimes();
+        if (ToolUtil.isEmpty(times)) {
+            return ResultUtil.error("请选择预约时间段");
+        }
         String[] dates = times.split(";");
+        if (dates.length == 0) {
+            return ResultUtil.error("请选择预约时间段");
+        }
 
         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+        format.setLenient(false);
         try {
-            String start = dates[0];
-            String startDateStr = start.substring(0, start.lastIndexOf("-"));
-            Date startDate = format.parse(startDateStr);
+            Pattern timeSlotPattern = Pattern.compile("^(\\d{4}-\\d{2}-\\d{2})\\s+(\\d{2}:\\d{2})-(\\d{2}:\\d{2})$");
+            // dates[0] 格式: 2025-10-14 19:00-19:30
+            Matcher startMatcher = timeSlotPattern.matcher(dates[0].trim());
+            // dates[dates.length - 1] 格式: 2025-10-14 20:30-21:00
+            Matcher endMatcher = timeSlotPattern.matcher(dates[dates.length - 1].trim());
+            if (!startMatcher.matches() || !endMatcher.matches()) {
+                return ResultUtil.error("预约时间段格式错误,请重新选择");
+            }
 
-            String end = dates[dates.length - 1];
-            String[] str = end.split(" ");
-            String endDateStr = str[0] + " " + str[1].substring(str[1].indexOf("-") + 1);
-            Date endDate = format.parse(endDateStr);
+            Date startDate = format.parse(startMatcher.group(1) + " " + startMatcher.group(2));
+            Date endDate = format.parse(endMatcher.group(1) + " " + endMatcher.group(3));
+            if (!endDate.after(startDate)) {
+                return ResultUtil.error("预约结束时间必须晚于开始时间");
+            }
+            
             siteBooking.setStartTime(startDate);
             siteBooking.setEndTime(endDate);
         } catch (Exception e) {
-            e.printStackTrace();
+            return ResultUtil.error("预约时间段格式错误,请重新选择");
         }
         siteBooking.setPayType(2);
         //存多少钱
         String money = siteBooking.getMoney();
+        if (ToolUtil.isEmpty(money)) {
+            return ResultUtil.error("请输入预约金额");
+        }
         String[] moneys = money.split(",");
-        siteBooking.setPayMoney(Double.valueOf(moneys[0]));
+        if (moneys.length == 0 || ToolUtil.isEmpty(moneys[0])) {
+            return ResultUtil.error("预约金额格式错误");
+        }
+        try {
+            siteBooking.setPayMoney(Double.valueOf(moneys[0]));
+        } catch (NumberFormatException e) {
+            return ResultUtil.error("预约金额格式错误");
+        }
+        siteBooking.setMoney(money);
         // 查询当前预约人是否是会员
         String phone = siteBooking.getPhone();
         String booker = siteBooking.getBooker();
@@ -1553,6 +1770,9 @@
             siteBooking.setCityCode(byId.getCityCode());
         }
 
+        if (siteBooking.getIsHalf()==2){
+            siteBooking.setHalfName(null);
+        }
         siteClient.addSiteBooking(siteBooking);
         System.out.println("=================" + siteBooking);
         return new ResultUtil<>(null, 200, null, null, null);

--
Gitblit v1.7.1