✨ 按客户区域计算毛利并完善汇总展示

This commit is contained in:
caojia
2026-11-05 15:41:20 +08:00
parent 891fdea7d3
commit 25801fe42f
9 changed files with 248 additions and 77 deletions
@@ -32,6 +32,7 @@ import com.bms.common.utils.uuid.IdUtils;
import com.bms.common.utils.poi.ExcelUtil;
// 提成功能暂停使用:import com.bms.price.domain.PrcProfitCommissionRate;
import com.bms.price.domain.PrcProfitReportExport;
import com.bms.price.enums.ProfitAreaRule;
import com.bms.price.domain.vo.PrcProfitReportOrderRow;
import com.bms.price.domain.vo.PrcProfitReportProductRow;
import com.bms.price.domain.vo.PrcProfitReportQuery;
@@ -58,6 +59,14 @@ public class PrcProfitReportController extends BaseController
return getDataTable(profitReportService.selectCustomerOptions(keywords));
}
/** 客户区域字典值与毛利规则的映射。 */
@PreAuthorize("@ss.hasPermi('report:profit:list')")
@GetMapping("/area-rules")
public AjaxResult areaRules()
{
return success(profitReportService.selectAreaRules());
}
@PreAuthorize("@ss.hasPermi('report:profit:list')")
@GetMapping("/summary")
public AjaxResult summary(PrcProfitReportQuery query)
@@ -118,7 +127,7 @@ public class PrcProfitReportController extends BaseController
try
{
List<PrcProfitReportOrderRow> rows = profitReportService.selectOrderRows(query);
return finishExport(export, rows, PrcProfitReportOrderRow.class, "订单毛利报表");
return finishExport(export, rows, PrcProfitReportOrderRow.class, "订单毛利报表", query.getProfitRule());
}
catch (RuntimeException e)
{
@@ -136,7 +145,7 @@ public class PrcProfitReportController extends BaseController
try
{
List<PrcProfitReportProductRow> rows = profitReportService.selectProductRows(query);
return finishExport(export, rows, PrcProfitReportProductRow.class, "产品毛利报表");
return finishExport(export, rows, PrcProfitReportProductRow.class, "产品毛利报表", query.getProfitRule());
}
catch (RuntimeException e)
{
@@ -200,7 +209,8 @@ public class PrcProfitReportController extends BaseController
return export;
}
private <T> AjaxResult finishExport(PrcProfitReportExport export, List<T> rows, Class<T> rowType, String sheetName)
private <T> AjaxResult finishExport(PrcProfitReportExport export, List<T> rows, Class<T> rowType, String sheetName,
String profitRule)
{
Path temp = null;
try
@@ -208,7 +218,12 @@ public class PrcProfitReportController extends BaseController
temp = Files.createTempFile("profit-report-", ".xlsx");
try (OutputStream output = Files.newOutputStream(temp))
{
new ExcelUtil<T>(rowType).exportExcelToStream(rows, sheetName, output);
ExcelUtil<T> excelUtil = new ExcelUtil<>(rowType);
if (ProfitAreaRule.GUANGZHOU.name().equals(profitRule))
{
excelUtil.hideColumn("costAmount", "redlineGrossProfit", "redlineGrossMarginRate");
}
excelUtil.exportExcelToStream(rows, sheetName, output);
}
String objectKey = "report/profit/" + DateUtils.datePath() + "/" + IdUtils.fastSimpleUUID() + ".xlsx";
export.setStorageKey(ossStorage.upload(temp, objectKey,
@@ -0,0 +1,53 @@
package com.bms.report;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import com.bms.common.exception.ServiceException;
import com.bms.price.domain.vo.PrcProfitReportQuery;
import com.bms.price.mapper.PrcProfitReportMapper;
import com.bms.price.service.impl.PrcProfitReportServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
class ProfitAreaRuleTest
{
@Test
void reportQueriesResolveRuleOnServerAndRejectUnknownAreas()
{
AtomicInteger calls = new AtomicInteger();
PrcProfitReportMapper mapper = (PrcProfitReportMapper) Proxy.newProxyInstance(
getClass().getClassLoader(), new Class<?>[] { PrcProfitReportMapper.class },
(proxy, method, args) -> {
calls.incrementAndGet();
return Collections.emptyList();
});
PrcProfitReportServiceImpl service = new PrcProfitReportServiceImpl();
ReflectionTestUtils.setField(service, "profitReportMapper", mapper);
PrcProfitReportQuery query = new PrcProfitReportQuery();
query.setCustomerArea(" GZ ");
query.setProfitRule("SHENZHEN");
service.selectSummary(query);
assertEquals("GZ", query.getCustomerArea());
assertEquals("GUANGZHOU", query.getProfitRule());
query.setCustomerArea("C");
query.setProfitRule("GUANGZHOU");
service.selectOrderRows(query);
assertEquals("SHENZHEN", query.getProfitRule());
service.selectProductRows(query);
assertEquals(3, calls.get());
query.setCustomerArea("UNKNOWN");
assertThrows(ServiceException.class, () -> service.selectProductRows(query));
query.setCustomerArea("");
assertThrows(ServiceException.class, () -> service.selectSummary(query));
assertEquals(3, calls.get());
assertEquals("SHENZHEN", service.selectAreaRules().get("C"));
assertEquals("GUANGZHOU", service.selectAreaRules().get("GZ"));
}
}
@@ -26,6 +26,8 @@ public class PrcProfitReportQuery extends BaseEntity
private List<String> customerCodes;
/** 按客户当前区域筛选。 */
private String customerArea;
/** 服务端根据客户区域确定的计算规则。 */
private String profitRule;
private String customerName;
/** 客户名称拆分后的多值查询条件。 */
private List<String> customerNames;
@@ -0,0 +1,43 @@
package com.bms.price.enums;
import java.util.LinkedHashMap;
import java.util.Map;
/** 客户区域字典值对应的毛利计算规则。 */
public enum ProfitAreaRule
{
SHENZHEN("C"),
GUANGZHOU("GZ");
private final String customerArea;
ProfitAreaRule(String customerArea)
{
this.customerArea = customerArea;
}
public static ProfitAreaRule fromCustomerArea(String customerArea)
{
if (customerArea != null)
{
for (ProfitAreaRule rule : values())
{
if (rule.customerArea.equals(customerArea.trim()))
{
return rule;
}
}
}
return null;
}
public static Map<String, String> areaRules()
{
Map<String, String> result = new LinkedHashMap<>();
for (ProfitAreaRule rule : values())
{
result.put(rule.customerArea, rule.name());
}
return result;
}
}
@@ -1,6 +1,7 @@
package com.bms.price.service;
import java.util.List;
import java.util.Map;
import com.bms.customer.customerinfo.domain.BmsCustomerOption;
// 提成功能暂停使用:import com.bms.price.domain.PrcProfitCommissionRate;
import com.bms.price.domain.PrcProfitReportExport;
@@ -14,6 +15,8 @@ public interface IPrcProfitReportService
{
List<BmsCustomerOption> selectCustomerOptions(String keywords);
Map<String, String> selectAreaRules();
List<PrcProfitReportOrderRow> selectOrderRows(PrcProfitReportQuery query);
List<PrcProfitReportProductRow> selectProductRows(PrcProfitReportQuery query);
@@ -3,6 +3,7 @@ package com.bms.price.service.impl;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
// 提成功能暂停使用:import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -19,6 +20,7 @@ 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.enums.ProfitAreaRule;
import com.bms.price.mapper.PrcProfitReportMapper;
import com.bms.price.service.IPrcProfitReportService;
@@ -48,6 +50,12 @@ public class PrcProfitReportServiceImpl implements IPrcProfitReportService
return customerMapper.selectCustomerOptions(query);
}
@Override
public Map<String, String> selectAreaRules()
{
return ProfitAreaRule.areaRules();
}
@Override
public List<PrcProfitReportOrderRow> selectOrderRows(PrcProfitReportQuery query)
{
@@ -105,12 +113,23 @@ public class PrcProfitReportServiceImpl implements IPrcProfitReportService
{
if (query == null)
{
return;
throw new ServiceException("请选择客户区域");
}
if ("COMMISSION_MISSING".equals(query.getDetailType()))
{
throw new ServiceException("提成功能已暂停使用");
}
if (query.getCustomerArea() == null || query.getCustomerArea().trim().isEmpty())
{
throw new ServiceException("请选择客户区域");
}
ProfitAreaRule rule = ProfitAreaRule.fromCustomerArea(query.getCustomerArea());
if (rule == null)
{
throw new ServiceException("该客户区域未配置毛利规则");
}
query.setCustomerArea(query.getCustomerArea().trim());
query.setProfitRule(rule.name());
query.setOrderNos(splitFilterValues(query.getOrderNo(), "订单号"));
query.setCustomerCodes(splitFilterValues(query.getCustomerCode(), "客户代码"));
query.setCustomerNames(splitFilterValues(query.getCustomerName(), "客户名称"));
@@ -200,8 +200,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where>
</sql>
<!-- 红线毛利 = 产品价 - 红线价;红线毛利率 = 红线毛利 / 红线价 * 100%。汇总先逐单计算再求和。 -->
<!-- 广州缺少有效销售价或红线价时不计算毛利;计费失败留下的双零占位也不参与计算。 -->
<sql id="guangzhouProfitAvailable">
i.sales_amount is not null and i.redline_amount is not null
and not (coalesce(p.billing_fail_status, 0) in (23, 24, 25) and i.sales_amount = 0 and i.redline_amount = 0)
</sql>
<sql id="profitExpressions">
<choose>
<when test="profitRule == 'GUANGZHOU'">
null as cost_amount, i.redline_amount,
null as redline_gross_profit, null as redline_gross_margin_rate,
i.sales_amount,
case when <include refid="guangzhouProfitAvailable"/>
then i.sales_amount - i.redline_amount else null end as sales_gross_profit,
case when <include refid="guangzhouProfitAvailable"/> and i.sales_amount != 0
then round((i.sales_amount - i.redline_amount) * 100 / i.sales_amount, 4)
else null end as sales_gross_margin_rate
</when>
<otherwise>
i.product_amount as cost_amount,
i.redline_amount,
case when i.product_amount is not null and i.redline_amount is not null
@@ -213,11 +230,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
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
-->
</otherwise>
</choose>
</sql>
<select id="selectOrderRows" resultMap="PrcProfitReportOrderResult">
@@ -234,9 +248,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
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,
<!-- 汇总和产品维度使用同一套逐单有效数据计算,毛利率按有效销售额加权。 -->
<sql id="profitAggregateExpressions">
<choose>
<when test="profitRule == 'GUANGZHOU'">
null as cost_amount,
sum(i.redline_amount) as redline_amount,
null as redline_gross_profit, null as redline_gross_margin_rate,
sum(i.sales_amount) as sales_amount,
sum(case when <include refid="guangzhouProfitAvailable"/>
then i.sales_amount - i.redline_amount else null end) as sales_gross_profit,
case when sum(case when <include refid="guangzhouProfitAvailable"/>
then i.sales_amount else null end) is null
or sum(case when <include refid="guangzhouProfitAvailable"/>
then i.sales_amount else null end) = 0
then null
else round(sum(case when <include refid="guangzhouProfitAvailable"/>
then i.sales_amount - i.redline_amount else null end) * 100 /
sum(case when <include refid="guangzhouProfitAvailable"/>
then i.sales_amount else null end), 4)
end as sales_gross_margin_rate
</when>
<otherwise>
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
@@ -266,12 +299,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
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,
-->
</otherwise>
</choose>
</sql>
<select id="selectProductRows" resultMap="PrcProfitReportProductResult">
select p.product_code, p.product_name, p.country_code, p.country_name,
i.currency, count(*) as order_count,
<include refid="profitAggregateExpressions"/>
<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
@@ -279,41 +314,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectSummary" resultMap="PrcProfitReportSummaryResult">
select 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.product_amount - i.redline_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.product_amount - i.redline_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="profitAggregateExpressions"/>
<include refid="profitReportFromAndWhere"/>
<if test="arBillStatus != null and arBillStatus != ''">
and p.ar_bill_status = #{arBillStatus}
+4
View File
@@ -19,6 +19,10 @@ export function listProfitCustomerOptions(params) {
})
}
export function listProfitAreaRules() {
return request({ url: '/report/profit/area-rules', method: 'get' })
}
export function listProfitSummary(params) {
return request({
url: '/report/profit/summary',
+53 -22
View File
@@ -70,7 +70,7 @@
</el-col>
<el-col :xs="24" :sm="12" :lg="8" class="filter-actions-col">
<div class="filter-item filter-actions">
<el-button type="primary" icon="Search" :disabled="!hasCustomerArea()" @click="handleSearch">搜索</el-button>
<el-button type="primary" icon="Search" :disabled="!hasProfitRule()" @click="handleSearch">搜索</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</div>
</el-col>
@@ -78,23 +78,26 @@
</el-form>
</el-card>
<div v-if="summaryList.length" class="summary-list">
<el-card v-for="summary in summaryList" :key="summary.currency" shadow="never" class="summary-card">
<el-alert v-if="areaRulesFailed" title="毛利规则加载失败,请刷新页面重试" type="error" :closable="false" show-icon class="rule-alert" />
<el-alert v-else-if="areaRules && hasCustomerArea() && !currentRule" title="该客户区域未配置毛利规则" type="warning" :closable="false" show-icon class="rule-alert" />
<div v-if="summaryLoaded && hasProfitRule()" class="summary-list">
<el-card v-for="summary in summaryCards" :key="summary.currency || 'empty'" shadow="never" class="summary-card">
<template #header>
<span>{{ summary.currency || '未标币种' }} 汇总</span>
<span class="summary-subtitle">订单 {{ summary.orderCount || 0 }} 条</span>
<span>{{ summary.empty ? '汇总' : `${summary.currency || '未标币种'} 汇总` }}</span>
<span class="summary-subtitle">订单 {{ summary.orderCount ?? '-' }} 条</span>
</template>
<el-row :gutter="12" class="summary-grid">
<el-col :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">红线价</div><div class="metric-value">{{ money(summary.redlineAmount) }}</div></div>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<el-col v-if="!isGuangzhou" :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">产品价</div><div class="metric-value">{{ money(summary.costAmount) }}</div></div>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">销售价</div><div class="metric-value">{{ money(summary.salesAmount) }}</div></div>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<el-col v-if="!isGuangzhou" :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">红线毛利</div><div class="metric-value">{{ money(summary.redlineGrossProfit) }} <small>{{ rate(summary.redlineGrossMarginRate) }}</small></div></div>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
@@ -139,14 +142,14 @@
icon="Download"
v-hasPermi="['report:profit:export']"
:loading="exporting"
:disabled="!hasCustomerArea()"
:disabled="!hasProfitRule()"
@click="handleExport"
>导出当前维度</el-button>
</div>
</div>
<div v-text-drag-copy="true" class="report-table-copy-area">
<el-table :key="dimension" v-loading="loading" :data="rows" border stripe :row-key="rowKey">
<el-table :key="dimension + '-' + currentRule" v-loading="loading" :data="rows" border stripe :row-key="rowKey">
<template v-if="dimension === 'product'">
<el-table-column prop="productCode" label="产品代码" min-width="120" show-overflow-tooltip />
<el-table-column prop="productName" label="产品名称" min-width="180" show-overflow-tooltip />
@@ -156,10 +159,10 @@
<el-table-column prop="orderCount" label="订单数" width="100" align="right">
<template #default="{ row }"><span class="success-value order-count-link" :class="{ 'is-disabled': !row.orderCount }" @click.stop="openOrderList(row)">{{ row.orderCount }}</span></template>
</el-table-column>
<el-table-column prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column prop="redlineAmount" label="红线价" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineAmount) }}</template></el-table-column>
<el-table-column prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<el-table-column prop="salesAmount" label="销售价" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesAmount) }}</template></el-table-column>
<el-table-column prop="salesGrossProfit" label="销售毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesGrossProfit) }}</template></el-table-column>
<el-table-column prop="salesGrossMarginRate" label="销售毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.salesGrossMarginRate) }}</template></el-table-column>
@@ -181,10 +184,10 @@
<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="countryName" label="国家/地区" min-width="110" show-overflow-tooltip />
<el-table-column prop="currency" label="币种" width="80" align="center" />
<el-table-column prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column prop="redlineAmount" label="红线价" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineAmount) }}</template></el-table-column>
<el-table-column prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<el-table-column prop="salesAmount" label="销售价" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesAmount) }}</template></el-table-column>
<el-table-column prop="salesGrossProfit" label="销售毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesGrossProfit) }}</template></el-table-column>
<el-table-column prop="salesGrossMarginRate" label="销售毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.salesGrossMarginRate) }}</template></el-table-column>
@@ -220,9 +223,11 @@
<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="countryName" label="国家/地区" width="110" />
<el-table-column prop="currency" label="币种" width="80" align="center" />
<el-table-column prop="costAmount" label="产品价" width="110" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="costAmount" label="产品价" width="110" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column prop="redlineAmount" label="红线价" width="110" align="right"><template #default="{ row }">{{ money(row.redlineAmount) }}</template></el-table-column>
<el-table-column prop="salesAmount" label="销售价" width="110" align="right"><template #default="{ row }">{{ money(row.salesAmount) }}</template></el-table-column>
<el-table-column v-if="isGuangzhou" prop="salesGrossProfit" label="销售毛利" width="110" align="right"><template #default="{ row }">{{ money(row.salesGrossProfit) }}</template></el-table-column>
<el-table-column v-if="isGuangzhou" prop="salesGrossMarginRate" label="销售毛利率" width="110" align="right"><template #default="{ row }">{{ rate(row.salesGrossMarginRate) }}</template></el-table-column>
<el-table-column prop="arBillStatus" label="应收状态" width="100" align="center"><template #default="{ row }">{{ arBillStatusLabel(row.arBillStatus) }}</template></el-table-column>
<template #empty><el-empty description="暂无符合条件的订单" /></template>
</el-table>
@@ -304,7 +309,7 @@
</template>
<script setup name="ProfitReport">
import { downloadProfitExport, exportProfitOrders, exportProfitProducts, listProfitCustomerOptions, listProfitExports, listProfitOrders, listProfitProducts, listProfitSummary } from '@/api/report/profit'
import { downloadProfitExport, exportProfitOrders, exportProfitProducts, listProfitAreaRules, listProfitCustomerOptions, listProfitExports, listProfitOrders, listProfitProducts, listProfitSummary } from '@/api/report/profit'
// 提成功能暂停使用:import { deleteProfitCommissionRate, listProfitCommissionRates, saveProfitCommissionRate } from '@/api/report/profit'
import { parseTime } from '@/utils/bms'
import { ElMessage } from 'element-plus'
@@ -323,7 +328,13 @@ const query = reactive({
belongSalerCode: '',
productCode: ''
})
const areaRules = ref(null)
const areaRulesFailed = ref(false)
const currentRule = computed(() => areaRules.value?.[String(query.customerArea)] || '')
const isGuangzhou = computed(() => currentRule.value === 'GUANGZHOU')
const summaryList = ref([])
const summaryLoaded = ref(false)
const summaryCards = computed(() => summaryList.value.length ? summaryList.value : [{ empty: true }])
let summaryRequestVersion = 0
const rows = ref([])
let rowsRequestVersion = 0
@@ -412,17 +423,23 @@ function hasCustomerArea() {
return query.customerArea !== '' && query.customerArea !== null && query.customerArea !== undefined
}
function hasProfitRule() {
return hasCustomerArea() && !!currentRule.value
}
function loadSummary() {
if (!hasCustomerArea() || !validateMultiValueFilters(false)) return Promise.resolve()
if (!hasProfitRule() || !validateMultiValueFilters(false)) return Promise.resolve()
const requestVersion = ++summaryRequestVersion
summaryLoaded.value = false
return listProfitSummary(buildQuery(false)).then(res => {
if (requestVersion !== summaryRequestVersion) return
summaryList.value = res.data || []
summaryLoaded.value = true
})
}
function loadRows() {
if (!hasCustomerArea() || !validateMultiValueFilters()) return Promise.resolve()
if (!hasProfitRule() || !validateMultiValueFilters()) return Promise.resolve()
const requestVersion = ++rowsRequestVersion
const requestDimension = dimension.value
loading.value = true
@@ -441,6 +458,7 @@ function clearReportResults() {
rowsRequestVersion++
orderListRequestVersion++
summaryList.value = []
summaryLoaded.value = false
rows.value = []
total.value = 0
loading.value = false
@@ -453,7 +471,7 @@ function clearReportResults() {
function handleSearch() {
clearReportResults()
if (!hasCustomerArea() || !validateMultiValueFilters()) return
if (!hasProfitRule() || !validateMultiValueFilters()) return
pageNum.value = 1
Promise.all([loadSummary(), loadRows()])
}
@@ -493,7 +511,7 @@ function openOrderList(row) {
function loadOrderList() {
if (!orderListContext.value) return
if (!hasCustomerArea() || !validateMultiValueFilters()) return
if (!hasProfitRule() || !validateMultiValueFilters()) return
const requestVersion = ++orderListRequestVersion
orderListLoading.value = true
const params = {
@@ -547,7 +565,7 @@ function arBillStatusLabel(value) {
}
async function handleExport() {
if (!hasCustomerArea() || !validateMultiValueFilters()) return
if (!hasProfitRule() || !validateMultiValueFilters()) return
exporting.value = true
const exporter = dimension.value === 'product' ? exportProfitProducts : exportProfitOrders
try {
@@ -659,6 +677,15 @@ function handleDeleteRate(row) {
}
*/
listProfitAreaRules().then(res => {
areaRules.value = res.data || {}
handleSearch()
}).catch(() => {
areaRules.value = {}
areaRulesFailed.value = true
clearReportResults()
})
watch(customer_area, options => {
if (!options?.length) {
query.customerArea = ''
@@ -707,6 +734,10 @@ watch(customer_area, options => {
gap: 8px;
}
.rule-alert {
margin-bottom: 14px;
}
.summary-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(560px, 1fr));