diff --git a/bms-back/bms-admin/src/test/java/com/bms/report/ArCommissionServiceTest.java b/bms-back/bms-admin/src/test/java/com/bms/report/ArCommissionServiceTest.java index a7bb20a1..43beb1d4 100644 --- a/bms-back/bms-admin/src/test/java/com/bms/report/ArCommissionServiceTest.java +++ b/bms-back/bms-admin/src/test/java/com/bms/report/ArCommissionServiceTest.java @@ -1,9 +1,11 @@ package com.bms.report; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; 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 static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Proxy; import java.math.BigDecimal; @@ -11,18 +13,93 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import com.bms.common.core.domain.entity.SysUser; +import com.bms.common.core.domain.model.LoginUser; import com.bms.receivable.domain.ArBalanceWriteoff; import com.bms.receivable.domain.ArBill; +import com.bms.receivable.mapper.ArBillMapper; import com.bms.receivable.mapper.ArCommissionMapper; import com.bms.receivable.profit.ArCommissionRate; import com.bms.receivable.profit.ArCommissionResult; import com.bms.receivable.profit.ArCommissionService; import com.bms.receivable.profit.ArProfitScope; import com.bms.receivable.profit.ArProfitScopeResolver; +import com.bms.receivable.profit.ArWriteoffRate; import com.bms.common.exception.ServiceException; class ArCommissionServiceTest { + @Test + void rateConfirmationRequiresCompleteProfitDataBeforeAnyWrite() + { + List calls = new ArrayList<>(); + AtomicReference complete = new AtomicReference<>(0); + ArCommissionMapper mapper = (ArCommissionMapper) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { ArCommissionMapper.class }, (proxy, method, args) -> { + calls.add(method.getName()); + return switch (method.getName()) { + case "selectBillIdByWriteoffId" -> 25L; + case "isBillProfitDataComplete" -> complete.get(); + case "confirmMissingRate", "reverseResults" -> 1; + case "countMissingRates" -> 0; + case "selectSettlementInputs" -> List.of(); + default -> throw new AssertionError(method.getName()); + }; + }); + ArBill bill = new ArBill(); + bill.setProfitSalesUserId(7L); + bill.setGenerationStatus("READY"); + ArBillMapper billMapper = (ArBillMapper) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { ArBillMapper.class }, (proxy, method, args) -> { + if ("selectArBillByIdForUpdate".equals(method.getName())) return bill; + throw new AssertionError(method.getName()); + }); + ArProfitScopeResolver resolver = new ArProfitScopeResolver() { + @Override public ArProfitScope resolve() { return new ArProfitScope(true, false, false, 1L, 1L); } + }; + ArCommissionService service = new ArCommissionService(mapper, resolver, null, billMapper); + ArWriteoffRate input = new ArWriteoffRate(); + input.setWriteoffId(18L); + input.setRate(new BigDecimal("5")); + input.setRemark("已核对"); + for (String status : List.of("CONFIRMED", "PARTIAL_WRITTEN_OFF", "WRITTEN_OFF")) + { + bill.setStatus(status); + calls.clear(); + ServiceException error = assertThrows(ServiceException.class, () -> service.confirmMissingRate(input)); + assertTrue(error.getMessage().contains("请先处理后再确认比例")); + assertFalse(calls.contains("confirmMissingRate")); + assertFalse(calls.contains("reverseResults")); + } + complete.set(null); + assertThrows(ServiceException.class, () -> service.confirmMissingRate(input)); + complete.set(1); + SysUser user = new SysUser(); + user.setUserName("finance"); + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(new LoginUser(user, java.util.Set.of()), null)); + try + { + bill.setStatus("PARTIAL_WRITTEN_OFF"); + calls.clear(); + service.confirmMissingRate(input); + assertTrue(calls.contains("confirmMissingRate")); + assertFalse(calls.contains("reverseResults")); + bill.setStatus("WRITTEN_OFF"); + calls.clear(); + service.confirmMissingRate(input); + assertTrue(calls.indexOf("isBillProfitDataComplete") < calls.indexOf("confirmMissingRate")); + assertTrue(calls.contains("reverseResults")); + assertTrue(calls.contains("selectSettlementInputs")); + } + finally + { + SecurityContextHolder.clearContext(); + } + } + @Test void commissionReadsUseServerResolvedScopeAndManagementRemainsRestricted() { diff --git a/bms-back/bms-admin/src/test/java/com/bms/report/ArProfitMapperSqlTest.java b/bms-back/bms-admin/src/test/java/com/bms/report/ArProfitMapperSqlTest.java index 225d2433..790cc154 100644 --- a/bms-back/bms-admin/src/test/java/com/bms/report/ArProfitMapperSqlTest.java +++ b/bms-back/bms-admin/src/test/java/com/bms/report/ArProfitMapperSqlTest.java @@ -158,6 +158,17 @@ class ArProfitMapperSqlTest .getBoundSql(Map.of("billId", 1L)).getSql(); assertTrue(settlement.contains("w.status = 'EFFECTIVE'")); assertTrue(settlement.contains("b.status = 'WRITTEN_OFF'")); + String validation = configuration.getMappedStatement("com.bms.receivable.mapper.ArCommissionMapper.isBillProfitDataComplete") + .getBoundSql(Map.of("billId", 1L)).getSql(); + assertTrue(validation.contains("detail.ar_bill_id = ?")); + assertTrue(validation.contains("d.ar_bill_id = ?")); + assertTrue(validation.contains("a.detail_total = b.total_amount")); + assertTrue(validation.contains("f.sales_amount = d.total_amount")); + assertTrue(validation.contains("f.fee_currency = d.currency")); + assertTrue(validation.contains("f.product_complete = 1")); + assertTrue(validation.contains("f.redline_complete = 1")); + assertFalse(validation.contains("b.status = 'WRITTEN_OFF'")); + assertFalse(validation.contains("profit_calc_status")); var insert = configuration.getMappedStatement("com.bms.receivable.mapper.ArCommissionMapper.insertResults") .getBoundSql(Map.of("items", java.util.List.of(new ArCommissionResult(), new ArCommissionResult()), "operator", "test")); assertTrue(insert.getSql().contains("ar_profit_commission_result")); diff --git a/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArCommissionMapper.java b/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArCommissionMapper.java index b261411a..abf7a45e 100644 --- a/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArCommissionMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArCommissionMapper.java @@ -23,6 +23,7 @@ public interface ArCommissionMapper List selectMissingRates(); List selectWriteoffRates(@Param("billId") Long billId); Long selectBillIdByWriteoffId(@Param("writeoffId") Long writeoffId); + Integer isBillProfitDataComplete(@Param("billId") Long billId); int countMissingRates(@Param("billId") Long billId); int confirmMissingRate(@Param("writeoffId") Long writeoffId, @Param("rate") java.math.BigDecimal rate, @Param("operator") String operator, @Param("remark") String remark); diff --git a/bms-back/bms-system/src/main/java/com/bms/receivable/profit/ArCommissionService.java b/bms-back/bms-system/src/main/java/com/bms/receivable/profit/ArCommissionService.java index e6b9200c..2b38c20d 100644 --- a/bms-back/bms-system/src/main/java/com/bms/receivable/profit/ArCommissionService.java +++ b/bms-back/bms-system/src/main/java/com/bms/receivable/profit/ArCommissionService.java @@ -134,6 +134,8 @@ public class ArCommissionService || ArBillStatus.PARTIAL_WRITTEN_OFF.getCode().equals(bill.getStatus()) || ArBillStatus.WRITTEN_OFF.getCode().equals(bill.getStatus()))) throw new ServiceException("账单未生成完成或状态不允许确认提成比例"); + if (!Integer.valueOf(1).equals(mapper.isBillProfitDataComplete(billId))) + throw new ServiceException("账单价格、金额或币种数据不完整或不一致,请先处理后再确认比例"); String operator = SecurityUtils.getUsername(); if (mapper.confirmMissingRate(input.getWriteoffId(), input.getRate(), operator, input.getRemark().trim()) != 1) throw new ServiceException("比例已确认或核销状态已变化,请刷新"); diff --git a/bms-back/bms-system/src/main/resources/mapper/receivable/ArCommissionMapper.xml b/bms-back/bms-system/src/main/resources/mapper/receivable/ArCommissionMapper.xml index 41f26222..a20ebb60 100644 --- a/bms-back/bms-system/src/main/resources/mapper/receivable/ArCommissionMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/receivable/ArCommissionMapper.xml @@ -79,6 +79,41 @@ + + + select fee.detail_id, case when count(fee.amount) = count(*) then sum(fee.amount) else null end as sales_amount, + case when count(fee.currency) = count(*) then count(distinct fee.currency) else 0 end as currency_count, + min(fee.currency) as fee_currency, + case when count(fee.product_amount) = count(*) then sum(fee.product_amount) else null end as product_amount, + case when count(fee.redline_amount) = count(*) then sum(fee.redline_amount) else null end as redline_amount, + case when count(fee.product_amount) = count(*) then 1 else 0 end as product_complete, + case when count(fee.redline_amount) = count(*) then 1 else 0 end as redline_complete + from ar_bill_detail_fee fee join ar_bill_detail detail on detail.detail_id = fee.detail_id + where detail.ar_bill_id = #{billId} group by fee.detail_id + + +