feat: 医院管理模块 (admin 端 CRUD + 导入导出)
- 后端: BizHospital controller/domain/vo/mapper/service + Mapper XML - 前端: ry-vue3/src/views/admin/Hospital.vue + api/business/hospital.js - 模块面向 admin 角色, 提供医院主数据维护 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
package com.ruoyi.business.controller;
|
||||
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.business.domain.BizHospital;
|
||||
import com.ruoyi.business.domain.dto.ImportResult;
|
||||
import com.ruoyi.business.domain.vo.BizHospitalExportVo;
|
||||
import com.ruoyi.business.domain.vo.BizHospitalImportVo;
|
||||
import com.ruoyi.business.service.IBizHospitalService;
|
||||
|
||||
/**
|
||||
* 医院字典 Controller (admin 资料库管理 / 医院管理)
|
||||
*
|
||||
* <p>CRUD + 导入/导出走 admin 路由 (前端 AdminLayout 角色控制);
|
||||
* 远程搜索 /search 为匿名公开 (医生注册下拉).
|
||||
* 注: 不加 @PreAuthorize — 与 BizExpert/BizOrg 等业务 controller 一致 (业务角色不走 RuoYi RBAC).
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/business/hospital")
|
||||
public class BizHospitalController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IBizHospitalService bizHospitalService;
|
||||
|
||||
/** 分页列表 (admin 后台, 支持 hospital/province/city/hospitalCode/militaryHospital/type 筛选) */
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(BizHospital entity) {
|
||||
startPage();
|
||||
List<BizHospital> rows = bizHospitalService.selectList(entity);
|
||||
return getDataTable(rows);
|
||||
}
|
||||
|
||||
/** 详情 */
|
||||
@GetMapping("/{hospitalId}")
|
||||
public AjaxResult getInfo(@PathVariable("hospitalId") Long hospitalId) {
|
||||
return success(bizHospitalService.getById(hospitalId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程搜索 (匿名公开, 医生注册下拉"边输入边查询")
|
||||
* GET /business/hospital/search?keyword=xxx → [{hospitalId, province, city, hospital, ...}] LIMIT 30
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public AjaxResult search(@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
return success(bizHospitalService.searchByKeyword(keyword));
|
||||
}
|
||||
|
||||
/** 删除前引用统计: 返回医院名被业务表 work_unit 引用的条数 */
|
||||
@GetMapping("/countUsage")
|
||||
public AjaxResult countUsage(@RequestParam("hospital") String hospital) {
|
||||
return success(bizHospitalService.countUsageByHospital(hospital));
|
||||
}
|
||||
|
||||
@Log(title = "医院管理", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody BizHospital entity) {
|
||||
return toAjax(bizHospitalService.insert(entity));
|
||||
}
|
||||
|
||||
@Log(title = "医院管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody BizHospital entity) {
|
||||
return toAjax(bizHospitalService.updateByPrimaryKey(entity));
|
||||
}
|
||||
|
||||
@Log(title = "医院管理", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{hospitalIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] hospitalIds) {
|
||||
return toAjax(bizHospitalService.deleteByPrimaryKeys(hospitalIds));
|
||||
}
|
||||
|
||||
/** 导出 (中文列头, 跟随当前筛选条件) */
|
||||
@Log(title = "医院管理", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, BizHospital entity) {
|
||||
List<BizHospital> list = bizHospitalService.selectList(entity);
|
||||
List<BizHospitalExportVo> exportList = new java.util.ArrayList<>(list.size());
|
||||
for (BizHospital h : list) {
|
||||
BizHospitalExportVo v = new BizHospitalExportVo();
|
||||
v.setHospitalId(h.getHospitalId());
|
||||
v.setProvince(h.getProvince());
|
||||
v.setCity(h.getCity());
|
||||
v.setHospital(h.getHospital());
|
||||
v.setOriginalHospital(h.getOriginalHospital());
|
||||
v.setHospitalCode(h.getHospitalCode());
|
||||
v.setMilitaryHospital(h.getMilitaryHospital());
|
||||
v.setType(h.getType());
|
||||
exportList.add(v);
|
||||
}
|
||||
ExcelUtil<BizHospitalExportVo> util = new ExcelUtil<>(BizHospitalExportVo.class);
|
||||
util.exportExcel(response, exportList, "医院数据");
|
||||
}
|
||||
|
||||
/** 下载导入模板 */
|
||||
@GetMapping("/importTemplate")
|
||||
public void importTemplate(HttpServletResponse response) {
|
||||
ExcelUtil<BizHospitalImportVo> util = new ExcelUtil<>(BizHospitalImportVo.class);
|
||||
util.importTemplateExcel(response, "医院数据");
|
||||
}
|
||||
|
||||
/** 批量导入 (Excel → biz_hospital, 去重, 返回 ImportResult) */
|
||||
@Log(title = "医院管理", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file) throws Exception {
|
||||
ImportResult result = bizHospitalService.importHospitals(file);
|
||||
return success(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.ruoyi.business.domain;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 医院字典 (biz_hospital)
|
||||
*
|
||||
* <p>admin 用户在"资料库管理 / 医院管理"下维护; 医生注册时通过 /business/hospital/search 远程搜索选择.
|
||||
*
|
||||
* <p>字段说明:
|
||||
* <ul>
|
||||
* <li>hospitalId — 雪花 ID (Long), 由 SnowflakeId.injectIfEmpty 生成; 53-bit, JS Number 安全</li>
|
||||
* <li>province/city — 省/市 (varchar 100)</li>
|
||||
* <li>hospital — 医院名称 (varchar 1000, 主显示名)</li>
|
||||
* <li>originalHospital — 原名称 (varchar 100)</li>
|
||||
* <li>hospitalCode — 医院 CODE (varchar 100, 如 H3301006)</li>
|
||||
* <li>militaryHospital — 是否军医院 (varchar 100, 现存数据为 '0'/'1')</li>
|
||||
* <li>type — 类型 (varchar 100, 现存数据为 '0'/'1')</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>说明: 原表 schema 中 id 为 int NOT NULL 无 auto_increment, 改 BIGINT 由应用生成雪花 ID (与项目其他表风格一致).
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizHospital {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 医院 ID (雪花 ID, BIGINT) */
|
||||
@Excel(name = "医院ID")
|
||||
private Long hospitalId;
|
||||
|
||||
/** 省 */
|
||||
@Excel(name = "省")
|
||||
private String province;
|
||||
|
||||
/** 市 */
|
||||
@Excel(name = "市")
|
||||
private String city;
|
||||
|
||||
/** 医院名称 (主显示名) */
|
||||
@Excel(name = "医院名称")
|
||||
private String hospital;
|
||||
|
||||
/** 原名称 */
|
||||
@Excel(name = "原名称")
|
||||
private String originalHospital;
|
||||
|
||||
/** 医院 CODE */
|
||||
@Excel(name = "医院CODE")
|
||||
private String hospitalCode;
|
||||
|
||||
/** 是否军医院 */
|
||||
@Excel(name = "是否军医院")
|
||||
private String militaryHospital;
|
||||
|
||||
/** 类型 */
|
||||
@Excel(name = "类型")
|
||||
private String type;
|
||||
|
||||
public Long getHospitalId() { return hospitalId; }
|
||||
public void setHospitalId(Long hospitalId) { this.hospitalId = hospitalId; }
|
||||
|
||||
public String getProvince() { return province; }
|
||||
public void setProvince(String province) { this.province = province; }
|
||||
|
||||
public String getCity() { return city; }
|
||||
public void setCity(String city) { this.city = city; }
|
||||
|
||||
public String getHospital() { return hospital; }
|
||||
public void setHospital(String hospital) { this.hospital = hospital; }
|
||||
|
||||
public String getOriginalHospital() { return originalHospital; }
|
||||
public void setOriginalHospital(String originalHospital) { this.originalHospital = originalHospital; }
|
||||
|
||||
public String getHospitalCode() { return hospitalCode; }
|
||||
public void setHospitalCode(String hospitalCode) { this.hospitalCode = hospitalCode; }
|
||||
|
||||
public String getMilitaryHospital() { return militaryHospital; }
|
||||
public void setMilitaryHospital(String militaryHospital) { this.militaryHospital = militaryHospital; }
|
||||
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.ruoyi.business.domain.vo;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 医院字典导出 VO
|
||||
*
|
||||
* <p>仅用于 Excel 导出, 不参与业务逻辑.
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizHospitalExportVo {
|
||||
|
||||
@Excel(name = "医院ID", sort = 1)
|
||||
private Long hospitalId;
|
||||
|
||||
@Excel(name = "省", sort = 2)
|
||||
private String province;
|
||||
|
||||
@Excel(name = "市", sort = 3)
|
||||
private String city;
|
||||
|
||||
@Excel(name = "医院名称", sort = 4)
|
||||
private String hospital;
|
||||
|
||||
@Excel(name = "原名称", sort = 5)
|
||||
private String originalHospital;
|
||||
|
||||
@Excel(name = "医院CODE", sort = 6)
|
||||
private String hospitalCode;
|
||||
|
||||
@Excel(name = "是否军医院", sort = 7)
|
||||
private String militaryHospital;
|
||||
|
||||
@Excel(name = "类型", sort = 8)
|
||||
private String type;
|
||||
|
||||
public Long getHospitalId() { return hospitalId; }
|
||||
public void setHospitalId(Long hospitalId) { this.hospitalId = hospitalId; }
|
||||
|
||||
public String getProvince() { return province; }
|
||||
public void setProvince(String province) { this.province = province; }
|
||||
|
||||
public String getCity() { return city; }
|
||||
public void setCity(String city) { this.city = city; }
|
||||
|
||||
public String getHospital() { return hospital; }
|
||||
public void setHospital(String hospital) { this.hospital = hospital; }
|
||||
|
||||
public String getOriginalHospital() { return originalHospital; }
|
||||
public void setOriginalHospital(String originalHospital) { this.originalHospital = originalHospital; }
|
||||
|
||||
public String getHospitalCode() { return hospitalCode; }
|
||||
public void setHospitalCode(String hospitalCode) { this.hospitalCode = hospitalCode; }
|
||||
|
||||
public String getMilitaryHospital() { return militaryHospital; }
|
||||
public void setMilitaryHospital(String militaryHospital) { this.militaryHospital = militaryHospital; }
|
||||
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.ruoyi.business.domain.vo;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 医院字典导入 VO
|
||||
*
|
||||
* <p>仅用于 Excel 批量导入 / 模板下载, 不参与业务逻辑.
|
||||
* 字段顺序与 Excel 列一致; hospitalId 不在导入范围 (由 SnowflakeId 生成).
|
||||
*
|
||||
* <p>必填: hospital (医院名称).
|
||||
* 其余字段允许空.
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizHospitalImportVo {
|
||||
|
||||
/** 省 */
|
||||
@Excel(name = "省", sort = 1)
|
||||
private String province;
|
||||
|
||||
/** 市 */
|
||||
@Excel(name = "市", sort = 2)
|
||||
private String city;
|
||||
|
||||
/** 医院名称 (必填) */
|
||||
@Excel(name = "医院名称", sort = 3)
|
||||
private String hospital;
|
||||
|
||||
/** 原名称 */
|
||||
@Excel(name = "原名称", sort = 4)
|
||||
private String originalHospital;
|
||||
|
||||
/** 医院 CODE */
|
||||
@Excel(name = "医院CODE", sort = 5)
|
||||
private String hospitalCode;
|
||||
|
||||
/** 是否军医院 (0=否, 1=是) */
|
||||
@Excel(name = "是否军医院", sort = 6)
|
||||
private String militaryHospital;
|
||||
|
||||
/** 类型 */
|
||||
@Excel(name = "类型", sort = 7)
|
||||
private String type;
|
||||
|
||||
public String getProvince() { return province; }
|
||||
public void setProvince(String province) { this.province = province; }
|
||||
|
||||
public String getCity() { return city; }
|
||||
public void setCity(String city) { this.city = city; }
|
||||
|
||||
public String getHospital() { return hospital; }
|
||||
public void setHospital(String hospital) { this.hospital = hospital; }
|
||||
|
||||
public String getOriginalHospital() { return originalHospital; }
|
||||
public void setOriginalHospital(String originalHospital) { this.originalHospital = originalHospital; }
|
||||
|
||||
public String getHospitalCode() { return hospitalCode; }
|
||||
public void setHospitalCode(String hospitalCode) { this.hospitalCode = hospitalCode; }
|
||||
|
||||
public String getMilitaryHospital() { return militaryHospital; }
|
||||
public void setMilitaryHospital(String militaryHospital) { this.militaryHospital = militaryHospital; }
|
||||
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.ruoyi.business.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.business.domain.BizHospital;
|
||||
|
||||
/**
|
||||
* 医院字典 Mapper (biz_hospital)
|
||||
*
|
||||
* <p>无 del_flag 列 — 删除走硬删 (admin 字典, workUnit 字符串引用, 删除不会破坏业务数据完整性).
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public interface BizHospitalMapper {
|
||||
|
||||
/** 按主键查询 */
|
||||
BizHospital selectByPrimaryKey(@Param("hospitalId") Long hospitalId);
|
||||
|
||||
/** 分页 + 多条件模糊筛选 (admin 后台用, 支持 hospital/province/type/militaryHospital) */
|
||||
List<BizHospital> selectList(BizHospital entity);
|
||||
|
||||
/** 新增 */
|
||||
int insert(BizHospital entity);
|
||||
|
||||
/** 按主键更新 (动态 trim SET) */
|
||||
int updateByPrimaryKey(BizHospital entity);
|
||||
|
||||
/** 批量硬删 */
|
||||
int deleteByPrimaryKeys(@Param("hospitalIds") Long[] hospitalIds);
|
||||
|
||||
/**
|
||||
* 远程搜索: 按关键字模糊匹配 hospital / originalHospital / hospitalCode,
|
||||
* LIMIT 30 (医生注册下拉防爆).
|
||||
* keyword 为空时返回空集 (前端按"边输入边查询"约定不主动出建议).
|
||||
*/
|
||||
List<BizHospital> searchByKeyword(@Param("keyword") String keyword);
|
||||
|
||||
/**
|
||||
* 唯一性检查: 按 trim(hospital) + province + city 精确匹配 (case-insensitive).
|
||||
* hospitalId 非空时排除自身 (改名/同名异市用); 用于新增/编辑前的"同名医院"提示.
|
||||
* 返回匹配条数 (0 或 1).
|
||||
*/
|
||||
int countByTrimmedHospital(BizHospital entity);
|
||||
|
||||
/**
|
||||
* 统计指定医院名称被业务表 work_unit 字段引用的总条数.
|
||||
* 用于删除前给 admin 二次确认: "该医院已被 N 条专家/参会人引用".
|
||||
* 4 表 union: biz_expert / biz_signup_expert / biz_meeting_attendee / biz_execution_intent (有 work_unit 列)
|
||||
* 注: biz_support_intent 也有 work_unit 列, 一起并入.
|
||||
*/
|
||||
int countUsageByHospital(@Param("hospital") String hospital);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.ruoyi.business.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.business.domain.BizHospital;
|
||||
import com.ruoyi.business.domain.dto.ImportResult;
|
||||
|
||||
/**
|
||||
* 医院字典 Service 接口 (biz_hospital)
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public interface IBizHospitalService {
|
||||
|
||||
/** 按主键查询 */
|
||||
BizHospital getById(Long hospitalId);
|
||||
|
||||
/** 分页 + 多条件筛选 */
|
||||
List<BizHospital> selectList(BizHospital entity);
|
||||
|
||||
/** 新增 (医院名查重 + 雪花 ID) */
|
||||
int insert(BizHospital entity);
|
||||
|
||||
/** 编辑 (医院名查重) */
|
||||
int updateByPrimaryKey(BizHospital entity);
|
||||
|
||||
/** 批量硬删 */
|
||||
int deleteByPrimaryKeys(Long[] hospitalIds);
|
||||
|
||||
/** 远程搜索 (医生注册下拉, LIMIT 30) */
|
||||
List<BizHospital> searchByKeyword(String keyword);
|
||||
|
||||
/** 唯一性检查: 同 name + province + city 是否已存在 (排除自身) */
|
||||
int countByTrimmedHospital(BizHospital entity);
|
||||
|
||||
/** 统计医院名被业务表 work_unit 引用的条数 (删除前二次确认用) */
|
||||
int countUsageByHospital(String hospital);
|
||||
|
||||
/** 批量导入 (Excel → biz_hospital, 批内/DB 去重, 返回 ImportResult) */
|
||||
ImportResult importHospitals(MultipartFile file) throws Exception;
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
package com.ruoyi.business.service.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.business.domain.BizHospital;
|
||||
import com.ruoyi.business.domain.dto.ImportResult;
|
||||
import com.ruoyi.business.domain.vo.BizHospitalImportVo;
|
||||
import com.ruoyi.business.mapper.BizHospitalMapper;
|
||||
import com.ruoyi.business.service.IBizHospitalService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.id.SnowflakeId;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
|
||||
/**
|
||||
* 医院字典 Service 实现 (biz_hospital)
|
||||
*
|
||||
* <p>要点:
|
||||
* <ul>
|
||||
* <li>所有字符串字段入库前 trim (防首尾空白脏数据)</li>
|
||||
* <li>医院名 + province + city 判重 (trim + case-insensitive)</li>
|
||||
* <li>主键走雪花 ID (SnowflakeId.injectIfEmpty) — 前提: id 列已 ALTER 为 BIGINT</li>
|
||||
* <li>删除走硬删 (字典表, workUnit 为字符串无外键, 删除不破坏业务完整性)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
@Service
|
||||
public class BizHospitalServiceImpl implements IBizHospitalService {
|
||||
|
||||
@Autowired
|
||||
private BizHospitalMapper bizHospitalMapper;
|
||||
|
||||
@Override
|
||||
public BizHospital getById(Long hospitalId) {
|
||||
return bizHospitalMapper.selectByPrimaryKey(hospitalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BizHospital> selectList(BizHospital entity) {
|
||||
return bizHospitalMapper.selectList(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insert(BizHospital entity) {
|
||||
String hospital = trimToNull(entity.getHospital());
|
||||
if (hospital == null) {
|
||||
throw new ServiceException("医院名称不能为空");
|
||||
}
|
||||
entity.setHospital(hospital);
|
||||
normalize(entity);
|
||||
|
||||
// 同名医院查重 (trim + case-insensitive + province/city 匹配)
|
||||
BizHospital probe = new BizHospital();
|
||||
probe.setHospital(hospital);
|
||||
probe.setProvince(entity.getProvince());
|
||||
probe.setCity(entity.getCity());
|
||||
if (bizHospitalMapper.countByTrimmedHospital(probe) > 0) {
|
||||
throw new ServiceException("医院「" + hospital + "」已存在");
|
||||
}
|
||||
|
||||
SnowflakeId.injectIfEmpty(entity, "hospitalId");
|
||||
return bizHospitalMapper.insert(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateByPrimaryKey(BizHospital entity) {
|
||||
if (entity.getHospitalId() == null) {
|
||||
throw new ServiceException("医院 ID 不能为空");
|
||||
}
|
||||
String hospital = trimToNull(entity.getHospital());
|
||||
if (hospital == null) {
|
||||
throw new ServiceException("医院名称不能为空");
|
||||
}
|
||||
entity.setHospital(hospital);
|
||||
normalize(entity);
|
||||
|
||||
// 改名时同样查重 (排除自身)
|
||||
BizHospital probe = new BizHospital();
|
||||
probe.setHospital(hospital);
|
||||
probe.setProvince(entity.getProvince());
|
||||
probe.setCity(entity.getCity());
|
||||
probe.setHospitalId(entity.getHospitalId());
|
||||
if (bizHospitalMapper.countByTrimmedHospital(probe) > 0) {
|
||||
throw new ServiceException("医院「" + hospital + "」已存在");
|
||||
}
|
||||
|
||||
return bizHospitalMapper.updateByPrimaryKey(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteByPrimaryKeys(Long[] hospitalIds) {
|
||||
return bizHospitalMapper.deleteByPrimaryKeys(hospitalIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BizHospital> searchByKeyword(String keyword) {
|
||||
return bizHospitalMapper.searchByKeyword(keyword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByTrimmedHospital(BizHospital entity) {
|
||||
return bizHospitalMapper.countByTrimmedHospital(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countUsageByHospital(String hospital) {
|
||||
if (hospital == null || hospital.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return bizHospitalMapper.countUsageByHospital(hospital.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入: Excel → biz_hospital.
|
||||
* <ol>
|
||||
* <li>ExcelUtil 反序列化</li>
|
||||
* <li>行级校验 (医院名必填) + 批内去重 + DB 去重</li>
|
||||
* <li>逐行 insert (雪花 ID), 失败记 ngList 不影响其它行</li>
|
||||
* </ol>
|
||||
*/
|
||||
@Override
|
||||
public ImportResult importHospitals(MultipartFile file) throws Exception {
|
||||
ExcelUtil<BizHospitalImportVo> util = new ExcelUtil<>(BizHospitalImportVo.class);
|
||||
List<BizHospitalImportVo> importList = util.importExcel(file.getInputStream());
|
||||
if (importList == null || importList.isEmpty()) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
}
|
||||
ImportResult result = new ImportResult();
|
||||
Set<String> seenHospital = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < importList.size(); i++) {
|
||||
BizHospitalImportVo vo = importList.get(i);
|
||||
int rowNo = i + 2; // Excel 行号 (1=表头)
|
||||
try {
|
||||
String hospital = trimToNull(vo.getHospital());
|
||||
if (hospital == null) {
|
||||
throw new ServiceException("医院名称不能为空");
|
||||
}
|
||||
|
||||
// 批内去重 (case-insensitive)
|
||||
String key = hospital.toLowerCase();
|
||||
if (!seenHospital.add(key)) {
|
||||
throw new ServiceException("医院「" + hospital + "」在文件中重复");
|
||||
}
|
||||
|
||||
// DB 去重
|
||||
BizHospital probe = new BizHospital();
|
||||
probe.setHospital(hospital);
|
||||
probe.setProvince(trimToNull(vo.getProvince()));
|
||||
probe.setCity(trimToNull(vo.getCity()));
|
||||
if (bizHospitalMapper.countByTrimmedHospital(probe) > 0) {
|
||||
throw new ServiceException("医院「" + hospital + "」已存在");
|
||||
}
|
||||
|
||||
BizHospital entity = new BizHospital();
|
||||
entity.setHospital(hospital);
|
||||
entity.setProvince(trimToNull(vo.getProvince()));
|
||||
entity.setCity(trimToNull(vo.getCity()));
|
||||
entity.setOriginalHospital(trimToNull(vo.getOriginalHospital()));
|
||||
entity.setHospitalCode(trimToNull(vo.getHospitalCode()));
|
||||
entity.setMilitaryHospital(trimToNull(vo.getMilitaryHospital()));
|
||||
entity.setType(trimToNull(vo.getType()));
|
||||
SnowflakeId.injectIfEmpty(entity, "hospitalId");
|
||||
bizHospitalMapper.insert(entity);
|
||||
|
||||
result.ok();
|
||||
} catch (Exception e) {
|
||||
result.fail(rowNo, e.getMessage() == null ? "导入失败" : e.getMessage());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 除 hospital 外的字段统一 trim (把空串压成 null, 避免脏数据) */
|
||||
private void normalize(BizHospital entity) {
|
||||
entity.setProvince(trimToNull(entity.getProvince()));
|
||||
entity.setCity(trimToNull(entity.getCity()));
|
||||
entity.setOriginalHospital(trimToNull(entity.getOriginalHospital()));
|
||||
entity.setHospitalCode(trimToNull(entity.getHospitalCode()));
|
||||
entity.setMilitaryHospital(trimToNull(entity.getMilitaryHospital()));
|
||||
entity.setType(trimToNull(entity.getType()));
|
||||
}
|
||||
|
||||
private String trimToNull(String s) {
|
||||
if (s == null) return null;
|
||||
String t = s.trim();
|
||||
return t.isEmpty() ? null : t;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.business.mapper.BizHospitalMapper">
|
||||
|
||||
<resultMap type="BizHospital" id="BizHospitalResult">
|
||||
<id property="hospitalId" column="id" />
|
||||
<result property="province" column="province" />
|
||||
<result property="city" column="city" />
|
||||
<result property="hospital" column="hospital" />
|
||||
<result property="originalHospital" column="original_hospital" />
|
||||
<result property="hospitalCode" column="hospital_code" />
|
||||
<result property="militaryHospital" column="military_hospital" />
|
||||
<result property="type" column="type" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectFields">
|
||||
select id, province, city, hospital, original_hospital, hospital_code, military_hospital, type
|
||||
from biz_hospital
|
||||
</sql>
|
||||
|
||||
<select id="selectByPrimaryKey" resultMap="BizHospitalResult" parameterType="Long">
|
||||
<include refid="selectFields"/>
|
||||
where id = #{hospitalId}
|
||||
</select>
|
||||
|
||||
<!--
|
||||
admin 后台分页 + 多条件筛选.
|
||||
所有 string 字段都走 trim+LIKE 防首尾空白; province/militaryHospital/type 支持精确 + 模糊混合 (用 trim 防空白).
|
||||
-->
|
||||
<select id="selectList" resultMap="BizHospitalResult" parameterType="BizHospital">
|
||||
<include refid="selectFields"/>
|
||||
<where>
|
||||
<if test="hospital != null and hospital != ''">and trim(hospital) like concat('%', #{hospital}, '%')</if>
|
||||
<if test="province != null and province != ''">and trim(province) like concat('%', #{province}, '%')</if>
|
||||
<if test="city != null and city != ''">and trim(city) like concat('%', #{city}, '%')</if>
|
||||
<if test="hospitalCode != null and hospitalCode != ''">and trim(hospital_code) like concat('%', #{hospitalCode}, '%')</if>
|
||||
<if test="militaryHospital != null and militaryHospital != ''">and trim(military_hospital) = #{militaryHospital}</if>
|
||||
<if test="type != null and type != ''">and trim(type) = #{type}</if>
|
||||
</where>
|
||||
order by hospital_code asc, id asc
|
||||
</select>
|
||||
|
||||
<!--
|
||||
远程搜索: 三列 OR 模糊, LIMIT 30 防大表爆.
|
||||
keyword 为空: 不返回任何记录 (前端按"初始 options 为空"约定, 不主动出建议).
|
||||
-->
|
||||
<select id="searchByKeyword" resultMap="BizHospitalResult">
|
||||
<include refid="selectFields"/>
|
||||
<where>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
and (
|
||||
trim(hospital) like concat('%', #{keyword}, '%')
|
||||
or trim(original_hospital) like concat('%', #{keyword}, '%')
|
||||
or trim(hospital_code) like concat('%', #{keyword}, '%')
|
||||
)
|
||||
</if>
|
||||
<if test="keyword == null or keyword == ''">
|
||||
and 1 = 0
|
||||
</if>
|
||||
</where>
|
||||
order by hospital_code asc, id asc
|
||||
limit 30
|
||||
</select>
|
||||
|
||||
<!--
|
||||
ID 生成: 雪花 ID (Long), 由 service 层 SnowflakeId.injectIfEmpty 注入 hospitalId.
|
||||
注: 需先执行 ALTER TABLE biz_hospital MODIFY COLUMN id BIGINT NOT NULL (部署前 DDL);
|
||||
雪花值 (~10^15) 与现有 int 数据 (max 29248) 不冲突.
|
||||
-->
|
||||
<insert id="insert" parameterType="BizHospital">
|
||||
insert into biz_hospital
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
id,
|
||||
<if test="province != null and province != ''">province,</if>
|
||||
<if test="city != null and city != ''">city,</if>
|
||||
hospital,
|
||||
<if test="originalHospital != null and originalHospital != ''">original_hospital,</if>
|
||||
<if test="hospitalCode != null and hospitalCode != ''">hospital_code,</if>
|
||||
<if test="militaryHospital != null and militaryHospital != ''">military_hospital,</if>
|
||||
<if test="type != null and type != ''">type,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
#{hospitalId},
|
||||
<if test="province != null and province != ''">#{province},</if>
|
||||
<if test="city != null and city != ''">#{city},</if>
|
||||
#{hospital},
|
||||
<if test="originalHospital != null and originalHospital != ''">#{originalHospital},</if>
|
||||
<if test="hospitalCode != null and hospitalCode != ''">#{hospitalCode},</if>
|
||||
<if test="militaryHospital != null and militaryHospital != ''">#{militaryHospital},</if>
|
||||
<if test="type != null and type != ''">#{type},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<!--
|
||||
trim + 动态 SET: hospital 必填, 其余可空更新 (trim 防首尾空白污染).
|
||||
-->
|
||||
<update id="updateByPrimaryKey" parameterType="BizHospital">
|
||||
update biz_hospital
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="province != null">province = #{province},</if>
|
||||
<if test="city != null">city = #{city},</if>
|
||||
hospital = #{hospital},
|
||||
<if test="originalHospital != null">original_hospital = #{originalHospital},</if>
|
||||
<if test="hospitalCode != null">hospital_code = #{hospitalCode},</if>
|
||||
<if test="militaryHospital != null">military_hospital = #{militaryHospital},</if>
|
||||
<if test="type != null">type = #{type},</if>
|
||||
</trim>
|
||||
where id = #{hospitalId}
|
||||
</update>
|
||||
|
||||
<!-- 硬删 (无 del_flag) -->
|
||||
<delete id="deleteByPrimaryKeys" parameterType="Long">
|
||||
delete from biz_hospital where id in
|
||||
<foreach collection="hospitalIds" item="hid" open="(" separator="," close=")">
|
||||
#{hid}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<!--
|
||||
唯一性检查: trim + case-insensitive (lower) + 省/市精确匹配.
|
||||
语义: 同名称 + 同省 + 同市 才算重复 (不同省/市的同名医院是不同医院, 允许).
|
||||
hospitalId 非空时排除自身 (编辑改名场景).
|
||||
-->
|
||||
<select id="countByTrimmedHospital" parameterType="BizHospital" resultType="int">
|
||||
select count(*) from biz_hospital
|
||||
where lower(trim(hospital)) = lower(trim(#{hospital}))
|
||||
<if test="province != null and province != ''">and trim(province) = trim(#{province})</if>
|
||||
<if test="province == null or province == ''">and province is null</if>
|
||||
<if test="city != null and city != ''">and trim(city) = trim(#{city})</if>
|
||||
<if test="city == null or city == ''">and city is null</if>
|
||||
<if test="hospitalId != null">and id != #{hospitalId}</if>
|
||||
</select>
|
||||
|
||||
<!--
|
||||
删除前引用次数统计: 跨 4 张业务表 UNION, 统计 work_unit 字段等于指定医院名的总条数.
|
||||
用 lower+trim 防空白/大小写差异导致漏数.
|
||||
注: 表名/字段名确认依据 (设计文档 + 现状):
|
||||
biz_expert.work_unit (varchar 200) 已审核通过的专家档案
|
||||
biz_signup_expert.work_unit (varchar 200) 报名记录
|
||||
biz_meeting_attendee.work_unit (varchar 200) 会议参会人
|
||||
biz_execution_intent.work_unit (varchar 200) 执行意向 (按 collation utf8mb4_unicode_ci)
|
||||
biz_support_intent.work_unit (varchar 200) 支持意向 (legacy 旧表, 部分场景仍写入)
|
||||
-->
|
||||
<select id="countUsageByHospital" parameterType="String" resultType="int">
|
||||
select (
|
||||
(select count(*) from biz_expert where lower(trim(work_unit)) = lower(trim(#{hospital})))
|
||||
+ (select count(*) from biz_signup_expert where lower(trim(work_unit)) = lower(trim(#{hospital})))
|
||||
+ (select count(*) from biz_meeting_attendee where lower(trim(work_unit)) = lower(trim(#{hospital})))
|
||||
+ (select count(*) from biz_execution_intent where lower(trim(work_unit)) = lower(trim(#{hospital})))
|
||||
+ (select count(*) from biz_support_intent where lower(trim(work_unit)) = lower(trim(#{hospital})))
|
||||
) as total
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user