增强客户编码管理功能,新增客户编码唯一性校验,确保同租户内客户编码不重复。更新相关API和前端表单验证逻辑,优化Excel导入功能以处理客户编码的有效性和唯一性。添加数据库约束以维护数据完整性。

This commit is contained in:
geht
2026-04-22 16:56:48 +08:00
parent 2610dbc4c0
commit 9d621433c6
7 changed files with 162 additions and 3 deletions

View File

@@ -13,15 +13,25 @@ import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.constant.MesXslCustomerBizStatus;
import org.jeecg.modules.xslmes.entity.MesXslCustomer;
import org.jeecg.modules.xslmes.service.IMesXslCustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* MES 客户管理
@@ -53,6 +63,14 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
@RequiresPermissions("xslmes:mes_xsl_customer:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody MesXslCustomer mesXslCustomer) {
if (oConvertUtils.isEmpty(mesXslCustomer.getCustomerCode())) {
return Result.error("客户编码不能为空");
}
String customerCode = mesXslCustomer.getCustomerCode().trim();
mesXslCustomer.setCustomerCode(customerCode);
if (mesXslCustomerService.existsSameCustomerCode(customerCode, null)) {
return Result.error("客户编码已存在,不允许重复");
}
// 新增默认启用0空白或未传时写入启用并与 iz_enable 对齐
String st = mesXslCustomer.getStatus();
if (st != null) {
@@ -72,6 +90,17 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
@RequiresPermissions("xslmes:mes_xsl_customer:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody MesXslCustomer mesXslCustomer) {
if (oConvertUtils.isEmpty(mesXslCustomer.getId())) {
return Result.error("主键不能为空");
}
if (oConvertUtils.isEmpty(mesXslCustomer.getCustomerCode())) {
return Result.error("客户编码不能为空");
}
String customerCode = mesXslCustomer.getCustomerCode().trim();
mesXslCustomer.setCustomerCode(customerCode);
if (mesXslCustomerService.existsSameCustomerCode(customerCode, mesXslCustomer.getId())) {
return Result.error("客户编码已存在,不允许重复");
}
if (mesXslCustomer.getStatus() != null) {
String s = mesXslCustomer.getStatus().trim();
mesXslCustomer.setStatus(s.isEmpty() ? null : s);
@@ -130,6 +159,20 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
return Result.OK("批量删除成功!");
}
@Operation(summary = "校验客户编码是否重复同租户dataId 为编辑时当前主键)")
@GetMapping(value = "/checkCustomerCode")
public Result<String> checkCustomerCode(
@RequestParam(name = "customerCode", required = true) String customerCode,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(customerCode) || customerCode.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesXslCustomerService.existsSameCustomerCode(customerCode, dataId)) {
return Result.error("该客户编码已存在");
}
return Result.OK("该值可用!");
}
@Operation(summary = "MES客户管理-通过id查询")
@GetMapping(value = "/queryById")
public Result<MesXslCustomer> queryById(@RequestParam(name = "id", required = true) String id) {
@@ -149,6 +192,75 @@ public class MesXslCustomerController extends JeecgController<MesXslCustomer, IM
@RequiresPermissions("xslmes:mes_xsl_customer:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, MesXslCustomer.class);
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
for (Map.Entry<String, MultipartFile> ent : fileMap.entrySet()) {
MultipartFile file = ent.getValue();
ImportParams params = new ImportParams();
params.setTitleRows(2);
params.setHeadRows(1);
params.setNeedSave(true);
try {
List<MesXslCustomer> list = ExcelImportUtil.importExcel(file.getInputStream(), MesXslCustomer.class, params);
if (list == null) {
list = List.of();
}
// 1客户编码非空、2导入文件内不重复、3与库中同租户数据不重复
Set<String> codesInFile = new HashSet<>();
for (int i = 0; i < list.size(); i++) {
MesXslCustomer row = list.get(i);
int rowNo = i + 1;
if (row == null) {
return Result.error("文件导入失败:第 " + rowNo + " 条数据无效(空行)");
}
String code = row.getCustomerCode();
if (code != null) {
code = code.trim();
}
if (oConvertUtils.isEmpty(code)) {
return Result.error("文件导入失败:第 " + rowNo + " 条客户编码不能为空");
}
row.setCustomerCode(code);
if (!codesInFile.add(code)) {
return Result.error("文件导入失败:客户编码【" + code + "】在导入文件中重复");
}
if (mesXslCustomerService.existsSameCustomerCode(code, null)) {
return Result.error("文件导入失败:第 " + rowNo + " 条客户编码【" + code + "】已存在,不允许重复");
}
}
for (MesXslCustomer row : list) {
if (row == null) {
continue;
}
String st = row.getStatus();
if (st != null) {
st = st.trim();
row.setStatus(st.isEmpty() ? null : st);
}
if (row.getStatus() == null || row.getStatus().isEmpty()) {
row.setStatus(MesXslCustomerBizStatus.ENABLED);
}
mesXslCustomerService.syncIzEnableWithStatus(row);
}
long start = System.currentTimeMillis();
mesXslCustomerService.saveBatch(list);
log.info("客户Excel导入完成耗时" + (System.currentTimeMillis() - start) + "ms行数=" + list.size());
return Result.ok("文件导入成功!数据行数:" + list.size());
} catch (Exception e) {
String msg = e.getMessage();
log.error(msg, e);
if (msg != null && msg.indexOf("Duplicate entry") >= 0) {
return Result.error("文件导入失败: 存在重复数据(客户编码在系统中需唯一)");
}
return Result.error("文件导入失败:" + e.getMessage());
} finally {
try {
file.getInputStream().close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
return Result.error("文件导入失败!");
}
}

View File

@@ -27,7 +27,7 @@ public class MesXslCustomer extends JeecgEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "客户编码", width = 20)
@Schema(description = "客户编码")
@Schema(description = "客户编码(同租户内唯一,不可重复)")
private String customerCode;
@Excel(name = "客户名称", width = 28)

View File

@@ -12,4 +12,9 @@ public interface IMesXslCustomerService extends IService<MesXslCustomer> {
* 按业务状态同步 izEnable停用字典值 1为 0其余为 1。删除仅 del_flag与 status 无关。
*/
void syncIzEnableWithStatus(MesXslCustomer entity);
/**
* 同租户下是否已存在相同客户编码(编辑时传 excludeId 排除自身;依赖多租户 SQL 只查当前租户)
*/
boolean existsSameCustomerCode(String customerCode, String excludeId);
}

View File

@@ -1,6 +1,7 @@
package org.jeecg.modules.xslmes.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.xslmes.constant.MesXslCustomerBizStatus;
import org.jeecg.modules.xslmes.entity.MesXslCustomer;
import org.jeecg.modules.xslmes.mapper.MesXslCustomerMapper;
@@ -29,4 +30,16 @@ public class MesXslCustomerServiceImpl extends ServiceImpl<MesXslCustomerMapper,
entity.setIzEnable(1);
}
}
@Override
public boolean existsSameCustomerCode(String customerCode, String excludeId) {
if (StringUtils.isBlank(customerCode)) {
return false;
}
String code = customerCode.trim();
return this.lambdaQuery()
.eq(MesXslCustomer::getCustomerCode, code)
.ne(StringUtils.isNotBlank(excludeId), MesXslCustomer::getId, excludeId)
.count() > 0L;
}
}

View File

@@ -0,0 +1,4 @@
-- 客户编码在同租户内唯一tenant_id 与多租户拦截一致NULL 已归 0 V3.9.2_20
-- 若执行失败请先排查SELECT tenant_id, customer_code, COUNT(*) c FROM mes_xsl_customer GROUP BY tenant_id, customer_code HAVING c > 1;
ALTER TABLE `mes_xsl_customer`
ADD UNIQUE KEY `uk_mes_xsl_customer_tenant_code` (`tenant_id`, `customer_code`);

View File

@@ -5,6 +5,7 @@ const { createConfirm } = useMessage();
enum Api {
list = '/xslmes/mesXslCustomer/list',
checkCustomerCode = '/xslmes/mesXslCustomer/checkCustomerCode',
queryById = '/xslmes/mesXslCustomer/queryById',
save = '/xslmes/mesXslCustomer/add',
edit = '/xslmes/mesXslCustomer/edit',
@@ -22,6 +23,10 @@ export const list = (params) => defHttp.get({ url: Api.list, params });
export const queryById = (params: { id: string }) => defHttp.get({ url: Api.queryById, params });
/** 客户编码唯一性校验(同租户;编辑传 dataId */
export const checkCustomerCode = (params: { customerCode: string; dataId?: string }) =>
defHttp.get({ url: Api.checkCustomerCode, params });
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();

View File

@@ -1,4 +1,5 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { checkCustomerCode } from './MesXslCustomer.api';
export const columns: BasicColumn[] = [
{ title: 'ID', align: 'center', dataIndex: 'id', width: 280, ellipsis: true, defaultHidden: true },
@@ -48,7 +49,26 @@ export const formSchema: FormSchema[] = [
field: 'customerCode',
required: true,
component: 'Input',
componentProps: { placeholder: '请输入客户编码' },
componentProps: { placeholder: '同租户内唯一,不可与已有客户重复' },
dynamicRules: ({ model }) => [
{ required: true, message: '请输入客户编码' },
{
validator: async (_rule, value) => {
const v = value == null ? '' : String(value).trim();
if (!v) {
return Promise.resolve();
}
try {
await checkCustomerCode({ customerCode: v, dataId: model?.id });
return Promise.resolve();
} catch (e: any) {
const msg = e?.response?.data?.message || e?.message || '该客户编码已存在';
return Promise.reject(msg);
}
},
trigger: 'blur',
},
],
},
{
label: '客户名称',