From ef7ac57bb825b43be0e1afb3acee552b778d541b Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Wed, 17 Jun 2026 09:56:13 +0800 Subject: [PATCH] commit --- .../CoffeeTicketActivityController.java | 70 ++++ .../controller/CoffeeTicketNewController.java | 92 +++++ .../CoffeeTicketStatisticsController.java | 166 ++++++++ .../H5CoffeeTicketActivityController.java | 42 ++ .../H5CoffeeTicketDetailController.java | 74 ++++ .../H5CoffeeTicketMineController.java | 62 +++ .../models/CoffeeTicketActivity.java | 90 ++++ .../models/CoffeeTicketRecord.java | 74 ++++ .../service/CoffeeTicketActivityService.java | 62 +++ .../service/CoffeeTicketRecordService.java | 116 ++++++ .../impl/CoffeeTicketActivityServiceImpl.java | 199 +++++++++ .../impl/CoffeeTicketRecordServiceImpl.java | 299 ++++++++++++++ .../vo/CoffeeTicketActivityH5DetailVO.java | 28 ++ .../vo/CoffeeTicketActivityH5ListVO.java | 27 ++ .../vo/CoffeeTicketGrabStatisticsVO.java | 70 ++++ .../vo/CoffeeTicketMineListVO.java | 24 ++ .../qsv/controller/QsvBankController.java | 6 + .../qsv/controller/QsvNewController.java | 4 +- .../qsv/controller/QsvOnlineController.java | 11 +- .../qsv/controller/QsvQuizRankController.java | 15 + .../qsv/controller/QsvSurveyController.java | 3 + .../qsv/h5controller/H5QsvQuizController.java | 2 +- .../h5controller/H5QsvSurveyController.java | 8 +- .../qsv/h5controller/H5QsvVoteController.java | 7 +- .../dayofficework/qsv/models/QsvActivity.java | 2 +- .../service/impl/QsvQuizRankServiceImpl.java | 1 + .../coffeeTicket/activity/index.html | 139 +++++++ .../dayofficework/coffeeTicket/new/index.html | 383 +++++++++++++++++ .../coffeeTicket/statistics/index.html | 212 ++++++++++ .../zhgh/dayofficework/qsv/activity/new.html | 269 ++++++------ .../zhgh/dayofficework/qsv/online/index.html | 258 +++++++++++- .../zhgh/dayofficework/qsv/quiz/rank.html | 19 + .../coffeeTicket/detail/index.html | 374 +++++++++++++++++ .../coffeeTicket/list/index.html | 390 ++++++++++++++++++ .../coffeeTicket/mine/index.html | 347 ++++++++++++++++ 35 files changed, 3778 insertions(+), 167 deletions(-) create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketActivityController.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketNewController.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketStatisticsController.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketActivityController.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketDetailController.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketMineController.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketActivity.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketRecord.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketActivityService.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketRecordService.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketActivityServiceImpl.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketRecordServiceImpl.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5DetailVO.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5ListVO.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketGrabStatisticsVO.java create mode 100644 src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketMineListVO.java create mode 100644 src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/activity/index.html create mode 100644 src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/new/index.html create mode 100644 src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/statistics/index.html create mode 100644 src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/detail/index.html create mode 100644 src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/list/index.html create mode 100644 src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/mine/index.html diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketActivityController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketActivityController.java new file mode 100644 index 0000000..969f430 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketActivityController.java @@ -0,0 +1,70 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketActivityService; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.validation.Valid; + +@IocBean +@At("/platform/coffeeTicket/activity") +@Ok("json:full") +@ApiOperation("咖啡抢票活动管理") +public class CoffeeTicketActivityController { + + @Inject + private CoffeeTicketActivityService coffeeTicketActivityService; + + @At("") + @Ok("beetl:/platform/zhgh/dayofficework/coffeeTicket/activity/index.html") + @SaCheckPermission("coffeeTicket.activity") + public void index() { + } + + /** + * 分页查询咖啡抢票活动。 + * + * @param pageForm 分页参数,包含 pageNumber、pageSize、排序字段等公共分页信息 + * @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选 + * @param title 活动标题关键字;为空时不按标题筛选 + * @return Result,data 为 Pagination,list 为活动列表,totalCount 为总数 + */ + @At + @SaCheckPermission("coffeeTicket.activity") + public Result pageData(@Valid PageForm pageForm, Integer year, String title) { + Cnd cnd = Cnd.NEW(); + cnd.andEX("YEAR(activityStartDate)", "=", year); + if (StrUtil.isNotBlank(title)) { + cnd.and(Cnd.likeEX("title", title)); + } + Pagination pagination = coffeeTicketActivityService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + return Result.success(pagination); + } + + @At + @SaCheckPermission(value = {"coffeeTicket.activity", "coffeeTicket.new"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + public Result findOne(@Valid String id) { + CoffeeTicketActivity activity = coffeeTicketActivityService.fetch(id); + return Result.success(activity); + } + + @At + @SaCheckPermission("coffeeTicket.activity") + @Aop(TransAop.READ_COMMITTED) + public Result delete(@Valid String id) { + coffeeTicketActivityService.deleteActivity(id); + return Result.success(); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketNewController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketNewController.java new file mode 100644 index 0000000..15f2786 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketNewController.java @@ -0,0 +1,92 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketActivityService; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; + +@IocBean +@At("/platform/coffeeTicket/new") +@Ok("json:full") +@ApiOperation("咖啡抢票平台活动创建") +public class CoffeeTicketNewController { + + @Inject + private CoffeeTicketActivityService coffeeTicketActivityService; + + @At("") + @Ok("beetl:/platform/zhgh/dayofficework/coffeeTicket/new/index.html") + @SaCheckPermission("coffeeTicket.new") + public void index() { + } + + /** + * 保存咖啡抢票活动。 + * + * @param activity 页面提交的活动配置,包含标题、封面、描述、活动开始/结束日期、可预约星期、票数、每日预约时间段、预约方式、活动地点和参与须知 + * @return Result,成功时 data 为 CoffeeTicketActivity,可通过 data.id 获取活动ID + */ + @At + @SaCheckPermission("coffeeTicket.new") + @Aop(TransAop.READ_COMMITTED) + public Result save(@Param("data") @Valid CoffeeTicketActivity activity) { + Result validateResult = validateActivity(activity); + if (validateResult != null) { + return validateResult; + } + CoffeeTicketActivity savedActivity = coffeeTicketActivityService.saveActivity(activity); + return Result.success(savedActivity); + } + + /** + * 只做入参完整性和范围校验,业务保存交给 service 层处理。 + */ + private Result validateActivity(CoffeeTicketActivity activity) { + if (activity == null) { + return Result.error("活动信息不能为空"); + } + if (StrUtil.isBlank(activity.getTitle())) { + return Result.error("请输入活动标题"); + } + if (StrUtil.isBlank(activity.getCover())) { + return Result.error("请上传活动封面"); + } + if (StrUtil.isBlank(activity.getDescription())) { + return Result.error("请输入活动描述"); + } + if (activity.getActivityStartDate() == null || activity.getActivityEndDate() == null) { + return Result.error("请选择活动周期"); + } + if (activity.getActivityStartDate().after(activity.getActivityEndDate())) { + return Result.error("活动结束日期必须晚于开始日期"); + } + if (CollUtil.isEmpty(activity.getWeekDays())) { + return Result.error("请选择可预约星期"); + } + if (activity.getTicketCount() == null || activity.getTicketCount() <= 0) { + return Result.error("每次可预约票数必须大于0"); + } + if (StrUtil.isBlank(activity.getReserveStartTime()) || StrUtil.isBlank(activity.getReserveEndTime())) { + return Result.error("请选择每日可预约时间段"); + } + if (activity.getReserveStartTime().compareTo(activity.getReserveEndTime()) >= 0) { + return Result.error("每日可预约结束时间必须晚于开始时间"); + } + if (StrUtil.isBlank(activity.getReserveMode())) { + return Result.error("请选择预约方式"); + } + return null; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketStatisticsController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketStatisticsController.java new file mode 100644 index 0000000..d8b3345 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/controller/CoffeeTicketStatisticsController.java @@ -0,0 +1,166 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.controller; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.date.DateUtil; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.CommonDownloadUtil; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketActivityService; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketRecordService; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.vo.CoffeeTicketGrabStatisticsVO; +import io.swagger.annotations.ApiOperation; +import org.apache.poi.ss.usermodel.Workbook; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@IocBean +@At("/platform/coffeeTicket/statistics") +@Ok("json:full") +@ApiOperation("咖啡抢票统计") +public class CoffeeTicketStatisticsController { + + @Inject + private CoffeeTicketActivityService coffeeTicketActivityService; + + @Inject + private CoffeeTicketRecordService coffeeTicketRecordService; + + @At("") + @Ok("beetl:/platform/zhgh/dayofficework/coffeeTicket/statistics/index.html") + @SaCheckPermission("coffeeTicket.statistics") + public void index() { + } + + /** + * 查询统计页活动下拉列表。 + * + * @param year 活动开始日期所属年度,传 yyyy 年份;为空时返回全部活动 + * @return Result,data 为 List,前端使用 id 作为活动值、title 作为活动名称 + */ + @At + @SaCheckPermission("coffeeTicket.statistics") + public Result activityList(@Param("year") Integer year) { + return Result.success(coffeeTicketActivityService.statisticsActivityList(year)); + } + + /** + * 分页查询咖啡抢票统计明细。 + * + * @param pageForm 分页参数,包含 pageNumber、pageSize、排序字段等公共分页信息 + * @param year 活动开始日期所属年度,传 yyyy 年份;为空且 activityId 为空时查询全部记录 + * @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动 + * @param unionId 抢票用户所属工会ID,传 vw_user.unionId;为空时不按工会筛选 + * @param unitId 抢票用户所属单位ID,传 vw_user.unitId;为空时不按单位筛选 + * @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选 + * @return Result,data 为 Pagination,list 为 CoffeeTicketGrabStatisticsVO 抢票明细列表 + */ + @At + @SaCheckPermission("coffeeTicket.statistics") + public Result pageData(@Valid PageForm pageForm, @Param("year") Integer year, @Param("activityId") String activityId, + @Param("unionId") String unionId, @Param("unitId") String unitId, @Param("grabDate") String grabDate) { + Pagination pagination = coffeeTicketRecordService.statisticsPageData(pageForm, year, activityId, unionId, unitId, grabDate); + return Result.success(pagination); + } + + /** + * 查询当前统计条件下有抢票记录的日期。 + * + * @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选 + * @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动 + * @param unionId 抢票用户所属工会ID,传 vw_user.unionId;为空时不按工会筛选 + * @param unitId 抢票用户所属单位ID,传 vw_user.unitId;为空时不按单位筛选 + * @return Result,data 为 yyyy-MM-dd 字符串列表,用于限制抢票日期选择器可选日期 + */ + @At + @SaCheckPermission("coffeeTicket.statistics") + public Result grabDateList(@Param("year") Integer year, @Param("activityId") String activityId, + @Param("unionId") String unionId, @Param("unitId") String unitId) { + return Result.success(coffeeTicketRecordService.statisticsGrabDateList(year, activityId, unionId, unitId)); + } + + /** + * 导出当前筛选条件下的咖啡抢票统计明细。 + * + * @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选 + * @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动 + * @param unionId 抢票用户所属工会ID,传 vw_user.unionId;为空时不按工会筛选 + * @param unitId 抢票用户所属单位ID,传 vw_user.unitId;为空时不按单位筛选 + * @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选 + * @param response HttpServletResponse,用于输出 xlsx 文件流 + * @return void,浏览器直接下载 Excel 文件 + */ + @At + @Ok("void") + @SaCheckPermission("coffeeTicket.statistics") + public void export(@Param("year") Integer year, @Param("activityId") String activityId, + @Param("unionId") String unionId, @Param("unitId") String unitId, + @Param("grabDate") String grabDate, HttpServletResponse response) { + List list = coffeeTicketRecordService.statisticsExportList(year, activityId, unionId, unitId, grabDate); + List exportList = list.stream().map(this::buildExportRow).collect(Collectors.toList()); + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("姓名", "userName", 20)); + entities.add(new ExcelExportEntity("工号", "loginName", 20)); + entities.add(new ExcelExportEntity("单位", "unitName", 25)); + entities.add(new ExcelExportEntity("分工会", "unionName", 25)); + entities.add(new ExcelExportEntity("活动名称", "activityTitle", 30)); + entities.add(new ExcelExportEntity("抢票张数", "ticketNum", 15)); + entities.add(new ExcelExportEntity("实际抢票时间", "grabTime", 25)); + + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, exportList); + CommonDownloadUtil.download("咖啡抢票统计.xlsx", workbook, response); + } + + /** + * 删除统计页面中的一条抢票记录。 + * + * @param recordId 抢票记录ID,传 coffee_ticket_record 表主键;删除后该用户可重新参与该活动抢票 + * @return Result,code 为 0 表示删除成功;非 0 时 msg 为失败原因 + */ + @At + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("coffeeTicket.statistics.delete") + public Result delete(@Param("recordId") @Valid String recordId) { + try { + coffeeTicketRecordService.deleteStatisticsRecord(recordId); + return Result.success(); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } + + /** + * 组装导出行数据,字段与页面当前统计列表保持一致,不包含操作列。 + * + * @param item 一条抢票统计明细 + * @return NutMap,key 与 ExcelExportEntity 中的字段名对应 + */ + private NutMap buildExportRow(CoffeeTicketGrabStatisticsVO item) { + return NutMap.NEW() + .setv("userName", item.getUserName()) + .setv("loginName", item.getLoginName()) + .setv("unitName", item.getUnitName()) + .setv("unionName", item.getUnionName()) + .setv("activityTitle", item.getActivityTitle()) + .setv("ticketNum", item.getTicketNum()) + .setv("grabTime", item.getGrabTime() == null ? "" : DateUtil.format(item.getGrabTime(), "yyyy-MM-dd HH:mm:ss")); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketActivityController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketActivityController.java new file mode 100644 index 0000000..2a09778 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketActivityController.java @@ -0,0 +1,42 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.h5controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketActivityService; +import io.swagger.annotations.ApiOperation; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.validation.Valid; + +@IocBean +@At("/platform/h5/coffeeTicket/list") +@Ok("json:full") +@ApiOperation("手机端咖啡抢票活动列表") +public class H5CoffeeTicketActivityController { + + @Inject + private CoffeeTicketActivityService coffeeTicketActivityService; + + @At("") + @Ok("beetl:/platform/zhghh5/dayofficework/coffeeTicket/list/index.html") + @SaCheckPermission("h5.coffeeTicket.list") + public void index() { + } + + /** + * 手机端抢票活动列表。 + * + * @param pageForm 分页参数,searchKeyword 可传活动标题或活动地点关键字 + * @param status 状态筛选,all全部、active报名中、upcoming即将开始、ended已结束 + * @return Result,data 为 Pagination,list 为 CoffeeTicketActivity 活动列表 + */ + @At + @SaCheckPermission("h5.coffeeTicket.list") + public Result pageData(@Valid PageForm pageForm, String status) { + return Result.success(coffeeTicketActivityService.h5PageData(pageForm, status)); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketDetailController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketDetailController.java new file mode 100644 index 0000000..55c1ee3 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketDetailController.java @@ -0,0 +1,74 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.h5controller; + +import cn.hutool.core.bean.BeanUtil; +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketRecord; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketActivityService; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketRecordService; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.vo.CoffeeTicketActivityH5DetailVO; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; + +@IocBean +@At("/platform/h5/coffeeTicket/detail") +@Ok("json:full") +@ApiOperation("手机端咖啡抢票活动详情") +public class H5CoffeeTicketDetailController { + + @Inject + private CoffeeTicketActivityService coffeeTicketActivityService; + + @Inject + private CoffeeTicketRecordService coffeeTicketRecordService; + + @At("") + @Ok("beetl:/platform/zhghh5/dayofficework/coffeeTicket/detail/index.html") + @SaCheckPermission("h5.coffeeTicket.list") + public void index() { + } + + @At + @SaCheckPermission("h5.coffeeTicket.list") + public Result findOne(@Valid String id) { + CoffeeTicketActivity activity = coffeeTicketActivityService.fetch(id); + if (activity == null) { + return Result.error("活动不存在"); + } + CoffeeTicketActivityH5DetailVO detailVO = BeanUtil.copyProperties(activity, CoffeeTicketActivityH5DetailVO.class); + CoffeeTicketRecord userRecord = coffeeTicketRecordService.fetchLatestUserRecord(id, SecurityUtil.getUserId()); + detailVO.setHasGrabbed(userRecord != null); + detailVO.setMyTicketNum(userRecord == null ? 0 : coffeeTicketRecordService.countUserTicketNum(id, SecurityUtil.getUserId())); + detailVO.setMyGrabTime(userRecord == null ? null : userRecord.getGrabTime()); + return Result.success(detailVO); + } + + /** + * 手机端立即抢票并保存记录。 + * + * @param id 活动ID,前端从详情页地址参数或活动详情数据中传入 + * @param ticketNum 本次抢票张数,当前页面默认传 1,用于判断余票是否足够并写入记录 + * @return Result,code 为 0 表示抢票记录保存成功,data 为 CoffeeTicketRecord;非 0 时 msg 为不可抢票原因 + */ + @At + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("h5.coffeeTicket.list") + public Result grab(@Param("id") @Valid String id, @Param("ticketNum") Integer ticketNum) { + try { + CoffeeTicketRecord record = coffeeTicketActivityService.grabH5Ticket(id, ticketNum); + return Result.success("抢票成功", record); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketMineController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketMineController.java new file mode 100644 index 0000000..1de9b32 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/h5controller/H5CoffeeTicketMineController.java @@ -0,0 +1,62 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.h5controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketRecordService; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; + +@IocBean +@At("/platform/h5/coffeeTicket/mine") +@Ok("json:full") +@ApiOperation("手机端我的咖啡抢票") +public class H5CoffeeTicketMineController { + + @Inject + private CoffeeTicketRecordService coffeeTicketRecordService; + + @At("") + @Ok("beetl:/platform/zhghh5/dayofficework/coffeeTicket/mine/index.html") + @SaCheckPermission("h5.coffeeTicket.list") + public void index() { + } + + /** + * 手机端我的抢票列表。 + * + * @param pageForm 分页参数,searchKeyword 可传活动标题关键字 + * @return Result,data 为 Pagination,list 为 CoffeeTicketMineListVO,按抢票时间倒序排列 + */ + @At + @SaCheckPermission("h5.coffeeTicket.list") + public Result pageData(@Valid PageForm pageForm) { + return Result.success(coffeeTicketRecordService.h5MinePageData(pageForm)); + } + + /** + * 手机端取消抢票。 + * + * @param recordId 抢票记录ID + * @return Result,code 为 0 表示取消成功 + */ + @At("/cancel") + @SaCheckPermission("h5.coffeeTicket.list") + @Aop(TransAop.READ_COMMITTED) + public Result cancel(@Param("recordId") @Valid String recordId) { + try { + coffeeTicketRecordService.cancelH5Ticket(recordId); + return Result.success("取消成功", null); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketActivity.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketActivity.java new file mode 100644 index 0000000..0480559 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketActivity.java @@ -0,0 +1,90 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +@Table("coffee_ticket_activity") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("咖啡抢票活动表") +public class CoffeeTicketActivity extends BaseModel implements Serializable { + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("活动标题") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String title; + + @Column + @Comment("活动封面") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String cover; + + @Column + @Comment("活动描述") + @ColDefine(type = ColType.TEXT) + private String description; + + @Column + @Comment("活动开始日期") + @ColDefine(type = ColType.DATE) + private Date activityStartDate; + + @Column + @Comment("活动结束日期") + @ColDefine(type = ColType.DATE) + private Date activityEndDate; + + @Column + @Comment("可预约星期,保存周一至周日对应的1-7") + @ColDefine(type = ColType.MYSQL_JSON) + private List weekDays; + + @Column + @Comment("每次可预约票数") + @ColDefine(type = ColType.INT) + private Integer ticketCount; + + @Column + @Comment("每日可预约开始时间") + @ColDefine(type = ColType.VARCHAR, width = 5) + private String reserveStartTime; + + @Column + @Comment("每日可预约结束时间") + @ColDefine(type = ColType.VARCHAR, width = 5) + private String reserveEndTime; + + @Column + @Comment("预约方式:WEEKLY每周固定,SPECIAL特殊日期") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String reserveMode; + + @Column + @Comment("活动地点") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String address; + + @Column + @Comment("参与须知") + @ColDefine(type = ColType.TEXT) + private String notice; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketRecord.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketRecord.java new file mode 100644 index 0000000..7472c43 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/models/CoffeeTicketRecord.java @@ -0,0 +1,74 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Index; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableIndexes; +import org.nutz.dao.entity.annotation.TableMeta; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.io.Serializable; +import java.util.Date; + +@EqualsAndHashCode(callSuper = true) +@Data +@Table("coffee_ticket_record") +@TableIndexes({ + @Index(name = "UK_COFFEE_TICKET_RECORD_ACTIVITY_USER", fields = {"activityId", "userId"}, unique = true) +}) +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("咖啡抢票记录表") +public class CoffeeTicketRecord extends BaseModel implements Serializable { + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("活动ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String activityId; + + @Column + @Comment("活动标题") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String activityTitle; + + @Column + @Comment("抢票张数") + @ColDefine(type = ColType.INT) + private Integer ticketNum; + + @Column + @Comment("抢票时间") + @ColDefine(type = ColType.DATETIME) + private Date grabTime; + + @Column + @Comment("用户ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String userId; + + @Column + @Comment("姓名") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String userName; + + @Column + @Comment("工号") + @ColDefine(type = ColType.VARCHAR, width = 120) + private String loginName; + + @Column + @Comment("状态:SUCCESS已抢票") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String status; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketActivityService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketActivityService.java new file mode 100644 index 0000000..bb85e37 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketActivityService.java @@ -0,0 +1,62 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketRecord; + +import java.util.List; + +public interface CoffeeTicketActivityService extends BaseService { + + /** + * 保存咖啡抢票活动基础配置。 + * + * @param activity 页面提交的活动配置,包含标题、封面、活动日期、预约星期、票数、每日预约时间段、地点和须知等内容 + * @return CoffeeTicketActivity 保存后的活动实体,新增时会带有生成后的活动ID + */ + CoffeeTicketActivity saveActivity(CoffeeTicketActivity activity); + + /** + * 删除咖啡抢票活动及其基础配置。 + * + * @param id 活动ID + */ + void deleteActivity(String id); + + /** + * 手机端分页查询可展示的咖啡抢票活动。 + * + * @param pageForm 分页参数,searchKeyword 可传活动标题或活动地点关键字 + * @param status 活动状态,all全部、active报名中、upcoming即将开始、ended已结束 + * @return Pagination,list 为 CoffeeTicketActivity 活动实体列表 + */ + Pagination h5PageData(PageForm pageForm, String status); + + /** + * 查询统计页面年度活动下拉列表。 + * + * @param year 活动开始日期所属年度,传 yyyy 年份;为空时返回全部咖啡抢票活动 + * @return List,按活动开始日期倒序排列 + */ + List statisticsActivityList(Integer year); + + /** + * 校验手机端抢票请求是否符合活动开放规则。 + * + * @param id 活动ID,必须传 coffee_ticket_activity 表中的活动主键 + * @param ticketNum 本次抢票张数,当前详情页默认传 1,用于判断是否超过活动配置的每次可预约票数 + * @return String,返回 null 表示校验通过;返回非空字符串表示不可抢票的具体原因 + */ + String checkH5GrabTicket(String id, Integer ticketNum); + + /** + * 手机端立即抢票并保存抢票记录。 + * + * @param id 活动ID,必须传 coffee_ticket_activity 表中的活动主键 + * @param ticketNum 本次抢票张数,当前详情页默认传 1 + * @return CoffeeTicketRecord,返回保存后的抢票记录;校验失败时抛出 IllegalStateException,异常消息为前端提示文案 + */ + CoffeeTicketRecord grabH5Ticket(String id, Integer ticketNum); +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketRecordService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketRecordService.java new file mode 100644 index 0000000..b2473c1 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/CoffeeTicketRecordService.java @@ -0,0 +1,116 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketRecord; + +import java.util.List; + +public interface CoffeeTicketRecordService extends BaseService { + + /** + * 统计指定活动已经占用的票数。 + * + * @param activityId 活动ID,必须传 coffee_ticket_activity 表中的活动主键 + * @return int,返回 coffee_ticket_record 中该活动 ticketNum 的合计值,无记录时返回 0 + */ + int countGrabbedTicketNum(String activityId); + + /** + * 判断用户是否已经抢过指定活动的票。 + * + * @param activityId 活动ID + * @param userId 当前登录用户ID + * @return boolean,true 表示该用户已有该活动抢票记录 + */ + boolean hasUserGrabbed(String activityId, String userId); + + /** + * 查询用户在指定活动下最近一次抢票记录。 + * + * @param activityId 活动ID + * @param userId 当前登录用户ID + * @return CoffeeTicketRecord,返回最近一次抢票记录;没有记录时返回 null + */ + CoffeeTicketRecord fetchLatestUserRecord(String activityId, String userId); + + /** + * 统计用户在指定活动下已抢票数。 + * + * @param activityId 活动ID + * @param userId 当前登录用户ID + * @return int,返回该用户在当前活动下 ticketNum 的合计值,无记录时返回 0 + */ + int countUserTicketNum(String activityId, String userId); + + /** + * 保存手机端抢票记录。 + * + * @param activity 已通过规则校验的活动实体,用于回填活动ID和标题 + * @param ticketNum 本次抢票张数,当前 H5 详情页默认传 1 + * @return CoffeeTicketRecord,返回已经写入数据库的抢票记录实体 + */ + CoffeeTicketRecord saveGrabRecord(CoffeeTicketActivity activity, Integer ticketNum); + + /** + * 手机端分页查询当前用户的抢票记录。 + * + * @param pageForm 分页参数,searchKeyword 可传活动标题关键字 + * @return Pagination,list 为 CoffeeTicketMineListVO,按抢票时间倒序排列 + */ + Pagination h5MinePageData(PageForm pageForm); + + /** + * PC端分页查询咖啡抢票统计明细。 + * + * @param pageForm 分页参数,包含 pageNumber、pageSize、排序字段等公共分页信息 + * @param year 活动开始日期所属年度,传 yyyy 年份;为空且 activityId 为空时查询全部记录 + * @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动 + * @param unionId 抢票用户所属工会ID,传 vw_user.unionId;为空时不按工会筛选 + * @param unitId 抢票用户所属单位ID,传 vw_user.unitId;为空时不按单位筛选 + * @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选 + * @return Pagination,list 为 CoffeeTicketGrabStatisticsVO,展示用户、活动、票数和抢票时间 + */ + Pagination statisticsPageData(PageForm pageForm, Integer year, String activityId, String unionId, String unitId, String grabDate); + + /** + * 查询当前统计条件下的全部抢票明细,用于按页面筛选条件导出。 + * + * @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选 + * @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动 + * @param unionId 抢票用户所属工会ID,传 vw_user.unionId;为空时不按工会筛选 + * @param unitId 抢票用户所属单位ID,传 vw_user.unitId;为空时不按单位筛选 + * @param grabDate 抢票日期,传 yyyy-MM-dd;为空时不按实际抢票日期筛选 + * @return List,返回与统计列表字段一致的全部匹配数据 + */ + List statisticsExportList(Integer year, String activityId, String unionId, String unitId, String grabDate); + + /** + * 查询当前统计条件下存在抢票记录的日期。 + * + * @param year 活动开始日期所属年度,传 yyyy 年份;为空时不按年度筛选 + * @param activityId 活动ID,传 coffee_ticket_activity 表主键;为空时按年度查询多个活动 + * @param unionId 抢票用户所属工会ID,传 vw_user.unionId;为空时不按工会筛选 + * @param unitId 抢票用户所属单位ID,传 vw_user.unitId;为空时不按单位筛选 + * @return List,返回 yyyy-MM-dd 格式的实际抢票日期列表,前端据此控制日期选择器可选范围 + */ + List statisticsGrabDateList(Integer year, String activityId, String unionId, String unitId); + + /** + * PC端统计页面删除一条抢票记录。 + * + * @param recordId 抢票记录ID,必须传 coffee_ticket_record 表中的记录主键 + * @return void,成功则删除该条抢票记录 + */ + void deleteStatisticsRecord(String recordId); + + /** + * 手机端取消一条抢票记录。 + * + * @param recordId 要取消的抢票记录ID + * @return void,成功则直接删除该条抢票记录 + */ + void cancelH5Ticket(String recordId); +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketActivityServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketActivityServiceImpl.java new file mode 100644 index 0000000..b22cb62 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketActivityServiceImpl.java @@ -0,0 +1,199 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketRecord; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketActivityService; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketRecordService; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.vo.CoffeeTicketActivityH5ListVO; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.DaoException; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +@IocBean(args = {"refer:dao"}) +public class CoffeeTicketActivityServiceImpl extends BaseServiceImpl implements CoffeeTicketActivityService { + + @Inject + private CoffeeTicketRecordService coffeeTicketRecordService; + + public CoffeeTicketActivityServiceImpl(Dao dao) { + super(dao); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public CoffeeTicketActivity saveActivity(CoffeeTicketActivity activity) { + dao().insertOrUpdate(activity); + return activity; + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void deleteActivity(String id) { + dao().delete(CoffeeTicketActivity.class, id); + } + + @Override + public Pagination h5PageData(PageForm pageForm, String status) { + Cnd cnd = Cnd.NEW(); + if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) { + String keyword = "%" + pageForm.getSearchKeyword() + "%"; + cnd.and(Cnd.exps("title", "like", keyword).or("address", "like", keyword)); + } + Date todayStart = DateUtil.beginOfDay(new Date()); + Date todayEnd = DateUtil.endOfDay(new Date()); + if ("active".equals(status)) { + cnd.and("activityStartDate", "<=", todayEnd); + cnd.and("activityEndDate", ">=", todayStart); + } else if ("upcoming".equals(status)) { + cnd.and("activityStartDate", ">", todayEnd); + } else if ("ended".equals(status)) { + cnd.and("activityEndDate", "<", todayStart); + } + cnd.desc("activityStartDate"); + Pagination pagination = listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + List activityList = pagination.getList(CoffeeTicketActivity.class); + List voList = activityList.stream().map(this::buildH5ListVO).toList(); + pagination.setList(voList); + return pagination; + } + + @Override + public List statisticsActivityList(Integer year) { + Cnd cnd = Cnd.NEW(); + cnd.andEX("YEAR(activityStartDate)", "=", year); + cnd.desc("activityStartDate"); + return dao().query(CoffeeTicketActivity.class, cnd); + } + + @Override + public String checkH5GrabTicket(String id, Integer ticketNum) { + if (StrUtil.isBlank(id)) { + return "活动ID不能为空"; + } + int currentTicketNum = ticketNum == null ? 1 : ticketNum; + if (currentTicketNum <= 0) { + return "抢票张数必须大于0"; + } + CoffeeTicketActivity activity = fetch(id); + if (activity == null) { + return "活动不存在"; + } + Date now = new Date(); + if (activity.getActivityStartDate() == null || activity.getActivityEndDate() == null) { + return "活动周期未配置"; + } + if (now.before(DateUtil.beginOfDay(activity.getActivityStartDate()))) { + return "活动尚未开始"; + } + if (now.after(DateUtil.endOfDay(activity.getActivityEndDate()))) { + return "活动已结束"; + } + List weekDays = activity.getWeekDays(); + if (weekDays == null || weekDays.isEmpty()) { + return "活动未配置可预约星期"; + } + if (!weekDays.contains(getTodayWeekValue(now))) { + return "今天不在可预约星期范围内"; + } + if (StrUtil.isBlank(activity.getReserveStartTime()) || StrUtil.isBlank(activity.getReserveEndTime())) { + return "活动未配置每日可预约时间段"; + } + String currentTime = DateUtil.format(now, "HH:mm"); + if (currentTime.compareTo(activity.getReserveStartTime()) < 0 || currentTime.compareTo(activity.getReserveEndTime()) > 0) { + return "当前不在每日可预约时间段内"; + } + if (activity.getTicketCount() == null || activity.getTicketCount() <= 0) { + return "活动票数不足"; + } + if (currentTicketNum > activity.getTicketCount()) { + return "本次抢票张数超过每次可预约票数"; + } + return null; + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public CoffeeTicketRecord grabH5Ticket(String id, Integer ticketNum) { + int currentTicketNum = ticketNum == null ? 1 : ticketNum; + String errorMsg = checkH5GrabTicket(id, currentTicketNum); + if (errorMsg != null) { + throw new IllegalStateException(errorMsg); + } + CoffeeTicketActivity activity = fetch(id); + if (coffeeTicketRecordService.hasUserGrabbed(activity.getId(), SecurityUtil.getUserId())) { + throw new IllegalStateException("您已抢过该活动票"); + } + int grabbedTicketNum = coffeeTicketRecordService.countGrabbedTicketNum(activity.getId()); + int remainingTicketNum = activity.getTicketCount() - grabbedTicketNum; + if (remainingTicketNum < currentTicketNum) { + throw new IllegalStateException("票数不足"); + } + try { + return coffeeTicketRecordService.saveGrabRecord(activity, currentTicketNum); + } catch (DaoException e) { + if (isDuplicateGrabException(e)) { + throw new IllegalStateException("您已抢过该活动票"); + } + throw e; + } + } + + /** + * 组装手机端列表展示对象,补齐活动总票数、已抢票数和剩余票数字段。 + * + * @param activity 当前页活动实体 + * @return CoffeeTicketActivityH5ListVO,包含活动基础字段和票数统计字段 + */ + private CoffeeTicketActivityH5ListVO buildH5ListVO(CoffeeTicketActivity activity) { + CoffeeTicketActivityH5ListVO vo = BeanUtil.copyProperties(activity, CoffeeTicketActivityH5ListVO.class); + int totalTicketNum = activity.getTicketCount() == null ? 0 : activity.getTicketCount(); + int grabbedTicketNum = coffeeTicketRecordService.countGrabbedTicketNum(activity.getId()); + vo.setTotalTicketNum(totalTicketNum); + vo.setGrabbedTicketNum(grabbedTicketNum); + vo.setRemainingTicketNum(Math.max(totalTicketNum - grabbedTicketNum, 0)); + return vo; + } + + /** + * 将 Java 星期值转换为页面保存的 1-7 格式,其中 1 表示周一、7 表示周日。 + * + * @param date 需要判断星期的日期 + * @return String,返回当前日期对应的星期配置值 + */ + private String getTodayWeekValue(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + if (dayOfWeek == Calendar.SUNDAY) { + return "7"; + } + return String.valueOf(dayOfWeek - 1); + } + + /** + * 数据库唯一索引用于兜底防止同一用户并发重复抢同一活动。 + * + * @param e 抢票记录插入时抛出的数据库异常 + * @return boolean,true 表示命中 activityId + userId 唯一约束 + */ + private boolean isDuplicateGrabException(DaoException e) { + String message = e.getMessage(); + return StrUtil.containsIgnoreCase(message, "Duplicate") + || StrUtil.containsIgnoreCase(message, "UK_COFFEE_TICKET_RECORD_ACTIVITY_USER"); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketRecordServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketRecordServiceImpl.java new file mode 100644 index 0000000..f28164f --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/service/impl/CoffeeTicketRecordServiceImpl.java @@ -0,0 +1,299 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.service.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.date.DatePattern; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketRecord; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.service.CoffeeTicketRecordService; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.vo.CoffeeTicketGrabStatisticsVO; +import com.budwk.app.zhgh.dayofficework.coffeeTicket.vo.CoffeeTicketMineListVO; +import cn.hutool.core.date.DateUtil; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; + +import java.util.Calendar; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@IocBean(args = {"refer:dao"}) +public class CoffeeTicketRecordServiceImpl extends BaseServiceImpl implements CoffeeTicketRecordService { + + public CoffeeTicketRecordServiceImpl(Dao dao) { + super(dao); + } + + @Override + public int countGrabbedTicketNum(String activityId) { + return dao().func(CoffeeTicketRecord.class, "sum", "ticketNum", Cnd.where("activityId", "=", activityId)); + } + + @Override + public boolean hasUserGrabbed(String activityId, String userId) { + return dao().count(CoffeeTicketRecord.class, Cnd.where("activityId", "=", activityId).and("userId", "=", userId)) > 0; + } + + @Override + public CoffeeTicketRecord fetchLatestUserRecord(String activityId, String userId) { + Cnd cnd = Cnd.where("activityId", "=", activityId).and("userId", "=", userId); + cnd.desc("grabTime"); + return dao().fetch(CoffeeTicketRecord.class, cnd); + } + + @Override + public int countUserTicketNum(String activityId, String userId) { + return dao().func(CoffeeTicketRecord.class, "sum", "ticketNum", Cnd.where("activityId", "=", activityId).and("userId", "=", userId)); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public CoffeeTicketRecord saveGrabRecord(CoffeeTicketActivity activity, Integer ticketNum) { + CoffeeTicketRecord record = new CoffeeTicketRecord(); + record.setActivityId(activity.getId()); + record.setActivityTitle(activity.getTitle()); + record.setTicketNum(ticketNum); + record.setGrabTime(new Date()); + record.setUserId(SecurityUtil.getUserId()); + record.setUserName(SecurityUtil.getUserUsername()); + record.setLoginName(SecurityUtil.getUserLoginname()); + record.setStatus("SUCCESS"); + return dao().insert(record); + } + + @Override + public Pagination h5MinePageData(PageForm pageForm) { + Cnd cnd = Cnd.where("userId", "=", SecurityUtil.getUserId()); + cnd.desc("grabTime"); + List recordList = dao().query(CoffeeTicketRecord.class, cnd); + List voList = recordList.stream() + .map(this::buildMineListVO) + .filter(item -> matchKeyword(item, pageForm.getSearchKeyword())) + .toList(); + int totalCount = voList.size(); + int fromIndex = Math.min((pageForm.getPageNumber() - 1) * pageForm.getPageSize(), totalCount); + int toIndex = Math.min(fromIndex + pageForm.getPageSize(), totalCount); + return new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), totalCount, new ArrayList<>(voList.subList(fromIndex, toIndex))); + } + + @Override + public Pagination statisticsPageData(PageForm pageForm, Integer year, String activityId, String unionId, String unitId, String grabDate) { + Sql sql = buildStatisticsSql(year, activityId, unionId, unitId, grabDate); + Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + List recordList = pagination.getList(); + List voList = recordList.stream().map(this::buildGrabStatisticsVO).collect(Collectors.toList()); + pagination.setList(voList); + return pagination; + } + + @Override + public List statisticsExportList(Integer year, String activityId, String unionId, String unitId, String grabDate) { + Sql sql = buildStatisticsSql(year, activityId, unionId, unitId, grabDate); + return listMap(sql).stream().map(this::buildGrabStatisticsVO).collect(Collectors.toList()); + } + + @Override + public List statisticsGrabDateList(Integer year, String activityId, String unionId, String unitId) { + Sql sql = Sqls.create(""" + SELECT + DATE_FORMAT(r.grabTime, '%Y-%m-%d') AS grabDate + FROM + coffee_ticket_record r + LEFT JOIN vw_user u ON u.id = r.userId + LEFT JOIN coffee_ticket_activity a ON a.id = r.activityId + $condition + GROUP BY DATE_FORMAT(r.grabTime, '%Y-%m-%d') + ORDER BY grabDate DESC + """); + Cnd cnd = Cnd.NEW(); + cnd.andEX("r.activityId", "=", activityId); + cnd.andEX("YEAR(a.activityStartDate)", "=", year); + cnd.andEX("u.unionId", "=", unionId); + cnd.andEX("u.unitId", "=", unitId); + sql.setCondition(cnd); + List list = listMap(sql); + return list.stream().map(item -> item.getString("grabDate")).collect(Collectors.toList()); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void deleteStatisticsRecord(String recordId) { + if (StrUtil.isBlank(recordId)) { + throw new IllegalStateException("抢票记录ID不能为空"); + } + CoffeeTicketRecord record = fetch(recordId); + if (record == null) { + throw new IllegalStateException("抢票记录不存在"); + } + dao().delete(record); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void cancelH5Ticket(String recordId) { + if (StrUtil.isBlank(recordId)) { + throw new IllegalStateException("抢票记录ID不能为空"); + } + CoffeeTicketRecord record = fetch(recordId); + if (record == null) { + throw new IllegalStateException("抢票记录不存在"); + } + if (!SecurityUtil.getUserId().equals(record.getUserId())) { + throw new IllegalStateException("无权取消该抢票记录"); + } + CoffeeTicketActivity activity = dao().fetch(CoffeeTicketActivity.class, record.getActivityId()); + if (activity == null) { + throw new IllegalStateException("活动不存在"); + } + if (!isWithinGrabWindow(activity)) { + throw new IllegalStateException("当前不在可取消时间内"); + } + dao().delete(record); + } + + /** + * 将抢票记录和活动信息组合成手机端“我的抢票”卡片数据。 + * + * @param record 当前用户的一条抢票记录 + * @return CoffeeTicketMineListVO,包含记录字段和活动字段 + */ + private CoffeeTicketMineListVO buildMineListVO(CoffeeTicketRecord record) { + CoffeeTicketActivity activity = dao().fetch(CoffeeTicketActivity.class, record.getActivityId()); + CoffeeTicketMineListVO vo = new CoffeeTicketMineListVO(); + vo.setRecordId(record.getId()); + vo.setActivityId(record.getActivityId()); + vo.setTitle(activity == null ? record.getActivityTitle() : activity.getTitle()); + vo.setCover(activity == null ? null : activity.getCover()); + vo.setDescription(activity == null ? null : activity.getDescription()); + vo.setActivityStartDate(activity == null ? null : activity.getActivityStartDate()); + vo.setActivityEndDate(activity == null ? null : activity.getActivityEndDate()); + vo.setReserveStartTime(activity == null ? null : activity.getReserveStartTime()); + vo.setReserveEndTime(activity == null ? null : activity.getReserveEndTime()); + vo.setAddress(activity == null ? null : activity.getAddress()); + vo.setTicketNum(record.getTicketNum()); + vo.setGrabTime(record.getGrabTime()); + vo.setCanCancel(activity != null && isWithinGrabWindow(activity)); + return vo; + } + + /** + * 组装PC端抢票统计明细,保留记录表中的用户、活动、票数和实际抢票时间。 + * + * @param record 当前页的一条抢票记录 + * @return CoffeeTicketGrabStatisticsVO,返回给统计页面展示的明细数据 + */ + private CoffeeTicketGrabStatisticsVO buildGrabStatisticsVO(NutMap record) { + CoffeeTicketGrabStatisticsVO vo = new CoffeeTicketGrabStatisticsVO(); + vo.setRecordId(record.getString("recordId")); + vo.setActivityId(record.getString("activityId")); + vo.setActivityTitle(record.getString("activityTitle")); + vo.setUserId(record.getString("userId")); + vo.setUserName(record.getString("userName")); + vo.setLoginName(record.getString("loginName")); + vo.setUnitId(record.getString("unitId")); + vo.setUnitName(record.getString("unitName")); + vo.setUnionId(record.getString("unionId")); + vo.setUnionName(record.getString("unionName")); + vo.setTicketNum(record.getInt("ticketNum")); + vo.setGrabTime(record.getAs("grabTime", Date.class)); + return vo; + } + + /** + * 构建PC端抢票统计查询SQL,分页列表和导出共用同一套筛选条件,避免页面展示与导出结果不一致。 + * + * @param year 活动开始日期所属年度 + * @param activityId 活动ID + * @param unionId 抢票用户所属工会ID + * @param unitId 抢票用户所属单位ID + * @param grabDate 实际抢票日期,格式 yyyy-MM-dd + * @return Sql,已带查询条件和按抢票时间倒序排序 + */ + private Sql buildStatisticsSql(Integer year, String activityId, String unionId, String unitId, String grabDate) { + Sql sql = Sqls.create(""" + SELECT + r.id AS recordId, + r.activityId, + r.activityTitle, + r.userId, + r.userName, + r.loginName, + u.unitId, + u.unitName, + u.unionId, + u.unionName, + r.ticketNum, + r.grabTime + FROM + coffee_ticket_record r + LEFT JOIN vw_user u ON u.id = r.userId + LEFT JOIN coffee_ticket_activity a ON a.id = r.activityId + $condition + """); + Cnd cnd = Cnd.NEW(); + cnd.andEX("r.activityId", "=", activityId); + cnd.andEX("YEAR(a.activityStartDate)", "=", year); + cnd.andEX("u.unionId", "=", unionId); + cnd.andEX("u.unitId", "=", unitId); + if (StrUtil.isNotBlank(grabDate)) { + Date date = DateUtil.parse(grabDate, DatePattern.NORM_DATE_PATTERN); + cnd.and("r.grabTime", ">=", DateUtil.beginOfDay(date)); + cnd.and("r.grabTime", "<=", DateUtil.endOfDay(date)); + } + cnd.desc("r.grabTime"); + sql.setCondition(cnd); + return sql; + } + + private boolean isWithinGrabWindow(CoffeeTicketActivity activity) { + Date now = new Date(); + if (activity.getActivityStartDate() == null || activity.getActivityEndDate() == null) { + return false; + } + if (now.before(DateUtil.beginOfDay(activity.getActivityStartDate()))) { + return false; + } + if (now.after(DateUtil.endOfDay(activity.getActivityEndDate()))) { + return false; + } + List weekDays = activity.getWeekDays(); + if (weekDays == null || weekDays.isEmpty()) { + return false; + } + if (!weekDays.contains(getTodayWeekValue(now))) { + return false; + } + if (StrUtil.isBlank(activity.getReserveStartTime()) || StrUtil.isBlank(activity.getReserveEndTime())) { + return false; + } + String currentTime = DateUtil.format(now, "HH:mm"); + if (currentTime.compareTo(activity.getReserveStartTime()) < 0 || currentTime.compareTo(activity.getReserveEndTime()) > 0) { + return false; + } + return true; + } + + private String getTodayWeekValue(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + if (dayOfWeek == Calendar.SUNDAY) { + return "7"; + } + return String.valueOf(dayOfWeek - 1); + } + + private boolean matchKeyword(CoffeeTicketMineListVO item, String keyword) { + return StrUtil.isBlank(keyword) || StrUtil.containsIgnoreCase(item.getTitle(), keyword); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5DetailVO.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5DetailVO.java new file mode 100644 index 0000000..fc13d19 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5DetailVO.java @@ -0,0 +1,28 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.vo; + +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@EqualsAndHashCode(callSuper = true) +@Data +public class CoffeeTicketActivityH5DetailVO extends CoffeeTicketActivity implements Serializable { + + /** + * 当前登录用户是否已经抢过该活动票。 + */ + private Boolean hasGrabbed; + + /** + * 当前登录用户在该活动下已抢票张数合计。 + */ + private Integer myTicketNum; + + /** + * 当前登录用户最近一次抢票时间。 + */ + private Date myGrabTime; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5ListVO.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5ListVO.java new file mode 100644 index 0000000..6b116ed --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketActivityH5ListVO.java @@ -0,0 +1,27 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.vo; + +import com.budwk.app.zhgh.dayofficework.coffeeTicket.models.CoffeeTicketActivity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@EqualsAndHashCode(callSuper = true) +@Data +public class CoffeeTicketActivityH5ListVO extends CoffeeTicketActivity implements Serializable { + + /** + * 活动总票数,当前业务按活动表 ticketCount 字段计算。 + */ + private Integer totalTicketNum; + + /** + * 已抢票数,统计抢票记录表中当前活动 ticketNum 合计。 + */ + private Integer grabbedTicketNum; + + /** + * 剩余票数,按 totalTicketNum - grabbedTicketNum 计算,小于 0 时按 0 返回。 + */ + private Integer remainingTicketNum; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketGrabStatisticsVO.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketGrabStatisticsVO.java new file mode 100644 index 0000000..4780047 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketGrabStatisticsVO.java @@ -0,0 +1,70 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +public class CoffeeTicketGrabStatisticsVO implements Serializable { + + /** + * 抢票记录ID,用于定位一条用户抢票明细。 + */ + private String recordId; + + /** + * 活动ID,对应 coffee_ticket_activity 表主键。 + */ + private String activityId; + + /** + * 活动标题,优先使用当前活动表标题,活动被删除时使用记录表冗余标题。 + */ + private String activityTitle; + + /** + * 抢票用户ID,对应当前登录用户体系中的用户主键。 + */ + private String userId; + + /** + * 抢票用户姓名,来源于抢票时保存的用户信息。 + */ + private String userName; + + /** + * 抢票用户工号,来源于抢票时保存的登录名。 + */ + private String loginName; + + /** + * 抢票用户所属单位ID,来源于 vw_user 当前用户视图。 + */ + private String unitId; + + /** + * 抢票用户所属单位名称,来源于 vw_user 当前用户视图。 + */ + private String unitName; + + /** + * 抢票用户所属工会ID,来源于 vw_user 当前用户视图。 + */ + private String unionId; + + /** + * 抢票用户所属工会名称,来源于 vw_user 当前用户视图。 + */ + private String unionName; + + /** + * 本次抢票张数。 + */ + private Integer ticketNum; + + /** + * 用户实际点击抢票并生成记录的时间。 + */ + private Date grabTime; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketMineListVO.java b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketMineListVO.java new file mode 100644 index 0000000..80b50b5 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/coffeeTicket/vo/CoffeeTicketMineListVO.java @@ -0,0 +1,24 @@ +package com.budwk.app.zhgh.dayofficework.coffeeTicket.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +public class CoffeeTicketMineListVO implements Serializable { + + private String recordId; + private String activityId; + private String title; + private String cover; + private String description; + private Date activityStartDate; + private Date activityEndDate; + private String reserveStartTime; + private String reserveEndTime; + private String address; + private Integer ticketNum; + private Date grabTime; + private Boolean canCancel; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvBankController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvBankController.java index 73fb63c..9cdb9b9 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvBankController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvBankController.java @@ -9,8 +9,10 @@ import com.budwk.app.zhgh.dayofficework.qsv.models.QsvBank; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject; import com.budwk.app.zhgh.dayofficework.qsv.service.QsvBankService; import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.mvc.annotation.At; @@ -51,6 +53,7 @@ public class QsvBankController { @At @SaCheckPermission("qsv.bank") + @Aop(TransAop.READ_COMMITTED) @ApiOperation("保存题库") public Result save(@Param("qsvBank") QsvBank qsvBank) { dao.insert(qsvBank); @@ -59,6 +62,7 @@ public class QsvBankController { @At @SaCheckPermission("qsv.bank") + @Aop(TransAop.READ_COMMITTED) @ApiOperation("修改题库") public Result update(@Param("qsvBank") QsvBank qsvBank) { dao.updateIgnoreNull(qsvBank); @@ -75,6 +79,7 @@ public class QsvBankController { @At @SaCheckPermission("qsv.bank") + @Aop(TransAop.READ_COMMITTED) @ApiOperation("删除题库") public Result delete(@Valid String id) { dao.delete(QsvBank.class, id); @@ -83,6 +88,7 @@ public class QsvBankController { @At @SaCheckPermission("qsv.bank") + @Aop(TransAop.READ_COMMITTED) @ApiOperation("保存题库题目") public Result saveSubjects(@Valid String id, @Param("subjects") QsvSubject[] subjects) { QsvBank qsvBank = dao.fetch(QsvBank.class, id); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvNewController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvNewController.java index e1a5855..a0d279b 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvNewController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvNewController.java @@ -13,8 +13,8 @@ import org.nutz.mvc.annotation.Ok; public class QsvNewController { @At("/new") - @Ok("beetl:/platform/zhgh/dayofficework/qsv/activity/new.html") - @SaCheckPermission("qsv.activity") + @Ok("beetl:/platform/zhgh/dayofficework/qsv/activity/index.html") + @SaCheckPermission("qsv.new") public void newPage() { } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvOnlineController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvOnlineController.java index 0d1a439..c3de6c2 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvOnlineController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvOnlineController.java @@ -10,12 +10,12 @@ import com.budwk.app.base.param.PageForm; import com.budwk.app.base.result.Result; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope; +import com.budwk.app.zhgh.dayofficework.qsv.dto.QsvCheckAnswerResult; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord; import com.budwk.app.zhgh.dayofficework.qsv.param.QsvAnswerParam; -import com.budwk.app.zhgh.dayofficework.qsv.dto.QsvCheckAnswerResult; import com.budwk.app.zhgh.dayofficework.qsv.service.QsvQuizService; import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService; import io.swagger.annotations.ApiOperation; @@ -31,14 +31,7 @@ import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; import javax.validation.Valid; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvQuizRankController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvQuizRankController.java index 1aa0e2e..dce19da 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvQuizRankController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvQuizRankController.java @@ -4,11 +4,14 @@ import cn.dev33.satoken.annotation.SaCheckPermission; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity; +import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord; import com.budwk.app.zhgh.dayofficework.qsv.param.QsvQuizRankPageForm; import com.budwk.app.zhgh.dayofficework.qsv.service.QsvQuizRankService; import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.mvc.annotation.At; @@ -64,5 +67,17 @@ public class QsvQuizRankController { qsvQuizRankService.exportXlsx(pageForm, response); } + @At + @SaCheckPermission("qsv.quiz.rank.delete") + @Aop(TransAop.READ_COMMITTED) + @ApiOperation("删除答题记录") + public Result deleteAnswerRecord(@Valid String id) { + QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, id); + if (record == null) { + return Result.error("答题记录不存在"); + } + dao.delete(QsvUserAnswerRecord.class, id); + return Result.success("删除成功"); + } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java index 584f252..5ba96df 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/controller/QsvSurveyController.java @@ -22,8 +22,10 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; @@ -377,6 +379,7 @@ public class QsvSurveyController { @At @SaCheckPermission("qsv.survey") + @Aop(TransAop.READ_COMMITTED) @ApiOperation("删除用户答题记录") @SLog(type = "qsv.survey", tag = "删除用户答题记录", msg = "删除用户答题记录") public Result deleteUserAnswer(@Valid String id) { diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java index fc49d07..c1d37c6 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvQuizController.java @@ -3,7 +3,6 @@ package com.budwk.app.zhgh.dayofficework.qsv.h5controller; import cn.dev33.satoken.annotation.SaCheckLogin; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import com.budwk.app.base.annotation.SLog; import com.budwk.app.base.result.Result; @@ -335,6 +334,7 @@ public class H5QsvQuizController { @At @ApiOperation("提交答题") @SLog(type = "qsv.quiz", tag = "提交答题", msg = "问卷调查提交答题") + @Aop(TransAop.READ_COMMITTED) public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) { QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId()); JSONObject extJson = answerRecord.getExtJson(); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvSurveyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvSurveyController.java index 264af86..9391025 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvSurveyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvSurveyController.java @@ -10,9 +10,9 @@ import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject; import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord; import com.budwk.app.zhgh.dayofficework.qsv.param.QsvAnswerParam; import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; -import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -22,11 +22,7 @@ import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; import javax.validation.Valid; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvVoteController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvVoteController.java index d1498af..d15739c 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvVoteController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/h5controller/H5QsvVoteController.java @@ -23,12 +23,7 @@ import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; import javax.validation.Valid; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/models/QsvActivity.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/models/QsvActivity.java index ea94a8d..839a68b 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/models/QsvActivity.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/models/QsvActivity.java @@ -123,7 +123,7 @@ public class QsvActivity extends BaseModel implements Serializable, SysHomeConve sysHomeActivity.setName(this.getTitle()); sysHomeActivity.setCover(this.getCover()); sysHomeActivity.setContent(this.getDescription()); - sysHomeActivity.setUrl("/platform/qsv/activity"); + sysHomeActivity.setUrl("/platform/qsv/online"); sysHomeActivity.setH5Url(getH5Url()); sysHomeActivity.setStartDate(this.getStartTime()); sysHomeActivity.setEndDate(this.getEndTime()); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizRankServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizRankServiceImpl.java index 9e64b2a..7102663 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizRankServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/qsv/service/impl/QsvQuizRankServiceImpl.java @@ -90,6 +90,7 @@ public class QsvQuizRankServiceImpl extends BaseServiceImpl private Sql buildQuerySql(String activityId, String mode, String scoreStatisticsMode, QsvQuizRankPageForm pageForm) { StringBuilder sqlBuilder = new StringBuilder(""" SELECT + t1.id, t1.userId, t1.userName, t1.loginName, diff --git a/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/activity/index.html b/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/activity/index.html new file mode 100644 index 0000000..60887cd --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/activity/index.html @@ -0,0 +1,139 @@ + + +
+ + + + + + + + + + + + + + + + + + 新增活动 + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/new/index.html b/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/new/index.html new file mode 100644 index 0000000..b527e38 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/new/index.html @@ -0,0 +1,383 @@ + + + + +
+ +

创建咖啡抢票活动

+ +
+
+ + + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/statistics/index.html b/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/statistics/index.html new file mode 100644 index 0000000..13c0925 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/coffeeTicket/statistics/index.html @@ -0,0 +1,212 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 导出 + + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/qsv/activity/new.html b/src/main/resources/views/platform/zhgh/dayofficework/qsv/activity/new.html index 8889c22..6116232 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/qsv/activity/new.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/qsv/activity/new.html @@ -71,76 +71,14 @@ layout("/layouts/platform.html"){
-
-
-

{{pageTitle}}

- 返回 -
- - -

基础信息

- - - - - {{item.label}} - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - 设置 - -
-
- -

题目信息

- - +
diff --git a/src/main/resources/views/platform/zhgh/dayofficework/qsv/quiz/rank.html b/src/main/resources/views/platform/zhgh/dayofficework/qsv/quiz/rank.html index 17b6761..d432e06 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/qsv/quiz/rank.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/qsv/quiz/rank.html @@ -69,6 +69,11 @@ layout("/layouts/platform.html"){ + + + @@ -106,6 +111,20 @@ layout("/layouts/platform.html"){ exportXlsx() { this.$downLoad("/platform/qsv/quizRank/exportXlsx", this.pageForm) }, + deleteAnswerRecord(id) { + this.$confirm("您确定要删除该答题记录吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning" + }).then(() => { + this.$axios.post("/platform/qsv/quizRank/deleteAnswerRecord", {id}).then((res) => { + if (res.code === 0) { + this.$message.success(res.msg) + this.pageData() + } + }) + }) + }, async flushUnits() { this.$set(this.pageForm, "unitId", null) this.unitList = [] diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/detail/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/detail/index.html new file mode 100644 index 0000000..0d13188 --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/detail/index.html @@ -0,0 +1,374 @@ + + + +
+ + + +
+ +
暂无封面
+
+ +
+
+
+
{{formData.title || '--'}}
+
{{statusInfo.text}}
+
+
{{formData.description || '暂无活动描述'}}
+
+
+ +
活动周期
+
{{formatDate(formData.activityStartDate)}} ~ {{formatDate(formData.activityEndDate)}}
+
+
+ +
可抢票星期
+
{{formatWeekDays(formData.weekDays)}}
+
+
+ +
可抢票票数
+
{{formData.ticketCount || 0}} 张
+
+
+ +
抢票时间段
+
{{formData.reserveStartTime || '--'}} - {{formData.reserveEndTime || '--'}}
+
+
+ +
活动地点
+
{{formData.address || '--'}}
+
+
+ +
我的抢票
+
已抢 {{formData.myTicketNum || 0}} 张
+
+
+
+
+ +
+
+ + 参与须知 +
+
{{noticeText}}
+
+ + +
+ + + + diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/list/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/list/index.html new file mode 100644 index 0000000..4172a97 --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/list/index.html @@ -0,0 +1,390 @@ + + + +
+ + + + +
+ + + + + + +
+
+ + +
+
+
+ +
暂无封面
+
{{getStatusInfo(item).text}}
+
+
+
{{item.title}}
+
+
+ + 活动时间 + {{formatDate(item.activityStartDate)}} 至 {{formatDate(item.activityEndDate)}} +
+
+ + 可抢票 + + {{weekMap[week] || week}} + + -- +
+
+ + 抢票时间 + {{item.reserveStartTime || '--'}} - {{item.reserveEndTime || '--'}} +
+
+ + 活动地点 + {{item.address || '--'}} +
+
+
+ 活动总票数 {{item.totalTicketNum || 0}} 张 + 剩余票数 {{item.remainingTicketNum || 0}} 张 +
+ 立即抢票 +
+
+
+
+ + + + + 抢票活动 + 我的抢票 + +
+ + + + diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/mine/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/mine/index.html new file mode 100644 index 0000000..2045c6d --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/coffeeTicket/mine/index.html @@ -0,0 +1,347 @@ + + + +
+ + + +
+
共 {{pageForm.totalCount || 0}} 张票
+
+
+ + +
+
+
+ +
暂无封面
+
+
+
+
{{item.title || '--'}}
+
+
+ + 已抢 {{item.ticketNum || 0}} 张 +
+
+
+ + 活动时间 {{formatDate(item.activityStartDate)}} 至 {{formatDate(item.activityEndDate)}} +
+
+ + 抢票时间 {{formatDateTime(item.grabTime)}} +
+
+ + 活动地点 {{item.address || '--'}} +
+
+
+ +
+
+
+ + + + + 抢票活动 + 我的抢票 + +
+ + + +