diff --git a/src/main/java/com/cmsen/ticket/application/manager/service/impl/SupplierServiceImpl.java b/src/main/java/com/cmsen/ticket/application/manager/service/impl/SupplierServiceImpl.java index c7c39a1..123cb57 100644 --- a/src/main/java/com/cmsen/ticket/application/manager/service/impl/SupplierServiceImpl.java +++ b/src/main/java/com/cmsen/ticket/application/manager/service/impl/SupplierServiceImpl.java @@ -28,12 +28,10 @@ import com.cmsen.ticket.application.manager.vo.GetMaterialParamVo; import com.cmsen.ticket.application.manager.vo.MatterV3ResponseVo; import com.cmsen.ticket.application.manager.vo.SupplierInvoiceMatchOrderVo; import com.cmsen.ticket.application.manager.vo.SupplierMaterialDefineVo; -import com.cmsen.ticket.application.service.ConcurService; import com.cmsen.ticket.application.service.SupplierOutService; import com.cmsen.ticket.bestsign.util.InterfaceUtil; import com.cmsen.ticket.domain.dto.*; import com.cmsen.ticket.domain.entity.*; -import com.cmsen.ticket.domain.entity.concur.ConcurConfigEntity; import com.cmsen.ticket.domain.entity.manager.*; import com.cmsen.ticket.domain.mapper.*; import com.cmsen.ticket.domain.mapper.manager.*; @@ -162,11 +160,7 @@ public class SupplierServiceImpl implements SupplierService { @Resource private AccountPeriodMapper accountPeriodMapper; - @Resource - private ConcurService concurService; - @Resource - private ConcurConfigEntity concurConfig; @Resource private TaxMappingMapper taxMappingMapper; @@ -771,14 +765,7 @@ public class SupplierServiceImpl implements SupplierService { } } - @Override - public RestResponse matchInvoices(String numberS, String numberE, Integer invoiceType) { - List list = corporateInvoiceMapper.supplierInvoiceMatchInvoiceList(numberS, numberE, invoiceType); - for (InvoiceItemListDto invoiceItemListDto : list) { - invoiceItemListDto.setUsedMoney(supplierOrderItemMatchingMapper.getUsedAmountByItemId(invoiceItemListDto.getId())); - } - return RestResponse.success(list); - } + @Transactional(rollbackFor = Exception.class) @Override @@ -2933,497 +2920,335 @@ public class SupplierServiceImpl implements SupplierService { // 2025-05-27 List stateList = supplierStateMapper.listByNumber(invoiceNumber); - List stateItems = new ArrayList<>();//supplierInvoiceMatchOrderVo if (!stateList.isEmpty()) { // 对账单逻辑 supplier_state 优先匹配原则 List itemList = corporateInvoiceItemsMapper.selectItemsByPid(invoiceId); + List stateInsertList = new ArrayList<>(); + // 需要重算差异的发票明细行(按 id 去重,避免同一行被反复计算) + Map stateDirtyItems = new LinkedHashMap<>(); for (SupplierState supplierState : stateList) { String receiveNo = supplierState.getExt01(); - String orderNo = supplierState.getPo(); - String stateItemNo = supplierState.getItemNo(); - String desc = supplierState.getDesc(); - BigDecimal amount = new BigDecimal(supplierState.getAmount()); - BigDecimal quantity = new BigDecimal(supplierState.getQuantity()); - BigDecimal price = amount.divide(quantity).setScale(2, BigDecimal.ROUND_HALF_UP); - if (org.apache.commons.lang3.StringUtils.isNotEmpty(receiveNo)) { - //4. 取这个发票的state第一行,如果有receive number(放在ext01上), - // 则取这个receive number的值去查收货表,获取相关信息,只有数量从state上取,金额计算。 - SupplierOrder stateOrder = supplierOrderMapper.selectByState(receiveNo, orderNo, stateItemNo); - if (null != stateOrder) { - // 可以匹配 查找items的name TODO 目前简单点是否包含 state的des描述 - boolean matchedFlag = false; - for (CorporateInvoiceItems item : itemList) { - BigDecimal itemTotal = new BigDecimal(item.getTotal()); - String name = item.getName(); - if (name.indexOf(desc) > -1) { - stateItems.add(item); - matchedFlag = true; - // 简单的表示匹配成功 - //插入supplier_order_item_matching - SupplierOrderItemMatching matching = new SupplierOrderItemMatching(); - matching.setInvoiceId(invoiceId); - matching.setItemId(item.getId()); - matching.setOrderId(stateOrder.getId()); - matching.setName(desc); - matching.setSpecification(item.getSpecification()); - matching.setUnit(supplierState.getUom()); - // 20220510price取值修改 - matching.setPrice(price); - // 20220508price取值修改 - //matching.setPrice(price.setScale(6, BigDecimal.ROUND_HALF_UP)); - matching.setQuantity(supplierState.getQuantity()); - log.info(matching.getQuantity()); - matching.setTotal(amount); - matching.setDeductionAmount(BigDecimal.ZERO); - matching.setDeductionTax(BigDecimal.ZERO); - matching.setDeductionTaxRate(item.getDeductionTaxRate()); - // tax 不是空并且 tax和deduction_tax - if (StringUtil.isNotBlank(item.getTax())) { - if (itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())).compareTo(BigDecimal.ZERO) == 1) { - BigDecimal percent = matching.getTotal().divide(itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())), 10, BigDecimal.ROUND_HALF_UP); - matching.setTax(new BigDecimal(item.getTax()).add(new BigDecimal(item.getDeductionTax() == null ? "0" : item.getDeductionTax())).multiply(percent)); - } else { - matching.setTax(BigDecimal.ZERO); - } - } - - matching.setReceivedTotal(amount); - matching.setTaxRate(item.getTaxRate()); - matching.setMatchingStatus(0); - matching.setReceiveNo(supplierState.getExt01()); - matching.setOrderNo(supplierState.getPo()); - matching.setItemNo(supplierState.getItemNo()); - if (new BigDecimal(matching.getQuantity()).compareTo(BigDecimal.ZERO) == 0) { - break; - } - // 插入匹配数据 - supplierOrderItemMatchingMapper.insert(matching); - } - } - if (!matchedFlag) { - CorporateInvoiceItems item = itemList.get(0); - stateItems.add(item); - BigDecimal itemTotal = new BigDecimal(item.getTotal()); - String name = item.getName(); - matchedFlag = true; - // 简单的表示匹配成功 - //插入supplier_order_item_matching - SupplierOrderItemMatching matching = new SupplierOrderItemMatching(); - matching.setInvoiceId(invoiceId); - matching.setItemId(item.getId()); - matching.setOrderId(stateOrder.getId()); - matching.setName(desc); - matching.setSpecification(item.getSpecification()); - matching.setUnit(supplierState.getUom()); - // 20220510price取值修改 - matching.setPrice(price); - // 20220508price取值修改 - //matching.setPrice(price.setScale(6, BigDecimal.ROUND_HALF_UP)); - matching.setQuantity(supplierState.getQuantity()); - log.info(matching.getQuantity()); - matching.setTotal(amount); - matching.setDeductionAmount(BigDecimal.ZERO); - matching.setDeductionTax(BigDecimal.ZERO); - matching.setDeductionTaxRate(item.getDeductionTaxRate()); - // tax 不是空并且 tax和deduction_tax - if (StringUtil.isNotBlank(item.getTax())) { - if (itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())).compareTo(BigDecimal.ZERO) == 1) { - BigDecimal percent = matching.getTotal().divide(itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())), 10, BigDecimal.ROUND_HALF_UP); - matching.setTax(new BigDecimal(item.getTax()).add(new BigDecimal(item.getDeductionTax() == null ? "0" : item.getDeductionTax())).multiply(percent)); - } else { - matching.setTax(BigDecimal.ZERO); - } - } - - matching.setReceivedTotal(amount); - matching.setTaxRate(item.getTaxRate()); - matching.setMatchingStatus(0); - matching.setReceiveNo(supplierState.getExt01()); - matching.setOrderNo(supplierState.getPo()); - matching.setItemNo(supplierState.getItemNo()); - if (new BigDecimal(matching.getQuantity()).compareTo(BigDecimal.ZERO) == 0) { - break; - } - // 插入匹配数据 - supplierOrderItemMatchingMapper.insert(matching); - } - } - // 20220430更新diff - for (CorporateInvoiceItems tmpItem : stateItems) { - updateDifference(corporateInvoice, tmpItem); - } - - } else { + if (org.apache.commons.lang3.StringUtils.isEmpty(receiveNo)) { //如果没有receive,先根据PO订单号 + 料号获取收货表的可匹配数量,先找下是否有同样的数量, // 如果有就匹配这一行,没有就按照先进先出来匹配,插入supplier order item matching - - + continue; + } + // 数量为 0 时不能作为单价分母,直接跳过 + BigDecimal quantity = dec(supplierState.getQuantity()); + if (quantity.signum() == 0) { + log.warn("对账单数量为0,跳过:invoiceId={}, po={}, itemNo={}", invoiceId, + supplierState.getPo(), supplierState.getItemNo()); + continue; + } + // 金额为空时不写 0 值数据,直接跳过 + if (StringUtil.isBlank(supplierState.getAmount())) { + log.warn("对账单金额为空,跳过:invoiceId={}, po={}, itemNo={}", invoiceId, + supplierState.getPo(), supplierState.getItemNo()); + continue; } + BigDecimal amount = dec(supplierState.getAmount()); + BigDecimal price = amount.divide(quantity, 2, RoundingMode.HALF_UP); + //4. 取这个发票的state第一行,如果有receive number(放在ext01上), + // 则取这个receive number的值去查收货表,获取相关信息,只有数量从state上取,金额计算。 + SupplierOrder stateOrder = supplierOrderMapper.selectByState(receiveNo, supplierState.getPo(), supplierState.getItemNo()); + if (null == stateOrder) { + continue; + } + // 可以匹配 查找items的name :优先名称包含 state 描述的行,未命中则兜底第一行;命中即停 + CorporateInvoiceItems item = findStateItem(itemList, supplierState.getDesc()); + if (null == item) { + log.warn("对账单未匹配到发票明细行:invoiceId={}, po={}, itemNo={}", invoiceId, + supplierState.getPo(), supplierState.getItemNo()); + continue; + } + // 简单的表示匹配成功,插入 supplier_order_item_matching + stateInsertList.add(buildStateMatching(invoiceId, item, supplierState, stateOrder, price, amount)); + stateDirtyItems.put(item.getId(), item); + } + // 批量插入匹配数据 + if (!stateInsertList.isEmpty()) { + supplierOrderItemMatchingMapper.insertBatch(stateInsertList); + } + // 20220430更新diff + for (CorporateInvoiceItems tmpItem : stateDirtyItems.values()) { + updateDifference(corporateInvoice, tmpItem); } - - } // 2025-05-29 如果是服务类行 按金额匹配 根据po查询永远匹配到第一个item上面。 // 根据PO 查询 supplierOrder if (0 == matchType && corporateInvoice.getInvoiceType() == 1) { - List insertList2 = new ArrayList<>(); - Map mapParams = new HashMap<>(); - if (!supplier.getSupplierSpecial()) { - mapParams.put("supplierCode", supplierCode); - } - mapParams.put("all", 0); - mapParams.put("isOutIn", 1); - String[] orderNos = supplierMatching.getOrderNo().split(","); - mapParams.put("pos", orderNos); - List orderList = supplierOrderMapper.listByPOs(mapParams); - List items = corporateInvoiceItemsMapper.selectItemsByPid(invoiceId); - if (!items.isEmpty()) { - CorporateInvoiceItems item = items.get(0); - BigDecimal itemTotal = new BigDecimal(item.getTotal()); - for (SupplierOrder order : orderList) { - log.info("order.getUnusedDquantity:{}", order.getUnUsedQuantity()); - order.setUsedAmount(supplierOrderItemMatchingMapper.getUsedAmountByOrderId(order.getId())); - order.setUsedQuantity(supplierOrderItemMatchingMapper.getUsedQuantityByOrderId(order.getId())); - //插入supplier_order_item_matching - SupplierOrderItemMatching matching = new SupplierOrderItemMatching(); - matching.setInvoiceId(invoiceId); - matching.setItemId(item.getId()); - matching.setOrderId(order.getId()); - matching.setName(order.getGoods()); - matching.setSpecification(item.getSpecification()); - matching.setUnit(order.getUom()); - BigDecimal price = order.getUnitPrice(); - // 20220510price取值修改 - matching.setPrice(order.getUnitPrice()); - if (0 == corporateInvoice.getMatchType()) { - BigDecimal percent = order.getUnUsedAmount().divide(itemTotal, 5, BigDecimal.ROUND_HALF_UP); - matching.setTotal(order.getUnUsedAmount() == null ? BigDecimal.ZERO : order.getUnUsedAmount()); - if (StringUtil.isNotBlank(item.getDeductionAmount())) { - matching.setDeductionAmount(new BigDecimal(item.getDeductionAmount()).multiply(percent)); - if (StringUtil.isNotBlank(item.getDeductionTax())) { - matching.setDeductionTax(new BigDecimal(item.getDeductionTax()).multiply(percent)); + // 没有PO时 split 和 IN 查询都没有意义,直接跳过 + if (StringUtil.isBlank(supplierMatching.getOrderNo())) { + log.warn("服务类匹配跳过:未匹配到PO,invoiceId={}", invoiceId); + } else { + Map mapParams = new HashMap<>(); + if (!supplier.getSupplierSpecial()) { + mapParams.put("supplierCode", supplierCode); + } + mapParams.put("all", 0); + mapParams.put("isOutIn", 1); + String[] orderNos = supplierMatching.getOrderNo().split(","); + mapParams.put("pos", orderNos); + List orderList = supplierOrderMapper.listByPOs(mapParams); + List items = corporateInvoiceItemsMapper.selectItemsByPid(invoiceId); + if (!items.isEmpty()) { + CorporateInvoiceItems item = items.get(0); + BigDecimal itemTotal = dec(item.getTotal()); + // 已分摊金额,防止超出明细金额继续匹配 + BigDecimal allocated = BigDecimal.ZERO; + for (SupplierOrder order : orderList) { + if (allocated.compareTo(itemTotal) >= 0) { + log.warn("服务类匹配已分摊满,剩余订单跳过:invoiceId={}, itemId={}", invoiceId, item.getId()); + break; + } + order.setUsedAmount(supplierOrderItemMatchingMapper.getUsedAmountByOrderId(order.getId())); + order.setUsedQuantity(supplierOrderItemMatchingMapper.getUsedQuantityByOrderId(order.getId())); + //插入supplier_order_item_matching + SupplierOrderItemMatching matching = new SupplierOrderItemMatching(); + matching.setInvoiceId(invoiceId); + matching.setItemId(item.getId()); + matching.setOrderId(order.getId()); + matching.setName(order.getGoods()); + matching.setSpecification(item.getSpecification()); + matching.setUnit(order.getUom()); + // 20220510price取值修改 + matching.setPrice(order.getUnitPrice()); + if (0 == corporateInvoice.getMatchType()) { + BigDecimal unUsedAmount = nvl(order.getUnUsedAmount()); + BigDecimal percent = div(unUsedAmount, itemTotal, 5); + matching.setTotal(unUsedAmount); + if (StringUtil.isNotBlank(item.getDeductionAmount())) { + matching.setDeductionAmount(dec(item.getDeductionAmount()).multiply(percent)); + if (StringUtil.isNotBlank(item.getDeductionTax())) { + matching.setDeductionTax(dec(item.getDeductionTax()).multiply(percent)); + } + matching.setDeductionTaxRate(item.getDeductionTaxRate()); } - matching.setDeductionTaxRate(item.getDeductionTaxRate()); + // 20220510单价修改 + matching.setQuantity(div(unUsedAmount, order.getUnitPrice(), 2).toString()); } - // 20220510单价修改 - //matching.setQuantity(matching.getTotal().divide(price, 2, BigDecimal.ROUND_HALF_UP).intValue()); - matching.setQuantity(matching.getTotal().divide(order.getUnitPrice(), 2, BigDecimal.ROUND_HALF_UP).toString()); - } - // tax 不是空并且 tax和deduction_tax - if (1 == priceType) { - matching.setTax(BigDecimal.ZERO); - } else { - if (StringUtil.isNotBlank(item.getTax())) { - if (itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())).compareTo(BigDecimal.ZERO) == 1) { - BigDecimal percent = matching.getTotal().divide(itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())), 10, BigDecimal.ROUND_HALF_UP); - matching.setTax(new BigDecimal(item.getTax()).add(new BigDecimal(item.getDeductionTax() == null ? "0" : item.getDeductionTax())).multiply(percent)); + // 数量没算出来(matchType 非 0)或数量为 0 的行不能入库 + if (StringUtil.isBlank(matching.getQuantity()) || dec(matching.getQuantity()).signum() == 0) { + continue; + } + // tax 不是空并且 tax和deduction_tax + if (1 == priceType) { + matching.setTax(BigDecimal.ZERO); + } else if (StringUtil.isNotBlank(item.getTax())) { + BigDecimal baseAmount = itemTotal.add(dec(item.getDeductionAmount())); + if (baseAmount.signum() > 0) { + matching.setTax(dec(item.getTax()).add(dec(item.getDeductionTax())) + .multiply(div(matching.getTotal(), baseAmount, 10))); } else { matching.setTax(BigDecimal.ZERO); } + } else { + matching.setTax(BigDecimal.ZERO); } + matching.setReceivedTotal(nvl(matching.getPrice()).multiply(dec(matching.getQuantity()))); + matching.setTaxRate(item.getTaxRate()); + matching.setMatchingStatus(0); + matching.setReceiveNo(order.getReceiveNo()); + matching.setOrderNo(order.getOrderNo()); + matching.setItemNo(order.getItemNo()); + allocated = allocated.add(nvl(matching.getTotal())); + // 插入匹配数据 + supplierOrderItemMatchingMapper.insert(matching); } - - matching.setReceivedTotal(order.getUnitPrice().multiply(new BigDecimal(matching.getQuantity()))); - matching.setTaxRate(item.getTaxRate()); - matching.setMatchingStatus(0); - matching.setReceiveNo(order.getReceiveNo()); - matching.setOrderNo(order.getOrderNo()); - matching.setItemNo(order.getItemNo()); - if (new BigDecimal(matching.getQuantity()).compareTo(BigDecimal.ZERO) == 0) { - break; - } - insertList2.add(matching); - // 插入匹配数据 - supplierOrderItemMatchingMapper.insert(matching); + updateDifference(corporateInvoice, item); } - updateDifference(corporateInvoice, item); } } else { // 待插入数据 List insertList = new ArrayList<>(); - Map matchMap = new HashMap<>(); - // 已使用数据 - Map matchMapNum = new HashMap<>(); - // 数据分组 - Map> matchList = new HashMap<>(); - StringBuffer poStr = new StringBuffer(); - for (SupplierInvoiceMatchOrderVo supplierInvoiceMatchOrderVo : result) { - // 判断itemNo是否已出现过 - String tmpKey = supplierInvoiceMatchOrderVo.getItemNo(); - int temId = supplierInvoiceMatchOrderVo.getId(); - if (matchList.get(tmpKey) != null) { - List tmp = matchList.get(tmpKey); - boolean existFlag1 = true; - for (SupplierInvoiceMatchOrderVo invoiceMatchOrderVo : tmp) { - if (invoiceMatchOrderVo.getId() == temId) { - existFlag1 = false; - } - } - if (existFlag1) { - tmp.add(supplierInvoiceMatchOrderVo); - matchList.put(tmpKey, tmp); - } - } else { - List tmp = new ArrayList<>(); - tmp.add(supplierInvoiceMatchOrderVo); - matchList.put(tmpKey, tmp); - } + // 按 itemNo 分组,同一 itemNo 下按 id 去重并保持出现顺序 + Map> grouped = new LinkedHashMap<>(); + for (SupplierInvoiceMatchOrderVo vo : result) { + grouped.computeIfAbsent(vo.getItemNo(), key -> new LinkedHashMap<>()).putIfAbsent(vo.getId(), vo); } // 新增物料和收货单价是否需要匹配 SysConfig AUTO_MATCHING_NEED_PRICE_EQUAL_CONIG = sysConfigMapper.selectByCode("AUTO_MATCHING_NEED_PRICE_EQUAL"); - log.info("matchList=>{}", JSONObject.toJSONString(matchList)); - List items = new ArrayList<>();//supplierInvoiceMatchOrderVo - for (String itemNo : matchList.keySet()) { - List items2 = null; - String unitPrice = null; - List simovs = matchList.get(itemNo); - if (simovs.isEmpty()) { - - } else { - unitPrice = simovs.get(0).getPrice().toString(); - } -// String needPriceRight = supplierMaterialMapper.selectItemTypeFromMaterialDefine(itemNo); - SupplierMaterialDefineVo supplierMaterialDefineVo = supplierMaterialMapper.getSimilarMaterialNo(itemNo, supplierCode, unitPrice); - log.info("supplierMaterialDefineVo=>{}", JSONObject.toJSONString(supplierMaterialDefineVo)); - String needPriceRight = null; - - if (null != supplierMaterialDefineVo) { -// needPriceRight = supplierMaterialDefineVo.getItemType(); - items2 = corporateInvoiceItemsMapper.selectForAutoSmartMatchingMulti3(invoiceId, supplierMaterialDefineVo.getSimilarItemNo()); - } else { - items2 = corporateInvoiceItemsMapper.selectForAutoSmartMatchingMulti2(invoiceId, itemNo); - } - int startIndex = 0; - BigDecimal useQty = BigDecimal.ZERO; - List lsMa = matchList.get(itemNo); - if (null != supplierMaterialDefineVo) { - if (!org.apache.commons.lang3.StringUtils.isEmpty(supplierMaterialDefineVo.getSimilarItemNo())) { - String[] similarItemNos = supplierMaterialDefineVo.getSimilarItemNo().split(","); - for (String no : similarItemNos) { - if (no.equals(itemNo)) continue; - if (null != matchList.get(no)) { - lsMa.addAll(matchList.get(no)); - } + boolean needPriceEqual = null != AUTO_MATCHING_NEED_PRICE_EQUAL_CONIG + && "1".equals(AUTO_MATCHING_NEED_PRICE_EQUAL_CONIG.getValue()); + // 需要重算差异的发票明细行,按 id 去重,避免同一行反复计算 + Map dirtyItems = new LinkedHashMap<>(); + // 已匹配订单号,去重且保持顺序 + LinkedHashSet matchedOrderNos = new LinkedHashSet<>(); + // 收货行剩余可匹配数量:内存记账,不再依赖同一次事务里刚插入的数据 + Map orderRemainQty = new HashMap<>(); + // 收货行缓存,避免同一行反复查询 + Map orderCache = new HashMap<>(); + // 发票明细行已匹配数量,避免每一行都回查一次 + Map itemMatchedQty = new HashMap<>(); + + for (Map.Entry> groupEntry : grouped.entrySet()) { + String itemNo = groupEntry.getKey(); + // 候选收货行:在副本上消耗,不再改动共享集合;按 id 去重 + LinkedHashMap available = new LinkedHashMap<>(groupEntry.getValue()); + SupplierInvoiceMatchOrderVo firstVo = available.isEmpty() ? null : available.values().iterator().next(); + String unitPrice = null == firstVo || null == firstVo.getPrice() ? null : firstVo.getPrice().toString(); + SupplierMaterialDefineVo supplierMaterialDefineVo = + supplierMaterialMapper.getSimilarMaterialNo(itemNo, supplierCode, unitPrice); + List items2 = null == supplierMaterialDefineVo + ? corporateInvoiceItemsMapper.selectForAutoSmartMatchingMulti2(invoiceId, itemNo) + : corporateInvoiceItemsMapper.selectForAutoSmartMatchingMulti3(invoiceId, supplierMaterialDefineVo.getSimilarItemNo()); + // 相似料号对应的收货行一并作为候选 + if (null != supplierMaterialDefineVo && StringUtil.isNotBlank(supplierMaterialDefineVo.getSimilarItemNo())) { + for (String no : supplierMaterialDefineVo.getSimilarItemNo().split(",")) { + if (no.equals(itemNo)) { + continue; + } + LinkedHashMap similarGroup = grouped.get(no); + if (null != similarGroup) { + available.putAll(similarGroup); } } } - int temp = 0; + if (null == items2 || items2.isEmpty()) { + continue; + } for (CorporateInvoiceItems item : items2) { - if (item.getName().equals("*金属制品*直角接头")) { - log.info("123"); - } - temp++; // 发票信息 - BigDecimal itemTotal = new BigDecimal(item.getTotal()); - BigDecimal itemQty = item.getUnUsedQuantity(); - BigDecimal itemQty2 = item.getUnUsedQuantity(); - BigDecimal lineQty = new BigDecimal("0"); - boolean isEnough = false; - // 2025 04 28 这里会存在多个物料匹配同一个行数据的情况。 这就使得这2个物料都匹配了同一个名称的明细行 - // 判断 是否有无剩余 / 或者超配 也同样需要从 items这里确保。 - String qtt = item.getQuantity(); - BigDecimal qt = new BigDecimal(qtt); - // 通过itemId去 supplier_order_item_matching查询匹配的数量。 - String itemId = item.getId(); - - BigDecimal matchedQt = supplierOrderItemMatchingMapper.getUsedQuantityByItemId(itemId); - if (matchedQt.compareTo(qt) >= 0) continue; - // 是否有完全匹配数据 + BigDecimal itemTotal = dec(item.getTotal()); + BigDecimal itemQty = nvl(item.getUnUsedQuantity()); + if (itemQty.signum() <= 0) { + continue; + } + // 该明细行已匹配的数量(含本次匹配过程中已插入的行) + BigDecimal matchedQty = itemMatchedQty.computeIfAbsent(item.getId(), + id -> nvl(supplierOrderItemMatchingMapper.getUsedQuantityByItemId(id))); + if (matchedQty.compareTo(dec(item.getQuantity())) >= 0) { + continue; + } + // 是否存在剩余数量完全相等的收货行:有则只匹配这一行,取收货时间最早的一行 boolean isOk = false; - Integer isId = 0; + int isId = 0; SupplierInvoiceMatchOrderVo isOkInvoice = null; - startIndex = 0; - for (int i = startIndex; i < lsMa.size(); i++) { - SupplierInvoiceMatchOrderVo so = lsMa.get(i); - if (null != AUTO_MATCHING_NEED_PRICE_EQUAL_CONIG && "1".equals(AUTO_MATCHING_NEED_PRICE_EQUAL_CONIG.getValue())) { - // 判断单价是否相等 - String price1 = item.getPrice(); - BigDecimal priceOne = new BigDecimal(price1).setScale(2, BigDecimal.ROUND_HALF_UP); - BigDecimal priceTwo = so.getPrice().setScale(2, BigDecimal.ROUND_HALF_UP); - if (priceOne.compareTo(priceTwo) != 0) { - continue; - } + for (SupplierInvoiceMatchOrderVo so : available.values()) { + if (needPriceEqual && !isSamePrice(item.getPrice(), so.getPrice())) { + continue; } - if (useQty.compareTo(BigDecimal.ZERO) > 0) continue; - if (itemQty.compareTo(so.getUsedQuantity()) == 0) { + if (itemQty.compareTo(nvl(so.getUsedQuantity())) == 0) { isOk = true; - if (isOkInvoice != null && isId > 0) { - if (isOkInvoice.getReceiveDate().after(so.getReceiveDate())) { - isOkInvoice = so; - isId = isOkInvoice.getId(); - } - } else { + if (null == isOkInvoice || isEarlier(so.getReceiveDate(), isOkInvoice.getReceiveDate())) { isOkInvoice = so; isId = so.getId(); } } } - int tryed = 0; - int maxTry = 50; - while (!lsMa.isEmpty() && tryed <= maxTry) { - tryed++; - int j = startIndex; - if (startIndex >= lsMa.size()) { - break; - } -// for (int j=startIndex;j iterator = available.values().iterator(); + while (iterator.hasNext() && remaining.signum() > 0) { + SupplierInvoiceMatchOrderVo so = iterator.next(); + if (needPriceEqual && !isSamePrice(item.getPrice(), so.getPrice())) { + continue; } - if (isOk && isId != supplierInvoiceMatchOrderVo.getId()) { - // lsMa.remove(supplierInvoiceMatchOrderVo); - startIndex++; + if (isOk && isId != so.getId()) { + // 已定位到完全匹配的收货行,其它行不再参与匹配 continue; } - SupplierOrder order = supplierOrderMapper.selectForAutoSmartMatching(supplierInvoiceMatchOrderVo.getId()); - log.info("order.getUnusedDquantity:{}", order.getUnUsedQuantity()); - if (order.getId() == 910187) { - log.info("====="); + SupplierOrder order = orderCache.computeIfAbsent(so.getId(), supplierOrderMapper::selectForAutoSmartMatching); + if (null == order) { + continue; } - if (null != item && null != order) { - items.add(item); - // 大于可用数量 - boolean qtyFlag = false; - if (supplierInvoiceMatchOrderVo.getUsedQuantity().subtract(useQty).compareTo(itemQty2) > 0) { - qtyFlag = true; - } - + BigDecimal orderAvail = orderRemainQty.get(order.getId()); + if (null == orderAvail) { order.setUsedAmount(supplierOrderItemMatchingMapper.getUsedAmountByOrderId(order.getId())); order.setUsedQuantity(supplierOrderItemMatchingMapper.getUsedQuantityByOrderId(order.getId())); - - //插入supplier_order_item_matching - SupplierOrderItemMatching matching = new SupplierOrderItemMatching(); - matching.setInvoiceId(invoiceId); - matching.setItemId(item.getId()); - matching.setOrderId(order.getId()); - matching.setName(order.getGoods()); - matching.setSpecification(item.getSpecification()); - matching.setUnit(order.getUom()); - BigDecimal price = order.getUnitPrice(); - // 20220510price取值修改 - matching.setPrice(order.getUnitPrice()); - if (0 == corporateInvoice.getMatchType()) { - BigDecimal percent = order.getUnUsedAmount().divide(itemTotal, 5, BigDecimal.ROUND_HALF_UP); - matching.setTotal(order.getUnUsedAmount() == null ? BigDecimal.ZERO : order.getUnUsedAmount()); - if (qtyFlag) { - percent = itemQty2.divide(itemQty, 5, BigDecimal.ROUND_HALF_UP); - matching.setTotal(order.getUnUsedAmount() == null ? BigDecimal.ZERO : order.getUnUsedAmount().multiply(percent)); - } - if (StringUtil.isNotBlank(item.getDeductionAmount())) { - matching.setDeductionAmount(new BigDecimal(item.getDeductionAmount()).multiply(percent)); - if (StringUtil.isNotBlank(item.getDeductionTax())) { - matching.setDeductionTax(new BigDecimal(item.getDeductionTax()).multiply(percent)); - } - matching.setDeductionTaxRate(item.getDeductionTaxRate()); - } - // 20220510单价修改 - //matching.setQuantity(matching.getTotal().divide(price, 2, BigDecimal.ROUND_HALF_UP).intValue()); - matching.setQuantity(matching.getTotal().divide(order.getUnitPrice(), 2, BigDecimal.ROUND_HALF_UP).toString()); - } else { - // 20220508pricsee取值修改 - //matching.setPrice(price.setScale(6, BigDecimal.ROUND_HALF_UP)); - if (new BigDecimal(order.getUnUsedQuantity()).compareTo(itemQty2) > 0) { - matching.setQuantity(itemQty2.toString()); - } else { - matching.setQuantity(order.getUnUsedQuantity()); - } - if (matching.getQuantity().indexOf("-") > -1) { - log.info(matching.getQuantity()); - } - - if (qtyFlag) { - matching.setQuantity(itemQty2.setScale(2, BigDecimal.ROUND_HALF_UP).toString()); - } - matching.setTotal(price.multiply(new BigDecimal(matching.getQuantity()))); - matching.setDeductionAmount(BigDecimal.ZERO); - matching.setDeductionTax(BigDecimal.ZERO); - matching.setDeductionTaxRate(item.getDeductionTaxRate()); - } - // tax 不是空并且 tax和deduction_tax - if (StringUtil.isNotBlank(item.getTax())) { - if (itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())).compareTo(BigDecimal.ZERO) == 1) { - BigDecimal percent = matching.getTotal().divide(itemTotal.add(new BigDecimal(item.getDeductionAmount() == null ? "0" : item.getDeductionAmount())), 10, BigDecimal.ROUND_HALF_UP); - matching.setTax(new BigDecimal(item.getTax()).add(new BigDecimal(item.getDeductionTax() == null ? "0" : item.getDeductionTax())).multiply(percent)); - } else { - matching.setTax(BigDecimal.ZERO); - } - } - - matching.setReceivedTotal(order.getUnitPrice().multiply(new BigDecimal(matching.getQuantity()))); - - matching.setTaxRate(item.getTaxRate()); - matching.setMatchingStatus(0); - matching.setReceiveNo(order.getReceiveNo()); - matching.setOrderNo(order.getOrderNo()); - matching.setItemNo(order.getItemNo()); - if (new BigDecimal(matching.getQuantity()).compareTo(BigDecimal.ZERO) == 0) { - break; - } - - insertList.add(matching); - // 插入匹配数据 - if (matching.getQuantity().indexOf("-") > -1) { - log.error("================="); + orderAvail = dec(order.getUnUsedQuantity()); + orderRemainQty.put(order.getId(), orderAvail); + } + if (orderAvail.signum() <= 0) { + continue; + } + // 收货行剩余 大于 发票行剩余:本次只匹配发票需要的部分,剩余留给后面的明细行 + boolean qtyFlag = orderAvail.compareTo(remaining) > 0; + BigDecimal matchQty = qtyFlag ? remaining : orderAvail; + + SupplierOrderItemMatching matching = new SupplierOrderItemMatching(); + matching.setInvoiceId(invoiceId); + matching.setItemId(item.getId()); + matching.setOrderId(order.getId()); + matching.setName(order.getGoods()); + matching.setSpecification(item.getSpecification()); + matching.setUnit(order.getUom()); + // 20220510price取值修改 + matching.setPrice(order.getUnitPrice()); + if (0 == corporateInvoice.getMatchType()) { + BigDecimal unUsedAmount = nvl(order.getUnUsedAmount()); + BigDecimal percent = div(unUsedAmount, itemTotal, 5); + BigDecimal matchAmount = unUsedAmount; + if (qtyFlag) { + percent = div(remaining, itemQty, 5); + matchAmount = unUsedAmount.multiply(percent); } - supplierOrderItemMatchingMapper.insert(matching); - // 拼接orderNo - if (poStr.toString().indexOf(matching.getOrderNo()) == -1) { - if (poStr.length() > 0) { - poStr.append(","); + matching.setTotal(matchAmount); + if (StringUtil.isNotBlank(item.getDeductionAmount())) { + matching.setDeductionAmount(dec(item.getDeductionAmount()).multiply(percent)); + if (StringUtil.isNotBlank(item.getDeductionTax())) { + matching.setDeductionTax(dec(item.getDeductionTax()).multiply(percent)); } - poStr.append(matching.getOrderNo()); - } - // 判断数据是否足够 - lineQty = lineQty.add(supplierInvoiceMatchOrderVo.getUsedQuantity().subtract(useQty)); - itemQty2 = itemQty2.subtract(supplierInvoiceMatchOrderVo.getUsedQuantity().subtract(useQty)); - // 足够则跳出 - startIndex = 0; - - if (lineQty.compareTo(itemQty) > 0) { - useQty = useQty.add(new BigDecimal(matching.getQuantity())); -// isEnough = true; - break; - } else if (lineQty.compareTo(itemQty) == 0) { -// startIndex++; - useQty = BigDecimal.ZERO; - lsMa.remove(supplierInvoiceMatchOrderVo); -// isEnough = true; - break; + matching.setDeductionTaxRate(item.getDeductionTaxRate()); } -// startIndex--; - useQty = BigDecimal.ZERO; - lsMa.remove(supplierInvoiceMatchOrderVo); + // 20220510单价修改 + matching.setQuantity(div(matchAmount, order.getUnitPrice(), 2).toString()); + } else { + // 20220508pricsee取值修改 + matching.setQuantity(matchQty.setScale(2, RoundingMode.HALF_UP).toString()); + matching.setTotal(nvl(order.getUnitPrice()).multiply(matchQty)); + matching.setDeductionAmount(BigDecimal.ZERO); + matching.setDeductionTax(BigDecimal.ZERO); + matching.setDeductionTaxRate(item.getDeductionTaxRate()); + } + if (dec(matching.getQuantity()).signum() == 0) { + // 数量为 0 的行不能入库,继续尝试下一行 continue; -// log.info("==:"+j); -// log.info("==:"+startIndex); -// log.info("==:{}",j 0) { + matching.setTax(dec(item.getTax()).add(dec(item.getDeductionTax())) + .multiply(div(matching.getTotal(), baseAmount, 10))); + } else { + matching.setTax(BigDecimal.ZERO); + } + } else { + matching.setTax(BigDecimal.ZERO); + } + matching.setReceivedTotal(nvl(order.getUnitPrice()).multiply(dec(matching.getQuantity()))); + matching.setTaxRate(item.getTaxRate()); + matching.setMatchingStatus(0); + matching.setReceiveNo(order.getReceiveNo()); + matching.setOrderNo(order.getOrderNo()); + matching.setItemNo(order.getItemNo()); + + insertList.add(matching); + // 插入匹配数据 + supplierOrderItemMatchingMapper.insert(matching); + if (StringUtil.isNotBlank(matching.getOrderNo())) { + matchedOrderNos.add(matching.getOrderNo()); + } + // 内存记账:扣减收货行和发票行的剩余数量 + orderRemainQty.put(order.getId(), orderAvail.subtract(matchQty)); + itemMatchedQty.put(item.getId(), matchedQty.add(matchQty)); + dirtyItems.putIfAbsent(item.getId(), item); + remaining = remaining.subtract(matchQty); + if (!qtyFlag) { + // 该收货行已耗尽,不再参与后续明细行的匹配 + iterator.remove(); + } } -// if(isEnough){ -// break; -// } - } } - if (!insertList.isEmpty()) { - if (supplierMatching.getOrderNo() == null || supplierMatching.getOrderNo().length() == 0) { - supplierMatching.setOrderNo(poStr.toString()); - supplierMatchingMapper.updateByPrimaryKey(supplierMatching); - } + if (!insertList.isEmpty() && StringUtil.isBlank(supplierMatching.getOrderNo())) { + supplierMatching.setOrderNo(String.join(",", matchedOrderNos)); + supplierMatchingMapper.updateByPrimaryKey(supplierMatching); } - for (CorporateInvoiceItems tmpItem : items) { + for (CorporateInvoiceItems tmpItem : dirtyItems.values()) { updateDifference(corporateInvoice, tmpItem); } } @@ -4104,6 +3929,121 @@ public class SupplierServiceImpl implements SupplierService { } } + /** + * 数字/金额字符串安全解析:null、空串、千分位、非数字 一律按 0 处理,避免 new BigDecimal 抛异常 + */ + private static BigDecimal dec(String value) { + if (value == null) { + return BigDecimal.ZERO; + } + String text = value.trim().replace(",", ""); + if (text.isEmpty()) { + return BigDecimal.ZERO; + } + try { + return new BigDecimal(text); + } catch (NumberFormatException e) { + log.warn("无法解析为数字,按 0 处理:{}", value); + return BigDecimal.ZERO; + } + } + + /** + * 数字/金额字符串安全解析并按指定精度取值 + */ + private static BigDecimal dec(String value, int scale) { + return dec(value).setScale(scale, RoundingMode.HALF_UP); + } + + /** + * 安全除法:被除数或除数为 null、除数为 0 时返回 0,避免 ArithmeticException + */ + private static BigDecimal div(BigDecimal dividend, BigDecimal divisor, int scale) { + if (dividend == null || divisor == null || divisor.signum() == 0) { + return BigDecimal.ZERO; + } + return dividend.divide(divisor, scale, RoundingMode.HALF_UP); + } + + /** + * 金额取 0 值兜底 + */ + private static BigDecimal nvl(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value; + } + + /** + * 收货单价与发票单价是否相等(都按 2 位小数比较) + */ + private static boolean isSamePrice(String invoicePrice, BigDecimal orderPrice) { + return null != orderPrice && dec(invoicePrice, 2).compareTo(orderPrice.setScale(2, RoundingMode.HALF_UP)) == 0; + } + + /** + * candidate 是否比 current 更早,null 视为最晚 + */ + private static boolean isEarlier(Date candidate, Date current) { + if (null == candidate) { + return false; + } + return null == current || candidate.before(current); + } + + /** + * 对账单明细匹配发票明细行:优先取名称包含对账单描述的行,未命中则兜底第一行 + */ + private CorporateInvoiceItems findStateItem(List itemList, String desc) { + if (itemList == null || itemList.isEmpty()) { + return null; + } + if (StringUtil.isNotBlank(desc)) { + for (CorporateInvoiceItems item : itemList) { + if (null != item.getName() && item.getName().contains(desc)) { + return item; + } + } + } + return itemList.get(0); + } + + /** + * 构建对账单(supplier_state)与发票明细行的匹配数据 + */ + private SupplierOrderItemMatching buildStateMatching(String invoiceId, CorporateInvoiceItems item, SupplierState supplierState, + SupplierOrder stateOrder, BigDecimal price, BigDecimal amount) { + // 折前金额,作为税额分摊的分母 + BigDecimal baseAmount = dec(item.getTotal()).add(dec(item.getDeductionAmount())); + SupplierOrderItemMatching matching = new SupplierOrderItemMatching(); + matching.setInvoiceId(invoiceId); + matching.setItemId(item.getId()); + matching.setOrderId(stateOrder.getId()); + matching.setName(supplierState.getDesc()); + matching.setSpecification(item.getSpecification()); + matching.setUnit(supplierState.getUom()); + // 20220510price取值修改 + matching.setPrice(price); + matching.setQuantity(supplierState.getQuantity()); + matching.setTotal(amount); + matching.setDeductionAmount(BigDecimal.ZERO); + matching.setDeductionTax(BigDecimal.ZERO); + matching.setDeductionTaxRate(item.getDeductionTaxRate()); + // tax 不是空并且 tax和deduction_tax + if (StringUtil.isNotBlank(item.getTax()) && baseAmount.signum() > 0) { + BigDecimal percent = div(matching.getTotal(), baseAmount, 10); + matching.setTax(dec(item.getTax()).add(dec(item.getDeductionTax())).multiply(percent)); + } else { + // 置 0 而非 null,避免后续 updateDifference 累加税额时空指针 + matching.setTax(BigDecimal.ZERO); + } + matching.setReceivedTotal(amount); + matching.setTaxRate(item.getTaxRate()); + matching.setMatchingStatus(0); + matching.setReceiveNo(supplierState.getExt01()); + matching.setOrderNo(supplierState.getPo()); + matching.setItemNo(supplierState.getItemNo()); + return matching; + } + public void updateDifference(CorporateInvoice corporateInvoice, CorporateInvoiceItems item) { log.info("supplierOrderItemMatching item.getId()=>{}", item.getId()); List supplierOrderItemMatchings = supplierOrderItemMatchingMapper.selectListByItemId(item.getId()); @@ -4111,7 +4051,7 @@ public class SupplierServiceImpl implements SupplierService { BigDecimal orderTotal = BigDecimal.ZERO; // ReceivedTotal合计 for (SupplierOrderItemMatching supplierOrderItemMatching : supplierOrderItemMatchings) { - orderTotal = orderTotal.add(supplierOrderItemMatching.getTotal()).add(supplierOrderItemMatching.getTax()); + orderTotal = orderTotal.add(nvl(supplierOrderItemMatching.getTotal())).add(nvl(supplierOrderItemMatching.getTax())); /* // 折前+数量 if (corporateInvoice.getPriceType().equals(1) && corporateInvoice.getMatchType().equals(1)&& null != supplierOrderItemMatching.getDeductionAmount()) { orderTotal = orderTotal.add(supplierOrderItemMatching.getDeductionAmount()); @@ -4128,16 +4068,10 @@ public class SupplierServiceImpl implements SupplierService { }*/ } // 发票金额 - BigDecimal invoiceMoney = new BigDecimal(item.getTotal()); - if (StringUtil.isNotEmpty(item.getDeductionAmount())) { - invoiceMoney = invoiceMoney.add(new BigDecimal(item.getDeductionAmount())); - } - if (StringUtil.isNotEmpty(item.getTax())) { - invoiceMoney = invoiceMoney.add(new BigDecimal(item.getTax())); - } - if (StringUtil.isNotEmpty(item.getDeductionTax())) { - invoiceMoney = invoiceMoney.add(new BigDecimal(item.getDeductionTax())); - } + BigDecimal invoiceMoney = dec(item.getTotal()) + .add(dec(item.getDeductionAmount())) + .add(dec(item.getTax())) + .add(dec(item.getDeductionTax())); BigDecimal difference = invoiceMoney.subtract(orderTotal).setScale(2, BigDecimal.ROUND_HALF_UP); log.info("invoiceMoney=>{} ,difference=>{}", invoiceMoney.toString(), difference.toString()); @@ -4896,28 +4830,6 @@ public class SupplierServiceImpl implements SupplierService { }}); // RPA调用判断 SysConfig config = sysConfigMapper.selectByCode("RPA_FLAG"); - if (config == null || config.getValue().equals("1")) { - //调用ics20220413 - String token = concurService.getToken(); - log.info("调用APInvoice接口 发票id{}", invoiceId); - String post = HttpRequest.post(concurConfig.getBaseUrl() + concurConfig.getSendApInvoice()) - .header("token", token) - .form("ocrId", invoiceId) - .form("opType", "sendApInvoice") - .execute().body(); - log.info("调用APInvoice接口{}", post); - if (post.indexOf("操作成功") > -1) { - // 20220413invoice_type:1-OA 2-ebs - List ids = supplierOrderItemMatchingMapper.selectIdsByStatusAndInvoiceId(invoiceId, 1); - supplierOutService.sendMatchingData(ids, invoiceId); - } else { - return Response.failure("ics sendApInvoice ERROR"); - } - } else { - - log.info("等待RPA修改状态:" + invoiceId); - } - CorporateInvoice corporateInvoice = corporateInvoiceMapper.selectByPrimaryKey(invoiceId); //插入日志记录 2022/6/17 @@ -6073,4 +5985,12 @@ public class SupplierServiceImpl implements SupplierService { } return token; } + @Override + public RestResponse matchInvoices(String numberS, String numberE, Integer invoiceType) { + List list = corporateInvoiceMapper.supplierInvoiceMatchInvoiceList(numberS, numberE, invoiceType); + for (InvoiceItemListDto invoiceItemListDto : list) { + invoiceItemListDto.setUsedMoney(supplierOrderItemMatchingMapper.getUsedAmountByItemId(invoiceItemListDto.getId())); + } + return RestResponse.success(list); + } }