|
@@ -1,40 +1,67 @@
|
|
|
package com.usky.vpp.service.impl;
|
|
package com.usky.vpp.service.impl;
|
|
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.usky.common.core.bean.CommonPage;
|
|
import com.usky.common.core.bean.CommonPage;
|
|
|
import com.usky.common.core.exception.BusinessException;
|
|
import com.usky.common.core.exception.BusinessException;
|
|
|
|
|
+import com.usky.common.security.utils.SecurityUtils;
|
|
|
|
|
+import com.usky.vpp.domain.VppContract;
|
|
|
import com.usky.vpp.domain.VppCustomer;
|
|
import com.usky.vpp.domain.VppCustomer;
|
|
|
import com.usky.vpp.domain.VppCustomerAccess;
|
|
import com.usky.vpp.domain.VppCustomerAccess;
|
|
|
import com.usky.vpp.domain.VppCustomerContact;
|
|
import com.usky.vpp.domain.VppCustomerContact;
|
|
|
|
|
+import com.usky.vpp.mapper.VppContractMapper;
|
|
|
import com.usky.vpp.mapper.VppCustomerAccessMapper;
|
|
import com.usky.vpp.mapper.VppCustomerAccessMapper;
|
|
|
import com.usky.vpp.mapper.VppCustomerContactMapper;
|
|
import com.usky.vpp.mapper.VppCustomerContactMapper;
|
|
|
import com.usky.vpp.mapper.VppCustomerMapper;
|
|
import com.usky.vpp.mapper.VppCustomerMapper;
|
|
|
import com.usky.vpp.service.VppCustomerService;
|
|
import com.usky.vpp.service.VppCustomerService;
|
|
|
import com.usky.vpp.service.vo.CustomerAccessAuditRequest;
|
|
import com.usky.vpp.service.vo.CustomerAccessAuditRequest;
|
|
|
-import com.usky.vpp.service.vo.CustomerAccessRequest;
|
|
|
|
|
|
|
+import com.usky.vpp.service.vo.CustomerAccessRequestVO;
|
|
|
|
|
+import com.usky.vpp.service.vo.CustomerAccessResponseVO;
|
|
|
import com.usky.vpp.service.vo.CustomerContactRequest;
|
|
import com.usky.vpp.service.vo.CustomerContactRequest;
|
|
|
|
|
+import com.usky.vpp.service.vo.CustomerRequestVO;
|
|
|
|
|
+import com.usky.vpp.service.vo.CustomerResponseVO;
|
|
|
import com.usky.vpp.util.VppAuditHelper;
|
|
import com.usky.vpp.util.VppAuditHelper;
|
|
|
-import com.usky.vpp.util.VppPageHelper;
|
|
|
|
|
|
|
+import com.usky.vpp.util.VppFieldValidator;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
-import org.springframework.util.StringUtils;
|
|
|
|
|
|
|
|
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.InputStream;
|
|
|
|
|
+import java.net.HttpURLConnection;
|
|
|
|
|
+import java.net.URL;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
+import java.util.function.Function;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 客户管理业务实现
|
|
* 客户管理业务实现
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author fyc
|
|
|
|
|
+ * @email yuchuan.fu@chinausky.com
|
|
|
|
|
+ * @date 2026/7/6
|
|
|
*/
|
|
*/
|
|
|
|
|
+@Slf4j
|
|
|
@Service
|
|
@Service
|
|
|
public class VppCustomerServiceImpl implements VppCustomerService {
|
|
public class VppCustomerServiceImpl implements VppCustomerService {
|
|
|
|
|
|
|
|
private static final int ACCESS_PENDING = 0;
|
|
private static final int ACCESS_PENDING = 0;
|
|
|
private static final int ACCESS_PASSED = 1;
|
|
private static final int ACCESS_PASSED = 1;
|
|
|
private static final int ACCESS_REJECTED = 2;
|
|
private static final int ACCESS_REJECTED = 2;
|
|
|
- private static final int LIFECYCLE_PENDING = 1;
|
|
|
|
|
- private static final int LIFECYCLE_ADMITTED = 2;
|
|
|
|
|
|
|
+ private static final int LIFECYCLE_ADMITTED = 1;
|
|
|
|
|
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private VppCustomerMapper customerMapper;
|
|
private VppCustomerMapper customerMapper;
|
|
@@ -42,11 +69,26 @@ public class VppCustomerServiceImpl implements VppCustomerService {
|
|
|
private VppCustomerAccessMapper accessMapper;
|
|
private VppCustomerAccessMapper accessMapper;
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private VppCustomerContactMapper contactMapper;
|
|
private VppCustomerContactMapper contactMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private VppContractMapper contractMapper;
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 准入管理 ====================
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public VppCustomerAccess submitAccess(CustomerAccessRequest request) {
|
|
|
|
|
|
|
+ public Boolean submitAccess(CustomerAccessRequestVO request) {
|
|
|
validateAccessRequest(request);
|
|
validateAccessRequest(request);
|
|
|
|
|
+
|
|
|
|
|
+ // 校验同租户下 accountNo 唯一
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerAccess> dupWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ dupWrapper.eq(VppCustomerAccess::getAccountNo, request.getAccountNo())
|
|
|
|
|
+ .eq(VppCustomerAccess::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomerAccess::getDeleteFlag, VppAuditHelper.NOT_DELETED);
|
|
|
|
|
+ Integer accessCount = accessMapper.selectCount(dupWrapper);
|
|
|
|
|
+ if (accessCount != null && accessCount > 0) {
|
|
|
|
|
+ throw new BusinessException("该电力户号已存在准入申请,请勿重复提交!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
VppCustomerAccess access = new VppCustomerAccess();
|
|
VppCustomerAccess access = new VppCustomerAccess();
|
|
|
access.setApplyNo(VppAuditHelper.nextApplyNo());
|
|
access.setApplyNo(VppAuditHelper.nextApplyNo());
|
|
|
access.setAccountNo(request.getAccountNo());
|
|
access.setAccountNo(request.getAccountNo());
|
|
@@ -58,112 +100,797 @@ public class VppCustomerServiceImpl implements VppCustomerService {
|
|
|
access.setCreditStatus(request.getCreditStatus());
|
|
access.setCreditStatus(request.getCreditStatus());
|
|
|
access.setAccessStatus(ACCESS_PENDING);
|
|
access.setAccessStatus(ACCESS_PENDING);
|
|
|
access.setApplyAt(LocalDateTime.now());
|
|
access.setApplyAt(LocalDateTime.now());
|
|
|
|
|
+ // 暂存联系人信息(审核通过时写入联系人表)
|
|
|
|
|
+ access.setContactName(request.getContactName());
|
|
|
|
|
+ access.setContactPhone(request.getContactPhone());
|
|
|
|
|
+ access.setContactEmail(request.getContactEmail());
|
|
|
|
|
+ access.setSigningDate(request.getSigningDate());
|
|
|
VppAuditHelper.fillCreate(access);
|
|
VppAuditHelper.fillCreate(access);
|
|
|
- accessMapper.insert(access);
|
|
|
|
|
- return access;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ int insert = accessMapper.insert(access);
|
|
|
|
|
+ return insert > 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public CommonPage<VppCustomerAccess> pageAccess(Map<String, Object> params) {
|
|
|
|
|
- Page<VppCustomerAccess> page = VppPageHelper.of(params);
|
|
|
|
|
|
|
+ public CommonPage<CustomerAccessResponseVO> pageAccess(Long id, Integer accessStatus, String customerName,
|
|
|
|
|
+ LocalDateTime startTime, LocalDateTime endTime,
|
|
|
|
|
+ Integer pageNum, Integer pageSize) {
|
|
|
|
|
+ IPage<VppCustomerAccess> page = new Page<>(pageNum, pageSize);
|
|
|
|
|
+
|
|
|
LambdaQueryWrapper<VppCustomerAccess> wrapper = new LambdaQueryWrapper<VppCustomerAccess>()
|
|
LambdaQueryWrapper<VppCustomerAccess> wrapper = new LambdaQueryWrapper<VppCustomerAccess>()
|
|
|
|
|
+ .eq(VppCustomerAccess::getTenantId, SecurityUtils.getTenantId())
|
|
|
.eq(VppCustomerAccess::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
.eq(VppCustomerAccess::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .eq(id != null, VppCustomerAccess::getId, id)
|
|
|
|
|
+ .eq(accessStatus != null, VppCustomerAccess::getAccessStatus, accessStatus)
|
|
|
|
|
+ .like(StringUtils.isNotBlank(customerName), VppCustomerAccess::getCustomerName, customerName)
|
|
|
|
|
+ .ge(startTime != null, VppCustomerAccess::getApplyAt, startTime)
|
|
|
|
|
+ .le(endTime != null, VppCustomerAccess::getApplyAt, endTime)
|
|
|
.orderByDesc(VppCustomerAccess::getApplyAt);
|
|
.orderByDesc(VppCustomerAccess::getApplyAt);
|
|
|
- if (params != null && params.get("accessStatus") != null) {
|
|
|
|
|
- wrapper.eq(VppCustomerAccess::getAccessStatus, Integer.parseInt(params.get("accessStatus").toString()));
|
|
|
|
|
|
|
+
|
|
|
|
|
+ page = accessMapper.selectPage(page, wrapper);
|
|
|
|
|
+
|
|
|
|
|
+ List<VppCustomerAccess> records = page.getRecords();
|
|
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
|
|
+ return new CommonPage<>(Collections.emptyList(), page.getTotal(), pageSize, pageNum);
|
|
|
}
|
|
}
|
|
|
- Page<VppCustomerAccess> result = accessMapper.selectPage(page, wrapper);
|
|
|
|
|
- return toCommonPage(result);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ List<CustomerAccessResponseVO> list = records.stream().map(entity -> {
|
|
|
|
|
+ CustomerAccessResponseVO vo = new CustomerAccessResponseVO();
|
|
|
|
|
+ BeanUtils.copyProperties(entity, vo);
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ return new CommonPage<>(list, page.getTotal(), pageSize, pageNum);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public void auditAccess(Long id, CustomerAccessAuditRequest request) {
|
|
|
|
|
- VppCustomerAccess access = accessMapper.selectById(id);
|
|
|
|
|
- if (access == null || VppAuditHelper.isDeleted(access.getDeleteFlag())) {
|
|
|
|
|
- throw new BusinessException("准入申请不存在");
|
|
|
|
|
|
|
+ public void auditAccess(CustomerAccessAuditRequest request) {
|
|
|
|
|
+ if (request.getId() == null) {
|
|
|
|
|
+ throw new BusinessException("准入申请ID不能为空!");
|
|
|
}
|
|
}
|
|
|
- if (access.getAccessStatus() != ACCESS_PENDING) {
|
|
|
|
|
- throw new BusinessException("当前状态不允许审核");
|
|
|
|
|
|
|
+ if (request.getResult() == null) {
|
|
|
|
|
+ throw new BusinessException("审核结果不能为空!");
|
|
|
}
|
|
}
|
|
|
- if (request.getPassed() == null) {
|
|
|
|
|
- throw new BusinessException("审核结果不能为空");
|
|
|
|
|
|
|
+ Long id = request.getId();
|
|
|
|
|
+ VppCustomerAccess access = findAccessById(id);
|
|
|
|
|
+
|
|
|
|
|
+ if (access.getAccessStatus() != ACCESS_PENDING) {
|
|
|
|
|
+ throw new BusinessException("当前状态不允许审核,仅待审核状态可操作!");
|
|
|
}
|
|
}
|
|
|
- access.setAuditOpinion(request.getAuditOpinion());
|
|
|
|
|
|
|
+
|
|
|
|
|
+ access.setAuditOpinion(request.getComment());
|
|
|
access.setAuditAt(LocalDateTime.now());
|
|
access.setAuditAt(LocalDateTime.now());
|
|
|
- if (Boolean.TRUE.equals(request.getPassed())) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (request.getResult() == ACCESS_PASSED) {
|
|
|
|
|
+ // 审核通过:校验同租户下 accountNo 和 customerName 唯一
|
|
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomer> dupWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ dupWrapper.eq(VppCustomer::getTenantId, tenantId)
|
|
|
|
|
+ .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .and(w -> w.eq(VppCustomer::getAccountNo, access.getAccountNo())
|
|
|
|
|
+ .or().eq(VppCustomer::getCustomerName, access.getCustomerName()));
|
|
|
|
|
+ Integer dupCount = customerMapper.selectCount(dupWrapper);
|
|
|
|
|
+ if (dupCount != null && dupCount > 0) {
|
|
|
|
|
+ throw new BusinessException("同租户下已存在相同电力户号或客户名称的客户!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
access.setAccessStatus(ACCESS_PASSED);
|
|
access.setAccessStatus(ACCESS_PASSED);
|
|
|
VppCustomer customer = buildCustomerFromAccess(access);
|
|
VppCustomer customer = buildCustomerFromAccess(access);
|
|
|
VppAuditHelper.fillCreate(customer);
|
|
VppAuditHelper.fillCreate(customer);
|
|
|
customerMapper.insert(customer);
|
|
customerMapper.insert(customer);
|
|
|
access.setCustomerId(customer.getId());
|
|
access.setCustomerId(customer.getId());
|
|
|
|
|
+
|
|
|
|
|
+ // 审核通过时,同步创建主联系人
|
|
|
|
|
+ if (StringUtils.isNotBlank(access.getContactName())) {
|
|
|
|
|
+ VppCustomerContact contact = new VppCustomerContact();
|
|
|
|
|
+ contact.setCustomerId(customer.getId());
|
|
|
|
|
+ contact.setContactName(access.getContactName());
|
|
|
|
|
+ contact.setContactPhone(access.getContactPhone());
|
|
|
|
|
+ contact.setContactEmail(access.getContactEmail());
|
|
|
|
|
+ contact.setIsPrimary(1);
|
|
|
|
|
+ VppAuditHelper.fillCreate(contact);
|
|
|
|
|
+ contactMapper.insert(contact);
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
access.setAccessStatus(ACCESS_REJECTED);
|
|
access.setAccessStatus(ACCESS_REJECTED);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
VppAuditHelper.fillUpdate(access);
|
|
VppAuditHelper.fillUpdate(access);
|
|
|
accessMapper.updateById(access);
|
|
accessMapper.updateById(access);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public CommonPage<VppCustomer> pageCustomer(Map<String, Object> params) {
|
|
|
|
|
- Page<VppCustomer> page = VppPageHelper.of(params);
|
|
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public Boolean deleteAccess(Long id) {
|
|
|
|
|
+ VppCustomerAccess access = findAccessById(id);
|
|
|
|
|
+ if (access.getAccessStatus() == ACCESS_PENDING) {
|
|
|
|
|
+ throw new BusinessException("待审核的准入申请无法删除,请先审核或驳回后再操作!");
|
|
|
|
|
+ }
|
|
|
|
|
+ VppAuditHelper.fillSoftDelete(access);
|
|
|
|
|
+ int result = accessMapper.updateById(access);
|
|
|
|
|
+ return result > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void exportAccess(String scope, String fileName, Long id, Integer accessStatus,
|
|
|
|
|
+ String customerName, LocalDateTime startTime, LocalDateTime endTime,
|
|
|
|
|
+ Integer current, Integer size, HttpServletResponse response) {
|
|
|
|
|
+ List<VppCustomerAccess> accessList;
|
|
|
|
|
+
|
|
|
|
|
+ switch (scope) {
|
|
|
|
|
+ case "current":
|
|
|
|
|
+ if (current == null || size == null) {
|
|
|
|
|
+ throw new BusinessException("current 模式下分页参数 current 和 size 不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ IPage<VppCustomerAccess> page = new Page<>(current, size);
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerAccess> currWrapper = new LambdaQueryWrapper<VppCustomerAccess>()
|
|
|
|
|
+ .eq(VppCustomerAccess::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomerAccess::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .orderByDesc(VppCustomerAccess::getApplyAt);
|
|
|
|
|
+ page = accessMapper.selectPage(page, currWrapper);
|
|
|
|
|
+ accessList = page.getRecords();
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "all":
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerAccess> allWrapper = new LambdaQueryWrapper<VppCustomerAccess>()
|
|
|
|
|
+ .eq(VppCustomerAccess::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomerAccess::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .orderByDesc(VppCustomerAccess::getApplyAt);
|
|
|
|
|
+ accessList = accessMapper.selectList(allWrapper);
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "filtered":
|
|
|
|
|
+ boolean hasFilter = id != null || accessStatus != null
|
|
|
|
|
+ || StringUtils.isNotBlank(customerName)
|
|
|
|
|
+ || startTime != null || endTime != null;
|
|
|
|
|
+ if (!hasFilter) {
|
|
|
|
|
+ throw new BusinessException("filtered 模式下请至少提供一个筛选条件(id / accessStatus / customerName / startTime / endTime)!");
|
|
|
|
|
+ }
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerAccess> filteredWrapper = new LambdaQueryWrapper<VppCustomerAccess>()
|
|
|
|
|
+ .eq(VppCustomerAccess::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomerAccess::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .eq(id != null, VppCustomerAccess::getId, id)
|
|
|
|
|
+ .eq(accessStatus != null, VppCustomerAccess::getAccessStatus, accessStatus)
|
|
|
|
|
+ .like(StringUtils.isNotBlank(customerName), VppCustomerAccess::getCustomerName, customerName)
|
|
|
|
|
+ .ge(startTime != null, VppCustomerAccess::getApplyAt, startTime)
|
|
|
|
|
+ .le(endTime != null, VppCustomerAccess::getApplyAt, endTime)
|
|
|
|
|
+ .orderByDesc(VppCustomerAccess::getApplyAt);
|
|
|
|
|
+ accessList = accessMapper.selectList(filteredWrapper);
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new BusinessException("不支持的导出范围:" + scope + ",请使用 current / all / filtered");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (accessList == null) {
|
|
|
|
|
+ accessList = Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 生成 Excel
|
|
|
|
|
+ try (org.apache.poi.xssf.usermodel.XSSFWorkbook workbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook()) {
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFSheet sheet = workbook.createSheet("准入申请数据");
|
|
|
|
|
+
|
|
|
|
|
+ // 表头样式
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFCellStyle headerStyle = workbook.createCellStyle();
|
|
|
|
|
+ headerStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.GREY_25_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFFont headerFont = workbook.createFont();
|
|
|
|
|
+ headerFont.setBold(true);
|
|
|
|
|
+ headerStyle.setFont(headerFont);
|
|
|
|
|
+
|
|
|
|
|
+ // 表头
|
|
|
|
|
+ String[] headers = {
|
|
|
|
|
+ "序号", "申请编号", "电力户号", "客户名称", "客户类型",
|
|
|
|
|
+ "供电公司", "用电容量(kW)", "信用状态", "审核状态",
|
|
|
|
|
+ "审核意见", "营业执照",
|
|
|
|
|
+ //"签约日期", "联系人姓名", "联系人电话", "联系人邮箱",
|
|
|
|
|
+ "申请时间", "审核时间", "创建时间"
|
|
|
|
|
+ };
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFRow headerRow = sheet.createRow(0);
|
|
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFCell cell = headerRow.createCell(i);
|
|
|
|
|
+ cell.setCellValue(headers[i]);
|
|
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 数据行
|
|
|
|
|
+ int rowIdx = 1;
|
|
|
|
|
+ for (VppCustomerAccess acc : accessList) {
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFRow row = sheet.createRow(rowIdx++);
|
|
|
|
|
+ setCell(row, 0, rowIdx - 1);
|
|
|
|
|
+ setCell(row, 1, acc.getApplyNo());
|
|
|
|
|
+ setCell(row, 2, acc.getAccountNo());
|
|
|
|
|
+ setCell(row, 3, acc.getCustomerName());
|
|
|
|
|
+ setCell(row, 4, typeLabel(acc.getCustomerType(), "中高压", "低压商用", "居民充电桩", "自有资产"));
|
|
|
|
|
+ setCell(row, 5, acc.getPowerCompany());
|
|
|
|
|
+ setCell(row, 6, numberStr(acc.getContractCapacity()));
|
|
|
|
|
+ setCell(row, 7, typeLabel(acc.getCreditStatus(), "正常", "关注", "不良"));
|
|
|
|
|
+ setCell(row, 8, accessStatusLabel(acc.getAccessStatus()));
|
|
|
|
|
+ setCell(row, 9, acc.getAuditOpinion());
|
|
|
|
|
+ insertLicenseImage(workbook, sheet, row, 10, acc.getBusinessLicenseUrl());
|
|
|
|
|
+ // setCell(row, 11, dateStr(acc.getSigningDate()));
|
|
|
|
|
+ // setCell(row, 12, acc.getContactName());
|
|
|
|
|
+ // setCell(row, 13, acc.getContactPhone());
|
|
|
|
|
+ // setCell(row, 14, acc.getContactEmail());
|
|
|
|
|
+ setCell(row, 11, dateTimeStr(acc.getApplyAt()));
|
|
|
|
|
+ setCell(row, 12, dateTimeStr(acc.getAuditAt()));
|
|
|
|
|
+ setCell(row, 13, dateTimeStr(acc.getCreateTime()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 营业执照列宽度
|
|
|
|
|
+ sheet.setColumnWidth(10, 28 * 256);
|
|
|
|
|
+
|
|
|
|
|
+ // 自动调整列宽
|
|
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
|
|
+ if (i == 10) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ sheet.autoSizeColumn(i);
|
|
|
|
|
+ int width = sheet.getColumnWidth(i);
|
|
|
|
|
+ if (width > 15000) {
|
|
|
|
|
+ sheet.setColumnWidth(i, 15000);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 写入响应
|
|
|
|
|
+ String encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", StandardCharsets.UTF_8.name())
|
|
|
|
|
+ .replace("+", "%20");
|
|
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
|
|
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
|
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName
|
|
|
|
|
+ + "; filename*=UTF-8''" + encodedFileName);
|
|
|
|
|
+ workbook.write(response.getOutputStream());
|
|
|
|
|
+ response.getOutputStream().flush();
|
|
|
|
|
+
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ throw new BusinessException("导出准入申请数据失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 客户管理 ====================
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public Boolean createCustomer(CustomerRequestVO vo) {
|
|
|
|
|
+ // 必填校验
|
|
|
|
|
+ if (StringUtils.isBlank(vo.getAccountNo())) {
|
|
|
|
|
+ throw new BusinessException("电力户号不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(vo.getCustomerName())) {
|
|
|
|
|
+ throw new BusinessException("客户名称不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getCustomerType() == null) {
|
|
|
|
|
+ throw new BusinessException("客户类型不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(vo.getPowerCompany())) {
|
|
|
|
|
+ throw new BusinessException("供电公司不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getContractCapacity() == null) {
|
|
|
|
|
+ throw new BusinessException("用电容量不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 同租户下 accountNo 和 customerName 唯一校验
|
|
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomer> dupWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ dupWrapper.eq(VppCustomer::getTenantId, tenantId)
|
|
|
|
|
+ .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .and(w -> w.eq(VppCustomer::getAccountNo, vo.getAccountNo())
|
|
|
|
|
+ .or().eq(VppCustomer::getCustomerName, vo.getCustomerName()));
|
|
|
|
|
+ Integer dupCount = customerMapper.selectCount(dupWrapper);
|
|
|
|
|
+ if (dupCount != null && dupCount > 0) {
|
|
|
|
|
+ throw new BusinessException("同租户下已存在相同电力户号或客户名称的客户!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构建客户
|
|
|
|
|
+ VppCustomer customer = new VppCustomer();
|
|
|
|
|
+ customer.setAccountNo(vo.getAccountNo());
|
|
|
|
|
+ customer.setCustomerName(vo.getCustomerName());
|
|
|
|
|
+ customer.setCustomerType(vo.getCustomerType());
|
|
|
|
|
+ customer.setPowerCompany(vo.getPowerCompany());
|
|
|
|
|
+ customer.setContractCapacity(vo.getContractCapacity());
|
|
|
|
|
+ customer.setCreditStatus(vo.getCreditStatus());
|
|
|
|
|
+ customer.setBusinessLicenseUrl(vo.getBusinessLicenseUrl());
|
|
|
|
|
+ customer.setLifecycleStatus(vo.getLifecycleStatus());
|
|
|
|
|
+ customer.setDrNotifyMinutes(vo.getDrNotifyMinutes());
|
|
|
|
|
+ customer.setDrUpCapacityKw(vo.getDrUpCapacityKw());
|
|
|
|
|
+ customer.setDrDownCapacityKw(vo.getDrDownCapacityKw());
|
|
|
|
|
+ customer.setProvince(vo.getProvince());
|
|
|
|
|
+ customer.setCity(vo.getCity());
|
|
|
|
|
+ customer.setDistrict(vo.getDistrict());
|
|
|
|
|
+ customer.setAddress(vo.getAddress());
|
|
|
|
|
+ customer.setRemark(vo.getRemark());
|
|
|
|
|
+ customer.setIsVppResource(vo.getIsVppResource());
|
|
|
|
|
+ customer.setVppCategory(vo.getVppCategory());
|
|
|
|
|
+ customer.setDrResourceType(vo.getDrResourceType());
|
|
|
|
|
+ customer.setVppProxyCode(vo.getVppProxyCode());
|
|
|
|
|
+ customer.setIndustry(vo.getIndustry());
|
|
|
|
|
+ customer.setIndustrySector(vo.getIndustrySector());
|
|
|
|
|
+ customer.setRunningCapacity(vo.getRunningCapacity());
|
|
|
|
|
+ customer.setProductionStartDate(vo.getProductionStartDate());
|
|
|
|
|
+ customer.setProductionEndDate(vo.getProductionEndDate());
|
|
|
|
|
+ customer.setPowerAddress(vo.getPowerAddress());
|
|
|
|
|
+ customer.setSigningDate(vo.getSigningDate());
|
|
|
|
|
+ VppAuditHelper.fillCreate(customer);
|
|
|
|
|
+ customerMapper.insert(customer);
|
|
|
|
|
+
|
|
|
|
|
+ // 若有联系人信息,创建主联系人
|
|
|
|
|
+ if (StringUtils.isNotBlank(vo.getContactName())) {
|
|
|
|
|
+ VppFieldValidator.validatePhone(vo.getContactPhone());
|
|
|
|
|
+ if (StringUtils.isNotBlank(vo.getContactEmail())) {
|
|
|
|
|
+ VppFieldValidator.validateEmail(vo.getContactEmail());
|
|
|
|
|
+ }
|
|
|
|
|
+ VppCustomerContact contact = new VppCustomerContact();
|
|
|
|
|
+ contact.setCustomerId(customer.getId());
|
|
|
|
|
+ contact.setContactName(vo.getContactName());
|
|
|
|
|
+ contact.setContactPhone(vo.getContactPhone());
|
|
|
|
|
+ contact.setContactEmail(vo.getContactEmail());
|
|
|
|
|
+ contact.setIsPrimary(1);
|
|
|
|
|
+ VppAuditHelper.fillCreate(contact);
|
|
|
|
|
+ contactMapper.insert(contact);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public CommonPage<CustomerResponseVO> pageCustomer(Long id, String customerName, String accountNo,
|
|
|
|
|
+ Integer lifecycleStatus, Integer customerType,
|
|
|
|
|
+ Integer pageNum, Integer pageSize) {
|
|
|
|
|
+ IPage<VppCustomer> page = new Page<>(pageNum, pageSize);
|
|
|
|
|
+
|
|
|
LambdaQueryWrapper<VppCustomer> wrapper = new LambdaQueryWrapper<VppCustomer>()
|
|
LambdaQueryWrapper<VppCustomer> wrapper = new LambdaQueryWrapper<VppCustomer>()
|
|
|
|
|
+ .eq(VppCustomer::getTenantId, SecurityUtils.getTenantId())
|
|
|
.eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
.eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .eq(id != null, VppCustomer::getId, id)
|
|
|
|
|
+ .like(StringUtils.isNotBlank(customerName), VppCustomer::getCustomerName, customerName)
|
|
|
|
|
+ .eq(StringUtils.isNotBlank(accountNo), VppCustomer::getAccountNo, accountNo)
|
|
|
|
|
+ .eq(lifecycleStatus != null, VppCustomer::getLifecycleStatus, lifecycleStatus)
|
|
|
|
|
+ .eq(customerType != null, VppCustomer::getCustomerType, customerType)
|
|
|
.orderByDesc(VppCustomer::getCreateTime);
|
|
.orderByDesc(VppCustomer::getCreateTime);
|
|
|
- if (params != null) {
|
|
|
|
|
- if (params.get("customerName") != null) {
|
|
|
|
|
- wrapper.like(VppCustomer::getCustomerName, params.get("customerName").toString());
|
|
|
|
|
|
|
+
|
|
|
|
|
+ page = customerMapper.selectPage(page, wrapper);
|
|
|
|
|
+ List<VppCustomer> records = page.getRecords();
|
|
|
|
|
+
|
|
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
|
|
+ return new CommonPage<>(Collections.emptyList(), page.getTotal(), pageSize, pageNum);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 批量查询关联合同
|
|
|
|
|
+ final Set<Long> customerIds = records.stream()
|
|
|
|
|
+ .map(VppCustomer::getId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ final Map<Long, List<VppContract>> contractMap;
|
|
|
|
|
+ if (!customerIds.isEmpty()) {
|
|
|
|
|
+ LambdaQueryWrapper<VppContract> contractWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ contractWrapper.in(VppContract::getCustomerId, customerIds)
|
|
|
|
|
+ .eq(VppContract::getDeleteFlag, 0)
|
|
|
|
|
+ .orderByDesc(VppContract::getCreateTime);
|
|
|
|
|
+ List<VppContract> allContracts = contractMapper.selectList(contractWrapper);
|
|
|
|
|
+ contractMap = allContracts.stream()
|
|
|
|
|
+ .collect(Collectors.groupingBy(VppContract::getCustomerId));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ contractMap = Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 批量查询关联联系人(取主联系人 is_primary=1)
|
|
|
|
|
+ final Map<Long, VppCustomerContact> contactMap;
|
|
|
|
|
+ if (!customerIds.isEmpty()) {
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerContact> contactWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ contactWrapper.in(VppCustomerContact::getCustomerId, customerIds)
|
|
|
|
|
+ .eq(VppCustomerContact::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomerContact::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .eq(VppCustomerContact::getIsPrimary, 1);
|
|
|
|
|
+ List<VppCustomerContact> allContacts = contactMapper.selectList(contactWrapper);
|
|
|
|
|
+ contactMap = allContacts.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(
|
|
|
|
|
+ VppCustomerContact::getCustomerId,
|
|
|
|
|
+ Function.identity(),
|
|
|
|
|
+ (a, b) -> a));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ contactMap = Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 组装响应VO
|
|
|
|
|
+ List<CustomerResponseVO> list = records.stream().map(entity -> {
|
|
|
|
|
+ CustomerResponseVO vo = new CustomerResponseVO();
|
|
|
|
|
+ BeanUtils.copyProperties(entity, vo);
|
|
|
|
|
+ // 设置关联合同
|
|
|
|
|
+ List<VppContract> contracts = contractMap.getOrDefault(entity.getId(), Collections.emptyList());
|
|
|
|
|
+ vo.setContracts(contracts.stream().map(this::toContractBrief).collect(Collectors.toList()));
|
|
|
|
|
+ // 设置关联联系人
|
|
|
|
|
+ VppCustomerContact contact = contactMap.get(entity.getId());
|
|
|
|
|
+ if (contact != null) {
|
|
|
|
|
+ vo.setContactName(contact.getContactName());
|
|
|
|
|
+ vo.setContactPhone(contact.getContactPhone());
|
|
|
|
|
+ vo.setContactEmail(contact.getContactEmail());
|
|
|
}
|
|
}
|
|
|
- if (params.get("accountNo") != null) {
|
|
|
|
|
- wrapper.eq(VppCustomer::getAccountNo, params.get("accountNo").toString());
|
|
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ return new CommonPage<>(list, page.getTotal(), pageSize, pageNum);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public CustomerResponseVO getCustomer(Long id) {
|
|
|
|
|
+ VppCustomer customer = findCustomerById(id);
|
|
|
|
|
+
|
|
|
|
|
+ CustomerResponseVO vo = new CustomerResponseVO();
|
|
|
|
|
+ BeanUtils.copyProperties(customer, vo);
|
|
|
|
|
+
|
|
|
|
|
+ // 关联查询合同
|
|
|
|
|
+ LambdaQueryWrapper<VppContract> contractWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ contractWrapper.eq(VppContract::getCustomerId, id)
|
|
|
|
|
+ .eq(VppContract::getDeleteFlag, 0)
|
|
|
|
|
+ .orderByDesc(VppContract::getCreateTime);
|
|
|
|
|
+ List<VppContract> contracts = contractMapper.selectList(contractWrapper);
|
|
|
|
|
+ vo.setContracts(contracts.stream().map(this::toContractBrief).collect(Collectors.toList()));
|
|
|
|
|
+
|
|
|
|
|
+ // 关联查询主联系人
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerContact> contactWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ contactWrapper.eq(VppCustomerContact::getCustomerId, id)
|
|
|
|
|
+ .eq(VppCustomerContact::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomerContact::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .eq(VppCustomerContact::getIsPrimary, 1);
|
|
|
|
|
+ VppCustomerContact contact = contactMapper.selectOne(contactWrapper);
|
|
|
|
|
+ if (contact != null) {
|
|
|
|
|
+ vo.setContactName(contact.getContactName());
|
|
|
|
|
+ vo.setContactPhone(contact.getContactPhone());
|
|
|
|
|
+ vo.setContactEmail(contact.getContactEmail());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public Boolean updateCustomer(CustomerRequestVO vo) {
|
|
|
|
|
+ if (vo.getId() == null) {
|
|
|
|
|
+ throw new BusinessException("客户ID不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ Long id = vo.getId();
|
|
|
|
|
+ VppCustomer existing = findCustomerById(id);
|
|
|
|
|
+
|
|
|
|
|
+ // 仅更新非空字段
|
|
|
|
|
+ if (StringUtils.isNotBlank(vo.getCustomerName())) {
|
|
|
|
|
+ if (!vo.getCustomerName().equals(existing.getCustomerName())) {
|
|
|
|
|
+ // 校验同租户下客户名称唯一
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomer> uniqueWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ uniqueWrapper.eq(VppCustomer::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomer::getCustomerName, vo.getCustomerName())
|
|
|
|
|
+ .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED);
|
|
|
|
|
+ Integer count = customerMapper.selectCount(uniqueWrapper);
|
|
|
|
|
+ if (count != null && count > 0) {
|
|
|
|
|
+ throw new BusinessException("客户名称已存在,请更换!");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- if (params.get("lifecycleStatus") != null) {
|
|
|
|
|
- wrapper.eq(VppCustomer::getLifecycleStatus, Integer.parseInt(params.get("lifecycleStatus").toString()));
|
|
|
|
|
|
|
+ existing.setCustomerName(vo.getCustomerName());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getCustomerType() != null) {
|
|
|
|
|
+ existing.setCustomerType(vo.getCustomerType());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isNotBlank(vo.getPowerCompany())) {
|
|
|
|
|
+ existing.setPowerCompany(vo.getPowerCompany());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getContractCapacity() != null) {
|
|
|
|
|
+ existing.setContractCapacity(vo.getContractCapacity());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getCreditStatus() != null) {
|
|
|
|
|
+ existing.setCreditStatus(vo.getCreditStatus());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getLifecycleStatus() != null) {
|
|
|
|
|
+ existing.setLifecycleStatus(vo.getLifecycleStatus());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getDrNotifyMinutes() != null) {
|
|
|
|
|
+ existing.setDrNotifyMinutes(vo.getDrNotifyMinutes());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getDrUpCapacityKw() != null) {
|
|
|
|
|
+ existing.setDrUpCapacityKw(vo.getDrUpCapacityKw());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getDrDownCapacityKw() != null) {
|
|
|
|
|
+ existing.setDrDownCapacityKw(vo.getDrDownCapacityKw());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getProvince() != null) {
|
|
|
|
|
+ existing.setProvince(vo.getProvince());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getCity() != null) {
|
|
|
|
|
+ existing.setCity(vo.getCity());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getDistrict() != null) {
|
|
|
|
|
+ existing.setDistrict(vo.getDistrict());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getAddress() != null) {
|
|
|
|
|
+ existing.setAddress(vo.getAddress());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getBusinessLicenseUrl() != null) {
|
|
|
|
|
+ existing.setBusinessLicenseUrl(vo.getBusinessLicenseUrl());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getRemark() != null) {
|
|
|
|
|
+ existing.setRemark(vo.getRemark());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getIsVppResource() != null) {
|
|
|
|
|
+ existing.setIsVppResource(vo.getIsVppResource());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getVppCategory() != null) {
|
|
|
|
|
+ existing.setVppCategory(vo.getVppCategory());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getDrResourceType() != null) {
|
|
|
|
|
+ existing.setDrResourceType(vo.getDrResourceType());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getVppProxyCode() != null) {
|
|
|
|
|
+ existing.setVppProxyCode(vo.getVppProxyCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getIndustry() != null) {
|
|
|
|
|
+ existing.setIndustry(vo.getIndustry());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getIndustrySector() != null) {
|
|
|
|
|
+ existing.setIndustrySector(vo.getIndustrySector());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getRunningCapacity() != null) {
|
|
|
|
|
+ existing.setRunningCapacity(vo.getRunningCapacity());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getProductionStartDate() != null) {
|
|
|
|
|
+ existing.setProductionStartDate(vo.getProductionStartDate());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getProductionEndDate() != null) {
|
|
|
|
|
+ existing.setProductionEndDate(vo.getProductionEndDate());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getPowerAddress() != null) {
|
|
|
|
|
+ existing.setPowerAddress(vo.getPowerAddress());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (vo.getSigningDate() != null) {
|
|
|
|
|
+ existing.setSigningDate(vo.getSigningDate());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 同步维护主联系人:如果传了联系人姓名,则 upsert 主联系人
|
|
|
|
|
+ if (StringUtils.isNotBlank(vo.getContactName())) {
|
|
|
|
|
+ // 校验联系人信息
|
|
|
|
|
+ VppFieldValidator.validatePhone(vo.getContactPhone());
|
|
|
|
|
+ if (StringUtils.isNotBlank(vo.getContactEmail())) {
|
|
|
|
|
+ VppFieldValidator.validateEmail(vo.getContactEmail());
|
|
|
}
|
|
}
|
|
|
- if (params.get("customerType") != null) {
|
|
|
|
|
- wrapper.eq(VppCustomer::getCustomerType, Integer.parseInt(params.get("customerType").toString()));
|
|
|
|
|
|
|
+
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerContact> contactWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ contactWrapper.eq(VppCustomerContact::getCustomerId, id)
|
|
|
|
|
+ .eq(VppCustomerContact::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomerContact::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .eq(VppCustomerContact::getIsPrimary, 1);
|
|
|
|
|
+ VppCustomerContact contact = contactMapper.selectOne(contactWrapper);
|
|
|
|
|
+
|
|
|
|
|
+ if (contact != null) {
|
|
|
|
|
+ // 更新已有主联系人
|
|
|
|
|
+ contact.setContactName(vo.getContactName());
|
|
|
|
|
+ contact.setContactPhone(vo.getContactPhone());
|
|
|
|
|
+ contact.setContactEmail(vo.getContactEmail());
|
|
|
|
|
+ VppAuditHelper.fillUpdate(contact);
|
|
|
|
|
+ contactMapper.updateById(contact);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 新增主联系人
|
|
|
|
|
+ contact = new VppCustomerContact();
|
|
|
|
|
+ contact.setCustomerId(id);
|
|
|
|
|
+ contact.setContactName(vo.getContactName());
|
|
|
|
|
+ contact.setContactPhone(vo.getContactPhone());
|
|
|
|
|
+ contact.setContactEmail(vo.getContactEmail());
|
|
|
|
|
+ contact.setIsPrimary(1);
|
|
|
|
|
+ VppAuditHelper.fillCreate(contact);
|
|
|
|
|
+ contactMapper.insert(contact);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return toCommonPage(customerMapper.selectPage(page, wrapper));
|
|
|
|
|
|
|
+
|
|
|
|
|
+ VppAuditHelper.fillUpdate(existing);
|
|
|
|
|
+ int result = customerMapper.updateById(existing);
|
|
|
|
|
+ return result > 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public VppCustomer getCustomer(Long id) {
|
|
|
|
|
- VppCustomer customer = customerMapper.selectById(id);
|
|
|
|
|
- if (customer == null || VppAuditHelper.isDeleted(customer.getDeleteFlag())) {
|
|
|
|
|
- throw new BusinessException("客户不存在");
|
|
|
|
|
- }
|
|
|
|
|
- return customer;
|
|
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public Boolean deleteCustomer(Long id) {
|
|
|
|
|
+ VppCustomer customer = findCustomerById(id);
|
|
|
|
|
+
|
|
|
|
|
+ VppAuditHelper.fillSoftDelete(customer);
|
|
|
|
|
+ int result = customerMapper.updateById(customer);
|
|
|
|
|
+ return result > 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
- public void updateCustomer(Long id, VppCustomer customer) {
|
|
|
|
|
- VppCustomer existing = getCustomer(id);
|
|
|
|
|
- customer.setId(existing.getId());
|
|
|
|
|
- customer.setAccountNo(existing.getAccountNo());
|
|
|
|
|
- customer.setCreateTime(existing.getCreateTime());
|
|
|
|
|
- customer.setCreatedBy(existing.getCreatedBy());
|
|
|
|
|
- VppAuditHelper.fillUpdate(customer);
|
|
|
|
|
- customerMapper.updateById(customer);
|
|
|
|
|
|
|
+ public void exportCustomers(String scope, String fileName, String customerName,
|
|
|
|
|
+ Integer lifecycleStatus, Integer current, Integer size,
|
|
|
|
|
+ HttpServletResponse response) {
|
|
|
|
|
+ List<VppCustomer> customers;
|
|
|
|
|
+
|
|
|
|
|
+ switch (scope) {
|
|
|
|
|
+ case "current":
|
|
|
|
|
+ // current 模式:必须传分页参数
|
|
|
|
|
+ if (current == null || size == null) {
|
|
|
|
|
+ throw new BusinessException("current 模式下分页参数 current 和 size 不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ IPage<VppCustomer> page = new Page<>(current, size);
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomer> currWrapper = new LambdaQueryWrapper<VppCustomer>()
|
|
|
|
|
+ .eq(VppCustomer::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .orderByDesc(VppCustomer::getCreateTime);
|
|
|
|
|
+ page = customerMapper.selectPage(page, currWrapper);
|
|
|
|
|
+ customers = page.getRecords();
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "all":
|
|
|
|
|
+ // all 模式:当前租户下所有未删除客户
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomer> allWrapper = new LambdaQueryWrapper<VppCustomer>()
|
|
|
|
|
+ .eq(VppCustomer::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .orderByDesc(VppCustomer::getCreateTime);
|
|
|
|
|
+ customers = customerMapper.selectList(allWrapper);
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "filtered":
|
|
|
|
|
+ // filtered 模式:必须至少传一个筛选条件
|
|
|
|
|
+ boolean hasFilter = StringUtils.isNotBlank(customerName) || lifecycleStatus != null;
|
|
|
|
|
+ if (!hasFilter) {
|
|
|
|
|
+ throw new BusinessException("filtered 模式下请至少提供一个筛选条件(customerName / lifecycleStatus)!");
|
|
|
|
|
+ }
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomer> filteredWrapper = new LambdaQueryWrapper<VppCustomer>()
|
|
|
|
|
+ .eq(VppCustomer::getTenantId, SecurityUtils.getTenantId())
|
|
|
|
|
+ .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
|
|
+ .like(StringUtils.isNotBlank(customerName), VppCustomer::getCustomerName, customerName)
|
|
|
|
|
+ .eq(lifecycleStatus != null, VppCustomer::getLifecycleStatus, lifecycleStatus)
|
|
|
|
|
+ .orderByDesc(VppCustomer::getCreateTime);
|
|
|
|
|
+ customers = customerMapper.selectList(filteredWrapper);
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new BusinessException("不支持的导出范围:" + scope + ",请使用 current / all / filtered");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (customers == null) {
|
|
|
|
|
+ customers = Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 批量查询关联合同
|
|
|
|
|
+ final Set<Long> customerIds = customers.stream()
|
|
|
|
|
+ .map(VppCustomer::getId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ final Map<Long, List<VppContract>> contractMap;
|
|
|
|
|
+ if (!customerIds.isEmpty()) {
|
|
|
|
|
+ LambdaQueryWrapper<VppContract> contractWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ contractWrapper.in(VppContract::getCustomerId, customerIds)
|
|
|
|
|
+ .eq(VppContract::getDeleteFlag, 0);
|
|
|
|
|
+ List<VppContract> allContracts = contractMapper.selectList(contractWrapper);
|
|
|
|
|
+ contractMap = allContracts.stream()
|
|
|
|
|
+ .collect(Collectors.groupingBy(VppContract::getCustomerId));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ contractMap = Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 生成 Excel
|
|
|
|
|
+ try (org.apache.poi.xssf.usermodel.XSSFWorkbook workbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook()) {
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFSheet sheet = workbook.createSheet("客户数据");
|
|
|
|
|
+
|
|
|
|
|
+ // 创建表头样式
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFCellStyle headerStyle = workbook.createCellStyle();
|
|
|
|
|
+ headerStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.GREY_25_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFFont headerFont = workbook.createFont();
|
|
|
|
|
+ headerFont.setBold(true);
|
|
|
|
|
+ headerStyle.setFont(headerFont);
|
|
|
|
|
+
|
|
|
|
|
+ // 表头
|
|
|
|
|
+ String[] headers = {
|
|
|
|
|
+ "序号", "电力户号", "客户名称", "客户类型", "供电公司",
|
|
|
|
|
+ "签约容量(kW)", "信用状态", "签约状态",
|
|
|
|
|
+ "省", "市", "区/县", "地址", "用电地址",
|
|
|
|
|
+ "是否VPP资源", "VPP分类", "需求响应资源分类",
|
|
|
|
|
+ "VPP代理编码", "所属行业", "所属产业", "运行容量(kW)",
|
|
|
|
|
+ "生产经营开始", "生产经营结束", "合同编号列表",
|
|
|
|
|
+ "营业执照", "签约日期", "备注", "创建时间"
|
|
|
|
|
+ };
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFRow headerRow = sheet.createRow(0);
|
|
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFCell cell = headerRow.createCell(i);
|
|
|
|
|
+ cell.setCellValue(headers[i]);
|
|
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 数据行
|
|
|
|
|
+ int rowIdx = 1;
|
|
|
|
|
+ for (VppCustomer c : customers) {
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFRow row = sheet.createRow(rowIdx++);
|
|
|
|
|
+ List<VppContract> contracts = contractMap.getOrDefault(c.getId(), Collections.emptyList());
|
|
|
|
|
+ String contractNos = contracts.stream()
|
|
|
|
|
+ .map(VppContract::getContractNo)
|
|
|
|
|
+ .collect(Collectors.joining("; "));
|
|
|
|
|
+
|
|
|
|
|
+ setCell(row, 0, rowIdx - 1); // 序号
|
|
|
|
|
+ setCell(row, 1, c.getAccountNo());
|
|
|
|
|
+ setCell(row, 2, c.getCustomerName());
|
|
|
|
|
+ setCell(row, 3, typeLabel(c.getCustomerType(), "中高压", "低压商用", "居民充电桩", "自有资产"));
|
|
|
|
|
+ setCell(row, 4, c.getPowerCompany());
|
|
|
|
|
+ setCell(row, 5, numberStr(c.getContractCapacity()));
|
|
|
|
|
+ setCell(row, 6, typeLabel(c.getCreditStatus(), "正常", "关注", "不良"));
|
|
|
|
|
+ setCell(row, 7, typeLabel(c.getLifecycleStatus(), "待签约", "已签约", "履约中", "待续约", "已续约", "已解约"));
|
|
|
|
|
+ setCell(row, 8, c.getProvince());
|
|
|
|
|
+ setCell(row, 9, c.getCity());
|
|
|
|
|
+ setCell(row, 10, c.getDistrict());
|
|
|
|
|
+ setCell(row, 11, c.getAddress());
|
|
|
|
|
+ setCell(row, 12, c.getPowerAddress());
|
|
|
|
|
+ setCell(row, 13, c.getIsVppResource() != null && c.getIsVppResource() == 1 ? "是" : "否");
|
|
|
|
|
+ setCell(row, 14, c.getVppCategory());
|
|
|
|
|
+ setCell(row, 15, c.getDrResourceType());
|
|
|
|
|
+ setCell(row, 16, c.getVppProxyCode());
|
|
|
|
|
+ setCell(row, 17, c.getIndustry());
|
|
|
|
|
+ setCell(row, 18, c.getIndustrySector());
|
|
|
|
|
+ setCell(row, 19, numberStr(c.getRunningCapacity()));
|
|
|
|
|
+ setCell(row, 20, timeStr(c.getProductionStartDate()));
|
|
|
|
|
+ setCell(row, 21, timeStr(c.getProductionEndDate()));
|
|
|
|
|
+ setCell(row, 22, contractNos);
|
|
|
|
|
+ insertLicenseImage(workbook, sheet, row, 23, c.getBusinessLicenseUrl());
|
|
|
|
|
+ setCell(row, 24, dateStr(c.getSigningDate()));
|
|
|
|
|
+ setCell(row, 25, c.getRemark());
|
|
|
|
|
+ setCell(row, 26, dateTimeStr(c.getCreateTime()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置营业执照列宽度(固定宽度以适配图片)
|
|
|
|
|
+ sheet.setColumnWidth(23, 28 * 256); // 约 28 字符宽
|
|
|
|
|
+
|
|
|
|
|
+ // 自动调整列宽
|
|
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
|
|
+ if (i == 23) {
|
|
|
|
|
+ continue; // 营业执照列已手动设置宽度
|
|
|
|
|
+ }
|
|
|
|
|
+ sheet.autoSizeColumn(i);
|
|
|
|
|
+ // 限制最大宽度,避免营业执照URL列过宽
|
|
|
|
|
+ int width = sheet.getColumnWidth(i);
|
|
|
|
|
+ if (width > 15000) {
|
|
|
|
|
+ sheet.setColumnWidth(i, 15000);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 写入响应
|
|
|
|
|
+ String encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", StandardCharsets.UTF_8.name())
|
|
|
|
|
+ .replace("+", "%20");
|
|
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
|
|
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
|
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName
|
|
|
|
|
+ + "; filename*=UTF-8''" + encodedFileName);
|
|
|
|
|
+ workbook.write(response.getOutputStream());
|
|
|
|
|
+ response.getOutputStream().flush();
|
|
|
|
|
+
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ throw new BusinessException("导出客户数据失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ==================== 联系人管理 ====================
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
- public CommonPage<VppCustomerContact> listContact(Long customerId, Map<String, Object> params) {
|
|
|
|
|
- getCustomer(customerId);
|
|
|
|
|
- Page<VppCustomerContact> page = VppPageHelper.of(params);
|
|
|
|
|
|
|
+ public CommonPage<VppCustomerContact> listContact(Long customerId, Integer pageNum, Integer pageSize) {
|
|
|
|
|
+ // 校验客户是否存在(租户内)
|
|
|
|
|
+ findCustomerById(customerId);
|
|
|
|
|
+
|
|
|
|
|
+ IPage<VppCustomerContact> page = new Page<>(pageNum, pageSize);
|
|
|
LambdaQueryWrapper<VppCustomerContact> wrapper = new LambdaQueryWrapper<VppCustomerContact>()
|
|
LambdaQueryWrapper<VppCustomerContact> wrapper = new LambdaQueryWrapper<VppCustomerContact>()
|
|
|
.eq(VppCustomerContact::getCustomerId, customerId)
|
|
.eq(VppCustomerContact::getCustomerId, customerId)
|
|
|
|
|
+ .eq(VppCustomerContact::getTenantId, SecurityUtils.getTenantId())
|
|
|
.eq(VppCustomerContact::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
.eq(VppCustomerContact::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
- .orderByDesc(VppCustomerContact::getIsPrimary);
|
|
|
|
|
- return toCommonPage(contactMapper.selectPage(page, wrapper));
|
|
|
|
|
|
|
+ .orderByDesc(VppCustomerContact::getIsPrimary)
|
|
|
|
|
+ .orderByAsc(VppCustomerContact::getId);
|
|
|
|
|
+
|
|
|
|
|
+ page = contactMapper.selectPage(page, wrapper);
|
|
|
|
|
+
|
|
|
|
|
+ return new CommonPage<>(page.getRecords(), page.getTotal(), pageSize, pageNum);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public VppCustomerContact addContact(Long customerId, CustomerContactRequest request) {
|
|
public VppCustomerContact addContact(Long customerId, CustomerContactRequest request) {
|
|
|
- getCustomer(customerId);
|
|
|
|
|
|
|
+ // 校验客户是否存在(租户内)
|
|
|
|
|
+ findCustomerById(customerId);
|
|
|
|
|
+
|
|
|
validateContactRequest(request);
|
|
validateContactRequest(request);
|
|
|
|
|
+
|
|
|
VppCustomerContact contact = new VppCustomerContact();
|
|
VppCustomerContact contact = new VppCustomerContact();
|
|
|
contact.setCustomerId(customerId);
|
|
contact.setCustomerId(customerId);
|
|
|
contact.setContactName(request.getContactName());
|
|
contact.setContactName(request.getContactName());
|
|
@@ -181,6 +908,7 @@ public class VppCustomerServiceImpl implements VppCustomerService {
|
|
|
public void updateContact(Long customerId, Long contactId, CustomerContactRequest request) {
|
|
public void updateContact(Long customerId, Long contactId, CustomerContactRequest request) {
|
|
|
VppCustomerContact contact = getContact(customerId, contactId);
|
|
VppCustomerContact contact = getContact(customerId, contactId);
|
|
|
validateContactRequest(request);
|
|
validateContactRequest(request);
|
|
|
|
|
+
|
|
|
contact.setContactName(request.getContactName());
|
|
contact.setContactName(request.getContactName());
|
|
|
contact.setContactPhone(request.getContactPhone());
|
|
contact.setContactPhone(request.getContactPhone());
|
|
|
contact.setContactEmail(request.getContactEmail());
|
|
contact.setContactEmail(request.getContactEmail());
|
|
@@ -188,6 +916,7 @@ public class VppCustomerServiceImpl implements VppCustomerService {
|
|
|
contact.setIsPrimary(request.getIsPrimary());
|
|
contact.setIsPrimary(request.getIsPrimary());
|
|
|
}
|
|
}
|
|
|
contact.setPosition(request.getPosition());
|
|
contact.setPosition(request.getPosition());
|
|
|
|
|
+
|
|
|
VppAuditHelper.fillUpdate(contact);
|
|
VppAuditHelper.fillUpdate(contact);
|
|
|
contactMapper.updateById(contact);
|
|
contactMapper.updateById(contact);
|
|
|
}
|
|
}
|
|
@@ -200,17 +929,7 @@ public class VppCustomerServiceImpl implements VppCustomerService {
|
|
|
contactMapper.updateById(contact);
|
|
contactMapper.updateById(contact);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private <T> CommonPage<T> toCommonPage(Page<T> page) {
|
|
|
|
|
- return new CommonPage<>(page.getRecords(), page.getTotal(), page.getCurrent(), page.getSize());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private VppCustomerContact getContact(Long customerId, Long contactId) {
|
|
|
|
|
- VppCustomerContact contact = contactMapper.selectById(contactId);
|
|
|
|
|
- if (contact == null || VppAuditHelper.isDeleted(contact.getDeleteFlag()) || !customerId.equals(contact.getCustomerId())) {
|
|
|
|
|
- throw new BusinessException("联系人不存在");
|
|
|
|
|
- }
|
|
|
|
|
- return contact;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // ==================== 内部辅助方法 ====================
|
|
|
|
|
|
|
|
private VppCustomer buildCustomerFromAccess(VppCustomerAccess access) {
|
|
private VppCustomer buildCustomerFromAccess(VppCustomerAccess access) {
|
|
|
VppCustomer customer = new VppCustomer();
|
|
VppCustomer customer = new VppCustomer();
|
|
@@ -223,33 +942,284 @@ public class VppCustomerServiceImpl implements VppCustomerService {
|
|
|
customer.setBusinessLicenseUrl(access.getBusinessLicenseUrl());
|
|
customer.setBusinessLicenseUrl(access.getBusinessLicenseUrl());
|
|
|
customer.setLifecycleStatus(LIFECYCLE_ADMITTED);
|
|
customer.setLifecycleStatus(LIFECYCLE_ADMITTED);
|
|
|
customer.setDrNotifyMinutes(30);
|
|
customer.setDrNotifyMinutes(30);
|
|
|
|
|
+ customer.setSigningDate(access.getSigningDate());
|
|
|
return customer;
|
|
return customer;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void validateAccessRequest(CustomerAccessRequest request) {
|
|
|
|
|
- if (request == null || !StringUtils.hasText(request.getAccountNo())) {
|
|
|
|
|
- throw new BusinessException("电力户号不能为空");
|
|
|
|
|
|
|
+ private CustomerResponseVO.ContractBriefVO toContractBrief(VppContract contract) {
|
|
|
|
|
+ CustomerResponseVO.ContractBriefVO brief = new CustomerResponseVO.ContractBriefVO();
|
|
|
|
|
+ brief.setId(contract.getId());
|
|
|
|
|
+ brief.setContractNo(contract.getContractNo());
|
|
|
|
|
+ brief.setContractName(contract.getContractName());
|
|
|
|
|
+ brief.setContractType(contract.getContractType());
|
|
|
|
|
+ brief.setContractStatus(contract.getContractStatus());
|
|
|
|
|
+ brief.setSignDate(contract.getSignDate());
|
|
|
|
|
+ brief.setEffectiveDate(contract.getEffectiveDate());
|
|
|
|
|
+ brief.setExpireDate(contract.getExpireDate());
|
|
|
|
|
+ return brief;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 租户内查询客户(含删除检查)
|
|
|
|
|
+ */
|
|
|
|
|
+ private VppCustomer findCustomerById(Long id) {
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomer> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.eq(VppCustomer::getId, id)
|
|
|
|
|
+ .eq(VppCustomer::getTenantId, SecurityUtils.getTenantId());
|
|
|
|
|
+ VppCustomer customer = customerMapper.selectOne(wrapper);
|
|
|
|
|
+ if (customer == null || VppAuditHelper.isDeleted(customer.getDeleteFlag())) {
|
|
|
|
|
+ throw new BusinessException("客户不存在或已被删除!");
|
|
|
|
|
+ }
|
|
|
|
|
+ return customer;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 租户内查询准入申请(含删除检查)
|
|
|
|
|
+ */
|
|
|
|
|
+ private VppCustomerAccess findAccessById(Long id) {
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerAccess> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.eq(VppCustomerAccess::getId, id)
|
|
|
|
|
+ .eq(VppCustomerAccess::getTenantId, SecurityUtils.getTenantId());
|
|
|
|
|
+ VppCustomerAccess access = accessMapper.selectOne(wrapper);
|
|
|
|
|
+ if (access == null || VppAuditHelper.isDeleted(access.getDeleteFlag())) {
|
|
|
|
|
+ throw new BusinessException("准入申请不存在或已被删除!");
|
|
|
|
|
+ }
|
|
|
|
|
+ return access;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private VppCustomerContact getContact(Long customerId, Long contactId) {
|
|
|
|
|
+ LambdaQueryWrapper<VppCustomerContact> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.eq(VppCustomerContact::getId, contactId)
|
|
|
|
|
+ .eq(VppCustomerContact::getCustomerId, customerId)
|
|
|
|
|
+ .eq(VppCustomerContact::getTenantId, SecurityUtils.getTenantId());
|
|
|
|
|
+ VppCustomerContact contact = contactMapper.selectOne(wrapper);
|
|
|
|
|
+ if (contact == null || VppAuditHelper.isDeleted(contact.getDeleteFlag())) {
|
|
|
|
|
+ throw new BusinessException("联系人不存在或已被删除!");
|
|
|
|
|
+ }
|
|
|
|
|
+ return contact;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void validateAccessRequest(CustomerAccessRequestVO request) {
|
|
|
|
|
+ if (request == null || StringUtils.isBlank(request.getAccountNo())) {
|
|
|
|
|
+ throw new BusinessException("电力户号不能为空!");
|
|
|
}
|
|
}
|
|
|
- if (!StringUtils.hasText(request.getCustomerName())) {
|
|
|
|
|
- throw new BusinessException("客户名称不能为空");
|
|
|
|
|
|
|
+ if (StringUtils.isBlank(request.getCustomerName())) {
|
|
|
|
|
+ throw new BusinessException("客户名称不能为空!");
|
|
|
}
|
|
}
|
|
|
if (request.getCustomerType() == null) {
|
|
if (request.getCustomerType() == null) {
|
|
|
- throw new BusinessException("客户类型不能为空");
|
|
|
|
|
|
|
+ throw new BusinessException("客户类型不能为空!");
|
|
|
}
|
|
}
|
|
|
- if (!StringUtils.hasText(request.getPowerCompany())) {
|
|
|
|
|
- throw new BusinessException("供电公司不能为空");
|
|
|
|
|
|
|
+ if (StringUtils.isBlank(request.getPowerCompany())) {
|
|
|
|
|
+ throw new BusinessException("供电公司不能为空!");
|
|
|
}
|
|
}
|
|
|
if (request.getContractCapacity() == null) {
|
|
if (request.getContractCapacity() == null) {
|
|
|
- throw new BusinessException("用电容量不能为空");
|
|
|
|
|
|
|
+ throw new BusinessException("用电容量不能为空!");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void validateContactRequest(CustomerContactRequest request) {
|
|
private void validateContactRequest(CustomerContactRequest request) {
|
|
|
- if (request == null || !StringUtils.hasText(request.getContactName())) {
|
|
|
|
|
- throw new BusinessException("联系人姓名不能为空");
|
|
|
|
|
|
|
+ if (request == null || StringUtils.isBlank(request.getContactName())) {
|
|
|
|
|
+ throw new BusinessException("联系人姓名不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(request.getContactPhone())) {
|
|
|
|
|
+ throw new BusinessException("联系电话不能为空!");
|
|
|
|
|
+ }
|
|
|
|
|
+ VppFieldValidator.validatePhone(request.getContactPhone());
|
|
|
|
|
+ if (StringUtils.isNotBlank(request.getContactEmail())) {
|
|
|
|
|
+ VppFieldValidator.validateEmail(request.getContactEmail());
|
|
|
}
|
|
}
|
|
|
- if (!StringUtils.hasText(request.getContactPhone())) {
|
|
|
|
|
- throw new BusinessException("联系电话不能为空");
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== Excel 导出辅助方法 ====================
|
|
|
|
|
+
|
|
|
|
|
+ private static void setCell(org.apache.poi.xssf.usermodel.XSSFRow row, int col, Object value) {
|
|
|
|
|
+ if (value != null) {
|
|
|
|
|
+ row.createCell(col).setCellValue(String.valueOf(value));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ row.createCell(col).setCellValue("");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 下载营业执照图片并嵌入到 Excel 单元格中
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param workbook 当前工作簿
|
|
|
|
|
+ * @param sheet 当前工作表
|
|
|
|
|
+ * @param row 当前行
|
|
|
|
|
+ * @param col 目标列索引
|
|
|
|
|
+ * @param imageUrl 营业执照图片 URL,为空或无效时写入"无"
|
|
|
|
|
+ */
|
|
|
|
|
+ private void insertLicenseImage(org.apache.poi.xssf.usermodel.XSSFWorkbook workbook,
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFSheet sheet,
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFRow row,
|
|
|
|
|
+ int col, String imageUrl) {
|
|
|
|
|
+ if (org.apache.commons.lang3.StringUtils.isBlank(imageUrl)) {
|
|
|
|
|
+ row.createCell(col).setCellValue("无");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("开始下载营业执照图片, URL: {}", imageUrl);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是相对路径(以 / 开头),尝试补全为本地文件路径或跳过
|
|
|
|
|
+ if (imageUrl.startsWith("/")) {
|
|
|
|
|
+ log.warn("营业执照URL是相对路径,无法通过HTTP下载: {}", imageUrl);
|
|
|
|
|
+ row.createCell(col).setCellValue("无");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 校验 URL 格式
|
|
|
|
|
+ if (!imageUrl.startsWith("http://") && !imageUrl.startsWith("https://")) {
|
|
|
|
|
+ log.warn("营业执照URL格式不正确: {}", imageUrl);
|
|
|
|
|
+ row.createCell(col).setCellValue("无");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ byte[] imageBytes = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ imageBytes = downloadImage(imageUrl);
|
|
|
|
|
+ log.info("图片下载成功, 大小: {} bytes, URL: {}", imageBytes.length, imageUrl);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("下载营业执照图片失败, URL: {}, 错误: {}", imageUrl, e.getMessage(), e);
|
|
|
|
|
+ row.createCell(col).setCellValue("加载失败");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (imageBytes == null || imageBytes.length == 0) {
|
|
|
|
|
+ row.createCell(col).setCellValue("无");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ int pictureType = detectImageType(imageBytes);
|
|
|
|
|
+ log.info("图片类型检测结果: {}, URL: {}", pictureType, imageUrl);
|
|
|
|
|
+ int pictureIdx = workbook.addPicture(imageBytes, pictureType);
|
|
|
|
|
+
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFDrawing drawing = sheet.createDrawingPatriarch();
|
|
|
|
|
+ int emuPerPixel = org.apache.poi.util.Units.EMU_PER_PIXEL;
|
|
|
|
|
+ // 全参构造:dx1, dy1, dx2, dy2, col1, row1, col2, row2
|
|
|
|
|
+ // dx/dy 设 2 像素微边距,图片紧贴单元格边界
|
|
|
|
|
+ org.apache.poi.xssf.usermodel.XSSFClientAnchor anchor =
|
|
|
|
|
+ new org.apache.poi.xssf.usermodel.XSSFClientAnchor(
|
|
|
|
|
+ 2 * emuPerPixel, 2 * emuPerPixel, // dx1, dy1 左/上边距
|
|
|
|
|
+ 2 * emuPerPixel, 2 * emuPerPixel, // dx2, dy2 右/下边距
|
|
|
|
|
+ col, row.getRowNum(), // col1, row1
|
|
|
|
|
+ col + 1, row.getRowNum() + 1 // col2, row2
|
|
|
|
|
+ );
|
|
|
|
|
+ anchor.setAnchorType(org.apache.poi.ss.usermodel.ClientAnchor.AnchorType.MOVE_AND_RESIZE);
|
|
|
|
|
+
|
|
|
|
|
+ drawing.createPicture(anchor, pictureIdx);
|
|
|
|
|
+
|
|
|
|
|
+ if (row.getHeight() < 2000) {
|
|
|
|
|
+ row.setHeight((short) 2000);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("营业执照图片插入Excel失败, URL: {}, 错误: {}", imageUrl, e.getMessage(), e);
|
|
|
|
|
+ row.createCell(col).setCellValue("图片插入失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 通过 HTTP 下载图片,返回字节数组
|
|
|
|
|
+ */
|
|
|
|
|
+ private static byte[] downloadImage(String imageUrl) throws IOException {
|
|
|
|
|
+ URL url = new URL(imageUrl);
|
|
|
|
|
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
|
|
|
+ conn.setConnectTimeout(10000);
|
|
|
|
|
+ conn.setReadTimeout(30000);
|
|
|
|
|
+ conn.setRequestMethod("GET");
|
|
|
|
|
+ conn.setDoInput(true);
|
|
|
|
|
+ conn.setInstanceFollowRedirects(true);
|
|
|
|
|
+ conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
|
|
|
|
+ conn.connect();
|
|
|
|
|
+
|
|
|
|
|
+ int responseCode = conn.getResponseCode();
|
|
|
|
|
+ if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
|
|
|
+ conn.disconnect();
|
|
|
|
|
+ throw new IOException("下载图片失败,HTTP " + responseCode + ", URL: " + imageUrl);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String contentType = conn.getContentType();
|
|
|
|
|
+ if (contentType != null && !contentType.startsWith("image/")) {
|
|
|
|
|
+ conn.disconnect();
|
|
|
|
|
+ throw new IOException("返回的不是图片类型: " + contentType + ", URL: " + imageUrl);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try (InputStream is = conn.getInputStream();
|
|
|
|
|
+ ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
|
|
|
|
+ byte[] buffer = new byte[8192];
|
|
|
|
|
+ int len;
|
|
|
|
|
+ while ((len = is.read(buffer)) != -1) {
|
|
|
|
|
+ bos.write(buffer, 0, len);
|
|
|
|
|
+ }
|
|
|
|
|
+ return bos.toByteArray();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ conn.disconnect();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据图片字节前几个魔术字节判断图片类型
|
|
|
|
|
+ */
|
|
|
|
|
+ private static int detectImageType(byte[] bytes) {
|
|
|
|
|
+ if (bytes == null || bytes.length < 3) {
|
|
|
|
|
+ return org.apache.poi.ss.usermodel.Workbook.PICTURE_TYPE_JPEG;
|
|
|
|
|
+ }
|
|
|
|
|
+ // PNG: 89 50 4E 47
|
|
|
|
|
+ if (bytes[0] == (byte) 0x89 && bytes[1] == (byte) 0x50
|
|
|
|
|
+ && bytes[2] == (byte) 0x4E && bytes[3] == (byte) 0x47) {
|
|
|
|
|
+ return org.apache.poi.ss.usermodel.Workbook.PICTURE_TYPE_PNG;
|
|
|
|
|
+ }
|
|
|
|
|
+ // JPEG: FF D8 FF
|
|
|
|
|
+ if (bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xD8 && bytes[2] == (byte) 0xFF) {
|
|
|
|
|
+ return org.apache.poi.ss.usermodel.Workbook.PICTURE_TYPE_JPEG;
|
|
|
|
|
+ }
|
|
|
|
|
+ // GIF: 47 49 46
|
|
|
|
|
+ if (bytes[0] == (byte) 0x47 && bytes[1] == (byte) 0x49 && bytes[2] == (byte) 0x46) {
|
|
|
|
|
+ return org.apache.poi.ss.usermodel.Workbook.PICTURE_TYPE_JPEG; // POI 4.x 用 JPEG 兜底
|
|
|
|
|
+ }
|
|
|
|
|
+ // BMP: 42 4D
|
|
|
|
|
+ if (bytes[0] == (byte) 0x42 && bytes[1] == (byte) 0x4D) {
|
|
|
|
|
+ return org.apache.poi.ss.usermodel.Workbook.PICTURE_TYPE_JPEG; // POI 4.x 用 JPEG 兜底
|
|
|
|
|
+ }
|
|
|
|
|
+ return org.apache.poi.ss.usermodel.Workbook.PICTURE_TYPE_JPEG;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String typeLabel(Integer code, String... labels) {
|
|
|
|
|
+ if (code == null || code < 1 || code > labels.length) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ return labels[code - 1];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String accessStatusLabel(Integer code) {
|
|
|
|
|
+ if (code == null || code < 0 || code > 2) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ switch (code) {
|
|
|
|
|
+ case 0:
|
|
|
|
|
+ return "待审核";
|
|
|
|
|
+ case 1:
|
|
|
|
|
+ return "已通过";
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ return "已驳回";
|
|
|
|
|
+ default:
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String numberStr(Object value) {
|
|
|
|
|
+ return value == null ? "" : value.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String timeStr(Object value) {
|
|
|
|
|
+ return value == null ? "" : value.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String dateStr(Object value) {
|
|
|
|
|
+ return value == null ? "" : value.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String dateTimeStr(Object value) {
|
|
|
|
|
+ return value == null ? "" : value.toString();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|