Commit 5986f1a8 by jianan

保费到期提醒19-新单跟进导出

parent 9c0b5ddd
...@@ -26,6 +26,7 @@ import com.yd.csf.api.listener.PolicyDataListener; ...@@ -26,6 +26,7 @@ import com.yd.csf.api.listener.PolicyDataListener;
import com.yd.csf.api.service.ApiCommissionConditionService; import com.yd.csf.api.service.ApiCommissionConditionService;
import com.yd.csf.api.service.ApiExpectedFortuneService; import com.yd.csf.api.service.ApiExpectedFortuneService;
import com.yd.csf.api.service.ApiPolicyFollowService; import com.yd.csf.api.service.ApiPolicyFollowService;
import com.yd.csf.api.service.ApiPolicyFollowExportService;
import com.yd.csf.feign.request.expectedfortune.ApiGenerateExpectedFortuneRequest; import com.yd.csf.feign.request.expectedfortune.ApiGenerateExpectedFortuneRequest;
import com.yd.csf.feign.response.appointment.ApiAppointmentDetailResponse; import com.yd.csf.feign.response.appointment.ApiAppointmentDetailResponse;
import com.yd.csf.feign.response.expectedfortune.ApiGenerateExpectedFortuneResponse; import com.yd.csf.feign.response.expectedfortune.ApiGenerateExpectedFortuneResponse;
...@@ -67,6 +68,7 @@ import javax.servlet.ServletOutputStream; ...@@ -67,6 +68,7 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.SQLIntegrityConstraintViolationException; import java.sql.SQLIntegrityConstraintViolationException;
import java.text.ParseException; import java.text.ParseException;
...@@ -112,6 +114,8 @@ public class ApiPolicyFollowController { ...@@ -112,6 +114,8 @@ public class ApiPolicyFollowController {
private FeignResultHelper feignResultHelper; private FeignResultHelper feignResultHelper;
@Resource @Resource
private ApiExpectedFortuneAsyncService apiExpectedFortuneAsyncService; private ApiExpectedFortuneAsyncService apiExpectedFortuneAsyncService;
@Resource
private ApiPolicyFollowExportService apiPolicyFollowExportService;
@Autowired @Autowired
private ApiCommissionConditionService apiCommissionConditionService; private ApiCommissionConditionService apiCommissionConditionService;
...@@ -510,6 +514,20 @@ public class ApiPolicyFollowController { ...@@ -510,6 +514,20 @@ public class ApiPolicyFollowController {
} }
/** /**
* 按页面查询条件导出新单跟进清单(与列表查询同一筛选口径,30 列模板,R 列为实时计算的保费到期日)
*/
@PostMapping("/export")
@Operation(summary = "导出新单跟进列表")
public void exportPolicyFollow(@RequestBody PolicyFollowQueryRequest policyFollowQueryRequest,
HttpServletResponse response) {
try {
apiPolicyFollowExportService.export(policyFollowQueryRequest, response);
} catch (Exception e) {
throw new RuntimeException("导出新单跟进清单失败", e);
}
}
/**
* 修改跟进状态 * 修改跟进状态
* *
* @param changePolicyFollowStatusRequest * @param changePolicyFollowStatusRequest
......
package com.yd.csf.api.service;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yd.common.exception.BusinessException;
import com.yd.csf.service.component.TemplateStyleStrategy;
import com.yd.csf.service.dto.PolicyFollowQueryRequest;
import com.yd.csf.service.model.PolicyFollow;
import com.yd.csf.service.service.PolicyFollowService;
import com.yd.csf.service.utils.RenewalDueDateCalculator;
import com.yd.csf.service.utils.SimpleDateUtils;
import com.yd.csf.service.vo.RenewalPolicyExportDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 新单跟进列表导出。
*
* <p>行集与分页页面查询完全一致(复用 {@link PolicyFollowService#getQueryWrapper},含其固定的 id 倒序、
* 不过滤 is_deleted 口径);列映射沿用保单清单 30 列模板,R 列「保费到期日」按当天逐行实时计算。</p>
*
* <p>健壮性:先 COUNT 行数闸门(空结果/超上限在提交下载响应头之前报错,可正常返回 JSON),
* 再分页拉取、分批写入,避免全量进内存;纯只读,不加事务。</p>
*/
@Service
@Slf4j
public class ApiPolicyFollowExportService {
/**
* 单次导出行数硬上限,超过需缩小筛选条件
*/
private static final long MAX_EXPORT_ROWS = 50_000L;
/**
* 分页拉取/分批写入大小,堆内只保留当前批
*/
private static final long PAGE_SIZE = 2_000L;
private static final String XLSX_CONTENT_TYPE =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");
@Resource
private PolicyFollowService policyFollowService;
public void export(PolicyFollowQueryRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
QueryWrapper<PolicyFollow> wrapper = policyFollowService.getQueryWrapper(request);
long total = policyFollowService.count(wrapper);
if (total == 0) {
throw new BusinessException("无导出数据");
}
if (total > MAX_EXPORT_ROWS) {
throw new BusinessException(String.format(
"导出数据量 %d 条超过上限 %d 条,请缩小筛选条件后再导出", total, MAX_EXPORT_ROWS));
}
long pages = (total + PAGE_SIZE - 1) / PAGE_SIZE;
LocalDate today = LocalDate.now();
// 先取第一页,让主查询在提交下载响应头之前执行:
// 一旦查询阶段抛错,响应尚未 committed,全局异常处理器仍可返回 JSON 提示。
Page<PolicyFollow> firstPage = policyFollowService.page(new Page<>(1, PAGE_SIZE, false), wrapper);
setDownloadHeaders(response, buildFileName(today));
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream(), RenewalPolicyExportDTO.class)
.useDefaultStyle(false)
.registerWriteHandler(new TemplateStyleStrategy())
.build()) {
WriteSheet sheet = EasyExcel.writerSheet("新单跟进").build();
writer.write(toRows(firstPage.getRecords(), today), sheet);
for (long pageNo = 2; pageNo <= pages; pageNo++) {
Page<PolicyFollow> pageData = policyFollowService.page(
new Page<>(pageNo, PAGE_SIZE, false), wrapper);
if (CollectionUtils.isEmpty(pageData.getRecords())) {
break;
}
writer.write(toRows(pageData.getRecords(), today), sheet);
}
} catch (IOException e) {
// 此时响应头已提交、无法再回 JSON,仅记录日志供排查
log.error("新单跟进导出写入失败,total={}", total, e);
throw new BusinessException("导出新单跟进清单失败");
}
}
private List<RenewalPolicyExportDTO> toRows(List<PolicyFollow> policies, LocalDate today) {
List<RenewalPolicyExportDTO> rows = new ArrayList<>(policies.size());
for (PolicyFollow p : policies) {
RenewalDueDateCalculator.Result result = RenewalDueDateCalculator.calculate(
SimpleDateUtils.toLocalDate(p.getEffectiveDate()),
p.getPaymentFrequency(),
p.getIssueNumber(),
today);
Date dueDate = result.isIncluded()
? Date.from(result.getDueDate().atStartOfDay(java.time.ZoneId.systemDefault()).toInstant())
: null;
rows.add(RenewalPolicyExportDTO.toRow(p, dueDate));
}
return rows;
}
private String buildFileName(LocalDate today) throws UnsupportedEncodingException {
String raw = "新单跟进清单_" + today.format(DAY_FMT) + ".xlsx";
return URLEncoder.encode(raw, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
}
private void setDownloadHeaders(HttpServletResponse response, String encodedFileName) {
response.setContentType(XLSX_CONTENT_TYPE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName);
}
}
...@@ -3,7 +3,6 @@ package com.yd.csf.service.component; ...@@ -3,7 +3,6 @@ package com.yd.csf.service.component;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.yd.csf.service.dto.RenewalDuePolicy; import com.yd.csf.service.dto.RenewalDuePolicy;
import com.yd.csf.service.dto.RenewalReminderData; import com.yd.csf.service.dto.RenewalReminderData;
import com.yd.csf.service.model.PolicyFollow;
import com.yd.csf.service.vo.RenewalPolicyExportDTO; import com.yd.csf.service.vo.RenewalPolicyExportDTO;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -14,7 +13,6 @@ import java.time.LocalDate; ...@@ -14,7 +13,6 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* 续期保单清单 Excel 生成(30 列,对齐 policy报表模版.xlsx) * 续期保单清单 Excel 生成(30 列,对齐 policy报表模版.xlsx)
...@@ -59,37 +57,9 @@ public class RenewalPolicyReportService { ...@@ -59,37 +57,9 @@ public class RenewalPolicyReportService {
return rows; return rows;
} }
for (RenewalDuePolicy item : data.getDuePolicies()) { for (RenewalDuePolicy item : data.getDuePolicies()) {
PolicyFollow p = item.getPolicyFollow(); java.util.Date dueDate = java.util.Date.from(item.getDueDate()
RenewalPolicyExportDTO row = new RenewalPolicyExportDTO(); .atStartOfDay(java.time.ZoneId.systemDefault()).toInstant());
row.setId(p.getId()); rows.add(RenewalPolicyExportDTO.toRow(item.getPolicyFollow(), dueDate));
row.setPolicyNo(p.getPolicyNo());
row.setProductName(p.getProductName());
row.setPolicyHolder(p.getPolicyHolder());
row.setPolicyHolderEn(p.getPolicyHolderEn());
row.setInsured(p.getInsured());
row.setStatus(p.getStatus());
row.setEachIssuePremium(p.getEachIssuePremium());
row.setPolicyCurrency(p.getPolicyCurrency());
row.setPolicyLevy(p.getPolicyLevy());
row.setEffectiveDate(p.getEffectiveDate());
row.setSignDate(p.getSignDate());
row.setIssueDate(p.getIssueDate());
row.setCoolingOffEndDate(p.getCoolingOffEndDate());
row.setDueDate(java.util.Date.from(item.getDueDate()
.atStartOfDay(java.time.ZoneId.systemDefault()).toInstant()));
row.setIssueNumber(Objects.toString(p.getIssueNumber(), null));
row.setPaymentFrequency(p.getPaymentFrequency());
row.setIsPrepay(p.getIsPrepay());
row.setPrepaidTerm(p.getPrepaidTerm());
row.setProductCate(p.getProductCate());
row.setInsuranceCompany(p.getInsuranceCompany());
row.setReconciliationCompany(p.getReconciliationCompany());
row.setFirstBroker(p.getFirstBroker());
row.setSigner(p.getSigner());
row.setPracticeCode(p.getPracticeCode());
row.setAppointmentNo(p.getAppointmentNo());
row.setSignLocation(p.getSignLocation());
rows.add(row);
} }
return rows; return rows;
} }
......
...@@ -2,13 +2,15 @@ package com.yd.csf.service.vo; ...@@ -2,13 +2,15 @@ package com.yd.csf.service.vo;
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat; import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.yd.csf.service.model.PolicyFollow;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.Objects;
/** /**
* 续期保单清单导出 DTO,列顺序与表头严格对齐 policy报表模版.xlsx(30 列)。 * 保单清单导出 DTO(续期跑批与新单跟进导出共用),列顺序与表头严格对齐 policy报表模版.xlsx(30 列)。
* 字体/边框/对齐/列宽/冻结统一由 {@link com.yd.csf.service.component.TemplateStyleStrategy} 复刻模板。 * 字体/边框/对齐/列宽/冻结统一由 {@link com.yd.csf.service.component.TemplateStyleStrategy} 复刻模板。
*/ */
@Data @Data
...@@ -108,4 +110,42 @@ public class RenewalPolicyExportDTO { ...@@ -108,4 +110,42 @@ public class RenewalPolicyExportDTO {
@ExcelProperty("签单地点") @ExcelProperty("签单地点")
private String signLocation; private String signLocation;
/**
* 按 PolicyFollow 字段搬运生成一行;PI/受保人英文/总保费及征费无数据源留空,状态保留字典码。
*
* @param p 保单记录
* @param dueDate 保费到期日(R 列),无下一期缴费日时传 null
*/
public static RenewalPolicyExportDTO toRow(PolicyFollow p, Date dueDate) {
RenewalPolicyExportDTO row = new RenewalPolicyExportDTO();
row.setId(p.getId());
row.setPolicyNo(p.getPolicyNo());
row.setProductName(p.getProductName());
row.setPolicyHolder(p.getPolicyHolder());
row.setPolicyHolderEn(p.getPolicyHolderEn());
row.setInsured(p.getInsured());
row.setStatus(p.getStatus());
row.setEachIssuePremium(p.getEachIssuePremium());
row.setPolicyCurrency(p.getPolicyCurrency());
row.setPolicyLevy(p.getPolicyLevy());
row.setEffectiveDate(p.getEffectiveDate());
row.setSignDate(p.getSignDate());
row.setIssueDate(p.getIssueDate());
row.setCoolingOffEndDate(p.getCoolingOffEndDate());
row.setDueDate(dueDate);
row.setIssueNumber(Objects.toString(p.getIssueNumber(), null));
row.setPaymentFrequency(p.getPaymentFrequency());
row.setIsPrepay(p.getIsPrepay());
row.setPrepaidTerm(p.getPrepaidTerm());
row.setProductCate(p.getProductCate());
row.setInsuranceCompany(p.getInsuranceCompany());
row.setReconciliationCompany(p.getReconciliationCompany());
row.setFirstBroker(p.getFirstBroker());
row.setSigner(p.getSigner());
row.setPracticeCode(p.getPracticeCode());
row.setAppointmentNo(p.getAppointmentNo());
row.setSignLocation(p.getSignLocation());
return row;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment