ManagementYKDTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/controller/specialTrain/TLineController.java
@@ -12,6 +12,7 @@ import com.stylefeng.guns.core.util.SinataUtil; import com.stylefeng.guns.core.util.ToolUtil; import com.stylefeng.guns.modular.system.model.*; import com.stylefeng.guns.modular.system.dao.LineShiftDriverMapper; import com.stylefeng.guns.modular.system.service.*; import com.stylefeng.guns.modular.system.util.ResultUtil; import org.springframework.beans.factory.annotation.Autowired; @@ -24,7 +25,9 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.math.BigDecimal; @@ -67,6 +70,12 @@ @Autowired private ITLineCompanyService itLineCompanyService; @Autowired private LineShiftDriverMapper lineShiftDriverMapper; @Autowired private ITDriverService tDriverService; /** * 跳转到跨城站点管理首页 @@ -420,10 +429,337 @@ lineCompany.setCompanyId(jsonObject.getInteger("companyId")); lineCompany.setInsertTime(new Date()); lineCompany.setInsertUserId(ShiroKit.getUser().getId()); lineCompany.setAutoPlan(2); itLineCompanyService.insert(lineCompany); } } return SUCCESS_TIP; } /** * 查询线路已分配企业 */ @RequestMapping(value = "/lineCompanyList") @ResponseBody public Object lineCompanyList(Integer lineId) { if (lineId == null) { return ResultUtil.error("线路ID不能为空"); } return ResultUtil.success(tLineService.getLineCompanyList(lineId)); } /** * 排班管理页面 */ @RequestMapping(value = "/schedulePage") public String schedulePage(Integer lineId, Integer companyId, Model model) { TLine line = tLineService.selectById(lineId); TCompany company = itCompanyService.selectById(companyId); TLineCompany lineCompany = itLineCompanyService.selectOne(new EntityWrapper<TLineCompany>() .eq("lineId", lineId).eq("companyId", companyId)); model.addAttribute("lineId", lineId); model.addAttribute("companyId", companyId); model.addAttribute("lineName", line == null ? "" : line.getName()); model.addAttribute("companyName", company == null ? "" : company.getName()); model.addAttribute("autoPlan", lineCompany == null || lineCompany.getAutoPlan() == null ? 2 : lineCompany.getAutoPlan()); return PREFIX + "tLine_schedule.html"; } /** * 排班管理-月份数据 */ @RequestMapping(value = "/schedule/month") @ResponseBody public Object scheduleMonth(Integer lineId, Integer companyId, String month) { try { TLineCompany lineCompany = checkLineCompany(lineId, companyId); if (lineCompany == null) { return ResultUtil.error("线路未关联该企业"); } if (SinataUtil.isEmpty(month)) { month = new SimpleDateFormat("yyyy-MM").format(new Date()); } SimpleDateFormat monthSdf = new SimpleDateFormat("yyyy-MM"); Date monthDate = monthSdf.parse(month); Calendar cal = Calendar.getInstance(); cal.setTime(monthDate); cal.set(Calendar.DAY_OF_MONTH, 1); String monthStart = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime()); int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); cal.set(Calendar.DAY_OF_MONTH, maxDay); String monthEnd = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime()); Integer companyType = getCompanyType(companyId); List<Map<String, Object>> stats = lineShiftDriverMapper.getMonthScheduleStats(lineId, monthStart, monthEnd, companyId, companyType); Map<String, Integer> dayAssignMap = new HashMap<>(); for (Map<String, Object> stat : stats) { dayAssignMap.put(String.valueOf(stat.get("dayStr")), Integer.parseInt(String.valueOf(stat.get("assignNum")))); } List<TLineShift> shifts = itLineShiftService.selectList(new EntityWrapper<TLineShift>().eq("lineId", lineId).ne("state", 3)); int totalNeed = 0; for (TLineShift shift : shifts) { totalNeed += (shift.getCarNum() == null ? 0 : shift.getCarNum()); } List<Map<String, Object>> dayList = new ArrayList<>(); cal.setTime(monthDate); cal.set(Calendar.DAY_OF_MONTH, 1); for (int i = 1; i <= maxDay; i++) { cal.set(Calendar.DAY_OF_MONTH, i); String dayStr = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime()); int assignNum = dayAssignMap.get(dayStr) == null ? 0 : dayAssignMap.get(dayStr); Map<String, Object> obj = new HashMap<>(); obj.put("day", dayStr); obj.put("assignNum", assignNum); obj.put("totalNeed", totalNeed); if (totalNeed <= 0) { obj.put("status", 0); obj.put("statusText", "无班次"); } else if (assignNum >= totalNeed) { obj.put("status", 1); obj.put("statusText", "已排班"); } else { obj.put("status", 2); obj.put("statusText", "未排满"); } dayList.add(obj); } Map<String, Object> result = new HashMap<>(); result.put("month", month); result.put("totalNeed", totalNeed); result.put("days", dayList); return ResultUtil.success(result); } catch (Exception ex) { ex.printStackTrace(); return ResultUtil.runErr(); } } /** * 排班管理-某天班次详情 */ @RequestMapping(value = "/schedule/dayDetail") @ResponseBody public Object scheduleDayDetail(Integer lineId, Integer companyId, String day) { try { TLineCompany lineCompany = checkLineCompany(lineId, companyId); if (lineCompany == null) { return ResultUtil.error("线路未关联该企业"); } Integer companyType = getCompanyType(companyId); List<Map<String, Object>> shiftList = lineShiftDriverMapper.getDayShiftSchedule(lineId, day, companyId, companyType); for (Map<String, Object> shift : shiftList) { Integer lineShiftId = Integer.valueOf(String.valueOf(shift.get("lineShiftId"))); List<Map<String, Object>> drivers = lineShiftDriverMapper.getDayShiftDrivers(lineShiftId, day, companyId, companyType); int carNum = shift.get("carNum") == null ? 0 : Integer.parseInt(String.valueOf(shift.get("carNum"))); int assignNum = shift.get("assignNum") == null ? 0 : Integer.parseInt(String.valueOf(shift.get("assignNum"))); shift.put("drivers", drivers); shift.put("remainNum", Math.max(carNum - assignNum, 0)); shift.put("canAdd", assignNum < carNum ? 1 : 0); } return ResultUtil.success(shiftList); } catch (Exception ex) { ex.printStackTrace(); return ResultUtil.runErr(); } } /** * 排班管理-可添加司机 */ @RequestMapping(value = "/schedule/drivers") @ResponseBody public Object scheduleDrivers(Integer lineId, Integer companyId, Integer lineShiftId, String day) { try { TLineCompany lineCompany = checkLineCompany(lineId, companyId); if (lineCompany == null) { return ResultUtil.error("线路未关联该企业"); } Integer companyType = getCompanyType(companyId); return ResultUtil.success(lineShiftDriverMapper.getAvailableDrivers(lineId, lineShiftId, day, companyId, companyType)); } catch (Exception ex) { ex.printStackTrace(); return ResultUtil.runErr(); } } /** * 排班管理-新增排班 */ @RequestMapping(value = "/schedule/add") @ResponseBody public Object scheduleAdd(Integer lineId, Integer companyId, Integer lineShiftId, String day, @RequestParam String driverIds) { try { TLineCompany lineCompany = checkLineCompany(lineId, companyId); if (lineCompany == null) { return ResultUtil.error("线路未关联该企业"); } TLineShift shift = itLineShiftService.selectById(lineShiftId); if (shift == null || shift.getState() == 3 || !shift.getLineId().equals(lineId)) { return ResultUtil.error("班次不存在"); } Date dayDate = parseDay(day); Integer companyType = getCompanyType(companyId); List<Map<String, Object>> currentDrivers = lineShiftDriverMapper.getDayShiftDrivers(lineShiftId, day, companyId, companyType); int currentAssignNum = currentDrivers == null ? 0 : currentDrivers.size(); int carNum = shift.getCarNum() == null ? 0 : shift.getCarNum(); if (currentAssignNum >= carNum) { return ResultUtil.error("当前班次已排满"); } List<Integer> driverIdList = parseIdList(driverIds); if (driverIdList.size() <= 0) { return ResultUtil.error("请选择司机"); } List<Map<String, Object>> availableDrivers = lineShiftDriverMapper.getAvailableDrivers(lineId, lineShiftId, day, companyId, companyType); Map<Integer, Integer> seatMap = new HashMap<>(); for (Map<String, Object> driver : availableDrivers) { Integer driverId = Integer.valueOf(String.valueOf(driver.get("driverId"))); Integer seatNum = driver.get("seatNum") == null ? 5 : Integer.valueOf(String.valueOf(driver.get("seatNum"))); seatMap.put(driverId, seatNum); } List<Integer> validDriverIds = new ArrayList<>(); for (Integer driverId : driverIdList) { if (seatMap.containsKey(driverId)) { validDriverIds.add(driverId); } } if (validDriverIds.size() <= 0) { return ResultUtil.error("没有可添加的司机"); } if (currentAssignNum + validDriverIds.size() > carNum) { return ResultUtil.error("可排班人数不足,当前班次最多还能添加" + (carNum - currentAssignNum) + "名司机"); } for (Integer driverId : validDriverIds) { Integer count = lineShiftDriverMapper.countExistsSchedule(lineShiftId, driverId, day); if (count != null && count > 0) { continue; } int seatNum = seatMap.get(driverId) == null ? 5 : seatMap.get(driverId); if (seatNum <= 0) { seatNum = 5; } LineShiftDriver record = new LineShiftDriver(); record.setLineShiftId(lineShiftId); record.setDriverId(driverId); record.setDay(dayDate); record.setTotalSeat(seatNum); record.setLaveSeat(seatNum); record.setLaveSeatNumber(buildSeatNoStr(seatNum)); record.setInserTime(new Date()); record.setAssignType(2); lineShiftDriverMapper.insert(record); } return ResultUtil.success(); } catch (Exception ex) { ex.printStackTrace(); return ResultUtil.runErr(); } } /** * 排班管理-删除手动排班 */ @RequestMapping(value = "/schedule/delete") @ResponseBody public Object scheduleDelete(Integer scheduleId, Integer lineId, Integer companyId) { try { TLineCompany lineCompany = checkLineCompany(lineId, companyId); if (lineCompany == null) { return ResultUtil.error("线路未关联该企业"); } LineShiftDriver record = lineShiftDriverMapper.selectById(scheduleId); if (record == null) { return ResultUtil.error("排班记录不存在"); } if (record.getAssignType() == null || record.getAssignType() != 2) { return ResultUtil.error("仅支持删除手动排班记录"); } TLineShift shift = itLineShiftService.selectById(record.getLineShiftId()); if (shift == null || !shift.getLineId().equals(lineId)) { return ResultUtil.error("排班记录线路不匹配"); } TDriver driver = tDriverService.selectById(record.getDriverId()); if (driver == null || !checkDriverBelongCompany(driver, companyId)) { return ResultUtil.error("司机企业信息不匹配"); } lineShiftDriverMapper.deleteById(scheduleId); return ResultUtil.success(); } catch (Exception ex) { ex.printStackTrace(); return ResultUtil.runErr(); } } /** * 排班管理-复制排班 */ @RequestMapping(value = "/schedule/copy") @ResponseBody public Object scheduleCopy(Integer lineId, Integer companyId, String sourceStart, String sourceEnd, String targetStart) { try { TLineCompany lineCompany = checkLineCompany(lineId, companyId); if (lineCompany == null) { return ResultUtil.error("线路未关联该企业"); } Date sourceStartDate = parseDay(sourceStart); Date sourceEndDate = parseDay(sourceEnd); Date targetStartDate = parseDay(targetStart); if (sourceEndDate.before(sourceStartDate)) { return ResultUtil.error("复制结束日期不能早于开始日期"); } Integer companyType = getCompanyType(companyId); List<LineShiftDriver> sourceList = lineShiftDriverMapper.getManualSchedulesForCopy(lineId, sourceStart, sourceEnd, companyId, companyType); int addNum = 0; int skipNum = 0; for (LineShiftDriver source : sourceList) { int diff = getDayDiff(sourceStartDate, source.getDay()); Date targetDay = addDays(targetStartDate, diff); String targetDayStr = formatDay(targetDay); Integer count = lineShiftDriverMapper.countExistsSchedule(source.getLineShiftId(), source.getDriverId(), targetDayStr); if (count != null && count > 0) { skipNum++; continue; } LineShiftDriver target = new LineShiftDriver(); target.setLineShiftId(source.getLineShiftId()); target.setDriverId(source.getDriverId()); target.setDay(targetDay); target.setLaveSeat(source.getLaveSeat()); target.setLaveSeatNumber(source.getLaveSeatNumber()); target.setTotalSeat(source.getTotalSeat()); target.setInserTime(new Date()); target.setAssignType(2); lineShiftDriverMapper.insert(target); addNum++; } Map<String, Object> result = new HashMap<>(); result.put("addNum", addNum); result.put("skipNum", skipNum); return ResultUtil.success(result); } catch (Exception ex) { ex.printStackTrace(); return ResultUtil.runErr(); } } /** * 排班管理-保存智能排班开关 */ @RequestMapping(value = "/schedule/autoPlan") @ResponseBody public Object scheduleAutoPlan(Integer lineId, Integer companyId, Integer autoPlan) { if (autoPlan == null || (autoPlan != 1 && autoPlan != 2)) { return ResultUtil.error("智能排班参数错误"); } TLineCompany lineCompany = checkLineCompany(lineId, companyId); if (lineCompany == null) { return ResultUtil.error("线路未关联该企业"); } lineCompany.setAutoPlan(autoPlan); itLineCompanyService.updateById(lineCompany); return ResultUtil.success(); } /** * 修改跨城站点管理 @@ -601,4 +937,97 @@ return ResultUtil.success(); } private TLineCompany checkLineCompany(Integer lineId, Integer companyId) { if (lineId == null || companyId == null) { return null; } return itLineCompanyService.selectOne(new EntityWrapper<TLineCompany>().eq("lineId", lineId).eq("companyId", companyId)); } private Integer getCompanyType(Integer companyId) { TCompany company = itCompanyService.selectById(companyId); return company == null ? null : company.getType(); } private boolean checkDriverBelongCompany(TDriver driver, Integer companyId) { if (driver == null || companyId == null) { return false; } Integer companyType = getCompanyType(companyId); if (companyType == null) { return false; } if (companyType == 2) { return driver.getCompanyId() != null && driver.getCompanyId().intValue() == companyId.intValue(); } if (companyType == 3) { return driver.getFranchiseeId() != null && driver.getFranchiseeId().intValue() == companyId.intValue(); } return false; } private Date parseDay(String day) throws Exception { return new SimpleDateFormat("yyyy-MM-dd").parse(day); } private String formatDay(Date day) { return new SimpleDateFormat("yyyy-MM-dd").format(day); } private int getDayDiff(Date start, Date end) { long oneDay = 24L * 60L * 60L * 1000L; long startTime = start.getTime() / oneDay; long endTime = end.getTime() / oneDay; return (int) (endTime - startTime); } private Date addDays(Date date, int days) { Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.add(Calendar.DAY_OF_MONTH, days); return cal.getTime(); } private String buildSeatNoStr(int seatNum) { StringBuilder builder = new StringBuilder(); for (int i = 1; i <= seatNum; i++) { if (i > 1) { builder.append(","); } builder.append(i); } return builder.toString(); } private List<Integer> parseIdList(String ids) { List<Integer> list = new ArrayList<>(); if (SinataUtil.isEmpty(ids)) { return list; } try { if (ids.startsWith("[")) { JSONArray array = JSON.parseArray(ids); for (int i = 0; i < array.size(); i++) { Integer id = array.getInteger(i); if (id != null && !list.contains(id)) { list.add(id); } } } else { String[] arr = ids.split(","); for (String s : arr) { if (SinataUtil.isNotEmpty(s)) { Integer id = Integer.valueOf(s.trim()); if (!list.contains(id)) { list.add(id); } } } } } catch (Exception ex) { ex.printStackTrace(); } return list; } } ManagementYKDTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/dao/LineShiftDriverMapper.java
@@ -28,4 +28,54 @@ * @return */ List<Map<String, Object>> queryNotInLineShiftDriver(@Param("lineShiftId") Integer lineShiftId, @Param("time") Date time); /** * 获取线路指定月份排班统计 */ List<Map<String, Object>> getMonthScheduleStats(@Param("lineId") Integer lineId, @Param("monthStart") String monthStart, @Param("monthEnd") String monthEnd, @Param("companyId") Integer companyId, @Param("companyType") Integer companyType); /** * 获取线路指定日期班次排班数据 */ List<Map<String, Object>> getDayShiftSchedule(@Param("lineId") Integer lineId, @Param("day") String day, @Param("companyId") Integer companyId, @Param("companyType") Integer companyType); /** * 获取班次在某天已排司机 */ List<Map<String, Object>> getDayShiftDrivers(@Param("lineShiftId") Integer lineShiftId, @Param("day") String day, @Param("companyId") Integer companyId, @Param("companyType") Integer companyType); /** * 获取可排班司机列表 */ List<Map<String, Object>> getAvailableDrivers(@Param("lineId") Integer lineId, @Param("lineShiftId") Integer lineShiftId, @Param("day") String day, @Param("companyId") Integer companyId, @Param("companyType") Integer companyType); /** * 获取复制源的手动排班记录 */ List<LineShiftDriver> getManualSchedulesForCopy(@Param("lineId") Integer lineId, @Param("sourceStart") String sourceStart, @Param("sourceEnd") String sourceEnd, @Param("companyId") Integer companyId, @Param("companyType") Integer companyType); /** * 统计某天某班次某司机是否已有排班 */ Integer countExistsSchedule(@Param("lineShiftId") Integer lineShiftId, @Param("driverId") Integer driverId, @Param("day") String day); } ManagementYKDTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/dao/mapping/LineShiftDriverMapper.xml
@@ -12,6 +12,7 @@ <result column="laveSeatNumber" property="laveSeatNumber"/> <result column="totalSeat" property="totalSeat"/> <result column="inserTime" property="inserTime"/> <result column="assignType" property="assignType"/> </resultMap> @@ -25,7 +26,8 @@ laveSeat as laveSeat, laveSeatNumber as laveSeatNumber, totalSeat as totalSeat, inserTime as inserTime inserTime as inserTime, assignType as assignType from t_line_shift_driver where 1 = 1 <if test="null != lineShiftId"> and lineShiftId = #{lineShiftId} @@ -58,4 +60,133 @@ where ls.id = #{lineShiftId} and DATE_FORMAT(lsd.`day`, '%Y-%m-%d') = DATE_FORMAT(#{time}, '%Y-%m-%d') ) </select> </mapper> <select id="getMonthScheduleStats" resultType="map"> select DATE_FORMAT(lsd.`day`, '%Y-%m-%d') as dayStr, COUNT(lsd.id) as assignNum, SUM(case when lsd.assignType = 2 then 1 else 0 end) as manualNum from t_line_shift_driver lsd left join t_line_shift ls on lsd.lineShiftId = ls.id left join t_driver d on d.id = lsd.driverId where ls.lineId = #{lineId} and ls.state != 3 and DATE_FORMAT(lsd.`day`, '%Y-%m-%d') between #{monthStart} and #{monthEnd} and d.flag != 3 <if test="companyType == 2"> and d.companyId = #{companyId} </if> <if test="companyType == 3"> and d.franchiseeId = #{companyId} </if> group by DATE_FORMAT(lsd.`day`, '%Y-%m-%d') </select> <select id="getDayShiftSchedule" resultType="map"> select ls.id as lineShiftId, ls.startTime as startTime, ls.endTime as endTime, ls.carNum as carNum, COUNT(lsd.id) as assignNum, SUM(case when lsd.assignType = 2 then 1 else 0 end) as manualNum from t_line_shift ls left join t_line_shift_driver lsd on ls.id = lsd.lineShiftId and DATE_FORMAT(lsd.`day`, '%Y-%m-%d') = #{day} left join t_driver d on d.id = lsd.driverId where ls.lineId = #{lineId} and ls.state != 3 and (lsd.id is null or d.flag != 3) <if test="companyType == 2"> and (lsd.id is null or d.companyId = #{companyId}) </if> <if test="companyType == 3"> and (lsd.id is null or d.franchiseeId = #{companyId}) </if> group by ls.id, ls.startTime, ls.endTime, ls.carNum order by ls.startTime asc </select> <select id="getDayShiftDrivers" resultType="map"> select lsd.id as id, lsd.driverId as driverId, d.`name` as driverName, d.phone as driverPhone, lsd.assignType as assignType from t_line_shift_driver lsd left join t_driver d on d.id = lsd.driverId where lsd.lineShiftId = #{lineShiftId} and DATE_FORMAT(lsd.`day`, '%Y-%m-%d') = #{day} and d.flag != 3 <if test="companyType == 2"> and d.companyId = #{companyId} </if> <if test="companyType == 3"> and d.franchiseeId = #{companyId} </if> order by d.`name` asc, d.phone asc </select> <select id="getAvailableDrivers" resultType="map"> select d.id as driverId, d.`name` as driverName, d.phone as driverPhone, ifnull(cm.seat, 5) as seatNum from t_driver d left join t_driver_line dl on dl.driverId = d.id left join t_car c on c.id = d.carId left join t_car_model cm on cm.id = c.carModelId where d.flag != 3 and d.authState = 2 and dl.lineId = #{lineId} <if test="companyType == 2"> and d.companyId = #{companyId} </if> <if test="companyType == 3"> and d.franchiseeId = #{companyId} </if> and d.id not in ( select driverId from t_line_shift_driver where lineShiftId = #{lineShiftId} and DATE_FORMAT(`day`, '%Y-%m-%d') = #{day} ) order by d.`name` asc, d.phone asc </select> <select id="getManualSchedulesForCopy" resultType="LineShiftDriver"> select lsd.id as id, lsd.lineShiftId as lineShiftId, lsd.driverId as driverId, lsd.`day` as `day`, lsd.laveSeat as laveSeat, lsd.laveSeatNumber as laveSeatNumber, lsd.totalSeat as totalSeat, lsd.inserTime as inserTime, lsd.assignType as assignType from t_line_shift_driver lsd left join t_line_shift ls on ls.id = lsd.lineShiftId left join t_driver d on d.id = lsd.driverId where lsd.assignType = 2 and ls.lineId = #{lineId} and ls.state != 3 and DATE_FORMAT(lsd.`day`, '%Y-%m-%d') between #{sourceStart} and #{sourceEnd} and d.flag != 3 <if test="companyType == 2"> and d.companyId = #{companyId} </if> <if test="companyType == 3"> and d.franchiseeId = #{companyId} </if> order by lsd.`day`, lsd.lineShiftId, lsd.driverId </select> <select id="countExistsSchedule" resultType="java.lang.Integer"> select count(1) from t_line_shift_driver where lineShiftId = #{lineShiftId} and driverId = #{driverId} and DATE_FORMAT(`day`, '%Y-%m-%d') = #{day} </select> </mapper> ManagementYKDTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/dao/mapping/TLineCompanyMapper.xml
@@ -9,11 +9,12 @@ <result column="companyId" property="companyId" /> <result column="insertTime" property="insertTime" /> <result column="insertUserId" property="insertUserId" /> <result column="autoPlan" property="autoPlan" /> </resultMap> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> id, lineId, companyId, insertTime, insertUserId id, lineId, companyId, insertTime, insertUserId, autoPlan </sql> </mapper> ManagementYKDTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/model/LineShiftDriver.java
@@ -53,6 +53,11 @@ */ @TableField("inserTime") private Date inserTime; /** * 排班来源(1=司机自领,2=手动排班) */ @TableField("assignType") private Integer assignType; public Integer getId() { return id; @@ -118,6 +123,14 @@ this.inserTime = inserTime; } public Integer getAssignType() { return assignType; } public void setAssignType(Integer assignType) { this.assignType = assignType; } @Override public String toString() { return "LineShiftDriver{" + @@ -129,6 +142,7 @@ ", laveSeatNumber='" + laveSeatNumber + '\'' + ", totalSeat=" + totalSeat + ", inserTime=" + inserTime + ", assignType=" + assignType + '}'; } } ManagementYKDTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/model/TLineCompany.java
@@ -41,6 +41,10 @@ * 添加用户ID */ private Integer insertUserId; /** * 是否智能排班(1=是,2=否) */ private Integer autoPlan; public Integer getId() { @@ -83,6 +87,14 @@ this.insertUserId = insertUserId; } public Integer getAutoPlan() { return autoPlan; } public void setAutoPlan(Integer autoPlan) { this.autoPlan = autoPlan; } @Override protected Serializable pkVal() { return this.id; @@ -96,6 +108,7 @@ ", companyId=" + companyId + ", insertTime=" + insertTime + ", insertUserId=" + insertUserId + ", autoPlan=" + autoPlan + "}"; } } ManagementYKDTravel/guns-admin/src/main/webapp/WEB-INF/view/system/tLine/tLine.html
@@ -52,6 +52,9 @@ @if(shiro.hasPermission("/tLine/addCompany")){ <#button name="分配企业" icon="fa-plus" clickFun="TLine.addCompany()" space="true"/> @} @if(shiro.hasPermission("/tLine/addCompany")){ <#button name="排班管理" icon="fa-calendar" clickFun="TLine.openScheduleManage()" space="true"/> @} </div> <#table id="TLineTable"/> </div> ManagementYKDTravel/guns-admin/src/main/webapp/WEB-INF/view/system/tLine/tLine_schedule.html
New file @@ -0,0 +1,98 @@ @layout("/common/_container.html"){ <style> .schedule-wrap { padding: 10px 20px; } .schedule-header { margin-bottom: 15px; } .schedule-header .line { font-size: 18px; font-weight: 700; margin-bottom: 12px; } .schedule-copy { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; } .schedule-copy .lbl { color: #333; min-width: 60px; } .schedule-copy input { width: 130px; } .schedule-switch { margin-left: 20px; display: flex; align-items: center; gap: 6px; } .calendar-panel { border: 1px solid #e5e6e7; margin-top: 15px; } .calendar-toolbar { height: 48px; display: flex; align-items: center; justify-content: center; gap: 16px; border-bottom: 1px solid #e5e6e7; } .calendar-toolbar .nav-btn { cursor: pointer; color: #1ab394; } .calendar-table { width: 100%; table-layout: fixed; border-collapse: collapse; } .calendar-table th, .calendar-table td { border: 1px solid #e5e6e7; vertical-align: top; height: 88px; padding: 6px; } .calendar-table th { height: 36px; text-align: center; font-weight: 600; } .calendar-day { cursor: pointer; position: relative; } .calendar-day.other { color: #aaa; background: #fafafa; cursor: default; } .calendar-day.active { outline: 2px solid #1ab394; outline-offset: -2px; } .calendar-day .num { font-size: 14px; font-weight: 600; } .calendar-day .tag { margin-top: 6px; display: inline-block; padding: 2px 6px; border-radius: 2px; color: #fff; font-size: 12px; } .calendar-day .tag.ok { background: #1c84c6; } .calendar-day .tag.warn { background: #f8ac59; } .calendar-day .tag.none { background: #999; } .shift-panel { margin-top: 16px; display: none; } .driver-tag { display: inline-block; margin: 2px 4px 2px 0; padding: 3px 8px; border-radius: 2px; color: #fff; background: #1c84c6; } .driver-tag.manual { background: #f8ac59; } .driver-tag .del { margin-left: 8px; cursor: pointer; } </style> <div class="schedule-wrap"> <input type="hidden" id="lineId" value="${lineId!}"/> <input type="hidden" id="companyId" value="${companyId!}"/> <input type="hidden" id="autoPlanValue" value="${autoPlan!2}"/> <div class="schedule-header"> <div class="line">排班管理</div> <div style="margin-bottom: 10px;">线路名称:<b id="lineName">${lineName!}</b> 企业:<b id="companyName">${companyName!}</b></div> <div class="schedule-copy"> <span class="lbl">排班复制</span> <span>复制日期</span> <input type="text" class="form-control" id="copyStartDate" placeholder="开始日期"/> <span>至</span> <input type="text" class="form-control" id="copyEndDate" placeholder="结束日期"/> <span>粘贴起始日期</span> <input type="text" class="form-control" id="targetStartDate" placeholder="选择日期"/> <button class="btn btn-primary btn-sm" onclick="TLineSchedule.copySchedule()">确认复制</button> <div class="schedule-switch"> <span>开启智能排班</span> <input type="checkbox" id="autoPlanSwitch" onchange="TLineSchedule.onAutoPlanChange(this)"> </div> </div> </div> <div class="calendar-panel"> <div class="calendar-toolbar"> <span class="nav-btn" onclick="TLineSchedule.prevMonth()">< 上月</span> <span id="calendarMonthText"></span> <span class="nav-btn" onclick="TLineSchedule.nextMonth()">下月 ></span> </div> <table class="calendar-table"> <thead> <tr> <th>星期一</th> <th>星期二</th> <th>星期三</th> <th>星期四</th> <th>星期五</th> <th>星期六</th> <th>星期日</th> </tr> </thead> <tbody id="calendarBody"></tbody> </table> </div> <div class="shift-panel" id="shiftPanel"> <h4 id="selectedDayText"></h4> <table class="table table-striped table-bordered"> <thead> <tr> <th style="width: 20%;">班次</th> <th style="width: 10%;">车次数</th> <th style="width: 55%;">司机(黄色为后台手动排班)</th> <th style="width: 15%;">操作</th> </tr> </thead> <tbody id="shiftTableBody"></tbody> </table> </div> </div> <script src="${ctxPath}/static/modular/system/tLine/tLine_schedule.js"></script> <script> laydate.render({elem: '#copyStartDate'}); laydate.render({elem: '#copyEndDate'}); laydate.render({elem: '#targetStartDate'}); </script> @} ManagementYKDTravel/guns-admin/src/main/webapp/static/modular/system/tLine/tLine.js
@@ -149,6 +149,75 @@ }; /** * 排班管理 */ TLine.openScheduleManage = function () { if (!this.check()) { return; } if (!TLine.seItem.companyNum || parseInt(TLine.seItem.companyNum, 10) <= 0) { Feng.info("暂未关联企业"); return; } var ajax = new $ax(Feng.ctxPath + "/tLine/lineCompanyList", function (res) { var list = []; if (res && res.status == 200 && res.data) { list = res.data; } else if ($.isArray(res)) { list = res; } if (!list || list.length <= 0) { Feng.info("暂未关联企业"); return; } if (list.length == 1) { TLine.openSchedulePage(TLine.seItem.id, list[0].companyId); return; } var options = ""; for (var i = 0; i < list.length; i++) { options += '<option value="' + list[i].companyId + '">' + (list[i].companyName || ('企业' + list[i].companyId)) + '</option>'; } var html = '<div style="padding:20px 30px;">' + '<div style="margin-bottom:10px;">请选择企业:</div>' + '<select id="scheduleCompanyId" class="form-control">' + options + '</select>' + '</div>'; layer.open({ type: 1, title: '选择企业', area: ['420px', '220px'], btn: ['确定', '取消'], content: html, yes: function (index) { var companyId = $("#scheduleCompanyId").val(); if (!companyId) { Feng.info("请选择企业"); return; } layer.close(index); TLine.openSchedulePage(TLine.seItem.id, companyId); } }); }, function () { Feng.error("获取企业列表失败"); }); ajax.set("lineId", TLine.seItem.id); ajax.start(); }; TLine.openSchedulePage = function (lineId, companyId) { var index = layer.open({ type: 2, title: '排班管理', area: ['100%', '100%'], fix: false, maxmin: true, content: Feng.ctxPath + '/tLine/schedulePage?lineId=' + lineId + '&companyId=' + companyId }); this.layerIndex = index; }; /** * 删除跨城站点管理 */ TLine.opt = function (type) { ManagementYKDTravel/guns-admin/src/main/webapp/static/modular/system/tLine/tLine_schedule.js
New file @@ -0,0 +1,337 @@ var TLineSchedule = { lineId: null, companyId: null, currentYear: null, currentMonth: null, selectedDay: null, monthStatsMap: {} }; TLineSchedule.init = function () { this.lineId = $("#lineId").val(); this.companyId = $("#companyId").val(); var now = new Date(); this.currentYear = now.getFullYear(); this.currentMonth = now.getMonth() + 1; var autoPlan = $("#autoPlanValue").val(); $("#autoPlanSwitch").prop("checked", autoPlan == "1"); this.loadMonthData(); }; TLineSchedule.getMonthStr = function () { return this.currentYear + "-" + (this.currentMonth < 10 ? "0" + this.currentMonth : this.currentMonth); }; TLineSchedule.prevMonth = function () { if (this.currentMonth == 1) { this.currentMonth = 12; this.currentYear -= 1; } else { this.currentMonth -= 1; } this.selectedDay = null; $("#shiftPanel").hide(); this.loadMonthData(); }; TLineSchedule.nextMonth = function () { if (this.currentMonth == 12) { this.currentMonth = 1; this.currentYear += 1; } else { this.currentMonth += 1; } this.selectedDay = null; $("#shiftPanel").hide(); this.loadMonthData(); }; TLineSchedule.loadMonthData = function () { var _this = this; var ajax = new $ax(Feng.ctxPath + "/tLine/schedule/month", function (res) { if (!res || res.status != 200) { Feng.error((res && res.msg) || "加载月份排班失败"); return; } _this.monthStatsMap = {}; var days = res.data && res.data.days ? res.data.days : []; for (var i = 0; i < days.length; i++) { _this.monthStatsMap[days[i].day] = days[i]; } _this.renderCalendar(); }, function () { Feng.error("加载月份排班失败"); }); ajax.set("lineId", this.lineId); ajax.set("companyId", this.companyId); ajax.set("month", this.getMonthStr()); ajax.start(); }; TLineSchedule.renderCalendar = function () { $("#calendarMonthText").text(this.currentYear + "年 " + this.currentMonth + "月"); var firstDay = new Date(this.currentYear, this.currentMonth - 1, 1); var firstWeekDay = (firstDay.getDay() + 6) % 7; var maxDay = new Date(this.currentYear, this.currentMonth, 0).getDate(); var prevMaxDay = new Date(this.currentYear, this.currentMonth - 1, 0).getDate(); var html = ""; var dayNum = 1; var nextNum = 1; for (var row = 0; row < 6; row++) { html += "<tr>"; for (var col = 0; col < 7; col++) { var index = row * 7 + col; if (index < firstWeekDay) { var prevDay = prevMaxDay - firstWeekDay + index + 1; html += '<td class="calendar-day other"><div class="num">' + prevDay + '</div></td>'; } else if (dayNum > maxDay) { html += '<td class="calendar-day other"><div class="num">' + nextNum + '</div></td>'; nextNum++; } else { var dayStr = this.currentYear + "-" + this.pad2(this.currentMonth) + "-" + this.pad2(dayNum); var stat = this.monthStatsMap[dayStr]; var tagHtml = ""; if (stat) { if (stat.status == 1) { tagHtml = '<div class="tag ok">' + stat.assignNum + ' 已排班</div>'; } else if (stat.status == 2) { tagHtml = '<div class="tag warn">' + stat.assignNum + ' 未排满</div>'; } else { tagHtml = '<div class="tag none">无班次</div>'; } } var activeCls = this.selectedDay == dayStr ? " active" : ""; html += '<td class="calendar-day' + activeCls + '" data-day="' + dayStr + '" onclick="TLineSchedule.clickDay(this)">' + '<div class="num">' + dayNum + '</div>' + tagHtml + '</td>'; dayNum++; } } html += "</tr>"; } $("#calendarBody").html(html); }; TLineSchedule.clickDay = function (el) { var day = $(el).attr("data-day"); if (!day) { return; } this.selectedDay = day; $("#calendarBody .calendar-day").removeClass("active"); $(el).addClass("active"); this.loadDayDetail(day); }; TLineSchedule.loadDayDetail = function (day) { var _this = this; var ajax = new $ax(Feng.ctxPath + "/tLine/schedule/dayDetail", function (res) { if (!res || res.status != 200) { Feng.error((res && res.msg) || "加载排班详情失败"); return; } $("#shiftPanel").show(); $("#selectedDayText").text(day + " 排班信息"); _this.renderShiftTable(res.data || []); }, function () { Feng.error("加载排班详情失败"); }); ajax.set("lineId", this.lineId); ajax.set("companyId", this.companyId); ajax.set("day", day); ajax.start(); }; TLineSchedule.renderShiftTable = function (shiftList) { var html = ""; for (var i = 0; i < shiftList.length; i++) { var shift = shiftList[i]; var drivers = shift.drivers || []; var driverHtml = ""; for (var j = 0; j < drivers.length; j++) { var d = drivers[j]; var cls = d.assignType == 2 ? "driver-tag manual" : "driver-tag"; var del = d.assignType == 2 ? '<span class="del" onclick="TLineSchedule.deleteSchedule(' + d.id + ')">x</span>' : ""; driverHtml += '<span class="' + cls + '">' + this.escapeHtml((d.driverName || "") + "-" + (d.driverPhone || "")) + del + '</span>'; } if (driverHtml === "") { driverHtml = "-"; } var canAdd = shift.canAdd == 1; var addBtn = canAdd ? ('<a href="javascript:;" onclick="TLineSchedule.openAddDialog(' + shift.lineShiftId + ',' + shift.remainNum + ')">添加</a>') : '<span style="color:#999;">已满</span>'; html += '<tr>' + '<td>' + this.escapeHtml((shift.startTime || "") + " - " + (shift.endTime || "")) + '</td>' + '<td>' + (shift.carNum || 0) + '</td>' + '<td>' + driverHtml + '</td>' + '<td>' + addBtn + '</td>' + '</tr>'; } $("#shiftTableBody").html(html); }; TLineSchedule.openAddDialog = function (lineShiftId, remainNum) { var _this = this; var ajax = new $ax(Feng.ctxPath + "/tLine/schedule/drivers", function (res) { if (!res || res.status != 200) { Feng.error((res && res.msg) || "获取可排司机失败"); return; } var list = res.data || []; if (list.length <= 0) { Feng.info("暂无可添加司机"); return; } var itemsHtml = ""; for (var i = 0; i < list.length; i++) { itemsHtml += '<label style="display:block;margin-bottom:6px;">' + '<input type="checkbox" name="scheduleDriverCheck" value="' + list[i].driverId + '"> ' + _this.escapeHtml((list[i].driverName || "") + "-" + (list[i].driverPhone || "")) + '</label>'; } var html = '<div style="padding:15px 20px;max-height:360px;overflow:auto;">' + '<div style="margin-bottom:8px;">可添加人数:' + remainNum + '</div>' + itemsHtml + '</div>'; layer.open({ type: 1, title: '选择司机', area: ['420px', '520px'], btn: ['确定', '取消'], content: html, yes: function (index) { var ids = []; $("input[name='scheduleDriverCheck']:checked").each(function () { ids.push(parseInt($(this).val(), 10)); }); if (ids.length <= 0) { Feng.info("请至少选择一名司机"); return; } if (ids.length > remainNum) { Feng.info("最多只能添加" + remainNum + "名司机"); return; } _this.addSchedule(lineShiftId, ids, function () { layer.close(index); }); } }); }, function () { Feng.error("获取可排司机失败"); }); ajax.set("lineId", this.lineId); ajax.set("companyId", this.companyId); ajax.set("lineShiftId", lineShiftId); ajax.set("day", this.selectedDay); ajax.start(); }; TLineSchedule.addSchedule = function (lineShiftId, driverIds, callback) { var _this = this; var ajax = new $ax(Feng.ctxPath + "/tLine/schedule/add", function (res) { if (!res || res.status != 200) { Feng.error((res && res.msg) || "添加排班失败"); return; } Feng.success("添加成功"); if (callback) { callback(); } _this.loadDayDetail(_this.selectedDay); _this.loadMonthData(); }, function () { Feng.error("添加排班失败"); }); ajax.set("lineId", this.lineId); ajax.set("companyId", this.companyId); ajax.set("lineShiftId", lineShiftId); ajax.set("day", this.selectedDay); ajax.set("driverIds", JSON.stringify(driverIds)); ajax.start(); }; TLineSchedule.deleteSchedule = function (scheduleId) { var _this = this; layer.confirm("确认删除该手动排班记录吗?", {icon: 3, title: "提示"}, function (index) { layer.close(index); var ajax = new $ax(Feng.ctxPath + "/tLine/schedule/delete", function (res) { if (!res || res.status != 200) { Feng.error((res && res.msg) || "删除失败"); return; } Feng.success("删除成功"); _this.loadDayDetail(_this.selectedDay); _this.loadMonthData(); }, function () { Feng.error("删除失败"); }); ajax.set("scheduleId", scheduleId); ajax.set("lineId", _this.lineId); ajax.set("companyId", _this.companyId); ajax.start(); }); }; TLineSchedule.copySchedule = function () { var sourceStart = $("#copyStartDate").val(); var sourceEnd = $("#copyEndDate").val(); var targetStart = $("#targetStartDate").val(); if (!sourceStart || !sourceEnd || !targetStart) { Feng.info("请完整填写复制日期与粘贴起始日期"); return; } var _this = this; var ajax = new $ax(Feng.ctxPath + "/tLine/schedule/copy", function (res) { if (!res || res.status != 200) { Feng.error((res && res.msg) || "复制失败"); return; } var data = res.data || {}; Feng.success("复制完成,新增" + (data.addNum || 0) + "条,跳过" + (data.skipNum || 0) + "条"); _this.loadMonthData(); if (_this.selectedDay) { _this.loadDayDetail(_this.selectedDay); } }, function () { Feng.error("复制失败"); }); ajax.set("lineId", this.lineId); ajax.set("companyId", this.companyId); ajax.set("sourceStart", sourceStart); ajax.set("sourceEnd", sourceEnd); ajax.set("targetStart", targetStart); ajax.start(); }; TLineSchedule.onAutoPlanChange = function (el) { var checked = $(el).prop("checked"); var autoPlan = checked ? 1 : 2; var _this = this; var ajax = new $ax(Feng.ctxPath + "/tLine/schedule/autoPlan", function (res) { if (!res || res.status != 200) { Feng.error((res && res.msg) || "保存失败"); $(el).prop("checked", !checked); return; } $("#autoPlanValue").val(autoPlan); Feng.success("保存成功"); }, function () { $(el).prop("checked", !checked); Feng.error("保存失败"); }); ajax.set("lineId", this.lineId); ajax.set("companyId", this.companyId); ajax.set("autoPlan", autoPlan); ajax.start(); }; TLineSchedule.pad2 = function (num) { return num < 10 ? "0" + num : "" + num; }; TLineSchedule.escapeHtml = function (str) { return (str || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); }; $(function () { TLineSchedule.init(); });