diff --git a/src/main/java/com/bonus/boot/manager/ca/im/controller/PayableController.java b/src/main/java/com/bonus/boot/manager/ca/im/controller/PayableController.java index 87ba394..62672bd 100644 --- a/src/main/java/com/bonus/boot/manager/ca/im/controller/PayableController.java +++ b/src/main/java/com/bonus/boot/manager/ca/im/controller/PayableController.java @@ -14,12 +14,18 @@ import com.bonus.boot.manager.manager.table.PageTableRequest; import com.bonus.boot.manager.manager.table.PageTableResponse; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -172,4 +178,287 @@ public class PayableController { } + @PostMapping("/importExcel") + @ApiOperation(value = "导入Excel数据") + public R importExcel(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return R.fail("请选择要导入的Excel文件"); + } + + try (InputStream is = file.getInputStream()) { + Workbook workbook = new XSSFWorkbook(is); + Sheet sheet = workbook.getSheetAt(0); + + // 验证Excel模板是否正确 + String validateResult = validateExcelTemplate(sheet); + if (validateResult != null) { + return R.fail(validateResult); + } + + List payableList = new ArrayList<>(); + List errorMsgs = new ArrayList<>(); + + // 从第二行开始读取数据(跳过表头) + for (int i = 2; i <= sheet.getLastRowNum(); i++) { + Row row = sheet.getRow(i); + if (row == null) { + continue; + } + + PayableBean payable = new PayableBean(); + boolean hasError = false; + + // 校验并设置日期 + String rq = getCellValueAsString(row.getCell(0)); + if (rq == null || rq.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:日期不能为空"); + hasError = true; + } else if (!rq.matches("^\\d{4}-\\d{2}-\\d{2}$")) { + errorMsgs.add("第" + (i + 1) + "行:日期格式不正确,应为yyyy-MM-dd格式"); + hasError = true; + } else { + try { + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); + sdf.setLenient(false); + sdf.parse(rq); + payable.setRq(rq); + } catch (Exception e) { + errorMsgs.add("第" + (i + 1) + "行:无效的日期"); + hasError = true; + } + } + + // 校验并设置单号 + String pzh = getCellValueAsString(row.getCell(1)); + if (pzh == null || pzh.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:单号不能为空"); + hasError = true; + } else { + payable.setPzh(pzh); + } + + // 校验并设置合同名称 + String htmc = getCellValueAsString(row.getCell(2)); + if (htmc == null || htmc.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:合同名称不能为空"); + hasError = true; + } else if (!dao.checkHtmcExists(htmc)) { + errorMsgs.add("第" + (i + 1) + "行:合同名称不存在"); + hasError = true; + } else { + payable.setHtmc(htmc); + } + + // 校验并设置供应商名称 + String gysmc = getCellValueAsString(row.getCell(3)); + if (gysmc == null || gysmc.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:供应商名称不能为空"); + hasError = true; + } else if (!dao.checkGysmcExists(gysmc, htmc)) { + errorMsgs.add("第" + (i + 1) + "行:供应商名称不存在或与合同不匹配"); + hasError = true; + } else { + payable.setGysmc(gysmc); + } + + // 校验并设置商品名称 + String spmc = getCellValueAsString(row.getCell(4)); + if (spmc == null || spmc.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:商品名称不能为空"); + hasError = true; + } else { + String spid = dao.getSpidByName(spmc); + if (spid == null) { + errorMsgs.add("第" + (i + 1) + "行:商品名称不存在"); + hasError = true; + } else { + payable.setSpbh(spid); + } + } + + // 校验并设置付款账号 + String accountName = getCellValueAsString(row.getCell(5)); + if (accountName == null || accountName.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:付款账号不能为空"); + hasError = true; + } else { + Integer accountId = dao.getAccountIdByName(accountName); + if (accountId == null) { + errorMsgs.add("第" + (i + 1) + "行:付款账号不存在"); + hasError = true; + } else { + payable.setAccountId(accountId); + } + } + + // 校验并设置金额 + String je = getCellValueAsString(row.getCell(6)); + if (je == null || je.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:金额不能为空"); + hasError = true; + } else if (!je.matches("^[0-9]+(\\.[0-9]{1,2})?$")) { + errorMsgs.add("第" + (i + 1) + "行:金额格式不正确"); + hasError = true; + } else { + payable.setJe(new BigDecimal(je)); + } + + // 校验并设置人次 + String personNum = getCellValueAsString(row.getCell(7)); + if (personNum == null || personNum.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:人次不能为空"); + hasError = true; + } else { + try { + // 处理可能带有小数点的数字 + double num = Double.parseDouble(personNum); + int intValue = (int) num; + if (intValue != num || intValue < 1 || intValue > 999999) { + errorMsgs.add("第" + (i + 1) + "行:人次必须为1-999999之间的正整数"); + hasError = true; + } else { + payable.setPersonNum(intValue); + } + } catch (NumberFormatException e) { + errorMsgs.add("第" + (i + 1) + "行:人次必须为1-999999之间的正整数"); + hasError = true; + } + } + + // 校验并设置发票号码 + String fphm = getCellValueAsString(row.getCell(8)); + if (fphm == null || fphm.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:发票号码不能为空"); + hasError = true; + } else { + payable.setFphm(fphm); + } + + // 校验并设置开票金额 + String kpje = getCellValueAsString(row.getCell(9)); + if (kpje == null || kpje.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:开票金额不能为空"); + hasError = true; + } else if (!kpje.matches("^[0-9]+(\\.[0-9]{1,2})?$")) { + errorMsgs.add("第" + (i + 1) + "行:开票金额格式不正确"); + hasError = true; + } else { + payable.setKpje(new BigDecimal(kpje)); + } + + // 校验并设置收票摘要 + String spzy = getCellValueAsString(row.getCell(10)); + if (spzy == null || spzy.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:收票摘要不能为空"); + hasError = true; + } else { + payable.setSpzy(spzy); + } + + // 校验并设置制单人 + String zdr = getCellValueAsString(row.getCell(11)); + if (zdr == null || zdr.trim().isEmpty()) { + errorMsgs.add("第" + (i + 1) + "行:制单人不能为空"); + hasError = true; + } else { + String zdrid = dao.getZdridByName(zdr); + if (zdrid == null) { + errorMsgs.add("第" + (i + 1) + "行:制单人不存在"); + hasError = true; + } else { + payable.setZdr(zdrid); + } + } + + // 只有当该行没有错误时才添加到列表 + if (!hasError) { + payableList.add(payable); + } + } + + // 如果有错误信息,返回所有错误信息 + if (!errorMsgs.isEmpty()) { + return R.fail(String.join("\n", errorMsgs)); + } + + // 没有错误时才进行保存操作 +// service.batchSave(payableList); + for (PayableBean payable : payableList) { + service.save(payable); + } + return R.ok("导入成功"); + + } catch (IOException e) { + return R.fail("导入失败:" + e.getMessage()); + } + } + + /** + * 验证Excel模板是否正确 + * @param sheet Excel工作表 + * @return 如果模板正确返回null,否则返回错误信息 + */ + private String validateExcelTemplate(Sheet sheet) { + // 预期的表头 + String[] expectedHeaders = { + "日期", "单号(凭证号)", "合同名称(项目名称)", "供应商名称", + "商品名称", "付款账号", "金额", "人次", + "发票号码", "开票金额", "收票摘要", "制单人" + }; + + // 获取第一行(表头) + Row headerRow = sheet.getRow(1); + if (headerRow == null) { + return "Excel文件格式错误:未找到表头行"; + } + + // 检查列数是否匹配 + if (headerRow.getPhysicalNumberOfCells() != expectedHeaders.length) { + return "Excel文件格式错误:列数不匹配,请使用正确的导入模板"; + } + + // 验证每一列的表头 + for (int i = 0; i < expectedHeaders.length; i++) { + Cell cell = headerRow.getCell(i); + String headerValue = getCellValueAsString(cell); + + if (headerValue == null || !headerValue.equals(expectedHeaders[i])) { + return String.format("Excel文件格式错误:第%d列应为'%s',实际为'%s',请使用正确的导入模板", + (i + 1), expectedHeaders[i], headerValue == null ? "空" : headerValue); + } + } + + // 检查是否有数据行 + if (sheet.getLastRowNum() < 1) { + return "Excel文件中没有数据"; + } + return null; + } + + private String getCellValueAsString(Cell cell) { + if (cell == null) { + return null; + } + + switch (cell.getCellType()) { + case STRING: + return cell.getStringCellValue(); + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + return new java.text.SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue()); + } + // 处理数值类型,去掉可能存在的.0后缀 + double numericValue = cell.getNumericCellValue(); + if (numericValue == (long) numericValue) { + return String.valueOf((long) numericValue); + } + return String.valueOf(numericValue); + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + case FORMULA: + return cell.getCellFormula(); + default: + return ""; + } + } } diff --git a/src/main/java/com/bonus/boot/manager/ca/im/dao/PayableDao.java b/src/main/java/com/bonus/boot/manager/ca/im/dao/PayableDao.java index c7af1e4..7777cb6 100644 --- a/src/main/java/com/bonus/boot/manager/ca/im/dao/PayableDao.java +++ b/src/main/java/com/bonus/boot/manager/ca/im/dao/PayableDao.java @@ -85,4 +85,14 @@ public interface PayableDao { List getHTIDByMC(String htmc); List getBankAccount(); + + boolean checkHtmcExists(String htmc); + + boolean checkGysmcExists(String gysmc, String htmc); + + String getSpidByName(String spmc); + + Integer getAccountIdByName(String accountName); + + String getZdridByName(String zdr); } diff --git a/src/main/java/com/bonus/boot/manager/ca/im/service/impl/PayableServiceImpl.java b/src/main/java/com/bonus/boot/manager/ca/im/service/impl/PayableServiceImpl.java index c8c148b..619c057 100644 --- a/src/main/java/com/bonus/boot/manager/ca/im/service/impl/PayableServiceImpl.java +++ b/src/main/java/com/bonus/boot/manager/ca/im/service/impl/PayableServiceImpl.java @@ -41,6 +41,7 @@ public class PayableServiceImpl implements PayableService { @Override public void save(PayableBean payableBean) { + System.err.println(payableBean); LoginUser loginUser = UserUtil.getLoginUser(); String username = loginUser.getUsername(); String userId = dao.getIdByName(username); diff --git a/src/main/resources/mappers/ca/PayableMapper.xml b/src/main/resources/mappers/ca/PayableMapper.xml index 9017a40..faebc7d 100644 --- a/src/main/resources/mappers/ca/PayableMapper.xml +++ b/src/main/resources/mappers/ca/PayableMapper.xml @@ -134,6 +134,9 @@ and a.zdr like concat ('%',#{params.zdr},'%') + + and a.je like concat ('%',#{params.je},'%') + and a.rq between #{params.startTime} and #{params.endTime} @@ -331,6 +334,11 @@ and a.zdr like concat ('%',#{params.zdr},'%') + + + and a.je like concat ('%',#{params.je},'%') + + and a.rq between #{params.startTime} and #{params.endTime} @@ -759,6 +767,39 @@ select id,name from ca_bm_pay_account_info where IS_ACTIVE = '1' + + + + + + + \ No newline at end of file diff --git a/src/main/resources/static/pages/finance/addPayable.html b/src/main/resources/static/pages/finance/addPayable.html index c67dcfe..0c8216b 100644 --- a/src/main/resources/static/pages/finance/addPayable.html +++ b/src/main/resources/static/pages/finance/addPayable.html @@ -272,10 +272,11 @@ }) } - layui.use(['layer', 'form'], function(){ + layui.use(['layer', 'form', 'upload'], function(){ layer = layui.layer; form = layui.form; var laydate = layui.laydate; + var upload = layui.upload; // 渲染 laydate.render({ elem: '#rq' diff --git a/src/main/resources/static/pages/finance/payable.html b/src/main/resources/static/pages/finance/payable.html index d3fa1bf..109e31d 100644 --- a/src/main/resources/static/pages/finance/payable.html +++ b/src/main/resources/static/pages/finance/payable.html @@ -59,11 +59,17 @@ -
+ +
+
+ +
+
+ + + + + +
@@ -164,7 +183,8 @@ gysmc :$('#gysmc').val(), spbh :$('#spbh').val(), pzh :$('#pzh').val(), - zdr :$('#zdr').val(), + // zdr :$('#zdr').val(), + je :$('#je').val(), startTime :$('#startTime').val(), endTime :$('#endTime').val() } //post请求必须加where ,post请求需要的参数 @@ -212,6 +232,11 @@ }, toolbar: "#toolbar" }); + + + $("#uploadExcel").click(function () { + importExcel(); + }); }); function buttonquerygysmc(htmcid) { @@ -344,7 +369,8 @@ gysmc :$('#gysmc').val(), spbh :$('#spbh').val(), pzh :$('#pzh').val(), - zdr :$('#zdr').val(), + // zdr :$('#zdr').val(), + je :$('#je').val(), accountId :$('#accountId').val(), startTime :$('#startTime').val(), endTime :$('#endTime').val() @@ -356,7 +382,7 @@ $("#exportBt").click(function () { var token = localStorage.getItem("token"); var loadingMsg = layer.msg('下载中,请稍候...', {icon: 16, scrollbar: false, time: 0}); - var url = ctxPath + "/payable/exp?htmc=" + $("#htmc").val().trim()+"&gysmc=" + $("#gysmc").val().trim()+ "&spbh=" + $("#spbh").val().trim()+"&pzh=" + $("#pzh").val().trim()+"&zdr=" + $("#zdr").val().trim()+"&accountId=" + $("#accountId").val().trim()+"&startTime=" + $("#startTime").val().trim()+"&endTime=" + $("#endTime").val().trim()+"&token=" + token; + var url = ctxPath + "/payable/exp?htmc=" + $("#htmc").val().trim()+"&gysmc=" + $("#gysmc").val().trim()+ "&spbh=" + $("#spbh").val().trim()+"&pzh=" + $("#pzh").val().trim()+"&je=" + $("#je").val().trim()+"&accountId=" + $("#accountId").val().trim()+"&startTime=" + $("#startTime").val().trim()+"&endTime=" + $("#endTime").val().trim()+"&token=" + token; var xhr = new XMLHttpRequest(); xhr.open("get", url, true); xhr.responseType = "blob"; // 转换流 @@ -376,4 +402,61 @@ }; xhr.send(); }); + + function downloadPayable() { + window.open(ctxPath + "/download/download?filename=应付录入导入模版.xlsx") + } + + function importExcel() { + var formData = new FormData($('form')[0]); + var name = $("#payableList").val(); + if (name == null || name == "") { + layer.msg("请上传正确的Excel表格!"); + return; + } + if (!(name.endsWith(".xls") || name.endsWith(".xlsx") || name.endsWith(".xlsm"))) { + layer.msg("请上传正确的Excel表格!"); + $("#payableList").val(""); + return; + } + formData.append("file", $("#payableList")[0].files[0]); + var idx = layer.msg('正在提交数据,请稍等...', { + icon: 16 + , shade: 0.01 + , time: '-1' + }); + setTimeout(function () { + $.ajax({ + url: ctxPath + "/payable/importExcel", + type: 'POST', + async: false, + data: formData, + timeout: 20000, + // 告诉jQuery不要去处理发送的数据 + processData: false, + // 告诉jQuery不要去设置Content-Type请求头 + contentType: false, + success: function (data) { + layer.close(idx); + if (data.code == 200) { + layer.alert("导入成功", {icon: 1}); + table.reload('menuTable');// 刷新页面 + } else if(data.code == 400){ + layer.alert("导入失败,请检查模版", {icon: 2}); + }else if(data.code == 500){ + layer.alert(data.msg, {icon: 2}); + }else { + layer.alert("导入失败,请检查模版", {icon: 2}); + } + + }, + error: function (XMLHttpRequest, textStatus, errorThrown) { + console.log(JSON.stringify(errorThrown)); + layer.close(idx); + } + }); + $("#payableList").val(""); + }, 1000); + + } \ No newline at end of file