✨ 完善应收账单毛利分析与提成展示

This commit is contained in:
caojia
2026-11-10 17:25:21 +08:00
parent 9004a1b921
commit 3e5ef25153
10 changed files with 215 additions and 31 deletions
@@ -2,6 +2,8 @@ package com.bms.report;
import static org.junit.jupiter.api.Assertions.assertEquals;
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.math.BigDecimal;
@@ -15,9 +17,49 @@ import com.bms.ar.mapper.ArCommissionMapper;
import com.bms.ar.profit.ArCommissionRate;
import com.bms.ar.profit.ArCommissionResult;
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
{
@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
void snapshotsTheRateOnceAtWriteoffAndPreservesMissingRates()
{
@@ -14,11 +14,58 @@ import com.bms.ar.profit.ArCommissionResult;
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
void parsesReportAndCommissionMappersAndScopesEachReportQuery() throws Exception
{
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))
{
@@ -40,11 +87,32 @@ class ArProfitMapperSqlTest
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();
assertTrue(bills.contains("from ar_bill b"));
assertTrue(bills.contains("b.bill_name"));
assertFalse(bills.contains("order_no"));
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("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")
.getBoundSql(Map.of("keyword", "sales"));
assertTrue(salesSql.getSql().contains("from sys_user u"));