From eff7ba234046d8cb34b11a4e3d91c6c74856ee4f Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Sun, 11 Oct 2026 22:50:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=B7=91=E5=BA=97=E5=A1=AB=E6=8A=A5):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B7=91=E5=BA=97=E5=A1=AB=E6=8A=A5=E8=A1=A8?= =?UTF-8?q?=E4=B8=8E=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自主填报拆分为「驻店填报 / 跑店填报」两种类型,跑店填报的填报内容是 「巡店列表(多门店,每条含门店/定位/打卡照片)+ 活动小结」,与 yxe_self_report 的单门店结构不兼容:yxe_self_report 既没有多门店字段, 也没有区分驻店/跑店类型的字段。为不污染既有 928 条历史数据与既有 审核/导出逻辑,本次新建独立表 yxe_visit_report。 - entity/param/mapper(+xml)/service(+impl)/controller 全套 - 接口:page、audit/page、GET、userList、GET /{id}、POST、PUT、 PUT /resubmit、DELETE /{id}、DELETE /batch、PUT /batch、POST /export - 审核通过复用自主填报逻辑:scene=6 写余额,备注「跑店填报审核通过获得收入」 - 提交人须 auth_status=2;resubmit 仅本人 + 仅已驳回记录 - store_count 由 store_list JSON 长度重算,不信任前端传值 - 列表接口(page / audit/page)裁剪 store_list 与 image: 一次巡店最多 9 图 × N 店,整列 JSON 可达数百 KB,列表只用 store_count, 明细展开时再按 id 拉详情;userList(填报记录页要渲染门店列表)与导出不裁剪 - 导出表头含门店清单/连锁清单等从 store_list 展开的列 配套: - yxe_visit_report.sql(建表,幂等,含执行前后核对 SELECT) - yxe-mp packageUser/self-report/select、visit - yxe-admin yxeVisitReport API 与跑店详情 --- .../controller/YxeVisitReportController.java | 395 ++++++++++++++++++ .../gxwebsoft/yxe/entity/YxeVisitReport.java | 88 ++++ .../yxe/mapper/YxeVisitReportMapper.java | 26 ++ .../yxe/mapper/xml/YxeVisitReportMapper.xml | 86 ++++ .../yxe/param/YxeVisitReportParam.java | 69 +++ .../yxe/service/YxeVisitReportService.java | 25 ++ .../impl/YxeVisitReportServiceImpl.java | 127 ++++++ yxe_visit_report.sql | 61 +++ 8 files changed, 877 insertions(+) create mode 100644 src/main/java/com/gxwebsoft/yxe/controller/YxeVisitReportController.java create mode 100644 src/main/java/com/gxwebsoft/yxe/entity/YxeVisitReport.java create mode 100644 src/main/java/com/gxwebsoft/yxe/mapper/YxeVisitReportMapper.java create mode 100644 src/main/java/com/gxwebsoft/yxe/mapper/xml/YxeVisitReportMapper.xml create mode 100644 src/main/java/com/gxwebsoft/yxe/param/YxeVisitReportParam.java create mode 100644 src/main/java/com/gxwebsoft/yxe/service/YxeVisitReportService.java create mode 100644 src/main/java/com/gxwebsoft/yxe/service/impl/YxeVisitReportServiceImpl.java create mode 100644 yxe_visit_report.sql diff --git a/src/main/java/com/gxwebsoft/yxe/controller/YxeVisitReportController.java b/src/main/java/com/gxwebsoft/yxe/controller/YxeVisitReportController.java new file mode 100644 index 0000000..cbe4740 --- /dev/null +++ b/src/main/java/com/gxwebsoft/yxe/controller/YxeVisitReportController.java @@ -0,0 +1,395 @@ +package com.gxwebsoft.yxe.controller; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.gxwebsoft.common.core.utils.ExcelExportUtil; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.FileRecord; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.shop.entity.UserBalanceLog; +import com.gxwebsoft.shop.service.UserBalanceLogService; +import com.gxwebsoft.yxe.entity.YxeVisitReport; +import com.gxwebsoft.yxe.param.YxeVisitReportParam; +import com.gxwebsoft.yxe.service.YxeVisitReportService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * 跑店填报控制器 + * + * @author Codex + * @since 2026-10-11 + */ +@Api(tags = "跑店填报管理") +@RestController +@RequestMapping("/api/yxe/yxe-visit-report") +public class YxeVisitReportController extends BaseController { + @Resource + private YxeVisitReportService yxeVisitReportService; + @Resource + private UserService userService; + @Resource + private ExcelExportUtil excelExportUtil; + @Resource + private UserBalanceLogService userBalanceLogService; + + @ApiOperation("分页查询跑店填报") + @GetMapping("/page") + public ApiResult> page(YxeVisitReportParam param) { + return success(trimListPayload(yxeVisitReportService.pageRel(param))); + } + + @ApiOperation("审核页分页查询跑店填报") + @GetMapping("/audit/page") + public ApiResult> auditPage(YxeVisitReportParam param) { + return success(trimListPayload(yxeVisitReportService.pageAuditSummary(param))); + } + + @ApiOperation("查询全部跑店填报") + @GetMapping() + public ApiResult> list(YxeVisitReportParam param) { + return success(yxeVisitReportService.listRel(param)); + } + + @ApiOperation("查询当前用户跑店填报") + @GetMapping("/userList") + public ApiResult> userList(YxeVisitReportParam param) { + param.setUserId(getLoginUserId()); + return success(yxeVisitReportService.listRel(param)); + } + + @ApiOperation("根据id查询跑店填报") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(yxeVisitReportService.getByIdRel(id)); + } + + @ApiOperation("添加跑店填报") + @PostMapping() + public ApiResult save(@RequestBody YxeVisitReport yxeVisitReport) { + User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录"); + } + if (!Integer.valueOf(2).equals(loginUser.getAuthStatus())) { + return fail("请先完成认证"); + } + yxeVisitReport.setUserId(loginUser.getUserId()); + yxeVisitReport.setStoreCount(countStoreItems(yxeVisitReport.getStoreList())); + if (yxeVisitReport.getStatus() == null) { + yxeVisitReport.setStatus(0); + } + if (yxeVisitReportService.save(yxeVisitReport)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + @ApiOperation("修改跑店填报") + @PutMapping() + public ApiResult update(@RequestBody YxeVisitReport yxeVisitReport) { + YxeVisitReport current = yxeVisitReport.getId() == null ? null : yxeVisitReportService.getById(yxeVisitReport.getId()); + boolean approving = current != null + && Integer.valueOf(1).equals(yxeVisitReport.getStatus()) + && !Integer.valueOf(1).equals(current.getStatus()); + if (approving) { + if (yxeVisitReport.getReward() == null) { + return fail("请填写奖金金额"); + } + if (yxeVisitReport.getReward().compareTo(BigDecimal.ZERO) < 0) { + return fail("奖金金额不能小于0"); + } + yxeVisitReport.setRejectReason(""); + } + // 巡店明细只在提交时写一次;审核端不带该字段时不要覆盖成 null + if (yxeVisitReport.getStoreList() != null) { + yxeVisitReport.setStoreCount(countStoreItems(yxeVisitReport.getStoreList())); + } + if (yxeVisitReportService.updateById(yxeVisitReport)) { + if (approving) { + YxeVisitReport latest = yxeVisitReportService.getById(yxeVisitReport.getId()); + User user = userService.getById(latest.getUserId()); + BigDecimal reward = latest.getReward() == null ? BigDecimal.ZERO : latest.getReward(); + UserBalanceLog userBalanceLog = new UserBalanceLog(); + userBalanceLog.setUserId(latest.getUserId()); + userBalanceLog.setOrderId(latest.getId()); + userBalanceLog.setScene(6); + userBalanceLog.setMoney(reward); + userBalanceLog.setBalance(user.getBalance().add(reward)); + userBalanceLog.setRemark("跑店填报审核通过获得收入"); + userBalanceLogService.save(userBalanceLog); + + user.setBalance(user.getBalance().add(reward)); + userService.updateById(user); + } + return success("修改成功"); + } + return fail("修改失败"); + } + + @ApiOperation("重新提交跑店填报") + @PutMapping("/resubmit") + public ApiResult resubmit(@RequestBody YxeVisitReport yxeVisitReport) { + User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("请先登录"); + } + if (!Integer.valueOf(2).equals(loginUser.getAuthStatus())) { + return fail("请先完成认证"); + } + if (yxeVisitReport == null || yxeVisitReport.getId() == null) { + return fail("缺少填报记录ID"); + } + YxeVisitReport current = yxeVisitReportService.getById(yxeVisitReport.getId()); + if (current == null) { + return fail("填报记录不存在"); + } + // 只能重新提交自己的记录,避免复用通用修改接口改到他人数据 + if (current.getUserId() == null || !current.getUserId().equals(loginUser.getUserId())) { + return fail("只能重新提交自己的填报记录"); + } + // 仅驳回状态可重提,防止绕过审核直接改回待审核/已通过 + if (!Integer.valueOf(2).equals(current.getStatus())) { + return fail("只有已驳回的填报记录可以重新提交"); + } + YxeVisitReport update = new YxeVisitReport(); + update.setId(current.getId()); + update.setStoreList(yxeVisitReport.getStoreList()); + update.setStoreCount(countStoreItems(yxeVisitReport.getStoreList())); + update.setImage(yxeVisitReport.getImage()); + update.setSummary(yxeVisitReport.getSummary()); + update.setLat(yxeVisitReport.getLat()); + update.setLng(yxeVisitReport.getLng()); + update.setLocation(yxeVisitReport.getLocation()); + // 奖金金额与新增提交保持一致:由审核端确认,改动巡店明细后仍需同步上一轮金额 + update.setReward(yxeVisitReport.getReward()); + // 状态与驳回原因由服务端决定:回到待审核,并清掉上一轮的驳回原因 + update.setStatus(0); + update.setRejectReason(""); + if (yxeVisitReportService.updateById(update)) { + return success("重新提交成功"); + } + return fail("重新提交失败"); + } + + @ApiOperation("删除跑店填报") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (yxeVisitReportService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @ApiOperation("批量修改跑店填报") + @PutMapping("/batch") + public ApiResult updateBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(yxeVisitReportService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + @ApiOperation("批量删除跑店填报") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (yxeVisitReportService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @ApiOperation("导出跑店填报") + @PostMapping("/export") + public ApiResult export(@RequestBody(required = false) YxeVisitReportParam param) throws IOException { + if (param == null) { + param = new YxeVisitReportParam(); + } + List list = yxeVisitReportService.listRel(param); + List headers = new ArrayList<>(); + headers.add("ID"); + headers.add("提交人ID"); + headers.add("提交人姓名"); + headers.add("提交人手机号"); + headers.add("用户分组"); + headers.add("巡店门店数"); + headers.add("门店清单"); + headers.add("连锁清单"); + headers.add("活动小结"); + headers.add("打卡时间"); + headers.add("奖金金额"); + headers.add("位置"); + headers.add("状态"); + headers.add("驳回原因"); + headers.add("创建时间"); + XSSFWorkbook workbook = excelExportUtil.createWorkbook("跑店填报", headers.toArray(new String[0])); + Sheet sheet = workbook.getSheetAt(0); + int rowNum = 1; + for (YxeVisitReport item : list) { + User user = userService.getById(item.getUserId()); + List rowValues = new ArrayList<>(); + rowValues.add(item.getId()); + rowValues.add(item.getUserId()); + rowValues.add(getUserName(user)); + rowValues.add(getUserPhone(user)); + rowValues.add(user == null ? "" : user.getUserGroup()); + rowValues.add(item.getStoreCount()); + rowValues.add(getStoreNamesText(item.getStoreList())); + rowValues.add(getChainNamesText(item.getStoreList())); + rowValues.add(getSummaryText(item.getSummary())); + rowValues.add(getVisitTimeText(item.getStoreList())); + rowValues.add(item.getReward()); + rowValues.add(item.getLocation()); + rowValues.add(getStatusText(item.getStatus())); + rowValues.add(item.getRejectReason()); + rowValues.add(item.getCreateTime()); + excelExportUtil.writeRow(sheet, rowNum++, rowValues.toArray()); + } + excelExportUtil.autoSizeColumns(sheet, headers.size()); + return success(excelExportUtil.saveWorkbook(workbook, "跑店填报数据", getLoginUserId())); + } + + /** + * 列表接口不带 store_list:一次巡店最多 9 张图 × N 家门店,整列 JSON 可能有几百 KB, + * 列表页只用到 store_count,明细展开时再按 id 拉详情。 + * 注意 userList(填报记录,前端要渲染门店列表)与 导出 不走这里。 + */ + private PageResult trimListPayload(PageResult pageResult) { + if (pageResult == null || pageResult.getList() == null) { + return pageResult; + } + for (YxeVisitReport item : pageResult.getList()) { + if (item == null) { + continue; + } + item.setStoreList(null); + item.setImage(null); + } + return pageResult; + } + + /** + * 巡店条数由 store_list JSON 长度决定,前端传值不可信,统一按内容重新计算 + */ + private int countStoreItems(String storeList) { + return parseStoreList(storeList).size(); + } + + private JSONArray parseStoreList(String storeList) { + if (StrUtil.isBlank(storeList)) { + return new JSONArray(); + } + try { + JSONArray parsed = JSON.parseArray(storeList); + return parsed == null ? new JSONArray() : parsed; + } catch (Exception ignored) { + return new JSONArray(); + } + } + + private String getStoreNamesText(String storeList) { + List names = new ArrayList<>(); + for (Object object : parseStoreList(storeList)) { + if (!(object instanceof JSONObject)) { + continue; + } + String name = ((JSONObject) object).getString("merchantName"); + if (StrUtil.isNotBlank(name) && !names.contains(name.trim())) { + names.add(name.trim()); + } + } + return String.join("、", names); + } + + private String getChainNamesText(String storeList) { + List names = new ArrayList<>(); + for (Object object : parseStoreList(storeList)) { + if (!(object instanceof JSONObject)) { + continue; + } + String name = ((JSONObject) object).getString("chainName"); + if (StrUtil.isNotBlank(name) && !names.contains(name.trim())) { + names.add(name.trim()); + } + } + return String.join("、", names); + } + + private String getVisitTimeText(String storeList) { + List times = new ArrayList<>(); + for (Object object : parseStoreList(storeList)) { + if (!(object instanceof JSONObject)) { + continue; + } + String time = ((JSONObject) object).getString("visitTime"); + if (StrUtil.isNotBlank(time)) { + times.add(time.trim()); + } + } + return String.join("、", times); + } + + private String getSummaryText(String summary) { + if (StrUtil.isBlank(summary)) { + return ""; + } + String text = summary.trim(); + try { + Object parsed = JSON.parse(text); + if (parsed == null) { + return ""; + } + if (parsed instanceof String) { + return ((String) parsed).trim(); + } + return parsed.toString(); + } catch (Exception ignored) { + return text; + } + } + + private String getUserName(User user) { + if (user == null) { + return ""; + } + if (user.getRealName() != null && !user.getRealName().trim().isEmpty()) { + return user.getRealName(); + } + return user.getNickname(); + } + + private String getUserPhone(User user) { + return user == null ? "" : user.getPhone(); + } + + private String getStatusText(Integer status) { + if (status == null) { + return ""; + } + switch (status) { + case 0: + return "待审核"; + case 1: + return "通过"; + case 2: + return "拒绝"; + default: + return String.valueOf(status); + } + } +} diff --git a/src/main/java/com/gxwebsoft/yxe/entity/YxeVisitReport.java b/src/main/java/com/gxwebsoft/yxe/entity/YxeVisitReport.java new file mode 100644 index 0000000..ce37eaa --- /dev/null +++ b/src/main/java/com/gxwebsoft/yxe/entity/YxeVisitReport.java @@ -0,0 +1,88 @@ +package com.gxwebsoft.yxe.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.gxwebsoft.common.system.entity.User; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 跑店填报 + * + * @author Codex + * @since 2026-10-11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "YxeVisitReport对象", description = "跑店填报") +@TableName("yxe_visit_report") +public class YxeVisitReport implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty(value = "提交人ID") + private Integer userId; + + @ApiModelProperty(value = "巡店门店数") + private Integer storeCount; + + @ApiModelProperty(value = "巡店列表JSON(门店/定位/打卡照片)") + private String storeList; + + @ApiModelProperty(value = "全部图片集合") + private String image; + + @ApiModelProperty(value = "活动小结") + private String summary; + + @ApiModelProperty(value = "奖金金额(审核时填写)") + private BigDecimal reward; + + @ApiModelProperty(value = "纬度(整单,取首条门店)") + private String lat; + + @ApiModelProperty(value = "经度(整单,取首条门店)") + private String lng; + + @ApiModelProperty(value = "定位地址(整单,取首条门店)") + private String location; + + @ApiModelProperty(value = "排序(数字越小越靠前)") + private Integer sortNumber; + + @ApiModelProperty(value = "备注") + private String comments; + + @ApiModelProperty(value = "状态, 0待审核, 1通过, 2拒绝") + private Integer status; + + @ApiModelProperty(value = "驳回原因") + private String rejectReason; + + @ApiModelProperty(value = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "租户id") + private Integer tenantId; + + @ApiModelProperty(value = "创建时间") + private LocalDateTime createTime; + + @ApiModelProperty(value = "修改时间") + private LocalDateTime updateTime; + + @TableField(exist = false) + private User user; +} diff --git a/src/main/java/com/gxwebsoft/yxe/mapper/YxeVisitReportMapper.java b/src/main/java/com/gxwebsoft/yxe/mapper/YxeVisitReportMapper.java new file mode 100644 index 0000000..7a7aa7c --- /dev/null +++ b/src/main/java/com/gxwebsoft/yxe/mapper/YxeVisitReportMapper.java @@ -0,0 +1,26 @@ +package com.gxwebsoft.yxe.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.yxe.entity.YxeVisitReport; +import com.gxwebsoft.yxe.param.YxeVisitReportParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 跑店填报Mapper + * + * @author Codex + * @since 2026-10-11 + */ +public interface YxeVisitReportMapper extends BaseMapper { + + List selectPageRel(@Param("page") IPage page, + @Param("param") YxeVisitReportParam param); + + List selectAuditPageRel(@Param("page") IPage page, + @Param("param") YxeVisitReportParam param); + + List selectListRel(@Param("param") YxeVisitReportParam param); +} diff --git a/src/main/java/com/gxwebsoft/yxe/mapper/xml/YxeVisitReportMapper.xml b/src/main/java/com/gxwebsoft/yxe/mapper/xml/YxeVisitReportMapper.xml new file mode 100644 index 0000000..35f9cd5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/yxe/mapper/xml/YxeVisitReportMapper.xml @@ -0,0 +1,86 @@ + + + + + + + SELECT a.* + FROM yxe_visit_report a + + + AND a.id = #{param.id} + + + AND a.id IN + + #{item} + + + + AND a.user_id = #{param.userId} + + + AND a.user_id IN (SELECT user_id FROM sys_user WHERE deleted = 0 AND user_group LIKE CONCAT('%', #{param.userGroup}, '%')) + + + AND a.user_id IN + + #{item} + + + + AND a.store_list LIKE CONCAT('%', #{param.chainName}, '%') + + + AND a.store_list LIKE CONCAT('%', #{param.merchantName}, '%') + + + AND a.create_time >= CONCAT(#{param.dateStart}, ' 00:00:00') + + + AND a.create_time <= CONCAT(#{param.dateEnd}, ' 23:59:59') + + + AND a.summary LIKE CONCAT('%', #{param.summary}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND ( + a.store_list LIKE CONCAT('%', #{param.keywords}, '%') + OR a.summary LIKE CONCAT('%', #{param.keywords}, '%') + OR a.location LIKE CONCAT('%', #{param.keywords}, '%') + ) + + + + + + + + + + + diff --git a/src/main/java/com/gxwebsoft/yxe/param/YxeVisitReportParam.java b/src/main/java/com/gxwebsoft/yxe/param/YxeVisitReportParam.java new file mode 100644 index 0000000..cf05b46 --- /dev/null +++ b/src/main/java/com/gxwebsoft/yxe/param/YxeVisitReportParam.java @@ -0,0 +1,69 @@ +package com.gxwebsoft.yxe.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Set; + +/** + * 跑店填报查询参数 + * + * @author Codex + * @since 2026-10-11 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@ApiModel(value = "YxeVisitReportParam对象", description = "跑店填报查询参数") +public class YxeVisitReportParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @QueryField(type = QueryType.EQ) + private Integer id; + + @TableField(exist = false) + private String idList; + + @QueryField(type = QueryType.EQ) + private Integer userId; + + @ApiModelProperty(value = "用户分组") + private String userGroup; + + @TableField(exist = false) + private Set userIds; + + private String chainName; + + private String merchantName; + + @TableField(exist = false) + private String dateStart; + + @TableField(exist = false) + private String dateEnd; + + private String summary; + + @ApiModelProperty(value = "状态, 0待审核, 1通过, 2拒绝") + @QueryField(type = QueryType.EQ) + private Integer status; + + @ApiModelProperty(value = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @ApiModelProperty(value = "提交人姓名") + @TableField(exist = false) + private String realName; + + @TableField(exist = false) + private Integer auditLite; +} diff --git a/src/main/java/com/gxwebsoft/yxe/service/YxeVisitReportService.java b/src/main/java/com/gxwebsoft/yxe/service/YxeVisitReportService.java new file mode 100644 index 0000000..4845eba --- /dev/null +++ b/src/main/java/com/gxwebsoft/yxe/service/YxeVisitReportService.java @@ -0,0 +1,25 @@ +package com.gxwebsoft.yxe.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.yxe.entity.YxeVisitReport; +import com.gxwebsoft.yxe.param.YxeVisitReportParam; + +import java.util.List; + +/** + * 跑店填报Service + * + * @author Codex + * @since 2026-10-11 + */ +public interface YxeVisitReportService extends IService { + + PageResult pageRel(YxeVisitReportParam param); + + PageResult pageAuditSummary(YxeVisitReportParam param); + + List listRel(YxeVisitReportParam param); + + YxeVisitReport getByIdRel(Integer id); +} diff --git a/src/main/java/com/gxwebsoft/yxe/service/impl/YxeVisitReportServiceImpl.java b/src/main/java/com/gxwebsoft/yxe/service/impl/YxeVisitReportServiceImpl.java new file mode 100644 index 0000000..348cbdc --- /dev/null +++ b/src/main/java/com/gxwebsoft/yxe/service/impl/YxeVisitReportServiceImpl.java @@ -0,0 +1,127 @@ +package com.gxwebsoft.yxe.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import com.gxwebsoft.yxe.entity.YxeVisitReport; +import com.gxwebsoft.yxe.mapper.YxeVisitReportMapper; +import com.gxwebsoft.yxe.param.YxeVisitReportParam; +import com.gxwebsoft.yxe.service.YxeVisitReportService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 跑店填报Service实现 + * + * @author Codex + * @since 2026-10-11 + */ +@Service +public class YxeVisitReportServiceImpl extends ServiceImpl implements YxeVisitReportService { + + @Resource + private UserService userService; + + @Override + public PageResult pageRel(YxeVisitReportParam param) { + if (!handleRealNameFilter(param)) { + return new PageResult<>(Collections.emptyList(), 0L); + } + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + list.forEach(this::setData); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public PageResult pageAuditSummary(YxeVisitReportParam param) { + if (!handleRealNameFilter(param)) { + return new PageResult<>(Collections.emptyList(), 0L); + } + PageParam page = new PageParam<>(param); + page.setDefaultOrder("create_time desc"); + List list = baseMapper.selectAuditPageRel(page, param); + // 审核摘要只要提交人身份,且不下发定位,避免列表接口携带大字段 + list.forEach(item -> { + if (item == null) { + return; + } + if (item.getUserId() != null) { + User user = userService.getById(item.getUserId()); + if (user != null) { + User liteUser = new User(); + liteUser.setUserId(user.getUserId()); + liteUser.setRealName(user.getRealName()); + liteUser.setNickname(user.getNickname()); + liteUser.setPhone(user.getPhone()); + item.setUser(liteUser); + } + } + item.setLat(null); + item.setLng(null); + item.setComments(null); + }); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(YxeVisitReportParam param) { + if (!handleRealNameFilter(param)) { + return Collections.emptyList(); + } + List list = baseMapper.selectListRel(param); + list.forEach(this::setData); + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public YxeVisitReport getByIdRel(Integer id) { + YxeVisitReportParam param = new YxeVisitReportParam(); + param.setId(id); + YxeVisitReport report = param.getOne(baseMapper.selectListRel(param)); + if (report != null) { + setData(report); + } + return report; + } + + private void setData(YxeVisitReport report) { + if (report == null) { + return; + } + if (report.getUserId() != null) { + report.setUser(userService.getById(report.getUserId())); + } + } + + private boolean handleRealNameFilter(YxeVisitReportParam param) { + if (param == null || StrUtil.isBlank(param.getRealName())) { + return true; + } + List users = userService.listByRealName(param.getRealName()); + if (users == null || users.isEmpty()) { + return false; + } + Set userIds = users.stream() + .map(User::getUserId) + .filter(userId -> userId != null && userId > 0) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (userIds.isEmpty()) { + return false; + } + param.setUserIds(userIds); + return true; + } +} diff --git a/yxe_visit_report.sql b/yxe_visit_report.sql new file mode 100644 index 0000000..63fc986 --- /dev/null +++ b/yxe_visit_report.sql @@ -0,0 +1,61 @@ +-- 用途:新增 yxe_visit_report 表(跑店填报) +-- 日期:2026-10-11 +-- 背景:甲方要求把「自主填报」拆成「驻店填报 / 跑店填报」两种类型: +-- 1) 原自主填报页改为「驻店填报」,数据仍存 yxe_self_report,不动; +-- 2) 新增「跑店填报」,填报内容为「巡店列表(多门店,每条含门店/定位/打卡照片)+ 活动小结」。 +-- 巡店列表是「一次填报多家门店」的数组结构,yxe_self_report 既没有多门店字段, +-- 也没有区分驻店/跑店类型的字段;本次选择新建独立表,避免污染既有 928 条历史数据 +-- 与既有审核/导出逻辑。 +-- 影响:新建空表,0 行业务数据;不改动 yxe_self_report 任何结构。 +-- 性质:幂等,可重复执行(CREATE TABLE IF NOT EXISTS)。 +-- 配套代码: +-- yxe-java YxeVisitReport.java / YxeVisitReportParam.java / YxeVisitReportMapper.java +-- / YxeVisitReportMapper.xml / YxeVisitReportService(+Impl) +-- / YxeVisitReportController.java +-- yxe-mp packageUser/self-report/select.vue(选择填报类型) +-- packageUser/self-report/visit.vue(跑店填报) +-- packageUser/self-report/records.vue(填报记录加类型切换) +-- pages/audit/index.vue(药审核加跑店填报类型) +-- yxe-admin src/api/yxe/yxeVisitReport/*、src/views/yxe/yxeSelfReport/*(类型筛选与跑店详情) + +-- 执行前核对(应为 0,即表不存在) +SELECT COUNT(*) AS table_exists +FROM information_schema.TABLES +WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'yxe_visit_report'; + +-- 变更:建表(已存在则跳过) +CREATE TABLE IF NOT EXISTS `yxe_visit_report` ( + `id` int NOT NULL AUTO_INCREMENT, + `user_id` int DEFAULT NULL COMMENT '提交人ID', + `store_count` int DEFAULT 0 COMMENT '巡店门店数', + `store_list` longtext COMMENT '巡店列表JSON(门店/定位/打卡照片)', + `image` longtext COMMENT '全部图片集合', + `summary` longtext COMMENT '活动小结', + `reward` decimal(10,2) DEFAULT NULL COMMENT '奖金金额(审核时填写)', + `lat` varchar(64) DEFAULT NULL COMMENT '纬度(整单,取首条门店)', + `lng` varchar(64) DEFAULT NULL COMMENT '经度(整单,取首条门店)', + `location` varchar(500) DEFAULT NULL COMMENT '定位地址(整单,取首条门店)', + `sort_number` int DEFAULT 0 COMMENT '排序', + `comments` varchar(500) DEFAULT NULL COMMENT '备注', + `status` tinyint DEFAULT 0 COMMENT '状态:0待审核,1通过,2拒绝', + `reject_reason` varchar(500) DEFAULT NULL COMMENT '驳回原因', + `deleted` tinyint DEFAULT 0 COMMENT '逻辑删除', + `tenant_id` int DEFAULT 10325 COMMENT '租户ID', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_yxe_visit_report_user_id` (`user_id`), + KEY `idx_yxe_visit_report_status` (`status`), + KEY `idx_yxe_visit_report_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跑店填报'; + +-- 执行后核对(应返回 1 行,且列/注释符合预期) +SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT, ORDINAL_POSITION +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'yxe_visit_report' +ORDER BY ORDINAL_POSITION; + +-- 执行后核对(应为 0,跑店填报为新建功能,无历史数据) +SELECT COUNT(*) AS row_count FROM `yxe_visit_report`;