feat: 合并 admin/manager 重复页 + 公示意向 + 劳务签署
页合并 (admin/manager 共用, route.path 角色感知): - expert: Experts.vue + ExpertNew.vue (list/new/edit/view 一套) - sponsor-orgs: SponsorOrgs.vue - sponsor-people: SponsorPeople.vue + SponsorPersonNew.vue + SponsorPersonDetail.vue - executor-orgs: ExecutorOrgs.vue - executor-people: ExecutorPeople.vue + ExecutorPersonNew.vue + ExecutorPersonDetail.vue - 给 SponsorPeople / ExecutorPeople op 列补上 查看/编辑 按钮 (潜在 bug 修复) - 详情弹窗统一用 el-descriptions 风格 (替代 admin 旧版 ElMessageBox.alert) 后端: - BizSignController + BizSignServiceImpl + PdfService (劳务签署 PDF 流程) - BizPublicityIntentController (公示意向: 支持/执行) - BizPublicitySupportIntent / BizPublicityExecutionIntent ExportVo - BizMeetingAttendee 字段合并 (bank_region 替代 bankProvince/bankCity) - BizExpert 字段合并 (id_card_attachments CSV, bank_region) - Excel 导入模板精简 (开户行 1 列) 前端: - doctor/SignFill + SignContract + SignSuccess (劳务签署 3 页流程) - ImportResultDialog 通用组件 - IdCardUploader 重构 (单 v-model:idCardAttachments, CSV 格式) - AreaCascader 调整 - Login.vue + AdminLayout.vue + PortalShell.vue + Home.vue 适配 DB: 字段合并 (id_card_front/back → id_card_attachments, bank_province/city → bank_region)
This commit is contained in:
+8
-3
@@ -131,9 +131,14 @@ public class BizAuthController extends BaseController {
|
||||
return error("验证码错误或已过期: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 3. 查用户 (mapper.checkPhoneUnique 返回 SysUser 或 null)
|
||||
SysUser user = userMapper.checkPhoneUnique(phone);
|
||||
if (user == null || user.getUserId() == null) {
|
||||
// 3. 查用户 (checkPhoneUnique 只 select user_id+phonenumber, 缺 role_type/user_name/status,
|
||||
// 需再按 userId 查完整 user, 否则 LoginUser 里 roleType=null 导致前端拿不到角色而跳回登录页)
|
||||
SysUser probe = userMapper.checkPhoneUnique(phone);
|
||||
if (probe == null || probe.getUserId() == null) {
|
||||
return error("该手机号未注册");
|
||||
}
|
||||
SysUser user = userMapper.selectUserById(probe.getUserId());
|
||||
if (user == null) {
|
||||
return error("该手机号未注册");
|
||||
}
|
||||
|
||||
|
||||
+14
-12
@@ -3,7 +3,6 @@ package com.ruoyi.business.controller;
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
@@ -15,6 +14,7 @@ import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.business.domain.BizExpert;
|
||||
import com.ruoyi.business.domain.dto.ImportResult;
|
||||
import com.ruoyi.business.domain.vo.BizExpertExportVo;
|
||||
import com.ruoyi.business.domain.vo.BizExpertImportVo;
|
||||
import com.ruoyi.business.service.IBizExpertService;
|
||||
@@ -98,7 +98,7 @@ public class BizExpertController extends BaseController
|
||||
* 导出专家列表 (中文列头)
|
||||
*/
|
||||
@Log(title = "专家", businessType = BusinessType.EXPORT)
|
||||
@PreAuthorize("@ss.hasPermi('business:expert:export')")
|
||||
// 注: 不加 @PreAuthorize, 与本 controller 其他 CRUD 端点一致 — manager 是业务角色, 不走 RuoYi RBAC 权限
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, BizExpert bizExpert)
|
||||
{
|
||||
@@ -116,8 +116,7 @@ public class BizExpertController extends BaseController
|
||||
v.setIdCard(e.getIdCard());
|
||||
v.setBankCard(e.getBankCard());
|
||||
v.setBankName(e.getBankName());
|
||||
v.setBankProvince(e.getBankProvince());
|
||||
v.setBankCity(e.getBankCity());
|
||||
v.setBankRegion(e.getBankRegion());
|
||||
v.setBankAddress(e.getBankAddress());
|
||||
v.setStatus(e.getStatus());
|
||||
v.setAuditStatus(e.getAuditStatus());
|
||||
@@ -141,19 +140,22 @@ public class BizExpertController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入专家 (Excel), 参考 RuoYi 系统用户导入模式
|
||||
* updateSupport=true: 同一手机号已存在 → 跳过 (按成功计)
|
||||
* 返回 message 含成功/失败计数 + 失败明细
|
||||
* 批量导入专家 (3 阶段批量):
|
||||
* <ol>
|
||||
* <li>Phase A: IN 查 sys_user (100/批), 记录已存在 phone</li>
|
||||
* <li>Phase B: batch insert sys_user (新 phone) (100/批)</li>
|
||||
* <li>Phase C: batch insert biz_expert (100/批)</li>
|
||||
* </ol>
|
||||
* updateSupport: 当前实现固定绑定已存在 phone → user_id (不再"跳过", 复用既有账号)
|
||||
* 返回 {@link ImportResult} 含成功/失败计数 + 失败明细 (行号 + 原因)
|
||||
*/
|
||||
@Log(title = "专家", businessType = BusinessType.IMPORT)
|
||||
@PreAuthorize("@ss.hasPermi('business:expert:import')")
|
||||
// 注: 不加 @PreAuthorize, 与本 controller 其他 CRUD 端点一致 — manager 是业务角色, 不走 RuoYi RBAC 权限
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
|
||||
{
|
||||
ExcelUtil<BizExpertImportVo> util = new ExcelUtil<BizExpertImportVo>(BizExpertImportVo.class);
|
||||
List<BizExpertImportVo> importList = util.importExcel(file.getInputStream());
|
||||
String operName = SecurityUtils.getUsername();
|
||||
String message = bizExpertService.importExpert(importList, updateSupport, operName);
|
||||
return success(message);
|
||||
ImportResult result = bizExpertService.importExpert(file, updateSupport, operName);
|
||||
return success(result);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-2
@@ -1,6 +1,8 @@
|
||||
package com.ruoyi.business.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.business.service.IBizMeetingAttendeeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
@@ -38,17 +40,32 @@ public class BizMeetingController extends BaseController
|
||||
{
|
||||
return success(bizMeetingService.getById(meetingId));
|
||||
}
|
||||
@Autowired
|
||||
private IBizMeetingAttendeeService attendeeService;
|
||||
|
||||
@Log(title = "会议", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody BizMeeting bizMeeting)
|
||||
{
|
||||
return toAjax(bizMeetingService.insert(bizMeeting));
|
||||
int rows = bizMeetingService.insert(bizMeeting);
|
||||
// 同步创建参会人中间表 (可选: 前端传 attendeeUserIds)
|
||||
Long[] attendeeUserIds = bizMeeting.getAttendeeUserIds();
|
||||
if (attendeeUserIds != null && attendeeUserIds.length > 0) {
|
||||
attendeeService.insertBatch(bizMeeting.getMeetingId(), attendeeUserIds);
|
||||
}
|
||||
return toAjax(rows);
|
||||
}
|
||||
@Log(title = "会议", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody BizMeeting bizMeeting)
|
||||
{
|
||||
return toAjax(bizMeetingService.updateByPrimaryKey(bizMeeting));
|
||||
int rows = bizMeetingService.updateByPrimaryKey(bizMeeting);
|
||||
// 同步追加参会人 (不去重, 由前端控制)
|
||||
Long[] attendeeUserIds = bizMeeting.getAttendeeUserIds();
|
||||
if (attendeeUserIds != null && attendeeUserIds.length > 0) {
|
||||
attendeeService.insertBatch(bizMeeting.getMeetingId(), attendeeUserIds);
|
||||
}
|
||||
return toAjax(rows);
|
||||
}
|
||||
@Log(title = "会议", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
|
||||
+61
@@ -1,6 +1,8 @@
|
||||
package com.ruoyi.business.controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
@@ -9,9 +11,12 @@ 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.SecurityUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.business.domain.BizProject;
|
||||
import com.ruoyi.business.domain.BizPublicityExecutionIntent;
|
||||
import com.ruoyi.business.domain.BizPublicitySupportIntent;
|
||||
import com.ruoyi.business.domain.vo.BizPublicityExecutionIntentExportVo;
|
||||
import com.ruoyi.business.domain.vo.BizPublicitySupportIntentExportVo;
|
||||
import com.ruoyi.business.service.IBizProjectService;
|
||||
import com.ruoyi.business.service.IBizPublicityExecutionIntentService;
|
||||
import com.ruoyi.business.service.IBizPublicitySupportIntentService;
|
||||
@@ -205,4 +210,60 @@ public class BizPublicityIntentController extends BaseController {
|
||||
public AjaxResult removeExecution(@PathVariable Long[] ids) {
|
||||
return toAjax(executionIntentService.deleteByPrimaryKeys(ids));
|
||||
}
|
||||
|
||||
// ============== 导出 (后端 ExcelUtil 写 .xlsx) ==============
|
||||
|
||||
/**
|
||||
* 导出执行意向 (按页面筛选条件透传, 无 startPage, 全量导出)
|
||||
* POST /business/publicityExecutionIntent/export?projectNo=&name=&...
|
||||
*/
|
||||
@Log(title = "导出执行意向", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/business/publicityExecutionIntent/export")
|
||||
public void exportExecution(HttpServletResponse response, BizPublicityExecutionIntent intent) {
|
||||
List<BizPublicityExecutionIntent> list = executionIntentService.selectList(intent);
|
||||
List<BizPublicityExecutionIntentExportVo> exportList = new ArrayList<>(list.size());
|
||||
for (BizPublicityExecutionIntent e : list) {
|
||||
BizPublicityExecutionIntentExportVo v = new BizPublicityExecutionIntentExportVo();
|
||||
v.setProjectNo(e.getProjectNo());
|
||||
v.setProjectName(e.getProjectName());
|
||||
v.setName(e.getName());
|
||||
v.setWorkUnit(e.getWorkUnit());
|
||||
v.setDepartment(e.getDepartment());
|
||||
v.setPosition(e.getPosition());
|
||||
v.setPhone(e.getPhone());
|
||||
v.setUserStatus(e.getUserId() != null ? "存在" : "不存在");
|
||||
v.setIntentStatus(e.getIntentStatus() == null || e.getIntentStatus().isEmpty() ? "待审核" : e.getIntentStatus());
|
||||
v.setCreateTime(e.getCreateTime());
|
||||
exportList.add(v);
|
||||
}
|
||||
ExcelUtil<BizPublicityExecutionIntentExportVo> util = new ExcelUtil<>(BizPublicityExecutionIntentExportVo.class);
|
||||
util.exportExcel(response, exportList, "执行意向");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出支持意向
|
||||
* POST /business/publicitySupportIntent/export
|
||||
*/
|
||||
@Log(title = "导出支持意向", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/business/publicitySupportIntent/export")
|
||||
public void exportSupport(HttpServletResponse response, BizPublicitySupportIntent intent) {
|
||||
List<BizPublicitySupportIntent> list = supportIntentService.selectList(intent);
|
||||
List<BizPublicitySupportIntentExportVo> exportList = new ArrayList<>(list.size());
|
||||
for (BizPublicitySupportIntent e : list) {
|
||||
BizPublicitySupportIntentExportVo v = new BizPublicitySupportIntentExportVo();
|
||||
v.setProjectNo(e.getProjectNo());
|
||||
v.setProjectName(e.getProjectName());
|
||||
v.setName(e.getName());
|
||||
v.setWorkUnit(e.getWorkUnit());
|
||||
v.setDepartment(e.getDepartment());
|
||||
v.setPosition(e.getPosition());
|
||||
v.setPhone(e.getPhone());
|
||||
v.setUserStatus(e.getUserId() != null ? "存在" : "不存在");
|
||||
v.setIntentStatus(e.getIntentStatus() == null || e.getIntentStatus().isEmpty() ? "待审核" : e.getIntentStatus());
|
||||
v.setCreateTime(e.getCreateTime());
|
||||
exportList.add(v);
|
||||
}
|
||||
ExcelUtil<BizPublicitySupportIntentExportVo> util = new ExcelUtil<>(BizPublicitySupportIntentExportVo.class);
|
||||
util.exportExcel(response, exportList, "支持意向");
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.ruoyi.business.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.business.domain.BizMeetingAttendee;
|
||||
import com.ruoyi.business.service.BizSignService;
|
||||
|
||||
/**
|
||||
* 医生劳务协议签署 Controller
|
||||
* 简化版: 不用 token, 走 SecurityUtils.getUserId() 识别医生 (用户登录后访问)
|
||||
*
|
||||
* 接口:
|
||||
* - GET /business/sign/info?attendeeId=X 拉填写页所有数据
|
||||
* - POST /business/sign/saveProfile 保存医生填的字段
|
||||
* - GET /business/sign/contract?attendeeId=X 拉完整协议 HTML
|
||||
* - POST /business/sign/submit 提交签字
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/business/sign")
|
||||
public class BizSignController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private BizSignService signService;
|
||||
|
||||
/** 医生填写页 GET /info?attendeeId=X */
|
||||
@GetMapping("/info")
|
||||
public AjaxResult getSignInfo(@RequestParam("attendeeId") Long attendeeId) {
|
||||
return success(signService.getSignInfo(attendeeId));
|
||||
}
|
||||
|
||||
/** 医生点 "保存" / "下一步" 后调 */
|
||||
@PostMapping("/saveProfile")
|
||||
public AjaxResult saveProfile(@RequestParam("attendeeId") Long attendeeId,
|
||||
@RequestBody BizMeetingAttendee form) {
|
||||
signService.saveProfile(attendeeId, form);
|
||||
return success("已保存");
|
||||
}
|
||||
|
||||
/** 拉完整协议 HTML (占位符已替换) */
|
||||
@GetMapping("/contract")
|
||||
public AjaxResult getContractHtml(@RequestParam("attendeeId") Long attendeeId) {
|
||||
// 注意: 不能用 success(String) (会把 HTML 当 msg), 必须显式传 data
|
||||
return AjaxResult.success("操作成功", signService.getContractHtml(attendeeId));
|
||||
}
|
||||
|
||||
/** 医生点 "提交" 签字 */
|
||||
@PostMapping("/submit")
|
||||
public AjaxResult submitSign(@RequestParam("attendeeId") Long attendeeId,
|
||||
@RequestBody java.util.Map<String, String> body,
|
||||
@RequestHeader(value = "X-Real-IP", required = false) String realIp) {
|
||||
String handsign = body.get("handsign");
|
||||
String contentHtml = body.get("contentHtml");
|
||||
String signedIp = (realIp != null && !realIp.isEmpty()) ? realIp : currentRequestIp();
|
||||
java.util.Map<String, String> pdfUrls = signService.submitSign(attendeeId, handsign, contentHtml, signedIp);
|
||||
return success(pdfUrls);
|
||||
}
|
||||
|
||||
private String currentRequestIp() {
|
||||
try {
|
||||
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attrs != null && attrs.getRequest() != null) {
|
||||
return attrs.getRequest().getRemoteAddr();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -66,16 +66,12 @@ public class BizExpert extends BaseEntity {
|
||||
private String bankCard;
|
||||
/** 银行名称 */
|
||||
private String bankName;
|
||||
/** 开户行省份 */
|
||||
private String bankProvince;
|
||||
/** 开户行城市 */
|
||||
private String bankCity;
|
||||
/** 开户行省/市 (1-3 段, / 分隔, 直辖市省=市) */
|
||||
private String bankRegion;
|
||||
/** 开户行地址 */
|
||||
private String bankAddress;
|
||||
/** 身份证正面URL */
|
||||
private String idCardFrontUrl;
|
||||
/** 身份证反面URL */
|
||||
private String idCardBackUrl;
|
||||
/** 身份证正面/反面 URL (CSV, 逗号分隔, 空段保留位) */
|
||||
private String idCardAttachments;
|
||||
/** 审核意见 */
|
||||
private String auditOpinion;
|
||||
/** 状态 0正常 1禁用 */
|
||||
@@ -124,14 +120,10 @@ public class BizExpert extends BaseEntity {
|
||||
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
|
||||
public String getBankName() { return bankName; }
|
||||
public void setBankName(String bankName) { this.bankName = bankName; }
|
||||
public String getBankProvince() { return bankProvince; }
|
||||
public void setBankProvince(String bankProvince) { this.bankProvince = bankProvince; }
|
||||
public String getBankCity() { return bankCity; }
|
||||
public void setBankCity(String bankCity) { this.bankCity = bankCity; }
|
||||
public String getBankRegion() { return bankRegion; }
|
||||
public void setBankRegion(String bankRegion) { this.bankRegion = bankRegion; }
|
||||
public String getBankAddress() { return bankAddress; }
|
||||
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
|
||||
public String getIdCardFrontUrl() { return idCardFrontUrl; }
|
||||
public void setIdCardFrontUrl(String idCardFrontUrl) { this.idCardFrontUrl = idCardFrontUrl; }
|
||||
public String getIdCardBackUrl() { return idCardBackUrl; }
|
||||
public void setIdCardBackUrl(String idCardBackUrl) { this.idCardBackUrl = idCardBackUrl; }
|
||||
public String getIdCardAttachments() { return idCardAttachments; }
|
||||
public void setIdCardAttachments(String idCardAttachments) { this.idCardAttachments = idCardAttachments; }
|
||||
}
|
||||
@@ -78,6 +78,8 @@ public class BizMeeting extends BaseEntity {
|
||||
private String laborSigned;
|
||||
/** 参会人 user_id (非持久化字段, 仅用于 mapper WHERE 过滤; controller 注入, 配合 biz_meeting_attendee 中间表) */
|
||||
private transient Long userId;
|
||||
/** 新增/编辑会议时传入, 自动批量写入 biz_meeting_attendee 中间表 (非持久化) */
|
||||
private Long[] attendeeUserIds;
|
||||
public Long getMeetingId() { return meetingId; }
|
||||
public void setMeetingId(Long meetingId) { this.meetingId = meetingId; }
|
||||
public String getProjectNo() { return projectNo; }
|
||||
@@ -112,6 +114,9 @@ public class BizMeeting extends BaseEntity {
|
||||
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||
public String getOrgName() { return orgName; }
|
||||
public void setOrgName(String orgName) { this.orgName = orgName; }
|
||||
private String address;
|
||||
public String getAddress() { return address; }
|
||||
public void setAddress(String address) { this.address = address; }
|
||||
public String getSupervisionOpinion() { return supervisionOpinion; }
|
||||
public void setSupervisionOpinion(String supervisionOpinion) { this.supervisionOpinion = supervisionOpinion; }
|
||||
public String getSupervisionBy() { return supervisionBy; }
|
||||
@@ -126,4 +131,6 @@ public class BizMeeting extends BaseEntity {
|
||||
public void setLaborSigned(String laborSigned) { this.laborSigned = laborSigned; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public Long[] getAttendeeUserIds() { return attendeeUserIds; }
|
||||
public void setAttendeeUserIds(Long[] attendeeUserIds) { this.attendeeUserIds = attendeeUserIds; }
|
||||
}
|
||||
+69
-1
@@ -1,5 +1,6 @@
|
||||
package com.ruoyi.business.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
@@ -7,7 +8,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 会议参会人 (biz_meeting_attendee 中间表)
|
||||
* 用于按 user_id 过滤"我参加的会议", 替代在 biz_meeting 上加冗余字段
|
||||
* 用于按 user_id 过滤"我参加的会议" + 医生签字劳务协议的快照数据
|
||||
*/
|
||||
public class BizMeetingAttendee extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -17,10 +18,36 @@ public class BizMeetingAttendee extends BaseEntity {
|
||||
private Long meetingId;
|
||||
/** user_id (FK sys_user.user_id) */
|
||||
private Long userId;
|
||||
/* ===== 签字快照字段 (从 biz_expert 预填,医生可改) ===== */
|
||||
private String name;
|
||||
private String phone;
|
||||
private String workUnit;
|
||||
private String department;
|
||||
private String title;
|
||||
private String idCard;
|
||||
private String bankCard;
|
||||
private String bankName;
|
||||
private String bankBranch;
|
||||
/** 开户行省/市 (1-3 段, / 分隔, 直辖市省=市) */
|
||||
private String bankRegion;
|
||||
private String bankAddress;
|
||||
private String accountName;
|
||||
private String idCardAttachments;
|
||||
/* ===== 劳务信息 (admin 预填,前端只读) ===== */
|
||||
private String laborForm;
|
||||
private BigDecimal feePreTax;
|
||||
private BigDecimal tax;
|
||||
private BigDecimal fee;
|
||||
/* ===== 审计 + 签字结果 ===== */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date signedAt;
|
||||
private String signedIp;
|
||||
/** 手写签名 Base64 (longtext) — 由前端手写板生成 */
|
||||
private String handsign;
|
||||
/** 劳务协议 URL (OSS) */
|
||||
private String laborProtocol;
|
||||
/** 脱敏版协议 URL (身份证/银行卡用 * 替换) */
|
||||
private String laborProtocolMasked;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
/* ====== 非持久化字段, 用于 selectUnsignedByUserId 联表查询 ====== */
|
||||
@@ -29,16 +56,57 @@ public class BizMeetingAttendee extends BaseEntity {
|
||||
private transient Date endTime;
|
||||
private transient String projectName;
|
||||
private transient String projectNo;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getMeetingId() { return meetingId; }
|
||||
public void setMeetingId(Long meetingId) { this.meetingId = meetingId; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getWorkUnit() { return workUnit; }
|
||||
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
|
||||
public String getDepartment() { return department; }
|
||||
public void setDepartment(String department) { this.department = department; }
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
public String getIdCard() { return idCard; }
|
||||
public void setIdCard(String idCard) { this.idCard = idCard; }
|
||||
public String getBankCard() { return bankCard; }
|
||||
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
|
||||
public String getBankName() { return bankName; }
|
||||
public void setBankName(String bankName) { this.bankName = bankName; }
|
||||
public String getBankBranch() { return bankBranch; }
|
||||
public void setBankBranch(String bankBranch) { this.bankBranch = bankBranch; }
|
||||
public String getBankRegion() { return bankRegion; }
|
||||
public void setBankRegion(String bankRegion) { this.bankRegion = bankRegion; }
|
||||
public String getBankAddress() { return bankAddress; }
|
||||
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
|
||||
public String getAccountName() { return accountName; }
|
||||
public void setAccountName(String accountName) { this.accountName = accountName; }
|
||||
public String getIdCardAttachments() { return idCardAttachments; }
|
||||
public void setIdCardAttachments(String idCardAttachments) { this.idCardAttachments = idCardAttachments; }
|
||||
public String getLaborForm() { return laborForm; }
|
||||
public void setLaborForm(String laborForm) { this.laborForm = laborForm; }
|
||||
public BigDecimal getFeePreTax() { return feePreTax; }
|
||||
public void setFeePreTax(BigDecimal feePreTax) { this.feePreTax = feePreTax; }
|
||||
public BigDecimal getTax() { return tax; }
|
||||
public void setTax(BigDecimal tax) { this.tax = tax; }
|
||||
public BigDecimal getFee() { return fee; }
|
||||
public void setFee(BigDecimal fee) { this.fee = fee; }
|
||||
public Date getSignedAt() { return signedAt; }
|
||||
public void setSignedAt(Date signedAt) { this.signedAt = signedAt; }
|
||||
public String getSignedIp() { return signedIp; }
|
||||
public void setSignedIp(String signedIp) { this.signedIp = signedIp; }
|
||||
public String getHandsign() { return handsign; }
|
||||
public void setHandsign(String handsign) { this.handsign = handsign; }
|
||||
public String getLaborProtocol() { return laborProtocol; }
|
||||
public void setLaborProtocol(String laborProtocol) { this.laborProtocol = laborProtocol; }
|
||||
public String getLaborProtocolMasked() { return laborProtocolMasked; }
|
||||
public void setLaborProtocolMasked(String laborProtocolMasked) { this.laborProtocolMasked = laborProtocolMasked; }
|
||||
public Date getCreateTime() { return createTime; }
|
||||
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||
public String getMeetingName() { return meetingName; }
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.ruoyi.business.domain.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Excel 批量导入结果 (前端通用)
|
||||
*
|
||||
* <p>与前端 {@code <ImportResultDialog>} 直接对应:
|
||||
* <ul>
|
||||
* <li>{@code okNum} / {@code ngNum} — 顶部汇总</li>
|
||||
* <li>{@code ngList} — 失败明细, 每条含 rowNum + message</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>当前仅 biz_expert 批量导入使用; 后续如需复用, 移至 ruoyi-common。
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class ImportResult {
|
||||
|
||||
/** 成功条数 */
|
||||
private int okNum;
|
||||
|
||||
/** 失败条数 */
|
||||
private int ngNum;
|
||||
|
||||
/** 失败明细 (Excel 行号 + 失败原因) */
|
||||
private List<NgRow> ngList = new ArrayList<>();
|
||||
|
||||
public void ok() { okNum++; }
|
||||
|
||||
public void fail(int rowNum, String message) {
|
||||
ngNum++;
|
||||
ngList.add(new NgRow(rowNum, message));
|
||||
}
|
||||
|
||||
public boolean hasFailure() { return ngNum > 0; }
|
||||
|
||||
public int getOkNum() { return okNum; }
|
||||
public void setOkNum(int okNum) { this.okNum = okNum; }
|
||||
public int getNgNum() { return ngNum; }
|
||||
public void setNgNum(int ngNum) { this.ngNum = ngNum; }
|
||||
public List<NgRow> getNgList() { return ngList; }
|
||||
public void setNgList(List<NgRow> ngList) { this.ngList = ngList; }
|
||||
|
||||
/** 失败行 (Excel 行号 + 原因) */
|
||||
public static class NgRow {
|
||||
private int rowNum;
|
||||
private String message;
|
||||
public NgRow() {}
|
||||
public NgRow(int rowNum, String message) {
|
||||
this.rowNum = rowNum;
|
||||
this.message = message;
|
||||
}
|
||||
public int getRowNum() { return rowNum; }
|
||||
public void setRowNum(int rowNum) { this.rowNum = rowNum; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
}
|
||||
}
|
||||
+5
-10
@@ -40,13 +40,10 @@ public class BizExpertExportVo {
|
||||
@Excel(name = "银行名称", sort = 9)
|
||||
private String bankName;
|
||||
|
||||
@Excel(name = "开户行省份", sort = 10)
|
||||
private String bankProvince;
|
||||
@Excel(name = "开户行省/市", sort = 10)
|
||||
private String bankRegion;
|
||||
|
||||
@Excel(name = "开户行城市", sort = 11)
|
||||
private String bankCity;
|
||||
|
||||
@Excel(name = "开户行地址", sort = 12)
|
||||
@Excel(name = "开户行地址", sort = 11)
|
||||
private String bankAddress;
|
||||
|
||||
@Excel(name = "状态", sort = 13, readConverterExp = "Y=正常,N=禁用")
|
||||
@@ -84,10 +81,8 @@ public class BizExpertExportVo {
|
||||
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
|
||||
public String getBankName() { return bankName; }
|
||||
public void setBankName(String bankName) { this.bankName = bankName; }
|
||||
public String getBankProvince() { return bankProvince; }
|
||||
public void setBankProvince(String bankProvince) { this.bankProvince = bankProvince; }
|
||||
public String getBankCity() { return bankCity; }
|
||||
public void setBankCity(String bankCity) { this.bankCity = bankCity; }
|
||||
public String getBankRegion() { return bankRegion; }
|
||||
public void setBankRegion(String bankRegion) { this.bankRegion = bankRegion; }
|
||||
public String getBankAddress() { return bankAddress; }
|
||||
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
|
||||
public String getStatus() { return status; }
|
||||
|
||||
+16
-44
@@ -8,6 +8,14 @@ import com.ruoyi.common.annotation.Excel;
|
||||
* <p>仅用于 Excel 批量导入 / 模板下载 / 导出, 不参与业务逻辑.
|
||||
* 字段顺序与 Excel 列一致, 调整时同步修改模板下载体验.
|
||||
*
|
||||
* <p>不包含的字段 (固定后端默认值或走其他导入通道):
|
||||
* <ul>
|
||||
* <li>状态 — 默认 'Y'(正常)</li>
|
||||
* <li>审核状态 — 默认 '2'(通过)</li>
|
||||
* <li>sys_user.status — 默认 '0'(正常)</li>
|
||||
* <li>身份证正面/反面 URL, 执业证书 URL, 职称证明 URL — 走单独的证书导入</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizExpertImportVo {
|
||||
@@ -40,46 +48,22 @@ public class BizExpertImportVo {
|
||||
@Excel(name = "身份证件号码", sort = 7)
|
||||
private String idCard;
|
||||
|
||||
/** 身份证件照片 正面URL */
|
||||
@Excel(name = "身份证正面URL", sort = 8)
|
||||
private String idCardFrontUrl;
|
||||
|
||||
/** 身份证件照片 反面URL */
|
||||
@Excel(name = "身份证反面URL", sort = 9)
|
||||
private String idCardBackUrl;
|
||||
|
||||
/** 执业医师证书 URL */
|
||||
@Excel(name = "执业医师证书URL", sort = 10)
|
||||
private String practiceCertUrl;
|
||||
|
||||
/** 职称证明 URL */
|
||||
@Excel(name = "职称证明URL", sort = 11)
|
||||
private String titleCertUrl;
|
||||
|
||||
/** 银行卡号 */
|
||||
@Excel(name = "银行卡号", sort = 12)
|
||||
@Excel(name = "银行卡号", sort = 8)
|
||||
private String bankCard;
|
||||
|
||||
/** 银行名称 */
|
||||
@Excel(name = "银行名称", sort = 13)
|
||||
@Excel(name = "银行名称", sort = 9)
|
||||
private String bankName;
|
||||
|
||||
/** 开户行省份 */
|
||||
@Excel(name = "开户行省份", sort = 14)
|
||||
private String bankProvince;
|
||||
|
||||
/** 开户行城市 */
|
||||
@Excel(name = "开户行城市", sort = 15)
|
||||
private String bankCity;
|
||||
/** 开户行省/市 (与 DB bank_region 一致, /分隔) */
|
||||
@Excel(name = "开户行省/市", sort = 10)
|
||||
private String bankRegion;
|
||||
|
||||
/** 开户行地址 */
|
||||
@Excel(name = "开户行地址", sort = 16)
|
||||
@Excel(name = "开户行地址", sort = 11)
|
||||
private String bankAddress;
|
||||
|
||||
/** 状态 (Y=正常 N=禁用, 留空默认 Y) */
|
||||
@Excel(name = "状态", sort = 17, readConverterExp = "Y=正常,N=禁用")
|
||||
private String status;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getPhone() { return phone; }
|
||||
@@ -94,24 +78,12 @@ public class BizExpertImportVo {
|
||||
public void setRegion(String region) { this.region = region; }
|
||||
public String getIdCard() { return idCard; }
|
||||
public void setIdCard(String idCard) { this.idCard = idCard; }
|
||||
public String getIdCardFrontUrl() { return idCardFrontUrl; }
|
||||
public void setIdCardFrontUrl(String idCardFrontUrl) { this.idCardFrontUrl = idCardFrontUrl; }
|
||||
public String getIdCardBackUrl() { return idCardBackUrl; }
|
||||
public void setIdCardBackUrl(String idCardBackUrl) { this.idCardBackUrl = idCardBackUrl; }
|
||||
public String getPracticeCertUrl() { return practiceCertUrl; }
|
||||
public void setPracticeCertUrl(String practiceCertUrl) { this.practiceCertUrl = practiceCertUrl; }
|
||||
public String getTitleCertUrl() { return titleCertUrl; }
|
||||
public void setTitleCertUrl(String titleCertUrl) { this.titleCertUrl = titleCertUrl; }
|
||||
public String getBankCard() { return bankCard; }
|
||||
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
|
||||
public String getBankName() { return bankName; }
|
||||
public void setBankName(String bankName) { this.bankName = bankName; }
|
||||
public String getBankProvince() { return bankProvince; }
|
||||
public void setBankProvince(String bankProvince) { this.bankProvince = bankProvince; }
|
||||
public String getBankCity() { return bankCity; }
|
||||
public void setBankCity(String bankCity) { this.bankCity = bankCity; }
|
||||
public String getBankRegion() { return bankRegion; }
|
||||
public void setBankRegion(String bankRegion) { this.bankRegion = bankRegion; }
|
||||
public String getBankAddress() { return bankAddress; }
|
||||
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
}
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.ruoyi.business.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 管理端 - 公示页执行意向导出 VO (中文列头)
|
||||
*
|
||||
* <p>数据源: biz_publicity_execution_intent (匿名+管理后台共用)
|
||||
* 仅用于 Excel 导出, 不参与业务逻辑.
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizPublicityExecutionIntentExportVo {
|
||||
|
||||
@Excel(name = "项目编号", sort = 1)
|
||||
private String projectNo;
|
||||
|
||||
@Excel(name = "项目名称", sort = 2)
|
||||
private String projectName;
|
||||
|
||||
@Excel(name = "姓名", sort = 3)
|
||||
private String name;
|
||||
|
||||
@Excel(name = "工作单位", sort = 4)
|
||||
private String workUnit;
|
||||
|
||||
@Excel(name = "部门", sort = 5)
|
||||
private String department;
|
||||
|
||||
@Excel(name = "职务", sort = 6)
|
||||
private String position;
|
||||
|
||||
@Excel(name = "手机号", sort = 7)
|
||||
private String phone;
|
||||
|
||||
@Excel(name = "账号状态", sort = 8)
|
||||
private String userStatus;
|
||||
|
||||
@Excel(name = "审核状态", sort = 9)
|
||||
private String intentStatus;
|
||||
|
||||
@Excel(name = "创建时间", sort = 10, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
public String getProjectNo() { return projectNo; }
|
||||
public void setProjectNo(String projectNo) { this.projectNo = projectNo; }
|
||||
public String getProjectName() { return projectName; }
|
||||
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getWorkUnit() { return workUnit; }
|
||||
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
|
||||
public String getDepartment() { return department; }
|
||||
public void setDepartment(String department) { this.department = department; }
|
||||
public String getPosition() { return position; }
|
||||
public void setPosition(String position) { this.position = position; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getUserStatus() { return userStatus; }
|
||||
public void setUserStatus(String userStatus) { this.userStatus = userStatus; }
|
||||
public String getIntentStatus() { return intentStatus; }
|
||||
public void setIntentStatus(String intentStatus) { this.intentStatus = intentStatus; }
|
||||
public Date getCreateTime() { return createTime; }
|
||||
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.ruoyi.business.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 管理端 - 公示页支持意向导出 VO (中文列头)
|
||||
*
|
||||
* <p>数据源: biz_publicity_support_intent (匿名+管理后台共用)
|
||||
* 仅用于 Excel 导出, 不参与业务逻辑.
|
||||
*
|
||||
* @author guoju
|
||||
*/
|
||||
public class BizPublicitySupportIntentExportVo {
|
||||
|
||||
@Excel(name = "项目编号", sort = 1)
|
||||
private String projectNo;
|
||||
|
||||
@Excel(name = "项目名称", sort = 2)
|
||||
private String projectName;
|
||||
|
||||
@Excel(name = "姓名", sort = 3)
|
||||
private String name;
|
||||
|
||||
@Excel(name = "工作单位", sort = 4)
|
||||
private String workUnit;
|
||||
|
||||
@Excel(name = "部门", sort = 5)
|
||||
private String department;
|
||||
|
||||
@Excel(name = "职务", sort = 6)
|
||||
private String position;
|
||||
|
||||
@Excel(name = "手机号", sort = 7)
|
||||
private String phone;
|
||||
|
||||
@Excel(name = "账号状态", sort = 8)
|
||||
private String userStatus;
|
||||
|
||||
@Excel(name = "审核状态", sort = 9)
|
||||
private String intentStatus;
|
||||
|
||||
@Excel(name = "创建时间", sort = 10, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
public String getProjectNo() { return projectNo; }
|
||||
public void setProjectNo(String projectNo) { this.projectNo = projectNo; }
|
||||
public String getProjectName() { return projectName; }
|
||||
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getWorkUnit() { return workUnit; }
|
||||
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
|
||||
public String getDepartment() { return department; }
|
||||
public void setDepartment(String department) { this.department = department; }
|
||||
public String getPosition() { return position; }
|
||||
public void setPosition(String position) { this.position = position; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getUserStatus() { return userStatus; }
|
||||
public void setUserStatus(String userStatus) { this.userStatus = userStatus; }
|
||||
public String getIntentStatus() { return intentStatus; }
|
||||
public void setIntentStatus(String intentStatus) { this.intentStatus = intentStatus; }
|
||||
public Date getCreateTime() { return createTime; }
|
||||
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||
}
|
||||
@@ -11,6 +11,12 @@ public interface BizExpertMapper
|
||||
BizExpert selectByUserId(Long userId);
|
||||
List<BizExpert> selectList(BizExpert entity);
|
||||
int insert(BizExpert entity);
|
||||
/**
|
||||
* 批量插入 biz_expert (foreach, 100/批由 service 层 chunk 控制)
|
||||
* @param list BizExpert 列表, caller 须自己 chunk 到 100 以内
|
||||
* @return 影响行数
|
||||
*/
|
||||
int batchInsert(List<BizExpert> list);
|
||||
int insertWithUserId(BizExpert entity);
|
||||
int updateByPrimaryKey(BizExpert entity);
|
||||
int updateByUserId(BizExpert entity);
|
||||
|
||||
+8
@@ -1,15 +1,23 @@
|
||||
package com.ruoyi.business.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.business.domain.BizMeetingAttendee;
|
||||
|
||||
public interface BizMeetingAttendeeMapper {
|
||||
int insert(BizMeetingAttendee entity);
|
||||
/** 批量插入参会人 (BizMeetingController.add 调用) */
|
||||
int insertBatch(@Param("meetingId") Long meetingId, @Param("userIds") Long[] userIds, @Param("createBy") String createBy);
|
||||
/** 医生填写信息保存草稿: 更新签字快照字段 + 劳务信息 (不含 handsign/PDF) */
|
||||
int updateProfile(BizMeetingAttendee entity);
|
||||
int updateHandsign(BizMeetingAttendee entity);
|
||||
/** 提交签字: 一次性存 handsign + labor_protocol + signed_at + signed_ip */
|
||||
int updateSign(BizMeetingAttendee entity);
|
||||
int updateLaborProtocol(BizMeetingAttendee entity);
|
||||
int deleteByMeetingId(Long meetingId);
|
||||
int deleteByMeetingIdAndUserId(BizMeetingAttendee entity);
|
||||
List<BizMeetingAttendee> selectByMeetingId(Long meetingId);
|
||||
List<BizMeetingAttendee> selectByUserId(Long userId);
|
||||
BizMeetingAttendee selectById(Long id);
|
||||
List<BizMeetingAttendee> selectUnsignedByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.business.service;
|
||||
|
||||
import com.ruoyi.business.domain.BizMeetingAttendee;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 医生劳务协议签署 service
|
||||
* 简化版: 不用 token, 走标准 Spring Security 的 SecurityUtils.getUserId() 识别医生
|
||||
* doctor 登录后, 移动端 /doctor/sign-fill?attendeeId=X 打开, 后端从 SecurityUtils 取用户
|
||||
*/
|
||||
public interface BizSignService {
|
||||
/** 医生填写页 GET /info?attendeeId=X: 返回默认值 (biz_expert 预填) + 已存 attendee 字段 + 选项 */
|
||||
Map<String, Object> getSignInfo(Long attendeeId);
|
||||
/** 医生填写页 POST /saveProfile: 批量 UPDATE attendee 字段 (不含签名) */
|
||||
void saveProfile(Long attendeeId, BizMeetingAttendee form);
|
||||
/** 签署页 GET /contract?attendeeId=X: 渲染完整 HTML (占位符替换 + 身份证附件 + 手写签名) */
|
||||
String getContractHtml(Long attendeeId);
|
||||
/** 签署页 POST /submit: 返回 {fullPdfUrl, maskedPdfUrl} */
|
||||
Map<String, String> submitSign(Long attendeeId, String handsignBase64, String contentHtml, String signedIp);
|
||||
}
|
||||
+16
-3
@@ -2,8 +2,10 @@ package com.ruoyi.business.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.business.domain.BizExpert;
|
||||
import com.ruoyi.business.domain.dto.ImportResult;
|
||||
import com.ruoyi.business.domain.vo.BizExpertImportVo;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 专家Service接口
|
||||
@@ -31,8 +33,19 @@ public interface IBizExpertService
|
||||
int deleteByPrimaryKeys(Long[] expertId);
|
||||
|
||||
/**
|
||||
* 批量导入专家: 每行调用 insert, updateSupport=true 时跳过已存在手机号(视为成功)
|
||||
* 返回 String 含成功/失败计数 + 失败明细 (前端 toast 显示)
|
||||
* 批量导入专家 (3 阶段批量版):
|
||||
* <ul>
|
||||
* <li>Phase A: IN 查 sys_user (100/批), 记录已存在 phone → userId</li>
|
||||
* <li>Phase B: 新 phone batch insert sys_user (100/批), 插完再 IN 查回 userId</li>
|
||||
* <li>Phase C: batch insert biz_expert (100/批), 已存在 phone 复用 Phase A userId</li>
|
||||
* </ul>
|
||||
* 单行校验失败不中断, 错误累计到 {@link ImportResult}。
|
||||
* 默认审核通过 (audit_status='2'), 用户名=密码=手机号, role_type='doctor'。
|
||||
*
|
||||
* @param file Excel 文件 (multipart)
|
||||
* @param updateSupport 已存在 phone 是否覆盖 update (当前实现: 始终绑定已有 user_id, 仅插 biz_expert)
|
||||
* @param operName 操作人 (写入 create_by / audit_by)
|
||||
* @return 导入结果
|
||||
*/
|
||||
String importExpert(List<BizExpertImportVo> importList, boolean updateSupport, String operName);
|
||||
ImportResult importExpert(MultipartFile file, boolean updateSupport, String operName) throws Exception;
|
||||
}
|
||||
|
||||
+7
-1
@@ -5,12 +5,18 @@ import com.ruoyi.business.domain.BizMeetingAttendee;
|
||||
|
||||
public interface IBizMeetingAttendeeService {
|
||||
int insert(BizMeetingAttendee entity);
|
||||
/** 批量插入参会人 (BizMeetingController.add 调用) */
|
||||
int insertBatch(Long meetingId, Long[] userIds);
|
||||
/** 医生填写信息保存草稿: 更新签字快照字段 + 劳务信息 (不含 handsign/PDF) */
|
||||
int updateProfile(BizMeetingAttendee entity);
|
||||
int updateHandsign(BizMeetingAttendee entity);
|
||||
/** 提交签字: 一次性存 handsign + labor_protocol + signed_at + signed_ip */
|
||||
int updateSign(BizMeetingAttendee entity);
|
||||
int updateLaborProtocol(BizMeetingAttendee entity);
|
||||
int deleteByMeetingId(Long meetingId);
|
||||
int deleteByMeetingIdAndUserId(BizMeetingAttendee entity);
|
||||
List<BizMeetingAttendee> selectByMeetingId(Long meetingId);
|
||||
List<BizMeetingAttendee> selectByUserId(Long userId);
|
||||
/** 当前用户的"待签署"会议列表 (handsign 或 labor_protocol 任一为空) */
|
||||
BizMeetingAttendee selectById(Long id);
|
||||
List<BizMeetingAttendee> selectUnsignedByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.ruoyi.business.service;
|
||||
|
||||
import com.itextpdf.html2pdf.ConverterProperties;
|
||||
import com.itextpdf.html2pdf.HtmlConverter;
|
||||
import com.itextpdf.io.font.PdfEncodings;
|
||||
import com.itextpdf.kernel.font.PdfFont;
|
||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
||||
import com.itextpdf.kernel.geom.PageSize;
|
||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||
import com.itextpdf.layout.font.FontProvider;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* HTML 转 PDF 服务
|
||||
* 用 iText 7 html2pdf (HtmlConverter) + 内置宋体 (simsun.ttc) 渲染中文
|
||||
* 输入: HTML 字符串 → 输出: PDF 文件 (存到 ruoyi.profile 目录, 返回 URL)
|
||||
* 说明: 相比 Flying Saucer (xhtmlrenderer 严格 XML 解析), html2pdf 走 jsoup HTML 解析,
|
||||
* 能容忍前端拼出的非 XHTML 内容 (如 <img> 未自闭合), 不会报 SAXParseException。
|
||||
*/
|
||||
@Service
|
||||
public class PdfService {
|
||||
|
||||
@Autowired
|
||||
private RuoYiConfig ruoyiConfig;
|
||||
|
||||
/**
|
||||
* HTML 字符串 → PDF 字节流
|
||||
*/
|
||||
public byte[] htmlToPdf(String htmlContent) {
|
||||
if (htmlContent == null || htmlContent.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("HTML 内容为空");
|
||||
}
|
||||
String fullHtml = wrapHtml(htmlContent);
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
PdfWriter writer = new PdfWriter(baos);
|
||||
PdfDocument pdfDocument = new PdfDocument(writer);
|
||||
pdfDocument.setDefaultPageSize(PageSize.A4);
|
||||
|
||||
ConverterProperties properties = new ConverterProperties();
|
||||
FontProvider fontProvider = new FontProvider();
|
||||
// 中文字体 (对齐 hwt-serve): 基础目录下 simsun.ttc, ",0" 取第 0 个 face
|
||||
PdfFont cjkFont = PdfFontFactory.createFont("simsun.ttc,0", PdfEncodings.IDENTITY_H, false);
|
||||
fontProvider.addFont(cjkFont.getFontProgram(), PdfEncodings.IDENTITY_H);
|
||||
properties.setFontProvider(fontProvider);
|
||||
|
||||
HtmlConverter.convertToPdf(fullHtml, pdfDocument, properties);
|
||||
pdfDocument.close();
|
||||
return baos.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("HTML 转 PDF 失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML → PDF 文件 (存到本地)
|
||||
* @return 完整 URL (前端可直接打开)
|
||||
*/
|
||||
public String htmlToPdfFile(String htmlContent, String bizPath) {
|
||||
byte[] pdfBytes = htmlToPdf(htmlContent);
|
||||
// 按 RuoYi 风格分目录: profile/labor/{date}/{filename}
|
||||
SimpleDateFormat dateDir = new SimpleDateFormat("yyyy-MM-dd");
|
||||
String today = dateDir.format(new Date());
|
||||
String datePath = (bizPath == null || bizPath.isEmpty() ? "labor" : bizPath) + "/" + today;
|
||||
String filename = System.currentTimeMillis() + "_" + (int)(Math.random() * 1000) + ".pdf";
|
||||
|
||||
String profilePath = ruoyiConfig.getProfile();
|
||||
File dir = new File(profilePath + File.separator + datePath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
File pdfFile = new File(dir, filename);
|
||||
try (FileOutputStream fos = new FileOutputStream(pdfFile)) {
|
||||
fos.write(pdfBytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("保存 PDF 失败: " + e.getMessage(), e);
|
||||
}
|
||||
// 返回 URL 路径 (前端拼 origin)
|
||||
String url = "/profile/" + datePath + "/" + filename;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包裹完整 HTML 结构 (HTML5, 供 html2pdf 解析)
|
||||
*/
|
||||
private String wrapHtml(String content) {
|
||||
String lower = content.trim().toLowerCase();
|
||||
if (lower.startsWith("<html") || lower.startsWith("<!doctype")) {
|
||||
return content;
|
||||
}
|
||||
return "<!DOCTYPE html><html><head><meta charset=\"UTF-8\" /></head><body>"
|
||||
+ content + "</body></html>";
|
||||
}
|
||||
}
|
||||
+228
-85
@@ -1,9 +1,19 @@
|
||||
package com.ruoyi.business.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.business.domain.BizExpert;
|
||||
import com.ruoyi.business.domain.dto.ImportResult;
|
||||
import com.ruoyi.business.domain.vo.BizExpertImportVo;
|
||||
import com.ruoyi.business.mapper.BizExpertMapper;
|
||||
import com.ruoyi.business.service.IBizExpertService;
|
||||
@@ -11,17 +21,27 @@ import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.id.IdGenerator;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.system.mapper.SysUserMapper;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
|
||||
@Service
|
||||
public class BizExpertServiceImpl implements IBizExpertService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(BizExpertServiceImpl.class);
|
||||
|
||||
/** 批量导入 chunk 大小, IN 查和 INSERT 都是这个粒度 */
|
||||
private static final int BATCH_SIZE = 100;
|
||||
|
||||
@Autowired
|
||||
private BizExpertMapper bizExpertMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@Autowired
|
||||
private SysUserMapper sysUserMapper;
|
||||
|
||||
@Override
|
||||
public BizExpert getById(Long expertId)
|
||||
{ return bizExpertMapper.selectByPrimaryKey(expertId); }
|
||||
@@ -139,105 +159,228 @@ public class BizExpertServiceImpl implements IBizExpertService
|
||||
{ return bizExpertMapper.deleteByPrimaryKeys(expertId); }
|
||||
|
||||
/**
|
||||
* 批量导入专家:
|
||||
* 1. 每行 必填校验 (姓名/手机号/工作单位/科室/职称, 手机号正则 1[0-9]{10})
|
||||
* 2. status 留空 → Y (正常); 非法值 → 抛错
|
||||
* 3. sys_user 同步创建 (与单条 insert 共用 sys_userService.insertUser)
|
||||
* 4. updateSupport=true 时: 同一手机号已存在 → 跳过 (按成功计)
|
||||
* 5. 单行失败不中断, 错误信息累计
|
||||
* 批量导入专家 (3 阶段批量版):
|
||||
* <ol>
|
||||
* <li>Phase 0: ExcelUtil 反序列化 + 行级必填校验, 失败行累计到 result.ngList</li>
|
||||
* <li>Phase A: 100/批 IN 查 sys_user, 记录已有 phone → userId 映射</li>
|
||||
* <li>Phase B: 100/批 batch insert sys_user (新 phone), 插完再 IN 查回 userId; 同步 updateRoleType='doctor'</li>
|
||||
* <li>Phase C: 100/批 batch insert biz_expert (所有有效行), 已存在 phone 复用 Phase A userId</li>
|
||||
* </ol>
|
||||
* 失败行为:
|
||||
* <ul>
|
||||
* <li>Phase B 整批失败 → 退化为单条逐行 insert, 失败的行记 ngList</li>
|
||||
* <li>Phase C 整批失败 → 同上</li>
|
||||
* <li>跨批失败不回滚已成功的批 (避免一条脏数据拖垮整批)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public String importExpert(List<BizExpertImportVo> importList, boolean updateSupport, String operName) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ImportResult importExpert(MultipartFile file, boolean updateSupport, String operName) throws Exception {
|
||||
// ==================== Phase 0: 解析 + 行级校验 ====================
|
||||
ExcelUtil<BizExpertImportVo> util = new ExcelUtil<>(BizExpertImportVo.class);
|
||||
List<BizExpertImportVo> importList = util.importExcel(file.getInputStream());
|
||||
if (importList == null || importList.isEmpty()) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
}
|
||||
int successNum = 0;
|
||||
int failureNum = 0;
|
||||
StringBuilder successMsg = new StringBuilder();
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
ImportResult result = new ImportResult();
|
||||
|
||||
// validRows: 校验通过的 {vo, rowNo}; status 固定 'Y'(正常), auditStatus 固定 '2'(通过)
|
||||
List<ValidatedRow> validRows = new ArrayList<>(importList.size());
|
||||
for (int i = 0; i < importList.size(); i++) {
|
||||
BizExpertImportVo vo = importList.get(i);
|
||||
int rowNo = i + 2; // Excel 行号 (1=表头)
|
||||
try {
|
||||
// 必填校验
|
||||
if (vo.getName() == null || vo.getName().trim().isEmpty()) {
|
||||
throw new ServiceException("姓名不能为空");
|
||||
}
|
||||
if (vo.getPhone() == null || vo.getPhone().trim().isEmpty()) {
|
||||
throw new ServiceException("手机号不能为空");
|
||||
}
|
||||
if (!vo.getPhone().matches("^1[0-9]\\d{9}$")) {
|
||||
throw new ServiceException("手机号格式不正确");
|
||||
}
|
||||
if (vo.getWorkUnit() == null || vo.getWorkUnit().trim().isEmpty()) {
|
||||
throw new ServiceException("工作单位不能为空");
|
||||
}
|
||||
if (vo.getDepartment() == null || vo.getDepartment().trim().isEmpty()) {
|
||||
throw new ServiceException("科室不能为空");
|
||||
}
|
||||
if (vo.getTitle() == null || vo.getTitle().trim().isEmpty()) {
|
||||
throw new ServiceException("职称不能为空");
|
||||
}
|
||||
// status 校验, 留空 → Y
|
||||
String status = vo.getStatus();
|
||||
if (status == null || status.trim().isEmpty()) {
|
||||
status = "Y";
|
||||
} else if (!"Y".equals(status) && !"N".equals(status)) {
|
||||
throw new ServiceException("状态只能是 Y(正常) 或 N(禁用)");
|
||||
}
|
||||
|
||||
// 同一手机号已存在 → updateSupport=true 跳过, 否则报错
|
||||
if (sysUserService.isPhoneRegistered(vo.getPhone())) {
|
||||
if (updateSupport) {
|
||||
successNum++;
|
||||
successMsg.append("<br/>" + successNum + "、手机号 " + vo.getPhone() + " 已存在, 已跳过");
|
||||
continue;
|
||||
}
|
||||
throw new ServiceException("手机号 " + vo.getPhone() + " 已存在");
|
||||
}
|
||||
|
||||
// 复用单条 insert 逻辑 (含 sys_user 创建)
|
||||
BizExpert entity = new BizExpert();
|
||||
entity.setName(vo.getName());
|
||||
entity.setPhone(vo.getPhone());
|
||||
entity.setWorkUnit(vo.getWorkUnit());
|
||||
entity.setDepartment(vo.getDepartment());
|
||||
entity.setTitle(vo.getTitle());
|
||||
entity.setRegion(vo.getRegion());
|
||||
entity.setIdCard(vo.getIdCard());
|
||||
entity.setIdCardFrontUrl(vo.getIdCardFrontUrl());
|
||||
entity.setIdCardBackUrl(vo.getIdCardBackUrl());
|
||||
entity.setPracticeCertUrl(vo.getPracticeCertUrl());
|
||||
entity.setTitleCertUrl(vo.getTitleCertUrl());
|
||||
entity.setBankCard(vo.getBankCard());
|
||||
entity.setBankName(vo.getBankName());
|
||||
entity.setBankProvince(vo.getBankProvince());
|
||||
entity.setBankCity(vo.getBankCity());
|
||||
entity.setBankAddress(vo.getBankAddress());
|
||||
entity.setStatus(status);
|
||||
// admin 批量导入默认通过审核 (与单条 admin 创建一致)
|
||||
entity.setAuditStatus("2");
|
||||
entity.setAuditBy(operName);
|
||||
entity.setAuditTime(new java.util.Date());
|
||||
entity.setCreateBy(operName);
|
||||
|
||||
insert(entity);
|
||||
successNum++;
|
||||
successMsg.append("<br/>" + successNum + "、账号 " + vo.getPhone() + " 导入成功");
|
||||
ValidatedRow vr = validateRow(vo, rowNo);
|
||||
validRows.add(vr);
|
||||
} catch (Exception e) {
|
||||
failureNum++;
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) msg = e.getClass().getSimpleName();
|
||||
failureMsg.append("<br/>" + failureNum + "、第 " + rowNo + " 行: " + msg);
|
||||
result.fail(rowNo, e.getMessage() == null ? "校验失败" : e.getMessage());
|
||||
}
|
||||
}
|
||||
if (failureNum > 0) {
|
||||
failureMsg.insert(0, "很抱歉, 部分导入失败, 共 " + failureNum + " 条:");
|
||||
if (validRows.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
if (successNum > 0) {
|
||||
successMsg.insert(0, "恭喜您, 数据已全部导入成功!共 " + successNum + " 条, 数据如下:");
|
||||
|
||||
// ==================== Phase A: IN 查 sys_user (100/批) ====================
|
||||
// phone -> userId, 含已存在 user (del_flag='0')
|
||||
Map<String, Long> phoneToUserId = new HashMap<>();
|
||||
List<String> allPhones = validRows.stream()
|
||||
.map(vr -> vr.vo.getPhone())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
for (List<String> chunk : chunk(allPhones, BATCH_SIZE)) {
|
||||
List<SysUser> existing = sysUserMapper.selectByPhoneList(chunk);
|
||||
for (SysUser u : existing) {
|
||||
phoneToUserId.put(u.getPhonenumber(), u.getUserId());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Phase B: batch insert sys_user (新 phone) ====================
|
||||
List<ValidatedRow> needNewUser = validRows.stream()
|
||||
.filter(vr -> !phoneToUserId.containsKey(vr.vo.getPhone()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (List<ValidatedRow> chunk : chunk(needNewUser, BATCH_SIZE)) {
|
||||
List<SysUser> newUsers = new ArrayList<>(chunk.size());
|
||||
List<String> chunkPhones = new ArrayList<>(chunk.size());
|
||||
for (ValidatedRow vr : chunk) {
|
||||
SysUser u = new SysUser();
|
||||
u.setUserName(vr.vo.getPhone());
|
||||
u.setNickName(vr.vo.getName());
|
||||
u.setPhonenumber(vr.vo.getPhone());
|
||||
u.setPassword(SecurityUtils.encryptPassword(vr.vo.getPhone()));
|
||||
u.setStatus("0");
|
||||
u.setDelFlag("0");
|
||||
u.setRoleType("doctor");
|
||||
u.setCreateBy(operName);
|
||||
newUsers.add(u);
|
||||
chunkPhones.add(vr.vo.getPhone());
|
||||
}
|
||||
try {
|
||||
sysUserMapper.batchInsertUsers(newUsers);
|
||||
// 整批 insert 后再 IN 查回 userId (foreach+useGeneratedKeys 不可靠)
|
||||
List<SysUser> created = sysUserMapper.selectByPhoneList(chunkPhones);
|
||||
for (SysUser u : created) {
|
||||
phoneToUserId.put(u.getPhonenumber(), u.getUserId());
|
||||
}
|
||||
// 兜底: 若某条 phone 仍未查到 userId, 退化为单条逐行, 计入 ngList
|
||||
for (ValidatedRow vr : chunk) {
|
||||
if (!phoneToUserId.containsKey(vr.vo.getPhone())) {
|
||||
try {
|
||||
insert(toEntity(vr.vo, operName, null));
|
||||
// 单条 insert 已经写了 sys_user, 再查一次
|
||||
SysUser u2 = sysUserMapper.selectUserByUserName(vr.vo.getPhone());
|
||||
if (u2 != null) phoneToUserId.put(vr.vo.getPhone(), u2.getUserId());
|
||||
else throw new ServiceException("建账号失败, 请重试");
|
||||
} catch (Exception ex) {
|
||||
result.fail(vr.rowNo, "建账号失败: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// 整批失败 → 单条逐行退化
|
||||
log.warn("batchInsertUsers 整批失败, 退化单条, 原因: {}", ex.getMessage());
|
||||
for (ValidatedRow vr : chunk) {
|
||||
try {
|
||||
insert(toEntity(vr.vo, operName, null));
|
||||
SysUser u2 = sysUserMapper.selectUserByUserName(vr.vo.getPhone());
|
||||
if (u2 != null) phoneToUserId.put(vr.vo.getPhone(), u2.getUserId());
|
||||
else throw new ServiceException("建账号失败, 请重试");
|
||||
} catch (Exception ex2) {
|
||||
result.fail(vr.rowNo, "建账号失败: " + ex2.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Phase C: batch insert biz_expert (100/批) ====================
|
||||
// 过滤出已有 userId 的有效行
|
||||
List<ValidatedRow> readyForExpert = new ArrayList<>(validRows.size());
|
||||
for (ValidatedRow vr : validRows) {
|
||||
if (phoneToUserId.containsKey(vr.vo.getPhone())) {
|
||||
readyForExpert.add(vr);
|
||||
}
|
||||
// 否则该行已在 Phase B 计入 ngList, 不再尝试插 biz_expert
|
||||
}
|
||||
|
||||
for (List<ValidatedRow> chunk : chunk(readyForExpert, BATCH_SIZE)) {
|
||||
List<BizExpert> experts = new ArrayList<>(chunk.size());
|
||||
for (ValidatedRow vr : chunk) {
|
||||
experts.add(toEntity(vr.vo, operName, phoneToUserId.get(vr.vo.getPhone())));
|
||||
}
|
||||
try {
|
||||
bizExpertMapper.batchInsert(experts);
|
||||
// 整批成功 → 累加 okNum
|
||||
for (int k = 0; k < chunk.size(); k++) {
|
||||
result.ok();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// 整批失败 → 单条逐行退化, 失败行记 ngList
|
||||
log.warn("batchInsert experts 整批失败, 退化单条, 原因: {}", ex.getMessage());
|
||||
for (ValidatedRow vr : chunk) {
|
||||
try {
|
||||
bizExpertMapper.insert(toEntity(vr.vo, operName, phoneToUserId.get(vr.vo.getPhone())));
|
||||
result.ok();
|
||||
} catch (Exception ex2) {
|
||||
result.fail(vr.rowNo, "插 biz_expert 失败: " + ex2.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 行级校验, 失败抛 ServiceException
|
||||
*/
|
||||
private ValidatedRow validateRow(BizExpertImportVo vo, int rowNo) {
|
||||
if (vo.getName() == null || vo.getName().trim().isEmpty()) {
|
||||
throw new ServiceException("姓名不能为空");
|
||||
}
|
||||
if (vo.getPhone() == null || vo.getPhone().trim().isEmpty()) {
|
||||
throw new ServiceException("手机号不能为空");
|
||||
}
|
||||
if (!vo.getPhone().matches("^1[0-9]\\d{9}$")) {
|
||||
throw new ServiceException("手机号格式不正确");
|
||||
}
|
||||
if (vo.getWorkUnit() == null || vo.getWorkUnit().trim().isEmpty()) {
|
||||
throw new ServiceException("工作单位不能为空");
|
||||
}
|
||||
if (vo.getDepartment() == null || vo.getDepartment().trim().isEmpty()) {
|
||||
throw new ServiceException("科室不能为空");
|
||||
}
|
||||
if (vo.getTitle() == null || vo.getTitle().trim().isEmpty()) {
|
||||
throw new ServiceException("职称不能为空");
|
||||
}
|
||||
return new ValidatedRow(vo, rowNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vo → BizExpert (含 userId / 雪花 expertId / 默认 status='Y', auditStatus='2')
|
||||
* 不设 URL 字段 (idCardFrontUrl / idCardBackUrl / practiceCertUrl / titleCertUrl),
|
||||
* 由单独的证书导入通道写入.
|
||||
*/
|
||||
private BizExpert toEntity(BizExpertImportVo vo, String operName, Long userId) {
|
||||
BizExpert entity = new BizExpert();
|
||||
entity.setExpertId(IdGenerator.generateId());
|
||||
if (userId != null) entity.setUserId(userId);
|
||||
entity.setName(vo.getName());
|
||||
entity.setPhone(vo.getPhone());
|
||||
entity.setWorkUnit(vo.getWorkUnit());
|
||||
entity.setDepartment(vo.getDepartment());
|
||||
entity.setTitle(vo.getTitle());
|
||||
entity.setRegion(vo.getRegion());
|
||||
entity.setIdCard(vo.getIdCard());
|
||||
entity.setBankCard(vo.getBankCard());
|
||||
entity.setBankName(vo.getBankName());
|
||||
entity.setBankRegion(vo.getBankRegion());
|
||||
entity.setBankAddress(vo.getBankAddress());
|
||||
entity.setStatus("Y");
|
||||
entity.setAuditStatus("2");
|
||||
entity.setAuditBy(operName);
|
||||
entity.setAuditTime(new Date());
|
||||
entity.setCreateBy(operName);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用 chunk 工具: list → List<List<T>>, 单批 ≤size
|
||||
*/
|
||||
private static <T> List<List<T>> chunk(List<T> list, int size) {
|
||||
List<List<T>> out = new ArrayList<>();
|
||||
for (int i = 0; i < list.size(); i += size) {
|
||||
out.add(list.subList(i, Math.min(i + size, list.size())));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 校验通过的中间结构 */
|
||||
private static class ValidatedRow {
|
||||
final BizExpertImportVo vo;
|
||||
final int rowNo;
|
||||
ValidatedRow(BizExpertImportVo vo, int rowNo) {
|
||||
this.vo = vo;
|
||||
this.rowNo = rowNo;
|
||||
}
|
||||
return successMsg.toString() + failureMsg.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+31
-9
@@ -6,50 +6,72 @@ import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.business.domain.BizMeetingAttendee;
|
||||
import com.ruoyi.business.mapper.BizMeetingAttendeeMapper;
|
||||
import com.ruoyi.business.service.IBizMeetingAttendeeService;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
|
||||
@Service
|
||||
public class BizMeetingAttendeeServiceImpl implements IBizMeetingAttendeeService {
|
||||
|
||||
@Autowired
|
||||
private BizMeetingAttendeeMapper bizMeetingAttendeeMapper;
|
||||
private BizMeetingAttendeeMapper mapper;
|
||||
|
||||
@Override
|
||||
public int insert(BizMeetingAttendee entity) {
|
||||
return bizMeetingAttendeeMapper.insert(entity);
|
||||
return mapper.insert(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertBatch(Long meetingId, Long[] userIds) {
|
||||
if (userIds == null || userIds.length == 0) return 0;
|
||||
return mapper.insertBatch(meetingId, userIds, SecurityUtils.getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateProfile(BizMeetingAttendee entity) {
|
||||
return mapper.updateProfile(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateHandsign(BizMeetingAttendee entity) {
|
||||
return bizMeetingAttendeeMapper.updateHandsign(entity);
|
||||
return mapper.updateHandsign(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateSign(BizMeetingAttendee entity) {
|
||||
return mapper.updateSign(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateLaborProtocol(BizMeetingAttendee entity) {
|
||||
return bizMeetingAttendeeMapper.updateLaborProtocol(entity);
|
||||
return mapper.updateLaborProtocol(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteByMeetingId(Long meetingId) {
|
||||
return bizMeetingAttendeeMapper.deleteByMeetingId(meetingId);
|
||||
return mapper.deleteByMeetingId(meetingId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteByMeetingIdAndUserId(BizMeetingAttendee entity) {
|
||||
return bizMeetingAttendeeMapper.deleteByMeetingIdAndUserId(entity);
|
||||
return mapper.deleteByMeetingIdAndUserId(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BizMeetingAttendee> selectByMeetingId(Long meetingId) {
|
||||
return bizMeetingAttendeeMapper.selectByMeetingId(meetingId);
|
||||
return mapper.selectByMeetingId(meetingId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BizMeetingAttendee> selectByUserId(Long userId) {
|
||||
return bizMeetingAttendeeMapper.selectByUserId(userId);
|
||||
return mapper.selectByUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BizMeetingAttendee selectById(Long id) {
|
||||
return mapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BizMeetingAttendee> selectUnsignedByUserId(Long userId) {
|
||||
return bizMeetingAttendeeMapper.selectUnsignedByUserId(userId);
|
||||
return mapper.selectUnsignedByUserId(userId);
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
package com.ruoyi.business.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.business.domain.BizExpert;
|
||||
import com.ruoyi.business.domain.BizLaborProtocolTemplate;
|
||||
import com.ruoyi.business.domain.BizMeeting;
|
||||
import com.ruoyi.business.domain.BizMeetingAttendee;
|
||||
import com.ruoyi.business.mapper.BizExpertMapper;
|
||||
import com.ruoyi.business.mapper.BizLaborProtocolTemplateMapper;
|
||||
import com.ruoyi.business.mapper.BizMeetingMapper;
|
||||
import com.ruoyi.business.service.BizSignService;
|
||||
import com.ruoyi.business.service.IBizMeetingAttendeeService;
|
||||
import com.ruoyi.business.service.PdfService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
|
||||
@Service
|
||||
public class BizSignServiceImpl implements BizSignService {
|
||||
|
||||
@Autowired
|
||||
private IBizMeetingAttendeeService attendeeService;
|
||||
@Autowired
|
||||
private BizExpertMapper expertMapper;
|
||||
@Autowired
|
||||
private BizMeetingMapper meetingMapper;
|
||||
@Autowired
|
||||
private BizLaborProtocolTemplateMapper templateMapper;
|
||||
@Autowired
|
||||
private PdfService pdfService;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSignInfo(Long attendeeId) {
|
||||
BizMeetingAttendee attendee = attendeeService.selectById(attendeeId);
|
||||
if (attendee == null) {
|
||||
throw new ServiceException("参会人记录不存在");
|
||||
}
|
||||
// 安全: 当前登录用户必须是该 attendee 的 user_id
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
if (currentUserId == null || !currentUserId.equals(attendee.getUserId())) {
|
||||
throw new ServiceException("无权访问该协议");
|
||||
}
|
||||
// 从 biz_expert 取默认值 (按 user_id 查)
|
||||
BizExpert expert = expertMapper.selectByUserId(attendee.getUserId());
|
||||
Map<String, Object> defaults = new HashMap<>();
|
||||
if (expert != null) {
|
||||
defaults.put("name", expert.getName() != null ? expert.getName() : "");
|
||||
defaults.put("phone", expert.getPhone() != null ? expert.getPhone() : "");
|
||||
defaults.put("workUnit", expert.getWorkUnit() != null ? expert.getWorkUnit() : "");
|
||||
defaults.put("department", expert.getDepartment() != null ? expert.getDepartment() : "");
|
||||
defaults.put("title", expert.getTitle() != null ? expert.getTitle() : "");
|
||||
defaults.put("idCard", expert.getIdCard() != null ? expert.getIdCard() : "");
|
||||
defaults.put("bankCard", expert.getBankCard() != null ? expert.getBankCard() : "");
|
||||
defaults.put("bankName", expert.getBankName() != null ? expert.getBankName() : "");
|
||||
defaults.put("bankRegion", expert.getBankRegion() != null ? expert.getBankRegion() : "");
|
||||
defaults.put("bankAddress", expert.getBankAddress() != null ? expert.getBankAddress() : "");
|
||||
}
|
||||
Map<String, Object> current = new HashMap<>();
|
||||
current.put("name", attendee.getName() != null ? attendee.getName() : (String) defaults.getOrDefault("name", ""));
|
||||
current.put("phone", attendee.getPhone() != null ? attendee.getPhone() : (String) defaults.getOrDefault("phone", ""));
|
||||
current.put("workUnit", attendee.getWorkUnit() != null ? attendee.getWorkUnit() : (String) defaults.getOrDefault("workUnit", ""));
|
||||
current.put("department", attendee.getDepartment() != null ? attendee.getDepartment() : (String) defaults.getOrDefault("department", ""));
|
||||
current.put("title", attendee.getTitle() != null ? attendee.getTitle() : (String) defaults.getOrDefault("title", ""));
|
||||
current.put("idCard", attendee.getIdCard() != null ? attendee.getIdCard() : (String) defaults.getOrDefault("idCard", ""));
|
||||
current.put("bankCard", attendee.getBankCard() != null ? attendee.getBankCard() : (String) defaults.getOrDefault("bankCard", ""));
|
||||
current.put("bankName", attendee.getBankName() != null ? attendee.getBankName() : (String) defaults.getOrDefault("bankName", ""));
|
||||
current.put("bankBranch", attendee.getBankBranch() != null ? attendee.getBankBranch() : "");
|
||||
current.put("bankRegion", attendee.getBankRegion() != null ? attendee.getBankRegion() : (String) defaults.getOrDefault("bankRegion", ""));
|
||||
current.put("bankAddress", attendee.getBankAddress() != null ? attendee.getBankAddress() : (String) defaults.getOrDefault("bankAddress", ""));
|
||||
current.put("accountName", attendee.getAccountName() != null ? attendee.getAccountName() : "");
|
||||
current.put("idCardAttachments", attendee.getIdCardAttachments() != null ? attendee.getIdCardAttachments() : "");
|
||||
current.put("laborForm", attendee.getLaborForm() != null ? attendee.getLaborForm() : "");
|
||||
current.put("feePreTax", attendee.getFeePreTax());
|
||||
current.put("tax", attendee.getTax());
|
||||
current.put("fee", attendee.getFee());
|
||||
|
||||
List<Map<String, String>> laborFormOptions = Arrays.asList(
|
||||
map("讲课", "讲课"), map("讨论", "讨论"),
|
||||
map("主持", "主持"), map("主席", "主席"),
|
||||
map("__other__", "其他"));
|
||||
List<String> titleOptions = Arrays.asList(
|
||||
"主任医师", "副主任医师", "主治(主管)医师", "医士",
|
||||
"主任药师", "药师", "药士",
|
||||
"主任护师", "副主任护师", "主管护师", "护师", "护士",
|
||||
"主任技师", "副主任技师", "主管技师", "技士",
|
||||
"研究员", "副研究员", "助理研究员", "研究实习员");
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("defaults", defaults);
|
||||
result.put("current", current);
|
||||
result.put("laborFormOptions", laborFormOptions);
|
||||
result.put("titleOptions", titleOptions);
|
||||
result.put("attendeeId", attendeeId);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, String> map(String value, String label) {
|
||||
Map<String, String> m = new HashMap<>();
|
||||
m.put("value", value);
|
||||
m.put("label", label);
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveProfile(Long attendeeId, BizMeetingAttendee form) {
|
||||
BizMeetingAttendee attendee = attendeeService.selectById(attendeeId);
|
||||
if (attendee == null) {
|
||||
throw new ServiceException("参会人记录不存在");
|
||||
}
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
if (currentUserId == null || !currentUserId.equals(attendee.getUserId())) {
|
||||
throw new ServiceException("无权访问该协议");
|
||||
}
|
||||
BizMeetingAttendee entity = new BizMeetingAttendee();
|
||||
entity.setId(attendeeId);
|
||||
entity.setName(form.getName());
|
||||
entity.setPhone(form.getPhone());
|
||||
entity.setWorkUnit(form.getWorkUnit());
|
||||
entity.setDepartment(form.getDepartment());
|
||||
entity.setTitle(form.getTitle());
|
||||
entity.setIdCard(form.getIdCard());
|
||||
entity.setBankCard(form.getBankCard());
|
||||
entity.setBankName(form.getBankName());
|
||||
entity.setBankBranch(form.getBankBranch());
|
||||
entity.setBankRegion(form.getBankRegion());
|
||||
entity.setBankAddress(form.getBankAddress());
|
||||
entity.setAccountName(form.getAccountName());
|
||||
entity.setIdCardAttachments(form.getIdCardAttachments());
|
||||
entity.setLaborForm(form.getLaborForm());
|
||||
entity.setUpdateBy(SecurityUtils.getUsername());
|
||||
attendeeService.updateProfile(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContractHtml(Long attendeeId) {
|
||||
BizMeetingAttendee attendee = attendeeService.selectById(attendeeId);
|
||||
if (attendee == null) {
|
||||
throw new ServiceException("参会人记录不存在");
|
||||
}
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
if (currentUserId == null || !currentUserId.equals(attendee.getUserId())) {
|
||||
throw new ServiceException("无权访问该协议");
|
||||
}
|
||||
BizLaborProtocolTemplate template = templateMapper.selectDefault();
|
||||
if (template == null) {
|
||||
throw new ServiceException("系统未配置默认劳务协议模板");
|
||||
}
|
||||
String html = template.getTemplateContent();
|
||||
html = replacePlaceholders(html, attendee);
|
||||
BizMeeting meeting = meetingMapper.selectByPrimaryKey(attendee.getMeetingId());
|
||||
if (meeting != null) {
|
||||
html = html.replaceAll("\\{会议名称\\}", nullToEmpty(meeting.getMeetingName()));
|
||||
html = html.replaceAll("\\{会议地址\\}", nullToEmpty(meeting.getAddress()));
|
||||
html = html.replaceAll("\\{会议时间\\}", formatDateTime(meeting.getStartTime()));
|
||||
html = html.replaceAll("\\{日期\\}", formatDate(meeting.getStartTime()));
|
||||
}
|
||||
if (attendee.getIdCardAttachments() != null && !attendee.getIdCardAttachments().isEmpty()) {
|
||||
String[] urls = attendee.getIdCardAttachments().split(",");
|
||||
StringBuilder imgs = new StringBuilder();
|
||||
for (String u : urls) {
|
||||
if (u.trim().isEmpty()) continue;
|
||||
imgs.append("<div style=\"margin:8px auto;text-align:center;\"><img src='").append(u.trim())
|
||||
.append("' style=\"max-width:240px;\"/></div>");
|
||||
}
|
||||
html = html.replaceAll("\\{身份证附件\\}", imgs.toString());
|
||||
}
|
||||
html = html.replaceAll("\\{费用总额\\}", attendee.getFee() != null ? attendee.getFee().toPlainString() : "");
|
||||
if (attendee.getHandsign() != null && !attendee.getHandsign().isEmpty()) {
|
||||
html = html.replaceAll("\\{手写签名\\}",
|
||||
"<img style='width: 60px; height: 30px' src='" + attendee.getHandsign() + "' />");
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
private String replacePlaceholders(String html, BizMeetingAttendee a) {
|
||||
html = html.replaceAll("\\{name\\}", nullToEmpty(a.getName()));
|
||||
html = html.replaceAll("\\{phone\\}", nullToEmpty(a.getPhone()));
|
||||
html = html.replaceAll("\\{dept\\}", nullToEmpty(a.getWorkUnit()));
|
||||
html = html.replaceAll("\\{工作单位\\}", nullToEmpty(a.getWorkUnit()));
|
||||
html = html.replaceAll("\\{科室\\}", nullToEmpty(a.getDepartment()));
|
||||
html = html.replaceAll("\\{劳务形式\\}", nullToEmpty(a.getLaborForm()));
|
||||
html = html.replaceAll("\\{账户名称(持卡人姓名)\\}", nullToEmpty(a.getAccountName()));
|
||||
html = html.replaceAll("\\{身份证号(外宾填写护照号)\\}", nullToEmpty(a.getIdCard()));
|
||||
html = html.replaceAll("\\{银行名称\\}", nullToEmpty(a.getBankName()));
|
||||
html = html.replaceAll("\\{开户银行名称(具体到支行)\\}", nullToEmpty(a.getBankBranch()));
|
||||
// 新版占位符 (合并字段): {开户行省/市} → bankRegion, {开户行地址} → bankAddress
|
||||
html = html.replaceAll("\\{开户行省/市\\}", nullToEmpty(a.getBankRegion()));
|
||||
html = html.replaceAll("\\{开户行地址\\}", nullToEmpty(a.getBankAddress()));
|
||||
// 旧版占位符 {开户银行地址} 保留向下兼容: 合并 bankRegion + bankAddress
|
||||
html = html.replaceAll("\\{开户银行地址\\}",
|
||||
nullToEmpty(a.getBankRegion()) + " " + nullToEmpty(a.getBankAddress()));
|
||||
html = html.replaceAll("\\{银行卡号\\}", nullToEmpty(a.getBankCard()));
|
||||
html = html.replaceAll("\\{医务职称\\}", nullToEmpty(a.getTitle()));
|
||||
return html;
|
||||
}
|
||||
|
||||
private String nullToEmpty(String s) { return s == null ? "" : s; }
|
||||
|
||||
/** 日期格式: yyyy-MM-dd (例如 2026-01-10) */
|
||||
private String formatDate(java.util.Date d) {
|
||||
if (d == null) return "";
|
||||
java.util.Calendar c = java.util.Calendar.getInstance();
|
||||
c.setTime(d);
|
||||
return String.format("%04d-%02d-%02d",
|
||||
c.get(java.util.Calendar.YEAR),
|
||||
c.get(java.util.Calendar.MONTH) + 1,
|
||||
c.get(java.util.Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
|
||||
/** 日期时间格式: yyyy-MM-dd HH:mm (例如 2026-01-10 10:00) */
|
||||
private String formatDateTime(java.util.Date d) {
|
||||
if (d == null) return "";
|
||||
java.util.Calendar c = java.util.Calendar.getInstance();
|
||||
c.setTime(d);
|
||||
return String.format("%04d-%02d-%02d %02d:%02d",
|
||||
c.get(java.util.Calendar.YEAR),
|
||||
c.get(java.util.Calendar.MONTH) + 1,
|
||||
c.get(java.util.Calendar.DAY_OF_MONTH),
|
||||
c.get(java.util.Calendar.HOUR_OF_DAY),
|
||||
c.get(java.util.Calendar.MINUTE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> submitSign(Long attendeeId, String handsignBase64, String contentHtml, String signedIp) {
|
||||
BizMeetingAttendee attendee = attendeeService.selectById(attendeeId);
|
||||
if (attendee == null) {
|
||||
throw new ServiceException("参会人记录不存在");
|
||||
}
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
if (currentUserId == null || !currentUserId.equals(attendee.getUserId())) {
|
||||
throw new ServiceException("无权访问该协议");
|
||||
}
|
||||
if (handsignBase64 == null || handsignBase64.isEmpty()) {
|
||||
throw new ServiceException("手写签名不能为空");
|
||||
}
|
||||
String fullPdfUrl = pdfService.htmlToPdfFile(contentHtml, "labor/" + attendee.getMeetingId());
|
||||
String maskedHtml = desensitizeHtml(contentHtml);
|
||||
String maskedPdfUrl = pdfService.htmlToPdfFile(maskedHtml, "labor/" + attendee.getMeetingId() + "/masked");
|
||||
BizMeetingAttendee update = new BizMeetingAttendee();
|
||||
update.setId(attendeeId);
|
||||
update.setHandsign(handsignBase64);
|
||||
update.setLaborProtocol(fullPdfUrl);
|
||||
update.setLaborProtocolMasked(maskedPdfUrl);
|
||||
update.setSignedAt(new java.util.Date());
|
||||
update.setSignedIp(signedIp);
|
||||
update.setUpdateBy(SecurityUtils.getUsername());
|
||||
attendeeService.updateSign(update);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("fullPdfUrl", fullPdfUrl);
|
||||
result.put("maskedPdfUrl", maskedPdfUrl);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String desensitizeHtml(String html) {
|
||||
if (html == null) return null;
|
||||
java.util.regex.Pattern idCardP = java.util.regex.Pattern.compile("(?<!\\d)(\\d{17}[\\dXx])(?!\\d)");
|
||||
java.util.regex.Matcher m = idCardP.matcher(html);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String v = m.group(1);
|
||||
String masked = v.substring(0, 6) + "********" + v.substring(14);
|
||||
m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(masked));
|
||||
}
|
||||
m.appendTail(sb);
|
||||
html = sb.toString();
|
||||
|
||||
java.util.regex.Pattern bankP = java.util.regex.Pattern.compile("(?<!\\d)(\\d{16,19})(?!\\d)");
|
||||
m = bankP.matcher(html);
|
||||
sb = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String v = m.group(1);
|
||||
String masked = v.substring(0, 4) + "********" + v.substring(v.length() - 4);
|
||||
m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(masked));
|
||||
}
|
||||
m.appendTail(sb);
|
||||
html = sb.toString();
|
||||
|
||||
java.util.regex.Pattern phoneP = java.util.regex.Pattern.compile("(?<!\\d)(1[3-9]\\d)(\\d{4})(\\d{4})(?!\\d)");
|
||||
m = phoneP.matcher(html);
|
||||
sb = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String masked = m.group(1) + "****" + m.group(3);
|
||||
m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(masked));
|
||||
}
|
||||
m.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,9 @@
|
||||
<result property="titleCertUrl" column="title_cert_url" />
|
||||
<result property="bankCard" column="bank_card" />
|
||||
<result property="bankName" column="bank_name" />
|
||||
<result property="bankProvince" column="bank_province" />
|
||||
<result property="bankCity" column="bank_city" />
|
||||
<result property="bankRegion" column="bank_region" />
|
||||
<result property="bankAddress" column="bank_address" />
|
||||
<result property="idCardFrontUrl" column="id_card_front_url" />
|
||||
<result property="idCardBackUrl" column="id_card_back_url" />
|
||||
<result property="idCardAttachments" column="id_card_attachments" />
|
||||
<result property="auditStatus" column="audit_status" />
|
||||
<result property="auditBy" column="audit_by" />
|
||||
<result property="auditTime" column="audit_time" />
|
||||
@@ -29,7 +27,7 @@
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
<sql id="selectFields">
|
||||
select expert_id, user_id, name, phone, region, id_card, work_unit, department, title, practice_cert_url, title_cert_url, bank_card, bank_name, bank_province, bank_city, bank_address, id_card_front_url, id_card_back_url, audit_status, audit_by, audit_time, status, create_by, create_time, update_by, update_time
|
||||
select expert_id, user_id, name, phone, region, id_card, work_unit, department, title, practice_cert_url, title_cert_url, bank_card, bank_name, bank_region, bank_address, id_card_attachments, audit_status, audit_by, audit_time, status, create_by, create_time, update_by, update_time
|
||||
from biz_expert
|
||||
</sql>
|
||||
<select id="selectByUserId" resultMap="BizExpertResult" parameterType="Long">
|
||||
@@ -62,14 +60,12 @@
|
||||
<if test="workUnit != null">work_unit,</if>
|
||||
<if test="department != null">department,</if>
|
||||
<if test="title != null">title,</if>
|
||||
<if test="idCardFrontUrl != null">id_card_front_url,</if>
|
||||
<if test="idCardBackUrl != null">id_card_back_url,</if>
|
||||
<if test="idCardAttachments != null">id_card_attachments,</if>
|
||||
<if test="practiceCertUrl != null">practice_cert_url,</if>
|
||||
<if test="titleCertUrl != null">title_cert_url,</if>
|
||||
<if test="bankCard != null">bank_card,</if>
|
||||
<if test="bankName != null">bank_name,</if>
|
||||
<if test="bankProvince != null">bank_province,</if>
|
||||
<if test="bankCity != null">bank_city,</if>
|
||||
<if test="bankRegion != null">bank_region,</if>
|
||||
<if test="bankAddress != null">bank_address,</if>
|
||||
<if test="auditStatus != null">audit_status,</if>
|
||||
<if test="auditBy != null">audit_by,</if>
|
||||
@@ -90,14 +86,12 @@
|
||||
<if test="workUnit != null">#{workUnit},</if>
|
||||
<if test="department != null">#{department},</if>
|
||||
<if test="title != null">#{title},</if>
|
||||
<if test="idCardFrontUrl != null">#{idCardFrontUrl},</if>
|
||||
<if test="idCardBackUrl != null">#{idCardBackUrl},</if>
|
||||
<if test="idCardAttachments != null">#{idCardAttachments},</if>
|
||||
<if test="practiceCertUrl != null">#{practiceCertUrl},</if>
|
||||
<if test="titleCertUrl != null">#{titleCertUrl},</if>
|
||||
<if test="bankCard != null">#{bankCard},</if>
|
||||
<if test="bankName != null">#{bankName},</if>
|
||||
<if test="bankProvince != null">#{bankProvince},</if>
|
||||
<if test="bankCity != null">#{bankCity},</if>
|
||||
<if test="bankRegion != null">#{bankRegion},</if>
|
||||
<if test="bankAddress != null">#{bankAddress},</if>
|
||||
<if test="auditStatus != null">#{auditStatus},</if>
|
||||
<if test="auditBy != null">#{auditBy},</if>
|
||||
@@ -121,11 +115,9 @@
|
||||
<if test="idCard != null">id_card = #{idCard},</if>
|
||||
<if test="bankCard != null">bank_card = #{bankCard},</if>
|
||||
<if test="bankName != null">bank_name = #{bankName},</if>
|
||||
<if test="bankProvince != null">bank_province = #{bankProvince},</if>
|
||||
<if test="bankCity != null">bank_city = #{bankCity},</if>
|
||||
<if test="bankRegion != null">bank_region = #{bankRegion},</if>
|
||||
<if test="bankAddress != null">bank_address = #{bankAddress},</if>
|
||||
<if test="idCardFrontUrl != null">id_card_front_url = #{idCardFrontUrl},</if>
|
||||
<if test="idCardBackUrl != null">id_card_back_url = #{idCardBackUrl},</if>
|
||||
<if test="idCardAttachments != null">id_card_attachments = #{idCardAttachments},</if>
|
||||
<if test="practiceCertUrl != null">practice_cert_url = #{practiceCertUrl},</if>
|
||||
<if test="titleCertUrl != null">title_cert_url = #{titleCertUrl},</if>
|
||||
<if test="auditOpinion != null">audit_opinion = #{auditOpinion},</if>
|
||||
@@ -149,11 +141,9 @@
|
||||
<if test="idCard != null">id_card,</if>
|
||||
<if test="bankCard != null">bank_card,</if>
|
||||
<if test="bankName != null">bank_name,</if>
|
||||
<if test="bankProvince != null">bank_province,</if>
|
||||
<if test="bankCity != null">bank_city,</if>
|
||||
<if test="bankRegion != null">bank_region,</if>
|
||||
<if test="bankAddress != null">bank_address,</if>
|
||||
<if test="idCardFrontUrl != null">id_card_front_url,</if>
|
||||
<if test="idCardBackUrl != null">id_card_back_url,</if>
|
||||
<if test="idCardAttachments != null">id_card_attachments,</if>
|
||||
<if test="practiceCertUrl != null">practice_cert_url,</if>
|
||||
<if test="titleCertUrl != null">title_cert_url,</if>
|
||||
</trim>
|
||||
@@ -169,11 +159,9 @@
|
||||
<if test="idCard != null">#{idCard},</if>
|
||||
<if test="bankCard != null">#{bankCard},</if>
|
||||
<if test="bankName != null">#{bankName},</if>
|
||||
<if test="bankProvince != null">#{bankProvince},</if>
|
||||
<if test="bankCity != null">#{bankCity},</if>
|
||||
<if test="bankRegion != null">#{bankRegion},</if>
|
||||
<if test="bankAddress != null">#{bankAddress},</if>
|
||||
<if test="idCardFrontUrl != null">#{idCardFrontUrl},</if>
|
||||
<if test="idCardBackUrl != null">#{idCardBackUrl},</if>
|
||||
<if test="idCardAttachments != null">#{idCardAttachments},</if>
|
||||
<if test="practiceCertUrl != null">#{practiceCertUrl},</if>
|
||||
<if test="titleCertUrl != null">#{titleCertUrl},</if>
|
||||
</trim>
|
||||
@@ -187,11 +175,9 @@
|
||||
<if test="idCard != null and idCard != ''">id_card = #{idCard},</if>
|
||||
<if test="bankCard != null and bankCard != ''">bank_card = #{bankCard},</if>
|
||||
<if test="bankName != null and bankName != ''">bank_name = #{bankName},</if>
|
||||
<if test="bankProvince != null and bankProvince != ''">bank_province = #{bankProvince},</if>
|
||||
<if test="bankCity != null and bankCity != ''">bank_city = #{bankCity},</if>
|
||||
<if test="bankRegion != null and bankRegion != ''">bank_region = #{bankRegion},</if>
|
||||
<if test="bankAddress != null and bankAddress != ''">bank_address = #{bankAddress},</if>
|
||||
<if test="idCardFrontUrl != null and idCardFrontUrl != ''">id_card_front_url = #{idCardFrontUrl},</if>
|
||||
<if test="idCardBackUrl != null and idCardBackUrl != ''">id_card_back_url = #{idCardBackUrl},</if>
|
||||
<if test="idCardAttachments != null and idCardAttachments != ''">id_card_attachments = #{idCardAttachments},</if>
|
||||
<if test="practiceCertUrl != null and practiceCertUrl != ''">practice_cert_url = #{practiceCertUrl},</if>
|
||||
<if test="titleCertUrl != null and titleCertUrl != ''">title_cert_url = #{titleCertUrl},</if>
|
||||
<if test="auditStatus != null and auditStatus != ''">audit_status = #{auditStatus},</if>
|
||||
@@ -206,6 +192,28 @@
|
||||
</trim>
|
||||
where expert_id = #{expertId}
|
||||
</update>
|
||||
<!-- 批量插入 biz_expert (单批 caller 控制 ≤100; sysdate() 由 DB 生成) -->
|
||||
<insert id="batchInsert" parameterType="list">
|
||||
insert into biz_expert
|
||||
(expert_id, user_id, name, phone, region, id_card,
|
||||
work_unit, department, title,
|
||||
practice_cert_url, title_cert_url,
|
||||
bank_card, bank_name, bank_region, bank_address,
|
||||
id_card_attachments,
|
||||
audit_status, audit_by, audit_time, status,
|
||||
create_by, create_time)
|
||||
values
|
||||
<foreach collection="list" item="e" separator=",">
|
||||
(#{e.expertId}, #{e.userId}, #{e.name}, #{e.phone}, #{e.region}, #{e.idCard},
|
||||
#{e.workUnit}, #{e.department}, #{e.title},
|
||||
#{e.practiceCertUrl}, #{e.titleCertUrl},
|
||||
#{e.bankCard}, #{e.bankName}, #{e.bankRegion}, #{e.bankAddress},
|
||||
#{e.idCardAttachments},
|
||||
#{e.auditStatus}, #{e.auditBy}, #{e.auditTime}, #{e.status},
|
||||
#{e.createBy}, sysdate())
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<delete id="deleteByPrimaryKey" parameterType="Long">
|
||||
delete from biz_expert where expert_id = #{expertId}
|
||||
</delete>
|
||||
|
||||
+73
-3
@@ -5,11 +5,31 @@
|
||||
<id property="id" column="id" />
|
||||
<result property="meetingId" column="meeting_id" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="name" column="name" />
|
||||
<result property="phone" column="phone" />
|
||||
<result property="workUnit" column="work_unit" />
|
||||
<result property="department" column="department" />
|
||||
<result property="title" column="title" />
|
||||
<result property="idCard" column="id_card" />
|
||||
<result property="bankCard" column="bank_card" />
|
||||
<result property="bankName" column="bank_name" />
|
||||
<result property="bankBranch" column="bank_branch" />
|
||||
<result property="bankRegion" column="bank_region" />
|
||||
<result property="bankAddress" column="bank_address" />
|
||||
<result property="accountName" column="account_name" />
|
||||
<result property="idCardAttachments" column="id_card_attachments" />
|
||||
<result property="laborForm" column="labor_form" />
|
||||
<result property="feePreTax" column="fee_pre_tax" />
|
||||
<result property="tax" column="tax" />
|
||||
<result property="fee" column="fee" />
|
||||
<result property="signedAt" column="signed_at" />
|
||||
<result property="signedIp" column="signed_ip" />
|
||||
<result property="handsign" column="handsign" />
|
||||
<result property="laborProtocol" column="labor_protocol" />
|
||||
<result property="laborProtocolMasked" column="labor_protocol_masked" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<!-- 联表字段 (非持久化, entity transient 字段接收) -->
|
||||
<!-- 联表字段 (非持久化, entity transient property 接收) -->
|
||||
<result property="meetingName" column="meeting_name" />
|
||||
<result property="startTime" column="start_time" />
|
||||
<result property="endTime" column="end_time" />
|
||||
@@ -20,6 +40,40 @@
|
||||
insert into biz_meeting_attendee(meeting_id, user_id, create_by, create_time)
|
||||
values(#{meetingId}, #{userId}, #{createBy}, sysdate())
|
||||
</insert>
|
||||
<!-- 批量插入参会人 (BizMeetingController.add 调用) -->
|
||||
<insert id="insertBatch">
|
||||
insert into biz_meeting_attendee(meeting_id, user_id, create_by, create_time)
|
||||
values
|
||||
<foreach collection="userIds" item="userId" separator=",">
|
||||
(#{meetingId}, #{userId}, #{createBy}, sysdate())
|
||||
</foreach>
|
||||
</insert>
|
||||
<!-- 医生填写信息保存草稿: 更新所有签字字段 + 劳务信息 (不含签名/PDF) -->
|
||||
<update id="updateProfile" parameterType="BizMeetingAttendee">
|
||||
update biz_meeting_attendee
|
||||
<set>
|
||||
<if test="name != null">name = #{name},</if>
|
||||
<if test="phone != null">phone = #{phone},</if>
|
||||
<if test="workUnit != null">work_unit = #{workUnit},</if>
|
||||
<if test="department != null">department = #{department},</if>
|
||||
<if test="title != null">title = #{title},</if>
|
||||
<if test="idCard != null">id_card = #{idCard},</if>
|
||||
<if test="bankCard != null">bank_card = #{bankCard},</if>
|
||||
<if test="bankName != null">bank_name = #{bankName},</if>
|
||||
<if test="bankBranch != null">bank_branch = #{bankBranch},</if>
|
||||
<if test="bankRegion != null">bank_region = #{bankRegion},</if>
|
||||
<if test="bankAddress != null">bank_address = #{bankAddress},</if>
|
||||
<if test="accountName != null">account_name = #{accountName},</if>
|
||||
<if test="idCardAttachments != null">id_card_attachments = #{idCardAttachments},</if>
|
||||
<if test="laborForm != null">labor_form = #{laborForm},</if>
|
||||
<if test="feePreTax != null">fee_pre_tax = #{feePreTax},</if>
|
||||
<if test="tax != null">tax = #{tax},</if>
|
||||
<if test="fee != null">fee = #{fee},</if>
|
||||
update_by = #{updateBy},
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
<update id="updateHandsign" parameterType="BizMeetingAttendee">
|
||||
update biz_meeting_attendee
|
||||
set handsign = #{handsign},
|
||||
@@ -27,6 +81,18 @@
|
||||
update_time = sysdate()
|
||||
where id = #{id}
|
||||
</update>
|
||||
<!-- 提交签字: 存 handsign + labor_protocol + signed_at + signed_ip -->
|
||||
<update id="updateSign" parameterType="BizMeetingAttendee">
|
||||
update biz_meeting_attendee
|
||||
set handsign = #{handsign},
|
||||
labor_protocol = #{laborProtocol},
|
||||
labor_protocol_masked = #{laborProtocolMasked},
|
||||
signed_at = #{signedAt},
|
||||
signed_ip = #{signedIp},
|
||||
update_by = #{updateBy},
|
||||
update_time = sysdate()
|
||||
where id = #{id}
|
||||
</update>
|
||||
<update id="updateLaborProtocol" parameterType="BizMeetingAttendee">
|
||||
update biz_meeting_attendee
|
||||
set labor_protocol = #{laborProtocol},
|
||||
@@ -41,13 +107,17 @@
|
||||
delete from biz_meeting_attendee where meeting_id = #{meetingId} and user_id = #{userId}
|
||||
</delete>
|
||||
<select id="selectByMeetingId" resultMap="BizMeetingAttendeeResult" parameterType="Long">
|
||||
select id, meeting_id, user_id, handsign, labor_protocol, create_by, create_time
|
||||
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time
|
||||
from biz_meeting_attendee where meeting_id = #{meetingId}
|
||||
</select>
|
||||
<select id="selectByUserId" resultMap="BizMeetingAttendeeResult" parameterType="Long">
|
||||
select id, meeting_id, user_id, handsign, labor_protocol, create_by, create_time
|
||||
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time
|
||||
from biz_meeting_attendee where user_id = #{userId}
|
||||
</select>
|
||||
<select id="selectById" resultMap="BizMeetingAttendeeResult" parameterType="Long">
|
||||
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time
|
||||
from biz_meeting_attendee where id = #{id}
|
||||
</select>
|
||||
<!--
|
||||
当前用户的"待签署"会议列表 (任一未签: handsign 或 labor_protocol 为 NULL)
|
||||
INNER JOIN biz_meeting 取会议名/时间/项目名, 用于 /doctor/home 工作台
|
||||
|
||||
+2
-2
@@ -32,10 +32,10 @@
|
||||
<include refid="selectFields"/>
|
||||
<where>
|
||||
<if test="projectId != null"> and project_id = #{projectId}</if>
|
||||
<if test="projectNo != null and projectNo != ''"> and project_no = #{projectNo}</if>
|
||||
<if test="projectNo != null and projectNo != ''"> and project_no like concat('%', #{projectNo}, '%')</if>
|
||||
<if test="projectName != null and projectName != ''"> and project_name like concat('%', #{projectName}, '%')</if>
|
||||
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
|
||||
<if test="phone != null and phone != ''"> and phone = #{phone}</if>
|
||||
<if test="phone != null and phone != ''"> and phone like concat('%', #{phone}, '%')</if>
|
||||
<if test="workUnit != null and workUnit != ''"> and work_unit like concat('%', #{workUnit}, '%')</if>
|
||||
<if test="intentStatus != null and intentStatus != ''"> and intent_status = #{intentStatus}</if>
|
||||
<if test="source != null and source != ''"> and source = #{source}</if>
|
||||
|
||||
+2
-2
@@ -32,10 +32,10 @@
|
||||
<include refid="selectFields"/>
|
||||
<where>
|
||||
<if test="projectId != null"> and project_id = #{projectId}</if>
|
||||
<if test="projectNo != null and projectNo != ''"> and project_no = #{projectNo}</if>
|
||||
<if test="projectNo != null and projectNo != ''"> and project_no like concat('%', #{projectNo}, '%')</if>
|
||||
<if test="projectName != null and projectName != ''"> and project_name like concat('%', #{projectName}, '%')</if>
|
||||
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
|
||||
<if test="phone != null and phone != ''"> and phone = #{phone}</if>
|
||||
<if test="phone != null and phone != ''"> and phone like concat('%', #{phone}, '%')</if>
|
||||
<if test="workUnit != null and workUnit != ''"> and work_unit like concat('%', #{workUnit}, '%')</if>
|
||||
<if test="intentStatus != null and intentStatus != ''"> and intent_status = #{intentStatus}</if>
|
||||
<if test="source != null and source != ''"> and source = #{source}</if>
|
||||
|
||||
Reference in New Issue
Block a user