| | |
| | | import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper; |
| | | import com.ruoyi.account.pojo.sales.AccountInvoiceApplication; |
| | | import com.ruoyi.account.pojo.sales.AccountSalesCollection; |
| | | import com.ruoyi.approve.bean.dto.ApprovalInstanceDto; |
| | | import com.ruoyi.approve.mapper.ApprovalTemplateMapper; |
| | | import com.ruoyi.approve.pojo.ApprovalInstance; |
| | | import com.ruoyi.approve.pojo.ApprovalTemplate; |
| | | import com.ruoyi.approve.service.ApprovalInstanceService; |
| | | import com.ruoyi.basic.enums.ApplicationTypeEnum; |
| | | import com.ruoyi.basic.enums.RecordTypeEnum; |
| | | import com.ruoyi.basic.mapper.CustomerMapper; |
| | | import com.ruoyi.basic.mapper.ProductMapper; |
| | | import com.ruoyi.basic.mapper.ProductModelMapper; |
| | | import com.ruoyi.basic.mapper.OilDepotMapper; |
| | | import com.ruoyi.basic.mapper.TankInfoMapper; |
| | | import com.ruoyi.basic.pojo.Customer; |
| | | import com.ruoyi.basic.pojo.OilDepot; |
| | | import com.ruoyi.basic.pojo.TankInfo; |
| | | import com.ruoyi.basic.utils.FileUtil; |
| | | import com.ruoyi.common.enums.FileNameType; |
| | | import com.ruoyi.common.enums.SaleEnum; |
| | | import com.ruoyi.common.enums.SalesQuotationStatusEnum; |
| | | import com.ruoyi.common.enums.TypeEnums; |
| | | import com.ruoyi.common.exception.base.BaseException; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.EnumUtil; |
| | |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | | import com.ruoyi.project.system.mapper.SysDeptMapper; |
| | | import com.ruoyi.project.system.mapper.SysUserMapper; |
| | | import com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto; |
| | | import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper; |
| | | import com.ruoyi.quality.mapper.QualityInspectMapper; |
| | | import com.ruoyi.sales.dto.*; |
| | | import com.ruoyi.sales.mapper.CommonFileMapper; |
| | | import com.ruoyi.sales.mapper.SalesLedgerArrivalMapper; |
| | | import com.ruoyi.sales.mapper.SalesLedgerMapper; |
| | | import com.ruoyi.sales.mapper.SalesLedgerProductMapper; |
| | | import com.ruoyi.sales.mapper.ShippingInfoMapper; |
| | | import com.ruoyi.sales.pojo.CommonFile; |
| | | import com.ruoyi.sales.pojo.SalesLedger; |
| | | import com.ruoyi.sales.pojo.SalesLedgerArrival; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import com.ruoyi.sales.pojo.ShippingInfo; |
| | | import com.ruoyi.sales.service.ISalesLedgerService; |
| | |
| | | private static final String LOCK_PREFIX = "contract_no_lock:"; |
| | | private static final long LOCK_WAIT_TIMEOUT = 10; // 锁等待超时时间(秒) |
| | | private static final long LOCK_EXPIRE_TIME = 30; // 锁自动过期时间(秒) |
| | | /** 销售类型:卖油(走产品明细) */ |
| | | private static final String LEDGER_TYPE_OIL = "卖油"; |
| | | /** 销售类型:代储(卖存储位置,走油库+储罐+手工合同金额) */ |
| | | private static final String LEDGER_TYPE_DEPOSIT = "代储"; |
| | | /** 发货状态:有到货记录即视为已发货(油品出库不再产发货单) */ |
| | | private static final String SHIPPING_STATUS_SHIPPED = "已发货"; |
| | | private final SalesLedgerMapper salesLedgerMapper; |
| | | private final SalesLedgerArrivalMapper salesLedgerArrivalMapper; |
| | | private final CustomerMapper customerMapper; |
| | | private final SalesLedgerProductMapper salesLedgerProductMapper; |
| | | private final SalesLedgerProductServiceImpl salesLedgerProductServiceImpl; |
| | |
| | | private final FileUtil fileUtil; |
| | | private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper; |
| | | private final AccountSalesCollectionMapper accountSalesCollectionMapper; |
| | | private final ApprovalTemplateMapper approvalTemplateMapper; |
| | | private final ApprovalInstanceService approvalInstanceService; |
| | | private final OilDepotMapper oilDepotMapper; |
| | | private final TankInfoMapper tankInfoMapper; |
| | | |
| | | @Autowired |
| | | private SysDeptMapper sysDeptMapper; |
| | |
| | | @Autowired |
| | | private ProductionProductMainService productionProductMainService; |
| | | @Autowired |
| | | private PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper; |
| | | ; |
| | | @Autowired |
| | | private SysUserMapper sysUserMapper; |
| | | |
| | | @Override |
| | | public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) { |
| | | return salesLedgerMapper.selectSalesLedgerList(salesLedgerDto); |
| | | List<SalesLedger> list = salesLedgerMapper.selectSalesLedgerList(salesLedgerDto); |
| | | fillTrustFund(list); |
| | | return list; |
| | | } |
| | | |
| | | public List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(Long relateId, SaleEnum type) { |
| | |
| | | productWrapper.eq(SalesLedgerProduct::getType, type.getCode()); |
| | | List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(productWrapper); |
| | | if (type.equals(SaleEnum.PURCHASE)) { |
| | | // 查询退货信息 |
| | | List<Long> productIds = salesLedgerProducts.stream().map(SalesLedgerProduct::getProductModelId).collect(Collectors.toList()); |
| | | List<SimpleReturnOrderGroupDto> groupListByProductIds = new ArrayList<>(); |
| | | if(CollectionUtils.isNotEmpty(productIds)){ |
| | | groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds); |
| | | } |
| | | Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getProductModelId, SimpleReturnOrderGroupDto::getSumReturnQuantity)); |
| | | salesLedgerProducts.forEach(item -> { |
| | | BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getProductModelId(), BigDecimal.ZERO); |
| | | item.setReturnQuality(returnQuality); |
| | | item.setAvailableQuality(item.getQuantity().subtract(returnQuality)); |
| | | BigDecimal returnQuantity = item.getReturnQuantity() == null ? BigDecimal.ZERO : item.getReturnQuantity(); |
| | | item.setReturnQuantity(returnQuantity); |
| | | BigDecimal quantity = item.getQuantity() == null ? BigDecimal.ZERO : item.getQuantity(); |
| | | item.setAvailableQuality(quantity.subtract(returnQuantity)); |
| | | }); |
| | | } |
| | | return salesLedgerProducts; |
| | |
| | | for (SalesLedgerProduct product : products) { |
| | | product.setRegister(SecurityUtils.getLoginUser().getUser().getNickName()); |
| | | product.setRegisterDate(LocalDateTime.now()); |
| | | // 发货信息 |
| | | ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>() |
| | | .eq(ShippingInfo::getSalesLedgerProductId, product.getId()) |
| | | .orderByDesc(ShippingInfo::getCreateTime) |
| | | // 发货状态:油品出库不再产发货单,改为「有到货记录即已发货」 |
| | | SalesLedgerArrival arrival = salesLedgerArrivalMapper.selectOne(new LambdaQueryWrapper<SalesLedgerArrival>() |
| | | .eq(SalesLedgerArrival::getSalesLedgerProductId, product.getId()) |
| | | .orderByDesc(SalesLedgerArrival::getArrivalDate) |
| | | .orderByDesc(SalesLedgerArrival::getId) |
| | | .last("limit 1")); |
| | | if (shippingInfo != null) { |
| | | product.setShippingStatus(shippingInfo.getStatus()); |
| | | if (arrival != null) { |
| | | product.setShippingStatus(SHIPPING_STATUS_SHIPPED); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | @Override |
| | | public IPage<SalesLedgerVo> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto) { |
| | | return salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto); |
| | | IPage<SalesLedgerVo> result = salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto); |
| | | fillTrustFund(result.getRecords()); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 回填客户的信托基金,并判断本笔合同金额是否已超出。 |
| | | * 超出与否由后端算好返回,前端只负责标红 |
| | | */ |
| | | private void fillTrustFund(List<? extends SalesLedger> records) { |
| | | if (CollectionUtils.isEmpty(records)) { |
| | | return; |
| | | } |
| | | List<Long> customerIds = records.stream() |
| | | .map(SalesLedger::getCustomerId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | if (customerIds.isEmpty()) { |
| | | return; |
| | | } |
| | | Map<Long, BigDecimal> trustFundMap = customerMapper.selectBatchIds(customerIds).stream() |
| | | .filter(item -> item.getTrustFund() != null) |
| | | .collect(Collectors.toMap(Customer::getId, Customer::getTrustFund, (existing, replacement) -> existing)); |
| | | |
| | | records.forEach(ledger -> { |
| | | BigDecimal trustFund = trustFundMap.get(ledger.getCustomerId()); |
| | | ledger.setTrustFund(trustFund); |
| | | ledger.setExceedsTrustFund(trustFund != null && ledger.getContractAmount() != null |
| | | && ledger.getContractAmount().compareTo(trustFund) > 0); |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | |
| | | salesLedger.setContractAmount(salesLedgerProductImportDtos.stream() |
| | | .map(SalesLedgerProductImportDto::getTaxInclusiveTotalPrice) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | salesLedger.setNetContractAmount(salesLedger.getContractAmount()); |
| | | salesLedgerMapper.insert(salesLedger); |
| | | |
| | | |
| | |
| | | for (SalesLedgerProduct product : products) { |
| | | product.setRegister(SecurityUtils.getLoginUser().getUser().getNickName()); |
| | | product.setRegisterDate(LocalDateTime.now()); |
| | | // 发货信息 |
| | | ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>() |
| | | .eq(ShippingInfo::getSalesLedgerProductId, product.getId()) |
| | | .orderByDesc(ShippingInfo::getCreateTime) |
| | | // 发货信息:油品出库不再产发货单,改按最新一条到货记录回填 |
| | | SalesLedgerArrival arrival = salesLedgerArrivalMapper.selectOne(new LambdaQueryWrapper<SalesLedgerArrival>() |
| | | .eq(SalesLedgerArrival::getSalesLedgerProductId, product.getId()) |
| | | .orderByDesc(SalesLedgerArrival::getArrivalDate) |
| | | .orderByDesc(SalesLedgerArrival::getId) |
| | | .last("limit 1")); |
| | | product.setShippingCarNumber(shippingInfo.getShippingCarNumber()); |
| | | product.setShippingDate(shippingInfo.getShippingDate()); |
| | | if (shippingInfo != null) { |
| | | product.setShippingStatus(shippingInfo.getStatus()); |
| | | if (arrival != null) { |
| | | product.setShippingCarNumber(arrival.getTruckPlateNo()); |
| | | product.setShippingDate(arrival.getArrivalDate() == null ? null : java.sql.Date.valueOf(arrival.getArrivalDate())); |
| | | product.setShippingStatus(SHIPPING_STATUS_SHIPPED); |
| | | } |
| | | } |
| | | // 过滤只保留发货记录 |
| | |
| | | // 删除附件表 |
| | | commonFileService.deleteByBusinessIds(idList, FileNameType.SALE.getValue()); |
| | | |
| | | // 删除台账对应的审批实例(同一台账可能反复提交/重审,会有多条) |
| | | List<ApprovalInstance> approvalInstances = approvalInstanceService.list( |
| | | new LambdaQueryWrapper<ApprovalInstance>() |
| | | .in(ApprovalInstance::getBusinessId, idList) |
| | | .eq(ApprovalInstance::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode()) |
| | | .eq(ApprovalInstance::getDeleted, 0)); |
| | | if (CollectionUtils.isNotEmpty(approvalInstances)) { |
| | | approvalInstanceService.delete(approvalInstances.stream() |
| | | .map(ApprovalInstance::getId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList())); |
| | | } |
| | | |
| | | // 删除生产管控数据 |
| | | //查询生产报工id |
| | | ArrayList<Long> mainIdList = productionProductMainService.listMain(idList); |
| | |
| | | SalesLedger salesLedger = convertToEntity(salesLedgerDto); |
| | | salesLedger.setCustomerName(customer.getCustomerName()); |
| | | salesLedger.setTenantId(customer.getTenantId()); |
| | | applyLedgerType(salesLedger, salesLedgerDto); |
| | | // 录入人:如果未传则使用当前登录用户 |
| | | if (StringUtils.isEmpty(salesLedger.getEntryPerson())) { |
| | | salesLedger.setEntryPerson(String.valueOf(SecurityUtils.getUserId())); |
| | | } |
| | | // 新增和修改都要重新走审批 |
| | | salesLedger.setApprovalStatus(SalesQuotationStatusEnum.PENDING.getCode()); |
| | | // 3. 新增或更新主表 |
| | | if (salesLedger.getId() == null) { |
| | | String contractNo = salesLedger.getSalesContractNo(); |
| | | if (StringUtils.isEmpty(contractNo)) { |
| | | contractNo = generateSalesContractNo(); |
| | | contractNo = generateSalesContractNo(salesLedgerDto.getEntryDate()); |
| | | } |
| | | salesLedger.setSalesContractNo(contractNo); |
| | | salesLedgerMapper.insert(salesLedger); |
| | | } else { |
| | | // 改单会重新走一遍审批:先作废此前未结束的审批实例,事务提交前还会重新发起 |
| | | approvalInstanceService.lambdaUpdate() |
| | | .set(ApprovalInstance::getStatus, "REJECTED") |
| | | .eq(ApprovalInstance::getBusinessId, salesLedger.getId()) |
| | | .eq(ApprovalInstance::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode()) |
| | | .update(); |
| | | salesLedgerMapper.updateById(salesLedger); |
| | | } |
| | | |
| | | |
| | | // 4. 处理子表数据 |
| | | List<SalesLedgerProduct> productList = salesLedgerDto.getProductData(); |
| | | // 4. 处理子表数据。代储没有产品明细,合同金额以手工填写的为准,不能被子表汇总覆盖 |
| | | List<SalesLedgerProduct> productList = LEDGER_TYPE_OIL.equals(salesLedger.getLedgerType()) |
| | | ? salesLedgerDto.getProductData() : null; |
| | | if (productList != null && !productList.isEmpty()) { |
| | | handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType())); |
| | | updateMainContractAmount( |
| | |
| | | ); |
| | | } |
| | | |
| | | // 5. 保存销售台账附件 |
| | | // 5. 兜底:确保实际合同金额有初始值等于合同金额 |
| | | if (salesLedger.getNetContractAmount() == null) { |
| | | SalesLedger dbLedger = salesLedgerMapper.selectById(salesLedger.getId()); |
| | | if (dbLedger != null && dbLedger.getNetContractAmount() == null && dbLedger.getContractAmount() != null) { |
| | | SalesLedger updateEntity = new SalesLedger(); |
| | | updateEntity.setId(salesLedger.getId()); |
| | | updateEntity.setNetContractAmount(dbLedger.getContractAmount()); |
| | | salesLedgerMapper.updateById(updateEntity); |
| | | } |
| | | } |
| | | |
| | | // 6. 保存销售台账附件 |
| | | fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, salesLedger.getId(), salesLedgerDto.getStorageBlobDTOs()); |
| | | |
| | | // 7. 发起审批。合同金额要取落库后的值(卖油的金额由第 4 步的产品明细汇总写回) |
| | | SalesLedger savedLedger = salesLedgerMapper.selectById(salesLedger.getId()); |
| | | startSalesLedgerApproval(savedLedger, customer.getTrustFund()); |
| | | |
| | | return 1; |
| | | } |
| | | |
| | | /** |
| | | * 校验销售类型并补齐代储的油库/储罐信息。 |
| | | * 卖油:合同金额只能由产品明细汇总产生;代储:没有明细,必须选油库、储罐并手工填合同金额 |
| | | */ |
| | | private void applyLedgerType(SalesLedger salesLedger, SalesLedgerDto salesLedgerDto) { |
| | | String ledgerType = salesLedgerDto.getLedgerType(); |
| | | if (StringUtils.isEmpty(ledgerType)) { |
| | | throw new BaseException("销售类型不能为空"); |
| | | } |
| | | if (LEDGER_TYPE_OIL.equals(ledgerType)) { |
| | | salesLedger.setOilDepotId(null); |
| | | salesLedger.setOilDepotName(null); |
| | | salesLedger.setTankId(null); |
| | | salesLedger.setTankNo(null); |
| | | salesLedger.setTonnage(null); |
| | | // updateById 会跳过 null,对修改是空操作;新增时与改造前一致(本就为 null) |
| | | salesLedger.setContractAmount(null); |
| | | return; |
| | | } |
| | | if (!LEDGER_TYPE_DEPOSIT.equals(ledgerType)) { |
| | | throw new BaseException("销售类型不合法:" + ledgerType); |
| | | } |
| | | if (salesLedgerDto.getOilDepotId() == null) { |
| | | throw new BaseException("代储必须选择油库"); |
| | | } |
| | | if (salesLedgerDto.getTankId() == null) { |
| | | throw new BaseException("代储必须选择储罐"); |
| | | } |
| | | if (salesLedgerDto.getContractAmount() == null |
| | | || salesLedgerDto.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new BaseException("代储必须填写合同金额"); |
| | | } |
| | | OilDepot oilDepot = oilDepotMapper.selectById(salesLedgerDto.getOilDepotId()); |
| | | if (oilDepot == null) { |
| | | throw new BaseException("油库不存在:" + salesLedgerDto.getOilDepotId()); |
| | | } |
| | | TankInfo tankInfo = tankInfoMapper.selectById(salesLedgerDto.getTankId()); |
| | | if (tankInfo == null) { |
| | | throw new BaseException("储罐不存在:" + salesLedgerDto.getTankId()); |
| | | } |
| | | // 名称以基础数据为准,不采信前端传值 |
| | | salesLedger.setOilDepotName(oilDepot.getDepotName()); |
| | | salesLedger.setTankNo(tankInfo.getTankNo()); |
| | | } |
| | | |
| | | /** |
| | | * 发起销售台账审批,走系统已有的 OA 审批流程。 |
| | | * 合同金额超出客户信托基金时,审批标题带上提醒字眼 |
| | | */ |
| | | private void startSalesLedgerApproval(SalesLedger ledger, BigDecimal trustFund) { |
| | | if (ledger == null) { |
| | | return; |
| | | } |
| | | ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne( |
| | | new LambdaQueryWrapper<ApprovalTemplate>() |
| | | .eq(ApprovalTemplate::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode()) |
| | | .eq(ApprovalTemplate::getDeleted, 0) |
| | | .orderByDesc(ApprovalTemplate::getId) |
| | | .last("LIMIT 1") |
| | | ); |
| | | if (approvalTemplate == null) { |
| | | throw new RuntimeException("请先配置销售台账审批模板"); |
| | | } |
| | | boolean exceedsTrustFund = trustFund != null && ledger.getContractAmount() != null |
| | | && ledger.getContractAmount().compareTo(trustFund) > 0; |
| | | String title = ledger.getSalesContractNo() + "审批"; |
| | | if (exceedsTrustFund) { |
| | | title += "(合同金额超出客户信托基金,请重点审核)"; |
| | | } |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto(); |
| | | approvalInstance.setTemplateId(approvalTemplate.getId()); |
| | | approvalInstance.setTemplateName(approvalTemplate.getTemplateName()); |
| | | approvalInstance.setBusinessId(ledger.getId()); |
| | | approvalInstance.setBusinessType(TypeEnums.SALES_LEDGER_APPROVAL.getCode()); |
| | | approvalInstance.setCurrentLevel(1); |
| | | approvalInstance.setTitle(title); |
| | | approvalInstance.setApplicantId(loginUser.getUserId()); |
| | | approvalInstance.setApplicantName(loginUser.getNickName()); |
| | | approvalInstance.setApplyTime(LocalDateTime.now()); |
| | | try { |
| | | approvalInstanceService.add(approvalInstance); |
| | | } catch (Exception e) { |
| | | log.error("SalesLedgerServiceImpl approve error for salesContractNo: {}", ledger.getSalesContractNo(), e); |
| | | throw new RuntimeException("审批失败: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public String generateSalesContractNo() { |
| | | LocalDate currentDate = LocalDate.now(); |
| | | public String generateSalesContractNo(Date entryDate) { |
| | | LocalDate currentDate = entryDate != null ? DateUtils.toLocalDate(entryDate) : LocalDate.now(); |
| | | String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE); |
| | | String lockKey = LOCK_PREFIX + datePart; |
| | | String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime(); // 唯一标识锁持有者 |
| | |
| | | amountField.setAccessible(true); |
| | | amountField.set(entity, totalAmount); |
| | | |
| | | // 实际合同金额初始值等于合同金额 |
| | | try { |
| | | Field netAmountField = mainEntityClass.getDeclaredField("netContractAmount"); |
| | | netAmountField.setAccessible(true); |
| | | netAmountField.set(entity, totalAmount); |
| | | } catch (NoSuchFieldException ignored) { |
| | | // 子类可能没有该字段,忽略 |
| | | } |
| | | |
| | | mainMapper.updateById(entity); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException("动态更新主表金额失败", e); |