jiaxing.zhou

feat(frontend): 添加返利管理功能模块

- 新增返利管理页面路由配置
- 实现出库列表页面字段和逻辑调整
- 更新发票管理页面引用类型和方法名
- 添加出库API接口及类型定义
- 主布局菜单增加返利管理入口- 权限管理模块API调用方法更新
- 新建返利管理详情页面组件- 实现返利数据查询、展示和操作功能
- 添加返利统计图表可视化功能
- 完善返利详情查看和编辑流程
package com.apple.erp.dto.request;
import lombok.Data;
import javax.validation.constraints.*;
import java.math.BigDecimal;
/**
* 返利新增请求DTO
*
* @author Apple ERP
*/
@Data
public class RebateAddReq {
/**
* 返利明细编号
*/
@Size(max = 30, message = "返利明细编号不能超过30个字符")
private String rebateNo;
/**
* 关联订单编号
*/
@NotBlank(message = "关联订单编号不能为空")
@Size(max = 30, message = "订单编号不能超过30个字符")
private String orderNo;
/**
* 经销商编码
*/
@NotBlank(message = "经销商编码不能为空")
@Size(max = 20, message = "经销商编码不能超过20个字符")
private String dealerCode;
/**
* 经销商名称
*/
@NotBlank(message = "经销商名称不能为空")
@Size(max = 100, message = "经销商名称不能超过100个字符")
private String dealerName;
/**
* 产品编码
*/
@NotBlank(message = "产品编码不能为空")
@Size(max = 30, message = "产品编码不能超过30个字符")
private String productCode;
/**
* 返利金额
*/
@NotNull(message = "返利金额不能为空")
@DecimalMin(value = "0", message = "返利金额不能小于0")
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
@NotBlank(message = "返利记录日期不能为空")
private String rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
@NotNull(message = "操作类型不能为空")
@Min(value = 1, message = "操作类型值无效")
@Max(value = 2, message = "操作类型值无效")
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
}
package com.apple.erp.dto.request;
import lombok.Data;
/**
* 返利台账明细查询请求DTO
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Data
public class RebateQueryReq {
/**
* 返利明细编号
*/
private String rebateNo;
/**
* 关联订单编号
*/
private String orderNo;
/**
* 经销商编码
*/
private String dealerCode;
/**
* 经销商名称
*/
private String dealerName;
/**
* 产品编码
*/
private String productCode;
/**
* 操作类型:1-新增,2-扣减
*/
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
/**
* 返利记录开始日期
*/
private String rebateStartDate;
/**
* 返利记录结束日期
*/
private String rebateEndDate;
/**
* 页码
*/
private Integer pageNum = 1;
/**
* 页面大小
*/
private Integer pageSize = 10;
}
package com.apple.erp.dto.request;
import lombok.Data;
import javax.validation.constraints.*;
import java.math.BigDecimal;
/**
* 返利更新请求DTO
*
* @author Apple ERP
*/
@Data
public class RebateUpdateReq {
/**
* 返利明细ID
*/
@NotNull(message = "返利明细ID不能为空")
private Long rebateId;
/**
* 返利明细编号
*/
@Size(max = 30, message = "返利明细编号不能超过30个字符")
private String rebateNo;
/**
* 关联订单编号
*/
@NotBlank(message = "关联订单编号不能为空")
@Size(max = 30, message = "订单编号不能超过30个字符")
private String orderNo;
/**
* 经销商编码
*/
@NotBlank(message = "经销商编码不能为空")
@Size(max = 20, message = "经销商编码不能超过20个字符")
private String dealerCode;
/**
* 经销商名称
*/
@NotBlank(message = "经销商名称不能为空")
@Size(max = 100, message = "经销商名称不能超过100个字符")
private String dealerName;
/**
* 产品编码
*/
@NotBlank(message = "产品编码不能为空")
@Size(max = 30, message = "产品编码不能超过30个字符")
private String productCode;
/**
* 返利金额
*/
@NotNull(message = "返利金额不能为空")
@DecimalMin(value = "0", message = "返利金额不能小于0")
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
@NotBlank(message = "返利记录日期不能为空")
private String rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
@NotNull(message = "操作类型不能为空")
@Min(value = 1, message = "操作类型值无效")
@Max(value = 2, message = "操作类型值无效")
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
}
package com.apple.erp.dto.response;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 返利响应DTO
*
* @author Apple ERP
*/
@Data
public class RebateRes {
/**
* 返利明细ID
*/
private Long rebateId;
/**
* 返利明细编号
*/
private String rebateNo;
/**
* 关联订单编号
*/
private String orderNo;
/**
* 经销商编码
*/
private String dealerCode;
/**
* 经销商名称
*/
private String dealerName;
/**
* 产品编码
*/
private String productCode;
/**
* 返利金额
*/
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
private LocalDate rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
private Integer operateType;
/**
* 操作类型文本
*/
private String operateTypeText;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
/**
* 计算状态文本
*/
private String calcFlagText;
/**
* 数据上传时间
*/
private LocalDateTime uploadTime;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 创建者
*/
private String createBy;
/**
* 更新者
*/
private String updateBy;
}
package com.apple.erp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 返利实体类
*
* @author Apple ERP
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_rebate_detail")
public class Rebate {
/**
* 返利明细ID
*/
@TableId(value = "rebate_id", type = IdType.AUTO)
private Long rebateId;
/**
* 返利明细编号
*/
@TableField("rebate_no")
private String rebateNo;
/**
* 关联订单编号
*/
@TableField("order_no")
private String orderNo;
/**
* 经销商编码
*/
@TableField("dealer_code")
private String dealerCode;
/**
* 经销商名称
*/
@TableField("dealer_name")
private String dealerName;
/**
* 产品编码
*/
@TableField("product_code")
private String productCode;
/**
* 返利金额
*/
@TableField("rebate_amount")
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
@TableField("rebate_date")
private LocalDate rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
@TableField("operate_type")
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
@TableField("calc_flag")
private Integer calcFlag;
/**
* 数据上传时间
*/
@TableField("upload_time")
private LocalDateTime uploadTime;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 创建者
*/
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
/**
* 更新者
*/
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 删除标志:0-存在,2-删除
*/
@TableField("del_flag")
@TableLogic(value = "0", delval = "2")
private String delFlag;
}
package com.apple.erp.mapper;
import com.apple.erp.entity.Rebate;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* 返利台账明细Mapper接口
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Mapper
public interface RebateMapper extends BaseMapper<Rebate> {
/**
* 分页查询返利明细列表
*
* @param page 分页参数
* @param params 查询参数
* @return 返利明细分页列表
*/
IPage<Rebate> selectRebatePage(Page<Rebate> page, @Param("params") Map<String, Object> params);
/**
* 根据经销商编码统计返利总金额
*
* @param dealerCode 经销商编码
* @param operateType 操作类型
* @return 返利总金额
*/
BigDecimal sumRebateAmountByDealer(@Param("dealerCode") String dealerCode, @Param("operateType") Integer operateType);
/**
* 根据产品编码统计返利总金额
*
* @param productCode 产品编码
* @param operateType 操作类型
* @return 返利总金额
*/
BigDecimal sumRebateAmountByProduct(@Param("productCode") String productCode, @Param("operateType") Integer operateType);
/**
* 统计各操作类型的返利数量
*
* @return 操作类型统计
*/
List<Map<String, Object>> countRebateByOperateType();
/**
* 查询未计算的返利明细列表
*
* @return 未计算返利明细列表
*/
List<Rebate> selectUnCalcRebates();
/**
* 根据返利编号查询返利明细
*
* @param rebateNo 返利编号
* @return 返利明细信息
*/
Rebate selectByRebateNo(@Param("rebateNo") String rebateNo);
/**
* 获取返利月度统计数据
*
* @param year 年份
* @return 月度统计数据
*/
List<Map<String, Object>> getRebateMonthlyStats(@Param("year") Integer year);
/**
* 获取返利状态统计数据
*
* @return 状态统计数据
*/
Map<String, Object> getRebateStatusStats();
/**
* 获取返利趋势统计数据
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 趋势统计数据
*/
List<Map<String, Object>> getRebateTrendStats(@Param("startDate") String startDate, @Param("endDate") String endDate);
}
package com.apple.erp.service;
import com.apple.erp.dto.request.RebateAddReq;
import com.apple.erp.dto.request.RebateQueryReq;
import com.apple.erp.dto.request.RebateUpdateReq;
import com.apple.erp.dto.response.RebateRes;
import com.apple.erp.entity.Rebate;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* 返利台账明细服务接口
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
public interface RebateService extends IService<Rebate> {
/**
* 分页查询返利明细列表
*
* @param queryReq 查询请求
* @return 返利明细分页列表
*/
IPage<RebateRes> getRebatePage(RebateQueryReq queryReq);
/**
* 根据ID查询返利明细详情
*
* @param rebateId 返利明细ID
* @return 返利明细详情
*/
RebateRes getRebateById(Long rebateId);
/**
* 新增返利明细
*
* @param addReq 新增请求
* @return 是否成功
*/
boolean addRebate(RebateAddReq addReq);
/**
* 更新返利明细
*
* @param updateReq 更新请求
* @return 是否成功
*/
boolean updateRebate(RebateUpdateReq updateReq);
/**
* 删除返利明细
*
* @param rebateId 返利明细ID
* @return 是否成功
*/
boolean deleteRebate(Long rebateId);
/**
* 批量删除返利明细
*
* @param rebateIds 返利明细ID列表
* @return 是否成功
*/
boolean batchDeleteRebate(List<Long> rebateIds);
/**
* 根据经销商编码统计返利总金额
*
* @param dealerCode 经销商编码
* @param operateType 操作类型(可选)
* @return 返利总金额
*/
BigDecimal sumRebateAmountByDealer(String dealerCode, Integer operateType);
/**
* 根据产品编码统计返利总金额
*
* @param productCode 产品编码
* @param operateType 操作类型(可选)
* @return 返利总金额
*/
BigDecimal sumRebateAmountByProduct(String productCode, Integer operateType);
/**
* 统计各操作类型的返利数量
*
* @return 操作类型统计
*/
List<Map<String, Object>> countRebateByOperateType();
/**
* 查询未计算的返利明细列表
*
* @return 未计算返利明细列表
*/
List<RebateRes> getUnCalcRebates();
/**
* 根据返利编号查询返利明细
*
* @param rebateNo 返利编号
* @return 返利明细信息
*/
RebateRes getRebateByNo(String rebateNo);
/**
* 生成返利编号
*
* @return 返利编号
*/
String generateRebateNo();
/**
* 获取返利月度统计数据
*
* @param year 年份
* @return 月度统计数据
*/
List<Map<String, Object>> getRebateMonthlyStats(Integer year);
/**
* 获取返利状态统计数据
*
* @return 状态统计数据
*/
Map<String, Object> getRebateStatusStats();
/**
* 获取返利趋势统计数据
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 趋势统计数据
*/
List<Map<String, Object>> getRebateTrendStats(String startDate, String endDate);
}
package com.apple.erp.service.impl;
import com.apple.erp.dto.request.RebateAddReq;
import com.apple.erp.dto.request.RebateQueryReq;
import com.apple.erp.dto.request.RebateUpdateReq;
import com.apple.erp.dto.response.RebateRes;
import com.apple.erp.entity.Rebate;
import com.apple.erp.mapper.RebateMapper;
import com.apple.erp.service.RebateService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* 返利台账明细服务实现类
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Slf4j
@Service
public class RebateServiceImpl extends ServiceImpl<RebateMapper, Rebate> implements RebateService {
@Autowired
private RebateMapper rebateMapper;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public IPage<RebateRes> getRebatePage(RebateQueryReq queryReq) {
// 构建查询参数
Map<String, Object> params = buildQueryParams(queryReq);
// 创建分页对象
Page<Rebate> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
// 分页查询
IPage<Rebate> rebatePage = rebateMapper.selectRebatePage(page, params);
// 转换为响应DTO
Page<RebateRes> resultPage = new Page<>(rebatePage.getCurrent(), rebatePage.getSize(), rebatePage.getTotal());
List<RebateRes> rebateResList = rebatePage.getRecords().stream()
.map(this::convertToRebateRes)
.collect(Collectors.toList());
resultPage.setRecords(rebateResList);
return resultPage;
}
@Override
public RebateRes getRebateById(Long rebateId) {
Rebate rebate = getById(rebateId);
return rebate != null ? convertToRebateRes(rebate) : null;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addRebate(RebateAddReq addReq) {
Rebate rebate = new Rebate();
BeanUtils.copyProperties(addReq, rebate);
// 设置返利编号(如果未提供)
if (addReq.getRebateNo() == null || addReq.getRebateNo().isEmpty()) {
rebate.setRebateNo(generateRebateNo());
}
// 转换日期字符串
if (addReq.getRebateDate() != null && !addReq.getRebateDate().isEmpty()) {
rebate.setRebateDate(LocalDate.parse(addReq.getRebateDate(), DATE_FORMATTER));
}
// 设置上传时间
rebate.setUploadTime(LocalDateTime.now());
// 默认计算状态为未计算
if (rebate.getCalcFlag() == null) {
rebate.setCalcFlag(0);
}
return save(rebate);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateRebate(RebateUpdateReq updateReq) {
Rebate rebate = getById(updateReq.getRebateId());
if (rebate == null) {
throw new RuntimeException("返利记录不存在");
}
BeanUtils.copyProperties(updateReq, rebate);
// 转换日期字符串
if (updateReq.getRebateDate() != null && !updateReq.getRebateDate().isEmpty()) {
rebate.setRebateDate(LocalDate.parse(updateReq.getRebateDate(), DATE_FORMATTER));
}
return updateById(rebate);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteRebate(Long rebateId) {
return removeById(rebateId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchDeleteRebate(List<Long> rebateIds) {
return removeByIds(rebateIds);
}
@Override
public BigDecimal sumRebateAmountByDealer(String dealerCode, Integer operateType) {
BigDecimal amount = rebateMapper.sumRebateAmountByDealer(dealerCode, operateType);
return amount != null ? amount : BigDecimal.ZERO;
}
@Override
public BigDecimal sumRebateAmountByProduct(String productCode, Integer operateType) {
BigDecimal amount = rebateMapper.sumRebateAmountByProduct(productCode, operateType);
return amount != null ? amount : BigDecimal.ZERO;
}
@Override
public List<Map<String, Object>> countRebateByOperateType() {
return rebateMapper.countRebateByOperateType();
}
@Override
public List<RebateRes> getUnCalcRebates() {
List<Rebate> rebates = rebateMapper.selectUnCalcRebates();
return rebates.stream()
.map(this::convertToRebateRes)
.collect(Collectors.toList());
}
@Override
public RebateRes getRebateByNo(String rebateNo) {
Rebate rebate = rebateMapper.selectByRebateNo(rebateNo);
return rebate != null ? convertToRebateRes(rebate) : null;
}
@Override
public String generateRebateNo() {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String prefix = "RB" + dateStr;
// 查询当天已有的返利编号数量
LambdaQueryWrapper<Rebate> wrapper = new LambdaQueryWrapper<>();
wrapper.likeRight(Rebate::getRebateNo, prefix);
long count = count(wrapper);
// 生成4位序号
String sequence = String.format("%04d", count + 1);
return prefix + sequence;
}
/**
* 构建查询参数
*/
private Map<String, Object> buildQueryParams(RebateQueryReq queryReq) {
Map<String, Object> params = new HashMap<>();
if (queryReq.getRebateNo() != null && !queryReq.getRebateNo().isEmpty()) {
params.put("rebateNo", queryReq.getRebateNo());
}
if (queryReq.getOrderNo() != null && !queryReq.getOrderNo().isEmpty()) {
params.put("orderNo", queryReq.getOrderNo());
}
if (queryReq.getDealerCode() != null && !queryReq.getDealerCode().isEmpty()) {
params.put("dealerCode", queryReq.getDealerCode());
}
if (queryReq.getDealerName() != null && !queryReq.getDealerName().isEmpty()) {
params.put("dealerName", queryReq.getDealerName());
}
if (queryReq.getProductCode() != null && !queryReq.getProductCode().isEmpty()) {
params.put("productCode", queryReq.getProductCode());
}
if (queryReq.getOperateType() != null) {
params.put("operateType", queryReq.getOperateType());
}
if (queryReq.getCalcFlag() != null) {
params.put("calcFlag", queryReq.getCalcFlag());
}
if (queryReq.getRebateStartDate() != null && !queryReq.getRebateStartDate().isEmpty()) {
params.put("rebateStartDate", queryReq.getRebateStartDate());
}
if (queryReq.getRebateEndDate() != null && !queryReq.getRebateEndDate().isEmpty()) {
params.put("rebateEndDate", queryReq.getRebateEndDate());
}
return params;
}
/**
* 转换为响应DTO
*/
private RebateRes convertToRebateRes(Rebate rebate) {
RebateRes rebateRes = new RebateRes();
BeanUtils.copyProperties(rebate, rebateRes);
// 设置操作类型文本
rebateRes.setOperateTypeText(getOperateTypeText(rebate.getOperateType()));
// 设置计算状态文本
rebateRes.setCalcFlagText(getCalcFlagText(rebate.getCalcFlag()));
return rebateRes;
}
/**
* 获取操作类型文本
*/
private String getOperateTypeText(Integer operateType) {
if (operateType == null) {
return "";
}
switch (operateType) {
case 1:
return "新增";
case 2:
return "扣减";
default:
return "未知";
}
}
/**
* 获取计算状态文本
*/
private String getCalcFlagText(Integer calcFlag) {
if (calcFlag == null) {
return "";
}
switch (calcFlag) {
case 0:
return "未计算";
case 1:
return "已计算";
default:
return "未知";
}
}
@Override
public List<Map<String, Object>> getRebateMonthlyStats(Integer year) {
log.info("获取返利月度统计数据,年份:{}", year);
return rebateMapper.getRebateMonthlyStats(year);
}
@Override
public Map<String, Object> getRebateStatusStats() {
log.info("获取返利状态统计数据");
return rebateMapper.getRebateStatusStats();
}
@Override
public List<Map<String, Object>> getRebateTrendStats(String startDate, String endDate) {
log.info("获取返利趋势统计数据,开始日期:{},结束日期:{}", startDate, endDate);
return rebateMapper.getRebateTrendStats(startDate, endDate);
}
}
......@@ -17,8 +17,8 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: MySQL@123
username: apple_erp_app
password: StrongPassw0rd!
druid:
# 初始连接数
initial-size: 5
......@@ -70,7 +70,7 @@ spring:
redis:
host: localhost
port: 6379
password:
password: 123456
database: 0
timeout: 10000ms
lettuce:
......
<?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.apple.erp.mapper.RebateMapper">
<!-- 返利明细基础字段 -->
<sql id="baseColumns">
rebate_id, rebate_no, order_no, dealer_code, dealer_name, product_code,
rebate_amount, rebate_date, operate_type, calc_flag, upload_time,
create_by, create_time, update_by, update_time
</sql>
<!-- 分页查询返利明细列表 -->
<select id="selectRebatePage" resultType="com.apple.erp.entity.Rebate">
SELECT
<include refid="baseColumns"/>
FROM t_rebate_detail
WHERE del_flag = '0'
<if test="params.rebateNo != null and params.rebateNo != ''">
AND rebate_no LIKE CONCAT('%', #{params.rebateNo}, '%')
</if>
<if test="params.orderNo != null and params.orderNo != ''">
AND order_no LIKE CONCAT('%', #{params.orderNo}, '%')
</if>
<if test="params.dealerCode != null and params.dealerCode != ''">
AND dealer_code = #{params.dealerCode}
</if>
<if test="params.dealerName != null and params.dealerName != ''">
AND dealer_name LIKE CONCAT('%', #{params.dealerName}, '%')
</if>
<if test="params.productCode != null and params.productCode != ''">
AND product_code = #{params.productCode}
</if>
<if test="params.operateType != null">
AND operate_type = #{params.operateType}
</if>
<if test="params.calcFlag != null">
AND calc_flag = #{params.calcFlag}
</if>
<if test="params.rebateStartDate != null and params.rebateStartDate != ''">
AND rebate_date &gt;= #{params.rebateStartDate}
</if>
<if test="params.rebateEndDate != null and params.rebateEndDate != ''">
AND rebate_date &lt;= #{params.rebateEndDate}
</if>
ORDER BY create_time DESC
</select>
<!-- 根据经销商编码统计返利总金额 -->
<select id="sumRebateAmountByDealer" resultType="java.math.BigDecimal">
SELECT COALESCE(SUM(rebate_amount), 0)
FROM t_rebate_detail
WHERE del_flag = '0'
AND dealer_code = #{dealerCode}
<if test="operateType != null">
AND operate_type = #{operateType}
</if>
</select>
<!-- 根据产品编码统计返利总金额 -->
<select id="sumRebateAmountByProduct" resultType="java.math.BigDecimal">
SELECT COALESCE(SUM(rebate_amount), 0)
FROM t_rebate_detail
WHERE del_flag = '0'
AND product_code = #{productCode}
<if test="operateType != null">
AND operate_type = #{operateType}
</if>
</select>
<!-- 统计各操作类型的返利数量 -->
<select id="countRebateByOperateType" resultType="java.util.Map">
SELECT
operate_type,
COUNT(*) as count,
COALESCE(SUM(rebate_amount), 0) as totalAmount
FROM t_rebate_detail
WHERE del_flag = '0'
GROUP BY operate_type
</select>
<!-- 查询未计算的返利明细列表 -->
<select id="selectUnCalcRebates" resultType="com.apple.erp.entity.Rebate">
SELECT
<include refid="baseColumns"/>
FROM t_rebate_detail
WHERE del_flag = '0'
AND calc_flag = 0
ORDER BY create_time ASC
</select>
<!-- 根据返利编号查询返利明细 -->
<select id="selectByRebateNo" resultType="com.apple.erp.entity.Rebate">
SELECT
<include refid="baseColumns"/>
FROM t_rebate_detail
WHERE del_flag = '0'
AND rebate_no = #{rebateNo}
</select>
<!-- 获取返利月度统计数据 -->
<select id="getRebateMonthlyStats" resultType="java.util.Map">
SELECT
MONTH(rebate_date) as month,
COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount,
COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
ELSE 0 END), 0) as calculatedAmount,
COUNT(*) as totalCount,
COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount
FROM t_rebate_detail
WHERE del_flag = '0'
AND YEAR(rebate_date) = #{year}
GROUP BY MONTH(rebate_date)
ORDER BY month
</select>
<!-- 获取返利状态统计数据 -->
<select id="getRebateStatusStats" resultType="java.util.Map">
SELECT
COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount,
COUNT(CASE WHEN calc_flag = 0 THEN 1 END) as unCalculatedCount,
COUNT(*) as totalCount,
COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
ELSE 0 END), 0) as calculatedAmount,
COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount
FROM t_rebate_detail
WHERE del_flag = '0'
</select>
<!-- 获取返利趋势统计数据 -->
<select id="getRebateTrendStats" resultType="java.util.Map">
SELECT
DATE(rebate_date) as date,
COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount,
COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
ELSE 0 END), 0) as calculatedAmount,
COUNT(*) as totalCount,
COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount
FROM t_rebate_detail
WHERE del_flag = '0'
<if test="startDate != null and startDate != ''">
AND rebate_date >= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND rebate_date &lt;= #{endDate}
</if>
GROUP BY DATE(rebate_date)
ORDER BY date
</select>
</mapper>
......@@ -7,21 +7,30 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ActionBar: typeof import('./src/components/common/ActionBar.vue')['default']
DataTable: typeof import('./src/components/common/DataTable.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
Header: typeof import('./src/components/layout/Header.vue')['default']
Pagination: typeof import('./src/components/common/Pagination.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SearchBar: typeof import('./src/components/common/SearchBar.vue')['default']
Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default']
SidebarItem: typeof import('./src/components/layout/SidebarItem.vue')['default']
TableToolbar: typeof import('./src/components/common/TableToolbar.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
......
import { request } from '../utils/request'
// 出库相关类型定义
export interface DeliveryInfo {
deliveryId: number
orderId: number
orderNo: string
dealerCode: string
dealerName: string
deliveryDate: string
totalAmount: number
deliveryStatus: number
createBy?: string
createTime?: string
updateBy?: string
updateTime?: string
deliveryItems?: DeliveryItemInfo[]
}
export interface DeliveryItemInfo {
itemId?: number
deliveryId?: number
productCode: string
productName: string
productQty: number
unitPrice: number
totalPrice: number
createBy?: string
createTime?: string
}
export interface DeliveryQueryReq {
pageNum?: number
pageSize?: number
orderNo?: string
dealerCode?: string
dealerName?: string
deliveryStartDate?: string
deliveryEndDate?: string
deliveryStatus?: number
}
export interface DeliveryAddReq {
orderId: number
deliveryDate: string
deliveryItems: Omit<DeliveryItemInfo, 'itemId' | 'deliveryId' | 'createBy' | 'createTime'>[]
}
export interface DeliveryUpdateReq {
deliveryId: number
deliveryDate?: string
deliveryStatus?: number
deliveryItems?: Omit<DeliveryItemInfo, 'itemId' | 'deliveryId' | 'createBy' | 'createTime'>[]
}
// 出库API接口
export const deliveryApi = {
// 分页查询出库列表
getDeliveryList: (params: DeliveryQueryReq) => {
return request.get('/api/delivery/list', { params })
},
// 获取出库详情
getDeliveryDetail: (deliveryId: number) => {
return request.get(`/api/delivery/${deliveryId}`)
},
// 新增出库
addDelivery: (data: DeliveryAddReq) => {
return request.post('/api/delivery/add', data)
},
// 更新出库
updateDelivery: (data: DeliveryUpdateReq) => {
return request.post('/api/delivery/update', data)
},
// 删除出库
deleteDelivery: (deliveryId: number) => {
return request.delete(`/api/delivery/${deliveryId}`)
},
// 批量删除出库
batchDeleteDelivery: (deliveryIds: number[]) => {
return request.post('/api/delivery/batchDelete', deliveryIds)
},
// 导出出库数据
exportDelivery: (params: DeliveryQueryReq) => {
return request.get('/api/delivery/export', { params, responseType: 'blob' })
}
}
export default deliveryApi
import { request } from '../utils/request'
// 发票相关类型定义
export interface InvoiceInfo {
invoiceId: number
orderId: number
orderNo: string
dealerCode: string
dealerName: string
invoiceDate: string
totalAmount: number
invoiceStatus: number
invoiceNo?: string
createBy?: string
createTime?: string
updateBy?: string
updateTime?: string
invoiceItems?: InvoiceItemInfo[]
}
export interface InvoiceItemInfo {
itemId?: number
invoiceId?: number
productCode: string
productName: string
productQty: number
unitPrice: number
totalPrice: number
createBy?: string
createTime?: string
}
export interface InvoiceQueryReq {
pageNum?: number
pageSize?: number
orderNo?: string
dealerCode?: string
dealerName?: string
invoiceStartDate?: string
invoiceEndDate?: string
invoiceStatus?: number
}
export interface InvoiceAddReq {
orderId: number
invoiceDate: string
invoiceNo?: string
invoiceItems: Omit<InvoiceItemInfo, 'itemId' | 'invoiceId' | 'createBy' | 'createTime'>[]
}
export interface InvoiceUpdateReq {
invoiceId: number
invoiceDate?: string
invoiceStatus?: number
invoiceNo?: string
invoiceItems?: Omit<InvoiceItemInfo, 'itemId' | 'invoiceId' | 'createBy' | 'createTime'>[]
}
// 发票API接口
export const invoiceApi = {
// 分页查询发票列表
getInvoiceList: (params: InvoiceQueryReq) => {
return request.get('/api/invoice/list', { params })
},
// 获取发票详情
getInvoiceDetail: (invoiceId: number) => {
return request.get(`/api/invoice/${invoiceId}`)
},
// 新增发票
addInvoice: (data: InvoiceAddReq) => {
return request.post('/api/invoice/add', data)
},
// 更新发票
updateInvoice: (data: InvoiceUpdateReq) => {
return request.post('/api/invoice/update', data)
},
// 删除发票
deleteInvoice: (invoiceId: number) => {
return request.delete(`/api/invoice/${invoiceId}`)
},
// 批量删除发票
batchDeleteInvoice: (invoiceIds: number[]) => {
return request.post('/api/invoice/batchDelete', invoiceIds)
},
// 导出发票数据
exportInvoice: (params: InvoiceQueryReq) => {
return request.get('/api/invoice/export', { params, responseType: 'blob' })
}
}
export default invoiceApi
import request from '@/utils/request'
// 返利台账明细类型定义
export interface Rebate {
rebateId?: number
rebateNo?: string
orderNo: string
dealerCode: string
dealerName: string
productCode: string
rebateAmount: number
rebateDate: string
operateType: number
operateTypeText?: string
calcFlag?: number
calcFlagText?: string
uploadTime?: string
createTime?: string
updateTime?: string
createBy?: string
updateBy?: string
}
// 返利查询参数
export interface RebateSearchParams {
pageNum: number
pageSize: number
rebateNo?: string
orderNo?: string
dealerCode?: string
dealerName?: string
productCode?: string
operateType?: number
calcFlag?: number
rebateStartDate?: string
rebateEndDate?: string
}
// 返利新增参数
export interface RebateAddParams {
rebateNo?: string
orderNo: string
dealerCode: string
dealerName: string
productCode: string
rebateAmount: number
rebateDate: string
operateType: number
calcFlag?: number
}
// 返利更新参数
export interface RebateUpdateParams extends RebateAddParams {
rebateId: number
}
// 分页查询返利列表
export const getRebatePage = (params: RebateSearchParams) => {
return request({
url: '/api/rebate/page',
method: 'post',
data: params
})
}
// 根据ID查询返利详情
export const getRebateById = (rebateId: number) => {
return request({
url: `/api/rebate/${rebateId}`,
method: 'get'
})
}
// 新增返利
export const addRebate = (data: RebateAddParams) => {
return request({
url: '/api/rebate/add',
method: 'post',
data
})
}
// 更新返利
export const updateRebate = (data: RebateUpdateParams) => {
return request({
url: '/api/rebate/update',
method: 'post',
data
})
}
// 删除返利
export const deleteRebate = (rebateId: number) => {
return request({
url: `/api/rebate/${rebateId}`,
method: 'delete'
})
}
// 批量删除返利
export const batchDeleteRebate = (rebateIds: number[]) => {
return request({
url: '/api/rebate/batchDelete',
method: 'post',
data: rebateIds
})
}
// 标记返利为已计算
export const markRebateCalculated = (rebateId: number) => {
return request({
url: `/api/rebate/markCalculated/${rebateId}`,
method: 'post'
})
}
// 批量标记返利为已计算
export const batchMarkRebateCalculated = (rebateIds: number[]) => {
return request({
url: '/api/rebate/batchMarkCalculated',
method: 'post',
data: rebateIds
})
}
// 根据经销商编码统计返利总金额
export const sumRebateAmountByDealer = (dealerCode: string, operateType?: number) => {
return request({
url: `/api/rebate/sumByDealer/${dealerCode}`,
method: 'get',
params: { operateType }
})
}
// 根据产品编码统计返利总金额
export const sumRebateAmountByProduct = (productCode: string, operateType?: number) => {
return request({
url: `/api/rebate/sumByProduct/${productCode}`,
method: 'get',
params: { operateType }
})
}
// 统计各操作类型的返利数量
export const countRebateByOperateType = () => {
return request({
url: '/api/rebate/countByOperateType',
method: 'get'
})
}
// 查询未计算的返利明细列表
export const getUnCalcRebates = () => {
return request({
url: '/api/rebate/unCalc',
method: 'get'
})
}
// 根据返利编号查询返利
export const getRebateByNo = (rebateNo: string) => {
return request({
url: `/api/rebate/byNo/${rebateNo}`,
method: 'get'
})
}
// 生成返利编号
export const generateRebateNo = () => {
return request({
url: '/api/rebate/generateNo',
method: 'get'
})
}
// 导出返利数据
export const exportRebate = (params: RebateSearchParams) => {
return request({
url: '/api/rebate/export',
method: 'post',
data: params,
responseType: 'blob'
})
}
// 获取返利月度统计数据
export const getRebateMonthlyStats = (year?: number) => {
return request({
url: '/api/rebate/monthlyStats',
method: 'get',
params: { year: year || new Date().getFullYear() }
})
}
// 获取返利状态统计数据
export const getRebateStatusStats = () => {
return request({
url: '/api/rebate/statusStats',
method: 'get'
})
}
// 获取返利趋势统计数据
export const getRebateTrendStats = (startDate?: string, endDate?: string) => {
return request({
url: '/api/rebate/trendStats',
method: 'get',
params: { startDate, endDate }
})
}
export default {
getRebatePage,
getRebateById,
addRebate,
updateRebate,
deleteRebate,
batchDeleteRebate,
markRebateCalculated,
batchMarkRebateCalculated,
sumRebateAmountByDealer,
sumRebateAmountByProduct,
countRebateByOperateType,
getUnCalcRebates,
getRebateByNo,
generateRebateNo,
exportRebate,
getRebateMonthlyStats,
getRebateStatusStats,
getRebateTrendStats
}
......@@ -146,6 +146,7 @@ const menuItems = ref([
{ name: '发票管理', path: '/main/invoice', icon: '🧾' },
{ name: '产品管理', path: '/main/product', icon: '📦' },
{ name: '经销商管理', path: '/main/dealer', icon: '🏢' },
{ name: '返利管理', path: '/main/rebate', icon: '💰' },
{
name: '系统设置',
icon: '⚙️',
......
......@@ -144,6 +144,15 @@ const staticRoutes: RouteRecordRaw[] = [
}
},
{
path: 'rebate',
name: 'Rebate',
component: () => import('@/views/rebate/index.vue'),
meta: {
title: '返利管理',
requiresAuth: true
}
},
{
path: 'settings',
name: 'Settings',
component: () => import('@/views/settings/index.vue'),
......
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { Menu, RouteRecord } from '@/types'
import { menuApi } from '@/api/menu'
import { getMenuListApi } from '@/api/menu'
import { useUserStore } from './user'
import router from '@/router'
......@@ -18,7 +18,7 @@ export const usePermissionStore = defineStore('permission', () => {
// 获取菜单列表
const getMenuList = async (): Promise<Menu[]> => {
try {
const response = await menuApi.getMenuTree()
const response: Menu[] = await getMenuListApi()
menuList.value = response
return response
} catch (error) {
......
......@@ -86,33 +86,33 @@
<table class="data-table">
<thead>
<tr>
<th>订单ID</th>
<th class="sortable">订单编号 ↕️</th>
<th>出库ID</th>
<th class="sortable">出库单号 ↕️</th>
<th>经销商编码</th>
<th>经销商名称</th>
<th>订单日期</th>
<th>订单金额</th>
<th>出库日期</th>
<th>出库金额</th>
<th>出库状态</th>
<th>出库时间</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="order in orderList" :key="order.orderId">
<td>{{ order.orderId }}</td>
<td>{{ order.orderNo }}</td>
<td>{{ order.dealerCode }}</td>
<td>{{ order.dealerName }}</td>
<td>{{ formatDate(order.orderDate) }}</td>
<td>{{ formatCurrency(order.totalAmount) }}</td>
<tr v-for="delivery in deliveryList" :key="delivery.deliveryId">
<td>{{ delivery.deliveryId }}</td>
<td>{{ delivery.deliveryNo }}</td>
<td>{{ delivery.dealerCode }}</td>
<td>{{ delivery.dealerName }}</td>
<td>{{ formatDate(delivery.deliveryDate) }}</td>
<td>{{ formatCurrency(delivery.totalAmount) }}</td>
<td>
<span :class="getDeliveryStatusClass(order.deliveryStatus)">
{{ getDeliveryStatusText(order.deliveryStatus) }}
<span :class="getDeliveryStatusClass(delivery.deliveryStatus)">
{{ getDeliveryStatusText(delivery.deliveryStatus) }}
</span>
</td>
<td>{{ formatTime(order.uploadTime || '') }}</td>
<td>{{ formatTime(delivery.createTime || '') }}</td>
<td class="action-col">
<button @click="handleView(order)" class="view-btn">👁️ 查看</button>
<button @click="handleView(delivery)" class="view-btn">👁️ 查看</button>
</td>
</tr>
</tbody>
......@@ -409,13 +409,13 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { orderApi, type OrderInfo, type OrderQueryReq } from '../../api/order'
import { deliveryApi, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery'
// 响应式数据
const loading = ref(false)
const orderList = ref<OrderInfo[]>([])
const deliveryList = ref<DeliveryInfo[]>([])
const showDetailDialog = ref(false)
const orderDetail = ref<OrderInfo | null>(null)
const deliveryDetail = ref<DeliveryInfo | null>(null)
// 新增出库相关数据
const showAddDialog = ref(false)
......@@ -507,21 +507,21 @@ const getPageNumbers = () => {
}
// 方法
const fetchOrders = async () => {
const fetchDeliveries = async () => {
try {
loading.value = true
const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
const response = await orderApi.getOrderList(params) as any
const response = await deliveryApi.getDeliveryList(params) as any
console.log('出库查询API响应:', response)
// 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
if (response && response.records) {
orderList.value = response.records || []
deliveryList.value = response.records || []
pagination.total = response.total || 0
pagination.pageNum = response.current || 1
pagination.pageSize = response.size || 10
} else {
orderList.value = []
deliveryList.value = []
pagination.total = 0
showMessage('获取出库列表失败', 'error')
}
......@@ -535,7 +535,7 @@ const fetchOrders = async () => {
const handleSearch = () => {
pagination.pageNum = 1
fetchOrders()
fetchDeliveries()
}
const handleReset = () => {
......@@ -548,24 +548,24 @@ const handleReset = () => {
endDate: ''
})
pagination.pageNum = 1
fetchOrders()
fetchDeliveries()
}
const handleView = async (order: OrderInfo) => {
const handleView = async (delivery: DeliveryInfo) => {
try {
const response = await orderApi.getOrderDetail(order.orderId) as any
const response = await deliveryApi.getDeliveryDetail(delivery.deliveryId) as any
console.log('出库查询详情API响应:', response)
// 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
if (response && response.orderId) {
orderDetail.value = response
if (response && response.deliveryId) {
deliveryDetail.value = response
showDetailDialog.value = true
} else {
showMessage('获取订单详情失败', 'error')
showMessage('获取出库详情失败', 'error')
}
} catch (error) {
console.error('获取订单详情失败:', error)
showMessage('获取订单详情失败, 请重试', 'error')
console.error('获取出库详情失败:', error)
showMessage('获取出库详情失败, 请重试', 'error')
}
}
......@@ -634,13 +634,13 @@ const handleSubmitAdd = async () => {
const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages.value) {
pagination.pageNum = page
fetchOrders()
fetchDeliveries()
}
}
const handlePageSizeChange = () => {
pagination.pageNum = 1
fetchOrders()
fetchDeliveries()
}
const handleTableSearch = () => {
......@@ -648,7 +648,7 @@ const handleTableSearch = () => {
}
const handleTableRefresh = () => {
fetchOrders()
fetchDeliveries()
}
const handleTableExport = () => {
......@@ -666,7 +666,7 @@ const closeDetailDialog = () => {
// 生命周期
onMounted(() => {
fetchOrders()
fetchDeliveries()
})
</script>
......
......@@ -323,11 +323,11 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { orderApi, type OrderInfo, type OrderQueryReq } from '../../api/order'
import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice'
// 响应式数据
const loading = ref(false)
const orderList = ref<OrderInfo[]>([])
const invoiceList = ref<InvoiceInfo[]>([])
const showDialog = ref(false)
const showDetailDialog = ref(false)
const isEdit = ref(false)
......@@ -424,7 +424,7 @@ const fetchOrders = async () => {
try {
loading.value = true
const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
const response = await orderApi.getOrderList(params) as any
const response = await invoiceApi.getInvoiceList(params) as any
console.log('发票管理API响应:', response)
// 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
......@@ -494,7 +494,7 @@ const handleEdit = (order: OrderInfo) => {
const handleView = async (order: OrderInfo) => {
try {
const response = await orderApi.getOrderDetail(order.orderId) as any
const response = await invoiceApi.getInvoiceDetail(invoice.invoiceId) as any
console.log('发票管理详情API响应:', response)
// 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
......
This diff is collapsed. Click to expand it.