✨ 完善应收账单毛利分析与提成展示
This commit is contained in:
+3
-3
@@ -99,7 +99,7 @@ public class ArProfitController extends BaseController
|
|||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("@ss.hasPermi('report:arProfit:commission')")
|
@PreAuthorize("@ss.hasPermi('report:arProfit:list')")
|
||||||
@GetMapping("/commissions")
|
@GetMapping("/commissions")
|
||||||
public TableDataInfo commissions(@RequestParam(required = false) Long billId,
|
public TableDataInfo commissions(@RequestParam(required = false) Long billId,
|
||||||
@RequestParam(defaultValue = "false") boolean includeReversed)
|
@RequestParam(defaultValue = "false") boolean includeReversed)
|
||||||
@@ -108,7 +108,7 @@ public class ArProfitController extends BaseController
|
|||||||
return getDataTable(commissions.results(billId, includeReversed));
|
return getDataTable(commissions.results(billId, includeReversed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("@ss.hasPermi('report:arProfit:commission')")
|
@PreAuthorize("@ss.hasPermi('report:arProfit:list')")
|
||||||
@GetMapping("/commissions/summary")
|
@GetMapping("/commissions/summary")
|
||||||
public AjaxResult commissionSummary(@RequestParam(required = false) Long billId) { return success(commissions.summary(billId)); }
|
public AjaxResult commissionSummary(@RequestParam(required = false) Long billId) { return success(commissions.summary(billId)); }
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ public class ArProfitController extends BaseController
|
|||||||
@PostMapping("/commissions/export")
|
@PostMapping("/commissions/export")
|
||||||
public void exportCommissions(HttpServletResponse response, @RequestParam(required = false) Long billId)
|
public void exportCommissions(HttpServletResponse response, @RequestParam(required = false) Long billId)
|
||||||
{
|
{
|
||||||
new ExcelUtil<>(ArCommissionResult.class).exportExcel(response, commissions.results(billId, false), "应收账单提成明细");
|
new ExcelUtil<>(ArCommissionResult.class).exportExcel(response, commissions.exportResults(billId), "应收账单提成明细");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Log(title = "应收账单毛利分析", businessType = BusinessType.EXPORT)
|
@Log(title = "应收账单毛利分析", businessType = BusinessType.EXPORT)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.bms.report;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -15,9 +17,49 @@ import com.bms.ar.mapper.ArCommissionMapper;
|
|||||||
import com.bms.ar.profit.ArCommissionRate;
|
import com.bms.ar.profit.ArCommissionRate;
|
||||||
import com.bms.ar.profit.ArCommissionResult;
|
import com.bms.ar.profit.ArCommissionResult;
|
||||||
import com.bms.ar.profit.ArCommissionService;
|
import com.bms.ar.profit.ArCommissionService;
|
||||||
|
import com.bms.ar.profit.ArProfitScope;
|
||||||
|
import com.bms.ar.profit.ArProfitScopeResolver;
|
||||||
|
import com.bms.common.exception.ServiceException;
|
||||||
|
|
||||||
class ArCommissionServiceTest
|
class ArCommissionServiceTest
|
||||||
{
|
{
|
||||||
|
@Test
|
||||||
|
void commissionReadsUseServerResolvedScopeAndManagementRemainsRestricted()
|
||||||
|
{
|
||||||
|
AtomicReference<ArProfitScope> actorScope = new AtomicReference<>(new ArProfitScope(false, true, false, 7L, 10L));
|
||||||
|
AtomicReference<Object[]> selected = new AtomicReference<>();
|
||||||
|
ArProfitScopeResolver resolver = new ArProfitScopeResolver() {
|
||||||
|
@Override public ArProfitScope resolve() { return actorScope.get(); }
|
||||||
|
};
|
||||||
|
ArCommissionMapper mapper = (ArCommissionMapper) Proxy.newProxyInstance(getClass().getClassLoader(),
|
||||||
|
new Class<?>[] { ArCommissionMapper.class }, (proxy, method, args) -> {
|
||||||
|
if ("selectResults".equals(method.getName()) || "selectCommissionSummary".equals(method.getName())) {
|
||||||
|
selected.set(args.clone());
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
throw new AssertionError(method.getName());
|
||||||
|
});
|
||||||
|
ArCommissionService service = new ArCommissionService(mapper, resolver, null, null);
|
||||||
|
|
||||||
|
service.results(999L, true);
|
||||||
|
assertEquals(999L, selected.get()[0]);
|
||||||
|
assertSame(actorScope.get(), selected.get()[2]);
|
||||||
|
service.summary(999L);
|
||||||
|
assertSame(actorScope.get(), selected.get()[1]);
|
||||||
|
assertThrows(ServiceException.class, service::selectRates);
|
||||||
|
assertThrows(ServiceException.class, service::missingRates);
|
||||||
|
assertThrows(ServiceException.class, () -> service.exportResults(null));
|
||||||
|
|
||||||
|
actorScope.set(new ArProfitScope(false, false, true, 8L, 10L));
|
||||||
|
service.results(null, false);
|
||||||
|
assertSame(actorScope.get(), selected.get()[2]);
|
||||||
|
assertThrows(ServiceException.class, service::selectRates);
|
||||||
|
|
||||||
|
actorScope.set(new ArProfitScope(true, false, false, 9L, 10L));
|
||||||
|
service.exportResults(null);
|
||||||
|
assertSame(actorScope.get(), selected.get()[2]);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void snapshotsTheRateOnceAtWriteoffAndPreservesMissingRates()
|
void snapshotsTheRateOnceAtWriteoffAndPreservesMissingRates()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,11 +14,58 @@ import com.bms.ar.profit.ArCommissionResult;
|
|||||||
|
|
||||||
class ArProfitMapperSqlTest
|
class ArProfitMapperSqlTest
|
||||||
{
|
{
|
||||||
|
@Test
|
||||||
|
void commissionInReportSummaryUsesBillFiltersAndDataScope() throws Exception
|
||||||
|
{
|
||||||
|
Configuration configuration = new Configuration();
|
||||||
|
String resource = "mapper/ar/ArProfitMapper.xml";
|
||||||
|
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
|
||||||
|
{
|
||||||
|
new XMLMapperBuilder(input, configuration, resource, configuration.getSqlFragments()).parse();
|
||||||
|
}
|
||||||
|
var statement = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectSummary");
|
||||||
|
ArProfitQuery query = new ArProfitQuery();
|
||||||
|
query.setCustomerId(5L);
|
||||||
|
query.setBillNo("AR123");
|
||||||
|
query.setStatus("WRITTEN_OFF");
|
||||||
|
query.setSalesUserId(8L);
|
||||||
|
query.setBeginBillDate(new java.util.Date(0));
|
||||||
|
query.setEndBillDate(new java.util.Date());
|
||||||
|
String sql = statement.getBoundSql(Map.of("query", query,
|
||||||
|
"scope", new ArProfitScope(false, true, false, 8L, 10L))).getSql();
|
||||||
|
int commissionStart = sql.indexOf("from ar_bill b join ar_profit_commission_result r");
|
||||||
|
assertTrue(commissionStart >= 0);
|
||||||
|
String commission = sql.substring(commissionStart);
|
||||||
|
assertTrue(commission.contains("r.result_status = 'READY'"));
|
||||||
|
assertTrue(commission.contains("r.currency = b.currency"));
|
||||||
|
assertTrue(commission.contains("b.status = 'WRITTEN_OFF'"));
|
||||||
|
assertTrue(commission.contains("b.profit_sales_user_id = ?"));
|
||||||
|
assertTrue(commission.contains("b.customer_id = ?"));
|
||||||
|
assertTrue(commission.contains("b.bill_no like"));
|
||||||
|
assertTrue(commission.contains("b.status = ?"));
|
||||||
|
assertTrue(commission.contains("b.bill_date >= ?"));
|
||||||
|
assertTrue(commission.contains("b.bill_date <= ?"));
|
||||||
|
assertTrue(commission.contains("group by b.currency"));
|
||||||
|
|
||||||
|
for (ArProfitScope scope : new ArProfitScope[] {
|
||||||
|
new ArProfitScope(false, false, true, 8L, 10L),
|
||||||
|
new ArProfitScope(false, false, false, 8L, 10L),
|
||||||
|
new ArProfitScope(true, false, false, 8L, 10L) })
|
||||||
|
{
|
||||||
|
String scopedSql = statement.getBoundSql(Map.of("query", new ArProfitQuery(), "scope", scope)).getSql();
|
||||||
|
String scopedCommission = scopedSql.substring(scopedSql.indexOf("from ar_bill b join ar_profit_commission_result r"));
|
||||||
|
if (scope.department()) assertTrue(scopedCommission.contains("find_in_set"));
|
||||||
|
else if (scope.all()) assertFalse(scopedCommission.contains("b.profit_sales_user_id = ?"));
|
||||||
|
else assertTrue(scopedCommission.contains("and 1 = 0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parsesReportAndCommissionMappersAndScopesEachReportQuery() throws Exception
|
void parsesReportAndCommissionMappersAndScopesEachReportQuery() throws Exception
|
||||||
{
|
{
|
||||||
Configuration configuration = new Configuration();
|
Configuration configuration = new Configuration();
|
||||||
for (String resource : new String[] { "mapper/ar/ArProfitMapper.xml", "mapper/ar/ArCommissionMapper.xml" })
|
// 提成 Mapper 先加载时也必须能解析引用的报表权限片段。
|
||||||
|
for (String resource : new String[] { "mapper/ar/ArCommissionMapper.xml", "mapper/ar/ArProfitMapper.xml" })
|
||||||
{
|
{
|
||||||
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
|
try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource))
|
||||||
{
|
{
|
||||||
@@ -40,11 +87,32 @@ class ArProfitMapperSqlTest
|
|||||||
String bills = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectBills")
|
String bills = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectBills")
|
||||||
.getBoundSql(Map.of("query", new ArProfitQuery(), "scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
|
.getBoundSql(Map.of("query", new ArProfitQuery(), "scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
|
||||||
assertTrue(bills.contains("from ar_bill b"));
|
assertTrue(bills.contains("from ar_bill b"));
|
||||||
|
assertTrue(bills.contains("b.bill_name"));
|
||||||
assertFalse(bills.contains("order_no"));
|
assertFalse(bills.contains("order_no"));
|
||||||
String billCommission = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper.selectCommissionSummary")
|
String billCommission = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper.selectCommissionSummary")
|
||||||
.getBoundSql(Map.of("billId", 9L)).getSql();
|
.getBoundSql(Map.of("billId", 9L, "scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
|
||||||
assertTrue(billCommission.contains("r.ar_bill_id = ?"));
|
assertTrue(billCommission.contains("r.ar_bill_id = ?"));
|
||||||
assertTrue(billCommission.contains("then null"));
|
assertTrue(billCommission.contains("then null"));
|
||||||
|
for (String id : new String[] { "selectResults", "selectCommissionSummary" })
|
||||||
|
{
|
||||||
|
var statement = configuration.getMappedStatement("com.bms.ar.mapper.ArCommissionMapper." + id);
|
||||||
|
String self = statement.getBoundSql(Map.of("billId", 9L, "includeReversed", true,
|
||||||
|
"scope", new ArProfitScope(false, true, false, 8L, 10L))).getSql();
|
||||||
|
String department = statement.getBoundSql(Map.of("includeReversed", false,
|
||||||
|
"scope", new ArProfitScope(false, false, true, 8L, 10L))).getSql();
|
||||||
|
String denied = statement.getBoundSql(Map.of("includeReversed", false,
|
||||||
|
"scope", new ArProfitScope(false, false, false, 8L, 10L))).getSql();
|
||||||
|
String all = statement.getBoundSql(Map.of("includeReversed", false,
|
||||||
|
"scope", new ArProfitScope(true, false, false, 8L, 10L))).getSql();
|
||||||
|
assertTrue(self.contains("b.profit_sales_user_id = ?"));
|
||||||
|
assertTrue(self.contains("r.ar_bill_id = ?"));
|
||||||
|
assertFalse(self.contains("find_in_set"));
|
||||||
|
assertTrue(department.contains("u.user_id = b.profit_sales_user_id"));
|
||||||
|
assertTrue(department.contains("find_in_set"));
|
||||||
|
assertTrue(denied.contains("and 1 = 0"));
|
||||||
|
assertFalse(all.contains("b.profit_sales_user_id = ?"));
|
||||||
|
assertFalse(all.contains("find_in_set"));
|
||||||
|
}
|
||||||
var salesSql = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectSalesOptions")
|
var salesSql = configuration.getMappedStatement("com.bms.ar.mapper.ArProfitMapper.selectSalesOptions")
|
||||||
.getBoundSql(Map.of("keyword", "sales"));
|
.getBoundSql(Map.of("keyword", "sales"));
|
||||||
assertTrue(salesSql.getSql().contains("from sys_user u"));
|
assertTrue(salesSql.getSql().contains("from sys_user u"));
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.bms.ar.profit.ArCommissionRate;
|
|||||||
import com.bms.ar.profit.ArWriteoffRate;
|
import com.bms.ar.profit.ArWriteoffRate;
|
||||||
import com.bms.ar.profit.ArCommissionResult;
|
import com.bms.ar.profit.ArCommissionResult;
|
||||||
import com.bms.ar.profit.ArCommissionSummary;
|
import com.bms.ar.profit.ArCommissionSummary;
|
||||||
|
import com.bms.ar.profit.ArProfitScope;
|
||||||
|
|
||||||
public interface ArCommissionMapper
|
public interface ArCommissionMapper
|
||||||
{
|
{
|
||||||
@@ -25,6 +26,7 @@ public interface ArCommissionMapper
|
|||||||
int countMissingRates(@Param("billId") Long billId);
|
int countMissingRates(@Param("billId") Long billId);
|
||||||
int confirmMissingRate(@Param("writeoffId") Long writeoffId, @Param("rate") java.math.BigDecimal rate,
|
int confirmMissingRate(@Param("writeoffId") Long writeoffId, @Param("rate") java.math.BigDecimal rate,
|
||||||
@Param("operator") String operator, @Param("remark") String remark);
|
@Param("operator") String operator, @Param("remark") String remark);
|
||||||
List<ArCommissionResult> selectResults(@Param("billId") Long billId, @Param("includeReversed") boolean includeReversed);
|
List<ArCommissionResult> selectResults(@Param("billId") Long billId, @Param("includeReversed") boolean includeReversed,
|
||||||
List<ArCommissionSummary> selectCommissionSummary(@Param("billId") Long billId);
|
@Param("scope") ArProfitScope scope);
|
||||||
|
List<ArCommissionSummary> selectCommissionSummary(@Param("billId") Long billId, @Param("scope") ArProfitScope scope);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,14 +97,17 @@ public class ArCommissionService
|
|||||||
|
|
||||||
public List<ArCommissionResult> results(Long billId, boolean includeReversed)
|
public List<ArCommissionResult> results(Long billId, boolean includeReversed)
|
||||||
{
|
{
|
||||||
requireFinance();
|
return mapper.selectResults(billId, includeReversed, scopeResolver.resolve());
|
||||||
return mapper.selectResults(billId, includeReversed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ArCommissionSummary> summary(Long billId)
|
public List<ArCommissionSummary> summary(Long billId)
|
||||||
{
|
{
|
||||||
requireFinance();
|
return mapper.selectCommissionSummary(billId, scopeResolver.resolve());
|
||||||
return mapper.selectCommissionSummary(billId);
|
}
|
||||||
|
|
||||||
|
public List<ArCommissionResult> exportResults(Long billId)
|
||||||
|
{
|
||||||
|
return mapper.selectResults(billId, false, requireFinance());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ArWriteoffRate> writeoffRates(Long billId)
|
public List<ArWriteoffRate> writeoffRates(Long billId)
|
||||||
@@ -141,8 +144,10 @@ public class ArCommissionService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requireFinance()
|
private ArProfitScope requireFinance()
|
||||||
{
|
{
|
||||||
if (!scopeResolver.resolve().all()) throw new ServiceException("仅财务和系统管理员可查看或维护提成");
|
ArProfitScope scope = scopeResolver.resolve();
|
||||||
|
if (!scope.all()) throw new ServiceException("仅财务和系统管理员可使用提成管理功能");
|
||||||
|
return scope;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class ArProfitRow
|
|||||||
{
|
{
|
||||||
private Long arBillId;
|
private Long arBillId;
|
||||||
@Excel(name = "账单号", width = 20) private String billNo;
|
@Excel(name = "账单号", width = 20) private String billNo;
|
||||||
|
@Excel(name = "账单名称", width = 24) private String billName;
|
||||||
@Excel(name = "客户代码", width = 16) private String customerCode;
|
@Excel(name = "客户代码", width = 16) private String customerCode;
|
||||||
@Excel(name = "客户名称", width = 24) private String customerName;
|
@Excel(name = "客户名称", width = 24) private String customerName;
|
||||||
private String customerArea;
|
private String customerArea;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class ArProfitSummary
|
|||||||
private BigDecimal productAmount;
|
private BigDecimal productAmount;
|
||||||
private BigDecimal salesGrossProfit;
|
private BigDecimal salesGrossProfit;
|
||||||
private BigDecimal salesGrossMarginRate;
|
private BigDecimal salesGrossMarginRate;
|
||||||
|
private BigDecimal commissionAmount;
|
||||||
private BigDecimal writtenOffAmount;
|
private BigDecimal writtenOffAmount;
|
||||||
private BigDecimal unwrittenAmount;
|
private BigDecimal unwrittenAmount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@
|
|||||||
<otherwise>and r.result_status != 'REVERSED' and b.status = 'WRITTEN_OFF' and b.generation_status = 'READY'</otherwise>
|
<otherwise>and r.result_status != 'REVERSED' and b.status = 'WRITTEN_OFF' and b.generation_status = 'READY'</otherwise>
|
||||||
</choose>
|
</choose>
|
||||||
<if test="billId != null">and r.ar_bill_id = #{billId}</if>
|
<if test="billId != null">and r.ar_bill_id = #{billId}</if>
|
||||||
|
<include refid="com.bms.ar.mapper.ArProfitMapper.scopeCondition"/>
|
||||||
order by r.result_id desc
|
order by r.result_id desc
|
||||||
</select>
|
</select>
|
||||||
<select id="selectCommissionSummary" resultMap="commissionSummary">
|
<select id="selectCommissionSummary" resultMap="commissionSummary">
|
||||||
@@ -120,6 +121,7 @@
|
|||||||
from ar_profit_commission_result r join ar_bill b on b.ar_bill_id = r.ar_bill_id
|
from ar_profit_commission_result r join ar_bill b on b.ar_bill_id = r.ar_bill_id
|
||||||
where r.result_status != 'REVERSED' and b.status = 'WRITTEN_OFF' and b.generation_status = 'READY'
|
where r.result_status != 'REVERSED' and b.status = 'WRITTEN_OFF' and b.generation_status = 'READY'
|
||||||
<if test="billId != null">and r.ar_bill_id = #{billId}</if>
|
<if test="billId != null">and r.ar_bill_id = #{billId}</if>
|
||||||
|
<include refid="com.bms.ar.mapper.ArProfitMapper.scopeCondition"/>
|
||||||
group by r.sales_user_id, r.currency
|
group by r.sales_user_id, r.currency
|
||||||
order by sales_user_name, r.currency
|
order by sales_user_name, r.currency
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
<mapper namespace="com.bms.ar.mapper.ArProfitMapper">
|
<mapper namespace="com.bms.ar.mapper.ArProfitMapper">
|
||||||
<resultMap id="row" type="com.bms.ar.profit.ArProfitRow">
|
<resultMap id="row" type="com.bms.ar.profit.ArProfitRow">
|
||||||
<id property="arBillId" column="ar_bill_id"/>
|
<id property="arBillId" column="ar_bill_id"/>
|
||||||
<result property="billNo" column="bill_no"/><result property="billDate" column="bill_date"/>
|
<result property="billNo" column="bill_no"/><result property="billName" column="bill_name"/>
|
||||||
|
<result property="billDate" column="bill_date"/>
|
||||||
<result property="customerCode" column="customer_code"/><result property="customerName" column="customer_name"/>
|
<result property="customerCode" column="customer_code"/><result property="customerName" column="customer_name"/>
|
||||||
<result property="customerArea" column="customer_area"/>
|
<result property="customerArea" column="customer_area"/>
|
||||||
<result property="salesUserName" column="sales_user_name"/><result property="currency" column="currency"/>
|
<result property="salesUserName" column="sales_user_name"/><result property="currency" column="currency"/>
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
<result property="currency" column="currency"/><result property="billCount" column="bill_count"/>
|
<result property="currency" column="currency"/><result property="billCount" column="bill_count"/>
|
||||||
<result property="salesAmount" column="sales_amount"/><result property="productAmount" column="product_amount"/>
|
<result property="salesAmount" column="sales_amount"/><result property="productAmount" column="product_amount"/>
|
||||||
<result property="salesGrossProfit" column="sales_gross_profit"/><result property="salesGrossMarginRate" column="sales_gross_margin_rate"/>
|
<result property="salesGrossProfit" column="sales_gross_profit"/><result property="salesGrossMarginRate" column="sales_gross_margin_rate"/>
|
||||||
|
<result property="commissionAmount" column="commission_amount"/>
|
||||||
<result property="writtenOffAmount" column="written_off_amount"/><result property="unwrittenAmount" column="unwritten_amount"/>
|
<result property="writtenOffAmount" column="written_off_amount"/><result property="unwrittenAmount" column="unwritten_amount"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
<resultMap id="detail" type="com.bms.ar.domain.ArBillDetail">
|
<resultMap id="detail" type="com.bms.ar.domain.ArBillDetail">
|
||||||
@@ -62,7 +64,7 @@
|
|||||||
<sql id="billRows">
|
<sql id="billRows">
|
||||||
<bind name="shenzhenArea" value="@com.bms.price.enums.ProfitAreaRule@SHENZHEN.getCustomerArea()"/>
|
<bind name="shenzhenArea" value="@com.bms.price.enums.ProfitAreaRule@SHENZHEN.getCustomerArea()"/>
|
||||||
<bind name="guangzhouArea" value="@com.bms.price.enums.ProfitAreaRule@GUANGZHOU.getCustomerArea()"/>
|
<bind name="guangzhouArea" value="@com.bms.price.enums.ProfitAreaRule@GUANGZHOU.getCustomerArea()"/>
|
||||||
select b.ar_bill_id, b.bill_no, b.bill_date, b.customer_code, b.customer_name,
|
select b.ar_bill_id, b.bill_no, b.bill_name, b.bill_date, b.customer_code, b.customer_name,
|
||||||
b.profit_customer_area as customer_area,
|
b.profit_customer_area as customer_area,
|
||||||
b.profit_sales_user_name as sales_user_name, b.currency, b.status,
|
b.profit_sales_user_name as sales_user_name, b.currency, b.status,
|
||||||
b.total_amount as sales_amount, b.written_off_amount, b.unwritten_amount,
|
b.total_amount as sales_amount, b.written_off_amount, b.unwritten_amount,
|
||||||
@@ -118,8 +120,18 @@
|
|||||||
case when sum(case when t.profit_incomplete = 0 then t.sales_amount else null end) = 0 then null
|
case when sum(case when t.profit_incomplete = 0 then t.sales_amount else null end) = 0 then null
|
||||||
else round(sum(t.sales_gross_profit) * 100 /
|
else round(sum(t.sales_gross_profit) * 100 /
|
||||||
sum(case when t.profit_incomplete = 0 then t.sales_amount else null end), 4) end as sales_gross_margin_rate,
|
sum(case when t.profit_incomplete = 0 then t.sales_amount else null end), 4) end as sales_gross_margin_rate,
|
||||||
sum(t.written_off_amount) as written_off_amount, sum(t.unwritten_amount) as unwritten_amount
|
sum(t.written_off_amount) as written_off_amount, sum(t.unwritten_amount) as unwritten_amount,
|
||||||
from (<include refid="billRows"/>) t group by t.currency order by t.currency
|
max(commission.commission_amount) as commission_amount
|
||||||
|
from (<include refid="billRows"/>) t
|
||||||
|
left join (
|
||||||
|
select b.currency, sum(r.commission_amount) as commission_amount
|
||||||
|
from ar_bill b join ar_profit_commission_result r
|
||||||
|
on r.ar_bill_id = b.ar_bill_id and r.currency = b.currency and r.result_status = 'READY'
|
||||||
|
<include refid="billConditions"/>
|
||||||
|
and b.status = 'WRITTEN_OFF'
|
||||||
|
group by b.currency
|
||||||
|
) commission on commission.currency = t.currency
|
||||||
|
group by t.currency order by t.currency
|
||||||
</select>
|
</select>
|
||||||
<select id="selectSalesOptions" resultType="com.bms.ar.profit.ArProfitSalesOption">
|
<select id="selectSalesOptions" resultType="com.bms.ar.profit.ArProfitSalesOption">
|
||||||
select u.user_id as userId, u.nick_name as name, u.user_name as code
|
select u.user_id as userId, u.nick_name as name, u.user_name as code
|
||||||
|
|||||||
@@ -36,6 +36,12 @@
|
|||||||
<div>销售价 <b>{{ money(summary.salesAmount) }}</b></div><div>产品价 <b>{{ money(summary.productAmount) }}</b></div>
|
<div>销售价 <b>{{ money(summary.salesAmount) }}</b></div><div>产品价 <b>{{ money(summary.productAmount) }}</b></div>
|
||||||
<div>销售毛利 <b>{{ money(summary.salesGrossProfit) }}</b></div>
|
<div>销售毛利 <b>{{ money(summary.salesGrossProfit) }}</b></div>
|
||||||
<div>销售毛利率 <b>{{ rate(summary.salesGrossMarginRate) }}</b></div>
|
<div>销售毛利率 <b>{{ rate(summary.salesGrossMarginRate) }}</b></div>
|
||||||
|
<div>
|
||||||
|
<el-tooltip content="仅统计已全部核销、提成计算成功且未冲回的订单提成;未全部核销或提成异常的订单暂不计入,无已计算提成时显示 -。" placement="top">
|
||||||
|
<span>正式提成 ⓘ</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<b>{{ money(summary.commissionAmount) }}</b>
|
||||||
|
</div>
|
||||||
<div>账单已核销 <b>{{ money(summary.writtenOffAmount) }}</b></div><div>账单未核销 <b>{{ money(summary.unwrittenAmount) }}</b></div>
|
<div>账单已核销 <b>{{ money(summary.writtenOffAmount) }}</b></div><div>账单未核销 <b>{{ money(summary.unwrittenAmount) }}</b></div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -50,6 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-table v-loading="loading" :data="rows" border stripe>
|
<el-table v-loading="loading" :data="rows" border stripe>
|
||||||
<el-table-column prop="billNo" label="账单号" min-width="170" show-overflow-tooltip />
|
<el-table-column prop="billNo" label="账单号" min-width="170" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="billName" label="账单名称" min-width="170" show-overflow-tooltip />
|
||||||
<el-table-column prop="billDate" label="账单日期" width="115" />
|
<el-table-column prop="billDate" label="账单日期" width="115" />
|
||||||
<el-table-column prop="customerCode" label="客户代码" width="110" />
|
<el-table-column prop="customerCode" label="客户代码" width="110" />
|
||||||
<el-table-column prop="customerName" label="客户名称" min-width="160" show-overflow-tooltip />
|
<el-table-column prop="customerName" label="客户名称" min-width="160" show-overflow-tooltip />
|
||||||
@@ -62,7 +69,15 @@
|
|||||||
<el-table-column label="已核销" min-width="110" align="right"><template #default="{ row }">{{ money(row.writtenOffAmount) }}</template></el-table-column>
|
<el-table-column label="已核销" min-width="110" align="right"><template #default="{ row }">{{ money(row.writtenOffAmount) }}</template></el-table-column>
|
||||||
<el-table-column label="未核销" min-width="110" align="right"><template #default="{ row }">{{ money(row.unwrittenAmount) }}</template></el-table-column>
|
<el-table-column label="未核销" min-width="110" align="right"><template #default="{ row }">{{ money(row.unwrittenAmount) }}</template></el-table-column>
|
||||||
<el-table-column label="状态" width="110"><template #default="{ row }">{{ statusLabel(row.status) }}</template></el-table-column>
|
<el-table-column label="状态" width="110"><template #default="{ row }">{{ statusLabel(row.status) }}</template></el-table-column>
|
||||||
<el-table-column label="操作" width="220" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openBillDetails(row)">账单明细</el-button><el-button v-hasPermi="['report:arProfit:commission']" link type="primary" @click="openBillCommission(row)">查看提成</el-button><el-button v-hasPermi="['report:arProfit:commission']" link type="primary" @click="openWriteoffRates(row)">核销比例</el-button></template></el-table-column>
|
<el-table-column label="操作" width="250" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="ar-profit-actions">
|
||||||
|
<el-button link type="primary" @click="openBillDetails(row)">账单明细</el-button>
|
||||||
|
<el-button v-hasPermi="['report:arProfit:list']" link type="primary" @click="openBillCommission(row)">查看提成</el-button>
|
||||||
|
<el-button v-hasPermi="['report:arProfit:commission']" link type="primary" @click="openWriteoffRates(row)">核销比例</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" @pagination="loadRows" />
|
<pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" @pagination="loadRows" />
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -87,15 +102,11 @@
|
|||||||
<el-dialog v-model="commissionVisible" title="提成管理" width="1100px" append-to-body>
|
<el-dialog v-model="commissionVisible" title="提成管理" width="1100px" append-to-body>
|
||||||
<el-tabs v-model="commissionTab" @tab-change="loadCommissionTab">
|
<el-tabs v-model="commissionTab" @tab-change="loadCommissionTab">
|
||||||
<el-tab-pane label="正式提成" name="results">
|
<el-tab-pane label="正式提成" name="results">
|
||||||
<el-alert v-if="commissionBillId" :title="'当前查看账单:' + commissionBillNo" type="info" :closable="false" show-icon>
|
|
||||||
<el-button link type="primary" @click="clearCommissionBill">查看全部账单</el-button>
|
|
||||||
</el-alert>
|
|
||||||
<div v-if="commissionBillId" class="summary-note">账单全部核销后生成正式提成;存在待处理明细时不展示整单提成合计。</div>
|
|
||||||
<el-button icon="Download" style="margin-bottom: 12px" @click="exportCommissions">导出提成明细</el-button>
|
<el-button icon="Download" style="margin-bottom: 12px" @click="exportCommissions">导出提成明细</el-button>
|
||||||
<el-checkbox v-model="includeReversed" style="margin: 0 0 12px 12px" @change="loadCommissionTab">显示已冲回记录</el-checkbox>
|
<el-checkbox v-model="includeReversed" style="margin: 0 0 12px 12px" @change="loadCommissionTab">显示已冲回记录</el-checkbox>
|
||||||
<el-table :data="commissionSummary" border size="small" style="margin-bottom: 16px">
|
<el-table :data="commissionSummary" border size="small" style="margin-bottom: 16px">
|
||||||
<el-table-column prop="salesUserName" label="销售员" /><el-table-column prop="currency" label="币种" width="80" />
|
<el-table-column prop="salesUserName" label="销售员" /><el-table-column prop="currency" label="币种" width="80" />
|
||||||
<el-table-column prop="orderCount" label="订单明细数" /><el-table-column prop="pendingCount" label="待处理订单数" />
|
<el-table-column prop="orderCount" label="订单明细数" /><el-table-column prop="pendingCount" label="异常订单数" />
|
||||||
<el-table-column label="正式提成合计" align="right"><template #default="{ row }">{{ money(row.commissionAmount) }}</template></el-table-column>
|
<el-table-column label="正式提成合计" align="right"><template #default="{ row }">{{ money(row.commissionAmount) }}</template></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-table :data="results" border size="small" v-loading="commissionLoading">
|
<el-table :data="results" border size="small" v-loading="commissionLoading">
|
||||||
@@ -131,6 +142,21 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<el-dialog v-model="billCommissionVisible" :title="'账单提成明细:' + billCommissionNo" width="1000px" append-to-body>
|
||||||
|
<el-table :data="billCommissionSummary" border size="small" style="margin-bottom: 16px">
|
||||||
|
<el-table-column prop="salesUserName" label="销售员" /><el-table-column prop="currency" label="币种" width="80" />
|
||||||
|
<el-table-column prop="orderCount" label="订单明细数" /><el-table-column prop="pendingCount" label="异常订单数" />
|
||||||
|
<el-table-column label="正式提成合计" align="right"><template #default="{ row }">{{ money(row.commissionAmount) }}</template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-table :data="billCommissionResults" border size="small" v-loading="billCommissionLoading">
|
||||||
|
<el-table-column prop="orderNo" label="订单号" min-width="170" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="salesUserName" label="销售员" width="110" /><el-table-column prop="currency" label="币种" width="80" />
|
||||||
|
<el-table-column label="销售毛利" width="120" align="right"><template #default="{ row }">{{ money(row.grossProfit) }}</template></el-table-column>
|
||||||
|
<el-table-column label="正式提成" width="120" align="right"><template #default="{ row }">{{ money(row.commissionAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="状态" min-width="130"><template #default="{ row }">{{ resultLabel(row.resultStatus) }}</template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<pagination v-show="billCommissionTotal > 0" v-model:page="billCommissionPage" v-model:limit="billCommissionSize" :total="billCommissionTotal" @pagination="loadBillCommission" />
|
||||||
|
</el-dialog>
|
||||||
<el-dialog v-model="confirmVisible" title="确认历史核销比例" width="480px" append-to-body>
|
<el-dialog v-model="confirmVisible" title="确认历史核销比例" width="480px" append-to-body>
|
||||||
<el-form label-width="100px"><el-form-item label="账单号">{{ confirmForm.billNo }}</el-form-item>
|
<el-form label-width="100px"><el-form-item label="账单号">{{ confirmForm.billNo }}</el-form-item>
|
||||||
<el-form-item label="比例(%)"><el-input-number v-model="confirmForm.rate" :min="0" :max="100" :precision="4" /></el-form-item>
|
<el-form-item label="比例(%)"><el-input-number v-model="confirmForm.rate" :min="0" :max="100" :precision="4" /></el-form-item>
|
||||||
@@ -172,9 +198,13 @@ const summaryCards = computed(() => !summaryLoaded.value ? [] : summaries.value.
|
|||||||
let requestVersion = 0
|
let requestVersion = 0
|
||||||
let commissionVersion = 0
|
let commissionVersion = 0
|
||||||
const commissionVisible = ref(false), commissionTab = ref('results'), commissionLoading = ref(false)
|
const commissionVisible = ref(false), commissionTab = ref('results'), commissionLoading = ref(false)
|
||||||
const commissionBillId = ref(null), commissionBillNo = ref('')
|
|
||||||
const results = ref([]), commissionTotal = ref(0), commissionPage = ref(1), commissionSize = ref(10)
|
const results = ref([]), commissionTotal = ref(0), commissionPage = ref(1), commissionSize = ref(10)
|
||||||
const commissionSummary = ref([])
|
const commissionSummary = ref([])
|
||||||
|
const billCommissionVisible = ref(false), billCommissionLoading = ref(false), billCommissionNo = ref('')
|
||||||
|
const billCommissionId = ref(null)
|
||||||
|
const billCommissionResults = ref([]), billCommissionTotal = ref(0), billCommissionPage = ref(1), billCommissionSize = ref(10)
|
||||||
|
const billCommissionSummary = ref([])
|
||||||
|
let billCommissionVersion = 0
|
||||||
const includeReversed = ref(false)
|
const includeReversed = ref(false)
|
||||||
const rates = ref([]), rateForm = reactive({ salesUserId: null, rate: null })
|
const rates = ref([]), rateForm = reactive({ salesUserId: null, rate: null })
|
||||||
const missingRates = ref([]), missingTotal = ref(0), missingPage = ref(1), missingSize = ref(10)
|
const missingRates = ref([]), missingTotal = ref(0), missingPage = ref(1), missingSize = ref(10)
|
||||||
@@ -227,7 +257,7 @@ async function loadBillDetails() {
|
|||||||
}
|
}
|
||||||
async function openFees(row) { feeVisible.value = true; feeLoading.value = true; fees.value = []; try { fees.value = (await listArProfitFees(row.detailId)).data || [] } finally { feeLoading.value = false } }
|
async function openFees(row) { feeVisible.value = true; feeLoading.value = true; fees.value = []; try { fees.value = (await listArProfitFees(row.detailId)).data || [] } finally { feeLoading.value = false } }
|
||||||
function exportRows() { proxy.download('/report/ar-profit/export', params(), `应收账单毛利分析_${Date.now()}.xlsx`) }
|
function exportRows() { proxy.download('/report/ar-profit/export', params(), `应收账单毛利分析_${Date.now()}.xlsx`) }
|
||||||
function exportCommissions() { proxy.download('/report/ar-profit/commissions/export', { billId: commissionBillId.value }, `应收账单提成明细_${Date.now()}.xlsx`) }
|
function exportCommissions() { proxy.download('/report/ar-profit/commissions/export', {}, `应收账单提成明细_${Date.now()}.xlsx`) }
|
||||||
function money(v) { return v === null || v === undefined || v === '' || !Number.isFinite(Number(v)) ? '-' : Number(v).toFixed(2) }
|
function money(v) { return v === null || v === undefined || v === '' || !Number.isFinite(Number(v)) ? '-' : Number(v).toFixed(2) }
|
||||||
function rate(v) { return v === null || v === undefined || v === '' || !Number.isFinite(Number(v)) ? '-' : Number(v).toFixed(2) + '%' }
|
function rate(v) { return v === null || v === undefined || v === '' || !Number.isFinite(Number(v)) ? '-' : Number(v).toFixed(2) + '%' }
|
||||||
function feeProductAmount(row) {
|
function feeProductAmount(row) {
|
||||||
@@ -237,19 +267,39 @@ function feeProductAmount(row) {
|
|||||||
}
|
}
|
||||||
function statusLabel(v) { return ({ CONFIRMED: '已确认', PARTIAL_WRITTEN_OFF: '部分核销', WRITTEN_OFF: '已核销' })[v] || v || '-' }
|
function statusLabel(v) { return ({ CONFIRMED: '已确认', PARTIAL_WRITTEN_OFF: '部分核销', WRITTEN_OFF: '已核销' })[v] || v || '-' }
|
||||||
function resultLabel(v) { return ({ READY: '已计算', MISSING_RATE: '比例待确认', MISSING_PRICE: '价格缺失', MISSING_SNAPSHOT: '归属或区域缺失', WRITEOFF_MISMATCH: '核销金额不一致', REVERSED: '已冲回' })[v] || v || '-' }
|
function resultLabel(v) { return ({ READY: '已计算', MISSING_RATE: '比例待确认', MISSING_PRICE: '价格缺失', MISSING_SNAPSHOT: '归属或区域缺失', WRITEOFF_MISMATCH: '核销金额不一致', REVERSED: '已冲回' })[v] || v || '-' }
|
||||||
function openCommission() { commissionBillId.value = null; commissionBillNo.value = ''; commissionPage.value = 1; commissionVisible.value = true; loadCommissionTab() }
|
function openCommission() { commissionPage.value = 1; commissionVisible.value = true; loadCommissionTab() }
|
||||||
function openBillCommission(row) { commissionBillId.value = row.arBillId; commissionBillNo.value = row.billNo; commissionPage.value = 1; commissionTab.value = 'results'; commissionVisible.value = true; loadCommissionTab() }
|
function openBillCommission(row) { billCommissionId.value = row.arBillId; billCommissionNo.value = row.billNo; billCommissionPage.value = 1; billCommissionResults.value = []; billCommissionSummary.value = []; billCommissionTotal.value = 0; billCommissionVisible.value = true; loadBillCommission() }
|
||||||
function clearCommissionBill() { commissionBillId.value = null; commissionBillNo.value = ''; commissionPage.value = 1; loadCommissionTab() }
|
async function loadBillCommission() {
|
||||||
|
const version = ++billCommissionVersion
|
||||||
|
const currentBillId = billCommissionId.value
|
||||||
|
if (!currentBillId) return
|
||||||
|
billCommissionLoading.value = true
|
||||||
|
billCommissionResults.value = []
|
||||||
|
billCommissionSummary.value = []
|
||||||
|
billCommissionTotal.value = 0
|
||||||
|
try {
|
||||||
|
const [result, summary] = await Promise.all([
|
||||||
|
listArProfitCommissions({ billId: currentBillId, pageNum: billCommissionPage.value, pageSize: billCommissionSize.value }),
|
||||||
|
listArProfitCommissionSummary({ billId: currentBillId })
|
||||||
|
])
|
||||||
|
if (version === billCommissionVersion) {
|
||||||
|
billCommissionResults.value = result.rows || []
|
||||||
|
billCommissionTotal.value = result.total || 0
|
||||||
|
billCommissionSummary.value = summary.data || []
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (version === billCommissionVersion) billCommissionLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
async function loadCommissionTab() {
|
async function loadCommissionTab() {
|
||||||
const version = ++commissionVersion
|
const version = ++commissionVersion
|
||||||
const tab = commissionTab.value
|
const tab = commissionTab.value
|
||||||
const billId = commissionBillId.value
|
|
||||||
commissionLoading.value = true
|
commissionLoading.value = true
|
||||||
if (tab === 'results') { results.value = []; commissionSummary.value = []; commissionTotal.value = 0 }
|
if (tab === 'results') { results.value = []; commissionSummary.value = []; commissionTotal.value = 0 }
|
||||||
try {
|
try {
|
||||||
if (tab === 'rates') { const r = await listArProfitRates(); if (version === commissionVersion) rates.value = r.data || [] }
|
if (tab === 'rates') { const r = await listArProfitRates(); if (version === commissionVersion) rates.value = r.data || [] }
|
||||||
else if (tab === 'missing') { const r = await listArProfitMissingRates({ pageNum: missingPage.value, pageSize: missingSize.value }); if (version === commissionVersion) { missingRates.value = r.rows || []; missingTotal.value = r.total || 0 } }
|
else if (tab === 'missing') { const r = await listArProfitMissingRates({ pageNum: missingPage.value, pageSize: missingSize.value }); if (version === commissionVersion) { missingRates.value = r.rows || []; missingTotal.value = r.total || 0 } }
|
||||||
else { const [r, summary] = await Promise.all([listArProfitCommissions({ billId, pageNum: commissionPage.value, pageSize: commissionSize.value, includeReversed: includeReversed.value }), listArProfitCommissionSummary({ billId })]); if (version === commissionVersion) { results.value = r.rows || []; commissionTotal.value = r.total || 0; commissionSummary.value = summary.data || [] } }
|
else { const [r, summary] = await Promise.all([listArProfitCommissions({ pageNum: commissionPage.value, pageSize: commissionSize.value, includeReversed: includeReversed.value }), listArProfitCommissionSummary()]); if (version === commissionVersion) { results.value = r.rows || []; commissionTotal.value = r.total || 0; commissionSummary.value = summary.data || [] } }
|
||||||
} finally { if (version === commissionVersion) commissionLoading.value = false }
|
} finally { if (version === commissionVersion) commissionLoading.value = false }
|
||||||
}
|
}
|
||||||
async function saveRate() {
|
async function saveRate() {
|
||||||
@@ -271,8 +321,9 @@ search()
|
|||||||
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); gap: 14px; }
|
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); gap: 14px; }
|
||||||
.metrics > div { min-width: 0; padding: 10px; background: var(--el-fill-color-lighter); color: var(--el-text-color-secondary); }
|
.metrics > div { min-width: 0; padding: 10px; background: var(--el-fill-color-lighter); color: var(--el-text-color-secondary); }
|
||||||
.metrics b { display: block; margin-top: 5px; color: var(--el-text-color-primary); }
|
.metrics b { display: block; margin-top: 5px; color: var(--el-text-color-primary); }
|
||||||
.summary-note { margin-top: 10px; color: var(--el-text-color-secondary); font-size: 12px; }
|
|
||||||
.heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
|
.heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
|
||||||
.table-title { font-size: 16px; font-weight: 500; }
|
.table-title { font-size: 16px; font-weight: 500; }
|
||||||
.sales-code { float: right; margin-left: 16px; color: var(--el-text-color-secondary); font-size: 12px; }
|
.sales-code { float: right; margin-left: 16px; color: var(--el-text-color-secondary); font-size: 12px; }
|
||||||
|
.ar-profit-actions { display: flex; align-items: center; justify-content: center; gap: 2px; white-space: nowrap; }
|
||||||
|
.ar-profit-actions .el-button { margin: 0; padding: 0 2px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user