package com.kaidi.oa.web; import com.kaidi.oa.common.ApiException; import com.kaidi.oa.common.ApiResp; import com.kaidi.oa.common.NotFoundException; import com.kaidi.oa.domain.AdminShuttleBus; import com.kaidi.oa.domain.AdminShuttlePassenger; import com.kaidi.oa.repository.AdminShuttleBusRepository; import com.kaidi.oa.repository.AdminShuttlePassengerRepository; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.time.Instant; import java.time.LocalDate; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 行政/办公室·通勤班车管理(补深车辆管理缺口)。 * 固定线路班车排班、乘客预约统计。 * 班车档案:/api/oa/admin-shuttles/buses * 乘客预约:/api/oa/admin-shuttles/passengers */ @RestController @RequestMapping("/api/oa/admin-shuttles") public class AdminShuttleController { private final AdminShuttleBusRepository busRepo; private final AdminShuttlePassengerRepository passengerRepo; public AdminShuttleController(AdminShuttleBusRepository busRepo, AdminShuttlePassengerRepository passengerRepo) { this.busRepo = busRepo; this.passengerRepo = passengerRepo; } // ---------- 班车路线档案 ---------- @GetMapping("/buses") public ApiResp> listBuses( @RequestParam(required = false) String status) { if (status != null && !status.isBlank()) { return ApiResp.ok(busRepo.findByStatusOrderByIdDesc(status)); } return ApiResp.ok(busRepo.findAllByOrderByRouteNameAsc()); } @GetMapping("/buses/{id}") public ApiResp getBus(@PathVariable Long id) { return ApiResp.ok(busRepo.findById(id) .orElseThrow(() -> new NotFoundException("shuttle bus not found: " + id))); } public record BusRequest( String routeName, String startStop, String endStop, String stops, String departureTime, String returnTime, String vehiclePlate, String driver, Integer seats, String runDays, String status, String remark) { } @PostMapping("/buses") public ApiResp createBus(@RequestBody BusRequest req) { if (req.routeName() == null || req.routeName().isBlank()) { throw new ApiException(400, "路线名称(routeName) 不能为空"); } AdminShuttleBus b = new AdminShuttleBus(); b.setRouteName(req.routeName()); b.setStartStop(req.startStop()); b.setEndStop(req.endStop()); b.setStops(req.stops()); b.setDepartureTime(req.departureTime()); b.setReturnTime(req.returnTime()); b.setVehiclePlate(req.vehiclePlate()); b.setDriver(req.driver()); b.setSeats(req.seats() == null ? 20 : req.seats()); b.setBookedCount(0); b.setRunDays(req.runDays() == null || req.runDays().isBlank() ? "工作日" : req.runDays()); b.setStatus(req.status() == null || req.status().isBlank() ? "运营中" : req.status()); b.setRemark(req.remark()); b.setCreatedAt(Instant.now()); b.setUpdatedAt(Instant.now()); return ApiResp.ok(busRepo.save(b)); } @PatchMapping("/buses/{id}") public ApiResp updateBus(@PathVariable Long id, @RequestBody BusRequest req) { AdminShuttleBus b = busRepo.findById(id) .orElseThrow(() -> new NotFoundException("shuttle bus not found: " + id)); if (req.routeName() != null && !req.routeName().isBlank()) b.setRouteName(req.routeName()); if (req.startStop() != null) b.setStartStop(req.startStop()); if (req.endStop() != null) b.setEndStop(req.endStop()); if (req.stops() != null) b.setStops(req.stops()); if (req.departureTime() != null) b.setDepartureTime(req.departureTime()); if (req.returnTime() != null) b.setReturnTime(req.returnTime()); if (req.vehiclePlate() != null) b.setVehiclePlate(req.vehiclePlate()); if (req.driver() != null) b.setDriver(req.driver()); if (req.seats() != null) b.setSeats(req.seats()); if (req.runDays() != null && !req.runDays().isBlank()) b.setRunDays(req.runDays()); if (req.status() != null && !req.status().isBlank()) b.setStatus(req.status()); if (req.remark() != null) b.setRemark(req.remark()); b.setUpdatedAt(Instant.now()); return ApiResp.ok(busRepo.save(b)); } @DeleteMapping("/buses/{id}") public ApiResp deleteBus(@PathVariable Long id) { if (!busRepo.existsById(id)) throw new NotFoundException("shuttle bus not found: " + id); busRepo.deleteById(id); return ApiResp.ok(null); } // ---------- 乘客预约 ---------- @GetMapping("/passengers") public ApiResp> listPassengers( @RequestParam(required = false) Long shuttleId, @RequestParam(required = false) String rideDate, @RequestParam(required = false) String status) { if (shuttleId != null && rideDate != null && !rideDate.isBlank()) { return ApiResp.ok(passengerRepo.findByShuttleIdAndRideDate(shuttleId, rideDate)); } if (status != null && !status.isBlank()) { return ApiResp.ok(passengerRepo.findByStatus(status)); } // default: today String today = LocalDate.now().toString(); List all = new ArrayList<>(); for (AdminShuttleBus bus : busRepo.findAllByOrderByRouteNameAsc()) { all.addAll(passengerRepo.findByShuttleIdAndRideDate(bus.getId(), rideDate != null ? rideDate : today)); } return ApiResp.ok(all); } public record PassengerRequest( Long shuttleId, String passenger, String dept, String rideDate, String direction, String boardStop) { } /** 预约乘坐:检查座位余量,不超过班车座位数。 */ @PostMapping("/passengers") @Transactional public ApiResp book(@RequestBody PassengerRequest req) { if (req.shuttleId() == null) throw new ApiException(400, "shuttleId 不能为空"); if (req.passenger() == null || req.passenger().isBlank()) { throw new ApiException(400, "乘客姓名(passenger) 不能为空"); } AdminShuttleBus bus = busRepo.findById(req.shuttleId()) .orElseThrow(() -> new NotFoundException("shuttle bus not found: " + req.shuttleId())); if (!"运营中".equals(bus.getStatus())) { throw new ApiException(409, "该班车当前状态为「" + bus.getStatus() + "」,不可预约"); } String rideDate = req.rideDate() == null || req.rideDate().isBlank() ? LocalDate.now().toString() : req.rideDate(); // 检查同日同班车座位余量 int booked = passengerRepo.countByShuttleIdAndRideDateAndStatus(req.shuttleId(), rideDate, "已预约"); if (bus.getSeats() != null && booked >= bus.getSeats()) { throw new ApiException(409, "该班车当日座位已满(" + bus.getSeats() + "座)"); } AdminShuttlePassenger p = new AdminShuttlePassenger(); p.setShuttleId(req.shuttleId()); p.setPassenger(req.passenger()); p.setDept(req.dept()); p.setRideDate(rideDate); p.setDirection(req.direction() == null || req.direction().isBlank() ? "去" : req.direction()); p.setBoardStop(req.boardStop()); p.setStatus("已预约"); p.setCreatedAt(Instant.now()); // 更新班车预约人数 bus.setBookedCount((bus.getBookedCount() == null ? 0 : bus.getBookedCount()) + 1); busRepo.save(bus); return ApiResp.ok(passengerRepo.save(p)); } /** 取消预约:已预约 → 已取消,释放座位。 */ @PostMapping("/passengers/{id}/cancel") @Transactional public ApiResp cancelBooking(@PathVariable Long id) { AdminShuttlePassenger p = passengerRepo.findById(id) .orElseThrow(() -> new NotFoundException("passenger not found: " + id)); if (!"已预约".equals(p.getStatus())) { throw new ApiException(409, "仅「已预约」可取消,当前:" + p.getStatus()); } p.setStatus("已取消"); passengerRepo.save(p); busRepo.findById(p.getShuttleId()).ifPresent(bus -> { int prev = bus.getBookedCount() == null ? 0 : bus.getBookedCount(); bus.setBookedCount(Math.max(0, prev - 1)); busRepo.save(bus); }); return ApiResp.ok(p); } /** 标记已乘坐:已预约 → 已乘坐。 */ @PostMapping("/passengers/{id}/checkin") @Transactional public ApiResp checkin(@PathVariable Long id) { AdminShuttlePassenger p = passengerRepo.findById(id) .orElseThrow(() -> new NotFoundException("passenger not found: " + id)); if (!"已预约".equals(p.getStatus())) { throw new ApiException(409, "仅「已预约」可签到,当前:" + p.getStatus()); } p.setStatus("已乘坐"); return ApiResp.ok(passengerRepo.save(p)); } // ---------- 乘客统计 ---------- public record ShuttleStatsRow(String routeName, String rideDate, long booked, long ridden, long cancelled) { } /** 班车乘客统计:按班车+日期聚合。 */ @GetMapping("/stats") public ApiResp> stats( @RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate) { Map map = new LinkedHashMap<>(); for (AdminShuttleBus bus : busRepo.findAllByOrderByRouteNameAsc()) { List passengers = passengerRepo.findByShuttleIdAndRideDate( bus.getId(), startDate == null ? LocalDate.now().toString() : startDate); for (AdminShuttlePassenger p : passengers) { String key = bus.getRouteName() + "||" + p.getRideDate(); long[] cnt = map.computeIfAbsent(key, k -> new long[3]); if ("已预约".equals(p.getStatus())) cnt[0]++; else if ("已乘坐".equals(p.getStatus())) cnt[1]++; else if ("已取消".equals(p.getStatus())) cnt[2]++; } } List rows = new ArrayList<>(); for (Map.Entry e : map.entrySet()) { String[] parts = e.getKey().split("\\|\\|"); long[] cnt = e.getValue(); rows.add(new ShuttleStatsRow(parts[0], parts.length > 1 ? parts[1] : "", cnt[0], cnt[1], cnt[2])); } return ApiResp.ok(rows); } }