<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper">

    <resultMap id="EmployeeRiskAggregateResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO">
        <result property="staffIdCard" column="staff_id_card"/>
        <result property="staffName" column="staff_name"/>
        <result property="deptId" column="dept_id"/>
        <result property="deptName" column="dept_name"/>
        <result property="ruleCount" column="rule_count"/>
        <result property="modelCount" column="model_count"/>
        <result property="hitCount" column="hit_count"/>
        <result property="topRuleCode" column="top_rule_code"/>
        <result property="topRuleName" column="top_rule_name"/>
        <result property="riskPoint" column="risk_point"/>
        <result property="riskLevelCode" column="risk_level_code"/>
        <result property="riskLevelName" column="risk_level_name"/>
        <result property="riskLevelSort" column="risk_level_sort"/>
    </resultMap>

    <resultMap id="RiskModelPeopleItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO">
        <id property="idNo" column="staff_id_card"/>
        <result property="staffName" column="staff_name"/>
        <result property="staffCode" column="staff_code"/>
        <result property="department" column="department"/>
        <collection property="modelNames"
                    column="{projectId=project_id,staffIdCard=staff_id_card,selectedModelCodes=selected_model_codes}"
                    ofType="java.lang.String"
                    select="selectRiskModelNamesByScope"/>
        <collection property="hitTagList"
                    column="{projectId=project_id,staffIdCard=staff_id_card,selectedModelCodes=selected_model_codes}"
                    ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"
                    select="selectRiskHitTagsByScope"/>
    </resultMap>

    <resultMap id="ExternalRiskModelPeopleItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO">
        <id property="idNo" column="cert_no"/>
        <result property="staffName" column="person_name"/>
        <result property="staffCode" column="subject_type"/>
        <result property="department" column="related_object"/>
        <collection property="modelNames"
                    column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
                    ofType="java.lang.String"
                    select="selectExternalRiskModelNamesByScope"/>
        <collection property="hitTagList"
                    column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
                    ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"
                    select="selectExternalRiskHitTagsByScope"/>
    </resultMap>

    <resultMap id="ExternalPersonWarningItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO">
        <result property="name" column="person_name"/>
        <result property="idNo" column="cert_no"/>
        <result property="subjectType" column="subject_type"/>
        <result property="riskLevel" column="risk_level_name"/>
        <result property="riskLevelType" column="risk_level_type"/>
        <result property="riskCount" column="risk_count"/>
        <result property="modelCount" column="model_count"/>
        <result property="riskPoint" column="risk_point"/>
        <result property="relatedObject" column="related_object"/>
        <result property="latestTradeTime" column="latest_trade_time"/>
        <collection property="riskPointTagList"
                    column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
                    ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"
                    select="selectExternalRiskHitTagsByScope"/>
    </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"/>
        <result property="nameListHitType" column="nameListHitType"/>
    </resultMap>

    <resultMap id="AbnormalAccountItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO">
        <result property="accountNo" column="accountNo"/>
        <result property="accountName" column="accountName"/>
        <result property="bankName" column="bankName"/>
        <result property="abnormalType" column="abnormalType"/>
        <result property="abnormalTime" column="abnormal_time"/>
        <result property="status" column="status"/>
    </resultMap>

    <resultMap id="ReportUploadSubjectResultMap"
               type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportUploadSubjectVO">
        <result property="subjectName" column="subjectName"/>
        <result property="accountNos" column="accountNos"/>
        <result property="minTrxDate" column="minTrxDate"/>
        <result property="maxTrxDate" column="maxTrxDate"/>
        <result property="fileCount" column="fileCount"/>
    </resultMap>

    <resultMap id="ReportModelSummaryResultMap"
               type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO">
        <result property="modelCode" column="modelCode"/>
        <result property="modelName" column="modelName"/>
        <result property="warningCount" column="warningCount"/>
        <result property="peopleCount" column="peopleCount"/>
        <result property="peopleNames" column="peopleNames"/>
    </resultMap>

    <resultMap id="ReportSuspiciousTransactionResultMap"
               type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO">
        <id property="bankStatementId" column="bankStatementId"/>
        <result property="trxDate" column="trxDate"/>
        <result property="leAccountNo" column="leAccountNo"/>
        <result property="leAccountName" column="leAccountName"/>
        <result property="customerAccountName" column="customerAccountName"/>
        <result property="customerAccountNo" column="customerAccountNo"/>
        <result property="relatedStaffName" column="relatedStaffName"/>
        <result property="relatedStaffCode" column="relatedStaffCode"/>
        <result property="userMemo" column="userMemo"/>
        <result property="cashType" column="cashType"/>
        <result property="hitTags" column="hitTags"/>
        <result property="displayAmount" column="displayAmount"/>
    </resultMap>

    <sql id="digitTableSql">
        select 0 as digit
        union all select 1
        union all select 2
        union all select 3
        union all select 4
        union all select 5
        union all select 6
        union all select 7
        union all select 8
        union all select 9
    </sql>

    <sql id="jsonArrayIndexSql">
        select ones.digit + tens.digit * 10 as idx
        from (
            <include refid="digitTableSql"/>
        ) ones
        cross join (
            <include refid="digitTableSql"/>
        ) tens
    </sql>

    <sql id="externalModelCodeFilterSql">
        ('EXTERNAL_LARGE_TRANSACTION',
         'EXTERNAL_ABNORMAL_TRANSACTION',
         'EXTERNAL_SUSPICIOUS_GAMBLING',
         'EXTERNAL_SUSPICIOUS_RELATION')
    </sql>

    <sql id="resolvedEmployeeRiskBaseSql">
        select distinct
            tr.id,
            tr.project_id,
            coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) as staff_id_card,
            coalesce(direct_staff.name, statement_staff.name, family_staff.name) as staff_name,
            cast(coalesce(direct_staff.staff_id, statement_staff.staff_id, family_staff.staff_id) as char) as staff_code,
            coalesce(direct_staff.dept_id, statement_staff.dept_id, family_staff.dept_id) as dept_id,
            tr.rule_code,
            tr.rule_name,
            tr.model_code,
            tr.model_name,
            tr.risk_level
        from ccdi_bank_statement_tag_result tr
        left join ccdi_base_staff direct_staff
            on tr.object_type = 'STAFF_ID_CARD'
           and tr.object_key = direct_staff.id_card
        left join ccdi_bank_statement bs
            on tr.bank_statement_id = bs.bank_statement_id
        left join ccdi_base_staff statement_staff
            on (tr.object_key is null or tr.object_key = '')
           and bs.cret_no = statement_staff.id_card
        left join ccdi_staff_fmy_relation relation
            on relation.status = 1
           and (
                ((tr.object_key is null or tr.object_key = '') and bs.cret_no = relation.relation_cert_no)
                or ((tr.object_key is not null and tr.object_key != '') and tr.object_type != 'STAFF_ID_CARD'
                    and tr.object_key = relation.relation_cert_no)
           )
        left join ccdi_base_staff family_staff
            on relation.person_id = family_staff.id_card
        where tr.project_id = #{projectId}
          and coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) is not null
    </sql>

    <sql id="employeeRiskAggregateSql">
        select
            agg.staff_id_card,
            agg.staff_name,
            agg.dept_id,
            dept.dept_name,
            agg.rule_count,
            agg.model_count,
            agg.hit_count,
            rule_pick.rule_code as top_rule_code,
            rule_pick.rule_name as top_rule_name,
            risk_points.risk_point,
            case
                when agg.rule_count >= 5 then 'HIGH'
                when agg.rule_count between 2 and 4 then 'MEDIUM'
                else 'LOW'
            end as risk_level_code,
            case
                when agg.rule_count >= 5 then '高风险'
                when agg.rule_count between 2 and 4 then '中风险'
                else '低风险'
            end as risk_level_name,
            case
                when agg.rule_count >= 5 then 1
                when agg.rule_count between 2 and 4 then 2
                else 3
            end as risk_level_sort
        from (
            select
                base.staff_id_card,
                max(base.staff_name) as staff_name,
                max(base.dept_id) as dept_id,
                count(distinct base.rule_code) as rule_count,
                count(distinct base.model_code) as model_count,
                count(1) as hit_count
            from (
                <include refid="resolvedEmployeeRiskBaseSql"/>
            ) base
            group by base.staff_id_card
        ) agg
        left join sys_dept dept on agg.dept_id = dept.dept_id
        left join (
            select
                chosen.staff_id_card,
                chosen.rule_code,
                chosen.rule_name
            from (
                select
                    grouped.staff_id_card,
                    grouped.rule_code,
                    grouped.rule_name,
                    grouped.hit_count
                from (
                    select
                        base.staff_id_card,
                        base.rule_code,
                        max(base.rule_name) as rule_name,
                        count(1) as hit_count
                    from (
                        <include refid="resolvedEmployeeRiskBaseSql"/>
                    ) base
                    group by base.staff_id_card, base.rule_code
                ) grouped
                where not exists (
                    select 1
                    from (
                        select
                            base.staff_id_card,
                            base.rule_code,
                            max(base.rule_name) as rule_name,
                            count(1) as hit_count
                        from (
                            <include refid="resolvedEmployeeRiskBaseSql"/>
                        ) base
                        group by base.staff_id_card, base.rule_code
                    ) challenger
                    where challenger.staff_id_card = grouped.staff_id_card
                      and (
                            challenger.hit_count > grouped.hit_count
                            or (challenger.hit_count = grouped.hit_count
                                and challenger.rule_code &lt; grouped.rule_code)
                      )
                )
            ) chosen
        ) rule_pick on rule_pick.staff_id_card = agg.staff_id_card
        left join (
            select
                grouped.staff_id_card,
                group_concat(grouped.rule_name order by grouped.hit_count desc, grouped.rule_code asc separator '、') as risk_point
            from (
                select
                    base.staff_id_card,
                    base.rule_code,
                    max(base.rule_name) as rule_name,
                    count(1) as hit_count
                from (
                    <include refid="resolvedEmployeeRiskBaseSql"/>
                ) base
                group by base.staff_id_card, base.rule_code
            ) grouped
            group by grouped.staff_id_card
        ) risk_points on risk_points.staff_id_card = agg.staff_id_card
    </sql>

    <select id="selectDashboardBaseByProjectId" resultType="com.ruoyi.ccdi.project.domain.CcdiProject">
        select
            project_id,
            project_name,
            target_count,
            high_risk_count,
            medium_risk_count,
            low_risk_count
        from ccdi_project
        where project_id = #{projectId}
          and del_flag = '0'
    </select>

    <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
            <include refid="riskPeopleOverviewSelectColumns"/>
        from ccdi_project_overview_employee_result result
        where result.project_id = #{query.projectId}
        <include refid="riskPeopleOverviewOrderBy"/>
    </select>

    <select id="selectRiskPeopleOverviewList" resultMap="EmployeeRiskAggregateResultMap">
        select
            <include refid="riskPeopleOverviewSelectColumns"/>
        from ccdi_project_overview_employee_result result
        where result.project_id = #{projectId}
        <include refid="riskPeopleOverviewOrderBy"/>
    </select>

    <select id="selectTopRiskPeopleByProjectId" resultMap="EmployeeRiskAggregateResultMap">
        select
            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
        from ccdi_project_overview_employee_result result
        where result.project_id = #{projectId}
          and result.risk_level_code in ('HIGH', 'MEDIUM')
        order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc
        limit 10
    </select>

    <select id="selectRiskModelCardsByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO">
        select
            models.model_code,
            models.model_name,
            coalesce(stats.warning_count, 0) as warning_count,
            coalesce(stats.people_count, 0) as people_count
        from (
            select
                rule.model_code,
                max(rule.model_name) as model_name
            from ccdi_bank_tag_rule rule
            where enabled = 1
            group by rule.model_code
        ) models
        left join (
            select
                json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))) as model_code,
                sum(cast(json_unquote(json_extract(
                    result.model_hit_summary_json,
                    concat('$[', idx.idx, '].warningCount')
                )) as unsigned)) as warning_count,
                count(distinct result.staff_id_card) as people_count
            from ccdi_project_overview_employee_result result
            join (
                <include refid="jsonArrayIndexSql"/>
            ) idx on idx.idx &lt; json_length(result.model_hit_summary_json)
            where result.project_id = #{projectId}
            group by json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode')))
        ) stats on models.model_code = stats.model_code
        order by warning_count desc, model_code asc
    </select>

    <select id="selectReportUploadSubjects" resultMap="ReportUploadSubjectResultMap">
        select
            trim(bs.LE_ACCOUNT_NAME) as subjectName,
            group_concat(distinct trim(bs.LE_ACCOUNT_NO) order by trim(bs.LE_ACCOUNT_NO) separator '、') as accountNos,
            date_format(min(
                case
                    when bs.TRX_DATE is null or trim(bs.TRX_DATE) = '' then null
                    when length(trim(bs.TRX_DATE)) = 10 then str_to_date(concat(trim(bs.TRX_DATE), ' 00:00:00'), '%Y-%m-%d %H:%i:%s')
                    else str_to_date(trim(bs.TRX_DATE), '%Y-%m-%d %H:%i:%s')
                end
            ), '%Y-%m-%d') as minTrxDate,
            date_format(max(
                case
                    when bs.TRX_DATE is null or trim(bs.TRX_DATE) = '' then null
                    when length(trim(bs.TRX_DATE)) = 10 then str_to_date(concat(trim(bs.TRX_DATE), ' 00:00:00'), '%Y-%m-%d %H:%i:%s')
                    else str_to_date(trim(bs.TRX_DATE), '%Y-%m-%d %H:%i:%s')
                end
            ), '%Y-%m-%d') as maxTrxDate,
            case
                when count(distinct fur.id) > 0 then count(distinct fur.id)
                else count(distinct bs.batch_id)
            end as fileCount
        from ccdi_bank_statement bs
        left join ccdi_file_upload_record fur
            on fur.project_id = bs.project_id
           and fur.log_id = bs.batch_id
        where bs.project_id = #{projectId}
          and bs.LE_ACCOUNT_NAME is not null
          and trim(bs.LE_ACCOUNT_NAME) != ''
        group by trim(bs.LE_ACCOUNT_NAME)
        order by trim(bs.LE_ACCOUNT_NAME) asc
    </select>

    <select id="selectReportRiskModelSummaries" resultMap="ReportModelSummaryResultMap">
        select
            model_scope.modelCode,
            max(model_scope.modelName) as modelName,
            sum(model_scope.warningCount) as warningCount,
            count(distinct model_scope.staffIdCard) as peopleCount,
            group_concat(distinct model_scope.staffName order by model_scope.staffName separator '、') as peopleNames
        from (
            select
                result.staff_id_card as staffIdCard,
                result.staff_name as staffName,
                json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))) as modelCode,
                json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as modelName,
                cast(json_unquote(json_extract(
                    result.model_hit_summary_json,
                    concat('$[', idx.idx, '].warningCount')
                )) as unsigned) as warningCount
            from ccdi_project_overview_employee_result result
            join (
                <include refid="jsonArrayIndexSql"/>
            ) idx on idx.idx &lt; json_length(result.model_hit_summary_json)
            where result.project_id = #{projectId}
        ) model_scope
        where model_scope.modelCode is not null
        group by model_scope.modelCode
        order by warningCount desc, model_scope.modelCode asc
    </select>

    <select id="selectReportRiskPeople" resultMap="RiskModelPeopleItemResultMap">
        select
            result.project_id,
            result.staff_id_card,
            result.staff_name,
            result.staff_code,
            result.dept_name as department,
            null as selected_model_codes
        from ccdi_project_overview_employee_result result
        where result.project_id = #{projectId}
        order by case result.risk_level_code
                     when 'HIGH' then 1
                     when 'MEDIUM' then 2
                     else 3
                 end,
                 result.model_count desc,
                 result.rule_count desc,
                 result.staff_id_card asc
    </select>

    <select id="selectRiskModelPeoplePage" resultMap="RiskModelPeopleItemResultMap">
        <bind name="projectId" value="query.projectId"/>
        select
            result.project_id,
            result.staff_id_card,
            result.staff_name,
            result.staff_code,
            result.dept_name as department,
            #{query.modelCodesCsv} as selected_model_codes
        from ccdi_project_overview_employee_result result
        where 1 = 1
          and result.project_id = #{query.projectId}
        <if test="query.modelCodes != null and query.modelCodes.size() > 0">
            <choose>
                <when test="query.matchMode == 'ALL'">
                    <foreach collection="query.modelCodes" item="modelCode">
                        and find_in_set(#{modelCode}, result.model_codes_csv)
                    </foreach>
                </when>
                <otherwise>
                    and (
                    <foreach collection="query.modelCodes" item="modelCode" separator=" or ">
                        find_in_set(#{modelCode}, result.model_codes_csv)
                    </foreach>
                    )
                </otherwise>
            </choose>
        </if>
        <if test="query.keyword != null and query.keyword != ''">
            and (
                result.staff_name like concat('%', trim(#{query.keyword}), '%')
                or result.staff_code like concat('%', trim(#{query.keyword}), '%')
            )
        </if>
        <if test="query.deptId != null">
            and result.dept_id = #{query.deptId}
        </if>
        order by result.staff_name asc, result.staff_id_card asc
    </select>

    <select id="selectRiskModelPeopleList" resultMap="RiskModelPeopleItemResultMap">
        <bind name="projectId" value="query.projectId"/>
        select
            result.project_id,
            result.staff_id_card,
            result.staff_name,
            result.staff_code,
            result.dept_name as department,
            #{query.modelCodesCsv} as selected_model_codes
        from ccdi_project_overview_employee_result result
        where 1 = 1
          and result.project_id = #{query.projectId}
        <if test="query.modelCodes != null and query.modelCodes.size() > 0">
            <choose>
                <when test="query.matchMode == 'ALL'">
                    <foreach collection="query.modelCodes" item="modelCode">
                        and find_in_set(#{modelCode}, result.model_codes_csv)
                    </foreach>
                </when>
                <otherwise>
                    and (
                    <foreach collection="query.modelCodes" item="modelCode" separator=" or ">
                        find_in_set(#{modelCode}, result.model_codes_csv)
                    </foreach>
                    )
                </otherwise>
            </choose>
        </if>
        <if test="query.keyword != null and query.keyword != ''">
            and (
                result.staff_name like concat('%', trim(#{query.keyword}), '%')
                or result.staff_code like concat('%', trim(#{query.keyword}), '%')
            )
        </if>
        <if test="query.deptId != null">
            and result.dept_id = #{query.deptId}
        </if>
        order by result.staff_name asc, result.staff_id_card asc
    </select>

    <sql id="externalPersonSubjectSql">
        select
            bs.project_id,
            bs.cret_no as cert_no,
            coalesce(
                max(intermediary.name),
                max(customer.name),
                max(nullif(trim(bs.LE_ACCOUNT_NAME), '')),
                '外部人员'
            ) as person_name,
            case
                when max(case when intermediary.person_id is not null then 1 else 0 end) > 0 then '中介'
                when max(case when customer.person_id is not null then 1 else 0 end) > 0 then '客户'
                else '外部人员'
            end as subject_type
        from ccdi_bank_statement bs
        left join ccdi_base_staff staff
            on staff.id_card = bs.cret_no
        left join ccdi_staff_fmy_relation relation
            on relation.status = 1
           and relation.relation_cert_no = bs.cret_no
        left join ccdi_biz_intermediary intermediary
            on intermediary.person_sub_type = '本人'
           and intermediary.person_id = bs.cret_no
        left join ccdi_credit_customer_base customer
            on customer.person_id = bs.cret_no
        where bs.project_id = #{externalProjectId}
          and bs.cret_no is not null
          and trim(bs.cret_no) != ''
          and staff.id_card is null
          and relation.relation_cert_no is null
        group by bs.project_id, bs.cret_no
    </sql>

    <sql id="externalPersonSourceSql">
        select
            subject.project_id,
            subject.cert_no,
            subject.person_name,
            subject.subject_type,
            bs.bank_statement_id,
            bs.TRX_DATE as trx_date,
            bs.CUSTOMER_ACCOUNT_NAME as customer_account_name,
            bs.customer_cert_no,
            case
                when counter_account.owner_type = 'EMPLOYEE' then '员工'
                when counter_account.owner_type = 'RELATION' then '员工亲属'
                when counter_account.owner_type = 'CREDIT_CUSTOMER' then '信贷客户'
                when counter_account.owner_type = 'INTERMEDIARY' then '中介库人员'
                when counter_staff.id_card is not null then '员工'
                when counter_relation.relation_cert_no is not null then '员工亲属'
                when counter_intermediary.person_id is not null then '中介库人员'
                else null
            end as related_object,
            tr.model_code,
            tr.model_name,
            tr.rule_code,
            tr.rule_name,
            tr.risk_level,
            tr.reason_detail
        from (
            <include refid="externalPersonSubjectSql"/>
        ) subject
        inner join ccdi_bank_statement bs
            on bs.project_id = subject.project_id
           and bs.cret_no = subject.cert_no
        inner join ccdi_bank_statement_tag_result tr
            on tr.project_id = bs.project_id
           and tr.bank_statement_id = bs.bank_statement_id
           and tr.model_code in <include refid="externalModelCodeFilterSql"/>
        left join ccdi_account_info counter_account
            on trim(bs.CUSTOMER_ACCOUNT_NO) != ''
           and counter_account.account_no = trim(bs.CUSTOMER_ACCOUNT_NO)
           and counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')
        left join ccdi_base_staff counter_staff
            on counter_account.account_no is null
           and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
           and counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
        left join ccdi_staff_fmy_relation counter_relation
            on counter_account.account_no is null
           and counter_relation.status = 1
           and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
           and counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)
        left join ccdi_biz_intermediary counter_intermediary
            on counter_account.account_no is null
           and counter_intermediary.person_sub_type = '本人'
           and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
           and counter_intermediary.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
        where trim(ifnull(bs.LE_ACCOUNT_NAME, '')) != trim(ifnull(bs.CUSTOMER_ACCOUNT_NAME, ''))

        union all

        select
            subject.project_id,
            subject.cert_no,
            subject.person_name,
            subject.subject_type,
            null as bank_statement_id,
            null as trx_date,
            null as customer_account_name,
            null as customer_cert_no,
            '资金' as related_object,
            tr.model_code,
            tr.model_name,
            tr.rule_code,
            tr.rule_name,
            tr.risk_level,
            tr.reason_detail
        from (
            <include refid="externalPersonSubjectSql"/>
        ) subject
        inner join ccdi_bank_statement_tag_result tr
            on tr.project_id = subject.project_id
           and tr.object_type = 'EXTERNAL_CERT_NO'
           and tr.object_key = subject.cert_no
           and tr.model_code in <include refid="externalModelCodeFilterSql"/>
    </sql>

    <sql id="externalPersonAggregateSql">
        select
            source.project_id,
            source.cert_no,
            max(source.person_name) as person_name,
            max(source.subject_type) as subject_type,
            count(*) as risk_count,
            count(distinct source.model_code) as model_count,
            group_concat(distinct source.rule_name order by source.rule_name separator '、') as risk_point,
            group_concat(distinct source.related_object order by source.related_object separator '、') as related_object,
            max(source.trx_date) as latest_trade_time,
            case
                when sum(case when source.risk_level = 'HIGH' then 1 else 0 end) > 0 then 'HIGH'
                when sum(case when source.risk_level = 'MEDIUM' then 1 else 0 end) > 0 then 'MEDIUM'
                else 'LOW'
            end as risk_level_code,
            null as selected_model_codes
        from (
            <include refid="externalPersonSourceSql"/>
        ) source
        group by source.project_id, source.cert_no
    </sql>

    <sql id="externalPersonWarningSelectSql">
        select
            agg.project_id,
            agg.cert_no,
            agg.person_name,
            agg.subject_type,
            agg.risk_count,
            agg.model_count,
            agg.risk_point,
            coalesce(agg.related_object, '-') as related_object,
            agg.latest_trade_time,
            agg.risk_level_code,
            case
                when agg.risk_level_code = 'HIGH' then '高风险'
                when agg.risk_level_code = 'MEDIUM' then '中风险'
                else '低风险'
            end as risk_level_name,
            case
                when agg.risk_level_code = 'HIGH' then 'danger'
                when agg.risk_level_code = 'MEDIUM' then 'warning'
                else 'info'
            end as risk_level_type,
            case
                when agg.risk_level_code = 'HIGH' then 1
                when agg.risk_level_code = 'MEDIUM' then 2
                else 3
            end as risk_level_sort,
            agg.selected_model_codes
        from (
            <include refid="externalPersonAggregateSql"/>
        ) agg
    </sql>

    <select id="selectExternalPersonWarningPage" resultMap="ExternalPersonWarningItemResultMap">
        <bind name="externalProjectId" value="query.projectId"/>
        select *
        from (
            <include refid="externalPersonWarningSelectSql"/>
        ) warning
        order by warning.risk_level_sort asc, warning.model_count desc, warning.risk_count desc, warning.latest_trade_time desc
    </select>

    <select id="selectExternalPersonWarningList" resultMap="ExternalPersonWarningItemResultMap">
        <bind name="externalProjectId" value="projectId"/>
        select *
        from (
            <include refid="externalPersonWarningSelectSql"/>
        ) warning
        order by warning.risk_level_sort asc, warning.model_count desc, warning.risk_count desc, warning.latest_trade_time desc
    </select>

    <select id="selectExternalRiskSummaryByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO">
        <bind name="externalProjectId" value="projectId"/>
        select
            count(*) as total,
            coalesce(sum(case when risk.risk_level_code = 'HIGH' then 1 else 0 end), 0) as high,
            coalesce(sum(case when risk.risk_level_code = 'MEDIUM' then 1 else 0 end), 0) as medium,
            coalesce(sum(case when risk.risk_level_code = 'LOW' then 1 else 0 end), 0) as low,
            coalesce(sum(case when risk.risk_level_code is null then 1 else 0 end), 0) as noRisk
        from (
            <include refid="externalPersonSubjectSql"/>
        ) subject
        left join (
            <include refid="externalPersonAggregateSql"/>
        ) risk
          on risk.project_id = subject.project_id
         and risk.cert_no = subject.cert_no
    </select>

    <select id="selectExternalRiskModelCardsByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO">
        <bind name="externalProjectId" value="projectId"/>
        select
            model_scope.model_code,
            max(model_scope.model_name) as model_name,
            count(*) as warning_count,
            count(distinct model_scope.cert_no) as people_count
        from (
            <include refid="externalPersonSourceSql"/>
        ) model_scope
        group by model_scope.model_code
        order by warning_count desc, model_scope.model_code asc
    </select>

    <select id="selectExternalRiskModelPeoplePage" resultMap="ExternalRiskModelPeopleItemResultMap">
        <bind name="externalProjectId" value="query.projectId"/>
        select
            warning.project_id,
            warning.cert_no,
            warning.person_name,
            warning.subject_type,
            warning.related_object,
            #{query.modelCodesCsv} as selected_model_codes
        from (
            <include refid="externalPersonWarningSelectSql"/>
        ) warning
        where 1 = 1
        <if test="query.modelCodes != null and query.modelCodes.size() > 0">
            <choose>
                <when test="query.matchMode == 'ALL'">
                    <foreach collection="query.modelCodes" item="modelCode">
                        and exists (
                            select 1
                            from (
                                <include refid="externalPersonSourceSql"/>
                            ) source
                            where source.cert_no = warning.cert_no
                              and source.model_code = #{modelCode}
                        )
                    </foreach>
                </when>
                <otherwise>
                    and exists (
                        select 1
                        from (
                            <include refid="externalPersonSourceSql"/>
                        ) source
                        where source.cert_no = warning.cert_no
                          and source.model_code in
                          <foreach collection="query.modelCodes" item="modelCode" open="(" separator="," close=")">
                              #{modelCode}
                          </foreach>
                    )
                </otherwise>
            </choose>
        </if>
        <if test="query.keyword != null and query.keyword != ''">
            and (
                warning.person_name like concat('%', trim(#{query.keyword}), '%')
                or warning.cert_no like concat('%', trim(#{query.keyword}), '%')
            )
        </if>
        order by warning.person_name asc, warning.cert_no asc
    </select>

    <select id="selectExternalRiskModelPeopleList" resultMap="ExternalRiskModelPeopleItemResultMap">
        <bind name="externalProjectId" value="query.projectId"/>
        select
            warning.project_id,
            warning.cert_no,
            warning.person_name,
            warning.subject_type,
            warning.related_object,
            #{query.modelCodesCsv} as selected_model_codes
        from (
            <include refid="externalPersonWarningSelectSql"/>
        ) warning
        where 1 = 1
        <if test="query.modelCodes != null and query.modelCodes.size() > 0">
            <choose>
                <when test="query.matchMode == 'ALL'">
                    <foreach collection="query.modelCodes" item="modelCode">
                        and exists (
                            select 1
                            from (
                                <include refid="externalPersonSourceSql"/>
                            ) source
                            where source.cert_no = warning.cert_no
                              and source.model_code = #{modelCode}
                        )
                    </foreach>
                </when>
                <otherwise>
                    and exists (
                        select 1
                        from (
                            <include refid="externalPersonSourceSql"/>
                        ) source
                        where source.cert_no = warning.cert_no
                          and source.model_code in
                          <foreach collection="query.modelCodes" item="modelCode" open="(" separator="," close=")">
                              #{modelCode}
                          </foreach>
                    )
                </otherwise>
            </choose>
        </if>
        <if test="query.keyword != null and query.keyword != ''">
            and (
                warning.person_name like concat('%', trim(#{query.keyword}), '%')
                or warning.cert_no like concat('%', trim(#{query.keyword}), '%')
            )
        </if>
        order by warning.person_name asc, warning.cert_no asc
    </select>

    <select id="selectExternalRiskModelNamesByScope" resultType="java.lang.String">
        <bind name="externalProjectId" value="projectId"/>
        select distinct source.model_name
        from (
            <include refid="externalPersonSourceSql"/>
        ) source
        where source.cert_no = #{certNo}
        <if test="selectedModelCodes != null and selectedModelCodes != ''">
            and find_in_set(source.model_code, #{selectedModelCodes})
        </if>
        order by source.model_name asc
    </select>

    <select id="selectExternalRiskHitTagsByScope" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO">
        <bind name="externalProjectId" value="projectId"/>
        select
            source.model_code as modelCode,
            max(source.model_name) as modelName,
            source.rule_code as ruleCode,
            max(source.rule_name) as ruleName,
            max(source.risk_level) as riskLevel,
            coalesce(
                max(case when source.bank_statement_id is null then nullif(source.reason_detail, '') end),
                max(nullif(source.reason_detail, ''))
            ) as reasonDetail
        from (
            <include refid="externalPersonSourceSql"/>
        ) source
        where source.cert_no = #{certNo}
        <if test="selectedModelCodes != null and selectedModelCodes != ''">
            and find_in_set(source.model_code, #{selectedModelCodes})
        </if>
        group by source.model_code, source.rule_code
        order by source.model_code asc, source.rule_code asc
    </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
    </sql>

    <sql id="suspiciousTransactionNameHitSql">
        select
            hits.bankStatementId,
            hits.suspiciousPersonName,
            hits.matchPriority,
            hits.nameListHitType
        from (
            select
                bs.bank_statement_id as bankStatementId,
                coalesce(credit_customer.name, account.account_name, '信贷客户账号') as suspiciousPersonName,
                1 as matchPriority,
                '信贷客户' as nameListHitType
            from ccdi_bank_statement bs
            inner join ccdi_account_info account
                on trim(bs.customer_account_no) != ''
               and account.owner_type = 'CREDIT_CUSTOMER'
               and account.account_no = bs.customer_account_no
            left join ccdi_credit_customer_base credit_customer
                on credit_customer.person_id = account.owner_id
            where bs.project_id = #{query.projectId}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000

            union all

            select
                bs.bank_statement_id as bankStatementId,
                coalesce(intermediary.name, enterprise.enterprise_name, account.account_name, '中介账号') as suspiciousPersonName,
                2 as matchPriority,
                '中介' as nameListHitType
            from ccdi_bank_statement bs
            inner join ccdi_account_info account
                on trim(bs.customer_account_no) != ''
               and account.owner_type = 'INTERMEDIARY'
               and account.account_no = bs.customer_account_no
            left join ccdi_biz_intermediary intermediary
                on intermediary.person_id = account.owner_id
            left join ccdi_enterprise_base_info enterprise
                on enterprise.social_credit_code = account.owner_id
            where bs.project_id = #{query.projectId}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000

            union all

            select
                bs.bank_statement_id as bankStatementId,
                intermediary.name as suspiciousPersonName,
                3 as matchPriority,
                '中介' as nameListHitType
            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}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000

            union all

            select
                bs.bank_statement_id as bankStatementId,
                enterprise.enterprise_name as suspiciousPersonName,
                4 as matchPriority,
                '中介' as nameListHitType
            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}
              and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
        ) hits
    </sql>

    <sql id="externalSuspiciousTransactionSql">
        select
            bs.bank_statement_id as bankStatementId,
            bs.TRX_DATE as trxDate,
            source.person_name as relatedPersonName,
            null as relatedStaffName,
            null as relatedStaffCode,
            source.subject_type as relationType,
            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,
            1 as hasModelRuleHit,
            0 as hasNameListHit,
            source.person_name as suspiciousPersonName,
            9 as matchPriority,
            '外部人员预警' as nameListHitType
        from (
            <bind name="externalProjectId" value="query.projectId"/>
            <include refid="externalPersonSourceSql"/>
        ) source
        inner join ccdi_bank_statement bs
            on bs.bank_statement_id = source.bank_statement_id
    </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,
            null as nameListHitType
        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,
            name_hits.nameListHitType
        from (
            <include refid="suspiciousTransactionBaseSql"/>
        ) base
        inner join (
            <include refid="suspiciousTransactionNameHitSql"/>
        ) name_hits on name_hits.bankStatementId = base.bankStatementId

        union all

        select
            external_hits.bankStatementId,
            external_hits.trxDate,
            external_hits.relatedPersonName,
            external_hits.relatedStaffName,
            external_hits.relatedStaffCode,
            external_hits.relationType,
            external_hits.userMemo,
            external_hits.cashType,
            external_hits.displayAmount,
            external_hits.hasModelRuleHit,
            external_hits.hasNameListHit,
            external_hits.suspiciousPersonName,
            external_hits.matchPriority,
            external_hits.nameListHitType
        from (
            <include refid="externalSuspiciousTransactionSql"/>
        ) external_hits
    </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,
            substring_index(
                min(
                    case
                        when merged.nameListHitType is not null and merged.nameListHitType != ''
                            then concat(lpad(merged.matchPriority, 2, '0'), '|', merged.nameListHitType)
                        else null
                    end
                ),
                '|',
                -1
            ) as nameListHitType
        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>
            <when test="query.suspiciousType == 'EXTERNAL_PERSON'">
                where final_result.nameListHitType = '外部人员预警'
            </when>
            <otherwise>
                where final_result.hasModelRuleHit = 1 or final_result.hasNameListHit = 1
            </otherwise>
        </choose>
    </sql>

    <select id="selectSuspiciousTransactionPage" resultMap="SuspiciousTransactionItemResultMap">
        <!-- ccdi_bank_statement_tag_result -->
        <!-- 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,
            final_result.nameListHitType
        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,
            final_result.nameListHitType
        from (
            <include refid="suspiciousTransactionAggregatedSql"/>
        ) final_result
        <include refid="suspiciousTransactionFilterSql"/>
        order by final_result.trxDate desc, final_result.bankStatementId desc
    </select>

    <select id="selectReportSuspiciousTransactionList" resultMap="ReportSuspiciousTransactionResultMap">
        select
            final_result.bankStatementId,
            final_result.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,
            final_result.relatedStaffName,
            final_result.relatedStaffCode,
            final_result.userMemo,
            final_result.cashType,
            case
                when final_result.nameListHitType = '中介' then
                    replace(
                        replace(ifnull(tag_result.hitTags, ''), '与客户之间非正常资金往来', '疑似与中介往来'),
                        '异常交易',
                        '疑似与中介往来'
                    )
                when final_result.nameListHitType = '信贷客户' then
                    replace(
                        replace(ifnull(tag_result.hitTags, ''), '与客户之间非正常资金往来', '与信贷客户之间非正常资金往来'),
                        '异常交易',
                        '与信贷客户之间非正常资金往来'
                    )
                else tag_result.hitTags
            end as hitTags,
            final_result.displayAmount
        from (
            <include refid="suspiciousTransactionAggregatedSql"/>
        ) final_result
        inner join ccdi_bank_statement bs
            on bs.bank_statement_id = final_result.bankStatementId
        left join (
            select
                tr.bank_statement_id,
                group_concat(distinct tr.rule_name order by tr.id separator '、') as hitTags
            from ccdi_bank_statement_tag_result tr
            where tr.project_id = #{query.projectId}
              and tr.bank_statement_id is not null
            group by tr.bank_statement_id
        ) tag_result on tag_result.bank_statement_id = final_result.bankStatementId
        <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>

    <sql id="abnormalAccountBaseSql">
        select
            account.account_no as accountNo,
            account.account_no as account_no,
            coalesce(nullif(account.account_name, ''), staff.name) as accountName,
            account.bank as bankName,
            tr.rule_name as abnormalType,
            tr.rule_code as rule_code,
            case
                when tr.rule_code = 'SUDDEN_ACCOUNT_CLOSURE' then date_format(account.invalid_date, '%Y-%m-%d')
                when tr.rule_code = 'DORMANT_ACCOUNT_LARGE_ACTIVATION' then substring(
                    substring_index(
                        substring_index(tr.reason_detail, '，', 2),
                        '首次交易日期',
                        -1
                    ),
                    1,
                    10
                )
                else null
            end as abnormal_time,
            case
                when account.status = 1 then '正常'
                when account.status = 2 then '已销户'
                else cast(account.status as char)
            end as status
        from ccdi_bank_statement_tag_result tr
        inner join ccdi_account_info account
            on account.owner_type = 'EMPLOYEE'
           and account.owner_id = tr.object_key
           and instr(tr.reason_detail, account.account_no) > 0
        left join ccdi_base_staff staff
            on staff.id_card = tr.object_key
    </sql>

    <select id="selectAbnormalAccountPage" resultMap="AbnormalAccountItemResultMap">
        <!-- tr.model_code = 'ABNORMAL_ACCOUNT' -->
        <!-- tr.bank_statement_id is null -->
        <!-- account.owner_type = 'EMPLOYEE' -->
        <!-- tr.reason_detail -->
        <!-- instr(tr.reason_detail, account.account_no) > 0 -->
        <!-- when account.status = 1 then '正常' -->
        <!-- when account.status = 2 then '已销户' -->
        <!-- when tr.rule_code = 'SUDDEN_ACCOUNT_CLOSURE' -->
        <!-- when tr.rule_code = 'DORMANT_ACCOUNT_LARGE_ACTIVATION' -->
        <!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
        select
            abnormal.accountNo,
            abnormal.accountName,
            abnormal.bankName,
            abnormal.abnormalType,
            abnormal.abnormal_time,
            abnormal.status
        from (
            <include refid="abnormalAccountBaseSql"/>
            where tr.project_id = #{query.projectId}
              and tr.model_code = 'ABNORMAL_ACCOUNT'
              and tr.bank_statement_id is null
        ) abnormal
        <!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
        order by abnormal.abnormal_time desc, abnormal.account_no asc, abnormal.rule_code asc
    </select>

    <select id="selectAbnormalAccountList" resultMap="AbnormalAccountItemResultMap">
        <!-- tr.model_code = 'ABNORMAL_ACCOUNT' -->
        <!-- tr.bank_statement_id is null -->
        <!-- account.owner_type = 'EMPLOYEE' -->
        <!-- tr.reason_detail -->
        <!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
        select
            abnormal.accountNo,
            abnormal.accountName,
            abnormal.bankName,
            abnormal.abnormalType,
            abnormal.abnormal_time,
            abnormal.status
        from (
            <include refid="abnormalAccountBaseSql"/>
            where tr.project_id = #{projectId}
              and tr.model_code = 'ABNORMAL_ACCOUNT'
              and tr.bank_statement_id is null
        ) abnormal
        <!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
        order by abnormal.abnormal_time desc, abnormal.account_no asc, abnormal.rule_code asc
    </select>

    <select id="selectRiskModelNamesByScope" resultType="java.lang.String">
        select
            json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as model_name
        from ccdi_project_overview_employee_result result
        join (
            <include refid="jsonArrayIndexSql"/>
        ) idx on idx.idx &lt; json_length(result.model_hit_summary_json)
        where result.project_id = #{projectId}
          and result.staff_id_card = #{staffIdCard}
        group by
            json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))),
            json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName')))
        order by json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))) asc
    </select>

    <select id="selectRiskHitTagsByScope" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO">
        select
            json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].modelCode'))) as model_code,
            max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].modelName')))) as model_name,
            json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) as rule_code,
            max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleName')))) as rule_name,
            max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].riskLevel')))) as risk_level,
            max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].reasonDetail')))) as reason_detail
        from ccdi_project_overview_employee_result result
        join (
            <include refid="jsonArrayIndexSql"/>
        ) idx on idx.idx &lt; json_length(result.hit_rules_json)
        where result.project_id = #{projectId}
          and result.staff_id_card = #{staffIdCard}
        group by json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].modelCode'))),
                 json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode')))
        order by case max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].riskLevel'))))
                     when 'HIGH' then 1
                     when 'MEDIUM' then 2
                     else 3
                 end,
                 json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].modelCode'))) asc,
                 json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) asc
    </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
            max(tr.model_code) as modelCode,
            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
            coalesce(sum(case when agg.rule_count >= 5 then 1 else 0 end), 0) as highRiskCount,
            coalesce(sum(case when agg.rule_count between 2 and 4 then 1 else 0 end), 0) as mediumRiskCount,
            coalesce(sum(case when agg.rule_count = 1 then 1 else 0 end), 0) as lowRiskCount
        from (
            select
                base.staff_id_card,
                count(distinct base.rule_code) as rule_count
            from (
                <include refid="resolvedEmployeeRiskBaseSql"/>
            ) base
            group by base.staff_id_card
        ) agg
    </select>

</mapper>
