feat(跑店填报): 新增跑店填报表与接口
自主填报拆分为「驻店填报 / 跑店填报」两种类型,跑店填报的填报内容是
「巡店列表(多门店,每条含门店/定位/打卡照片)+ 活动小结」,与
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 与跑店详情
This commit is contained in:
@@ -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<PageResult<YxeVisitReport>> page(YxeVisitReportParam param) {
|
||||
return success(trimListPayload(yxeVisitReportService.pageRel(param)));
|
||||
}
|
||||
|
||||
@ApiOperation("审核页分页查询跑店填报")
|
||||
@GetMapping("/audit/page")
|
||||
public ApiResult<PageResult<YxeVisitReport>> auditPage(YxeVisitReportParam param) {
|
||||
return success(trimListPayload(yxeVisitReportService.pageAuditSummary(param)));
|
||||
}
|
||||
|
||||
@ApiOperation("查询全部跑店填报")
|
||||
@GetMapping()
|
||||
public ApiResult<List<YxeVisitReport>> list(YxeVisitReportParam param) {
|
||||
return success(yxeVisitReportService.listRel(param));
|
||||
}
|
||||
|
||||
@ApiOperation("查询当前用户跑店填报")
|
||||
@GetMapping("/userList")
|
||||
public ApiResult<List<YxeVisitReport>> userList(YxeVisitReportParam param) {
|
||||
param.setUserId(getLoginUserId());
|
||||
return success(yxeVisitReportService.listRel(param));
|
||||
}
|
||||
|
||||
@ApiOperation("根据id查询跑店填报")
|
||||
@GetMapping("/{id}")
|
||||
public ApiResult<YxeVisitReport> 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<YxeVisitReport> batchParam) {
|
||||
if (batchParam.update(yxeVisitReportService, "id")) {
|
||||
return success("修改成功");
|
||||
}
|
||||
return fail("修改失败");
|
||||
}
|
||||
|
||||
@ApiOperation("批量删除跑店填报")
|
||||
@DeleteMapping("/batch")
|
||||
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
|
||||
if (yxeVisitReportService.removeByIds(ids)) {
|
||||
return success("删除成功");
|
||||
}
|
||||
return fail("删除失败");
|
||||
}
|
||||
|
||||
@ApiOperation("导出跑店填报")
|
||||
@PostMapping("/export")
|
||||
public ApiResult<FileRecord> export(@RequestBody(required = false) YxeVisitReportParam param) throws IOException {
|
||||
if (param == null) {
|
||||
param = new YxeVisitReportParam();
|
||||
}
|
||||
List<YxeVisitReport> list = yxeVisitReportService.listRel(param);
|
||||
List<String> 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<Object> 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<YxeVisitReport> trimListPayload(PageResult<YxeVisitReport> 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<String> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<YxeVisitReport> {
|
||||
|
||||
List<YxeVisitReport> selectPageRel(@Param("page") IPage<YxeVisitReport> page,
|
||||
@Param("param") YxeVisitReportParam param);
|
||||
|
||||
List<YxeVisitReport> selectAuditPageRel(@Param("page") IPage<YxeVisitReport> page,
|
||||
@Param("param") YxeVisitReportParam param);
|
||||
|
||||
List<YxeVisitReport> selectListRel(@Param("param") YxeVisitReportParam param);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gxwebsoft.yxe.mapper.YxeVisitReportMapper">
|
||||
|
||||
<!-- 门店明细在 store_list JSON 里,连锁/门店筛选只能按文本模糊匹配 -->
|
||||
<sql id="selectSql">
|
||||
SELECT a.*
|
||||
FROM yxe_visit_report a
|
||||
<where>
|
||||
<if test="param.id != null">
|
||||
AND a.id = #{param.id}
|
||||
</if>
|
||||
<if test="param.idList != null and param.idList != ''">
|
||||
AND a.id IN
|
||||
<foreach collection="param.idList.split(',')" item="item" separator="," open="(" close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="param.userId != null">
|
||||
AND a.user_id = #{param.userId}
|
||||
</if>
|
||||
<if test="param.userGroup != null and param.userGroup != ''">
|
||||
AND a.user_id IN (SELECT user_id FROM sys_user WHERE deleted = 0 AND user_group LIKE CONCAT('%', #{param.userGroup}, '%'))
|
||||
</if>
|
||||
<if test="param.userIds != null and param.userIds.size() > 0">
|
||||
AND a.user_id IN
|
||||
<foreach collection="param.userIds" item="item" separator="," open="(" close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="param.chainName != null and param.chainName != ''">
|
||||
AND a.store_list LIKE CONCAT('%', #{param.chainName}, '%')
|
||||
</if>
|
||||
<if test="param.merchantName != null and param.merchantName != ''">
|
||||
AND a.store_list LIKE CONCAT('%', #{param.merchantName}, '%')
|
||||
</if>
|
||||
<if test="param.dateStart != null and param.dateStart != ''">
|
||||
AND a.create_time >= CONCAT(#{param.dateStart}, ' 00:00:00')
|
||||
</if>
|
||||
<if test="param.dateEnd != null and param.dateEnd != ''">
|
||||
AND a.create_time <= CONCAT(#{param.dateEnd}, ' 23:59:59')
|
||||
</if>
|
||||
<if test="param.summary != null">
|
||||
AND a.summary LIKE CONCAT('%', #{param.summary}, '%')
|
||||
</if>
|
||||
<if test="param.status != null">
|
||||
AND a.status = #{param.status}
|
||||
</if>
|
||||
<if test="param.deleted != null">
|
||||
AND a.deleted = #{param.deleted}
|
||||
</if>
|
||||
<if test="param.deleted == null">
|
||||
AND a.deleted = 0
|
||||
</if>
|
||||
<if test="param.createTimeStart != null">
|
||||
AND a.create_time >= #{param.createTimeStart}
|
||||
</if>
|
||||
<if test="param.createTimeEnd != null">
|
||||
AND a.create_time <= #{param.createTimeEnd}
|
||||
</if>
|
||||
<if test="param.keywords != null">
|
||||
AND (
|
||||
a.store_list LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
OR a.summary LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
OR a.location LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<select id="selectPageRel" resultType="com.gxwebsoft.yxe.entity.YxeVisitReport">
|
||||
<include refid="selectSql"></include>
|
||||
ORDER BY a.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectAuditPageRel" resultType="com.gxwebsoft.yxe.entity.YxeVisitReport">
|
||||
<include refid="selectSql"></include>
|
||||
ORDER BY a.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectListRel" resultType="com.gxwebsoft.yxe.entity.YxeVisitReport">
|
||||
<include refid="selectSql"></include>
|
||||
ORDER BY a.create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -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<Integer> 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;
|
||||
}
|
||||
@@ -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<YxeVisitReport> {
|
||||
|
||||
PageResult<YxeVisitReport> pageRel(YxeVisitReportParam param);
|
||||
|
||||
PageResult<YxeVisitReport> pageAuditSummary(YxeVisitReportParam param);
|
||||
|
||||
List<YxeVisitReport> listRel(YxeVisitReportParam param);
|
||||
|
||||
YxeVisitReport getByIdRel(Integer id);
|
||||
}
|
||||
@@ -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<YxeVisitReportMapper, YxeVisitReport> implements YxeVisitReportService {
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
@Override
|
||||
public PageResult<YxeVisitReport> pageRel(YxeVisitReportParam param) {
|
||||
if (!handleRealNameFilter(param)) {
|
||||
return new PageResult<>(Collections.emptyList(), 0L);
|
||||
}
|
||||
PageParam<YxeVisitReport, YxeVisitReportParam> page = new PageParam<>(param);
|
||||
page.setDefaultOrder("sort_number asc, create_time desc");
|
||||
List<YxeVisitReport> list = baseMapper.selectPageRel(page, param);
|
||||
list.forEach(this::setData);
|
||||
return new PageResult<>(list, page.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<YxeVisitReport> pageAuditSummary(YxeVisitReportParam param) {
|
||||
if (!handleRealNameFilter(param)) {
|
||||
return new PageResult<>(Collections.emptyList(), 0L);
|
||||
}
|
||||
PageParam<YxeVisitReport, YxeVisitReportParam> page = new PageParam<>(param);
|
||||
page.setDefaultOrder("create_time desc");
|
||||
List<YxeVisitReport> 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<YxeVisitReport> listRel(YxeVisitReportParam param) {
|
||||
if (!handleRealNameFilter(param)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<YxeVisitReport> list = baseMapper.selectListRel(param);
|
||||
list.forEach(this::setData);
|
||||
PageParam<YxeVisitReport, YxeVisitReportParam> 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<User> users = userService.listByRealName(param.getRealName());
|
||||
if (users == null || users.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Set<Integer> 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;
|
||||
}
|
||||
}
|
||||
@@ -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`;
|
||||
Reference in New Issue
Block a user