|
@@ -4,6 +4,8 @@ import cn.hutool.core.collection.CollectionUtil;
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.hutool.extra.spring.SpringUtil;
|
|
import cn.hutool.extra.spring.SpringUtil;
|
|
|
|
|
+import cn.afterturn.easypoi.excel.ExcelImportUtil;
|
|
|
|
|
+import cn.afterturn.easypoi.excel.entity.ImportParams;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.TypeReference;
|
|
import com.alibaba.fastjson.TypeReference;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
@@ -31,12 +33,19 @@ import com.jeeplus.sys.service.dto.OfficeDTO;
|
|
|
import com.jeeplus.sys.service.dto.RoleDTO;
|
|
import com.jeeplus.sys.service.dto.RoleDTO;
|
|
|
import com.jeeplus.sys.service.dto.UserDTO;
|
|
import com.jeeplus.sys.service.dto.UserDTO;
|
|
|
import com.jeeplus.utils.StringUtils;
|
|
import com.jeeplus.utils.StringUtils;
|
|
|
|
|
+import com.jeeplus.sys.feign.IWorkAttachmentApi;
|
|
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
import com.baomidou.mybatisplus.*;
|
|
import com.baomidou.mybatisplus.*;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.FileInputStream;
|
|
|
|
|
+import java.io.FileOutputStream;
|
|
|
|
|
+import java.io.OutputStream;
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
@@ -1712,4 +1721,564 @@ public class ZsReimbursementInfoService {
|
|
|
String createUserId = zsReimbursementFileSupplementMapper.getReimbursementCreateUserIdByProcessInstanceId(id);
|
|
String createUserId = zsReimbursementFileSupplementMapper.getReimbursementCreateUserIdByProcessInstanceId(id);
|
|
|
return createUserId;
|
|
return createUserId;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 导入功能 ====================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询sort以'1'开头的末级报销类别
|
|
|
|
|
+ * @return 类别列表
|
|
|
|
|
+ */
|
|
|
|
|
+ public List<Map<String, Object>> findAllLeafCategories() {
|
|
|
|
|
+ return infoMapper.findAllLeafCategories();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 中审-报销批量导入
|
|
|
|
|
+ * @param file Excel文件
|
|
|
|
|
+ * @param currentUser 当前登录用户
|
|
|
|
|
+ * @return 导入结果
|
|
|
|
|
+ */
|
|
|
|
|
+ public ZsReimbursementImportResult importReimbursement(MultipartFile file, UserDTO currentUser) throws Exception {
|
|
|
|
|
+ ZsReimbursementImportResult result = new ZsReimbursementImportResult();
|
|
|
|
|
+ List<Map<String, Object>> errorMessages = new ArrayList<>();
|
|
|
|
|
+ Map<Integer, String> rowErrorMap = new LinkedHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ String currentUserId = currentUser.getId();
|
|
|
|
|
+ String currentUserName = currentUser.getName();
|
|
|
|
|
+ String companyId = currentUser.getCompanyDTO().getId();
|
|
|
|
|
+ String currentToken = TokenProvider.getCurrentToken();
|
|
|
|
|
+
|
|
|
|
|
+ // 当前用户部门
|
|
|
|
|
+ String currentUserDeptId = "";
|
|
|
|
|
+ String currentUserDeptName = "";
|
|
|
|
|
+ if (currentUser.getOfficeDTO() != null && StringUtils.isNotBlank(currentUser.getOfficeDTO().getId())) {
|
|
|
|
|
+ currentUserDeptId = currentUser.getOfficeDTO().getId();
|
|
|
|
|
+ currentUserDeptName = currentUser.getOfficeDTO().getName();
|
|
|
|
|
+ if (StringUtils.isBlank(currentUserDeptName)) {
|
|
|
|
|
+ OfficeDTO officeDTO = SpringUtil.getBean(IOfficeApi.class).getOfficeById(currentUserDeptId);
|
|
|
|
|
+ if (officeDTO != null) {
|
|
|
|
|
+ currentUserDeptName = officeDTO.getName();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果仍然没有部门,从数据库查询
|
|
|
|
|
+ if (StringUtils.isBlank(currentUserDeptName)) {
|
|
|
|
|
+ List<Map<String, Object>> users = infoMapper.findUserByName(currentUserName);
|
|
|
|
|
+ if (users != null && !users.isEmpty()) {
|
|
|
|
|
+ Map<String, Object> u = users.get(0);
|
|
|
|
|
+ currentUserDeptId = u.get("office_id") != null ? u.get("office_id").toString() : "";
|
|
|
|
|
+ currentUserDeptName = u.get("office_name") != null ? u.get("office_name").toString() : "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 解析Excel
|
|
|
|
|
+ ImportParams params = new ImportParams();
|
|
|
|
|
+ params.setTitleRows(0);
|
|
|
|
|
+ params.setHeadRows(1);
|
|
|
|
|
+ List<ZsReimbursementImportDto> rows = ExcelImportUtil.importExcel(file.getInputStream(), ZsReimbursementImportDto.class, params);
|
|
|
|
|
+
|
|
|
|
|
+ if (rows == null || rows.isEmpty()) {
|
|
|
|
|
+ throw new RuntimeException("Excel文件为空或格式不正确");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 分组
|
|
|
|
|
+ List<List<Map<String, Object>>> groups = new ArrayList<>();
|
|
|
|
|
+ for (int i = 0; i < rows.size(); i++) {
|
|
|
|
|
+ ZsReimbursementImportDto dto = rows.get(i);
|
|
|
|
|
+ String aloneFlag = safeStr(dto.getAloneFlag());
|
|
|
|
|
+ String sourceTypeName = safeStr(dto.getSourceTypeName());
|
|
|
|
|
+ String userName = safeStr(dto.getUserName());
|
|
|
|
|
+ String typeName = safeStr(dto.getTypeName());
|
|
|
|
|
+
|
|
|
|
|
+ // 跳过模板描述行
|
|
|
|
|
+ if (aloneFlag.contains("请删除") || aloneFlag.contains("填写提示")) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 跳过模板示例行
|
|
|
|
|
+ if (aloneFlag.contains("[示例]")) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 跳过全空行
|
|
|
|
|
+ if (StringUtils.isBlank(aloneFlag) && StringUtils.isBlank(sourceTypeName)
|
|
|
|
|
+ && StringUtils.isBlank(userName) && StringUtils.isBlank(typeName)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 只处理合法数据行
|
|
|
|
|
+ if (!"是".equals(aloneFlag) && !"同上".equals(aloneFlag)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> row = new HashMap<>();
|
|
|
|
|
+ row.put("originalIndex", i);
|
|
|
|
|
+ row.put("excelRowNum", i + 2);
|
|
|
|
|
+ row.put("dto", dto);
|
|
|
|
|
+ row.put("sourceTypeName", sourceTypeName);
|
|
|
|
|
+ row.put("userName", userName);
|
|
|
|
|
+ row.put("typeName", typeName);
|
|
|
|
|
+
|
|
|
|
|
+ if ("是".equals(aloneFlag) || groups.isEmpty()) {
|
|
|
|
|
+ List<Map<String, Object>> group = new ArrayList<>();
|
|
|
|
|
+ group.add(row);
|
|
|
|
|
+ groups.add(group);
|
|
|
|
|
+ } else if ("同上".equals(aloneFlag)) {
|
|
|
|
|
+ groups.get(groups.size() - 1).add(row);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (groups.isEmpty()) {
|
|
|
|
|
+ throw new RuntimeException("未找到有效的数据行,请确认已删除模板示例行并填入实际数据");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 逐组校验
|
|
|
|
|
+ int successCount = 0, failCount = 0, successDetailCount = 0, failDetailCount = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (List<Map<String, Object>> group : groups) {
|
|
|
|
|
+ List<String> groupErrors = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 3.1 组内一致性校验
|
|
|
|
|
+ Map<String, Object> firstRow = group.get(0);
|
|
|
|
|
+ String firstSourceTypeName = (String) firstRow.get("sourceTypeName");
|
|
|
|
|
+
|
|
|
|
|
+ for (int r = 1; r < group.size(); r++) {
|
|
|
|
|
+ Map<String, Object> row = group.get(r);
|
|
|
|
|
+ int rowNum = (int) row.get("excelRowNum");
|
|
|
|
|
+
|
|
|
|
|
+ if (!firstSourceTypeName.equals(row.get("sourceTypeName"))) {
|
|
|
|
|
+ String err = "第" + rowNum + "行:同组内报销类型与第一行不一致";
|
|
|
|
|
+ groupErrors.add(err);
|
|
|
|
|
+ rowErrorMap.put((int) row.get("originalIndex"), err);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3.2 逐行数据匹配校验
|
|
|
|
|
+ for (Map<String, Object> row : group) {
|
|
|
|
|
+ ZsReimbursementImportDto dto = (ZsReimbursementImportDto) row.get("dto");
|
|
|
|
|
+ int rowNum = (int) row.get("excelRowNum");
|
|
|
|
|
+ int originalIndex = (int) row.get("originalIndex");
|
|
|
|
|
+ String stn = (String) row.get("sourceTypeName");
|
|
|
|
|
+ List<String> rowErrors = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 报销类型校验
|
|
|
|
|
+ if (!"兴光项目报销".equals(stn) && !"兴光报告报销".equals(stn)
|
|
|
|
|
+ && !"其他报销".equals(stn) && !"中审项目报销".equals(stn)) {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:报销类型必须为兴光项目报销、兴光报告报销、其他报销或中审项目报销");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 报销类别匹配
|
|
|
|
|
+ Map<String, Object> category = null;
|
|
|
|
|
+ if (StringUtils.isNotBlank((String) row.get("typeName"))) {
|
|
|
|
|
+ category = infoMapper.findCategoryByName((String) row.get("typeName"));
|
|
|
|
|
+ if (category == null) {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:报销类别「" + row.get("typeName") + "」不存在或不是末级节点");
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:报销类别不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 报销人匹配
|
|
|
|
|
+ String userId = currentUserId;
|
|
|
|
|
+ String userNameVal = currentUserName;
|
|
|
|
|
+ String deptId = currentUserDeptId;
|
|
|
|
|
+ String deptName = currentUserDeptName;
|
|
|
|
|
+ if (StringUtils.isNotBlank((String) row.get("userName"))) {
|
|
|
|
|
+ List<Map<String, Object>> users = infoMapper.findUserByName((String) row.get("userName"));
|
|
|
|
|
+ if (users == null || users.isEmpty()) {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:报销人「" + row.get("userName") + "」不存在");
|
|
|
|
|
+ } else if (users.size() > 1) {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:报销人「" + row.get("userName") + "」存在重名,请手动录入后调整");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Map<String, Object> u = users.get(0);
|
|
|
|
|
+ userId = u.get("id").toString();
|
|
|
|
|
+ userNameVal = u.get("name").toString();
|
|
|
|
|
+ deptId = u.get("office_id") != null ? u.get("office_id").toString() : "";
|
|
|
|
|
+ deptName = u.get("office_name") != null ? u.get("office_name").toString() : "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ row.put("matchedUserId", userId);
|
|
|
|
|
+ row.put("matchedUserName", userNameVal);
|
|
|
|
|
+ row.put("matchedDeptId", deptId);
|
|
|
|
|
+ row.put("matchedDeptName", deptName);
|
|
|
|
|
+
|
|
|
|
|
+ // 报销详情必填
|
|
|
|
|
+ if (StringUtils.isBlank(safeStr(dto.getProjectName()))) {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:报销详情不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 费用处理:汇总 = 非数电发票 + 其他,可手动填写也可自动计算
|
|
|
|
|
+ String ordinaryNumber = safeStr(dto.getOrdinaryNumber());
|
|
|
|
|
+ String subsidy = safeStr(dto.getSubsidy());
|
|
|
|
|
+ String numberCount = safeStr(dto.getNumberCount());
|
|
|
|
|
+ if (StringUtils.isBlank(numberCount)) {
|
|
|
|
|
+ numberCount = calcSum(ordinaryNumber, subsidy);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 至少有一项费用
|
|
|
|
|
+ if ("0".equals(numberCount) && StringUtils.isBlank(ordinaryNumber) && StringUtils.isBlank(subsidy)) {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:费用不能全部为空或为0");
|
|
|
|
|
+ }
|
|
|
|
|
+ row.put("ordinaryNumber", ordinaryNumber);
|
|
|
|
|
+ row.put("subsidy", subsidy);
|
|
|
|
|
+ row.put("numberCount", numberCount);
|
|
|
|
|
+
|
|
|
|
|
+ // 张数处理
|
|
|
|
|
+ int ordinaryReceiptNumber = dto.getOrdinaryReceiptNumber() != null ? dto.getOrdinaryReceiptNumber() : 0;
|
|
|
|
|
+ row.put("ordinaryReceiptNumber", ordinaryReceiptNumber);
|
|
|
|
|
+ row.put("receiptNumberCount", ordinaryReceiptNumber);
|
|
|
|
|
+
|
|
|
|
|
+ // type=8 特殊校验:发票号和立项号至少填写一项
|
|
|
|
|
+ if ("中审项目报销".equals(stn)) {
|
|
|
|
|
+ String invoiceNumber = safeStr(dto.getInvoiceNumber());
|
|
|
|
|
+ String projectApprovalNumber = safeStr(dto.getProjectApprovalNumber());
|
|
|
|
|
+ if (StringUtils.isBlank(invoiceNumber) && StringUtils.isBlank(projectApprovalNumber)) {
|
|
|
|
|
+ rowErrors.add("第" + rowNum + "行:中审项目报销的发票号和立项号至少填写一项");
|
|
|
|
|
+ }
|
|
|
|
|
+ row.put("invoiceNumber", invoiceNumber);
|
|
|
|
|
+ row.put("projectApprovalNumber", projectApprovalNumber);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 存储匹配结果
|
|
|
|
|
+ row.put("category", category);
|
|
|
|
|
+
|
|
|
|
|
+ if (!rowErrors.isEmpty()) {
|
|
|
|
|
+ for (String err : rowErrors) {
|
|
|
|
|
+ groupErrors.add(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!rowErrorMap.containsKey(originalIndex)) {
|
|
|
|
|
+ rowErrorMap.put(originalIndex, rowErrors.get(0));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 根据校验结果写入数据库或标记失败
|
|
|
|
|
+ if (groupErrors.isEmpty()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ writeGroupToDb(group, companyId, currentToken, currentUserId, currentUserName, currentUserDeptId, currentUserDeptName);
|
|
|
|
|
+ successCount++;
|
|
|
|
|
+ successDetailCount += group.size();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failDetailCount += group.size();
|
|
|
|
|
+ for (Map<String, Object> r : group) {
|
|
|
|
|
+ int oi = (int) r.get("originalIndex");
|
|
|
|
|
+ if (!rowErrorMap.containsKey(oi)) {
|
|
|
|
|
+ rowErrorMap.put(oi, "第" + r.get("excelRowNum") + "行:写入失败 - " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ errorMessages.add(createErrorEntry(
|
|
|
|
|
+ (int) group.get(0).get("excelRowNum"),
|
|
|
|
|
+ "写入失败: " + e.getMessage()));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failDetailCount += group.size();
|
|
|
|
|
+ for (String err : groupErrors) {
|
|
|
|
|
+ errorMessages.add(createErrorEntry(0, err));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 如果有失败,生成错误Excel
|
|
|
|
|
+ if (!rowErrorMap.isEmpty()) {
|
|
|
|
|
+ String errorPath = generateErrorExcel(file, rowErrorMap);
|
|
|
|
|
+ result.setErrorFilePath(errorPath);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result.setSuccessCount(successCount);
|
|
|
|
|
+ result.setFailCount(failCount);
|
|
|
|
|
+ result.setSuccessDetailCount(successDetailCount);
|
|
|
|
|
+ result.setFailDetailCount(failDetailCount);
|
|
|
|
|
+ result.setErrorMessages(errorMessages);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将校验通过的一组数据写入数据库
|
|
|
|
|
+ */
|
|
|
|
|
+ private void writeGroupToDb(List<Map<String, Object>> group, String companyId, String currentToken,
|
|
|
|
|
+ String currentUserId, String currentUserName, String currentDeptId, String currentDeptName) throws Exception {
|
|
|
|
|
+ String infoId = UUID.randomUUID().toString().replace("-", "");
|
|
|
|
|
+ // 生成报销编号(与单条新增使用相同的bizCode)
|
|
|
|
|
+ Date targetDate = DateUtil.parseDate("2026-01-01");
|
|
|
|
|
+ boolean isAfterTargetDate = DateUtil.date().after(targetDate);
|
|
|
|
|
+ String bizCode = isAfterTargetDate ? ZsSaveInfoDto.NEW_BIZ_CODE : ZsSaveInfoDto.BIZ_CODE;
|
|
|
|
|
+ String no = SpringUtil.getBean(IWorkAttachmentApi.class)
|
|
|
|
|
+ .genSerialNum(companyId, bizCode, currentToken);
|
|
|
|
|
+
|
|
|
|
|
+ // 取第一行确定sourceType
|
|
|
|
|
+ ZsReimbursementImportDto firstDto = (ZsReimbursementImportDto) group.get(0).get("dto");
|
|
|
|
|
+ String sourceTypeName = (String) group.get(0).get("sourceTypeName");
|
|
|
|
|
+ String sourceType;
|
|
|
|
|
+ switch (sourceTypeName) {
|
|
|
|
|
+ case "兴光项目报销": sourceType = "1"; break;
|
|
|
|
|
+ case "兴光报告报销": sourceType = "3"; break;
|
|
|
|
|
+ case "中审项目报销": sourceType = "8"; break;
|
|
|
|
|
+ default: sourceType = "4"; break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 写入主表 zs_reimbursement_info(经办人 = 当前登录人)
|
|
|
|
|
+ ZsReimbursementInfo info = new ZsReimbursementInfo();
|
|
|
|
|
+ info.setId(infoId);
|
|
|
|
|
+ info.setNo(no);
|
|
|
|
|
+ info.setUserId(currentUserId);
|
|
|
|
|
+ info.setUserName(currentUserName);
|
|
|
|
|
+ info.setDepartment(currentDeptId); // 存储部门ID,前端会根据ID查询部门名称
|
|
|
|
|
+ info.setReimDate(new Date());
|
|
|
|
|
+ info.setSourceType(sourceType);
|
|
|
|
|
+ info.setType("1"); // 暂存
|
|
|
|
|
+ info.setReimbursementType("0"); // 普通报销
|
|
|
|
|
+ info.setCreateById(currentUserId);
|
|
|
|
|
+ info.setCreateTime(new Date());
|
|
|
|
|
+ info.setUpdateById(currentUserId);
|
|
|
|
|
+ info.setUpdateTime(new Date());
|
|
|
|
|
+ info.setDelFlag(0);
|
|
|
|
|
+ infoMapper.insert(info);
|
|
|
|
|
+
|
|
|
|
|
+ // 写入明细记录
|
|
|
|
|
+ for (Map<String, Object> row : group) {
|
|
|
|
|
+ ZsReimbursementImportDto dto = (ZsReimbursementImportDto) row.get("dto");
|
|
|
|
|
+ String userId = (String) row.get("matchedUserId");
|
|
|
|
|
+ String userName = (String) row.get("matchedUserName");
|
|
|
|
|
+ String deptId = (String) row.get("matchedDeptId");
|
|
|
|
|
+ String deptName = (String) row.get("matchedDeptName");
|
|
|
|
|
+ String ordinaryNumber = (String) row.get("ordinaryNumber");
|
|
|
|
|
+ String subsidy = (String) row.get("subsidy");
|
|
|
|
|
+ String numberCount = (String) row.get("numberCount");
|
|
|
|
|
+ int ordinaryReceiptNumber = (int) row.get("ordinaryReceiptNumber");
|
|
|
|
|
+ int receiptNumberCount = (int) row.get("receiptNumberCount");
|
|
|
|
|
+ Map<String, Object> category = (Map<String, Object>) row.get("category");
|
|
|
|
|
+
|
|
|
|
|
+ String detailId = UUID.randomUUID().toString().replace("-", "");
|
|
|
|
|
+ String typeId = category != null ? category.get("id").toString() : "";
|
|
|
|
|
+ String typeName = category != null ? category.get("name").toString() : "";
|
|
|
|
|
+
|
|
|
|
|
+ switch (sourceType) {
|
|
|
|
|
+ case "1": {
|
|
|
|
|
+ // 兴光项目报销 → zs_reimbursement_detail_info
|
|
|
|
|
+ ZsReimbursementDetailInfo detail = new ZsReimbursementDetailInfo();
|
|
|
|
|
+ detail.setId(detailId);
|
|
|
|
|
+ detail.setInfoId(infoId);
|
|
|
|
|
+ detail.setUserId(userId);
|
|
|
|
|
+ detail.setUserName(userName);
|
|
|
|
|
+ detail.setDeptId(deptId);
|
|
|
|
|
+ detail.setDeptName(deptName);
|
|
|
|
|
+ detail.setTypeId(typeId);
|
|
|
|
|
+ detail.setTypeName(typeName);
|
|
|
|
|
+ detail.setProjectName(safeStr(dto.getProjectName()));
|
|
|
|
|
+ detail.setNumber("0");
|
|
|
|
|
+ detail.setOrdinaryNumber(ordinaryNumber);
|
|
|
|
|
+ detail.setSubsidy(subsidy);
|
|
|
|
|
+ detail.setNumberCount(numberCount);
|
|
|
|
|
+ detail.setReceiptNumber(0);
|
|
|
|
|
+ detail.setOrdinaryReceiptNumber(ordinaryReceiptNumber);
|
|
|
|
|
+ detail.setReceiptNumberCount(receiptNumberCount);
|
|
|
|
|
+ detail.setContent(safeStr(dto.getContent()));
|
|
|
|
|
+ detail.setDays(dto.getDays());
|
|
|
|
|
+ detail.setUniqueId(UUID.randomUUID().toString().replace("-", ""));
|
|
|
|
|
+ detail.setReimbursementType("0");
|
|
|
|
|
+ detail.setCreateById(currentUserId);
|
|
|
|
|
+ detail.setCreateTime(new Date());
|
|
|
|
|
+ detail.setUpdateById(currentUserId);
|
|
|
|
|
+ detail.setUpdateTime(new Date());
|
|
|
|
|
+ detail.setDelFlag(0);
|
|
|
|
|
+ detailInfoMapper.insert(detail);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ case "3": {
|
|
|
|
|
+ // 兴光报告报销 → zs_reimbursement_detail_info_report
|
|
|
|
|
+ ZsReimbursementDetailInfoReport detail = new ZsReimbursementDetailInfoReport();
|
|
|
|
|
+ detail.setId(detailId);
|
|
|
|
|
+ detail.setInfoId(infoId);
|
|
|
|
|
+ detail.setUserId(userId);
|
|
|
|
|
+ detail.setUserName(userName);
|
|
|
|
|
+ detail.setDeptId(deptId);
|
|
|
|
|
+ detail.setDeptName(deptName);
|
|
|
|
|
+ detail.setTypeId(typeId);
|
|
|
|
|
+ detail.setTypeName(typeName);
|
|
|
|
|
+ detail.setProjectName(safeStr(dto.getProjectName()));
|
|
|
|
|
+ detail.setNumber("0");
|
|
|
|
|
+ detail.setOrdinaryNumber(ordinaryNumber);
|
|
|
|
|
+ detail.setSubsidy(subsidy);
|
|
|
|
|
+ detail.setNumberCount(numberCount);
|
|
|
|
|
+ detail.setReceiptNumber(0);
|
|
|
|
|
+ detail.setOrdinaryReceiptNumber(ordinaryReceiptNumber);
|
|
|
|
|
+ detail.setReceiptNumberCount(receiptNumberCount);
|
|
|
|
|
+ detail.setContent(safeStr(dto.getContent()));
|
|
|
|
|
+ detail.setDays(dto.getDays());
|
|
|
|
|
+ detail.setUniqueId(UUID.randomUUID().toString().replace("-", ""));
|
|
|
|
|
+ detail.setReimbursementType("0");
|
|
|
|
|
+ detail.setCreateById(currentUserId);
|
|
|
|
|
+ detail.setCreateTime(new Date());
|
|
|
|
|
+ detail.setUpdateById(currentUserId);
|
|
|
|
|
+ detail.setUpdateTime(new Date());
|
|
|
|
|
+ detail.setDelFlag(0);
|
|
|
|
|
+ zsReimbursementDetailInfoReportMapper.insert(detail);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ case "8": {
|
|
|
|
|
+ // 中审项目报销 → zs_reimbursement_detail_info_zs_project
|
|
|
|
|
+ ZsReimbursementDetailInfoProject detail = new ZsReimbursementDetailInfoProject();
|
|
|
|
|
+ detail.setId(detailId);
|
|
|
|
|
+ detail.setInfoId(infoId);
|
|
|
|
|
+ detail.setUserId(userId);
|
|
|
|
|
+ detail.setUserName(userName);
|
|
|
|
|
+ detail.setDeptId(deptId);
|
|
|
|
|
+ detail.setDeptName(deptName);
|
|
|
|
|
+ detail.setTypeId(typeId);
|
|
|
|
|
+ detail.setTypeName(typeName);
|
|
|
|
|
+ detail.setProjectName(safeStr(dto.getProjectName()));
|
|
|
|
|
+ detail.setInvoiceNumber((String) row.get("invoiceNumber"));
|
|
|
|
|
+ detail.setProjectApprovalNumber((String) row.get("projectApprovalNumber"));
|
|
|
|
|
+ detail.setNumber("0");
|
|
|
|
|
+ detail.setOrdinaryNumber(ordinaryNumber);
|
|
|
|
|
+ detail.setSubsidy(subsidy);
|
|
|
|
|
+ detail.setNumberCount(numberCount);
|
|
|
|
|
+ detail.setReceiptNumber(0);
|
|
|
|
|
+ detail.setOrdinaryReceiptNumber(ordinaryReceiptNumber);
|
|
|
|
|
+ detail.setReceiptNumberCount(receiptNumberCount);
|
|
|
|
|
+ detail.setContent(safeStr(dto.getContent()));
|
|
|
|
|
+ detail.setDays(dto.getDays());
|
|
|
|
|
+ detail.setUniqueId(UUID.randomUUID().toString().replace("-", ""));
|
|
|
|
|
+ detail.setReimbursementType("0");
|
|
|
|
|
+ detail.setCreateById(currentUserId);
|
|
|
|
|
+ detail.setCreateTime(new Date());
|
|
|
|
|
+ detail.setUpdateById(currentUserId);
|
|
|
|
|
+ detail.setUpdateTime(new Date());
|
|
|
|
|
+ detail.setDelFlag(0);
|
|
|
|
|
+ zsReimbursementDetailInfoProjectMapper.insert(detail);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ default: {
|
|
|
|
|
+ // 其他报销 → zs_reimbursement_detail_info_other
|
|
|
|
|
+ ZsReimbursementDetailInfoOther detail = new ZsReimbursementDetailInfoOther();
|
|
|
|
|
+ detail.setId(detailId);
|
|
|
|
|
+ detail.setInfoId(infoId);
|
|
|
|
|
+ detail.setUserId(userId);
|
|
|
|
|
+ detail.setUserName(userName);
|
|
|
|
|
+ detail.setDeptId(deptId);
|
|
|
|
|
+ detail.setDeptName(deptName);
|
|
|
|
|
+ detail.setTypeId(typeId);
|
|
|
|
|
+ detail.setTypeName(typeName);
|
|
|
|
|
+ detail.setProjectName(safeStr(dto.getProjectName()));
|
|
|
|
|
+ detail.setNumber("0");
|
|
|
|
|
+ detail.setOrdinaryNumber(ordinaryNumber);
|
|
|
|
|
+ detail.setSubsidy(subsidy);
|
|
|
|
|
+ detail.setNumberCount(numberCount);
|
|
|
|
|
+ detail.setReceiptNumber(0);
|
|
|
|
|
+ detail.setOrdinaryReceiptNumber(ordinaryReceiptNumber);
|
|
|
|
|
+ detail.setReceiptNumberCount(receiptNumberCount);
|
|
|
|
|
+ detail.setContent(safeStr(dto.getContent()));
|
|
|
|
|
+ detail.setDays(dto.getDays());
|
|
|
|
|
+ detail.setUniqueId(UUID.randomUUID().toString().replace("-", ""));
|
|
|
|
|
+ detail.setReimbursementType("0");
|
|
|
|
|
+ detail.setCreateById(currentUserId);
|
|
|
|
|
+ detail.setCreateTime(new Date());
|
|
|
|
|
+ detail.setUpdateById(currentUserId);
|
|
|
|
|
+ detail.setUpdateTime(new Date());
|
|
|
|
|
+ detail.setDelFlag(0);
|
|
|
|
|
+ zsReimbursementDetailInfoOtherMapper.insert(detail);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成错误Excel文件
|
|
|
|
|
+ */
|
|
|
|
|
+ private String generateErrorExcel(MultipartFile originalFile, Map<Integer, String> rowErrorMap) throws Exception {
|
|
|
|
|
+ Workbook workbook = WorkbookFactory.create(originalFile.getInputStream());
|
|
|
|
|
+ Sheet sheet = workbook.getSheetAt(0);
|
|
|
|
|
+
|
|
|
|
|
+ int errorColIndex = 13; // 13列数据之后 = 第14列(0-based)
|
|
|
|
|
+
|
|
|
|
|
+ CellStyle errorStyle = workbook.createCellStyle();
|
|
|
|
|
+ Font errorFont = workbook.createFont();
|
|
|
|
|
+ errorFont.setColor(IndexedColors.RED.getIndex());
|
|
|
|
|
+ errorStyle.setFont(errorFont);
|
|
|
|
|
+ errorStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
|
|
|
|
|
+ errorStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
+
|
|
|
|
|
+ Row headerRow = sheet.getRow(0);
|
|
|
|
|
+ if (headerRow != null) {
|
|
|
|
|
+ Cell errorHeaderCell = headerRow.createCell(errorColIndex);
|
|
|
|
|
+ errorHeaderCell.setCellValue("错误原因");
|
|
|
|
|
+ errorHeaderCell.setCellStyle(errorStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<Integer, String> entry : rowErrorMap.entrySet()) {
|
|
|
|
|
+ int dataRowIndex = entry.getKey();
|
|
|
|
|
+ int excelRowNum = dataRowIndex + 1;
|
|
|
|
|
+ Row dataRow = sheet.getRow(excelRowNum);
|
|
|
|
|
+ if (dataRow == null) {
|
|
|
|
|
+ dataRow = sheet.createRow(excelRowNum);
|
|
|
|
|
+ }
|
|
|
|
|
+ for (int c = 0; c <= errorColIndex; c++) {
|
|
|
|
|
+ Cell cell = dataRow.getCell(c);
|
|
|
|
|
+ if (cell == null) {
|
|
|
|
|
+ cell = dataRow.createCell(c);
|
|
|
|
|
+ }
|
|
|
|
|
+ cell.setCellStyle(errorStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+ Cell errorCell = dataRow.getCell(errorColIndex);
|
|
|
|
|
+ if (errorCell == null) {
|
|
|
|
|
+ errorCell = dataRow.createCell(errorColIndex);
|
|
|
|
|
+ }
|
|
|
|
|
+ errorCell.setCellValue(entry.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String tempPath = System.getProperty("java.io.tmpdir") + File.separator
|
|
|
|
|
+ + "zs_import_error_" + System.currentTimeMillis() + ".xlsx";
|
|
|
|
|
+ try (FileOutputStream fos = new FileOutputStream(tempPath)) {
|
|
|
|
|
+ workbook.write(fos);
|
|
|
|
|
+ }
|
|
|
|
|
+ workbook.close();
|
|
|
|
|
+ return tempPath;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 安全转字符串
|
|
|
|
|
+ */
|
|
|
|
|
+ private String safeStr(Object obj) {
|
|
|
|
|
+ if (obj == null) return "";
|
|
|
|
|
+ String s = obj.toString().trim();
|
|
|
|
|
+ if (s.endsWith(".0")) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Double.parseDouble(s);
|
|
|
|
|
+ s = s.substring(0, s.length() - 2);
|
|
|
|
|
+ } catch (NumberFormatException ignored) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ return s;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建错误条目
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, Object> createErrorEntry(int row, String message) {
|
|
|
|
|
+ Map<String, Object> entry = new HashMap<>();
|
|
|
|
|
+ entry.put("row", row);
|
|
|
|
|
+ entry.put("message", message);
|
|
|
|
|
+ return entry;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算汇总 = 非数电发票(费用) + 其他(费用)
|
|
|
|
|
+ */
|
|
|
|
|
+ private String calcSum(String ordinaryNumber, String subsidy) {
|
|
|
|
|
+ double ordinary = parseDouble(ordinaryNumber);
|
|
|
|
|
+ double subsidyVal = parseDouble(subsidy);
|
|
|
|
|
+ double sum = ordinary + subsidyVal;
|
|
|
|
|
+ if (sum == 0) return "0";
|
|
|
|
|
+ return String.valueOf(sum);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 安全解析double
|
|
|
|
|
+ */
|
|
|
|
|
+ private double parseDouble(String str) {
|
|
|
|
|
+ if (StringUtils.isBlank(str)) return 0;
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Double.parseDouble(str);
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|