Compare commits
107 Commits
adb6b00ed0
...
a8e15e16d9
| Author | SHA1 | Date | |
|---|---|---|---|
| a8e15e16d9 | |||
| 933214a495 | |||
| b96161ecf4 | |||
| 3f424a5b7e | |||
| ea6bd5213f | |||
| 28b846134a | |||
| 40194c86fb | |||
| c9838024b3 | |||
| d582a65978 | |||
| 0a3c03dcf9 | |||
| dd3aa5bbae | |||
| 865d8f823b | |||
| 9df1b956b3 | |||
| 0889ee4533 | |||
| f2cc9e2700 | |||
| a019abb950 | |||
| d6457491e8 | |||
| 46d190aa74 | |||
| b098d4eed1 | |||
| eb0d896114 | |||
| 36576fab78 | |||
| c1d56cc153 | |||
| 26f77bf458 | |||
| 65f25a9258 | |||
| 2cb4481c3b | |||
| 559572da8c | |||
| c1018fea0c | |||
| cf36b5f05a | |||
| 5e968c8716 | |||
| ed1d07ad05 | |||
| dc578762b3 | |||
| e44e632bb6 | |||
| 3f98d59741 | |||
| e7ad46edaf | |||
| 966754e8c6 | |||
| 7cffdc9e2b | |||
| a76806acfc | |||
| 55c8f1c29c | |||
| d914c93e93 | |||
| 3ea227141d | |||
| 3a2e4d86e3 | |||
| 9e0efe8010 | |||
| 2c793eaed6 | |||
| d931ac185d | |||
| 0e593a9202 | |||
| 762af9de90 | |||
| b5733486fd | |||
| d4421862a8 | |||
| 21b8b7bf41 | |||
| 2b701602ff | |||
| cda1028c48 | |||
| 60f935da27 | |||
| 17a6c389d1 | |||
| 1e3ea8d4c9 | |||
| 3fb02f1391 | |||
| 04381dc434 | |||
| 2866767503 | |||
| d1bfeb8e63 | |||
| 255a41c936 | |||
| ed427f7a42 | |||
| 7fb1543c4c | |||
| 0746a44b32 | |||
| d174dc739f | |||
| 54cd982603 | |||
| e957cdcc81 | |||
| 9442a4116c | |||
| be3448eb44 | |||
| 8e0274df88 | |||
| 5867cd5057 | |||
| 78ae93330c | |||
| a52fb35bd3 | |||
| 717f836190 | |||
| 8df9dbacd8 | |||
| 155da36e78 | |||
| 13769da668 | |||
| e521169a7c | |||
| ad4e115787 | |||
| ed54b01d26 | |||
| 85f4e7bc61 | |||
| a13c73f9a8 | |||
| 137d6630fe | |||
| b14eef8482 | |||
| 2793cf437c | |||
| ad8099889c | |||
| 05ac43f26b | |||
| 071c02192d | |||
| 5eea3c66ff | |||
| f217d59f09 | |||
| 3e8e44ae30 | |||
| 98430b4c8d | |||
| 1770d304e5 | |||
| d745481eeb | |||
| 0fc61aa3cb | |||
| 04c9cfc42e | |||
| 5ba70789d4 | |||
| 0b80c18838 | |||
| 3dc639778e | |||
| 8a6b844509 | |||
| 0dbf5c5ca4 | |||
| c1a588b3fd | |||
| 1d013dc6df | |||
| dd93798cb9 | |||
| eb4988f80e | |||
| 5f8c5a9ec5 | |||
| 805bef4099 | |||
| 294164a504 | |||
| bb49d78a3a |
@@ -27,7 +27,7 @@
|
|||||||
- 前端开发直接在当前分支进行,不需要额外创建 git worktree
|
- 前端开发直接在当前分支进行,不需要额外创建 git worktree
|
||||||
- 测试结束后,自动关闭测试过程中启动的前后端进程
|
- 测试结束后,自动关闭测试过程中启动的前后端进程
|
||||||
- 遇到 MCP 数据库操作时,使用项目配置文件中的数据库连接信息
|
- 遇到 MCP 数据库操作时,使用项目配置文件中的数据库连接信息
|
||||||
- 执行包含中文内容的 MySQL SQL 脚本时,禁止直接手写 `mysql -e` 或普通重定向执行;必须优先使用 `bin/mysql_utf8_exec.sh <sql-file>`,确保会话字符集为 `utf8mb4`,避免写入乱码
|
- 执行包含中文内容的 MySQL SQL 脚本或数据库导入时,禁止直接手写 `mysql -e` 或普通重定向执行;必须优先使用 `bin/mysql_utf8_exec.sh <sql-file>`,确保会话字符集为 `utf8mb4`,避免导入或写入乱码
|
||||||
- 银行流水打标相关规则与参数编码需要统一使用全大写;新增或修改 `rule_code`、`indicator_code`、`param_code` 时,禁止混用大小写风格
|
- 银行流水打标相关规则与参数编码需要统一使用全大写;新增或修改 `rule_code`、`indicator_code`、`param_code` 时,禁止混用大小写风格
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -164,7 +164,7 @@ return AjaxResult.success(result);
|
|||||||
- 非业务字段如 `create_by`、`create_time` 由后端自动维护
|
- 非业务字段如 `create_by`、`create_time` 由后端自动维护
|
||||||
- 前端表单不要暴露通用审计字段
|
- 前端表单不要暴露通用审计字段
|
||||||
- 新增菜单、字典、初始化数据时,同步补充 SQL 脚本
|
- 新增菜单、字典、初始化数据时,同步补充 SQL 脚本
|
||||||
- 执行数据库脚本前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新时默认使用 `bin/mysql_utf8_exec.sh`
|
- 执行数据库脚本或导入数据库前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新、导入时默认使用 `bin/mysql_utf8_exec.sh`
|
||||||
|
|
||||||
### 前端规范
|
### 前端规范
|
||||||
|
|
||||||
|
|||||||
BIN
assets/员工账户.xlsx
Normal file
BIN
assets/员工账户.xlsx
Normal file
Binary file not shown.
@@ -11,8 +11,8 @@ TARGET_DIR="$ROOT_DIR/ruoyi-admin/target"
|
|||||||
JAR_NAME="ruoyi-admin.jar"
|
JAR_NAME="ruoyi-admin.jar"
|
||||||
SERVER_PORT=62318
|
SERVER_PORT=62318
|
||||||
STOP_WAIT_SECONDS=30
|
STOP_WAIT_SECONDS=30
|
||||||
APP_KEYWORD="$JAR_NAME"
|
APP_MARKER="-Dccdi.backend.root=$ROOT_DIR"
|
||||||
JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
|
JAVA_OPTS="$APP_MARKER -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
|
||||||
|
|
||||||
timestamp() {
|
timestamp() {
|
||||||
date "+%Y-%m-%d %H:%M:%S"
|
date "+%Y-%m-%d %H:%M:%S"
|
||||||
@@ -42,24 +42,54 @@ ensure_command() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_managed_backend_pid() {
|
||||||
|
pid="$1"
|
||||||
|
if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
args=$(ps -o args= -p "$pid" 2>/dev/null || true)
|
||||||
|
if [ -z "${args:-}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$args" in
|
||||||
|
*"$APP_MARKER"*"$JAR_NAME"*|*"$JAR_NAME"*"$APP_MARKER"*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -f "$PID_FILE" ]; then
|
||||||
|
file_pid=$(cat "$PID_FILE" 2>/dev/null || true)
|
||||||
|
if [ "${file_pid:-}" = "$pid" ]; then
|
||||||
|
case "$args" in
|
||||||
|
*"java"*"-jar"*"$JAR_NAME"*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
collect_pids() {
|
collect_pids() {
|
||||||
all_pids=""
|
all_pids=""
|
||||||
|
|
||||||
if [ -f "$PID_FILE" ]; then
|
if [ -f "$PID_FILE" ]; then
|
||||||
file_pid=$(cat "$PID_FILE" 2>/dev/null || true)
|
file_pid=$(cat "$PID_FILE" 2>/dev/null || true)
|
||||||
if [ -n "${file_pid:-}" ] && kill -0 "$file_pid" 2>/dev/null; then
|
if [ -n "${file_pid:-}" ] && is_managed_backend_pid "$file_pid"; then
|
||||||
all_pids="$all_pids $file_pid"
|
all_pids="$all_pids $file_pid"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true)
|
marker_pids=$(pgrep -f "$APP_MARKER" 2>/dev/null || true)
|
||||||
if [ -n "${port_pids:-}" ]; then
|
if [ -n "${marker_pids:-}" ]; then
|
||||||
all_pids="$all_pids $port_pids"
|
for pid in $marker_pids; do
|
||||||
fi
|
if is_managed_backend_pid "$pid"; then
|
||||||
|
all_pids="$all_pids $pid"
|
||||||
app_pids=$(pgrep -f "$APP_KEYWORD" 2>/dev/null || true)
|
fi
|
||||||
if [ -n "${app_pids:-}" ]; then
|
done
|
||||||
all_pids="$all_pids $app_pids"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
unique_pids=""
|
unique_pids=""
|
||||||
@@ -155,6 +185,12 @@ status_backend() {
|
|||||||
pids=$(collect_pids)
|
pids=$(collect_pids)
|
||||||
if [ -n "${pids:-}" ]; then
|
if [ -n "${pids:-}" ]; then
|
||||||
log_info "后端正在运行,进程: $pids"
|
log_info "后端正在运行,进程: $pids"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true)
|
||||||
|
if [ -n "${port_pids:-}" ]; then
|
||||||
|
log_info "未发现脚本托管的后端进程,但端口 $SERVER_PORT 被其他进程占用: $port_pids"
|
||||||
else
|
else
|
||||||
log_info "后端未运行"
|
log_info "后端未运行"
|
||||||
fi
|
fi
|
||||||
@@ -190,6 +226,7 @@ main() {
|
|||||||
ensure_command mvn
|
ensure_command mvn
|
||||||
ensure_command lsof
|
ensure_command lsof
|
||||||
ensure_command pgrep
|
ensure_command pgrep
|
||||||
|
ensure_command ps
|
||||||
ensure_command tail
|
ensure_command tail
|
||||||
|
|
||||||
action="${1:-restart}"
|
action="${1:-restart}"
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ public class GetBankStatementResponse {
|
|||||||
/** 对手方备注 */
|
/** 对手方备注 */
|
||||||
private String customerReference;
|
private String customerReference;
|
||||||
|
|
||||||
|
/** 交易对手方证件号 */
|
||||||
|
private String customerCertNo;
|
||||||
|
|
||||||
|
/** 交易对手方统一社会信用代码 */
|
||||||
|
private String customerSocialCreditCode;
|
||||||
|
|
||||||
// ===== 摘要和备注 =====
|
// ===== 摘要和备注 =====
|
||||||
|
|
||||||
/** 用户交易摘要 */
|
/** 用户交易摘要 */
|
||||||
|
|||||||
@@ -6,18 +6,23 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
|||||||
import com.ruoyi.common.core.page.PageDomain;
|
import com.ruoyi.common.core.page.PageDomain;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.core.page.TableSupport;
|
import com.ruoyi.common.core.page.TableSupport;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 纪检初核项目管理Controller
|
* 纪检初核项目管理Controller
|
||||||
*
|
*
|
||||||
@@ -53,6 +58,17 @@ public class CcdiProjectController extends BaseController {
|
|||||||
return AjaxResult.success("项目更新成功", vo);
|
return AjaxResult.success("项目更新成功", vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归档项目
|
||||||
|
*/
|
||||||
|
@PostMapping("/{projectId}/archive")
|
||||||
|
@Operation(summary = "归档项目")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||||
|
public AjaxResult archiveProject(@PathVariable Long projectId) {
|
||||||
|
projectService.archiveProject(projectId, SecurityUtils.getUsername());
|
||||||
|
return AjaxResult.success("项目归档成功");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除项目
|
* 删除项目
|
||||||
*/
|
*/
|
||||||
@@ -88,6 +104,28 @@ public class CcdiProjectController extends BaseController {
|
|||||||
return getDataTable(result.getRecords(), result.getTotal());
|
return getDataTable(result.getRecords(), result.getTotal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询历史项目列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/history")
|
||||||
|
@Operation(summary = "查询历史项目列表")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
|
||||||
|
public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) {
|
||||||
|
List<CcdiProjectHistoryListItemVO> result = projectService.listHistoryProjects(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从历史项目导入
|
||||||
|
*/
|
||||||
|
@PostMapping("/import")
|
||||||
|
@Operation(summary = "导入历史项目")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
|
||||||
|
public AjaxResult importFromHistory(@Validated @RequestBody CcdiProjectImportHistoryDTO dto) {
|
||||||
|
CcdiProjectVO vo = projectService.importFromHistory(dto, SecurityUtils.getUsername());
|
||||||
|
return AjaxResult.success("项目创建成功", vo);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询项目状态统计
|
* 查询项目状态统计
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
package com.ruoyi.ccdi.project.controller;
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
||||||
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结果总览控制器
|
* 结果总览控制器
|
||||||
*/
|
*/
|
||||||
@@ -45,8 +59,8 @@ public class CcdiProjectOverviewController extends BaseController {
|
|||||||
@GetMapping("/risk-people")
|
@GetMapping("/risk-people")
|
||||||
@Operation(summary = "查询风险人员总览")
|
@Operation(summary = "查询风险人员总览")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
public AjaxResult getRiskPeople(Long projectId) {
|
public AjaxResult getRiskPeople(CcdiProjectRiskPeopleQueryDTO queryDTO) {
|
||||||
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(projectId);
|
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(queryDTO);
|
||||||
return AjaxResult.success(overview);
|
return AjaxResult.success(overview);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,4 +96,76 @@ public class CcdiProjectOverviewController extends BaseController {
|
|||||||
CcdiProjectRiskModelPeopleVO people = overviewService.getRiskModelPeople(queryDTO);
|
CcdiProjectRiskModelPeopleVO people = overviewService.getRiskModelPeople(queryDTO);
|
||||||
return AjaxResult.success(people);
|
return AjaxResult.success(people);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目分析详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/person-analysis/detail")
|
||||||
|
@Operation(summary = "查询项目分析详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) {
|
||||||
|
CcdiProjectPersonAnalysisDetailVO detail = overviewService.getPersonAnalysisDetail(queryDTO);
|
||||||
|
return AjaxResult.success(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询涉疑交易明细
|
||||||
|
*/
|
||||||
|
@GetMapping("/suspicious-transactions")
|
||||||
|
@Operation(summary = "查询涉疑交易明细")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getSuspiciousTransactions(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||||
|
CcdiProjectSuspiciousTransactionPageVO pageVO = overviewService.getSuspiciousTransactions(queryDTO);
|
||||||
|
return AjaxResult.success(pageVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目员工负面征信
|
||||||
|
*/
|
||||||
|
@GetMapping("/employee-credit-negative")
|
||||||
|
@Operation(summary = "查询项目员工负面征信")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getEmployeeCreditNegative(CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO) {
|
||||||
|
CcdiProjectEmployeeCreditNegativePageVO pageVO = overviewService.getEmployeeCreditNegative(queryDTO);
|
||||||
|
return AjaxResult.success(pageVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出涉疑交易明细
|
||||||
|
*/
|
||||||
|
@PostMapping("/suspicious-transactions/export")
|
||||||
|
@Operation(summary = "导出涉疑交易明细")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public void exportSuspiciousTransactions(
|
||||||
|
HttpServletResponse response,
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
List<CcdiProjectSuspiciousTransactionExcel> rows = overviewService.exportSuspiciousTransactions(queryDTO);
|
||||||
|
ExcelUtil<CcdiProjectSuspiciousTransactionExcel> util =
|
||||||
|
new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class);
|
||||||
|
util.exportExcel(response, rows, "涉疑交易明细");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出风险人员总览
|
||||||
|
*/
|
||||||
|
@PostMapping("/risk-people/export")
|
||||||
|
@Operation(summary = "导出风险人员总览")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public void exportRiskPeople(HttpServletResponse response, Long projectId) {
|
||||||
|
List<CcdiProjectRiskPeopleOverviewExcel> rows = overviewService.exportRiskPeopleOverview(projectId);
|
||||||
|
ExcelUtil<CcdiProjectRiskPeopleOverviewExcel> util =
|
||||||
|
new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class);
|
||||||
|
util.exportExcel(response, rows, "风险人员总览");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出风险明细
|
||||||
|
*/
|
||||||
|
@PostMapping("/risk-details/export")
|
||||||
|
@Operation(summary = "导出风险明细")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||||
|
overviewService.exportRiskDetails(response, projectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
package com.ruoyi.ccdi.project.controller;
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
|
||||||
@@ -48,4 +60,70 @@ public class CcdiProjectSpecialCheckController extends BaseController {
|
|||||||
CcdiProjectFamilyAssetLiabilityDetailVO result = specialCheckService.getFamilyAssetLiabilityDetail(queryDTO);
|
CcdiProjectFamilyAssetLiabilityDetailVO result = specialCheckService.getFamilyAssetLiabilityDetail(queryDTO);
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询采购拓展列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/extended-query/purchase/list")
|
||||||
|
@Operation(summary = "查询采购拓展列表")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getExtendedPurchaseList(@Validated CcdiProjectExtendedPurchaseQueryDTO queryDTO) {
|
||||||
|
CcdiProjectExtendedPurchaseListVO result = specialCheckService.getExtendedPurchaseList(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询采购拓展详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/extended-query/purchase/detail")
|
||||||
|
@Operation(summary = "查询采购拓展详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getExtendedPurchaseDetail(@Validated CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO) {
|
||||||
|
CcdiProjectExtendedPurchaseDetailVO result = specialCheckService.getExtendedPurchaseDetail(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询招聘拓展列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/extended-query/recruitment/list")
|
||||||
|
@Operation(summary = "查询招聘拓展列表")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getExtendedRecruitmentList(@Validated CcdiProjectExtendedRecruitmentQueryDTO queryDTO) {
|
||||||
|
CcdiProjectExtendedRecruitmentListVO result = specialCheckService.getExtendedRecruitmentList(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询招聘拓展详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/extended-query/recruitment/detail")
|
||||||
|
@Operation(summary = "查询招聘拓展详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getExtendedRecruitmentDetail(@Validated CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO) {
|
||||||
|
CcdiProjectExtendedRecruitmentDetailVO result = specialCheckService.getExtendedRecruitmentDetail(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询调动拓展列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/extended-query/transfer/list")
|
||||||
|
@Operation(summary = "查询调动拓展列表")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getExtendedTransferList(@Validated CcdiProjectExtendedTransferQueryDTO queryDTO) {
|
||||||
|
CcdiProjectExtendedTransferListVO result = specialCheckService.getExtendedTransferList(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询调动拓展详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/extended-query/transfer/detail")
|
||||||
|
@Operation(summary = "查询调动拓展详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getExtendedTransferDetail(@Validated CcdiProjectExtendedTransferDetailQueryDTO queryDTO) {
|
||||||
|
CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目员工负面征信查询 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectEmployeeCreditNegativeQueryDTO {
|
||||||
|
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
private Integer pageNum;
|
||||||
|
|
||||||
|
private Integer pageSize;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查采购拓展查询详情入参
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedPurchaseDetailQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 采购事项ID */
|
||||||
|
@NotBlank(message = "采购事项ID不能为空")
|
||||||
|
private String purchaseId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查采购拓展查询列表入参
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedPurchaseQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 申请人姓名 */
|
||||||
|
private String applicantName;
|
||||||
|
|
||||||
|
/** 申请日期开始 */
|
||||||
|
private String applyDateStart;
|
||||||
|
|
||||||
|
/** 申请日期结束 */
|
||||||
|
private String applyDateEnd;
|
||||||
|
|
||||||
|
/** 页码 */
|
||||||
|
private Integer pageNum = 1;
|
||||||
|
|
||||||
|
/** 每页条数 */
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查招聘拓展查询详情入参
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedRecruitmentDetailQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 招聘项目编号 */
|
||||||
|
@NotBlank(message = "招聘项目编号不能为空")
|
||||||
|
private String recruitId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查招聘拓展查询列表入参
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedRecruitmentQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 面试官姓名 */
|
||||||
|
private String interviewerName;
|
||||||
|
|
||||||
|
/** 页码 */
|
||||||
|
private Integer pageNum = 1;
|
||||||
|
|
||||||
|
/** 每页条数 */
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查调动拓展查询详情入参
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedTransferDetailQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 主键ID */
|
||||||
|
@NotNull(message = "主键ID不能为空")
|
||||||
|
private Long id;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查调动拓展查询列表入参
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedTransferQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 员工姓名 */
|
||||||
|
private String staffName;
|
||||||
|
|
||||||
|
/** 调动日期开始 */
|
||||||
|
private String transferDateStart;
|
||||||
|
|
||||||
|
/** 调动日期结束 */
|
||||||
|
private String transferDateEnd;
|
||||||
|
|
||||||
|
/** 页码 */
|
||||||
|
private Integer pageNum = 1;
|
||||||
|
|
||||||
|
/** 每页条数 */
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史项目导入DTO
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectImportHistoryDTO {
|
||||||
|
|
||||||
|
/** 新项目名称 */
|
||||||
|
@NotBlank(message = "项目名称不能为空")
|
||||||
|
@Length(max = 200, message = "项目名称长度不能超过200个字符")
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/** 项目描述 */
|
||||||
|
@Length(max = 500, message = "项目描述长度不能超过500个字符")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/** 来源项目ID列表 */
|
||||||
|
@NotEmpty(message = "来源项目不能为空")
|
||||||
|
private List<Long> sourceProjectIds;
|
||||||
|
|
||||||
|
/** 流水起始日期 */
|
||||||
|
private String startDate;
|
||||||
|
|
||||||
|
/** 流水结束日期 */
|
||||||
|
private String endDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目分析详情查询DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectPersonAnalysisDetailQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 员工身份证号 */
|
||||||
|
private String staffIdCard;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险人员总览查询 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectRiskPeopleQueryDTO {
|
||||||
|
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
private Integer pageNum;
|
||||||
|
|
||||||
|
private Integer pageSize;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 涉疑交易明细查询DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectSuspiciousTransactionQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 涉疑类型 */
|
||||||
|
private String suspiciousType;
|
||||||
|
|
||||||
|
/** 页码 */
|
||||||
|
private Integer pageNum;
|
||||||
|
|
||||||
|
/** 每页数量 */
|
||||||
|
private Integer pageSize;
|
||||||
|
}
|
||||||
@@ -112,6 +112,12 @@ public class CcdiBankStatement implements Serializable {
|
|||||||
/** 对手方备注 */
|
/** 对手方备注 */
|
||||||
private String customerReference;
|
private String customerReference;
|
||||||
|
|
||||||
|
/** 交易对手方证件号 */
|
||||||
|
private String customerCertNo;
|
||||||
|
|
||||||
|
/** 交易对手方统一社会信用代码 */
|
||||||
|
private String customerSocialCreditCode;
|
||||||
|
|
||||||
// ===== 摘要和备注 =====
|
// ===== 摘要和备注 =====
|
||||||
|
|
||||||
/** 用户交易摘要 */
|
/** 用户交易摘要 */
|
||||||
@@ -199,6 +205,8 @@ public class CcdiBankStatement implements Serializable {
|
|||||||
entity.setCustomerLeId(item.getCustomerId());
|
entity.setCustomerLeId(item.getCustomerId());
|
||||||
entity.setCustomerAccountName(item.getCustomerName());
|
entity.setCustomerAccountName(item.getCustomerName());
|
||||||
entity.setBatchSequence(item.getUploadSequnceNumber());
|
entity.setBatchSequence(item.getUploadSequnceNumber());
|
||||||
|
entity.setCustomerCertNo(item.getCustomerCertNo());
|
||||||
|
entity.setCustomerSocialCreditCode(item.getCustomerSocialCreditCode());
|
||||||
|
|
||||||
// 5. 特殊字段处理
|
// 5. 特殊字段处理
|
||||||
entity.setMetaJson(null); // 根据文档要求强制设为 null
|
entity.setMetaJson(null); // 根据文档要求强制设为 null
|
||||||
|
|||||||
@@ -44,6 +44,15 @@ public class CcdiFileUploadRecord implements Serializable {
|
|||||||
/** 文件状态:uploading-上传中,parsing-解析中,parsed_success-解析成功,parsed_failed-解析失败 */
|
/** 文件状态:uploading-上传中,parsing-解析中,parsed_success-解析成功,parsed_failed-解析失败 */
|
||||||
private String fileStatus;
|
private String fileStatus;
|
||||||
|
|
||||||
|
/** 来源类型 */
|
||||||
|
private String sourceType;
|
||||||
|
|
||||||
|
/** 来源项目ID */
|
||||||
|
private Long sourceProjectId;
|
||||||
|
|
||||||
|
/** 来源项目名称 */
|
||||||
|
private String sourceProjectName;
|
||||||
|
|
||||||
/** 主体名称(多个用逗号分隔) */
|
/** 主体名称(多个用逗号分隔) */
|
||||||
private String enterpriseNames;
|
private String enterpriseNames;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.event;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史项目导入提交事件
|
||||||
|
*/
|
||||||
|
public class CcdiProjectHistoryImportSubmittedEvent {
|
||||||
|
|
||||||
|
private final Long targetProjectId;
|
||||||
|
private final Integer targetLsfxProjectId;
|
||||||
|
private final CcdiProjectImportHistoryDTO dto;
|
||||||
|
private final String operator;
|
||||||
|
|
||||||
|
public CcdiProjectHistoryImportSubmittedEvent(Long targetProjectId, Integer targetLsfxProjectId,
|
||||||
|
CcdiProjectImportHistoryDTO dto, String operator) {
|
||||||
|
this.targetProjectId = targetProjectId;
|
||||||
|
this.targetLsfxProjectId = targetLsfxProjectId;
|
||||||
|
this.dto = dto;
|
||||||
|
this.operator = operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTargetProjectId() {
|
||||||
|
return targetProjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getTargetLsfxProjectId() {
|
||||||
|
return targetLsfxProjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CcdiProjectImportHistoryDTO getDto() {
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOperator() {
|
||||||
|
return operator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目员工负面征信导出对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectEmployeeCreditNegativeExcel {
|
||||||
|
|
||||||
|
@Excel(name = "员工姓名")
|
||||||
|
private String personName;
|
||||||
|
|
||||||
|
@Excel(name = "身份证号")
|
||||||
|
private String personId;
|
||||||
|
|
||||||
|
@Excel(name = "最近征信查询日期")
|
||||||
|
private String queryDate;
|
||||||
|
|
||||||
|
@Excel(name = "民事案件笔数")
|
||||||
|
private Integer civilCnt;
|
||||||
|
|
||||||
|
@Excel(name = "民事案件金额")
|
||||||
|
private BigDecimal civilLmt;
|
||||||
|
|
||||||
|
@Excel(name = "强制执行笔数")
|
||||||
|
private Integer enforceCnt;
|
||||||
|
|
||||||
|
@Excel(name = "强制执行金额")
|
||||||
|
private BigDecimal enforceLmt;
|
||||||
|
|
||||||
|
@Excel(name = "行政处罚笔数")
|
||||||
|
private Integer admCnt;
|
||||||
|
|
||||||
|
@Excel(name = "行政处罚金额")
|
||||||
|
private BigDecimal admLmt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险人员总览导出对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectRiskPeopleOverviewExcel {
|
||||||
|
|
||||||
|
@Excel(name = "姓名")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Excel(name = "身份证号")
|
||||||
|
private String idNo;
|
||||||
|
|
||||||
|
@Excel(name = "所属部门")
|
||||||
|
private String department;
|
||||||
|
|
||||||
|
@Excel(name = "疑似违规数")
|
||||||
|
private Integer riskCount;
|
||||||
|
|
||||||
|
@Excel(name = "风险等级")
|
||||||
|
private String riskLevel;
|
||||||
|
|
||||||
|
@Excel(name = "命中模型数")
|
||||||
|
private Integer modelCount;
|
||||||
|
|
||||||
|
@Excel(name = "核心异常点")
|
||||||
|
private String riskPoint;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 涉疑交易导出对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectSuspiciousTransactionExcel {
|
||||||
|
|
||||||
|
@Excel(name = "交易时间")
|
||||||
|
private String trxDate;
|
||||||
|
|
||||||
|
@Excel(name = "可疑人员")
|
||||||
|
private String suspiciousPersonName;
|
||||||
|
|
||||||
|
@Excel(name = "关联人")
|
||||||
|
private String relatedPersonName;
|
||||||
|
|
||||||
|
@Excel(name = "关联员工")
|
||||||
|
private String relatedStaffDisplay;
|
||||||
|
|
||||||
|
@Excel(name = "关系")
|
||||||
|
private String relationType;
|
||||||
|
|
||||||
|
@Excel(name = "摘要/交易类型")
|
||||||
|
private String summaryAndCashType;
|
||||||
|
|
||||||
|
@Excel(name = "交易金额")
|
||||||
|
private BigDecimal displayAmount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目员工负面征信行
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectEmployeeCreditNegativeItemVO {
|
||||||
|
|
||||||
|
private String personName;
|
||||||
|
|
||||||
|
private String personId;
|
||||||
|
|
||||||
|
private String queryDate;
|
||||||
|
|
||||||
|
private Integer civilCnt;
|
||||||
|
|
||||||
|
private BigDecimal civilLmt;
|
||||||
|
|
||||||
|
private Integer enforceCnt;
|
||||||
|
|
||||||
|
private BigDecimal enforceLmt;
|
||||||
|
|
||||||
|
private Integer admCnt;
|
||||||
|
|
||||||
|
private BigDecimal admLmt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目员工负面征信分页结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectEmployeeCreditNegativePageVO {
|
||||||
|
|
||||||
|
private List<CcdiProjectEmployeeCreditNegativeItemVO> rows = new ArrayList<>();
|
||||||
|
|
||||||
|
private Long total = 0L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查采购拓展查询详情
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedPurchaseDetailVO {
|
||||||
|
|
||||||
|
private String purchaseId;
|
||||||
|
|
||||||
|
private String purchaseCategory;
|
||||||
|
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
private String subjectName;
|
||||||
|
|
||||||
|
private String subjectDesc;
|
||||||
|
|
||||||
|
private BigDecimal purchaseQty;
|
||||||
|
|
||||||
|
private BigDecimal budgetAmount;
|
||||||
|
|
||||||
|
private BigDecimal bidAmount;
|
||||||
|
|
||||||
|
private BigDecimal actualAmount;
|
||||||
|
|
||||||
|
private BigDecimal contractAmount;
|
||||||
|
|
||||||
|
private BigDecimal settlementAmount;
|
||||||
|
|
||||||
|
private String purchaseMethod;
|
||||||
|
|
||||||
|
private String supplierName;
|
||||||
|
|
||||||
|
private String contactPerson;
|
||||||
|
|
||||||
|
private String contactPhone;
|
||||||
|
|
||||||
|
private String supplierUscc;
|
||||||
|
|
||||||
|
private String supplierBankAccount;
|
||||||
|
|
||||||
|
private String applyDate;
|
||||||
|
|
||||||
|
private String planApproveDate;
|
||||||
|
|
||||||
|
private String announceDate;
|
||||||
|
|
||||||
|
private String bidOpenDate;
|
||||||
|
|
||||||
|
private String contractSignDate;
|
||||||
|
|
||||||
|
private String expectedDeliveryDate;
|
||||||
|
|
||||||
|
private String actualDeliveryDate;
|
||||||
|
|
||||||
|
private String acceptanceDate;
|
||||||
|
|
||||||
|
private String settlementDate;
|
||||||
|
|
||||||
|
private String applicantId;
|
||||||
|
|
||||||
|
private String applicantName;
|
||||||
|
|
||||||
|
private String applyDepartment;
|
||||||
|
|
||||||
|
private String purchaseLeaderId;
|
||||||
|
|
||||||
|
private String purchaseLeaderName;
|
||||||
|
|
||||||
|
private String purchaseDepartment;
|
||||||
|
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
private String updatedBy;
|
||||||
|
|
||||||
|
private String updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查采购拓展查询列表项
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedPurchaseListItemVO {
|
||||||
|
|
||||||
|
/** 采购事项ID */
|
||||||
|
private String purchaseId;
|
||||||
|
|
||||||
|
/** 项目名称 */
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/** 标的物名称 */
|
||||||
|
private String subjectName;
|
||||||
|
|
||||||
|
/** 申请人姓名 */
|
||||||
|
private String applicantName;
|
||||||
|
|
||||||
|
/** 申请日期 */
|
||||||
|
private String applyDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查采购拓展查询列表结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedPurchaseListVO {
|
||||||
|
|
||||||
|
/** 列表数据 */
|
||||||
|
private List<CcdiProjectExtendedPurchaseListItemVO> rows;
|
||||||
|
|
||||||
|
/** 总数 */
|
||||||
|
private Long total;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查招聘拓展查询详情
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedRecruitmentDetailVO {
|
||||||
|
|
||||||
|
private String recruitId;
|
||||||
|
|
||||||
|
private String recruitName;
|
||||||
|
|
||||||
|
private String posName;
|
||||||
|
|
||||||
|
private String posCategory;
|
||||||
|
|
||||||
|
private String posDesc;
|
||||||
|
|
||||||
|
private String candName;
|
||||||
|
|
||||||
|
private String candEdu;
|
||||||
|
|
||||||
|
private String candId;
|
||||||
|
|
||||||
|
private String candSchool;
|
||||||
|
|
||||||
|
private String candMajor;
|
||||||
|
|
||||||
|
private String candGrad;
|
||||||
|
|
||||||
|
private String admitStatus;
|
||||||
|
|
||||||
|
private String interviewerName1;
|
||||||
|
|
||||||
|
private String interviewerId1;
|
||||||
|
|
||||||
|
private String interviewerName2;
|
||||||
|
|
||||||
|
private String interviewerId2;
|
||||||
|
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
private String updatedBy;
|
||||||
|
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查招聘拓展查询列表项
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedRecruitmentListItemVO {
|
||||||
|
|
||||||
|
/** 招聘项目编号 */
|
||||||
|
private String recruitId;
|
||||||
|
|
||||||
|
/** 招聘项目名称 */
|
||||||
|
private String recruitName;
|
||||||
|
|
||||||
|
/** 职位名称 */
|
||||||
|
private String posName;
|
||||||
|
|
||||||
|
/** 面试官摘要 */
|
||||||
|
private String interviewerNameSummary;
|
||||||
|
|
||||||
|
/** 录用情况 */
|
||||||
|
private String admitStatus;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查招聘拓展查询列表结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedRecruitmentListVO {
|
||||||
|
|
||||||
|
/** 列表数据 */
|
||||||
|
private List<CcdiProjectExtendedRecruitmentListItemVO> rows;
|
||||||
|
|
||||||
|
/** 总数 */
|
||||||
|
private Long total;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查调动拓展查询详情
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedTransferDetailVO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long staffId;
|
||||||
|
|
||||||
|
private String staffName;
|
||||||
|
|
||||||
|
private String transferType;
|
||||||
|
|
||||||
|
private String transferSubType;
|
||||||
|
|
||||||
|
private Long deptIdBefore;
|
||||||
|
|
||||||
|
private String deptNameBefore;
|
||||||
|
|
||||||
|
private String gradeBefore;
|
||||||
|
|
||||||
|
private String positionBefore;
|
||||||
|
|
||||||
|
private String salaryLevelBefore;
|
||||||
|
|
||||||
|
private Long deptIdAfter;
|
||||||
|
|
||||||
|
private String deptNameAfter;
|
||||||
|
|
||||||
|
private String gradeAfter;
|
||||||
|
|
||||||
|
private String positionAfter;
|
||||||
|
|
||||||
|
private String salaryLevelAfter;
|
||||||
|
|
||||||
|
private Date transferDate;
|
||||||
|
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
private String updatedBy;
|
||||||
|
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查调动拓展查询列表项
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedTransferListItemVO {
|
||||||
|
|
||||||
|
/** 主键ID */
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 员工姓名 */
|
||||||
|
private String staffName;
|
||||||
|
|
||||||
|
/** 调动类型 */
|
||||||
|
private String transferType;
|
||||||
|
|
||||||
|
/** 调动前部门 */
|
||||||
|
private String deptNameBefore;
|
||||||
|
|
||||||
|
/** 调动后部门 */
|
||||||
|
private String deptNameAfter;
|
||||||
|
|
||||||
|
/** 调动日期 */
|
||||||
|
private String transferDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专项核查调动拓展查询列表结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectExtendedTransferListVO {
|
||||||
|
|
||||||
|
/** 列表数据 */
|
||||||
|
private List<CcdiProjectExtendedTransferListItemVO> rows;
|
||||||
|
|
||||||
|
/** 总数 */
|
||||||
|
private Long total;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史项目列表项VO
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectHistoryListItemVO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 项目名称 */
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/** 项目描述 */
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/** 项目状态 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 是否归档 */
|
||||||
|
private Integer isArchived;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目分析异常明细
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectPersonAnalysisAbnormalDetailVO {
|
||||||
|
|
||||||
|
private List<CcdiProjectPersonAnalysisAbnormalGroupVO> groups;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目分析异常分组
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectPersonAnalysisAbnormalGroupVO {
|
||||||
|
|
||||||
|
private String groupCode;
|
||||||
|
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
private String groupType;
|
||||||
|
|
||||||
|
private List<?> records;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目分析人员基础信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectPersonAnalysisBasicInfoVO {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String idNo;
|
||||||
|
|
||||||
|
private String staffCode;
|
||||||
|
|
||||||
|
private String department;
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
private String riskLevel;
|
||||||
|
|
||||||
|
private String projectName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目分析详情
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectPersonAnalysisDetailVO {
|
||||||
|
|
||||||
|
private CcdiProjectPersonAnalysisBasicInfoVO basicInfo;
|
||||||
|
|
||||||
|
private CcdiProjectPersonAnalysisAbnormalDetailVO abnormalDetail;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目分析对象型异常补充字段
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectPersonAnalysisObjectFieldVO {
|
||||||
|
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目分析对象型异常记录
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectPersonAnalysisObjectRecordVO {
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
private String subtitle;
|
||||||
|
|
||||||
|
private List<String> riskTags;
|
||||||
|
|
||||||
|
private String reasonDetail;
|
||||||
|
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
private List<CcdiProjectPersonAnalysisObjectFieldVO> extraFields = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -9,5 +9,11 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class CcdiProjectRiskPeopleOverviewVO {
|
public class CcdiProjectRiskPeopleOverviewVO {
|
||||||
|
|
||||||
private List<CcdiProjectRiskPeopleOverviewItemVO> overviewList;
|
private List<CcdiProjectRiskPeopleOverviewItemVO> rows;
|
||||||
|
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
private Long pageNum;
|
||||||
|
|
||||||
|
private Long pageSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 涉疑交易明细行
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectSuspiciousTransactionItemVO {
|
||||||
|
|
||||||
|
private Long bankStatementId;
|
||||||
|
|
||||||
|
private String trxDate;
|
||||||
|
|
||||||
|
private String suspiciousPersonName;
|
||||||
|
|
||||||
|
private String relatedPersonName;
|
||||||
|
|
||||||
|
private String relatedStaffName;
|
||||||
|
|
||||||
|
private String relatedStaffCode;
|
||||||
|
|
||||||
|
private String relationType;
|
||||||
|
|
||||||
|
private String userMemo;
|
||||||
|
|
||||||
|
private String cashType;
|
||||||
|
|
||||||
|
private BigDecimal displayAmount;
|
||||||
|
|
||||||
|
private Boolean hasModelRuleHit;
|
||||||
|
|
||||||
|
private Boolean hasNameListHit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 涉疑交易分页结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectSuspiciousTransactionPageVO {
|
||||||
|
|
||||||
|
private List<CcdiProjectSuspiciousTransactionItemVO> rows;
|
||||||
|
|
||||||
|
private Long total;
|
||||||
|
}
|
||||||
@@ -30,6 +30,11 @@ public interface CcdiBankStatementMapper extends BaseMapper<CcdiBankStatement> {
|
|||||||
int deleteByProjectIdAndBatchId(@Param("projectId") Long projectId,
|
int deleteByProjectIdAndBatchId(@Param("projectId") Long projectId,
|
||||||
@Param("batchId") Integer batchId);
|
@Param("batchId") Integer batchId);
|
||||||
|
|
||||||
|
List<CcdiBankStatement> selectStatementsForHistoryImport(@Param("projectId") Long projectId,
|
||||||
|
@Param("batchId") Integer batchId,
|
||||||
|
@Param("startDate") String startDate,
|
||||||
|
@Param("endDate") String endDate);
|
||||||
|
|
||||||
Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
|
Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
|
||||||
@Param("query") CcdiBankStatementQueryDTO query);
|
@Param("query") CcdiBankStatementQueryDTO query);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,14 @@ public interface CcdiFileUploadRecordMapper extends BaseMapper<CcdiFileUploadRec
|
|||||||
*/
|
*/
|
||||||
int insertBatch(@Param("list") List<CcdiFileUploadRecord> records);
|
int insertBatch(@Param("list") List<CcdiFileUploadRecord> records);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询来源项目中解析成功且具备logId的文件记录
|
||||||
|
*
|
||||||
|
* @param projectIds 来源项目ID列表
|
||||||
|
* @return 文件记录列表
|
||||||
|
*/
|
||||||
|
List<CcdiFileUploadRecord> selectSuccessfulRecordsByProjectIds(@Param("projectIds") List<Long> projectIds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统计各状态文件数量
|
* 统计各状态文件数量
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 项目Mapper接口
|
* 项目Mapper接口
|
||||||
*
|
*
|
||||||
@@ -24,6 +27,14 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
|
|||||||
*/
|
*/
|
||||||
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO);
|
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询历史项目列表
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 历史项目列表
|
||||||
|
*/
|
||||||
|
List<CcdiProjectHistoryListItemVO> selectHistoryProjects(@Param("queryDTO") CcdiProjectQueryDTO queryDTO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新项目风险人数
|
* 更新项目风险人数
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
package com.ruoyi.ccdi.project.mapper;
|
package com.ruoyi.ccdi.project.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
@@ -29,10 +37,22 @@ public interface CcdiProjectOverviewMapper {
|
|||||||
/**
|
/**
|
||||||
* 查询风险人员总览
|
* 查询风险人员总览
|
||||||
*
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param query 查询条件
|
||||||
|
* @return 风险人员聚合分页结果
|
||||||
|
*/
|
||||||
|
Page<CcdiProjectEmployeeRiskAggregateVO> selectRiskPeopleOverviewPage(
|
||||||
|
Page<CcdiProjectEmployeeRiskAggregateVO> page,
|
||||||
|
@Param("query") CcdiProjectRiskPeopleQueryDTO query
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询风险人员总览导出列表
|
||||||
|
*
|
||||||
* @param projectId 项目ID
|
* @param projectId 项目ID
|
||||||
* @return 风险人员聚合列表
|
* @return 风险人员聚合列表
|
||||||
*/
|
*/
|
||||||
List<CcdiProjectEmployeeRiskAggregateVO> selectRiskPeopleOverviewByProjectId(@Param("projectId") Long projectId);
|
List<CcdiProjectEmployeeRiskAggregateVO> selectRiskPeopleOverviewList(@Param("projectId") Long projectId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询中高风险TOP10
|
* 查询中高风险TOP10
|
||||||
@@ -62,6 +82,48 @@ public interface CcdiProjectOverviewMapper {
|
|||||||
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query
|
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询涉疑交易明细
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param query 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
Page<CcdiProjectSuspiciousTransactionItemVO> selectSuspiciousTransactionPage(
|
||||||
|
Page<CcdiProjectSuspiciousTransactionItemVO> page,
|
||||||
|
@Param("query") CcdiProjectSuspiciousTransactionQueryDTO query
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询项目员工负面征信
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param query 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
Page<CcdiProjectEmployeeCreditNegativeItemVO> selectEmployeeCreditNegativePage(
|
||||||
|
Page<CcdiProjectEmployeeCreditNegativeItemVO> page,
|
||||||
|
@Param("query") CcdiProjectEmployeeCreditNegativeQueryDTO query
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目员工负面征信导出列表
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 导出列表
|
||||||
|
*/
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeItemVO> selectEmployeeCreditNegativeList(@Param("projectId") Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询涉疑交易导出列表
|
||||||
|
*
|
||||||
|
* @param query 查询条件
|
||||||
|
* @return 导出列表
|
||||||
|
*/
|
||||||
|
List<CcdiProjectSuspiciousTransactionItemVO> selectSuspiciousTransactionList(
|
||||||
|
@Param("query") CcdiProjectSuspiciousTransactionQueryDTO query
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按员工范围查询命中标签
|
* 按员工范围查询命中标签
|
||||||
*
|
*
|
||||||
@@ -76,6 +138,42 @@ public interface CcdiProjectOverviewMapper {
|
|||||||
@Param("selectedModelCodes") String selectedModelCodes
|
@Param("selectedModelCodes") String selectedModelCodes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目分析基础信息
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param staffIdCard 员工身份证号
|
||||||
|
* @return 项目分析基础信息
|
||||||
|
*/
|
||||||
|
CcdiProjectPersonAnalysisBasicInfoVO selectPersonAnalysisBasicInfo(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("staffIdCard") String staffIdCard
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目分析流水异常明细
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param staffIdCard 员工身份证号
|
||||||
|
* @return 流水异常明细
|
||||||
|
*/
|
||||||
|
List<CcdiBankStatementListVO> selectPersonAnalysisStatementRows(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("staffIdCard") String staffIdCard
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目分析对象型异常记录
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param staffIdCard 员工身份证号
|
||||||
|
* @return 对象型异常记录
|
||||||
|
*/
|
||||||
|
List<CcdiProjectPersonAnalysisObjectRecordVO> selectPersonAnalysisObjectRows(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("staffIdCard") String staffIdCard
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询项目风险人数汇总
|
* 查询项目风险人数汇总
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
package com.ruoyi.ccdi.project.mapper;
|
package com.ruoyi.ccdi.project.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListItemVO;
|
||||||
@@ -61,4 +71,76 @@ public interface CcdiProjectSpecialCheckMapper {
|
|||||||
@Param("staffIdCard") String staffIdCard,
|
@Param("staffIdCard") String staffIdCard,
|
||||||
@Param("spouseIdCard") String spouseIdCard
|
@Param("spouseIdCard") String spouseIdCard
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询专项核查采购拓展列表
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
Page<CcdiProjectExtendedPurchaseListItemVO> selectExtendedPurchasePage(
|
||||||
|
@Param("page") Page<CcdiProjectExtendedPurchaseListItemVO> page,
|
||||||
|
@Param("query") CcdiProjectExtendedPurchaseQueryDTO queryDTO
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询专项核查采购拓展详情
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param purchaseId 采购事项ID
|
||||||
|
* @return 详情
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedPurchaseDetailVO selectExtendedPurchaseDetail(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("purchaseId") String purchaseId
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询专项核查招聘拓展列表
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
Page<CcdiProjectExtendedRecruitmentListItemVO> selectExtendedRecruitmentPage(
|
||||||
|
@Param("page") Page<CcdiProjectExtendedRecruitmentListItemVO> page,
|
||||||
|
@Param("query") CcdiProjectExtendedRecruitmentQueryDTO queryDTO
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询专项核查招聘拓展详情
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param recruitId 招聘项目编号
|
||||||
|
* @return 详情
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedRecruitmentDetailVO selectExtendedRecruitmentDetail(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("recruitId") String recruitId
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询专项核查调动拓展列表
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
Page<CcdiProjectExtendedTransferListItemVO> selectExtendedTransferPage(
|
||||||
|
@Param("page") Page<CcdiProjectExtendedTransferListItemVO> page,
|
||||||
|
@Param("query") CcdiProjectExtendedTransferQueryDTO queryDTO
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询专项核查调动拓展详情
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param id 主键ID
|
||||||
|
* @return 详情
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedTransferDetailVO selectExtendedTransferDetail(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("id") Long id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史项目导入服务
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public interface ICcdiProjectHistoryImportService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交历史项目导入任务
|
||||||
|
*
|
||||||
|
* @param targetProjectId 目标项目ID
|
||||||
|
* @param targetLsfxProjectId 目标流水分析项目ID
|
||||||
|
* @param dto 导入参数
|
||||||
|
* @param operator 操作人
|
||||||
|
*/
|
||||||
|
void submitImport(Long targetProjectId, Integer targetLsfxProjectId,
|
||||||
|
CcdiProjectImportHistoryDTO dto, String operator);
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
package com.ruoyi.ccdi.project.service;
|
package com.ruoyi.ccdi.project.service;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结果总览服务接口
|
* 结果总览服务接口
|
||||||
@@ -23,10 +36,10 @@ public interface ICcdiProjectOverviewService {
|
|||||||
/**
|
/**
|
||||||
* 查询风险人员总览
|
* 查询风险人员总览
|
||||||
*
|
*
|
||||||
* @param projectId 项目ID
|
* @param queryDTO 查询条件
|
||||||
* @return 风险人员总览
|
* @return 风险人员总览
|
||||||
*/
|
*/
|
||||||
CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(Long projectId);
|
CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(CcdiProjectRiskPeopleQueryDTO queryDTO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询中高风险人员TOP10
|
* 查询中高风险人员TOP10
|
||||||
@@ -36,6 +49,16 @@ public interface ICcdiProjectOverviewService {
|
|||||||
*/
|
*/
|
||||||
CcdiProjectTopRiskPeopleVO getTopRiskPeople(Long projectId);
|
CcdiProjectTopRiskPeopleVO getTopRiskPeople(Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目分析详情
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 项目分析详情
|
||||||
|
*/
|
||||||
|
default CcdiProjectPersonAnalysisDetailVO getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) {
|
||||||
|
return new CcdiProjectPersonAnalysisDetailVO();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询风险模型卡片
|
* 查询风险模型卡片
|
||||||
*
|
*
|
||||||
@@ -56,6 +79,71 @@ public interface ICcdiProjectOverviewService {
|
|||||||
return new CcdiProjectRiskModelPeopleVO();
|
return new CcdiProjectRiskModelPeopleVO();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询涉疑交易明细
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
default CcdiProjectSuspiciousTransactionPageVO getSuspiciousTransactions(
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
return new CcdiProjectSuspiciousTransactionPageVO();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出涉疑交易明细
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 导出列表
|
||||||
|
*/
|
||||||
|
default List<CcdiProjectSuspiciousTransactionExcel> exportSuspiciousTransactions(
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出风险人员总览
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 导出列表
|
||||||
|
*/
|
||||||
|
default List<CcdiProjectRiskPeopleOverviewExcel> exportRiskPeopleOverview(Long projectId) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一导出风险明细
|
||||||
|
*
|
||||||
|
* @param response 响应流
|
||||||
|
* @param projectId 项目ID
|
||||||
|
*/
|
||||||
|
default void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出项目员工负面征信
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 导出列表
|
||||||
|
*/
|
||||||
|
default List<CcdiProjectEmployeeCreditNegativeExcel> exportEmployeeCreditNegative(Long projectId) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目员工负面征信
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
default CcdiProjectEmployeeCreditNegativePageVO getEmployeeCreditNegative(
|
||||||
|
CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
return new CcdiProjectEmployeeCreditNegativePageVO();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重算结果总览员工结果并同步项目风险人数
|
* 重算结果总览员工结果并同步项目风险人数
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.ruoyi.ccdi.project.service;
|
package com.ruoyi.ccdi.project.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 项目Service接口
|
* 项目Service接口
|
||||||
*
|
*
|
||||||
@@ -53,6 +57,23 @@ public interface ICcdiProjectService {
|
|||||||
*/
|
*/
|
||||||
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO);
|
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询历史项目列表
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 历史项目列表
|
||||||
|
*/
|
||||||
|
List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从历史项目导入
|
||||||
|
*
|
||||||
|
* @param dto 导入参数
|
||||||
|
* @param operator 操作人
|
||||||
|
* @return 新建项目
|
||||||
|
*/
|
||||||
|
CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询各状态的项目总数(不受搜索条件影响)
|
* 查询各状态的项目总数(不受搜索条件影响)
|
||||||
*
|
*
|
||||||
@@ -60,6 +81,14 @@ public interface ICcdiProjectService {
|
|||||||
*/
|
*/
|
||||||
CcdiProjectStatusCountsVO getStatusCounts();
|
CcdiProjectStatusCountsVO getStatusCounts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归档项目
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param operator 操作人
|
||||||
|
*/
|
||||||
|
void archiveProject(Long projectId, String operator);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新项目状态
|
* 更新项目状态
|
||||||
*
|
*
|
||||||
@@ -76,6 +105,14 @@ public interface ICcdiProjectService {
|
|||||||
*/
|
*/
|
||||||
void ensureProjectCanStartTagging(Long projectId);
|
void ensureProjectCanStartTagging(Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验项目是否未归档
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param message 拒绝文案
|
||||||
|
*/
|
||||||
|
void ensureProjectNotArchived(Long projectId, String message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验项目是否允许写入
|
* 校验项目是否允许写入
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
package com.ruoyi.ccdi.project.service;
|
package com.ruoyi.ccdi.project.service;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
||||||
|
|
||||||
@@ -29,4 +41,54 @@ public interface ICcdiProjectSpecialCheckService {
|
|||||||
CcdiProjectFamilyAssetLiabilityDetailVO getFamilyAssetLiabilityDetail(
|
CcdiProjectFamilyAssetLiabilityDetailVO getFamilyAssetLiabilityDetail(
|
||||||
CcdiProjectFamilyAssetLiabilityDetailQueryDTO queryDTO
|
CcdiProjectFamilyAssetLiabilityDetailQueryDTO queryDTO
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询采购拓展列表
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 列表结果
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedPurchaseListVO getExtendedPurchaseList(CcdiProjectExtendedPurchaseQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询采购拓展详情
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 详情结果
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedPurchaseDetailVO getExtendedPurchaseDetail(CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询招聘拓展列表
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 列表结果
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedRecruitmentListVO getExtendedRecruitmentList(CcdiProjectExtendedRecruitmentQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询招聘拓展详情
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 详情结果
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedRecruitmentDetailVO getExtendedRecruitmentDetail(
|
||||||
|
CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询调动拓展列表
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 列表结果
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedTransferListVO getExtendedTransferList(CcdiProjectExtendedTransferQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询调动拓展详情
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 详情结果
|
||||||
|
*/
|
||||||
|
CcdiProjectExtendedTransferDetailVO getExtendedTransferDetail(CcdiProjectExtendedTransferDetailQueryDTO queryDTO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
|||||||
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
|
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||||
import com.ruoyi.lsfx.constants.LsfxConstants;
|
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||||
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
||||||
@@ -169,6 +170,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
throw new IllegalArgumentException("开始日期不能晚于结束日期");
|
throw new IllegalArgumentException("开始日期不能晚于结束日期");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许上传或拉取数据");
|
||||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||||
|
|
||||||
CcdiProject project = projectMapper.selectById(projectId);
|
CcdiProject project = projectMapper.selectById(projectId);
|
||||||
@@ -323,6 +325,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
log.info("【文件上传】开始批量上传: projectId={}, 文件数量={}, username={}",
|
log.info("【文件上传】开始批量上传: projectId={}, 文件数量={}, username={}",
|
||||||
projectId, files.length, username);
|
projectId, files.length, username);
|
||||||
|
|
||||||
|
projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许上传或拉取数据");
|
||||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||||
|
|
||||||
// 1. 生成批次ID
|
// 1. 生成批次ID
|
||||||
@@ -962,6 +965,9 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
if (record == null) {
|
if (record == null) {
|
||||||
throw new RuntimeException("上传记录不存在");
|
throw new RuntimeException("上传记录不存在");
|
||||||
}
|
}
|
||||||
|
if ("HISTORY_IMPORT".equals(record.getSourceType())) {
|
||||||
|
throw new ServiceException("历史导入文件不支持删除");
|
||||||
|
}
|
||||||
if (!"parsed_success".equals(record.getFileStatus())) {
|
if (!"parsed_success".equals(record.getFileStatus())) {
|
||||||
if ("deleted".equals(record.getFileStatus())) {
|
if ("deleted".equals(record.getFileStatus())) {
|
||||||
throw new RuntimeException("文件已删除,请勿重复操作");
|
throw new RuntimeException("文件已删除,请勿重复操作");
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ public class CcdiModelParamServiceImpl implements ICcdiModelParamService {
|
|||||||
Long projectId = saveDTO.getProjectId();
|
Long projectId = saveDTO.getProjectId();
|
||||||
|
|
||||||
if (projectId > 0) {
|
if (projectId > 0) {
|
||||||
|
projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许修改参数");
|
||||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
||||||
switchToCustomConfigIfNeeded(getRequiredProject(projectId));
|
switchToCustomConfigIfNeeded(getRequiredProject(projectId));
|
||||||
}
|
}
|
||||||
@@ -192,6 +193,7 @@ public class CcdiModelParamServiceImpl implements ICcdiModelParamService {
|
|||||||
Long projectId = saveAllDTO.getProjectId();
|
Long projectId = saveAllDTO.getProjectId();
|
||||||
|
|
||||||
if (projectId > 0) {
|
if (projectId > 0) {
|
||||||
|
projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许修改参数");
|
||||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
||||||
switchToCustomConfigIfNeeded(getRequiredProject(projectId));
|
switchToCustomConfigIfNeeded(getRequiredProject(projectId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent;
|
||||||
|
import com.ruoyi.ccdi.project.service.ICcdiProjectHistoryImportService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史项目导入事件监听器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class CcdiProjectHistoryImportEventListener {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiProjectHistoryImportService historyImportService;
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
public void onSubmitted(CcdiProjectHistoryImportSubmittedEvent event) {
|
||||||
|
historyImportService.submitImport(
|
||||||
|
event.getTargetProjectId(),
|
||||||
|
event.getTargetLsfxProjectId(),
|
||||||
|
event.getDto(),
|
||||||
|
event.getOperator()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||||
|
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
|
||||||
|
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
|
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
|
||||||
|
import com.ruoyi.ccdi.project.service.ICcdiProjectHistoryImportService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史项目导入服务实现
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class CcdiProjectHistoryImportServiceImpl implements ICcdiProjectHistoryImportService {
|
||||||
|
|
||||||
|
private static final AtomicInteger HISTORY_IMPORT_BATCH_ID = new AtomicInteger(1_000_000);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiBankStatementMapper bankStatementMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiFileUploadRecordMapper recordMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiProjectMapper projectMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Qualifier("fileUploadExecutor")
|
||||||
|
private Executor fileUploadExecutor;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiBankTagService bankTagService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void submitImport(Long targetProjectId, Integer targetLsfxProjectId,
|
||||||
|
CcdiProjectImportHistoryDTO dto, String operator) {
|
||||||
|
fileUploadExecutor.execute(() -> executeImport(targetProjectId, targetLsfxProjectId, dto, operator));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeImport(Long targetProjectId, Integer targetLsfxProjectId,
|
||||||
|
CcdiProjectImportHistoryDTO dto, String operator) {
|
||||||
|
List<CcdiFileUploadRecord> sourceRecords = recordMapper.selectSuccessfulRecordsByProjectIds(dto.getSourceProjectIds());
|
||||||
|
if (sourceRecords == null || sourceRecords.isEmpty()) {
|
||||||
|
log.info("【项目历史导入】无可复制的来源批次: projectId={}, sourceProjectIds={}",
|
||||||
|
targetProjectId, dto.getSourceProjectIds());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CcdiBankStatement> statementsToInsert = new ArrayList<>();
|
||||||
|
List<CcdiFileUploadRecord> recordsToInsert = new ArrayList<>();
|
||||||
|
Set<String> dedupKeys = new HashSet<>();
|
||||||
|
|
||||||
|
for (CcdiFileUploadRecord sourceRecord : sourceRecords) {
|
||||||
|
List<CcdiBankStatement> sourceStatements = bankStatementMapper.selectStatementsForHistoryImport(
|
||||||
|
sourceRecord.getProjectId(), sourceRecord.getLogId(), dto.getStartDate(), dto.getEndDate()
|
||||||
|
);
|
||||||
|
if (sourceStatements == null || sourceStatements.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer newBatchId = HISTORY_IMPORT_BATCH_ID.incrementAndGet();
|
||||||
|
int sizeBefore = statementsToInsert.size();
|
||||||
|
|
||||||
|
for (CcdiBankStatement sourceStatement : sourceStatements) {
|
||||||
|
CcdiBankStatement targetStatement = copyStatement(sourceStatement, targetProjectId, targetLsfxProjectId, newBatchId);
|
||||||
|
if (dedupKeys.add(buildDedupKey(targetStatement))) {
|
||||||
|
statementsToInsert.add(targetStatement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statementsToInsert.size() > sizeBefore) {
|
||||||
|
recordsToInsert.add(buildHistoryImportRecord(
|
||||||
|
sourceRecord, targetProjectId, targetLsfxProjectId, newBatchId, resolveSourceProjectName(sourceRecord.getProjectId()), operator
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statementsToInsert.isEmpty()) {
|
||||||
|
bankStatementMapper.insertBatch(statementsToInsert);
|
||||||
|
}
|
||||||
|
if (!recordsToInsert.isEmpty()) {
|
||||||
|
recordMapper.insertBatch(recordsToInsert);
|
||||||
|
}
|
||||||
|
if (!statementsToInsert.isEmpty()) {
|
||||||
|
refreshProjectTargetCount(targetProjectId);
|
||||||
|
bankTagService.submitAutoRebuild(targetProjectId, TriggerType.AUTO_BATCH_UPLOAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiBankStatement copyStatement(CcdiBankStatement sourceStatement, Long targetProjectId,
|
||||||
|
Integer targetLsfxProjectId, Integer newBatchId) {
|
||||||
|
CcdiBankStatement targetStatement = new CcdiBankStatement();
|
||||||
|
BeanUtils.copyProperties(sourceStatement, targetStatement);
|
||||||
|
targetStatement.setBankStatementId(null);
|
||||||
|
targetStatement.setProjectId(targetProjectId);
|
||||||
|
targetStatement.setGroupId(targetLsfxProjectId);
|
||||||
|
targetStatement.setBatchId(newBatchId);
|
||||||
|
normalizeDedupFields(targetStatement);
|
||||||
|
return targetStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiFileUploadRecord buildHistoryImportRecord(CcdiFileUploadRecord sourceRecord, Long targetProjectId,
|
||||||
|
Integer targetLsfxProjectId, Integer newBatchId,
|
||||||
|
String sourceProjectName, String operator) {
|
||||||
|
CcdiFileUploadRecord record = new CcdiFileUploadRecord();
|
||||||
|
record.setProjectId(targetProjectId);
|
||||||
|
record.setLsfxProjectId(targetLsfxProjectId);
|
||||||
|
record.setLogId(newBatchId);
|
||||||
|
record.setFileName(sourceRecord.getFileName());
|
||||||
|
record.setFileSize(sourceRecord.getFileSize());
|
||||||
|
record.setFileStatus("parsed_success");
|
||||||
|
record.setEnterpriseNames(sourceRecord.getEnterpriseNames());
|
||||||
|
record.setAccountNos(sourceRecord.getAccountNos());
|
||||||
|
record.setUploadTime(new Date());
|
||||||
|
record.setUploadUser(operator);
|
||||||
|
record.setSourceType("HISTORY_IMPORT");
|
||||||
|
record.setSourceProjectId(sourceRecord.getProjectId());
|
||||||
|
record.setSourceProjectName(sourceProjectName);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSourceProjectName(Long sourceProjectId) {
|
||||||
|
CcdiProject sourceProject = projectMapper.selectById(sourceProjectId);
|
||||||
|
return sourceProject == null ? null : sourceProject.getProjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshProjectTargetCount(Long targetProjectId) {
|
||||||
|
CcdiProject targetProject = projectMapper.selectById(targetProjectId);
|
||||||
|
if (targetProject == null) {
|
||||||
|
log.warn("【项目历史导入】刷新目标人数时项目不存在: projectId={}", targetProjectId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int targetCount = bankStatementMapper.countMatchedStaffCountByProjectId(targetProjectId);
|
||||||
|
targetProject.setTargetCount(targetCount);
|
||||||
|
projectMapper.updateById(targetProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalizeDedupFields(CcdiBankStatement statement) {
|
||||||
|
statement.setLeAccountNo(trimToNull(statement.getLeAccountNo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildDedupKey(CcdiBankStatement statement) {
|
||||||
|
return String.join("|",
|
||||||
|
valueOf(statement.getTrxDate()),
|
||||||
|
valueOf(statement.getCurrency()),
|
||||||
|
valueOf(statement.getLeAccountNo()),
|
||||||
|
valueOf(statement.getLeAccountName()),
|
||||||
|
valueOf(statement.getAccountId()),
|
||||||
|
valueOf(statement.getCustomerAccountName()),
|
||||||
|
valueOf(statement.getCustomerAccountNo()),
|
||||||
|
valueOf(statement.getCustomerBank()),
|
||||||
|
valueOf(statement.getCustomerReference()),
|
||||||
|
valueOf(statement.getCustomerCertNo()),
|
||||||
|
valueOf(statement.getCustomerSocialCreditCode()),
|
||||||
|
valueOf(statement.getAmountDr()),
|
||||||
|
valueOf(statement.getAmountCr()),
|
||||||
|
valueOf(statement.getAmountBalance()),
|
||||||
|
valueOf(statement.getCashType()),
|
||||||
|
valueOf(statement.getUserMemo()),
|
||||||
|
valueOf(statement.getBankComments()),
|
||||||
|
valueOf(statement.getBankTrxNumber()),
|
||||||
|
valueOf(statement.getBank()),
|
||||||
|
valueOf(statement.getTrxFlag()),
|
||||||
|
valueOf(statement.getTrxType()),
|
||||||
|
valueOf(statement.getExceptionType()),
|
||||||
|
valueOf(statement.getInternalFlag()),
|
||||||
|
valueOf(statement.getCreateDate()),
|
||||||
|
valueOf(statement.getPaymentMethod()),
|
||||||
|
valueOf(statement.getCretNo())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String valueOf(Object value) {
|
||||||
|
return Objects.toString(value, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,24 @@ package com.ruoyi.ccdi.project.service.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisAbnormalDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisAbnormalGroupVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
||||||
@@ -13,16 +29,25 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
|||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -43,9 +68,15 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
@Resource
|
@Resource
|
||||||
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
|
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiProjectRiskDetailWorkbookExporter workbookExporter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CcdiProjectOverviewDashboardVO getDashboard(Long projectId) {
|
public CcdiProjectOverviewDashboardVO getDashboard(Long projectId) {
|
||||||
CcdiProject project = overviewMapper.selectDashboardBaseByProjectId(projectId);
|
CcdiProject project = overviewMapper.selectDashboardBaseByProjectId(projectId);
|
||||||
@@ -73,16 +104,26 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(Long projectId) {
|
public CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(CcdiProjectRiskPeopleQueryDTO queryDTO) {
|
||||||
|
Long projectId = queryDTO.getProjectId();
|
||||||
ensureProjectExists(projectId);
|
ensureProjectExists(projectId);
|
||||||
|
|
||||||
List<CcdiProjectRiskPeopleOverviewItemVO> overviewList = overviewMapper.selectRiskPeopleOverviewByProjectId(projectId)
|
Page<CcdiProjectEmployeeRiskAggregateVO> page = new Page<>(
|
||||||
|
defaultRiskPeoplePageNum(queryDTO.getPageNum()),
|
||||||
|
defaultRiskPeoplePageSize(queryDTO.getPageSize())
|
||||||
|
);
|
||||||
|
Page<CcdiProjectEmployeeRiskAggregateVO> resultPage = overviewMapper.selectRiskPeopleOverviewPage(page, queryDTO);
|
||||||
|
|
||||||
|
List<CcdiProjectRiskPeopleOverviewItemVO> rows = defaultList(resultPage == null ? null : resultPage.getRecords())
|
||||||
.stream()
|
.stream()
|
||||||
.map(aggregate -> buildRiskPeopleItem(projectId, aggregate))
|
.map(aggregate -> buildRiskPeopleItem(projectId, aggregate))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
CcdiProjectRiskPeopleOverviewVO overview = new CcdiProjectRiskPeopleOverviewVO();
|
CcdiProjectRiskPeopleOverviewVO overview = new CcdiProjectRiskPeopleOverviewVO();
|
||||||
overview.setOverviewList(overviewList);
|
overview.setRows(rows);
|
||||||
|
overview.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||||
|
overview.setPageNum(page.getCurrent());
|
||||||
|
overview.setPageSize(page.getSize());
|
||||||
return overview;
|
return overview;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +141,31 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
return topRiskPeople;
|
return topRiskPeople;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectPersonAnalysisDetailVO getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
CcdiProjectPersonAnalysisBasicInfoVO basicInfo = overviewMapper.selectPersonAnalysisBasicInfo(
|
||||||
|
queryDTO.getProjectId(),
|
||||||
|
queryDTO.getStaffIdCard()
|
||||||
|
);
|
||||||
|
List<CcdiBankStatementListVO> statementRows = defaultList(overviewMapper.selectPersonAnalysisStatementRows(
|
||||||
|
queryDTO.getProjectId(),
|
||||||
|
queryDTO.getStaffIdCard()
|
||||||
|
));
|
||||||
|
List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows = defaultList(overviewMapper.selectPersonAnalysisObjectRows(
|
||||||
|
queryDTO.getProjectId(),
|
||||||
|
queryDTO.getStaffIdCard()
|
||||||
|
));
|
||||||
|
attachStatementHitTags(statementRows, queryDTO.getProjectId());
|
||||||
|
normalizeObjectRows(objectRows);
|
||||||
|
|
||||||
|
CcdiProjectPersonAnalysisDetailVO detail = new CcdiProjectPersonAnalysisDetailVO();
|
||||||
|
detail.setBasicInfo(basicInfo == null ? new CcdiProjectPersonAnalysisBasicInfoVO() : basicInfo);
|
||||||
|
detail.setAbnormalDetail(buildAbnormalDetail(statementRows, objectRows));
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CcdiProjectRiskModelCardsVO getRiskModelCards(Long projectId) {
|
public CcdiProjectRiskModelCardsVO getRiskModelCards(Long projectId) {
|
||||||
ensureProjectExists(projectId);
|
ensureProjectExists(projectId);
|
||||||
@@ -131,6 +197,91 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
return people;
|
return people;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectSuspiciousTransactionPageVO getSuspiciousTransactions(
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
normalizeSuspiciousTransactionQuery(queryDTO);
|
||||||
|
|
||||||
|
Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(
|
||||||
|
defaultPageNum(queryDTO.getPageNum()),
|
||||||
|
defaultPageSize(queryDTO.getPageSize())
|
||||||
|
);
|
||||||
|
Page<CcdiProjectSuspiciousTransactionItemVO> resultPage =
|
||||||
|
overviewMapper.selectSuspiciousTransactionPage(page, queryDTO);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionPageVO result = new CcdiProjectSuspiciousTransactionPageVO();
|
||||||
|
result.setRows(defaultList(resultPage == null ? null : resultPage.getRecords()));
|
||||||
|
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CcdiProjectSuspiciousTransactionExcel> exportSuspiciousTransactions(
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
normalizeSuspiciousTransactionQuery(queryDTO);
|
||||||
|
|
||||||
|
return defaultList(overviewMapper.selectSuspiciousTransactionList(queryDTO)).stream()
|
||||||
|
.map(this::buildSuspiciousTransactionExcelRow)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CcdiProjectRiskPeopleOverviewExcel> exportRiskPeopleOverview(Long projectId) {
|
||||||
|
ensureProjectExists(projectId);
|
||||||
|
|
||||||
|
return defaultList(overviewMapper.selectRiskPeopleOverviewList(projectId)).stream()
|
||||||
|
.map(aggregate -> buildRiskPeopleItem(projectId, aggregate))
|
||||||
|
.map(this::buildRiskPeopleExcelRow)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectEmployeeCreditNegativePageVO getEmployeeCreditNegative(
|
||||||
|
CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
Page<CcdiProjectEmployeeCreditNegativeItemVO> page = new Page<>(
|
||||||
|
defaultPageNum(queryDTO.getPageNum()),
|
||||||
|
defaultPageSize(queryDTO.getPageSize())
|
||||||
|
);
|
||||||
|
Page<CcdiProjectEmployeeCreditNegativeItemVO> resultPage =
|
||||||
|
overviewMapper.selectEmployeeCreditNegativePage(page, queryDTO);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativePageVO result = new CcdiProjectEmployeeCreditNegativePageVO();
|
||||||
|
result.setRows(defaultList(resultPage == null ? null : resultPage.getRecords()));
|
||||||
|
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setSuspiciousType("ALL");
|
||||||
|
|
||||||
|
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows = exportSuspiciousTransactions(queryDTO);
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows = exportEmployeeCreditNegative(projectId);
|
||||||
|
try {
|
||||||
|
workbookExporter.export(response, projectId, suspiciousRows, creditRows);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ServiceException("导出风险明细失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CcdiProjectEmployeeCreditNegativeExcel> exportEmployeeCreditNegative(Long projectId) {
|
||||||
|
ensureProjectExists(projectId);
|
||||||
|
|
||||||
|
return defaultList(overviewMapper.selectEmployeeCreditNegativeList(projectId)).stream()
|
||||||
|
.map(this::buildEmployeeCreditNegativeExcelRow)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void refreshOverviewEmployeeResults(Long projectId, String operator) {
|
public void refreshOverviewEmployeeResults(Long projectId, String operator) {
|
||||||
@@ -197,6 +348,18 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CcdiProjectRiskPeopleOverviewExcel buildRiskPeopleExcelRow(CcdiProjectRiskPeopleOverviewItemVO item) {
|
||||||
|
CcdiProjectRiskPeopleOverviewExcel row = new CcdiProjectRiskPeopleOverviewExcel();
|
||||||
|
row.setName(item.getName());
|
||||||
|
row.setIdNo(item.getIdNo());
|
||||||
|
row.setDepartment(item.getDepartment());
|
||||||
|
row.setRiskCount(item.getRiskCount());
|
||||||
|
row.setRiskLevel(item.getRiskLevel());
|
||||||
|
row.setModelCount(item.getModelCount());
|
||||||
|
row.setRiskPoint(item.getRiskPoint());
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureProjectExists(Long projectId) {
|
private void ensureProjectExists(Long projectId) {
|
||||||
getRequiredProject(projectId);
|
getRequiredProject(projectId);
|
||||||
}
|
}
|
||||||
@@ -209,6 +372,14 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
|
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void normalizeSuspiciousTransactionQuery(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||||
|
if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) {
|
||||||
|
queryDTO.setSuspiciousType("ALL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
queryDTO.setSuspiciousType(queryDTO.getSuspiciousType().trim().toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) {
|
private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) {
|
||||||
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
|
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
|
||||||
stat.setKey(key);
|
stat.setKey(key);
|
||||||
@@ -241,6 +412,14 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
return value == null ? 0 : value;
|
return value == null ? 0 : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long defaultRiskPeoplePageNum(Integer pageNum) {
|
||||||
|
return pageNum == null || pageNum <= 0 ? 1L : pageNum.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long defaultRiskPeoplePageSize(Integer pageSize) {
|
||||||
|
return pageSize == null || pageSize <= 0 ? 5L : pageSize.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
private long defaultPageNum(Integer pageNum) {
|
private long defaultPageNum(Integer pageNum) {
|
||||||
return pageNum == null || pageNum < 1 ? 1L : pageNum.longValue();
|
return pageNum == null || pageNum < 1 ? 1L : pageNum.longValue();
|
||||||
}
|
}
|
||||||
@@ -253,6 +432,118 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
return value == null ? List.of() : value;
|
return value == null ? List.of() : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CcdiProjectSuspiciousTransactionExcel buildSuspiciousTransactionExcelRow(
|
||||||
|
CcdiProjectSuspiciousTransactionItemVO item
|
||||||
|
) {
|
||||||
|
CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel();
|
||||||
|
row.setTrxDate(item.getTrxDate());
|
||||||
|
row.setSuspiciousPersonName(item.getSuspiciousPersonName());
|
||||||
|
row.setRelatedPersonName(item.getRelatedPersonName());
|
||||||
|
row.setRelatedStaffDisplay(formatRelatedStaff(item.getRelatedStaffName(), item.getRelatedStaffCode()));
|
||||||
|
row.setRelationType(item.getRelationType());
|
||||||
|
row.setSummaryAndCashType(formatSummaryAndCashType(item.getUserMemo(), item.getCashType()));
|
||||||
|
row.setDisplayAmount(item.getDisplayAmount());
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectEmployeeCreditNegativeExcel buildEmployeeCreditNegativeExcelRow(
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO item
|
||||||
|
) {
|
||||||
|
CcdiProjectEmployeeCreditNegativeExcel row = new CcdiProjectEmployeeCreditNegativeExcel();
|
||||||
|
row.setPersonName(item.getPersonName());
|
||||||
|
row.setPersonId(item.getPersonId());
|
||||||
|
row.setQueryDate(item.getQueryDate());
|
||||||
|
row.setCivilCnt(item.getCivilCnt());
|
||||||
|
row.setCivilLmt(item.getCivilLmt());
|
||||||
|
row.setEnforceCnt(item.getEnforceCnt());
|
||||||
|
row.setEnforceLmt(item.getEnforceLmt());
|
||||||
|
row.setAdmCnt(item.getAdmCnt());
|
||||||
|
row.setAdmLmt(item.getAdmLmt());
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatRelatedStaff(String relatedStaffName, String relatedStaffCode) {
|
||||||
|
if (relatedStaffName == null || relatedStaffName.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (relatedStaffCode == null || relatedStaffCode.isBlank()) {
|
||||||
|
return relatedStaffName;
|
||||||
|
}
|
||||||
|
return relatedStaffName + "(" + relatedStaffCode + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatSummaryAndCashType(String userMemo, String cashType) {
|
||||||
|
String safeMemo = userMemo == null ? "" : userMemo;
|
||||||
|
String safeCashType = cashType == null ? "" : cashType;
|
||||||
|
return safeMemo + "/" + safeCashType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectPersonAnalysisAbnormalDetailVO buildAbnormalDetail(
|
||||||
|
List<CcdiBankStatementListVO> statementRows,
|
||||||
|
List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows
|
||||||
|
) {
|
||||||
|
List<CcdiProjectPersonAnalysisAbnormalGroupVO> groups = new ArrayList<>();
|
||||||
|
if (!statementRows.isEmpty()) {
|
||||||
|
groups.add(buildAbnormalGroup("BANK_STATEMENT", "流水异常明细", "BANK_STATEMENT", statementRows));
|
||||||
|
}
|
||||||
|
if (!objectRows.isEmpty()) {
|
||||||
|
groups.add(buildAbnormalGroup("RELATED_OBJECT", "异常对象摘要", "OBJECT", objectRows));
|
||||||
|
}
|
||||||
|
CcdiProjectPersonAnalysisAbnormalDetailVO abnormalDetail = new CcdiProjectPersonAnalysisAbnormalDetailVO();
|
||||||
|
abnormalDetail.setGroups(groups);
|
||||||
|
return abnormalDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectPersonAnalysisAbnormalGroupVO buildAbnormalGroup(
|
||||||
|
String groupCode,
|
||||||
|
String groupName,
|
||||||
|
String groupType,
|
||||||
|
List<?> records
|
||||||
|
) {
|
||||||
|
CcdiProjectPersonAnalysisAbnormalGroupVO group = new CcdiProjectPersonAnalysisAbnormalGroupVO();
|
||||||
|
group.setGroupCode(groupCode);
|
||||||
|
group.setGroupName(groupName);
|
||||||
|
group.setGroupType(groupType);
|
||||||
|
group.setRecords(records);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachStatementHitTags(List<CcdiBankStatementListVO> statementRows, Long projectId) {
|
||||||
|
if (statementRows.isEmpty() || projectId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Long> bankStatementIds = statementRows.stream()
|
||||||
|
.map(CcdiBankStatementListVO::getBankStatementId)
|
||||||
|
.filter(item -> item != null)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (bankStatementIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = defaultList(
|
||||||
|
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds)
|
||||||
|
).stream().filter(item -> item.getBankStatementId() != null)
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
CcdiBankStatementHitTagVO::getBankStatementId,
|
||||||
|
LinkedHashMap::new,
|
||||||
|
Collectors.toList()
|
||||||
|
));
|
||||||
|
statementRows.forEach(row -> row.setHitTags(new ArrayList<>(
|
||||||
|
hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalizeObjectRows(List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows) {
|
||||||
|
objectRows.forEach(row -> {
|
||||||
|
if (row.getRiskTags() == null) {
|
||||||
|
row.setRiskTags(new ArrayList<>());
|
||||||
|
}
|
||||||
|
if (row.getExtraFields() == null) {
|
||||||
|
row.setExtraFields(new ArrayList<>());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private CcdiProject getRequiredProject(Long projectId) {
|
private CcdiProject getRequiredProject(Long projectId) {
|
||||||
CcdiProject project = projectMapper.selectById(projectId);
|
CcdiProject project = projectMapper.selectById(projectId);
|
||||||
if (project == null) {
|
if (project == null) {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import com.ruoyi.common.utils.file.FileUtils;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险明细工作簿导出器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class CcdiProjectRiskDetailWorkbookExporter {
|
||||||
|
|
||||||
|
private static final String CONTENT_TYPE =
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||||
|
|
||||||
|
public void export(
|
||||||
|
HttpServletResponse response,
|
||||||
|
Long projectId,
|
||||||
|
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows,
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows
|
||||||
|
) throws IOException {
|
||||||
|
response.setContentType(CONTENT_TYPE);
|
||||||
|
FileUtils.setAttachmentResponseHeader(response, "风险明细_" + projectId + ".xlsx");
|
||||||
|
|
||||||
|
try (Workbook workbook = new XSSFWorkbook()) {
|
||||||
|
writeSuspiciousSheet(workbook.createSheet("涉疑交易明细"), suspiciousRows);
|
||||||
|
writeCreditSheet(workbook.createSheet("员工负面征信信息"), creditRows);
|
||||||
|
writeAbnormalAccountSheet(workbook.createSheet("异常账户人员信息"));
|
||||||
|
workbook.write(response.getOutputStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSuspiciousSheet(Sheet sheet, List<CcdiProjectSuspiciousTransactionExcel> rows) {
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
String[] headers = { "交易时间", "可疑人员", "关联人", "关联员工", "关系", "摘要/交易类型", "交易金额" };
|
||||||
|
writeHeader(header, headers);
|
||||||
|
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
CcdiProjectSuspiciousTransactionExcel item = rows.get(i);
|
||||||
|
Row row = sheet.createRow(i + 1);
|
||||||
|
row.createCell(0).setCellValue(safeText(item.getTrxDate()));
|
||||||
|
row.createCell(1).setCellValue(safeText(item.getSuspiciousPersonName()));
|
||||||
|
row.createCell(2).setCellValue(safeText(item.getRelatedPersonName()));
|
||||||
|
row.createCell(3).setCellValue(safeText(item.getRelatedStaffDisplay()));
|
||||||
|
row.createCell(4).setCellValue(safeText(item.getRelationType()));
|
||||||
|
row.createCell(5).setCellValue(safeText(item.getSummaryAndCashType()));
|
||||||
|
row.createCell(6).setCellValue(safeNumber(item.getDisplayAmount()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeCreditSheet(Sheet sheet, List<CcdiProjectEmployeeCreditNegativeExcel> rows) {
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
String[] headers = {
|
||||||
|
"员工姓名",
|
||||||
|
"身份证号",
|
||||||
|
"最近征信查询日期",
|
||||||
|
"民事案件笔数",
|
||||||
|
"民事案件金额",
|
||||||
|
"强制执行笔数",
|
||||||
|
"强制执行金额",
|
||||||
|
"行政处罚笔数",
|
||||||
|
"行政处罚金额"
|
||||||
|
};
|
||||||
|
writeHeader(header, headers);
|
||||||
|
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
CcdiProjectEmployeeCreditNegativeExcel item = rows.get(i);
|
||||||
|
Row row = sheet.createRow(i + 1);
|
||||||
|
row.createCell(0).setCellValue(safeText(item.getPersonName()));
|
||||||
|
row.createCell(1).setCellValue(safeText(item.getPersonId()));
|
||||||
|
row.createCell(2).setCellValue(safeText(item.getQueryDate()));
|
||||||
|
row.createCell(3).setCellValue(item.getCivilCnt() == null ? 0 : item.getCivilCnt());
|
||||||
|
row.createCell(4).setCellValue(safeNumber(item.getCivilLmt()));
|
||||||
|
row.createCell(5).setCellValue(item.getEnforceCnt() == null ? 0 : item.getEnforceCnt());
|
||||||
|
row.createCell(6).setCellValue(safeNumber(item.getEnforceLmt()));
|
||||||
|
row.createCell(7).setCellValue(item.getAdmCnt() == null ? 0 : item.getAdmCnt());
|
||||||
|
row.createCell(8).setCellValue(safeNumber(item.getAdmLmt()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeAbnormalAccountSheet(Sheet sheet) {
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
String[] headers = { "账号", "开户人", "银行", "异常类型", "异常发生时间", "状态" };
|
||||||
|
writeHeader(header, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeHeader(Row row, String[] headers) {
|
||||||
|
for (int i = 0; i < headers.length; i++) {
|
||||||
|
row.createCell(i).setCellValue(headers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeText(String value) {
|
||||||
|
return value == null ? "" : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double safeNumber(BigDecimal value) {
|
||||||
|
return value == null ? 0D : value.doubleValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
|
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
@@ -17,11 +20,15 @@ import com.ruoyi.lsfx.domain.response.GetTokenResponse;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +46,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
|||||||
@Resource
|
@Resource
|
||||||
private LsfxAnalysisClient lsfxAnalysisClient;
|
private LsfxAnalysisClient lsfxAnalysisClient;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
|
public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
|
||||||
@@ -114,6 +124,30 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
|||||||
return projectMapper.selectProjectPage(page, queryDTO);
|
return projectMapper.selectProjectPage(page, queryDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO) {
|
||||||
|
return projectMapper.selectHistoryProjects(queryDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator) {
|
||||||
|
CcdiProjectSaveDTO saveDTO = new CcdiProjectSaveDTO();
|
||||||
|
saveDTO.setProjectName(dto.getProjectName());
|
||||||
|
saveDTO.setDescription(dto.getDescription());
|
||||||
|
saveDTO.setConfigType("default");
|
||||||
|
CcdiProjectVO project = createProject(saveDTO);
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
|
applicationEventPublisher.publishEvent(
|
||||||
|
new CcdiProjectHistoryImportSubmittedEvent(project.getProjectId(), project.getLsfxProjectId(), dto, operator)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CcdiProjectStatusCountsVO getStatusCounts() {
|
public CcdiProjectStatusCountsVO getStatusCounts() {
|
||||||
CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO();
|
CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO();
|
||||||
@@ -152,6 +186,26 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void archiveProject(Long projectId, String operator) {
|
||||||
|
CcdiProject project = getRequiredProject(projectId);
|
||||||
|
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
|
||||||
|
throw new ServiceException("项目已归档,无需重复操作");
|
||||||
|
}
|
||||||
|
if (!CcdiProjectStatusConstants.COMPLETED.equals(project.getStatus())) {
|
||||||
|
throw new ServiceException("仅已完成项目允许归档");
|
||||||
|
}
|
||||||
|
project.setStatus(CcdiProjectStatusConstants.ARCHIVED);
|
||||||
|
project.setIsArchived(1);
|
||||||
|
project.setUpdateBy(operator);
|
||||||
|
project.setUpdateTime(new Date());
|
||||||
|
projectMapper.updateById(project);
|
||||||
|
log.info("【项目】项目状态变更: projectId={}, projectName={}, oldStatus={}, oldStatusLabel={}, newStatus={}, newStatusLabel={}, operator={}",
|
||||||
|
project.getProjectId(), project.getProjectName(), CcdiProjectStatusConstants.COMPLETED,
|
||||||
|
resolveStatusLabel(CcdiProjectStatusConstants.COMPLETED), CcdiProjectStatusConstants.ARCHIVED,
|
||||||
|
resolveStatusLabel(CcdiProjectStatusConstants.ARCHIVED), resolveOperator(operator));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateProjectStatus(Long projectId, String status, String operator) {
|
public void updateProjectStatus(Long projectId, String status, String operator) {
|
||||||
CcdiProject project = getRequiredProject(projectId);
|
CcdiProject project = getRequiredProject(projectId);
|
||||||
@@ -179,6 +233,14 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ensureProjectNotArchived(Long projectId, String message) {
|
||||||
|
CcdiProject project = getRequiredProject(projectId);
|
||||||
|
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
|
||||||
|
throw new ServiceException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void ensureProjectWritable(Long projectId, String message) {
|
public void ensureProjectWritable(Long projectId, String message) {
|
||||||
CcdiProject project = getRequiredProject(projectId);
|
CcdiProject project = getRequiredProject(projectId);
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
package com.ruoyi.ccdi.project.service.impl;
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListItemVO;
|
||||||
@@ -58,6 +74,107 @@ public class CcdiProjectSpecialCheckServiceImpl implements ICcdiProjectSpecialCh
|
|||||||
return detail;
|
return detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectExtendedPurchaseListVO getExtendedPurchaseList(CcdiProjectExtendedPurchaseQueryDTO queryDTO) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
Page<CcdiProjectExtendedPurchaseListItemVO> page = new Page<>(
|
||||||
|
defaultPageNum(queryDTO.getPageNum()),
|
||||||
|
defaultPageSize(queryDTO.getPageSize())
|
||||||
|
);
|
||||||
|
Page<CcdiProjectExtendedPurchaseListItemVO> resultPage = specialCheckMapper.selectExtendedPurchasePage(page, queryDTO);
|
||||||
|
|
||||||
|
CcdiProjectExtendedPurchaseListVO result = new CcdiProjectExtendedPurchaseListVO();
|
||||||
|
result.setRows(resultPage == null ? List.of() : defaultList(resultPage.getRecords()));
|
||||||
|
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectExtendedPurchaseDetailVO getExtendedPurchaseDetail(
|
||||||
|
CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
CcdiProjectExtendedPurchaseDetailVO detail = specialCheckMapper.selectExtendedPurchaseDetail(
|
||||||
|
queryDTO.getProjectId(),
|
||||||
|
queryDTO.getPurchaseId()
|
||||||
|
);
|
||||||
|
if (detail == null) {
|
||||||
|
throw new ServiceException("当前记录不属于该项目专项核查范围");
|
||||||
|
}
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectExtendedRecruitmentListVO getExtendedRecruitmentList(
|
||||||
|
CcdiProjectExtendedRecruitmentQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
Page<CcdiProjectExtendedRecruitmentListItemVO> page = new Page<>(
|
||||||
|
defaultPageNum(queryDTO.getPageNum()),
|
||||||
|
defaultPageSize(queryDTO.getPageSize())
|
||||||
|
);
|
||||||
|
Page<CcdiProjectExtendedRecruitmentListItemVO> resultPage = specialCheckMapper.selectExtendedRecruitmentPage(
|
||||||
|
page,
|
||||||
|
queryDTO
|
||||||
|
);
|
||||||
|
|
||||||
|
CcdiProjectExtendedRecruitmentListVO result = new CcdiProjectExtendedRecruitmentListVO();
|
||||||
|
result.setRows(resultPage == null ? List.of() : defaultList(resultPage.getRecords()));
|
||||||
|
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectExtendedRecruitmentDetailVO getExtendedRecruitmentDetail(
|
||||||
|
CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
CcdiProjectExtendedRecruitmentDetailVO detail = specialCheckMapper.selectExtendedRecruitmentDetail(
|
||||||
|
queryDTO.getProjectId(),
|
||||||
|
queryDTO.getRecruitId()
|
||||||
|
);
|
||||||
|
if (detail == null) {
|
||||||
|
throw new ServiceException("当前记录不属于该项目专项核查范围");
|
||||||
|
}
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectExtendedTransferListVO getExtendedTransferList(CcdiProjectExtendedTransferQueryDTO queryDTO) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
Page<CcdiProjectExtendedTransferListItemVO> page = new Page<>(
|
||||||
|
defaultPageNum(queryDTO.getPageNum()),
|
||||||
|
defaultPageSize(queryDTO.getPageSize())
|
||||||
|
);
|
||||||
|
Page<CcdiProjectExtendedTransferListItemVO> resultPage = specialCheckMapper.selectExtendedTransferPage(page, queryDTO);
|
||||||
|
|
||||||
|
CcdiProjectExtendedTransferListVO result = new CcdiProjectExtendedTransferListVO();
|
||||||
|
result.setRows(resultPage == null ? List.of() : defaultList(resultPage.getRecords()));
|
||||||
|
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiProjectExtendedTransferDetailVO getExtendedTransferDetail(
|
||||||
|
CcdiProjectExtendedTransferDetailQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
ensureProjectExists(queryDTO.getProjectId());
|
||||||
|
|
||||||
|
CcdiProjectExtendedTransferDetailVO detail = specialCheckMapper.selectExtendedTransferDetail(
|
||||||
|
queryDTO.getProjectId(),
|
||||||
|
queryDTO.getId()
|
||||||
|
);
|
||||||
|
if (detail == null) {
|
||||||
|
throw new ServiceException("当前记录不属于该项目专项核查范围");
|
||||||
|
}
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureProjectExists(Long projectId) {
|
private void ensureProjectExists(Long projectId) {
|
||||||
CcdiProject project = projectMapper.selectById(projectId);
|
CcdiProject project = projectMapper.selectById(projectId);
|
||||||
if (project == null) {
|
if (project == null) {
|
||||||
@@ -112,4 +229,12 @@ public class CcdiProjectSpecialCheckServiceImpl implements ICcdiProjectSpecialCh
|
|||||||
private <T> List<T> defaultList(List<T> list) {
|
private <T> List<T> defaultList(List<T> list) {
|
||||||
return list == null ? List.of() : list;
|
return list == null ? List.of() : list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long defaultPageNum(Integer pageNum) {
|
||||||
|
return pageNum == null || pageNum < 1 ? 1L : pageNum.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long defaultPageSize(Integer pageSize) {
|
||||||
|
return pageSize == null || pageSize < 1 ? 10L : pageSize.longValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="customerAccountNo" column="CUSTOMER_ACCOUNT_NO" />
|
<result property="customerAccountNo" column="CUSTOMER_ACCOUNT_NO" />
|
||||||
<result property="customerBank" column="customer_bank" />
|
<result property="customerBank" column="customer_bank" />
|
||||||
<result property="customerReference" column="customer_reference" />
|
<result property="customerReference" column="customer_reference" />
|
||||||
|
<result property="customerCertNo" column="customer_cert_no" />
|
||||||
|
<result property="customerSocialCreditCode" column="customer_social_credit_code" />
|
||||||
<result property="userMemo" column="USER_MEMO" />
|
<result property="userMemo" column="USER_MEMO" />
|
||||||
<result property="bankComments" column="BANK_COMMENTS" />
|
<result property="bankComments" column="BANK_COMMENTS" />
|
||||||
<result property="bankTrxNumber" column="BANK_TRX_NUMBER" />
|
<result property="bankTrxNumber" column="BANK_TRX_NUMBER" />
|
||||||
@@ -47,16 +49,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectCcdiBankStatementVo">
|
<sql id="selectCcdiBankStatementVo">
|
||||||
select bank_statement_id, project_id, LE_ID, ACCOUNT_ID, group_id,
|
bank_statement_id, project_id, LE_ID, ACCOUNT_ID, group_id,
|
||||||
LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE,
|
LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE,
|
||||||
TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE,
|
TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE,
|
||||||
CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO,
|
CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO,
|
||||||
customer_bank, customer_reference, USER_MEMO, BANK_COMMENTS,
|
customer_bank, customer_reference, customer_cert_no, customer_social_credit_code, USER_MEMO, BANK_COMMENTS,
|
||||||
BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE,
|
BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE,
|
||||||
internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by,
|
internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by,
|
||||||
meta_json, no_balance, begin_balance, end_balance,
|
meta_json, no_balance, begin_balance, end_balance,
|
||||||
override_bs_id, payment_method, cret_no
|
override_bs_id, payment_method, cret_no
|
||||||
from ccdi_bank_statement
|
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<resultMap id="CcdiBankStatementListVOResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO">
|
<resultMap id="CcdiBankStatementListVOResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO">
|
||||||
@@ -297,6 +298,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<include refid="statementOrderBy"/>
|
<include refid="statementOrderBy"/>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectStatementsForHistoryImport" resultMap="CcdiBankStatementResult">
|
||||||
|
SELECT
|
||||||
|
<include refid="selectCcdiBankStatementVo"/>
|
||||||
|
FROM ccdi_bank_statement bs
|
||||||
|
<where>
|
||||||
|
(bs.project_id = #{projectId})
|
||||||
|
AND (bs.batch_id = #{batchId})
|
||||||
|
<if test="startDate != null and startDate != ''">
|
||||||
|
AND (<include refid="parsedTrxDateExpr"/>) <![CDATA[ >= ]]>
|
||||||
|
CASE
|
||||||
|
WHEN LENGTH(TRIM(#{startDate})) = 10
|
||||||
|
THEN STR_TO_DATE(CONCAT(TRIM(#{startDate}), ' 00:00:00'), '%Y-%m-%d %H:%i:%s')
|
||||||
|
ELSE STR_TO_DATE(TRIM(#{startDate}), '%Y-%m-%d %H:%i:%s')
|
||||||
|
END
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null and endDate != ''">
|
||||||
|
AND (<include refid="parsedTrxDateExpr"/>) <![CDATA[ <= ]]>
|
||||||
|
CASE
|
||||||
|
WHEN LENGTH(TRIM(#{endDate})) = 10
|
||||||
|
THEN STR_TO_DATE(CONCAT(TRIM(#{endDate}), ' 23:59:59'), '%Y-%m-%d %H:%i:%s')
|
||||||
|
ELSE STR_TO_DATE(TRIM(#{endDate}), '%Y-%m-%d %H:%i:%s')
|
||||||
|
END
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY bs.batch_sequence ASC, bs.bank_statement_id ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectStatementDetailById" resultMap="CcdiBankStatementDetailVOResultMap">
|
<select id="selectStatementDetailById" resultMap="CcdiBankStatementDetailVOResultMap">
|
||||||
SELECT
|
SELECT
|
||||||
bs.bank_statement_id AS bankStatementId,
|
bs.bank_statement_id AS bankStatementId,
|
||||||
@@ -326,10 +354,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
bs.cret_no AS cretNo,
|
bs.cret_no AS cretNo,
|
||||||
bs.CREATE_DATE AS createDate,
|
bs.CREATE_DATE AS createDate,
|
||||||
fur.file_name AS originalFileName,
|
fur.file_name AS originalFileName,
|
||||||
fur.upload_time AS uploadTime
|
fur.upload_time AS uploadTime,
|
||||||
|
fur.source_project_name AS sourceProjectName
|
||||||
FROM ccdi_bank_statement bs
|
FROM ccdi_bank_statement bs
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT latest_record.project_id, latest_record.log_id, latest_record.file_name, latest_record.upload_time
|
SELECT latest_record.project_id, latest_record.log_id, latest_record.file_name, latest_record.upload_time,
|
||||||
|
latest_record.source_project_name
|
||||||
FROM ccdi_file_upload_record latest_record
|
FROM ccdi_file_upload_record latest_record
|
||||||
INNER JOIN (
|
INNER JOIN (
|
||||||
SELECT project_id, log_id, MAX(id) AS max_id
|
SELECT project_id, log_id, MAX(id) AS max_id
|
||||||
@@ -383,7 +413,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE,
|
LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE,
|
||||||
TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE,
|
TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE,
|
||||||
CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO,
|
CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO,
|
||||||
customer_bank, customer_reference, USER_MEMO, BANK_COMMENTS,
|
customer_bank, customer_reference, customer_cert_no, customer_social_credit_code, USER_MEMO, BANK_COMMENTS,
|
||||||
BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE,
|
BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE,
|
||||||
internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by,
|
internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by,
|
||||||
meta_json, no_balance, begin_balance, end_balance,
|
meta_json, no_balance, begin_balance, end_balance,
|
||||||
@@ -395,7 +425,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
#{item.leAccountName}, #{item.leAccountNo}, #{item.accountingDateId}, #{item.accountingDate},
|
#{item.leAccountName}, #{item.leAccountNo}, #{item.accountingDateId}, #{item.accountingDate},
|
||||||
#{item.trxDate}, #{item.currency}, #{item.amountDr}, #{item.amountCr}, #{item.amountBalance},
|
#{item.trxDate}, #{item.currency}, #{item.amountDr}, #{item.amountCr}, #{item.amountBalance},
|
||||||
#{item.cashType}, #{item.customerLeId}, #{item.customerAccountName}, #{item.customerAccountNo},
|
#{item.cashType}, #{item.customerLeId}, #{item.customerAccountName}, #{item.customerAccountNo},
|
||||||
#{item.customerBank}, #{item.customerReference}, #{item.userMemo}, #{item.bankComments},
|
#{item.customerBank}, #{item.customerReference}, #{item.customerCertNo}, #{item.customerSocialCreditCode}, #{item.userMemo}, #{item.bankComments},
|
||||||
#{item.bankTrxNumber}, #{item.bank}, #{item.trxFlag}, #{item.trxType}, #{item.exceptionType},
|
#{item.bankTrxNumber}, #{item.bank}, #{item.trxFlag}, #{item.trxType}, #{item.exceptionType},
|
||||||
#{item.internalFlag}, #{item.batchId}, #{item.batchSequence}, #{item.createDate}, #{item.createdBy},
|
#{item.internalFlag}, #{item.batchId}, #{item.batchSequence}, #{item.createDate}, #{item.createdBy},
|
||||||
#{item.metaJson}, #{item.noBalance}, #{item.beginBalance}, #{item.endBalance},
|
#{item.metaJson}, #{item.noBalance}, #{item.beginBalance}, #{item.endBalance},
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="fileName" column="file_name" />
|
<result property="fileName" column="file_name" />
|
||||||
<result property="fileSize" column="file_size" />
|
<result property="fileSize" column="file_size" />
|
||||||
<result property="fileStatus" column="file_status" />
|
<result property="fileStatus" column="file_status" />
|
||||||
|
<result property="sourceType" column="source_type" />
|
||||||
|
<result property="sourceProjectId" column="source_project_id" />
|
||||||
|
<result property="sourceProjectName" column="source_project_name" />
|
||||||
<result property="enterpriseNames" column="enterprise_names" />
|
<result property="enterpriseNames" column="enterprise_names" />
|
||||||
<result property="accountNos" column="account_nos" />
|
<result property="accountNos" column="account_nos" />
|
||||||
<result property="errorMessage" column="error_message" />
|
<result property="errorMessage" column="error_message" />
|
||||||
@@ -21,26 +24,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<sql id="selectCcdiFileUploadRecordVo">
|
<sql id="selectCcdiFileUploadRecordVo">
|
||||||
select id, project_id, lsfx_project_id, log_id, file_name, file_size,
|
select id, project_id, lsfx_project_id, log_id, file_name, file_size,
|
||||||
file_status, enterprise_names, account_nos, error_message,
|
file_status, source_type, source_project_id, source_project_name,
|
||||||
upload_time, upload_user
|
enterprise_names, account_nos, error_message, upload_time, upload_user
|
||||||
from ccdi_file_upload_record
|
from ccdi_file_upload_record
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<!-- 批量插入 -->
|
<!-- 批量插入 -->
|
||||||
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into ccdi_file_upload_record (
|
insert into ccdi_file_upload_record (
|
||||||
project_id, lsfx_project_id, file_name, file_size, file_status,
|
project_id, lsfx_project_id, log_id, file_name, file_size, file_status,
|
||||||
|
source_type, source_project_id, source_project_name,
|
||||||
enterprise_names, account_nos, upload_time, upload_user
|
enterprise_names, account_nos, upload_time, upload_user
|
||||||
) values
|
) values
|
||||||
<foreach collection="list" item="item" separator=",">
|
<foreach collection="list" item="item" separator=",">
|
||||||
(
|
(
|
||||||
#{item.projectId}, #{item.lsfxProjectId}, #{item.fileName},
|
#{item.projectId}, #{item.lsfxProjectId}, #{item.logId}, #{item.fileName},
|
||||||
#{item.fileSize}, #{item.fileStatus}, #{item.enterpriseNames},
|
#{item.fileSize}, #{item.fileStatus}, #{item.sourceType},
|
||||||
#{item.accountNos}, #{item.uploadTime}, #{item.uploadUser}
|
#{item.sourceProjectId}, #{item.sourceProjectName},
|
||||||
|
#{item.enterpriseNames}, #{item.accountNos}, #{item.uploadTime}, #{item.uploadUser}
|
||||||
)
|
)
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<select id="selectSuccessfulRecordsByProjectIds" resultMap="CcdiFileUploadRecordResult">
|
||||||
|
<include refid="selectCcdiFileUploadRecordVo"/>
|
||||||
|
where project_id in
|
||||||
|
<foreach collection="projectIds" item="projectId" open="(" separator="," close=")">
|
||||||
|
#{projectId}
|
||||||
|
</foreach>
|
||||||
|
and file_status = 'parsed_success'
|
||||||
|
and log_id is not null
|
||||||
|
order by project_id asc, log_id asc, id asc
|
||||||
|
</select>
|
||||||
|
|
||||||
<!-- 统计各状态文件数量 -->
|
<!-- 统计各状态文件数量 -->
|
||||||
<select id="countByStatus" resultType="java.util.Map">
|
<select id="countByStatus" resultType="java.util.Map">
|
||||||
select file_status as `status`, count(*) as count
|
select file_status as `status`, count(*) as count
|
||||||
|
|||||||
@@ -19,6 +19,15 @@
|
|||||||
<result property="createByName" column="create_by_name"/>
|
<result property="createByName" column="create_by_name"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="ProjectHistoryListItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO">
|
||||||
|
<id property="projectId" column="project_id"/>
|
||||||
|
<result property="projectName" column="project_name"/>
|
||||||
|
<result property="description" column="description"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="isArchived" column="is_archived"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<!-- 分页查询项目列表 -->
|
<!-- 分页查询项目列表 -->
|
||||||
<select id="selectProjectPage" resultMap="ProjectVOResultMap">
|
<select id="selectProjectPage" resultMap="ProjectVOResultMap">
|
||||||
SELECT
|
SELECT
|
||||||
@@ -41,6 +50,24 @@
|
|||||||
ORDER BY p.update_time DESC
|
ORDER BY p.update_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectHistoryProjects" resultMap="ProjectHistoryListItemResultMap">
|
||||||
|
SELECT
|
||||||
|
p.project_id,
|
||||||
|
p.project_name,
|
||||||
|
p.description,
|
||||||
|
p.status,
|
||||||
|
p.is_archived,
|
||||||
|
p.create_time
|
||||||
|
FROM ccdi_project p
|
||||||
|
<where>
|
||||||
|
p.status in ('1', '2')
|
||||||
|
<if test="queryDTO.projectName != null and queryDTO.projectName != ''">
|
||||||
|
AND p.project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY p.update_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
<update id="updateRiskCountsByProjectId">
|
<update id="updateRiskCountsByProjectId">
|
||||||
update ccdi_project
|
update ccdi_project
|
||||||
set high_risk_count = #{highRiskCount},
|
set high_risk_count = #{highRiskCount},
|
||||||
|
|||||||
@@ -33,6 +33,21 @@
|
|||||||
select="selectRiskHitTagsByScope"/>
|
select="selectRiskHitTagsByScope"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="SuspiciousTransactionItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO">
|
||||||
|
<id property="bankStatementId" column="bankStatementId"/>
|
||||||
|
<result property="trxDate" column="trxDate"/>
|
||||||
|
<result property="suspiciousPersonName" column="suspiciousPersonName"/>
|
||||||
|
<result property="relatedPersonName" column="relatedPersonName"/>
|
||||||
|
<result property="relatedStaffName" column="relatedStaffName"/>
|
||||||
|
<result property="relatedStaffCode" column="relatedStaffCode"/>
|
||||||
|
<result property="relationType" column="relationType"/>
|
||||||
|
<result property="userMemo" column="userMemo"/>
|
||||||
|
<result property="cashType" column="cashType"/>
|
||||||
|
<result property="displayAmount" column="displayAmount"/>
|
||||||
|
<result property="hasModelRuleHit" column="hasModelRuleHit"/>
|
||||||
|
<result property="hasNameListHit" column="hasNameListHit"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<sql id="digitTableSql">
|
<sql id="digitTableSql">
|
||||||
select 0 as digit
|
select 0 as digit
|
||||||
union all select 1
|
union all select 1
|
||||||
@@ -208,32 +223,48 @@
|
|||||||
and del_flag = '0'
|
and del_flag = '0'
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectRiskPeopleOverviewByProjectId" resultMap="EmployeeRiskAggregateResultMap">
|
<sql id="riskPeopleOverviewSelectColumns">
|
||||||
|
result.staff_id_card,
|
||||||
|
result.staff_name,
|
||||||
|
result.dept_id,
|
||||||
|
result.dept_name,
|
||||||
|
result.rule_count,
|
||||||
|
result.model_count,
|
||||||
|
result.hit_count,
|
||||||
|
null as top_rule_code,
|
||||||
|
null as top_rule_name,
|
||||||
|
result.risk_point,
|
||||||
|
result.risk_level_code,
|
||||||
|
case
|
||||||
|
when result.risk_level_code = 'HIGH' then '高风险'
|
||||||
|
when result.risk_level_code = 'MEDIUM' then '中风险'
|
||||||
|
else '低风险'
|
||||||
|
end as risk_level_name,
|
||||||
|
case
|
||||||
|
when result.risk_level_code = 'HIGH' then 1
|
||||||
|
when result.risk_level_code = 'MEDIUM' then 2
|
||||||
|
else 3
|
||||||
|
end as risk_level_sort
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="riskPeopleOverviewOrderBy">
|
||||||
|
order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectRiskPeopleOverviewPage" resultMap="EmployeeRiskAggregateResultMap">
|
||||||
select
|
select
|
||||||
result.staff_id_card,
|
<include refid="riskPeopleOverviewSelectColumns"/>
|
||||||
result.staff_name,
|
from ccdi_project_overview_employee_result result
|
||||||
result.dept_id,
|
where result.project_id = #{query.projectId}
|
||||||
result.dept_name,
|
<include refid="riskPeopleOverviewOrderBy"/>
|
||||||
result.rule_count,
|
</select>
|
||||||
result.model_count,
|
|
||||||
result.hit_count,
|
<select id="selectRiskPeopleOverviewList" resultMap="EmployeeRiskAggregateResultMap">
|
||||||
null as top_rule_code,
|
select
|
||||||
null as top_rule_name,
|
<include refid="riskPeopleOverviewSelectColumns"/>
|
||||||
result.risk_point,
|
|
||||||
result.risk_level_code,
|
|
||||||
case
|
|
||||||
when result.risk_level_code = 'HIGH' then '高风险'
|
|
||||||
when result.risk_level_code = 'MEDIUM' then '中风险'
|
|
||||||
else '低风险'
|
|
||||||
end as risk_level_name,
|
|
||||||
case
|
|
||||||
when result.risk_level_code = 'HIGH' then 1
|
|
||||||
when result.risk_level_code = 'MEDIUM' then 2
|
|
||||||
else 3
|
|
||||||
end as risk_level_sort
|
|
||||||
from ccdi_project_overview_employee_result result
|
from ccdi_project_overview_employee_result result
|
||||||
where result.project_id = #{projectId}
|
where result.project_id = #{projectId}
|
||||||
order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc
|
<include refid="riskPeopleOverviewOrderBy"/>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectTopRiskPeopleByProjectId" resultMap="EmployeeRiskAggregateResultMap">
|
<select id="selectTopRiskPeopleByProjectId" resultMap="EmployeeRiskAggregateResultMap">
|
||||||
@@ -338,6 +369,281 @@
|
|||||||
order by result.staff_name asc, result.staff_id_card asc
|
order by result.staff_name asc, result.staff_id_card asc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<sql id="suspiciousTransactionBaseSql">
|
||||||
|
select
|
||||||
|
bs.bank_statement_id as bankStatementId,
|
||||||
|
bs.TRX_DATE as trxDate,
|
||||||
|
bs.USER_MEMO as userMemo,
|
||||||
|
bs.CASH_TYPE as cashType,
|
||||||
|
case
|
||||||
|
when ifnull(bs.AMOUNT_CR, 0) > 0 then bs.AMOUNT_CR
|
||||||
|
when ifnull(bs.AMOUNT_DR, 0) > 0 then -bs.AMOUNT_DR
|
||||||
|
else 0
|
||||||
|
end as displayAmount,
|
||||||
|
coalesce(relation.relation_name, direct_staff.name, bs.CUSTOMER_ACCOUNT_NAME) as relatedPersonName,
|
||||||
|
coalesce(family_staff.name, direct_staff.name) as relatedStaffName,
|
||||||
|
cast(coalesce(family_staff.staff_id, direct_staff.staff_id) as char) as relatedStaffCode,
|
||||||
|
case
|
||||||
|
when direct_staff.id_card is not null then '本人'
|
||||||
|
when relation.relation_type is not null and trim(relation.relation_type) != '' then relation.relation_type
|
||||||
|
else '关联人'
|
||||||
|
end as relationType
|
||||||
|
from ccdi_bank_statement bs
|
||||||
|
left join ccdi_base_staff direct_staff
|
||||||
|
on bs.cret_no = direct_staff.id_card
|
||||||
|
left join ccdi_staff_fmy_relation relation
|
||||||
|
on relation.status = 1
|
||||||
|
and relation.relation_cert_no = bs.cret_no
|
||||||
|
left join ccdi_base_staff family_staff
|
||||||
|
on relation.person_id = family_staff.id_card
|
||||||
|
where bs.project_id = #{query.projectId}
|
||||||
|
and (direct_staff.id_card is not null or relation.person_id is not null)
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="suspiciousTransactionModelHitSql">
|
||||||
|
select distinct
|
||||||
|
tr.bank_statement_id as bankStatementId
|
||||||
|
from ccdi_bank_statement_tag_result tr
|
||||||
|
where tr.project_id = #{query.projectId}
|
||||||
|
and tr.bank_statement_id is not null
|
||||||
|
and tr.rule_name like '%可疑%'
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="suspiciousTransactionNameHitSql">
|
||||||
|
select
|
||||||
|
hits.bankStatementId,
|
||||||
|
hits.suspiciousPersonName,
|
||||||
|
hits.matchPriority
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
bs.bank_statement_id as bankStatementId,
|
||||||
|
intermediary.name as suspiciousPersonName,
|
||||||
|
1 as matchPriority
|
||||||
|
from ccdi_bank_statement bs
|
||||||
|
inner join ccdi_biz_intermediary intermediary
|
||||||
|
on trim(bs.customer_cert_no) != ''
|
||||||
|
and intermediary.person_id = bs.customer_cert_no
|
||||||
|
where bs.project_id = #{query.projectId}
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
bs.bank_statement_id as bankStatementId,
|
||||||
|
enterprise.enterprise_name as suspiciousPersonName,
|
||||||
|
2 as matchPriority
|
||||||
|
from ccdi_bank_statement bs
|
||||||
|
inner join ccdi_enterprise_base_info enterprise
|
||||||
|
on trim(bs.customer_social_credit_code) != ''
|
||||||
|
and enterprise.social_credit_code = bs.customer_social_credit_code
|
||||||
|
and enterprise.risk_level = '1'
|
||||||
|
and enterprise.ent_source = 'INTERMEDIARY'
|
||||||
|
where bs.project_id = #{query.projectId}
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
bs.bank_statement_id as bankStatementId,
|
||||||
|
intermediary.name as suspiciousPersonName,
|
||||||
|
3 as matchPriority
|
||||||
|
from ccdi_bank_statement bs
|
||||||
|
inner join ccdi_biz_intermediary intermediary
|
||||||
|
on trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
|
||||||
|
and intermediary.name = bs.CUSTOMER_ACCOUNT_NAME
|
||||||
|
where bs.project_id = #{query.projectId}
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
bs.bank_statement_id as bankStatementId,
|
||||||
|
enterprise.enterprise_name as suspiciousPersonName,
|
||||||
|
3 as matchPriority
|
||||||
|
from ccdi_bank_statement bs
|
||||||
|
inner join ccdi_enterprise_base_info enterprise
|
||||||
|
on trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
|
||||||
|
and enterprise.enterprise_name = bs.CUSTOMER_ACCOUNT_NAME
|
||||||
|
and enterprise.risk_level = '1'
|
||||||
|
and enterprise.ent_source = 'INTERMEDIARY'
|
||||||
|
where bs.project_id = #{query.projectId}
|
||||||
|
) hits
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="suspiciousTransactionMergedSql">
|
||||||
|
select
|
||||||
|
base.bankStatementId,
|
||||||
|
base.trxDate,
|
||||||
|
base.relatedPersonName,
|
||||||
|
base.relatedStaffName,
|
||||||
|
base.relatedStaffCode,
|
||||||
|
base.relationType,
|
||||||
|
base.userMemo,
|
||||||
|
base.cashType,
|
||||||
|
base.displayAmount,
|
||||||
|
1 as hasModelRuleHit,
|
||||||
|
0 as hasNameListHit,
|
||||||
|
null as suspiciousPersonName,
|
||||||
|
null as matchPriority
|
||||||
|
from (
|
||||||
|
<include refid="suspiciousTransactionBaseSql"/>
|
||||||
|
) base
|
||||||
|
inner join (
|
||||||
|
<include refid="suspiciousTransactionModelHitSql"/>
|
||||||
|
) model_hits on model_hits.bankStatementId = base.bankStatementId
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
base.bankStatementId,
|
||||||
|
base.trxDate,
|
||||||
|
base.relatedPersonName,
|
||||||
|
base.relatedStaffName,
|
||||||
|
base.relatedStaffCode,
|
||||||
|
base.relationType,
|
||||||
|
base.userMemo,
|
||||||
|
base.cashType,
|
||||||
|
base.displayAmount,
|
||||||
|
0 as hasModelRuleHit,
|
||||||
|
1 as hasNameListHit,
|
||||||
|
name_hits.suspiciousPersonName,
|
||||||
|
name_hits.matchPriority
|
||||||
|
from (
|
||||||
|
<include refid="suspiciousTransactionBaseSql"/>
|
||||||
|
) base
|
||||||
|
inner join (
|
||||||
|
<include refid="suspiciousTransactionNameHitSql"/>
|
||||||
|
) name_hits on name_hits.bankStatementId = base.bankStatementId
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="suspiciousTransactionAggregatedSql">
|
||||||
|
select
|
||||||
|
merged.bankStatementId,
|
||||||
|
max(merged.trxDate) as trxDate,
|
||||||
|
coalesce(
|
||||||
|
substring_index(
|
||||||
|
min(
|
||||||
|
case
|
||||||
|
when merged.suspiciousPersonName is not null and merged.suspiciousPersonName != ''
|
||||||
|
then concat(lpad(merged.matchPriority, 2, '0'), '|', merged.suspiciousPersonName)
|
||||||
|
else null
|
||||||
|
end
|
||||||
|
),
|
||||||
|
'|',
|
||||||
|
-1
|
||||||
|
),
|
||||||
|
max(merged.relatedPersonName)
|
||||||
|
) as suspiciousPersonName,
|
||||||
|
max(merged.relatedPersonName) as relatedPersonName,
|
||||||
|
max(merged.relatedStaffName) as relatedStaffName,
|
||||||
|
max(merged.relatedStaffCode) as relatedStaffCode,
|
||||||
|
max(merged.relationType) as relationType,
|
||||||
|
max(merged.userMemo) as userMemo,
|
||||||
|
max(merged.cashType) as cashType,
|
||||||
|
max(merged.displayAmount) as displayAmount,
|
||||||
|
max(merged.hasModelRuleHit) as hasModelRuleHit,
|
||||||
|
max(merged.hasNameListHit) as hasNameListHit
|
||||||
|
from (
|
||||||
|
<include refid="suspiciousTransactionMergedSql"/>
|
||||||
|
) merged
|
||||||
|
group by merged.bankStatementId
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<sql id="suspiciousTransactionFilterSql">
|
||||||
|
<choose>
|
||||||
|
<when test="query.suspiciousType == 'NAME_LIST'">
|
||||||
|
where final_result.hasNameListHit = 1
|
||||||
|
</when>
|
||||||
|
<when test="query.suspiciousType == 'MODEL_RULE'">
|
||||||
|
where final_result.hasModelRuleHit = 1
|
||||||
|
</when>
|
||||||
|
<otherwise>
|
||||||
|
where final_result.hasModelRuleHit = 1 or final_result.hasNameListHit = 1
|
||||||
|
</otherwise>
|
||||||
|
</choose>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectSuspiciousTransactionPage" resultMap="SuspiciousTransactionItemResultMap">
|
||||||
|
<!-- rule_name like '%可疑%' -->
|
||||||
|
<!-- ccdi_biz_intermediary -->
|
||||||
|
<!-- ccdi_enterprise_base_info -->
|
||||||
|
<!-- group by merged.bankStatementId -->
|
||||||
|
select
|
||||||
|
final_result.bankStatementId,
|
||||||
|
final_result.trxDate,
|
||||||
|
final_result.suspiciousPersonName,
|
||||||
|
final_result.relatedPersonName,
|
||||||
|
final_result.relatedStaffName,
|
||||||
|
final_result.relatedStaffCode,
|
||||||
|
final_result.relationType,
|
||||||
|
final_result.userMemo,
|
||||||
|
final_result.cashType,
|
||||||
|
final_result.displayAmount,
|
||||||
|
final_result.hasModelRuleHit,
|
||||||
|
final_result.hasNameListHit
|
||||||
|
from (
|
||||||
|
<include refid="suspiciousTransactionAggregatedSql"/>
|
||||||
|
) final_result
|
||||||
|
<include refid="suspiciousTransactionFilterSql"/>
|
||||||
|
order by final_result.trxDate desc, final_result.bankStatementId desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectSuspiciousTransactionList" resultMap="SuspiciousTransactionItemResultMap">
|
||||||
|
select
|
||||||
|
final_result.bankStatementId,
|
||||||
|
final_result.trxDate,
|
||||||
|
final_result.suspiciousPersonName,
|
||||||
|
final_result.relatedPersonName,
|
||||||
|
final_result.relatedStaffName,
|
||||||
|
final_result.relatedStaffCode,
|
||||||
|
final_result.relationType,
|
||||||
|
final_result.userMemo,
|
||||||
|
final_result.cashType,
|
||||||
|
final_result.displayAmount,
|
||||||
|
final_result.hasModelRuleHit,
|
||||||
|
final_result.hasNameListHit
|
||||||
|
from (
|
||||||
|
<include refid="suspiciousTransactionAggregatedSql"/>
|
||||||
|
) final_result
|
||||||
|
<include refid="suspiciousTransactionFilterSql"/>
|
||||||
|
order by final_result.trxDate desc, final_result.bankStatementId desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectEmployeeCreditNegativePage"
|
||||||
|
resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO">
|
||||||
|
select
|
||||||
|
coalesce(neg.person_name, result.staff_name) as personName,
|
||||||
|
neg.person_id as personId,
|
||||||
|
date_format(neg.query_date, '%Y-%m-%d') as queryDate,
|
||||||
|
ifnull(neg.civil_cnt, 0) as civilCnt,
|
||||||
|
ifnull(neg.civil_lmt, 0) as civilLmt,
|
||||||
|
ifnull(neg.enforce_cnt, 0) as enforceCnt,
|
||||||
|
ifnull(neg.enforce_lmt, 0) as enforceLmt,
|
||||||
|
ifnull(neg.adm_cnt, 0) as admCnt,
|
||||||
|
ifnull(neg.adm_lmt, 0) as admLmt
|
||||||
|
from ccdi_project_overview_employee_result result
|
||||||
|
inner join ccdi_credit_negative_info neg
|
||||||
|
on neg.person_id = result.staff_id_card
|
||||||
|
where result.project_id = #{query.projectId}
|
||||||
|
order by neg.query_date desc, neg.person_id asc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectEmployeeCreditNegativeList"
|
||||||
|
resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO">
|
||||||
|
select
|
||||||
|
coalesce(neg.person_name, result.staff_name) as personName,
|
||||||
|
neg.person_id as personId,
|
||||||
|
date_format(neg.query_date, '%Y-%m-%d') as queryDate,
|
||||||
|
ifnull(neg.civil_cnt, 0) as civilCnt,
|
||||||
|
ifnull(neg.civil_lmt, 0) as civilLmt,
|
||||||
|
ifnull(neg.enforce_cnt, 0) as enforceCnt,
|
||||||
|
ifnull(neg.enforce_lmt, 0) as enforceLmt,
|
||||||
|
ifnull(neg.adm_cnt, 0) as admCnt,
|
||||||
|
ifnull(neg.adm_lmt, 0) as admLmt
|
||||||
|
from ccdi_project_overview_employee_result result
|
||||||
|
inner join ccdi_credit_negative_info neg
|
||||||
|
on neg.person_id = result.staff_id_card
|
||||||
|
where result.project_id = #{projectId}
|
||||||
|
order by neg.query_date desc, neg.person_id asc
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectRiskModelNamesByScope" resultType="java.lang.String">
|
<select id="selectRiskModelNamesByScope" resultType="java.lang.String">
|
||||||
select
|
select
|
||||||
json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as model_name
|
json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as model_name
|
||||||
@@ -377,6 +683,94 @@
|
|||||||
json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) asc
|
json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) asc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectPersonAnalysisBasicInfo" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO">
|
||||||
|
select
|
||||||
|
coalesce(staff.name, result.staff_name) as name,
|
||||||
|
result.staff_id_card as idNo,
|
||||||
|
result.staff_code as staffCode,
|
||||||
|
dept.dept_name as department,
|
||||||
|
staff.phone as phone,
|
||||||
|
case
|
||||||
|
when result.risk_level_code = 'HIGH' then '高风险'
|
||||||
|
when result.risk_level_code = 'MEDIUM' then '中风险'
|
||||||
|
else '低风险'
|
||||||
|
end as riskLevel,
|
||||||
|
project.project_name as projectName
|
||||||
|
from ccdi_project_overview_employee_result result
|
||||||
|
left join ccdi_base_staff staff
|
||||||
|
on staff.id_card = result.staff_id_card
|
||||||
|
left join sys_dept dept
|
||||||
|
on dept.dept_id = coalesce(staff.dept_id, result.dept_id)
|
||||||
|
left join ccdi_project project
|
||||||
|
on project.project_id = result.project_id
|
||||||
|
where result.project_id = #{projectId}
|
||||||
|
and result.staff_id_card = #{staffIdCard}
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectPersonAnalysisStatementRows" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO">
|
||||||
|
select distinct
|
||||||
|
bs.bank_statement_id as bankStatementId,
|
||||||
|
bs.TRX_DATE as trxDate,
|
||||||
|
bs.LE_ACCOUNT_NO as leAccountNo,
|
||||||
|
bs.LE_ACCOUNT_NAME as leAccountName,
|
||||||
|
bs.CUSTOMER_ACCOUNT_NAME as customerAccountName,
|
||||||
|
bs.CUSTOMER_ACCOUNT_NO as customerAccountNo,
|
||||||
|
bs.USER_MEMO as userMemo,
|
||||||
|
bs.CASH_TYPE as cashType,
|
||||||
|
case
|
||||||
|
when ifnull(bs.AMOUNT_CR, 0) > 0 then bs.AMOUNT_CR
|
||||||
|
when ifnull(bs.AMOUNT_DR, 0) > 0 then -bs.AMOUNT_DR
|
||||||
|
else 0
|
||||||
|
end as displayAmount
|
||||||
|
from ccdi_bank_statement bs
|
||||||
|
inner join ccdi_bank_statement_tag_result tr
|
||||||
|
on tr.project_id = bs.project_id
|
||||||
|
and tr.bank_statement_id = bs.bank_statement_id
|
||||||
|
left join ccdi_staff_fmy_relation relation
|
||||||
|
on relation.status = 1
|
||||||
|
and relation.relation_cert_no = bs.cret_no
|
||||||
|
where bs.project_id = #{projectId}
|
||||||
|
and (
|
||||||
|
bs.cret_no = #{staffIdCard}
|
||||||
|
or relation.person_id = #{staffIdCard}
|
||||||
|
or tr.object_key = #{staffIdCard}
|
||||||
|
)
|
||||||
|
order by bs.bank_statement_id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectPersonAnalysisObjectRows" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO">
|
||||||
|
select
|
||||||
|
coalesce(max(staff.name), max(relation.relation_name), max(tr.object_key), max(tr.object_type)) as title,
|
||||||
|
max(case
|
||||||
|
when tr.object_type = 'STAFF_ID_CARD' then '员工对象'
|
||||||
|
else tr.object_type
|
||||||
|
end) as subtitle,
|
||||||
|
max(tr.reason_detail) as reasonDetail,
|
||||||
|
max(tr.rule_name) as summary
|
||||||
|
from ccdi_bank_statement_tag_result tr
|
||||||
|
left join ccdi_base_staff staff
|
||||||
|
on tr.object_type = 'STAFF_ID_CARD'
|
||||||
|
and tr.object_key = staff.id_card
|
||||||
|
left join ccdi_staff_fmy_relation relation
|
||||||
|
on relation.status = 1
|
||||||
|
and tr.object_key = relation.relation_cert_no
|
||||||
|
where tr.project_id = #{projectId}
|
||||||
|
and tr.bank_statement_id is null
|
||||||
|
and (
|
||||||
|
tr.object_key = #{staffIdCard}
|
||||||
|
or exists (
|
||||||
|
select 1
|
||||||
|
from ccdi_staff_fmy_relation relation_scope
|
||||||
|
where relation_scope.status = 1
|
||||||
|
and relation_scope.person_id = #{staffIdCard}
|
||||||
|
and relation_scope.relation_cert_no = tr.object_key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
group by coalesce(tr.object_key, tr.object_type), tr.rule_code
|
||||||
|
order by title asc, tr.rule_code asc
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectRiskCountSummaryByProjectId" resultType="map">
|
<select id="selectRiskCountSummaryByProjectId" resultType="map">
|
||||||
select
|
select
|
||||||
coalesce(sum(case when agg.rule_count >= 5 then 1 else 0 end), 0) as highRiskCount,
|
coalesce(sum(case when agg.rule_count >= 5 then 1 else 0 end), 0) as highRiskCount,
|
||||||
|
|||||||
@@ -39,6 +39,34 @@
|
|||||||
<result property="queryDate" column="query_date"/>
|
<result property="queryDate" column="query_date"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="ExtendedPurchaseListItemResultMap"
|
||||||
|
type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO">
|
||||||
|
<id property="purchaseId" column="purchase_id"/>
|
||||||
|
<result property="projectName" column="project_name"/>
|
||||||
|
<result property="subjectName" column="subject_name"/>
|
||||||
|
<result property="applicantName" column="applicant_name"/>
|
||||||
|
<result property="applyDate" column="apply_date"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="ExtendedRecruitmentListItemResultMap"
|
||||||
|
type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListItemVO">
|
||||||
|
<id property="recruitId" column="recruit_id"/>
|
||||||
|
<result property="recruitName" column="recruit_name"/>
|
||||||
|
<result property="posName" column="pos_name"/>
|
||||||
|
<result property="interviewerNameSummary" column="interviewer_name_summary"/>
|
||||||
|
<result property="admitStatus" column="admit_status"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="ExtendedTransferListItemResultMap"
|
||||||
|
type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListItemVO">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="staffName" column="staff_name"/>
|
||||||
|
<result property="transferType" column="transfer_type"/>
|
||||||
|
<result property="deptNameBefore" column="dept_name_before"/>
|
||||||
|
<result property="deptNameAfter" column="dept_name_after"/>
|
||||||
|
<result property="transferDate" column="transfer_date"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<resultMap id="FamilyAssetLiabilityDetailResultMap"
|
<resultMap id="FamilyAssetLiabilityDetailResultMap"
|
||||||
type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO">
|
type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO">
|
||||||
<association property="incomeDetail"
|
<association property="incomeDetail"
|
||||||
@@ -462,4 +490,230 @@
|
|||||||
debt.debt_name asc
|
debt.debt_name asc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExtendedPurchasePage" resultMap="ExtendedPurchaseListItemResultMap">
|
||||||
|
<bind name="projectId" value="query.projectId"/>
|
||||||
|
select
|
||||||
|
p.purchase_id,
|
||||||
|
p.project_name,
|
||||||
|
p.subject_name,
|
||||||
|
p.applicant_name,
|
||||||
|
p.apply_date
|
||||||
|
from ccdi_purchase_transaction p
|
||||||
|
inner join (
|
||||||
|
select distinct scope.staff_name
|
||||||
|
from (
|
||||||
|
<include refid="projectEmployeeScopeSql"/>
|
||||||
|
) scope
|
||||||
|
where scope.staff_name is not null
|
||||||
|
and scope.staff_name != ''
|
||||||
|
) scoped_staff
|
||||||
|
on scoped_staff.staff_name = p.applicant_name
|
||||||
|
<where>
|
||||||
|
<if test="query.applicantName != null and query.applicantName != ''">
|
||||||
|
and p.applicant_name like concat('%', #{query.applicantName}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="query.applyDateStart != null and query.applyDateStart != ''">
|
||||||
|
and p.apply_date >= #{query.applyDateStart}
|
||||||
|
</if>
|
||||||
|
<if test="query.applyDateEnd != null and query.applyDateEnd != ''">
|
||||||
|
and p.apply_date <= #{query.applyDateEnd}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
order by p.apply_date desc, p.create_time desc, p.purchase_id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExtendedPurchaseDetail"
|
||||||
|
resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO">
|
||||||
|
select
|
||||||
|
p.purchase_id,
|
||||||
|
p.purchase_category,
|
||||||
|
p.project_name,
|
||||||
|
p.subject_name,
|
||||||
|
p.subject_desc,
|
||||||
|
p.purchase_qty,
|
||||||
|
p.budget_amount,
|
||||||
|
p.bid_amount,
|
||||||
|
p.actual_amount,
|
||||||
|
p.contract_amount,
|
||||||
|
p.settlement_amount,
|
||||||
|
p.purchase_method,
|
||||||
|
p.supplier_name,
|
||||||
|
p.contact_person,
|
||||||
|
p.contact_phone,
|
||||||
|
p.supplier_uscc,
|
||||||
|
p.supplier_bank_account,
|
||||||
|
p.apply_date,
|
||||||
|
p.plan_approve_date,
|
||||||
|
p.announce_date,
|
||||||
|
p.bid_open_date,
|
||||||
|
p.contract_sign_date,
|
||||||
|
p.expected_delivery_date,
|
||||||
|
p.actual_delivery_date,
|
||||||
|
p.acceptance_date,
|
||||||
|
p.settlement_date,
|
||||||
|
p.applicant_id,
|
||||||
|
p.applicant_name,
|
||||||
|
p.apply_department,
|
||||||
|
p.purchase_leader_id,
|
||||||
|
p.purchase_leader_name,
|
||||||
|
p.purchase_department,
|
||||||
|
p.created_by,
|
||||||
|
p.create_time,
|
||||||
|
p.updated_by,
|
||||||
|
p.update_time
|
||||||
|
from ccdi_purchase_transaction p
|
||||||
|
inner join (
|
||||||
|
select distinct scope.staff_name
|
||||||
|
from (
|
||||||
|
<include refid="projectEmployeeScopeSql"/>
|
||||||
|
) scope
|
||||||
|
where scope.staff_name is not null
|
||||||
|
and scope.staff_name != ''
|
||||||
|
) scoped_staff
|
||||||
|
on scoped_staff.staff_name = p.applicant_name
|
||||||
|
where p.purchase_id = #{purchaseId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExtendedRecruitmentPage" resultMap="ExtendedRecruitmentListItemResultMap">
|
||||||
|
<bind name="projectId" value="query.projectId"/>
|
||||||
|
select distinct r.recruit_id,
|
||||||
|
r.recruit_name,
|
||||||
|
r.pos_name,
|
||||||
|
concat_ws(' / ',
|
||||||
|
nullif(trim(r.interviewer_name1), ''),
|
||||||
|
nullif(trim(r.interviewer_name2), '')
|
||||||
|
) as interviewer_name_summary,
|
||||||
|
r.admit_status
|
||||||
|
from ccdi_staff_recruitment r
|
||||||
|
where exists (
|
||||||
|
select 1
|
||||||
|
from (
|
||||||
|
<include refid="projectEmployeeScopeSql"/>
|
||||||
|
) scope
|
||||||
|
where scope.staff_name is not null
|
||||||
|
and scope.staff_name != ''
|
||||||
|
and (
|
||||||
|
scope.staff_name = r.interviewer_name1
|
||||||
|
or scope.staff_name = r.interviewer_name2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
<if test="query.interviewerName != null and query.interviewerName != ''">
|
||||||
|
and (
|
||||||
|
r.interviewer_name1 like concat('%', #{query.interviewerName}, '%')
|
||||||
|
or r.interviewer_name2 like concat('%', #{query.interviewerName}, '%')
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
order by r.create_time desc, r.recruit_id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExtendedRecruitmentDetail"
|
||||||
|
resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO">
|
||||||
|
select
|
||||||
|
r.recruit_id,
|
||||||
|
r.recruit_name,
|
||||||
|
r.pos_name,
|
||||||
|
r.pos_category,
|
||||||
|
r.pos_desc,
|
||||||
|
r.cand_name,
|
||||||
|
r.cand_edu,
|
||||||
|
r.cand_id,
|
||||||
|
r.cand_school,
|
||||||
|
r.cand_major,
|
||||||
|
r.cand_grad,
|
||||||
|
r.admit_status,
|
||||||
|
r.interviewer_name1,
|
||||||
|
r.interviewer_id1,
|
||||||
|
r.interviewer_name2,
|
||||||
|
r.interviewer_id2,
|
||||||
|
r.created_by,
|
||||||
|
r.create_time,
|
||||||
|
r.updated_by,
|
||||||
|
r.update_time
|
||||||
|
from ccdi_staff_recruitment r
|
||||||
|
where r.recruit_id = #{recruitId}
|
||||||
|
and exists (
|
||||||
|
select 1
|
||||||
|
from (
|
||||||
|
<include refid="projectEmployeeScopeSql"/>
|
||||||
|
) scope
|
||||||
|
where scope.staff_name is not null
|
||||||
|
and scope.staff_name != ''
|
||||||
|
and (
|
||||||
|
scope.staff_name = r.interviewer_name1
|
||||||
|
or scope.staff_name = r.interviewer_name2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExtendedTransferPage" resultMap="ExtendedTransferListItemResultMap">
|
||||||
|
<bind name="projectId" value="query.projectId"/>
|
||||||
|
select
|
||||||
|
t.id,
|
||||||
|
s.name as staff_name,
|
||||||
|
t.transfer_type,
|
||||||
|
t.dept_name_before,
|
||||||
|
t.dept_name_after,
|
||||||
|
t.transfer_date
|
||||||
|
from ccdi_staff_transfer t
|
||||||
|
inner join ccdi_base_staff s
|
||||||
|
on t.staff_id = s.staff_id
|
||||||
|
inner join (
|
||||||
|
select distinct scope.staff_name
|
||||||
|
from (
|
||||||
|
<include refid="projectEmployeeScopeSql"/>
|
||||||
|
) scope
|
||||||
|
where scope.staff_name is not null
|
||||||
|
and scope.staff_name != ''
|
||||||
|
) scope
|
||||||
|
on scope.staff_name = s.name
|
||||||
|
<where>
|
||||||
|
<if test="query.staffName != null and query.staffName != ''">
|
||||||
|
and s.name like concat('%', #{query.staffName}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="query.transferDateStart != null and query.transferDateStart != ''">
|
||||||
|
and t.transfer_date >= #{query.transferDateStart}
|
||||||
|
</if>
|
||||||
|
<if test="query.transferDateEnd != null and query.transferDateEnd != ''">
|
||||||
|
and t.transfer_date <= #{query.transferDateEnd}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
order by t.transfer_date desc, t.create_time desc, t.id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExtendedTransferDetail"
|
||||||
|
resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO">
|
||||||
|
select
|
||||||
|
t.id,
|
||||||
|
t.staff_id,
|
||||||
|
s.name as staff_name,
|
||||||
|
t.transfer_type,
|
||||||
|
t.transfer_sub_type,
|
||||||
|
t.dept_id_before,
|
||||||
|
t.dept_name_before,
|
||||||
|
t.grade_before,
|
||||||
|
t.position_before,
|
||||||
|
t.salary_level_before,
|
||||||
|
t.dept_id_after,
|
||||||
|
t.dept_name_after,
|
||||||
|
t.grade_after,
|
||||||
|
t.position_after,
|
||||||
|
t.salary_level_after,
|
||||||
|
t.transfer_date,
|
||||||
|
t.created_by,
|
||||||
|
t.create_time,
|
||||||
|
t.updated_by,
|
||||||
|
t.update_time
|
||||||
|
from ccdi_staff_transfer t
|
||||||
|
inner join ccdi_base_staff s
|
||||||
|
on t.staff_id = s.staff_id
|
||||||
|
where t.id = #{id}
|
||||||
|
and exists (
|
||||||
|
select 1
|
||||||
|
from (
|
||||||
|
<include refid="projectEmployeeScopeSql"/>
|
||||||
|
) scope
|
||||||
|
where scope.staff_name = s.name
|
||||||
|
)
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class CcdiProjectControllerContractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeArchiveProjectEndpointContract() throws Exception {
|
||||||
|
RequestMapping requestMapping = CcdiProjectController.class.getAnnotation(RequestMapping.class);
|
||||||
|
Method method = CcdiProjectController.class.getMethod("archiveProject", Long.class);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(requestMapping);
|
||||||
|
assertEquals("/ccdi/project", requestMapping.value()[0]);
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/{projectId}/archive", postMapping.value()[0]);
|
||||||
|
assertNotNull(preAuthorize);
|
||||||
|
assertEquals("@ss.hasPermi('ccdi:project:edit')", preAuthorize.value());
|
||||||
|
assertNotNull(operation);
|
||||||
|
assertEquals("归档项目", operation.summary());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeHistoryImportEndpointContracts() throws Exception {
|
||||||
|
Method history = CcdiProjectController.class.getMethod("listHistoryProjects", CcdiProjectQueryDTO.class);
|
||||||
|
GetMapping historyMapping = history.getAnnotation(GetMapping.class);
|
||||||
|
assertNotNull(historyMapping);
|
||||||
|
assertEquals("/history", historyMapping.value()[0]);
|
||||||
|
|
||||||
|
Method importing = CcdiProjectController.class.getMethod("importFromHistory", CcdiProjectImportHistoryDTO.class);
|
||||||
|
PostMapping importMapping = importing.getAnnotation(PostMapping.class);
|
||||||
|
assertNotNull(importMapping);
|
||||||
|
assertEquals("/import", importMapping.value()[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||||
|
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiProjectControllerTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiProjectController controller;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiProjectService projectService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldArchiveProject() throws Exception {
|
||||||
|
try (MockedStatic<com.ruoyi.common.utils.SecurityUtils> mocked = mockStatic(com.ruoyi.common.utils.SecurityUtils.class)) {
|
||||||
|
mocked.when(com.ruoyi.common.utils.SecurityUtils::getUsername).thenReturn("tester");
|
||||||
|
|
||||||
|
AjaxResult result = controller.archiveProject(40L);
|
||||||
|
|
||||||
|
assertEquals(200, result.get("code"));
|
||||||
|
assertEquals("项目归档成功", result.get("msg"));
|
||||||
|
verify(projectService).archiveProject(40L, "tester");
|
||||||
|
}
|
||||||
|
|
||||||
|
Method method = CcdiProjectController.class.getMethod("archiveProject", Long.class);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/{projectId}/archive", postMapping.value()[0]);
|
||||||
|
assertNotNull(preAuthorize);
|
||||||
|
assertEquals("@ss.hasPermi('ccdi:project:edit')", preAuthorize.value());
|
||||||
|
assertNotNull(operation);
|
||||||
|
assertEquals("归档项目", operation.summary());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldImportFromHistoryAndReturnCreatedProject() {
|
||||||
|
CcdiProjectImportHistoryDTO dto = new CcdiProjectImportHistoryDTO();
|
||||||
|
dto.setProjectName("新建项目");
|
||||||
|
|
||||||
|
CcdiProjectVO project = new CcdiProjectVO();
|
||||||
|
project.setProjectId(88L);
|
||||||
|
project.setProjectName("新建项目");
|
||||||
|
when(projectService.importFromHistory(eq(dto), eq("tester"))).thenReturn(project);
|
||||||
|
|
||||||
|
try (MockedStatic<com.ruoyi.common.utils.SecurityUtils> mocked = mockStatic(com.ruoyi.common.utils.SecurityUtils.class)) {
|
||||||
|
mocked.when(com.ruoyi.common.utils.SecurityUtils::getUsername).thenReturn("tester");
|
||||||
|
|
||||||
|
AjaxResult result = controller.importFromHistory(dto);
|
||||||
|
|
||||||
|
assertEquals(200, result.get("code"));
|
||||||
|
assertEquals("项目创建成功", result.get("msg"));
|
||||||
|
assertSame(project, result.get("data"));
|
||||||
|
verify(projectService).importFromHistory(dto, "tester");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
package com.ruoyi.ccdi.project.controller;
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -63,4 +66,119 @@ class CcdiProjectOverviewControllerContractTest {
|
|||||||
assertTrue(fieldNames.contains("pageNum"));
|
assertTrue(fieldNames.contains("pageNum"));
|
||||||
assertTrue(fieldNames.contains("pageSize"));
|
assertTrue(fieldNames.contains("pageSize"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposePersonAnalysisDetailEndpointContract() throws Exception {
|
||||||
|
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||||
|
Class<?> queryDtoClass =
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO");
|
||||||
|
Method method = controllerClass.getMethod("getPersonAnalysisDetail", queryDtoClass);
|
||||||
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(getMapping);
|
||||||
|
assertEquals("/person-analysis/detail", getMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
assertEquals(queryDtoClass, method.getParameterTypes()[0]);
|
||||||
|
assertEquals(AjaxResult.class, method.getReturnType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposePersonAnalysisDetailQueryDtoFields() throws Exception {
|
||||||
|
Class<?> dtoClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO");
|
||||||
|
|
||||||
|
List<String> fieldNames = Arrays.stream(dtoClass.getDeclaredFields())
|
||||||
|
.map(Field::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
assertEquals(List.of("projectId", "staffIdCard"), fieldNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeSuspiciousTransactionsEndpointContract() throws Exception {
|
||||||
|
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||||
|
Class<?> queryDtoClass =
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO");
|
||||||
|
Method method = controllerClass.getMethod("getSuspiciousTransactions", queryDtoClass);
|
||||||
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(getMapping);
|
||||||
|
assertEquals("/suspicious-transactions", getMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
assertEquals(queryDtoClass, method.getParameterTypes()[0]);
|
||||||
|
assertEquals(AjaxResult.class, method.getReturnType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeEmployeeCreditNegativeEndpointContract() throws Exception {
|
||||||
|
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||||
|
Class<?> queryDtoClass =
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO");
|
||||||
|
Method method = controllerClass.getMethod("getEmployeeCreditNegative", queryDtoClass);
|
||||||
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(getMapping);
|
||||||
|
assertEquals("/employee-credit-negative", getMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
assertEquals(queryDtoClass, method.getParameterTypes()[0]);
|
||||||
|
assertEquals(AjaxResult.class, method.getReturnType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeSuspiciousTransactionsExportEndpointContract() throws Exception {
|
||||||
|
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||||
|
Class<?> queryDtoClass =
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO");
|
||||||
|
Method method = controllerClass.getMethod(
|
||||||
|
"exportSuspiciousTransactions",
|
||||||
|
HttpServletResponse.class,
|
||||||
|
queryDtoClass
|
||||||
|
);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/suspicious-transactions/export", postMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeRiskDetailsExportEndpointContract() throws Exception {
|
||||||
|
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||||
|
Method method = controllerClass.getMethod(
|
||||||
|
"exportRiskDetails",
|
||||||
|
HttpServletResponse.class,
|
||||||
|
Long.class
|
||||||
|
);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/risk-details/export", postMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeSuspiciousTransactionsQueryDtoFields() throws Exception {
|
||||||
|
Class<?> dtoClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO");
|
||||||
|
|
||||||
|
List<String> fieldNames = Arrays.stream(dtoClass.getDeclaredFields())
|
||||||
|
.map(Field::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
assertEquals(List.of("projectId", "suspiciousType", "pageNum", "pageSize"), fieldNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeEmployeeCreditNegativeQueryDtoFields() throws Exception {
|
||||||
|
Class<?> dtoClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO");
|
||||||
|
|
||||||
|
List<String> fieldNames = Arrays.stream(dtoClass.getDeclaredFields())
|
||||||
|
.map(Field::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
assertEquals(List.of("projectId", "pageNum", "pageSize"), fieldNames);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,43 @@
|
|||||||
package com.ruoyi.ccdi.project.controller;
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.same;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -69,20 +86,31 @@ class CcdiProjectOverviewControllerTest {
|
|||||||
riskPointTag.setRuleName("多工资转入");
|
riskPointTag.setRuleName("多工资转入");
|
||||||
item.setRiskPointTagList(List.of(riskPointTag));
|
item.setRiskPointTagList(List.of(riskPointTag));
|
||||||
CcdiProjectRiskPeopleOverviewVO overview = new CcdiProjectRiskPeopleOverviewVO();
|
CcdiProjectRiskPeopleOverviewVO overview = new CcdiProjectRiskPeopleOverviewVO();
|
||||||
overview.setOverviewList(List.of(item));
|
Method setRowsMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("setRows", List.class);
|
||||||
when(overviewService.getRiskPeopleOverview(40L)).thenReturn(overview);
|
Method setTotalMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("setTotal", Long.class);
|
||||||
|
Method setPageNumMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("setPageNum", Long.class);
|
||||||
|
Method setPageSizeMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("setPageSize", Long.class);
|
||||||
|
Method getRowsMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("getRows");
|
||||||
|
Method getTotalMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("getTotal");
|
||||||
|
Method getPageNumMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("getPageNum");
|
||||||
|
Method getPageSizeMethod = CcdiProjectRiskPeopleOverviewVO.class.getMethod("getPageSize");
|
||||||
|
setRowsMethod.invoke(overview, List.of(item));
|
||||||
|
setTotalMethod.invoke(overview, 1L);
|
||||||
|
setPageNumMethod.invoke(overview, 1L);
|
||||||
|
setPageSizeMethod.invoke(overview, 5L);
|
||||||
|
|
||||||
AjaxResult result = controller.getRiskPeople(40L);
|
assertEquals("中风险", ((List<CcdiProjectRiskPeopleOverviewItemVO>) getRowsMethod.invoke(overview)).getFirst().getRiskLevel());
|
||||||
|
assertEquals("warning", ((List<CcdiProjectRiskPeopleOverviewItemVO>) getRowsMethod.invoke(overview)).getFirst().getRiskLevelType());
|
||||||
|
assertEquals(4, ((List<CcdiProjectRiskPeopleOverviewItemVO>) getRowsMethod.invoke(overview)).getFirst().getModelCount());
|
||||||
|
assertEquals("SALARY", ((List<CcdiProjectRiskPeopleOverviewItemVO>) getRowsMethod.invoke(overview)).getFirst().getRiskPointTagList().getFirst().getModelCode());
|
||||||
|
assertEquals(1L, getTotalMethod.invoke(overview));
|
||||||
|
assertEquals(1L, getPageNumMethod.invoke(overview));
|
||||||
|
assertEquals(5L, getPageSizeMethod.invoke(overview));
|
||||||
|
|
||||||
assertEquals(200, result.get("code"));
|
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||||
CcdiProjectRiskPeopleOverviewVO data = (CcdiProjectRiskPeopleOverviewVO) result.get("data");
|
"getRiskPeople",
|
||||||
assertEquals("中风险", data.getOverviewList().getFirst().getRiskLevel());
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO")
|
||||||
assertEquals("warning", data.getOverviewList().getFirst().getRiskLevelType());
|
);
|
||||||
assertEquals(4, data.getOverviewList().getFirst().getModelCount());
|
|
||||||
assertEquals("SALARY", data.getOverviewList().getFirst().getRiskPointTagList().getFirst().getModelCode());
|
|
||||||
verify(overviewService).getRiskPeopleOverview(40L);
|
|
||||||
|
|
||||||
Method method = CcdiProjectOverviewController.class.getMethod("getRiskPeople", Long.class);
|
|
||||||
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||||
|
|
||||||
@@ -110,4 +138,207 @@ class CcdiProjectOverviewControllerTest {
|
|||||||
assertNotNull(preAuthorize);
|
assertNotNull(preAuthorize);
|
||||||
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposePersonAnalysisDetailEndpoint() throws Exception {
|
||||||
|
CcdiProjectPersonAnalysisDetailQueryDTO queryDTO = new CcdiProjectPersonAnalysisDetailQueryDTO();
|
||||||
|
queryDTO.setProjectId(40L);
|
||||||
|
queryDTO.setStaffIdCard("330000000000000001");
|
||||||
|
|
||||||
|
CcdiProjectPersonAnalysisBasicInfoVO basicInfo = new CcdiProjectPersonAnalysisBasicInfoVO();
|
||||||
|
basicInfo.setName("李四");
|
||||||
|
CcdiProjectPersonAnalysisDetailVO detail = new CcdiProjectPersonAnalysisDetailVO();
|
||||||
|
detail.setBasicInfo(basicInfo);
|
||||||
|
when(overviewService.getPersonAnalysisDetail(queryDTO)).thenReturn(detail);
|
||||||
|
|
||||||
|
AjaxResult result = controller.getPersonAnalysisDetail(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(200, result.get("code"));
|
||||||
|
CcdiProjectPersonAnalysisDetailVO data = (CcdiProjectPersonAnalysisDetailVO) result.get("data");
|
||||||
|
assertEquals("李四", data.getBasicInfo().getName());
|
||||||
|
verify(overviewService).getPersonAnalysisDetail(queryDTO);
|
||||||
|
|
||||||
|
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||||
|
"getPersonAnalysisDetail",
|
||||||
|
CcdiProjectPersonAnalysisDetailQueryDTO.class
|
||||||
|
);
|
||||||
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
|
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(getMapping);
|
||||||
|
assertEquals("/person-analysis/detail", getMapping.value()[0]);
|
||||||
|
assertNotNull(preAuthorize);
|
||||||
|
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeSuspiciousTransactionsEndpoint() throws Exception {
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||||
|
queryDTO.setProjectId(40L);
|
||||||
|
queryDTO.setSuspiciousType("ALL");
|
||||||
|
queryDTO.setPageNum(1);
|
||||||
|
queryDTO.setPageSize(10);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionPageVO pageVO = new CcdiProjectSuspiciousTransactionPageVO();
|
||||||
|
pageVO.setRows(List.of());
|
||||||
|
pageVO.setTotal(0L);
|
||||||
|
when(overviewService.getSuspiciousTransactions(queryDTO)).thenReturn(pageVO);
|
||||||
|
|
||||||
|
AjaxResult result = controller.getSuspiciousTransactions(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(200, result.get("code"));
|
||||||
|
assertEquals(pageVO, result.get("data"));
|
||||||
|
verify(overviewService).getSuspiciousTransactions(queryDTO);
|
||||||
|
|
||||||
|
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||||
|
"getSuspiciousTransactions",
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO.class
|
||||||
|
);
|
||||||
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
|
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||||
|
|
||||||
|
assertNotNull(getMapping);
|
||||||
|
assertEquals("/suspicious-transactions", getMapping.value()[0]);
|
||||||
|
assertNotNull(preAuthorize);
|
||||||
|
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeEmployeeCreditNegativeEndpoint() throws Exception {
|
||||||
|
CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO = new CcdiProjectEmployeeCreditNegativeQueryDTO();
|
||||||
|
queryDTO.setProjectId(40L);
|
||||||
|
queryDTO.setPageNum(1);
|
||||||
|
queryDTO.setPageSize(5);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO item = new CcdiProjectEmployeeCreditNegativeItemVO();
|
||||||
|
item.setPersonName("李四");
|
||||||
|
item.setPersonId("330000000000000001");
|
||||||
|
item.setCivilCnt(1);
|
||||||
|
item.setCivilLmt(new BigDecimal("10000.00"));
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativePageVO pageVO = new CcdiProjectEmployeeCreditNegativePageVO();
|
||||||
|
pageVO.setRows(List.of(item));
|
||||||
|
pageVO.setTotal(1L);
|
||||||
|
when(overviewService.getEmployeeCreditNegative(queryDTO)).thenReturn(pageVO);
|
||||||
|
|
||||||
|
AjaxResult result = controller.getEmployeeCreditNegative(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(200, result.get("code"));
|
||||||
|
assertEquals(pageVO, result.get("data"));
|
||||||
|
verify(overviewService).getEmployeeCreditNegative(queryDTO);
|
||||||
|
|
||||||
|
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||||
|
"getEmployeeCreditNegative",
|
||||||
|
CcdiProjectEmployeeCreditNegativeQueryDTO.class
|
||||||
|
);
|
||||||
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
|
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(getMapping);
|
||||||
|
assertEquals("/employee-credit-negative", getMapping.value()[0]);
|
||||||
|
assertNotNull(preAuthorize);
|
||||||
|
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeSuspiciousTransactionsExportEndpoint() throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||||
|
CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel();
|
||||||
|
row.setSuspiciousPersonName("张三");
|
||||||
|
row.setDisplayAmount(new java.math.BigDecimal("10.00"));
|
||||||
|
when(overviewService.exportSuspiciousTransactions(same(queryDTO))).thenReturn(List.of(row));
|
||||||
|
|
||||||
|
controller.exportSuspiciousTransactions(response, queryDTO);
|
||||||
|
|
||||||
|
verify(overviewService).exportSuspiciousTransactions(same(queryDTO));
|
||||||
|
assertTrue(response.getContentType().startsWith("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
|
||||||
|
assertTrue(response.getContentAsByteArray().length > 0);
|
||||||
|
|
||||||
|
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||||
|
"exportSuspiciousTransactions",
|
||||||
|
jakarta.servlet.http.HttpServletResponse.class,
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO.class
|
||||||
|
);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/suspicious-transactions/export", postMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeRiskDetailsExportEndpoint() throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
controller.exportRiskDetails(response, 40L);
|
||||||
|
|
||||||
|
verify(overviewService).exportRiskDetails(same(response), same(40L));
|
||||||
|
|
||||||
|
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||||
|
"exportRiskDetails",
|
||||||
|
jakarta.servlet.http.HttpServletResponse.class,
|
||||||
|
Long.class
|
||||||
|
);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/risk-details/export", postMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeRiskPeopleExportContract() throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
CcdiProjectRiskPeopleOverviewExcel row = new CcdiProjectRiskPeopleOverviewExcel();
|
||||||
|
row.setName("张三");
|
||||||
|
row.setRiskPoint("薪酬异常");
|
||||||
|
when(overviewService.exportRiskPeopleOverview(40L)).thenReturn(List.of(row));
|
||||||
|
|
||||||
|
controller.exportRiskPeople(response, 40L);
|
||||||
|
|
||||||
|
verify(overviewService).exportRiskPeopleOverview(40L);
|
||||||
|
assertTrue(response.getContentType().startsWith("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
|
||||||
|
assertTrue(response.getContentAsByteArray().length > 0);
|
||||||
|
|
||||||
|
Method controllerMethod = CcdiProjectOverviewController.class.getMethod(
|
||||||
|
"exportRiskPeople",
|
||||||
|
jakarta.servlet.http.HttpServletResponse.class,
|
||||||
|
Long.class
|
||||||
|
);
|
||||||
|
PostMapping postMapping = controllerMethod.getAnnotation(PostMapping.class);
|
||||||
|
Operation operation = controllerMethod.getAnnotation(Operation.class);
|
||||||
|
PreAuthorize preAuthorize = controllerMethod.getAnnotation(PreAuthorize.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/risk-people/export", postMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
assertEquals("导出风险人员总览", operation.summary());
|
||||||
|
assertNotNull(preAuthorize);
|
||||||
|
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||||
|
|
||||||
|
Method serviceMethod = ICcdiProjectOverviewService.class.getMethod("exportRiskPeopleOverview", Long.class);
|
||||||
|
assertEquals(List.class, serviceMethod.getReturnType());
|
||||||
|
|
||||||
|
assertExcelColumnName("name", "姓名");
|
||||||
|
assertExcelColumnName("idNo", "身份证号");
|
||||||
|
assertExcelColumnName("department", "所属部门");
|
||||||
|
assertExcelColumnName("riskCount", "疑似违规数");
|
||||||
|
assertExcelColumnName("riskLevel", "风险等级");
|
||||||
|
assertExcelColumnName("modelCount", "命中模型数");
|
||||||
|
assertExcelColumnName("riskPoint", "核心异常点");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertExcelColumnName(String fieldName, String expectedColumnName) throws Exception {
|
||||||
|
Field field = CcdiProjectRiskPeopleOverviewExcel.class.getDeclaredField(fieldName);
|
||||||
|
Excel excel = field.getAnnotation(Excel.class);
|
||||||
|
assertNotNull(excel);
|
||||||
|
assertEquals(expectedColumnName, excel.name());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
package com.ruoyi.ccdi.project.controller;
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
|
||||||
@@ -87,4 +99,76 @@ class CcdiProjectSpecialCheckControllerTest {
|
|||||||
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||||
assertNotNull(operation);
|
assertNotNull(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAjaxResultSuccessForExtendedPurchaseEndpoints() {
|
||||||
|
CcdiProjectExtendedPurchaseQueryDTO listQuery = new CcdiProjectExtendedPurchaseQueryDTO();
|
||||||
|
listQuery.setProjectId(40L);
|
||||||
|
CcdiProjectExtendedPurchaseListVO listVO = new CcdiProjectExtendedPurchaseListVO();
|
||||||
|
when(specialCheckService.getExtendedPurchaseList(listQuery)).thenReturn(listVO);
|
||||||
|
|
||||||
|
AjaxResult listResult = controller.getExtendedPurchaseList(listQuery);
|
||||||
|
|
||||||
|
assertEquals(200, listResult.get("code"));
|
||||||
|
assertEquals(listVO, listResult.get("data"));
|
||||||
|
|
||||||
|
CcdiProjectExtendedPurchaseDetailQueryDTO detailQuery = new CcdiProjectExtendedPurchaseDetailQueryDTO();
|
||||||
|
detailQuery.setProjectId(40L);
|
||||||
|
detailQuery.setPurchaseId("CG-001");
|
||||||
|
CcdiProjectExtendedPurchaseDetailVO detailVO = new CcdiProjectExtendedPurchaseDetailVO();
|
||||||
|
when(specialCheckService.getExtendedPurchaseDetail(detailQuery)).thenReturn(detailVO);
|
||||||
|
|
||||||
|
AjaxResult detailResult = controller.getExtendedPurchaseDetail(detailQuery);
|
||||||
|
|
||||||
|
assertEquals(200, detailResult.get("code"));
|
||||||
|
assertEquals(detailVO, detailResult.get("data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAjaxResultSuccessForExtendedRecruitmentEndpoints() {
|
||||||
|
CcdiProjectExtendedRecruitmentQueryDTO listQuery = new CcdiProjectExtendedRecruitmentQueryDTO();
|
||||||
|
listQuery.setProjectId(40L);
|
||||||
|
CcdiProjectExtendedRecruitmentListVO listVO = new CcdiProjectExtendedRecruitmentListVO();
|
||||||
|
when(specialCheckService.getExtendedRecruitmentList(listQuery)).thenReturn(listVO);
|
||||||
|
|
||||||
|
AjaxResult listResult = controller.getExtendedRecruitmentList(listQuery);
|
||||||
|
|
||||||
|
assertEquals(200, listResult.get("code"));
|
||||||
|
assertEquals(listVO, listResult.get("data"));
|
||||||
|
|
||||||
|
CcdiProjectExtendedRecruitmentDetailQueryDTO detailQuery = new CcdiProjectExtendedRecruitmentDetailQueryDTO();
|
||||||
|
detailQuery.setProjectId(40L);
|
||||||
|
detailQuery.setRecruitId("ZP-001");
|
||||||
|
CcdiProjectExtendedRecruitmentDetailVO detailVO = new CcdiProjectExtendedRecruitmentDetailVO();
|
||||||
|
when(specialCheckService.getExtendedRecruitmentDetail(detailQuery)).thenReturn(detailVO);
|
||||||
|
|
||||||
|
AjaxResult detailResult = controller.getExtendedRecruitmentDetail(detailQuery);
|
||||||
|
|
||||||
|
assertEquals(200, detailResult.get("code"));
|
||||||
|
assertEquals(detailVO, detailResult.get("data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAjaxResultSuccessForExtendedTransferEndpoints() {
|
||||||
|
CcdiProjectExtendedTransferQueryDTO listQuery = new CcdiProjectExtendedTransferQueryDTO();
|
||||||
|
listQuery.setProjectId(40L);
|
||||||
|
CcdiProjectExtendedTransferListVO listVO = new CcdiProjectExtendedTransferListVO();
|
||||||
|
when(specialCheckService.getExtendedTransferList(listQuery)).thenReturn(listVO);
|
||||||
|
|
||||||
|
AjaxResult listResult = controller.getExtendedTransferList(listQuery);
|
||||||
|
|
||||||
|
assertEquals(200, listResult.get("code"));
|
||||||
|
assertEquals(listVO, listResult.get("data"));
|
||||||
|
|
||||||
|
CcdiProjectExtendedTransferDetailQueryDTO detailQuery = new CcdiProjectExtendedTransferDetailQueryDTO();
|
||||||
|
detailQuery.setProjectId(40L);
|
||||||
|
detailQuery.setId(1L);
|
||||||
|
CcdiProjectExtendedTransferDetailVO detailVO = new CcdiProjectExtendedTransferDetailVO();
|
||||||
|
when(specialCheckService.getExtendedTransferDetail(detailQuery)).thenReturn(detailVO);
|
||||||
|
|
||||||
|
AjaxResult detailResult = controller.getExtendedTransferDetail(detailQuery);
|
||||||
|
|
||||||
|
assertEquals(200, detailResult.get("code"));
|
||||||
|
assertEquals(detailVO, detailResult.get("data"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiProjectSpecialCheckExtendedQueryContractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeExtendedPurchaseEndpointsAndContracts() throws Exception {
|
||||||
|
assertEndpoint(
|
||||||
|
"getExtendedPurchaseList",
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO",
|
||||||
|
"/extended-query/purchase/list"
|
||||||
|
);
|
||||||
|
assertEndpoint(
|
||||||
|
"getExtendedPurchaseDetail",
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO",
|
||||||
|
"/extended-query/purchase/detail"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO",
|
||||||
|
"projectId",
|
||||||
|
"applicantName",
|
||||||
|
"applyDateStart",
|
||||||
|
"applyDateEnd",
|
||||||
|
"pageNum",
|
||||||
|
"pageSize"
|
||||||
|
);
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO",
|
||||||
|
"projectId",
|
||||||
|
"purchaseId"
|
||||||
|
);
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO",
|
||||||
|
"rows",
|
||||||
|
"total"
|
||||||
|
);
|
||||||
|
assertExactFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO",
|
||||||
|
"purchaseId",
|
||||||
|
"projectName",
|
||||||
|
"subjectName",
|
||||||
|
"applicantName",
|
||||||
|
"applyDate"
|
||||||
|
);
|
||||||
|
assertDetailVoOwnedByProject("com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeExtendedRecruitmentEndpointsAndContracts() throws Exception {
|
||||||
|
assertEndpoint(
|
||||||
|
"getExtendedRecruitmentList",
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO",
|
||||||
|
"/extended-query/recruitment/list"
|
||||||
|
);
|
||||||
|
assertEndpoint(
|
||||||
|
"getExtendedRecruitmentDetail",
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO",
|
||||||
|
"/extended-query/recruitment/detail"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO",
|
||||||
|
"projectId",
|
||||||
|
"interviewerName",
|
||||||
|
"pageNum",
|
||||||
|
"pageSize"
|
||||||
|
);
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO",
|
||||||
|
"projectId",
|
||||||
|
"recruitId"
|
||||||
|
);
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListVO",
|
||||||
|
"rows",
|
||||||
|
"total"
|
||||||
|
);
|
||||||
|
assertExactFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListItemVO",
|
||||||
|
"recruitId",
|
||||||
|
"recruitName",
|
||||||
|
"posName",
|
||||||
|
"interviewerNameSummary",
|
||||||
|
"admitStatus"
|
||||||
|
);
|
||||||
|
assertDetailVoOwnedByProject("com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeExtendedTransferEndpointsAndContracts() throws Exception {
|
||||||
|
assertEndpoint(
|
||||||
|
"getExtendedTransferList",
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO",
|
||||||
|
"/extended-query/transfer/list"
|
||||||
|
);
|
||||||
|
assertEndpoint(
|
||||||
|
"getExtendedTransferDetail",
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO",
|
||||||
|
"/extended-query/transfer/detail"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO",
|
||||||
|
"projectId",
|
||||||
|
"staffName",
|
||||||
|
"transferDateStart",
|
||||||
|
"transferDateEnd",
|
||||||
|
"pageNum",
|
||||||
|
"pageSize"
|
||||||
|
);
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO",
|
||||||
|
"projectId",
|
||||||
|
"id"
|
||||||
|
);
|
||||||
|
assertFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO",
|
||||||
|
"rows",
|
||||||
|
"total"
|
||||||
|
);
|
||||||
|
assertExactFields(
|
||||||
|
"com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListItemVO",
|
||||||
|
"id",
|
||||||
|
"staffName",
|
||||||
|
"transferType",
|
||||||
|
"deptNameBefore",
|
||||||
|
"deptNameAfter",
|
||||||
|
"transferDate"
|
||||||
|
);
|
||||||
|
assertDetailVoOwnedByProject("com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEndpoint(String methodName, String dtoClassName, String expectedPath) throws Exception {
|
||||||
|
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectSpecialCheckController");
|
||||||
|
Class<?> dtoClass = Class.forName(dtoClassName);
|
||||||
|
Method method = controllerClass.getMethod(methodName, dtoClass);
|
||||||
|
|
||||||
|
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||||
|
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(getMapping);
|
||||||
|
assertEquals(expectedPath, getMapping.value()[0]);
|
||||||
|
assertNotNull(preAuthorize);
|
||||||
|
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertFields(String className, String... expectedFields) throws Exception {
|
||||||
|
Class<?> type = Class.forName(className);
|
||||||
|
List<String> fieldNames = Arrays.stream(type.getDeclaredFields()).map(Field::getName).collect(Collectors.toList());
|
||||||
|
for (String expectedField : expectedFields) {
|
||||||
|
assertTrue(fieldNames.contains(expectedField), className + " 缺少字段 " + expectedField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertExactFields(String className, String... expectedFields) throws Exception {
|
||||||
|
Class<?> type = Class.forName(className);
|
||||||
|
List<String> fieldNames = Arrays.stream(type.getDeclaredFields()).map(Field::getName).collect(Collectors.toList());
|
||||||
|
|
||||||
|
assertEquals(expectedFields.length, fieldNames.size(), className + " 字段数量不符合预期");
|
||||||
|
for (String expectedField : expectedFields) {
|
||||||
|
assertTrue(fieldNames.contains(expectedField), className + " 缺少字段 " + expectedField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDetailVoOwnedByProject(String className) throws Exception {
|
||||||
|
Class<?> type = Class.forName(className);
|
||||||
|
assertTrue(type.getName().startsWith("com.ruoyi.ccdi.project.domain.vo."));
|
||||||
|
assertTrue(!type.getName().contains("info.collection"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,4 +91,17 @@ class CcdiBankStatementTest {
|
|||||||
assertEquals(1, entity.getInternalFlag(), "Integer 类型应该正确复制");
|
assertEquals(1, entity.getInternalFlag(), "Integer 类型应该正确复制");
|
||||||
assertEquals(100, entity.getTrxType(), "Integer 类型应该正确复制");
|
assertEquals(100, entity.getTrxType(), "Integer 类型应该正确复制");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFromResponse_ShouldMapCounterpartyIdentityFields() {
|
||||||
|
BankStatementItem item = new BankStatementItem();
|
||||||
|
item.setCustomerCertNo("330101199001011234");
|
||||||
|
item.setCustomerSocialCreditCode("91330100123456789X");
|
||||||
|
|
||||||
|
CcdiBankStatement entity = CcdiBankStatement.fromResponse(item);
|
||||||
|
|
||||||
|
assertNotNull(entity, "转换结果不应为 null");
|
||||||
|
assertEquals("330101199001011234", entity.getCustomerCertNo());
|
||||||
|
assertEquals("91330100123456789X", entity.getCustomerSocialCreditCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.io.InputStream;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
@@ -132,6 +133,67 @@ class CcdiBankStatementMapperXmlTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fileUploadRecordMapper_shouldContainHistoryImportSourceFields() throws Exception {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader()
|
||||||
|
.getResourceAsStream("mapper/ccdi/project/CcdiFileUploadRecordMapper.xml")) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("source_type"), xml);
|
||||||
|
assertTrue(xml.contains("source_project_id"), xml);
|
||||||
|
assertTrue(xml.contains("source_project_name"), xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("fur.source_project_name"), xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void historyImportQueries_shouldFilterSuccessfulSourceBatchesAndDateRange() throws Exception {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader()
|
||||||
|
.getResourceAsStream("mapper/ccdi/project/CcdiFileUploadRecordMapper.xml")) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("selectSuccessfulRecordsByProjectIds"), xml);
|
||||||
|
assertTrue(xml.contains("file_status = 'parsed_success'"), xml);
|
||||||
|
assertTrue(xml.contains("log_id is not null"), xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("selectStatementsForHistoryImport"), xml);
|
||||||
|
assertTrue(xml.contains("bs.project_id = #{projectId}"), xml);
|
||||||
|
assertTrue(xml.contains("bs.batch_id = #{batchId}"), xml);
|
||||||
|
assertTrue(xml.contains("#{startDate}"), xml);
|
||||||
|
assertTrue(xml.contains("#{endDate}"), xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectStatementsForHistoryImport_shouldNotGenerateDuplicatedSelectKeyword() throws Exception {
|
||||||
|
MappedStatement mappedStatement = loadMappedStatement(
|
||||||
|
"com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper.selectStatementsForHistoryImport");
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("projectId", 48L);
|
||||||
|
params.put("batchId", 17094);
|
||||||
|
params.put("startDate", "2026-03-17");
|
||||||
|
params.put("endDate", "2026-03-18");
|
||||||
|
|
||||||
|
BoundSql boundSql = mappedStatement.getBoundSql(params);
|
||||||
|
String sql = boundSql.getSql().replaceAll("\\s+", " ").trim();
|
||||||
|
|
||||||
|
assertFalse(sql.startsWith("SELECT select"), sql);
|
||||||
|
assertTrue(sql.startsWith("SELECT bank_statement_id"), sql);
|
||||||
|
assertFalse(sql.contains("FROM ccdi_bank_statement FROM ccdi_bank_statement"), sql);
|
||||||
|
assertFalse(sql.contains("?AND"), sql);
|
||||||
|
assertTrue(sql.contains("WHERE (bs.project_id = ?) AND (bs.batch_id = ?) AND ( CASE"), sql);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void insertBatch_shouldAvoidUpdatingAutoIncrementPrimaryKeyInDuplicateBranch() throws Exception {
|
void insertBatch_shouldAvoidUpdatingAutoIncrementPrimaryKeyInDuplicateBranch() throws Exception {
|
||||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||||
@@ -143,6 +205,21 @@ class CcdiBankStatementMapperXmlTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapperXml_shouldContainCounterpartyIdentityColumns() throws Exception {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("<result property=\"customerCertNo\" column=\"customer_cert_no\" />"), xml);
|
||||||
|
assertTrue(
|
||||||
|
xml.contains("<result property=\"customerSocialCreditCode\" column=\"customer_social_credit_code\" />"),
|
||||||
|
xml
|
||||||
|
);
|
||||||
|
assertTrue(xml.contains("customer_bank, customer_reference, customer_cert_no, customer_social_credit_code,"), xml);
|
||||||
|
assertTrue(xml.contains("#{item.customerBank}, #{item.customerReference}, #{item.customerCertNo}, #{item.customerSocialCreditCode},"), xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MappedStatement loadMappedStatement(String statementId) throws Exception {
|
private MappedStatement loadMappedStatement(String statementId) throws Exception {
|
||||||
Configuration configuration = new Configuration();
|
Configuration configuration = new Configuration();
|
||||||
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
|
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
|
||||||
|
|||||||
@@ -13,17 +13,32 @@ class CcdiProjectOverviewMapperSqlTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldReadOverviewQueriesFromEmployeeResultTable() throws Exception {
|
void shouldReadOverviewQueriesFromEmployeeResultTable() throws Exception {
|
||||||
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||||
String riskPeopleSql = extractSelect(xml, "selectRiskPeopleOverviewByProjectId");
|
String riskPeopleSql = extractSelect(xml, "selectRiskPeopleOverviewPage");
|
||||||
|
String riskPeopleExportSql = extractSelect(xml, "selectRiskPeopleOverviewList");
|
||||||
String topRiskPeopleSql = extractSelect(xml, "selectTopRiskPeopleByProjectId");
|
String topRiskPeopleSql = extractSelect(xml, "selectTopRiskPeopleByProjectId");
|
||||||
String riskModelCardsSql = extractSelect(xml, "selectRiskModelCardsByProjectId");
|
String riskModelCardsSql = extractSelect(xml, "selectRiskModelCardsByProjectId");
|
||||||
String riskModelPeopleSql = extractSelect(xml, "selectRiskModelPeoplePage");
|
String riskModelPeopleSql = extractSelect(xml, "selectRiskModelPeoplePage");
|
||||||
|
|
||||||
|
assertTrue(xml.contains("<sql id=\"riskPeopleOverviewSelectColumns\">"), xml);
|
||||||
|
assertTrue(xml.contains("<sql id=\"riskPeopleOverviewOrderBy\">"), xml);
|
||||||
assertTrue(riskPeopleSql.contains("from ccdi_project_overview_employee_result"));
|
assertTrue(riskPeopleSql.contains("from ccdi_project_overview_employee_result"));
|
||||||
assertTrue(riskPeopleSql.contains("risk_level_code"));
|
assertTrue(riskPeopleSql.contains("result.project_id = #{query.projectId}"), riskPeopleSql);
|
||||||
assertTrue(riskPeopleSql.contains("model_count"));
|
assertTrue(riskPeopleSql.contains("<include refid=\"riskPeopleOverviewSelectColumns\"/>"), riskPeopleSql);
|
||||||
assertTrue(riskPeopleSql.contains("risk_point"));
|
assertTrue(riskPeopleSql.contains("<include refid=\"riskPeopleOverviewOrderBy\"/>"), riskPeopleSql);
|
||||||
|
assertTrue(
|
||||||
|
xml.contains(
|
||||||
|
"order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc"
|
||||||
|
),
|
||||||
|
xml
|
||||||
|
);
|
||||||
|
assertFalse(riskPeopleSql.contains("limit 10"), riskPeopleSql);
|
||||||
assertFalse(riskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
|
assertFalse(riskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
|
||||||
|
|
||||||
|
assertTrue(riskPeopleExportSql.contains("from ccdi_project_overview_employee_result"), riskPeopleExportSql);
|
||||||
|
assertTrue(riskPeopleExportSql.contains("result.project_id = #{projectId}"), riskPeopleExportSql);
|
||||||
|
assertTrue(riskPeopleExportSql.contains("<include refid=\"riskPeopleOverviewSelectColumns\"/>"), riskPeopleExportSql);
|
||||||
|
assertTrue(riskPeopleExportSql.contains("<include refid=\"riskPeopleOverviewOrderBy\"/>"), riskPeopleExportSql);
|
||||||
|
|
||||||
assertTrue(topRiskPeopleSql.contains("from ccdi_project_overview_employee_result"));
|
assertTrue(topRiskPeopleSql.contains("from ccdi_project_overview_employee_result"));
|
||||||
assertTrue(topRiskPeopleSql.contains("risk_level_code in ('HIGH', 'MEDIUM')"));
|
assertTrue(topRiskPeopleSql.contains("risk_level_code in ('HIGH', 'MEDIUM')"));
|
||||||
assertFalse(topRiskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
|
assertFalse(topRiskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
|
||||||
@@ -45,10 +60,73 @@ class CcdiProjectOverviewMapperSqlTest {
|
|||||||
assertFalse(xml.contains("json_table("), xml);
|
assertFalse(xml.contains("json_table("), xml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposePersonAnalysisDetailQueries() throws Exception {
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||||
|
String basicInfoSql = extractSelect(xml, "selectPersonAnalysisBasicInfo");
|
||||||
|
String statementRowsSql = extractSelect(xml, "selectPersonAnalysisStatementRows");
|
||||||
|
String objectRowsSql = extractSelect(xml, "selectPersonAnalysisObjectRows");
|
||||||
|
|
||||||
|
assertTrue(basicInfoSql.contains("ccdi_base_staff"), basicInfoSql);
|
||||||
|
assertTrue(basicInfoSql.contains("left join sys_dept"), basicInfoSql);
|
||||||
|
assertTrue(basicInfoSql.contains("ccdi_project_overview_employee_result"), basicInfoSql);
|
||||||
|
|
||||||
|
assertTrue(statementRowsSql.contains("from ccdi_bank_statement"), statementRowsSql);
|
||||||
|
assertTrue(statementRowsSql.contains("ccdi_bank_statement_tag_result"), statementRowsSql);
|
||||||
|
assertTrue(statementRowsSql.contains("bs.project_id = #{projectId}"), statementRowsSql);
|
||||||
|
|
||||||
|
assertTrue(objectRowsSql.contains("from ccdi_bank_statement_tag_result"), objectRowsSql);
|
||||||
|
assertTrue(objectRowsSql.contains("tr.object_type"), objectRowsSql);
|
||||||
|
assertTrue(objectRowsSql.contains("tr.reason_detail"), objectRowsSql);
|
||||||
|
assertTrue(objectRowsSql.contains("as reasonDetail"), objectRowsSql);
|
||||||
|
assertTrue(objectRowsSql.contains("tr.rule_code"), objectRowsSql);
|
||||||
|
assertTrue(objectRowsSql.contains("group by coalesce(tr.object_key, tr.object_type), tr.rule_code"), objectRowsSql);
|
||||||
|
assertFalse(objectRowsSql.contains("group_concat(distinct tr.reason_detail"), objectRowsSql);
|
||||||
|
assertFalse(objectRowsSql.contains("group_concat(distinct tr.rule_name"), objectRowsSql);
|
||||||
|
assertTrue(objectRowsSql.contains("tr.staff_id_card = #{staffIdCard}") || objectRowsSql.contains("#{staffIdCard}"), objectRowsSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeSuspiciousTransactionAggregationQuery() throws Exception {
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||||
|
String suspiciousSql = extractSelect(xml, "selectSuspiciousTransactionPage");
|
||||||
|
|
||||||
|
assertTrue(suspiciousSql.contains("rule_name like '%可疑%'"), suspiciousSql);
|
||||||
|
assertTrue(suspiciousSql.contains("ccdi_biz_intermediary"), suspiciousSql);
|
||||||
|
assertTrue(suspiciousSql.contains("ccdi_enterprise_base_info"), suspiciousSql);
|
||||||
|
assertTrue(suspiciousSql.contains("group by merged.bankStatementId"), suspiciousSql);
|
||||||
|
assertTrue(suspiciousSql.contains("hasModelRuleHit"), suspiciousSql);
|
||||||
|
assertTrue(suspiciousSql.contains("hasNameListHit"), suspiciousSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeEmployeeCreditNegativeQuery() throws Exception {
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||||
|
String employeeCreditSql = extractSelect(xml, "selectEmployeeCreditNegativePage");
|
||||||
|
String employeeCreditExportSql = extractSelect(xml, "selectEmployeeCreditNegativeList");
|
||||||
|
|
||||||
|
assertTrue(employeeCreditSql.contains("from ccdi_project_overview_employee_result"), employeeCreditSql);
|
||||||
|
assertTrue(employeeCreditSql.contains("inner join ccdi_credit_negative_info"), employeeCreditSql);
|
||||||
|
assertTrue(employeeCreditSql.contains("result.project_id = #{query.projectId}"), employeeCreditSql);
|
||||||
|
assertTrue(employeeCreditSql.contains("order by neg.query_date desc, neg.person_id asc"), employeeCreditSql);
|
||||||
|
assertFalse(employeeCreditSql.contains("ccdi_debts_info"), employeeCreditSql);
|
||||||
|
|
||||||
|
assertTrue(employeeCreditExportSql.contains("from ccdi_project_overview_employee_result"), employeeCreditExportSql);
|
||||||
|
assertTrue(employeeCreditExportSql.contains("inner join ccdi_credit_negative_info"), employeeCreditExportSql);
|
||||||
|
assertTrue(employeeCreditExportSql.contains("result.project_id = #{projectId}"), employeeCreditExportSql);
|
||||||
|
assertTrue(
|
||||||
|
employeeCreditExportSql.contains("order by neg.query_date desc, neg.person_id asc"),
|
||||||
|
employeeCreditExportSql
|
||||||
|
);
|
||||||
|
assertFalse(employeeCreditExportSql.contains("ccdi_debts_info"), employeeCreditExportSql);
|
||||||
|
}
|
||||||
|
|
||||||
private String extractSelect(String xml, String selectId) {
|
private String extractSelect(String xml, String selectId) {
|
||||||
String start = "<select id=\"" + selectId + "\"";
|
String start = "<select id=\"" + selectId + "\"";
|
||||||
int startIndex = xml.indexOf(start);
|
int startIndex = xml.indexOf(start);
|
||||||
|
assertTrue(startIndex >= 0, "missing select: " + selectId);
|
||||||
int endIndex = xml.indexOf("</select>", startIndex);
|
int endIndex = xml.indexOf("</select>", startIndex);
|
||||||
|
assertTrue(endIndex >= 0, "missing closing select tag: " + selectId);
|
||||||
return xml.substring(startIndex, endIndex);
|
return xml.substring(startIndex, endIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.ruoyi.ccdi.project.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiProjectSpecialCheckExtendedPurchaseSqlTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeExtendedPurchaseMapperMethodsAndSqlCaliber() throws Exception {
|
||||||
|
Class<?> mapperClass = Class.forName("com.ruoyi.ccdi.project.mapper.CcdiProjectSpecialCheckMapper");
|
||||||
|
Class<?> queryClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO");
|
||||||
|
|
||||||
|
Method listMethod = mapperClass.getMethod("selectExtendedPurchasePage", Page.class, queryClass);
|
||||||
|
Method detailMethod = mapperClass.getMethod("selectExtendedPurchaseDetail", Long.class, String.class);
|
||||||
|
assertEquals("Page", listMethod.getReturnType().getSimpleName());
|
||||||
|
assertEquals("CcdiProjectExtendedPurchaseDetailVO", detailMethod.getReturnType().getSimpleName());
|
||||||
|
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml"));
|
||||||
|
String listSql = extractSelect(xml, "selectExtendedPurchasePage");
|
||||||
|
String detailSql = extractSelect(xml, "selectExtendedPurchaseDetail");
|
||||||
|
|
||||||
|
assertTrue(listSql.contains("projectEmployeeScopeSql"));
|
||||||
|
assertTrue(listSql.contains("name=\"projectId\" value=\"query.projectId\""));
|
||||||
|
assertTrue(listSql.contains("select distinct scope.staff_name"));
|
||||||
|
assertTrue(listSql.contains("applicant_name"));
|
||||||
|
assertTrue(listSql.contains("apply_date"));
|
||||||
|
assertTrue(listSql.contains("purchase_id"));
|
||||||
|
assertTrue(listSql.contains("project_name"));
|
||||||
|
assertTrue(listSql.contains("subject_name"));
|
||||||
|
assertTrue(listSql.contains("order by p.apply_date desc, p.create_time desc, p.purchase_id desc"));
|
||||||
|
assertTrue(detailSql.contains("projectEmployeeScopeSql"));
|
||||||
|
assertTrue(detailSql.contains("purchase_id = #{purchaseId}"));
|
||||||
|
assertTrue(detailSql.contains("applicant_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractSelect(String xml, String selectId) {
|
||||||
|
String start = "<select id=\"" + selectId + "\"";
|
||||||
|
int startIndex = xml.indexOf(start);
|
||||||
|
int endIndex = xml.indexOf("</select>", startIndex);
|
||||||
|
return xml.substring(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.ruoyi.ccdi.project.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiProjectSpecialCheckExtendedRecruitmentSqlTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeExtendedRecruitmentMapperMethodsAndSqlCaliber() throws Exception {
|
||||||
|
Class<?> mapperClass = Class.forName("com.ruoyi.ccdi.project.mapper.CcdiProjectSpecialCheckMapper");
|
||||||
|
Class<?> queryClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO");
|
||||||
|
|
||||||
|
Method listMethod = mapperClass.getMethod("selectExtendedRecruitmentPage", Page.class, queryClass);
|
||||||
|
Method detailMethod = mapperClass.getMethod("selectExtendedRecruitmentDetail", Long.class, String.class);
|
||||||
|
assertEquals("Page", listMethod.getReturnType().getSimpleName());
|
||||||
|
assertEquals("CcdiProjectExtendedRecruitmentDetailVO", detailMethod.getReturnType().getSimpleName());
|
||||||
|
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml"));
|
||||||
|
String listSql = extractSelect(xml, "selectExtendedRecruitmentPage");
|
||||||
|
String detailSql = extractSelect(xml, "selectExtendedRecruitmentDetail");
|
||||||
|
|
||||||
|
assertTrue(listSql.contains("projectEmployeeScopeSql"));
|
||||||
|
assertTrue(listSql.contains("name=\"projectId\" value=\"query.projectId\""));
|
||||||
|
assertTrue(listSql.contains("select distinct r.recruit_id"));
|
||||||
|
assertTrue(listSql.contains("interviewer_name1"));
|
||||||
|
assertTrue(listSql.contains("interviewer_name2"));
|
||||||
|
assertTrue(listSql.contains("concat_ws(' / '"));
|
||||||
|
assertTrue(listSql.contains("admit_status"));
|
||||||
|
assertTrue(detailSql.contains("projectEmployeeScopeSql"));
|
||||||
|
assertTrue(detailSql.contains("recruit_id = #{recruitId}"));
|
||||||
|
assertTrue(detailSql.contains("interviewer_name1"));
|
||||||
|
assertTrue(detailSql.contains("interviewer_name2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractSelect(String xml, String selectId) {
|
||||||
|
String start = "<select id=\"" + selectId + "\"";
|
||||||
|
int startIndex = xml.indexOf(start);
|
||||||
|
int endIndex = xml.indexOf("</select>", startIndex);
|
||||||
|
return xml.substring(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.ruoyi.ccdi.project.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiProjectSpecialCheckExtendedTransferSqlTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeExtendedTransferMapperMethodsAndSqlCaliber() throws Exception {
|
||||||
|
Class<?> mapperClass = Class.forName("com.ruoyi.ccdi.project.mapper.CcdiProjectSpecialCheckMapper");
|
||||||
|
Class<?> queryClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO");
|
||||||
|
|
||||||
|
Method listMethod = mapperClass.getMethod("selectExtendedTransferPage", Page.class, queryClass);
|
||||||
|
Method detailMethod = mapperClass.getMethod("selectExtendedTransferDetail", Long.class, Long.class);
|
||||||
|
assertEquals("Page", listMethod.getReturnType().getSimpleName());
|
||||||
|
assertEquals("CcdiProjectExtendedTransferDetailVO", detailMethod.getReturnType().getSimpleName());
|
||||||
|
|
||||||
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml"));
|
||||||
|
String listSql = extractSelect(xml, "selectExtendedTransferPage");
|
||||||
|
String detailSql = extractSelect(xml, "selectExtendedTransferDetail");
|
||||||
|
|
||||||
|
assertTrue(listSql.contains("projectEmployeeScopeSql"));
|
||||||
|
assertTrue(listSql.contains("name=\"projectId\" value=\"query.projectId\""));
|
||||||
|
assertTrue(listSql.contains("ccdi_staff_transfer t"));
|
||||||
|
assertTrue(listSql.contains("ccdi_base_staff s"));
|
||||||
|
assertTrue(listSql.contains("scope.staff_name = s.name"));
|
||||||
|
assertTrue(listSql.contains("transfer_date"));
|
||||||
|
assertTrue(listSql.contains("order by t.transfer_date desc, t.create_time desc, t.id desc"));
|
||||||
|
assertTrue(detailSql.contains("projectEmployeeScopeSql"));
|
||||||
|
assertTrue(detailSql.contains("t.id = #{id}"));
|
||||||
|
assertTrue(detailSql.contains("scope.staff_name = s.name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractSelect(String xml, String selectId) {
|
||||||
|
String start = "<select id=\"" + selectId + "\"";
|
||||||
|
int startIndex = xml.indexOf(start);
|
||||||
|
int endIndex = xml.indexOf("</select>", startIndex);
|
||||||
|
return xml.substring(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,27 @@ class CcdiProjectOverviewServiceStructureTest {
|
|||||||
Class<?> clazz = Class.forName("com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService");
|
Class<?> clazz = Class.forName("com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService");
|
||||||
|
|
||||||
assertNotNull(clazz.getMethod("getDashboard", Long.class));
|
assertNotNull(clazz.getMethod("getDashboard", Long.class));
|
||||||
assertNotNull(clazz.getMethod("getRiskPeopleOverview", Long.class));
|
assertNotNull(clazz.getMethod(
|
||||||
|
"getRiskPeopleOverview",
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO")
|
||||||
|
));
|
||||||
assertNotNull(clazz.getMethod("getTopRiskPeople", Long.class));
|
assertNotNull(clazz.getMethod("getTopRiskPeople", Long.class));
|
||||||
|
assertNotNull(clazz.getMethod(
|
||||||
|
"getPersonAnalysisDetail",
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO")
|
||||||
|
));
|
||||||
|
assertNotNull(clazz.getMethod(
|
||||||
|
"getSuspiciousTransactions",
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO")
|
||||||
|
));
|
||||||
|
assertNotNull(clazz.getMethod(
|
||||||
|
"getEmployeeCreditNegative",
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO")
|
||||||
|
));
|
||||||
|
assertNotNull(clazz.getMethod(
|
||||||
|
"exportSuspiciousTransactions",
|
||||||
|
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO")
|
||||||
|
));
|
||||||
assertNotNull(clazz.getMethod("refreshOverviewEmployeeResults", Long.class, String.class));
|
assertNotNull(clazz.getMethod("refreshOverviewEmployeeResults", Long.class, String.class));
|
||||||
assertNotNull(clazz.getMethod("refreshProjectRiskCounts", Long.class, String.class));
|
assertNotNull(clazz.getMethod("refreshProjectRiskCounts", Long.class, String.class));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ class CcdiFileUploadServiceImplTest {
|
|||||||
assertEquals("admin", inserted.get().get(0).getUploadUser());
|
assertEquals("admin", inserted.get().get(0).getUploadUser());
|
||||||
assertEquals("uploading", inserted.get().get(0).getFileStatus());
|
assertEquals("uploading", inserted.get().get(0).getFileStatus());
|
||||||
assertEquals(1, TransactionSynchronizationManager.getSynchronizations().size());
|
assertEquals(1, TransactionSynchronizationManager.getSynchronizations().size());
|
||||||
|
verify(projectService).ensureProjectNotArchived(PROJECT_ID, "已归档项目暂不允许上传或拉取数据");
|
||||||
|
verify(projectService).ensureProjectWritable(PROJECT_ID, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||||
} finally {
|
} finally {
|
||||||
TransactionSynchronizationManager.clearSynchronization();
|
TransactionSynchronizationManager.clearSynchronization();
|
||||||
}
|
}
|
||||||
@@ -409,6 +411,20 @@ class CcdiFileUploadServiceImplTest {
|
|||||||
assertTrue(exception.getMessage().contains("仅支持删除解析成功文件"));
|
assertTrue(exception.getMessage().contains("仅支持删除解析成功文件"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteFileUploadRecord_shouldRejectHistoryImportRecord() {
|
||||||
|
CcdiFileUploadRecord record = buildRecord();
|
||||||
|
record.setFileStatus("parsed_success");
|
||||||
|
record.setSourceType("HISTORY_IMPORT");
|
||||||
|
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class,
|
||||||
|
() -> service.deleteFileUploadRecord(RECORD_ID, 9527L));
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("历史导入文件不支持删除"));
|
||||||
|
verify(lsfxClient, never()).deleteFiles(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails() {
|
void deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails() {
|
||||||
CcdiFileUploadRecord record = buildRecord();
|
CcdiFileUploadRecord record = buildRecord();
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ class CcdiModelParamServiceImplTest {
|
|||||||
service.saveAllParams(buildSaveAllDto());
|
service.saveAllParams(buildSaveAllDto());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数");
|
||||||
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PARAM_CHANGE);
|
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PARAM_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +179,7 @@ class CcdiModelParamServiceImplTest {
|
|||||||
service.saveParams(saveDTO);
|
service.saveParams(saveDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数");
|
||||||
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PARAM_CHANGE);
|
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PARAM_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||||
|
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
|
||||||
|
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
|
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiProjectHistoryImportServiceImplTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiProjectHistoryImportServiceImpl service;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiBankStatementMapper bankStatementMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiFileUploadRecordMapper recordMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectMapper projectMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Executor fileUploadExecutor;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiBankTagService bankTagService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFilterStatementsByTrxDateAndDeduplicateAcrossSourceProjects() {
|
||||||
|
CcdiProjectImportHistoryDTO dto = buildImportDto();
|
||||||
|
when(recordMapper.selectSuccessfulRecordsByProjectIds(dto.getSourceProjectIds()))
|
||||||
|
.thenReturn(List.of(buildSourceRecord(11L, 101, "批次A"), buildSourceRecord(12L, 202, "批次B")));
|
||||||
|
|
||||||
|
when(bankStatementMapper.selectStatementsForHistoryImport(11L, 101, "2026-01-01", "2026-01-31"))
|
||||||
|
.thenReturn(List.of(buildStatement("2026-01-10", "6222", "100.00", "备注A")));
|
||||||
|
when(bankStatementMapper.selectStatementsForHistoryImport(12L, 202, "2026-01-01", "2026-01-31"))
|
||||||
|
.thenReturn(List.of(
|
||||||
|
buildStatement("2026-01-10", "6222", "100.00", "备注A"),
|
||||||
|
buildStatement("2026-01-11", "6333", "200.00", "备注B")
|
||||||
|
));
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
Runnable runnable = invocation.getArgument(0);
|
||||||
|
runnable.run();
|
||||||
|
return null;
|
||||||
|
}).when(fileUploadExecutor).execute(any(Runnable.class));
|
||||||
|
|
||||||
|
AtomicReference<List<CcdiBankStatement>> insertedStatements = new AtomicReference<>();
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
insertedStatements.set(List.copyOf(invocation.getArgument(0)));
|
||||||
|
return insertedStatements.get().size();
|
||||||
|
}).when(bankStatementMapper).insertBatch(anyList());
|
||||||
|
|
||||||
|
when(projectMapper.selectById(11L)).thenReturn(buildProject(11L, "历史项目A"));
|
||||||
|
when(projectMapper.selectById(12L)).thenReturn(buildProject(12L, "历史项目B"));
|
||||||
|
|
||||||
|
service.submitImport(90L, 3001, dto, "tester");
|
||||||
|
|
||||||
|
assertEquals(2, insertedStatements.get().size());
|
||||||
|
assertTrue(insertedStatements.get().stream().allMatch(item -> Long.valueOf(90L).equals(item.getProjectId())));
|
||||||
|
assertTrue(insertedStatements.get().stream().allMatch(item -> Integer.valueOf(3001).equals(item.getGroupId())));
|
||||||
|
verify(bankStatementMapper).selectStatementsForHistoryImport(11L, 101, "2026-01-01", "2026-01-31");
|
||||||
|
verify(bankStatementMapper).selectStatementsForHistoryImport(12L, 202, "2026-01-01", "2026-01-31");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGenerateNewBatchIdsAndHistoryImportFileRecordsOnlyForSuccessfulSourceBatches() {
|
||||||
|
CcdiProjectImportHistoryDTO dto = buildImportDto();
|
||||||
|
CcdiFileUploadRecord sourceRecord = buildSourceRecord(11L, 101, "批次A");
|
||||||
|
when(recordMapper.selectSuccessfulRecordsByProjectIds(dto.getSourceProjectIds()))
|
||||||
|
.thenReturn(List.of(sourceRecord, buildSourceRecord(12L, 202, "批次B")));
|
||||||
|
|
||||||
|
when(bankStatementMapper.selectStatementsForHistoryImport(11L, 101, "2026-01-01", "2026-01-31"))
|
||||||
|
.thenReturn(List.of(buildStatement("2026-01-12", "6444", "300.00", "备注C")));
|
||||||
|
when(bankStatementMapper.selectStatementsForHistoryImport(12L, 202, "2026-01-01", "2026-01-31"))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
Runnable runnable = invocation.getArgument(0);
|
||||||
|
runnable.run();
|
||||||
|
return null;
|
||||||
|
}).when(fileUploadExecutor).execute(any(Runnable.class));
|
||||||
|
|
||||||
|
AtomicReference<List<CcdiBankStatement>> insertedStatements = new AtomicReference<>();
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
insertedStatements.set(List.copyOf(invocation.getArgument(0)));
|
||||||
|
return insertedStatements.get().size();
|
||||||
|
}).when(bankStatementMapper).insertBatch(anyList());
|
||||||
|
|
||||||
|
AtomicReference<List<CcdiFileUploadRecord>> insertedRecords = new AtomicReference<>();
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
insertedRecords.set(List.copyOf(invocation.getArgument(0)));
|
||||||
|
return insertedRecords.get().size();
|
||||||
|
}).when(recordMapper).insertBatch(anyList());
|
||||||
|
|
||||||
|
when(projectMapper.selectById(11L)).thenReturn(buildProject(11L, "历史项目A"));
|
||||||
|
|
||||||
|
service.submitImport(90L, 3001, dto, "tester");
|
||||||
|
|
||||||
|
assertEquals(1, insertedStatements.get().size());
|
||||||
|
assertNotEquals(101, insertedStatements.get().get(0).getBatchId());
|
||||||
|
assertEquals(1, insertedRecords.get().size());
|
||||||
|
assertEquals("HISTORY_IMPORT", insertedRecords.get().get(0).getSourceType());
|
||||||
|
assertEquals(11L, insertedRecords.get().get(0).getSourceProjectId());
|
||||||
|
assertEquals("历史项目A", insertedRecords.get().get(0).getSourceProjectName());
|
||||||
|
assertEquals("parsed_success", insertedRecords.get().get(0).getFileStatus());
|
||||||
|
assertNotEquals(sourceRecord.getLogId(), insertedRecords.get().get(0).getLogId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRefreshTargetCountAndSubmitAutoRebuildAfterImport() {
|
||||||
|
CcdiProjectImportHistoryDTO dto = buildImportDto();
|
||||||
|
when(recordMapper.selectSuccessfulRecordsByProjectIds(dto.getSourceProjectIds()))
|
||||||
|
.thenReturn(List.of(buildSourceRecord(11L, 101, "批次A")));
|
||||||
|
when(bankStatementMapper.selectStatementsForHistoryImport(11L, 101, "2026-01-01", "2026-01-31"))
|
||||||
|
.thenReturn(List.of(buildStatement("2026-01-12", "6444", "300.00", "备注C")));
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
Runnable runnable = invocation.getArgument(0);
|
||||||
|
runnable.run();
|
||||||
|
return null;
|
||||||
|
}).when(fileUploadExecutor).execute(any(Runnable.class));
|
||||||
|
when(projectMapper.selectById(11L)).thenReturn(buildProject(11L, "历史项目A"));
|
||||||
|
when(projectMapper.selectById(90L)).thenReturn(buildProject(90L, "新项目"));
|
||||||
|
when(bankStatementMapper.countMatchedStaffCountByProjectId(90L)).thenReturn(3);
|
||||||
|
|
||||||
|
service.submitImport(90L, 3001, dto, "tester");
|
||||||
|
|
||||||
|
verify(projectMapper).updateById(org.mockito.ArgumentMatchers.<CcdiProject>argThat(project ->
|
||||||
|
Long.valueOf(90L).equals(project.getProjectId()) && Integer.valueOf(3).equals(project.getTargetCount())
|
||||||
|
));
|
||||||
|
verify(bankTagService).submitAutoRebuild(90L, TriggerType.AUTO_BATCH_UPLOAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectImportHistoryDTO buildImportDto() {
|
||||||
|
CcdiProjectImportHistoryDTO dto = new CcdiProjectImportHistoryDTO();
|
||||||
|
dto.setProjectName("新项目");
|
||||||
|
dto.setDescription("从历史复制");
|
||||||
|
dto.setSourceProjectIds(List.of(11L, 12L));
|
||||||
|
dto.setStartDate("2026-01-01");
|
||||||
|
dto.setEndDate("2026-01-31");
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiFileUploadRecord buildSourceRecord(Long sourceProjectId, Integer logId, String fileName) {
|
||||||
|
CcdiFileUploadRecord record = new CcdiFileUploadRecord();
|
||||||
|
record.setProjectId(sourceProjectId);
|
||||||
|
record.setLogId(logId);
|
||||||
|
record.setFileName(fileName);
|
||||||
|
record.setFileStatus("parsed_success");
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProject buildProject(Long projectId, String projectName) {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(projectId);
|
||||||
|
project.setProjectName(projectName);
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiBankStatement buildStatement(String trxDate, String accountNo, String amountCr, String memo) {
|
||||||
|
CcdiBankStatement statement = new CcdiBankStatement();
|
||||||
|
statement.setTrxDate(trxDate);
|
||||||
|
statement.setLeAccountNo(accountNo);
|
||||||
|
statement.setAmountCr(new BigDecimal(amountCr));
|
||||||
|
statement.setUserMemo(memo);
|
||||||
|
statement.setCustomerAccountName("对手方");
|
||||||
|
statement.setCustomerAccountNo("7000");
|
||||||
|
statement.setBatchSequence(1);
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiProjectOverviewServiceEmployeeCreditNegativeTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiProjectOverviewServiceImpl service;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectOverviewMapper overviewMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectMapper projectMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmployeeCreditNegativePage() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO item = new CcdiProjectEmployeeCreditNegativeItemVO();
|
||||||
|
item.setPersonName("李四");
|
||||||
|
item.setPersonId("330000000000000001");
|
||||||
|
item.setQueryDate("2026-03-20");
|
||||||
|
item.setCivilCnt(1);
|
||||||
|
item.setCivilLmt(new BigDecimal("10000.00"));
|
||||||
|
|
||||||
|
Page<CcdiProjectEmployeeCreditNegativeItemVO> page = new Page<>(1, 5);
|
||||||
|
page.setRecords(List.of(item));
|
||||||
|
page.setTotal(1L);
|
||||||
|
when(overviewMapper.selectEmployeeCreditNegativePage(any(Page.class), any(CcdiProjectEmployeeCreditNegativeQueryDTO.class)))
|
||||||
|
.thenReturn(page);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO = new CcdiProjectEmployeeCreditNegativeQueryDTO();
|
||||||
|
queryDTO.setProjectId(40L);
|
||||||
|
queryDTO.setPageNum(1);
|
||||||
|
queryDTO.setPageSize(5);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativePageVO result = service.getEmployeeCreditNegative(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(1, result.getRows().size());
|
||||||
|
assertEquals(1L, result.getTotal());
|
||||||
|
assertEquals("李四", result.getRows().getFirst().getPersonName());
|
||||||
|
verify(overviewMapper).selectEmployeeCreditNegativePage(
|
||||||
|
any(Page.class),
|
||||||
|
argThat(query -> query.getProjectId().equals(40L))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowWhenEmployeeCreditNegativeProjectDoesNotExist() {
|
||||||
|
when(projectMapper.selectById(99L)).thenReturn(null);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO = new CcdiProjectEmployeeCreditNegativeQueryDTO();
|
||||||
|
queryDTO.setProjectId(99L);
|
||||||
|
|
||||||
|
assertThrows(ServiceException.class, () -> service.getEmployeeCreditNegative(queryDTO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportEmployeeCreditNegativeRows() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO item = new CcdiProjectEmployeeCreditNegativeItemVO();
|
||||||
|
item.setPersonName("李四");
|
||||||
|
item.setPersonId("330000000000000001");
|
||||||
|
item.setQueryDate("2026-03-20");
|
||||||
|
item.setCivilCnt(1);
|
||||||
|
item.setCivilLmt(new BigDecimal("10000.00"));
|
||||||
|
when(overviewMapper.selectEmployeeCreditNegativeList(40L)).thenReturn(List.of(item));
|
||||||
|
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeExcel> rows = service.exportEmployeeCreditNegative(40L);
|
||||||
|
|
||||||
|
assertEquals(1, rows.size());
|
||||||
|
assertEquals("李四", rows.getFirst().getPersonName());
|
||||||
|
assertEquals("330000000000000001", rows.getFirst().getPersonId());
|
||||||
|
verify(overviewMapper).selectEmployeeCreditNegativeList(eq(40L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowWhenExportEmployeeCreditNegativeProjectDoesNotExist() {
|
||||||
|
when(projectMapper.selectById(100L)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(ServiceException.class, () -> service.exportEmployeeCreditNegative(100L));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,25 +2,38 @@ package com.ruoyi.ccdi.project.service.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
@@ -33,6 +46,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.clearInvocations;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -51,9 +66,15 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
|
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectRiskDetailWorkbookExporter workbookExporter;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldBuildDashboardWithNoRiskCount() {
|
void shouldBuildDashboardWithNoRiskCount() {
|
||||||
CcdiProject project = new CcdiProject();
|
CcdiProject project = new CcdiProject();
|
||||||
@@ -76,6 +97,7 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
CcdiProject project = new CcdiProject();
|
CcdiProject project = new CcdiProject();
|
||||||
project.setProjectId(40L);
|
project.setProjectId(40L);
|
||||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
CcdiProjectRiskPeopleQueryDTO queryDTO = buildRiskPeopleQuery(40L);
|
||||||
|
|
||||||
CcdiProjectEmployeeRiskAggregateVO aggregate = new CcdiProjectEmployeeRiskAggregateVO();
|
CcdiProjectEmployeeRiskAggregateVO aggregate = new CcdiProjectEmployeeRiskAggregateVO();
|
||||||
aggregate.setStaffName("李四");
|
aggregate.setStaffName("李四");
|
||||||
@@ -86,24 +108,70 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
aggregate.setRiskLevelCode("HIGH");
|
aggregate.setRiskLevelCode("HIGH");
|
||||||
aggregate.setModelCount(3);
|
aggregate.setModelCount(3);
|
||||||
aggregate.setRiskPoint("大额单笔收入、疑似兼职");
|
aggregate.setRiskPoint("大额单笔收入、疑似兼职");
|
||||||
when(overviewMapper.selectRiskPeopleOverviewByProjectId(40L)).thenReturn(List.of(aggregate));
|
Page<CcdiProjectEmployeeRiskAggregateVO> resultPage = new Page<>(1, 5);
|
||||||
|
resultPage.setRecords(List.of(aggregate));
|
||||||
|
resultPage.setTotal(1L);
|
||||||
|
when(overviewMapper.selectRiskPeopleOverviewPage(any(Page.class), any(CcdiProjectRiskPeopleQueryDTO.class)))
|
||||||
|
.thenReturn(resultPage);
|
||||||
when(overviewMapper.selectRiskHitTagsByScope(40L, "330000000000000001", null)).thenReturn(List.of(
|
when(overviewMapper.selectRiskHitTagsByScope(40L, "330000000000000001", null)).thenReturn(List.of(
|
||||||
buildHitTag("LARGE_TRANSACTION", "大额交易模型", "RULE_A", "大额单笔收入", "HIGH"),
|
buildHitTag("LARGE_TRANSACTION", "大额交易模型", "RULE_A", "大额单笔收入", "HIGH"),
|
||||||
buildHitTag("PART_TIME", "兼职取酬模型", "RULE_B", "疑似兼职", "MEDIUM")
|
buildHitTag("PART_TIME", "兼职取酬模型", "RULE_B", "疑似兼职", "MEDIUM")
|
||||||
));
|
));
|
||||||
|
|
||||||
CcdiProjectRiskPeopleOverviewVO overview = service.getRiskPeopleOverview(40L);
|
CcdiProjectRiskPeopleOverviewVO overview = service.getRiskPeopleOverview(queryDTO);
|
||||||
|
|
||||||
assertEquals(1, overview.getOverviewList().size());
|
assertEquals(1, overview.getRows().size());
|
||||||
assertEquals(8, overview.getOverviewList().getFirst().getRiskCount());
|
assertEquals(1L, overview.getTotal());
|
||||||
assertEquals("高风险", overview.getOverviewList().getFirst().getRiskLevel());
|
assertEquals(1L, overview.getPageNum());
|
||||||
assertEquals("danger", overview.getOverviewList().getFirst().getRiskLevelType());
|
assertEquals(5L, overview.getPageSize());
|
||||||
assertEquals(3, overview.getOverviewList().getFirst().getModelCount());
|
assertEquals(8, overview.getRows().getFirst().getRiskCount());
|
||||||
assertEquals(2, overview.getOverviewList().getFirst().getRiskPointTagList().size());
|
assertEquals("高风险", overview.getRows().getFirst().getRiskLevel());
|
||||||
assertEquals("LARGE_TRANSACTION", overview.getOverviewList().getFirst().getRiskPointTagList().getFirst().getModelCode());
|
assertEquals("danger", overview.getRows().getFirst().getRiskLevelType());
|
||||||
assertEquals("大额交易模型", overview.getOverviewList().getFirst().getRiskPointTagList().getFirst().getModelName());
|
assertEquals(3, overview.getRows().getFirst().getModelCount());
|
||||||
assertEquals("大额单笔收入、疑似兼职", overview.getOverviewList().getFirst().getRiskPoint());
|
assertEquals(2, overview.getRows().getFirst().getRiskPointTagList().size());
|
||||||
assertEquals("查看详情", overview.getOverviewList().getFirst().getActionLabel());
|
assertEquals("LARGE_TRANSACTION", overview.getRows().getFirst().getRiskPointTagList().getFirst().getModelCode());
|
||||||
|
assertEquals("大额交易模型", overview.getRows().getFirst().getRiskPointTagList().getFirst().getModelName());
|
||||||
|
assertEquals("大额单笔收入、疑似兼职", overview.getRows().getFirst().getRiskPoint());
|
||||||
|
assertEquals("查看详情", overview.getRows().getFirst().getActionLabel());
|
||||||
|
verify(overviewMapper).selectRiskPeopleOverviewPage(
|
||||||
|
argThat(page -> page.getCurrent() == 1L && page.getSize() == 5L),
|
||||||
|
argThat(query -> query.getProjectId().equals(40L))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDefaultRiskPeoplePageNumAndPageSizeToOneAndFive() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
Page<CcdiProjectEmployeeRiskAggregateVO> emptyPage = new Page<>(1, 5);
|
||||||
|
emptyPage.setRecords(List.of());
|
||||||
|
emptyPage.setTotal(0L);
|
||||||
|
when(overviewMapper.selectRiskPeopleOverviewPage(any(Page.class), any(CcdiProjectRiskPeopleQueryDTO.class)))
|
||||||
|
.thenReturn(emptyPage);
|
||||||
|
|
||||||
|
CcdiProjectRiskPeopleQueryDTO queryDTO = new CcdiProjectRiskPeopleQueryDTO();
|
||||||
|
queryDTO.setProjectId(40L);
|
||||||
|
service.getRiskPeopleOverview(queryDTO);
|
||||||
|
|
||||||
|
verify(overviewMapper).selectRiskPeopleOverviewPage(
|
||||||
|
argThat(page -> page.getCurrent() == 1L && page.getSize() == 5L),
|
||||||
|
any(CcdiProjectRiskPeopleQueryDTO.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
clearInvocations(overviewMapper);
|
||||||
|
|
||||||
|
CcdiProjectRiskPeopleQueryDTO invalidQuery = new CcdiProjectRiskPeopleQueryDTO();
|
||||||
|
invalidQuery.setProjectId(40L);
|
||||||
|
invalidQuery.setPageNum(0);
|
||||||
|
invalidQuery.setPageSize(-1);
|
||||||
|
service.getRiskPeopleOverview(invalidQuery);
|
||||||
|
|
||||||
|
verify(overviewMapper, times(1)).selectRiskPeopleOverviewPage(
|
||||||
|
argThat(page -> page.getCurrent() == 1L && page.getSize() == 5L),
|
||||||
|
any(CcdiProjectRiskPeopleQueryDTO.class)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -128,14 +196,168 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
assertEquals("查看详情", topRiskPeople.getTopRiskList().getFirst().getActionLabel());
|
assertEquals("查看详情", topRiskPeople.getTopRiskList().getFirst().getActionLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportRiskPeopleOverviewRowsWithSameFieldsAsPage() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeRiskAggregateVO aggregate = new CcdiProjectEmployeeRiskAggregateVO();
|
||||||
|
aggregate.setStaffName("李四");
|
||||||
|
aggregate.setStaffIdCard("330000000000000001");
|
||||||
|
aggregate.setDeptName("信息二部");
|
||||||
|
aggregate.setRuleCount(5);
|
||||||
|
aggregate.setHitCount(8);
|
||||||
|
aggregate.setRiskLevelCode("HIGH");
|
||||||
|
aggregate.setModelCount(3);
|
||||||
|
aggregate.setRiskPoint("大额单笔收入、疑似兼职");
|
||||||
|
when(overviewMapper.selectRiskPeopleOverviewList(40L)).thenReturn(List.of(aggregate));
|
||||||
|
when(overviewMapper.selectRiskHitTagsByScope(40L, "330000000000000001", null)).thenReturn(List.of(
|
||||||
|
buildHitTag("LARGE_TRANSACTION", "大额交易模型", "RULE_A", "大额单笔收入", "HIGH"),
|
||||||
|
buildHitTag("PART_TIME", "兼职取酬模型", "RULE_B", "疑似兼职", "MEDIUM")
|
||||||
|
));
|
||||||
|
|
||||||
|
List<CcdiProjectRiskPeopleOverviewExcel> rows = service.exportRiskPeopleOverview(40L);
|
||||||
|
|
||||||
|
assertEquals(1, rows.size());
|
||||||
|
assertEquals("李四", rows.getFirst().getName());
|
||||||
|
assertEquals("330000000000000001", rows.getFirst().getIdNo());
|
||||||
|
assertEquals("信息二部", rows.getFirst().getDepartment());
|
||||||
|
assertEquals(8, rows.getFirst().getRiskCount());
|
||||||
|
assertEquals("高风险", rows.getFirst().getRiskLevel());
|
||||||
|
assertEquals(3, rows.getFirst().getModelCount());
|
||||||
|
assertEquals("大额单笔收入、疑似兼职", rows.getFirst().getRiskPoint());
|
||||||
|
verify(overviewMapper).selectRiskPeopleOverviewList(40L);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldThrowWhenProjectDoesNotExist() {
|
void shouldThrowWhenProjectDoesNotExist() {
|
||||||
when(projectMapper.selectById(99L)).thenReturn(null);
|
when(projectMapper.selectById(99L)).thenReturn(null);
|
||||||
|
|
||||||
assertThrows(ServiceException.class, () -> service.getRiskPeopleOverview(99L));
|
assertThrows(ServiceException.class, () -> service.getRiskPeopleOverview(buildRiskPeopleQuery(99L)));
|
||||||
|
assertThrows(ServiceException.class, () -> service.exportRiskPeopleOverview(99L));
|
||||||
assertThrows(ServiceException.class, () -> service.getTopRiskPeople(99L));
|
assertThrows(ServiceException.class, () -> service.getTopRiskPeople(99L));
|
||||||
assertThrows(ServiceException.class, () -> service.getRiskModelCards(99L));
|
assertThrows(ServiceException.class, () -> service.getRiskModelCards(99L));
|
||||||
assertThrows(ServiceException.class, () -> service.getRiskModelPeople(buildRiskModelPeopleQuery(99L)));
|
assertThrows(ServiceException.class, () -> service.getRiskModelPeople(buildRiskModelPeopleQuery(99L)));
|
||||||
|
assertThrows(ServiceException.class, () -> service.getPersonAnalysisDetail(buildPersonAnalysisDetailQuery(99L)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportRiskDetailsWorkbook() throws Exception {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionItemVO suspiciousItem = new CcdiProjectSuspiciousTransactionItemVO();
|
||||||
|
suspiciousItem.setTrxDate("2026-03-20 10:00:00");
|
||||||
|
suspiciousItem.setSuspiciousPersonName("张三");
|
||||||
|
suspiciousItem.setRelatedPersonName("张三");
|
||||||
|
suspiciousItem.setRelatedStaffName("张三");
|
||||||
|
suspiciousItem.setRelatedStaffCode("1001");
|
||||||
|
suspiciousItem.setRelationType("本人");
|
||||||
|
suspiciousItem.setUserMemo("转账");
|
||||||
|
suspiciousItem.setCashType("转账");
|
||||||
|
suspiciousItem.setDisplayAmount(new BigDecimal("100.00"));
|
||||||
|
when(overviewMapper.selectSuspiciousTransactionList(any())).thenReturn(List.of(suspiciousItem));
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO creditItem = new CcdiProjectEmployeeCreditNegativeItemVO();
|
||||||
|
creditItem.setPersonName("李四");
|
||||||
|
creditItem.setPersonId("330000000000000001");
|
||||||
|
creditItem.setQueryDate("2026-03-20");
|
||||||
|
creditItem.setCivilCnt(1);
|
||||||
|
creditItem.setCivilLmt(new BigDecimal("20000.00"));
|
||||||
|
when(overviewMapper.selectEmployeeCreditNegativeList(40L)).thenReturn(List.of(creditItem));
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
service.exportRiskDetails(response, 40L);
|
||||||
|
|
||||||
|
verify(overviewMapper).selectSuspiciousTransactionList(argThat(query ->
|
||||||
|
query.getProjectId().equals(40L) && "ALL".equals(query.getSuspiciousType())
|
||||||
|
));
|
||||||
|
verify(workbookExporter).export(
|
||||||
|
eq(response),
|
||||||
|
eq(40L),
|
||||||
|
argThat((List<CcdiProjectSuspiciousTransactionExcel> rows) ->
|
||||||
|
rows.size() == 1 && "张三".equals(rows.getFirst().getSuspiciousPersonName())
|
||||||
|
),
|
||||||
|
argThat((List<CcdiProjectEmployeeCreditNegativeExcel> rows) ->
|
||||||
|
rows.size() == 1 && "李四".equals(rows.getFirst().getPersonName())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnPersonAnalysisDetailWithBasicInfoAndGroupedAbnormalDetail() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectPersonAnalysisBasicInfoVO basicInfo = new CcdiProjectPersonAnalysisBasicInfoVO();
|
||||||
|
basicInfo.setName("李四");
|
||||||
|
basicInfo.setIdNo("330000000000000001");
|
||||||
|
basicInfo.setStaffCode("10001");
|
||||||
|
basicInfo.setDepartment("信息二部");
|
||||||
|
basicInfo.setPhone("13800000000");
|
||||||
|
basicInfo.setRiskLevel("高风险");
|
||||||
|
basicInfo.setProjectName("测试项目");
|
||||||
|
when(overviewMapper.selectPersonAnalysisBasicInfo(40L, "330000000000000001")).thenReturn(basicInfo);
|
||||||
|
|
||||||
|
CcdiBankStatementListVO statementRow = new CcdiBankStatementListVO();
|
||||||
|
statementRow.setBankStatementId(1L);
|
||||||
|
statementRow.setTrxDate("2026-03-01 10:00:00");
|
||||||
|
statementRow.setLeAccountNo("62220001");
|
||||||
|
statementRow.setLeAccountName("李四");
|
||||||
|
statementRow.setCustomerAccountName("张三");
|
||||||
|
statementRow.setCustomerAccountNo("62220002");
|
||||||
|
statementRow.setUserMemo("转账");
|
||||||
|
statementRow.setCashType("转账汇款");
|
||||||
|
statementRow.setDisplayAmount(new BigDecimal("1000.00"));
|
||||||
|
when(overviewMapper.selectPersonAnalysisStatementRows(40L, "330000000000000001"))
|
||||||
|
.thenReturn(List.of(statementRow));
|
||||||
|
|
||||||
|
com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO hitTag =
|
||||||
|
new com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO();
|
||||||
|
hitTag.setBankStatementId(1L);
|
||||||
|
hitTag.setRuleCode("RULE_A");
|
||||||
|
hitTag.setRuleName("大额转账");
|
||||||
|
hitTag.setRiskLevel("HIGH");
|
||||||
|
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(40L, List.of(1L)))
|
||||||
|
.thenReturn(List.of(hitTag));
|
||||||
|
|
||||||
|
CcdiProjectPersonAnalysisObjectRecordVO objectRow = new CcdiProjectPersonAnalysisObjectRecordVO();
|
||||||
|
objectRow.setTitle("张三");
|
||||||
|
objectRow.setSubtitle("关联人员");
|
||||||
|
objectRow.setRiskTags(List.of("频繁往来"));
|
||||||
|
objectRow.setReasonDetail("命中近30日高频往来规则,存在多笔短周期回流");
|
||||||
|
objectRow.setSummary("高频往来");
|
||||||
|
CcdiProjectPersonAnalysisObjectRecordVO objectRowTwo = new CcdiProjectPersonAnalysisObjectRecordVO();
|
||||||
|
objectRowTwo.setTitle("张三");
|
||||||
|
objectRowTwo.setSubtitle("关联人员");
|
||||||
|
objectRowTwo.setRiskTags(List.of("异常关联"));
|
||||||
|
objectRowTwo.setReasonDetail("命中跨主体异常关联规则,存在关键时间点往来");
|
||||||
|
objectRowTwo.setSummary("跨主体关联");
|
||||||
|
when(overviewMapper.selectPersonAnalysisObjectRows(40L, "330000000000000001"))
|
||||||
|
.thenReturn(List.of(objectRow, objectRowTwo));
|
||||||
|
|
||||||
|
CcdiProjectPersonAnalysisDetailVO result = service.getPersonAnalysisDetail(buildPersonAnalysisDetailQuery(40L));
|
||||||
|
|
||||||
|
assertNotNull(result.getBasicInfo());
|
||||||
|
assertEquals("李四", result.getBasicInfo().getName());
|
||||||
|
assertEquals("高风险", result.getBasicInfo().getRiskLevel());
|
||||||
|
assertNotNull(result.getAbnormalDetail());
|
||||||
|
assertNotNull(result.getAbnormalDetail().getGroups());
|
||||||
|
assertEquals(2, result.getAbnormalDetail().getGroups().size());
|
||||||
|
assertEquals("BANK_STATEMENT", result.getAbnormalDetail().getGroups().get(0).getGroupType());
|
||||||
|
assertEquals("OBJECT", result.getAbnormalDetail().getGroups().get(1).getGroupType());
|
||||||
|
List<?> statementRecords = result.getAbnormalDetail().getGroups().get(0).getRecords();
|
||||||
|
assertEquals(1, ((CcdiBankStatementListVO) statementRecords.getFirst()).getHitTags().size());
|
||||||
|
List<?> objectRecords = result.getAbnormalDetail().getGroups().get(1).getRecords();
|
||||||
|
assertEquals(2, objectRecords.size());
|
||||||
|
assertEquals(
|
||||||
|
"命中近30日高频往来规则,存在多笔短周期回流",
|
||||||
|
((CcdiProjectPersonAnalysisObjectRecordVO) objectRecords.getFirst()).getReasonDetail()
|
||||||
|
);
|
||||||
|
assertNotNull(((CcdiProjectPersonAnalysisObjectRecordVO) objectRecords.getFirst()).getExtraFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -274,6 +496,14 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CcdiProjectRiskPeopleQueryDTO buildRiskPeopleQuery(Long projectId) {
|
||||||
|
CcdiProjectRiskPeopleQueryDTO queryDTO = new CcdiProjectRiskPeopleQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setPageNum(1);
|
||||||
|
queryDTO.setPageSize(5);
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
|
||||||
private CcdiProjectRiskModelPeopleQueryDTO buildRiskModelPeopleQuery(Long projectId) {
|
private CcdiProjectRiskModelPeopleQueryDTO buildRiskModelPeopleQuery(Long projectId) {
|
||||||
CcdiProjectRiskModelPeopleQueryDTO queryDTO = new CcdiProjectRiskModelPeopleQueryDTO();
|
CcdiProjectRiskModelPeopleQueryDTO queryDTO = new CcdiProjectRiskModelPeopleQueryDTO();
|
||||||
queryDTO.setProjectId(projectId);
|
queryDTO.setProjectId(projectId);
|
||||||
@@ -282,6 +512,13 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
return queryDTO;
|
return queryDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CcdiProjectPersonAnalysisDetailQueryDTO buildPersonAnalysisDetailQuery(Long projectId) {
|
||||||
|
CcdiProjectPersonAnalysisDetailQueryDTO queryDTO = new CcdiProjectPersonAnalysisDetailQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setStaffIdCard("330000000000000001");
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
|
||||||
private CcdiProjectOverviewEmployeeResult buildEmployeeResult(String riskLevelCode) {
|
private CcdiProjectOverviewEmployeeResult buildEmployeeResult(String riskLevelCode) {
|
||||||
CcdiProjectOverviewEmployeeResult result = new CcdiProjectOverviewEmployeeResult();
|
CcdiProjectOverviewEmployeeResult result = new CcdiProjectOverviewEmployeeResult();
|
||||||
result.setRiskLevelCode(riskLevelCode);
|
result.setRiskLevelCode(riskLevelCode);
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiProjectOverviewServiceSuspiciousTransactionTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiProjectOverviewServiceImpl service;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectOverviewMapper overviewMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectMapper projectMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnDeduplicatedSuspiciousTransactions() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionItemVO item = new CcdiProjectSuspiciousTransactionItemVO();
|
||||||
|
item.setBankStatementId(101L);
|
||||||
|
item.setSuspiciousPersonName("王五");
|
||||||
|
item.setRelatedPersonName("孙七");
|
||||||
|
item.setRelatedStaffName("孙七");
|
||||||
|
item.setRelatedStaffCode("809901");
|
||||||
|
item.setRelationType("配偶");
|
||||||
|
item.setUserMemo("零钱商户消费");
|
||||||
|
item.setCashType("转账");
|
||||||
|
item.setDisplayAmount(new BigDecimal("200000.00"));
|
||||||
|
item.setHasModelRuleHit(true);
|
||||||
|
item.setHasNameListHit(true);
|
||||||
|
|
||||||
|
Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(1, 10);
|
||||||
|
page.setRecords(List.of(item));
|
||||||
|
page.setTotal(1);
|
||||||
|
when(overviewMapper.selectSuspiciousTransactionPage(any(Page.class), any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
|
||||||
|
.thenReturn(page);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||||
|
queryDTO.setProjectId(40L);
|
||||||
|
queryDTO.setSuspiciousType("name_list");
|
||||||
|
queryDTO.setPageNum(1);
|
||||||
|
queryDTO.setPageSize(10);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(1, result.getRows().size());
|
||||||
|
assertEquals(1L, result.getTotal());
|
||||||
|
assertTrue(result.getRows().getFirst().getHasModelRuleHit());
|
||||||
|
assertTrue(result.getRows().getFirst().getHasNameListHit());
|
||||||
|
verify(overviewMapper).selectSuspiciousTransactionPage(
|
||||||
|
any(Page.class),
|
||||||
|
argThat(query -> "NAME_LIST".equals(query.getSuspiciousType()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportSuspiciousTransactionsWithCurrentFilter() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionItemVO item = new CcdiProjectSuspiciousTransactionItemVO();
|
||||||
|
item.setTrxDate("2024-01-15 10:00:00");
|
||||||
|
item.setSuspiciousPersonName("孙七");
|
||||||
|
item.setRelatedPersonName("孙七");
|
||||||
|
item.setRelatedStaffName("孙七");
|
||||||
|
item.setRelatedStaffCode("809901");
|
||||||
|
item.setRelationType("本人");
|
||||||
|
item.setUserMemo("");
|
||||||
|
item.setCashType("转账");
|
||||||
|
item.setDisplayAmount(new BigDecimal("500000.00"));
|
||||||
|
when(overviewMapper.selectSuspiciousTransactionList(any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
|
||||||
|
.thenReturn(List.of(item));
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||||
|
queryDTO.setProjectId(40L);
|
||||||
|
|
||||||
|
List<CcdiProjectSuspiciousTransactionExcel> rows = service.exportSuspiciousTransactions(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(1, rows.size());
|
||||||
|
assertEquals("孙七(809901)", rows.getFirst().getRelatedStaffDisplay());
|
||||||
|
assertEquals("/转账", rows.getFirst().getSummaryAndCashType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowWhenSuspiciousTransactionProjectDoesNotExist() {
|
||||||
|
when(projectMapper.selectById(99L)).thenReturn(null);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||||
|
queryDTO.setProjectId(99L);
|
||||||
|
|
||||||
|
assertThrows(ServiceException.class, () -> service.getSuspiciousTransactions(queryDTO));
|
||||||
|
assertThrows(ServiceException.class, () -> service.exportSuspiciousTransactions(queryDTO));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiProjectRiskDetailWorkbookExporterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportWorkbookWithThreeOrderedSheets() throws Exception {
|
||||||
|
CcdiProjectRiskDetailWorkbookExporter exporter = new CcdiProjectRiskDetailWorkbookExporter();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionExcel suspiciousRow = new CcdiProjectSuspiciousTransactionExcel();
|
||||||
|
suspiciousRow.setTrxDate("2026-03-20 10:00:00");
|
||||||
|
suspiciousRow.setSuspiciousPersonName("张三");
|
||||||
|
suspiciousRow.setRelatedPersonName("张三");
|
||||||
|
suspiciousRow.setRelatedStaffDisplay("张三(1001)");
|
||||||
|
suspiciousRow.setRelationType("本人");
|
||||||
|
suspiciousRow.setSummaryAndCashType("转账/转账");
|
||||||
|
suspiciousRow.setDisplayAmount(new BigDecimal("100.00"));
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeExcel creditRow = new CcdiProjectEmployeeCreditNegativeExcel();
|
||||||
|
creditRow.setPersonName("李四");
|
||||||
|
creditRow.setPersonId("330000000000000001");
|
||||||
|
creditRow.setQueryDate("2026-03-20");
|
||||||
|
creditRow.setCivilCnt(1);
|
||||||
|
creditRow.setCivilLmt(new BigDecimal("20000.00"));
|
||||||
|
|
||||||
|
exporter.export(response, 40L, List.of(suspiciousRow), List.of(creditRow));
|
||||||
|
|
||||||
|
assertTrue(response.getContentType().startsWith(
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
));
|
||||||
|
|
||||||
|
try (var workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||||
|
assertEquals(3, workbook.getNumberOfSheets());
|
||||||
|
assertEquals("涉疑交易明细", workbook.getSheetAt(0).getSheetName());
|
||||||
|
assertEquals("员工负面征信信息", workbook.getSheetAt(1).getSheetName());
|
||||||
|
assertEquals("异常账户人员信息", workbook.getSheetAt(2).getSheetName());
|
||||||
|
assertEquals("账号", workbook.getSheetAt(2).getRow(0).getCell(0).getStringCellValue());
|
||||||
|
assertEquals("状态", workbook.getSheetAt(2).getRow(0).getCell(5).getStringCellValue());
|
||||||
|
assertEquals(1, workbook.getSheetAt(2).getPhysicalNumberOfRows());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,25 +4,38 @@ import ch.qos.logback.classic.Logger;
|
|||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
import ch.qos.logback.core.read.ListAppender;
|
import ch.qos.logback.core.read.ListAppender;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||||
import com.ruoyi.lsfx.domain.response.GetTokenResponse;
|
import com.ruoyi.lsfx.domain.response.GetTokenResponse;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@@ -37,6 +50,9 @@ class CcdiProjectServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private LsfxAnalysisClient lsfxAnalysisClient;
|
private LsfxAnalysisClient lsfxAnalysisClient;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCountTaggingProjectsSeparately() {
|
void shouldCountTaggingProjectsSeparately() {
|
||||||
when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L);
|
when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L);
|
||||||
@@ -68,6 +84,105 @@ class CcdiProjectServiceImplTest {
|
|||||||
() -> service.ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数"));
|
() -> service.ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldArchiveCompletedProject() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
project.setProjectName("专案A");
|
||||||
|
project.setStatus("1");
|
||||||
|
project.setIsArchived(0);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
service.archiveProject(40L, "tester");
|
||||||
|
|
||||||
|
assertEquals("2", project.getStatus());
|
||||||
|
assertEquals(1, project.getIsArchived());
|
||||||
|
verify(projectMapper).updateById(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectArchivingProjectWhenStatusIsNotCompleted() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(41L);
|
||||||
|
project.setStatus("0");
|
||||||
|
when(projectMapper.selectById(41L)).thenReturn(project);
|
||||||
|
|
||||||
|
assertThrows(ServiceException.class, () -> service.archiveProject(41L, "tester"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectWritingWhenProjectIsArchived() {
|
||||||
|
CcdiProject archived = new CcdiProject();
|
||||||
|
archived.setProjectId(42L);
|
||||||
|
archived.setStatus("2");
|
||||||
|
when(projectMapper.selectById(42L)).thenReturn(archived);
|
||||||
|
|
||||||
|
assertThrows(ServiceException.class,
|
||||||
|
() -> service.ensureProjectNotArchived(42L, "已归档项目暂不允许修改参数"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldOnlyReturnCompletedAndArchivedHistoryProjects() {
|
||||||
|
CcdiProjectQueryDTO queryDTO = new CcdiProjectQueryDTO();
|
||||||
|
queryDTO.setProjectName("历史");
|
||||||
|
|
||||||
|
CcdiProjectHistoryListItemVO completed = new CcdiProjectHistoryListItemVO();
|
||||||
|
completed.setProjectId(1L);
|
||||||
|
completed.setStatus("1");
|
||||||
|
|
||||||
|
CcdiProjectHistoryListItemVO archived = new CcdiProjectHistoryListItemVO();
|
||||||
|
archived.setProjectId(2L);
|
||||||
|
archived.setStatus("2");
|
||||||
|
|
||||||
|
when(projectMapper.selectHistoryProjects(queryDTO)).thenReturn(List.of(completed, archived));
|
||||||
|
|
||||||
|
List<CcdiProjectHistoryListItemVO> result = service.listHistoryProjects(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(2, result.size());
|
||||||
|
assertEquals(List.of(completed, archived), result);
|
||||||
|
verify(projectMapper).selectHistoryProjects(queryDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateProjectThenPublishHistoryImportEventAfterCommit() {
|
||||||
|
CcdiProjectImportHistoryDTO dto = new CcdiProjectImportHistoryDTO();
|
||||||
|
dto.setProjectName("新项目");
|
||||||
|
dto.setDescription("从历史导入");
|
||||||
|
dto.setSourceProjectIds(List.of(11L, 12L));
|
||||||
|
dto.setStartDate("2026-01-01");
|
||||||
|
dto.setEndDate("2026-01-31");
|
||||||
|
|
||||||
|
when(lsfxAnalysisClient.getToken(any())).thenReturn(buildTokenResponse(3001));
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
CcdiProject project = invocation.getArgument(0);
|
||||||
|
project.setProjectId(90L);
|
||||||
|
return 1;
|
||||||
|
}).when(projectMapper).insert(any(CcdiProject.class));
|
||||||
|
|
||||||
|
TransactionSynchronizationManager.initSynchronization();
|
||||||
|
try {
|
||||||
|
CcdiProjectVO project = service.importFromHistory(dto, "tester");
|
||||||
|
|
||||||
|
assertNotNull(project);
|
||||||
|
assertEquals(90L, project.getProjectId());
|
||||||
|
assertEquals(1, TransactionSynchronizationManager.getSynchronizations().size());
|
||||||
|
verify(applicationEventPublisher, never()).publishEvent(any());
|
||||||
|
|
||||||
|
TransactionSynchronizationManager.getSynchronizations().forEach(sync -> sync.afterCommit());
|
||||||
|
|
||||||
|
ArgumentCaptor<Object> eventCaptor = ArgumentCaptor.forClass(Object.class);
|
||||||
|
verify(applicationEventPublisher).publishEvent(eventCaptor.capture());
|
||||||
|
CcdiProjectHistoryImportSubmittedEvent event =
|
||||||
|
(CcdiProjectHistoryImportSubmittedEvent) eventCaptor.getValue();
|
||||||
|
assertEquals(90L, event.getTargetProjectId());
|
||||||
|
assertEquals(3001, event.getTargetLsfxProjectId());
|
||||||
|
assertEquals("tester", event.getOperator());
|
||||||
|
assertEquals(dto, event.getDto());
|
||||||
|
} finally {
|
||||||
|
TransactionSynchronizationManager.clearSynchronization();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldLogProjectInitialStatusWhenProjectIsCreated() {
|
void shouldLogProjectInitialStatusWhenProjectIsCreated() {
|
||||||
CcdiProjectSaveDTO dto = new CcdiProjectSaveDTO();
|
CcdiProjectSaveDTO dto = new CcdiProjectSaveDTO();
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListItemVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectSpecialCheckMapper;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiProjectSpecialCheckExtendedQueryServiceImplTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiProjectSpecialCheckServiceImpl service;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectSpecialCheckMapper specialCheckMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectMapper projectMapper;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowProjectNotFoundForExtendedQueries() {
|
||||||
|
when(projectMapper.selectById(99L)).thenReturn(null);
|
||||||
|
|
||||||
|
ServiceException purchaseListException = assertThrows(
|
||||||
|
ServiceException.class,
|
||||||
|
() -> service.getExtendedPurchaseList(buildPurchaseQuery(99L, 1, 10))
|
||||||
|
);
|
||||||
|
ServiceException purchaseDetailException = assertThrows(
|
||||||
|
ServiceException.class,
|
||||||
|
() -> service.getExtendedPurchaseDetail(buildPurchaseDetailQuery(99L, "CG-001"))
|
||||||
|
);
|
||||||
|
ServiceException recruitmentListException = assertThrows(
|
||||||
|
ServiceException.class,
|
||||||
|
() -> service.getExtendedRecruitmentList(buildRecruitmentQuery(99L, "张三", 1, 10))
|
||||||
|
);
|
||||||
|
ServiceException transferDetailException = assertThrows(
|
||||||
|
ServiceException.class,
|
||||||
|
() -> service.getExtendedTransferDetail(buildTransferDetailQuery(99L, 1L))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("项目不存在", purchaseListException.getMessage());
|
||||||
|
assertEquals("项目不存在", purchaseDetailException.getMessage());
|
||||||
|
assertEquals("项目不存在", recruitmentListException.getMessage());
|
||||||
|
assertEquals("项目不存在", transferDetailException.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldBuildPurchaseListRowsAndTotalFromPage() {
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(buildProject(40L));
|
||||||
|
|
||||||
|
Page<CcdiProjectExtendedPurchaseListItemVO> page = new Page<>(2, 5, 8);
|
||||||
|
CcdiProjectExtendedPurchaseListItemVO item = new CcdiProjectExtendedPurchaseListItemVO();
|
||||||
|
item.setPurchaseId("CG-001");
|
||||||
|
page.setRecords(List.of(item));
|
||||||
|
when(specialCheckMapper.selectExtendedPurchasePage(any(), any())).thenReturn(page);
|
||||||
|
|
||||||
|
CcdiProjectExtendedPurchaseListVO result = service.getExtendedPurchaseList(buildPurchaseQuery(40L, 2, 5));
|
||||||
|
|
||||||
|
assertNotNull(result.getRows());
|
||||||
|
assertEquals(1, result.getRows().size());
|
||||||
|
assertEquals(8L, result.getTotal());
|
||||||
|
|
||||||
|
ArgumentCaptor<Page<CcdiProjectExtendedPurchaseListItemVO>> pageCaptor = ArgumentCaptor.forClass(Page.class);
|
||||||
|
verify(specialCheckMapper).selectExtendedPurchasePage(pageCaptor.capture(), any());
|
||||||
|
assertEquals(2L, pageCaptor.getValue().getCurrent());
|
||||||
|
assertEquals(5L, pageCaptor.getValue().getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyRecruitmentAndTransferRowsInsteadOfNull() {
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(buildProject(40L));
|
||||||
|
when(specialCheckMapper.selectExtendedRecruitmentPage(any(), any())).thenReturn(new Page<>(1, 10, 0));
|
||||||
|
when(specialCheckMapper.selectExtendedTransferPage(any(), any())).thenReturn(new Page<>(1, 10, 0));
|
||||||
|
|
||||||
|
CcdiProjectExtendedRecruitmentListVO recruitmentResult =
|
||||||
|
service.getExtendedRecruitmentList(buildRecruitmentQuery(40L, "李四", 1, 10));
|
||||||
|
CcdiProjectExtendedTransferListVO transferResult = service.getExtendedTransferList(buildTransferQuery(40L, 1, 10));
|
||||||
|
|
||||||
|
assertNotNull(recruitmentResult.getRows());
|
||||||
|
assertEquals(0, recruitmentResult.getRows().size());
|
||||||
|
assertEquals(0L, recruitmentResult.getTotal());
|
||||||
|
assertNotNull(transferResult.getRows());
|
||||||
|
assertEquals(0, transferResult.getRows().size());
|
||||||
|
assertEquals(0L, transferResult.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPassInterviewerNameAndPaginationToRecruitmentMapper() {
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(buildProject(40L));
|
||||||
|
|
||||||
|
Page<CcdiProjectExtendedRecruitmentListItemVO> page = new Page<>(3, 4, 2);
|
||||||
|
page.setRecords(List.of(new CcdiProjectExtendedRecruitmentListItemVO()));
|
||||||
|
when(specialCheckMapper.selectExtendedRecruitmentPage(any(), any())).thenReturn(page);
|
||||||
|
|
||||||
|
CcdiProjectExtendedRecruitmentQueryDTO queryDTO = buildRecruitmentQuery(40L, "王五", 3, 4);
|
||||||
|
CcdiProjectExtendedRecruitmentListVO result = service.getExtendedRecruitmentList(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(2L, result.getTotal());
|
||||||
|
ArgumentCaptor<Page<CcdiProjectExtendedRecruitmentListItemVO>> pageCaptor = ArgumentCaptor.forClass(Page.class);
|
||||||
|
ArgumentCaptor<CcdiProjectExtendedRecruitmentQueryDTO> queryCaptor = ArgumentCaptor.forClass(
|
||||||
|
CcdiProjectExtendedRecruitmentQueryDTO.class
|
||||||
|
);
|
||||||
|
verify(specialCheckMapper).selectExtendedRecruitmentPage(pageCaptor.capture(), queryCaptor.capture());
|
||||||
|
assertEquals(3L, pageCaptor.getValue().getCurrent());
|
||||||
|
assertEquals(4L, pageCaptor.getValue().getSize());
|
||||||
|
assertEquals("王五", queryCaptor.getValue().getInterviewerName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowClearErrorWhenExtendedDetailDoesNotBelongToProjectScope() {
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(buildProject(40L));
|
||||||
|
when(specialCheckMapper.selectExtendedPurchaseDetail(40L, "CG-001")).thenReturn(null);
|
||||||
|
when(specialCheckMapper.selectExtendedRecruitmentDetail(40L, "ZP-001")).thenReturn(null);
|
||||||
|
when(specialCheckMapper.selectExtendedTransferDetail(40L, 1L)).thenReturn(null);
|
||||||
|
|
||||||
|
ServiceException purchaseException = assertThrows(
|
||||||
|
ServiceException.class,
|
||||||
|
() -> service.getExtendedPurchaseDetail(buildPurchaseDetailQuery(40L, "CG-001"))
|
||||||
|
);
|
||||||
|
ServiceException recruitmentException = assertThrows(
|
||||||
|
ServiceException.class,
|
||||||
|
() -> service.getExtendedRecruitmentDetail(buildRecruitmentDetailQuery(40L, "ZP-001"))
|
||||||
|
);
|
||||||
|
ServiceException transferException = assertThrows(
|
||||||
|
ServiceException.class,
|
||||||
|
() -> service.getExtendedTransferDetail(buildTransferDetailQuery(40L, 1L))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("当前记录不属于该项目专项核查范围", purchaseException.getMessage());
|
||||||
|
assertEquals("当前记录不属于该项目专项核查范围", recruitmentException.getMessage());
|
||||||
|
assertEquals("当前记录不属于该项目专项核查范围", transferException.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProject buildProject(Long projectId) {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(projectId);
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectExtendedPurchaseQueryDTO buildPurchaseQuery(Long projectId, int pageNum, int pageSize) {
|
||||||
|
CcdiProjectExtendedPurchaseQueryDTO queryDTO = new CcdiProjectExtendedPurchaseQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setPageNum(pageNum);
|
||||||
|
queryDTO.setPageSize(pageSize);
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectExtendedPurchaseDetailQueryDTO buildPurchaseDetailQuery(Long projectId, String purchaseId) {
|
||||||
|
CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO = new CcdiProjectExtendedPurchaseDetailQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setPurchaseId(purchaseId);
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectExtendedRecruitmentQueryDTO buildRecruitmentQuery(
|
||||||
|
Long projectId,
|
||||||
|
String interviewerName,
|
||||||
|
int pageNum,
|
||||||
|
int pageSize
|
||||||
|
) {
|
||||||
|
CcdiProjectExtendedRecruitmentQueryDTO queryDTO = new CcdiProjectExtendedRecruitmentQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setInterviewerName(interviewerName);
|
||||||
|
queryDTO.setPageNum(pageNum);
|
||||||
|
queryDTO.setPageSize(pageSize);
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectExtendedRecruitmentDetailQueryDTO buildRecruitmentDetailQuery(Long projectId, String recruitId) {
|
||||||
|
CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO = new CcdiProjectExtendedRecruitmentDetailQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setRecruitId(recruitId);
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectExtendedTransferQueryDTO buildTransferQuery(Long projectId, int pageNum, int pageSize) {
|
||||||
|
CcdiProjectExtendedTransferQueryDTO queryDTO = new CcdiProjectExtendedTransferQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setPageNum(pageNum);
|
||||||
|
queryDTO.setPageSize(pageSize);
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiProjectExtendedTransferDetailQueryDTO buildTransferDetailQuery(Long projectId, Long id) {
|
||||||
|
CcdiProjectExtendedTransferDetailQueryDTO queryDTO = new CcdiProjectExtendedTransferDetailQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setId(id);
|
||||||
|
return queryDTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
465
docs/design/2026-03-24-special-check-extended-query-design.md
Normal file
465
docs/design/2026-03-24-special-check-extended-query-design.md
Normal file
@@ -0,0 +1,465 @@
|
|||||||
|
# 专项核查拓展查询卡片设计文档
|
||||||
|
|
||||||
|
**模块**: 项目详情 - 专项核查
|
||||||
|
**日期**: 2026-03-24
|
||||||
|
**状态**: 已确认
|
||||||
|
|
||||||
|
## 一、背景
|
||||||
|
|
||||||
|
当前项目详情页的专项核查页签已包含两块内容:
|
||||||
|
|
||||||
|
1. 员工家庭资产负债专项核查
|
||||||
|
2. 图谱外链展示占位卡片
|
||||||
|
|
||||||
|
其中图谱外链卡片当前仅作为后续接入外链图谱页面的预留入口,尚未承载真实业务查询能力。
|
||||||
|
|
||||||
|
本轮需求是在图谱外链卡片下方新增一张“拓展查询”卡片,用于查询项目范围内员工的采购、招聘、调动记录,并支持按主题 Tab 切换、按主题对应姓名字段和时间范围检索,以及按记录查看完整详情。
|
||||||
|
|
||||||
|
## 二、目标
|
||||||
|
|
||||||
|
本次设计目标如下:
|
||||||
|
|
||||||
|
1. 在专项核查页签中新增“拓展查询”卡片,位置固定在图谱外链卡片下方。
|
||||||
|
2. 通过 Tab 切换展示 3 类拓展查询主题:
|
||||||
|
- 采购记录
|
||||||
|
- 招聘记录
|
||||||
|
- 调动记录
|
||||||
|
3. 列表数据只查询当前专项核查同范围员工,不扩展到项目全部目标员工。
|
||||||
|
4. 查询栏按主题独立配置,不共享无关筛选项。
|
||||||
|
5. 列表展示必要字段,操作列提供“查看详情”按钮。
|
||||||
|
6. 点击“查看详情”后,通过弹窗展示该条记录全部字段。
|
||||||
|
|
||||||
|
## 三、范围
|
||||||
|
|
||||||
|
### 3.1 本次范围
|
||||||
|
|
||||||
|
- 新增专项核查页的拓展查询卡片
|
||||||
|
- 新增采购、招聘、调动 3 个主题的 Tab 切换
|
||||||
|
- 新增项目范围拓展查询列表接口
|
||||||
|
- 新增项目范围拓展查询详情接口
|
||||||
|
- 新增对应前端查询、分页、详情弹窗交互
|
||||||
|
- 补充前后端测试与验证记录
|
||||||
|
|
||||||
|
### 3.2 不在本次范围
|
||||||
|
|
||||||
|
- 不接入真实图谱外链地址
|
||||||
|
- 不改动现有员工家庭资产负债专项核查逻辑
|
||||||
|
- 不新开独立路由页面
|
||||||
|
- 不扩展到项目全部目标员工
|
||||||
|
- 不为招聘记录新增时间筛选字段
|
||||||
|
- 不新增导出、编辑、删除等操作
|
||||||
|
- 不引入缓存、异步预计算或额外结果表
|
||||||
|
|
||||||
|
## 四、已确认业务口径
|
||||||
|
|
||||||
|
本次设计确认以下口径,后续实施必须严格遵守:
|
||||||
|
|
||||||
|
1. “项目范围内员工”定义为当前专项核查同范围员工,即现有专项核查链路中已归并出来的员工集合。
|
||||||
|
2. 不按项目全部目标员工查询。
|
||||||
|
3. 查询卡片位置固定在“图谱外链展示”卡片下方。
|
||||||
|
4. 采用横向 Tab 切换主题,不使用竖向主题导航。
|
||||||
|
5. 每个主题使用独立查询栏,不共享查询表单。
|
||||||
|
6. 采购记录按“申请人姓名 + 申请日期范围”筛选。
|
||||||
|
7. 招聘记录按“面试官姓名”筛选,本次不增加时间筛选。
|
||||||
|
8. 调动记录按“员工姓名 + 调动日期范围”筛选。
|
||||||
|
9. 列表只展示必要字段,详情弹窗展示该条记录全部字段。
|
||||||
|
|
||||||
|
## 五、方案对比
|
||||||
|
|
||||||
|
### 5.1 方案 A:图谱卡片下方新增独立拓展查询卡片
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 保留现有图谱卡片不变
|
||||||
|
- 在图谱卡片下方新增一张“拓展查询”卡片
|
||||||
|
- 卡片内部顶部使用横向 Tab 切换主题
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 完全符合“在图谱外链卡片下面添加拓展查询功能卡片”的原始需求
|
||||||
|
- 不影响现有图谱占位卡片结构
|
||||||
|
- 页面层级清晰,用户认知成本低
|
||||||
|
- 前端改造路径最短
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 页面纵向高度会增加
|
||||||
|
|
||||||
|
### 5.2 方案 B:图谱卡片与拓展查询合并为一个大卡片
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 图谱占位与拓展查询共用同一张卡片
|
||||||
|
|
||||||
|
问题:
|
||||||
|
|
||||||
|
- 与“在图谱外链卡片下面添加卡片”的需求不一致
|
||||||
|
- 图谱占位与实际查询职责混在一起,层次不清
|
||||||
|
|
||||||
|
### 5.3 方案 C:独立拓展查询卡片,但使用左侧竖向主题导航
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 与方案 A 一样新增独立卡片
|
||||||
|
- 但主题切换改为左侧竖向导航
|
||||||
|
|
||||||
|
问题:
|
||||||
|
|
||||||
|
- 用户已明确要求通过 Tab 页切换主题
|
||||||
|
- 与当前页面已有横向区块风格不一致
|
||||||
|
|
||||||
|
### 5.4 推荐方案
|
||||||
|
|
||||||
|
采用方案 A:在图谱外链卡片下方新增独立拓展查询卡片,卡片内部使用横向 Tab 切换采购、招聘、调动 3 个主题。
|
||||||
|
|
||||||
|
## 六、页面结构设计
|
||||||
|
|
||||||
|
专项核查页整体结构调整后如下:
|
||||||
|
|
||||||
|
1. 员工家庭资产负债专项核查卡片
|
||||||
|
2. 图谱外链展示卡片
|
||||||
|
3. 拓展查询卡片
|
||||||
|
|
||||||
|
其中前两张卡片不修改现有交互,仅在其下方追加第三张卡片。
|
||||||
|
|
||||||
|
拓展查询卡片的渲染条件独立于家庭资产负债列表数据:
|
||||||
|
|
||||||
|
- 只要当前 `projectId` 有效,就应展示拓展查询卡片
|
||||||
|
- 即使家庭资产负债列表为空,拓展查询卡片仍需可见并可使用
|
||||||
|
|
||||||
|
拓展查询卡片结构如下:
|
||||||
|
|
||||||
|
### 6.1 卡片头部
|
||||||
|
|
||||||
|
- 标题:拓展查询
|
||||||
|
- 副标题:查询专项核查同范围员工的采购、招聘、调动记录
|
||||||
|
|
||||||
|
### 6.2 Tab 区
|
||||||
|
|
||||||
|
- Tab 1:采购记录
|
||||||
|
- Tab 2:招聘记录
|
||||||
|
- Tab 3:调动记录
|
||||||
|
|
||||||
|
### 6.3 主题查询区
|
||||||
|
|
||||||
|
每个 Tab 使用独立查询栏:
|
||||||
|
|
||||||
|
- 采购记录:
|
||||||
|
- 申请人姓名
|
||||||
|
- 申请日期范围
|
||||||
|
- 搜索
|
||||||
|
- 重置
|
||||||
|
- 招聘记录:
|
||||||
|
- 面试官姓名
|
||||||
|
- 搜索
|
||||||
|
- 重置
|
||||||
|
- 调动记录:
|
||||||
|
- 员工姓名
|
||||||
|
- 调动日期范围
|
||||||
|
- 搜索
|
||||||
|
- 重置
|
||||||
|
|
||||||
|
### 6.4 列表区
|
||||||
|
|
||||||
|
Tab 下方展示当前主题列表与分页。
|
||||||
|
|
||||||
|
切换 Tab 时:
|
||||||
|
|
||||||
|
- 不共用查询表单
|
||||||
|
- 不互相覆盖列表数据
|
||||||
|
- 各主题保留自己的查询条件与分页状态
|
||||||
|
- 首次进入某个 Tab 时再触发该 Tab 的首查
|
||||||
|
|
||||||
|
### 6.5 详情弹窗
|
||||||
|
|
||||||
|
操作列统一提供“查看详情”按钮。
|
||||||
|
|
||||||
|
点击后:
|
||||||
|
|
||||||
|
- 采购记录弹出采购详情弹窗
|
||||||
|
- 招聘记录弹出招聘详情弹窗
|
||||||
|
- 调动记录弹出调动详情弹窗
|
||||||
|
|
||||||
|
详情弹窗展示该条记录全部字段,不裁剪字段内容。
|
||||||
|
|
||||||
|
## 七、列表字段设计
|
||||||
|
|
||||||
|
### 7.1 采购记录列表字段
|
||||||
|
|
||||||
|
- 采购事项 ID
|
||||||
|
- 项目名称
|
||||||
|
- 标的物名称
|
||||||
|
- 申请人姓名
|
||||||
|
- 申请日期
|
||||||
|
- 操作
|
||||||
|
|
||||||
|
### 7.2 招聘记录列表字段
|
||||||
|
|
||||||
|
- 招聘项目编号
|
||||||
|
- 招聘项目名称
|
||||||
|
- 职位名称
|
||||||
|
- 面试官姓名摘要
|
||||||
|
- 录用情况
|
||||||
|
- 操作
|
||||||
|
|
||||||
|
其中面试官姓名摘要定义为:
|
||||||
|
|
||||||
|
- 优先展示 `面试官1姓名 / 面试官2姓名`
|
||||||
|
- 如某一个为空,则只展示存在的姓名
|
||||||
|
|
||||||
|
### 7.3 调动记录列表字段
|
||||||
|
|
||||||
|
- 员工姓名
|
||||||
|
- 调动类型
|
||||||
|
- 调动前部门
|
||||||
|
- 调动后部门
|
||||||
|
- 调动日期
|
||||||
|
- 操作
|
||||||
|
|
||||||
|
## 八、后端设计
|
||||||
|
|
||||||
|
### 8.1 接口归属
|
||||||
|
|
||||||
|
本次不直接复用信息采集模块现有 `/list` 接口,而是在专项核查域新增项目范围拓展查询接口,统一挂在:
|
||||||
|
|
||||||
|
- `CcdiProjectSpecialCheckController`
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
1. 现有信息采集模块接口仅面向各自主表查询,不包含“专项核查同范围员工”的限制。
|
||||||
|
2. 如果前端调用现有接口后自行过滤,会导致项目范围口径不稳定。
|
||||||
|
3. 在专项核查域统一收口,后续维护更清晰。
|
||||||
|
|
||||||
|
### 8.2 接口设计
|
||||||
|
|
||||||
|
建议新增 6 个接口:
|
||||||
|
|
||||||
|
- `GET /ccdi/project/special-check/extended-query/purchase/list`
|
||||||
|
- `GET /ccdi/project/special-check/extended-query/purchase/detail`
|
||||||
|
- `GET /ccdi/project/special-check/extended-query/recruitment/list`
|
||||||
|
- `GET /ccdi/project/special-check/extended-query/recruitment/detail`
|
||||||
|
- `GET /ccdi/project/special-check/extended-query/transfer/list`
|
||||||
|
- `GET /ccdi/project/special-check/extended-query/transfer/detail`
|
||||||
|
|
||||||
|
### 8.3 项目范围员工口径复用
|
||||||
|
|
||||||
|
项目范围员工统一复用当前专项核查 mapper 中的员工范围定义,即:
|
||||||
|
|
||||||
|
- `CcdiProjectSpecialCheckMapper.xml` 中的 `projectEmployeeScopeSql`
|
||||||
|
|
||||||
|
该范围已经与当前专项核查页保持一致,本轮不重新定义口径,不新增第二套项目员工范围逻辑。
|
||||||
|
|
||||||
|
### 8.4 列表查询设计
|
||||||
|
|
||||||
|
#### 采购记录
|
||||||
|
|
||||||
|
数据来源:
|
||||||
|
|
||||||
|
- `ccdi_purchase_transaction`
|
||||||
|
|
||||||
|
范围限制:
|
||||||
|
|
||||||
|
- 记录必须命中专项核查同范围员工
|
||||||
|
|
||||||
|
筛选条件:
|
||||||
|
|
||||||
|
- `projectId`
|
||||||
|
- `applicantName`
|
||||||
|
- `applyDateStart`
|
||||||
|
- `applyDateEnd`
|
||||||
|
|
||||||
|
员工姓名口径:
|
||||||
|
|
||||||
|
- 按采购记录的 `applicant_name` 检索
|
||||||
|
|
||||||
|
时间口径:
|
||||||
|
|
||||||
|
- 按采购记录的 `apply_date` 检索
|
||||||
|
|
||||||
|
#### 招聘记录
|
||||||
|
|
||||||
|
数据来源:
|
||||||
|
|
||||||
|
- `ccdi_staff_recruitment`
|
||||||
|
|
||||||
|
范围限制:
|
||||||
|
|
||||||
|
- 记录必须命中专项核查同范围员工
|
||||||
|
|
||||||
|
筛选条件:
|
||||||
|
|
||||||
|
- `projectId`
|
||||||
|
- `interviewerName`
|
||||||
|
|
||||||
|
员工姓名口径:
|
||||||
|
|
||||||
|
- 按招聘记录的 `interviewer_name_1` 或 `interviewer_name_2` 检索
|
||||||
|
- 同一条招聘记录若同时命中 `interviewer_name_1` 与 `interviewer_name_2`,列表结果必须按招聘记录主键去重
|
||||||
|
|
||||||
|
时间口径:
|
||||||
|
|
||||||
|
- 本次不提供招聘时间筛选
|
||||||
|
|
||||||
|
#### 调动记录
|
||||||
|
|
||||||
|
数据来源:
|
||||||
|
|
||||||
|
- `ccdi_staff_transfer`
|
||||||
|
- `ccdi_base_staff`
|
||||||
|
|
||||||
|
范围限制:
|
||||||
|
|
||||||
|
- 记录必须命中专项核查同范围员工
|
||||||
|
|
||||||
|
筛选条件:
|
||||||
|
|
||||||
|
- `projectId`
|
||||||
|
- `staffName`
|
||||||
|
- `transferDateStart`
|
||||||
|
- `transferDateEnd`
|
||||||
|
|
||||||
|
员工姓名口径:
|
||||||
|
|
||||||
|
- 按调动记录对应员工的 `staff_name` 检索
|
||||||
|
|
||||||
|
时间口径:
|
||||||
|
|
||||||
|
- 按 `transfer_date` 检索
|
||||||
|
|
||||||
|
### 8.5 详情查询设计
|
||||||
|
|
||||||
|
详情接口按各自主键查询,并返回该条记录全部字段:
|
||||||
|
|
||||||
|
- 采购详情:按 `purchaseId`
|
||||||
|
- 招聘详情:按 `recruitId`
|
||||||
|
- 调动详情:按 `id`
|
||||||
|
|
||||||
|
详情查询仍需校验该记录属于当前专项核查同范围员工;不属于时返回业务错误,避免越权或口径漂移。
|
||||||
|
|
||||||
|
### 8.6 DTO / VO 设计
|
||||||
|
|
||||||
|
建议在 `ccdi-project` 模块下新增独立 DTO:
|
||||||
|
|
||||||
|
- 采购拓展查询 DTO
|
||||||
|
- 招聘拓展查询 DTO
|
||||||
|
- 调动拓展查询 DTO
|
||||||
|
|
||||||
|
其中招聘主题查询字段命名统一使用 `interviewerName`,不再使用泛化的 `staffName`。
|
||||||
|
采购主题查询字段命名统一使用 `applicantName`,不再使用泛化的 `staffName`。
|
||||||
|
|
||||||
|
列表返回对象按主题独立定义轻量 VO,仅保留列表必要字段。
|
||||||
|
|
||||||
|
详情返回可优先复用现有 VO 字段结构:
|
||||||
|
|
||||||
|
- `CcdiPurchaseTransactionVO`
|
||||||
|
- `CcdiStaffRecruitmentVO`
|
||||||
|
- `CcdiStaffTransferVO`
|
||||||
|
|
||||||
|
如直接复用存在耦合问题,再在 `ccdi-project` 下补充专项核查详情 VO,但本轮优先选择最短路径实现。
|
||||||
|
|
||||||
|
## 九、前端设计
|
||||||
|
|
||||||
|
### 9.1 页面挂载位置
|
||||||
|
|
||||||
|
继续以:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/SpecialCheck.vue`
|
||||||
|
|
||||||
|
作为专项核查页签容器,不新增路由。
|
||||||
|
|
||||||
|
### 9.2 组件拆分
|
||||||
|
|
||||||
|
建议新增一张独立的拓展查询区块组件,例如:
|
||||||
|
|
||||||
|
- `ExtendedQuerySection.vue`
|
||||||
|
|
||||||
|
职责如下:
|
||||||
|
|
||||||
|
- 管理当前激活 Tab
|
||||||
|
- 管理各 Tab 的查询参数
|
||||||
|
- 驱动列表加载与分页
|
||||||
|
- 打开并关闭详情弹窗
|
||||||
|
|
||||||
|
详情展示建议按主题拆分为小组件或局部模板:
|
||||||
|
|
||||||
|
- 采购详情复用现有采购页的分组展示方式
|
||||||
|
- 招聘详情复用现有招聘页的分组展示方式
|
||||||
|
- 调动详情补齐与前两者一致的详情展示结构
|
||||||
|
|
||||||
|
### 9.3 状态管理
|
||||||
|
|
||||||
|
每个 Tab 独立维护以下状态:
|
||||||
|
|
||||||
|
- `query`
|
||||||
|
- `list`
|
||||||
|
- `loading`
|
||||||
|
- `pageNum`
|
||||||
|
- `pageSize`
|
||||||
|
- `total`
|
||||||
|
- `detailOpen`
|
||||||
|
- `detailLoading`
|
||||||
|
- `detailData`
|
||||||
|
|
||||||
|
原则如下:
|
||||||
|
|
||||||
|
1. 不共享查询表单
|
||||||
|
2. 不共享列表数据
|
||||||
|
3. 切换 Tab 时保留各自上次查询状态
|
||||||
|
4. 首次进入 Tab 时再请求列表
|
||||||
|
|
||||||
|
### 9.4 空态与异常
|
||||||
|
|
||||||
|
- 列表无数据时展示当前主题无数据空态
|
||||||
|
- 列表查询失败时提示错误信息,并清空当前主题列表
|
||||||
|
- 详情查询失败时只提示错误,不污染列表状态
|
||||||
|
- `projectId` 为空时沿用当前专项核查页已有空态逻辑
|
||||||
|
|
||||||
|
## 十、测试与验证设计
|
||||||
|
|
||||||
|
### 10.1 前端验证
|
||||||
|
|
||||||
|
补充以下验证:
|
||||||
|
|
||||||
|
1. 专项核查页新增拓展查询卡片结构断言
|
||||||
|
2. 卡片位于图谱外链卡片下方的结构断言
|
||||||
|
3. 3 个主题 Tab 的存在性断言
|
||||||
|
4. 不同 Tab 显示不同查询栏字段的断言
|
||||||
|
5. 操作列“查看详情”按钮存在性断言
|
||||||
|
6. 调动主题详情弹窗存在性断言
|
||||||
|
7. 前端构建验证
|
||||||
|
|
||||||
|
### 10.2 后端验证
|
||||||
|
|
||||||
|
补充以下验证:
|
||||||
|
|
||||||
|
1. 项目范围员工口径是否与 `projectEmployeeScopeSql` 一致
|
||||||
|
2. 采购列表按申请人姓名和申请日期范围过滤是否正确
|
||||||
|
3. 招聘列表按面试官姓名过滤是否正确
|
||||||
|
4. 调动列表按员工姓名和调动日期范围过滤是否正确
|
||||||
|
5. 详情接口是否能校验记录属于当前项目范围员工
|
||||||
|
6. 记录不属于当前项目范围员工时是否返回预期错误
|
||||||
|
|
||||||
|
### 10.3 联调验证
|
||||||
|
|
||||||
|
联调时重点验证:
|
||||||
|
|
||||||
|
1. Tab 切换后各自查询条件不会互相污染
|
||||||
|
2. 招聘主题不展示时间范围
|
||||||
|
3. 采购与调动主题展示时间范围
|
||||||
|
4. 详情弹窗字段完整性与原业务页一致
|
||||||
|
|
||||||
|
## 十一、实施约束
|
||||||
|
|
||||||
|
本轮实施需遵守以下要求:
|
||||||
|
|
||||||
|
1. 不新增兼容性分支或降级方案。
|
||||||
|
2. 不在需求外扩展更多主题。
|
||||||
|
3. 不新增导出、编辑、删除、批量操作。
|
||||||
|
4. 不改变现有图谱占位卡片行为。
|
||||||
|
5. 根据本设计文档产出两份实施计划:
|
||||||
|
- 后端实施计划放 `docs/plans/backend/`
|
||||||
|
- 前端实施计划放 `docs/plans/frontend/`
|
||||||
|
|
||||||
|
## 十二、结论
|
||||||
|
|
||||||
|
本次采用“图谱外链卡片下方新增独立拓展查询卡片”的方案,在专项核查页签内新增采购、招聘、调动 3 个主题查询能力。所有查询严格限制为当前专项核查同范围员工,采购与调动支持时间范围筛选,招聘仅支持面试官姓名筛选。前端不新增路由,后端在专项核查域新增统一接口,保持实现路径最短、页面职责清晰、业务口径稳定。
|
||||||
364
docs/design/2026-03-25-lsfx-mock-all-hit-sql-alignment-design.md
Normal file
364
docs/design/2026-03-25-lsfx-mock-all-hit-sql-alignment-design.md
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
# lsfx-mock all 模式 SQL 对齐强命中设计
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
当前 `lsfx-mock-server` 的 `--rule-hit-mode all` 已支持为多类规则生成流水样本,但生成策略仍带有早期“语义化样本”特征,未完全对齐主工程真实 SQL 的命中口径。
|
||||||
|
|
||||||
|
在最新排查中,以下 5 条已落地真实 SQL 的规则仍无法稳定命中:
|
||||||
|
|
||||||
|
- `SPECIAL_AMOUNT_TRANSACTION`
|
||||||
|
- `MONTHLY_FIXED_INCOME`
|
||||||
|
- `SUSPICIOUS_INCOME_KEYWORD`
|
||||||
|
- `FIXED_COUNTERPARTY_TRANSFER`
|
||||||
|
- `LOW_INCOME_RELATIVE_LARGE_TRANSACTION`
|
||||||
|
|
||||||
|
问题根因不是主工程打标链路异常,而是 Mock 样本和关联表数据与后端 XML SQL 条件不一致:
|
||||||
|
|
||||||
|
- 特殊金额样本金额错误
|
||||||
|
- 固定月收入样本月数不足
|
||||||
|
- 收入关键词样本摘要不在 SQL 词表内
|
||||||
|
- 固定对手转入样本主体落在家属证件号
|
||||||
|
- 低收入亲属规则缺少满足阈值的关系表收入基线
|
||||||
|
|
||||||
|
本次设计目标是在不修改主工程打标逻辑的前提下,只通过调整 `lsfx-mock-server` 的 `all` 模式生成策略,让以上 5 条真实 SQL 规则稳定命中,并尽量抑制额外噪声命中。
|
||||||
|
|
||||||
|
## 2. 目标
|
||||||
|
|
||||||
|
- 仅针对 `--rule-hit-mode all` 生效,`subset` 模式保持现状不变。
|
||||||
|
- 仅覆盖后端 XML 中已落地真实 SQL 的规则,不处理占位 SQL。
|
||||||
|
- 让上述 5 条规则在真实链路下稳定命中:
|
||||||
|
- Mock 返回流水
|
||||||
|
- 主工程入库 `ccdi_bank_statement`
|
||||||
|
- 触发重打标
|
||||||
|
- 在 `ccdi_bank_statement_tag_result` 中看到命中结果
|
||||||
|
- 允许通过补写最小关联表基线满足 SQL 前置条件。
|
||||||
|
- 尽量压住因普通噪声流水导致的误命中或聚合口径污染。
|
||||||
|
|
||||||
|
## 3. 非目标
|
||||||
|
|
||||||
|
- 不修改主工程 `ccdi-project` 中的 XML SQL、Service 或打标逻辑。
|
||||||
|
- 不处理占位 SQL 规则:
|
||||||
|
- `ABNORMAL_CUSTOMER_TRANSACTION`
|
||||||
|
- `INTEREST_PAYMENT_BY_OTHERS`
|
||||||
|
- 以及其他仍为 `where 1 = 0` 的规则
|
||||||
|
- 不把 `subset` 模式升级成同样的强命中模型。
|
||||||
|
- 不引入 DSL、规则解释器或额外配置中心。
|
||||||
|
- 不做与本次 5 条规则无关的通用重构。
|
||||||
|
|
||||||
|
## 4. 范围
|
||||||
|
|
||||||
|
### 4.1 目标规则
|
||||||
|
|
||||||
|
- `SUSPICIOUS_RELATION / SPECIAL_AMOUNT_TRANSACTION`
|
||||||
|
- `SUSPICIOUS_PART_TIME / MONTHLY_FIXED_INCOME`
|
||||||
|
- `SUSPICIOUS_PART_TIME / SUSPICIOUS_INCOME_KEYWORD`
|
||||||
|
- `SUSPICIOUS_PART_TIME / FIXED_COUNTERPARTY_TRANSFER`
|
||||||
|
- `ABNORMAL_TRANSACTION / LOW_INCOME_RELATIVE_LARGE_TRANSACTION`
|
||||||
|
|
||||||
|
### 4.2 涉及模块
|
||||||
|
|
||||||
|
- `lsfx-mock-server/services/file_service.py`
|
||||||
|
- `lsfx-mock-server/services/statement_service.py`
|
||||||
|
- `lsfx-mock-server/services/statement_rule_samples.py`
|
||||||
|
- `lsfx-mock-server/services/phase2_baseline_service.py`
|
||||||
|
- `lsfx-mock-server/tests/test_file_service.py`
|
||||||
|
- `lsfx-mock-server/tests/test_statement_service.py`
|
||||||
|
- 视需要补充或调整集成测试
|
||||||
|
|
||||||
|
## 5. 方案对比
|
||||||
|
|
||||||
|
### 5.1 方案 A:SQL 镜像驱动强命中
|
||||||
|
|
||||||
|
按后端 XML SQL 条件重写 `all` 模式样本和关联表基线。每条目标规则拥有专属、确定性的输入组,并对噪声数据施加约束。
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 与真实 SQL 口径一致
|
||||||
|
- 命中稳定,可重复验证
|
||||||
|
- 便于定位问题,后续逐条扩展也清晰
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 需要逐条核对 SQL 条件并同步改写样本函数
|
||||||
|
|
||||||
|
### 5.2 方案 B:最小命中修补
|
||||||
|
|
||||||
|
仅把当前明显不命中的样本做最小修修补补,不系统控制噪声和规则串扰。
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 改动量较小
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 容易出现“目标规则命中了,但多出一批误命中”
|
||||||
|
- 后续 SQL 小改动后容易再次失效
|
||||||
|
|
||||||
|
### 5.3 方案 C:配置化规则生成器
|
||||||
|
|
||||||
|
抽象规则配置层,由统一生成器按配置组装样本。
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 长期扩展性好
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 明显超出本次最短路径诉求
|
||||||
|
- 需要引入新的抽象和维护成本
|
||||||
|
|
||||||
|
## 6. 推荐方案
|
||||||
|
|
||||||
|
采用方案 A。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 这是当前最短且逻辑闭环的实现路径。
|
||||||
|
- 目标是“真实 SQL 下稳定命中”,因此 Mock 端必须直接对齐 XML 条件,而不是继续依靠语义相近的样本。
|
||||||
|
- 只在 `all` 模式启用该策略,可以在不破坏现有 `subset` 习惯的前提下完成强命中能力。
|
||||||
|
|
||||||
|
## 7. 设计原则
|
||||||
|
|
||||||
|
- `all` 模式中的目标规则样本必须是 deterministic 的,不依赖随机碰运气。
|
||||||
|
- 每条目标规则都要有独立的命中样本簇,尽量避免多条规则共享同一组核心流水。
|
||||||
|
- 规则所需的主体类型必须与 SQL 首层 join 一致:
|
||||||
|
- 仅员工本人:样本 `cret_no` 必须落在 `ccdi_base_staff.id_card`
|
||||||
|
- 依赖家属:样本 `cret_no` 必须落在 `ccdi_staff_fmy_relation.relation_cert_no`
|
||||||
|
- 对存在聚合或收口条件的规则,必须同时控制背景噪声,避免误触发额外对手方、额外月份或额外支出。
|
||||||
|
- 关联表基线只写“命中真实 SQL 所需的最小数据”,并且应可重复写入、可覆盖旧状态。
|
||||||
|
|
||||||
|
## 8. 模块设计
|
||||||
|
|
||||||
|
### 8.1 `file_service.py`
|
||||||
|
|
||||||
|
职责保持为“生成命中计划并记录文件级上下文”,但 `all` 模式逻辑调整为:
|
||||||
|
|
||||||
|
- 继续保留现有大额交易、第一期、第二期规则计划字段,避免影响调用方结构。
|
||||||
|
- 对本次 5 条目标规则,在 `all` 模式下强制加入计划。
|
||||||
|
- 占位 SQL 规则不进入 `all` 模式强命中计划。
|
||||||
|
- 在记录中保留足够的身份上下文,供基线服务和样本服务共享使用。
|
||||||
|
|
||||||
|
`subset` 模式不改。
|
||||||
|
|
||||||
|
### 8.2 `statement_rule_samples.py`
|
||||||
|
|
||||||
|
保留“每条规则一个构造函数”的现有结构,不引入新 DSL。
|
||||||
|
|
||||||
|
本次只重写以下函数:
|
||||||
|
|
||||||
|
- `build_special_amount_transaction_samples`
|
||||||
|
- `build_monthly_fixed_income_samples`
|
||||||
|
- `build_suspicious_income_keyword_samples`
|
||||||
|
- `build_fixed_counterparty_transfer_samples`
|
||||||
|
- `build_low_income_relative_large_transaction_samples`
|
||||||
|
|
||||||
|
同时对 `all` 模式下的噪声样本约束做最小增强,避免干扰:
|
||||||
|
|
||||||
|
- 不制造过多重复 `CUSTOMER_ACCOUNT_NAME`
|
||||||
|
- 不制造额外稳定季度转入
|
||||||
|
- 不制造会破坏“工资后无支出”或“固定月收入稳定性”的额外流水
|
||||||
|
|
||||||
|
### 8.3 `phase2_baseline_service.py`
|
||||||
|
|
||||||
|
继续作为关联表最小基线服务使用,但扩展为支持本次规则所需的关系表精确写入。
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
- 针对 `LOW_INCOME_RELATIVE_LARGE_TRANSACTION` 写入低收入家属基线
|
||||||
|
- 针对 `SPECIAL_AMOUNT_TRANSACTION` 保证目标对手不被识别成配偶/子女
|
||||||
|
- 只覆盖规则命中的必要字段,不额外灌入无关主数据
|
||||||
|
|
||||||
|
幂等策略:
|
||||||
|
|
||||||
|
- 对既有关系数据,按目标人员主键做“精确覆盖”而不是追加污染
|
||||||
|
- 保持同一员工域下重复执行结果一致
|
||||||
|
|
||||||
|
## 9. 规则级生成策略
|
||||||
|
|
||||||
|
### 9.1 `SPECIAL_AMOUNT_TRANSACTION`
|
||||||
|
|
||||||
|
对应 SQL 特征:
|
||||||
|
|
||||||
|
- 员工本人 `inner join ccdi_base_staff`
|
||||||
|
- 对手不是配偶/子女
|
||||||
|
- 金额必须为 `520` 或 `1314`
|
||||||
|
|
||||||
|
生成策略:
|
||||||
|
|
||||||
|
- 样本主体使用员工本人证件号
|
||||||
|
- 生成 1 至 2 笔支出或收入流水,金额固定取 `520` 或 `1314`
|
||||||
|
- 对手方名称使用专属固定名称
|
||||||
|
- 基线服务不为该对手建立“配偶/子女”关系
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- 不再使用 `88888.88`
|
||||||
|
- 不复用现有家庭关系中的配偶、子女姓名,避免误入排除条件
|
||||||
|
|
||||||
|
### 9.2 `MONTHLY_FIXED_INCOME`
|
||||||
|
|
||||||
|
对应 SQL 特征:
|
||||||
|
|
||||||
|
- 员工本人 `inner join ccdi_base_staff`
|
||||||
|
- 近 12 个月至少 6 个月月收入大于阈值
|
||||||
|
- 排除工资代发主体和工资摘要
|
||||||
|
- 月度波动率不高于 `0.3`
|
||||||
|
|
||||||
|
生成策略:
|
||||||
|
|
||||||
|
- 样本主体切到员工本人
|
||||||
|
- 构造连续 6 个月固定转入,每月 1 笔
|
||||||
|
- 对手方固定
|
||||||
|
- 月金额保持完全一致或极小波动
|
||||||
|
- 对手方名称不是本行工资代发主体
|
||||||
|
- 摘要不包含工资相关关键词
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- all 模式噪声流水不能再向该员工叠加大额杂散流入,避免抬高波动率
|
||||||
|
|
||||||
|
### 9.3 `SUSPICIOUS_INCOME_KEYWORD`
|
||||||
|
|
||||||
|
对应 SQL 特征:
|
||||||
|
|
||||||
|
- 员工本人 `inner join ccdi_base_staff`
|
||||||
|
- 收入流水
|
||||||
|
- 摘要或交易类型命中收入关键词词表
|
||||||
|
|
||||||
|
生成策略:
|
||||||
|
|
||||||
|
- 样本主体使用员工本人
|
||||||
|
- 生成 1 笔收入流水
|
||||||
|
- 摘要直接使用 SQL 关键词集合中的明确命中词,例如“劳务费发放”或“奖金发放”
|
||||||
|
- 交易类型可同步设置为“劳务费”或“代发收入”一类,形成双重保险
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- 不再使用“咨询返现收入”“合作收入”这类 SQL 不识别的语义词
|
||||||
|
|
||||||
|
### 9.4 `FIXED_COUNTERPARTY_TRANSFER`
|
||||||
|
|
||||||
|
对应 SQL 特征:
|
||||||
|
|
||||||
|
- 员工本人 `inner join ccdi_base_staff`
|
||||||
|
- 近 12 个月内,固定对手方至少 2 个季度累计转入位于区间 `[3000,15000]`
|
||||||
|
- 满足条件的固定对手数量 `< 3`
|
||||||
|
|
||||||
|
生成策略:
|
||||||
|
|
||||||
|
- 样本主体改为员工本人
|
||||||
|
- 固定 1 个专属对手方
|
||||||
|
- 至少覆盖 2 个季度,建议 2 到 3 个季度
|
||||||
|
- 每季度累计转入控制在区间中部,避免靠边界
|
||||||
|
- 每季度仅保留 1 笔或少量固定笔数,便于验证
|
||||||
|
|
||||||
|
噪声控制:
|
||||||
|
|
||||||
|
- all 模式噪声流水中,员工本人不得再出现过多重复对手方季度转入
|
||||||
|
- 避免出现第二、第三个误满足区间条件的稳定对手
|
||||||
|
|
||||||
|
### 9.5 `LOW_INCOME_RELATIVE_LARGE_TRANSACTION`
|
||||||
|
|
||||||
|
对应 SQL 特征:
|
||||||
|
|
||||||
|
- 家属证件号与 `ccdi_staff_fmy_relation.relation_cert_no` 关联
|
||||||
|
- 家属年收入为空、为 0,或折月小于 `3000`
|
||||||
|
- 家属累计交易金额大于 `100000`
|
||||||
|
|
||||||
|
生成策略:
|
||||||
|
|
||||||
|
- 使用目标员工的家属证件号作为流水主体
|
||||||
|
- 生成 2 笔以上累计超过 `100000` 的交易
|
||||||
|
- 基线服务把该家属 `annual_income` 精确设置为:
|
||||||
|
- `0`
|
||||||
|
- `null`
|
||||||
|
- 或低于 `36000`
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- 不依赖项目库现存家属收入
|
||||||
|
- 每次 all 模式生成时都要确保目标家属收入状态被重置到可命中口径
|
||||||
|
|
||||||
|
## 10. 噪声控制设计
|
||||||
|
|
||||||
|
当前 `StatementService` 中的随机噪声会对聚合类规则造成干扰。本次只做最小必要控制:
|
||||||
|
|
||||||
|
- `all` 模式下继续保留背景噪声,但员工本人使用更收敛的对手方集合
|
||||||
|
- 对目标员工:
|
||||||
|
- 避免随机制造跨季度重复转入
|
||||||
|
- 避免随机制造大量同月对公收入
|
||||||
|
- 避免随机制造会抬高 `MONTHLY_FIXED_INCOME` 波动率的异常大额流入
|
||||||
|
- 对家属样本:
|
||||||
|
- 避免随机覆盖低收入亲属规则的专属家属域
|
||||||
|
|
||||||
|
实现上优先采用“all 模式下缩窄噪声候选集”而不是彻底禁掉噪声。
|
||||||
|
|
||||||
|
## 11. 数据流
|
||||||
|
|
||||||
|
1. `FileService` 在 `all` 模式下构建强命中规则计划。
|
||||||
|
2. `phase2_baseline_service` 根据计划写入最小关联表基线。
|
||||||
|
3. `StatementService` 根据规则计划生成专属流水样本,并补受控噪声。
|
||||||
|
4. 主工程按原流程拉取、入库并执行打标。
|
||||||
|
5. 目标规则在结果表中稳定出现命中记录。
|
||||||
|
|
||||||
|
## 12. 测试与验证
|
||||||
|
|
||||||
|
### 12.1 Mock 单元测试
|
||||||
|
|
||||||
|
至少补以下断言:
|
||||||
|
|
||||||
|
- `SPECIAL_AMOUNT_TRANSACTION` 样本金额为 `520/1314`
|
||||||
|
- `MONTHLY_FIXED_INCOME` 样本月份数不少于 `6`
|
||||||
|
- `SUSPICIOUS_INCOME_KEYWORD` 摘要或交易类型命中 SQL 关键词
|
||||||
|
- `FIXED_COUNTERPARTY_TRANSFER` 样本主体为员工本人而非家属
|
||||||
|
- `LOW_INCOME_RELATIVE_LARGE_TRANSACTION` 所需家属基线收入满足低收入条件
|
||||||
|
|
||||||
|
### 12.2 联调验证
|
||||||
|
|
||||||
|
沿用真实链路验证:
|
||||||
|
|
||||||
|
1. 启动 Mock:`main.py --rule-hit-mode all`
|
||||||
|
2. 拉取流水并导入项目
|
||||||
|
3. 触发重打标
|
||||||
|
4. 查询 `ccdi_bank_statement_tag_result`
|
||||||
|
|
||||||
|
验收通过标准:
|
||||||
|
|
||||||
|
- 上述 5 条规则全部存在命中记录
|
||||||
|
- 命中主体与预期主体一致
|
||||||
|
- 没有明显失控的额外噪声命中
|
||||||
|
|
||||||
|
## 13. 风险与控制
|
||||||
|
|
||||||
|
风险:
|
||||||
|
|
||||||
|
- 规则间共享主体导致聚合口径互相污染
|
||||||
|
- 噪声流水再次引入额外固定对手或异常月收入
|
||||||
|
- 关系表覆盖不当污染其他联调项目
|
||||||
|
|
||||||
|
控制:
|
||||||
|
|
||||||
|
- 每条目标规则尽量使用独立样本簇
|
||||||
|
- all 模式下对目标员工域启用受控噪声
|
||||||
|
- 基线服务只覆盖当前命中域,不做全表灌数
|
||||||
|
|
||||||
|
## 14. 实施建议
|
||||||
|
|
||||||
|
按以下顺序执行最稳妥:
|
||||||
|
|
||||||
|
1. 先改 5 条样本函数
|
||||||
|
2. 再补低收入家属基线写入
|
||||||
|
3. 再收紧 all 模式噪声
|
||||||
|
4. 最后补单元测试和联调验证
|
||||||
|
|
||||||
|
这样可以分层确认:
|
||||||
|
|
||||||
|
- 样本本身对不对
|
||||||
|
- 主数据前置条件够不够
|
||||||
|
- 噪声是否把聚合口径冲坏
|
||||||
|
|
||||||
|
## 15. 结论
|
||||||
|
|
||||||
|
本次采用“只改 `all` 模式、只改真实 SQL 对应规则、按 XML 口径重写样本与最小基线”的方案,是满足当前需求的最短路径。
|
||||||
|
|
||||||
|
设计完成后,后续实施只需围绕 5 条目标规则逐条落地,不需要修改主工程,也不需要引入额外抽象层。
|
||||||
@@ -0,0 +1,453 @@
|
|||||||
|
# 结果总览项目分析弹窗设计文档
|
||||||
|
|
||||||
|
**日期**: 2026-03-25
|
||||||
|
**模块**: 初核项目详情 - 结果总览
|
||||||
|
**作者**: Codex
|
||||||
|
**状态**: 已批准
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
本设计用于补齐 `结果总览` 页面中两处人员列表的“查看项目”能力:
|
||||||
|
|
||||||
|
- `风险人员总览`
|
||||||
|
- `命中模型涉及人员列表`
|
||||||
|
|
||||||
|
点击后,统一打开同一个“项目分析”弹窗,在弹窗内展示人员分析工作台。视觉方向以用户提供的截图为基准,采用“左侧人物档案 + 右侧分析页签”的工作台布局;同时对左侧信息栏做适度收敛,避免在弹窗内重复完整页面级导航。
|
||||||
|
|
||||||
|
本轮目标是先完成前端高保真弹窗与基础切换能力,内容允许基于现有结果总览数据和静态占位组装,不在本轮扩展后端接口。
|
||||||
|
|
||||||
|
## 2. 设计范围
|
||||||
|
|
||||||
|
### 2.1 包含内容
|
||||||
|
|
||||||
|
- 在 `结果总览` 页内新增统一的“项目分析”弹窗交互
|
||||||
|
- 两个入口列表共用一套弹窗壳子与信息架构
|
||||||
|
- 弹窗内保留 5 个页签:
|
||||||
|
- 异常明细
|
||||||
|
- 资产分析
|
||||||
|
- 征信摘要
|
||||||
|
- 关系图谱
|
||||||
|
- 资金流向
|
||||||
|
- 默认页签为 `异常明细`
|
||||||
|
- 左侧侧栏展示当前人员基础信息、命中模型摘要、排查记录摘要
|
||||||
|
- 右侧 `异常明细` 页签按截图风格做完整高保真布局
|
||||||
|
- 其余 4 个页签提供可切换的高保真静态承载
|
||||||
|
|
||||||
|
### 2.2 不包含内容
|
||||||
|
|
||||||
|
- 不新增后端接口
|
||||||
|
- 不改造项目详情页路由或导航结构
|
||||||
|
- 不把弹窗拆成新页面或新页签
|
||||||
|
- 不在本轮接通 5 个页签的真实分析链路
|
||||||
|
- 不扩展导出、权限、审批流等截图之外的新业务流程
|
||||||
|
- 不补充兜底、降级或兼容性分支方案
|
||||||
|
|
||||||
|
## 3. 当前上下文
|
||||||
|
|
||||||
|
当前结果总览主入口与相关区块如下:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskModelSection.vue`
|
||||||
|
|
||||||
|
当前状态:
|
||||||
|
|
||||||
|
1. 两个区块的操作列都已有“查看详情”按钮位
|
||||||
|
2. 现有按钮尚未绑定统一弹窗交互
|
||||||
|
3. `RiskPeopleSection.vue` 已具备人员基础信息、风险等级、核心异常点等展示字段
|
||||||
|
4. `RiskModelSection.vue` 已具备人员信息、命中模型、异常标签等展示字段
|
||||||
|
5. 结果总览当前真实接口仅覆盖仪表盘、风险人员总览、模型卡片与模型命中人员列表,不足以支撑完整项目分析工作台
|
||||||
|
|
||||||
|
因此,本轮设计应遵循最短路径实现:只在现有前端链路上新增统一弹窗,不扩散到后端。
|
||||||
|
|
||||||
|
## 4. 设计目标
|
||||||
|
|
||||||
|
### 4.1 业务目标
|
||||||
|
|
||||||
|
- 让用户在结果总览中直接查看某名人员对应的项目分析内容
|
||||||
|
- 让两个入口汇聚到同一个分析弹窗,避免形成两套详情语义
|
||||||
|
- 保持“从列表进入分析工作台”的浏览连贯性
|
||||||
|
|
||||||
|
### 4.2 交互目标
|
||||||
|
|
||||||
|
- 点击后立即打开弹窗,不跳页、不二次确认
|
||||||
|
- 默认落在最有信息密度的 `异常明细` 页签
|
||||||
|
- 若入口来自模型命中列表,应在弹窗顶部和侧栏中体现当前命中模型上下文
|
||||||
|
|
||||||
|
### 4.3 视觉目标
|
||||||
|
|
||||||
|
- 高保真贴近用户截图中的工作台气质
|
||||||
|
- 左栏稍微简化,避免页面级导航感过强
|
||||||
|
- 与当前结果总览页保持同一套蓝灰白色系和风险标签语义,不做成另一套产品
|
||||||
|
|
||||||
|
## 5. 视觉方向
|
||||||
|
|
||||||
|
### 5.1 Visual Thesis
|
||||||
|
|
||||||
|
以“调查工作台”为核心视觉意图,整体观感应当克制、聚焦、带有分析场景的秩序感,而不是普通业务弹窗。
|
||||||
|
|
||||||
|
### 5.2 Content Plan
|
||||||
|
|
||||||
|
- 首屏:人物身份与风险背景
|
||||||
|
- 主体:异常明细主视图
|
||||||
|
- 支撑:模型摘要、排查记录摘要、辅助页签
|
||||||
|
- 收束:让用户在一次打开中快速判断“这个人为什么值得继续查”
|
||||||
|
|
||||||
|
### 5.3 Interaction Thesis
|
||||||
|
|
||||||
|
- 打开弹窗后直接进入异常明细首屏,形成明确阅读起点
|
||||||
|
- 页签切换保持轻量、稳定,不做过多动画干扰
|
||||||
|
- 来源于模型命中列表时,强调当前模型上下文,形成“从哪里来、为什么点进来”的连贯感
|
||||||
|
|
||||||
|
## 6. 方案比较
|
||||||
|
|
||||||
|
### 6.1 方案 A:工作台式高保真弹窗
|
||||||
|
|
||||||
|
结构:
|
||||||
|
|
||||||
|
- 左侧人物档案
|
||||||
|
- 右侧五页签工作区
|
||||||
|
- 异常明细为主视图
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 与用户截图最接近
|
||||||
|
- 能承载“查看项目”所需的分析工作台语义
|
||||||
|
- 后续逐步接真实页签数据时不需要推翻结构
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 前端结构比普通详情弹窗更复杂
|
||||||
|
- 需要严格控制左栏信息量,避免显得臃肿
|
||||||
|
|
||||||
|
### 6.2 方案 B:顶部信息带 + 页签主区
|
||||||
|
|
||||||
|
结构:
|
||||||
|
|
||||||
|
- 顶部统一人物信息带
|
||||||
|
- 下方五页签区域
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 结构规整
|
||||||
|
- 实现复杂度较低
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 与截图差距较大
|
||||||
|
- 人员档案沉浸感较弱
|
||||||
|
|
||||||
|
### 6.3 方案 C:轻量明细弹窗
|
||||||
|
|
||||||
|
结构:
|
||||||
|
|
||||||
|
- 人员概要
|
||||||
|
- 单屏明细区
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 实现最轻
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 无法承载用户明确要求的完整页签工作台
|
||||||
|
- 容易退化成普通表格弹窗
|
||||||
|
|
||||||
|
### 6.4 方案结论
|
||||||
|
|
||||||
|
采用 **方案 A:工作台式高保真弹窗**,同时将左侧栏从“完整工作台导航”收敛为“人物分析侧栏”,保证高保真方向正确且信息密度适中。
|
||||||
|
|
||||||
|
## 7. 信息架构设计
|
||||||
|
|
||||||
|
### 7.1 打开入口
|
||||||
|
|
||||||
|
以下两个入口统一打开同一个弹窗:
|
||||||
|
|
||||||
|
1. `风险人员总览` 的操作列
|
||||||
|
2. `命中模型涉及人员列表` 的操作列
|
||||||
|
|
||||||
|
### 7.2 弹窗顶栏
|
||||||
|
|
||||||
|
顶栏内容建议为:
|
||||||
|
|
||||||
|
- 主标题:`项目分析`
|
||||||
|
- 次级上下文:`姓名 | 工号 | 命中模型数/当前模型`
|
||||||
|
- 关闭按钮
|
||||||
|
|
||||||
|
不在顶栏继续放置页面级返回、导出报告等完整页面操作,避免弹窗承担页面壳子职责。
|
||||||
|
|
||||||
|
### 7.3 左侧人物分析侧栏
|
||||||
|
|
||||||
|
左侧侧栏只保留三组信息:
|
||||||
|
|
||||||
|
1. **人员基础信息**
|
||||||
|
- 姓名
|
||||||
|
- 工号
|
||||||
|
- 部门
|
||||||
|
- 风险等级
|
||||||
|
- 所属项目
|
||||||
|
2. **命中模型摘要**
|
||||||
|
- 命中模型数
|
||||||
|
- 模型名称摘要
|
||||||
|
- 核心异常标签
|
||||||
|
3. **排查记录摘要**
|
||||||
|
- 最近状态
|
||||||
|
- 简要备注或占位摘要
|
||||||
|
|
||||||
|
不保留完整项目侧边导航,不复刻截图中的“返回项目 / 生成报告 / 关注 / 推荐对象信息 / 排查记录”全量信息层级。
|
||||||
|
|
||||||
|
### 7.4 右侧主工作区
|
||||||
|
|
||||||
|
右侧主区保留五页签:
|
||||||
|
|
||||||
|
1. 异常明细
|
||||||
|
2. 资产分析
|
||||||
|
3. 征信摘要
|
||||||
|
4. 关系图谱
|
||||||
|
5. 资金流向
|
||||||
|
|
||||||
|
默认进入 `异常明细`,并作为本轮内容最完整的主视图。
|
||||||
|
|
||||||
|
## 8. 页面节奏与视觉结构
|
||||||
|
|
||||||
|
### 8.1 外层弹窗
|
||||||
|
|
||||||
|
- 使用大尺寸弹窗,宽度建议约 `1360px`
|
||||||
|
- 内容区高度按视口收敛,采用内部滚动
|
||||||
|
- 外层视觉重心在内容区,不依赖厚重边框和多层卡片堆叠
|
||||||
|
|
||||||
|
### 8.2 左侧侧栏
|
||||||
|
|
||||||
|
- 背景色较主内容区略暖或略灰
|
||||||
|
- 信息块之间通过分隔线与留白组织
|
||||||
|
- 不使用过多卡片和阴影,避免产生“页面套页面”感
|
||||||
|
|
||||||
|
### 8.3 右侧页签工作区
|
||||||
|
|
||||||
|
- 页签条作为稳定导航带
|
||||||
|
- 页签下方以“白底大区块 + 表格/摘要”的工作台形式展开
|
||||||
|
- `异常明细` 首屏强调三件事:
|
||||||
|
1. 当前人员风险背景
|
||||||
|
2. 异常明细主列表
|
||||||
|
3. 若干需要继续追查的异常摘要块
|
||||||
|
|
||||||
|
### 8.4 与当前结果总览页关系
|
||||||
|
|
||||||
|
- 保持同一套产品色与风险标签语义
|
||||||
|
- 弹窗整体更沉浸、更聚焦
|
||||||
|
- 不把结果总览主页面的四大区块原样塞进弹窗
|
||||||
|
|
||||||
|
## 9. 页签内容设计
|
||||||
|
|
||||||
|
### 9.1 异常明细页签
|
||||||
|
|
||||||
|
该页签为本轮主视图,按截图节奏组织:
|
||||||
|
|
||||||
|
- 顶部异常分组标题
|
||||||
|
- 主表格区:展示交易时间、本方账号、对方账号、摘要/交易类型、交易金额、标记状态等
|
||||||
|
- 补充摘要区:如频繁转账账户异常、关联交易异常等块级摘要
|
||||||
|
- 底部操作区按需保留,但不在本轮扩展真实业务动作
|
||||||
|
|
||||||
|
此页签允许使用当前行数据 + 本地静态映射方式组装高保真内容。
|
||||||
|
|
||||||
|
### 9.2 资产分析页签
|
||||||
|
|
||||||
|
- 展示高保真静态结构
|
||||||
|
- 体现资产概览、资产异动或资产分布分析的阅读节奏
|
||||||
|
- 本轮不接真实接口
|
||||||
|
|
||||||
|
### 9.3 征信摘要页签
|
||||||
|
|
||||||
|
- 展示高保真静态结构
|
||||||
|
- 体现授信、负债、逾期、查询记录等摘要位
|
||||||
|
- 本轮不接真实接口
|
||||||
|
|
||||||
|
### 9.4 关系图谱页签
|
||||||
|
|
||||||
|
- 展示高保真静态结构
|
||||||
|
- 体现中心人物、关系节点、关系类型与摘要说明
|
||||||
|
- 本轮不接真实接口
|
||||||
|
|
||||||
|
### 9.5 资金流向页签
|
||||||
|
|
||||||
|
- 展示高保真静态结构
|
||||||
|
- 体现流入流出方向、关键对手方、异常流向摘要
|
||||||
|
- 本轮不接真实接口
|
||||||
|
|
||||||
|
## 10. 组件拆分方案
|
||||||
|
|
||||||
|
### 10.1 入口编排组件
|
||||||
|
|
||||||
|
保留:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
- 继续作为结果总览页面主入口
|
||||||
|
- 统一维护项目分析弹窗开关状态
|
||||||
|
- 保存当前选中人员、入口来源、来源上下文
|
||||||
|
|
||||||
|
### 10.2 列表区块组件
|
||||||
|
|
||||||
|
涉及:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskModelSection.vue`
|
||||||
|
|
||||||
|
职责调整:
|
||||||
|
|
||||||
|
- 将“查看项目”改为触发事件
|
||||||
|
- 向上抛出当前行信息
|
||||||
|
- 不在区块内部自行维护弹窗状态
|
||||||
|
|
||||||
|
### 10.3 新增统一弹窗组件
|
||||||
|
|
||||||
|
建议新增:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisDialog.vue`
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
- 渲染大尺寸项目分析弹窗
|
||||||
|
- 负责左侧人物分析侧栏
|
||||||
|
- 负责右侧五页签导航与切换
|
||||||
|
- 管理默认页签与关闭行为
|
||||||
|
|
||||||
|
### 10.4 新增轻量子组件
|
||||||
|
|
||||||
|
建议按需拆分为:
|
||||||
|
|
||||||
|
- `ProjectAnalysisSidebar.vue`
|
||||||
|
- `ProjectAnalysisAbnormalTab.vue`
|
||||||
|
- `ProjectAnalysisPlaceholderTab.vue`
|
||||||
|
|
||||||
|
拆分原则:
|
||||||
|
|
||||||
|
- 服务于文件聚焦,不做过度抽象
|
||||||
|
- 异常明细单独成块,其他静态页签可共用一套占位骨架
|
||||||
|
|
||||||
|
## 11. 数据策略
|
||||||
|
|
||||||
|
### 11.1 本轮数据来源
|
||||||
|
|
||||||
|
本轮不新增后端接口,数据优先来自当前结果总览已拿到的列表行数据:
|
||||||
|
|
||||||
|
- `风险人员总览` 当前行
|
||||||
|
- `命中模型涉及人员列表` 当前行
|
||||||
|
|
||||||
|
### 11.2 左栏字段来源
|
||||||
|
|
||||||
|
优先映射:
|
||||||
|
|
||||||
|
- 姓名
|
||||||
|
- 工号
|
||||||
|
- 部门
|
||||||
|
- 风险等级
|
||||||
|
- 命中模型数
|
||||||
|
- 命中模型名称
|
||||||
|
- 异常标签
|
||||||
|
|
||||||
|
若来源于模型命中列表,则补充“当前命中模型”上下文;若来源于风险人员总览,则按人员风险概览口径展示。
|
||||||
|
|
||||||
|
### 11.3 右侧内容来源
|
||||||
|
|
||||||
|
- `异常明细`:由当前行信息 + 本地静态映射组合生成
|
||||||
|
- 其余页签:由静态高保真结构承载,不伪装成真实分析结果
|
||||||
|
|
||||||
|
### 11.4 缺字段策略
|
||||||
|
|
||||||
|
- 缺工号、部门、身份证号等字段时展示 `-`
|
||||||
|
- 缺异常标签时展示“暂无异常标签”
|
||||||
|
- 不在前端额外推导用户未提供的数据
|
||||||
|
|
||||||
|
## 12. 交互设计
|
||||||
|
|
||||||
|
### 12.1 打开行为
|
||||||
|
|
||||||
|
- 点击“查看项目”后立即打开弹窗
|
||||||
|
- 不跳页
|
||||||
|
- 不弹二次确认
|
||||||
|
- 默认进入 `异常明细`
|
||||||
|
- 默认滚动到弹窗顶部
|
||||||
|
|
||||||
|
### 12.2 来源感知
|
||||||
|
|
||||||
|
若入口来自 `命中模型涉及人员列表`:
|
||||||
|
|
||||||
|
- 顶部摘要展示当前命中模型
|
||||||
|
- 左侧命中模型摘要中强调当前模型
|
||||||
|
|
||||||
|
若入口来自 `风险人员总览`:
|
||||||
|
|
||||||
|
- 以人员风险背景为主,不额外强调某一模型
|
||||||
|
|
||||||
|
### 12.3 页签切换
|
||||||
|
|
||||||
|
- 五个页签均可点击切换
|
||||||
|
- 本轮只有 `异常明细` 是完整主视图
|
||||||
|
- 其他页签只承担高保真承载,不做真实联动
|
||||||
|
|
||||||
|
### 12.4 关闭与重开
|
||||||
|
|
||||||
|
- 关闭时清空当前选中人员和来源上下文
|
||||||
|
- 再次打开时始终回到 `异常明细`
|
||||||
|
- 不保留上次停留页签
|
||||||
|
|
||||||
|
## 13. 状态设计
|
||||||
|
|
||||||
|
### 13.1 打开前
|
||||||
|
|
||||||
|
- 页面原有列表与筛选行为保持不变
|
||||||
|
|
||||||
|
### 13.2 打开后
|
||||||
|
|
||||||
|
- 弹窗内部独立滚动
|
||||||
|
- 不影响结果总览页当前筛选、分页与卡片选中状态
|
||||||
|
|
||||||
|
### 13.3 空态
|
||||||
|
|
||||||
|
- 左栏缺字段时以 `-` 占位
|
||||||
|
- 标签为空时显示统一空文案
|
||||||
|
- 静态页签若无内容,使用高保真占位结构而不是纯 `el-empty`
|
||||||
|
|
||||||
|
### 13.4 异常态
|
||||||
|
|
||||||
|
本轮不扩展新的接口错误处理流,沿用前端静态组装范围,不引入额外异常流程。
|
||||||
|
|
||||||
|
## 14. 验证要点
|
||||||
|
|
||||||
|
本轮验证聚焦前端静态与交互边界:
|
||||||
|
|
||||||
|
1. 两个入口都能打开同一个弹窗
|
||||||
|
2. 不同来源可以正确带出当前行信息
|
||||||
|
3. 默认页签为 `异常明细`
|
||||||
|
4. 页签切换稳定,关闭重开后重置为默认页签
|
||||||
|
5. 左栏字段缺失、标签为空、标签过长时布局不塌
|
||||||
|
6. 弹窗桌面端宽度和常见页面宽度下布局稳定
|
||||||
|
7. 结果总览原有模型筛选、分页、标签高亮不受影响
|
||||||
|
|
||||||
|
## 15. 实施边界结论
|
||||||
|
|
||||||
|
本方案采用最短路径实现:
|
||||||
|
|
||||||
|
1. 不新增后端接口
|
||||||
|
2. 不扩展用户需求之外的新业务流程
|
||||||
|
3. 不做兼容性或补丁式平行方案
|
||||||
|
4. 只在结果总览现有前端链路上增加统一项目分析弹窗
|
||||||
|
|
||||||
|
该方案满足以下要求:
|
||||||
|
|
||||||
|
- 逻辑单一
|
||||||
|
- 入口统一
|
||||||
|
- 风格贴近用户截图
|
||||||
|
- 后续具备逐页签接入真实能力的扩展空间
|
||||||
|
|
||||||
|
## 16. 后续计划入口
|
||||||
|
|
||||||
|
设计确认后,下一步应输出两份实施计划:
|
||||||
|
|
||||||
|
- 后端实施计划:说明本轮后端保持不改动或仅补充边界说明
|
||||||
|
- 前端实施计划:说明弹窗、事件抛出、组件拆分、静态高保真页签和验证步骤
|
||||||
|
|
||||||
|
本轮实际开发应优先编写前端实施计划,并在计划中明确“不新增后端接口”的边界。
|
||||||
@@ -0,0 +1,359 @@
|
|||||||
|
# 结果总览查看详情窗口整体展示优化设计
|
||||||
|
|
||||||
|
**日期**: 2026-03-25
|
||||||
|
**模块**: 初核项目详情 - 结果总览 - 项目分析弹窗
|
||||||
|
**作者**: Codex
|
||||||
|
**状态**: 已确认
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
当前 `结果总览` 页中的“查看详情”弹窗已经具备统一入口、基础左右分栏和真实详情加载能力,但整体视觉仍存在以下问题:
|
||||||
|
|
||||||
|
- 弹窗内部存在明显“页面套页面”感,整体偏重
|
||||||
|
- 顶部留白过大,首屏可见内容不足
|
||||||
|
- 左右分栏比例与留白不够舒展,主内容区压缩感较强
|
||||||
|
- 左侧侧栏被主区高度强行拉长,人物档案区显得拖沓
|
||||||
|
- 左下角“核心异常标签”在多标签场景下无法完整展示
|
||||||
|
- 头部、侧栏、页签、表格、标签之间未形成统一的工作台视觉语言
|
||||||
|
|
||||||
|
本次设计只聚焦“整体展示效果优化”,允许对弹窗头部、侧栏组织和主区阅读节奏做明显重排,但不改变现有业务入口、路由和接口边界。
|
||||||
|
|
||||||
|
## 2. 设计范围
|
||||||
|
|
||||||
|
### 2.1 包含内容
|
||||||
|
|
||||||
|
- 重构 `ProjectAnalysisDialog.vue` 的整体视觉骨架
|
||||||
|
- 重排弹窗头带、左侧档案面板、右侧主工作区的层级关系
|
||||||
|
- 统一异常明细、占位页签、标签、摘要块、表格的视觉语言
|
||||||
|
- 保留现有“查看详情”入口与默认页签行为
|
||||||
|
- 保留现有真实详情接口调用链路
|
||||||
|
|
||||||
|
### 2.2 不包含内容
|
||||||
|
|
||||||
|
- 不新增后端接口
|
||||||
|
- 不调整结果总览列表层字段、按钮位置和交互入口
|
||||||
|
- 不新增页面、路由或抽屉式替代方案
|
||||||
|
- 不扩展导出、审批、关注等本轮需求之外的功能
|
||||||
|
- 不增加兼容性、补丁式或降级分支方案
|
||||||
|
|
||||||
|
## 3. 当前上下文
|
||||||
|
|
||||||
|
当前相关文件主要包括:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisDialog.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisSidebar.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisAbnormalTab.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisPlaceholderTab.vue`
|
||||||
|
|
||||||
|
当前实现特点:
|
||||||
|
|
||||||
|
1. 弹窗外层采用 `el-dialog`,内部再包一层 `project-analysis-shell`
|
||||||
|
2. 当前使用右侧主区独立滚动,左侧侧栏不滚动,造成阅读节奏割裂
|
||||||
|
3. 左侧和右侧虽已收进同一外壳,但仍有较强“卡片拼装”感
|
||||||
|
4. “当前命中模型”作为独立提示块出现在主区顶部,容易打断阅读
|
||||||
|
5. 侧栏以字段表单式平铺为主,人物档案感不足
|
||||||
|
6. 异常明细区块、占位页签和侧栏之间的边框、背景、圆角节奏不统一
|
||||||
|
|
||||||
|
## 4. 设计目标
|
||||||
|
|
||||||
|
### 4.1 视觉目标
|
||||||
|
|
||||||
|
- 去除“弹窗里再套一页”的套娃感
|
||||||
|
- 压缩顶部无效留白,让首屏尽量多展示真实内容
|
||||||
|
- 让窗口更像一个完整的分析工作台,而不是多张卡片临时拼接
|
||||||
|
- 在保持当前产品色系下,形成更稳定、更克制的调查分析气质
|
||||||
|
|
||||||
|
### 4.2 交互目标
|
||||||
|
|
||||||
|
- 用户打开弹窗后,首屏先看清“是谁、风险怎样、为什么点进来”
|
||||||
|
- 弹窗内容区统一滚动,左右两栏保持同一阅读路径
|
||||||
|
- 默认仍进入 `异常明细`,不改变用户现有操作路径
|
||||||
|
- 来源于模型命中列表时,上下文提示应更自然地并入头部信息
|
||||||
|
|
||||||
|
### 4.3 实现目标
|
||||||
|
|
||||||
|
- 维持现有数据链路与组件职责,不扩散到后端与页面壳层
|
||||||
|
- 将改动边界收敛在弹窗及其直属子组件内
|
||||||
|
|
||||||
|
## 5. 视觉方向
|
||||||
|
|
||||||
|
### 5.1 Visual Thesis
|
||||||
|
|
||||||
|
以“沉浸式分析工作台”为核心方向,整体界面强调一层主壳、两块功能面和连续阅读流,避免多层容器叠加造成的后台表单感。
|
||||||
|
|
||||||
|
### 5.2 Content Plan
|
||||||
|
|
||||||
|
1. 首屏识别人和风险上下文
|
||||||
|
2. 主体展示项目内异常分析内容
|
||||||
|
3. 辅助承载其余四个分析页签
|
||||||
|
4. 在一次打开中完成快速判断与继续追查的阅读起点
|
||||||
|
|
||||||
|
### 5.3 Interaction Thesis
|
||||||
|
|
||||||
|
- 通过头部信息头带建立进入时的第一落点
|
||||||
|
- 通过左侧档案面板维持人物语义,不让侧栏沦为字段表单
|
||||||
|
- 通过主区摘要条和页签导航建立清晰、稳定的操作路径
|
||||||
|
|
||||||
|
## 6. 方案比较
|
||||||
|
|
||||||
|
### 6.1 方案 A:去壳化微调版
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 保留当前左右分栏与页签布局
|
||||||
|
- 仅去掉部分边框、圆角和外层容器感
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 改动边界最小
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 只能缓解表层问题
|
||||||
|
- 套娃感和层级割裂无法根治
|
||||||
|
|
||||||
|
### 6.2 方案 B:沉浸式分析工作台
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 保留左右工作台语义
|
||||||
|
- 弹窗本身作为唯一主壳,并显著放大工作区尺寸
|
||||||
|
- 头部、侧栏、主区阅读节奏全部重排
|
||||||
|
- 改为统一滚动,侧栏标签区完整展开
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 能同时解决套娃感、比例失衡和风格碎片化问题
|
||||||
|
- 与当前业务场景最匹配
|
||||||
|
- 后续继续补真实页签内容时不需要再次推翻结构
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 结构调整比简单样式修饰更明显
|
||||||
|
|
||||||
|
### 6.3 方案 C:单栏纵向叙事版
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 弱化或取消侧栏
|
||||||
|
- 全部内容按纵向单列展开
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 阅读路径最直接
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 不符合当前“分析工作台”场景
|
||||||
|
- 会削弱多页签和多模块并存时的效率感
|
||||||
|
|
||||||
|
### 6.4 结论
|
||||||
|
|
||||||
|
采用 **方案 B:沉浸式分析工作台**。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
1. 能一次性解决顶部留白、左右比例、标签展示不全和套娃感几类问题
|
||||||
|
2. 保留左右工作台语义,不偏离当前业务场景
|
||||||
|
3. 不需要引入新接口或新页面,仍然是最短路径实现
|
||||||
|
|
||||||
|
## 7. 总体设计
|
||||||
|
|
||||||
|
### 7.1 外层骨架
|
||||||
|
|
||||||
|
弹窗整体改为“三段式节奏”:
|
||||||
|
|
||||||
|
1. 顶部信息头带
|
||||||
|
2. 主体工作区
|
||||||
|
3. 底部留白收束
|
||||||
|
|
||||||
|
其中,`el-dialog` 内容区直接承担唯一外壳职责,不再在内部额外包一层大白卡式 `shell` 容器。
|
||||||
|
|
||||||
|
尺寸策略调整为:
|
||||||
|
|
||||||
|
- 弹窗整体尺寸较当前版本进一步放大,以“尽量多展示首屏内容”为优先目标
|
||||||
|
- 顶部与视口的空白显著压缩,不再保留当前过大的上边距观感
|
||||||
|
- 高度控制仍以视口内完整可操作为前提,不引入页面级滚动穿透问题
|
||||||
|
|
||||||
|
### 7.2 主体布局
|
||||||
|
|
||||||
|
主体仍保留左右分栏,但重设比例与层次:
|
||||||
|
|
||||||
|
- 左侧:固定窄栏,宽度约 `300px ~ 320px`
|
||||||
|
- 右侧:自适应主工作区
|
||||||
|
- 左右两区不再依赖粗边框硬切,而是通过背景层次、留白和局部分隔建立边界
|
||||||
|
- 左侧不再被右侧主区高度强行拉满,应按自身内容自然结束
|
||||||
|
- 弹窗内容区改为统一纵向滚动,左右两栏随同一滚动容器一起移动
|
||||||
|
|
||||||
|
### 7.3 头部信息头带
|
||||||
|
|
||||||
|
头部替代原有简单弹窗标题,组织方式如下:
|
||||||
|
|
||||||
|
- 左侧主信息:
|
||||||
|
- 姓名
|
||||||
|
- 风险等级状态
|
||||||
|
- 工号 / 部门 / 所属项目
|
||||||
|
- 右侧操作:
|
||||||
|
- 关闭按钮
|
||||||
|
- 右侧补充上下文:
|
||||||
|
- 若入口来自模型命中列表,则展示“当前命中模型”
|
||||||
|
|
||||||
|
“当前命中模型”不再占用主区独立一行,而是并入头带完成来源说明。
|
||||||
|
|
||||||
|
### 7.4 左侧人物档案面板
|
||||||
|
|
||||||
|
左栏改为“人物档案面板”,按以下顺序组织:
|
||||||
|
|
||||||
|
1. 人物身份区
|
||||||
|
2. 命中模型摘要区
|
||||||
|
3. 核心异常标签区
|
||||||
|
|
||||||
|
具体原则:
|
||||||
|
|
||||||
|
- 不再使用强表单感的左右对齐字段列表作为主要视觉形式
|
||||||
|
- 通过人物姓名、风险等级徽标和简洁元信息建立识别性
|
||||||
|
- 核心异常标签作为独立内容块完整展示
|
||||||
|
- 标签区必须占满可用宽度,并支持从左到右自动换行
|
||||||
|
- 不允许通过截断、隐藏或压缩到单列的方式“勉强塞下”
|
||||||
|
- 如果某组信息不足,则保持简洁,不硬凑空块
|
||||||
|
|
||||||
|
### 7.5 右侧主工作区
|
||||||
|
|
||||||
|
主区改为连续阅读流:
|
||||||
|
|
||||||
|
1. 头部信息头带
|
||||||
|
2. 页签导航
|
||||||
|
3. 当前页签主体内容
|
||||||
|
|
||||||
|
其中头带优先展示当前人员在本项目内最关键的进入上下文,包括:
|
||||||
|
|
||||||
|
- 姓名
|
||||||
|
- 风险等级
|
||||||
|
- 工号 / 部门 / 所属项目
|
||||||
|
- 当前命中模型(仅模型入口显示)
|
||||||
|
|
||||||
|
### 7.6 异常明细页签
|
||||||
|
|
||||||
|
`异常明细` 仍为默认页签和主视图,组织节奏调整为:
|
||||||
|
|
||||||
|
1. 分组标题与一句摘要
|
||||||
|
2. 异常明细表格
|
||||||
|
3. 对象异常或补充摘要区
|
||||||
|
|
||||||
|
目标是让主表格继续作为信息中心,同时不再被多余边框和零散块状结构打断。
|
||||||
|
|
||||||
|
滚动规则调整为:
|
||||||
|
|
||||||
|
- 不再保留主区内部独立滚动容器
|
||||||
|
- 表格、对象卡片和分页都放回统一文档流
|
||||||
|
- 用户滚动一次即可连续浏览左侧档案和右侧内容,不再出现左右阅读节奏脱节
|
||||||
|
|
||||||
|
### 7.7 其他页签
|
||||||
|
|
||||||
|
`资产分析`、`征信摘要`、`关系图谱`、`资金流向` 四个页签本轮仍可保持静态承载,但视觉上统一为同一套工作台区块,不再采用简单占位板式呈现。
|
||||||
|
|
||||||
|
## 8. 视觉规范
|
||||||
|
|
||||||
|
### 8.1 背景与层次
|
||||||
|
|
||||||
|
- 最外层弹窗:唯一主壳
|
||||||
|
- 左栏背景:较主区略深的浅灰蓝面
|
||||||
|
- 主区背景:白色或极浅白底
|
||||||
|
- 关键内容块:局部轻边框或轻底色区分
|
||||||
|
|
||||||
|
避免同屏重复出现多层边框、圆角和阴影。
|
||||||
|
|
||||||
|
### 8.2 圆角与边框
|
||||||
|
|
||||||
|
- 圆角只保留在外层弹窗和主区关键块
|
||||||
|
- 大面积容器减少边框存在感
|
||||||
|
- 优先通过留白、标题层级、背景差和局部轻分隔线建立秩序
|
||||||
|
|
||||||
|
### 8.3 间距体系
|
||||||
|
|
||||||
|
统一使用三档垂直节奏:
|
||||||
|
|
||||||
|
- 紧凑信息:`8px`
|
||||||
|
- 区块内常规间距:`16px`
|
||||||
|
- 大区块间距:`24px`
|
||||||
|
|
||||||
|
主区和侧栏的左右内边距保持同一基线,避免视觉错位。
|
||||||
|
|
||||||
|
### 8.4 标签与状态
|
||||||
|
|
||||||
|
- 风险等级、异常标签、命中模型提示统一为浅底色、细边框、小圆角体系
|
||||||
|
- 左侧“核心异常标签”需优先保证完整可读,再考虑一屏展示数量
|
||||||
|
- 不混用重阴影和高饱和整块底色
|
||||||
|
- 不同标签的差异主要通过语义色和轻度背景体现
|
||||||
|
|
||||||
|
### 8.5 表格
|
||||||
|
|
||||||
|
- 表格仍是主工作区核心阅读面
|
||||||
|
- 弱化网格感,减少厚边框
|
||||||
|
- 通过表头层级、行高、次级文字颜色和金额色建立信息密度
|
||||||
|
|
||||||
|
## 9. 交互与状态
|
||||||
|
|
||||||
|
### 9.1 保持不变
|
||||||
|
|
||||||
|
- `风险人员总览` 与 `命中模型涉及人员列表` 的“查看详情”入口不变
|
||||||
|
- 默认页签仍为 `异常明细`
|
||||||
|
- 仍使用现有详情接口按需拉取真实数据
|
||||||
|
|
||||||
|
### 9.2 调整点
|
||||||
|
|
||||||
|
- 加载态与报错态并入主区头部附近,减少打断感
|
||||||
|
- 来源为模型命中列表时,模型上下文移入头带
|
||||||
|
- 弹窗内容区改为统一滚动,左侧与右侧保持同一浏览路径
|
||||||
|
- 打开弹窗后的首屏信息顺序改为:
|
||||||
|
- 人员识别
|
||||||
|
- 风险背景
|
||||||
|
- 当前来源上下文
|
||||||
|
- 详细分析内容
|
||||||
|
|
||||||
|
## 10. 影响范围
|
||||||
|
|
||||||
|
本轮前端改动预计收敛在以下文件:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisDialog.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisSidebar.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisAbnormalTab.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisPlaceholderTab.vue`
|
||||||
|
|
||||||
|
必要时补充弹窗结构与视觉契约相关单测,但不涉及:
|
||||||
|
|
||||||
|
- 后端接口
|
||||||
|
- 项目详情页路由
|
||||||
|
- 结果总览列表层逻辑
|
||||||
|
- 其他业务模块详情弹窗
|
||||||
|
|
||||||
|
## 11. 验收标准
|
||||||
|
|
||||||
|
1. 打开弹窗后,用户能在首屏快速识别“是谁、风险怎样、为什么点进来”,且顶部空白明显收紧
|
||||||
|
2. 弹窗尺寸相较当前版本明显放大,但仍保持在视口内稳定展示
|
||||||
|
3. 窗口整体不再呈现“弹窗里再套一页”的视觉感受
|
||||||
|
4. 左右分栏比例更稳定,右侧主区成为明确视觉中心
|
||||||
|
5. 弹窗内容区统一滚动,不再保留右侧独立滚动
|
||||||
|
6. 左侧侧栏不再被主区强制拉成整块长面板
|
||||||
|
7. 左下角“核心异常标签”在多标签、长标签场景下可完整换行展示
|
||||||
|
8. 头带、侧栏、页签、表格、标签形成统一工作台语言
|
||||||
|
9. 不改变当前业务入口、接口和默认页签行为
|
||||||
|
|
||||||
|
## 12. 结论
|
||||||
|
|
||||||
|
本次设计采用“沉浸式分析工作台”作为结果总览查看详情窗口的整体优化方向:
|
||||||
|
|
||||||
|
1. 去掉多余外壳,收口为一层主壳
|
||||||
|
2. 重排头带、侧栏和主区节奏
|
||||||
|
3. 统一标签、摘要、页签和表格视觉语言
|
||||||
|
|
||||||
|
该方案满足用户确认的核心诉求:
|
||||||
|
|
||||||
|
- 去除套娃感
|
||||||
|
- 压缩顶部留白并放大首屏展示
|
||||||
|
- 调顺分栏比例
|
||||||
|
- 改为统一滚动
|
||||||
|
- 让左下角标签完整展示
|
||||||
|
- 统一整体展示效果
|
||||||
|
|
||||||
|
同时保持当前接口与业务路径不变,属于符合现有边界的最短路径优化方案。
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
# 结果总览项目分析弹窗真实详情设计
|
||||||
|
|
||||||
|
**日期**: 2026-03-25
|
||||||
|
**模块**: 初核项目详情 - 结果总览
|
||||||
|
**作者**: Codex
|
||||||
|
**状态**: 已确认
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
现有 `结果总览` 页的“项目分析”弹窗已完成前端壳子,但弹窗内容仍依赖本地静态拼装:
|
||||||
|
|
||||||
|
- 弹窗宽度偏窄,流水类信息阅读空间不足
|
||||||
|
- `异常明细` 仍使用本地 mock 数据
|
||||||
|
- 左侧 `人员基础信息` 未读取员工信息表真实数据
|
||||||
|
- `命中模型摘要` 当前由弹窗内部拼装,和入口上下文绑定不清晰
|
||||||
|
|
||||||
|
本次设计只解决上述真实详情接入问题,目标是在保留现有弹窗结构的前提下,把“查看详情”改造成可查询真实人员详情和真实异常明细的工作台弹窗。
|
||||||
|
|
||||||
|
## 2. 设计目标
|
||||||
|
|
||||||
|
### 2.1 包含内容
|
||||||
|
|
||||||
|
- 调整 `项目分析` 弹窗宽度
|
||||||
|
- 新增结果总览详情专用后端接口
|
||||||
|
- `人员基础信息` 改为读取员工信息表真实数据
|
||||||
|
- `异常明细` 改为调用真实接口返回
|
||||||
|
- `命中模型摘要` 改为由外部列表透传进入弹窗
|
||||||
|
- `异常明细` 中:
|
||||||
|
- 流水维度异常按表格展示
|
||||||
|
- `object` 类型异常标签按摘要卡展示
|
||||||
|
|
||||||
|
### 2.2 不包含内容
|
||||||
|
|
||||||
|
- 不改造项目详情页路由和导航
|
||||||
|
- 不扩展弹窗内 `资产分析 / 征信摘要 / 关系图谱 / 资金流向` 的真实接口
|
||||||
|
- 不把弹窗拆成新页面
|
||||||
|
- 不向现有列表接口塞入详情字段
|
||||||
|
- 不增加兼容分支、降级分支或补丁式旁路方案
|
||||||
|
|
||||||
|
## 3. 当前上下文
|
||||||
|
|
||||||
|
当前相关前端文件:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisDialog.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisSidebar.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisAbnormalTab.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js`
|
||||||
|
|
||||||
|
当前相关后端文件:
|
||||||
|
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java`
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java`
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java`
|
||||||
|
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java`
|
||||||
|
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBaseStaff.java`
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
1. 弹窗宽度固定为 `1280px`,流水类表格列空间不足
|
||||||
|
2. 异常明细页仍是“当前行数据 + 静态模板”拼装
|
||||||
|
3. 左侧基础信息没有使用员工主数据
|
||||||
|
4. 结果总览后端尚无详情接口,只有列表类接口
|
||||||
|
|
||||||
|
## 4. 方案比较
|
||||||
|
|
||||||
|
### 4.1 方案 A:新增结果总览详情专用接口
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 在结果总览控制器下新增详情接口
|
||||||
|
- 一次返回人员基础信息和异常明细
|
||||||
|
- 前端弹窗只负责展示和状态管理
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 接口职责清晰,列表与详情边界明确
|
||||||
|
- 前端不需要并发拼多个真实接口
|
||||||
|
- 后续继续扩展其他页签时可以沿用同一详情链路
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 需要补一条新的后端查询链路
|
||||||
|
|
||||||
|
### 4.2 方案 B:前端并发查询员工信息和异常明细
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 员工信息单独查员工模块
|
||||||
|
- 异常明细单独查结果总览模块
|
||||||
|
- 前端弹窗自行合并两份结果
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 单个接口改动较小
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 前端状态更散
|
||||||
|
- 打开弹窗时更容易出现半成功半失败
|
||||||
|
- 员工信息与详情上下文容易失去统一入口
|
||||||
|
|
||||||
|
### 4.3 方案 C:把详情字段塞回现有列表接口
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 在风险人员总览和模型人员列表接口中直接追加详情字段
|
||||||
|
- 弹窗尽量少请求或不请求
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 表面改动快
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 列表接口职责被拉坏
|
||||||
|
- 数据量被放大
|
||||||
|
- 不符合“查看详情单独获取真实信息”的语义
|
||||||
|
|
||||||
|
### 4.4 结论
|
||||||
|
|
||||||
|
采用 **方案 A**。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
1. 最符合“详情数据独立获取”的业务语义
|
||||||
|
2. 不破坏现有结果总览列表接口职责
|
||||||
|
3. 前后端边界最稳定,后续扩展成本最低
|
||||||
|
|
||||||
|
## 5. 总体设计
|
||||||
|
|
||||||
|
### 5.1 打开入口
|
||||||
|
|
||||||
|
以下两个入口继续共用同一个 `项目分析` 弹窗:
|
||||||
|
|
||||||
|
1. `风险人员总览`
|
||||||
|
2. `命中模型涉及人员列表`
|
||||||
|
|
||||||
|
### 5.2 打开流程
|
||||||
|
|
||||||
|
1. 用户点击列表中的 `查看详情`
|
||||||
|
2. 前端立即打开弹窗,默认进入 `异常明细`
|
||||||
|
3. 前端将入口行中的 `命中模型摘要` 直接透传给弹窗
|
||||||
|
4. 前端发起结果总览详情接口请求
|
||||||
|
5. 接口返回后刷新左侧真实人员基础信息和右侧真实异常明细
|
||||||
|
|
||||||
|
### 5.3 数据来源边界
|
||||||
|
|
||||||
|
- `人员基础信息`:后端详情接口内部查询员工信息表 `ccdi_base_staff`
|
||||||
|
- `命中模型摘要`:由外部列表透传,不由详情接口负责
|
||||||
|
- `异常明细`:由详情接口统一返回真实信息
|
||||||
|
|
||||||
|
## 6. 后端设计
|
||||||
|
|
||||||
|
### 6.1 接口设计
|
||||||
|
|
||||||
|
建议在结果总览控制器下新增接口:
|
||||||
|
|
||||||
|
- `GET /ccdi/project/overview/person-analysis/detail`
|
||||||
|
|
||||||
|
入参:
|
||||||
|
|
||||||
|
- `projectId`
|
||||||
|
- `staffIdCard`
|
||||||
|
|
||||||
|
只保留这两个字段,避免把外层上下文和真实详情查询混在一起。
|
||||||
|
|
||||||
|
### 6.2 返回结构
|
||||||
|
|
||||||
|
返回对象建议收口为:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"basicInfo": {
|
||||||
|
"name": "张三",
|
||||||
|
"idNo": "3301********1234",
|
||||||
|
"staffCode": "A1023",
|
||||||
|
"department": "信息二部",
|
||||||
|
"phone": "138****0000",
|
||||||
|
"riskLevel": "高风险",
|
||||||
|
"projectName": "项目A"
|
||||||
|
},
|
||||||
|
"abnormalDetail": {
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"groupCode": "BANK_STATEMENT",
|
||||||
|
"groupName": "流水异常明细",
|
||||||
|
"groupType": "BANK_STATEMENT",
|
||||||
|
"records": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupCode": "RELATED_OBJECT",
|
||||||
|
"groupName": "异常对象摘要",
|
||||||
|
"groupType": "OBJECT",
|
||||||
|
"records": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 人员基础信息查询
|
||||||
|
|
||||||
|
人员基础信息统一从员工信息表读取:
|
||||||
|
|
||||||
|
- 主表:`ccdi_base_staff`
|
||||||
|
- 关联补充:
|
||||||
|
- 部门名称
|
||||||
|
- 当前项目名称
|
||||||
|
- 结果总览员工结果表中的风险等级
|
||||||
|
|
||||||
|
查询口径:
|
||||||
|
|
||||||
|
1. `staffIdCard` 作为员工识别主键
|
||||||
|
2. 员工表查不到时,仍允许返回异常明细,但 `basicInfo` 中对应字段为空
|
||||||
|
3. 不新增前端二次查员工接口
|
||||||
|
|
||||||
|
### 6.4 异常明细查询
|
||||||
|
|
||||||
|
异常明细统一组织为 `groups` 列表,每组包含:
|
||||||
|
|
||||||
|
- `groupCode`
|
||||||
|
- `groupName`
|
||||||
|
- `groupType`
|
||||||
|
- `records`
|
||||||
|
|
||||||
|
本轮只支持两种 `groupType`:
|
||||||
|
|
||||||
|
- `BANK_STATEMENT`
|
||||||
|
- `OBJECT`
|
||||||
|
|
||||||
|
### 6.5 流水维度异常
|
||||||
|
|
||||||
|
`BANK_STATEMENT` 类型的 `records` 直接按流水明细展示口径返回,字段尽量贴近现有流水查询 VO:
|
||||||
|
|
||||||
|
- `bankStatementId`
|
||||||
|
- `trxDate`
|
||||||
|
- `leAccountNo`
|
||||||
|
- `leAccountName`
|
||||||
|
- `customerAccountName`
|
||||||
|
- `customerAccountNo`
|
||||||
|
- `userMemo`
|
||||||
|
- `cashType`
|
||||||
|
- `displayAmount`
|
||||||
|
- `hitTags`
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
1. 展示口径与现有“流水明细查询”页面保持一致
|
||||||
|
2. 命中标签继续沿用当前流水标签结果表查询方式
|
||||||
|
3. 不再为弹窗单独造一套与流水查询不一致的字段命名
|
||||||
|
|
||||||
|
### 6.6 `OBJECT` 类型异常
|
||||||
|
|
||||||
|
`OBJECT` 类型记录用于卡片展示,返回摘要结构,不返回整对象平铺结构。
|
||||||
|
|
||||||
|
每条记录建议包含:
|
||||||
|
|
||||||
|
- `title`
|
||||||
|
- `subtitle`
|
||||||
|
- `riskTags`
|
||||||
|
- `summary`
|
||||||
|
- `extraFields`
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `title`:对象主识别名称
|
||||||
|
- `subtitle`:对象类型、关系或补充说明
|
||||||
|
- `riskTags`:异常标签数组
|
||||||
|
- `summary`:一句话摘要
|
||||||
|
- `extraFields`:最多 2 到 4 个补充字段
|
||||||
|
|
||||||
|
这样可满足“卡片展示核心信息”的要求,避免对象型数据在弹窗内变成字段堆叠。
|
||||||
|
|
||||||
|
### 6.7 查询范围
|
||||||
|
|
||||||
|
无论从哪个入口进入,详情接口都返回“该人员在当前项目下的全部异常明细”。
|
||||||
|
|
||||||
|
即使入口来自 `命中模型涉及人员列表`,也:
|
||||||
|
|
||||||
|
- 顶部继续显示当前命中模型上下文
|
||||||
|
- 但异常明细不按当前模型过滤
|
||||||
|
|
||||||
|
## 7. 前端设计
|
||||||
|
|
||||||
|
### 7.1 弹窗尺寸
|
||||||
|
|
||||||
|
`ProjectAnalysisDialog.vue` 中弹窗宽度从 `1280px` 调整为 `1440px`。
|
||||||
|
|
||||||
|
布局建议:
|
||||||
|
|
||||||
|
- 左侧侧栏宽度从 `320px` 微调到 `340px`
|
||||||
|
- 右侧主工作区尽量为流水表格腾出空间
|
||||||
|
|
||||||
|
### 7.2 侧栏展示
|
||||||
|
|
||||||
|
左侧侧栏拆成两类来源:
|
||||||
|
|
||||||
|
1. 真实详情接口返回:
|
||||||
|
- 姓名
|
||||||
|
- 身份证号
|
||||||
|
- 工号
|
||||||
|
- 部门
|
||||||
|
- 联系方式
|
||||||
|
- 风险等级
|
||||||
|
- 所属项目
|
||||||
|
2. 外层透传:
|
||||||
|
- 命中模型数
|
||||||
|
- 当前命中模型
|
||||||
|
- 核心异常标签
|
||||||
|
|
||||||
|
`排查记录摘要` 本轮没有真实口径,继续保留占位文案,不新增接口。
|
||||||
|
|
||||||
|
### 7.3 异常明细渲染
|
||||||
|
|
||||||
|
`ProjectAnalysisAbnormalTab.vue` 改为分组渲染:
|
||||||
|
|
||||||
|
1. `BANK_STATEMENT`
|
||||||
|
- 渲染表格
|
||||||
|
- 表头风格参考 `DetailQuery.vue`
|
||||||
|
2. `OBJECT`
|
||||||
|
- 渲染摘要卡列表
|
||||||
|
- 每张卡只展示核心字段
|
||||||
|
|
||||||
|
空数据处理:
|
||||||
|
|
||||||
|
- 空分组不展示
|
||||||
|
- 全部为空时展示统一空态
|
||||||
|
|
||||||
|
### 7.4 Mock 替换策略
|
||||||
|
|
||||||
|
`preliminaryCheck.mock.js` 中的弹窗数据构造逻辑不再承担真实详情拼装职责。
|
||||||
|
|
||||||
|
保留内容:
|
||||||
|
|
||||||
|
- 占位页签文案
|
||||||
|
- 少量默认结构常量
|
||||||
|
|
||||||
|
移出内容:
|
||||||
|
|
||||||
|
- `basicInfo`
|
||||||
|
- `abnormalDetail`
|
||||||
|
|
||||||
|
这两部分改为完全由真实接口驱动。
|
||||||
|
|
||||||
|
## 8. 状态与交互设计
|
||||||
|
|
||||||
|
### 8.1 打开时机
|
||||||
|
|
||||||
|
点击后立即打开弹窗,不等待接口先返回。
|
||||||
|
|
||||||
|
### 8.2 加载态
|
||||||
|
|
||||||
|
- 左侧真实基础信息未返回前显示骨架或 `-`
|
||||||
|
- 右侧 `异常明细` 区域显示 `v-loading` 或骨架态
|
||||||
|
- 外层透传的命中模型摘要可先展示
|
||||||
|
|
||||||
|
### 8.3 错误态
|
||||||
|
|
||||||
|
如果详情接口失败:
|
||||||
|
|
||||||
|
- 弹窗保持打开
|
||||||
|
- 右侧异常明细区域展示错误提示和重试入口
|
||||||
|
- 左侧命中模型摘要继续保留
|
||||||
|
- 未获取到的基础信息字段显示为空
|
||||||
|
|
||||||
|
### 8.4 页签行为
|
||||||
|
|
||||||
|
- 默认页签始终为 `异常明细`
|
||||||
|
- 关闭弹窗后再次打开,仍回到 `异常明细`
|
||||||
|
- 其余 4 个页签继续展示占位内容,不在本次扩展真实数据
|
||||||
|
|
||||||
|
## 9. 测试与验收要点
|
||||||
|
|
||||||
|
### 9.1 后端验收
|
||||||
|
|
||||||
|
1. 详情接口可根据 `projectId + staffIdCard` 返回真实详情
|
||||||
|
2. 人员基础信息来源于员工信息表
|
||||||
|
3. 流水型异常可返回真实流水记录和命中标签
|
||||||
|
4. `OBJECT` 类型异常可返回摘要卡结构
|
||||||
|
5. 从模型人员列表进入时,异常明细仍返回该人的全部异常
|
||||||
|
|
||||||
|
### 9.2 前端验收
|
||||||
|
|
||||||
|
1. 弹窗宽度明显加宽,流水表格可读性提升
|
||||||
|
2. 点击 `查看详情` 后弹窗立即打开
|
||||||
|
3. 左侧命中模型摘要由外层透传
|
||||||
|
4. 左侧人员基础信息展示真实数据
|
||||||
|
5. 流水型异常按表格展示
|
||||||
|
6. `OBJECT` 类型异常按摘要卡展示
|
||||||
|
7. 详情请求失败时弹窗不关闭,可重试
|
||||||
|
|
||||||
|
## 10. 结论
|
||||||
|
|
||||||
|
本方案采用“**结果总览新增专用详情接口 + 前端弹窗按类型渲染真实数据**”的最短路径实现:
|
||||||
|
|
||||||
|
1. 不污染现有列表接口
|
||||||
|
2. 不让前端并发拼多条真实接口
|
||||||
|
3. 人员基础信息、异常明细、入口模型摘要三类职责边界清晰
|
||||||
|
4. 既满足本轮真实详情接入,也为后续逐步接通更多页签留出稳定扩展位
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
# 专项核查员工家庭资产负债展开区改版设计
|
||||||
|
|
||||||
|
**日期**: 2026-03-25
|
||||||
|
**模块**: 项目详情 - 专项核查 - 员工家庭资产负债专项核查
|
||||||
|
**作者**: Codex
|
||||||
|
**状态**: 已确认
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
当前专项核查页中的“员工家庭资产负债专项核查”已经具备列表与展开详情能力,但展开区仍沿用“三列卡片 + 表格明细”的结构,与最新原型存在明显差异:
|
||||||
|
|
||||||
|
- 信息层级偏散,展开后阅读路径不稳定
|
||||||
|
- 逐条明细表过重,不符合当前原型的“汇总式详查”表达
|
||||||
|
- 资产、负债、指标和风险结论没有形成清晰的纵向阅读节奏
|
||||||
|
|
||||||
|
本次设计只改造展开后的详情展示效果,不调整列表层字段、列顺序、查看详情交互、接口契约和页面路由。
|
||||||
|
|
||||||
|
## 2. 设计目标
|
||||||
|
|
||||||
|
### 2.1 包含内容
|
||||||
|
|
||||||
|
- 仅改造 `FamilyAssetLiabilityDetail.vue` 的展开区展示结构
|
||||||
|
- 展开区改为自上而下 5 张纵向汇总卡片
|
||||||
|
- 每张卡片标题直接展示该模块汇总数字
|
||||||
|
- 卡片内部简洁展示汇总数据的来源项
|
||||||
|
- 资产与负债来源项改为按现有类型聚合后全部展示
|
||||||
|
- 风险结论按真实风险等级切换样式与文案
|
||||||
|
|
||||||
|
### 2.2 不包含内容
|
||||||
|
|
||||||
|
- 不调整列表层列顺序、列文案和操作按钮
|
||||||
|
- 不改造 `查看详情` 的交互入口和按需加载机制
|
||||||
|
- 不新增后端字段、后端接口或路由
|
||||||
|
- 不保留现有逐条表格明细展示
|
||||||
|
- 不增加兼容分支、降级分支、二级折叠或 tooltip 解释
|
||||||
|
|
||||||
|
## 3. 当前上下文
|
||||||
|
|
||||||
|
当前前端相关文件:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/FamilyAssetLiabilitySection.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/FamilyAssetLiabilityDetail.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/SpecialCheck.vue`
|
||||||
|
- `ruoyi-ui/src/api/ccdi/projectSpecialCheck.js`
|
||||||
|
|
||||||
|
当前详情接口已返回以下结构:
|
||||||
|
|
||||||
|
- `incomeDetail`
|
||||||
|
- `assetDetail`
|
||||||
|
- `debtDetail`
|
||||||
|
- `summary`
|
||||||
|
|
||||||
|
其中 `summary` 已包含:
|
||||||
|
|
||||||
|
- `totalIncome`
|
||||||
|
- `totalDebt`
|
||||||
|
- `totalAsset`
|
||||||
|
- `comparisonAmount`
|
||||||
|
- `riskLevelCode`
|
||||||
|
- `riskLevelName`
|
||||||
|
|
||||||
|
这意味着本次改版无需调整接口,即可直接支撑总额、净资产、关键指标和风险结果展示。
|
||||||
|
|
||||||
|
## 4. 视觉方向
|
||||||
|
|
||||||
|
### 4.1 Visual Thesis
|
||||||
|
|
||||||
|
展开区改为“工作台式纵向汇总带”,每段只承载一个核心结论,用浅灰标题条、白底内容区和简洁来源项建立稳定层级。
|
||||||
|
|
||||||
|
### 4.2 Content Plan
|
||||||
|
|
||||||
|
1. 总收入
|
||||||
|
2. 总负债
|
||||||
|
3. 总资产
|
||||||
|
4. 关键指标
|
||||||
|
5. 详查结果
|
||||||
|
|
||||||
|
### 4.3 Interaction Thesis
|
||||||
|
|
||||||
|
- 保留现有“点击列表行内展开”的唯一交互,不额外增加二级折叠
|
||||||
|
- 通过模块顺序和标题汇总值强化信息导向,不靠复杂动效制造层级
|
||||||
|
- 风险结果卡通过颜色和结论文案形成唯一视觉落点
|
||||||
|
|
||||||
|
## 5. 方案比较
|
||||||
|
|
||||||
|
### 5.1 方案 A:完全沿用现有三列卡片,仅替换表格样式
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 保留 `收入 / 负债 / 资产` 三列结构
|
||||||
|
- 仅把内部表格改成更轻的列表展示
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 改动最少
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 与原型的纵向分段结构不一致
|
||||||
|
- 无法形成“总额 -> 指标 -> 结论”的阅读链路
|
||||||
|
|
||||||
|
### 5.2 方案 B:双列摘要面板
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 左侧聚合资产和负债
|
||||||
|
- 右侧聚合指标和结果
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 信息比较紧凑
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 更像通用后台摘要区,不够贴近原型
|
||||||
|
- 用户新确认的顺序无法自然落地
|
||||||
|
|
||||||
|
### 5.3 方案 C:纵向 5 段汇总卡片
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 展开区改为 5 张单列卡片,顺序固定为:
|
||||||
|
- 总收入
|
||||||
|
- 总负债
|
||||||
|
- 总资产
|
||||||
|
- 关键指标
|
||||||
|
- 详查结果
|
||||||
|
- 每张卡片标题右侧直接展示汇总值
|
||||||
|
- 资产、负债卡片内部仅展示聚合后的来源项
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 最贴近当前确认原型和用户确认顺序
|
||||||
|
- 只动详情组件,改动边界最清晰
|
||||||
|
- 能自然去掉逐条表格,收拢成简洁汇总式详查
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 放弃现有逐条明细表阅读方式
|
||||||
|
|
||||||
|
### 5.4 结论
|
||||||
|
|
||||||
|
采用 **方案 C:纵向 5 段汇总卡片**。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
1. 完整匹配用户最终确认顺序
|
||||||
|
2. 不改接口即可实现
|
||||||
|
3. 视觉层级清晰,符合原型的“汇总式展开详情”目标
|
||||||
|
|
||||||
|
## 6. 总体设计
|
||||||
|
|
||||||
|
### 6.1 展开区结构
|
||||||
|
|
||||||
|
展开区固定改为以下顺序:
|
||||||
|
|
||||||
|
1. `总收入`
|
||||||
|
2. `总负债`
|
||||||
|
3. `总资产`
|
||||||
|
4. `关键指标`
|
||||||
|
5. `详查结果`
|
||||||
|
|
||||||
|
每个模块都采用统一结构:
|
||||||
|
|
||||||
|
- 标题左侧:模块名称
|
||||||
|
- 标题右侧:该模块汇总数字
|
||||||
|
- 内容区:来源项或指标项
|
||||||
|
|
||||||
|
### 6.2 模块规则
|
||||||
|
|
||||||
|
#### 6.2.1 总收入
|
||||||
|
|
||||||
|
标题右侧展示 `家庭总年收入`。
|
||||||
|
|
||||||
|
内容区固定展示两条来源项:
|
||||||
|
|
||||||
|
- 本人收入
|
||||||
|
- 配偶收入
|
||||||
|
|
||||||
|
不新增额外说明文字。
|
||||||
|
|
||||||
|
#### 6.2.2 总负债
|
||||||
|
|
||||||
|
标题右侧展示 `家庭总负债`。
|
||||||
|
|
||||||
|
内容区展示“按现有负债类型聚合后的来源项”,每条结构统一为:
|
||||||
|
|
||||||
|
- 负债类型名
|
||||||
|
- 聚合金额
|
||||||
|
- 占总负债比例
|
||||||
|
|
||||||
|
所有聚合项全部展示,不截断、不合并为“其他”。
|
||||||
|
|
||||||
|
#### 6.2.3 总资产
|
||||||
|
|
||||||
|
标题右侧展示 `家庭总资产`。
|
||||||
|
|
||||||
|
内容区展示“按现有资产类型聚合后的来源项”,每条结构统一为:
|
||||||
|
|
||||||
|
- 资产类型名
|
||||||
|
- 聚合金额
|
||||||
|
- 占总资产比例
|
||||||
|
|
||||||
|
所有聚合项全部展示,不截断、不合并为“其他”。
|
||||||
|
|
||||||
|
#### 6.2.4 关键指标
|
||||||
|
|
||||||
|
标题右侧固定展示指标数 `3`。
|
||||||
|
|
||||||
|
内容区固定展示以下 3 项:
|
||||||
|
|
||||||
|
- 资产负债率 = 总负债 / 总资产
|
||||||
|
- 资产/收入比 = 总资产 / 总收入
|
||||||
|
- 负债/收入比 = 总负债 / 总收入
|
||||||
|
|
||||||
|
展示格式:
|
||||||
|
|
||||||
|
- 百分比项使用 `%`
|
||||||
|
- 倍数项使用 `倍`
|
||||||
|
- 分母为 `0` 时显示 `-`
|
||||||
|
|
||||||
|
#### 6.2.5 详查结果
|
||||||
|
|
||||||
|
标题右侧展示 `riskLevelName`。
|
||||||
|
|
||||||
|
内容区展示单条结论文案,按真实风险等级切换样式和文案:
|
||||||
|
|
||||||
|
- `NORMAL`:结构基本合理
|
||||||
|
- `RISK`:负债与收入压力偏高
|
||||||
|
- `HIGH`:资产负债结构明显异常
|
||||||
|
- `MISSING_INFO`:当前信息不完整
|
||||||
|
|
||||||
|
不新增二级解释区。
|
||||||
|
|
||||||
|
## 7. 数据映射与计算规则
|
||||||
|
|
||||||
|
### 7.1 直接取值
|
||||||
|
|
||||||
|
- 总收入:`incomeDetail.totalIncome` 或 `summary.totalIncome`
|
||||||
|
- 总负债:`debtDetail.totalDebt` 或 `summary.totalDebt`
|
||||||
|
- 总资产:`assetDetail.totalAsset` 或 `summary.totalAsset`
|
||||||
|
- 风险等级:`summary.riskLevelCode`、`summary.riskLevelName`
|
||||||
|
|
||||||
|
### 7.2 前端计算
|
||||||
|
|
||||||
|
- 净资产 = 总资产 - 总负债
|
||||||
|
- 资产负债率 = 总负债 / 总资产
|
||||||
|
- 资产/收入比 = 总资产 / 总收入
|
||||||
|
- 负债/收入比 = 总负债 / 总收入
|
||||||
|
|
||||||
|
### 7.3 聚合规则
|
||||||
|
|
||||||
|
资产来源项:
|
||||||
|
|
||||||
|
- 基于 `assetDetail.items`
|
||||||
|
- 按现有类型字段聚合
|
||||||
|
- 保留全部聚合项
|
||||||
|
|
||||||
|
负债来源项:
|
||||||
|
|
||||||
|
- 基于 `debtDetail.items`
|
||||||
|
- 按现有类型字段聚合
|
||||||
|
- 保留全部聚合项
|
||||||
|
|
||||||
|
本次不要求后端新增分类字段,统一由前端基于现有字段归并。
|
||||||
|
|
||||||
|
## 8. 样式约束
|
||||||
|
|
||||||
|
- 取消现有逐条明细表和表头
|
||||||
|
- 资产、负债来源项改为紧凑摘要条目
|
||||||
|
- 桌面端可使用两列或三列流式排布承载来源项
|
||||||
|
- 窄屏回落为单列
|
||||||
|
- 金额统一使用 `¥` + 千分位格式
|
||||||
|
- 标题区与内容区仍延续专项核查现有白底大区块语义,不引入新主题色体系
|
||||||
|
|
||||||
|
## 9. 验证要点
|
||||||
|
|
||||||
|
### 9.1 结构验证
|
||||||
|
|
||||||
|
- 展开区顺序必须为:总收入 -> 总负债 -> 总资产 -> 关键指标 -> 详查结果
|
||||||
|
- 每张卡片标题中必须出现对应汇总数字
|
||||||
|
- 资产与负债来源项不再使用表格
|
||||||
|
|
||||||
|
### 9.2 计算验证
|
||||||
|
|
||||||
|
- 关键指标基于当前总额值计算,不重复请求接口
|
||||||
|
- 分母为 `0` 时显示 `-`
|
||||||
|
- 风险结果卡的标题和结论文案与真实风险等级一致
|
||||||
|
|
||||||
|
### 9.3 范围验证
|
||||||
|
|
||||||
|
- 列表层结构、列顺序和交互不变
|
||||||
|
- 接口路径与返回结构不变
|
||||||
|
- 项目切换、展开缓存和首次展开按需加载机制不变
|
||||||
|
|
||||||
|
## 10. 实施边界
|
||||||
|
|
||||||
|
本次前端实施默认只涉及:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/FamilyAssetLiabilityDetail.vue`
|
||||||
|
|
||||||
|
必要时补充与该组件直接相关的单元测试与样式断言,但不扩大到列表层和后端实现。
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# 项目管理列表重新分析确认与刷新设计文档
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
项目管理列表页已经接入“重新分析”真实调用链路:
|
||||||
|
|
||||||
|
- 前端通过 `POST /ccdi/project/tags/rebuild` 提交项目级重打标
|
||||||
|
- 成功后提示“已开始重新分析”
|
||||||
|
- 随后调用 `getList()` 刷新列表与状态统计
|
||||||
|
|
||||||
|
当前缺口主要有两个:
|
||||||
|
|
||||||
|
1. 用户点击“重新分析”后会立即发起请求,缺少二次确认
|
||||||
|
2. 需要明确本轮仍然沿用成功后的全列表刷新策略,不做局部行补丁更新
|
||||||
|
|
||||||
|
本次需求是在现有真实调用能力之上补齐交互确认,并保持最短路径实现。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
- 为项目列表中的“重新分析”按钮增加确认弹窗
|
||||||
|
- 用户确认后才触发 `rebuildProjectTags`
|
||||||
|
- 调用成功后继续刷新项目列表与顶部状态统计
|
||||||
|
- 保持现有失败提示、按钮提交态和状态驱动展示逻辑
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
### In Scope
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/index.vue` 中“重新分析”交互链路补充确认弹窗
|
||||||
|
- 沿用现有 `reAnalyzeLoadingMap` 防重复点击
|
||||||
|
- 成功后沿用 `getList()` 刷新列表和状态统计
|
||||||
|
- 取消确认、调用成功、调用失败三类前端行为收口
|
||||||
|
- 补充对应前端验证与实施文档
|
||||||
|
|
||||||
|
### Out of Scope
|
||||||
|
|
||||||
|
- 不新增后端接口
|
||||||
|
- 不新增轮询、进度提示、消息推送
|
||||||
|
- 不做局部列表行更新或乐观更新
|
||||||
|
- 不调整“重新分析”按钮显示条件
|
||||||
|
- 不变更后端重打标、风险人数计算或状态流转逻辑
|
||||||
|
|
||||||
|
## 现状分析
|
||||||
|
|
||||||
|
### 前端现状
|
||||||
|
|
||||||
|
`ruoyi-ui/src/views/ccdiProject/index.vue` 中的 `handleReAnalyze(row)` 当前流程为:
|
||||||
|
|
||||||
|
1. 判断当前项目是否处于重新分析提交中
|
||||||
|
2. 设置按钮 loading
|
||||||
|
3. 调用 `rebuildProjectTags({ projectId })`
|
||||||
|
4. 成功提示“已开始重新分析”
|
||||||
|
5. 调用 `getList()` 刷新列表
|
||||||
|
6. 失败时弹出错误提示
|
||||||
|
|
||||||
|
当前缺少用户确认步骤,因此点击即执行。
|
||||||
|
|
||||||
|
### 后端现状
|
||||||
|
|
||||||
|
后端现有 `/ccdi/project/tags/rebuild` 能力已满足本轮需求:
|
||||||
|
|
||||||
|
- 支持按 `projectId` 提交项目级重打标
|
||||||
|
- 会按既有流程更新项目状态
|
||||||
|
- 完成后由现有链路刷新结果与风险人数
|
||||||
|
|
||||||
|
因此本轮仍然只做前端交互补充,不引入后端源码改造。
|
||||||
|
|
||||||
|
## 方案选择
|
||||||
|
|
||||||
|
### 推荐方案
|
||||||
|
|
||||||
|
在列表页现有 `handleReAnalyze(row)` 中补充确认弹窗,确认后继续执行当前请求和刷新逻辑。
|
||||||
|
|
||||||
|
执行顺序:
|
||||||
|
|
||||||
|
1. 用户点击“重新分析”
|
||||||
|
2. 前端弹出确认框
|
||||||
|
3. 用户点击“确定”后再进入接口调用
|
||||||
|
4. 调用成功后提示“已开始重新分析”
|
||||||
|
5. 继续调用 `getList()` 刷新列表与统计
|
||||||
|
|
||||||
|
采用该方案的原因:
|
||||||
|
|
||||||
|
- 改动最小,符合最短路径要求
|
||||||
|
- 保持 `ProjectTable.vue` 只负责事件透传,不下沉业务交互逻辑
|
||||||
|
- 与当前页面级操作风格一致,便于复用现有 loading 和刷新逻辑
|
||||||
|
|
||||||
|
### 不采用的方案
|
||||||
|
|
||||||
|
#### 方案一:在 `ProjectTable.vue` 内直接处理确认弹窗
|
||||||
|
|
||||||
|
不采用原因:
|
||||||
|
|
||||||
|
- 表格组件职责变重
|
||||||
|
- 确认逻辑与接口逻辑分散在父子组件之间
|
||||||
|
- 不利于后续维护同类页面级操作
|
||||||
|
|
||||||
|
#### 方案二:抽象通用确认执行器
|
||||||
|
|
||||||
|
不采用原因:
|
||||||
|
|
||||||
|
- 对当前需求属于过度设计
|
||||||
|
- 只为单个按钮增加抽象,收益低于额外复杂度
|
||||||
|
|
||||||
|
## 详细设计
|
||||||
|
|
||||||
|
## 1. 交互设计
|
||||||
|
|
||||||
|
点击“重新分析”后,前端先弹出确认框,文案为:
|
||||||
|
|
||||||
|
`确认对项目“{项目名称}”重新分析吗?重新分析将重新计算项目标签。`
|
||||||
|
|
||||||
|
交互约束:
|
||||||
|
|
||||||
|
- 点击“确定”才继续发起请求
|
||||||
|
- 点击“取消”直接结束,不提示失败,不刷新列表
|
||||||
|
- 弹窗只承担确认职责,不展示额外解释信息
|
||||||
|
|
||||||
|
## 2. 数据流与状态设计
|
||||||
|
|
||||||
|
确认后的请求链路保持现有实现:
|
||||||
|
|
||||||
|
1. 读取 `projectId`
|
||||||
|
2. 设置当前项目按钮 loading
|
||||||
|
3. 调用 `rebuildProjectTags({ projectId })`
|
||||||
|
4. 成功后提示 `已开始重新分析`
|
||||||
|
5. 调用 `getList()` 刷新列表和顶部统计
|
||||||
|
6. 在 `finally` 中清理 loading
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `getList()` 已并行请求项目列表和状态统计,继续复用即可保证界面一致性
|
||||||
|
- 不做局部行状态修改,避免列表和顶部统计出现时序不一致
|
||||||
|
|
||||||
|
## 3. 异常与边界设计
|
||||||
|
|
||||||
|
### 取消确认
|
||||||
|
|
||||||
|
- 用户主动取消时,不发请求
|
||||||
|
- 不设置 loading
|
||||||
|
- 不弹错误提示
|
||||||
|
|
||||||
|
### 接口失败
|
||||||
|
|
||||||
|
- 优先展示后端返回的 `error.message`
|
||||||
|
- 无明确业务文案时,统一提示 `重新分析失败,请稍后重试`
|
||||||
|
- 失败时不刷新列表,保持当前数据稳定
|
||||||
|
|
||||||
|
### 防重复提交
|
||||||
|
|
||||||
|
- 保持单项目维度的 `reAnalyzeLoadingMap`
|
||||||
|
- 只有在用户确认后才进入 loading
|
||||||
|
- 提交结束后恢复按钮状态
|
||||||
|
|
||||||
|
## 4. 测试设计
|
||||||
|
|
||||||
|
前端验证聚焦以下场景:
|
||||||
|
|
||||||
|
1. 点击“重新分析”先出现确认弹窗
|
||||||
|
2. 点击“取消”时不发请求、不刷新列表
|
||||||
|
3. 点击“确定”后发起接口调用
|
||||||
|
4. 请求成功时提示“已开始重新分析”并刷新列表
|
||||||
|
5. 请求失败时提示失败信息,按钮恢复可点击
|
||||||
|
|
||||||
|
回归重点:
|
||||||
|
|
||||||
|
- 重新分析按钮仍只在既有状态条件下显示
|
||||||
|
- 成功刷新后项目状态和顶部统计继续由后端返回结果驱动
|
||||||
|
|
||||||
|
## 5. 后端影响说明
|
||||||
|
|
||||||
|
本轮后端无需改造。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 现有 `/ccdi/project/tags/rebuild` 已满足提交能力
|
||||||
|
- 现有 `getList()` 刷新结果已经满足页面回显需要
|
||||||
|
- 本次变更仅增加用户确认,不改变接口语义
|
||||||
|
|
||||||
|
## 6. 风险与约束
|
||||||
|
|
||||||
|
- 本轮仍然是“提交后刷新一次”的模式,不展示任务进度
|
||||||
|
- 如果后端异步处理时间较长,用户看到项目进入处理中状态属于预期
|
||||||
|
- 方案依赖现有后端重打标链路稳定可用,不额外建设旁路能力
|
||||||
|
|
||||||
|
## 7. 本次设计结论
|
||||||
|
|
||||||
|
本次需求采用“列表页现有重新分析链路上补确认弹窗,并在成功后继续全列表刷新”的方案。
|
||||||
|
|
||||||
|
该方案满足以下要求:
|
||||||
|
|
||||||
|
- 最短路径实现
|
||||||
|
- 不引入补丁式旁路方案
|
||||||
|
- 前后端职责边界清晰
|
||||||
|
- 列表数据与统计刷新保持一致
|
||||||
266
docs/design/2026-03-27-results-overview-card-merge-design.md
Normal file
266
docs/design/2026-03-27-results-overview-card-merge-design.md
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
# 结果总览卡片结构合并设计文档
|
||||||
|
|
||||||
|
**日期**: 2026-03-27
|
||||||
|
**模块**: 初核项目详情 - 结果总览
|
||||||
|
**作者**: Codex
|
||||||
|
**状态**: 已确认
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
当前结果总览页顶部存在两个连续区块:
|
||||||
|
|
||||||
|
- `风险仪表盘`
|
||||||
|
- `风险人员总览`
|
||||||
|
|
||||||
|
两者都服务于同一类“项目风险总览”信息,但当前被拆成两个独立白卡,导致首屏结构割裂,标题层级重复,且统计卡片虽然已有 `label` 与 `icon` 数据字段,视觉上没有形成统一的“总览卡片”语义。
|
||||||
|
|
||||||
|
本次需求要求在不新增页面元素、不扩展中风险 TOP10 等新区域的前提下,把现有结果总览页面中的顶部两块内容收拢为同一个大卡片,并统一后续区块命名。
|
||||||
|
|
||||||
|
## 2. 设计范围
|
||||||
|
|
||||||
|
### 2.1 包含内容
|
||||||
|
|
||||||
|
- 将 `风险仪表盘` 与 `风险人员总览` 合并为一个 `风险总览` 卡片
|
||||||
|
- 为结果总览顶部 5 个统计卡片补充明确标题与小图标展示
|
||||||
|
- 将第二个卡片标题统一为 `风险模型`
|
||||||
|
- 将第三个卡片标题统一为 `风险明细`
|
||||||
|
- 补充本次设计文档与设计记录
|
||||||
|
|
||||||
|
### 2.2 不包含内容
|
||||||
|
|
||||||
|
- 不新增 `中风险人员TOP10`、`高风险人员清单` 等额外区块
|
||||||
|
- 不新增或修改后端接口
|
||||||
|
- 不修改统计口径、人员表格字段含义与查看详情链路
|
||||||
|
- 不调整风险模型区和风险明细区的业务内容
|
||||||
|
- 不做兼容性、补丁式或降级方案分支
|
||||||
|
|
||||||
|
## 3. 当前上下文
|
||||||
|
|
||||||
|
当前结果总览页主要由以下组件组成:
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/OverviewStats.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskModelSection.vue`
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskDetailSection.vue`
|
||||||
|
|
||||||
|
当前实现特点如下:
|
||||||
|
|
||||||
|
1. `OverviewStats.vue` 独立渲染外层白卡、标题、副标题与统计卡片行。
|
||||||
|
2. `RiskPeopleSection.vue` 独立渲染另一张白卡,内部包含标题、副标题、导出按钮与风险人员表格。
|
||||||
|
3. `preliminaryCheck.mock.js` 和真实接口装配中,顶部统计项已经具备 `label`、`icon`、`tone` 字段。
|
||||||
|
4. 风险模型区和风险明细区已经作为独立区块存在,后续继续保留。
|
||||||
|
|
||||||
|
## 4. 设计目标
|
||||||
|
|
||||||
|
### 4.1 视觉目标
|
||||||
|
|
||||||
|
- 顶部信息收拢为一个清晰的“风险总览”首屏卡片
|
||||||
|
- 让统计卡片与风险人员表格形成上下连续的总览阅读流
|
||||||
|
- 去掉 `风险仪表盘` 与 `风险人员总览` 的重复壳层与重复标题感
|
||||||
|
- 统一页面主区块命名,形成:
|
||||||
|
- `风险总览`
|
||||||
|
- `风险模型`
|
||||||
|
- `风险明细`
|
||||||
|
|
||||||
|
### 4.2 实现目标
|
||||||
|
|
||||||
|
- 沿用当前组件和数据结构,不新增接口与平行组件体系
|
||||||
|
- 将改动控制在结果总览前端视图层
|
||||||
|
- 保持现有导出、查看详情、模型联动等交互行为不变
|
||||||
|
|
||||||
|
## 5. 方案比较
|
||||||
|
|
||||||
|
### 5.1 方案 A:父层合并,保留子组件职责
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 在 `PreliminaryCheck.vue` 中新增统一的 `风险总览` 外层卡片
|
||||||
|
- `OverviewStats.vue` 仅负责统计卡片内容
|
||||||
|
- `RiskPeopleSection.vue` 仅负责风险人员表格内容
|
||||||
|
- 两个子组件不再各自渲染独立白卡壳层
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 最短路径实现
|
||||||
|
- 不改数据结构
|
||||||
|
- 组件职责仍然清晰
|
||||||
|
- 风险最小,最符合“按当前页面元素合并”的要求
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 需要同步收掉两个子组件原有的外层样式壳
|
||||||
|
|
||||||
|
### 5.2 方案 B:把统计区直接并入风险人员组件
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 由 `RiskPeopleSection.vue` 同时负责标题、统计卡片、人员表格
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 页面结构集中在一个组件中,阅读更直观
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 组件职责变重
|
||||||
|
- 后续维护与复用性变差
|
||||||
|
|
||||||
|
### 5.3 方案 C:新增组合组件承接三段结构
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 新建结果总览组合组件,内部再调用统计与人员子块
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 语义完整
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 对本次小范围需求来说偏重
|
||||||
|
- 会增加一层额外封装,不是最短路径
|
||||||
|
|
||||||
|
### 5.4 结论
|
||||||
|
|
||||||
|
采用 **方案 A:父层合并,保留子组件职责**。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
1. 与当前组件拆分方式最匹配
|
||||||
|
2. 不引入多余封装
|
||||||
|
3. 能在最小改动下完成结构合并和标题统一
|
||||||
|
|
||||||
|
## 6. 总体设计
|
||||||
|
|
||||||
|
### 6.1 页面区块结构
|
||||||
|
|
||||||
|
结果总览页主内容区调整为三张主卡片:
|
||||||
|
|
||||||
|
1. `风险总览`
|
||||||
|
2. `风险模型`
|
||||||
|
3. `风险明细`
|
||||||
|
|
||||||
|
其中第一张 `风险总览` 卡片内部结构为:
|
||||||
|
|
||||||
|
1. 卡片标题栏:`风险总览`
|
||||||
|
2. 第一部分:5 个统计小卡片
|
||||||
|
3. 第二部分:原 `风险人员总览` 表格与导出按钮
|
||||||
|
|
||||||
|
### 6.2 风险总览卡片
|
||||||
|
|
||||||
|
`风险总览` 卡片承接原来的两块内容,但只保留一层外壳。
|
||||||
|
|
||||||
|
内容顺序固定为:
|
||||||
|
|
||||||
|
1. 标题栏
|
||||||
|
2. 统计卡片区
|
||||||
|
3. 风险人员表格区
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 不再单独显示 `风险仪表盘` 标题与副标题
|
||||||
|
- 不再单独显示 `风险人员总览` 作为平级卡片标题
|
||||||
|
- 风险人员列表继续展示当前已有字段和操作入口
|
||||||
|
|
||||||
|
### 6.3 统计卡片展示
|
||||||
|
|
||||||
|
顶部 5 个统计小卡片继续沿用当前数据项,不改口径:
|
||||||
|
|
||||||
|
- 总人数
|
||||||
|
- 高风险
|
||||||
|
- 中风险
|
||||||
|
- 低风险
|
||||||
|
- 无预警人数
|
||||||
|
|
||||||
|
每个卡片统一采用:
|
||||||
|
|
||||||
|
- 左侧小图标
|
||||||
|
- 右侧标题
|
||||||
|
- 下方主数值
|
||||||
|
|
||||||
|
图标、颜色继续复用现有 `icon` 与 `tone` 字段,不额外设计新的数据协议。
|
||||||
|
|
||||||
|
### 6.4 风险模型卡片
|
||||||
|
|
||||||
|
第二张主卡片标题统一为 `风险模型`。
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- 保持当前风险模型区内容、筛选、联动和查看详情行为不变
|
||||||
|
- 本次仅调整标题命名和与首卡片的层级关系,不扩展新功能
|
||||||
|
|
||||||
|
### 6.5 风险明细卡片
|
||||||
|
|
||||||
|
第三张主卡片标题统一为 `风险明细`。
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- 保持当前风险明细区内容和行为不变
|
||||||
|
- 本次仅统一标题和页面主区块语义
|
||||||
|
|
||||||
|
## 7. 组件改动边界
|
||||||
|
|
||||||
|
### 7.1 `PreliminaryCheck.vue`
|
||||||
|
|
||||||
|
- 负责重新编排顶部区块
|
||||||
|
- 将 `OverviewStats` 与 `RiskPeopleSection` 包进统一的 `风险总览` 容器
|
||||||
|
- 保持 `RiskModelSection` 与 `RiskDetailSection` 继续作为独立区块
|
||||||
|
|
||||||
|
### 7.2 `OverviewStats.vue`
|
||||||
|
|
||||||
|
- 去掉自身独立大卡片壳层与原 `风险仪表盘` 标题、副标题
|
||||||
|
- 保留统计卡片栅格内容
|
||||||
|
- 完成统计卡片“标题 + 小图标 + 数值”展示
|
||||||
|
|
||||||
|
### 7.3 `RiskPeopleSection.vue`
|
||||||
|
|
||||||
|
- 去掉自身独立白卡壳层
|
||||||
|
- 作为 `风险总览` 卡片中的下半部分存在
|
||||||
|
- 保留当前导出按钮、表格字段与查看详情事件
|
||||||
|
|
||||||
|
### 7.4 `RiskModelSection.vue`
|
||||||
|
|
||||||
|
- 区块标题调整为 `风险模型`
|
||||||
|
- 其余数据与交互逻辑保持不变
|
||||||
|
|
||||||
|
### 7.5 `RiskDetailSection.vue`
|
||||||
|
|
||||||
|
- 区块标题调整为 `风险明细`
|
||||||
|
- 其余数据与交互逻辑保持不变
|
||||||
|
|
||||||
|
## 8. 数据与交互约束
|
||||||
|
|
||||||
|
本次设计不调整以下内容:
|
||||||
|
|
||||||
|
- `currentData.summary`
|
||||||
|
- `currentData.riskPeople`
|
||||||
|
- `currentData.riskModels`
|
||||||
|
- `currentData.riskDetails`
|
||||||
|
|
||||||
|
也就是说:
|
||||||
|
|
||||||
|
1. 不新增后端接口
|
||||||
|
2. 不改接口拼装逻辑
|
||||||
|
3. 不改统计卡片数量与字段口径
|
||||||
|
4. 不改风险人员表格字段含义
|
||||||
|
5. 不改模型区和风险明细区的业务行为
|
||||||
|
|
||||||
|
## 9. 验证口径
|
||||||
|
|
||||||
|
实施后需验证以下结果:
|
||||||
|
|
||||||
|
1. 首屏顶部只保留一个 `风险总览` 大卡片
|
||||||
|
2. `风险总览` 内同时包含统计卡片区和风险人员表格区
|
||||||
|
3. 5 个统计卡片都展示标题与小图标,数值与现有口径一致
|
||||||
|
4. 风险人员列表导出、查看详情与表格字段正常
|
||||||
|
5. 第二个卡片标题为 `风险模型`
|
||||||
|
6. 第三个卡片标题为 `风险明细`
|
||||||
|
7. 其他区块位置和现有交互不受影响
|
||||||
|
|
||||||
|
## 10. 后续动作
|
||||||
|
|
||||||
|
待用户审阅本设计文档后,继续输出两份实施计划:
|
||||||
|
|
||||||
|
- `docs/plans/backend/` 下的后端实施计划
|
||||||
|
- `docs/plans/frontend/` 下的前端实施计划
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# 结果总览涉疑交易明细与流水明细查询对齐设计
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
- 结果总览页的“风险明细 > 涉疑交易明细”已接通真实接口,但当前列表列结构、详情弹窗内容和分页交互均与“流水明细查询”页面不一致。
|
||||||
|
- 用户要求以“流水明细查询”页为基准对齐交互与视觉,仅保留涉疑交易特有的“关联员工”字段,其余定制列移除。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
- 列表对齐“流水明细查询”页面的主表结构与样式。
|
||||||
|
- 详情弹窗对齐“流水明细查询”页面的字段布局、原始文件区域与命中异常标签区域。
|
||||||
|
- 分页固定每页 5 条,筛选切换后回到第一页。
|
||||||
|
- 保留“关联员工”列,作为涉疑交易明细相对流水明细查询的唯一额外业务列。
|
||||||
|
|
||||||
|
## 方案
|
||||||
|
|
||||||
|
### 列表
|
||||||
|
|
||||||
|
- `RiskDetailSection.vue` 保留“涉疑交易明细”区块与筛选下拉。
|
||||||
|
- 表格列改为:
|
||||||
|
- 交易时间
|
||||||
|
- 本方账户
|
||||||
|
- 对方账户
|
||||||
|
- 关联员工
|
||||||
|
- 摘要 / 交易类型
|
||||||
|
- 异常标签
|
||||||
|
- 交易金额
|
||||||
|
- 详情
|
||||||
|
- 保持与 `DetailQuery.vue` 相同的多行单元格结构、金额颜色和异常标签样式。
|
||||||
|
|
||||||
|
### 分页
|
||||||
|
|
||||||
|
- 组件内部新增独立分页状态:
|
||||||
|
- `suspiciousPageNum`
|
||||||
|
- `suspiciousPageSize` 固定为 `5`
|
||||||
|
- `suspiciousTotal`
|
||||||
|
- 初次加载、筛选切换和翻页都通过 `getOverviewSuspiciousTransactions` 重新请求。
|
||||||
|
- 分页组件沿用仓库现有 `Pagination`,但限制 `pageSizes` 为 `[5]`,并移除 `sizes` 布局项。
|
||||||
|
|
||||||
|
### 详情弹窗
|
||||||
|
|
||||||
|
- 详情弹窗结构对齐 `DetailQuery.vue`:
|
||||||
|
- 基础字段宫格
|
||||||
|
- 原始文件信息
|
||||||
|
- 命中异常标签
|
||||||
|
- 详情数据继续复用 `getBankStatementDetail(bankStatementId)`,避免新增后端接口。
|
||||||
|
|
||||||
|
### 异常标签
|
||||||
|
|
||||||
|
- 结果总览涉疑交易列表接口当前不直接返回 `hitTags`。
|
||||||
|
- 前端在列表加载完成后,按当前页流水 `bankStatementId` 逐条调用详情接口补齐 `hitTags`,仅处理当前页 5 条数据,保证逻辑闭环且不扩大后端改造范围。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- 前端:
|
||||||
|
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskDetailSection.vue`
|
||||||
|
- `ruoyi-ui/tests/unit/`
|
||||||
|
- 文档:
|
||||||
|
- 当前设计文档
|
||||||
|
- 本轮实施记录
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 单测验证列表列名、分页配置、详情弹窗字段与异常标签区域。
|
||||||
|
- 浏览器联调验证:
|
||||||
|
- 初始加载为 5 条
|
||||||
|
- 筛选切换可翻页
|
||||||
|
- 详情弹窗样式与字段对齐
|
||||||
|
- 导出仍可用
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
# 项目详情风险总览员工列表分页设计文档
|
||||||
|
|
||||||
|
**模块**: 项目详情 - 结果总览 - 风险总览员工列表
|
||||||
|
**日期**: 2026-03-29
|
||||||
|
|
||||||
|
## 一、背景
|
||||||
|
|
||||||
|
当前项目详情页 `结果总览 -> 风险总览` 中的员工列表直接消费 `GET /ccdi/project/overview/risk-people` 返回的全量 `overviewList`。页面没有分页能力,项目内风险员工较多时会导致:
|
||||||
|
|
||||||
|
1. 单屏信息过长,浏览成本高。
|
||||||
|
2. 前端一次性渲染全量列表,交互体验不稳定。
|
||||||
|
3. 接口返回风格与当前结果总览域内其他分页接口不一致。
|
||||||
|
|
||||||
|
本轮需求要求为该员工列表增加分页,并固定为每页 5 条。同时,需求明确要求改造现有接口,不采用前端本地切片或新增补丁接口的方式。
|
||||||
|
|
||||||
|
## 二、目标
|
||||||
|
|
||||||
|
本次设计目标如下:
|
||||||
|
|
||||||
|
1. 将 `GET /ccdi/project/overview/risk-people` 改造成真实分页接口。
|
||||||
|
2. 风险总览员工列表固定每页展示 5 条。
|
||||||
|
3. 前端翻页时仅刷新员工列表,不影响结果总览内其他区块。
|
||||||
|
4. 分页接口返回结构对齐项目现有分页风格,统一为 `rows + total + pageNum + pageSize`。
|
||||||
|
|
||||||
|
## 三、范围
|
||||||
|
|
||||||
|
### 3.1 本次范围
|
||||||
|
|
||||||
|
- 改造 `risk-people` 接口入参与返回结构
|
||||||
|
- 为后端风险人员查询增加数据库分页
|
||||||
|
- 调整项目详情结果总览首屏数据装配
|
||||||
|
- 在 `RiskPeopleSection.vue` 增加分页条与翻页请求
|
||||||
|
- 补充本次设计文档、设计记录,以及后续前后端实施计划入口
|
||||||
|
|
||||||
|
### 3.2 不在本次范围
|
||||||
|
|
||||||
|
- 不修改风险仪表盘接口
|
||||||
|
- 不修改风险模型卡片与模型命中人员查询
|
||||||
|
- 不修改风险明细区块
|
||||||
|
- 不新增筛选条件、搜索条件或排序条件
|
||||||
|
- 不修改风险等级、命中模型数、核心异常点的业务口径
|
||||||
|
- 不删除现有 `GET /ccdi/project/overview/top-risk-people`
|
||||||
|
|
||||||
|
## 四、现状分析
|
||||||
|
|
||||||
|
### 4.1 前端现状
|
||||||
|
|
||||||
|
当前 `PreliminaryCheck.vue` 在页面加载时并发请求:
|
||||||
|
|
||||||
|
- `GET /ccdi/project/overview/dashboard`
|
||||||
|
- `GET /ccdi/project/overview/risk-people`
|
||||||
|
- `GET /ccdi/project/overview/risk-models/cards`
|
||||||
|
- `GET /ccdi/project/overview/suspicious-transactions`
|
||||||
|
- `GET /ccdi/project/overview/employee-credit-negative`
|
||||||
|
|
||||||
|
其中 `risk-people` 的返回结果被直接注入 `currentData.riskPeople.overviewList`,`RiskPeopleSection.vue` 直接用该数组渲染表格,没有分页状态,也没有独立二次加载链路。
|
||||||
|
|
||||||
|
### 4.2 后端现状
|
||||||
|
|
||||||
|
当前 `CcdiProjectOverviewController.getRiskPeople(Long projectId)` 只接收项目 ID。
|
||||||
|
|
||||||
|
`CcdiProjectOverviewServiceImpl.getRiskPeopleOverview(Long projectId)` 的实现为:
|
||||||
|
|
||||||
|
1. 校验项目存在
|
||||||
|
2. 调用 `overviewMapper.selectRiskPeopleOverviewByProjectId(projectId)` 查询全量员工结果
|
||||||
|
3. 在 Java 层逐行映射为 `overviewList`
|
||||||
|
4. 返回 `CcdiProjectRiskPeopleOverviewVO { overviewList }`
|
||||||
|
|
||||||
|
当前 SQL 直接从 `ccdi_project_overview_employee_result` 查询并排序:
|
||||||
|
|
||||||
|
- `risk_level_sort asc`
|
||||||
|
- `model_count desc`
|
||||||
|
- `rule_count desc`
|
||||||
|
- `staff_id_card asc`
|
||||||
|
|
||||||
|
现状能够提供正确列表语义,但不具备分页能力。
|
||||||
|
|
||||||
|
## 五、方案对比
|
||||||
|
|
||||||
|
### 5.1 方案 A:改造现有 `risk-people` 为标准分页接口
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 保持接口路径不变
|
||||||
|
- 入参扩展为 `projectId + pageNum + pageSize`
|
||||||
|
- 返回结构改为 `rows + total + pageNum + pageSize`
|
||||||
|
- 后端通过数据库分页查询返回当前页数据
|
||||||
|
- 前端首屏与翻页统一走该接口
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 满足“改接口”的明确要求
|
||||||
|
- 与 `risk-models/people`、`employee-credit-negative` 的分页风格一致
|
||||||
|
- 逻辑单一,没有重复接口
|
||||||
|
- 数据量增大时性能与语义都正确
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 需要同步调整前后端契约与测试
|
||||||
|
|
||||||
|
### 5.2 方案 B:新增 `risk-people/page`,保留原接口不动
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 原 `risk-people` 继续返回全量列表
|
||||||
|
- 新增单独分页接口供前端切换
|
||||||
|
|
||||||
|
问题:
|
||||||
|
|
||||||
|
- 留下两个语义重复的接口
|
||||||
|
- 不符合“改接口”的要求
|
||||||
|
- 增加后续维护成本
|
||||||
|
|
||||||
|
### 5.3 方案 C:后端仍全量查,Java 或前端再切页
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 接口表面返回分页结构
|
||||||
|
- 实际仍走全量查询后截断
|
||||||
|
|
||||||
|
问题:
|
||||||
|
|
||||||
|
- 不是真分页
|
||||||
|
- 数据量增大时性能与语义都不成立
|
||||||
|
- 属于补丁式方案,不符合最短路径要求
|
||||||
|
|
||||||
|
### 5.4 结论
|
||||||
|
|
||||||
|
采用方案 A。
|
||||||
|
|
||||||
|
## 六、接口设计
|
||||||
|
|
||||||
|
### 6.1 接口路径
|
||||||
|
|
||||||
|
- `GET /ccdi/project/overview/risk-people`
|
||||||
|
|
||||||
|
### 6.2 入参
|
||||||
|
|
||||||
|
- `projectId`: 项目 ID,必填
|
||||||
|
- `pageNum`: 页码,非必填,默认 `1`
|
||||||
|
- `pageSize`: 每页条数,非必填,默认 `5`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 前端固定传 `pageSize = 5`
|
||||||
|
- 后端默认值同样收敛为 `5`,避免前端漏传时行为偏移
|
||||||
|
|
||||||
|
### 6.3 返回结构
|
||||||
|
|
||||||
|
返回结构统一为:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"name": "李四",
|
||||||
|
"idNo": "330000000000000001",
|
||||||
|
"department": "信息二部",
|
||||||
|
"riskCount": 5,
|
||||||
|
"riskLevel": "中风险",
|
||||||
|
"riskLevelType": "warning",
|
||||||
|
"modelCount": 4,
|
||||||
|
"riskPoint": "大额单笔收入、疑似兼职",
|
||||||
|
"actionLabel": "查看项目"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 18,
|
||||||
|
"pageNum": 1,
|
||||||
|
"pageSize": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 原 `overviewList` 字段移除,统一改为 `rows`
|
||||||
|
- 单行字段保持现有页面绑定语义不变
|
||||||
|
- 本次不引入额外统计字段
|
||||||
|
|
||||||
|
## 七、后端设计
|
||||||
|
|
||||||
|
### 7.1 控制器
|
||||||
|
|
||||||
|
`CcdiProjectOverviewController.getRiskPeople` 改为接收独立 DTO,例如:
|
||||||
|
|
||||||
|
- `CcdiProjectRiskPeopleQueryDTO`
|
||||||
|
|
||||||
|
DTO 仅包含:
|
||||||
|
|
||||||
|
- `projectId`
|
||||||
|
- `pageNum`
|
||||||
|
- `pageSize`
|
||||||
|
|
||||||
|
不额外引入筛选项,保持最短路径。
|
||||||
|
|
||||||
|
### 7.2 服务层
|
||||||
|
|
||||||
|
`ICcdiProjectOverviewService.getRiskPeopleOverview` 改为接收查询 DTO,并返回分页 VO。
|
||||||
|
|
||||||
|
服务层职责:
|
||||||
|
|
||||||
|
1. 校验项目存在
|
||||||
|
2. 规范化分页参数,默认 `pageNum=1`、`pageSize=5`
|
||||||
|
3. 构造 MyBatis Plus `Page`
|
||||||
|
4. 调用 mapper 分页查询
|
||||||
|
5. 将记录映射为现有员工列表行结构
|
||||||
|
6. 返回 `rows + total + pageNum + pageSize`
|
||||||
|
|
||||||
|
### 7.3 VO 调整
|
||||||
|
|
||||||
|
`CcdiProjectRiskPeopleOverviewVO` 改为标准分页 VO,字段包括:
|
||||||
|
|
||||||
|
- `List<CcdiProjectRiskPeopleOverviewItemVO> rows`
|
||||||
|
- `Long total`
|
||||||
|
- `Long pageNum`
|
||||||
|
- `Long pageSize`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `CcdiProjectRiskPeopleOverviewItemVO` 本身字段不做语义调整
|
||||||
|
- 既有的风险等级映射与异常点标签来源保持不变
|
||||||
|
|
||||||
|
### 7.4 Mapper 与 SQL
|
||||||
|
|
||||||
|
Mapper 从“全量列表查询”改为“分页查询”,直接在数据库层完成分页。
|
||||||
|
|
||||||
|
排序规则保持现状不变:
|
||||||
|
|
||||||
|
- `risk_level_sort asc`
|
||||||
|
- `model_count desc`
|
||||||
|
- `rule_count desc`
|
||||||
|
- `staff_id_card asc`
|
||||||
|
|
||||||
|
数据来源继续使用 `ccdi_project_overview_employee_result`,不新增统计口径,不回退到历史复杂聚合链路。
|
||||||
|
|
||||||
|
### 7.5 默认值与边界
|
||||||
|
|
||||||
|
- `projectId` 为空时沿用现有参数校验/项目不存在校验逻辑
|
||||||
|
- `pageNum <= 0` 时按 `1` 处理
|
||||||
|
- `pageSize <= 0` 或为空时按 `5` 处理
|
||||||
|
- 本次不开放前端修改每页条数,接口虽接收 `pageSize`,但页面固定使用 5
|
||||||
|
|
||||||
|
## 八、前端设计
|
||||||
|
|
||||||
|
### 8.1 API 封装
|
||||||
|
|
||||||
|
`ruoyi-ui/src/api/ccdi/projectOverview.js` 中:
|
||||||
|
|
||||||
|
- `getOverviewRiskPeople` 从接收单个 `projectId` 改为接收 `params`
|
||||||
|
- 透传:
|
||||||
|
- `projectId`
|
||||||
|
- `pageNum`
|
||||||
|
- `pageSize`
|
||||||
|
|
||||||
|
### 8.2 首屏加载
|
||||||
|
|
||||||
|
`PreliminaryCheck.vue` 首次加载结果总览时,请求:
|
||||||
|
|
||||||
|
- `getOverviewRiskPeople({ projectId, pageNum: 1, pageSize: 5 })`
|
||||||
|
|
||||||
|
返回结果注入 `currentData.riskPeople` 时,直接保存分页结构:
|
||||||
|
|
||||||
|
- `rows`
|
||||||
|
- `total`
|
||||||
|
- `pageNum`
|
||||||
|
- `pageSize`
|
||||||
|
|
||||||
|
页面是否进入 `loaded` 状态的判断,从原来的 `overviewList.length` 改为 `rows.length`。
|
||||||
|
|
||||||
|
### 8.3 风险总览员工列表组件
|
||||||
|
|
||||||
|
`RiskPeopleSection.vue` 调整为:
|
||||||
|
|
||||||
|
1. 表格数据源从 `sectionData.overviewList` 改为 `sectionData.rows`
|
||||||
|
2. 在表格下方增加分页组件
|
||||||
|
3. 分页组件固定:
|
||||||
|
- `:page-sizes="[5]"`
|
||||||
|
- `layout="total, prev, pager, next, jumper"`
|
||||||
|
4. 页码变化时触发独立请求,仅刷新员工列表分页数据
|
||||||
|
|
||||||
|
分页条展示规则:
|
||||||
|
|
||||||
|
- `total > 0` 时展示
|
||||||
|
- `total = 0` 时隐藏
|
||||||
|
|
||||||
|
### 8.4 页面刷新策略
|
||||||
|
|
||||||
|
翻页时只刷新风险总览员工列表,不重新拉取:
|
||||||
|
|
||||||
|
- 风险仪表盘
|
||||||
|
- 风险模型卡片
|
||||||
|
- 风险明细涉疑交易
|
||||||
|
- 风险明细员工负面征信
|
||||||
|
|
||||||
|
这样可以避免其他区块闪动,也避免把简单翻页放大成整页重载。
|
||||||
|
|
||||||
|
### 8.5 交互保持不变
|
||||||
|
|
||||||
|
以下内容本次保持不变:
|
||||||
|
|
||||||
|
- 表格列顺序
|
||||||
|
- 风险等级标签渲染
|
||||||
|
- 核心异常点标签拆分与色板逻辑
|
||||||
|
- 操作列文案与点击事件
|
||||||
|
- 空态文案
|
||||||
|
|
||||||
|
## 九、测试设计
|
||||||
|
|
||||||
|
### 9.1 后端测试
|
||||||
|
|
||||||
|
新增或调整以下验证:
|
||||||
|
|
||||||
|
1. Controller 测试
|
||||||
|
- 断言 `/risk-people` 接口改为接收 DTO
|
||||||
|
- 断言返回 `rows + total + pageNum + pageSize`
|
||||||
|
|
||||||
|
2. Service 测试
|
||||||
|
- 断言服务层使用分页参数构造 `Page`
|
||||||
|
- 断言现有字段映射未变化
|
||||||
|
- 断言默认分页参数回落为 `1 / 5`
|
||||||
|
|
||||||
|
3. Mapper/SQL 测试
|
||||||
|
- 断言风险人员查询改为分页查询方法
|
||||||
|
- 断言排序字段未变化
|
||||||
|
- 断言数据来源仍为 `ccdi_project_overview_employee_result`
|
||||||
|
|
||||||
|
### 9.2 前端测试
|
||||||
|
|
||||||
|
新增或调整以下验证:
|
||||||
|
|
||||||
|
1. API 封装测试
|
||||||
|
- 断言 `getOverviewRiskPeople(params)` 透传 `projectId/pageNum/pageSize`
|
||||||
|
|
||||||
|
2. 页面接入测试
|
||||||
|
- 断言 `PreliminaryCheck.vue` 首次加载传 `pageNum: 1`
|
||||||
|
- 断言固定传 `pageSize: 5`
|
||||||
|
- 断言风险人员数据改为读取 `rows`
|
||||||
|
|
||||||
|
3. 风险总览组件测试
|
||||||
|
- 断言 `RiskPeopleSection.vue` 存在分页组件
|
||||||
|
- 断言分页绑定 `rows/total/pageNum/pageSize`
|
||||||
|
- 断言分页大小固定为 5
|
||||||
|
|
||||||
|
## 十、实施文档要求
|
||||||
|
|
||||||
|
本次设计确认后,按仓库规范继续产出两份实施计划:
|
||||||
|
|
||||||
|
- 后端实施计划:`docs/plans/backend/`
|
||||||
|
- 前端实施计划:`docs/plans/frontend/`
|
||||||
|
|
||||||
|
实施完成后,补充对应实施记录,记录本次真实改动内容。
|
||||||
|
|
||||||
|
## 十一、结论
|
||||||
|
|
||||||
|
本次需求本质是将结果总览中的风险员工列表从“全量列表展示”升级为“标准分页列表展示”。
|
||||||
|
|
||||||
|
最终方案为:
|
||||||
|
|
||||||
|
1. 保持 `GET /ccdi/project/overview/risk-people` 路径不变
|
||||||
|
2. 改为标准分页接口,返回 `rows + total + pageNum + pageSize`
|
||||||
|
3. 后端在数据库层做真分页,默认每页 5 条
|
||||||
|
4. 前端首屏和翻页统一走该接口
|
||||||
|
5. 翻页仅刷新员工列表,不重载结果总览其他区块
|
||||||
|
|
||||||
|
该方案满足需求边界明确、链路完整、实现路径最短,且不引入额外补丁接口或兼容分支。
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user