diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/LabelPackageController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/LabelPackageController.java index 69282591..e8fab8af 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/LabelPackageController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/LabelPackageController.java @@ -24,6 +24,7 @@ import com.bms.common.core.domain.AjaxResult; import com.bms.common.core.page.TableDataInfo; import com.bms.common.enums.BusinessType; import com.bms.common.exception.ServiceException; +import com.bms.price.domain.PayProduct; import com.bms.price.domain.PaySaveCommand; import com.bms.price.domain.PrcCustomerPriceSet; import com.bms.price.domain.PrcCustomerPriceSetBatch; @@ -208,6 +209,15 @@ public class LabelPackageController extends BaseController return success(payProductService.selectPayProductDetail(productId)); } + /** 保存打单产品明细(重量段/附加费等),按当前 term 覆盖子表 */ + @PreAuthorize("@ss.hasPermi('price:label:edit')") + @Log(title = "打单产品明细", businessType = BusinessType.UPDATE) + @PutMapping("/product") + public AjaxResult updateProduct(@RequestBody PayProduct product) + { + return toAjax(payProductService.updateDetail(product)); + } + /** 查询打单产品已绑定渠道(默认应收 AR) */ @PreAuthorize("@ss.hasPermi('price:label:query')") @GetMapping("/product/{productId}/channels") diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java index 8a5495c7..885c230b 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java @@ -173,10 +173,16 @@ public class ArBillController extends BaseController { AjaxResult result = success(); result.put("rows", pageData.getRows()); result.put("total", pageData.getTotal()); - result.put("totalAmount", billService.sumPreviewBill(query)); return result; } + /** 按需汇总生成预览范围内的应收金额。 */ + @PreAuthorize("@ss.hasPermi('ar:bill:generate')") + @PostMapping("/generate/preview/amount") + public AjaxResult previewAmount(@RequestBody ArBillGenerateQuery query) { + return success(billService.sumPreviewBill(query)); + } + /** 按生成预览使用的相同筛选条件提交异步导出任务。 */ @Log(title = "应收账单生成订单", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('ar:bill:generate')") diff --git a/bms-back/bms-admin/src/test/java/com/bms/receivable/ArBillConfirmTest.java b/bms-back/bms-admin/src/test/java/com/bms/receivable/ArBillConfirmTest.java new file mode 100644 index 00000000..5a956f77 --- /dev/null +++ b/bms-back/bms-admin/src/test/java/com/bms/receivable/ArBillConfirmTest.java @@ -0,0 +1,122 @@ +package com.bms.receivable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.util.ReflectionTestUtils; + +import com.bms.common.core.domain.entity.SysUser; +import com.bms.common.core.domain.model.LoginUser; +import com.bms.common.exception.ServiceException; +import com.bms.receivable.domain.ArBill; +import com.bms.receivable.domain.ArBillLog; +import com.bms.receivable.mapper.ArBillDetailMapper; +import com.bms.receivable.mapper.ArBillExportMapper; +import com.bms.receivable.mapper.ArBillMapper; +import com.bms.receivable.security.ArBillScopeResolver; +import com.bms.receivable.service.IArBillLogService; +import com.bms.receivable.service.impl.ArBillServiceImpl; + +class ArBillConfirmTest { + private final ArBillMapper billMapper = mock(ArBillMapper.class); + private final ArBillDetailMapper detailMapper = mock(ArBillDetailMapper.class); + private final ArBillExportMapper exportMapper = mock(ArBillExportMapper.class); + private final ArBillScopeResolver scope = mock(ArBillScopeResolver.class); + private final IArBillLogService logService = mock(IArBillLogService.class); + private final ArBillServiceImpl service = new ArBillServiceImpl(); + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(service, "billMapper", billMapper); + ReflectionTestUtils.setField(service, "detailMapper", detailMapper); + ReflectionTestUtils.setField(service, "exportMapper", exportMapper); + ReflectionTestUtils.setField(service, "billScope", scope); + ReflectionTestUtils.setField(service, "logService", logService); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken( + new LoginUser(7L, 1L, new SysUser(), Set.of()), null)); + } + + @AfterEach + void clearSecurity() { + SecurityContextHolder.clearContext(); + } + + @Test + void readyDraftCanBeConfirmedWithoutReadingDetails() { + ArBill draft = bill("DRAFT", "READY"); + draft.setTotalAmount(new BigDecimal("123.45")); + ArBill confirmed = bill("CONFIRMED", "READY"); + when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn(draft); + when(billMapper.updateArBillStatus(11L, "CONFIRMED", null, "finance", "DRAFT")).thenReturn(1); + when(billMapper.selectArBillByIdScoped(11L, null)).thenReturn(confirmed); + when(exportMapper.selectByBillId(11L)).thenReturn(List.of()); + + assertSame(confirmed, service.confirmBill(11L, null, "finance", "request-1")); + verify(scope).requireBillAccessible(11L); + verifyNoInteractions(detailMapper); + verify(logService).record(any(ArBillLog.class)); + } + + @Test + void unfinishedOrNonDraftBillStillCannotBeConfirmed() { + when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn( + bill("DRAFT", "GENERATING"), bill("CONFIRMED", "READY")); + + ServiceException unfinished = assertThrows(ServiceException.class, + () -> service.confirmBill(11L, null, "finance", "request-1")); + assertEquals("账单尚未生成完成,暂不能确认", unfinished.getMessage()); + ServiceException notDraft = assertThrows(ServiceException.class, + () -> service.confirmBill(11L, null, "finance", "request-2")); + assertEquals("只有草稿账单可以确认,当前状态为 CONFIRMED", notDraft.getMessage()); + verify(billMapper, never()).updateArBillStatus(eq(11L), any(), nullable(String.class), any(), any()); + verifyNoInteractions(detailMapper); + } + + @Test + void inaccessibleBillStillCannotBeConfirmed() { + when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn(bill("DRAFT", "READY")); + doThrow(new ServiceException("账单不存在或无权访问")).when(scope).requireBillAccessible(11L); + + assertThrows(ServiceException.class, () -> service.confirmBill(11L, null, "finance", "request-1")); + verify(billMapper, never()).updateArBillStatus(eq(11L), any(), nullable(String.class), any(), any()); + verifyNoInteractions(detailMapper); + } + + @Test + void concurrentStatusChangeStillPreventsConfirmation() { + when(billMapper.selectArBillByIdForUpdate(11L)).thenReturn(bill("DRAFT", "READY")); + + ServiceException error = assertThrows(ServiceException.class, + () -> service.confirmBill(11L, null, "finance", "request-1")); + assertEquals("账单状态已发生变化,请刷新后重试", error.getMessage()); + verify(logService, never()).record(any(ArBillLog.class)); + verifyNoInteractions(detailMapper); + } + + private static ArBill bill(String status, String generationStatus) { + ArBill bill = new ArBill(); + bill.setArBillId(11L); + bill.setStatus(status); + bill.setGenerationStatus(generationStatus); + return bill; + } +} diff --git a/bms-back/bms-admin/src/test/java/com/bms/receivable/ArBillGeneratePreviewTest.java b/bms-back/bms-admin/src/test/java/com/bms/receivable/ArBillGeneratePreviewTest.java new file mode 100644 index 00000000..65820fc4 --- /dev/null +++ b/bms-back/bms-admin/src/test/java/com/bms/receivable/ArBillGeneratePreviewTest.java @@ -0,0 +1,112 @@ +package com.bms.receivable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.test.util.ReflectionTestUtils; + +import com.github.pagehelper.PageHelper; +import com.bms.common.config.BmsConfig; +import com.bms.common.core.domain.AjaxResult; +import com.bms.common.core.redis.RedisCache; +import com.bms.price.domain.PrcOrderBill; +import com.bms.receivable.domain.vo.ArBillGenerateQuery; +import com.bms.receivable.security.ArBillScopeResolver; +import com.bms.receivable.service.IArBillService; +import com.bms.receivable.task.ArBillGeneratePreviewExportRunner; +import com.bms.web.controller.receivable.ArBillController; + +class ArBillGeneratePreviewTest { + @TempDir + Path tempDir; + + @Test + void previewDoesNotCalculateAmountUntilAmountEndpointIsCalled() { + IArBillService service = mock(IArBillService.class); + ArBillController controller = new ArBillController(); + ReflectionTestUtils.setField(controller, "billService", service); + ArBillGenerateQuery query = new ArBillGenerateQuery(); + when(service.previewBill(query)).thenReturn(List.of(new PrcOrderBill())); + when(service.sumPreviewBill(query)).thenReturn(new BigDecimal("12.34")); + + try { + AjaxResult preview = controller.preview(query); + assertFalse(preview.containsKey("totalAmount")); + verify(service, never()).sumPreviewBill(query); + } finally { + PageHelper.clearPage(); + } + + assertEquals(new BigDecimal("12.34"), controller.previewAmount(query).get("data")); + verify(service).sumPreviewBill(query); + } + + @Test + void exportEndsWithTheSumOfTheExportedAmounts() throws Exception { + String previousProfile = BmsConfig.getProfile(); + try { + new BmsConfig().setProfile(tempDir.toString()); + IArBillService service = mock(IArBillService.class); + ArBillGenerateQuery query = new ArBillGenerateQuery(); + when(service.previewBill(same(query), isNull())).thenReturn(List.of( + bill("A", "0.10"), bill("B", "0.20"), bill("C", "-0.05"), bill("D", null))); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor() { + @Override + public void execute(Runnable task) { + task.run(); + } + }; + ArBillGeneratePreviewExportRunner runner = new ArBillGeneratePreviewExportRunner( + service, mock(RedisCache.class), mock(ArBillScopeResolver.class), executor); + for (String dateType : List.of("BIZ_DATE", "PICKUP_DATE")) { + query.setDateType(dateType); + String taskId = runner.submit(query, 1L); + Path file = tempDir.resolve("download/arBillGeneratePreviewExport/" + taskId + ".xlsx"); + + try (InputStream in = Files.newInputStream(file); Workbook workbook = WorkbookFactory.create(in)) { + Sheet sheet = workbook.getSheetAt(0); + Row header = sheet.getRow(0); + Row lastRow = sheet.getRow(sheet.getLastRowNum()); + assertEquals("合计", lastRow.getCell(column(header, "订单号")).getStringCellValue()); + assertEquals(0.25, lastRow.getCell(column(header, "应收金额")).getNumericCellValue(), 0.000001); + assertEquals(5, sheet.getLastRowNum()); + } + } + } finally { + new BmsConfig().setProfile(previousProfile); + } + } + + private static PrcOrderBill bill(String orderNo, String amount) { + PrcOrderBill bill = new PrcOrderBill(); + bill.setOrderNo(orderNo); + bill.setIncomeTotal(amount == null ? null : new BigDecimal(amount)); + return bill; + } + + private static int column(Row header, String name) { + for (int i = 0; i < header.getLastCellNum(); i++) { + if (name.equals(header.getCell(i).getStringCellValue())) return i; + } + throw new AssertionError("缺少导出列: " + name); + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PaySurcharge.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PaySurcharge.java index 3c5fcae6..c44a1167 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PaySurcharge.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PaySurcharge.java @@ -73,10 +73,4 @@ public class PaySurcharge private BigDecimal weightMax; private Integer sortNo; - - /** - * 是否计费并落库:false 不写入 pay_surcharge;null/缺省视为要计费(兼容应付全量保存)。 - * 仅作为前端传参,不落库字段。 - */ - private Boolean needBill; } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PayProductServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PayProductServiceImpl.java index 85c56002..b9e60317 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PayProductServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PayProductServiceImpl.java @@ -461,8 +461,6 @@ public class PayProductServiceImpl implements IPayProductService { product.setExtraLines(new ArrayList<>()); } - // 计数前先按「是否计费」过滤,与落库行数一致 - retainBillableSurcharges(product); product.setWeightCount(product.getWeightBands().size()); product.setSurchargeCount(product.getSurcharges().size()); product.setExtraCount(product.getExtraLines().size()); @@ -1531,7 +1529,6 @@ public class PayProductServiceImpl implements IPayProductService private void insertSurcharges(PayProduct product) { - retainBillableSurcharges(product); List rows = product.getSurcharges(); if (rows == null || rows.isEmpty()) { @@ -1550,27 +1547,6 @@ public class PayProductServiceImpl implements IPayProductService } } - /** - * 仅保留需要计费的附加费:needBill=false 不入库;null 视为要计费。 - */ - private void retainBillableSurcharges(PayProduct product) - { - List rows = product.getSurcharges(); - if (rows == null || rows.isEmpty()) - { - return; - } - List kept = new ArrayList(); - for (PaySurcharge fee : rows) - { - if (fee != null && !Boolean.FALSE.equals(fee.getNeedBill())) - { - kept.add(fee); - } - } - product.setSurcharges(kept); - } - /** * 按附加费名称映射 prc_surcharge.sc_id;币种不能为空:附加费 -> 价卡 -> EUR。 */ diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java index 5ae57441..1fe05347 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java @@ -3127,7 +3127,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService } /** - * 业务规则计费重量(VOL_8000 等)。 + * 业务规则计费重量(THREE_SIDE / VOL_8000)。 * 如需禁用:注释掉 resolveWeightByRule 调用,始终返回 baseWeight。 */ private BigDecimal applyBizRuleWeight(TmsPriceBillRequest request, BigDecimal baseWeight) diff --git a/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArBillDetailMapper.java b/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArBillDetailMapper.java index f361e703..b6a39366 100644 --- a/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArBillDetailMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/receivable/mapper/ArBillDetailMapper.java @@ -16,11 +16,6 @@ public interface ArBillDetailMapper { List selectByBillIdAndSourceIds(@Param("arBillId") Long arBillId, @Param("sourceBillIds") List sourceBillIds); - /** - * 查询账单明细及费用明细,避免详情页逐层查询。 - */ - List selectByBillIdWithFees(Long arBillId); - List selectExportPage(@Param("arBillId") Long arBillId, @Param("lastDetailNo") Integer lastDetailNo, @Param("pageSize") Integer pageSize); diff --git a/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java index 8e27d0be..808b8933 100644 --- a/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java @@ -985,7 +985,7 @@ public class ArBillServiceImpl implements IArBillService { } /** - * 确认已完成生成且明细金额校验通过的草稿账单。 + * 确认已完成生成的草稿账单。 */ @Override @Transactional(rollbackFor = Exception.class) @@ -1007,11 +1007,6 @@ public class ArBillServiceImpl implements IArBillService { bill.getStatus(), "只有草稿账单可以确认,当前状态为 " + bill.getStatus(), operator, requestId); } - if (!validateDetails(arBillId, bill.getTotalAmount())) { - throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM, - bill.getStatus(), - "账单费用明细汇总与订单或账单总额不一致", operator, requestId); - } if (billMapper.updateArBillStatus(arBillId, ArBillStatus.CONFIRMED.getCode(), StringUtils.isEmpty(request == null ? null : request.getRemark()) ? bill.getRemark() : request.getRemark(), operator, ArBillStatus.DRAFT.getCode()) != 1) { @@ -1313,26 +1308,6 @@ public class ArBillServiceImpl implements IArBillService { return message.length() > 500 ? message.substring(0, 500) : message; } - private boolean validateDetails(Long arBillId, BigDecimal billTotal) { - List details = detailMapper.selectByBillIdWithFees(arBillId); - BigDecimal detailTotal = BigDecimal.ZERO; - for (ArBillDetail detail : details) { - BigDecimal feeTotal = BigDecimal.ZERO; - List fees = detail.getFees(); - if (fees == null) { - fees = new ArrayList(); - } - for (ArBillDetailFee fee : fees) { - feeTotal = feeTotal.add(nvl(fee.getAmount())); - } - if (feeTotal.compareTo(nvl(detail.getTotalAmount())) != 0) { - return false; - } - detailTotal = detailTotal.add(nvl(detail.getTotalAmount())); - } - return !details.isEmpty() && detailTotal.compareTo(nvl(billTotal)) == 0; - } - private ArBillDetail toDetail(ArBill bill, PrcOrderBill source, PrcIncomeBill sourceIncomeBill, int detailNo, String operator) { ArBillDetail detail = new ArBillDetail(); diff --git a/bms-back/bms-system/src/main/java/com/bms/receivable/task/ArBillGeneratePreviewExportRunner.java b/bms-back/bms-system/src/main/java/com/bms/receivable/task/ArBillGeneratePreviewExportRunner.java index 39c8a12c..6b7ad2df 100644 --- a/bms-back/bms-system/src/main/java/com/bms/receivable/task/ArBillGeneratePreviewExportRunner.java +++ b/bms-back/bms-system/src/main/java/com/bms/receivable/task/ArBillGeneratePreviewExportRunner.java @@ -1,6 +1,7 @@ package com.bms.receivable.task; import java.io.OutputStream; +import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -75,10 +76,18 @@ public class ArBillGeneratePreviewExportRunner { put(taskId, userId, "RUNNING", 5, "正在查询订单", null, 0, fileName, null); List bills = billService.previewBill(query, scope); put(taskId, userId, "RUNNING", 40, "正在生成 Excel", null, bills.size(), fileName, null); - List rows = new ArrayList<>(bills.size()); + List rows = new ArrayList<>(bills.size() + 1); + BigDecimal totalAmount = BigDecimal.ZERO; for (PrcOrderBill bill : bills) { rows.add(ArBillGeneratePreviewExportRow.from(bill)); + if (bill.getIncomeTotal() != null) { + totalAmount = totalAmount.add(bill.getIncomeTotal()); + } } + ArBillGeneratePreviewExportRow totalRow = new ArBillGeneratePreviewExportRow(); + totalRow.setOrderNo("合计"); + totalRow.setIncomeTotal(totalAmount); + rows.add(totalRow); ExcelUtil util = new ExcelUtil<>(ArBillGeneratePreviewExportRow.class); if (ArBillDateType.PICKUP_DATE == ArBillDateType.of(query.getDateType())) { util.hideColumn("bizDate"); diff --git a/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsBizRuleService.java b/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsBizRuleService.java index f2f17da3..1353209d 100644 --- a/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsBizRuleService.java +++ b/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsBizRuleService.java @@ -21,7 +21,7 @@ public interface IBmsBizRuleService int deleteByIds(Long[] ruleIds); /** - * 将生效的重量规则(目前为 VOL_8000)应用到计费请求。 + * 将生效的重量规则(THREE_SIDE / VOL_8000)应用到计费请求。 * @return 规则命中时返回覆盖后的计费重量;无规则适用时返回 null */ BigDecimal resolveWeightByRule(TmsPriceBillRequest request, BigDecimal systemWeight); diff --git a/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsBizRuleServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsBizRuleServiceImpl.java index 253b3991..8fad44a9 100644 --- a/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsBizRuleServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsBizRuleServiceImpl.java @@ -136,8 +136,12 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService { return null; } - // VOL_8000 在本地入库 / 计费时应用。THREE_SIDE 仅作为配置 - // (上游已应用该产品规则)。 + // 先三边之和取重,未命中再走 VOL_8000 + BigDecimal threeSide = resolveThreeSideWeight(request, systemWeight); + if (threeSide != null) + { + return threeSide; + } BigDecimal vol8000 = resolveVol8000(request, systemWeight); if (vol8000 != null) { @@ -146,6 +150,47 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService return null; } + /** + * 三边之和取重:命中 THREE_SIDE 规则时, + * L+W+H < 上限取本地收货重;>= 上限取已有计费重。 + * + * @param existingChargeWeight 当前已有计费重(未被三边规则覆盖前) + * @return 命中规则时返回计费重;未命中返回 null + */ + private BigDecimal resolveThreeSideWeight(TmsPriceBillRequest request, BigDecimal existingChargeWeight) + { + List rules = bizRuleMapper.selectActiveByType(RULE_TYPE_THREE_SIDE); + if (rules == null || rules.isEmpty()) + { + return null; + } + for (BmsBizRule rule : rules) + { + if (!matchThreeSideDims(rule, request, existingChargeWeight)) + { + continue; + } + if (rule.getSideSumMax() == null) + { + continue; + } + BigDecimal sum = sideSum(request); + if (sum == null) + { + continue; + } + // 三边之和 < 上限(如 90):取本地收货重;无收货重则保留已有计费重 + if (sum.compareTo(rule.getSideSumMax()) < 0) + { + BigDecimal receiveWeight = firstPositive(request.getReceiveWeight()); + return receiveWeight != null ? receiveWeight : existingChargeWeight; + } + // 三边之和 >= 上限:保留已有计费重 + return existingChargeWeight; + } + return null; + } + /** * VOL_8000:chargeWeight = max(L*W*H/8000, receive_weight)。 * 将体积重(/8000)与收货重比较。 @@ -273,7 +318,10 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService return new ArrayList(set); } - private boolean matchThreeSide(BmsBizRule rule, TmsPriceBillRequest request, BigDecimal systemWeight) + /** + * 三边规则维度匹配(不包含三边和阈值判断)。 + */ + private boolean matchThreeSideDims(BmsBizRule rule, TmsPriceBillRequest request, BigDecimal systemWeight) { if (rule == null) { @@ -292,19 +340,7 @@ public class BmsBizRuleServiceImpl implements IBmsBizRuleService return false; } BigDecimal checkWeight = firstPositive(request.getReceiveWeight(), systemWeight, request.getWeight()); - if (!inWeightRange(checkWeight, rule.getWeightMin(), rule.getWeightMax())) - { - return false; - } - if (rule.getSideSumMax() != null) - { - BigDecimal sum = sideSum(request); - if (sum == null || sum.compareTo(rule.getSideSumMax()) >= 0) - { - return false; - } - } - return true; + return inWeightRange(checkWeight, rule.getWeightMin(), rule.getWeightMax()); } private boolean dimMatch(String requestCode, String ruleCode, boolean tryProductMap) diff --git a/bms-back/bms-system/src/main/resources/mapper/receivable/ArBillDetailMapper.xml b/bms-back/bms-system/src/main/resources/mapper/receivable/ArBillDetailMapper.xml index b54acb31..710ffbaf 100644 --- a/bms-back/bms-system/src/main/resources/mapper/receivable/ArBillDetailMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/receivable/ArBillDetailMapper.xml @@ -69,49 +69,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by detail_no - - - - - - - - - - - - - - - - - - - - - - - - - -