diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/report/PrcProfitReportController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/report/PrcProfitReportController.java new file mode 100644 index 00000000..aaa28ea3 --- /dev/null +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/report/PrcProfitReportController.java @@ -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 rows = profitReportService.selectOrderRows(query); + return getDataTable(rows); + } + + @PreAuthorize("@ss.hasPermi('report:profit:list')") + @GetMapping("/products") + public TableDataInfo products(PrcProfitReportQuery query) + { + startPage(); + List 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 rows = profitReportService.selectOrderRows(query); + new ExcelUtil(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 rows = profitReportService.selectProductRows(query); + new ExcelUtil(PrcProfitReportProductRow.class) + .exportExcel(response, rows, "产品毛利报表"); + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcProfitCommissionRate.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcProfitCommissionRate.java new file mode 100644 index 00000000..9744dcb8 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcProfitCommissionRate.java @@ -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; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportOrderRow.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportOrderRow.java new file mode 100644 index 00000000..9a80b6c9 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportOrderRow.java @@ -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; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportProductRow.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportProductRow.java new file mode 100644 index 00000000..03b3e454 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportProductRow.java @@ -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; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportQuery.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportQuery.java new file mode 100644 index 00000000..bba53666 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportQuery.java @@ -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 orderNos; + private String customerCode; + /** 客户代码拆分后的多值查询条件。 */ + private List customerCodes; + private String customerName; + /** 客户名称拆分后的多值查询条件。 */ + private List customerNames; + private String belongSalerCode; + private String productCode; + private String countryCode; + private String currency; + /** 是否按产品维度分组值下钻订单明细,允许分组字段为空。 */ + private Boolean productDrilldown; + /** 订单明细弹窗的筛选类型:ALL、COMMISSION_MISSING、INCOMPLETE。 */ + private String detailType; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportSummary.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportSummary.java new file mode 100644 index 00000000..337ec0e4 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/PrcProfitReportSummary.java @@ -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; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcProfitCommissionRateMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcProfitCommissionRateMapper.java new file mode 100644 index 00000000..38c91f98 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcProfitCommissionRateMapper.java @@ -0,0 +1,13 @@ +package com.bms.price.mapper; + +import java.util.List; +import com.bms.price.domain.PrcProfitCommissionRate; + +public interface PrcProfitCommissionRateMapper +{ + List selectAll(); + + int upsert(PrcProfitCommissionRate rate); + + int deleteById(Long rateId); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcProfitReportMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcProfitReportMapper.java new file mode 100644 index 00000000..c9137889 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcProfitReportMapper.java @@ -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 selectOrderRows(PrcProfitReportQuery query); + + List selectProductRows(PrcProfitReportQuery query); + + List selectSummary(PrcProfitReportQuery query); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcProfitReportService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcProfitReportService.java new file mode 100644 index 00000000..3877c9ac --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcProfitReportService.java @@ -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 selectOrderRows(PrcProfitReportQuery query); + + List selectProductRows(PrcProfitReportQuery query); + + List selectSummary(PrcProfitReportQuery query); + + List selectCommissionRates(); + + void saveCommissionRate(PrcProfitCommissionRate rate); + + int deleteCommissionRate(Long rateId); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcProfitReportServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcProfitReportServiceImpl.java new file mode 100644 index 00000000..dbb317a1 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcProfitReportServiceImpl.java @@ -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 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 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 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 splitFilterValues(String rawValue, String fieldName) + { + LinkedHashSet 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 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); + } +} diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcProfitCommissionRateMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcProfitCommissionRateMapper.xml new file mode 100644 index 00000000..ab53881c --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcProfitCommissionRateMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + 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() + + + + delete from prc_profit_commission_rate where rate_id = #{rateId} + + diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcProfitReportMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcProfitReportMapper.xml new file mode 100644 index 00000000..5db9c8ca --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcProfitReportMapper.xml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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' + + + and p.biz_date >= #{beginBizDate} + + + and p.biz_date <= #{endBizDate} + + + + and ( + + p.order_no like concat('%', #{orderValue}, '%') + + ) + + + and p.order_no like concat('%', #{orderNo}, '%') + + + + + and p.customer_code in + + #{customerValue} + + + + and p.customer_code = #{customerCode} + + + + + and ( + + p.customer_name like concat('%', #{customerValue}, '%') + + ) + + + and p.customer_name like concat('%', #{customerName}, '%') + + + + + and p.product_code <=> #{productCode} + and p.country_code <=> #{countryCode} + and i.currency <=> #{currency} + + and i.belong_saler_code = #{belongSalerCode} + + + + + and i.belong_saler_code = #{belongSalerCode} + + + and p.product_code = #{productCode} + + + and p.country_code = #{countryCode} + + + and i.currency = #{currency} + + + + + + and (cr.commission_rate is null or i.product_amount is null or i.sales_amount is null) + + + and (i.product_amount is null or i.redline_amount is null or i.sales_amount is null) + + + + 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} + ) + + + + + + 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 + + + + + + + + diff --git a/bms-back/sql/bms_init.sql b/bms-back/sql/bms_init.sql index fe4325a6..cad525e2 100644 --- a/bms-back/sql/bms_init.sql +++ b/bms-back/sql/bms_init.sql @@ -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`), diff --git a/bms-back/sql/price/profit_report.sql b/bms-back/sql/price/profit_report.sql new file mode 100644 index 00000000..214568a2 --- /dev/null +++ b/bms-back/sql/price/profit_report.sql @@ -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; diff --git a/bms-back/sql/price/profit_report_menu.sql b/bms-back/sql/price/profit_report_menu.sql new file mode 100644 index 00000000..64cfe450 --- /dev/null +++ b/bms-back/sql/price/profit_report_menu.sql @@ -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); diff --git a/bms-front/src/api/report/profit.js b/bms-front/src/api/report/profit.js new file mode 100644 index 00000000..efcee09d --- /dev/null +++ b/bms-front/src/api/report/profit.js @@ -0,0 +1,55 @@ +import request, { download } from '@/utils/request' + +export function listProfitSummary(params) { + return request({ + url: '/report/profit/summary', + method: 'get', + params + }) +} + +export function listProfitOrders(params) { + return request({ + url: '/report/profit/orders', + method: 'get', + params + }) +} + +export function listProfitProducts(params) { + return request({ + url: '/report/profit/products', + method: 'get', + params + }) +} + +export function exportProfitOrders(params) { + return download('/report/profit/orders/export', params, '订单毛利报表.xlsx') +} + +export function exportProfitProducts(params) { + return download('/report/profit/products/export', params, '产品毛利报表.xlsx') +} + +export function listProfitCommissionRates() { + return request({ + url: '/report/profit/commission-rates', + method: 'get' + }) +} + +export function saveProfitCommissionRate(data) { + return request({ + url: '/report/profit/commission-rates', + method: 'post', + data + }) +} + +export function deleteProfitCommissionRate(rateId) { + return request({ + url: '/report/profit/commission-rates/' + rateId, + method: 'delete' + }) +} diff --git a/bms-front/src/views/report/profit/index.vue b/bms-front/src/views/report/profit/index.vue new file mode 100644 index 00000000..f18f4b78 --- /dev/null +++ b/bms-front/src/views/report/profit/index.vue @@ -0,0 +1,699 @@ + + + + +