From 9d621433c6a1d0ba8a66e7a27659da0a427767f1 Mon Sep 17 00:00:00 2001 From: geht Date: Wed, 22 Apr 2026 16:56:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=AE=A2=E6=88=B7=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=AE=A2=E6=88=B7=E7=BC=96=E7=A0=81=E5=94=AF=E4=B8=80?= =?UTF-8?q?=E6=80=A7=E6=A0=A1=E9=AA=8C=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=90=8C?= =?UTF-8?q?=E7=A7=9F=E6=88=B7=E5=86=85=E5=AE=A2=E6=88=B7=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E4=B8=8D=E9=87=8D=E5=A4=8D=E3=80=82=E6=9B=B4=E6=96=B0=E7=9B=B8?= =?UTF-8?q?=E5=85=B3API=E5=92=8C=E5=89=8D=E7=AB=AF=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?Excel=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD=E4=BB=A5=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=AE=A2=E6=88=B7=E7=BC=96=E7=A0=81=E7=9A=84=E6=9C=89?= =?UTF-8?q?=E6=95=88=E6=80=A7=E5=92=8C=E5=94=AF=E4=B8=80=E6=80=A7=E3=80=82?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=BA=93=E7=BA=A6=E6=9D=9F?= =?UTF-8?q?=E4=BB=A5=E7=BB=B4=E6=8A=A4=E6=95=B0=E6=8D=AE=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MesXslCustomerController.java | 114 +++++++++++++++++- .../modules/xslmes/entity/MesXslCustomer.java | 2 +- .../service/IMesXslCustomerService.java | 5 + .../impl/MesXslCustomerServiceImpl.java | 13 ++ ...es_xsl_customer_code_unique_per_tenant.sql | 4 + .../mesXslCustomer/MesXslCustomer.api.ts | 5 + .../mesXslCustomer/MesXslCustomer.data.ts | 22 +++- 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_30__mes_xsl_customer_code_unique_per_tenant.sql diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslCustomerController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslCustomerController.java index afc3520..2e7d0a4 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslCustomerController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslCustomerController.java @@ -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 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 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 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 queryById(@RequestParam(name = "id", required = true) String id) { @@ -149,6 +192,75 @@ public class MesXslCustomerController extends JeecgController importExcel(HttpServletRequest request, HttpServletResponse response) { - return super.importExcel(request, response, MesXslCustomer.class); + MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; + Map fileMap = multipartRequest.getFileMap(); + for (Map.Entry ent : fileMap.entrySet()) { + MultipartFile file = ent.getValue(); + ImportParams params = new ImportParams(); + params.setTitleRows(2); + params.setHeadRows(1); + params.setNeedSave(true); + try { + List list = ExcelImportUtil.importExcel(file.getInputStream(), MesXslCustomer.class, params); + if (list == null) { + list = List.of(); + } + // 1)客户编码非空、2)导入文件内不重复、3)与库中同租户数据不重复 + Set 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("文件导入失败!"); } } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslCustomer.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslCustomer.java index a698b00..0a63b04 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslCustomer.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslCustomer.java @@ -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) diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslCustomerService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslCustomerService.java index ee17e54..e46e16e 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslCustomerService.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslCustomerService.java @@ -12,4 +12,9 @@ public interface IMesXslCustomerService extends IService { * 按业务状态同步 izEnable:停用(字典值 1)为 0,其余为 1。删除仅 del_flag,与 status 无关。 */ void syncIzEnableWithStatus(MesXslCustomer entity); + + /** + * 同租户下是否已存在相同客户编码(编辑时传 excludeId 排除自身;依赖多租户 SQL 只查当前租户) + */ + boolean existsSameCustomerCode(String customerCode, String excludeId); } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslCustomerServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslCustomerServiceImpl.java index 9462b76..b34c881 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslCustomerServiceImpl.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslCustomerServiceImpl.java @@ -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 0L; + } } diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_30__mes_xsl_customer_code_unique_per_tenant.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_30__mes_xsl_customer_code_unique_per_tenant.sql new file mode 100644 index 0000000..4e1eab3 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_30__mes_xsl_customer_code_unique_per_tenant.sql @@ -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`); diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.api.ts b/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.api.ts index 32b0e48..82113bb 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.api.ts +++ b/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.api.ts @@ -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(); diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.data.ts b/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.data.ts index 6e011e3..c7ed236 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.data.ts +++ b/jeecgboot-vue3/src/views/xslmes/mesXslCustomer/MesXslCustomer.data.ts @@ -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: '客户名称',