✨ 新增毛利分析报表及提成设置

This commit is contained in:
caojia
2026-10-14 16:57:44 +08:00
parent c0c9c798b3
commit 0087aa3712
17 changed files with 1671 additions and 0 deletions
@@ -0,0 +1,103 @@
package com.bms.web.controller.report;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.bms.common.annotation.Log;
import com.bms.common.core.controller.BaseController;
import com.bms.common.core.domain.AjaxResult;
import com.bms.common.core.page.TableDataInfo;
import com.bms.common.enums.BusinessType;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.domain.PrcProfitCommissionRate;
import com.bms.price.domain.vo.PrcProfitReportOrderRow;
import com.bms.price.domain.vo.PrcProfitReportProductRow;
import com.bms.price.domain.vo.PrcProfitReportQuery;
import com.bms.price.domain.vo.PrcProfitReportSummary;
import com.bms.price.service.IPrcProfitReportService;
/** 产品/订单维度毛利报表。 */
@RestController
@RequestMapping("/report/profit")
public class PrcProfitReportController extends BaseController
{
@Autowired
private IPrcProfitReportService profitReportService;
@PreAuthorize("@ss.hasPermi('report:profit:list')")
@GetMapping("/summary")
public AjaxResult summary(PrcProfitReportQuery query)
{
return success(profitReportService.selectSummary(query));
}
@PreAuthorize("@ss.hasPermi('report:profit:commission:list')")
@GetMapping("/commission-rates")
public AjaxResult commissionRates()
{
return success(profitReportService.selectCommissionRates());
}
@Log(title = "毛利报表-业务员提成比例", businessType = BusinessType.INSERT)
@PreAuthorize("@ss.hasPermi('report:profit:commission:save')")
@PostMapping("/commission-rates")
public AjaxResult saveCommissionRate(@RequestBody PrcProfitCommissionRate rate)
{
profitReportService.saveCommissionRate(rate);
return success();
}
@Log(title = "毛利报表-业务员提成比例", businessType = BusinessType.DELETE)
@PreAuthorize("@ss.hasPermi('report:profit:commission:remove')")
@DeleteMapping("/commission-rates/{rateId}")
public AjaxResult deleteCommissionRate(@PathVariable Long rateId)
{
return toAjax(profitReportService.deleteCommissionRate(rateId));
}
@PreAuthorize("@ss.hasPermi('report:profit:list')")
@GetMapping("/orders")
public TableDataInfo orders(PrcProfitReportQuery query)
{
startPage();
List<PrcProfitReportOrderRow> rows = profitReportService.selectOrderRows(query);
return getDataTable(rows);
}
@PreAuthorize("@ss.hasPermi('report:profit:list')")
@GetMapping("/products")
public TableDataInfo products(PrcProfitReportQuery query)
{
startPage();
List<PrcProfitReportProductRow> rows = profitReportService.selectProductRows(query);
return getDataTable(rows);
}
@Log(title = "毛利报表-订单维度", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('report:profit:export')")
@PostMapping("/orders/export")
public void exportOrders(HttpServletResponse response, PrcProfitReportQuery query)
{
List<PrcProfitReportOrderRow> rows = profitReportService.selectOrderRows(query);
new ExcelUtil<PrcProfitReportOrderRow>(PrcProfitReportOrderRow.class)
.exportExcel(response, rows, "订单毛利报表");
}
@Log(title = "毛利报表-产品维度", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('report:profit:export')")
@PostMapping("/products/export")
public void exportProducts(HttpServletResponse response, PrcProfitReportQuery query)
{
List<PrcProfitReportProductRow> rows = profitReportService.selectProductRows(query);
new ExcelUtil<PrcProfitReportProductRow>(PrcProfitReportProductRow.class)
.exportExcel(response, rows, "产品毛利报表");
}
}
@@ -0,0 +1,27 @@
package com.bms.price.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/** 业务员固定提成比例配置。commissionRate 按百分数存储,例如 5 表示 5%。 */
@Data
public class PrcProfitCommissionRate implements Serializable
{
private static final long serialVersionUID = 1L;
private Long rateId;
private String belongSalerCode;
private String belongSaler;
private BigDecimal commissionRate;
private String status;
private String remark;
private String createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,59 @@
package com.bms.price.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.bms.common.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/** 订单维度毛利报表行。 */
@Data
public class PrcProfitReportOrderRow implements Serializable
{
private static final long serialVersionUID = 1L;
@Excel(name = "订单号", width = 24)
private String orderNo;
@Excel(name = "客户代码", width = 16)
private String customerCode;
@Excel(name = "客户名称", width = 24)
private String customerName;
@Excel(name = "业务员", width = 16)
private String belongSaler;
@Excel(name = "业务员编码", width = 16)
private String belongSalerCode;
@Excel(name = "产品代码", width = 16)
private String productCode;
@Excel(name = "产品名称", width = 24)
private String productName;
@Excel(name = "国家代码", width = 12)
private String countryCode;
@Excel(name = "国家/地区", width = 18)
private String countryName;
@Excel(name = "币种", width = 10)
private String currency;
@Excel(name = "计费时间", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 22)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date bizDate;
@Excel(name = "产品价(目录价)", scale = 4, width = 20)
private BigDecimal costAmount;
@Excel(name = "红线价", scale = 4, width = 16)
private BigDecimal redlineAmount;
@Excel(name = "红线毛利", scale = 4, width = 16)
private BigDecimal redlineGrossProfit;
@Excel(name = "红线毛利率(%)", scale = 4, width = 18)
private BigDecimal redlineGrossMarginRate;
@Excel(name = "销售价", scale = 4, width = 16)
private BigDecimal salesAmount;
@Excel(name = "销售毛利", scale = 4, width = 16)
private BigDecimal salesGrossProfit;
@Excel(name = "销售毛利率(%)", scale = 4, width = 18)
private BigDecimal salesGrossMarginRate;
@Excel(name = "提成比例(%)", scale = 4, width = 16)
private BigDecimal commissionRate;
@Excel(name = "提成金额", scale = 4, width = 16)
private BigDecimal commissionAmount;
@Excel(name = "应收状态", width = 14)
private String arBillStatus;
}
@@ -0,0 +1,46 @@
package com.bms.price.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import com.bms.common.annotation.Excel;
import lombok.Data;
/** 产品+目的地维度毛利汇总行。 */
@Data
public class PrcProfitReportProductRow implements Serializable
{
private static final long serialVersionUID = 1L;
@Excel(name = "产品代码", width = 16)
private String productCode;
@Excel(name = "产品名称", width = 24)
private String productName;
@Excel(name = "国家代码", width = 12)
private String countryCode;
@Excel(name = "国家/地区", width = 18)
private String countryName;
@Excel(name = "币种", width = 10)
private String currency;
@Excel(name = "订单数", width = 12)
private Long orderCount;
@Excel(name = "产品价(目录价)", scale = 4, width = 20)
private BigDecimal costAmount;
@Excel(name = "红线价", scale = 4, width = 16)
private BigDecimal redlineAmount;
@Excel(name = "红线毛利", scale = 4, width = 16)
private BigDecimal redlineGrossProfit;
@Excel(name = "红线毛利率(%)", scale = 4, width = 18)
private BigDecimal redlineGrossMarginRate;
@Excel(name = "销售价", scale = 4, width = 16)
private BigDecimal salesAmount;
@Excel(name = "销售毛利", scale = 4, width = 16)
private BigDecimal salesGrossProfit;
@Excel(name = "销售毛利率(%)", scale = 4, width = 18)
private BigDecimal salesGrossMarginRate;
@Excel(name = "提成金额", scale = 4, width = 16)
private BigDecimal commissionAmount;
@Excel(name = "未计算提成订单数", width = 20)
private Long commissionMissingOrderCount;
@Excel(name = "产品价或价格缺失订单数", width = 22)
private Long incompleteOrderCount;
}
@@ -0,0 +1,38 @@
package com.bms.price.domain.vo;
import java.util.Date;
import java.util.List;
import org.springframework.format.annotation.DateTimeFormat;
import com.bms.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/** 毛利报表查询条件。 */
@Data
@EqualsAndHashCode(callSuper = true)
public class PrcProfitReportQuery extends BaseEntity
{
private static final long serialVersionUID = 1L;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date beginBizDate;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endBizDate;
private String orderNo;
/** 订单号拆分后的多值查询条件。 */
private List<String> orderNos;
private String customerCode;
/** 客户代码拆分后的多值查询条件。 */
private List<String> customerCodes;
private String customerName;
/** 客户名称拆分后的多值查询条件。 */
private List<String> customerNames;
private String belongSalerCode;
private String productCode;
private String countryCode;
private String currency;
/** 是否按产品维度分组值下钻订单明细,允许分组字段为空。 */
private Boolean productDrilldown;
/** 订单明细弹窗的筛选类型:ALL、COMMISSION_MISSING、INCOMPLETE。 */
private String detailType;
}
@@ -0,0 +1,25 @@
package com.bms.price.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
/** 毛利报表汇总指标。 */
@Data
public class PrcProfitReportSummary implements Serializable
{
private static final long serialVersionUID = 1L;
private Long orderCount;
private Long incompleteOrderCount;
private String currency;
private BigDecimal costAmount;
private BigDecimal redlineAmount;
private BigDecimal redlineGrossProfit;
private BigDecimal redlineGrossMarginRate;
private BigDecimal salesAmount;
private BigDecimal salesGrossProfit;
private BigDecimal salesGrossMarginRate;
private BigDecimal commissionAmount;
private Long commissionMissingOrderCount;
}
@@ -0,0 +1,13 @@
package com.bms.price.mapper;
import java.util.List;
import com.bms.price.domain.PrcProfitCommissionRate;
public interface PrcProfitCommissionRateMapper
{
List<PrcProfitCommissionRate> selectAll();
int upsert(PrcProfitCommissionRate rate);
int deleteById(Long rateId);
}
@@ -0,0 +1,17 @@
package com.bms.price.mapper;
import java.util.List;
import com.bms.price.domain.vo.PrcProfitReportOrderRow;
import com.bms.price.domain.vo.PrcProfitReportProductRow;
import com.bms.price.domain.vo.PrcProfitReportQuery;
import com.bms.price.domain.vo.PrcProfitReportSummary;
/** 毛利报表 mapper;金额事实取自 prc_income_bill。 */
public interface PrcProfitReportMapper
{
List<PrcProfitReportOrderRow> selectOrderRows(PrcProfitReportQuery query);
List<PrcProfitReportProductRow> selectProductRows(PrcProfitReportQuery query);
List<PrcProfitReportSummary> selectSummary(PrcProfitReportQuery query);
}
@@ -0,0 +1,24 @@
package com.bms.price.service;
import java.util.List;
import com.bms.price.domain.PrcProfitCommissionRate;
import com.bms.price.domain.vo.PrcProfitReportOrderRow;
import com.bms.price.domain.vo.PrcProfitReportProductRow;
import com.bms.price.domain.vo.PrcProfitReportQuery;
import com.bms.price.domain.vo.PrcProfitReportSummary;
/** 毛利报表服务。 */
public interface IPrcProfitReportService
{
List<PrcProfitReportOrderRow> selectOrderRows(PrcProfitReportQuery query);
List<PrcProfitReportProductRow> selectProductRows(PrcProfitReportQuery query);
List<PrcProfitReportSummary> selectSummary(PrcProfitReportQuery query);
List<PrcProfitCommissionRate> selectCommissionRates();
void saveCommissionRate(PrcProfitCommissionRate rate);
int deleteCommissionRate(Long rateId);
}
@@ -0,0 +1,139 @@
package com.bms.price.service.impl;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.annotation.DataScope;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.SecurityUtils;
import com.bms.price.domain.PrcProfitCommissionRate;
import com.bms.price.mapper.PrcProfitCommissionRateMapper;
import com.bms.price.domain.vo.PrcProfitReportOrderRow;
import com.bms.price.domain.vo.PrcProfitReportProductRow;
import com.bms.price.domain.vo.PrcProfitReportQuery;
import com.bms.price.domain.vo.PrcProfitReportSummary;
import com.bms.price.mapper.PrcProfitReportMapper;
import com.bms.price.service.IPrcProfitReportService;
/** 毛利报表:以 prc_income_bill 为金额事实表,不关联应付模块。 */
@Service
public class PrcProfitReportServiceImpl implements IPrcProfitReportService
{
private static final int MULTI_VALUE_FILTER_LIMIT = 2000;
@Autowired
private PrcProfitReportMapper profitReportMapper;
@Autowired
private PrcProfitCommissionRateMapper commissionRateMapper;
@Override
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "report:profit:list")
public List<PrcProfitReportOrderRow> selectOrderRows(PrcProfitReportQuery query)
{
normalizeMultiValueFilters(query);
return profitReportMapper.selectOrderRows(query);
}
@Override
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "report:profit:list")
public List<PrcProfitReportProductRow> selectProductRows(PrcProfitReportQuery query)
{
normalizeMultiValueFilters(query);
return profitReportMapper.selectProductRows(query);
}
@Override
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "report:profit:list")
public List<PrcProfitReportSummary> selectSummary(PrcProfitReportQuery query)
{
normalizeMultiValueFilters(query);
return profitReportMapper.selectSummary(query);
}
/** 将报表的多行输入拆分为去重后的查询条件,支持换行、空格、逗号和分号分隔。 */
private void normalizeMultiValueFilters(PrcProfitReportQuery query)
{
if (query == null)
{
return;
}
query.setOrderNos(splitFilterValues(query.getOrderNo(), "订单号"));
query.setCustomerCodes(splitFilterValues(query.getCustomerCode(), "客户代码"));
query.setCustomerNames(splitFilterValues(query.getCustomerName(), "客户名称"));
}
private List<String> splitFilterValues(String rawValue, String fieldName)
{
LinkedHashSet<String> values = new LinkedHashSet<>();
if (rawValue != null && !rawValue.trim().isEmpty())
{
for (String item : rawValue.split("[\\s,,;;]+"))
{
String value = item.trim();
if (!value.isEmpty())
{
values.add(value);
}
}
}
if (values.size() > MULTI_VALUE_FILTER_LIMIT)
{
throw new ServiceException(fieldName + "最多输入 " + MULTI_VALUE_FILTER_LIMIT + " 个不重复值");
}
return new ArrayList<>(values);
}
@Override
public List<PrcProfitCommissionRate> selectCommissionRates()
{
return commissionRateMapper.selectAll();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveCommissionRate(PrcProfitCommissionRate rate)
{
if (rate == null || rate.getBelongSalerCode() == null || rate.getBelongSalerCode().trim().isEmpty())
{
throw new ServiceException("业务员编码不能为空");
}
BigDecimal commissionRate = rate.getCommissionRate();
if (commissionRate == null || commissionRate.compareTo(BigDecimal.ZERO) < 0
|| commissionRate.compareTo(new BigDecimal("100")) > 0)
{
throw new ServiceException("提成比例必须在 0 至 100 之间");
}
String status = rate.getStatus();
if (!"0".equals(status) && !"1".equals(status))
{
throw new ServiceException("提成比例状态不正确");
}
String salerCode = rate.getBelongSalerCode().trim();
String salerName = rate.getBelongSaler() == null ? null : rate.getBelongSaler().trim();
if (salerCode.length() > 64 || (salerName != null && salerName.length() > 64)
|| (rate.getRemark() != null && rate.getRemark().length() > 500))
{
throw new ServiceException("业务员编码、名称或备注超过最大长度");
}
rate.setBelongSalerCode(salerCode);
rate.setBelongSaler(salerName);
rate.setCreateBy(SecurityUtils.getUsername());
commissionRateMapper.upsert(rate);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteCommissionRate(Long rateId)
{
if (rateId == null)
{
throw new ServiceException("提成比例配置ID不能为空");
}
return commissionRateMapper.deleteById(rateId);
}
}
@@ -0,0 +1,44 @@
<?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.bms.price.mapper.PrcProfitCommissionRateMapper">
<resultMap type="PrcProfitCommissionRate" id="PrcProfitCommissionRateResult">
<id property="rateId" column="rate_id"/>
<result property="belongSalerCode" column="belong_saler_code"/>
<result property="belongSaler" column="belong_saler"/>
<result property="commissionRate" column="commission_rate"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<select id="selectAll" resultMap="PrcProfitCommissionRateResult">
select rate_id, belong_saler_code, belong_saler, commission_rate, status,
remark, create_by, create_time, update_by, update_time
from prc_profit_commission_rate
order by belong_saler_code
</select>
<insert id="upsert" parameterType="PrcProfitCommissionRate">
insert into prc_profit_commission_rate (
belong_saler_code, belong_saler, commission_rate, status, remark, create_by, create_time
) values (
#{belongSalerCode}, #{belongSaler}, #{commissionRate}, #{status}, #{remark}, #{createBy}, sysdate()
)
on duplicate key update
belong_saler = values(belong_saler),
commission_rate = values(commission_rate),
status = values(status),
remark = values(remark),
update_by = values(create_by),
update_time = sysdate()
</insert>
<delete id="deleteById" parameterType="Long">
delete from prc_profit_commission_rate where rate_id = #{rateId}
</delete>
</mapper>
@@ -0,0 +1,276 @@
<?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.bms.price.mapper.PrcProfitReportMapper">
<resultMap type="PrcProfitReportOrderRow" id="PrcProfitReportOrderResult">
<result property="orderNo" column="order_no"/>
<result property="customerCode" column="customer_code"/>
<result property="customerName" column="customer_name"/>
<result property="belongSaler" column="belong_saler"/>
<result property="belongSalerCode" column="belong_saler_code"/>
<result property="productCode" column="product_code"/>
<result property="productName" column="product_name"/>
<result property="countryCode" column="country_code"/>
<result property="countryName" column="country_name"/>
<result property="currency" column="currency"/>
<result property="bizDate" column="biz_date"/>
<result property="costAmount" column="cost_amount"/>
<result property="redlineAmount" column="redline_amount"/>
<result property="redlineGrossProfit" column="redline_gross_profit"/>
<result property="redlineGrossMarginRate" column="redline_gross_margin_rate"/>
<result property="salesAmount" column="sales_amount"/>
<result property="salesGrossProfit" column="sales_gross_profit"/>
<result property="salesGrossMarginRate" column="sales_gross_margin_rate"/>
<result property="commissionRate" column="commission_rate"/>
<result property="commissionAmount" column="commission_amount"/>
<result property="arBillStatus" column="ar_bill_status"/>
</resultMap>
<resultMap type="PrcProfitReportProductRow" id="PrcProfitReportProductResult">
<result property="productCode" column="product_code"/>
<result property="productName" column="product_name"/>
<result property="countryCode" column="country_code"/>
<result property="countryName" column="country_name"/>
<result property="currency" column="currency"/>
<result property="orderCount" column="order_count"/>
<result property="costAmount" column="cost_amount"/>
<result property="redlineAmount" column="redline_amount"/>
<result property="redlineGrossProfit" column="redline_gross_profit"/>
<result property="redlineGrossMarginRate" column="redline_gross_margin_rate"/>
<result property="salesAmount" column="sales_amount"/>
<result property="salesGrossProfit" column="sales_gross_profit"/>
<result property="salesGrossMarginRate" column="sales_gross_margin_rate"/>
<result property="commissionAmount" column="commission_amount"/>
<result property="commissionMissingOrderCount" column="commission_missing_order_count"/>
<result property="incompleteOrderCount" column="incomplete_order_count"/>
</resultMap>
<resultMap type="PrcProfitReportSummary" id="PrcProfitReportSummaryResult">
<result property="orderCount" column="order_count"/>
<result property="incompleteOrderCount" column="incomplete_order_count"/>
<result property="currency" column="currency"/>
<result property="costAmount" column="cost_amount"/>
<result property="redlineAmount" column="redline_amount"/>
<result property="redlineGrossProfit" column="redline_gross_profit"/>
<result property="redlineGrossMarginRate" column="redline_gross_margin_rate"/>
<result property="salesAmount" column="sales_amount"/>
<result property="salesGrossProfit" column="sales_gross_profit"/>
<result property="salesGrossMarginRate" column="sales_gross_margin_rate"/>
<result property="commissionAmount" column="commission_amount"/>
<result property="commissionMissingOrderCount" column="commission_missing_order_count"/>
</resultMap>
<sql id="profitReportFromAndWhere">
from prc_income_bill i
inner join prc_order_bill p on p.bill_id = i.bill_id
left join prc_profit_commission_rate cr
on cr.belong_saler_code = i.belong_saler_code and cr.status = '0'
<where>
<if test="beginBizDate != null">
and p.biz_date &gt;= #{beginBizDate}
</if>
<if test="endBizDate != null">
and p.biz_date &lt;= #{endBizDate}
</if>
<choose>
<when test="orderNos != null and orderNos.size() > 0">
and (
<foreach collection="orderNos" item="orderValue" separator=" or ">
p.order_no like concat('%', #{orderValue}, '%')
</foreach>
)
</when>
<when test="orderNo != null and orderNo != ''">
and p.order_no like concat('%', #{orderNo}, '%')
</when>
</choose>
<choose>
<when test="customerCodes != null and customerCodes.size() > 0">
and p.customer_code in
<foreach collection="customerCodes" item="customerValue" open="(" separator="," close=")">
#{customerValue}
</foreach>
</when>
<when test="customerCode != null and customerCode != ''">
and p.customer_code = #{customerCode}
</when>
</choose>
<choose>
<when test="customerNames != null and customerNames.size() > 0">
and (
<foreach collection="customerNames" item="customerValue" separator=" or ">
p.customer_name like concat('%', #{customerValue}, '%')
</foreach>
)
</when>
<when test="customerName != null and customerName != ''">
and p.customer_name like concat('%', #{customerName}, '%')
</when>
</choose>
<choose>
<when test="productDrilldown != null and productDrilldown">
and p.product_code &lt;=&gt; #{productCode}
and p.country_code &lt;=&gt; #{countryCode}
and i.currency &lt;=&gt; #{currency}
<if test="belongSalerCode != null and belongSalerCode != ''">
and i.belong_saler_code = #{belongSalerCode}
</if>
</when>
<otherwise>
<if test="belongSalerCode != null and belongSalerCode != ''">
and i.belong_saler_code = #{belongSalerCode}
</if>
<if test="productCode != null and productCode != ''">
and p.product_code = #{productCode}
</if>
<if test="countryCode != null and countryCode != ''">
and p.country_code = #{countryCode}
</if>
<if test="currency != null and currency != ''">
and i.currency = #{currency}
</if>
</otherwise>
</choose>
<choose>
<when test="detailType == 'COMMISSION_MISSING'">
and (cr.commission_rate is null or i.product_amount is null or i.sales_amount is null)
</when>
<when test="detailType == 'INCOMPLETE'">
and (i.product_amount is null or i.redline_amount is null or i.sales_amount is null)
</when>
</choose>
<if test="params != null and params.dataScope != null and params.dataScope != ''">
and exists (
select 1
from sys_user su
where su.del_flag = '0'
and (
(p.belong_saler_code is not null and p.belong_saler_code != ''
and (su.user_name = p.belong_saler_code collate utf8mb4_general_ci
or su.user_id = p.belong_saler_code + 0))
or
(p.settlement_man_code is not null and p.settlement_man_code != ''
and (su.user_name = p.settlement_man_code collate utf8mb4_general_ci
or su.user_id = p.settlement_man_code + 0))
)
${params.dataScope}
)
</if>
</where>
</sql>
<sql id="profitExpressions">
i.product_amount as cost_amount,
i.redline_amount,
case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount - i.product_amount else null end as redline_gross_profit,
case when i.product_amount is not null and i.redline_amount is not null and i.redline_amount != 0
then round((i.redline_amount - i.product_amount) * 100 / i.redline_amount, 4) else null end as redline_gross_margin_rate,
i.sales_amount,
case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount - i.product_amount else null end as sales_gross_profit,
case when i.product_amount is not null and i.sales_amount is not null and i.sales_amount != 0
then round((i.sales_amount - i.product_amount) * 100 / i.sales_amount, 4) else null end as sales_gross_margin_rate,
cr.commission_rate,
case when cr.commission_rate is not null and i.product_amount is not null and i.sales_amount is not null
then round((i.sales_amount - i.product_amount) * cr.commission_rate / 100, 4) else null end as commission_amount
</sql>
<select id="selectOrderRows" resultMap="PrcProfitReportOrderResult">
select p.order_no, p.customer_code, p.customer_name,
i.belong_saler, i.belong_saler_code,
p.product_code, p.product_name, p.country_code, p.country_name,
i.currency, p.biz_date,
<include refid="profitExpressions"/>,
p.ar_bill_status
<include refid="profitReportFromAndWhere"/>
order by p.biz_date desc, p.bill_id desc
</select>
<select id="selectProductRows" resultMap="PrcProfitReportProductResult">
select p.product_code, p.product_name, p.country_code, p.country_name,
i.currency, count(*) as order_count,
sum(i.product_amount) as cost_amount,
sum(i.redline_amount) as redline_amount,
sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount - i.product_amount else null end) as redline_gross_profit,
case when sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount else null end) is null
or sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount else null end) = 0
then null
else round(
sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount - i.product_amount else null end) * 100 /
sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount else null end), 4)
end as redline_gross_margin_rate,
sum(i.sales_amount) as sales_amount,
sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount - i.product_amount else null end) as sales_gross_profit,
case when sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount else null end) is null
or sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount else null end) = 0
then null
else round(
sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount - i.product_amount else null end) * 100 /
sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount else null end), 4)
end as sales_gross_margin_rate,
sum(case when cr.commission_rate is not null and i.product_amount is not null and i.sales_amount is not null
then (i.sales_amount - i.product_amount) * cr.commission_rate / 100 else null end) as commission_amount,
sum(case when cr.commission_rate is null or i.product_amount is null or i.sales_amount is null
then 1 else 0 end) as commission_missing_order_count,
sum(case when i.product_amount is null or i.redline_amount is null or i.sales_amount is null
then 1 else 0 end) as incomplete_order_count
<include refid="profitReportFromAndWhere"/>
group by p.product_code, p.product_name, p.country_code, p.country_name, i.currency
order by p.product_code, p.country_code, i.currency
</select>
<select id="selectSummary" resultMap="PrcProfitReportSummaryResult">
select i.currency, count(*) as order_count,
sum(case when i.product_amount is null or i.redline_amount is null or i.sales_amount is null
then 1 else 0 end) as incomplete_order_count,
sum(i.product_amount) as cost_amount,
sum(i.redline_amount) as redline_amount,
sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount - i.product_amount else null end) as redline_gross_profit,
case when sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount else null end) is null
or sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount else null end) = 0
then null
else round(
sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount - i.product_amount else null end) * 100 /
sum(case when i.product_amount is not null and i.redline_amount is not null
then i.redline_amount else null end), 4)
end as redline_gross_margin_rate,
sum(i.sales_amount) as sales_amount,
sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount - i.product_amount else null end) as sales_gross_profit,
case when sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount else null end) is null
or sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount else null end) = 0
then null
else round(
sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount - i.product_amount else null end) * 100 /
sum(case when i.product_amount is not null and i.sales_amount is not null
then i.sales_amount else null end), 4)
end as sales_gross_margin_rate,
sum(case when cr.commission_rate is not null and i.product_amount is not null and i.sales_amount is not null
then (i.sales_amount - i.product_amount) * cr.commission_rate / 100 else null end) as commission_amount,
sum(case when cr.commission_rate is null or i.product_amount is null or i.sales_amount is null
then 1 else 0 end) as commission_missing_order_count
<include refid="profitReportFromAndWhere"/>
group by i.currency
order by i.currency
</select>
</mapper>
+20
View File
@@ -956,6 +956,25 @@ CREATE TABLE `prc_income_bill` (
CONSTRAINT `fk_income_bill_order_bill` FOREIGN KEY (`bill_id`) REFERENCES `prc_order_bill` (`bill_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收主表(订单计费与应收费用中间层,与订单一对一)';
-- ----------------------------
-- 表结构:`prc_profit_commission_rate`
-- ----------------------------
CREATE TABLE `prc_profit_commission_rate` (
`rate_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '提成比例ID',
`belong_saler_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务员编码',
`belong_saler` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务员名称',
`commission_rate` decimal(7,4) NOT NULL COMMENT '提成比例(百分数,例如5表示5%)',
`status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '状态:0正常 1停用',
`remark` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`rate_id`),
UNIQUE KEY `uk_profit_commission_saler` (`belong_saler_code`),
KEY `idx_profit_commission_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='毛利报表业务员固定提成比例';
-- ----------------------------
-- 表结构:`prc_income_bill_fee`
-- ----------------------------
@@ -1062,6 +1081,7 @@ CREATE TABLE `prc_order_bill` (
UNIQUE KEY `uk_bill_order` (`order_no`),
UNIQUE KEY `uk_bill_key` (`bill_key`),
KEY `idx_bill_customer` (`customer_code`,`biz_date`),
KEY `idx_bill_biz_date_report` (`biz_date`,`bill_id`),
KEY `idx_bill_product` (`product_code`,`country_code`),
KEY `idx_bill_ar_status` (`customer_id`,`currency`,`ar_bill_status`,`biz_date`),
KEY `idx_bill_ar_async_cursor` (`customer_id`,`currency`,`ar_bill_status`,`create_time`,`bill_id`),
+30
View File
@@ -0,0 +1,30 @@
-- 毛利报表:提成比例配置表与计费日期查询索引。
-- 每位业务员维护一条固定比例;提成比例以百分数保存,例如 5 表示 5%。
CREATE TABLE IF NOT EXISTS `prc_profit_commission_rate` (
`rate_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '提成比例ID',
`belong_saler_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务员编码',
`belong_saler` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务员名称',
`commission_rate` decimal(7,4) NOT NULL COMMENT '提成比例(百分数,例如5表示5%)',
`status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '状态:0正常 1停用',
`remark` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`rate_id`),
UNIQUE KEY `uk_profit_commission_saler` (`belong_saler_code`),
KEY `idx_profit_commission_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='毛利报表业务员固定提成比例';
SET @index_exists := (
SELECT COUNT(1) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill'
AND INDEX_NAME = 'idx_bill_biz_date_report'
);
SET @sql := IF(@index_exists = 0,
'ALTER TABLE `prc_order_bill` ADD INDEX `idx_bill_biz_date_report` (`biz_date`, `bill_id`)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
+56
View File
@@ -0,0 +1,56 @@
-- 报表中心 - 毛利分析及提成比例维护权限
SET NAMES utf8mb4;
SET @report_menu_id := (
SELECT menu_id FROM sys_menu
WHERE parent_id = 0 AND menu_type = 'M' AND menu_name = N'报表中心'
ORDER BY menu_id LIMIT 1
);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type,
visible, status, perms, icon, create_by, create_time, remark)
SELECT N'报表中心', 0, 8, 'report', NULL, 1, 0, 'M', '0', '0', '', 'chart', 'admin', sysdate(), N'经营分析报表'
FROM DUAL WHERE @report_menu_id IS NULL;
SET @report_menu_id := (
SELECT menu_id FROM sys_menu
WHERE parent_id = 0 AND menu_type = 'M' AND menu_name = N'报表中心'
ORDER BY menu_id LIMIT 1
);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache,
menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT N'毛利分析', @report_menu_id, 1, 'profit', 'report/profit/index', '', 'ProfitReport', 1, 0,
'C', '0', '0', 'report:profit:list', 'chart', 'admin', sysdate(), N'按产品和订单查看红线/销售毛利与提成'
FROM DUAL
WHERE @report_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'report:profit:list');
SET @profit_menu_id := (SELECT menu_id FROM sys_menu WHERE perms = 'report:profit:list' ORDER BY menu_id LIMIT 1);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type,
visible, status, perms, icon, create_by, create_time)
SELECT N'导出', @profit_menu_id, 1, '', NULL, 1, 0, 'F', '0', '0', 'report:profit:export', '#', 'admin', sysdate()
FROM DUAL WHERE @profit_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'report:profit:export');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type,
visible, status, perms, icon, create_by, create_time)
SELECT N'提成比例查询', @profit_menu_id, 2, '', NULL, 1, 0, 'F', '0', '0', 'report:profit:commission:list', '#', 'admin', sysdate()
FROM DUAL WHERE @profit_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'report:profit:commission:list');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type,
visible, status, perms, icon, create_by, create_time)
SELECT N'提成比例维护', @profit_menu_id, 3, '', NULL, 1, 0, 'F', '0', '0', 'report:profit:commission:save', '#', 'admin', sysdate()
FROM DUAL WHERE @profit_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'report:profit:commission:save');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type,
visible, status, perms, icon, create_by, create_time)
SELECT N'提成比例删除', @profit_menu_id, 4, '', NULL, 1, 0, 'F', '0', '0', 'report:profit:commission:remove', '#', 'admin', sysdate()
FROM DUAL WHERE @profit_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'report:profit:commission:remove');
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, m.menu_id
FROM sys_menu m
WHERE (m.menu_id = @report_menu_id OR m.menu_id = @profit_menu_id OR m.parent_id = @profit_menu_id)
AND NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id);