| | |
| | | import cn.iocoder.yudao.module.crm.controller.admin.bidbond.vo.CrmBidBondSaveReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.bidbond.CrmBidBondDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.bidbond.CrmBidBondMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.quotation.CrmSaleQuotationMapper; |
| | | import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants; |
| | | import cn.iocoder.yudao.module.crm.enums.bidbond.CrmBidBondRefundStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.bidbond.CrmBidResultEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; |
| | | import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; |
| | | import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO; |
| | |
| | | @Resource |
| | | private CrmBidBondMapper bidBondMapper; |
| | | @Resource |
| | | private CrmSaleQuotationMapper saleQuotationMapper; |
| | | @Resource |
| | | private CrmCustomerService customerService; |
| | | @Resource |
| | | private ErpSaleOrderApi erpSaleOrderApi; |
| | |
| | | bond.setId(null); |
| | | bond.setNo(generateBondNo()); |
| | | bidBondMapper.insert(bond); |
| | | // 回写报价单绑定关系 |
| | | if (bond.getQuotationId() != null) { |
| | | saleQuotationMapper.bindBidBond(bond.getQuotationId(), bond.getId(), bond.getNo()); |
| | | } |
| | | return bond.getId(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateBidBond(CrmBidBondSaveReqVO updateReqVO) { |
| | | validateBidBondExists(updateReqVO.getId()); |
| | | CrmBidBondDO oldBond = validateBidBondExists(updateReqVO.getId()); |
| | | CrmBidBondDO updateObj = validateAndBuild(updateReqVO); |
| | | updateObj.setId(updateReqVO.getId()); |
| | | // 单号不允许修改,保持原值 |
| | | updateObj.setNo(null); |
| | | bidBondMapper.updateByIdIncludingNulls(updateObj); |
| | | // 报价单变化时:先解绑旧报价单,再绑定新报价单 |
| | | if (oldBond.getQuotationId() != null && !oldBond.getQuotationId().equals(updateObj.getQuotationId())) { |
| | | saleQuotationMapper.bindBidBond(oldBond.getQuotationId(), null, null); |
| | | } |
| | | if (updateObj.getQuotationId() != null && !updateObj.getQuotationId().equals(oldBond.getQuotationId())) { |
| | | saleQuotationMapper.bindBidBond(updateObj.getQuotationId(), updateObj.getId(), oldBond.getNo()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteBidBond(Long id) { |
| | | validateBidBondExists(id); |
| | | CrmBidBondDO bond = validateBidBondExists(id); |
| | | if (CrmBidResultEnum.WON.getResult().equals(bond.getBidResult()) && bond.getQuotationId() != null) { |
| | | throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_BOND_DELETE_FAIL_WON); |
| | | } |
| | | // 解除报价单绑定后再删除 |
| | | if (bond.getQuotationId() != null) { |
| | | saleQuotationMapper.bindBidBond(bond.getQuotationId(), null, null); |
| | | } |
| | | bidBondMapper.deleteById(id); |
| | | } |
| | | |
| | |
| | | saleOrderNo = order.getNo(); |
| | | } |
| | | |
| | | // 5. 退回状态与金额 |
| | | // 5. 关联报价单 |
| | | CrmSaleQuotationDO quotation = validateQuotation(reqVO, bidResult); |
| | | |
| | | // 6. 退回状态与金额 |
| | | Integer refundStatus = resolveRefundStatus(reqVO.getRefundStatus(), bidResult); |
| | | validateRefund(reqVO, bondAmount, refundStatus); |
| | | |
| | |
| | | bond.setBidResult(bidResult); |
| | | bond.setRefundStatus(refundStatus); |
| | | bond.setSaleOrderNo(saleOrderNo); |
| | | bond.setQuotationNo(quotation != null ? quotation.getNo() : null); |
| | | bond.setQuotationRound(quotation != null && quotation.getRevisionNo() != null |
| | | ? quotation.getRevisionNo() : 0); |
| | | return bond; |
| | | } |
| | | |
| | | /** |
| | | * 校验并加载关联报价单 |
| | | */ |
| | | private CrmSaleQuotationDO validateQuotation(CrmBidBondSaveReqVO reqVO, Integer bidResult) { |
| | | // 中标时报价单必填,保证中标后可以据此转合同 |
| | | if (CrmBidResultEnum.WON.getResult().equals(bidResult) && reqVO.getQuotationId() == null) { |
| | | throw new ServiceException(ErrorCodeConstants.BID_BOND_WON_QUOTATION_REQUIRED.getCode(), |
| | | "中标结果为【已中标】时必须关联报价单,否则无法凭该报价单转合同。" |
| | | + "请选择本次中标的销售报价单后重新保存。"); |
| | | } |
| | | if (reqVO.getQuotationId() == null) { |
| | | return null; |
| | | } |
| | | CrmSaleQuotationDO quotation = saleQuotationMapper.selectById(reqVO.getQuotationId()); |
| | | if (quotation == null) { |
| | | throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_NOT_EXISTS.getCode(), |
| | | "关联报价单(ID=" + reqVO.getQuotationId() + ")不存在,请刷新报价单列表后重新选择。"); |
| | | } |
| | | if (!Objects.equals(reqVO.getCustomerId(), quotation.getCustomerId())) { |
| | | throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_CUSTOMER_MISMATCH.getCode(), |
| | | "关联报价单[" + quotation.getNo() + "]的客户(ID=" + quotation.getCustomerId() |
| | | + ")与保证金客户(ID=" + reqVO.getCustomerId() |
| | | + ")不一致。请改选该客户名下的报价单,或先调整保证金客户。"); |
| | | } |
| | | if (!Set.of(CrmSaleQuotationStatusEnum.DRAFT.getStatus(), |
| | | CrmSaleQuotationStatusEnum.APPROVE.getStatus(), |
| | | CrmSaleQuotationStatusEnum.REJECT.getStatus()).contains(quotation.getStatus())) { |
| | | throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_STATUS_INVALID.getCode(), |
| | | "关联报价单[" + quotation.getNo() + "]当前状态为【" |
| | | + getQuotationStatusName(quotation.getStatus()) |
| | | + "】,只有「草稿、审批通过、审批不通过」的报价单可以绑定投标。" |
| | | + "请改选其他报价单,或先重新报价后再绑定。"); |
| | | } |
| | | if (quotation.getBidBondId() != null && !Objects.equals(quotation.getBidBondId(), reqVO.getId())) { |
| | | CrmBidBondDO boundBond = bidBondMapper.selectById(quotation.getBidBondId()); |
| | | throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_ALREADY_BOUND.getCode(), |
| | | "关联报价单[" + quotation.getNo() + "]已绑定投标[" |
| | | + (boundBond != null ? boundBond.getNo() : "ID=" + quotation.getBidBondId()) |
| | | + "],同一报价单只能绑定一张投标。请改选其他报价单,或先在该投标中解除关联。"); |
| | | } |
| | | return quotation; |
| | | } |
| | | |
| | | private Integer resolveRefundStatus(Integer refundStatus, Integer bidResult) { |
| | |
| | | Map<Long, CrmCustomerDO> customerMap = customerIds.isEmpty() ? Map.of() |
| | | : customerService.getCustomerMap(customerIds); |
| | | Map<Long, ErpSaleOrderRespDTO> orderMap = loadSaleOrderMap(bonds); |
| | | Map<Long, CrmSaleQuotationDO> quotationMap = loadQuotationMap(bonds); |
| | | |
| | | return bonds.stream().map(bond -> { |
| | | CrmBidBondRespVO respVO = BeanUtils.toBean(bond, CrmBidBondRespVO.class); |
| | |
| | | respVO.setSaleOrderNo(order.getNo()); |
| | | respVO.setSaleOrderAmount(order.getTotalPrice()); |
| | | } |
| | | CrmSaleQuotationDO quotation = bond.getQuotationId() == null ? null |
| | | : quotationMap.get(bond.getQuotationId()); |
| | | if (quotation != null) { |
| | | respVO.setQuotationNo(quotation.getNo()); |
| | | respVO.setQuotationStatus(quotation.getStatus()); |
| | | respVO.setQuotationTotalPrice(quotation.getTotalPrice()); |
| | | } |
| | | return respVO; |
| | | }).collect(Collectors.toList()); |
| | | } |
| | | |
| | | private Map<Long, CrmSaleQuotationDO> loadQuotationMap(List<CrmBidBondDO> bonds) { |
| | | Set<Long> quotationIds = bonds.stream().map(CrmBidBondDO::getQuotationId) |
| | | .filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | if (quotationIds.isEmpty()) { |
| | | return Map.of(); |
| | | } |
| | | return saleQuotationMapper.selectByIds(quotationIds).stream() |
| | | .collect(Collectors.toMap(CrmSaleQuotationDO::getId, q -> q, (a, b) -> a)); |
| | | } |
| | | |
| | | /** |
| | |
| | | return orderMap; |
| | | } |
| | | |
| | | private String getQuotationStatusName(Integer status) { |
| | | CrmSaleQuotationStatusEnum statusEnum = CrmSaleQuotationStatusEnum.getByStatus(status); |
| | | return statusEnum != null ? statusEnum.getName() : "未知状态(" + status + ")"; |
| | | } |
| | | |
| | | private String getSaleOrderStatusName(Integer status) { |
| | | if (status == null) { |
| | | return "未知"; |