|
|
@@ -12,6 +12,7 @@ import com.yykj.sjnmtybt.dto.request.PersonnelQueryRequest;
|
|
|
import com.yykj.sjnmtybt.dto.request.PersonnelSaveRequest;
|
|
|
import com.yykj.sjnmtybt.dto.response.PersonnelVO;
|
|
|
import com.yykj.sjnmtybt.dto.response.RegionVO;
|
|
|
+import com.yykj.sjnmtybt.enums.InsuranceMatchResultEnum;
|
|
|
import com.yykj.sjnmtybt.enums.PersonnelStatusEnum;
|
|
|
import com.yykj.sjnmtybt.enums.RegionLevelEnum;
|
|
|
import org.apache.poi.ss.usermodel.Cell;
|
|
|
@@ -39,9 +40,16 @@ import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Comparator;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
+import java.util.concurrent.Executors;
|
|
|
+import java.util.concurrent.Future;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
|
/**
|
|
|
* 人员底数 Excel/CSV 模板、导入、导出。
|
|
|
@@ -288,6 +296,12 @@ public class PersonnelImportExportService {
|
|
|
List<Map<String, Object>> failRows = new ArrayList<Map<String, Object>>();
|
|
|
Map<String, String> townIdByName = loadTownIdByName();
|
|
|
Map<String, String> villageIdByKey = loadVillageIdByKey();
|
|
|
+ Map<String, BigDecimal> standardByTown = new HashMap<String, BigDecimal>();
|
|
|
+ int[] maxBiz = new int[]{100};
|
|
|
+ Map<String, Map<String, Object>> existingByIdNumber = loadExistingByIdNumber(maxBiz);
|
|
|
+ AtomicInteger nextBizSeq = new AtomicInteger(maxBiz[0]);
|
|
|
+ Set<String> seenInFile = new HashSet<String>();
|
|
|
+ List<ImportWork> works = new ArrayList<ImportWork>();
|
|
|
|
|
|
int rowNum = 0;
|
|
|
boolean headerSkipped = false;
|
|
|
@@ -312,9 +326,17 @@ public class PersonnelImportExportService {
|
|
|
|
|
|
try {
|
|
|
PersonnelSaveRequest req = toSaveRequest(cols, colMap, defaultTownId, defaultVillageId,
|
|
|
- townIdByName, villageIdByKey);
|
|
|
- flow.savePersonnel(req);
|
|
|
- success++;
|
|
|
+ townIdByName, villageIdByKey, existingByIdNumber, standardByTown);
|
|
|
+ String normId = req.getIdNumber().trim().toUpperCase();
|
|
|
+ if (!seenInFile.add(normId)) {
|
|
|
+ throw new IllegalArgumentException("本文件内身份证号重复");
|
|
|
+ }
|
|
|
+ ImportWork w = new ImportWork();
|
|
|
+ w.rowNum = rowNum;
|
|
|
+ w.cols = cols;
|
|
|
+ w.req = req;
|
|
|
+ w.existing = existingByIdNumber.get(normId);
|
|
|
+ works.add(w);
|
|
|
} catch (Exception ex) {
|
|
|
fail++;
|
|
|
String errMsg = ex.getMessage() == null ? "导入失败" : ex.getMessage();
|
|
|
@@ -323,6 +345,10 @@ public class PersonnelImportExportService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ int[] writeResult = persistImportWorks(works, nextBizSeq, errors, failRows);
|
|
|
+ success += writeResult[0];
|
|
|
+ fail += writeResult[1];
|
|
|
+
|
|
|
String fileName = file.getOriginalFilename();
|
|
|
String errorSummary = buildErrorSummary(errors);
|
|
|
recordImportHistory(fileName, defaultTownId, defaultVillageId, success, fail, errorSummary);
|
|
|
@@ -346,6 +372,171 @@ public class PersonnelImportExportService {
|
|
|
return importFile(file, townId, villageId);
|
|
|
}
|
|
|
|
|
|
+ private static final class ImportWork {
|
|
|
+ int rowNum;
|
|
|
+ List<String> cols;
|
|
|
+ PersonnelSaveRequest req;
|
|
|
+ Map<String, Object> existing;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 一次拉取现有人员,按身份证号索引,避免每行全表查询。 */
|
|
|
+ private Map<String, Map<String, Object>> loadExistingByIdNumber(int[] maxBizHolder) {
|
|
|
+ Map<String, Map<String, Object>> m = new HashMap<String, Map<String, Object>>();
|
|
|
+ if (!dms.isEnabled()) return m;
|
|
|
+ SjnmtybtProperties.Ref ref = props.getDms().getColumns().getPersonnel();
|
|
|
+ List<Map<String, Object>> rows = dms.selectContentListParallel(
|
|
|
+ ref.getColumnId(), ref.getModelId(), 100, 4);
|
|
|
+ if (rows == null) return m;
|
|
|
+ int max = maxBizHolder == null || maxBizHolder.length == 0 ? 100 : maxBizHolder[0];
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ if (row == null) continue;
|
|
|
+ String biz = DmsRowUtils.personnelBizId(row);
|
|
|
+ if (biz != null && biz.matches("P\\d+")) {
|
|
|
+ try {
|
|
|
+ max = Math.max(max, Integer.parseInt(biz.substring(1)));
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String idn = DmsRowUtils.str(row, "c_id_number");
|
|
|
+ if (!StringUtils.hasText(idn)) continue;
|
|
|
+ String key = idn.trim().toUpperCase();
|
|
|
+ if (!m.containsKey(key)) m.put(key, row);
|
|
|
+ }
|
|
|
+ if (maxBizHolder != null && maxBizHolder.length > 0) maxBizHolder[0] = max;
|
|
|
+ return m;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 并行写入 DMS,最后由调用方统一清缓存。返回 [success, fail] */
|
|
|
+ private int[] persistImportWorks(List<ImportWork> works, AtomicInteger nextBizSeq,
|
|
|
+ List<String> errors, List<Map<String, Object>> failRows) {
|
|
|
+ if (works == null || works.isEmpty()) return new int[]{0, 0};
|
|
|
+ if (!dms.isEnabled()) {
|
|
|
+ int ok = 0;
|
|
|
+ int fail = 0;
|
|
|
+ for (ImportWork w : works) {
|
|
|
+ try {
|
|
|
+ flow.savePersonnel(w.req);
|
|
|
+ ok++;
|
|
|
+ } catch (Exception ex) {
|
|
|
+ fail++;
|
|
|
+ String errMsg = ex.getMessage() == null ? "导入失败" : ex.getMessage();
|
|
|
+ errors.add("第" + w.rowNum + "行:" + errMsg);
|
|
|
+ failRows.add(buildFailRow(w.rowNum, errMsg, w.cols));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new int[]{ok, fail};
|
|
|
+ }
|
|
|
+ final SjnmtybtProperties.Ref ref = props.getDms().getColumns().getPersonnel();
|
|
|
+ int poolSize = Math.max(1, Math.min(8, works.size()));
|
|
|
+ ExecutorService pool = Executors.newFixedThreadPool(poolSize);
|
|
|
+ AtomicInteger ok = new AtomicInteger(0);
|
|
|
+ List<Future<?>> futures = new ArrayList<Future<?>>();
|
|
|
+ try {
|
|
|
+ for (final ImportWork w : works) {
|
|
|
+ futures.add(pool.submit(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ try {
|
|
|
+ persistOne(ref, w, nextBizSeq);
|
|
|
+ int n = ok.incrementAndGet();
|
|
|
+ if (n % 200 == 0 || n == works.size()) {
|
|
|
+ System.out.println("[personnel-import] persisted " + n + "/" + works.size());
|
|
|
+ }
|
|
|
+ } catch (Exception ex) {
|
|
|
+ String errMsg = ex.getMessage() == null ? "导入失败" : ex.getMessage();
|
|
|
+ synchronized (errors) {
|
|
|
+ errors.add("第" + w.rowNum + "行:" + errMsg);
|
|
|
+ failRows.add(buildFailRow(w.rowNum, errMsg, w.cols));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ for (Future<?> f : futures) {
|
|
|
+ try {
|
|
|
+ f.get(180, TimeUnit.SECONDS);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ // 单条失败已在任务内记录;超时也记一条
|
|
|
+ if (ex.getMessage() != null && ex.getMessage().contains("Timeout")) {
|
|
|
+ synchronized (errors) {
|
|
|
+ errors.add("写入超时:" + (ex.getMessage() == null ? "未知" : ex.getMessage()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ pool.shutdownNow();
|
|
|
+ }
|
|
|
+ return new int[]{ok.get(), works.size() - ok.get()};
|
|
|
+ }
|
|
|
+
|
|
|
+ private void persistOne(SjnmtybtProperties.Ref ref, ImportWork w, AtomicInteger nextBizSeq) {
|
|
|
+ PersonnelSaveRequest req = w.req;
|
|
|
+ Map<String, Object> existing = w.existing;
|
|
|
+ if (existing != null) {
|
|
|
+ String dmsId = DmsRowUtils.str(existing, "id");
|
|
|
+ String bizId = DmsRowUtils.personnelBizId(existing);
|
|
|
+ if (!StringUtils.hasText(bizId)) bizId = req.getId();
|
|
|
+ Map<String, Object> content = buildImportContent(req, bizId, existing);
|
|
|
+ dms.updateContent(ref.getColumnId(), ref.getModelId(), dmsId, content);
|
|
|
+ Map<String, Object> merged = new LinkedHashMap<String, Object>(existing);
|
|
|
+ merged.putAll(content);
|
|
|
+ data.upsertPersonnelCache(merged);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String bizId = "P" + String.format("%03d", Integer.valueOf(nextBizSeq.incrementAndGet()));
|
|
|
+ Map<String, Object> content = buildImportContent(req, bizId, null);
|
|
|
+ String dmsId = dms.addContent(ref.getColumnId(), ref.getModelId(), content);
|
|
|
+ if (StringUtils.hasText(dmsId)) {
|
|
|
+ content.put("id", dmsId);
|
|
|
+ }
|
|
|
+ data.upsertPersonnelCache(content);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildImportContent(PersonnelSaveRequest req, String bizId,
|
|
|
+ Map<String, Object> existing) {
|
|
|
+ Map<String, Object> content = new LinkedHashMap<String, Object>();
|
|
|
+ content.put("title", req.getName());
|
|
|
+ content.put("c_biz_id", bizId);
|
|
|
+ content.put("c_name", req.getName());
|
|
|
+ content.put("c_id_number", req.getIdNumber());
|
|
|
+ content.put("c_district_id", "D_SJ");
|
|
|
+ content.put("c_town_id", req.getTownId());
|
|
|
+ content.put("c_village_id", req.getVillageId());
|
|
|
+ content.put("c_input_village_id", req.getVillageId());
|
|
|
+ content.put("c_phone_number", req.getPhoneNumber());
|
|
|
+ content.put("c_social_insurance_code", req.getSocialInsuranceCode());
|
|
|
+ content.put("c_bank_card_number", req.getBankCardNumber());
|
|
|
+ content.put("c_bank_name", req.getBankName());
|
|
|
+ content.put("c_payee_name", req.getPayeeName() == null ? req.getName() : req.getPayeeName());
|
|
|
+ content.put("c_enjoy_start_month", req.getEnjoyStartMonth());
|
|
|
+ content.put("c_monthly_standard", req.getMonthlyStandard());
|
|
|
+ content.put("c_heir_name", req.getHeirName());
|
|
|
+ content.put("c_heir_id_number", req.getHeirIdNumber());
|
|
|
+ content.put("c_heir_relation", req.getHeirRelation());
|
|
|
+ content.put("c_heir_bank_card", req.getHeirBankCard());
|
|
|
+ content.put("c_heir_phone", req.getHeirPhone());
|
|
|
+ content.put("c_death_date", req.getDeathDate());
|
|
|
+ content.put("c_source", "Excel导入");
|
|
|
+ content.put("c_remarks", req.getRemarks() == null ? "" : req.getRemarks().trim());
|
|
|
+ content.put("content", "MOCK_SEED_260804");
|
|
|
+ if (existing == null) {
|
|
|
+ content.put("c_biz_status", PersonnelStatusEnum.DRAFT.name());
|
|
|
+ content.put("c_pay_this_month", "false");
|
|
|
+ content.put("c_insurance_match_result", InsuranceMatchResultEnum.PENDING.name());
|
|
|
+ } else {
|
|
|
+ String oldStatus = DmsRowUtils.str(existing, "c_biz_status");
|
|
|
+ content.put("c_biz_status", StringUtils.hasText(oldStatus)
|
|
|
+ ? oldStatus : PersonnelStatusEnum.DRAFT.name());
|
|
|
+ String oldPay = DmsRowUtils.str(existing, "c_pay_this_month");
|
|
|
+ content.put("c_pay_this_month", StringUtils.hasText(oldPay) ? oldPay : "false");
|
|
|
+ String oldMatch = DmsRowUtils.str(existing, "c_insurance_match_result");
|
|
|
+ content.put("c_insurance_match_result", StringUtils.hasText(oldMatch)
|
|
|
+ ? oldMatch : InsuranceMatchResultEnum.PENDING.name());
|
|
|
+ }
|
|
|
+ return content;
|
|
|
+ }
|
|
|
+
|
|
|
/** 将导入失败行导出为 xlsx(含错误原因列) */
|
|
|
@SuppressWarnings("unchecked")
|
|
|
public byte[] buildFailExportBytes(List<Map<String, Object>> failRows) {
|
|
|
@@ -571,7 +762,9 @@ public class PersonnelImportExportService {
|
|
|
private PersonnelSaveRequest toSaveRequest(List<String> cols, Map<String, Integer> colMap,
|
|
|
String defaultTown, String defaultVillage,
|
|
|
Map<String, String> townIdByName,
|
|
|
- Map<String, String> villageIdByKey) {
|
|
|
+ Map<String, String> villageIdByKey,
|
|
|
+ Map<String, Map<String, Object>> existingByIdNumber,
|
|
|
+ Map<String, BigDecimal> standardByTown) {
|
|
|
boolean named = colMap != null && !colMap.isEmpty();
|
|
|
// 兼容旧模板:姓名,身份证,街镇ID,村居ID,...(第3列不是手机号)
|
|
|
boolean legacy = !named && cols.size() <= 12 && !looksLikePhone(get(cols, 2))
|
|
|
@@ -691,21 +884,21 @@ public class PersonnelImportExportService {
|
|
|
|
|
|
String townId = resolveTownId(townRaw, defaultTown, townIdByName);
|
|
|
String villageId = resolveVillageId(villageRaw, townId, defaultVillage, villageIdByKey);
|
|
|
- BigDecimal standard = resolveMonthlyStandard(townId);
|
|
|
+ BigDecimal standard = standardByTown.get(townId);
|
|
|
+ if (!standardByTown.containsKey(townId)) {
|
|
|
+ standard = data.currentMonthlyStandard(townId);
|
|
|
+ standardByTown.put(townId, standard);
|
|
|
+ }
|
|
|
+ if (standard == null) {
|
|
|
+ throw new BusinessException(400, "未找到街镇当前有效月补标准,无法导入: " + townId);
|
|
|
+ }
|
|
|
|
|
|
String existId = null;
|
|
|
- PersonnelQueryRequest q = new PersonnelQueryRequest();
|
|
|
- q.setPageNum(1);
|
|
|
- q.setPageSize(5);
|
|
|
- q.setIdNumber(idNumber);
|
|
|
- PageResult<PersonnelVO> found = data.pagePersonnel(q);
|
|
|
- if (found.getList() != null) {
|
|
|
- for (PersonnelVO p : found.getList()) {
|
|
|
- if (idNumber.equals(p.getIdNumber())) {
|
|
|
- existId = p.getId();
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
+ Map<String, Object> existRow = existingByIdNumber == null
|
|
|
+ ? null : existingByIdNumber.get(idNumber.trim().toUpperCase());
|
|
|
+ if (existRow != null) {
|
|
|
+ existId = DmsRowUtils.personnelBizId(existRow);
|
|
|
+ if (!StringUtils.hasText(existId)) existId = DmsRowUtils.str(existRow, "id");
|
|
|
}
|
|
|
|
|
|
PersonnelSaveRequest req = new PersonnelSaveRequest();
|