File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.itn.itnhub.dashboard.DashboardMapper">
<!--
record라서 constructor 매핑을 쓴다(ProcessItemMapper의 statsResultMap과 같은 방식).
arg 순서는 DashboardOrgRow의 컴포넌트 순서와 반드시 같아야 한다.
-->
<resultMap id="orgRowResultMap" type="kr.itn.itnhub.dashboard.DashboardOrgRow">
<constructor>
<idArg column="id" javaType="java.lang.Long"/>
<arg column="org_no" javaType="java.lang.String"/>
<arg column="org_name" javaType="java.lang.String"/>
<arg column="stage" javaType="java.lang.Integer"/>
<arg column="has_channel" javaType="_boolean"/>
<arg column="lawyer_name" javaType="java.lang.String"/>
<arg column="mj_name" javaType="java.lang.String"/>
<arg column="lawyer_assigned_date" javaType="java.lang.String"/>
<arg column="review_total" javaType="_int"/>
<arg column="review_done" javaType="_int"/>
<arg column="process_total" javaType="_int"/>
<arg column="process_done" javaType="_int"/>
<arg column="stage_changed_at" javaType="java.lang.Long"/>
<arg column="last_changed_at" javaType="java.lang.Long"/>
<arg column="re_count" javaType="_int"/>
</constructor>
</resultMap>
<!--
기관 1행당 집계를 서브쿼리로 붙인다. 기관별로 countByOrg/stats를 반복 호출하면
기관 50곳 × 3종 = 150회 조회가 되므로(대시보드는 화면 진입마다 그린다) 여기서 한 방에 끝낸다.
stage_at / any_at 두 값이 서로 다르다:
- stage_at = 지금 단계로 넘어온 시각. 카드의 D+n은 이걸 쓴다
- any_at = 단계 이력 전체의 최신값. "최근 변경"의 재료 중 하나다
lateral을 쓰는 이유는 filter 절에서 o.stage를 참조해야 하기 때문이다.
last_changed_at은 greatest()로 합친다. Postgres의 greatest는 NULL을 무시하므로
권리확인/처리/메모가 아직 없는 기관도 organization.updated_at으로 값이 채워진다.
organization.updated_at만으로는 부족한 이유: 배정·단계·채널 변경 때만 갱신되고
권리확인 업로드나 업무메모 작성은 이 컬럼을 건드리지 않는다.
re_count는 RE 테이블이 아직 없어서 0으로 고정한다. RE 모델이 들어오면 여기에
서브쿼리 하나만 붙이면 화면은 그대로 동작한다.
-->
<select id="findOrgRows" resultMap="orgRowResultMap">
select
o.id,
o.org_no,
o.org_name,
o.stage,
(o.channel_id_mj is not null or o.channel_id_law is not null) as has_channel,
lc.name as lawyer_name,
mc.name as mj_name,
o.lawyer_assigned_date,
coalesce(rv.total, 0) as review_total,
coalesce(rv.done, 0) as review_done,
coalesce(pr.total, 0) as process_total,
coalesce(pr.done, 0) as process_done,
(extract(epoch from sh.stage_at) * 1000)::bigint as stage_changed_at,
(extract(epoch from greatest(o.updated_at, sh.any_at, rv.last_at, pr.last_at, wm.last_at)) * 1000)::bigint
as last_changed_at,
0 as re_count
from organization o
left join contact lc on lc.id = o.lawyer_contact_id
left join contact mc on mc.id = o.mj_contact_id
left join (
select org_id,
count(*) as total,
count(*) filter (where review_result is not null and review_result <> '') as done,
max(updated_at) as last_at
from review_item group by org_id
) rv on rv.org_id = o.id
left join (
select org_id,
count(*) as total,
count(*) filter (where process_status = #{doneStatus}) as done,
max(updated_at) as last_at
from process_item group by org_id
) pr on pr.org_id = o.id
left join (
select org_id, max(created_at) as last_at from work_memo group by org_id
) wm on wm.org_id = o.id
left join lateral (
select max(h.changed_at) filter (where h.stage = o.stage) as stage_at,
max(h.changed_at) as any_at
from stage_history h
where h.org_id = o.id
) sh on true
order by o.org_no asc, o.org_name asc
</select>
</mapper>